Browse Source

Merge pull request #9000 from SomberNight/202404_ecc_schnorr

ecc: add bindings for schnorr sign/verify, and refactor
master
ThomasV 2 years ago committed by GitHub
parent
commit
8759928ec0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 13
      .cirrus.yml
  2. 4
      contrib/android/Dockerfile
  3. 2
      contrib/make_libsecp256k1.sh
  4. 5
      electrum/channel_db.py
  5. 2
      electrum/commands.py
  6. 247
      electrum/ecc.py
  7. 39
      electrum/ecc_fast.py
  8. 4
      electrum/gui/qml/qedaemon.py
  9. 2
      electrum/gui/qt/main_window.py
  10. 2
      electrum/gui/qt/update_checker.py
  11. 2
      electrum/keystore.py
  12. 7
      electrum/lnaddr.py
  13. 17
      electrum/lnchannel.py
  14. 20
      electrum/lnpeer.py
  15. 4
      electrum/lnutil.py
  16. 3
      electrum/lnverifier.py
  17. 2
      electrum/lnworker.py
  18. 4
      electrum/paymentrequest.py
  19. 2
      electrum/plugins/bitbox02/bitbox02.py
  20. 2
      electrum/plugins/coldcard/coldcard.py
  21. 18
      electrum/plugins/digitalbitbox/digitalbitbox.py
  22. 2
      electrum/plugins/trustedcoin/common_qt.py
  23. 4
      electrum/storage.py
  24. 8
      electrum/transaction.py
  25. 43
      tests/test_bitcoin.py
  26. 118
      tests/test_ecc.py

13
.cirrus.yml

