Browse Source

prepare for separation of ecc module:

- move encrypt/sign functions elsewhere
- remove local dependencies in ecc.py, ecc_fast.py (except logging)
master
ThomasV 2 years ago
parent
commit
2f3d89f415
  1. 46
      electrum/bitcoin.py
  2. 4
      electrum/commands.py
  3. 40
      electrum/crypto.py
  4. 84
      electrum/ecc.py
  5. 2
      electrum/gui/qml/qedaemon.py
  6. 5
      electrum/gui/qt/main_window.py
  7. 5
      electrum/keystore.py
  8. 4
      electrum/paymentrequest.py
  9. 6
      electrum/storage.py
  10. 2
      electrum/transaction.py
  11. 59
      tests/test_bitcoin.py
  12. 4
      tests/test_ecc.py

46
electrum/bitcoin.py

@ -769,7 +769,7 @@ def taproot_tweak_pubkey(pubkey32: bytes, h: bytes) -> Tuple[int, bytes]:
assert len(pubkey32) == 32, len(pubkey32) assert len(pubkey32) == 32, len(pubkey32)
int_from_bytes = lambda x: int.from_bytes(x, byteorder="big", signed=False) int_from_bytes = lambda x: int.from_bytes(x, byteorder="big", signed=False)
tweak = int_from_bytes(ecc.bip340_tagged_hash(b"TapTweak", pubkey32 + h)) tweak = int_from_bytes(bip340_tagged_hash(b"TapTweak", pubkey32 + h))
if tweak >= ecc.CURVE_ORDER: if tweak >= ecc.CURVE_ORDER:
raise ValueError raise ValueError
P = ecc.ECPubkey(b"\x02" + pubkey32) P = ecc.ECPubkey(b"\x02" + pubkey32)
@ -786,7 +786,7 @@ def taproot_tweak_seckey(seckey0: bytes, h: bytes) -> bytes:
P = ecc.ECPrivkey(seckey0) P = ecc.ECPrivkey(seckey0)
seckey = P.secret_scalar if P.has_even_y() else ecc.CURVE_ORDER - P.secret_scalar seckey = P.secret_scalar if P.has_even_y() else ecc.CURVE_ORDER - P.secret_scalar
pubkey32 = P.get_public_key_bytes(compressed=True)[1:] pubkey32 = P.get_public_key_bytes(compressed=True)[1:]
tweak = int_from_bytes(ecc.bip340_tagged_hash(b"TapTweak", pubkey32 + h)) tweak = int_from_bytes(bip340_tagged_hash(b"TapTweak", pubkey32 + h))
if tweak >= ecc.CURVE_ORDER: if tweak >= ecc.CURVE_ORDER:
raise ValueError raise ValueError
return int.to_bytes((seckey + tweak) % ecc.CURVE_ORDER, length=32, byteorder="big", signed=False) return int.to_bytes((seckey + tweak) % ecc.CURVE_ORDER, length=32, byteorder="big", signed=False)
@ -798,18 +798,21 @@ def taproot_tweak_seckey(seckey0: bytes, h: bytes) -> bytes:
TapTreeLeaf = Tuple[int, bytes] TapTreeLeaf = Tuple[int, bytes]
TapTree = Union[TapTreeLeaf, Sequence['TapTree']] TapTree = Union[TapTreeLeaf, Sequence['TapTree']]
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)
def taproot_tree_helper(script_tree: TapTree): def taproot_tree_helper(script_tree: TapTree):
if isinstance(script_tree, tuple): if isinstance(script_tree, tuple):
leaf_version, script = script_tree leaf_version, script = script_tree
h = ecc.bip340_tagged_hash(b"TapLeaf", bytes([leaf_version]) + witness_push(script)) h = bip340_tagged_hash(b"TapLeaf", bytes([leaf_version]) + witness_push(script))
return ([((leaf_version, script), bytes())], h) return ([((leaf_version, script), bytes())], h)
left, left_h = taproot_tree_helper(script_tree[0]) left, left_h = taproot_tree_helper(script_tree[0])
right, right_h = taproot_tree_helper(script_tree[1]) right, right_h = taproot_tree_helper(script_tree[1])
ret = [(l, c + right_h) for l, c in left] + [(l, c + left_h) for l, c in right] ret = [(l, c + right_h) for l, c in left] + [(l, c + left_h) for l, c in right]
if right_h < left_h: if right_h < left_h:
left_h, right_h = right_h, left_h left_h, right_h = right_h, left_h
return (ret, ecc.bip340_tagged_hash(b"TapBranch", left_h + right_h)) return (ret, bip340_tagged_hash(b"TapBranch", left_h + right_h))
def taproot_output_script(internal_pubkey: bytes, *, script_tree: Optional[TapTree]) -> bytes: def taproot_output_script(internal_pubkey: bytes, *, script_tree: Optional[TapTree]) -> bytes:
@ -838,3 +841,38 @@ def control_block_for_taproot_script_spend(
pubkey_data = bytes([output_pubkey_y_parity + leaf_version]) + internal_pubkey pubkey_data = bytes([output_pubkey_y_parity + leaf_version]) + internal_pubkey
control_block = pubkey_data + merkle_path control_block = pubkey_data + merkle_path
return (leaf_script, control_block) return (leaf_script, control_block)
# user message signing
def usermessage_magic(message: bytes) -> bytes:
from .bitcoin import var_int
length = var_int(len(message))
return b"\x18Bitcoin Signed Message:\n" + length + message
def ecdsa_sign_usermessage(ec_privkey, message: Union[bytes, str], *, is_compressed: bool) -> bytes:
message = to_bytes(message, 'utf8')
msg32 = sha256d(usermessage_magic(message))
return ec_privkey.ecdsa_sign_recoverable(msg32, is_compressed=is_compressed)
def verify_usermessage_with_address(address: str, sig65: bytes, message: bytes, *, net=None) -> bool:
from .bitcoin import pubkey_to_address
from .ecc import ECPubkey
assert_bytes(sig65, message)
if net is None: net = constants.net
h = sha256d(usermessage_magic(message))
try:
public_key, compressed, txin_type_guess = ECPubkey.from_ecdsa_sig65(sig65, h)
except Exception as e:
return False
# check public key using the address
pubkey_hex = public_key.get_public_key_hex(compressed)
txin_types = (txin_type_guess,) if txin_type_guess else ('p2pkh', 'p2wpkh', 'p2wpkh-p2sh')
for txin_type in txin_types:
addr = pubkey_to_address(txin_type, pubkey_hex, net=net)
if address == addr:
break
else:
return False
# check message
# note: `$ bitcoin-cli verifymessage` does NOT enforce the low-S rule for ecdsa sigs
return public_key.ecdsa_verify(sig65[1:], h, enforce_low_s=False)

4
electrum/commands.py

@ -716,7 +716,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_usermessage_with_address(address, sig, message) return bitcoin.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,
@ -910,7 +910,7 @@ class Commands:
except TypeError: except TypeError:
raise UserFacingException(f"message must be a string-like object instead of {repr(message)}") raise UserFacingException(f"message must be a string-like object instead of {repr(message)}")
public_key = ecc.ECPubkey(bfh(pubkey)) public_key = ecc.ECPubkey(bfh(pubkey))
encrypted = public_key.encrypt_message(message) encrypted = crypto.ecies_encrypt_message(public_key, message)
return encrypted.decode('utf-8') return encrypted.decode('utf-8')
@command('wp') @command('wp')

40
electrum/crypto.py

@ -34,7 +34,7 @@ from typing import Union, Mapping, Optional
from .util import assert_bytes, InvalidPassword, to_bytes, to_string, WalletFileException, versiontuple from .util import assert_bytes, InvalidPassword, to_bytes, to_string, WalletFileException, versiontuple
from .i18n import _ from .i18n import _
from .logging import get_logger from .logging import get_logger
from . import ecc
_logger = get_logger(__name__) _logger = get_logger(__name__)
@ -443,3 +443,41 @@ def chacha20_decrypt(*, key: bytes, nonce: bytes, data: bytes) -> bytes:
decryptor = cipher.decryptor() decryptor = cipher.decryptor()
return decryptor.update(data) return decryptor.update(data)
raise Exception("no chacha20 backend found") raise Exception("no chacha20 backend found")
def ecies_encrypt_message(ec_pubkey, 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
"""
assert_bytes(message)
ephemeral = ecc.ECPrivkey.generate_random_key()
ecdh_key = (ec_pubkey * ephemeral.secret_scalar).get_public_key_bytes(compressed=True)
key = hashlib.sha512(ecdh_key).digest()
iv, key_e, key_m = key[0:16], key[16:32], key[32:]
ciphertext = aes_encrypt_with_iv(key_e, iv, message)
ephemeral_pubkey = ephemeral.get_public_key_bytes(compressed=True)
encrypted = magic + ephemeral_pubkey + ciphertext
mac = hmac_oneshot(key_m, encrypted, hashlib.sha256)
return base64.b64encode(encrypted + mac)
def ecies_decrypt_message(ec_privkey, encrypted: Union[str, bytes], *, magic: bytes=b'BIE1') -> bytes:
encrypted = base64.b64decode(encrypted) # type: bytes
if len(encrypted) < 85:
raise Exception('invalid ciphertext: length')
magic_found = encrypted[:4]
ephemeral_pubkey_bytes = encrypted[4:37]
ciphertext = encrypted[37:-32]
mac = encrypted[-32:]
if magic_found != magic:
raise Exception('invalid ciphertext: invalid magic bytes')
try:
ephemeral_pubkey = ecc.ECPubkey(ephemeral_pubkey_bytes)
except ecc.InvalidECPointException as e:
raise Exception('invalid ciphertext: invalid ephemeral pubkey') from e
ecdh_key = (ephemeral_pubkey * ec_privkey.secret_scalar).get_public_key_bytes(compressed=True)
key = hashlib.sha512(ecdh_key).digest()
iv, key_e, key_m = key[0:16], key[16:32], key[32:]
if mac != hmac_oneshot(key_m, encrypted[:-32], hashlib.sha256):
raise InvalidPassword()
return aes_decrypt_with_iv(key_e, iv, ciphertext)

84
electrum/ecc.py

@ -26,20 +26,17 @@
import base64 import base64
import hashlib import hashlib
import functools import functools
import secrets
from typing import Union, Tuple, Optional from typing import Union, Tuple, Optional
from ctypes import ( from ctypes import (
byref, c_char_p, c_size_t, create_string_buffer, cast, byref, c_char_p, c_size_t, create_string_buffer, cast,
) )
from .util import bfh, assert_bytes, to_bytes, InvalidPassword, profiler, randrange
from .crypto import (sha256, sha256d, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot)
from . import constants
from .logging import get_logger
from . import ecc_fast from . import ecc_fast
from .ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED, LibModuleMissing from .ecc_fast import _libsecp256k1, SECP256K1_EC_UNCOMPRESSED, LibModuleMissing
_logger = get_logger(__name__) def assert_bytes(x):
assert isinstance(x, (bytes, bytearray))
# Some unit tests need to create ECDSA sigs without grinding the R value (and just use RFC6979). # Some unit tests need to create ECDSA sigs without grinding the R value (and just use RFC6979).
# see https://github.com/bitcoin/bitcoin/pull/13666 # see https://github.com/bitcoin/bitcoin/pull/13666
@ -382,23 +379,6 @@ class ECPubkey(object):
return False return False
return True 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
"""
assert_bytes(message)
ephemeral = ECPrivkey.generate_random_key()
ecdh_key = (self * ephemeral.secret_scalar).get_public_key_bytes(compressed=True)
key = hashlib.sha512(ecdh_key).digest()
iv, key_e, key_m = key[0:16], key[16:32], key[32:]
ciphertext = aes_encrypt_with_iv(key_e, iv, message)
ephemeral_pubkey = ephemeral.get_public_key_bytes(compressed=True)
encrypted = magic + ephemeral_pubkey + ciphertext
mac = hmac_oneshot(key_m, encrypted, hashlib.sha256)
return base64.b64encode(encrypted + mac)
@classmethod @classmethod
def order(cls) -> int: def order(cls) -> int:
return CURVE_ORDER return CURVE_ORDER
@ -424,34 +404,6 @@ CURVE_ORDER = 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D
POINT_AT_INFINITY = ECPubkey(None) POINT_AT_INFINITY = ECPubkey(None)
def usermessage_magic(message: bytes) -> bytes:
from .bitcoin import var_int
length = var_int(len(message))
return b"\x18Bitcoin Signed Message:\n" + length + message
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(usermessage_magic(message))
try:
public_key, compressed, txin_type_guess = ECPubkey.from_ecdsa_sig65(sig65, h)
except Exception as e:
return False
# check public key using the address
pubkey_hex = public_key.get_public_key_hex(compressed)
txin_types = (txin_type_guess,) if txin_type_guess else ('p2pkh', 'p2wpkh', 'p2wpkh-p2sh')
for txin_type in txin_types:
addr = pubkey_to_address(txin_type, pubkey_hex, net=net)
if address == addr:
break
else:
return False
# check message
# note: `$ bitcoin-cli verifymessage` does NOT enforce the low-S rule for ecdsa sigs
return public_key.ecdsa_verify(sig65[1:], h, enforce_low_s=False)
def is_secret_within_curve_range(secret: Union[int, bytes]) -> bool: def is_secret_within_curve_range(secret: Union[int, bytes]) -> bool:
if isinstance(secret, bytes): if isinstance(secret, bytes):
@ -499,7 +451,7 @@ class ECPrivkey(ECPubkey):
@classmethod @classmethod
def generate_random_key(cls) -> 'ECPrivkey': def generate_random_key(cls) -> 'ECPrivkey':
randint = randrange(CURVE_ORDER) randint = secrets.randbelow(CURVE_ORDER - 1) + 1
ephemeral_exponent = int.to_bytes(randint, length=32, byteorder='big', signed=False) ephemeral_exponent = int.to_bytes(randint, length=32, byteorder='big', signed=False)
return ECPrivkey(ephemeral_exponent) return ECPrivkey(ephemeral_exponent)
@ -592,31 +544,6 @@ class ECPrivkey(ECPubkey):
sig65, recid = bruteforce_recid(sig64) sig65, recid = bruteforce_recid(sig64)
return sig65 return sig65
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')
magic_found = encrypted[:4]
ephemeral_pubkey_bytes = encrypted[4:37]
ciphertext = encrypted[37:-32]
mac = encrypted[-32:]
if magic_found != magic:
raise Exception('invalid ciphertext: invalid magic bytes')
try:
ephemeral_pubkey = ECPubkey(ephemeral_pubkey_bytes)
except InvalidECPointException as e:
raise Exception('invalid ciphertext: invalid ephemeral pubkey') from e
ecdh_key = (ephemeral_pubkey * self.secret_scalar).get_public_key_bytes(compressed=True)
key = hashlib.sha512(ecdh_key).digest()
iv, key_e, key_m = key[0:16], key[16:32], key[32:]
if mac != hmac_oneshot(key_m, encrypted[:-32], hashlib.sha256):
raise InvalidPassword()
return aes_decrypt_with_iv(key_e, iv, ciphertext)
def construct_ecdsa_sig65(sig64: bytes, recid: int, *, is_compressed: bool) -> bytes: def construct_ecdsa_sig65(sig64: bytes, recid: int, *, is_compressed: bool) -> bytes:
@ -624,6 +551,3 @@ def construct_ecdsa_sig65(sig64: bytes, recid: int, *, is_compressed: bool) -> b
return bytes([27 + recid + comp]) + sig64 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)

2
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_usermessage_with_address from electrum.bitcoin 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

5
electrum/gui/qt/main_window.py

@ -1993,7 +1993,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_usermessage_with_address(address, sig, message) verified = bitcoin.verify_usermessage_with_address(address, sig, message)
except Exception as e: except Exception as e:
verified = False verified = False
if verified: if verified:
@ -2057,6 +2057,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self.thread.add(task, on_success=setText) self.thread.add(task, on_success=setText)
def do_encrypt(self, message_e, pubkey_e, encrypted_e): def do_encrypt(self, message_e, pubkey_e, encrypted_e):
from electrum import crypto
message = message_e.toPlainText() message = message_e.toPlainText()
message = message.encode('utf-8') message = message.encode('utf-8')
try: try:
@ -2065,7 +2066,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self.logger.exception('Invalid Public key') self.logger.exception('Invalid Public key')
self.show_warning(_('Invalid Public key')) self.show_warning(_('Invalid Public key'))
return return
encrypted = public_key.encrypt_message(message) encrypted = crypto.ecies_encrypt_message(public_key, message)
encrypted_e.setText(encrypted.decode('ascii')) encrypted_e.setText(encrypted.decode('ascii'))
def encrypt_message(self, address=''): def encrypt_message(self, address=''):

5
electrum/keystore.py

@ -41,6 +41,7 @@ from .bip32 import (convert_bip32_strpath_to_intpath, BIP32_PRIME,
KeyOriginInfo) KeyOriginInfo)
from .descriptor import PubkeyProvider from .descriptor import PubkeyProvider
from .ecc import string_to_number from .ecc import string_to_number
from . import crypto
from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATEST, from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATEST,
SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion, hash_160, SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion, hash_160,
CiphertextFormatError) CiphertextFormatError)
@ -223,12 +224,12 @@ 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.ecdsa_sign_usermessage(message, is_compressed=compressed) return bitcoin.ecdsa_sign_usermessage(key, 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)
ec = ecc.ECPrivkey(privkey) ec = ecc.ECPrivkey(privkey)
decrypted = ec.decrypt_message(message) decrypted = crypto.ecies_decrypt_message(ec, message)
return decrypted return decrypted
def sign_transaction(self, tx, password): def sign_transaction(self, tx, password):

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_usermessage_with_address(address, sig, message): if bitcoin.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.ecdsa_sign_usermessage(message, is_compressed=compressed) pr.signature = bitcoin.ecdsa_sign_usermessage(ec_key, message, is_compressed=compressed)
def verify_cert_chain(chain): def verify_cert_chain(chain):

