From 44e27ac8b5854322c334713d40cf1d1409007f68 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 11 Apr 2024 13:09:57 +0000 Subject: [PATCH 1/7] ecc: add bindings for schnorr sign/verify and require "schnorrsig" and "extrakeys" modules of libsecp256k1 --- .../p4a_recipes/libsecp256k1/__init__.py | 3 + contrib/make_libsecp256k1.sh | 2 + electrum/ecc.py | 46 ++++++++++ electrum/ecc_fast.py | 32 ++++++- tests/test_ecc.py | 84 +++++++++++++++++++ 5 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 tests/test_ecc.py diff --git a/contrib/android/p4a_recipes/libsecp256k1/__init__.py b/contrib/android/p4a_recipes/libsecp256k1/__init__.py index c4406078e..3aac5df50 100644 --- a/contrib/android/p4a_recipes/libsecp256k1/__init__.py +++ b/contrib/android/p4a_recipes/libsecp256k1/__init__.py @@ -10,5 +10,8 @@ class LibSecp256k1RecipePinned(LibSecp256k1Recipe): url = "https://github.com/bitcoin-core/secp256k1/archive/{version}.zip" sha512sum = "64080d7c3345fe8117787e328a09a3b493c38880cabf73d34e472ab0db4cb17ff989689f0c785680bdba39c446dc8a64d34587f4a0797b225c5687d0eb2da607" + # TODO: configure needs options: + # --enable-module-extrakeys + # --enable-module-schnorrsig recipe = LibSecp256k1RecipePinned() diff --git a/contrib/make_libsecp256k1.sh b/contrib/make_libsecp256k1.sh index 4b8f03b3a..adb20fcf4 100755 --- a/contrib/make_libsecp256k1.sh +++ b/contrib/make_libsecp256k1.sh @@ -52,6 +52,8 @@ info "Building $pkgname..." $AUTOCONF_FLAGS \ --prefix="$here/$pkgname/dist" \ --enable-module-recovery \ + --enable-module-extrakeys \ + --enable-module-schnorrsig \ --enable-experimental \ --enable-module-ecdh \ --disable-benchmark \ diff --git a/electrum/ecc.py b/electrum/ecc.py index 774f24cf3..91b9a4dc5 100644 --- a/electrum/ecc.py +++ b/electrum/ecc.py @@ -236,6 +236,7 @@ class ECPubkey(object): return self._y def _to_libsecp256k1_pubkey_ptr(self): + """pointer to `secp256k1_pubkey` C struct""" pubkey = create_string_buffer(64) public_pair_bytes = self.get_public_key_bytes(compressed=False) ret = _libsecp256k1.secp256k1_ec_pubkey_parse( @@ -244,6 +245,16 @@ class ECPubkey(object): raise Exception('public key could not be parsed or is invalid') return pubkey + def _to_libsecp256k1_xonly_pubkey_ptr(self): + """pointer to `secp256k1_xonly_pubkey` C struct""" + pubkey = create_string_buffer(64) + pk_bytes = self.get_public_key_bytes(compressed=True)[1:] + ret = _libsecp256k1.secp256k1_xonly_pubkey_parse( + _libsecp256k1.ctx, pubkey, pk_bytes) + if 1 != ret: + raise Exception('public key could not be parsed or is invalid') + return pubkey + @classmethod def _from_libsecp256k1_pubkey_ptr(cls, pubkey) -> 'ECPubkey': pubkey_serialized = create_string_buffer(65) @@ -341,6 +352,17 @@ class ECPubkey(object): return False return True + def verify_message_schnorr(self, sig64: bytes, msg32: bytes) -> bool: + assert isinstance(sig64, bytes), type(sig64) + assert len(sig64) == 64, len(sig64) + assert isinstance(msg32, bytes), type(msg32) + assert len(msg32) == 32, len(msg32) + msglen = 32 + pubkey = self._to_libsecp256k1_xonly_pubkey_ptr() + if 1 != _libsecp256k1.secp256k1_schnorrsig_verify(_libsecp256k1.ctx, sig64, msg32, msglen, pubkey): + return False + return True + def encrypt_message(self, message: bytes, magic: bytes = b'BIE1') -> bytes: """ ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; hmac-sha256 is used as the mac @@ -501,6 +523,30 @@ class ECPrivkey(ECPubkey): sig = sigencode(r, s) return sig + def sign_schnorr(self, msg32: bytes, *, aux_rand32: bytes = None) -> bytes: + assert isinstance(msg32, bytes), type(msg32) + assert len(msg32) == 32, len(msg32) + if aux_rand32 is None: + aux_rand32 = bytes(32) + assert isinstance(aux_rand32, bytes), type(aux_rand32) + assert len(aux_rand32) == 32, len(aux_rand32) + # construct "keypair" obj + privkey_bytes = self.secret_scalar.to_bytes(32, byteorder="big") + keypair = create_string_buffer(96) + ret = _libsecp256k1.secp256k1_keypair_create(_libsecp256k1.ctx, keypair, privkey_bytes) + if 1 != ret: + raise Exception('secret key was invalid') + # sign msg and verify sig + sig64 = create_string_buffer(64) + ret = _libsecp256k1.secp256k1_schnorrsig_sign32( + _libsecp256k1.ctx, sig64, msg32, keypair, aux_rand32) + sig64 = bytes(sig64) + if 1 != ret: + raise Exception('signing failure') + if not self.verify_message_schnorr(sig64, msg32): + raise Exception("sanity check failed: signature we just created does not verify!") + return sig64 + def sign_transaction(self, hashed_preimage: bytes) -> bytes: return self.sign(hashed_preimage, sigencode=der_sig_from_r_and_s) diff --git a/electrum/ecc_fast.py b/electrum/ecc_fast.py index a27e21149..1730cc263 100644 --- a/electrum/ecc_fast.py +++ b/electrum/ecc_fast.py @@ -1,4 +1,9 @@ -# taken (with minor modifications) from pycoin +# Copyright (c) 2013-2018 Richard Kiss +# Copyright (c) 2018-2024 The Electrum developers +# Distributed under the MIT software license, see the accompanying +# file LICENCE or http://www.opensource.org/licenses/mit-license.php +# +# Originally based on pycoin: # https://github.com/richardkiss/pycoin/blob/01b1787ed902df23f99a55deb00d8cd076a906fe/pycoin/ecdsa/native/secp256k1.py import os @@ -127,6 +132,31 @@ def load_library(): raise LibModuleMissing('libsecp256k1 library found but it was built ' 'without required module (--enable-module-recovery)') + # --enable-module-schnorrsig + try: + secp256k1.secp256k1_schnorrsig_sign32.argtypes = [c_void_p, c_char_p, c_char_p, c_char_p, c_char_p] + secp256k1.secp256k1_schnorrsig_sign32.restype = c_int + + secp256k1.secp256k1_schnorrsig_verify.argtypes = [c_void_p, c_char_p, c_char_p, c_size_t, c_char_p] + secp256k1.secp256k1_schnorrsig_verify.restype = c_int + except (OSError, AttributeError): + raise LibModuleMissing('libsecp256k1 library found but it was built ' + 'without required module (--enable-module-schnorrsig)') + + # --enable-module-extrakeys + try: + secp256k1.secp256k1_xonly_pubkey_parse.argtypes = [c_void_p, c_char_p, c_char_p] + secp256k1.secp256k1_xonly_pubkey_parse.restype = c_int + + secp256k1.secp256k1_xonly_pubkey_serialize.argtypes = [c_void_p, c_char_p, c_char_p] + secp256k1.secp256k1_xonly_pubkey_serialize.restype = c_int + + secp256k1.secp256k1_keypair_create.argtypes = [c_void_p, c_char_p, c_char_p] + secp256k1.secp256k1_keypair_create.restype = c_int + except (OSError, AttributeError): + raise LibModuleMissing('libsecp256k1 library found but it was built ' + 'without required module (--enable-module-extrakeys)') + secp256k1.ctx = secp256k1.secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY) ret = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32)) if not ret: diff --git a/tests/test_ecc.py b/tests/test_ecc.py new file mode 100644 index 000000000..76b7a0d17 --- /dev/null +++ b/tests/test_ecc.py @@ -0,0 +1,84 @@ +import csv +import io + +from electrum import ecc +from electrum.ecc import ECPubkey, ECPrivkey +from electrum.crypto import sha256 + +from . import ElectrumTestCase + + +# note: lots of ecc-related tests are in test_bitcoin.py. + +class TestSchnorr(ElectrumTestCase): + + def test_vectors_from_bip0340(self): + bip0340_vectors = """index,secret key,public key,aux_rand,message,signature,verification result,comment +0,0000000000000000000000000000000000000000000000000000000000000003,F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000,E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0,TRUE, +1,B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,0000000000000000000000000000000000000000000000000000000000000001,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A,TRUE, +2,C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9,DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8,C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906,7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C,5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7,TRUE, +3,0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710,25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3,TRUE,test fails if msg is reduced modulo p or n +4,,D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9,,4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4,TRUE, +5,,EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key not on the curve +6,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2,FALSE,has_even_y(R) is false +7,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD,FALSE,negated message +8,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6,FALSE,negated s value +9,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0 +10,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1 +11,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is not an X coordinate on the curve +12,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is equal to field size +13,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,sig[32:64] is equal to curve order +14,,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key is not a valid X coordinate because it exceeds the field size +""" + with io.StringIO(bip0340_vectors) as f: + csvreader = csv.reader(f) + next(csvreader) # skip first line + for idx, seckey, pubkey, aux_rand, message, signature, expected_res, comment in csvreader: + idx = int(idx) + with self.subTest(msg=f"{idx=}"): + try: + pubkey = ECPubkey(bytes.fromhex("02" + pubkey)) + except ecc.InvalidECPointException: + if idx in (5, 14, ): # expected for these tests + continue + raise + msg32 = bytes.fromhex(message) + signature = bytes.fromhex(signature) + if seckey: + seckey = ECPrivkey(bytes.fromhex(seckey)) + aux_rand = bytes.fromhex(aux_rand) + sig_created = seckey.sign_schnorr(msg32, aux_rand32=aux_rand) + self.assertEqual(signature, sig_created) + is_sig_good = pubkey.verify_message_schnorr(signature, msg32) + expected_res = True if expected_res == "TRUE" else False + self.assertEqual(expected_res, is_sig_good) + + def test_sign_schnorr_aux_rand(self): + seckey = ECPrivkey(bytes.fromhex("B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF")) + msg32 = sha256("hello there") + sig1 = seckey.sign_schnorr(msg32, aux_rand32=None) + sig2 = seckey.sign_schnorr(msg32, aux_rand32=b"\x00" * 32) + self.assertEqual(sig1, sig2) + sig3 = seckey.sign_schnorr(msg32, aux_rand32=bytes(range(32))) + self.assertNotEqual(sig1, sig3) + + def test_y_parity_malleability(self): + # BIP-0340 says: + # > A hypothetical verification algorithm that treats points as public keys, + # > and takes the point P directly as input would fail any time a point with + # > odd Y is used. While it is possible to correct for this by negating points + # > with odd Y coordinate before further processing, this would result in a scheme + # > where every (message, signature) pair is valid for two public keys (a type of + # > malleability that exists for ECDSA as well, but we don't wish to retain). + # > We avoid these problems by treating just the X coordinate as public key. + # + # As the API in ecc.py treats points as public keys, this malleability exists here: + seckey = ECPrivkey.from_secret_scalar(1337) + pubkey1 = ECPubkey(seckey.get_public_key_bytes()) + pubkey2 = ecc.CURVE_ORDER * ecc.GENERATOR + (-1) * pubkey1 + self.assertNotEqual(pubkey1.get_public_key_bytes(True), pubkey2.get_public_key_bytes(True)) + self.assertEqual(pubkey1.get_public_key_bytes(True)[1:], pubkey2.get_public_key_bytes(True)[1:]) + msg32 = sha256("hello there") + sig = seckey.sign_schnorr(msg32, aux_rand32=None) + self.assertTrue(pubkey1.verify_message_schnorr(sig, msg32)) + self.assertTrue(pubkey2.verify_message_schnorr(sig, msg32)) From 8677a91dceba0cf4985e62bd7fba385a3da30bd9 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 11 Apr 2024 15:23:07 +0000 Subject: [PATCH 2/7] android: update p4a ref to have https://github.com/SomberNight/python-for-android/commit/04e80084ebbb9bcfdbb32e814d4a1f5826ffe4eb > recipe: libsecp256k1: enable modules for schnorr sigs --- contrib/android/Dockerfile | 4 ++-- contrib/android/p4a_recipes/libsecp256k1/__init__.py | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/contrib/android/Dockerfile b/contrib/android/Dockerfile index 55635d889..91ec60ee0 100644 --- a/contrib/android/Dockerfile +++ b/contrib/android/Dockerfile @@ -197,8 +197,8 @@ RUN cd /opt \ && git remote add sombernight https://github.com/SomberNight/python-for-android \ && git remote add accumulator https://github.com/accumulator/python-for-android \ && git fetch --all \ - # commit: from branch accumulator/qt6-wip (note: careful with force-pushing! see #8162) \ - && git checkout "0c507ead85cc603d0e4a2f5e58e9247a1fae262d^{commit}" \ + # commit: from branch sombernight/qt6-wip (note: careful with force-pushing! see #8162) \ + && git checkout "04e80084ebbb9bcfdbb32e814d4a1f5826ffe4eb^{commit}" \ && /opt/venv/bin/python3 -m pip install --no-build-isolation --no-dependencies -e . # build env vars diff --git a/contrib/android/p4a_recipes/libsecp256k1/__init__.py b/contrib/android/p4a_recipes/libsecp256k1/__init__.py index 3aac5df50..c4406078e 100644 --- a/contrib/android/p4a_recipes/libsecp256k1/__init__.py +++ b/contrib/android/p4a_recipes/libsecp256k1/__init__.py @@ -10,8 +10,5 @@ class LibSecp256k1RecipePinned(LibSecp256k1Recipe): url = "https://github.com/bitcoin-core/secp256k1/archive/{version}.zip" sha512sum = "64080d7c3345fe8117787e328a09a3b493c38880cabf73d34e472ab0db4cb17ff989689f0c785680bdba39c446dc8a64d34587f4a0797b225c5687d0eb2da607" - # TODO: configure needs options: - # --enable-module-extrakeys - # --enable-module-schnorrsig recipe = LibSecp256k1RecipePinned() From e72210b5d223057493d76a4e42e52fae03ca0811 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 11 Apr 2024 13:16:26 +0000 Subject: [PATCH 3/7] ecc: clean-up return value checks --- electrum/ecc.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/electrum/ecc.py b/electrum/ecc.py index 91b9a4dc5..7c3970702 100644 --- a/electrum/ecc.py +++ b/electrum/ecc.py @@ -65,13 +65,13 @@ def der_sig_from_r_and_s(r: int, s: int) -> bytes: int.to_bytes(s, length=32, byteorder="big")) sig = create_string_buffer(64) ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string) - if not ret: + if 1 != ret: raise Exception("Bad signature") ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) der_sig = create_string_buffer(80) # this much space should be enough der_sig_size = c_size_t(len(der_sig)) ret = _libsecp256k1.secp256k1_ecdsa_signature_serialize_der(_libsecp256k1.ctx, der_sig, byref(der_sig_size), sig) - if not ret: + if 1 != ret: raise Exception("failed to serialize DER sig") der_sig_size = der_sig_size.value return bytes(der_sig)[:der_sig_size] @@ -81,7 +81,7 @@ def get_r_and_s_from_der_sig(der_sig: bytes) -> Tuple[int, int]: assert isinstance(der_sig, bytes) sig = create_string_buffer(64) ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_der(_libsecp256k1.ctx, sig, der_sig, len(der_sig)) - if not ret: + if 1 != ret: raise Exception("Bad signature") ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) compact_signature = create_string_buffer(64) @@ -96,7 +96,7 @@ def get_r_and_s_from_sig_string(sig_string: bytes) -> Tuple[int, int]: raise Exception("sig_string must be bytes, and 64 bytes exactly") sig = create_string_buffer(64) ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string) - if not ret: + if 1 != ret: raise Exception("Bad signature") ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) compact_signature = create_string_buffer(64) @@ -111,7 +111,7 @@ def sig_string_from_r_and_s(r: int, s: int) -> bytes: int.to_bytes(s, length=32, byteorder="big")) sig = create_string_buffer(64) ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string) - if not ret: + if 1 != ret: raise Exception("Bad signature") ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) compact_signature = create_string_buffer(64) @@ -124,7 +124,7 @@ def _x_and_y_from_pubkey_bytes(pubkey: bytes) -> Tuple[int, int]: pubkey_ptr = create_string_buffer(64) ret = _libsecp256k1.secp256k1_ec_pubkey_parse( _libsecp256k1.ctx, pubkey_ptr, pubkey, len(pubkey)) - if not ret: + if 1 != ret: raise InvalidECPointException('public key could not be parsed or is invalid') pubkey_serialized = create_string_buffer(65) @@ -164,11 +164,11 @@ class ECPubkey(object): sig65 = create_string_buffer(65) ret = _libsecp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact( _libsecp256k1.ctx, sig65, sig_string, recid) - if not ret: + if 1 != ret: raise Exception('failed to parse signature') pubkey = create_string_buffer(64) ret = _libsecp256k1.secp256k1_ecdsa_recover(_libsecp256k1.ctx, pubkey, sig65, msg_hash) - if not ret: + if 1 != ret: raise InvalidECPointException('failed to recover public key') return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey) @@ -241,7 +241,7 @@ class ECPubkey(object): public_pair_bytes = self.get_public_key_bytes(compressed=False) ret = _libsecp256k1.secp256k1_ec_pubkey_parse( _libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes)) - if not ret: + if 1 != ret: raise Exception('public key could not be parsed or is invalid') return pubkey @@ -278,7 +278,7 @@ class ECPubkey(object): pubkey = self._to_libsecp256k1_pubkey_ptr() ret = _libsecp256k1.secp256k1_ec_pubkey_tweak_mul(_libsecp256k1.ctx, pubkey, other.to_bytes(32, byteorder="big")) - if not ret: + if 1 != ret: return POINT_AT_INFINITY return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey) @@ -299,7 +299,7 @@ class ECPubkey(object): pubkey2 = cast(pubkey2, c_char_p) array_of_pubkey_ptrs = (c_char_p * 2)(pubkey1, pubkey2) ret = _libsecp256k1.secp256k1_ec_pubkey_combine(_libsecp256k1.ctx, pubkey_sum, array_of_pubkey_ptrs, 2) - if not ret: + if 1 != ret: return POINT_AT_INFINITY return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey_sum) @@ -343,7 +343,7 @@ class ECPubkey(object): sig = create_string_buffer(64) ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string) - if not ret: + if 1 != ret: return False ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) @@ -500,7 +500,7 @@ class ECPrivkey(ECPubkey): ret = _libsecp256k1.secp256k1_ecdsa_sign( _libsecp256k1.ctx, sig, msg_hash, privkey_bytes, nonce_function, extra_entropy) - if not ret: + if 1 != ret: raise Exception('the nonce generation function failed, or the private key was invalid') compact_signature = create_string_buffer(64) _libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig) From bd9d0ccc336da77b395809cd01ae94a4bcd9e32e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 11 Apr 2024 15:13:41 +0000 Subject: [PATCH 4/7] ecc: refactor/clean-up sign/verify APIs --- electrum/channel_db.py | 5 +- electrum/commands.py | 2 +- electrum/ecc.py | 159 +++++++++--------- electrum/gui/qml/qedaemon.py | 4 +- electrum/gui/qt/main_window.py | 2 +- electrum/gui/qt/update_checker.py | 4 +- electrum/keystore.py | 2 +- electrum/lnaddr.py | 7 +- electrum/lnchannel.py | 17 +- electrum/lnpeer.py | 20 +-- electrum/lnutil.py | 4 +- electrum/lnverifier.py | 3 +- electrum/lnworker.py | 2 +- electrum/paymentrequest.py | 4 +- electrum/plugins/bitbox02/bitbox02.py | 2 +- electrum/plugins/coldcard/coldcard.py | 2 +- .../plugins/digitalbitbox/digitalbitbox.py | 18 +- electrum/plugins/trustedcoin/common_qt.py | 2 +- electrum/storage.py | 4 +- electrum/transaction.py | 8 +- tests/test_bitcoin.py | 43 ++--- tests/test_ecc.py | 16 +- 22 files changed, 165 insertions(+), 165 deletions(-) diff --git a/electrum/channel_db.py b/electrum/channel_db.py index 904426254..7b10d3e4c 100644 --- a/electrum/channel_db.py +++ b/electrum/channel_db.py @@ -46,6 +46,7 @@ from .lnutil import (LNPeerAddr, format_short_channel_id, ShortChannelID, from .lnverifier import LNChannelVerifier, verify_sig_for_channel_update from .lnmsg import decode_msg from . import ecc +from .ecc import ECPubkey from .crypto import sha256d from .lnmsg import FailedToParseMsg @@ -605,7 +606,7 @@ class ChannelDB(SqlDB): pubkeys = [payload['node_id_1'], payload['node_id_2'], payload['bitcoin_key_1'], payload['bitcoin_key_2']] sigs = [payload['node_signature_1'], payload['node_signature_2'], payload['bitcoin_signature_1'], payload['bitcoin_signature_2']] for pubkey, sig in zip(pubkeys, sigs): - if not ecc.verify_signature(pubkey, sig, h): + if not ECPubkey(pubkey).ecdsa_verify(sig, h): raise InvalidGossipMsg('signature failed') @classmethod @@ -613,7 +614,7 @@ class ChannelDB(SqlDB): pubkey = payload['node_id'] signature = payload['signature'] h = sha256d(payload['raw'][66:]) - if not ecc.verify_signature(pubkey, signature, h): + if not ECPubkey(pubkey).ecdsa_verify(signature, h): raise InvalidGossipMsg('signature failed') def add_node_announcements(self, msg_payloads): diff --git a/electrum/commands.py b/electrum/commands.py index 3be45e86e..14b655dc7 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -713,7 +713,7 @@ class Commands: """Verify a signature.""" sig = base64.b64decode(signature) message = util.to_bytes(message) - return ecc.verify_message_with_address(address, sig, message) + return ecc.verify_usermessage_with_address(address, sig, message) @command('wp') async def payto(self, destination, amount, fee=None, feerate=None, from_addr=None, from_coins=None, change_addr=None, diff --git a/electrum/ecc.py b/electrum/ecc.py index 7c3970702..5e81fa4cf 100644 --- a/electrum/ecc.py +++ b/electrum/ecc.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Electrum - lightweight Bitcoin client -# Copyright (C) 2018 The Electrum developers +# Copyright (C) 2018-2024 The Electrum developers # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files @@ -28,8 +28,7 @@ import hashlib import functools from typing import Union, Tuple, Optional from ctypes import ( - byref, c_byte, c_int, c_uint, c_char_p, c_size_t, c_void_p, create_string_buffer, - CFUNCTYPE, POINTER, cast + byref, c_char_p, c_size_t, create_string_buffer, cast, ) from .util import bfh, assert_bytes, to_bytes, InvalidPassword, profiler, randrange @@ -50,21 +49,22 @@ def string_to_number(b: bytes) -> int: return int.from_bytes(b, byteorder='big', signed=False) -def sig_string_from_der_sig(der_sig: bytes) -> bytes: - r, s = get_r_and_s_from_der_sig(der_sig) - return sig_string_from_r_and_s(r, s) +def ecdsa_sig64_from_der_sig(der_sig: bytes) -> bytes: + r, s = get_r_and_s_from_ecdsa_der_sig(der_sig) + return ecdsa_sig64_from_r_and_s(r, s) -def der_sig_from_sig_string(sig_string: bytes) -> bytes: - r, s = get_r_and_s_from_sig_string(sig_string) - return der_sig_from_r_and_s(r, s) +def ecdsa_der_sig_from_ecdsa_sig64(sig64: bytes) -> bytes: + r, s = get_r_and_s_from_ecdsa_sig64(sig64) + return ecdsa_der_sig_from_r_and_s(r, s) -def der_sig_from_r_and_s(r: int, s: int) -> bytes: - sig_string = (int.to_bytes(r, length=32, byteorder="big") + - int.to_bytes(s, length=32, byteorder="big")) +def ecdsa_der_sig_from_r_and_s(r: int, s: int) -> bytes: + sig64 = ( + int.to_bytes(r, length=32, byteorder="big") + + int.to_bytes(s, length=32, byteorder="big")) sig = create_string_buffer(64) - ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string) + ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig64) if 1 != ret: raise Exception("Bad signature") ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) @@ -77,7 +77,7 @@ def der_sig_from_r_and_s(r: int, s: int) -> bytes: return bytes(der_sig)[:der_sig_size] -def get_r_and_s_from_der_sig(der_sig: bytes) -> Tuple[int, int]: +def get_r_and_s_from_ecdsa_der_sig(der_sig: bytes) -> Tuple[int, int]: assert isinstance(der_sig, bytes) sig = create_string_buffer(64) ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_der(_libsecp256k1.ctx, sig, der_sig, len(der_sig)) @@ -91,11 +91,11 @@ def get_r_and_s_from_der_sig(der_sig: bytes) -> Tuple[int, int]: return r, s -def get_r_and_s_from_sig_string(sig_string: bytes) -> Tuple[int, int]: - if not (isinstance(sig_string, bytes) and len(sig_string) == 64): - raise Exception("sig_string must be bytes, and 64 bytes exactly") +def get_r_and_s_from_ecdsa_sig64(sig64: bytes) -> Tuple[int, int]: + if not (isinstance(sig64, bytes) and len(sig64) == 64): + raise Exception("sig64 must be bytes, and 64 bytes exactly") sig = create_string_buffer(64) - ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string) + ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig64) if 1 != ret: raise Exception("Bad signature") ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) @@ -106,11 +106,12 @@ def get_r_and_s_from_sig_string(sig_string: bytes) -> Tuple[int, int]: return r, s -def sig_string_from_r_and_s(r: int, s: int) -> bytes: - sig_string = (int.to_bytes(r, length=32, byteorder="big") + - int.to_bytes(s, length=32, byteorder="big")) +def ecdsa_sig64_from_r_and_s(r: int, s: int) -> bytes: + sig64 = ( + int.to_bytes(r, length=32, byteorder="big") + + int.to_bytes(s, length=32, byteorder="big")) sig = create_string_buffer(64) - ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string) + ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig64) if 1 != ret: raise Exception("Bad signature") ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) @@ -155,28 +156,31 @@ class ECPubkey(object): self._x, self._y = None, None @classmethod - def from_sig_string(cls, sig_string: bytes, recid: int, msg_hash: bytes) -> 'ECPubkey': - assert_bytes(sig_string) - if len(sig_string) != 64: - raise Exception(f'wrong encoding used for signature? len={len(sig_string)} (should be 64)') + def from_ecdsa_sig64(cls, sig64: bytes, recid: int, msg32: bytes) -> 'ECPubkey': + assert_bytes(sig64) + if len(sig64) != 64: + raise Exception(f'wrong encoding used for signature? len={len(sig64)} (should be 64)') if not (0 <= recid <= 3): raise ValueError('recid is {}, but should be 0 <= recid <= 3'.format(recid)) + assert isinstance(msg32, (bytes, bytearray)), type(msg32) + assert len(msg32) == 32, len(msg32) sig65 = create_string_buffer(65) ret = _libsecp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact( - _libsecp256k1.ctx, sig65, sig_string, recid) + _libsecp256k1.ctx, sig65, sig64, recid) if 1 != ret: raise Exception('failed to parse signature') pubkey = create_string_buffer(64) - ret = _libsecp256k1.secp256k1_ecdsa_recover(_libsecp256k1.ctx, pubkey, sig65, msg_hash) + ret = _libsecp256k1.secp256k1_ecdsa_recover(_libsecp256k1.ctx, pubkey, sig65, msg32) if 1 != ret: raise InvalidECPointException('failed to recover public key') return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey) @classmethod - def from_signature65(cls, sig: bytes, msg_hash: bytes) -> Tuple['ECPubkey', bool, Optional[str]]: - if len(sig) != 65: - raise Exception(f'wrong encoding used for signature? len={len(sig)} (should be 65)') - nV = sig[0] + def from_ecdsa_sig65(cls, sig65: bytes, msg32: bytes) -> Tuple['ECPubkey', bool, Optional[str]]: + assert_bytes(sig65) + if len(sig65) != 65: + raise Exception(f'wrong encoding used for signature? len={len(sig65)} (should be 65)') + nV = sig65[0] # as per BIP-0137: # 27-30: p2pkh (uncompressed) # 31-34: p2pkh (compressed) @@ -199,7 +203,7 @@ class ECPubkey(object): else: compressed = False recid = nV - 27 - pubkey = cls.from_sig_string(sig[1:], recid, msg_hash) + pubkey = cls.from_ecdsa_sig64(sig65[1:], recid, msg32) return pubkey, compressed, txin_type_guess @classmethod @@ -321,38 +325,36 @@ class ECPubkey(object): p2 = ((other.x() or 0), (other.y() or 0)) return p1 < p2 - def verify_message_for_address(self, sig65: bytes, message: bytes, algo=lambda x: sha256d(msg_magic(x))) -> bool: - assert_bytes(message) - h = algo(message) + def ecdsa_verify_recoverable(self, sig65: bytes, msg32: bytes) -> bool: try: - public_key, compressed, txin_type_guess = self.from_signature65(sig65, h) + public_key, _compressed, _txin_type_guess = self.from_ecdsa_sig65(sig65, msg32) except Exception: return False # check public key if public_key != self: return False # check message - return self.verify_message_hash(sig65[1:], h) + return self.ecdsa_verify(sig65[1:], msg32) - def verify_message_hash(self, sig_string: bytes, msg_hash: bytes) -> bool: - assert_bytes(sig_string) - if len(sig_string) != 64: + def ecdsa_verify(self, sig64: bytes, msg32: bytes) -> bool: + assert_bytes(sig64) + if len(sig64) != 64: return False - if not (isinstance(msg_hash, bytes) and len(msg_hash) == 32): + if not (isinstance(msg32, bytes) and len(msg32) == 32): return False sig = create_string_buffer(64) - ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig_string) + ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_compact(_libsecp256k1.ctx, sig, sig64) if 1 != ret: return False ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) pubkey = self._to_libsecp256k1_pubkey_ptr() - if 1 != _libsecp256k1.secp256k1_ecdsa_verify(_libsecp256k1.ctx, sig, msg_hash, pubkey): + if 1 != _libsecp256k1.secp256k1_ecdsa_verify(_libsecp256k1.ctx, sig, msg32, pubkey): return False return True - def verify_message_schnorr(self, sig64: bytes, msg32: bytes) -> bool: + def schnorr_verify(self, sig64: bytes, msg32: bytes) -> bool: assert isinstance(sig64, bytes), type(sig64) assert len(sig64) == 64, len(sig64) assert isinstance(msg32, bytes), type(msg32) @@ -363,7 +365,7 @@ class ECPubkey(object): return False return True - def encrypt_message(self, message: bytes, magic: bytes = b'BIE1') -> bytes: + def encrypt_message(self, message: bytes, *, magic: bytes = b'BIE1') -> bytes: """ ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; hmac-sha256 is used as the mac """ @@ -402,23 +404,19 @@ CURVE_ORDER = 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D POINT_AT_INFINITY = ECPubkey(None) -def msg_magic(message: bytes) -> bytes: +def usermessage_magic(message: bytes) -> bytes: from .bitcoin import var_int length = bfh(var_int(len(message))) return b"\x18Bitcoin Signed Message:\n" + length + message -def verify_signature(pubkey: bytes, sig: bytes, h: bytes) -> bool: - return ECPubkey(pubkey).verify_message_hash(sig, h) - - -def verify_message_with_address(address: str, sig65: bytes, message: bytes, *, net=None) -> bool: +def verify_usermessage_with_address(address: str, sig65: bytes, message: bytes, *, net=None) -> bool: from .bitcoin import pubkey_to_address assert_bytes(sig65, message) if net is None: net = constants.net - h = sha256d(msg_magic(message)) + h = sha256d(usermessage_magic(message)) try: - public_key, compressed, txin_type_guess = ECPubkey.from_signature65(sig65, h) + public_key, compressed, txin_type_guess = ECPubkey.from_ecdsa_sig65(sig65, h) except Exception as e: return False # check public key using the address @@ -431,7 +429,7 @@ def verify_message_with_address(address: str, sig65: bytes, message: bytes, *, n else: return False # check message - return public_key.verify_message_hash(sig65[1:], h) + return public_key.ecdsa_verify(sig65[1:], h) def is_secret_within_curve_range(secret: Union[int, bytes]) -> bool: @@ -487,18 +485,18 @@ class ECPrivkey(ECPubkey): def get_secret_bytes(self) -> bytes: return int.to_bytes(self.secret_scalar, length=32, byteorder='big', signed=False) - def sign(self, msg_hash: bytes, sigencode=None) -> bytes: - if not (isinstance(msg_hash, bytes) and len(msg_hash) == 32): - raise Exception("msg_hash to be signed must be bytes, and 32 bytes exactly") + def ecdsa_sign(self, msg32: bytes, *, sigencode=None) -> bytes: + if not (isinstance(msg32, bytes) and len(msg32) == 32): + raise Exception("msg32 to be signed must be bytes, and 32 bytes exactly") if sigencode is None: - sigencode = sig_string_from_r_and_s + sigencode = ecdsa_sig64_from_r_and_s privkey_bytes = self.secret_scalar.to_bytes(32, byteorder="big") nonce_function = None sig = create_string_buffer(64) def sign_with_extra_entropy(extra_entropy): ret = _libsecp256k1.secp256k1_ecdsa_sign( - _libsecp256k1.ctx, sig, msg_hash, privkey_bytes, + _libsecp256k1.ctx, sig, msg32, privkey_bytes, nonce_function, extra_entropy) if 1 != ret: raise Exception('the nonce generation function failed, or the private key was invalid') @@ -516,14 +514,14 @@ class ECPrivkey(ECPubkey): extra_entropy = counter.to_bytes(32, byteorder="little") r, s = sign_with_extra_entropy(extra_entropy=extra_entropy) - sig_string = sig_string_from_r_and_s(r, s) - if not self.verify_message_hash(sig_string, msg_hash): + sig64 = ecdsa_sig64_from_r_and_s(r, s) + if not self.ecdsa_verify(sig64, msg32): raise Exception("sanity check failed: signature we just created does not verify!") sig = sigencode(r, s) return sig - def sign_schnorr(self, msg32: bytes, *, aux_rand32: bytes = None) -> bytes: + def schnorr_sign(self, msg32: bytes, *, aux_rand32: bytes = None) -> bytes: assert isinstance(msg32, bytes), type(msg32) assert len(msg32) == 32, len(msg32) if aux_rand32 is None: @@ -543,35 +541,30 @@ class ECPrivkey(ECPubkey): sig64 = bytes(sig64) if 1 != ret: raise Exception('signing failure') - if not self.verify_message_schnorr(sig64, msg32): + if not self.schnorr_verify(sig64, msg32): raise Exception("sanity check failed: signature we just created does not verify!") return sig64 - def sign_transaction(self, hashed_preimage: bytes) -> bytes: - return self.sign(hashed_preimage, sigencode=der_sig_from_r_and_s) - - def sign_message( - self, - message: Union[bytes, str], - is_compressed: bool, - algo=lambda x: sha256d(msg_magic(x)), - ) -> bytes: - def bruteforce_recid(sig_string): + def ecdsa_sign_recoverable(self, msg32: bytes, *, is_compressed: bool) -> bytes: + def bruteforce_recid(sig64: bytes): for recid in range(4): - sig65 = construct_sig65(sig_string, recid, is_compressed) - if not self.verify_message_for_address(sig65, message, algo): + sig65 = construct_ecdsa_sig65(sig64, recid, is_compressed=is_compressed) + if not self.ecdsa_verify_recoverable(sig65, msg32): continue return sig65, recid else: raise Exception("error: cannot sign message. no recid fits..") - message = to_bytes(message, 'utf8') - msg_hash = algo(message) - sig_string = self.sign(msg_hash, sigencode=sig_string_from_r_and_s) - sig65, recid = bruteforce_recid(sig_string) + sig64 = self.ecdsa_sign(msg32, sigencode=ecdsa_sig64_from_r_and_s) + sig65, recid = bruteforce_recid(sig64) return sig65 - def decrypt_message(self, encrypted: Union[str, bytes], magic: bytes=b'BIE1') -> bytes: + def ecdsa_sign_usermessage(self, message: Union[bytes, str], *, is_compressed: bool) -> bytes: + message = to_bytes(message, 'utf8') + msg32 = sha256d(usermessage_magic(message)) + return self.ecdsa_sign_recoverable(msg32, is_compressed=is_compressed) + + def decrypt_message(self, encrypted: Union[str, bytes], *, magic: bytes=b'BIE1') -> bytes: encrypted = base64.b64decode(encrypted) # type: bytes if len(encrypted) < 85: raise Exception('invalid ciphertext: length') @@ -593,6 +586,6 @@ class ECPrivkey(ECPubkey): return aes_decrypt_with_iv(key_e, iv, ciphertext) -def construct_sig65(sig_string: bytes, recid: int, is_compressed: bool) -> bytes: +def construct_ecdsa_sig65(sig64: bytes, recid: int, *, is_compressed: bool) -> bytes: comp = 4 if is_compressed else 0 - return bytes([27 + recid + comp]) + sig_string + return bytes([27 + recid + comp]) + sig64 diff --git a/electrum/gui/qml/qedaemon.py b/electrum/gui/qml/qedaemon.py index 88b70379e..223bdc1bd 100644 --- a/electrum/gui/qml/qedaemon.py +++ b/electrum/gui/qml/qedaemon.py @@ -12,7 +12,7 @@ from electrum.util import WalletFileException, standardize_path, InvalidPassword from electrum.plugin import run_hook from electrum.lnchannel import ChannelState from electrum.bitcoin import is_address -from electrum.ecc import verify_message_with_address +from electrum.ecc import verify_usermessage_with_address from electrum.storage import StorageReadWriteError from .auth import AuthMixin, auth_protect @@ -372,7 +372,7 @@ class QEDaemon(AuthMixin, QObject): try: # This can throw on invalid base64 sig = base64.b64decode(str(signature.strip())) - verified = verify_message_with_address(address, sig, message) + verified = verify_usermessage_with_address(address, sig, message) except Exception as e: verified = False return verified diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index bad544f5f..c755d896e 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -1982,7 +1982,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): try: # This can throw on invalid base64 sig = base64.b64decode(str(signature.toPlainText())) - verified = ecc.verify_message_with_address(address, sig, message) + verified = ecc.verify_usermessage_with_address(address, sig, message) except Exception as e: verified = False if verified: diff --git a/electrum/gui/qt/update_checker.py b/electrum/gui/qt/update_checker.py index d0166742c..7e8d1c8f9 100644 --- a/electrum/gui/qt/update_checker.py +++ b/electrum/gui/qt/update_checker.py @@ -123,8 +123,8 @@ class UpdateCheckThread(QThread, Logger): continue sig = base64.b64decode(sig) msg = version_num.encode('utf-8') - if ecc.verify_message_with_address(address=address, sig65=sig, message=msg, - net=constants.BitcoinMainnet): + if ecc.verify_usermessage_with_address(address=address, sig65=sig, message=msg, + net=constants.BitcoinMainnet): self.logger.info(f"valid sig for version announcement '{version_num}' from address '{address}'") break else: diff --git a/electrum/keystore.py b/electrum/keystore.py index dc155cb75..f71ba1926 100644 --- a/electrum/keystore.py +++ b/electrum/keystore.py @@ -223,7 +223,7 @@ class Software_KeyStore(KeyStore): def sign_message(self, sequence, message, password, *, script_type=None) -> bytes: privkey, compressed = self.get_private_key(sequence, password) key = ecc.ECPrivkey(privkey) - return key.sign_message(message, compressed) + return key.ecdsa_sign_usermessage(message, is_compressed=compressed) def decrypt_message(self, sequence, message, password) -> bytes: privkey, compressed = self.get_private_key(sequence, password) diff --git a/electrum/lnaddr.py b/electrum/lnaddr.py index c409a705b..a27fc696b 100644 --- a/electrum/lnaddr.py +++ b/electrum/lnaddr.py @@ -252,8 +252,9 @@ def lnencode(addr: 'LnAddr', privkey) -> str: # We actually sign the hrp, then data (padded to 8 bits with zeroes). msg = hrp.encode("ascii") + data.tobytes() + msg32 = sha256(msg).digest() privkey = ecc.ECPrivkey(privkey) - sig = privkey.sign_message(msg, is_compressed=False, algo=lambda x:sha256(x).digest()) + sig = privkey.ecdsa_sign_recoverable(msg32, is_compressed=False) recovery_flag = bytes([sig[0] - 27]) sig = bytes(sig[1:]) + recovery_flag data += sig @@ -550,13 +551,13 @@ def lndecode(invoice: str, *, verbose=False, net=None) -> LnAddr: # # A reader MUST use the `n` field to validate the signature instead of # performing signature recovery if a valid `n` field is provided. - if not ecc.ECPubkey(addr.pubkey).verify_message_hash(sigdecoded[:64], hrp_hash): + if not ecc.ECPubkey(addr.pubkey).ecdsa_verify(sigdecoded[:64], hrp_hash): raise LnDecodeException("bad signature") pubkey_copy = addr.pubkey class WrappedBytesKey: serialize = lambda: pubkey_copy addr.pubkey = WrappedBytesKey else: # Recover pubkey from signature. - addr.pubkey = SerializableKey(ecc.ECPubkey.from_sig_string(sigdecoded[:64], sigdecoded[64], hrp_hash)) + addr.pubkey = SerializableKey(ecc.ECPubkey.from_ecdsa_sig64(sigdecoded[:64], sigdecoded[64], hrp_hash)) return addr diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py index 49fff87f2..f135dbe4d 100644 --- a/electrum/lnchannel.py +++ b/electrum/lnchannel.py @@ -34,6 +34,7 @@ from aiorpcx import NetAddress import attr from . import ecc +from .ecc import ECPubkey from . import constants, util from .util import bfh, chunks, TxMinedInfo from .invoices import PR_PAID @@ -802,7 +803,7 @@ class Channel(AbstractChannel): timestamp=now(), ) sighash = sha256d(chan_upd[2 + 64:]) - sig = ecc.ECPrivkey(self.lnworker.node_keypair.privkey).sign(sighash, ecc.sig_string_from_r_and_s) + sig = ecc.ECPrivkey(self.lnworker.node_keypair.privkey).ecdsa_sign(sighash, sigencode=ecc.ecdsa_sig64_from_r_and_s) message_type, payload = decode_msg(chan_upd) payload['signature'] = sig chan_upd = encode_msg(message_type, **payload) @@ -1099,7 +1100,7 @@ class Channel(AbstractChannel): ctx_output_idx=ctx_output_idx, htlc=htlc) sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey)) - htlc_sig = ecc.sig_string_from_der_sig(sig[:-1]) + htlc_sig = ecc.ecdsa_sig64_from_der_sig(sig[:-1]) htlcsigs.append((ctx_output_idx, htlc_sig)) htlcsigs.sort() htlcsigs = [x[1] for x in htlcsigs] @@ -1122,7 +1123,7 @@ class Channel(AbstractChannel): pending_local_commitment = self.get_next_commitment(LOCAL) preimage_hex = pending_local_commitment.serialize_preimage(0) pre_hash = sha256d(bfh(preimage_hex)) - if not ecc.verify_signature(self.config[REMOTE].multisig_key.pubkey, sig, pre_hash): + if not ECPubkey(self.config[REMOTE].multisig_key.pubkey).ecdsa_verify(sig, pre_hash): raise LNProtocolWarning( f'failed verifying signature for our updated commitment transaction. ' f'sig={sig.hex()}. ' @@ -1169,7 +1170,7 @@ class Channel(AbstractChannel): preimage_hex = htlc_tx.serialize_preimage(0) pre_hash = sha256d(bfh(preimage_hex)) remote_htlc_pubkey = derive_pubkey(self.config[REMOTE].htlc_basepoint.pubkey, pcp) - if not ecc.verify_signature(remote_htlc_pubkey, htlc_sig, pre_hash): + if not ECPubkey(remote_htlc_pubkey).ecdsa_verify(htlc_sig, pre_hash): raise LNProtocolWarning( f'failed verifying HTLC signatures: {htlc=}, {htlc_direction=}. ' f'htlc_tx={htlc_tx.serialize()}. ' @@ -1185,7 +1186,7 @@ class Channel(AbstractChannel): data = self.config[LOCAL].current_htlc_signatures htlc_sigs = list(chunks(data, 64)) htlc_sig = htlc_sigs[htlc_relative_idx] - remote_htlc_sig = ecc.der_sig_from_sig_string(htlc_sig) + Sighash.to_sigbytes(Sighash.ALL) + remote_htlc_sig = ecc.ecdsa_der_sig_from_ecdsa_sig64(htlc_sig) + Sighash.to_sigbytes(Sighash.ALL) return remote_htlc_sig def revoke_current_commitment(self): @@ -1599,7 +1600,7 @@ class Channel(AbstractChannel): outputs=outputs) der_sig = bfh(closing_tx.sign_txin(0, self.config[LOCAL].multisig_key.privkey)) - sig = ecc.sig_string_from_der_sig(der_sig[:-1]) + sig = ecc.ecdsa_sig64_from_der_sig(der_sig[:-1]) return sig, closing_tx def signature_fits(self, tx: PartialTransaction) -> bool: @@ -1607,7 +1608,7 @@ class Channel(AbstractChannel): preimage_hex = tx.serialize_preimage(0) msg_hash = sha256d(bfh(preimage_hex)) assert remote_sig - res = ecc.verify_signature(self.config[REMOTE].multisig_key.pubkey, remote_sig, msg_hash) + res = ECPubkey(self.config[REMOTE].multisig_key.pubkey).ecdsa_verify(remote_sig, msg_hash) return res def force_close_tx(self) -> PartialTransaction: @@ -1615,7 +1616,7 @@ class Channel(AbstractChannel): assert self.signature_fits(tx) tx.sign({self.config[LOCAL].multisig_key.pubkey.hex(): (self.config[LOCAL].multisig_key.privkey, True)}) remote_sig = self.config[LOCAL].current_commitment_signature - remote_sig = ecc.der_sig_from_sig_string(remote_sig) + Sighash.to_sigbytes(Sighash.ALL) + remote_sig = ecc.ecdsa_der_sig_from_ecdsa_sig64(remote_sig) + Sighash.to_sigbytes(Sighash.ALL) tx.add_signature_to_txin(txin_idx=0, signing_pubkey=self.config[REMOTE].multisig_key.pubkey.hex(), sig=remote_sig.hex()) diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index 3f8c35242..6bff0c8ab 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -19,7 +19,7 @@ from aiorpcx import ignore_after from .crypto import sha256, sha256d from . import bitcoin, util from . import ecc -from .ecc import sig_string_from_r_and_s, der_sig_from_sig_string +from .ecc import ecdsa_sig64_from_r_and_s, ecdsa_der_sig_from_ecdsa_sig64, ECPubkey from . import constants from .util import (bfh, log_exceptions, ignore_exceptions, chunks, OldTaskGroup, UnrelatedTransactionException, error_text_bytes_to_safe_str) @@ -424,9 +424,9 @@ class Peer(Logger): h = chan.get_channel_announcement_hash() node_signature = payload["node_signature"] bitcoin_signature = payload["bitcoin_signature"] - if not ecc.verify_signature(chan.config[REMOTE].multisig_key.pubkey, bitcoin_signature, h): + if not ECPubkey(chan.config[REMOTE].multisig_key.pubkey).ecdsa_verify(bitcoin_signature, h): raise Exception("bitcoin_sig invalid in announcement_signatures") - if not ecc.verify_signature(self.pubkey, node_signature, h): + if not ECPubkey(self.pubkey).ecdsa_verify(node_signature, h): raise Exception("node_sig invalid in announcement_signatures") chan.config[REMOTE].announcement_node_sig = node_signature chan.config[REMOTE].announcement_bitcoin_sig = bitcoin_signature @@ -1453,7 +1453,7 @@ class Peer(Logger): addrlen=len(addresses), addresses=addresses) h = sha256d(raw_msg[64+2:]) - signature = ecc.ECPrivkey(self.privkey).sign(h, sig_string_from_r_and_s) + signature = ecc.ECPrivkey(self.privkey).ecdsa_sign(h, sigencode=ecdsa_sig64_from_r_and_s) message_type, payload = decode_msg(raw_msg) payload['signature'] = signature raw_msg = encode_msg(message_type, **payload) @@ -1516,8 +1516,8 @@ class Peer(Logger): if not is_reply and chan.config[REMOTE].announcement_node_sig: return h = chan.get_channel_announcement_hash() - bitcoin_signature = ecc.ECPrivkey(chan.config[LOCAL].multisig_key.privkey).sign(h, sig_string_from_r_and_s) - node_signature = ecc.ECPrivkey(self.privkey).sign(h, sig_string_from_r_and_s) + bitcoin_signature = ecc.ECPrivkey(chan.config[LOCAL].multisig_key.privkey).ecdsa_sign(h, sigencode=ecdsa_sig64_from_r_and_s) + node_signature = ecc.ECPrivkey(self.privkey).ecdsa_sign(h, sigencode=ecdsa_sig64_from_r_and_s) self.send_message( "announcement_signatures", channel_id=chan.channel_id, @@ -2446,11 +2446,11 @@ class Peer(Logger): closing_signed_tlvs=closing_signed_tlvs, ) - def verify_signature(tx, sig): + def verify_signature(tx: 'PartialTransaction', sig) -> bool: their_pubkey = chan.config[REMOTE].multisig_key.pubkey preimage_hex = tx.serialize_preimage(0) pre_hash = sha256d(bfh(preimage_hex)) - return ecc.verify_signature(their_pubkey, sig, pre_hash) + return ECPubkey(their_pubkey).ecdsa_verify(sig, pre_hash) async def receive_closing_signed(): nonlocal our_sig, closing_tx @@ -2571,11 +2571,11 @@ class Peer(Logger): closing_tx.add_signature_to_txin( txin_idx=0, signing_pubkey=chan.config[LOCAL].multisig_key.pubkey.hex(), - sig=(der_sig_from_sig_string(our_sig) + Sighash.to_sigbytes(Sighash.ALL)).hex()) + sig=(ecdsa_der_sig_from_ecdsa_sig64(our_sig) + Sighash.to_sigbytes(Sighash.ALL)).hex()) closing_tx.add_signature_to_txin( txin_idx=0, signing_pubkey=chan.config[REMOTE].multisig_key.pubkey.hex(), - sig=(der_sig_from_sig_string(their_sig) + Sighash.to_sigbytes(Sighash.ALL)).hex()) + sig=(ecdsa_der_sig_from_ecdsa_sig64(their_sig) + Sighash.to_sigbytes(Sighash.ALL)).hex()) # save local transaction and set state try: self.lnworker.wallet.adb.add_transaction(closing_tx) diff --git a/electrum/lnutil.py b/electrum/lnutil.py index f30aebe92..bbcd9e5aa 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -21,7 +21,7 @@ from .util import format_short_id as format_short_channel_id from .crypto import sha256, pw_decode_with_version_and_mac from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOutpoint, PartialTxOutput, opcodes, TxOutput) -from .ecc import CURVE_ORDER, sig_string_from_der_sig, ECPubkey, string_to_number +from .ecc import CURVE_ORDER, ecdsa_sig64_from_der_sig, ECPubkey, string_to_number from . import ecc, bitcoin, crypto, transaction from . import descriptor from .bitcoin import (push_script, redeem_script_to_address, address_to_script, @@ -1084,7 +1084,7 @@ def make_commitment_output_to_remote_address(remote_payment_pubkey: bytes) -> st def sign_and_get_sig_string(tx: PartialTransaction, local_config, remote_config): tx.sign({local_config.multisig_key.pubkey.hex(): (local_config.multisig_key.privkey, True)}) sig = tx.inputs()[0].part_sigs[local_config.multisig_key.pubkey] - sig_64 = sig_string_from_der_sig(sig[:-1]) + sig_64 = ecdsa_sig64_from_der_sig(sig[:-1]) return sig_64 def funding_output_script(local_config, remote_config) -> str: diff --git a/electrum/lnverifier.py b/electrum/lnverifier.py index 63c3f6984..95512a06c 100644 --- a/electrum/lnverifier.py +++ b/electrum/lnverifier.py @@ -31,6 +31,7 @@ import aiorpcx from . import bitcoin from . import ecc +from .ecc import ECPubkey from . import constants from .util import bfh, NetworkJobOnDefaultServer from .lnutil import funding_output_script_from_keys, ShortChannelID @@ -183,6 +184,6 @@ def verify_sig_for_channel_update(chan_upd: dict, node_id: bytes) -> bool: pre_hash = msg_bytes[2+64:] h = sha256d(pre_hash) sig = chan_upd['signature'] - if not ecc.verify_signature(node_id, sig, h): + if not ECPubkey(node_id).ecdsa_verify(sig, h): return False return True diff --git a/electrum/lnworker.py b/electrum/lnworker.py index fa28cf56d..0943a1b1c 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -51,7 +51,7 @@ from .logging import Logger from .lntransport import LNTransport, LNResponderTransport, LNTransportBase from .lnpeer import Peer, LN_P2P_NETWORK_TIMEOUT from .lnaddr import lnencode, LnAddr, lndecode -from .ecc import der_sig_from_sig_string +from .ecc import ecdsa_der_sig_from_ecdsa_sig64 from .lnchannel import Channel, AbstractChannel from .lnchannel import ChannelState, PeerState, HTLCWithStatus from .lnrater import LNRater diff --git a/electrum/paymentrequest.py b/electrum/paymentrequest.py index 49082f422..33f64aad6 100644 --- a/electrum/paymentrequest.py +++ b/electrum/paymentrequest.py @@ -235,7 +235,7 @@ class PaymentRequest: address = info.get('address') pr.signature = b'' message = pr.SerializeToString() - if ecc.verify_message_with_address(address, sig, message): + if ecc.verify_usermessage_with_address(address, sig, message): self._verified_success_msg = 'Verified with DNSSEC' self._verified_success = True return True @@ -356,7 +356,7 @@ def sign_request_with_alias(pr, alias, alias_privkey): message = pr.SerializeToString() ec_key = ecc.ECPrivkey(alias_privkey) compressed = bitcoin.is_compressed_privkey(alias_privkey) - pr.signature = ec_key.sign_message(message, compressed) + pr.signature = ec_key.ecdsa_sign_usermessage(message, is_compressed=compressed) def verify_cert_chain(chain): diff --git a/electrum/plugins/bitbox02/bitbox02.py b/electrum/plugins/bitbox02/bitbox02.py index 60e9638a0..5e8ecee32 100644 --- a/electrum/plugins/bitbox02/bitbox02.py +++ b/electrum/plugins/bitbox02/bitbox02.py @@ -537,7 +537,7 @@ class BitBox02Client(HardwareClientBase): if len(sigs) != len(tx.inputs()): raise Exception("Incorrect number of inputs signed.") # Should never occur sighash = Sighash.to_sigbytes(Sighash.ALL).hex() - signatures = [ecc.der_sig_from_sig_string(x[1]).hex() + sighash for x in sigs] + signatures = [ecc.ecdsa_der_sig_from_ecdsa_sig64(x[1]).hex() + sighash for x in sigs] tx.update_signatures(signatures) def sign_message(self, keypath: str, message: bytes, script_type: str) -> bytes: diff --git a/electrum/plugins/coldcard/coldcard.py b/electrum/plugins/coldcard/coldcard.py index 096a469c1..60ca93e0b 100644 --- a/electrum/plugins/coldcard/coldcard.py +++ b/electrum/plugins/coldcard/coldcard.py @@ -45,7 +45,7 @@ try: # verify a signature (65 bytes) over the session key, using the master bip32 node # - customized to use specific EC library of Electrum. pubkey = BIP32Node.from_xkey(expect_xpub).eckey - return pubkey.verify_message_hash(sig[1:65], self.session_key) + return pubkey.ecdsa_verify(sig[1:65], self.session_key) except ImportError as e: if not (isinstance(e, ModuleNotFoundError) and e.name == 'ckcc'): diff --git a/electrum/plugins/digitalbitbox/digitalbitbox.py b/electrum/plugins/digitalbitbox/digitalbitbox.py index 4440fd43c..8faccfc3b 100644 --- a/electrum/plugins/digitalbitbox/digitalbitbox.py +++ b/electrum/plugins/digitalbitbox/digitalbitbox.py @@ -23,7 +23,7 @@ from electrum.bip32 import BIP32Node, convert_bip32_intpath_to_strpath, is_all_p from electrum.bip32 import normalize_bip32_derivation from electrum import descriptor from electrum import ecc -from electrum.ecc import msg_magic +from electrum.ecc import usermessage_magic from electrum.wallet import Standard_Wallet from electrum import constants from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, Sighash @@ -470,7 +470,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): message = message.encode('utf8') inputPath = self.get_derivation_prefix() + "/%d/%d" % sequence inputPath = normalize_bip32_derivation(inputPath, hardened_char="'") - msg_hash = sha256d(msg_magic(message)) + msg_hash = sha256d(usermessage_magic(message)) inputHash = to_hexstr(msg_hash) hasharray = [] hasharray.append({'hash': inputHash, 'keypath': inputPath}) @@ -500,19 +500,19 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): # firmware > v2.1.1 sig_string = binascii.unhexlify(reply['sign'][0]['sig']) recid = int(reply['sign'][0]['recid'], 16) - sig = ecc.construct_sig65(sig_string, recid, True) - pubkey, compressed, txin_type_guess = ecc.ECPubkey.from_signature65(sig, msg_hash) + sig = ecc.construct_ecdsa_sig65(sig_string, recid, is_compressed=True) + pubkey, compressed, txin_type_guess = ecc.ECPubkey.from_ecdsa_sig65(sig, msg_hash) addr = public_key_to_p2pkh(pubkey.get_public_key_bytes(compressed=compressed)) - if ecc.verify_message_with_address(addr, sig, message) is False: + if ecc.verify_usermessage_with_address(addr, sig, message) is False: raise Exception(_("Could not sign message")) elif 'pubkey' in reply['sign'][0]: # firmware <= v2.1.1 for recid in range(4): sig_string = binascii.unhexlify(reply['sign'][0]['sig']) - sig = ecc.construct_sig65(sig_string, recid, True) + sig = ecc.construct_ecdsa_sig65(sig_string, recid, is_compressed=True) try: addr = public_key_to_p2pkh(binascii.unhexlify(reply['sign'][0]['pubkey'])) - if ecc.verify_message_with_address(addr, sig, message): + if ecc.verify_usermessage_with_address(addr, sig, message): break except Exception: continue @@ -649,7 +649,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): recid = int(signed['recid'], 16) s = binascii.unhexlify(signed['sig']) h = inputhasharray[i] - pk = ecc.ECPubkey.from_sig_string(s, recid, h) + pk = ecc.ECPubkey.from_ecdsa_sig64(s, recid, h) pk = pk.get_public_key_hex(compressed=True) elif 'pubkey' in signed: # firmware <= v2.1.1 @@ -658,7 +658,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): continue sig_r = int(signed['sig'][:64], 16) sig_s = int(signed['sig'][64:], 16) - sig = ecc.der_sig_from_r_and_s(sig_r, sig_s) + sig = ecc.ecdsa_der_sig_from_r_and_s(sig_r, sig_s) sig = to_hexstr(sig) + Sighash.to_sigbytes(Sighash.ALL).hex() tx.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_bytes.hex(), sig=sig) except UserCancelled: diff --git a/electrum/plugins/trustedcoin/common_qt.py b/electrum/plugins/trustedcoin/common_qt.py index 0b2679fc0..a29ac6990 100644 --- a/electrum/plugins/trustedcoin/common_qt.py +++ b/electrum/plugins/trustedcoin/common_qt.py @@ -208,7 +208,7 @@ class TrustedcoinPluginQObject(PluginQObject): def f(xprv): rootnode = BIP32Node.from_xkey(xprv) key = rootnode.subkey_at_private_derivation((0, 0)).eckey - sig = key.sign_message(message, True) + sig = key.ecdsa_sign_usermessage(message, is_compressed=True) return base64.b64encode(sig).decode() signatures = [f(x) for x in [xprv1, xprv2]] diff --git a/electrum/storage.py b/electrum/storage.py index d5770424a..f9f9cff44 100644 --- a/electrum/storage.py +++ b/electrum/storage.py @@ -190,7 +190,7 @@ class WalletStorage(Logger): ec_key = self.get_eckey_from_password(password) if self.raw: enc_magic = self._get_encryption_magic() - s = zlib.decompress(ec_key.decrypt_message(self.raw, enc_magic)) + s = zlib.decompress(ec_key.decrypt_message(self.raw, magic=enc_magic)) s = s.decode('utf8') else: s = '' @@ -205,7 +205,7 @@ class WalletStorage(Logger): c = zlib.compress(s, level=zlib.Z_BEST_SPEED) enc_magic = self._get_encryption_magic() public_key = ecc.ECPubkey(bfh(self.pubkey)) - s = public_key.encrypt_message(c, enc_magic) + s = public_key.encrypt_message(c, magic=enc_magic) s = s.decode('utf8') return s diff --git a/electrum/transaction.py b/electrum/transaction.py index 62d1af0b8..fe50a3750 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -2153,7 +2153,7 @@ class PartialTransaction(Transaction): pre_hash = sha256d(bfh(self.serialize_preimage(txin_index, bip143_shared_txdigest_fields=bip143_shared_txdigest_fields))) privkey = ecc.ECPrivkey(privkey_bytes) - sig = privkey.sign_transaction(pre_hash) + sig = privkey.ecdsa_sign(pre_hash, sigencode=ecc.ecdsa_der_sig_from_r_and_s) sig = sig.hex() + Sighash.to_sigbytes(sighash).hex() return sig @@ -2208,16 +2208,16 @@ class PartialTransaction(Transaction): if bfh(sig) in list(txin.part_sigs.values()): continue pre_hash = sha256d(bfh(self.serialize_preimage(i))) - sig_string = ecc.sig_string_from_der_sig(bfh(sig[:-2])) + sig_string = ecc.ecdsa_sig64_from_der_sig(bfh(sig[:-2])) for recid in range(4): try: - public_key = ecc.ECPubkey.from_sig_string(sig_string, recid, pre_hash) + public_key = ecc.ECPubkey.from_ecdsa_sig64(sig_string, recid, pre_hash) except ecc.InvalidECPointException: # the point might not be on the curve for some recid values continue pubkey_hex = public_key.get_public_key_hex(compressed=True) if pubkey_hex in pubkeys: - if not public_key.verify_message_hash(sig_string, pre_hash): + if not public_key.ecdsa_verify(sig_string, pre_hash): continue _logger.info(f"adding sig: txin_idx={i}, signing_pubkey={pubkey_hex}, sig={sig}") self.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_hex, sig=sig) diff --git a/tests/test_bitcoin.py b/tests/test_bitcoin.py index 0a8d29741..88de7c484 100644 --- a/tests/test_bitcoin.py +++ b/tests/test_bitcoin.py @@ -163,7 +163,7 @@ class Test_bitcoin(ElectrumTestCase): for message in [b"Chancellor on brink of second bailout for banks", b'\xff'*512]: self._do_test_crypto(message) - def _do_test_crypto(self, message): + def _do_test_crypto(self, message: bytes): G = ecc.GENERATOR _r = G.order() pvk = randrange(_r) @@ -186,9 +186,9 @@ class Test_bitcoin(ElectrumTestCase): dec2 = eck.decrypt_message(enc) self.assertEqual(message, dec2) - signature = eck.sign_message(message, True) - #print signature - self.assertTrue(eck.verify_message_for_address(signature, message)) + msg32 = sha256d(ecc.usermessage_magic(message)) + sig65 = eck.ecdsa_sign_recoverable(msg32, is_compressed=True) + self.assertTrue(eck.ecdsa_verify_recoverable(sig65, msg32)) def test_ecc_sanity(self): G = ecc.GENERATOR @@ -221,7 +221,7 @@ class Test_bitcoin(ElectrumTestCase): def sign_message_with_wif_privkey(wif_privkey: str, msg: bytes) -> bytes: txin_type, privkey, compressed = deserialize_privkey(wif_privkey) key = ecc.ECPrivkey(privkey) - return key.sign_message(msg, compressed) + return key.ecdsa_sign_usermessage(msg, is_compressed=compressed) def test_signmessage_legacy_address(self): msg1 = b'Chancellor on brink of second bailout for banks' @@ -240,11 +240,11 @@ class Test_bitcoin(ElectrumTestCase): self.assertEqual(sig1_b64, b'Hzsu0U/THAsPz/MSuXGBKSULz2dTfmrg1NsAhFp+wH5aKfmX4Db7ExLGa7FGn0m6Mf43KsbEOWpvUUUBTM3Uusw=') self.assertEqual(sig2_b64, b'HBQdYfv7kOrxmRewLJnG7sV6KlU71O04hUnE4tai97p7Pg+D+yKaWXsdGgHTrKw90caQMo/D6b//qX50ge9P9iI=') - self.assertTrue(ecc.verify_message_with_address(addr1, sig1, msg1)) - self.assertTrue(ecc.verify_message_with_address(addr2, sig2, msg2)) + self.assertTrue(ecc.verify_usermessage_with_address(addr1, sig1, msg1)) + self.assertTrue(ecc.verify_usermessage_with_address(addr2, sig2, msg2)) - self.assertFalse(ecc.verify_message_with_address(addr1, b'wrong', msg1)) - self.assertFalse(ecc.verify_message_with_address(addr1, sig2, msg1)) + self.assertFalse(ecc.verify_usermessage_with_address(addr1, b'wrong', msg1)) + self.assertFalse(ecc.verify_usermessage_with_address(addr1, sig2, msg1)) def test_signmessage_segwit_witness_v0_address(self): msg = b'Electrum' @@ -252,14 +252,14 @@ class Test_bitcoin(ElectrumTestCase): sig1 = self.sign_message_with_wif_privkey("p2wpkh-p2sh:L1cgMEnShp73r9iCukoPE3MogLeueNYRD9JVsfT1zVHyPBR3KqBY", msg) addr1 = "3DYoBqQ5N6dADzyQjy9FT1Ls4amiYVaqTG" self.assertEqual(base64.b64encode(sig1), b'HyFaND+87TtVbRhkTfT3mPNBCQcJ32XXtNZGW8sFldJsNpOPCegEmdcCf5Thy18hdMH88GLxZLkOby/EwVUuSeA=') - self.assertTrue(ecc.verify_message_with_address(addr1, sig1, msg)) - self.assertFalse(ecc.verify_message_with_address(addr1, sig1, b'heyheyhey')) + self.assertTrue(ecc.verify_usermessage_with_address(addr1, sig1, msg)) + self.assertFalse(ecc.verify_usermessage_with_address(addr1, sig1, b'heyheyhey')) # p2wpkh sig2 = self.sign_message_with_wif_privkey("p2wpkh:L1cgMEnShp73r9iCukoPE3MogLeueNYRD9JVsfT1zVHyPBR3KqBY", msg) addr2 = "bc1qq2tmmcngng78nllq2pvrkchcdukemtj56uyue0" self.assertEqual(base64.b64encode(sig2), b'HyFaND+87TtVbRhkTfT3mPNBCQcJ32XXtNZGW8sFldJsNpOPCegEmdcCf5Thy18hdMH88GLxZLkOby/EwVUuSeA=') - self.assertTrue(ecc.verify_message_with_address(addr2, sig2, msg)) - self.assertFalse(ecc.verify_message_with_address(addr2, sig2, b'heyheyhey')) + self.assertTrue(ecc.verify_usermessage_with_address(addr2, sig2, msg)) + self.assertFalse(ecc.verify_usermessage_with_address(addr2, sig2, b'heyheyhey')) def test_signmessage_segwit_witness_v0_address_test_we_also_accept_sigs_from_trezor(self): """Trezor and some other projects use a slightly different scheme for message-signing @@ -272,13 +272,13 @@ class Test_bitcoin(ElectrumTestCase): addr2 = "bc1qannfxke2tfd4l7vhepehpvt05y83v3qsf6nfkk" sig1 = bytes.fromhex("23744de4516fac5c140808015664516a32fead94de89775cec7e24dbc24fe133075ac09301c4cc8e197bea4b6481661d5b8e9bf19d8b7b8a382ecdb53c2ee0750d") sig2 = bytes.fromhex("28b55d7600d9e9a7e2a49155ddf3cfdb8e796c207faab833010fa41fb7828889bc47cf62348a7aaa0923c0832a589fab541e8f12eb54fb711c90e2307f0f66b194") - self.assertTrue(ecc.verify_message_with_address(address=addr1, sig65=sig1, message=msg)) - self.assertTrue(ecc.verify_message_with_address(address=addr2, sig65=sig2, message=msg)) + self.assertTrue(ecc.verify_usermessage_with_address(address=addr1, sig65=sig1, message=msg)) + self.assertTrue(ecc.verify_usermessage_with_address(address=addr2, sig65=sig2, message=msg)) # if there is type information in the header of the sig (first byte), enforce that: sig1_wrongtype = bytes.fromhex("27744de4516fac5c140808015664516a32fead94de89775cec7e24dbc24fe133075ac09301c4cc8e197bea4b6481661d5b8e9bf19d8b7b8a382ecdb53c2ee0750d") sig2_wrongtype = bytes.fromhex("24b55d7600d9e9a7e2a49155ddf3cfdb8e796c207faab833010fa41fb7828889bc47cf62348a7aaa0923c0832a589fab541e8f12eb54fb711c90e2307f0f66b194") - self.assertFalse(ecc.verify_message_with_address(address=addr1, sig65=sig1_wrongtype, message=msg)) - self.assertFalse(ecc.verify_message_with_address(address=addr2, sig65=sig2_wrongtype, message=msg)) + self.assertFalse(ecc.verify_usermessage_with_address(address=addr1, sig65=sig1_wrongtype, message=msg)) + self.assertFalse(ecc.verify_usermessage_with_address(address=addr2, sig65=sig2_wrongtype, message=msg)) @needs_test_with_all_aes_implementations def test_decrypt_message(self): @@ -303,17 +303,20 @@ class Test_bitcoin(ElectrumTestCase): def test_sign_transaction(self): eckey1 = ecc.ECPrivkey(bfh('7e1255fddb52db1729fc3ceb21a46f95b8d9fe94cc83425e936a6c5223bb679d')) - sig1 = eckey1.sign_transaction(bfh('5a548b12369a53faaa7e51b5081829474ebdd9c924b3a8230b69aa0be254cd94')) + sig1 = eckey1.ecdsa_sign(bfh('5a548b12369a53faaa7e51b5081829474ebdd9c924b3a8230b69aa0be254cd94'), + sigencode=ecc.ecdsa_der_sig_from_r_and_s) self.assertEqual('3044022066e7d6a954006cce78a223f5edece8aaedcf3607142e9677acef1cfcb91cfdde022065cb0b5401bf16959ce7b785ea7fd408be5e4cb7d8f1b1a32c78eac6f73678d9', sig1.hex()) eckey2 = ecc.ECPrivkey(bfh('c7ce8c1462c311eec24dff9e2532ac6241e50ae57e7d1833af21942136972f23')) - sig2 = eckey2.sign_transaction(bfh('642a2e66332f507c92bda910158dfe46fc10afbf72218764899d3af99a043fac')) + sig2 = eckey2.ecdsa_sign(bfh('642a2e66332f507c92bda910158dfe46fc10afbf72218764899d3af99a043fac'), + sigencode=ecc.ecdsa_der_sig_from_r_and_s) self.assertEqual('30440220618513f4cfc87dde798ce5febae7634c23e7b9254a1eabf486be820f6a7c2c4702204fef459393a2b931f949e63ced06888f35e286e446dc46feb24b5b5f81c6ed52', sig2.hex()) @disable_ecdsa_r_value_grinding def test_sign_transaction_without_ecdsa_r_value_grinding(self): eckey1 = ecc.ECPrivkey(bfh('7e1255fddb52db1729fc3ceb21a46f95b8d9fe94cc83425e936a6c5223bb679d')) - sig1 = eckey1.sign_transaction(bfh('5a548b12369a53faaa7e51b5081829474ebdd9c924b3a8230b69aa0be254cd94')) + sig1 = eckey1.ecdsa_sign(bfh('5a548b12369a53faaa7e51b5081829474ebdd9c924b3a8230b69aa0be254cd94'), + sigencode=ecc.ecdsa_der_sig_from_r_and_s) self.assertEqual('3045022100902a288b98392254cd23c0e9a49ac6d7920f171b8249a48e484b998f1874a2010220723d844826828f092cf400cb210c4fa0b8cd1b9d1a7f21590e78e022ff6476b9', sig1.hex()) @needs_test_with_all_aes_implementations diff --git a/tests/test_ecc.py b/tests/test_ecc.py index 76b7a0d17..2a555137a 100644 --- a/tests/test_ecc.py +++ b/tests/test_ecc.py @@ -47,19 +47,19 @@ class TestSchnorr(ElectrumTestCase): if seckey: seckey = ECPrivkey(bytes.fromhex(seckey)) aux_rand = bytes.fromhex(aux_rand) - sig_created = seckey.sign_schnorr(msg32, aux_rand32=aux_rand) + sig_created = seckey.schnorr_sign(msg32, aux_rand32=aux_rand) self.assertEqual(signature, sig_created) - is_sig_good = pubkey.verify_message_schnorr(signature, msg32) + is_sig_good = pubkey.schnorr_verify(signature, msg32) expected_res = True if expected_res == "TRUE" else False self.assertEqual(expected_res, is_sig_good) def test_sign_schnorr_aux_rand(self): seckey = ECPrivkey(bytes.fromhex("B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF")) msg32 = sha256("hello there") - sig1 = seckey.sign_schnorr(msg32, aux_rand32=None) - sig2 = seckey.sign_schnorr(msg32, aux_rand32=b"\x00" * 32) + sig1 = seckey.schnorr_sign(msg32, aux_rand32=None) + sig2 = seckey.schnorr_sign(msg32, aux_rand32=b"\x00" * 32) self.assertEqual(sig1, sig2) - sig3 = seckey.sign_schnorr(msg32, aux_rand32=bytes(range(32))) + sig3 = seckey.schnorr_sign(msg32, aux_rand32=bytes(range(32))) self.assertNotEqual(sig1, sig3) def test_y_parity_malleability(self): @@ -79,6 +79,6 @@ class TestSchnorr(ElectrumTestCase): self.assertNotEqual(pubkey1.get_public_key_bytes(True), pubkey2.get_public_key_bytes(True)) self.assertEqual(pubkey1.get_public_key_bytes(True)[1:], pubkey2.get_public_key_bytes(True)[1:]) msg32 = sha256("hello there") - sig = seckey.sign_schnorr(msg32, aux_rand32=None) - self.assertTrue(pubkey1.verify_message_schnorr(sig, msg32)) - self.assertTrue(pubkey2.verify_message_schnorr(sig, msg32)) + sig = seckey.schnorr_sign(msg32, aux_rand32=None) + self.assertTrue(pubkey1.schnorr_verify(sig, msg32)) + self.assertTrue(pubkey2.schnorr_verify(sig, msg32)) From 6bf7542b25fb08cae165930bfabbd08086d83ee5 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 11 Apr 2024 16:37:27 +0000 Subject: [PATCH 5/7] ci: regtests: build own libsecp256k1 instead of using apt - version in apt is too old (cirrus is using ubuntu 22.04 LTS atm) for schnorr module - this way we have better control of exact version to use --- .cirrus.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index 048abc4a7..3eb811935 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -99,7 +99,8 @@ task: populate_script: mkdir -p /tmp/bitcoind install_script: - apt-get update - - apt-get -y install libsecp256k1-dev curl jq bc + - apt-get -y install curl jq bc + # install electrum - pip3 install .[tests] # install e-x some commits after 1.16.0 tag - pip3 install git+https://github.com/spesmilo/electrumx.git@4e66804dc0d668cd6bd4602b547e2f5b2e227e97 @@ -109,6 +110,16 @@ task: - BITCOIND_URL=https://bitcoincore.org/bin/bitcoin-core-$BITCOIND_VERSION/$BITCOIND_FILENAME - tar -xaf $BITCOIND_PATH || (rm -f /tmp/bitcoind/* && curl --output $BITCOIND_PATH $BITCOIND_URL && tar -xaf $BITCOIND_PATH) - cp -a bitcoin-$BITCOIND_VERSION/* /usr/ + libsecp_build_cache: + folder: contrib/_saved_secp256k1_build + fingerprint_script: sha256sum ./contrib/make_libsecp256k1.sh + populate_script: + - apt-get -y install automake libtool + - ./contrib/make_libsecp256k1.sh + - mkdir contrib/_saved_secp256k1_build + - cp electrum/libsecp256k1.so.* contrib/_saved_secp256k1_build/ + libsecp_install_script: + - cp contrib/_saved_secp256k1_build/* electrum/ bitcoind_service_background_script: - tests/regtest/run_bitcoind.sh electrumx_service_background_script: From 52f1a2ce25c09cdf821f790355708556c5a70530 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 12 Apr 2024 14:20:45 +0000 Subject: [PATCH 6/7] ecc: add method "bip340_tagged_hash" I decided to use the stdlib (hashlib) instead of libsecp for this, as it is simple enough, and the former is faster on my PC. Added a unit test that compares the two. --- electrum/ecc.py | 13 ++++++++++++- tests/test_ecc.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/electrum/ecc.py b/electrum/ecc.py index 5e81fa4cf..299f84365 100644 --- a/electrum/ecc.py +++ b/electrum/ecc.py @@ -32,7 +32,7 @@ from ctypes import ( ) from .util import bfh, assert_bytes, to_bytes, InvalidPassword, profiler, randrange -from .crypto import (sha256d, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot) +from .crypto import (sha256, sha256d, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot) from . import constants from .logging import get_logger from .ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED @@ -522,6 +522,12 @@ class ECPrivkey(ECPubkey): return sig def schnorr_sign(self, msg32: bytes, *, aux_rand32: bytes = None) -> bytes: + """Creates a BIP-340 schnorr signature for the given message (hash) + and using the optional auxiliary random data. + + note: msg32 is supposed to be a 32 byte hash of the message to be signed. + The BIP recommends using bip340_tagged_hash for hashing the message. + """ assert isinstance(msg32, bytes), type(msg32) assert len(msg32) == 32, len(msg32) if aux_rand32 is None: @@ -589,3 +595,8 @@ class ECPrivkey(ECPubkey): def construct_ecdsa_sig65(sig64: bytes, recid: int, *, is_compressed: bool) -> bytes: comp = 4 if is_compressed else 0 return bytes([27 + recid + comp]) + sig64 + + +def bip340_tagged_hash(tag: bytes, msg: bytes) -> bytes: + # note: _libsecp256k1.secp256k1_tagged_sha256 benchmarks about 70% slower than this (on my machine) + return sha256(sha256(tag) + sha256(tag) + msg) diff --git a/tests/test_ecc.py b/tests/test_ecc.py index 2a555137a..8197a8449 100644 --- a/tests/test_ecc.py +++ b/tests/test_ecc.py @@ -1,8 +1,13 @@ import csv +from ctypes import ( + c_int, c_char_p, c_size_t, c_void_p, create_string_buffer, +) import io from electrum import ecc from electrum.ecc import ECPubkey, ECPrivkey +from electrum.ecc_fast import _libsecp256k1 +from electrum import crypto from electrum.crypto import sha256 from . import ElectrumTestCase @@ -82,3 +87,32 @@ class TestSchnorr(ElectrumTestCase): sig = seckey.schnorr_sign(msg32, aux_rand32=None) self.assertTrue(pubkey1.schnorr_verify(sig, msg32)) self.assertTrue(pubkey2.schnorr_verify(sig, msg32)) + + def test_bip340_tagged_hash(self): + try: + _libsecp256k1.secp256k1_tagged_sha256.argtypes = [c_void_p, c_char_p, c_char_p, c_size_t, c_char_p, c_size_t] + _libsecp256k1.secp256k1_tagged_sha256.restype = c_int + except (OSError, AttributeError): + raise Exception('libsecp256k1 library too old: missing secp256k1_tagged_sha256 method') + + def bip340_tagged_hash__from_libsecp(tag: bytes, msg: bytes) -> bytes: + assert isinstance(tag, bytes), type(tag) + assert isinstance(msg, bytes), type(msg) + thash = create_string_buffer(32) + ret = _libsecp256k1.secp256k1_tagged_sha256( + _libsecp256k1.ctx, thash, tag, len(tag), msg, len(msg)) + assert 1 == ret, ret + thash = bytes(thash) + return thash + + data = ( + (b"", b""), + (b"", b"hello there"), + (b"mytag", b""), + (b"mytag", b"hello there"), + (bytes(range(256)) * 10, bytes(range(256)) * 50), + (bytes(range(256)) * 1000, bytes(range(256)) * 5000), + ) + for tag, msg in data: + self.assertEqual(bip340_tagged_hash__from_libsecp(tag, msg), + ecc.bip340_tagged_hash(tag, msg)) From fae672c60cbe30448da743abbcfb12731adbceb7 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 12 Apr 2024 16:57:51 +0000 Subject: [PATCH 7/7] ecc: make libsecp256k1 "schnorrsig" module only required when used It would be simple to hard fail at import time if any of the interesting libsecp modules are missing, as it was done before this commit. However, some Linux distros (atm current ubuntu lts, 22.04) lack new enough libsecp. Also, for now, we don't use the schnorr APIs yet anyway. Until we start to rely on them more, it is feasible to only require them when they are used. I am hoping we will be able to revert this commit later though, to keep things simple. --- electrum/ecc.py | 15 ++++++++++++++- electrum/ecc_fast.py | 15 +++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/electrum/ecc.py b/electrum/ecc.py index 299f84365..b28759f76 100644 --- a/electrum/ecc.py +++ b/electrum/ecc.py @@ -35,7 +35,8 @@ from .util import bfh, assert_bytes, to_bytes, InvalidPassword, profiler, randra from .crypto import (sha256, sha256d, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot) from . import constants from .logging import get_logger -from .ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED +from . import ecc_fast +from .ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED, LibModuleMissing _logger = get_logger(__name__) @@ -251,6 +252,10 @@ class ECPubkey(object): def _to_libsecp256k1_xonly_pubkey_ptr(self): """pointer to `secp256k1_xonly_pubkey` C struct""" + if not ecc_fast.HAS_SCHNORR: + raise LibModuleMissing( + 'libsecp256k1 library found but it was built ' + 'without required modules (--enable-module-schnorrsig --enable-module-extrakeys)') pubkey = create_string_buffer(64) pk_bytes = self.get_public_key_bytes(compressed=True)[1:] ret = _libsecp256k1.secp256k1_xonly_pubkey_parse( @@ -359,6 +364,10 @@ class ECPubkey(object): assert len(sig64) == 64, len(sig64) assert isinstance(msg32, bytes), type(msg32) assert len(msg32) == 32, len(msg32) + if not ecc_fast.HAS_SCHNORR: + raise LibModuleMissing( + 'libsecp256k1 library found but it was built ' + 'without required modules (--enable-module-schnorrsig --enable-module-extrakeys)') msglen = 32 pubkey = self._to_libsecp256k1_xonly_pubkey_ptr() if 1 != _libsecp256k1.secp256k1_schnorrsig_verify(_libsecp256k1.ctx, sig64, msg32, msglen, pubkey): @@ -534,6 +543,10 @@ class ECPrivkey(ECPubkey): aux_rand32 = bytes(32) assert isinstance(aux_rand32, bytes), type(aux_rand32) assert len(aux_rand32) == 32, len(aux_rand32) + if not ecc_fast.HAS_SCHNORR: + raise LibModuleMissing( + 'libsecp256k1 library found but it was built ' + 'without required modules (--enable-module-schnorrsig --enable-module-extrakeys)') # construct "keypair" obj privkey_bytes = self.secret_scalar.to_bytes(32, byteorder="big") keypair = create_string_buffer(96) diff --git a/electrum/ecc_fast.py b/electrum/ecc_fast.py index 1730cc263..b4ba9f8fe 100644 --- a/electrum/ecc_fast.py +++ b/electrum/ecc_fast.py @@ -42,6 +42,8 @@ class LibModuleMissing(Exception): pass def load_library(): + global HAS_SCHNORR + # note: for a mapping between bitcoin-core/secp256k1 git tags and .so.V libtool version numbers, # see https://github.com/bitcoin-core/secp256k1/pull/1055#issuecomment-1227505189 tested_libversions = [2, 1, 0, ] # try latest version first @@ -140,8 +142,10 @@ def load_library(): secp256k1.secp256k1_schnorrsig_verify.argtypes = [c_void_p, c_char_p, c_char_p, c_size_t, c_char_p] secp256k1.secp256k1_schnorrsig_verify.restype = c_int except (OSError, AttributeError): - raise LibModuleMissing('libsecp256k1 library found but it was built ' - 'without required module (--enable-module-schnorrsig)') + _logger.warning(f"libsecp256k1 library found but it was built without desired module (--enable-module-schnorrsig)") + HAS_SCHNORR = False + # raise LibModuleMissing('libsecp256k1 library found but it was built ' + # 'without required module (--enable-module-schnorrsig)') # --enable-module-extrakeys try: @@ -154,8 +158,10 @@ def load_library(): secp256k1.secp256k1_keypair_create.argtypes = [c_void_p, c_char_p, c_char_p] secp256k1.secp256k1_keypair_create.restype = c_int except (OSError, AttributeError): - raise LibModuleMissing('libsecp256k1 library found but it was built ' - 'without required module (--enable-module-extrakeys)') + _logger.warning(f"libsecp256k1 library found but it was built without desired module (--enable-module-extrakeys)") + HAS_SCHNORR = False + # raise LibModuleMissing('libsecp256k1 library found but it was built ' + # 'without required module (--enable-module-extrakeys)') secp256k1.ctx = secp256k1.secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY) ret = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32)) @@ -170,6 +176,7 @@ def load_library(): _libsecp256k1 = None +HAS_SCHNORR = True try: _libsecp256k1 = load_library() except BaseException as e: