Browse Source

almost finished keys

tb
ofek 7 years ago
parent
commit
8f612bdc95
  1. 2
      coincurve/__init__.py
  2. 93
      coincurve/ecdsa.py
  3. 3
      coincurve/flags.py
  4. 47
      coincurve/keys.py
  5. 26
      coincurve/utils.py
  6. 1198
      tests/data/ecdsa_custom_nonce_sig.json
  7. 21
      tests/samples.py
  8. 64
      tests/test_custom_nonce.py
  9. 22
      tests/test_ecdh.py
  10. 102
      tests/test_ecdsa.py
  11. 140
      tests/test_err.py
  12. 63
      tests/test_flags.py
  13. 46
      tests/test_pubkey.py
  14. 44
      tests/test_tweak.py

2
coincurve/__init__.py

@ -1,2 +1,2 @@
from coincurve.context import GLOBAL_CONTEXT
from coincurve.context import GLOBAL_CONTEXT, Context
from coincurve.keys import PrivateKey, PublicKey

93
coincurve/ecdsa.py

@ -1,51 +1,57 @@
from coincurve import GLOBAL_CONTEXT
from ._libsecp256k1 import ffi, lib
MAX_SIG_LENGTH = 72
CDATA_SIG_LENGTH = 64
def ecdsa_serialize(raw_sig):
len_sig = 74
output = ffi.new('unsigned char[%d]' % len_sig)
outputlen = ffi.new('size_t *', len_sig)
def cdata_to_der(cdata, context=GLOBAL_CONTEXT):
der = ffi.new('unsigned char[%d]' % MAX_SIG_LENGTH)
der_length = ffi.new('size_t *', MAX_SIG_LENGTH)
res = lib.secp256k1_ecdsa_signature_serialize_der(
self.ctx, output, outputlen, raw_sig)
context.ctx, der, der_length, cdata
)
assert res == 1
return bytes(ffi.buffer(output, outputlen[0]))
return bytes(ffi.buffer(der, der_length[0]))
def ecdsa_deserialize(ser_sig):
raw_sig = ffi.new('secp256k1_ecdsa_signature *')
def der_to_cdata(der, context=GLOBAL_CONTEXT):
cdata = ffi.new('secp256k1_ecdsa_signature *')
res = lib.secp256k1_ecdsa_signature_parse_der(
self.ctx, raw_sig, ser_sig, len(ser_sig))
context.ctx, cdata, der, len(der)
)
assert res == 1
return raw_sig
return cdata
def ecdsa_serialize_compact(raw_sig):
len_sig = 64
output = ffi.new('unsigned char[%d]' % len_sig)
def serialize_compact(raw_sig, context=GLOBAL_CONTEXT):
output = ffi.new('unsigned char[%d]' % CDATA_SIG_LENGTH)
res = lib.secp256k1_ecdsa_signature_serialize_compact(
self.ctx, output, raw_sig)
context.ctx, output, raw_sig
)
assert res == 1
return bytes(ffi.buffer(output, len_sig))
return bytes(ffi.buffer(output, CDATA_SIG_LENGTH))
def ecdsa_deserialize_compact(ser_sig):
def deserialize_compact(ser_sig, context=GLOBAL_CONTEXT):
if len(ser_sig) != 64:
raise Exception("invalid signature length")
raw_sig = ffi.new('secp256k1_ecdsa_signature *')
res = lib.secp256k1_ecdsa_signature_parse_compact(
self.ctx, raw_sig, ser_sig)
context.ctx, raw_sig, ser_sig
)
assert res == 1
return raw_sig
def ecdsa_signature_normalize(raw_sig, check_only=False):
def signature_normalize(raw_sig, context=GLOBAL_CONTEXT):
"""
Check and optionally convert a signature to a normalized lower-S form.
If check_only is True then the normalized signature is not returned.
@ -55,50 +61,38 @@ def ecdsa_signature_normalize(raw_sig, check_only=False):
normalized), and the normalized signature. When check_only is True,
the normalized signature returned is always None.
"""
if check_only:
sigout = ffi.NULL
else:
sigout = ffi.new('secp256k1_ecdsa_signature *')
sigout = ffi.new('secp256k1_ecdsa_signature *')
result = lib.secp256k1_ecdsa_signature_normalize(
self.ctx, sigout, raw_sig)
res = lib.secp256k1_ecdsa_signature_normalize(
context.ctx, sigout, raw_sig
)
return (bool(result), sigout if sigout != ffi.NULL else None)
return not not res, sigout
def ecdsa_recover(msg, recover_sig, raw=False, digest=hashlib.sha256):
if not HAS_RECOVERABLE:
raise Exception("secp256k1_recovery not enabled")
if self.flags & ALL_FLAGS != ALL_FLAGS:
raise Exception("instance not configured for ecdsa recover")
msg32 = _hash32(msg, raw, digest)
def recover(msg, recover_sig, context=GLOBAL_CONTEXT):
pubkey = ffi.new('secp256k1_pubkey *')
recovered = lib.secp256k1_ecdsa_recover(
self.ctx, pubkey, recover_sig, msg32)
context.ctx, pubkey, recover_sig, msg
)
if recovered:
return pubkey
raise Exception('failed to recover ECDSA public key')
def ecdsa_recoverable_serialize(recover_sig):
if not HAS_RECOVERABLE:
raise Exception("secp256k1_recovery not enabled")
outputlen = 64
output = ffi.new('unsigned char[%d]' % outputlen)
def recoverable_to_der(recover_sig, context=GLOBAL_CONTEXT):
output = ffi.new('unsigned char[%d]' % CDATA_SIG_LENGTH)
recid = ffi.new('int *')
lib.secp256k1_ecdsa_recoverable_signature_serialize_compact(
self.ctx, output, recid, recover_sig)
context.ctx, output, recid, recover_sig
)
return bytes(ffi.buffer(output, outputlen)), recid[0]
return bytes(ffi.buffer(output, CDATA_SIG_LENGTH)), recid[0]
def ecdsa_recoverable_deserialize(ser_sig, rec_id):
if not HAS_RECOVERABLE:
raise Exception("secp256k1_recovery not enabled")
def der_to_recoverable(ser_sig, rec_id, context=GLOBAL_CONTEXT):
if rec_id < 0 or rec_id > 3:
raise Exception("invalid rec_id")
if len(ser_sig) != 64:
@ -107,20 +101,19 @@ def ecdsa_recoverable_deserialize(ser_sig, rec_id):
recover_sig = ffi.new('secp256k1_ecdsa_recoverable_signature *')
parsed = lib.secp256k1_ecdsa_recoverable_signature_parse_compact(
self.ctx, recover_sig, ser_sig, rec_id)
context.ctx, recover_sig, ser_sig, rec_id
)
if parsed:
return recover_sig
else:
raise Exception('failed to parse ECDSA compact sig')
def ecdsa_recoverable_convert(recover_sig):
if not HAS_RECOVERABLE:
raise Exception("secp256k1_recovery not enabled")
def recoverable_convert(recover_sig, context=GLOBAL_CONTEXT):
normal_sig = ffi.new('secp256k1_ecdsa_signature *')
lib.secp256k1_ecdsa_recoverable_signature_convert(
self.ctx, normal_sig, recover_sig)
context.ctx, normal_sig, recover_sig
)
return normal_sig

