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))