6
electrum/storage.py

@ -31,7 +31,7 @@ import zlib
from enum import IntEnum from enum import IntEnum
from typing import Optional from typing import Optional
from . import ecc from . import ecc, crypto
from .util import (profiler, InvalidPassword, WalletFileException, bfh, standardize_path, from .util import (profiler, InvalidPassword, WalletFileException, bfh, standardize_path,
test_read_write_permissions, os_chmod) test_read_write_permissions, os_chmod)
@ -192,7 +192,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, magic=enc_magic)) s = zlib.decompress(crypto.ecies_decrypt_message(ec_key, self.raw, magic=enc_magic))
s = s.decode('utf8') s = s.decode('utf8')
else: else:
s = '' s = ''
@ -207,7 +207,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, magic=enc_magic) s = crypto.ecies_encrypt_message(public_key, c, magic=enc_magic)
s = s.decode('utf8') s = s.decode('utf8')
return s return s

2
electrum/transaction.py

@ -2309,7 +2309,7 @@ class PartialTransaction(Transaction):
merkle_root = txin.tap_merkle_root or bytes() merkle_root = txin.tap_merkle_root or bytes()
output_privkey_bytes = taproot_tweak_seckey(privkey_bytes, merkle_root) output_privkey_bytes = taproot_tweak_seckey(privkey_bytes, merkle_root)
output_privkey = ecc.ECPrivkey(output_privkey_bytes) output_privkey = ecc.ECPrivkey(output_privkey_bytes)
msg_hash = ecc.bip340_tagged_hash(b"TapSighash", pre_hash) msg_hash = bitcoin.bip340_tagged_hash(b"TapSighash", pre_hash)
sig = output_privkey.schnorr_sign(msg_hash) sig = output_privkey.schnorr_sign(msg_hash)
sighash = txin.sighash if txin.sighash is not None else Sighash.DEFAULT sighash = txin.sighash if txin.sighash is not None else Sighash.DEFAULT
else: else:

59
tests/test_bitcoin.py

@ -23,12 +23,12 @@ from electrum.bip32 import (BIP32Node, convert_bip32_intpath_to_strpath,
is_xpub, convert_bip32_strpath_to_intpath, is_xpub, convert_bip32_strpath_to_intpath,
normalize_bip32_derivation, is_all_public_derivation) normalize_bip32_derivation, is_all_public_derivation)
from electrum.crypto import sha256d, SUPPORTED_PW_HASH_VERSIONS from electrum.crypto import sha256d, SUPPORTED_PW_HASH_VERSIONS
from electrum import ecc, crypto, constants from electrum import ecc, crypto, constants, bitcoin
from electrum.util import bfh, InvalidPassword, randrange from electrum.util import bfh, InvalidPassword, randrange
from electrum.storage import WalletStorage from electrum.storage import WalletStorage
from electrum.keystore import xtype_from_derivation from electrum.keystore import xtype_from_derivation
from electrum import ecc_fast from electrum import ecc_fast, crypto
from . import ElectrumTestCase from . import ElectrumTestCase
from . import FAST_TESTS from . import FAST_TESTS
@ -182,16 +182,16 @@ class Test_bitcoin(ElectrumTestCase):
eck = ecc.ECPrivkey.from_secret_scalar(pvk) eck = ecc.ECPrivkey.from_secret_scalar(pvk)
#print "Compressed public key ", pubkey_c.encode('hex') #print "Compressed public key ", pubkey_c.encode('hex')
enc = ecc.ECPubkey(pubkey_c).encrypt_message(message) enc = crypto.ecies_encrypt_message(ecc.ECPubkey(pubkey_c), message)
dec = eck.decrypt_message(enc) dec = crypto.ecies_decrypt_message(eck, enc)
self.assertEqual(message, dec) self.assertEqual(message, dec)
#print "Uncompressed public key", pubkey_u.encode('hex') #print "Uncompressed public key", pubkey_u.encode('hex')
#enc2 = EC_KEY.encrypt_message(message, pubkey_u) #enc2 = EC_KEY.encrypt_message(message, pubkey_u)
dec2 = eck.decrypt_message(enc) dec2 = crypto.ecies_decrypt_message(eck, enc)
self.assertEqual(message, dec2) self.assertEqual(message, dec2)
msg32 = sha256d(ecc.usermessage_magic(message)) msg32 = sha256d(bitcoin.usermessage_magic(message))
sig65 = eck.ecdsa_sign_recoverable(msg32, is_compressed=True) sig65 = eck.ecdsa_sign_recoverable(msg32, is_compressed=True)
self.assertTrue(eck.ecdsa_verify_recoverable(sig65, msg32)) self.assertTrue(eck.ecdsa_verify_recoverable(sig65, msg32))
@ -226,7 +226,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.ecdsa_sign_usermessage(msg, is_compressed=compressed) return bitcoin.ecdsa_sign_usermessage(key, 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'
@ -245,11 +245,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_usermessage_with_address(addr1, sig1, msg1)) self.assertTrue(bitcoin.verify_usermessage_with_address(addr1, sig1, msg1))
self.assertTrue(ecc.verify_usermessage_with_address(addr2, sig2, msg2)) self.assertTrue(bitcoin.verify_usermessage_with_address(addr2, sig2, msg2))
self.assertFalse(ecc.verify_usermessage_with_address(addr1, b'wrong', msg1)) self.assertFalse(bitcoin.verify_usermessage_with_address(addr1, b'wrong', msg1))
self.assertFalse(ecc.verify_usermessage_with_address(addr1, sig2, msg1)) self.assertFalse(bitcoin.verify_usermessage_with_address(addr1, sig2, msg1))
def test_signmessage_low_s(self): def test_signmessage_low_s(self):
"""`$ bitcoin-cli verifymessage` does NOT enforce the low-S rule for ecdsa sigs. This tests we do the same.""" """`$ bitcoin-cli verifymessage` does NOT enforce the low-S rule for ecdsa sigs. This tests we do the same."""
@ -257,8 +257,8 @@ class Test_bitcoin(ElectrumTestCase):
sig_low_s = b'Hzsu0U/THAsPz/MSuXGBKSULz2dTfmrg1NsAhFp+wH5aKfmX4Db7ExLGa7FGn0m6Mf43KsbEOWpvUUUBTM3Uusw=' sig_low_s = b'Hzsu0U/THAsPz/MSuXGBKSULz2dTfmrg1NsAhFp+wH5aKfmX4Db7ExLGa7FGn0m6Mf43KsbEOWpvUUUBTM3Uusw='
sig_high_s = b'IDsu0U/THAsPz/MSuXGBKSULz2dTfmrg1NsAhFp+wH5a1gZoH8kE7O05lE65YLZFzLx3sh/rDzXMbo1dQAJhhnU=' sig_high_s = b'IDsu0U/THAsPz/MSuXGBKSULz2dTfmrg1NsAhFp+wH5a1gZoH8kE7O05lE65YLZFzLx3sh/rDzXMbo1dQAJhhnU='
msg = b'Chancellor on brink of second bailout for banks' msg = b'Chancellor on brink of second bailout for banks'
self.assertTrue(ecc.verify_usermessage_with_address(address=addr, sig65=base64.b64decode(sig_low_s), message=msg)) self.assertTrue(bitcoin.verify_usermessage_with_address(address=addr, sig65=base64.b64decode(sig_low_s), message=msg))
self.assertTrue(ecc.verify_usermessage_with_address(address=addr, sig65=base64.b64decode(sig_high_s), message=msg)) self.assertTrue(bitcoin.verify_usermessage_with_address(address=addr, sig65=base64.b64decode(sig_high_s), message=msg))
def test_signmessage_segwit_witness_v0_address(self): def test_signmessage_segwit_witness_v0_address(self):
msg = b'Electrum' msg = b'Electrum'
@ -266,14 +266,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_usermessage_with_address(addr1, sig1, msg)) self.assertTrue(bitcoin.verify_usermessage_with_address(addr1, sig1, msg))
self.assertFalse(ecc.verify_usermessage_with_address(addr1, sig1, b'heyheyhey')) self.assertFalse(bitcoin.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_usermessage_with_address(addr2, sig2, msg)) self.assertTrue(bitcoin.verify_usermessage_with_address(addr2, sig2, msg))
self.assertFalse(ecc.verify_usermessage_with_address(addr2, sig2, b'heyheyhey')) self.assertFalse(bitcoin.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
@ -286,20 +286,23 @@ 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_usermessage_with_address(address=addr1, sig65=sig1, message=msg)) self.assertTrue(bitcoin.verify_usermessage_with_address(address=addr1, sig65=sig1, message=msg))
self.assertTrue(ecc.verify_usermessage_with_address(address=addr2, sig65=sig2, message=msg)) self.assertTrue(bitcoin.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_usermessage_with_address(address=addr1, sig65=sig1_wrongtype, message=msg)) self.assertFalse(bitcoin.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)) self.assertFalse(bitcoin.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):
key = WalletStorage.get_eckey_from_password('pw123') key = WalletStorage.get_eckey_from_password('pw123')
self.assertEqual(b'me<(s_s)>age', key.decrypt_message(b'QklFMQMDFtgT3zWSQsa+Uie8H/WvfUjlu9UN9OJtTt3KlgKeSTi6SQfuhcg1uIz9hp3WIUOFGTLr4RNQBdjPNqzXwhkcPi2Xsbiw6UCNJncVPJ6QBg==')) self.assertEqual(b'me<(s_s)>age', crypto.ecies_decrypt_message(
self.assertEqual(b'me<(s_s)>age', key.decrypt_message(b'QklFMQKXOXbylOQTSMGfo4MFRwivAxeEEkewWQrpdYTzjPhqjHcGBJwdIhB7DyRfRQihuXx1y0ZLLv7XxLzrILzkl/H4YUtZB4uWjuOAcmxQH4i/Og==')) key, b'QklFMQMDFtgT3zWSQsa+Uie8H/WvfUjlu9UN9OJtTt3KlgKeSTi6SQfuhcg1uIz9hp3WIUOFGTLr4RNQBdjPNqzXwhkcPi2Xsbiw6UCNJncVPJ6QBg=='))
self.assertEqual(b'hey_there' * 100, key.decrypt_message(b'QklFMQLOOsabsXtGQH8edAa6VOUa5wX8/DXmxX9NyHoAx1a5bWgllayGRVPeI2bf0ZdWK0tfal0ap0ZIVKbd2eOJybqQkILqT6E1/Syzq0Zicyb/AA1eZNkcX5y4gzloxinw00ubCA8M7gcUjJpOqbnksATcJ5y2YYXcHMGGfGurWu6uJ/UyrNobRidWppRMW5yR9/6utyNvT6OHIolCMEf7qLcmtneoXEiz51hkRdZS7weNf9mGqSbz9a2NL3sdh1A0feHIjAZgcCKcAvksNUSauf0/FnIjzTyPRpjRDMeDC8Ci3sGiuO3cvpWJwhZfbjcS26KmBv2CHWXfRRNFYOInHZNIXWNAoBB47Il5bGSMd+uXiGr+SQ9tNvcu+BiJNmFbxYqg+oQ8dGAl1DtvY2wJVY8k7vO9BIWSpyIxfGw7EDifhc5vnOmGe016p6a01C3eVGxgl23UYMrP7+fpjOcPmTSF4rk5U5ljEN3MSYqlf1QEv0OqlI9q1TwTK02VBCjMTYxDHsnt04OjNBkNO8v5uJ4NR+UUDBEp433z53I59uawZ+dbk4v4ZExcl8EGmKm3Gzbal/iJ/F7KQuX2b/ySEhLOFVYFWxK73X1nBvCSK2mC2/8fCw8oI5pmvzJwQhcCKTdEIrz3MMvAHqtPScDUOjzhXxInQOCb3+UBj1PPIdqkYLvZss1TEaBwYZjLkVnK2MBj7BaqT6Rp6+5A/fippUKHsnB6eYMEPR2YgDmCHL+4twxHJG6UWdP3ybaKiiAPy2OHNP6PTZ0HrqHOSJzBSDD+Z8YpaRg29QX3UEWlqnSKaan0VYAsV1VeaN0XFX46/TWO0L5tjhYVXJJYGqo6tIQJymxATLFRF6AZaD1Mwd27IAL04WkmoQoXfO6OFfwdp/shudY/1gBkDBvGPICBPtnqkvhGF+ZF3IRkuPwiFWeXmwBxKHsRx/3+aJu32Ml9+za41zVk2viaxcGqwTc5KMexQFLAUwqhv+aIik7U+5qk/gEVSuRoVkihoweFzKolNF+BknH2oB4rZdPixag5Zje3DvgjsSFlOl69W/67t/Gs8htfSAaHlsB8vWRQr9+v/lxTbrAw+O0E+sYGoObQ4qQMyQshNZEHbpPg63eWiHtJJnrVBvOeIbIHzoLDnMDsWVWZSMzAQ1vhX1H5QLgSEbRlKSliVY03kDkh/Nk/KOn+B2q37Ialq4JcRoIYFGJ8AoYEAD0tRuTqFddIclE75HzwaNG7NyKW1plsa72ciOPwsPJsdd5F0qdSQ3OSKtooTn7uf6dXOc4lDkfrVYRlZ0PX')) self.assertEqual(b'me<(s_s)>age', crypto.ecies_decrypt_message(
key, b'QklFMQKXOXbylOQTSMGfo4MFRwivAxeEEkewWQrpdYTzjPhqjHcGBJwdIhB7DyRfRQihuXx1y0ZLLv7XxLzrILzkl/H4YUtZB4uWjuOAcmxQH4i/Og=='))
self.assertEqual(b'hey_there' * 100, crypto.ecies_decrypt_message(
key, b'QklFMQLOOsabsXtGQH8edAa6VOUa5wX8/DXmxX9NyHoAx1a5bWgllayGRVPeI2bf0ZdWK0tfal0ap0ZIVKbd2eOJybqQkILqT6E1/Syzq0Zicyb/AA1eZNkcX5y4gzloxinw00ubCA8M7gcUjJpOqbnksATcJ5y2YYXcHMGGfGurWu6uJ/UyrNobRidWppRMW5yR9/6utyNvT6OHIolCMEf7qLcmtneoXEiz51hkRdZS7weNf9mGqSbz9a2NL3sdh1A0feHIjAZgcCKcAvksNUSauf0/FnIjzTyPRpjRDMeDC8Ci3sGiuO3cvpWJwhZfbjcS26KmBv2CHWXfRRNFYOInHZNIXWNAoBB47Il5bGSMd+uXiGr+SQ9tNvcu+BiJNmFbxYqg+oQ8dGAl1DtvY2wJVY8k7vO9BIWSpyIxfGw7EDifhc5vnOmGe016p6a01C3eVGxgl23UYMrP7+fpjOcPmTSF4rk5U5ljEN3MSYqlf1QEv0OqlI9q1TwTK02VBCjMTYxDHsnt04OjNBkNO8v5uJ4NR+UUDBEp433z53I59uawZ+dbk4v4ZExcl8EGmKm3Gzbal/iJ/F7KQuX2b/ySEhLOFVYFWxK73X1nBvCSK2mC2/8fCw8oI5pmvzJwQhcCKTdEIrz3MMvAHqtPScDUOjzhXxInQOCb3+UBj1PPIdqkYLvZss1TEaBwYZjLkVnK2MBj7BaqT6Rp6+5A/fippUKHsnB6eYMEPR2YgDmCHL+4twxHJG6UWdP3ybaKiiAPy2OHNP6PTZ0HrqHOSJzBSDD+Z8YpaRg29QX3UEWlqnSKaan0VYAsV1VeaN0XFX46/TWO0L5tjhYVXJJYGqo6tIQJymxATLFRF6AZaD1Mwd27IAL04WkmoQoXfO6OFfwdp/shudY/1gBkDBvGPICBPtnqkvhGF+ZF3IRkuPwiFWeXmwBxKHsRx/3+aJu32Ml9+za41zVk2viaxcGqwTc5KMexQFLAUwqhv+aIik7U+5qk/gEVSuRoVkihoweFzKolNF+BknH2oB4rZdPixag5Zje3DvgjsSFlOl69W/67t/Gs8htfSAaHlsB8vWRQr9+v/lxTbrAw+O0E+sYGoObQ4qQMyQshNZEHbpPg63eWiHtJJnrVBvOeIbIHzoLDnMDsWVWZSMzAQ1vhX1H5QLgSEbRlKSliVY03kDkh/Nk/KOn+B2q37Ialq4JcRoIYFGJ8AoYEAD0tRuTqFddIclE75HzwaNG7NyKW1plsa72ciOPwsPJsdd5F0qdSQ3OSKtooTn7uf6dXOc4lDkfrVYRlZ0PX'))
@needs_test_with_all_aes_implementations @needs_test_with_all_aes_implementations
def test_encrypt_message(self): def test_encrypt_message(self):
@ -309,10 +312,10 @@ class Test_bitcoin(ElectrumTestCase):
b'cannot think of anything funny' b'cannot think of anything funny'
] ]
for plaintext in msgs: for plaintext in msgs:
ciphertext1 = key.encrypt_message(plaintext) ciphertext1 = crypto.ecies_encrypt_message(key, plaintext)
ciphertext2 = key.encrypt_message(plaintext) ciphertext2 = crypto.ecies_encrypt_message(key, plaintext)
self.assertEqual(plaintext, key.decrypt_message(ciphertext1)) self.assertEqual(plaintext, crypto.ecies_decrypt_message(key, ciphertext1))
self.assertEqual(plaintext, key.decrypt_message(ciphertext2)) self.assertEqual(plaintext, crypto.ecies_decrypt_message(key, ciphertext2))
self.assertNotEqual(ciphertext1, ciphertext2) self.assertNotEqual(ciphertext1, ciphertext2)
def test_sign_transaction(self): def test_sign_transaction(self):

4
tests/test_ecc.py

@ -4,7 +4,7 @@ from ctypes import (
) )
import io import io
from electrum import ecc from electrum import ecc, bitcoin
from electrum.ecc import ECPubkey, ECPrivkey from electrum.ecc import ECPubkey, ECPrivkey
from electrum.ecc_fast import _libsecp256k1 from electrum.ecc_fast import _libsecp256k1
from electrum import crypto from electrum import crypto
@ -115,7 +115,7 @@ class TestSchnorr(ElectrumTestCase):
) )
for tag, msg in data: for tag, msg in data:
self.assertEqual(bip340_tagged_hash__from_libsecp(tag, msg), self.assertEqual(bip340_tagged_hash__from_libsecp(tag, msg),
ecc.bip340_tagged_hash(tag, msg)) bitcoin.bip340_tagged_hash(tag, msg))
class TestEcdsa(ElectrumTestCase): class TestEcdsa(ElectrumTestCase):

Loading…
Cancel
Save