3
coincurve/flags.py

@ -8,6 +8,3 @@ CONTEXT_FLAGS = {CONTEXT_SIGN, CONTEXT_VERIFY, CONTEXT_ALL, CONTEXT_NONE}
EC_COMPRESSED = lib.SECP256K1_EC_COMPRESSED
EC_UNCOMPRESSED = lib.SECP256K1_EC_UNCOMPRESSED
HAS_RECOVERABLE = hasattr(lib, 'secp256k1_ecdsa_sign_recoverable')
HAS_ECDH = hasattr(lib, 'secp256k1_ecdh')

47
coincurve/keys.py

@ -1,6 +1,7 @@
from coincurve import GLOBAL_CONTEXT
from coincurve.ecdsa import cdata_to_der, recoverable_to_der
from coincurve.flags import EC_COMPRESSED, EC_UNCOMPRESSED
from coincurve.utils import get_valid_secret, validate_secret
from coincurve.utils import get_valid_secret, sha256, validate_secret
from ._libsecp256k1 import ffi, lib
@ -9,7 +10,9 @@ class PrivateKey:
self.secret = (validate_secret(secret) if secret is not None
else get_valid_secret())
self.context = context
self.public_key = PublicKey.from_secret(self.secret, self.context)
self.public_key = PublicKey.from_valid_secret(
self.secret, self.context
)
def update_public_key(self):
res = lib.secp256k1_ec_pubkey_create(
@ -17,38 +20,35 @@ class PrivateKey:
)
assert res == 1
def ecdsa_sign(self, msg_hash, nonce=None):
def sign(self, message, hasher=sha256):
msg_hash = hasher(message)
if len(msg_hash) != 32:
raise ValueError('Message hash must be 32 bytes long.')
signature = ffi.new('secp256k1_ecdsa_signature *')
if not nonce:
nonce_fn = ffi.NULL
nonce_data = ffi.NULL
else:
nonce_fn, nonce_data = nonce
res = lib.secp256k1_ecdsa_sign(
self.context.ctx, signature, msg_hash, self.secret, nonce_fn,
nonce_data
self.context.ctx, signature, msg_hash, self.secret, ffi.NULL,
ffi.NULL
)
assert res == 1
return signature
return cdata_to_der(signature, self.context)
def ecdsa_sign_recoverable(self, msg_hash):
def sign_recoverable(self, message, hasher=sha256):
msg_hash = hasher(message)
if len(msg_hash) != 32:
raise ValueError('Message hash must be 32 bytes long.')
signature = ffi.new('secp256k1_ecdsa_recoverable_signature *')
res = lib.secp256k1_ecdsa_sign_recoverable(
self.context.ctx, signature, msg_hash, self.secret, ffi.NULL, ffi.NULL
self.context.ctx, signature, msg_hash, self.secret, ffi.NULL,
ffi.NULL
)
assert res == 1
return signature
return recoverable_to_der(signature, self.context)
def add(self, scalar, update=False):
"""
@ -131,15 +131,25 @@ class PublicKey:
return PublicKey(public_key, context)
@classmethod
def from_valid_secret(cls, secret, context=GLOBAL_CONTEXT):
public_key = ffi.new('secp256k1_pubkey *')
res = lib.secp256k1_ec_pubkey_create(
context.ctx, public_key, secret
)
assert res == 1
return PublicKey(public_key, context)
def format(self, compressed=True):
length = 33 if compressed else 65
serialized = ffi.new('unsigned char [%d]' % length)
output_len = ffi.new('size_t *', length)
compression = EC_COMPRESSED if compressed else EC_UNCOMPRESSED
lib.secp256k1_ec_pubkey_serialize(
self.context.ctx, serialized, output_len, self.public_key,
compression
EC_COMPRESSED if compressed else EC_UNCOMPRESSED
)
return bytes(ffi.buffer(serialized, length))
@ -156,7 +166,8 @@ class PublicKey:
self.public_key = new_key
def ecdsa_verify(self, msg_hash, signature):
def verify(self, signature, message, hasher=sha256):
msg_hash = hasher(message)
if len(msg_hash) != 32:
raise ValueError('Message hash must be 32 bytes long.')

26
coincurve/utils.py

@ -1,9 +1,35 @@
from base64 import b64decode, b64encode
from hashlib import sha256 as _sha256
from os import urandom
GROUP_ORDER = (b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
b'\xfe\xba\xae\xdc\xe6\xafH\xa0;\xbf\xd2^\x8c\xd06AA')
KEY_SIZE = 32
ZERO = b'\x00'
PEM_HEADER = b'-----BEGIN PRIVATE KEY-----\n'
PEM_FOOTER = b'-----END PRIVATE KEY-----\n'
def sha256(bytestr):
return _sha256(bytestr).digest()
def chunk_data(data, size):
return (data[i:i + size] for i in range(0, len(data), size))
def der_to_pem(der):
return b''.join([
PEM_HEADER,
b'\n'.join(chunk_data(b64encode(der), 64)), b'\n',
PEM_FOOTER
])
def pem_to_der(pem):
return b64decode(
pem.strip()[28:-25].replace(b'\n', b'')
)
def get_valid_secret():

1198
tests/data/ecdsa_custom_nonce_sig.json

File diff suppressed because it is too large

21
tests/samples.py

@ -0,0 +1,21 @@
PRIVATE_KEY_BYTES = b'\xc2\x8a\x9f\x80s\x8fw\rRx\x03\xa5f\xcfo\xc3\xed\xf6\xce\xa5\x86\xc4\xfcJR#\xa5\xady~\x1a\xc3'
PRIVATE_KEY_DER = (b"0\x81\x84\x02\x01\x000\x10\x06\x07*\x86H\xce=\x02\x01\x06"
b"\x05+\x81\x04\x00\n\x04m0k\x02\x01\x01\x04 \xc2\x8a\x9f"
b"\x80s\x8fw\rRx\x03\xa5f\xcfo\xc3\xed\xf6\xce\xa5\x86\xc4"
b"\xfcJR#\xa5\xady~\x1a\xc3\xa1D\x03B\x00\x04=\\(u\xc9\xbd"
b"\x11hu\xa7\x1a]\xb6L\xff\xcb\x139k\x16=\x03\x9b\x1d\x93'"
b"\x82H\x91\x80C4v\xa45**\xdd\x00\xeb\xb0\xd5\xc9LQ[r\xeb"
b"\x10\xf1\xfd\x8f?\x03\xb4/J+%[\xfc\x9a\xa9\xe3")
PRIVATE_KEY_HEX = 'c28a9f80738f770d527803a566cf6fc3edf6cea586c4fc4a5223a5ad797e1ac3'
PRIVATE_KEY_NUM = 87993618360805341115891506172036624893404292644470266399436498750715784469187
PRIVATE_KEY_PEM = (b'-----BEGIN PRIVATE KEY-----\n'
b'MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgwoqfgHOPdw1SeAOlZs9v\n'
b'w+32zqWGxPxKUiOlrXl+GsOhRANCAAQ9XCh1yb0RaHWnGl22TP/LEzlrFj0Dmx2T\n'
b'J4JIkYBDNHakNSoq3QDrsNXJTFFbcusQ8f2PPwO0L0orJVv8mqnj\n'
b'-----END PRIVATE KEY-----\n')
PUBLIC_KEY_COMPRESSED = b"\x03=\\(u\xc9\xbd\x11hu\xa7\x1a]\xb6L\xff\xcb\x139k\x16=\x03\x9b\x1d\x93'\x82H\x91\x80C4"
PUBLIC_KEY_UNCOMPRESSED = (b"\x04=\\(u\xc9\xbd\x11hu\xa7\x1a]\xb6L\xff\xcb\x139k\x16=\x03"
b"\x9b\x1d\x93'\x82H\x91\x80C4v\xa45**\xdd\x00\xeb\xb0\xd5\xc9"
b"LQ[r\xeb\x10\xf1\xfd\x8f?\x03\xb4/J+%[\xfc\x9a\xa9\xe3")
PUBLIC_KEY_X = 27753912938952041417634381842191885283234814940840273460372041880794577257268
PUBLIC_KEY_Y = 53663045980837260634637807506183816949039230809110041985901491152185762425315

64
tests/test_custom_nonce.py

@ -1,64 +0,0 @@
import os
import json
import pytest
import coincurve
HERE = os.path.dirname(os.path.abspath(__file__))
DATA = os.path.join(HERE, 'data')
from cffi import FFI
ffi = FFI()
ffi.cdef('static int nonce_function_rand(unsigned char *nonce32,'
'const unsigned char *msg32,const unsigned char *key32,'
'const unsigned char *algo16,void *data,unsigned int attempt);')
#The most elementary conceivable nonce function, acting
#as a passthrough: user provides random data which must be
#a valid scalar nonce. This is not ideal,
#since libsecp256k1 expects the nonce output from
#this function to result in a valid sig (s!=0), and will
#increment the counter ("attempt") and try again if it fails;
#since we don't increment the counter here, that will not succeed.
#Of course the likelihood of such an error is infinitesimal.
#TLDR this is not intended to be used in real life; use
#deterministic signatures.
ffi.set_source("_noncefunc",
"""
static int nonce_function_rand(unsigned char *nonce32,
const unsigned char *msg32,
const unsigned char *key32,
const unsigned char *algo16,
void *data,
unsigned int attempt)
{
memcpy(nonce32,data,32);
return 1;
}
""")
ffi.compile()
import _noncefunc
from _noncefunc import ffi
def test_ecdsa_with_custom_nonce():
with open(os.path.join(DATA, 'ecdsa_custom_nonce_sig.json')) as f:
data = f.read()
vec = json.loads(data)['vectors']
inst = coincurve.PrivateKey()
for item in vec:
seckey = bytes(bytearray.fromhex(item['privkey']))
msg32 = bytes(bytearray.fromhex(item['msg']))
sig = bytes(bytearray.fromhex(item['sig']))
randnonce = bytes(bytearray.fromhex(item['nonce']))
inst.set_raw_privkey(seckey)
nf = ffi.addressof(_noncefunc.lib, "nonce_function_rand")
ndata = ffi.new("char [32]", randnonce)
sig_raw = inst.ecdsa_sign(msg32, raw=True, custom_nonce=(nf, ndata))
sig_check = inst.ecdsa_serialize(sig_raw)
assert sig_check == sig
assert inst.ecdsa_serialize(inst.ecdsa_deserialize(sig_check)) == sig_check
assert inst.pubkey.ecdsa_verify(msg32, sig_raw, raw=True)

22
tests/test_ecdh.py

@ -1,22 +0,0 @@
import pytest
import coincurve
def test_ecdh():
if not coincurve.HAS_ECDH:
pytest.skip('secp256k1_ecdh not enabled, skipping')
return
pubkey = coincurve.PrivateKey().pubkey
p = coincurve.PublicKey(pubkey.public_key)
with pytest.raises(Exception):
# Bad scalar length.
p.ecdh(b'')
with pytest.raises(Exception):
# Bad scalar type.
p.ecdh([])
res = p.ecdh(b'0' * 32)
assert type(res) == bytes

102
tests/test_ecdsa.py

@ -1,102 +0,0 @@
import os
import json
import pytest
from io import StringIO
import coincurve
HERE = os.path.dirname(os.path.abspath(__file__))
DATA = os.path.join(HERE, 'data')
def test_ecdsa():
with open(os.path.join(DATA, 'ecdsa_sig.json')) as f:
data = f.read()
vec = json.loads(data)['vectors']
inst = coincurve.PrivateKey()
for item in vec:
seckey = bytes(bytearray.fromhex(item['privkey']))
msg32 = bytes(bytearray.fromhex(item['msg']))
sig = bytes(bytearray.fromhex(item['sig'])[:-1])
inst.set_raw_privkey(seckey)
sig_raw = inst.ecdsa_sign(msg32, raw=True)
sig_check = inst.ecdsa_serialize(sig_raw)
assert sig_check == sig
assert inst.ecdsa_serialize(inst.ecdsa_deserialize(sig_check)) == sig_check
assert inst.pubkey.ecdsa_verify(msg32, sig_raw, raw=True)
def test_ecdsa_compact():
key = coincurve.PrivateKey()
raw_sig = key.ecdsa_sign(b'test')
assert key.pubkey.ecdsa_verify(b'test', raw_sig)
compact = key.ecdsa_serialize_compact(raw_sig)
assert len(compact) == 64
sig_raw = key.ecdsa_deserialize_compact(compact)
assert key.ecdsa_serialize_compact(sig_raw) == compact
assert key.pubkey.ecdsa_verify(b'test', sig_raw)
def test_ecdsa_normalize():
key = coincurve.PrivateKey()
raw_sig = key.ecdsa_sign(b'hi')
had_to_normalize, normsig = key.ecdsa_signature_normalize(raw_sig)
assert had_to_normalize == False
assert key.ecdsa_serialize(normsig) == key.ecdsa_serialize(raw_sig)
assert key.ecdsa_serialize_compact(normsig) == \
key.ecdsa_serialize_compact(raw_sig)
had_to_normalize, normsig = key.ecdsa_signature_normalize(
raw_sig, check_only=True)
assert had_to_normalize == False
assert normsig == None
sig = b'\xAA' + (b'\xFF' * 31) + b'\xAA' + (b'\xFF' * 31)
raw_sig = key.ecdsa_deserialize_compact(sig)
normalized, normsig = key.ecdsa_signature_normalize(raw_sig)
assert normalized == True
assert key.ecdsa_serialize(normsig) != key.ecdsa_serialize(raw_sig)
normalized, normsig = key.ecdsa_signature_normalize(raw_sig, True)
assert normalized == True
assert normsig == None
def test_ecdsa_recover():
if not coincurve.HAS_RECOVERABLE:
pytest.skip('secp256k1_recovery not enabled, skipping')
return
class MyECDSA(coincurve.Base, coincurve.ECDSA):
def __init__(self):
coincurve.Base.__init__(self, ctx=None, flags=coincurve.ALL_FLAGS)
privkey = coincurve.PrivateKey()
unrelated = MyECDSA()
# Create a signature that allows recovering the public key.
recsig = privkey.ecdsa_sign_recoverable(b'hello')
# Recover the public key.
pubkey = unrelated.ecdsa_recover(b'hello', recsig)
# Check that the recovered public key matches the one used
# in privkey.pubkey.
pubser = coincurve.PublicKey(pubkey).serialize()
assert privkey.pubkey.serialize() == pubser
# Check that after serializing and deserializing recsig
# we still recover the same public key.
recsig_ser = unrelated.ecdsa_recoverable_serialize(recsig)
recsig2 = unrelated.ecdsa_recoverable_deserialize(*recsig_ser)
pubkey2 = unrelated.ecdsa_recover(b'hello', recsig2)
pubser2 = coincurve.PublicKey(pubkey2).serialize()
assert pubser == pubser2
raw_sig = unrelated.ecdsa_recoverable_convert(recsig2)
unrelated.ecdsa_deserialize(unrelated.ecdsa_serialize(raw_sig))

140
tests/test_err.py

@ -1,140 +0,0 @@
import pytest
import hashlib
import coincurve
def test_privkey():
with pytest.raises(TypeError):
key = 'abc'
coincurve.PrivateKey(key)
with pytest.raises(TypeError):
key = bytearray.fromhex('a' * 32) # This will result in 16 bytes.
coincurve.PrivateKey(bytes(key))
with pytest.raises(Exception):
coincurve.PrivateKey(bytes(bytearray.fromhex('0' * 64)))
with pytest.raises(Exception):
coincurve.PrivateKey(bytes(bytearray.fromhex('F' * 64)))
with pytest.raises(Exception):
# This is a good raw key, but here it's being passed as serialized.
coincurve.PrivateKey(b'1' * 32, raw=False)
# "good" key, should be fine.
assert coincurve.PrivateKey(b'1' * 32)
def test_publickey():
with pytest.raises(Exception):
# Must be bytes.
# In Python 2 this will not raise a TypeError
# since bytes is an alias to str, instead it will fail
# during deserialization.
coincurve.PublicKey('abc', raw=True)
with pytest.raises(Exception):
coincurve.PublicKey([], raw=True)
with pytest.raises(Exception):
# Invalid size.
coincurve.PublicKey(b'abc', raw=True)
with pytest.raises(Exception):
# Invalid public key.
coincurve.PublicKey(b'a' * 33, raw=True)
# Invalid usage: passing a raw public key but not specifying raw=True.
with pytest.raises(TypeError):
coincurve.PublicKey(b'a' * 33)
# No public key.
assert coincurve.PublicKey()
pub1 = coincurve.PrivateKey().pubkey.public_key
new = coincurve.PublicKey()
with pytest.raises(AssertionError):
# Trying to combine with something that is not a public key.
new.combine([pub1, coincurve.ffi.NULL])
new = coincurve.PublicKey()
with pytest.raises(AssertionError):
# Nothing to combine.
new.combine([])
def test_ecdsa():
rawkey = (b'\xc9\xa9)Z\xf8Er\x97\x8b\xa23\x1f\xf7\xb6\x82qQ\xdc9\xc1'
b'\x1d\xac6\xfd\xeb\x11\x05\xb1\xdf\x86\xb3\xe6')
priv = coincurve.PrivateKey(rawkey)
with pytest.raises(Exception):
# Bad digest function (doesn't produce 256 bits).
priv.ecdsa_sign(b'hi', digest=hashlib.sha1)
raw_sig = priv.ecdsa_sign(b'hi')
assert priv.pubkey.ecdsa_verify(b'hi', raw_sig)
with pytest.raises(AssertionError):
sig = priv.ecdsa_serialize(raw_sig)[:-1]
priv.ecdsa_deserialize(sig)
sig = priv.ecdsa_serialize(raw_sig)
sig = sig[:-1] + bytes(sig[0:1]) # Assuming sig[0] != sig[-1].
invalid_sig = priv.ecdsa_deserialize(sig)
assert not priv.pubkey.ecdsa_verify(b'hi', invalid_sig)
def test_ecdsa_compact():
key = coincurve.PrivateKey()
raw_sig = key.ecdsa_sign(b'hi')
with pytest.raises(TypeError):
# Should pass a compact serialization.
key.ecdsa_deserialize_compact(raw_sig)
ser = key.ecdsa_serialize(raw_sig)
with pytest.raises(Exception):
# A serialization that is not compact has more than 64 bytes.
key.ecdsa_deserialize_compact(ser)
def test_ecdsa_recoverable():
if not coincurve.HAS_RECOVERABLE:
pytest.skip('secp256k1_recovery not enabled, skipping')
return
key = '32a8935ffdb984a498b0f7ac8943e0d2ac084e81c809595fd19fde41522f1837'
priv = coincurve.PrivateKey(bytes(bytearray.fromhex(key)))
sig = priv.ecdsa_sign_recoverable(b'hi')
sig_ser, rec_id = priv.ecdsa_recoverable_serialize(sig)
assert rec_id == 1
with pytest.raises(Exception):
# Invalid rec_id (must be between 0 and 3)
priv.ecdsa_recoverable_deserialize(sig_ser, -1)
# Deserialize using a rec_id that does not match.
sig = priv.ecdsa_recoverable_deserialize(sig_ser, 2)
with pytest.raises(Exception):
# Now try to recover the public key.
priv.ecdsa_recover(b'hi', sig)
# Invalid size.
with pytest.raises(Exception):
priv.ecdsa_recoverable_deserialize(b'hello', 0)
def test_tweak():
key = coincurve.PrivateKey()
# Tweak out of range
scalar = b'\xFF' * 32
with pytest.raises(Exception):
key.tweak_mul(scalar)
with pytest.raises(Exception):
key.tweak_add(scalar)
with pytest.raises(Exception):
key.pubkey.tweak_mul(scalar)
with pytest.raises(Exception):
key.pubkey.tweak_add(scalar)

63
tests/test_flags.py

@ -1,63 +0,0 @@
import pytest
import coincurve
def test_values():
assert coincurve.FLAG_VERIFY == (
coincurve.lib.SECP256K1_FLAGS_TYPE_CONTEXT |
coincurve.lib.SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)
assert coincurve.FLAG_VERIFY == 257
assert coincurve.FLAG_SIGN == (
coincurve.lib.SECP256K1_FLAGS_TYPE_CONTEXT |
coincurve.lib.SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
assert coincurve.FLAG_SIGN == 513
assert coincurve.ALL_FLAGS == coincurve.FLAG_SIGN | coincurve.FLAG_VERIFY
def test_privkey():
with pytest.raises(AssertionError):
coincurve.PrivateKey(flags=coincurve.FLAG_VERIFY)
with pytest.raises(AssertionError):
coincurve.PrivateKey(flags=0)
privkey = coincurve.PrivateKey(flags=coincurve.FLAG_SIGN)
sig = privkey.ecdsa_sign(b'hi')
with pytest.raises(Exception):
# FLAG_SIGN was not specified.
privkey.pubkey.ecdsa_verify(b'hi', sig)
assert privkey.flags == privkey.pubkey.flags
privkey = coincurve.PrivateKey()
sig = privkey.ecdsa_sign(b'hi')
assert privkey.pubkey.ecdsa_verify(b'hi', sig)
def test_pubkey():
privkey = coincurve.PrivateKey()
sig = privkey.ecdsa_sign(b'hello')
pubkeyser = privkey.pubkey.serialize()
pubkey = coincurve.PublicKey(pubkeyser, raw=True, flags=coincurve.NO_FLAGS)
with pytest.raises(Exception):
# FLAG_SIGN was not specified.
pubkey.ecdsa_verify(b'hello', sig)
pubkey = coincurve.PublicKey(pubkeyser, raw=True)
assert pubkey.ecdsa_verify(b'hello', sig)
def test_recoverable():
if not coincurve.HAS_RECOVERABLE:
pytest.skip('secp256k1_recovery not enabled, skipping')
return
privkey = coincurve.PrivateKey(flags=coincurve.FLAG_SIGN)
x = privkey.ecdsa_sign_recoverable(b'hi')
with pytest.raises(Exception):
# All flags required.
privkey.ecdsa_recover(b'hi', x)
privkey = coincurve.PrivateKey()
x = privkey.ecdsa_sign_recoverable(b'hi')
privkey.ecdsa_recover(b'hi', x)

46
tests/test_pubkey.py

@ -1,46 +0,0 @@
import os
import json
from io import StringIO
import coincurve
HERE = os.path.dirname(os.path.abspath(__file__))
DATA = os.path.join(HERE, 'data')
def test_pubkey_from_privkey():
with open(os.path.join(DATA, 'pubkey.json')) as f:
data = f.read()
vec = json.loads(data)['vectors']
inst = coincurve.PrivateKey()
for item in vec:
seckey = bytes(bytearray.fromhex(item['seckey']))
pubkey_uncp = bytes(bytearray.fromhex(item['pubkey']))
pubkey_comp = bytes(bytearray.fromhex(item['compressed']))
inst.set_raw_privkey(seckey)
assert inst.pubkey.serialize(compressed=False) == pubkey_uncp
assert inst.pubkey.serialize(compressed=True) == pubkey_comp
assert inst.deserialize(inst.serialize()) == seckey
def test_pubkey_combine():
k1 = coincurve.PrivateKey()
k2 = coincurve.PrivateKey()
pub1 = k1.pubkey.public_key
pub2 = k2.pubkey.public_key
new = coincurve.PublicKey()
assert new.public_key is None
res = new.combine([pub1, pub2])
assert new.public_key == res
new = coincurve.PublicKey()
assert new.public_key is None
res = new.combine([pub1])
assert new.public_key == res
assert new.serialize() == k1.pubkey.serialize()

44
tests/test_tweak.py

@ -1,44 +0,0 @@
import pytest
import coincurve
def test_pubkey_tweak():
inst = coincurve.PrivateKey()
pub = inst.pubkey
scalar = [b'\x01' * 32]
with pytest.raises(TypeError):
pub.tweak_add(scalar)
with pytest.raises(TypeError):
pub.tweak_mul(scalar)
scalar = b'\x01' * 31
with pytest.raises(TypeError):
pub.tweak_add(scalar)
with pytest.raises(TypeError):
pub.tweak_mul(scalar)
scalar = scalar + b'\x01'
res = pub.tweak_add(scalar)
assert isinstance(res, coincurve.PublicKey)
assert res.serialize() != pub.serialize()
def test_privkey_tweak():
key = coincurve.PrivateKey()
scalar = [b'\x01' * 32]
with pytest.raises(TypeError):
key.tweak_add(scalar)
with pytest.raises(TypeError):
key.tweak_mul(scalar)
scalar = b'\x01' * 31
with pytest.raises(TypeError):
key.tweak_add(scalar)
with pytest.raises(TypeError):
key.tweak_mul(scalar)
scalar = scalar + b'\x01'
res = key.tweak_add(scalar)
assert isinstance(res, bytes) and len(res) == 32
Loading…
Cancel
Save