From e177ab31b7ce294a7ab38f798320db0ecdad7193 Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Mon, 5 Jul 2021 11:47:03 +0200 Subject: [PATCH 1/2] grid low R by default --- src/embit/ec.py | 39 ++++++++++++++++++------------ src/embit/util/ctypes_secp256k1.py | 8 +++--- src/embit/util/key.py | 10 +++++--- src/embit/util/py_secp256k1.py | 4 +-- tests/tests/test_bindings.py | 5 ++++ tests/tests/test_ecc.py | 9 +++++++ tests/tests/test_psbt.py | 2 +- 7 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/embit/ec.py b/src/embit/ec.py index 2833a08..2118ea7 100644 --- a/src/embit/ec.py +++ b/src/embit/ec.py @@ -13,6 +13,18 @@ class ECError(EmbitError): pass +class Signature(EmbitBase): + def __init__(self, sig): + self._sig = sig + + def write_to(self, stream) -> int: + return stream.write(secp256k1.ecdsa_signature_serialize_der(self._sig)) + + @classmethod + def read_from(cls, stream): + der = stream.read(2) + der += stream.read(der[1]) + return cls(secp256k1.ecdsa_signature_parse_der(der)) class PublicKey(EmbitKey): def __init__(self, point: bytes, compressed: bool = True): @@ -140,8 +152,17 @@ def from_base58(cls, s): def get_public_key(self): return PublicKey(secp256k1.ec_pubkey_create(self._secret), self.compressed) - def sign(self, msg_hash): - return Signature(secp256k1.ecdsa_sign(msg_hash, self._secret)) + def sign(self, msg_hash, grind=True): + sig = Signature(secp256k1.ecdsa_sign(msg_hash, self._secret)) + if grind: + counter = 1 + while len(sig.serialize()) > 70: + sig = Signature(secp256k1.ecdsa_sign(msg_hash, self._secret, None, counter.to_bytes(32, 'little'))) + counter += 1 + # just in case we get in infinite loop for some reason + if counter > 200: + break + return sig def write_to(self, stream) -> int: # return a copy of the secret @@ -155,17 +176,3 @@ def read_from(cls, stream): @property def is_private(self) -> bool: return True - - -class Signature(EmbitBase): - def __init__(self, sig): - self._sig = sig - - def write_to(self, stream) -> int: - return stream.write(secp256k1.ecdsa_signature_serialize_der(self._sig)) - - @classmethod - def read_from(cls, stream): - der = stream.read(2) - der += stream.read(der[1]) - return cls(secp256k1.ecdsa_signature_parse_der(der)) diff --git a/src/embit/util/ctypes_secp256k1.py b/src/embit/util/ctypes_secp256k1.py index d69bd83..e6ba3ba 100644 --- a/src/embit/util/ctypes_secp256k1.py +++ b/src/embit/util/ctypes_secp256k1.py @@ -147,7 +147,7 @@ def _init(flags=(CONTEXT_SIGN | CONTEXT_VERIFY)): c_char_p, c_char_p, c_void_p, - c_void_p, + c_char_p, ] secp256k1.secp256k1_ecdsa_sign.restype = c_int @@ -448,13 +448,15 @@ def ecdsa_verify(sig, msg, pub, context=_secp.ctx): return bool(r) -def ecdsa_sign(msg, secret, context=_secp.ctx): +def ecdsa_sign(msg, secret, nonce_function=None, extra_data=None, context=_secp.ctx): if len(msg) != 32: raise ValueError("Message should be 32 bytes long") if len(secret) != 32: raise ValueError("Secret key should be 32 bytes long") + if extra_data and len(extra_data) != 32: + raise ValueError("Extra data should be 32 bytes long") sig = bytes(64) - r = _secp.secp256k1_ecdsa_sign(context, sig, msg, secret, None, None) + r = _secp.secp256k1_ecdsa_sign(context, sig, msg, secret, nonce_function, extra_data) if r == 0: raise ValueError("Failed to sign") return sig diff --git a/src/embit/util/key.py b/src/embit/util/key.py index d3d2111..83a08ea 100644 --- a/src/embit/util/key.py +++ b/src/embit/util/key.py @@ -400,14 +400,16 @@ def get_pubkey(self): ret.compressed = self.compressed return ret - def sign_ecdsa(self, msg, low_s=True): + def sign_ecdsa(self, msg, nonce_function=None, extra_data=None, low_s=True): """Construct a DER-encoded ECDSA signature with this key. See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the ECDSA signer algorithm.""" assert self.valid z = int.from_bytes(msg, "big") - k = deterministic_k(self.secret, z) + if nonce_function is None: + nonce_function = deterministic_k + k = nonce_function(self.secret, z, extra_data=extra_data) R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)])) r = R[0] % SECP256K1_ORDER s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER @@ -427,7 +429,7 @@ def sign_ecdsa(self, msg, low_s=True): ) -def deterministic_k(secret, z): +def deterministic_k(secret, z, extra_data=None): # RFC6979, optimized for secp256k1 k = b"\x00" * 32 v = b"\x01" * 32 @@ -435,6 +437,8 @@ def deterministic_k(secret, z): z -= SECP256K1_ORDER z_bytes = z.to_bytes(32, "big") secret_bytes = secret.to_bytes(32, "big") + if extra_data is not None: + z_bytes += extra_data k = hmac.new(k, v + b"\x00" + secret_bytes + z_bytes, "sha256").digest() v = hmac.new(k, v, "sha256").digest() k = hmac.new(k, v + b"\x01" + secret_bytes + z_bytes, "sha256").digest() diff --git a/src/embit/util/py_secp256k1.py b/src/embit/util/py_secp256k1.py index 9ed5323..6820be9 100644 --- a/src/embit/util/py_secp256k1.py +++ b/src/embit/util/py_secp256k1.py @@ -170,14 +170,14 @@ def ecdsa_verify(sig, msg, pub, context=None): return pubkey.verify_ecdsa(ecdsa_signature_serialize_der(sig), msg) -def ecdsa_sign(msg, secret, context=None): +def ecdsa_sign(msg, secret, nonce_function=None, extra_data=None, context=None): if len(msg) != 32: raise ValueError("Message should be 32 bytes long") if len(secret) != 32: raise ValueError("Secret key should be 32 bytes long") pk = _key.ECKey() pk.set(secret, False) - sig = pk.sign_ecdsa(msg) + sig = pk.sign_ecdsa(msg, nonce_function, extra_data) return ecdsa_signature_parse_der(sig) diff --git a/tests/tests/test_bindings.py b/tests/tests/test_bindings.py index e7ff693..aed416d 100644 --- a/tests/tests/test_bindings.py +++ b/tests/tests/test_bindings.py @@ -30,6 +30,11 @@ def test_cross(self): msg = b"7" * 32 sig = ctypes_secp256k1.ecdsa_sign(msg, secret) self.assertEqual(sig, py_secp256k1.ecdsa_sign(msg, secret)) + + # check that extra data is handled in the same way + sig = ctypes_secp256k1.ecdsa_sign(msg, secret, None, b"1"*32) + self.assertEqual(sig, py_secp256k1.ecdsa_sign(msg, secret, None, b"1"*32)) + compact = py_secp256k1.ecdsa_signature_serialize_compact(sig) der = py_secp256k1.ecdsa_signature_serialize_der(sig) self.assertEqual( diff --git a/tests/tests/test_ecc.py b/tests/tests/test_ecc.py index 7ef50e1..6ecd0a1 100644 --- a/tests/tests/test_ecc.py +++ b/tests/tests/test_ecc.py @@ -15,6 +15,15 @@ def test_identity(self): g_hex = hexlify(der) self.assertEqual(answer, g_hex) + def test_grind(self): + pk = PrivateKey(b"1"*32) + msgs = [ bytes([i]*32) for i in range(255) ] + for msg in msgs: + sig = pk.sign(msg) + der = sig.serialize() + # low R is grinded + self.assertTrue(len(der)<=70) + def test_pubkeys(self): valid_keys = [ ( diff --git a/tests/tests/test_psbt.py b/tests/tests/test_psbt.py index 1d62037..077a92e 100644 --- a/tests/tests/test_psbt.py +++ b/tests/tests/test_psbt.py @@ -96,7 +96,7 @@ def test_sign(self): exp_partial_sigs = [ { "029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f": "3044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01", - "02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7": "3045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01", + "02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7": "30440220631a989fe738a92ad01986023312c19214fe2802b39e5cbc1ac3678806c692c3022039db6c387bd267716dfdb3d4d8da50b8e85d213326ba7c7daaa4c0ce41eb922301", }, { "03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc": "3044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01", From db9671ac2ac7a83cbd6831cc3b8827393c7530d3 Mon Sep 17 00:00:00 2001 From: Stepan Snigirev Date: Mon, 5 Jul 2021 12:52:12 +0200 Subject: [PATCH 2/2] fix bip39 test for micropython --- tests/tests/test_bip39.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests/test_bip39.py b/tests/tests/test_bip39.py index ca13bcc..44a89e7 100644 --- a/tests/tests/test_bip39.py +++ b/tests/tests/test_bip39.py @@ -229,7 +229,7 @@ def test_alternate_wordlist(self): for mnemonic, expected_seed in mnemonics: self.assertTrue(mnemonic_is_valid(mnemonic, wordlist=ES_WORDLIST)) - self.assertEqual(mnemonic_to_seed(mnemonic, wordlist=ES_WORDLIST).hex(), expected_seed) + self.assertEqual(hexlify(mnemonic_to_seed(mnemonic, wordlist=ES_WORDLIST)).decode(), expected_seed) self.assertEqual( mnemonic_from_bytes(