@ -99,7 +99,8 @@ task:
populate_script: mkdir -p /tmp/bitcoind populate_script: mkdir -p /tmp/bitcoind
install_script: install_script:
- apt-get update - apt-get update
- apt-get -y install libsecp256k1-dev curl jq bc - apt-get -y install curl jq bc
# install electrum
- pip3 install .[tests] - pip3 install .[tests]
# install e-x some commits after 1.16.0 tag # install e-x some commits after 1.16.0 tag
- pip3 install git+https://github.com/spesmilo/electrumx.git@4e66804dc0d668cd6bd4602b547e2f5b2e227e97 - 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 - 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) - tar -xaf $BITCOIND_PATH || (rm -f /tmp/bitcoind/* && curl --output $BITCOIND_PATH $BITCOIND_URL && tar -xaf $BITCOIND_PATH)
- cp -a bitcoin-$BITCOIND_VERSION/* /usr/ - 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: bitcoind_service_background_script:
- tests/regtest/run_bitcoind.sh - tests/regtest/run_bitcoind.sh
electrumx_service_background_script: electrumx_service_background_script:

4
contrib/android/Dockerfile

@ -197,8 +197,8 @@ RUN cd /opt \
&& git remote add sombernight https://github.com/SomberNight/python-for-android \ && git remote add sombernight https://github.com/SomberNight/python-for-android \
&& git remote add accumulator https://github.com/accumulator/python-for-android \ && git remote add accumulator https://github.com/accumulator/python-for-android \
&& git fetch --all \ && git fetch --all \
# commit: from branch accumulator/qt6-wip (note: careful with force-pushing! see #8162) \ # commit: from branch sombernight/qt6-wip (note: careful with force-pushing! see #8162) \
&& git checkout "0c507ead85cc603d0e4a2f5e58e9247a1fae262d^{commit}" \ && git checkout "04e80084ebbb9bcfdbb32e814d4a1f5826ffe4eb^{commit}" \
&& /opt/venv/bin/python3 -m pip install --no-build-isolation --no-dependencies -e . && /opt/venv/bin/python3 -m pip install --no-build-isolation --no-dependencies -e .
# build env vars # build env vars

2
contrib/make_libsecp256k1.sh

@ -52,6 +52,8 @@ info "Building $pkgname..."
$AUTOCONF_FLAGS \ $AUTOCONF_FLAGS \
--prefix="$here/$pkgname/dist" \ --prefix="$here/$pkgname/dist" \
--enable-module-recovery \ --enable-module-recovery \
--enable-module-extrakeys \
--enable-module-schnorrsig \
--enable-experimental \ --enable-experimental \
--enable-module-ecdh \ --enable-module-ecdh \
--disable-benchmark \ --disable-benchmark \

5
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 .lnverifier import LNChannelVerifier, verify_sig_for_channel_update
from .lnmsg import decode_msg from .lnmsg import decode_msg
from . import ecc from . import ecc
from .ecc import ECPubkey
from .crypto import sha256d from .crypto import sha256d
from .lnmsg import FailedToParseMsg 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']] 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']] sigs = [payload['node_signature_1'], payload['node_signature_2'], payload['bitcoin_signature_1'], payload['bitcoin_signature_2']]
for pubkey, sig in zip(pubkeys, sigs): 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') raise InvalidGossipMsg('signature failed')
@classmethod @classmethod
@ -613,7 +614,7 @@ class ChannelDB(SqlDB):
pubkey = payload['node_id'] pubkey = payload['node_id']
signature = payload['signature'] signature = payload['signature']
h = sha256d(payload['raw'][66:]) 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') raise InvalidGossipMsg('signature failed')
def add_node_announcements(self, msg_payloads): def add_node_announcements(self, msg_payloads):

2
electrum/commands.py

@ -713,7 +713,7 @@ class Commands:
"""Verify a signature.""" """Verify a signature."""
sig = base64.b64decode(signature) sig = base64.b64decode(signature)
message = util.to_bytes(message) 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') @command('wp')
async def payto(self, destination, amount, fee=None, feerate=None, from_addr=None, from_coins=None, change_addr=None, async def payto(self, destination, amount, fee=None, feerate=None, from_addr=None, from_coins=None, change_addr=None,

247
electrum/ecc.py

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Electrum - lightweight Bitcoin client # 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 # Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files # obtaining a copy of this software and associated documentation files
@ -28,15 +28,15 @@ import hashlib
import functools import functools
from typing import Union, Tuple, Optional from typing import Union, Tuple, Optional
from ctypes import ( from ctypes import (
byref, c_byte, c_int, c_uint, c_char_p, c_size_t, c_void_p, create_string_buffer, byref, c_char_p, c_size_t, create_string_buffer, cast,
CFUNCTYPE, POINTER, cast
) )
from .util import bfh, assert_bytes, to_bytes, InvalidPassword, profiler, randrange 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 . import constants
from .logging import get_logger 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__) _logger = get_logger(__name__)
@ -50,38 +50,39 @@ def string_to_number(b: bytes) -> int:
return int.from_bytes(b, byteorder='big', signed=False) return int.from_bytes(b, byteorder='big', signed=False)
def sig_string_from_der_sig(der_sig: bytes) -> bytes: def ecdsa_sig64_from_der_sig(der_sig: bytes) -> bytes:
r, s = get_r_and_s_from_der_sig(der_sig) r, s = get_r_and_s_from_ecdsa_der_sig(der_sig)
return sig_string_from_r_and_s(r, s) return ecdsa_sig64_from_r_and_s(r, s)
def der_sig_from_sig_string(sig_string: bytes) -> bytes: def ecdsa_der_sig_from_ecdsa_sig64(sig64: bytes) -> bytes:
r, s = get_r_and_s_from_sig_string(sig_string) r, s = get_r_and_s_from_ecdsa_sig64(sig64)
return der_sig_from_r_and_s(r, s) return ecdsa_der_sig_from_r_and_s(r, s)
def der_sig_from_r_and_s(r: int, s: int) -> bytes: def ecdsa_der_sig_from_r_and_s(r: int, s: int) -> bytes:
sig_string = (int.to_bytes(r, length=32, byteorder="big") + sig64 = (
int.to_bytes(r, length=32, byteorder="big") +
int.to_bytes(s, length=32, byteorder="big")) int.to_bytes(s, length=32, byteorder="big"))
sig = create_string_buffer(64) 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 not ret: if 1 != ret:
raise Exception("Bad signature") raise Exception("Bad signature")
ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
der_sig = create_string_buffer(80) # this much space should be enough der_sig = create_string_buffer(80) # this much space should be enough
der_sig_size = c_size_t(len(der_sig)) 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) 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") raise Exception("failed to serialize DER sig")
der_sig_size = der_sig_size.value der_sig_size = der_sig_size.value
return bytes(der_sig)[:der_sig_size] 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) assert isinstance(der_sig, bytes)
sig = create_string_buffer(64) sig = create_string_buffer(64)
ret = _libsecp256k1.secp256k1_ecdsa_signature_parse_der(_libsecp256k1.ctx, sig, der_sig, len(der_sig)) 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") raise Exception("Bad signature")
ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
compact_signature = create_string_buffer(64) compact_signature = create_string_buffer(64)
@ -91,12 +92,12 @@ def get_r_and_s_from_der_sig(der_sig: bytes) -> Tuple[int, int]:
return r, s return r, s
def get_r_and_s_from_sig_string(sig_string: bytes) -> Tuple[int, int]: def get_r_and_s_from_ecdsa_sig64(sig64: bytes) -> Tuple[int, int]:
if not (isinstance(sig_string, bytes) and len(sig_string) == 64): if not (isinstance(sig64, bytes) and len(sig64) == 64):
raise Exception("sig_string must be bytes, and 64 bytes exactly") raise Exception("sig64 must be bytes, and 64 bytes exactly")
sig = create_string_buffer(64) 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 not ret: if 1 != ret:
raise Exception("Bad signature") raise Exception("Bad signature")
ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
compact_signature = create_string_buffer(64) compact_signature = create_string_buffer(64)
@ -106,12 +107,13 @@ def get_r_and_s_from_sig_string(sig_string: bytes) -> Tuple[int, int]:
return r, s return r, s
def sig_string_from_r_and_s(r: int, s: int) -> bytes: def ecdsa_sig64_from_r_and_s(r: int, s: int) -> bytes:
sig_string = (int.to_bytes(r, length=32, byteorder="big") + sig64 = (
int.to_bytes(r, length=32, byteorder="big") +
int.to_bytes(s, length=32, byteorder="big")) int.to_bytes(s, length=32, byteorder="big"))
sig = create_string_buffer(64) 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 not ret: if 1 != ret:
raise Exception("Bad signature") raise Exception("Bad signature")
ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
compact_signature = create_string_buffer(64) compact_signature = create_string_buffer(64)
@ -124,7 +126,7 @@ def _x_and_y_from_pubkey_bytes(pubkey: bytes) -> Tuple[int, int]:
pubkey_ptr = create_string_buffer(64) pubkey_ptr = create_string_buffer(64)
ret = _libsecp256k1.secp256k1_ec_pubkey_parse( ret = _libsecp256k1.secp256k1_ec_pubkey_parse(
_libsecp256k1.ctx, pubkey_ptr, pubkey, len(pubkey)) _libsecp256k1.ctx, pubkey_ptr, pubkey, len(pubkey))
if not ret: if 1 != ret:
raise InvalidECPointException('public key could not be parsed or is invalid') raise InvalidECPointException('public key could not be parsed or is invalid')
pubkey_serialized = create_string_buffer(65) pubkey_serialized = create_string_buffer(65)
@ -155,28 +157,31 @@ class ECPubkey(object):
self._x, self._y = None, None self._x, self._y = None, None
@classmethod @classmethod
def from_sig_string(cls, sig_string: bytes, recid: int, msg_hash: bytes) -> 'ECPubkey': def from_ecdsa_sig64(cls, sig64: bytes, recid: int, msg32: bytes) -> 'ECPubkey':
assert_bytes(sig_string) assert_bytes(sig64)
if len(sig_string) != 64: if len(sig64) != 64:
raise Exception(f'wrong encoding used for signature? len={len(sig_string)} (should be 64)') raise Exception(f'wrong encoding used for signature? len={len(sig64)} (should be 64)')
if not (0 <= recid <= 3): if not (0 <= recid <= 3):
raise ValueError('recid is {}, but should be 0 <= recid <= 3'.format(recid)) 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) sig65 = create_string_buffer(65)
ret = _libsecp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact( ret = _libsecp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact(
_libsecp256k1.ctx, sig65, sig_string, recid) _libsecp256k1.ctx, sig65, sig64, recid)
if not ret: if 1 != ret:
raise Exception('failed to parse signature') raise Exception('failed to parse signature')
pubkey = create_string_buffer(64) 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 not ret: if 1 != ret:
raise InvalidECPointException('failed to recover public key') raise InvalidECPointException('failed to recover public key')
return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey) return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey)
@classmethod @classmethod
def from_signature65(cls, sig: bytes, msg_hash: bytes) -> Tuple['ECPubkey', bool, Optional[str]]: def from_ecdsa_sig65(cls, sig65: bytes, msg32: bytes) -> Tuple['ECPubkey', bool, Optional[str]]:
if len(sig) != 65: assert_bytes(sig65)
raise Exception(f'wrong encoding used for signature? len={len(sig)} (should be 65)') if len(sig65) != 65:
nV = sig[0] raise Exception(f'wrong encoding used for signature? len={len(sig65)} (should be 65)')
nV = sig65[0]
# as per BIP-0137: # as per BIP-0137:
# 27-30: p2pkh (uncompressed) # 27-30: p2pkh (uncompressed)
# 31-34: p2pkh (compressed) # 31-34: p2pkh (compressed)
@ -199,7 +204,7 @@ class ECPubkey(object):
else: else:
compressed = False compressed = False
recid = nV - 27 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 return pubkey, compressed, txin_type_guess
@classmethod @classmethod
@ -236,11 +241,26 @@ class ECPubkey(object):
return self._y return self._y
def _to_libsecp256k1_pubkey_ptr(self): def _to_libsecp256k1_pubkey_ptr(self):
"""pointer to `secp256k1_pubkey` C struct"""
pubkey = create_string_buffer(64) pubkey = create_string_buffer(64)
public_pair_bytes = self.get_public_key_bytes(compressed=False) public_pair_bytes = self.get_public_key_bytes(compressed=False)
ret = _libsecp256k1.secp256k1_ec_pubkey_parse( ret = _libsecp256k1.secp256k1_ec_pubkey_parse(
_libsecp256k1.ctx, pubkey, public_pair_bytes, len(public_pair_bytes)) _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
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(
_libsecp256k1.ctx, pubkey, pk_bytes)
if 1 != ret:
raise Exception('public key could not be parsed or is invalid') raise Exception('public key could not be parsed or is invalid')
return pubkey return pubkey
@ -267,7 +287,7 @@ class ECPubkey(object):
pubkey = self._to_libsecp256k1_pubkey_ptr() pubkey = self._to_libsecp256k1_pubkey_ptr()
ret = _libsecp256k1.secp256k1_ec_pubkey_tweak_mul(_libsecp256k1.ctx, pubkey, other.to_bytes(32, byteorder="big")) 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 POINT_AT_INFINITY
return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey) return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey)
@ -288,7 +308,7 @@ class ECPubkey(object):
pubkey2 = cast(pubkey2, c_char_p) pubkey2 = cast(pubkey2, c_char_p)
array_of_pubkey_ptrs = (c_char_p * 2)(pubkey1, pubkey2) 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) 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 POINT_AT_INFINITY
return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey_sum) return ECPubkey._from_libsecp256k1_pubkey_ptr(pubkey_sum)
@ -310,38 +330,51 @@ class ECPubkey(object):
p2 = ((other.x() or 0), (other.y() or 0)) p2 = ((other.x() or 0), (other.y() or 0))
return p1 < p2 return p1 < p2
def verify_message_for_address(self, sig65: bytes, message: bytes, algo=lambda x: sha256d(msg_magic(x))) -> bool: def ecdsa_verify_recoverable(self, sig65: bytes, msg32: bytes) -> bool:
assert_bytes(message)
h = algo(message)
try: 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: except Exception:
return False return False
# check public key # check public key
if public_key != self: if public_key != self:
return False return False
# check message # 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: def ecdsa_verify(self, sig64: bytes, msg32: bytes) -> bool:
assert_bytes(sig_string) assert_bytes(sig64)
if len(sig_string) != 64: if len(sig64) != 64:
return False return False
if not (isinstance(msg_hash, bytes) and len(msg_hash) == 32): if not (isinstance(msg32, bytes) and len(msg32) == 32):
return False return False
sig = create_string_buffer(64) 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 not ret: if 1 != ret:
return False return False
ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig) ret = _libsecp256k1.secp256k1_ecdsa_signature_normalize(_libsecp256k1.ctx, sig, sig)
pubkey = self._to_libsecp256k1_pubkey_ptr() 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 False
return True return True
def encrypt_message(self, message: bytes, magic: bytes = b'BIE1') -> bytes: 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)
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):
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 ECIES encryption/decryption methods; AES-128-CBC with PKCS7 is used as the cipher; hmac-sha256 is used as the mac
""" """
@ -380,23 +413,19 @@ CURVE_ORDER = 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D
POINT_AT_INFINITY = ECPubkey(None) POINT_AT_INFINITY = ECPubkey(None)
def msg_magic(message: bytes) -> bytes: def usermessage_magic(message: bytes) -> bytes:
from .bitcoin import var_int from .bitcoin import var_int
length = bfh(var_int(len(message))) length = bfh(var_int(len(message)))
return b"\x18Bitcoin Signed Message:\n" + length + message return b"\x18Bitcoin Signed Message:\n" + length + message
def verify_signature(pubkey: bytes, sig: bytes, h: bytes) -> bool: def verify_usermessage_with_address(address: str, sig65: bytes, message: bytes, *, net=None) -> bool:
return ECPubkey(pubkey).verify_message_hash(sig, h)
def verify_message_with_address(address: str, sig65: bytes, message: bytes, *, net=None) -> bool:
from .bitcoin import pubkey_to_address from .bitcoin import pubkey_to_address
assert_bytes(sig65, message) assert_bytes(sig65, message)
if net is None: net = constants.net if net is None: net = constants.net
h = sha256d(msg_magic(message)) h = sha256d(usermessage_magic(message))
try: 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: except Exception as e:
return False return False
# check public key using the address # check public key using the address
@ -409,7 +438,7 @@ def verify_message_with_address(address: str, sig65: bytes, message: bytes, *, n
else: else:
return False return False
# check message # 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: def is_secret_within_curve_range(secret: Union[int, bytes]) -> bool:
@ -465,20 +494,20 @@ class ECPrivkey(ECPubkey):
def get_secret_bytes(self) -> bytes: def get_secret_bytes(self) -> bytes:
return int.to_bytes(self.secret_scalar, length=32, byteorder='big', signed=False) return int.to_bytes(self.secret_scalar, length=32, byteorder='big', signed=False)
def sign(self, msg_hash: bytes, sigencode=None) -> bytes: def ecdsa_sign(self, msg32: bytes, *, sigencode=None) -> bytes:
if not (isinstance(msg_hash, bytes) and len(msg_hash) == 32): if not (isinstance(msg32, bytes) and len(msg32) == 32):
raise Exception("msg_hash to be signed must be bytes, and 32 bytes exactly") raise Exception("msg32 to be signed must be bytes, and 32 bytes exactly")
if sigencode is None: 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") privkey_bytes = self.secret_scalar.to_bytes(32, byteorder="big")
nonce_function = None nonce_function = None
sig = create_string_buffer(64) sig = create_string_buffer(64)
def sign_with_extra_entropy(extra_entropy): def sign_with_extra_entropy(extra_entropy):
ret = _libsecp256k1.secp256k1_ecdsa_sign( ret = _libsecp256k1.secp256k1_ecdsa_sign(
_libsecp256k1.ctx, sig, msg_hash, privkey_bytes, _libsecp256k1.ctx, sig, msg32, privkey_bytes,
nonce_function, extra_entropy) nonce_function, extra_entropy)
if not ret: if 1 != ret:
raise Exception('the nonce generation function failed, or the private key was invalid') raise Exception('the nonce generation function failed, or the private key was invalid')
compact_signature = create_string_buffer(64) compact_signature = create_string_buffer(64)
_libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig) _libsecp256k1.secp256k1_ecdsa_signature_serialize_compact(_libsecp256k1.ctx, compact_signature, sig)
@ -494,38 +523,67 @@ class ECPrivkey(ECPubkey):
extra_entropy = counter.to_bytes(32, byteorder="little") extra_entropy = counter.to_bytes(32, byteorder="little")
r, s = sign_with_extra_entropy(extra_entropy=extra_entropy) r, s = sign_with_extra_entropy(extra_entropy=extra_entropy)
sig_string = sig_string_from_r_and_s(r, s) sig64 = ecdsa_sig64_from_r_and_s(r, s)
if not self.verify_message_hash(sig_string, msg_hash): if not self.ecdsa_verify(sig64, msg32):
raise Exception("sanity check failed: signature we just created does not verify!") raise Exception("sanity check failed: signature we just created does not verify!")
sig = sigencode(r, s) sig = sigencode(r, s)
return sig return sig
def sign_transaction(self, hashed_preimage: bytes) -> bytes: def schnorr_sign(self, msg32: bytes, *, aux_rand32: bytes = None) -> bytes:
return self.sign(hashed_preimage, sigencode=der_sig_from_r_and_s) """Creates a BIP-340 schnorr signature for the given message (hash)
and using the optional auxiliary random data.
def sign_message( note: msg32 is supposed to be a 32 byte hash of the message to be signed.
self, The BIP recommends using bip340_tagged_hash for hashing the message.
message: Union[bytes, str], """
is_compressed: bool, assert isinstance(msg32, bytes), type(msg32)
algo=lambda x: sha256d(msg_magic(x)), assert len(msg32) == 32, len(msg32)
) -> bytes: if aux_rand32 is None:
def bruteforce_recid(sig_string): 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)
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.schnorr_verify(sig64, msg32):
raise Exception("sanity check failed: signature we just created does not verify!")
return sig64
def ecdsa_sign_recoverable(self, msg32: bytes, *, is_compressed: bool) -> bytes:
def bruteforce_recid(sig64: bytes):
for recid in range(4): for recid in range(4):
sig65 = construct_sig65(sig_string, recid, is_compressed) sig65 = construct_ecdsa_sig65(sig64, recid, is_compressed=is_compressed)
if not self.verify_message_for_address(sig65, message, algo): if not self.ecdsa_verify_recoverable(sig65, msg32):
continue continue
return sig65, recid return sig65, recid
else: else:
raise Exception("error: cannot sign message. no recid fits..") raise Exception("error: cannot sign message. no recid fits..")
message = to_bytes(message, 'utf8') sig64 = self.ecdsa_sign(msg32, sigencode=ecdsa_sig64_from_r_and_s)
msg_hash = algo(message) sig65, recid = bruteforce_recid(sig64)
sig_string = self.sign(msg_hash, sigencode=sig_string_from_r_and_s)
sig65, recid = bruteforce_recid(sig_string)
return sig65 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 encrypted = base64.b64decode(encrypted) # type: bytes
if len(encrypted) < 85: if len(encrypted) < 85:
raise Exception('invalid ciphertext: length') raise Exception('invalid ciphertext: length')
@ -547,6 +605,11 @@ class ECPrivkey(ECPubkey):
return aes_decrypt_with_iv(key_e, iv, ciphertext) 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 comp = 4 if is_compressed else 0
return bytes([27 + recid + comp]) + sig_string 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)

39
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 # https://github.com/richardkiss/pycoin/blob/01b1787ed902df23f99a55deb00d8cd076a906fe/pycoin/ecdsa/native/secp256k1.py
import os import os
@ -37,6 +42,8 @@ class LibModuleMissing(Exception): pass
def load_library(): def load_library():
global HAS_SCHNORR
# note: for a mapping between bitcoin-core/secp256k1 git tags and .so.V libtool version numbers, # 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 # see https://github.com/bitcoin-core/secp256k1/pull/1055#issuecomment-1227505189
tested_libversions = [2, 1, 0, ] # try latest version first tested_libversions = [2, 1, 0, ] # try latest version first
@ -127,6 +134,35 @@ def load_library():
raise LibModuleMissing('libsecp256k1 library found but it was built ' raise LibModuleMissing('libsecp256k1 library found but it was built '
'without required module (--enable-module-recovery)') '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):
_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:
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):
_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) secp256k1.ctx = secp256k1.secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY)
ret = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32)) ret = secp256k1.secp256k1_context_randomize(secp256k1.ctx, os.urandom(32))
if not ret: if not ret:
@ -140,6 +176,7 @@ def load_library():
_libsecp256k1 = None _libsecp256k1 = None
HAS_SCHNORR = True
try: try:
_libsecp256k1 = load_library() _libsecp256k1 = load_library()
except BaseException as e: except BaseException as e:

4
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.plugin import run_hook
from electrum.lnchannel import ChannelState from electrum.lnchannel import ChannelState
from electrum.bitcoin import is_address 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 electrum.storage import StorageReadWriteError
from .auth import AuthMixin, auth_protect from .auth import AuthMixin, auth_protect
@ -372,7 +372,7 @@ class QEDaemon(AuthMixin, QObject):
try: try:
# This can throw on invalid base64 # This can throw on invalid base64
sig = base64.b64decode(str(signature.strip())) 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: except Exception as e:
verified = False verified = False
return verified return verified

2
electrum/gui/qt/main_window.py

@ -1982,7 +1982,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
try: try:
# This can throw on invalid base64 # This can throw on invalid base64
sig = base64.b64decode(str(signature.toPlainText())) 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: except Exception as e:
verified = False verified = False
if verified: if verified:

2
electrum/gui/qt/update_checker.py

@ -123,7 +123,7 @@ class UpdateCheckThread(QThread, Logger):
continue continue
sig = base64.b64decode(sig) sig = base64.b64decode(sig)
msg = version_num.encode('utf-8') msg = version_num.encode('utf-8')
if ecc.verify_message_with_address(address=address, sig65=sig, message=msg, if ecc.verify_usermessage_with_address(address=address, sig65=sig, message=msg,
net=constants.BitcoinMainnet): net=constants.BitcoinMainnet):
self.logger.info(f"valid sig for version announcement '{version_num}' from address '{address}'") self.logger.info(f"valid sig for version announcement '{version_num}' from address '{address}'")
break break

2
electrum/keystore.py

@ -223,7 +223,7 @@ class Software_KeyStore(KeyStore):
def sign_message(self, sequence, message, password, *, script_type=None) -> bytes: def sign_message(self, sequence, message, password, *, script_type=None) -> bytes:
privkey, compressed = self.get_private_key(sequence, password) privkey, compressed = self.get_private_key(sequence, password)
key = ecc.ECPrivkey(privkey) 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: def decrypt_message(self, sequence, message, password) -> bytes:
privkey, compressed = self.get_private_key(sequence, password) privkey, compressed = self.get_private_key(sequence, password)

7
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). # We actually sign the hrp, then data (padded to 8 bits with zeroes).
msg = hrp.encode("ascii") + data.tobytes() msg = hrp.encode("ascii") + data.tobytes()
msg32 = sha256(msg).digest()
privkey = ecc.ECPrivkey(privkey) 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]) recovery_flag = bytes([sig[0] - 27])
sig = bytes(sig[1:]) + recovery_flag sig = bytes(sig[1:]) + recovery_flag
data += sig 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 # A reader MUST use the `n` field to validate the signature instead of
# performing signature recovery if a valid `n` field is provided. # 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") raise LnDecodeException("bad signature")
pubkey_copy = addr.pubkey pubkey_copy = addr.pubkey
class WrappedBytesKey: class WrappedBytesKey:
serialize = lambda: pubkey_copy serialize = lambda: pubkey_copy
addr.pubkey = WrappedBytesKey addr.pubkey = WrappedBytesKey
else: # Recover pubkey from signature. 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 return addr

17
electrum/lnchannel.py

@ -34,6 +34,7 @@ from aiorpcx import NetAddress
import attr import attr
from . import ecc from . import ecc
from .ecc import ECPubkey
from . import constants, util from . import constants, util
from .util import bfh, chunks, TxMinedInfo from .util import bfh, chunks, TxMinedInfo
from .invoices import PR_PAID from .invoices import PR_PAID
@ -802,7 +803,7 @@ class Channel(AbstractChannel):
timestamp=now(), timestamp=now(),
) )
sighash = sha256d(chan_upd[2 + 64:]) 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) message_type, payload = decode_msg(chan_upd)
payload['signature'] = sig payload['signature'] = sig
chan_upd = encode_msg(message_type, **payload) chan_upd = encode_msg(message_type, **payload)
@ -1099,7 +1100,7 @@ class Channel(AbstractChannel):
ctx_output_idx=ctx_output_idx, ctx_output_idx=ctx_output_idx,
htlc=htlc) htlc=htlc)
sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey)) 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.append((ctx_output_idx, htlc_sig))
htlcsigs.sort() htlcsigs.sort()
htlcsigs = [x[1] for x in htlcsigs] htlcsigs = [x[1] for x in htlcsigs]
@ -1122,7 +1123,7 @@ class Channel(AbstractChannel):
pending_local_commitment = self.get_next_commitment(LOCAL) pending_local_commitment = self.get_next_commitment(LOCAL)
preimage_hex = pending_local_commitment.serialize_preimage(0) preimage_hex = pending_local_commitment.serialize_preimage(0)
pre_hash = sha256d(bfh(preimage_hex)) 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( raise LNProtocolWarning(
f'failed verifying signature for our updated commitment transaction. ' f'failed verifying signature for our updated commitment transaction. '
f'sig={sig.hex()}. ' f'sig={sig.hex()}. '
@ -1169,7 +1170,7 @@ class Channel(AbstractChannel):
preimage_hex = htlc_tx.serialize_preimage(0) preimage_hex = htlc_tx.serialize_preimage(0)
pre_hash = sha256d(bfh(preimage_hex)) pre_hash = sha256d(bfh(preimage_hex))
remote_htlc_pubkey = derive_pubkey(self.config[REMOTE].htlc_basepoint.pubkey, pcp) 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( raise LNProtocolWarning(
f'failed verifying HTLC signatures: {htlc=}, {htlc_direction=}. ' f'failed verifying HTLC signatures: {htlc=}, {htlc_direction=}. '
f'htlc_tx={htlc_tx.serialize()}. ' f'htlc_tx={htlc_tx.serialize()}. '
@ -1185,7 +1186,7 @@ class Channel(AbstractChannel):
data = self.config[LOCAL].current_htlc_signatures data = self.config[LOCAL].current_htlc_signatures
htlc_sigs = list(chunks(data, 64)) htlc_sigs = list(chunks(data, 64))
htlc_sig = htlc_sigs[htlc_relative_idx] 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 return remote_htlc_sig
def revoke_current_commitment(self): def revoke_current_commitment(self):
@ -1599,7 +1600,7 @@ class Channel(AbstractChannel):
outputs=outputs) outputs=outputs)
der_sig = bfh(closing_tx.sign_txin(0, self.config[LOCAL].multisig_key.privkey)) 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 return sig, closing_tx
def signature_fits(self, tx: PartialTransaction) -> bool: def signature_fits(self, tx: PartialTransaction) -> bool:
@ -1607,7 +1608,7 @@ class Channel(AbstractChannel):
preimage_hex = tx.serialize_preimage(0) preimage_hex = tx.serialize_preimage(0)
msg_hash = sha256d(bfh(preimage_hex)) msg_hash = sha256d(bfh(preimage_hex))
assert remote_sig 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 return res
def force_close_tx(self) -> PartialTransaction: def force_close_tx(self) -> PartialTransaction:
@ -1615,7 +1616,7 @@ class Channel(AbstractChannel):
assert self.signature_fits(tx) assert self.signature_fits(tx)
tx.sign({self.config[LOCAL].multisig_key.pubkey.hex(): (self.config[LOCAL].multisig_key.privkey, True)}) 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 = 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, tx.add_signature_to_txin(txin_idx=0,
signing_pubkey=self.config[REMOTE].multisig_key.pubkey.hex(), signing_pubkey=self.config[REMOTE].multisig_key.pubkey.hex(),
sig=remote_sig.hex()) sig=remote_sig.hex())

20
electrum/lnpeer.py

@ -19,7 +19,7 @@ from aiorpcx import ignore_after
from .crypto import sha256, sha256d from .crypto import sha256, sha256d
from . import bitcoin, util from . import bitcoin, util
from . import ecc 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 . import constants
from .util import (bfh, log_exceptions, ignore_exceptions, chunks, OldTaskGroup, from .util import (bfh, log_exceptions, ignore_exceptions, chunks, OldTaskGroup,
UnrelatedTransactionException, error_text_bytes_to_safe_str) UnrelatedTransactionException, error_text_bytes_to_safe_str)
@ -424,9 +424,9 @@ class Peer(Logger):
h = chan.get_channel_announcement_hash() h = chan.get_channel_announcement_hash()
node_signature = payload["node_signature"] node_signature = payload["node_signature"]
bitcoin_signature = payload["bitcoin_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") 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") raise Exception("node_sig invalid in announcement_signatures")
chan.config[REMOTE].announcement_node_sig = node_signature chan.config[REMOTE].announcement_node_sig = node_signature
chan.config[REMOTE].announcement_bitcoin_sig = bitcoin_signature chan.config[REMOTE].announcement_bitcoin_sig = bitcoin_signature
@ -1453,7 +1453,7 @@ class Peer(Logger):
addrlen=len(addresses), addrlen=len(addresses),
addresses=addresses) addresses=addresses)
h = sha256d(raw_msg[64+2:]) 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) message_type, payload = decode_msg(raw_msg)
payload['signature'] = signature payload['signature'] = signature
raw_msg = encode_msg(message_type, **payload) 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: if not is_reply and chan.config[REMOTE].announcement_node_sig:
return return
h = chan.get_channel_announcement_hash() h = chan.get_channel_announcement_hash()
bitcoin_signature = ecc.ECPrivkey(chan.config[LOCAL].multisig_key.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).sign(h, sig_string_from_r_and_s) node_signature = ecc.ECPrivkey(self.privkey).ecdsa_sign(h, sigencode=ecdsa_sig64_from_r_and_s)
self.send_message( self.send_message(
"announcement_signatures", "announcement_signatures",
channel_id=chan.channel_id, channel_id=chan.channel_id,
@ -2446,11 +2446,11 @@ class Peer(Logger):
closing_signed_tlvs=closing_signed_tlvs, 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 their_pubkey = chan.config[REMOTE].multisig_key.pubkey
preimage_hex = tx.serialize_preimage(0) preimage_hex = tx.serialize_preimage(0)
pre_hash = sha256d(bfh(preimage_hex)) 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(): async def receive_closing_signed():
nonlocal our_sig, closing_tx nonlocal our_sig, closing_tx
@ -2571,11 +2571,11 @@ class Peer(Logger):
closing_tx.add_signature_to_txin( closing_tx.add_signature_to_txin(
txin_idx=0, txin_idx=0,
signing_pubkey=chan.config[LOCAL].multisig_key.pubkey.hex(), 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( closing_tx.add_signature_to_txin(
txin_idx=0, txin_idx=0,
signing_pubkey=chan.config[REMOTE].multisig_key.pubkey.hex(), 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 # save local transaction and set state
try: try:
self.lnworker.wallet.adb.add_transaction(closing_tx) self.lnworker.wallet.adb.add_transaction(closing_tx)

4
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 .crypto import sha256, pw_decode_with_version_and_mac
from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOutpoint, from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOutpoint,
PartialTxOutput, opcodes, TxOutput) 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 ecc, bitcoin, crypto, transaction
from . import descriptor from . import descriptor
from .bitcoin import (push_script, redeem_script_to_address, address_to_script, 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): 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)}) 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 = 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 return sig_64
def funding_output_script(local_config, remote_config) -> str: def funding_output_script(local_config, remote_config) -> str:

3
electrum/lnverifier.py

@ -31,6 +31,7 @@ import aiorpcx
from . import bitcoin from . import bitcoin
from . import ecc from . import ecc
from .ecc import ECPubkey
from . import constants from . import constants
from .util import bfh, NetworkJobOnDefaultServer from .util import bfh, NetworkJobOnDefaultServer
from .lnutil import funding_output_script_from_keys, ShortChannelID 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:] pre_hash = msg_bytes[2+64:]
h = sha256d(pre_hash) h = sha256d(pre_hash)
sig = chan_upd['signature'] 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 False
return True return True

2
electrum/lnworker.py

@ -51,7 +51,7 @@ from .logging import Logger
from .lntransport import LNTransport, LNResponderTransport, LNTransportBase from .lntransport import LNTransport, LNResponderTransport, LNTransportBase
from .lnpeer import Peer, LN_P2P_NETWORK_TIMEOUT from .lnpeer import Peer, LN_P2P_NETWORK_TIMEOUT
from .lnaddr import lnencode, LnAddr, lndecode 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 Channel, AbstractChannel
from .lnchannel import ChannelState, PeerState, HTLCWithStatus from .lnchannel import ChannelState, PeerState, HTLCWithStatus
from .lnrater import LNRater from .lnrater import LNRater

4
electrum/paymentrequest.py

@ -235,7 +235,7 @@ class PaymentRequest:
address = info.get('address') address = info.get('address')
pr.signature = b'' pr.signature = b''
message = pr.SerializeToString() 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_msg = 'Verified with DNSSEC'
self._verified_success = True self._verified_success = True
return True return True
@ -356,7 +356,7 @@ def sign_request_with_alias(pr, alias, alias_privkey):
message = pr.SerializeToString() message = pr.SerializeToString()
ec_key = ecc.ECPrivkey(alias_privkey) ec_key = ecc.ECPrivkey(alias_privkey)
compressed = bitcoin.is_compressed_privkey(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): def verify_cert_chain(chain):

2
electrum/plugins/bitbox02/bitbox02.py

@ -537,7 +537,7 @@ class BitBox02Client(HardwareClientBase):
if len(sigs) != len(tx.inputs()): if len(sigs) != len(tx.inputs()):
raise Exception("Incorrect number of inputs signed.") # Should never occur raise Exception("Incorrect number of inputs signed.") # Should never occur
sighash = Sighash.to_sigbytes(Sighash.ALL).hex() 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) tx.update_signatures(signatures)
def sign_message(self, keypath: str, message: bytes, script_type: str) -> bytes: def sign_message(self, keypath: str, message: bytes, script_type: str) -> bytes:

2
electrum/plugins/coldcard/coldcard.py

@ -45,7 +45,7 @@ try:
# verify a signature (65 bytes) over the session key, using the master bip32 node # verify a signature (65 bytes) over the session key, using the master bip32 node
# - customized to use specific EC library of Electrum. # - customized to use specific EC library of Electrum.
pubkey = BIP32Node.from_xkey(expect_xpub).eckey 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: except ImportError as e:
if not (isinstance(e, ModuleNotFoundError) and e.name == 'ckcc'): if not (isinstance(e, ModuleNotFoundError) and e.name == 'ckcc'):

18
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.bip32 import normalize_bip32_derivation
from electrum import descriptor from electrum import descriptor
from electrum import ecc from electrum import ecc
from electrum.ecc import msg_magic from electrum.ecc import usermessage_magic
from electrum.wallet import Standard_Wallet from electrum.wallet import Standard_Wallet
from electrum import constants from electrum import constants
from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, Sighash from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, Sighash
@ -470,7 +470,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
message = message.encode('utf8') message = message.encode('utf8')
inputPath = self.get_derivation_prefix() + "/%d/%d" % sequence inputPath = self.get_derivation_prefix() + "/%d/%d" % sequence
inputPath = normalize_bip32_derivation(inputPath, hardened_char="'") inputPath = normalize_bip32_derivation(inputPath, hardened_char="'")
msg_hash = sha256d(msg_magic(message)) msg_hash = sha256d(usermessage_magic(message))
inputHash = to_hexstr(msg_hash) inputHash = to_hexstr(msg_hash)
hasharray = [] hasharray = []
hasharray.append({'hash': inputHash, 'keypath': inputPath}) hasharray.append({'hash': inputHash, 'keypath': inputPath})
@ -500,19 +500,19 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
# firmware > v2.1.1 # firmware > v2.1.1
sig_string = binascii.unhexlify(reply['sign'][0]['sig']) sig_string = binascii.unhexlify(reply['sign'][0]['sig'])
recid = int(reply['sign'][0]['recid'], 16) recid = int(reply['sign'][0]['recid'], 16)
sig = ecc.construct_sig65(sig_string, recid, True) sig = ecc.construct_ecdsa_sig65(sig_string, recid, is_compressed=True)
pubkey, compressed, txin_type_guess = ecc.ECPubkey.from_signature65(sig, msg_hash) 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)) 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")) raise Exception(_("Could not sign message"))
elif 'pubkey' in reply['sign'][0]: elif 'pubkey' in reply['sign'][0]:
# firmware <= v2.1.1 # firmware <= v2.1.1
for recid in range(4): for recid in range(4):
sig_string = binascii.unhexlify(reply['sign'][0]['sig']) 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: try:
addr = public_key_to_p2pkh(binascii.unhexlify(reply['sign'][0]['pubkey'])) 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 break
except Exception: except Exception:
continue continue
@ -649,7 +649,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
recid = int(signed['recid'], 16) recid = int(signed['recid'], 16)
s = binascii.unhexlify(signed['sig']) s = binascii.unhexlify(signed['sig'])
h = inputhasharray[i] 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) pk = pk.get_public_key_hex(compressed=True)
elif 'pubkey' in signed: elif 'pubkey' in signed:
# firmware <= v2.1.1 # firmware <= v2.1.1
@ -658,7 +658,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
continue continue
sig_r = int(signed['sig'][:64], 16) sig_r = int(signed['sig'][:64], 16)
sig_s = 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() 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) tx.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_bytes.hex(), sig=sig)
except UserCancelled: except UserCancelled:

2
electrum/plugins/trustedcoin/common_qt.py

@ -208,7 +208,7 @@ class TrustedcoinPluginQObject(PluginQObject):
def f(xprv): def f(xprv):
rootnode = BIP32Node.from_xkey(xprv) rootnode = BIP32Node.from_xkey(xprv)
key = rootnode.subkey_at_private_derivation((0, 0)).eckey 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() return base64.b64encode(sig).decode()
signatures = [f(x) for x in [xprv1, xprv2]] signatures = [f(x) for x in [xprv1, xprv2]]

4
electrum/storage.py

@ -190,7 +190,7 @@ class WalletStorage(Logger):
ec_key = self.get_eckey_from_password(password) ec_key = self.get_eckey_from_password(password)
if self.raw: if self.raw:
enc_magic = self._get_encryption_magic() 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') s = s.decode('utf8')
else: else:
s = '' s = ''
@ -205,7 +205,7 @@ class WalletStorage(Logger):
c = zlib.compress(s, level=zlib.Z_BEST_SPEED) c = zlib.compress(s, level=zlib.Z_BEST_SPEED)
enc_magic = self._get_encryption_magic() enc_magic = self._get_encryption_magic()
public_key = ecc.ECPubkey(bfh(self.pubkey)) 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') s = s.decode('utf8')
return s return s

8
electrum/transaction.py

@ -2153,7 +2153,7 @@ class PartialTransaction(Transaction):
pre_hash = sha256d(bfh(self.serialize_preimage(txin_index, pre_hash = sha256d(bfh(self.serialize_preimage(txin_index,
bip143_shared_txdigest_fields=bip143_shared_txdigest_fields))) bip143_shared_txdigest_fields=bip143_shared_txdigest_fields)))
privkey = ecc.ECPrivkey(privkey_bytes) 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() sig = sig.hex() + Sighash.to_sigbytes(sighash).hex()
return sig return sig
@ -2208,16 +2208,16 @@ class PartialTransaction(Transaction):
if bfh(sig) in list(txin.part_sigs.values()): if bfh(sig) in list(txin.part_sigs.values()):
continue continue
pre_hash = sha256d(bfh(self.serialize_preimage(i))) 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): for recid in range(4):
try: 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: except ecc.InvalidECPointException:
# the point might not be on the curve for some recid values # the point might not be on the curve for some recid values
continue continue
pubkey_hex = public_key.get_public_key_hex(compressed=True) pubkey_hex = public_key.get_public_key_hex(compressed=True)
if pubkey_hex in pubkeys: 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 continue
_logger.info(f"adding sig: txin_idx={i}, signing_pubkey={pubkey_hex}, sig={sig}") _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) self.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_hex, sig=sig)

43
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]: for message in [b"Chancellor on brink of second bailout for banks", b'\xff'*512]:
self._do_test_crypto(message) self._do_test_crypto(message)
def _do_test_crypto(self, message): def _do_test_crypto(self, message: bytes):
G = ecc.GENERATOR G = ecc.GENERATOR
_r = G.order() _r = G.order()
pvk = randrange(_r) pvk = randrange(_r)
@ -186,9 +186,9 @@ class Test_bitcoin(ElectrumTestCase):
dec2 = eck.decrypt_message(enc) dec2 = eck.decrypt_message(enc)
self.assertEqual(message, dec2) self.assertEqual(message, dec2)
signature = eck.sign_message(message, True) msg32 = sha256d(ecc.usermessage_magic(message))
#print signature sig65 = eck.ecdsa_sign_recoverable(msg32, is_compressed=True)
self.assertTrue(eck.verify_message_for_address(signature, message)) self.assertTrue(eck.ecdsa_verify_recoverable(sig65, msg32))
def test_ecc_sanity(self): def test_ecc_sanity(self):
G = ecc.GENERATOR G = ecc.GENERATOR
@ -221,7 +221,7 @@ class Test_bitcoin(ElectrumTestCase):
def sign_message_with_wif_privkey(wif_privkey: str, msg: bytes) -> bytes: def sign_message_with_wif_privkey(wif_privkey: str, msg: bytes) -> bytes:
txin_type, privkey, compressed = deserialize_privkey(wif_privkey) txin_type, privkey, compressed = deserialize_privkey(wif_privkey)
key = ecc.ECPrivkey(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): def test_signmessage_legacy_address(self):
msg1 = b'Chancellor on brink of second bailout for banks' 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(sig1_b64, b'Hzsu0U/THAsPz/MSuXGBKSULz2dTfmrg1NsAhFp+wH5aKfmX4Db7ExLGa7FGn0m6Mf43KsbEOWpvUUUBTM3Uusw=')
self.assertEqual(sig2_b64, b'HBQdYfv7kOrxmRewLJnG7sV6KlU71O04hUnE4tai97p7Pg+D+yKaWXsdGgHTrKw90caQMo/D6b//qX50ge9P9iI=') self.assertEqual(sig2_b64, b'HBQdYfv7kOrxmRewLJnG7sV6KlU71O04hUnE4tai97p7Pg+D+yKaWXsdGgHTrKw90caQMo/D6b//qX50ge9P9iI=')
self.assertTrue(ecc.verify_message_with_address(addr1, sig1, msg1)) self.assertTrue(ecc.verify_usermessage_with_address(addr1, sig1, msg1))
self.assertTrue(ecc.verify_message_with_address(addr2, sig2, msg2)) 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_usermessage_with_address(addr1, b'wrong', msg1))
self.assertFalse(ecc.verify_message_with_address(addr1, sig2, msg1)) self.assertFalse(ecc.verify_usermessage_with_address(addr1, sig2, msg1))
def test_signmessage_segwit_witness_v0_address(self): def test_signmessage_segwit_witness_v0_address(self):
msg = b'Electrum' msg = b'Electrum'
@ -252,14 +252,14 @@ class Test_bitcoin(ElectrumTestCase):
sig1 = self.sign_message_with_wif_privkey("p2wpkh-p2sh:L1cgMEnShp73r9iCukoPE3MogLeueNYRD9JVsfT1zVHyPBR3KqBY", msg) sig1 = self.sign_message_with_wif_privkey("p2wpkh-p2sh:L1cgMEnShp73r9iCukoPE3MogLeueNYRD9JVsfT1zVHyPBR3KqBY", msg)
addr1 = "3DYoBqQ5N6dADzyQjy9FT1Ls4amiYVaqTG" addr1 = "3DYoBqQ5N6dADzyQjy9FT1Ls4amiYVaqTG"
self.assertEqual(base64.b64encode(sig1), b'HyFaND+87TtVbRhkTfT3mPNBCQcJ32XXtNZGW8sFldJsNpOPCegEmdcCf5Thy18hdMH88GLxZLkOby/EwVUuSeA=') self.assertEqual(base64.b64encode(sig1), b'HyFaND+87TtVbRhkTfT3mPNBCQcJ32XXtNZGW8sFldJsNpOPCegEmdcCf5Thy18hdMH88GLxZLkOby/EwVUuSeA=')
self.assertTrue(ecc.verify_message_with_address(addr1, sig1, msg)) self.assertTrue(ecc.verify_usermessage_with_address(addr1, sig1, msg))
self.assertFalse(ecc.verify_message_with_address(addr1, sig1, b'heyheyhey')) self.assertFalse(ecc.verify_usermessage_with_address(addr1, sig1, b'heyheyhey'))
# p2wpkh # p2wpkh
sig2 = self.sign_message_with_wif_privkey("p2wpkh:L1cgMEnShp73r9iCukoPE3MogLeueNYRD9JVsfT1zVHyPBR3KqBY", msg) sig2 = self.sign_message_with_wif_privkey("p2wpkh:L1cgMEnShp73r9iCukoPE3MogLeueNYRD9JVsfT1zVHyPBR3KqBY", msg)
addr2 = "bc1qq2tmmcngng78nllq2pvrkchcdukemtj56uyue0" addr2 = "bc1qq2tmmcngng78nllq2pvrkchcdukemtj56uyue0"
self.assertEqual(base64.b64encode(sig2), b'HyFaND+87TtVbRhkTfT3mPNBCQcJ32XXtNZGW8sFldJsNpOPCegEmdcCf5Thy18hdMH88GLxZLkOby/EwVUuSeA=') self.assertEqual(base64.b64encode(sig2), b'HyFaND+87TtVbRhkTfT3mPNBCQcJ32XXtNZGW8sFldJsNpOPCegEmdcCf5Thy18hdMH88GLxZLkOby/EwVUuSeA=')
self.assertTrue(ecc.verify_message_with_address(addr2, sig2, msg)) self.assertTrue(ecc.verify_usermessage_with_address(addr2, sig2, msg))
self.assertFalse(ecc.verify_message_with_address(addr2, sig2, b'heyheyhey')) 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): 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 """Trezor and some other projects use a slightly different scheme for message-signing
@ -272,13 +272,13 @@ class Test_bitcoin(ElectrumTestCase):
addr2 = "bc1qannfxke2tfd4l7vhepehpvt05y83v3qsf6nfkk" addr2 = "bc1qannfxke2tfd4l7vhepehpvt05y83v3qsf6nfkk"
sig1 = bytes.fromhex("23744de4516fac5c140808015664516a32fead94de89775cec7e24dbc24fe133075ac09301c4cc8e197bea4b6481661d5b8e9bf19d8b7b8a382ecdb53c2ee0750d") sig1 = bytes.fromhex("23744de4516fac5c140808015664516a32fead94de89775cec7e24dbc24fe133075ac09301c4cc8e197bea4b6481661d5b8e9bf19d8b7b8a382ecdb53c2ee0750d")
sig2 = bytes.fromhex("28b55d7600d9e9a7e2a49155ddf3cfdb8e796c207faab833010fa41fb7828889bc47cf62348a7aaa0923c0832a589fab541e8f12eb54fb711c90e2307f0f66b194") sig2 = bytes.fromhex("28b55d7600d9e9a7e2a49155ddf3cfdb8e796c207faab833010fa41fb7828889bc47cf62348a7aaa0923c0832a589fab541e8f12eb54fb711c90e2307f0f66b194")
self.assertTrue(ecc.verify_message_with_address(address=addr1, sig65=sig1, message=msg)) self.assertTrue(ecc.verify_usermessage_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=addr2, sig65=sig2, message=msg))
# if there is type information in the header of the sig (first byte), enforce that: # if there is type information in the header of the sig (first byte), enforce that:
sig1_wrongtype = bytes.fromhex("27744de4516fac5c140808015664516a32fead94de89775cec7e24dbc24fe133075ac09301c4cc8e197bea4b6481661d5b8e9bf19d8b7b8a382ecdb53c2ee0750d") sig1_wrongtype = bytes.fromhex("27744de4516fac5c140808015664516a32fead94de89775cec7e24dbc24fe133075ac09301c4cc8e197bea4b6481661d5b8e9bf19d8b7b8a382ecdb53c2ee0750d")
sig2_wrongtype = bytes.fromhex("24b55d7600d9e9a7e2a49155ddf3cfdb8e796c207faab833010fa41fb7828889bc47cf62348a7aaa0923c0832a589fab541e8f12eb54fb711c90e2307f0f66b194") sig2_wrongtype = bytes.fromhex("24b55d7600d9e9a7e2a49155ddf3cfdb8e796c207faab833010fa41fb7828889bc47cf62348a7aaa0923c0832a589fab541e8f12eb54fb711c90e2307f0f66b194")
self.assertFalse(ecc.verify_message_with_address(address=addr1, sig65=sig1_wrongtype, message=msg)) self.assertFalse(ecc.verify_usermessage_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=addr2, sig65=sig2_wrongtype, message=msg))
@needs_test_with_all_aes_implementations @needs_test_with_all_aes_implementations
def test_decrypt_message(self): def test_decrypt_message(self):
@ -303,17 +303,20 @@ class Test_bitcoin(ElectrumTestCase):
def test_sign_transaction(self): def test_sign_transaction(self):
eckey1 = ecc.ECPrivkey(bfh('7e1255fddb52db1729fc3ceb21a46f95b8d9fe94cc83425e936a6c5223bb679d')) 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()) self.assertEqual('3044022066e7d6a954006cce78a223f5edece8aaedcf3607142e9677acef1cfcb91cfdde022065cb0b5401bf16959ce7b785ea7fd408be5e4cb7d8f1b1a32c78eac6f73678d9', sig1.hex())
eckey2 = ecc.ECPrivkey(bfh('c7ce8c1462c311eec24dff9e2532ac6241e50ae57e7d1833af21942136972f23')) 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()) self.assertEqual('30440220618513f4cfc87dde798ce5febae7634c23e7b9254a1eabf486be820f6a7c2c4702204fef459393a2b931f949e63ced06888f35e286e446dc46feb24b5b5f81c6ed52', sig2.hex())
@disable_ecdsa_r_value_grinding @disable_ecdsa_r_value_grinding
def test_sign_transaction_without_ecdsa_r_value_grinding(self): def test_sign_transaction_without_ecdsa_r_value_grinding(self):
eckey1 = ecc.ECPrivkey(bfh('7e1255fddb52db1729fc3ceb21a46f95b8d9fe94cc83425e936a6c5223bb679d')) 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()) self.assertEqual('3045022100902a288b98392254cd23c0e9a49ac6d7920f171b8249a48e484b998f1874a2010220723d844826828f092cf400cb210c4fa0b8cd1b9d1a7f21590e78e022ff6476b9', sig1.hex())
@needs_test_with_all_aes_implementations @needs_test_with_all_aes_implementations

118
tests/test_ecc.py

@ -0,0 +1,118 @@
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
# 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.schnorr_sign(msg32, aux_rand32=aux_rand)
self.assertEqual(signature, sig_created)
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.schnorr_sign(msg32, aux_rand32=None)
sig2 = seckey.schnorr_sign(msg32, aux_rand32=b"\x00" * 32)
self.assertEqual(sig1, sig2)
sig3 = seckey.schnorr_sign(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.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))
Loading…
Cancel
Save