From 2f1095510c2f50bfe2c2ebc0c7159e9b8f945aca Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 26 Apr 2024 20:09:00 +0000 Subject: [PATCH 1/2] bitcoin.py/transaction.py: API changes: rm most hex usage Instead of some functions operating with hex strings, and others using bytes, this consolidates most things to use bytes. This mainly focuses on bitcoin.py and transaction.py, and then adapts the API usages in other files. Notably, - scripts, - pubkeys, - signatures should be bytes in almost all places now. --- electrum/address_synchronizer.py | 4 +- electrum/bip32.py | 6 +- electrum/bitcoin.py | 145 ++++++------- electrum/blockchain.py | 35 ++-- electrum/commands.py | 4 +- electrum/constants.py | 2 +- electrum/descriptor.py | 30 +-- electrum/ecc.py | 2 +- electrum/gui/text.py | 2 +- electrum/lnchannel.py | 32 +-- electrum/lnpeer.py | 20 +- electrum/lnsweep.py | 28 +-- electrum/lnutil.py | 40 ++-- electrum/lnworker.py | 2 +- electrum/payment_identifier.py | 12 +- electrum/paymentrequest.py | 4 +- electrum/plugins/bitbox02/bitbox02.py | 4 +- electrum/plugins/coldcard/coldcard.py | 2 +- .../plugins/digitalbitbox/digitalbitbox.py | 6 +- electrum/plugins/jade/jade.py | 9 +- electrum/plugins/keepkey/keepkey.py | 4 +- electrum/plugins/ledger/ledger.py | 33 +-- electrum/plugins/safe_t/safe_t.py | 4 +- electrum/plugins/trezor/trezor.py | 4 +- electrum/scripts/peers.py | 2 +- electrum/submarine_swaps.py | 27 ++- electrum/transaction.py | 195 +++++++++--------- electrum/wallet.py | 16 +- electrum/wallet_db.py | 4 +- tests/test_bitcoin.py | 152 ++++++-------- tests/test_lnpeer.py | 2 +- tests/test_lnutil.py | 16 +- tests/test_transaction.py | 34 +-- 33 files changed, 423 insertions(+), 459 deletions(-) diff --git a/electrum/address_synchronizer.py b/electrum/address_synchronizer.py index 437da067b..413409b84 100644 --- a/electrum/address_synchronizer.py +++ b/electrum/address_synchronizer.py @@ -341,7 +341,7 @@ class AddressSynchronizer(Logger, EventListener): for n, txo in enumerate(tx.outputs()): v = txo.value ser = tx_hash + ':%d'%n - scripthash = bitcoin.script_to_scripthash(txo.scriptpubkey.hex()) + scripthash = bitcoin.script_to_scripthash(txo.scriptpubkey) self.db.add_prevout_by_scripthash(scripthash, prevout=TxOutpoint.from_str(ser), value=v) addr = txo.address if addr and self.is_mine(addr): @@ -407,7 +407,7 @@ class AddressSynchronizer(Logger, EventListener): self.unconfirmed_tx.pop(tx_hash, None) if tx: for idx, txo in enumerate(tx.outputs()): - scripthash = bitcoin.script_to_scripthash(txo.scriptpubkey.hex()) + scripthash = bitcoin.script_to_scripthash(txo.scriptpubkey) prevout = TxOutpoint(bfh(tx_hash), idx) self.db.remove_prevout_by_scripthash(scripthash, prevout=prevout, value=txo.value) util.trigger_callback('adb_removed_tx', self, tx_hash, tx) diff --git a/electrum/bip32.py b/electrum/bip32.py index 85cc27fdb..834b64155 100644 --- a/electrum/bip32.py +++ b/electrum/bip32.py @@ -11,7 +11,7 @@ from .util import bfh, BitcoinException from . import constants from . import ecc from .crypto import hash_160, hmac_oneshot -from .bitcoin import rev_hex, int_to_hex, EncodeBase58Check, DecodeBase58Check +from .bitcoin import EncodeBase58Check, DecodeBase58Check from .logging import get_logger @@ -49,7 +49,7 @@ def CKD_priv(parent_privkey: bytes, parent_chaincode: bytes, child_index: int) - is_hardened_child = bool(child_index & BIP32_PRIME) return _CKD_priv(parent_privkey=parent_privkey, parent_chaincode=parent_chaincode, - child_index=bfh(rev_hex(int_to_hex(child_index, 4))), + child_index=int.to_bytes(child_index, length=4, byteorder="big", signed=False), is_hardened_child=is_hardened_child) @@ -85,7 +85,7 @@ def CKD_pub(parent_pubkey: bytes, parent_chaincode: bytes, child_index: int) -> if child_index & BIP32_PRIME: raise Exception('not possible to derive hardened child from parent pubkey') return _CKD_pub(parent_pubkey=parent_pubkey, parent_chaincode=parent_chaincode, - child_index=bfh(rev_hex(int_to_hex(child_index, 4)))) + child_index=int.to_bytes(child_index, length=4, byteorder="big", signed=False)) # helper function, callable with arbitrary 'child_index' byte-string. diff --git a/electrum/bitcoin.py b/electrum/bitcoin.py index 43ada289a..9e5a57ad4 100644 --- a/electrum/bitcoin.py +++ b/electrum/bitcoin.py @@ -197,34 +197,14 @@ class opcodes(IntEnum): return bytes([self]).hex() -def rev_hex(s: str) -> str: - return bfh(s)[::-1].hex() - - -def int_to_hex(i: int, length: int=1) -> str: - """Converts int to little-endian hex string. - `length` is the number of bytes available - """ - if not isinstance(i, int): - raise TypeError('{} instead of int'.format(i)) - range_size = pow(256, length) - if i < -(range_size//2) or i >= range_size: - raise OverflowError('cannot convert int {} to hex ({} bytes)'.format(i, length)) - if i < 0: - # two's complement - i = range_size + i - s = hex(i)[2:].rstrip('L') - s = "0"*(2*length - len(s)) + s - return rev_hex(s) - -def script_num_to_hex(i: int) -> str: +def script_num_to_bytes(i: int) -> bytes: """See CScriptNum in Bitcoin Core. - Encodes an integer as hex, to be used in script. + Encodes an integer as bytes, to be used in script. ported from https://github.com/bitcoin/bitcoin/blob/8cbc5c4be4be22aca228074f087a374a7ec38be8/src/script/script.h#L326 """ if i == 0: - return '' + return b"" result = bytearray() neg = i < 0 @@ -238,104 +218,102 @@ def script_num_to_hex(i: int) -> str: elif neg: result[-1] |= 0x80 - return result.hex() + return bytes(result) -def var_int(i: int) -> str: +def var_int(i: int) -> bytes: # https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer # https://github.com/bitcoin/bitcoin/blob/efe1ee0d8d7f82150789f1f6840f139289628a2b/src/serialize.h#L247 # "CompactSize" assert i >= 0, i - if i<0xfd: - return int_to_hex(i) - elif i<=0xffff: - return "fd"+int_to_hex(i,2) - elif i<=0xffffffff: - return "fe"+int_to_hex(i,4) + if i < 0xfd: + return int.to_bytes(i, length=1, byteorder="little", signed=False) + elif i <= 0xffff: + return b"\xfd" + int.to_bytes(i, length=2, byteorder="little", signed=False) + elif i <= 0xffffffff: + return b"\xfe" + int.to_bytes(i, length=4, byteorder="little", signed=False) else: - return "ff"+int_to_hex(i,8) + return b"\xff" + int.to_bytes(i, length=8, byteorder="little", signed=False) -def witness_push(item: str) -> str: - """Returns data in the form it should be present in the witness. - hex -> hex - """ - return var_int(len(item) // 2) + item +def witness_push(item: bytes) -> bytes: + """Returns data in the form it should be present in the witness.""" + return var_int(len(item)) + item -def _op_push(i: int) -> str: +def _op_push(i: int) -> bytes: if i < opcodes.OP_PUSHDATA1: - return int_to_hex(i) + return int.to_bytes(i, length=1, byteorder="little", signed=False) elif i <= 0xff: - return opcodes.OP_PUSHDATA1.hex() + int_to_hex(i, 1) + return bytes([opcodes.OP_PUSHDATA1]) + int.to_bytes(i, length=1, byteorder="little", signed=False) elif i <= 0xffff: - return opcodes.OP_PUSHDATA2.hex() + int_to_hex(i, 2) + return bytes([opcodes.OP_PUSHDATA2]) + int.to_bytes(i, length=2, byteorder="little", signed=False) else: - return opcodes.OP_PUSHDATA4.hex() + int_to_hex(i, 4) + return bytes([opcodes.OP_PUSHDATA4]) + int.to_bytes(i, length=4, byteorder="little", signed=False) -def push_script(data: str) -> str: +def push_script(data: bytes) -> bytes: """Returns pushed data to the script, automatically choosing canonical opcodes depending on the length of the data. - hex -> hex ported from https://github.com/btcsuite/btcd/blob/fdc2bc867bda6b351191b5872d2da8270df00d13/txscript/scriptbuilder.go#L128 """ - data = bfh(data) data_len = len(data) # "small integer" opcodes if data_len == 0 or data_len == 1 and data[0] == 0: - return opcodes.OP_0.hex() + return bytes([opcodes.OP_0]) elif data_len == 1 and data[0] <= 16: - return bytes([opcodes.OP_1 - 1 + data[0]]).hex() + return bytes([opcodes.OP_1 - 1 + data[0]]) elif data_len == 1 and data[0] == 0x81: - return opcodes.OP_1NEGATE.hex() + return bytes([opcodes.OP_1NEGATE]) - return _op_push(data_len) + data.hex() + return _op_push(data_len) + data -def make_op_return(x:bytes) -> bytes: - return bytes([opcodes.OP_RETURN]) + bytes.fromhex(push_script(x.hex())) +def make_op_return(x: bytes) -> bytes: + return bytes([opcodes.OP_RETURN]) + push_script(x) def add_number_to_script(i: int) -> bytes: - return bfh(push_script(script_num_to_hex(i))) + return push_script(script_num_to_bytes(i)) -def construct_witness(items: Sequence[Union[str, int, bytes]]) -> str: +def construct_witness(items: Sequence[Union[str, int, bytes]]) -> bytes: """Constructs a witness from the given stack items.""" - witness = var_int(len(items)) + witness = bytearray() + witness += var_int(len(items)) for item in items: if type(item) is int: - item = script_num_to_hex(item) + item = script_num_to_bytes(item) elif isinstance(item, (bytes, bytearray)): - item = item.hex() + pass # use as-is else: - assert is_hex_str(item) + assert is_hex_str(item), repr(item) + item = bfh(item) witness += witness_push(item) - return witness + return bytes(witness) -def construct_script(items: Sequence[Union[str, int, bytes, opcodes]], values=None) -> str: +def construct_script(items: Sequence[Union[str, int, bytes, opcodes]], values=None) -> bytes: """Constructs bitcoin script from given items.""" - script = '' + script = bytearray() values = values or {} for i, item in enumerate(items): if i in values: item = values[i] if isinstance(item, opcodes): - script += item.hex() + script += bytes([item]) elif type(item) is int: - script += add_number_to_script(item).hex() + script += add_number_to_script(item) elif isinstance(item, (bytes, bytearray)): - script += push_script(item.hex()) + script += push_script(item) elif isinstance(item, str): assert is_hex_str(item) - script += push_script(item) + script += push_script(bfh(item)) else: raise Exception(f'unexpected item for script: {item!r}') - return script + return bytes(script) def relayfee(network: 'Network' = None) -> int: @@ -412,15 +390,11 @@ def hash_to_segwit_addr(h: bytes, witver: int, *, net=None) -> str: def public_key_to_p2wpkh(public_key: bytes, *, net=None) -> str: return hash_to_segwit_addr(hash_160(public_key), witver=0, net=net) -def script_to_p2wsh(script: str, *, net=None) -> str: - return hash_to_segwit_addr(sha256(bfh(script)), witver=0, net=net) - -def p2wpkh_nested_script(pubkey: str) -> str: - pkh = hash_160(bfh(pubkey)) - return construct_script([0, pkh]) +def script_to_p2wsh(script: bytes, *, net=None) -> str: + return hash_to_segwit_addr(sha256(script), witver=0, net=net) -def p2wsh_nested_script(witness_script: str) -> str: - wsh = sha256(bfh(witness_script)) +def p2wsh_nested_script(witness_script: bytes) -> bytes: + wsh = sha256(witness_script) return construct_script([0, wsh]) def pubkey_to_address(txin_type: str, pubkey: str, *, net=None) -> str: @@ -430,27 +404,28 @@ def pubkey_to_address(txin_type: str, pubkey: str, *, net=None) -> str: # TODO this method is confusingly named -def redeem_script_to_address(txin_type: str, scriptcode: str, *, net=None) -> str: +def redeem_script_to_address(txin_type: str, scriptcode: bytes, *, net=None) -> str: + assert isinstance(scriptcode, bytes) if txin_type == 'p2sh': # given scriptcode is a redeem_script - return hash160_to_p2sh(hash_160(bfh(scriptcode)), net=net) + return hash160_to_p2sh(hash_160(scriptcode), net=net) elif txin_type == 'p2wsh': # given scriptcode is a witness_script return script_to_p2wsh(scriptcode, net=net) elif txin_type == 'p2wsh-p2sh': # given scriptcode is a witness_script redeem_script = p2wsh_nested_script(scriptcode) - return hash160_to_p2sh(hash_160(bfh(redeem_script)), net=net) + return hash160_to_p2sh(hash_160(redeem_script), net=net) else: raise NotImplementedError(txin_type) -def script_to_address(script: str, *, net=None) -> Optional[str]: +def script_to_address(script: bytes, *, net=None) -> Optional[str]: from .transaction import get_address_from_output_script - return get_address_from_output_script(bfh(script), net=net) + return get_address_from_output_script(script, net=net) -def address_to_script(addr: str, *, net=None) -> str: +def address_to_script(addr: str, *, net=None) -> bytes: if net is None: net = constants.net if not is_address(addr, net=net): raise BitcoinException(f"invalid bitcoin address: {addr}") @@ -461,7 +436,7 @@ def address_to_script(addr: str, *, net=None) -> str: return construct_script([witver, bytes(witprog)]) addrtype, hash_160_ = b58_address_to_hash160(addr) if addrtype == net.ADDRTYPE_P2PKH: - script = pubkeyhash_to_p2pkh_script(hash_160_.hex()) + script = pubkeyhash_to_p2pkh_script(hash_160_) elif addrtype == net.ADDRTYPE_P2SH: script = construct_script([opcodes.OP_HASH160, hash_160_, opcodes.OP_EQUAL]) else: @@ -514,14 +489,12 @@ def address_to_scripthash(addr: str, *, net=None) -> str: return script_to_scripthash(script) -def script_to_scripthash(script: str) -> str: - h = sha256(bfh(script))[0:32] +def script_to_scripthash(script: bytes) -> str: + h = sha256(script) return h[::-1].hex() -def public_key_to_p2pk_script(pubkey: str) -> str: - return construct_script([pubkey, opcodes.OP_CHECKSIG]) -def pubkeyhash_to_p2pkh_script(pubkey_hash160: str) -> str: +def pubkeyhash_to_p2pkh_script(pubkey_hash160: bytes) -> bytes: return construct_script([ opcodes.OP_DUP, opcodes.OP_HASH160, @@ -756,7 +729,7 @@ def minikey_to_private_key(text: str) -> bytes: def _get_dummy_address(purpose: str) -> str: - return redeem_script_to_address('p2wsh', sha256(bytes(purpose, "utf8")).hex()) + return redeem_script_to_address('p2wsh', sha256(bytes(purpose, "utf8"))) _dummy_addr_funcs = set() class DummyAddress: diff --git a/electrum/blockchain.py b/electrum/blockchain.py index f7af34da1..65b83f554 100644 --- a/electrum/blockchain.py +++ b/electrum/blockchain.py @@ -26,7 +26,7 @@ import time from typing import Optional, Dict, Mapping, Sequence, TYPE_CHECKING from . import util -from .bitcoin import hash_encode, int_to_hex, rev_hex +from .bitcoin import hash_encode from .crypto import sha256d from . import constants from .util import bfh, with_lock @@ -49,13 +49,14 @@ class MissingHeader(Exception): class InvalidHeader(Exception): pass -def serialize_header(header_dict: dict) -> str: - s = int_to_hex(header_dict['version'], 4) \ - + rev_hex(header_dict['prev_block_hash']) \ - + rev_hex(header_dict['merkle_root']) \ - + int_to_hex(int(header_dict['timestamp']), 4) \ - + int_to_hex(int(header_dict['bits']), 4) \ - + int_to_hex(int(header_dict['nonce']), 4) +def serialize_header(header_dict: dict) -> bytes: + s = ( + int.to_bytes(header_dict['version'], length=4, byteorder="little", signed=False) + + bfh(header_dict['prev_block_hash'])[::-1] + + bfh(header_dict['merkle_root'])[::-1] + + int.to_bytes(int(header_dict['timestamp']), length=4, byteorder="little", signed=False) + + int.to_bytes(int(header_dict['bits']), length=4, byteorder="little", signed=False) + + int.to_bytes(int(header_dict['nonce']), length=4, byteorder="little", signed=False)) return s def deserialize_header(s: bytes, height: int) -> dict: @@ -63,14 +64,13 @@ def deserialize_header(s: bytes, height: int) -> dict: raise InvalidHeader('Invalid header: {}'.format(s)) if len(s) != HEADER_SIZE: raise InvalidHeader('Invalid header length: {}'.format(len(s))) - hex_to_int = lambda s: int.from_bytes(s, byteorder='little') h = {} - h['version'] = hex_to_int(s[0:4]) + h['version'] = int.from_bytes(s[0:4], byteorder='little') h['prev_block_hash'] = hash_encode(s[4:36]) h['merkle_root'] = hash_encode(s[36:68]) - h['timestamp'] = hex_to_int(s[68:72]) - h['bits'] = hex_to_int(s[72:76]) - h['nonce'] = hex_to_int(s[76:80]) + h['timestamp'] = int.from_bytes(s[68:72], byteorder='little') + h['bits'] = int.from_bytes(s[72:76], byteorder='little') + h['nonce'] = int.from_bytes(s[76:80], byteorder='little') h['block_height'] = height return h @@ -82,8 +82,9 @@ def hash_header(header: dict) -> str: return hash_raw_header(serialize_header(header)) -def hash_raw_header(header: str) -> str: - return hash_encode(sha256d(bfh(header))) +def hash_raw_header(header: bytes) -> str: + assert isinstance(header, bytes) + return hash_encode(sha256d(header)) pow_hash_header = hash_header @@ -413,7 +414,7 @@ class Blockchain(Logger): # swap parameters self.parent, parent.parent = parent.parent, self # type: Optional[Blockchain], Optional[Blockchain] self.forkpoint, parent.forkpoint = parent.forkpoint, self.forkpoint - self._forkpoint_hash, parent._forkpoint_hash = parent._forkpoint_hash, hash_raw_header(parent_data[:HEADER_SIZE].hex()) + self._forkpoint_hash, parent._forkpoint_hash = parent._forkpoint_hash, hash_raw_header(parent_data[:HEADER_SIZE]) self._prev_hash, parent._prev_hash = parent._prev_hash, self._prev_hash # parent's new name os.replace(child_old_name, parent.path()) @@ -454,7 +455,7 @@ class Blockchain(Logger): @with_lock def save_header(self, header: dict) -> None: delta = header.get('block_height') - self.forkpoint - data = bfh(serialize_header(header)) + data = serialize_header(header) # headers are only _appended_ to the end: assert delta == self.size(), (delta, self.size()) assert len(data) == HEADER_SIZE diff --git a/electrum/commands.py b/electrum/commands.py index 14b655dc7..dd7f1a586 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -494,8 +494,8 @@ class Commands: """Create multisig address""" assert isinstance(pubkeys, list), (type(num), type(pubkeys)) redeem_script = multisig_script(pubkeys, num) - address = bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script))) - return {'address':address, 'redeemScript':redeem_script} + address = bitcoin.hash160_to_p2sh(hash_160(redeem_script)) + return {'address': address, 'redeemScript': redeem_script.hex()} @command('w') async def freeze(self, address: str, wallet: Abstract_Wallet = None): diff --git a/electrum/constants.py b/electrum/constants.py index 9e379755d..445bd8490 100644 --- a/electrum/constants.py +++ b/electrum/constants.py @@ -74,7 +74,7 @@ class AbstractNet: @classmethod def rev_genesis_bytes(cls) -> bytes: - return bytes.fromhex(bitcoin.rev_hex(cls.GENESIS)) + return bytes.fromhex(cls.GENESIS)[::-1] class BitcoinMainnet(AbstractNet): diff --git a/electrum/descriptor.py b/electrum/descriptor.py index 3bce2da20..e2904f3f2 100644 --- a/electrum/descriptor.py +++ b/electrum/descriptor.py @@ -75,7 +75,7 @@ class ExpandedScripts: self._scriptcode_for_sighash = value def address(self, *, net=None) -> Optional[str]: - return bitcoin.script_to_address(self.output_script.hex(), net=net) + return bitcoin.script_to_address(self.output_script, net=net) class ScriptSolutionInner(NamedTuple): @@ -379,9 +379,9 @@ class Descriptor(object): witness = None script_sig = None if self.is_segwit(): - witness = bfh(construct_witness(sol.witness_items)) + witness = construct_witness(sol.witness_items) else: - script_sig = bfh(construct_script(sol.witness_items)) + script_sig = construct_script(sol.witness_items) return ScriptSolutionTop( witness=witness, script_sig=script_sig, @@ -473,7 +473,7 @@ class PKDescriptor(Descriptor): def expand(self, *, pos: Optional[int] = None) -> "ExpandedScripts": pubkey = self.pubkeys[0].get_pubkey_bytes(pos=pos) script = construct_script([pubkey, opcodes.OP_CHECKSIG]) - return ExpandedScripts(output_script=bytes.fromhex(script)) + return ExpandedScripts(output_script=script) def _satisfy_inner(self, *, sigdata=None, allow_dummy=False) -> ScriptSolutionInner: if sigdata is None: sigdata = {} @@ -513,9 +513,9 @@ class PKHDescriptor(Descriptor): def expand(self, *, pos: Optional[int] = None) -> "ExpandedScripts": pubkey = self.pubkeys[0].get_pubkey_bytes(pos=pos) - pkh = hash_160(pubkey).hex() + pkh = hash_160(pubkey) script = bitcoin.pubkeyhash_to_p2pkh_script(pkh) - return ExpandedScripts(output_script=bytes.fromhex(script)) + return ExpandedScripts(output_script=script) def _satisfy_inner(self, *, sigdata=None, allow_dummy=False) -> ScriptSolutionInner: if sigdata is None: sigdata = {} @@ -556,10 +556,10 @@ class WPKHDescriptor(Descriptor): def expand(self, *, pos: Optional[int] = None) -> "ExpandedScripts": pkh = hash_160(self.pubkeys[0].get_pubkey_bytes(pos=pos)) output_script = construct_script([0, pkh]) - scriptcode = bitcoin.pubkeyhash_to_p2pkh_script(pkh.hex()) + scriptcode = bitcoin.pubkeyhash_to_p2pkh_script(pkh) return ExpandedScripts( - output_script=bytes.fromhex(output_script), - scriptcode_for_sighash=bytes.fromhex(scriptcode), + output_script=output_script, + scriptcode_for_sighash=scriptcode, ) def _satisfy_inner(self, *, sigdata=None, allow_dummy=False) -> ScriptSolutionInner: @@ -625,7 +625,7 @@ class MultisigDescriptor(Descriptor): der_pks = [p.get_pubkey_bytes(pos=pos) for p in self.pubkeys] if self.is_sorted: der_pks.sort() - script = bfh(construct_script([self.thresh, *der_pks, len(der_pks), opcodes.OP_CHECKMULTISIG])) + script = construct_script([self.thresh, *der_pks, len(der_pks), opcodes.OP_CHECKMULTISIG]) return ExpandedScripts(output_script=script) def _satisfy_inner(self, *, sigdata=None, allow_dummy=False) -> ScriptSolutionInner: @@ -678,7 +678,7 @@ class SHDescriptor(Descriptor): sub_scripts = self.subdescriptors[0].expand(pos=pos) redeem_script = sub_scripts.output_script witness_script = sub_scripts.witness_script - script = bfh(construct_script([opcodes.OP_HASH160, hash_160(redeem_script), opcodes.OP_EQUAL])) + script = construct_script([opcodes.OP_HASH160, hash_160(redeem_script), opcodes.OP_EQUAL]) return ExpandedScripts( output_script=script, redeem_script=redeem_script, @@ -697,10 +697,10 @@ class SHDescriptor(Descriptor): witness = None if isinstance(subdesc, (WSHDescriptor, WPKHDescriptor)): # witness_v0 nested in p2sh witness = subdesc.satisfy(sigdata=sigdata, allow_dummy=allow_dummy).witness - script_sig = bfh(construct_script([redeem_script])) + script_sig = construct_script([redeem_script]) else: # legacy p2sh subsol = subdesc._satisfy_inner(sigdata=sigdata, allow_dummy=allow_dummy) - script_sig = bfh(construct_script([*subsol.witness_items, redeem_script])) + script_sig = construct_script([*subsol.witness_items, redeem_script]) return ScriptSolutionTop( witness=witness, script_sig=script_sig, @@ -724,7 +724,7 @@ class WSHDescriptor(Descriptor): assert len(self.subdescriptors) == 1 sub_scripts = self.subdescriptors[0].expand(pos=pos) witness_script = sub_scripts.output_script - output_script = bfh(construct_script([0, sha256(witness_script)])) + output_script = construct_script([0, sha256(witness_script)]) return ExpandedScripts( output_script=output_script, witness_script=witness_script, @@ -740,7 +740,7 @@ class WSHDescriptor(Descriptor): witness_script = self.expand().witness_script witness = construct_witness([*subsol.witness_items, witness_script]) return ScriptSolutionTop( - witness=bytes.fromhex(witness), + witness=witness, ) def is_segwit(self) -> bool: diff --git a/electrum/ecc.py b/electrum/ecc.py index b28759f76..99d2b4e2f 100644 --- a/electrum/ecc.py +++ b/electrum/ecc.py @@ -415,7 +415,7 @@ POINT_AT_INFINITY = ECPubkey(None) def usermessage_magic(message: bytes) -> bytes: from .bitcoin import var_int - length = bfh(var_int(len(message))) + length = var_int(len(message)) return b"\x18Bitcoin Signed Message:\n" + length + message diff --git a/electrum/gui/text.py b/electrum/gui/text.py index c5280f7a4..e34dbca93 100644 --- a/electrum/gui/text.py +++ b/electrum/gui/text.py @@ -624,7 +624,7 @@ class ElectrumGui(BaseElectrumGui, EventListener): return elif is_address(self.str_recipient): amount_sat = self.parse_amount(self.str_amount) - scriptpubkey = bytes.fromhex(address_to_script(self.str_recipient)) + scriptpubkey = address_to_script(self.str_recipient) outputs = [PartialTxOutput(scriptpubkey=scriptpubkey, value=amount_sat)] invoice = self.wallet.create_invoice( outputs=outputs, diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py index f135dbe4d..ef171eacd 100644 --- a/electrum/lnchannel.py +++ b/electrum/lnchannel.py @@ -1099,7 +1099,7 @@ class Channel(AbstractChannel): commit=pending_remote_commitment, ctx_output_idx=ctx_output_idx, htlc=htlc) - sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey)) + sig = htlc_tx.sign_txin(0, their_remote_htlc_privkey) htlc_sig = ecc.ecdsa_sig64_from_der_sig(sig[:-1]) htlcsigs.append((ctx_output_idx, htlc_sig)) htlcsigs.sort() @@ -1121,13 +1121,13 @@ class Channel(AbstractChannel): assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes pending_local_commitment = self.get_next_commitment(LOCAL) - preimage_hex = pending_local_commitment.serialize_preimage(0) - pre_hash = sha256d(bfh(preimage_hex)) - if not ECPubkey(self.config[REMOTE].multisig_key.pubkey).ecdsa_verify(sig, pre_hash): + pre_hash = pending_local_commitment.serialize_preimage(0) + msg_hash = sha256d(pre_hash) + if not ECPubkey(self.config[REMOTE].multisig_key.pubkey).ecdsa_verify(sig, msg_hash): raise LNProtocolWarning( f'failed verifying signature for our updated commitment transaction. ' f'sig={sig.hex()}. ' - f'pre_hash={pre_hash.hex()}. ' + f'msg_hash={msg_hash.hex()}. ' f'pubkey={self.config[REMOTE].multisig_key.pubkey}. ' f'ctx={pending_local_commitment.serialize()} ' ) @@ -1167,16 +1167,16 @@ class Channel(AbstractChannel): commit=ctx, ctx_output_idx=ctx_output_idx, htlc=htlc) - preimage_hex = htlc_tx.serialize_preimage(0) - pre_hash = sha256d(bfh(preimage_hex)) + pre_hash = htlc_tx.serialize_preimage(0) + msg_hash = sha256d(pre_hash) remote_htlc_pubkey = derive_pubkey(self.config[REMOTE].htlc_basepoint.pubkey, pcp) - if not ECPubkey(remote_htlc_pubkey).ecdsa_verify(htlc_sig, pre_hash): + if not ECPubkey(remote_htlc_pubkey).ecdsa_verify(htlc_sig, msg_hash): raise LNProtocolWarning( f'failed verifying HTLC signatures: {htlc=}, {htlc_direction=}. ' f'htlc_tx={htlc_tx.serialize()}. ' f'htlc_sig={htlc_sig.hex()}. ' f'remote_htlc_pubkey={remote_htlc_pubkey.hex()}. ' - f'pre_hash={pre_hash.hex()}. ' + f'msg_hash={msg_hash.hex()}. ' f'ctx={ctx.serialize()}. ' f'ctx_output_idx={ctx_output_idx}. ' f'ctn={ctn}. ' @@ -1587,8 +1587,8 @@ class Channel(AbstractChannel): }, local_amount_msat=self.balance(LOCAL), remote_amount_msat=self.balance(REMOTE) if not drop_remote else 0, - local_script=local_script.hex(), - remote_script=remote_script.hex(), + local_script=local_script, + remote_script=remote_script, htlcs=[], dust_limit_sat=self.config[LOCAL].dust_limit_sat) @@ -1599,14 +1599,14 @@ class Channel(AbstractChannel): funding_sat=self.constraints.capacity, outputs=outputs) - der_sig = bfh(closing_tx.sign_txin(0, self.config[LOCAL].multisig_key.privkey)) + der_sig = closing_tx.sign_txin(0, self.config[LOCAL].multisig_key.privkey) sig = ecc.ecdsa_sig64_from_der_sig(der_sig[:-1]) return sig, closing_tx def signature_fits(self, tx: PartialTransaction) -> bool: remote_sig = self.config[LOCAL].current_commitment_signature - preimage_hex = tx.serialize_preimage(0) - msg_hash = sha256d(bfh(preimage_hex)) + pre_hash = tx.serialize_preimage(0) + msg_hash = sha256d(pre_hash) assert remote_sig res = ECPubkey(self.config[REMOTE].multisig_key.pubkey).ecdsa_verify(remote_sig, msg_hash) return res @@ -1618,8 +1618,8 @@ class Channel(AbstractChannel): remote_sig = self.config[LOCAL].current_commitment_signature remote_sig = ecc.ecdsa_der_sig_from_ecdsa_sig64(remote_sig) + Sighash.to_sigbytes(Sighash.ALL) tx.add_signature_to_txin(txin_idx=0, - signing_pubkey=self.config[REMOTE].multisig_key.pubkey.hex(), - sig=remote_sig.hex()) + signing_pubkey=self.config[REMOTE].multisig_key.pubkey, + sig=remote_sig) assert tx.is_complete() return tx diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py index 6f9e2c0ad..23819da5f 100644 --- a/electrum/lnpeer.py +++ b/electrum/lnpeer.py @@ -2368,7 +2368,7 @@ class Peer(Logger): if chan.config[LOCAL].upfront_shutdown_script: scriptpubkey = chan.config[LOCAL].upfront_shutdown_script else: - scriptpubkey = bfh(bitcoin.address_to_script(chan.sweep_address)) + scriptpubkey = bitcoin.address_to_script(chan.sweep_address) assert scriptpubkey # wait until no more pending updates (bolt2) chan.set_can_send_ctx_updates(False) @@ -2421,7 +2421,7 @@ class Peer(Logger): if chan.config[LOCAL].upfront_shutdown_script: our_scriptpubkey = chan.config[LOCAL].upfront_shutdown_script else: - our_scriptpubkey = bfh(bitcoin.address_to_script(chan.sweep_address)) + our_scriptpubkey = bitcoin.address_to_script(chan.sweep_address) assert our_scriptpubkey # estimate fee of closing tx dummy_sig, dummy_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=0) @@ -2448,9 +2448,9 @@ class Peer(Logger): def verify_signature(tx: 'PartialTransaction', sig) -> bool: their_pubkey = chan.config[REMOTE].multisig_key.pubkey - preimage_hex = tx.serialize_preimage(0) - pre_hash = sha256d(bfh(preimage_hex)) - return ECPubkey(their_pubkey).ecdsa_verify(sig, pre_hash) + pre_hash = tx.serialize_preimage(0) + msg_hash = sha256d(pre_hash) + return ECPubkey(their_pubkey).ecdsa_verify(sig, msg_hash) async def receive_closing_signed(): nonlocal our_sig, closing_tx @@ -2476,7 +2476,7 @@ class Peer(Logger): raise Exception('failed to verify their signature') # at this point we know how the closing tx looks like # check that their output is above their scriptpubkey's network dust limit - to_remote_set = closing_tx.get_output_idxs_from_scriptpubkey(their_scriptpubkey.hex()) + to_remote_set = closing_tx.get_output_idxs_from_scriptpubkey(their_scriptpubkey) if not drop_remote and to_remote_set: to_remote_idx = to_remote_set.pop() to_remote_amount = closing_tx.outputs()[to_remote_idx].value @@ -2570,12 +2570,12 @@ class Peer(Logger): # add signatures closing_tx.add_signature_to_txin( txin_idx=0, - signing_pubkey=chan.config[LOCAL].multisig_key.pubkey.hex(), - sig=(ecdsa_der_sig_from_ecdsa_sig64(our_sig) + Sighash.to_sigbytes(Sighash.ALL)).hex()) + signing_pubkey=chan.config[LOCAL].multisig_key.pubkey, + sig=ecdsa_der_sig_from_ecdsa_sig64(our_sig) + Sighash.to_sigbytes(Sighash.ALL)) closing_tx.add_signature_to_txin( txin_idx=0, - signing_pubkey=chan.config[REMOTE].multisig_key.pubkey.hex(), - sig=(ecdsa_der_sig_from_ecdsa_sig64(their_sig) + Sighash.to_sigbytes(Sighash.ALL)).hex()) + signing_pubkey=chan.config[REMOTE].multisig_key.pubkey, + sig=ecdsa_der_sig_from_ecdsa_sig64(their_sig) + Sighash.to_sigbytes(Sighash.ALL)) # save local transaction and set state try: self.lnworker.wallet.adb.add_transaction(closing_tx) diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py index fe5f78fb7..539d39c28 100644 --- a/electrum/lnsweep.py +++ b/electrum/lnsweep.py @@ -55,7 +55,7 @@ def create_sweeptxs_for_watchtower(chan: 'Channel', ctx: Transaction, per_commit # to_local revocation_pubkey = ecc.ECPrivkey(other_revocation_privkey).get_public_key_bytes(compressed=True) witness_script = make_commitment_output_to_local_witness_script( - revocation_pubkey, to_self_delay, this_delayed_pubkey).hex() + revocation_pubkey, to_self_delay, this_delayed_pubkey) to_local_address = redeem_script_to_address('p2wsh', witness_script) output_idxs = ctx.get_output_idxs_from_address(to_local_address) if output_idxs: @@ -64,7 +64,7 @@ def create_sweeptxs_for_watchtower(chan: 'Channel', ctx: Transaction, per_commit sweep_address=sweep_address, ctx=ctx, output_idx=output_idx, - witness_script=bfh(witness_script), + witness_script=witness_script, privkey=other_revocation_privkey, is_revocation=True, config=chan.lnworker.config) @@ -122,7 +122,7 @@ def create_sweeptx_for_their_revoked_ctx( # to_local revocation_pubkey = ecc.ECPrivkey(other_revocation_privkey).get_public_key_bytes(compressed=True) witness_script = make_commitment_output_to_local_witness_script( - revocation_pubkey, to_self_delay, this_delayed_pubkey).hex() + revocation_pubkey, to_self_delay, this_delayed_pubkey) to_local_address = redeem_script_to_address('p2wsh', witness_script) output_idxs = ctx.get_output_idxs_from_address(to_local_address) if output_idxs: @@ -131,7 +131,7 @@ def create_sweeptx_for_their_revoked_ctx( sweep_address=sweep_address, ctx=ctx, output_idx=output_idx, - witness_script=bfh(witness_script), + witness_script=witness_script, privkey=other_revocation_privkey, is_revocation=True, config=chan.lnworker.config) @@ -162,7 +162,7 @@ def create_sweeptx_for_their_revoked_htlc( # same witness script as to_local revocation_pubkey = ecc.ECPrivkey(other_revocation_privkey).get_public_key_bytes(compressed=True) witness_script = make_commitment_output_to_local_witness_script( - revocation_pubkey, to_self_delay, this_delayed_pubkey).hex() + revocation_pubkey, to_self_delay, this_delayed_pubkey) htlc_address = redeem_script_to_address('p2wsh', witness_script) # check that htlc_tx is a htlc if htlc_tx.outputs()[0].address != htlc_address: @@ -170,7 +170,7 @@ def create_sweeptx_for_their_revoked_htlc( gen_tx = lambda: create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx( sweep_address=sweep_address, htlc_tx=htlc_tx, - htlctx_witness_script=bfh(witness_script), + htlctx_witness_script=witness_script, privkey=other_revocation_privkey, is_revocation=True, config=chan.lnworker.config) @@ -204,7 +204,7 @@ def create_sweeptxs_for_our_ctx( per_commitment_point=our_pcp).to_bytes(32, 'big') our_localdelayed_pubkey = our_localdelayed_privkey.get_public_key_bytes(compressed=True) to_local_witness_script = make_commitment_output_to_local_witness_script( - their_revocation_pubkey, to_self_delay, our_localdelayed_pubkey).hex() + their_revocation_pubkey, to_self_delay, our_localdelayed_pubkey) to_local_address = redeem_script_to_address('p2wsh', to_local_witness_script) to_remote_address = None # test if this is our_ctx @@ -231,7 +231,7 @@ def create_sweeptxs_for_our_ctx( sweep_address=sweep_address, ctx=ctx, output_idx=output_idx, - witness_script=bfh(to_local_witness_script), + witness_script=to_local_witness_script, privkey=our_localdelayed_privkey.get_secret_bytes(), is_revocation=False, to_self_delay=to_self_delay, @@ -358,7 +358,7 @@ def create_sweeptxs_for_their_ctx( our_revocation_pubkey = derive_blinded_pubkey(our_conf.revocation_basepoint.pubkey, their_pcp) their_delayed_pubkey = derive_pubkey(their_conf.delayed_basepoint.pubkey, their_pcp) witness_script = make_commitment_output_to_local_witness_script( - our_revocation_pubkey, our_conf.to_self_delay, their_delayed_pubkey).hex() + our_revocation_pubkey, our_conf.to_self_delay, their_delayed_pubkey) to_local_address = redeem_script_to_address('p2wsh', witness_script) to_remote_address = None # test if this is their ctx @@ -469,9 +469,9 @@ def create_htlctx_that_spends_from_our_ctx( ctx_output_idx=ctx_output_idx, name=f'our_ctx_{ctx_output_idx}_htlc_tx_{htlc.payment_hash.hex()}') remote_htlc_sig = chan.get_remote_htlc_sig_for_htlc(htlc_relative_idx=htlc_relative_idx) - local_htlc_sig = bfh(htlc_tx.sign_txin(0, local_htlc_privkey)) + local_htlc_sig = htlc_tx.sign_txin(0, local_htlc_privkey) txin = htlc_tx.inputs()[0] - witness_program = bfh(Transaction.get_preimage_script(txin)) + witness_program = Transaction.get_preimage_script(txin) txin.witness = make_htlc_tx_witness(remote_htlc_sig, local_htlc_sig, preimage, witness_program) return witness_script, htlc_tx @@ -496,13 +496,13 @@ def create_sweeptx_their_ctx_htlc( if outvalue <= dust_threshold(): return None sweep_outputs = [PartialTxOutput.from_address_and_value(sweep_address, outvalue)] tx = PartialTransaction.from_io(sweep_inputs, sweep_outputs, version=2, locktime=cltv_abs) - sig = bfh(tx.sign_txin(0, privkey)) + sig = tx.sign_txin(0, privkey) if not is_revocation: witness = construct_witness([sig, preimage, witness_script]) else: revocation_pubkey = privkey_to_pubkey(privkey) witness = construct_witness([sig, revocation_pubkey, witness_script]) - tx.inputs()[0].witness = bfh(witness) + tx.inputs()[0].witness = witness assert tx.is_complete() return tx @@ -561,7 +561,7 @@ def create_sweeptx_ctx_to_local( sweep_tx = PartialTransaction.from_io(sweep_inputs, sweep_outputs, version=2) sig = sweep_tx.sign_txin(0, privkey) witness = construct_witness([sig, int(is_revocation), witness_script]) - sweep_tx.inputs()[0].witness = bfh(witness) + sweep_tx.inputs()[0].witness = witness return sweep_tx diff --git a/electrum/lnutil.py b/electrum/lnutil.py index bbcd9e5aa..44aad8fb8 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -24,7 +24,7 @@ from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOut from .ecc import CURVE_ORDER, ecdsa_sig64_from_der_sig, ECPubkey, string_to_number from . import ecc, bitcoin, crypto, transaction from . import descriptor -from .bitcoin import (push_script, redeem_script_to_address, address_to_script, +from .bitcoin import (redeem_script_to_address, address_to_script, construct_witness, construct_script) from . import segwit_addr from .i18n import _ @@ -591,7 +591,9 @@ def derive_blinded_privkey(basepoint_secret: bytes, per_commitment_secret: bytes return int.to_bytes(sum, length=32, byteorder='big', signed=False) -def make_htlc_tx_output(amount_msat, local_feerate, revocationpubkey, local_delayedpubkey, success, to_self_delay): +def make_htlc_tx_output( + amount_msat, local_feerate, revocationpubkey, local_delayedpubkey, success, to_self_delay, +) -> Tuple[bytes, PartialTxOutput]: assert type(amount_msat) is int assert type(local_feerate) is int script = make_commitment_output_to_local_witness_script( @@ -600,7 +602,7 @@ def make_htlc_tx_output(amount_msat, local_feerate, revocationpubkey, local_dela delayed_pubkey=local_delayedpubkey, ) - p2wsh = bitcoin.redeem_script_to_address('p2wsh', script.hex()) + p2wsh = bitcoin.redeem_script_to_address('p2wsh', script) weight = HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT fee = local_feerate * weight fee = fee // 1000 * 1000 @@ -615,7 +617,7 @@ def make_htlc_tx_witness(remotehtlcsig: bytes, localhtlcsig: bytes, assert type(localhtlcsig) is bytes assert type(payment_preimage) is bytes assert type(witness_script) is bytes - return bfh(construct_witness([0, remotehtlcsig, localhtlcsig, payment_preimage, witness_script])) + return construct_witness([0, remotehtlcsig, localhtlcsig, payment_preimage, witness_script]) def make_htlc_tx_inputs(htlc_output_txid: str, htlc_output_index: int, amount_msat: int, witness_script: str) -> List[PartialTxInput]: @@ -648,7 +650,7 @@ def make_offered_htlc( assert type(remote_htlcpubkey) is bytes assert type(local_htlcpubkey) is bytes assert type(payment_hash) is bytes - script = bfh(construct_script([ + script = construct_script([ opcodes.OP_DUP, opcodes.OP_HASH160, bitcoin.hash_160(revocation_pubkey), @@ -675,7 +677,7 @@ def make_offered_htlc( opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF, - ])) + ]) return script def make_received_htlc( @@ -690,7 +692,7 @@ def make_received_htlc( assert type(i) is bytes assert type(cltv_abs) is int - script = bfh(construct_script([ + script = construct_script([ opcodes.OP_DUP, opcodes.OP_HASH160, bitcoin.hash_160(revocation_pubkey), @@ -720,7 +722,7 @@ def make_received_htlc( opcodes.OP_CHECKSIG, opcodes.OP_ENDIF, opcodes.OP_ENDIF, - ])) + ]) return script WITNESS_TEMPLATE_OFFERED_HTLC = [ @@ -830,7 +832,7 @@ def possible_output_idxs_of_htlc_in_ctx(*, chan: 'Channel', pcp: bytes, subject: local_htlc_pubkey=htlc_pubkey, payment_hash=payment_hash, cltv_abs=cltv_abs) - htlc_address = redeem_script_to_address('p2wsh', preimage_script.hex()) + htlc_address = redeem_script_to_address('p2wsh', preimage_script) candidates = ctx.get_output_idxs_from_address(htlc_address) return {output_idx for output_idx in candidates if ctx.outputs()[output_idx].value == htlc.amount_msat // 1000} @@ -938,20 +940,20 @@ LOCAL = HTLCOwner.LOCAL REMOTE = HTLCOwner.REMOTE def make_commitment_outputs(*, fees_per_participant: Mapping[HTLCOwner, int], local_amount_msat: int, remote_amount_msat: int, - local_script: str, remote_script: str, htlcs: List[ScriptHtlc], dust_limit_sat: int) -> Tuple[List[PartialTxOutput], List[PartialTxOutput]]: + local_script: bytes, remote_script: bytes, htlcs: List[ScriptHtlc], dust_limit_sat: int) -> Tuple[List[PartialTxOutput], List[PartialTxOutput]]: # BOLT-03: "Base commitment transaction fees are extracted from the funder's amount; # if that amount is insufficient, the entire amount of the funder's output is used." # -> if funder cannot afford feerate, their output might go negative, so take max(0, x) here: to_local_amt = max(0, local_amount_msat - fees_per_participant[LOCAL]) - to_local = PartialTxOutput(scriptpubkey=bfh(local_script), value=to_local_amt // 1000) + to_local = PartialTxOutput(scriptpubkey=local_script, value=to_local_amt // 1000) to_remote_amt = max(0, remote_amount_msat - fees_per_participant[REMOTE]) - to_remote = PartialTxOutput(scriptpubkey=bfh(remote_script), value=to_remote_amt // 1000) + to_remote = PartialTxOutput(scriptpubkey=remote_script, value=to_remote_amt // 1000) non_htlc_outputs = [to_local, to_remote] htlc_outputs = [] for script, htlc in htlcs: - addr = bitcoin.redeem_script_to_address('p2wsh', script.hex()) - htlc_outputs.append(PartialTxOutput(scriptpubkey=bfh(address_to_script(addr)), + addr = bitcoin.redeem_script_to_address('p2wsh', script) + htlc_outputs.append(PartialTxOutput(scriptpubkey=address_to_script(addr), value=htlc.amount_msat // 1000)) # trim outputs @@ -1060,7 +1062,7 @@ def make_commitment_output_to_local_witness_script( assert type(revocation_pubkey) is bytes assert type(to_self_delay) is int assert type(delayed_pubkey) is bytes - script = bfh(construct_script([ + script = construct_script([ opcodes.OP_IF, revocation_pubkey, opcodes.OP_ELSE, @@ -1070,13 +1072,13 @@ def make_commitment_output_to_local_witness_script( delayed_pubkey, opcodes.OP_ENDIF, opcodes.OP_CHECKSIG, - ])) + ]) return script def make_commitment_output_to_local_address( revocation_pubkey: bytes, to_self_delay: int, delayed_pubkey: bytes) -> str: local_script = make_commitment_output_to_local_witness_script(revocation_pubkey, to_self_delay, delayed_pubkey) - return bitcoin.redeem_script_to_address('p2wsh', local_script.hex()) + return bitcoin.redeem_script_to_address('p2wsh', local_script) def make_commitment_output_to_remote_address(remote_payment_pubkey: bytes) -> str: return bitcoin.pubkey_to_address('p2wpkh', remote_payment_pubkey.hex()) @@ -1087,10 +1089,10 @@ def sign_and_get_sig_string(tx: PartialTransaction, local_config, remote_config) sig_64 = ecdsa_sig64_from_der_sig(sig[:-1]) return sig_64 -def funding_output_script(local_config, remote_config) -> str: +def funding_output_script(local_config: 'LocalConfig', remote_config: 'RemoteConfig') -> bytes: return funding_output_script_from_keys(local_config.multisig_key.pubkey, remote_config.multisig_key.pubkey) -def funding_output_script_from_keys(pubkey1: bytes, pubkey2: bytes) -> str: +def funding_output_script_from_keys(pubkey1: bytes, pubkey2: bytes) -> bytes: pubkeys = sorted([pubkey1.hex(), pubkey2.hex()]) return transaction.multisig_script(pubkeys, 2) diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 62d930bb6..7fb40590d 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -58,7 +58,7 @@ from .lnrater import LNRater from . import lnutil from .lnutil import funding_output_script from .lnutil import serialize_htlc_key, deserialize_htlc_key -from .bitcoin import redeem_script_to_address, DummyAddress +from .bitcoin import DummyAddress from .lnutil import (Outpoint, LNPeerAddr, get_compressed_pubkey_from_bech32, extract_nodeid, PaymentFailure, split_host_port, ConnStringFormatError, diff --git a/electrum/payment_identifier.py b/electrum/payment_identifier.py index 37aafa73f..1161dfb63 100644 --- a/electrum/payment_identifier.py +++ b/electrum/payment_identifier.py @@ -320,7 +320,7 @@ class PaymentIdentifier(Logger): 'security check, DNSSEC, and thus may not be correct.').format(key) try: assert bitcoin.is_address(address) - scriptpubkey = bytes.fromhex(bitcoin.address_to_script(address)) + scriptpubkey = bitcoin.address_to_script(address) self._type = PaymentIdentifierType.OPENALIAS self.spk = scriptpubkey self.set_state(PaymentIdentifierState.AVAILABLE) @@ -518,20 +518,20 @@ class PaymentIdentifier(Logger): def parse_output(self, x: str) -> Tuple[Optional[bytes], bool]: try: address = self.parse_address(x) - return bytes.fromhex(bitcoin.address_to_script(address)), True + return bitcoin.address_to_script(address), True except Exception as e: pass try: m = re.match('^' + RE_SCRIPT_FN + '$', x) script = self.parse_script(str(m.group(1))) - return bytes.fromhex(script), False + return script, False except Exception as e: pass return None, False - def parse_script(self, x: str) -> str: - script = '' + def parse_script(self, x: str) -> bytes: + script = bytearray() for word in x.split(): if word[0:3] == 'OP_': opcode_int = opcodes[word] @@ -539,7 +539,7 @@ class PaymentIdentifier(Logger): else: bytes.fromhex(word) # to test it is hex data script += construct_script([word]) - return script + return bytes(script) def parse_amount(self, x: str) -> Union[str, int]: x = x.strip() diff --git a/electrum/paymentrequest.py b/electrum/paymentrequest.py index 33f64aad6..ba2f4a58b 100644 --- a/electrum/paymentrequest.py +++ b/electrum/paymentrequest.py @@ -291,7 +291,7 @@ class PaymentRequest: paymnt.merchant_data = pay_det.merchant_data paymnt.transactions.append(bfh(raw_tx)) ref_out = paymnt.refund_to.add() - ref_out.script = util.bfh(address_to_script(refund_addr)) + ref_out.script = address_to_script(refund_addr) paymnt.memo = "Paid using Electrum" pm = paymnt.SerializeToString() payurl = urllib.parse.urlparse(pay_det.payment_url) @@ -334,7 +334,7 @@ def make_unsigned_request(req: 'Invoice'): if amount is None: amount = 0 memo = req.message - script = bfh(address_to_script(addr)) + script = address_to_script(addr) outputs = [(script, amount)] pd = pb2.PaymentDetails() if constants.net.TESTNET: diff --git a/electrum/plugins/bitbox02/bitbox02.py b/electrum/plugins/bitbox02/bitbox02.py index 5e8ecee32..5c1f33748 100644 --- a/electrum/plugins/bitbox02/bitbox02.py +++ b/electrum/plugins/bitbox02/bitbox02.py @@ -536,8 +536,8 @@ class BitBox02Client(HardwareClientBase): # Fill signatures if len(sigs) != len(tx.inputs()): raise Exception("Incorrect number of inputs signed.") # Should never occur - sighash = Sighash.to_sigbytes(Sighash.ALL).hex() - signatures = [ecc.ecdsa_der_sig_from_ecdsa_sig64(x[1]).hex() + sighash for x in sigs] + sighash = Sighash.to_sigbytes(Sighash.ALL) + signatures = [ecc.ecdsa_der_sig_from_ecdsa_sig64(x[1]) + sighash for x in sigs] tx.update_signatures(signatures) def sign_message(self, keypath: str, message: bytes, script_type: str) -> bytes: diff --git a/electrum/plugins/coldcard/coldcard.py b/electrum/plugins/coldcard/coldcard.py index 00793ae70..c9f55308b 100644 --- a/electrum/plugins/coldcard/coldcard.py +++ b/electrum/plugins/coldcard/coldcard.py @@ -600,7 +600,7 @@ class ColdcardPlugin(HW_PluginBase): xfp_int = xfp_int_from_xfp_bytes(fp_bytes) xfp_paths.append([xfp_int] + list(der_full)) - script = bfh(wallet.pubkeys_to_scriptcode(pubkey_hexes)) + script = wallet.pubkeys_to_scriptcode(pubkey_hexes) keystore.show_p2sh_address(wallet.m, script, xfp_paths, txin_type) diff --git a/electrum/plugins/digitalbitbox/digitalbitbox.py b/electrum/plugins/digitalbitbox/digitalbitbox.py index 8faccfc3b..8cb454176 100644 --- a/electrum/plugins/digitalbitbox/digitalbitbox.py +++ b/electrum/plugins/digitalbitbox/digitalbitbox.py @@ -547,7 +547,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): if not inputPath: self.give_error("No matching pubkey for sign_transaction") # should never happen inputPath = convert_bip32_intpath_to_strpath(inputPath) - inputHash = sha256d(bfh(tx.serialize_preimage(i))) + inputHash = sha256d(tx.serialize_preimage(i)) hasharray_i = {'hash': to_hexstr(inputHash), 'keypath': inputPath} hasharray.append(hasharray_i) inputhasharray.append(inputHash) @@ -659,8 +659,8 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore): sig_r = int(signed['sig'][:64], 16) sig_s = int(signed['sig'][64:], 16) sig = ecc.ecdsa_der_sig_from_r_and_s(sig_r, sig_s) - sig = to_hexstr(sig) + Sighash.to_sigbytes(Sighash.ALL).hex() - tx.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_bytes.hex(), sig=sig) + sig = sig + Sighash.to_sigbytes(Sighash.ALL) + tx.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_bytes, sig=sig) except UserCancelled: raise except BaseException as e: diff --git a/electrum/plugins/jade/jade.py b/electrum/plugins/jade/jade.py index 3d7d04220..8a36b69ed 100644 --- a/electrum/plugins/jade/jade.py +++ b/electrum/plugins/jade/jade.py @@ -268,7 +268,6 @@ class Jade_KeyStore(Hardware_KeyStore): pubkey, path = self.find_my_pubkey_in_txinout(txin) witness_input = txin.is_segwit() redeem_script = Transaction.get_preimage_script(txin) - redeem_script = bytes.fromhex(redeem_script) if redeem_script is not None else None input_tx = txin.utxo input_tx = bytes.fromhex(input_tx.serialize()) if input_tx is not None else None @@ -314,9 +313,11 @@ class Jade_KeyStore(Hardware_KeyStore): for index, (txin, signature) in enumerate(zip(tx.inputs(), signatures)): pubkey, path = self.find_my_pubkey_in_txinout(txin) if pubkey is not None and signature is not None: - tx.add_signature_to_txin(txin_idx=index, - signing_pubkey=pubkey.hex(), - sig=signature.hex()) + tx.add_signature_to_txin( + txin_idx=index, + signing_pubkey=pubkey, + sig=signature, + ) finally: self.handler.finished() diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py index db288693b..a5f577a53 100644 --- a/electrum/plugins/keepkey/keepkey.py +++ b/electrum/plugins/keepkey/keepkey.py @@ -266,8 +266,8 @@ class KeepKeyPlugin(HW_PluginBase): outputs = self.tx_outputs(tx, keystore=keystore) signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime, version=tx.version)[0] - sighash = Sighash.to_sigbytes(Sighash.ALL).hex() - signatures = [(x.hex() + sighash) for x in signatures] + sighash = Sighash.to_sigbytes(Sighash.ALL) + signatures = [(sig + sighash) for sig in signatures] tx.update_signatures(signatures) @runs_in_hwd_thread diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py index 8d62650c9..c6ecd8ba4 100644 --- a/electrum/plugins/ledger/ledger.py +++ b/electrum/plugins/ledger/ledger.py @@ -9,7 +9,7 @@ from typing import Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING from electrum import bip32, constants, ecc from electrum import descriptor from electrum.bip32 import BIP32Node, convert_bip32_intpath_to_strpath, normalize_bip32_derivation -from electrum.bitcoin import EncodeBase58Check, int_to_hex, is_b58_address, is_segwit_script_type, var_int +from electrum.bitcoin import EncodeBase58Check, is_b58_address, is_segwit_script_type, var_int from electrum.crypto import hash_160 from electrum.i18n import _ from electrum.keystore import Hardware_KeyStore @@ -584,7 +584,7 @@ class Ledger_Client_Legacy(Ledger_Client): self.give_error("No matching pubkey for sign_transaction") # should never happen full_path = convert_bip32_intpath_to_strpath(full_path)[2:] - redeemScript = Transaction.get_preimage_script(txin) + redeemScript = Transaction.get_preimage_script(txin).hex() txin_prev_tx = txin.utxo if txin_prev_tx is None and not txin.is_segwit(): raise UserFacingException(_('Missing previous tx for legacy input.')) @@ -604,13 +604,14 @@ class Ledger_Client_Legacy(Ledger_Client): if not is_txin_legacy_multisig(txin): self.give_error("P2SH / regular input mixed in same transaction not supported") # should never happen - txOutput = var_int(len(tx.outputs())) + txOutput = bytearray() + txOutput += var_int(len(tx.outputs())) for o in tx.outputs(): - txOutput += int_to_hex(o.value, 8) - script = o.scriptpubkey.hex() - txOutput += var_int(len(script) // 2) + txOutput += int.to_bytes(o.value, length=8, byteorder="little", signed=False) + script = o.scriptpubkey + txOutput += var_int(len(script)) txOutput += script - txOutput = bfh(txOutput) + txOutput = bytes(txOutput) if not self.supports_multi_output(): if len(tx.outputs()) > 2: @@ -649,11 +650,11 @@ class Ledger_Client_Legacy(Ledger_Client): # Get trusted inputs from the original transactions for input_idx, utxo in enumerate(inputs): self.handler.show_message(_("Preparing transaction inputs...") + f" (phase1, {input_idx}/{len(inputs)})") - sequence = int_to_hex(utxo[5], 4) + sequence = int.to_bytes(utxo[5], length=4, byteorder="little", signed=False) if segwitTransaction and not self.supports_segwit_trustedInputs(): tmp = bfh(utxo[3])[::-1] - tmp += bfh(int_to_hex(utxo[1], 4)) - tmp += bfh(int_to_hex(utxo[6], 8)) # txin['value'] + tmp += int.to_bytes(utxo[1], length=4, byteorder="little", signed=False) + tmp += int.to_bytes(utxo[6], length=8, byteorder="little", signed=False) # txin['value'] chipInputs.append({'value': tmp, 'witness': True, 'sequence': sequence}) redeemScripts.append(bfh(utxo[2])) elif (not p2shTransaction) or self.supports_multi_output(): @@ -669,7 +670,7 @@ class Ledger_Client_Legacy(Ledger_Client): redeemScripts.append(txtmp.outputs[utxo[1]].script) else: tmp = bfh(utxo[3])[::-1] - tmp += bfh(int_to_hex(utxo[1], 4)) + tmp += int.to_bytes(utxo[1], length=4, byteorder="little", signed=False) chipInputs.append({'value': tmp, 'sequence': sequence}) redeemScripts.append(bfh(utxo[2])) @@ -703,8 +704,8 @@ class Ledger_Client_Legacy(Ledger_Client): inputSignature[0] = 0x30 # force for 1.4.9+ my_pubkey = inputs[inputIndex][4] tx.add_signature_to_txin(txin_idx=inputIndex, - signing_pubkey=my_pubkey.hex(), - sig=inputSignature.hex()) + signing_pubkey=my_pubkey, + sig=inputSignature) inputIndex = inputIndex + 1 else: while inputIndex < len(inputs): @@ -728,8 +729,8 @@ class Ledger_Client_Legacy(Ledger_Client): inputSignature[0] = 0x30 # force for 1.4.9+ my_pubkey = inputs[inputIndex][4] tx.add_signature_to_txin(txin_idx=inputIndex, - signing_pubkey=my_pubkey.hex(), - sig=inputSignature.hex()) + signing_pubkey=my_pubkey, + sig=inputSignature) inputIndex = inputIndex + 1 firstTransaction = False except UserWarning: @@ -1247,7 +1248,7 @@ class Ledger_Client_New(Ledger_Client): input_sigs = self.client.sign_psbt(psbt, wallet, wallet_hmac) for idx, part_sig in input_sigs: tx.add_signature_to_txin( - txin_idx=idx, signing_pubkey=part_sig.pubkey.hex(), sig=part_sig.signature.hex()) + txin_idx=idx, signing_pubkey=part_sig.pubkey, sig=part_sig.signature) except DenyError: pass # cancelled by user except BaseException as e: diff --git a/electrum/plugins/safe_t/safe_t.py b/electrum/plugins/safe_t/safe_t.py index a7981b317..baeba7984 100644 --- a/electrum/plugins/safe_t/safe_t.py +++ b/electrum/plugins/safe_t/safe_t.py @@ -236,8 +236,8 @@ class SafeTPlugin(HW_PluginBase): outputs = self.tx_outputs(tx, keystore=keystore) signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, lock_time=tx.locktime, version=tx.version)[0] - sighash = Sighash.to_sigbytes(Sighash.ALL).hex() - signatures = [(x.hex() + sighash) for x in signatures] + sighash = Sighash.to_sigbytes(Sighash.ALL) + signatures = [(sig + sighash) for sig in signatures] tx.update_signatures(signatures) @runs_in_hwd_thread diff --git a/electrum/plugins/trezor/trezor.py b/electrum/plugins/trezor/trezor.py index 0405c0d1e..da6a57170 100644 --- a/electrum/plugins/trezor/trezor.py +++ b/electrum/plugins/trezor/trezor.py @@ -346,8 +346,8 @@ class TrezorPlugin(HW_PluginBase): amount_unit=self.get_trezor_amount_unit(), serialize=False, prev_txes=prev_tx) - sighash = Sighash.to_sigbytes(Sighash.ALL).hex() - signatures = [((x.hex() + sighash) if x else None) for x in signatures] + sighash = Sighash.to_sigbytes(Sighash.ALL) + signatures = [((sig + sighash) if sig else None) for sig in signatures] tx.update_signatures(signatures) @runs_in_hwd_thread diff --git a/electrum/scripts/peers.py b/electrum/scripts/peers.py index 64c25e0cd..0a65edf2d 100755 --- a/electrum/scripts/peers.py +++ b/electrum/scripts/peers.py @@ -21,7 +21,7 @@ async def f(): results = await network.send_multiple_requests(peers, 'blockchain.headers.subscribe', []) for server, header in sorted(results.items(), key=lambda x: x[1].get('height')): height = header.get('height') - blockhash = hash_raw_header(header.get('hex')) + blockhash = hash_raw_header(bytes.fromhex(header.get('hex'))) print(server, height, blockhash) finally: stopping_fut.set_result(1) diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py index 56e232d6f..f14c2d31d 100644 --- a/electrum/submarine_swaps.py +++ b/electrum/submarine_swaps.py @@ -12,8 +12,8 @@ import aiohttp from . import lnutil from .crypto import sha256, hash_160 from .ecc import ECPrivkey -from .bitcoin import (script_to_p2wsh, opcodes, p2wsh_nested_script, push_script, - is_segwit_address, construct_witness) +from .bitcoin import (script_to_p2wsh, opcodes, + construct_witness) from .transaction import PartialTxInput, PartialTxOutput, PartialTransaction, Transaction, TxInput, TxOutpoint from .transaction import script_GetOp, match_script_against_template, OPPushDataGeneric, OPPushDataPubkey from .util import log_exceptions, BelowDustLimit, OldTaskGroup @@ -87,18 +87,17 @@ WITNESS_TEMPLATE_REVERSE_SWAP = [ def check_reverse_redeem_script( *, - redeem_script: str, + redeem_script: bytes, lockup_address: str, payment_hash: bytes, locktime: int, refund_pubkey: bytes = None, claim_pubkey: bytes = None, ) -> None: - redeem_script = bytes.fromhex(redeem_script) parsed_script = [x for x in script_GetOp(redeem_script)] if not match_script_against_template(redeem_script, WITNESS_TEMPLATE_REVERSE_SWAP): raise Exception("rswap check failed: scriptcode does not match template") - if script_to_p2wsh(redeem_script.hex()) != lockup_address: + if script_to_p2wsh(redeem_script) != lockup_address: raise Exception("rswap check failed: inconsistent scriptcode and address") if ripemd(payment_hash) != parsed_script[5][1]: raise Exception("rswap check failed: our preimage not in script") @@ -438,7 +437,7 @@ class SwapManager(Logger): def add_normal_swap( self, *, - redeem_script: str, + redeem_script: bytes, locktime: int, # onchain onchain_amount_sat: int, lightning_amount_sat: int, @@ -487,7 +486,7 @@ class SwapManager(Logger): lockup_address = script_to_p2wsh(redeem_script) receive_address = self.wallet.get_receiving_address() swap = SwapData( - redeem_script = bytes.fromhex(redeem_script), + redeem_script=redeem_script, locktime = locktime, privkey = our_privkey, preimage = None, @@ -533,7 +532,7 @@ class SwapManager(Logger): def add_reverse_swap( self, *, - redeem_script: str, + redeem_script: bytes, locktime: int, # onchain privkey: bytes, lightning_amount_sat: int, @@ -545,7 +544,7 @@ class SwapManager(Logger): lockup_address = script_to_p2wsh(redeem_script) receive_address = self.wallet.get_receiving_address() swap = SwapData( - redeem_script = bytes.fromhex(redeem_script), + redeem_script = redeem_script, locktime = locktime, privkey = privkey, preimage = preimage, @@ -645,7 +644,7 @@ class SwapManager(Logger): onchain_amount = data["expectedAmount"] locktime = data["timeoutBlockHeight"] lockup_address = data["address"] - redeem_script = data["redeemScript"] + redeem_script = bytes.fromhex(data["redeemScript"]) # verify redeem_script is built with our pubkey and preimage check_reverse_redeem_script( redeem_script=redeem_script, @@ -805,7 +804,7 @@ class SwapManager(Logger): invoice = data['invoice'] fee_invoice = data.get('minerFeeInvoice') lockup_address = data['lockupAddress'] - redeem_script = data['redeemScript'] + redeem_script = bytes.fromhex(data['redeemScript']) locktime = data['timeoutBlockHeight'] onchain_amount = data["onchainAmount"] response_id = data['id'] @@ -1052,7 +1051,7 @@ class SwapManager(Logger): txin.witness_script = witness_script sig_dummy = b'\x00' * 71 # DER-encoded ECDSA sig, with low S and low R witness = [sig_dummy, preimage, witness_script] - txin.witness_sizehint = len(bytes.fromhex(construct_witness(witness))) + txin.witness_sizehint = len(construct_witness(witness)) @classmethod def sign_tx(cls, tx: PartialTransaction, swap: SwapData) -> None: @@ -1063,9 +1062,9 @@ class SwapManager(Logger): assert txin.prevout.txid.hex() == swap.funding_txid txin.script_sig = b'' txin.witness_script = witness_script - sig = bytes.fromhex(tx.sign_txin(0, swap.privkey)) + sig = tx.sign_txin(0, swap.privkey) witness = [sig, preimage, witness_script] - txin.witness = bytes.fromhex(construct_witness(witness)) + txin.witness = construct_witness(witness) @classmethod def _create_and_sign_claim_tx( diff --git a/electrum/transaction.py b/electrum/transaction.py index fe50a3750..c407c95b2 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -47,8 +47,7 @@ from .util import profiler, to_bytes, bfh, chunks, is_hex_str, parse_max_spend from .bitcoin import (TYPE_ADDRESS, TYPE_SCRIPT, hash_160, hash160_to_p2sh, hash160_to_p2pkh, hash_to_segwit_addr, var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN, - int_to_hex, push_script, b58_address_to_hash160, - opcodes, add_number_to_script, base_decode, + opcodes, base_decode, base_encode, construct_witness, construct_script) from .crypto import sha256d from .logging import get_logger @@ -138,13 +137,13 @@ class TxOutput: @classmethod def from_address_and_value(cls, address: str, value: Union[int, str]) -> Union['TxOutput', 'PartialTxOutput']: - return cls(scriptpubkey=bfh(bitcoin.address_to_script(address)), + return cls(scriptpubkey=bitcoin.address_to_script(address), value=value) def serialize_to_network(self) -> bytes: buf = int.to_bytes(self.value, 8, byteorder="little", signed=False) script = self.scriptpubkey - buf += bfh(var_int(len(script.hex()) // 2)) + buf += var_int(len(script)) buf += script return buf @@ -212,9 +211,9 @@ class TxOutput: class BIP143SharedTxDigestFields(NamedTuple): - hashPrevouts: str - hashSequence: str - hashOutputs: str + hashPrevouts: bytes + hashSequence: bytes + hashOutputs: bytes class TxOutpoint(NamedTuple): @@ -241,7 +240,7 @@ class TxOutpoint(NamedTuple): return [self.txid.hex(), self.out_idx] def serialize_to_network(self) -> bytes: - return self.txid[::-1] + bfh(int_to_hex(self.out_idx, 4)) + return self.txid[::-1] + int.to_bytes(self.out_idx, length=4, byteorder="little", signed=False) def is_coinbase(self) -> bool: return self.txid == bytes(32) @@ -356,9 +355,9 @@ class TxInput: # Prev hash and index s = self.prevout.serialize_to_network() # Script length, script, sequence - s += bytes.fromhex(var_int(len(script_sig))) + s += var_int(len(script_sig)) s += script_sig - s += bytes.fromhex(int_to_hex(self.nsequence, 4)) + s += int.to_bytes(self.nsequence, length=4, byteorder="little", signed=False) return s def witness_elements(self) -> Sequence[bytes]: @@ -714,7 +713,7 @@ def parse_input(vds: BCDataStream) -> TxInput: def parse_witness(vds: BCDataStream, txin: TxInput) -> None: n = vds.read_compact_size() witness_elements = list(vds.read_bytes(vds.read_compact_size()) for i in range(n)) - txin.witness = bfh(construct_witness(witness_elements)) + txin.witness = construct_witness(witness_elements) def parse_output(vds: BCDataStream) -> TxOutput: @@ -729,7 +728,7 @@ def parse_output(vds: BCDataStream) -> TxOutput: # pay & redeem scripts -def multisig_script(public_keys: Sequence[str], m: int) -> str: +def multisig_script(public_keys: Sequence[str], m: int) -> bytes: n = len(public_keys) assert 1 <= m <= n <= 15, f'm {m}, n {n}' return construct_script([m, *public_keys, n, opcodes.OP_CHECKMULTISIG]) @@ -833,18 +832,18 @@ class Transaction: raise SerializationError('extra junk at the end') @classmethod - def serialize_witness(cls, txin: TxInput, *, estimate_size=False) -> str: + def serialize_witness(cls, txin: TxInput, *, estimate_size=False) -> bytes: if txin.witness is not None: - return txin.witness.hex() + return txin.witness if txin.is_coinbase_input(): - return '' + return b"" assert isinstance(txin, PartialTxInput) if not txin.is_segwit(): return construct_witness([]) if estimate_size and txin.witness_sizehint is not None: - return '00' * txin.witness_sizehint + return bytes(txin.witness_sizehint) dummy_desc = None if estimate_size: @@ -852,22 +851,22 @@ class Transaction: if desc := (txin.script_descriptor or dummy_desc): sol = desc.satisfy(allow_dummy=estimate_size, sigdata=txin.part_sigs) if sol.witness is not None: - return sol.witness.hex() + return sol.witness return construct_witness([]) raise UnknownTxinType("cannot construct witness") @classmethod - def input_script(self, txin: TxInput, *, estimate_size=False) -> str: + def input_script(self, txin: TxInput, *, estimate_size=False) -> bytes: if txin.script_sig is not None: - return txin.script_sig.hex() + return txin.script_sig if txin.is_coinbase_input(): - return '' + return b"" assert isinstance(txin, PartialTxInput) if txin.is_p2sh_segwit() and txin.redeem_script: return construct_script([txin.redeem_script]) if txin.is_native_segwit(): - return '' + return b"" dummy_desc = None if estimate_size: @@ -876,37 +875,39 @@ class Transaction: if desc.is_segwit(): if redeem_script := desc.expand().redeem_script: return construct_script([redeem_script]) - return "" + return b"" sol = desc.satisfy(allow_dummy=estimate_size, sigdata=txin.part_sigs) if sol.script_sig is not None: - return sol.script_sig.hex() - return "" + return sol.script_sig + return b"" raise UnknownTxinType("cannot construct scriptSig") @classmethod - def get_preimage_script(cls, txin: 'PartialTxInput') -> str: + def get_preimage_script(cls, txin: 'PartialTxInput') -> bytes: if txin.witness_script: if opcodes.OP_CODESEPARATOR in [x[0] for x in script_GetOp(txin.witness_script)]: raise Exception('OP_CODESEPARATOR black magic is not supported') - return txin.witness_script.hex() + return txin.witness_script if not txin.is_segwit() and txin.redeem_script: if opcodes.OP_CODESEPARATOR in [x[0] for x in script_GetOp(txin.redeem_script)]: raise Exception('OP_CODESEPARATOR black magic is not supported') - return txin.redeem_script.hex() + return txin.redeem_script if desc := txin.script_descriptor: sc = desc.expand() if script := sc.scriptcode_for_sighash: - return script.hex() + return script raise Exception(f"don't know scriptcode for descriptor: {desc.to_string()}") raise UnknownTxinType(f'cannot construct preimage_script') def _calc_bip143_shared_txdigest_fields(self) -> BIP143SharedTxDigestFields: inputs = self.inputs() outputs = self.outputs() - hashPrevouts = sha256d(b''.join(txin.prevout.serialize_to_network() for txin in inputs)).hex() - hashSequence = sha256d(bfh(''.join(int_to_hex(txin.nsequence, 4) for txin in inputs))).hex() - hashOutputs = sha256d(bfh(''.join(o.serialize_to_network().hex() for o in outputs))).hex() + hashPrevouts = sha256d(b''.join(txin.prevout.serialize_to_network() for txin in inputs)) + hashSequence = sha256d(b''.join( + int.to_bytes(txin.nsequence, length=4, byteorder="little", signed=False) + for txin in inputs)) + hashOutputs = sha256d(b''.join(o.serialize_to_network() for o in outputs)) return BIP143SharedTxDigestFields(hashPrevouts=hashPrevouts, hashSequence=hashSequence, hashOutputs=hashOutputs) @@ -934,19 +935,20 @@ class Transaction: note: (not include_sigs) implies force_legacy """ self.deserialize() - nVersion = int_to_hex(self.version, 4) - nLocktime = int_to_hex(self.locktime, 4) + nVersion = int.to_bytes(self.version, length=4, byteorder="little", signed=True).hex() + nLocktime = int.to_bytes(self.locktime, length=4, byteorder="little", signed=False).hex() inputs = self.inputs() outputs = self.outputs() def create_script_sig(txin: TxInput) -> bytes: if include_sigs: script_sig = self.input_script(txin, estimate_size=estimate_size) - return bytes.fromhex(script_sig) + return script_sig return b"" - txins = var_int(len(inputs)) + ''.join(txin.serialize_to_network(script_sig=create_script_sig(txin)).hex() - for txin in inputs) - txouts = var_int(len(outputs)) + ''.join(o.serialize_to_network().hex() for o in outputs) + txins = var_int(len(inputs)).hex() + ''.join( + txin.serialize_to_network(script_sig=create_script_sig(txin)).hex() + for txin in inputs) + txouts = var_int(len(outputs)).hex() + ''.join(o.serialize_to_network().hex() for o in outputs) use_segwit_ser_for_estimate_size = estimate_size and self.is_segwit(guess_for_address=True) use_segwit_ser_for_actual_use = not estimate_size and self.is_segwit() @@ -954,7 +956,7 @@ class Transaction: if include_sigs and not force_legacy and use_segwit_ser: marker = '00' flag = '01' - witness = ''.join(self.serialize_witness(x, estimate_size=estimate_size) for x in inputs) + witness = ''.join(self.serialize_witness(x, estimate_size=estimate_size).hex() for x in inputs) return nVersion + marker + flag + txins + txouts + witness + nLocktime else: return nVersion + txins + txouts + nLocktime @@ -1073,7 +1075,7 @@ class Transaction: """Whether the tx explicitly signals BIP-0125 replace-by-fee.""" return any([txin.nsequence < 0xffffffff - 1 for txin in self.inputs()]) - def estimated_size(self): + def estimated_size(self) -> int: """Return an estimated virtual tx size in vbytes. BIP-0141 defines 'Virtual transaction size' to be weight/4 rounded up. This definition is only for humans, and has little meaning otherwise. @@ -1084,13 +1086,13 @@ class Transaction: return self.virtual_size_from_weight(weight) @classmethod - def estimated_input_weight(cls, txin: TxInput, is_segwit_tx: bool): + def estimated_input_weight(cls, txin: TxInput, is_segwit_tx: bool) -> int: '''Return an estimate of serialized input weight in weight units.''' script_sig = cls.input_script(txin, estimate_size=True) - input_size = len(txin.serialize_to_network(script_sig=bytes.fromhex(script_sig))) + input_size = len(txin.serialize_to_network(script_sig=script_sig)) if txin.is_segwit(guess_for_address=True): - witness_size = len(cls.serialize_witness(txin, estimate_size=True)) // 2 + witness_size = len(cls.serialize_witness(txin, estimate_size=True)) else: witness_size = 1 if is_segwit_tx else 0 @@ -1103,15 +1105,15 @@ class Transaction: return cls.estimated_output_size_for_script(script) @classmethod - def estimated_output_size_for_script(cls, script: str) -> int: + def estimated_output_size_for_script(cls, script: bytes) -> int: """Return an estimate of serialized output size in bytes.""" # 8 byte value + varint script len + script - script_len = len(script) // 2 - var_int_len = len(var_int(script_len)) // 2 + script_len = len(script) + var_int_len = len(var_int(script_len)) return 8 + var_int_len + script_len @classmethod - def virtual_size_from_weight(cls, weight): + def virtual_size_from_weight(cls, weight: int) -> int: return weight // 4 + (weight % 4 > 0) @classmethod @@ -1132,8 +1134,8 @@ class Transaction: if not self.is_segwit(guess_for_address=estimate): return 0 inputs = self.inputs() - witness = ''.join(self.serialize_witness(x, estimate_size=estimate) for x in inputs) - witness_size = len(witness) // 2 + 2 # include marker and flag + witness = b"".join(self.serialize_witness(x, estimate_size=estimate) for x in inputs) + witness_size = len(witness) + 2 # include marker and flag return witness_size def estimated_base_size(self): @@ -1149,17 +1151,16 @@ class Transaction: def is_complete(self) -> bool: return True - def get_output_idxs_from_scriptpubkey(self, script: str) -> Set[int]: + def get_output_idxs_from_scriptpubkey(self, script: bytes) -> Set[int]: """Returns the set indices of outputs with given script.""" - assert isinstance(script, str) # hex + assert isinstance(script, bytes) # build cache if there isn't one yet # note: can become stale and return incorrect data # if the tx is modified later; that's out of scope. if not hasattr(self, '_script_to_output_idx'): d = defaultdict(set) for output_idx, o in enumerate(self.outputs()): - o_script = o.scriptpubkey.hex() - assert isinstance(o_script, str) + o_script = o.scriptpubkey d[o_script].add(output_idx) self._script_to_output_idx = d return set(self._script_to_output_idx[script]) # copy @@ -1347,9 +1348,9 @@ class PSBTSection: def create_psbt_writer(cls, fd): def wr(key_type: int, val: bytes, key: bytes = b''): full_key = cls.get_fullkey_from_keytype_and_key(key_type, key) - fd.write(bytes.fromhex(var_int(len(full_key)))) # key_size + fd.write(var_int(len(full_key))) # key_size fd.write(full_key) # key - fd.write(bytes.fromhex(var_int(len(val)))) # val_size + fd.write(var_int(len(val))) # val_size fd.write(val) # val return wr @@ -1363,7 +1364,7 @@ class PSBTSection: @classmethod def get_fullkey_from_keytype_and_key(cls, key_type: int, key: bytes) -> bytes: - key_type_bytes = bytes.fromhex(var_int(key_type)) + key_type_bytes = var_int(key_type) return key_type_bytes + key def _serialize_psbt_section(self, fd): @@ -1492,11 +1493,11 @@ class PartialTxInput(TxInput, PSBTSection): f"If a redeemScript is provided, the scriptPubKey must be for that redeemScript") if self.witness_script: if self.redeem_script: - if self.redeem_script != bfh(bitcoin.p2wsh_nested_script(self.witness_script.hex())): + if self.redeem_script != bitcoin.p2wsh_nested_script(self.witness_script): raise PSBTInputConsistencyFailure(f"PSBT input validation: " f"If a witnessScript is provided, the redeemScript must be for that witnessScript") elif self.address: - if self.address != bitcoin.script_to_p2wsh(self.witness_script.hex()): + if self.address != bitcoin.script_to_p2wsh(self.witness_script): raise PSBTInputConsistencyFailure(f"PSBT input validation: " f"If a witnessScript is provided, the scriptPubKey must be for that witnessScript") @@ -1617,7 +1618,7 @@ class PartialTxInput(TxInput, PSBTSection): if (spk := super().scriptpubkey) is not None: return spk if self._trusted_address is not None: - return bfh(bitcoin.address_to_script(self._trusted_address)) + return bitcoin.address_to_script(self._trusted_address) if self.witness_utxo: return self.witness_utxo.scriptpubkey return None @@ -1657,8 +1658,8 @@ class PartialTxInput(TxInput, PSBTSection): clear_fields_when_finalized() return # already finalized if self.is_complete(): - self.script_sig = bfh(Transaction.input_script(self)) - self.witness = bfh(Transaction.serialize_witness(self)) + self.script_sig = Transaction.input_script(self) + self.witness = Transaction.serialize_witness(self) clear_fields_when_finalized() def combine_with_other_txin(self, other_txin: 'TxInput') -> None: @@ -2086,16 +2087,16 @@ class PartialTransaction(Transaction): self.invalidate_ser_cache() def serialize_preimage(self, txin_index: int, *, - bip143_shared_txdigest_fields: BIP143SharedTxDigestFields = None) -> str: - nVersion = int_to_hex(self.version, 4) - nLocktime = int_to_hex(self.locktime, 4) + bip143_shared_txdigest_fields: BIP143SharedTxDigestFields = None) -> bytes: + nVersion = int.to_bytes(self.version, length=4, byteorder="little", signed=True) + nLocktime = int.to_bytes(self.locktime, length=4, byteorder="little", signed=False) inputs = self.inputs() outputs = self.outputs() txin = inputs[txin_index] sighash = txin.sighash if txin.sighash is not None else Sighash.ALL if not Sighash.is_valid(sighash): raise Exception(f"SIGHASH_FLAG ({sighash}) not supported!") - nHashType = int_to_hex(sighash, 4) + nHashType = int.to_bytes(sighash, length=4, byteorder="little", signed=False) preimage_script = self.get_preimage_script(txin) if txin.is_segwit(): if bip143_shared_txdigest_fields is None: @@ -2103,28 +2104,29 @@ class PartialTransaction(Transaction): if not (sighash & Sighash.ANYONECANPAY): hashPrevouts = bip143_shared_txdigest_fields.hashPrevouts else: - hashPrevouts = '00' * 32 + hashPrevouts = bytes(32) if not (sighash & Sighash.ANYONECANPAY) and (sighash & 0x1f) != Sighash.SINGLE and (sighash & 0x1f) != Sighash.NONE: hashSequence = bip143_shared_txdigest_fields.hashSequence else: - hashSequence = '00' * 32 + hashSequence = bytes(32) if (sighash & 0x1f) != Sighash.SINGLE and (sighash & 0x1f) != Sighash.NONE: hashOutputs = bip143_shared_txdigest_fields.hashOutputs elif (sighash & 0x1f) == Sighash.SINGLE and txin_index < len(outputs): - hashOutputs = sha256d(outputs[txin_index].serialize_to_network()).hex() + hashOutputs = sha256d(outputs[txin_index].serialize_to_network()) else: - hashOutputs = '00' * 32 - outpoint = txin.prevout.serialize_to_network().hex() - scriptCode = var_int(len(preimage_script) // 2) + preimage_script - amount = int_to_hex(txin.value_sats(), 8) - nSequence = int_to_hex(txin.nsequence, 4) + hashOutputs = bytes(32) + outpoint = txin.prevout.serialize_to_network() + scriptCode = var_int(len(preimage_script)) + preimage_script + amount = int.to_bytes(txin.value_sats(), length=8, byteorder="little", signed=False) + nSequence = int.to_bytes(txin.nsequence, length=4, byteorder="little", signed=False) preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nHashType else: if sighash != Sighash.ALL: raise Exception(f"SIGHASH_FLAG ({sighash}) not supported! (for legacy sighash)") - txins = var_int(len(inputs)) + ''.join(txin.serialize_to_network(script_sig=bfh(preimage_script) if txin_index==k else b"").hex() - for k, txin in enumerate(inputs)) - txouts = var_int(len(outputs)) + ''.join(o.serialize_to_network().hex() for o in outputs) + txins = var_int(len(inputs)) + b"".join( + txin.serialize_to_network(script_sig=preimage_script if txin_index==k else b"") + for k, txin in enumerate(inputs)) + txouts = var_int(len(outputs)) + b"".join(o.serialize_to_network() for o in outputs) preimage = nVersion + txins + txouts + nLocktime + nHashType return preimage @@ -2141,20 +2143,26 @@ class PartialTransaction(Transaction): _logger.info(f"adding signature for {pubkey}. spending utxo {txin.prevout.to_str()}") sec, compressed = keypairs[pubkey] sig = self.sign_txin(i, sec, bip143_shared_txdigest_fields=bip143_shared_txdigest_fields) - self.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey, sig=sig) + self.add_signature_to_txin(txin_idx=i, signing_pubkey=bfh(pubkey), sig=sig) _logger.debug(f"is_complete {self.is_complete()}") self.invalidate_ser_cache() - def sign_txin(self, txin_index, privkey_bytes, *, bip143_shared_txdigest_fields=None) -> str: + def sign_txin( + self, + txin_index: int, + privkey_bytes: bytes, + *, + bip143_shared_txdigest_fields=None, + ) -> bytes: txin = self.inputs()[txin_index] txin.validate_data(for_signing=True) sighash = txin.sighash if txin.sighash is not None else Sighash.ALL - pre_hash = sha256d(bfh(self.serialize_preimage(txin_index, - bip143_shared_txdigest_fields=bip143_shared_txdigest_fields))) + pre_hash = self.serialize_preimage(txin_index, bip143_shared_txdigest_fields=bip143_shared_txdigest_fields) + msg_hash = sha256d(pre_hash) privkey = ecc.ECPrivkey(privkey_bytes) - sig = privkey.ecdsa_sign(pre_hash, sigencode=ecc.ecdsa_der_sig_from_r_and_s) - sig = sig.hex() + Sighash.to_sigbytes(sighash).hex() + sig = privkey.ecdsa_sign(msg_hash, sigencode=ecc.ecdsa_der_sig_from_r_and_s) + sig = sig + Sighash.to_sigbytes(sighash) return sig def is_complete(self) -> bool: @@ -2189,7 +2197,7 @@ class PartialTransaction(Transaction): raw_bytes = self.serialize_as_bytes() return base64.b64encode(raw_bytes).decode('ascii') - def update_signatures(self, signatures: Sequence[Union[str, None]]): + def update_signatures(self, signatures: Sequence[Union[bytes, None]]) -> None: """Add new signatures to a transaction `signatures` is expected to be a list of sigs with signatures[i] @@ -2201,33 +2209,32 @@ class PartialTransaction(Transaction): if len(self.inputs()) != len(signatures): raise Exception('expected {} signatures; got {}'.format(len(self.inputs()), len(signatures))) for i, txin in enumerate(self.inputs()): - pubkeys = [pk.hex() for pk in txin.pubkeys] sig = signatures[i] if sig is None: continue - if bfh(sig) in list(txin.part_sigs.values()): + if sig in list(txin.part_sigs.values()): continue - pre_hash = sha256d(bfh(self.serialize_preimage(i))) - sig_string = ecc.ecdsa_sig64_from_der_sig(bfh(sig[:-2])) + msg_hash = sha256d(self.serialize_preimage(i)) + sig64 = ecc.ecdsa_sig64_from_der_sig(sig[:-1]) for recid in range(4): try: - public_key = ecc.ECPubkey.from_ecdsa_sig64(sig_string, recid, pre_hash) + public_key = ecc.ECPubkey.from_ecdsa_sig64(sig64, recid, msg_hash) except ecc.InvalidECPointException: # the point might not be on the curve for some recid values continue - pubkey_hex = public_key.get_public_key_hex(compressed=True) - if pubkey_hex in pubkeys: - if not public_key.ecdsa_verify(sig_string, pre_hash): + pubkey_bytes = public_key.get_public_key_bytes(compressed=True) + if pubkey_bytes in txin.pubkeys: + if not public_key.ecdsa_verify(sig64, msg_hash): continue - _logger.info(f"adding sig: txin_idx={i}, signing_pubkey={pubkey_hex}, sig={sig}") - self.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_hex, sig=sig) + _logger.info(f"adding sig: txin_idx={i}, signing_pubkey={pubkey_bytes.hex()}, sig={sig.hex()}") + self.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_bytes, sig=sig) break # redo raw self.invalidate_ser_cache() - def add_signature_to_txin(self, *, txin_idx: int, signing_pubkey: str, sig: str): + def add_signature_to_txin(self, *, txin_idx: int, signing_pubkey: bytes, sig: bytes) -> None: txin = self._inputs[txin_idx] - txin.part_sigs[bfh(signing_pubkey)] = bfh(sig) + txin.part_sigs[signing_pubkey] = sig # force re-serialization txin.script_sig = None txin.witness = None diff --git a/electrum/wallet.py b/electrum/wallet.py index b1fa943f0..088d6d091 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -111,14 +111,14 @@ async def _append_utxos_to_inputs( script_descriptor: 'descriptor.Descriptor', imax: int, ) -> None: - script = script_descriptor.expand().output_script.hex() + script = script_descriptor.expand().output_script scripthash = bitcoin.script_to_scripthash(script) async def append_single_utxo(item): prev_tx_raw = await network.get_transaction(item['tx_hash']) prev_tx = Transaction(prev_tx_raw) prev_txout = prev_tx.outputs()[item['tx_pos']] - if scripthash != bitcoin.script_to_scripthash(prev_txout.scriptpubkey.hex()): + if scripthash != bitcoin.script_to_scripthash(prev_txout.scriptpubkey): raise Exception('scripthash mismatch when sweeping') prevout_str = item['tx_hash'] + ':%d' % item['tx_pos'] prevout = TxOutpoint.from_str(prevout_str) @@ -182,7 +182,7 @@ async def sweep( inputs, keypairs = await sweep_preparations(privkeys, network, imax) total = sum(txin.value_sats() for txin in inputs) if fee is None: - outputs = [PartialTxOutput(scriptpubkey=bfh(bitcoin.address_to_script(to_address)), + outputs = [PartialTxOutput(scriptpubkey=bitcoin.address_to_script(to_address), value=total)] tx = PartialTransaction.from_io(inputs, outputs) fee = config.estimate_fee(tx.estimated_size()) @@ -191,7 +191,7 @@ async def sweep( if total - fee < dust_threshold(network): raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, dust_threshold(network))) - outputs = [PartialTxOutput(scriptpubkey=bfh(bitcoin.address_to_script(to_address)), + outputs = [PartialTxOutput(scriptpubkey=bitcoin.address_to_script(to_address), value=total - fee)] if locktime is None: locktime = get_locktime_for_new_transaction(network) @@ -1297,7 +1297,7 @@ class Abstract_Wallet(ABC, Logger, EventListener): conf_needed = None # type: Optional[int] with self.lock, self.transaction_lock: for invoice_scriptpubkey, invoice_amt in invoice_amounts.items(): - scripthash = bitcoin.script_to_scripthash(invoice_scriptpubkey.hex()) + scripthash = bitcoin.script_to_scripthash(invoice_scriptpubkey) prevouts_and_values = self.db.get_prevouts_by_scripthash(scripthash) confs_and_values = [] for prevout, v in prevouts_and_values: @@ -2266,12 +2266,12 @@ class Abstract_Wallet(ABC, Logger, EventListener): delta_total = target_fee - cur_fee if delta_total <= 0: break - out_size_total = sum(Transaction.estimated_output_size_for_script(out.scriptpubkey.hex()) + out_size_total = sum(Transaction.estimated_output_size_for_script(out.scriptpubkey) for (idx, out) in s if idx not in del_out_idxs) if out_size_total == 0: # no outputs left to decrease raise CannotBumpFee(_('Could not find suitable outputs')) for idx, out in s: - out_size = Transaction.estimated_output_size_for_script(out.scriptpubkey.hex()) + out_size = Transaction.estimated_output_size_for_script(out.scriptpubkey) delta = int(math.ceil(delta_total * out_size / out_size_total)) if out.value - delta >= self.dust_threshold(): new_output_value = out.value - delta @@ -3804,7 +3804,7 @@ class Multisig_Wallet(Deterministic_Wallet): redeem_script = self.pubkeys_to_scriptcode(pubkeys) return bitcoin.redeem_script_to_address(self.txin_type, redeem_script) - def pubkeys_to_scriptcode(self, pubkeys: Sequence[str]) -> str: + def pubkeys_to_scriptcode(self, pubkeys: Sequence[str]) -> bytes: return transaction.multisig_script(sorted(pubkeys), self.m) def derive_pubkeys(self, c, i): diff --git a/electrum/wallet_db.py b/electrum/wallet_db.py index 82ac750e6..fc3004d75 100644 --- a/electrum/wallet_db.py +++ b/electrum/wallet_db.py @@ -522,7 +522,7 @@ class WalletDBUpgrader(Logger): tx = Transaction(raw_tx) for idx, txout in enumerate(tx.outputs()): outpoint = f"{txid}:{idx}" - scripthash = script_to_scripthash(txout.scriptpubkey.hex()) + scripthash = script_to_scripthash(txout.scriptpubkey) prevouts_by_scripthash[scripthash].append((outpoint, txout.value)) self.put('prevouts_by_scripthash', prevouts_by_scripthash) @@ -1126,7 +1126,7 @@ class WalletDBUpgrader(Logger): tx = Transaction(raw_tx) for idx, txout in enumerate(tx.outputs()): outpoint = f"{txid}:{idx}" - scripthash = script_to_scripthash(txout.scriptpubkey.hex()) + scripthash = script_to_scripthash(txout.scriptpubkey) if scripthash not in prevouts_by_scripthash: prevouts_by_scripthash[scripthash] = {} prevouts_by_scripthash[scripthash][outpoint] = txout.value diff --git a/tests/test_bitcoin.py b/tests/test_bitcoin.py index 88de7c484..be0b05330 100644 --- a/tests/test_bitcoin.py +++ b/tests/test_bitcoin.py @@ -8,7 +8,7 @@ from electrum.bitcoin import (public_key_to_p2pkh, address_from_private_key, deserialize_privkey, serialize_privkey, is_segwit_address, is_b58_address, address_to_scripthash, is_minikey, is_compressed_privkey, EncodeBase58Check, DecodeBase58Check, - script_num_to_hex, push_script, add_number_to_script, int_to_hex, + script_num_to_bytes, push_script, add_number_to_script, opcodes, base_encode, base_decode, BitcoinException) from electrum import bip32 from electrum import segwit_addr @@ -423,83 +423,63 @@ class Test_bitcoin(ElectrumTestCase): self.assertEqual(b'\x95MZI\xfdp\xd9\xb8\xbc\xdb5\xd2R&x)\x95\x7f~\xf7\xfalt\xf8\x84\x19\xbd\xc5\xe8"\t\xf4', sha256d(u"test")) - def test_int_to_hex(self): - self.assertEqual('00', int_to_hex(0, 1)) - self.assertEqual('ff', int_to_hex(-1, 1)) - self.assertEqual('00000000', int_to_hex(0, 4)) - self.assertEqual('01000000', int_to_hex(1, 4)) - self.assertEqual('7f', int_to_hex(127, 1)) - self.assertEqual('7f00', int_to_hex(127, 2)) - self.assertEqual('80', int_to_hex(128, 1)) - self.assertEqual('80', int_to_hex(-128, 1)) - self.assertEqual('8000', int_to_hex(128, 2)) - self.assertEqual('ff', int_to_hex(255, 1)) - self.assertEqual('ff7f', int_to_hex(32767, 2)) - self.assertEqual('0080', int_to_hex(-32768, 2)) - self.assertEqual('ffff', int_to_hex(65535, 2)) - with self.assertRaises(OverflowError): int_to_hex(256, 1) - with self.assertRaises(OverflowError): int_to_hex(-129, 1) - with self.assertRaises(OverflowError): int_to_hex(-257, 1) - with self.assertRaises(OverflowError): int_to_hex(65536, 2) - with self.assertRaises(OverflowError): int_to_hex(-32769, 2) - def test_var_int(self): for i in range(0xfd): - self.assertEqual(var_int(i), "{:02x}".format(i)) - - self.assertEqual(var_int(0xfd), "fdfd00") - self.assertEqual(var_int(0xfe), "fdfe00") - self.assertEqual(var_int(0xff), "fdff00") - self.assertEqual(var_int(0x1234), "fd3412") - self.assertEqual(var_int(0xffff), "fdffff") - self.assertEqual(var_int(0x10000), "fe00000100") - self.assertEqual(var_int(0x12345678), "fe78563412") - self.assertEqual(var_int(0xffffffff), "feffffffff") - self.assertEqual(var_int(0x100000000), "ff0000000001000000") - self.assertEqual(var_int(0x0123456789abcdef), "ffefcdab8967452301") + self.assertEqual(var_int(i), bfh("{:02x}".format(i))) + + self.assertEqual(var_int(0xfd), bfh("fdfd00")) + self.assertEqual(var_int(0xfe), bfh("fdfe00")) + self.assertEqual(var_int(0xff), bfh("fdff00")) + self.assertEqual(var_int(0x1234), bfh("fd3412")) + self.assertEqual(var_int(0xffff), bfh("fdffff")) + self.assertEqual(var_int(0x10000), bfh("fe00000100")) + self.assertEqual(var_int(0x12345678), bfh("fe78563412")) + self.assertEqual(var_int(0xffffffff), bfh("feffffffff")) + self.assertEqual(var_int(0x100000000), bfh("ff0000000001000000")) + self.assertEqual(var_int(0x0123456789abcdef), bfh("ffefcdab8967452301")) def test_op_push(self): - self.assertEqual(_op_push(0x00), '00') - self.assertEqual(_op_push(0x12), '12') - self.assertEqual(_op_push(0x4b), '4b') - self.assertEqual(_op_push(0x4c), '4c4c') - self.assertEqual(_op_push(0xfe), '4cfe') - self.assertEqual(_op_push(0xff), '4cff') - self.assertEqual(_op_push(0x100), '4d0001') - self.assertEqual(_op_push(0x1234), '4d3412') - self.assertEqual(_op_push(0xfffe), '4dfeff') - self.assertEqual(_op_push(0xffff), '4dffff') - self.assertEqual(_op_push(0x10000), '4e00000100') - self.assertEqual(_op_push(0x12345678), '4e78563412') + self.assertEqual(_op_push(0x00), bfh('00')) + self.assertEqual(_op_push(0x12), bfh('12')) + self.assertEqual(_op_push(0x4b), bfh('4b')) + self.assertEqual(_op_push(0x4c), bfh('4c4c')) + self.assertEqual(_op_push(0xfe), bfh('4cfe')) + self.assertEqual(_op_push(0xff), bfh('4cff')) + self.assertEqual(_op_push(0x100), bfh('4d0001')) + self.assertEqual(_op_push(0x1234), bfh('4d3412')) + self.assertEqual(_op_push(0xfffe), bfh('4dfeff')) + self.assertEqual(_op_push(0xffff), bfh('4dffff')) + self.assertEqual(_op_push(0x10000), bfh('4e00000100')) + self.assertEqual(_op_push(0x12345678), bfh('4e78563412')) def test_script_num_to_hex(self): # test vectors from https://github.com/btcsuite/btcd/blob/fdc2bc867bda6b351191b5872d2da8270df00d13/txscript/scriptnum.go#L77 - self.assertEqual(script_num_to_hex(127), '7f') - self.assertEqual(script_num_to_hex(-127), 'ff') - self.assertEqual(script_num_to_hex(128), '8000') - self.assertEqual(script_num_to_hex(-128), '8080') - self.assertEqual(script_num_to_hex(129), '8100') - self.assertEqual(script_num_to_hex(-129), '8180') - self.assertEqual(script_num_to_hex(256), '0001') - self.assertEqual(script_num_to_hex(-256), '0081') - self.assertEqual(script_num_to_hex(32767), 'ff7f') - self.assertEqual(script_num_to_hex(-32767), 'ffff') - self.assertEqual(script_num_to_hex(32768), '008000') - self.assertEqual(script_num_to_hex(-32768), '008080') + self.assertEqual(script_num_to_bytes(127), bfh('7f')) + self.assertEqual(script_num_to_bytes(-127), bfh('ff')) + self.assertEqual(script_num_to_bytes(128), bfh('8000')) + self.assertEqual(script_num_to_bytes(-128), bfh('8080')) + self.assertEqual(script_num_to_bytes(129), bfh('8100')) + self.assertEqual(script_num_to_bytes(-129), bfh('8180')) + self.assertEqual(script_num_to_bytes(256), bfh('0001')) + self.assertEqual(script_num_to_bytes(-256), bfh('0081')) + self.assertEqual(script_num_to_bytes(32767), bfh('ff7f')) + self.assertEqual(script_num_to_bytes(-32767), bfh('ffff')) + self.assertEqual(script_num_to_bytes(32768), bfh('008000')) + self.assertEqual(script_num_to_bytes(-32768), bfh('008080')) def test_push_script(self): # https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#push-operators - self.assertEqual(push_script(''), bytes([opcodes.OP_0]).hex()) - self.assertEqual(push_script('07'), bytes([opcodes.OP_7]).hex()) - self.assertEqual(push_script('10'), bytes([opcodes.OP_16]).hex()) - self.assertEqual(push_script('81'), bytes([opcodes.OP_1NEGATE]).hex()) - self.assertEqual(push_script('11'), '0111') - self.assertEqual(push_script(75 * '42'), '4b' + 75 * '42') - self.assertEqual(push_script(76 * '42'), (bytes([opcodes.OP_PUSHDATA1]) + bfh('4c' + 76 * '42')).hex()) - self.assertEqual(push_script(100 * '42'), (bytes([opcodes.OP_PUSHDATA1]) + bfh('64' + 100 * '42')).hex()) - self.assertEqual(push_script(255 * '42'), (bytes([opcodes.OP_PUSHDATA1]) + bfh('ff' + 255 * '42')).hex()) - self.assertEqual(push_script(256 * '42'), (bytes([opcodes.OP_PUSHDATA2]) + bfh('0001' + 256 * '42')).hex()) - self.assertEqual(push_script(520 * '42'), (bytes([opcodes.OP_PUSHDATA2]) + bfh('0802' + 520 * '42')).hex()) + self.assertEqual(push_script(b""), bytes([opcodes.OP_0])) + self.assertEqual(push_script(b'\x07'), bytes([opcodes.OP_7])) + self.assertEqual(push_script(b'\x10'), bytes([opcodes.OP_16])) + self.assertEqual(push_script(b'\x81'), bytes([opcodes.OP_1NEGATE])) + self.assertEqual(push_script(b'\x11'), bfh('0111')) + self.assertEqual(push_script(75 * b'\x42'), bfh('4b' + 75 * '42')) + self.assertEqual(push_script(76 * b'\x42'), bytes([opcodes.OP_PUSHDATA1]) + bfh('4c' + 76 * '42')) + self.assertEqual(push_script(100 * b'\x42'), bytes([opcodes.OP_PUSHDATA1]) + bfh('64' + 100 * '42')) + self.assertEqual(push_script(255 * b'\x42'), bytes([opcodes.OP_PUSHDATA1]) + bfh('ff' + 255 * '42')) + self.assertEqual(push_script(256 * b'\x42'), bytes([opcodes.OP_PUSHDATA2]) + bfh('0001' + 256 * '42')) + self.assertEqual(push_script(520 * b'\x42'), bytes([opcodes.OP_PUSHDATA2]) + bfh('0802' + 520 * '42')) def test_add_number_to_script(self): # https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#numbers @@ -528,17 +508,17 @@ class Test_bitcoin(ElectrumTestCase): # bech32/bech32m native segwit # test vectors from BIP-0173 # note: the ones that are commented out have been invalidated by BIP-0350 - self.assertEqual(address_to_script('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4'), '0014751e76e8199196d454941c45d1b3a323f1433bd6') + self.assertEqual(address_to_script('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4').hex(), '0014751e76e8199196d454941c45d1b3a323f1433bd6') # self.assertEqual(address_to_script('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx'), '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6') # self.assertEqual(address_to_script('BC1SW50QA3JX3S'), '6002751e') # self.assertEqual(address_to_script('bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'), '5210751e76e8199196d454941c45d1b3a323') # bech32/bech32m native segwit # test vectors from BIP-0350 - self.assertEqual(address_to_script('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y'), '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6') - self.assertEqual(address_to_script('BC1SW50QGDZ25J'), '6002751e') - self.assertEqual(address_to_script('bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs'), '5210751e76e8199196d454941c45d1b3a323') - self.assertEqual(address_to_script('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0'), '512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798') + self.assertEqual(address_to_script('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y').hex(), '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6') + self.assertEqual(address_to_script('BC1SW50QGDZ25J').hex(), '6002751e') + self.assertEqual(address_to_script('bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs').hex(), '5210751e76e8199196d454941c45d1b3a323') + self.assertEqual(address_to_script('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0').hex(), '512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798') # invalid addresses (from BIP-0173) self.assertFalse(is_address('tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty')) @@ -570,12 +550,12 @@ class Test_bitcoin(ElectrumTestCase): self.assertFalse(is_address('bc1gmk9yu')) # base58 P2PKH - self.assertEqual(address_to_script('14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG'), '76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac') - self.assertEqual(address_to_script('1BEqfzh4Y3zzLosfGhw1AsqbEKVW6e1qHv'), '76a914704f4b81cadb7bf7e68c08cd3657220f680f863c88ac') + self.assertEqual(address_to_script('14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG').hex(), '76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac') + self.assertEqual(address_to_script('1BEqfzh4Y3zzLosfGhw1AsqbEKVW6e1qHv').hex(), '76a914704f4b81cadb7bf7e68c08cd3657220f680f863c88ac') # base58 P2SH - self.assertEqual(address_to_script('35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT'), 'a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487') - self.assertEqual(address_to_script('3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji'), 'a914f47c8954e421031ad04ecd8e7752c9479206b9d387') + self.assertEqual(address_to_script('35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT').hex(), 'a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487') + self.assertEqual(address_to_script('3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji').hex(), 'a914f47c8954e421031ad04ecd8e7752c9479206b9d387') def test_address_to_payload(self): # bech32 P2WPKH @@ -698,14 +678,14 @@ class Test_bitcoin_testnet(ElectrumTestCase): def test_address_to_script(self): # bech32/bech32m native segwit # test vectors from BIP-0173 - self.assertEqual(address_to_script('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7'), '00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262') - self.assertEqual(address_to_script('tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy'), '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433') + self.assertEqual(address_to_script('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7').hex(), '00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262') + self.assertEqual(address_to_script('tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy').hex(), '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433') # bech32/bech32m native segwit # test vectors from BIP-0350 - self.assertEqual(address_to_script('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7'), '00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262') - self.assertEqual(address_to_script('tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy'), '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433') - self.assertEqual(address_to_script('tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c'), '5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433') + self.assertEqual(address_to_script('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7').hex(), '00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262') + self.assertEqual(address_to_script('tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy').hex(), '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433') + self.assertEqual(address_to_script('tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c').hex(), '5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433') # invalid addresses (from BIP-0173) self.assertFalse(is_address('tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty')) @@ -737,12 +717,12 @@ class Test_bitcoin_testnet(ElectrumTestCase): self.assertFalse(is_address('bc1gmk9yu')) # base58 P2PKH - self.assertEqual(address_to_script('mutXcGt1CJdkRvXuN2xoz2quAAQYQ59bRX'), '76a9149da64e300c5e4eb4aaffc9c2fd465348d5618ad488ac') - self.assertEqual(address_to_script('miqtaRTkU3U8rzwKbEHx3g8FSz8GJtPS3K'), '76a914247d2d5b6334bdfa2038e85b20fc15264f8e5d2788ac') + self.assertEqual(address_to_script('mutXcGt1CJdkRvXuN2xoz2quAAQYQ59bRX').hex(), '76a9149da64e300c5e4eb4aaffc9c2fd465348d5618ad488ac') + self.assertEqual(address_to_script('miqtaRTkU3U8rzwKbEHx3g8FSz8GJtPS3K').hex(), '76a914247d2d5b6334bdfa2038e85b20fc15264f8e5d2788ac') # base58 P2SH - self.assertEqual(address_to_script('2N3LSvr3hv5EVdfcrxg2Yzecf3SRvqyBE4p'), 'a9146eae23d8c4a941316017946fc761a7a6c85561fb87') - self.assertEqual(address_to_script('2NE4ZdmxFmUgwu5wtfoN2gVniyMgRDYq1kk'), 'a914e4567743d378957cd2ee7072da74b1203c1a7a0b87') + self.assertEqual(address_to_script('2N3LSvr3hv5EVdfcrxg2Yzecf3SRvqyBE4p').hex(), 'a9146eae23d8c4a941316017946fc761a7a6c85561fb87') + self.assertEqual(address_to_script('2NE4ZdmxFmUgwu5wtfoN2gVniyMgRDYq1kk').hex(), 'a914e4567743d378957cd2ee7072da74b1203c1a7a0b87') class Test_xprv_xpub(ElectrumTestCase): diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py index 096b02781..1eae2580d 100644 --- a/tests/test_lnpeer.py +++ b/tests/test_lnpeer.py @@ -1208,7 +1208,7 @@ class TestPeerDirect(TestPeer): # shutdown script bob_uss_pub = lnutil.privkey_to_pubkey(os.urandom(32)) bob_uss_addr = bitcoin.pubkey_to_address('p2wpkh', bob_uss_pub.hex()) - bob_uss = bfh(bitcoin.address_to_script(bob_uss_addr)) + bob_uss = bitcoin.address_to_script(bob_uss_addr) # bob commits to close to bob_uss alice_channel.config[HTLCOwner.REMOTE].upfront_shutdown_script = bob_uss diff --git a/tests/test_lnutil.py b/tests/test_lnutil.py index f17f29d3a..b6f5b0101 100644 --- a/tests/test_lnutil.py +++ b/tests/test_lnutil.py @@ -508,7 +508,7 @@ class TestLNUtil(ElectrumTestCase): htlc_payment_preimage[4] = b"\x04" * 32 htlc[4] = make_received_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[4]), cltv_abs=htlc_cltv_timeout[4]) - remote_signature = "304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b70606" + remote_signature = bfh("304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b70606") output_commit_tx = "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" htlc_obj = {} @@ -594,7 +594,7 @@ class TestLNUtil(ElectrumTestCase): our_htlc_tx_witness = make_htlc_tx_witness( remotehtlcsig=bfh(remote_htlc_sig) + b"\x01", # 0x01 is SIGHASH_ALL - localhtlcsig=bfh(local_sig), + localhtlcsig=local_sig, payment_preimage=htlc_payment_preimage if success else b'', # will put 00 on witness if timeout witness_script=htlc) our_htlc_tx._inputs[0].witness = our_htlc_tx_witness @@ -604,7 +604,7 @@ class TestLNUtil(ElectrumTestCase): to_local_msat= 6988000000 to_remote_msat= 3000000000 local_feerate_per_kw= 9651181 - remote_signature = "3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e" + remote_signature = bfh("3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e") output_commit_tx= "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" our_commit_tx = make_commitment( @@ -633,7 +633,7 @@ class TestLNUtil(ElectrumTestCase): to_local_msat= 6988000000 to_remote_msat= 3000000000 local_feerate_per_kw= 9651936 - remote_signature = "3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e" + remote_signature = bfh("3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e") output_commit_tx= "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" our_commit_tx = make_commitment( @@ -701,7 +701,7 @@ class TestLNUtil(ElectrumTestCase): # actual commitment transaction fee = 10860 # to_local amount 6989140 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) - remote_signature = "3045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c0" + remote_signature = bfh("3045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c0") # local_signature = 3044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c3836939 our_commit_tx = make_commitment( ctn=commitment_number, @@ -728,14 +728,14 @@ class TestLNUtil(ElectrumTestCase): def sign_and_insert_remote_sig(self, tx: PartialTransaction, remote_pubkey, remote_signature, pubkey, privkey): assert type(remote_pubkey) is bytes assert len(remote_pubkey) == 33 - assert type(remote_signature) is str + assert type(remote_signature) is bytes assert type(pubkey) is bytes assert type(privkey) is bytes assert len(pubkey) == 33 assert len(privkey) == 33 tx.sign({pubkey.hex(): (privkey[:-1], True)}) - sighash = Sighash.to_sigbytes(Sighash.ALL).hex() - tx.add_signature_to_txin(txin_idx=0, signing_pubkey=remote_pubkey.hex(), sig=remote_signature + sighash) + sighash = Sighash.to_sigbytes(Sighash.ALL) + tx.add_signature_to_txin(txin_idx=0, signing_pubkey=remote_pubkey, sig=remote_signature + sighash) def test_get_compressed_pubkey_from_bech32(self): self.assertEqual(b'\x03\x84\xef\x87\xd9d\xa2\xaaa7=\xff\xb8\xfe=t8[}>;\n\x13\xa8e\x8eo:\xf5Mi\xb5H', diff --git a/tests/test_transaction.py b/tests/test_transaction.py index 50fbafeda..060d06637 100644 --- a/tests/test_transaction.py +++ b/tests/test_transaction.py @@ -18,7 +18,7 @@ signed_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b v2_blob = "0200000001191601a44a81e061502b7bfbc6eaa1cef6d1e6af5308ef96c9342f71dbf4b9b5000000006b483045022100a6d44d0a651790a477e75334adfb8aae94d6612d01187b2c02526e340a7fd6c8022028bdf7a64a54906b13b145cd5dab21a26bd4b85d6044e9b97bceab5be44c2a9201210253e8e0254b0c95776786e40984c1aa32a7d03efa6bdacdea5f421b774917d346feffffff026b20fa04000000001976a914024db2e87dd7cfd0e5f266c5f212e21a31d805a588aca0860100000000001976a91421919b94ae5cefcdf0271191459157cdb41c4cbf88aca6240700" signed_segwit_blob = "01000000000101b66d722484f2db63e827ebf41d02684fed0c6550e85015a6c9d41ef216a8a6f00000000000fdffffff0280c3c90100000000160014b65ce60857f7e7892b983851c2a8e3526d09e4ab64bac30400000000160014c478ebbc0ab2097706a98e10db7cf101839931c4024730440220789c7d47f876638c58d98733c30ae9821c8fa82b470285dcdf6db5994210bf9f02204163418bbc44af701212ad42d884cc613f3d3d831d2d0cc886f767cca6e0235e012103083a6dc250816d771faa60737bfe78b23ad619f6b458e0a1f1688e3a0605e79c00000000" -signed_blob_signatures = ['3046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d98501',] +signed_blob_signatures = [bfh('3046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d98501'),] class TestBCDataStream(ElectrumTestCase): @@ -81,11 +81,11 @@ class TestBCDataStream(ElectrumTestCase): class TestTransaction(ElectrumTestCase): def test_match_against_script_template(self): - script = bfh(construct_script([opcodes.OP_5, bytes(29)])) + script = construct_script([opcodes.OP_5, bytes(29)]) self.assertTrue(match_script_against_template(script, SCRIPTPUBKEY_TEMPLATE_ANYSEGWIT)) - script = bfh(construct_script([opcodes.OP_NOP, bytes(30)])) + script = construct_script([opcodes.OP_NOP, bytes(30)]) self.assertFalse(match_script_against_template(script, SCRIPTPUBKEY_TEMPLATE_ANYSEGWIT)) - script = bfh(construct_script([opcodes.OP_0, bytes(50)])) + script = construct_script([opcodes.OP_0, bytes(50)]) self.assertFalse(match_script_against_template(script, SCRIPTPUBKEY_TEMPLATE_ANYSEGWIT)) def test_tx_update_signatures(self): @@ -875,9 +875,9 @@ class TestTransactionTestnet(ElectrumTestCase): prevout = TxOutpoint(txid=bfh('6d500966f9e494b38a04545f0cea35fc7b3944e341a64b804fed71cdee11d434'), out_idx=1) txin = PartialTxInput(prevout=prevout) txin.nsequence = 2 ** 32 - 3 - redeem_script = bfh(construct_script([ + redeem_script = construct_script([ locktime, opcodes.OP_CHECKLOCKTIMEVERIFY, opcodes.OP_DROP, pubkey, opcodes.OP_CHECKSIG, - ])) + ]) txin.redeem_script = redeem_script # Build the Transaction Output @@ -887,7 +887,7 @@ class TestTransactionTestnet(ElectrumTestCase): # Build and sign the transaction tx = PartialTransaction.from_io([txin], [txout], locktime=locktime, version=1) sig = tx.sign_txin(0, privkey) - txin.script_sig = bfh(construct_script([sig, redeem_script])) + txin.script_sig = construct_script([sig, redeem_script]) # note: in testnet3 chain, signature differs (no low-R grinding), # so txid there is: a8110bbdd40d65351f615897d98c33cbe33e4ebedb4ba2fc9e8c644423dadc93 @@ -903,10 +903,10 @@ class TestTransactionTestnet(ElectrumTestCase): # Build the Transaction Input _, privkey, compressed = deserialize_privkey(wif) pubkey = ECPrivkey(privkey).get_public_key_hex(compressed=compressed) - witness_script = bfh(construct_script([ + witness_script = construct_script([ locktime, opcodes.OP_CHECKLOCKTIMEVERIFY, opcodes.OP_DROP, pubkey, opcodes.OP_CHECKSIG, - ])) - from_addr = bitcoin.script_to_p2wsh(witness_script.hex()) + ]) + from_addr = bitcoin.script_to_p2wsh(witness_script) self.assertEqual("tb1q9dn6qke9924xe3zmptmhrdge0s043pjxpjndypgnu2t9fvsd4crs2qjuer", from_addr) prevout = TxOutpoint(txid=bfh('8680971efd5203025cffe746f8598d0a704fae81f236ffe009c2609ec673d59a'), out_idx=0) txin = PartialTxInput(prevout=prevout) @@ -922,7 +922,7 @@ class TestTransactionTestnet(ElectrumTestCase): # Build and sign the transaction tx = PartialTransaction.from_io([txin], [txout], locktime=locktime, version=2) sig = tx.sign_txin(0, privkey) - txin.witness = bfh(construct_witness([sig, witness_script])) + txin.witness = construct_witness([sig, witness_script]) self.assertEqual('1cdb274755b144090c7134b6459e8d4cb6b4552fe620102836d751e8389b2694', tx.txid()) @@ -949,7 +949,7 @@ class TestSighashTypes(ElectrumTestCase): tx = PartialTransaction.from_io(inputs=[self.txin], outputs=[self.txout1,self.txout2], locktime=self.locktime, version=1, BIP69_sort=False) sig = tx.sign_txin(0,privkey) self.assertEqual('304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01', - sig) + sig.hex()) def test_check_sighash_types_sighash_none(self): self.txin.sighash=Sighash.NONE @@ -957,7 +957,7 @@ class TestSighashTypes(ElectrumTestCase): tx = PartialTransaction.from_io(inputs=[self.txin], outputs=[self.txout1,self.txout2], locktime=self.locktime, version=1, BIP69_sort=False) sig = tx.sign_txin(0,privkey) self.assertEqual('3044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502', - sig) + sig.hex()) def test_check_sighash_types_sighash_single(self): self.txin.sighash=Sighash.SINGLE @@ -965,7 +965,7 @@ class TestSighashTypes(ElectrumTestCase): tx = PartialTransaction.from_io(inputs=[self.txin], outputs=[self.txout1,self.txout2], locktime=self.locktime, version=1, BIP69_sort=False) sig = tx.sign_txin(0,privkey) self.assertEqual('3044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403', - sig) + sig.hex()) @disable_ecdsa_r_value_grinding def test_check_sighash_types_sighash_all_anyonecanpay(self): @@ -974,7 +974,7 @@ class TestSighashTypes(ElectrumTestCase): tx = PartialTransaction.from_io(inputs=[self.txin], outputs=[self.txout1,self.txout2], locktime=self.locktime, version=1, BIP69_sort=False) sig = tx.sign_txin(0,privkey) self.assertEqual('3045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381', - sig) + sig.hex()) @disable_ecdsa_r_value_grinding def test_check_sighash_types_sighash_none_anyonecanpay(self): @@ -983,7 +983,7 @@ class TestSighashTypes(ElectrumTestCase): tx = PartialTransaction.from_io(inputs=[self.txin], outputs=[self.txout1,self.txout2], locktime=self.locktime, version=1, BIP69_sort=False) sig = tx.sign_txin(0,privkey) self.assertEqual('3045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a0882', - sig) + sig.hex()) def test_check_sighash_types_sighash_single_anyonecanpay(self): self.txin.sighash=Sighash.SINGLE|Sighash.ANYONECANPAY @@ -991,4 +991,4 @@ class TestSighashTypes(ElectrumTestCase): tx = PartialTransaction.from_io(inputs=[self.txin], outputs=[self.txout1,self.txout2], locktime=self.locktime, version=1, BIP69_sort=False) sig = tx.sign_txin(0,privkey) self.assertEqual('30440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783', - sig) + sig.hex()) From 13d9677e534cc82aeace3d428a6bc78eadb0e1d4 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 29 Apr 2024 16:32:19 +0000 Subject: [PATCH 2/2] transaction: tx.sign API change: rm hex usage --- electrum/commands.py | 8 ++++---- electrum/gui/qt/main_window.py | 22 ++++++++++++++++++---- electrum/gui/qt/send_tab.py | 4 ++-- electrum/gui/qt/transaction_dialog.py | 6 +++--- electrum/keystore.py | 14 ++++++++------ electrum/lnchannel.py | 2 +- electrum/lnsweep.py | 6 +++--- electrum/lnutil.py | 2 +- electrum/transaction.py | 15 +++++++-------- electrum/wallet.py | 18 ++++++++++-------- tests/test_lnutil.py | 4 ++-- 11 files changed, 59 insertions(+), 42 deletions(-) diff --git a/electrum/commands.py b/electrum/commands.py index dd7f1a586..890f347c7 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -422,9 +422,9 @@ class Commands: sec = txin_dict.get('privkey') if sec: txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec) - pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed) - keypairs[pubkey] = privkey, compressed - desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey, script_type=txin_type) + pubkey = ecc.ECPrivkey(privkey).get_public_key_bytes(compressed=compressed) + keypairs[pubkey] = privkey + desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey.hex(), script_type=txin_type) txin.script_descriptor = desc inputs.append(txin) @@ -465,7 +465,7 @@ class Commands: if address in txins_dict.keys(): for txin in txins_dict[address]: txin.script_descriptor = desc - tx.sign({pubkey.hex(): (priv2, compressed)}) + tx.sign({pubkey: priv2}) return tx.serialize() diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index c755d896e..2cb35999c 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -34,7 +34,7 @@ import base64 from functools import partial import queue import asyncio -from typing import Optional, TYPE_CHECKING, Sequence, List, Union, Dict, Set +from typing import Optional, TYPE_CHECKING, Sequence, List, Union, Dict, Set, Mapping import concurrent.futures from PyQt5.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont, QFontMetrics @@ -1106,7 +1106,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): self, tx: Transaction, *, - external_keypairs=None, + external_keypairs: Mapping[bytes, bytes] = None, payment_identifier: PaymentIdentifier = None, ): show_transaction(tx, parent=self, external_keypairs=external_keypairs, payment_identifier=payment_identifier) @@ -1269,10 +1269,24 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener): self.send_tab.broadcast_transaction(tx, payment_identifier=payment_identifier) @protected - def sign_tx(self, tx, *, callback, external_keypairs, password): + def sign_tx( + self, + tx: PartialTransaction, + *, + callback, + external_keypairs: Optional[Mapping[bytes, bytes]], + password, + ): self.sign_tx_with_password(tx, callback=callback, password=password, external_keypairs=external_keypairs) - def sign_tx_with_password(self, tx: PartialTransaction, *, callback, password, external_keypairs=None): + def sign_tx_with_password( + self, + tx: PartialTransaction, + *, + callback, + password, + external_keypairs: Mapping[bytes, bytes] = None, + ): '''Sign the transaction in a separate thread. When done, calls the callback with a success code of True or False. ''' diff --git a/electrum/gui/qt/send_tab.py b/electrum/gui/qt/send_tab.py index 8566b6a79..e14ae70e2 100644 --- a/electrum/gui/qt/send_tab.py +++ b/electrum/gui/qt/send_tab.py @@ -3,7 +3,7 @@ # file LICENCE or http://www.opensource.org/licenses/mit-license.php from decimal import Decimal -from typing import Optional, TYPE_CHECKING, Sequence, List, Callable, Union +from typing import Optional, TYPE_CHECKING, Sequence, List, Callable, Union, Mapping from PyQt5.QtCore import pyqtSignal, QPoint, QSize, Qt from PyQt5.QtWidgets import (QLabel, QVBoxLayout, QGridLayout, QHBoxLayout, QWidget, QToolTip, QPushButton, QApplication) @@ -295,7 +295,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger): outputs: List[PartialTxOutput], *, nonlocal_only=False, - external_keypairs=None, + external_keypairs: Mapping[bytes, bytes] = None, get_coins: Callable[..., Sequence[PartialTxInput]] = None, invoice: Optional[Invoice] = None ) -> None: diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py index 8f1835751..eb3b80694 100644 --- a/electrum/gui/qt/transaction_dialog.py +++ b/electrum/gui/qt/transaction_dialog.py @@ -30,7 +30,7 @@ import copy import datetime import traceback import time -from typing import TYPE_CHECKING, Callable, Optional, List, Union, Tuple +from typing import TYPE_CHECKING, Callable, Optional, List, Union, Tuple, Mapping from functools import partial from decimal import Decimal @@ -410,7 +410,7 @@ def show_transaction( *, parent: 'ElectrumWindow', prompt_if_unsaved: bool = False, - external_keypairs=None, + external_keypairs: Mapping[bytes, bytes] = None, payment_identifier: 'PaymentIdentifier' = None, ): try: @@ -438,7 +438,7 @@ class TxDialog(QDialog, MessageBoxMixin): *, parent: 'ElectrumWindow', prompt_if_unsaved: bool, - external_keypairs=None, + external_keypairs: Mapping[bytes, bytes] = None, payment_identifier: 'PaymentIdentifier' = None, ): '''Transactions in the wallet will show their description. diff --git a/electrum/keystore.py b/electrum/keystore.py index f71ba1926..4c5c5b804 100644 --- a/electrum/keystore.py +++ b/electrum/keystore.py @@ -107,13 +107,13 @@ class KeyStore(Logger, ABC): """Returns whether the keystore can be encrypted with a password.""" pass - def _get_tx_derivations(self, tx: 'PartialTransaction') -> Dict[str, Union[Sequence[int], str]]: + def _get_tx_derivations(self, tx: 'PartialTransaction') -> Dict[bytes, Union[Sequence[int], str]]: keypairs = {} for txin in tx.inputs(): keypairs.update(self._get_txin_derivations(txin)) return keypairs - def _get_txin_derivations(self, txin: 'PartialTxInput') -> Dict[str, Union[Sequence[int], str]]: + def _get_txin_derivations(self, txin: 'PartialTxInput') -> Dict[bytes, Union[Sequence[int], str]]: if txin.is_complete(): return {} keypairs = {} @@ -124,7 +124,7 @@ class KeyStore(Logger, ABC): derivation = self.get_pubkey_derivation(pubkey, txin) if not derivation: continue - keypairs[pubkey.hex()] = derivation + keypairs[pubkey] = derivation return keypairs def can_sign(self, tx: 'Transaction', *, ignore_watching_only=False) -> bool: @@ -237,9 +237,11 @@ class Software_KeyStore(KeyStore): # Raise if password is not correct. self.check_password(password) # Add private keys - keypairs = self._get_tx_derivations(tx) - for k, v in keypairs.items(): - keypairs[k] = self.get_private_key(v, password) + keypairs = {} + pubkey_to_deriv_map = self._get_tx_derivations(tx) + for pubkey, deriv in pubkey_to_deriv_map.items(): + privkey, is_compressed = self.get_private_key(deriv, password) + keypairs[pubkey] = privkey # Sign if keypairs: tx.sign(keypairs) diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py index ef171eacd..18c5156ca 100644 --- a/electrum/lnchannel.py +++ b/electrum/lnchannel.py @@ -1614,7 +1614,7 @@ class Channel(AbstractChannel): def force_close_tx(self) -> PartialTransaction: tx = self.get_latest_commitment(LOCAL) 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: self.config[LOCAL].multisig_key.privkey}) remote_sig = self.config[LOCAL].current_commitment_signature remote_sig = ecc.ecdsa_der_sig_from_ecdsa_sig64(remote_sig) + Sighash.to_sigbytes(Sighash.ALL) tx.add_signature_to_txin(txin_idx=0, diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py index 539d39c28..d80707004 100644 --- a/electrum/lnsweep.py +++ b/electrum/lnsweep.py @@ -511,12 +511,12 @@ def create_sweeptx_their_ctx_to_remote( sweep_address: str, ctx: Transaction, output_idx: int, our_payment_privkey: ecc.ECPrivkey, config: SimpleConfig) -> Optional[PartialTransaction]: - our_payment_pubkey = our_payment_privkey.get_public_key_hex(compressed=True) + our_payment_pubkey = our_payment_privkey.get_public_key_bytes(compressed=True) val = ctx.outputs()[output_idx].value prevout = TxOutpoint(txid=bfh(ctx.txid()), out_idx=output_idx) txin = PartialTxInput(prevout=prevout) txin._trusted_value_sats = val - desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=our_payment_pubkey, script_type='p2wpkh') + desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=our_payment_pubkey.hex(), script_type='p2wpkh') txin.script_descriptor = desc sweep_inputs = [txin] tx_size_bytes = 110 # approx size of p2wpkh->p2wpkh @@ -526,7 +526,7 @@ def create_sweeptx_their_ctx_to_remote( sweep_outputs = [PartialTxOutput.from_address_and_value(sweep_address, outvalue)] sweep_tx = PartialTransaction.from_io(sweep_inputs, sweep_outputs) sweep_tx.set_rbf(True) - sweep_tx.sign({our_payment_pubkey: (our_payment_privkey.get_secret_bytes(), True)}) + sweep_tx.sign({our_payment_pubkey: our_payment_privkey.get_secret_bytes()}) if not sweep_tx.is_complete(): raise Exception('channel close sweep tx is not complete') return sweep_tx diff --git a/electrum/lnutil.py b/electrum/lnutil.py index 44aad8fb8..102548c58 100644 --- a/electrum/lnutil.py +++ b/electrum/lnutil.py @@ -1084,7 +1084,7 @@ def make_commitment_output_to_remote_address(remote_payment_pubkey: bytes) -> st return bitcoin.pubkey_to_address('p2wpkh', remote_payment_pubkey.hex()) 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: local_config.multisig_key.privkey}) sig = tx.inputs()[0].part_sigs[local_config.multisig_key.pubkey] sig_64 = ecdsa_sig64_from_der_sig(sig[:-1]) return sig_64 diff --git a/electrum/transaction.py b/electrum/transaction.py index c407c95b2..5ae075f6b 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -33,7 +33,7 @@ import sys import io import base64 from typing import (Sequence, Union, NamedTuple, Tuple, Optional, Iterable, - Callable, List, Dict, Set, TYPE_CHECKING) + Callable, List, Dict, Set, TYPE_CHECKING, Mapping) from collections import defaultdict from enum import IntEnum import itertools @@ -2130,22 +2130,21 @@ class PartialTransaction(Transaction): preimage = nVersion + txins + txouts + nLocktime + nHashType return preimage - def sign(self, keypairs) -> None: - # keypairs: pubkey_hex -> (secret_bytes, is_compressed) + def sign(self, keypairs: Mapping[bytes, bytes]) -> None: + # keypairs: pubkey_bytes -> secret_bytes bip143_shared_txdigest_fields = self._calc_bip143_shared_txdigest_fields() for i, txin in enumerate(self.inputs()): - pubkeys = [pk.hex() for pk in txin.pubkeys] - for pubkey in pubkeys: + for pubkey in txin.pubkeys: if txin.is_complete(): break if pubkey not in keypairs: continue _logger.info(f"adding signature for {pubkey}. spending utxo {txin.prevout.to_str()}") - sec, compressed = keypairs[pubkey] + sec = keypairs[pubkey] sig = self.sign_txin(i, sec, bip143_shared_txdigest_fields=bip143_shared_txdigest_fields) - self.add_signature_to_txin(txin_idx=i, signing_pubkey=bfh(pubkey), sig=sig) + self.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey, sig=sig) - _logger.debug(f"is_complete {self.is_complete()}") + _logger.debug(f"tx.sign() finished. is_complete={self.is_complete()}") self.invalidate_ser_cache() def sign_txin( diff --git a/electrum/wallet.py b/electrum/wallet.py index 088d6d091..101d431d5 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -40,7 +40,7 @@ from functools import partial from collections import defaultdict from numbers import Number from decimal import Decimal -from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequence, Dict, Any, Set, Iterable +from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequence, Dict, Any, Set, Iterable, Mapping from abc import ABC, abstractmethod import itertools import threading @@ -136,20 +136,22 @@ async def _append_utxos_to_inputs( await group.spawn(append_single_utxo(item)) -async def sweep_preparations(privkeys, network: 'Network', imax=100): +async def sweep_preparations( + privkeys: Iterable[str], network: 'Network', imax=100, +) -> Tuple[Sequence[PartialTxInput], Mapping[bytes, bytes]]: - async def find_utxos_for_privkey(txin_type, privkey, compressed): - pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed) - desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey, script_type=txin_type) + async def find_utxos_for_privkey(txin_type: str, privkey: bytes, compressed: bool): + pubkey = ecc.ECPrivkey(privkey).get_public_key_bytes(compressed=compressed) + desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey.hex(), script_type=txin_type) await _append_utxos_to_inputs( inputs=inputs, network=network, script_descriptor=desc, imax=imax) - keypairs[pubkey] = privkey, compressed + keypairs[pubkey] = privkey inputs = [] # type: List[PartialTxInput] - keypairs = {} + keypairs = {} # type: Dict[bytes, bytes] async with OldTaskGroup() as group: for sec in privkeys: txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec) @@ -169,7 +171,7 @@ async def sweep_preparations(privkeys, network: 'Network', imax=100): async def sweep( - privkeys, + privkeys: Iterable[str], *, network: 'Network', config: 'SimpleConfig', diff --git a/tests/test_lnutil.py b/tests/test_lnutil.py index b6f5b0101..3689253d7 100644 --- a/tests/test_lnutil.py +++ b/tests/test_lnutil.py @@ -725,7 +725,7 @@ class TestLNUtil(ElectrumTestCase): ref_commit_tx_str = '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311054a56a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c383693901483045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220' self.assertEqual(str(our_commit_tx), ref_commit_tx_str) - def sign_and_insert_remote_sig(self, tx: PartialTransaction, remote_pubkey, remote_signature, pubkey, privkey): + def sign_and_insert_remote_sig(self, tx: PartialTransaction, remote_pubkey: bytes, remote_signature: bytes, pubkey: bytes, privkey: bytes): assert type(remote_pubkey) is bytes assert len(remote_pubkey) == 33 assert type(remote_signature) is bytes @@ -733,7 +733,7 @@ class TestLNUtil(ElectrumTestCase): assert type(privkey) is bytes assert len(pubkey) == 33 assert len(privkey) == 33 - tx.sign({pubkey.hex(): (privkey[:-1], True)}) + tx.sign({pubkey: privkey[:-1]}) sighash = Sighash.to_sigbytes(Sighash.ALL) tx.add_signature_to_txin(txin_idx=0, signing_pubkey=remote_pubkey, sig=remote_signature + sighash)