Browse Source

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.
master
SomberNight 2 years ago
parent
commit
2f1095510c
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 4
      electrum/address_synchronizer.py
  2. 6
      electrum/bip32.py
  3. 145
      electrum/bitcoin.py
  4. 35
      electrum/blockchain.py
  5. 4
      electrum/commands.py
  6. 2
      electrum/constants.py
  7. 30
      electrum/descriptor.py
  8. 2
      electrum/ecc.py
  9. 2
      electrum/gui/text.py
  10. 32
      electrum/lnchannel.py
  11. 20
      electrum/lnpeer.py
  12. 28
      electrum/lnsweep.py
  13. 40
      electrum/lnutil.py
  14. 2
      electrum/lnworker.py
  15. 12
      electrum/payment_identifier.py
  16. 4
      electrum/paymentrequest.py
  17. 4
      electrum/plugins/bitbox02/bitbox02.py
  18. 2
      electrum/plugins/coldcard/coldcard.py
  19. 6
      electrum/plugins/digitalbitbox/digitalbitbox.py
  20. 9
      electrum/plugins/jade/jade.py
  21. 4
      electrum/plugins/keepkey/keepkey.py
  22. 33
      electrum/plugins/ledger/ledger.py
  23. 4
      electrum/plugins/safe_t/safe_t.py
  24. 4
      electrum/plugins/trezor/trezor.py
  25. 2
      electrum/scripts/peers.py
  26. 27
      electrum/submarine_swaps.py
  27. 195
      electrum/transaction.py
  28. 16
      electrum/wallet.py
  29. 4
      electrum/wallet_db.py
  30. 152
      tests/test_bitcoin.py
  31. 2
      tests/test_lnpeer.py
  32. 16
      tests/test_lnutil.py
  33. 34
      tests/test_transaction.py

4
electrum/address_synchronizer.py

@ -341,7 +341,7 @@ class AddressSynchronizer(Logger, EventListener):
for n, txo in enumerate(tx.outputs()): for n, txo in enumerate(tx.outputs()):
v = txo.value v = txo.value
ser = tx_hash + ':%d'%n 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) self.db.add_prevout_by_scripthash(scripthash, prevout=TxOutpoint.from_str(ser), value=v)
addr = txo.address addr = txo.address
if addr and self.is_mine(addr): if addr and self.is_mine(addr):
@ -407,7 +407,7 @@ class AddressSynchronizer(Logger, EventListener):
self.unconfirmed_tx.pop(tx_hash, None) self.unconfirmed_tx.pop(tx_hash, None)
if tx: if tx:
for idx, txo in enumerate(tx.outputs()): 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) prevout = TxOutpoint(bfh(tx_hash), idx)
self.db.remove_prevout_by_scripthash(scripthash, prevout=prevout, value=txo.value) self.db.remove_prevout_by_scripthash(scripthash, prevout=prevout, value=txo.value)
util.trigger_callback('adb_removed_tx', self, tx_hash, tx) util.trigger_callback('adb_removed_tx', self, tx_hash, tx)

6
electrum/bip32.py

@ -11,7 +11,7 @@ from .util import bfh, BitcoinException
from . import constants from . import constants
from . import ecc from . import ecc
from .crypto import hash_160, hmac_oneshot 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 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) is_hardened_child = bool(child_index & BIP32_PRIME)
return _CKD_priv(parent_privkey=parent_privkey, return _CKD_priv(parent_privkey=parent_privkey,
parent_chaincode=parent_chaincode, 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) 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') if child_index & BIP32_PRIME: raise Exception('not possible to derive hardened child from parent pubkey')
return _CKD_pub(parent_pubkey=parent_pubkey, return _CKD_pub(parent_pubkey=parent_pubkey,
parent_chaincode=parent_chaincode, 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. # helper function, callable with arbitrary 'child_index' byte-string.

145
electrum/bitcoin.py

@ -197,34 +197,14 @@ class opcodes(IntEnum):
return bytes([self]).hex() return bytes([self]).hex()
def rev_hex(s: str) -> str: def script_num_to_bytes(i: int) -> bytes:
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:
"""See CScriptNum in Bitcoin Core. """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 ported from https://github.com/bitcoin/bitcoin/blob/8cbc5c4be4be22aca228074f087a374a7ec38be8/src/script/script.h#L326
""" """
if i == 0: if i == 0:
return '' return b""
result = bytearray() result = bytearray()
neg = i < 0 neg = i < 0
@ -238,104 +218,102 @@ def script_num_to_hex(i: int) -> str:
elif neg: elif neg:
result[-1] |= 0x80 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://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer
# https://github.com/bitcoin/bitcoin/blob/efe1ee0d8d7f82150789f1f6840f139289628a2b/src/serialize.h#L247 # https://github.com/bitcoin/bitcoin/blob/efe1ee0d8d7f82150789f1f6840f139289628a2b/src/serialize.h#L247
# "CompactSize" # "CompactSize"
assert i >= 0, i assert i >= 0, i
if i<0xfd: if i < 0xfd:
return int_to_hex(i) return int.to_bytes(i, length=1, byteorder="little", signed=False)
elif i<=0xffff: elif i <= 0xffff:
return "fd"+int_to_hex(i,2) return b"\xfd" + int.to_bytes(i, length=2, byteorder="little", signed=False)
elif i<=0xffffffff: elif i <= 0xffffffff:
return "fe"+int_to_hex(i,4) return b"\xfe" + int.to_bytes(i, length=4, byteorder="little", signed=False)
else: 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: def witness_push(item: bytes) -> bytes:
"""Returns data in the form it should be present in the witness. """Returns data in the form it should be present in the witness."""
hex -> hex return var_int(len(item)) + item
"""
return var_int(len(item) // 2) + item
def _op_push(i: int) -> str: def _op_push(i: int) -> bytes:
if i < opcodes.OP_PUSHDATA1: if i < opcodes.OP_PUSHDATA1:
return int_to_hex(i) return int.to_bytes(i, length=1, byteorder="little", signed=False)
elif i <= 0xff: 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: 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: 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 """Returns pushed data to the script, automatically
choosing canonical opcodes depending on the length of the data. 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 ported from https://github.com/btcsuite/btcd/blob/fdc2bc867bda6b351191b5872d2da8270df00d13/txscript/scriptbuilder.go#L128
""" """
data = bfh(data)
data_len = len(data) data_len = len(data)
# "small integer" opcodes # "small integer" opcodes
if data_len == 0 or data_len == 1 and data[0] == 0: 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: 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: 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: def make_op_return(x: bytes) -> bytes:
return bytes([opcodes.OP_RETURN]) + bytes.fromhex(push_script(x.hex())) return bytes([opcodes.OP_RETURN]) + push_script(x)
def add_number_to_script(i: int) -> bytes: 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.""" """Constructs a witness from the given stack items."""
witness = var_int(len(items)) witness = bytearray()
witness += var_int(len(items))
for item in items: for item in items:
if type(item) is int: if type(item) is int:
item = script_num_to_hex(item) item = script_num_to_bytes(item)
elif isinstance(item, (bytes, bytearray)): elif isinstance(item, (bytes, bytearray)):
item = item.hex() pass # use as-is
else: else:
assert is_hex_str(item) assert is_hex_str(item), repr(item)
item = bfh(item)
witness += witness_push(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.""" """Constructs bitcoin script from given items."""
script = '' script = bytearray()
values = values or {} values = values or {}
for i, item in enumerate(items): for i, item in enumerate(items):
if i in values: if i in values:
item = values[i] item = values[i]
if isinstance(item, opcodes): if isinstance(item, opcodes):
script += item.hex() script += bytes([item])
elif type(item) is int: elif type(item) is int:
script += add_number_to_script(item).hex() script += add_number_to_script(item)
elif isinstance(item, (bytes, bytearray)): elif isinstance(item, (bytes, bytearray)):
script += push_script(item.hex()) script += push_script(item)
elif isinstance(item, str): elif isinstance(item, str):
assert is_hex_str(item) assert is_hex_str(item)
script += push_script(item) script += push_script(bfh(item))
else: else:
raise Exception(f'unexpected item for script: {item!r}') raise Exception(f'unexpected item for script: {item!r}')
return script return bytes(script)
def relayfee(network: 'Network' = None) -> int: 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: def public_key_to_p2wpkh(public_key: bytes, *, net=None) -> str:
return hash_to_segwit_addr(hash_160(public_key), witver=0, net=net) return hash_to_segwit_addr(hash_160(public_key), witver=0, net=net)
def script_to_p2wsh(script: str, *, net=None) -> str: def script_to_p2wsh(script: bytes, *, net=None) -> str:
return hash_to_segwit_addr(sha256(bfh(script)), witver=0, net=net) return hash_to_segwit_addr(sha256(script), witver=0, net=net)
def p2wpkh_nested_script(pubkey: str) -> str:
pkh = hash_160(bfh(pubkey))
return construct_script([0, pkh])
def p2wsh_nested_script(witness_script: str) -> str: def p2wsh_nested_script(witness_script: bytes) -> bytes:
wsh = sha256(bfh(witness_script)) wsh = sha256(witness_script)
return construct_script([0, wsh]) return construct_script([0, wsh])
def pubkey_to_address(txin_type: str, pubkey: str, *, net=None) -> str: 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 # 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': if txin_type == 'p2sh':
# given scriptcode is a redeem_script # 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': elif txin_type == 'p2wsh':
# given scriptcode is a witness_script # given scriptcode is a witness_script
return script_to_p2wsh(scriptcode, net=net) return script_to_p2wsh(scriptcode, net=net)
elif txin_type == 'p2wsh-p2sh': elif txin_type == 'p2wsh-p2sh':
# given scriptcode is a witness_script # given scriptcode is a witness_script
redeem_script = p2wsh_nested_script(scriptcode) 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: else:
raise NotImplementedError(txin_type) 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 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 net is None: net = constants.net
if not is_address(addr, net=net): if not is_address(addr, net=net):
raise BitcoinException(f"invalid bitcoin address: {addr}") 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)]) return construct_script([witver, bytes(witprog)])
addrtype, hash_160_ = b58_address_to_hash160(addr) addrtype, hash_160_ = b58_address_to_hash160(addr)
if addrtype == net.ADDRTYPE_P2PKH: 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: elif addrtype == net.ADDRTYPE_P2SH:
script = construct_script([opcodes.OP_HASH160, hash_160_, opcodes.OP_EQUAL]) script = construct_script([opcodes.OP_HASH160, hash_160_, opcodes.OP_EQUAL])
else: else:
@ -514,14 +489,12 @@ def address_to_scripthash(addr: str, *, net=None) -> str:
return script_to_scripthash(script) return script_to_scripthash(script)
def script_to_scripthash(script: str) -> str: def script_to_scripthash(script: bytes) -> str:
h = sha256(bfh(script))[0:32] h = sha256(script)
return h[::-1].hex() 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([ return construct_script([
opcodes.OP_DUP, opcodes.OP_DUP,
opcodes.OP_HASH160, opcodes.OP_HASH160,
@ -756,7 +729,7 @@ def minikey_to_private_key(text: str) -> bytes:
def _get_dummy_address(purpose: str) -> str: 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() _dummy_addr_funcs = set()
class DummyAddress: class DummyAddress:

35
electrum/blockchain.py

@ -26,7 +26,7 @@ import time
from typing import Optional, Dict, Mapping, Sequence, TYPE_CHECKING from typing import Optional, Dict, Mapping, Sequence, TYPE_CHECKING
from . import util from . import util
from .bitcoin import hash_encode, int_to_hex, rev_hex from .bitcoin import hash_encode
from .crypto import sha256d from .crypto import sha256d
from . import constants from . import constants
from .util import bfh, with_lock from .util import bfh, with_lock
@ -49,13 +49,14 @@ class MissingHeader(Exception):
class InvalidHeader(Exception): class InvalidHeader(Exception):
pass pass
def serialize_header(header_dict: dict) -> str: def serialize_header(header_dict: dict) -> bytes:
s = int_to_hex(header_dict['version'], 4) \ s = (
+ rev_hex(header_dict['prev_block_hash']) \ int.to_bytes(header_dict['version'], length=4, byteorder="little", signed=False)
+ rev_hex(header_dict['merkle_root']) \ + bfh(header_dict['prev_block_hash'])[::-1]
+ int_to_hex(int(header_dict['timestamp']), 4) \ + bfh(header_dict['merkle_root'])[::-1]
+ int_to_hex(int(header_dict['bits']), 4) \ + int.to_bytes(int(header_dict['timestamp']), length=4, byteorder="little", signed=False)
+ int_to_hex(int(header_dict['nonce']), 4) + 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 return s
def deserialize_header(s: bytes, height: int) -> dict: 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)) raise InvalidHeader('Invalid header: {}'.format(s))
if len(s) != HEADER_SIZE: if len(s) != HEADER_SIZE:
raise InvalidHeader('Invalid header length: {}'.format(len(s))) raise InvalidHeader('Invalid header length: {}'.format(len(s)))
hex_to_int = lambda s: int.from_bytes(s, byteorder='little')
h = {} 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['prev_block_hash'] = hash_encode(s[4:36])
h['merkle_root'] = hash_encode(s[36:68]) h['merkle_root'] = hash_encode(s[36:68])
h['timestamp'] = hex_to_int(s[68:72]) h['timestamp'] = int.from_bytes(s[68:72], byteorder='little')
h['bits'] = hex_to_int(s[72:76]) h['bits'] = int.from_bytes(s[72:76], byteorder='little')
h['nonce'] = hex_to_int(s[76:80]) h['nonce'] = int.from_bytes(s[76:80], byteorder='little')
h['block_height'] = height h['block_height'] = height
return h return h
@ -82,8 +82,9 @@ def hash_header(header: dict) -> str:
return hash_raw_header(serialize_header(header)) return hash_raw_header(serialize_header(header))
def hash_raw_header(header: str) -> str: def hash_raw_header(header: bytes) -> str:
return hash_encode(sha256d(bfh(header))) assert isinstance(header, bytes)
return hash_encode(sha256d(header))
pow_hash_header = hash_header pow_hash_header = hash_header
@ -413,7 +414,7 @@ class Blockchain(Logger):
# swap parameters # swap parameters
self.parent, parent.parent = parent.parent, self # type: Optional[Blockchain], Optional[Blockchain] self.parent, parent.parent = parent.parent, self # type: Optional[Blockchain], Optional[Blockchain]
self.forkpoint, parent.forkpoint = parent.forkpoint, self.forkpoint 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 self._prev_hash, parent._prev_hash = parent._prev_hash, self._prev_hash
# parent's new name # parent's new name
os.replace(child_old_name, parent.path()) os.replace(child_old_name, parent.path())
@ -454,7 +455,7 @@ class Blockchain(Logger):
@with_lock @with_lock
def save_header(self, header: dict) -> None: def save_header(self, header: dict) -> None:
delta = header.get('block_height') - self.forkpoint delta = header.get('block_height') - self.forkpoint
data = bfh(serialize_header(header)) data = serialize_header(header)
# headers are only _appended_ to the end: # headers are only _appended_ to the end:
assert delta == self.size(), (delta, self.size()) assert delta == self.size(), (delta, self.size())
assert len(data) == HEADER_SIZE assert len(data) == HEADER_SIZE

4
electrum/commands.py

@ -494,8 +494,8 @@ class Commands:
"""Create multisig address""" """Create multisig address"""
assert isinstance(pubkeys, list), (type(num), type(pubkeys)) assert isinstance(pubkeys, list), (type(num), type(pubkeys))
redeem_script = multisig_script(pubkeys, num) redeem_script = multisig_script(pubkeys, num)
address = bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script))) address = bitcoin.hash160_to_p2sh(hash_160(redeem_script))
return {'address':address, 'redeemScript':redeem_script} return {'address': address, 'redeemScript': redeem_script.hex()}
@command('w') @command('w')
async def freeze(self, address: str, wallet: Abstract_Wallet = None): async def freeze(self, address: str, wallet: Abstract_Wallet = None):

2
electrum/constants.py

@ -74,7 +74,7 @@ class AbstractNet:
@classmethod @classmethod
def rev_genesis_bytes(cls) -> bytes: def rev_genesis_bytes(cls) -> bytes:
return bytes.fromhex(bitcoin.rev_hex(cls.GENESIS)) return bytes.fromhex(cls.GENESIS)[::-1]
class BitcoinMainnet(AbstractNet): class BitcoinMainnet(AbstractNet):

30
electrum/descriptor.py

@ -75,7 +75,7 @@ class ExpandedScripts:
self._scriptcode_for_sighash = value self._scriptcode_for_sighash = value
def address(self, *, net=None) -> Optional[str]: 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): class ScriptSolutionInner(NamedTuple):
@ -379,9 +379,9 @@ class Descriptor(object):
witness = None witness = None
script_sig = None script_sig = None
if self.is_segwit(): if self.is_segwit():
witness = bfh(construct_witness(sol.witness_items)) witness = construct_witness(sol.witness_items)
else: else:
script_sig = bfh(construct_script(sol.witness_items)) script_sig = construct_script(sol.witness_items)
return ScriptSolutionTop( return ScriptSolutionTop(
witness=witness, witness=witness,
script_sig=script_sig, script_sig=script_sig,
@ -473,7 +473,7 @@ class PKDescriptor(Descriptor):
def expand(self, *, pos: Optional[int] = None) -> "ExpandedScripts": def expand(self, *, pos: Optional[int] = None) -> "ExpandedScripts":
pubkey = self.pubkeys[0].get_pubkey_bytes(pos=pos) pubkey = self.pubkeys[0].get_pubkey_bytes(pos=pos)
script = construct_script([pubkey, opcodes.OP_CHECKSIG]) 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: def _satisfy_inner(self, *, sigdata=None, allow_dummy=False) -> ScriptSolutionInner:
if sigdata is None: sigdata = {} if sigdata is None: sigdata = {}
@ -513,9 +513,9 @@ class PKHDescriptor(Descriptor):
def expand(self, *, pos: Optional[int] = None) -> "ExpandedScripts": def expand(self, *, pos: Optional[int] = None) -> "ExpandedScripts":
pubkey = self.pubkeys[0].get_pubkey_bytes(pos=pos) 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) 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: def _satisfy_inner(self, *, sigdata=None, allow_dummy=False) -> ScriptSolutionInner:
if sigdata is None: sigdata = {} if sigdata is None: sigdata = {}
@ -556,10 +556,10 @@ class WPKHDescriptor(Descriptor):
def expand(self, *, pos: Optional[int] = None) -> "ExpandedScripts": def expand(self, *, pos: Optional[int] = None) -> "ExpandedScripts":
pkh = hash_160(self.pubkeys[0].get_pubkey_bytes(pos=pos)) pkh = hash_160(self.pubkeys[0].get_pubkey_bytes(pos=pos))
output_script = construct_script([0, pkh]) output_script = construct_script([0, pkh])
scriptcode = bitcoin.pubkeyhash_to_p2pkh_script(pkh.hex()) scriptcode = bitcoin.pubkeyhash_to_p2pkh_script(pkh)
return ExpandedScripts( return ExpandedScripts(
output_script=bytes.fromhex(output_script), output_script=output_script,
scriptcode_for_sighash=bytes.fromhex(scriptcode), scriptcode_for_sighash=scriptcode,
) )
def _satisfy_inner(self, *, sigdata=None, allow_dummy=False) -> ScriptSolutionInner: 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] der_pks = [p.get_pubkey_bytes(pos=pos) for p in self.pubkeys]
if self.is_sorted: if self.is_sorted:
der_pks.sort() 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) return ExpandedScripts(output_script=script)
def _satisfy_inner(self, *, sigdata=None, allow_dummy=False) -> ScriptSolutionInner: 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) sub_scripts = self.subdescriptors[0].expand(pos=pos)
redeem_script = sub_scripts.output_script redeem_script = sub_scripts.output_script
witness_script = sub_scripts.witness_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( return ExpandedScripts(
output_script=script, output_script=script,
redeem_script=redeem_script, redeem_script=redeem_script,
@ -697,10 +697,10 @@ class SHDescriptor(Descriptor):
witness = None witness = None
if isinstance(subdesc, (WSHDescriptor, WPKHDescriptor)): # witness_v0 nested in p2sh if isinstance(subdesc, (WSHDescriptor, WPKHDescriptor)): # witness_v0 nested in p2sh
witness = subdesc.satisfy(sigdata=sigdata, allow_dummy=allow_dummy).witness 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 else: # legacy p2sh
subsol = subdesc._satisfy_inner(sigdata=sigdata, allow_dummy=allow_dummy) 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( return ScriptSolutionTop(
witness=witness, witness=witness,
script_sig=script_sig, script_sig=script_sig,
@ -724,7 +724,7 @@ class WSHDescriptor(Descriptor):
assert len(self.subdescriptors) == 1 assert len(self.subdescriptors) == 1
sub_scripts = self.subdescriptors[0].expand(pos=pos) sub_scripts = self.subdescriptors[0].expand(pos=pos)
witness_script = sub_scripts.output_script 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( return ExpandedScripts(
output_script=output_script, output_script=output_script,
witness_script=witness_script, witness_script=witness_script,
@ -740,7 +740,7 @@ class WSHDescriptor(Descriptor):
witness_script = self.expand().witness_script witness_script = self.expand().witness_script
witness = construct_witness([*subsol.witness_items, witness_script]) witness = construct_witness([*subsol.witness_items, witness_script])
return ScriptSolutionTop( return ScriptSolutionTop(
witness=bytes.fromhex(witness), witness=witness,
) )
def is_segwit(self) -> bool: def is_segwit(self) -> bool:

2
electrum/ecc.py

@ -415,7 +415,7 @@ POINT_AT_INFINITY = ECPubkey(None)
def usermessage_magic(message: bytes) -> bytes: def usermessage_magic(message: bytes) -> bytes:
from .bitcoin import var_int from .bitcoin import var_int
length = bfh(var_int(len(message))) length = var_int(len(message))
return b"\x18Bitcoin Signed Message:\n" + length + message return b"\x18Bitcoin Signed Message:\n" + length + message

2
electrum/gui/text.py

@ -624,7 +624,7 @@ class ElectrumGui(BaseElectrumGui, EventListener):
return return
elif is_address(self.str_recipient): elif is_address(self.str_recipient):
amount_sat = self.parse_amount(self.str_amount) 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)] outputs = [PartialTxOutput(scriptpubkey=scriptpubkey, value=amount_sat)]
invoice = self.wallet.create_invoice( invoice = self.wallet.create_invoice(
outputs=outputs, outputs=outputs,

32
electrum/lnchannel.py

@ -1099,7 +1099,7 @@ class Channel(AbstractChannel):
commit=pending_remote_commitment, commit=pending_remote_commitment,
ctx_output_idx=ctx_output_idx, ctx_output_idx=ctx_output_idx,
htlc=htlc) htlc=htlc)
sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey)) sig = htlc_tx.sign_txin(0, their_remote_htlc_privkey)
htlc_sig = ecc.ecdsa_sig64_from_der_sig(sig[:-1]) htlc_sig = ecc.ecdsa_sig64_from_der_sig(sig[:-1])
htlcsigs.append((ctx_output_idx, htlc_sig)) htlcsigs.append((ctx_output_idx, htlc_sig))
htlcsigs.sort() htlcsigs.sort()
@ -1121,13 +1121,13 @@ class Channel(AbstractChannel):
assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes
pending_local_commitment = self.get_next_commitment(LOCAL) pending_local_commitment = self.get_next_commitment(LOCAL)
preimage_hex = pending_local_commitment.serialize_preimage(0) pre_hash = pending_local_commitment.serialize_preimage(0)
pre_hash = sha256d(bfh(preimage_hex)) msg_hash = sha256d(pre_hash)
if not ECPubkey(self.config[REMOTE].multisig_key.pubkey).ecdsa_verify(sig, pre_hash): if not ECPubkey(self.config[REMOTE].multisig_key.pubkey).ecdsa_verify(sig, msg_hash):
raise LNProtocolWarning( raise LNProtocolWarning(
f'failed verifying signature for our updated commitment transaction. ' f'failed verifying signature for our updated commitment transaction. '
f'sig={sig.hex()}. ' f'sig={sig.hex()}. '
f'pre_hash={pre_hash.hex()}. ' f'msg_hash={msg_hash.hex()}. '
f'pubkey={self.config[REMOTE].multisig_key.pubkey}. ' f'pubkey={self.config[REMOTE].multisig_key.pubkey}. '
f'ctx={pending_local_commitment.serialize()} ' f'ctx={pending_local_commitment.serialize()} '
) )
@ -1167,16 +1167,16 @@ class Channel(AbstractChannel):
commit=ctx, commit=ctx,
ctx_output_idx=ctx_output_idx, ctx_output_idx=ctx_output_idx,
htlc=htlc) htlc=htlc)
preimage_hex = htlc_tx.serialize_preimage(0) pre_hash = htlc_tx.serialize_preimage(0)
pre_hash = sha256d(bfh(preimage_hex)) msg_hash = sha256d(pre_hash)
remote_htlc_pubkey = derive_pubkey(self.config[REMOTE].htlc_basepoint.pubkey, pcp) remote_htlc_pubkey = derive_pubkey(self.config[REMOTE].htlc_basepoint.pubkey, pcp)
if not ECPubkey(remote_htlc_pubkey).ecdsa_verify(htlc_sig, pre_hash): if not ECPubkey(remote_htlc_pubkey).ecdsa_verify(htlc_sig, msg_hash):
raise LNProtocolWarning( raise LNProtocolWarning(
f'failed verifying HTLC signatures: {htlc=}, {htlc_direction=}. ' f'failed verifying HTLC signatures: {htlc=}, {htlc_direction=}. '
f'htlc_tx={htlc_tx.serialize()}. ' f'htlc_tx={htlc_tx.serialize()}. '
f'htlc_sig={htlc_sig.hex()}. ' f'htlc_sig={htlc_sig.hex()}. '
f'remote_htlc_pubkey={remote_htlc_pubkey.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={ctx.serialize()}. '
f'ctx_output_idx={ctx_output_idx}. ' f'ctx_output_idx={ctx_output_idx}. '
f'ctn={ctn}. ' f'ctn={ctn}. '
@ -1587,8 +1587,8 @@ class Channel(AbstractChannel):
}, },
local_amount_msat=self.balance(LOCAL), local_amount_msat=self.balance(LOCAL),
remote_amount_msat=self.balance(REMOTE) if not drop_remote else 0, remote_amount_msat=self.balance(REMOTE) if not drop_remote else 0,
local_script=local_script.hex(), local_script=local_script,
remote_script=remote_script.hex(), remote_script=remote_script,
htlcs=[], htlcs=[],
dust_limit_sat=self.config[LOCAL].dust_limit_sat) dust_limit_sat=self.config[LOCAL].dust_limit_sat)
@ -1599,14 +1599,14 @@ class Channel(AbstractChannel):
funding_sat=self.constraints.capacity, funding_sat=self.constraints.capacity,
outputs=outputs) 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]) sig = ecc.ecdsa_sig64_from_der_sig(der_sig[:-1])
return sig, closing_tx return sig, closing_tx
def signature_fits(self, tx: PartialTransaction) -> bool: def signature_fits(self, tx: PartialTransaction) -> bool:
remote_sig = self.config[LOCAL].current_commitment_signature remote_sig = self.config[LOCAL].current_commitment_signature
preimage_hex = tx.serialize_preimage(0) pre_hash = tx.serialize_preimage(0)
msg_hash = sha256d(bfh(preimage_hex)) msg_hash = sha256d(pre_hash)
assert remote_sig assert remote_sig
res = ECPubkey(self.config[REMOTE].multisig_key.pubkey).ecdsa_verify(remote_sig, msg_hash) res = ECPubkey(self.config[REMOTE].multisig_key.pubkey).ecdsa_verify(remote_sig, msg_hash)
return res return res
@ -1618,8 +1618,8 @@ class Channel(AbstractChannel):
remote_sig = self.config[LOCAL].current_commitment_signature remote_sig = self.config[LOCAL].current_commitment_signature
remote_sig = ecc.ecdsa_der_sig_from_ecdsa_sig64(remote_sig) + Sighash.to_sigbytes(Sighash.ALL) remote_sig = ecc.ecdsa_der_sig_from_ecdsa_sig64(remote_sig) + Sighash.to_sigbytes(Sighash.ALL)
tx.add_signature_to_txin(txin_idx=0, tx.add_signature_to_txin(txin_idx=0,
signing_pubkey=self.config[REMOTE].multisig_key.pubkey.hex(), signing_pubkey=self.config[REMOTE].multisig_key.pubkey,
sig=remote_sig.hex()) sig=remote_sig)
assert tx.is_complete() assert tx.is_complete()
return tx return tx

20
electrum/lnpeer.py

@ -2368,7 +2368,7 @@ class Peer(Logger):
if chan.config[LOCAL].upfront_shutdown_script: if chan.config[LOCAL].upfront_shutdown_script:
scriptpubkey = chan.config[LOCAL].upfront_shutdown_script scriptpubkey = chan.config[LOCAL].upfront_shutdown_script
else: else:
scriptpubkey = bfh(bitcoin.address_to_script(chan.sweep_address)) scriptpubkey = bitcoin.address_to_script(chan.sweep_address)
assert scriptpubkey assert scriptpubkey
# wait until no more pending updates (bolt2) # wait until no more pending updates (bolt2)
chan.set_can_send_ctx_updates(False) chan.set_can_send_ctx_updates(False)
@ -2421,7 +2421,7 @@ class Peer(Logger):
if chan.config[LOCAL].upfront_shutdown_script: if chan.config[LOCAL].upfront_shutdown_script:
our_scriptpubkey = chan.config[LOCAL].upfront_shutdown_script our_scriptpubkey = chan.config[LOCAL].upfront_shutdown_script
else: else:
our_scriptpubkey = bfh(bitcoin.address_to_script(chan.sweep_address)) our_scriptpubkey = bitcoin.address_to_script(chan.sweep_address)
assert our_scriptpubkey assert our_scriptpubkey
# estimate fee of closing tx # estimate fee of closing tx
dummy_sig, dummy_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=0) 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: def verify_signature(tx: 'PartialTransaction', sig) -> bool:
their_pubkey = chan.config[REMOTE].multisig_key.pubkey their_pubkey = chan.config[REMOTE].multisig_key.pubkey
preimage_hex = tx.serialize_preimage(0) pre_hash = tx.serialize_preimage(0)
pre_hash = sha256d(bfh(preimage_hex)) msg_hash = sha256d(pre_hash)
return ECPubkey(their_pubkey).ecdsa_verify(sig, pre_hash) return ECPubkey(their_pubkey).ecdsa_verify(sig, msg_hash)
async def receive_closing_signed(): async def receive_closing_signed():
nonlocal our_sig, closing_tx nonlocal our_sig, closing_tx
@ -2476,7 +2476,7 @@ class Peer(Logger):
raise Exception('failed to verify their signature') raise Exception('failed to verify their signature')
# at this point we know how the closing tx looks like # at this point we know how the closing tx looks like
# check that their output is above their scriptpubkey's network dust limit # 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: if not drop_remote and to_remote_set:
to_remote_idx = to_remote_set.pop() to_remote_idx = to_remote_set.pop()
to_remote_amount = closing_tx.outputs()[to_remote_idx].value to_remote_amount = closing_tx.outputs()[to_remote_idx].value
@ -2570,12 +2570,12 @@ class Peer(Logger):
# add signatures # add signatures
closing_tx.add_signature_to_txin( closing_tx.add_signature_to_txin(
txin_idx=0, txin_idx=0,
signing_pubkey=chan.config[LOCAL].multisig_key.pubkey.hex(), signing_pubkey=chan.config[LOCAL].multisig_key.pubkey,
sig=(ecdsa_der_sig_from_ecdsa_sig64(our_sig) + Sighash.to_sigbytes(Sighash.ALL)).hex()) sig=ecdsa_der_sig_from_ecdsa_sig64(our_sig) + Sighash.to_sigbytes(Sighash.ALL))
closing_tx.add_signature_to_txin( closing_tx.add_signature_to_txin(
txin_idx=0, txin_idx=0,
signing_pubkey=chan.config[REMOTE].multisig_key.pubkey.hex(), signing_pubkey=chan.config[REMOTE].multisig_key.pubkey,
sig=(ecdsa_der_sig_from_ecdsa_sig64(their_sig) + Sighash.to_sigbytes(Sighash.ALL)).hex()) sig=ecdsa_der_sig_from_ecdsa_sig64(their_sig) + Sighash.to_sigbytes(Sighash.ALL))
# save local transaction and set state # save local transaction and set state
try: try:
self.lnworker.wallet.adb.add_transaction(closing_tx) self.lnworker.wallet.adb.add_transaction(closing_tx)

28
electrum/lnsweep.py

@ -55,7 +55,7 @@ def create_sweeptxs_for_watchtower(chan: 'Channel', ctx: Transaction, per_commit
# to_local # to_local
revocation_pubkey = ecc.ECPrivkey(other_revocation_privkey).get_public_key_bytes(compressed=True) revocation_pubkey = ecc.ECPrivkey(other_revocation_privkey).get_public_key_bytes(compressed=True)
witness_script = make_commitment_output_to_local_witness_script( 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) to_local_address = redeem_script_to_address('p2wsh', witness_script)
output_idxs = ctx.get_output_idxs_from_address(to_local_address) output_idxs = ctx.get_output_idxs_from_address(to_local_address)
if output_idxs: if output_idxs:
@ -64,7 +64,7 @@ def create_sweeptxs_for_watchtower(chan: 'Channel', ctx: Transaction, per_commit
sweep_address=sweep_address, sweep_address=sweep_address,
ctx=ctx, ctx=ctx,
output_idx=output_idx, output_idx=output_idx,
witness_script=bfh(witness_script), witness_script=witness_script,
privkey=other_revocation_privkey, privkey=other_revocation_privkey,
is_revocation=True, is_revocation=True,
config=chan.lnworker.config) config=chan.lnworker.config)
@ -122,7 +122,7 @@ def create_sweeptx_for_their_revoked_ctx(
# to_local # to_local
revocation_pubkey = ecc.ECPrivkey(other_revocation_privkey).get_public_key_bytes(compressed=True) revocation_pubkey = ecc.ECPrivkey(other_revocation_privkey).get_public_key_bytes(compressed=True)
witness_script = make_commitment_output_to_local_witness_script( 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) to_local_address = redeem_script_to_address('p2wsh', witness_script)
output_idxs = ctx.get_output_idxs_from_address(to_local_address) output_idxs = ctx.get_output_idxs_from_address(to_local_address)
if output_idxs: if output_idxs:
@ -131,7 +131,7 @@ def create_sweeptx_for_their_revoked_ctx(
sweep_address=sweep_address, sweep_address=sweep_address,
ctx=ctx, ctx=ctx,
output_idx=output_idx, output_idx=output_idx,
witness_script=bfh(witness_script), witness_script=witness_script,
privkey=other_revocation_privkey, privkey=other_revocation_privkey,
is_revocation=True, is_revocation=True,
config=chan.lnworker.config) config=chan.lnworker.config)
@ -162,7 +162,7 @@ def create_sweeptx_for_their_revoked_htlc(
# same witness script as to_local # same witness script as to_local
revocation_pubkey = ecc.ECPrivkey(other_revocation_privkey).get_public_key_bytes(compressed=True) revocation_pubkey = ecc.ECPrivkey(other_revocation_privkey).get_public_key_bytes(compressed=True)
witness_script = make_commitment_output_to_local_witness_script( 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) htlc_address = redeem_script_to_address('p2wsh', witness_script)
# check that htlc_tx is a htlc # check that htlc_tx is a htlc
if htlc_tx.outputs()[0].address != htlc_address: 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( gen_tx = lambda: create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx(
sweep_address=sweep_address, sweep_address=sweep_address,
htlc_tx=htlc_tx, htlc_tx=htlc_tx,
htlctx_witness_script=bfh(witness_script), htlctx_witness_script=witness_script,
privkey=other_revocation_privkey, privkey=other_revocation_privkey,
is_revocation=True, is_revocation=True,
config=chan.lnworker.config) config=chan.lnworker.config)
@ -204,7 +204,7 @@ def create_sweeptxs_for_our_ctx(
per_commitment_point=our_pcp).to_bytes(32, 'big') per_commitment_point=our_pcp).to_bytes(32, 'big')
our_localdelayed_pubkey = our_localdelayed_privkey.get_public_key_bytes(compressed=True) our_localdelayed_pubkey = our_localdelayed_privkey.get_public_key_bytes(compressed=True)
to_local_witness_script = make_commitment_output_to_local_witness_script( 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_local_address = redeem_script_to_address('p2wsh', to_local_witness_script)
to_remote_address = None to_remote_address = None
# test if this is our_ctx # test if this is our_ctx
@ -231,7 +231,7 @@ def create_sweeptxs_for_our_ctx(
sweep_address=sweep_address, sweep_address=sweep_address,
ctx=ctx, ctx=ctx,
output_idx=output_idx, output_idx=output_idx,
witness_script=bfh(to_local_witness_script), witness_script=to_local_witness_script,
privkey=our_localdelayed_privkey.get_secret_bytes(), privkey=our_localdelayed_privkey.get_secret_bytes(),
is_revocation=False, is_revocation=False,
to_self_delay=to_self_delay, 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) 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) their_delayed_pubkey = derive_pubkey(their_conf.delayed_basepoint.pubkey, their_pcp)
witness_script = make_commitment_output_to_local_witness_script( 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_local_address = redeem_script_to_address('p2wsh', witness_script)
to_remote_address = None to_remote_address = None
# test if this is their ctx # test if this is their ctx
@ -469,9 +469,9 @@ def create_htlctx_that_spends_from_our_ctx(
ctx_output_idx=ctx_output_idx, ctx_output_idx=ctx_output_idx,
name=f'our_ctx_{ctx_output_idx}_htlc_tx_{htlc.payment_hash.hex()}') 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) 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] 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) txin.witness = make_htlc_tx_witness(remote_htlc_sig, local_htlc_sig, preimage, witness_program)
return witness_script, htlc_tx return witness_script, htlc_tx
@ -496,13 +496,13 @@ def create_sweeptx_their_ctx_htlc(
if outvalue <= dust_threshold(): return None if outvalue <= dust_threshold(): return None
sweep_outputs = [PartialTxOutput.from_address_and_value(sweep_address, outvalue)] sweep_outputs = [PartialTxOutput.from_address_and_value(sweep_address, outvalue)]
tx = PartialTransaction.from_io(sweep_inputs, sweep_outputs, version=2, locktime=cltv_abs) 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: if not is_revocation:
witness = construct_witness([sig, preimage, witness_script]) witness = construct_witness([sig, preimage, witness_script])
else: else:
revocation_pubkey = privkey_to_pubkey(privkey) revocation_pubkey = privkey_to_pubkey(privkey)
witness = construct_witness([sig, revocation_pubkey, witness_script]) witness = construct_witness([sig, revocation_pubkey, witness_script])
tx.inputs()[0].witness = bfh(witness) tx.inputs()[0].witness = witness
assert tx.is_complete() assert tx.is_complete()
return tx return tx
@ -561,7 +561,7 @@ def create_sweeptx_ctx_to_local(
sweep_tx = PartialTransaction.from_io(sweep_inputs, sweep_outputs, version=2) sweep_tx = PartialTransaction.from_io(sweep_inputs, sweep_outputs, version=2)
sig = sweep_tx.sign_txin(0, privkey) sig = sweep_tx.sign_txin(0, privkey)
witness = construct_witness([sig, int(is_revocation), witness_script]) 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 return sweep_tx

40
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 .ecc import CURVE_ORDER, ecdsa_sig64_from_der_sig, ECPubkey, string_to_number
from . import ecc, bitcoin, crypto, transaction from . import ecc, bitcoin, crypto, transaction
from . import descriptor from . import descriptor
from .bitcoin import (push_script, redeem_script_to_address, address_to_script, from .bitcoin import (redeem_script_to_address, address_to_script,
construct_witness, construct_script) construct_witness, construct_script)
from . import segwit_addr from . import segwit_addr
from .i18n import _ 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) 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(amount_msat) is int
assert type(local_feerate) is int assert type(local_feerate) is int
script = make_commitment_output_to_local_witness_script( 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, 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 weight = HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT
fee = local_feerate * weight fee = local_feerate * weight
fee = fee // 1000 * 1000 fee = fee // 1000 * 1000
@ -615,7 +617,7 @@ def make_htlc_tx_witness(remotehtlcsig: bytes, localhtlcsig: bytes,
assert type(localhtlcsig) is bytes assert type(localhtlcsig) is bytes
assert type(payment_preimage) is bytes assert type(payment_preimage) is bytes
assert type(witness_script) 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, def make_htlc_tx_inputs(htlc_output_txid: str, htlc_output_index: int,
amount_msat: int, witness_script: str) -> List[PartialTxInput]: amount_msat: int, witness_script: str) -> List[PartialTxInput]:
@ -648,7 +650,7 @@ def make_offered_htlc(
assert type(remote_htlcpubkey) is bytes assert type(remote_htlcpubkey) is bytes
assert type(local_htlcpubkey) is bytes assert type(local_htlcpubkey) is bytes
assert type(payment_hash) is bytes assert type(payment_hash) is bytes
script = bfh(construct_script([ script = construct_script([
opcodes.OP_DUP, opcodes.OP_DUP,
opcodes.OP_HASH160, opcodes.OP_HASH160,
bitcoin.hash_160(revocation_pubkey), bitcoin.hash_160(revocation_pubkey),
@ -675,7 +677,7 @@ def make_offered_htlc(
opcodes.OP_CHECKSIG, opcodes.OP_CHECKSIG,
opcodes.OP_ENDIF, opcodes.OP_ENDIF,
opcodes.OP_ENDIF, opcodes.OP_ENDIF,
])) ])
return script return script
def make_received_htlc( def make_received_htlc(
@ -690,7 +692,7 @@ def make_received_htlc(
assert type(i) is bytes assert type(i) is bytes
assert type(cltv_abs) is int assert type(cltv_abs) is int
script = bfh(construct_script([ script = construct_script([
opcodes.OP_DUP, opcodes.OP_DUP,
opcodes.OP_HASH160, opcodes.OP_HASH160,
bitcoin.hash_160(revocation_pubkey), bitcoin.hash_160(revocation_pubkey),
@ -720,7 +722,7 @@ def make_received_htlc(
opcodes.OP_CHECKSIG, opcodes.OP_CHECKSIG,
opcodes.OP_ENDIF, opcodes.OP_ENDIF,
opcodes.OP_ENDIF, opcodes.OP_ENDIF,
])) ])
return script return script
WITNESS_TEMPLATE_OFFERED_HTLC = [ 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, local_htlc_pubkey=htlc_pubkey,
payment_hash=payment_hash, payment_hash=payment_hash,
cltv_abs=cltv_abs) 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) candidates = ctx.get_output_idxs_from_address(htlc_address)
return {output_idx for output_idx in candidates return {output_idx for output_idx in candidates
if ctx.outputs()[output_idx].value == htlc.amount_msat // 1000} if ctx.outputs()[output_idx].value == htlc.amount_msat // 1000}
@ -938,20 +940,20 @@ LOCAL = HTLCOwner.LOCAL
REMOTE = HTLCOwner.REMOTE REMOTE = HTLCOwner.REMOTE
def make_commitment_outputs(*, fees_per_participant: Mapping[HTLCOwner, int], local_amount_msat: int, remote_amount_msat: int, 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; # 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 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: # -> 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_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_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] non_htlc_outputs = [to_local, to_remote]
htlc_outputs = [] htlc_outputs = []
for script, htlc in htlcs: for script, htlc in htlcs:
addr = bitcoin.redeem_script_to_address('p2wsh', script.hex()) addr = bitcoin.redeem_script_to_address('p2wsh', script)
htlc_outputs.append(PartialTxOutput(scriptpubkey=bfh(address_to_script(addr)), htlc_outputs.append(PartialTxOutput(scriptpubkey=address_to_script(addr),
value=htlc.amount_msat // 1000)) value=htlc.amount_msat // 1000))
# trim outputs # trim outputs
@ -1060,7 +1062,7 @@ def make_commitment_output_to_local_witness_script(
assert type(revocation_pubkey) is bytes assert type(revocation_pubkey) is bytes
assert type(to_self_delay) is int assert type(to_self_delay) is int
assert type(delayed_pubkey) is bytes assert type(delayed_pubkey) is bytes
script = bfh(construct_script([ script = construct_script([
opcodes.OP_IF, opcodes.OP_IF,
revocation_pubkey, revocation_pubkey,
opcodes.OP_ELSE, opcodes.OP_ELSE,
@ -1070,13 +1072,13 @@ def make_commitment_output_to_local_witness_script(
delayed_pubkey, delayed_pubkey,
opcodes.OP_ENDIF, opcodes.OP_ENDIF,
opcodes.OP_CHECKSIG, opcodes.OP_CHECKSIG,
])) ])
return script return script
def make_commitment_output_to_local_address( def make_commitment_output_to_local_address(
revocation_pubkey: bytes, to_self_delay: int, delayed_pubkey: bytes) -> str: 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) 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: def make_commitment_output_to_remote_address(remote_payment_pubkey: bytes) -> str:
return bitcoin.pubkey_to_address('p2wpkh', remote_payment_pubkey.hex()) 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]) sig_64 = ecdsa_sig64_from_der_sig(sig[:-1])
return sig_64 return sig_64
def funding_output_script(local_config, remote_config) -> str: def funding_output_script(local_config: 'LocalConfig', remote_config: 'RemoteConfig') -> bytes:
return funding_output_script_from_keys(local_config.multisig_key.pubkey, remote_config.multisig_key.pubkey) 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()]) pubkeys = sorted([pubkey1.hex(), pubkey2.hex()])
return transaction.multisig_script(pubkeys, 2) return transaction.multisig_script(pubkeys, 2)

2
electrum/lnworker.py

@ -58,7 +58,7 @@ from .lnrater import LNRater
from . import lnutil from . import lnutil
from .lnutil import funding_output_script from .lnutil import funding_output_script
from .lnutil import serialize_htlc_key, deserialize_htlc_key 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, from .lnutil import (Outpoint, LNPeerAddr,
get_compressed_pubkey_from_bech32, extract_nodeid, get_compressed_pubkey_from_bech32, extract_nodeid,
PaymentFailure, split_host_port, ConnStringFormatError, PaymentFailure, split_host_port, ConnStringFormatError,

12
electrum/payment_identifier.py

@ -320,7 +320,7 @@ class PaymentIdentifier(Logger):
'security check, DNSSEC, and thus may not be correct.').format(key) 'security check, DNSSEC, and thus may not be correct.').format(key)
try: try:
assert bitcoin.is_address(address) assert bitcoin.is_address(address)
scriptpubkey = bytes.fromhex(bitcoin.address_to_script(address)) scriptpubkey = bitcoin.address_to_script(address)
self._type = PaymentIdentifierType.OPENALIAS self._type = PaymentIdentifierType.OPENALIAS
self.spk = scriptpubkey self.spk = scriptpubkey
self.set_state(PaymentIdentifierState.AVAILABLE) self.set_state(PaymentIdentifierState.AVAILABLE)
@ -518,20 +518,20 @@ class PaymentIdentifier(Logger):
def parse_output(self, x: str) -> Tuple[Optional[bytes], bool]: def parse_output(self, x: str) -> Tuple[Optional[bytes], bool]:
try: try:
address = self.parse_address(x) 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: except Exception as e:
pass pass
try: try:
m = re.match('^' + RE_SCRIPT_FN + '$', x) m = re.match('^' + RE_SCRIPT_FN + '$', x)
script = self.parse_script(str(m.group(1))) script = self.parse_script(str(m.group(1)))
return bytes.fromhex(script), False return script, False
except Exception as e: except Exception as e:
pass pass
return None, False return None, False
def parse_script(self, x: str) -> str: def parse_script(self, x: str) -> bytes:
script = '' script = bytearray()
for word in x.split(): for word in x.split():
if word[0:3] == 'OP_': if word[0:3] == 'OP_':
opcode_int = opcodes[word] opcode_int = opcodes[word]
@ -539,7 +539,7 @@ class PaymentIdentifier(Logger):
else: else:
bytes.fromhex(word) # to test it is hex data bytes.fromhex(word) # to test it is hex data
script += construct_script([word]) script += construct_script([word])
return script return bytes(script)
def parse_amount(self, x: str) -> Union[str, int]: def parse_amount(self, x: str) -> Union[str, int]:
x = x.strip() x = x.strip()

4
electrum/paymentrequest.py

@ -291,7 +291,7 @@ class PaymentRequest:
paymnt.merchant_data = pay_det.merchant_data paymnt.merchant_data = pay_det.merchant_data
paymnt.transactions.append(bfh(raw_tx)) paymnt.transactions.append(bfh(raw_tx))
ref_out = paymnt.refund_to.add() 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" paymnt.memo = "Paid using Electrum"
pm = paymnt.SerializeToString() pm = paymnt.SerializeToString()
payurl = urllib.parse.urlparse(pay_det.payment_url) payurl = urllib.parse.urlparse(pay_det.payment_url)
@ -334,7 +334,7 @@ def make_unsigned_request(req: 'Invoice'):
if amount is None: if amount is None:
amount = 0 amount = 0
memo = req.message memo = req.message
script = bfh(address_to_script(addr)) script = address_to_script(addr)
outputs = [(script, amount)] outputs = [(script, amount)]
pd = pb2.PaymentDetails() pd = pb2.PaymentDetails()
if constants.net.TESTNET: if constants.net.TESTNET:

4
electrum/plugins/bitbox02/bitbox02.py

@ -536,8 +536,8 @@ class BitBox02Client(HardwareClientBase):
# Fill signatures # Fill signatures
if len(sigs) != len(tx.inputs()): if len(sigs) != len(tx.inputs()):
raise Exception("Incorrect number of inputs signed.") # Should never occur raise Exception("Incorrect number of inputs signed.") # Should never occur
sighash = Sighash.to_sigbytes(Sighash.ALL).hex() sighash = Sighash.to_sigbytes(Sighash.ALL)
signatures = [ecc.ecdsa_der_sig_from_ecdsa_sig64(x[1]).hex() + sighash for x in sigs] signatures = [ecc.ecdsa_der_sig_from_ecdsa_sig64(x[1]) + sighash for x in sigs]
tx.update_signatures(signatures) tx.update_signatures(signatures)
def sign_message(self, keypath: str, message: bytes, script_type: str) -> bytes: def sign_message(self, keypath: str, message: bytes, script_type: str) -> bytes:

2
electrum/plugins/coldcard/coldcard.py

@ -600,7 +600,7 @@ class ColdcardPlugin(HW_PluginBase):
xfp_int = xfp_int_from_xfp_bytes(fp_bytes) xfp_int = xfp_int_from_xfp_bytes(fp_bytes)
xfp_paths.append([xfp_int] + list(der_full)) 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) keystore.show_p2sh_address(wallet.m, script, xfp_paths, txin_type)

6
electrum/plugins/digitalbitbox/digitalbitbox.py

@ -547,7 +547,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
if not inputPath: if not inputPath:
self.give_error("No matching pubkey for sign_transaction") # should never happen self.give_error("No matching pubkey for sign_transaction") # should never happen
inputPath = convert_bip32_intpath_to_strpath(inputPath) 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_i = {'hash': to_hexstr(inputHash), 'keypath': inputPath}
hasharray.append(hasharray_i) hasharray.append(hasharray_i)
inputhasharray.append(inputHash) inputhasharray.append(inputHash)
@ -659,8 +659,8 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
sig_r = int(signed['sig'][:64], 16) sig_r = int(signed['sig'][:64], 16)
sig_s = int(signed['sig'][64:], 16) sig_s = int(signed['sig'][64:], 16)
sig = ecc.ecdsa_der_sig_from_r_and_s(sig_r, sig_s) sig = ecc.ecdsa_der_sig_from_r_and_s(sig_r, sig_s)
sig = to_hexstr(sig) + Sighash.to_sigbytes(Sighash.ALL).hex() sig = sig + Sighash.to_sigbytes(Sighash.ALL)
tx.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_bytes.hex(), sig=sig) tx.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_bytes, sig=sig)
except UserCancelled: except UserCancelled:
raise raise
except BaseException as e: except BaseException as e:

9
electrum/plugins/jade/jade.py

@ -268,7 +268,6 @@ class Jade_KeyStore(Hardware_KeyStore):
pubkey, path = self.find_my_pubkey_in_txinout(txin) pubkey, path = self.find_my_pubkey_in_txinout(txin)
witness_input = txin.is_segwit() witness_input = txin.is_segwit()
redeem_script = Transaction.get_preimage_script(txin) 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 = txin.utxo
input_tx = bytes.fromhex(input_tx.serialize()) if input_tx is not None else None 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)): for index, (txin, signature) in enumerate(zip(tx.inputs(), signatures)):
pubkey, path = self.find_my_pubkey_in_txinout(txin) pubkey, path = self.find_my_pubkey_in_txinout(txin)
if pubkey is not None and signature is not None: if pubkey is not None and signature is not None:
tx.add_signature_to_txin(txin_idx=index, tx.add_signature_to_txin(
signing_pubkey=pubkey.hex(), txin_idx=index,
sig=signature.hex()) signing_pubkey=pubkey,
sig=signature,
)
finally: finally:
self.handler.finished() self.handler.finished()

4
electrum/plugins/keepkey/keepkey.py

@ -266,8 +266,8 @@ class KeepKeyPlugin(HW_PluginBase):
outputs = self.tx_outputs(tx, keystore=keystore) outputs = self.tx_outputs(tx, keystore=keystore)
signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, signatures = client.sign_tx(self.get_coin_name(), inputs, outputs,
lock_time=tx.locktime, version=tx.version)[0] lock_time=tx.locktime, version=tx.version)[0]
sighash = Sighash.to_sigbytes(Sighash.ALL).hex() sighash = Sighash.to_sigbytes(Sighash.ALL)
signatures = [(x.hex() + sighash) for x in signatures] signatures = [(sig + sighash) for sig in signatures]
tx.update_signatures(signatures) tx.update_signatures(signatures)
@runs_in_hwd_thread @runs_in_hwd_thread

33
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 bip32, constants, ecc
from electrum import descriptor from electrum import descriptor
from electrum.bip32 import BIP32Node, convert_bip32_intpath_to_strpath, normalize_bip32_derivation 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.crypto import hash_160
from electrum.i18n import _ from electrum.i18n import _
from electrum.keystore import Hardware_KeyStore 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 self.give_error("No matching pubkey for sign_transaction") # should never happen
full_path = convert_bip32_intpath_to_strpath(full_path)[2:] 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 txin_prev_tx = txin.utxo
if txin_prev_tx is None and not txin.is_segwit(): if txin_prev_tx is None and not txin.is_segwit():
raise UserFacingException(_('Missing previous tx for legacy input.')) 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): if not is_txin_legacy_multisig(txin):
self.give_error("P2SH / regular input mixed in same transaction not supported") # should never happen 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(): for o in tx.outputs():
txOutput += int_to_hex(o.value, 8) txOutput += int.to_bytes(o.value, length=8, byteorder="little", signed=False)
script = o.scriptpubkey.hex() script = o.scriptpubkey
txOutput += var_int(len(script) // 2) txOutput += var_int(len(script))
txOutput += script txOutput += script
txOutput = bfh(txOutput) txOutput = bytes(txOutput)
if not self.supports_multi_output(): if not self.supports_multi_output():
if len(tx.outputs()) > 2: if len(tx.outputs()) > 2:
@ -649,11 +650,11 @@ class Ledger_Client_Legacy(Ledger_Client):
# Get trusted inputs from the original transactions # Get trusted inputs from the original transactions
for input_idx, utxo in enumerate(inputs): for input_idx, utxo in enumerate(inputs):
self.handler.show_message(_("Preparing transaction inputs...") + f" (phase1, {input_idx}/{len(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(): if segwitTransaction and not self.supports_segwit_trustedInputs():
tmp = bfh(utxo[3])[::-1] 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)
tmp += bfh(int_to_hex(utxo[6], 8)) # txin['value'] tmp += int.to_bytes(utxo[6], length=8, byteorder="little", signed=False) # txin['value']
chipInputs.append({'value': tmp, 'witness': True, 'sequence': sequence}) chipInputs.append({'value': tmp, 'witness': True, 'sequence': sequence})
redeemScripts.append(bfh(utxo[2])) redeemScripts.append(bfh(utxo[2]))
elif (not p2shTransaction) or self.supports_multi_output(): 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) redeemScripts.append(txtmp.outputs[utxo[1]].script)
else: else:
tmp = bfh(utxo[3])[::-1] 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}) chipInputs.append({'value': tmp, 'sequence': sequence})
redeemScripts.append(bfh(utxo[2])) redeemScripts.append(bfh(utxo[2]))
@ -703,8 +704,8 @@ class Ledger_Client_Legacy(Ledger_Client):
inputSignature[0] = 0x30 # force for 1.4.9+ inputSignature[0] = 0x30 # force for 1.4.9+
my_pubkey = inputs[inputIndex][4] my_pubkey = inputs[inputIndex][4]
tx.add_signature_to_txin(txin_idx=inputIndex, tx.add_signature_to_txin(txin_idx=inputIndex,
signing_pubkey=my_pubkey.hex(), signing_pubkey=my_pubkey,
sig=inputSignature.hex()) sig=inputSignature)
inputIndex = inputIndex + 1 inputIndex = inputIndex + 1
else: else:
while inputIndex < len(inputs): while inputIndex < len(inputs):
@ -728,8 +729,8 @@ class Ledger_Client_Legacy(Ledger_Client):
inputSignature[0] = 0x30 # force for 1.4.9+ inputSignature[0] = 0x30 # force for 1.4.9+
my_pubkey = inputs[inputIndex][4] my_pubkey = inputs[inputIndex][4]
tx.add_signature_to_txin(txin_idx=inputIndex, tx.add_signature_to_txin(txin_idx=inputIndex,
signing_pubkey=my_pubkey.hex(), signing_pubkey=my_pubkey,
sig=inputSignature.hex()) sig=inputSignature)
inputIndex = inputIndex + 1 inputIndex = inputIndex + 1
firstTransaction = False firstTransaction = False
except UserWarning: except UserWarning:
@ -1247,7 +1248,7 @@ class Ledger_Client_New(Ledger_Client):
input_sigs = self.client.sign_psbt(psbt, wallet, wallet_hmac) input_sigs = self.client.sign_psbt(psbt, wallet, wallet_hmac)
for idx, part_sig in input_sigs: for idx, part_sig in input_sigs:
tx.add_signature_to_txin( 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: except DenyError:
pass # cancelled by user pass # cancelled by user
except BaseException as e: except BaseException as e:

4
electrum/plugins/safe_t/safe_t.py

@ -236,8 +236,8 @@ class SafeTPlugin(HW_PluginBase):
outputs = self.tx_outputs(tx, keystore=keystore) outputs = self.tx_outputs(tx, keystore=keystore)
signatures = client.sign_tx(self.get_coin_name(), inputs, outputs, signatures = client.sign_tx(self.get_coin_name(), inputs, outputs,
lock_time=tx.locktime, version=tx.version)[0] lock_time=tx.locktime, version=tx.version)[0]
sighash = Sighash.to_sigbytes(Sighash.ALL).hex() sighash = Sighash.to_sigbytes(Sighash.ALL)
signatures = [(x.hex() + sighash) for x in signatures] signatures = [(sig + sighash) for sig in signatures]
tx.update_signatures(signatures) tx.update_signatures(signatures)
@runs_in_hwd_thread @runs_in_hwd_thread

4
electrum/plugins/trezor/trezor.py

@ -346,8 +346,8 @@ class TrezorPlugin(HW_PluginBase):
amount_unit=self.get_trezor_amount_unit(), amount_unit=self.get_trezor_amount_unit(),
serialize=False, serialize=False,
prev_txes=prev_tx) prev_txes=prev_tx)
sighash = Sighash.to_sigbytes(Sighash.ALL).hex() sighash = Sighash.to_sigbytes(Sighash.ALL)
signatures = [((x.hex() + sighash) if x else None) for x in signatures] signatures = [((sig + sighash) if sig else None) for sig in signatures]
tx.update_signatures(signatures) tx.update_signatures(signatures)
@runs_in_hwd_thread @runs_in_hwd_thread

2
electrum/scripts/peers.py

@ -21,7 +21,7 @@ async def f():
results = await network.send_multiple_requests(peers, 'blockchain.headers.subscribe', []) results = await network.send_multiple_requests(peers, 'blockchain.headers.subscribe', [])
for server, header in sorted(results.items(), key=lambda x: x[1].get('height')): for server, header in sorted(results.items(), key=lambda x: x[1].get('height')):
height = header.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) print(server, height, blockhash)
finally: finally:
stopping_fut.set_result(1) stopping_fut.set_result(1)

27
electrum/submarine_swaps.py

@ -12,8 +12,8 @@ import aiohttp
from . import lnutil from . import lnutil
from .crypto import sha256, hash_160 from .crypto import sha256, hash_160
from .ecc import ECPrivkey from .ecc import ECPrivkey
from .bitcoin import (script_to_p2wsh, opcodes, p2wsh_nested_script, push_script, from .bitcoin import (script_to_p2wsh, opcodes,
is_segwit_address, construct_witness) construct_witness)
from .transaction import PartialTxInput, PartialTxOutput, PartialTransaction, Transaction, TxInput, TxOutpoint from .transaction import PartialTxInput, PartialTxOutput, PartialTransaction, Transaction, TxInput, TxOutpoint
from .transaction import script_GetOp, match_script_against_template, OPPushDataGeneric, OPPushDataPubkey from .transaction import script_GetOp, match_script_against_template, OPPushDataGeneric, OPPushDataPubkey
from .util import log_exceptions, BelowDustLimit, OldTaskGroup from .util import log_exceptions, BelowDustLimit, OldTaskGroup
@ -87,18 +87,17 @@ WITNESS_TEMPLATE_REVERSE_SWAP = [
def check_reverse_redeem_script( def check_reverse_redeem_script(
*, *,
redeem_script: str, redeem_script: bytes,
lockup_address: str, lockup_address: str,
payment_hash: bytes, payment_hash: bytes,
locktime: int, locktime: int,
refund_pubkey: bytes = None, refund_pubkey: bytes = None,
claim_pubkey: bytes = None, claim_pubkey: bytes = None,
) -> None: ) -> None:
redeem_script = bytes.fromhex(redeem_script)
parsed_script = [x for x in script_GetOp(redeem_script)] parsed_script = [x for x in script_GetOp(redeem_script)]
if not match_script_against_template(redeem_script, WITNESS_TEMPLATE_REVERSE_SWAP): if not match_script_against_template(redeem_script, WITNESS_TEMPLATE_REVERSE_SWAP):
raise Exception("rswap check failed: scriptcode does not match template") 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") raise Exception("rswap check failed: inconsistent scriptcode and address")
if ripemd(payment_hash) != parsed_script[5][1]: if ripemd(payment_hash) != parsed_script[5][1]:
raise Exception("rswap check failed: our preimage not in script") raise Exception("rswap check failed: our preimage not in script")
@ -438,7 +437,7 @@ class SwapManager(Logger):
def add_normal_swap( def add_normal_swap(
self, *, self, *,
redeem_script: str, redeem_script: bytes,
locktime: int, # onchain locktime: int, # onchain
onchain_amount_sat: int, onchain_amount_sat: int,
lightning_amount_sat: int, lightning_amount_sat: int,
@ -487,7 +486,7 @@ class SwapManager(Logger):
lockup_address = script_to_p2wsh(redeem_script) lockup_address = script_to_p2wsh(redeem_script)
receive_address = self.wallet.get_receiving_address() receive_address = self.wallet.get_receiving_address()
swap = SwapData( swap = SwapData(
redeem_script = bytes.fromhex(redeem_script), redeem_script=redeem_script,
locktime = locktime, locktime = locktime,
privkey = our_privkey, privkey = our_privkey,
preimage = None, preimage = None,
@ -533,7 +532,7 @@ class SwapManager(Logger):
def add_reverse_swap( def add_reverse_swap(
self, self,
*, *,
redeem_script: str, redeem_script: bytes,
locktime: int, # onchain locktime: int, # onchain
privkey: bytes, privkey: bytes,
lightning_amount_sat: int, lightning_amount_sat: int,
@ -545,7 +544,7 @@ class SwapManager(Logger):
lockup_address = script_to_p2wsh(redeem_script) lockup_address = script_to_p2wsh(redeem_script)
receive_address = self.wallet.get_receiving_address() receive_address = self.wallet.get_receiving_address()
swap = SwapData( swap = SwapData(
redeem_script = bytes.fromhex(redeem_script), redeem_script = redeem_script,
locktime = locktime, locktime = locktime,
privkey = privkey, privkey = privkey,
preimage = preimage, preimage = preimage,
@ -645,7 +644,7 @@ class SwapManager(Logger):
onchain_amount = data["expectedAmount"] onchain_amount = data["expectedAmount"]
locktime = data["timeoutBlockHeight"] locktime = data["timeoutBlockHeight"]
lockup_address = data["address"] lockup_address = data["address"]
redeem_script = data["redeemScript"] redeem_script = bytes.fromhex(data["redeemScript"])
# verify redeem_script is built with our pubkey and preimage # verify redeem_script is built with our pubkey and preimage
check_reverse_redeem_script( check_reverse_redeem_script(
redeem_script=redeem_script, redeem_script=redeem_script,
@ -805,7 +804,7 @@ class SwapManager(Logger):
invoice = data['invoice'] invoice = data['invoice']
fee_invoice = data.get('minerFeeInvoice') fee_invoice = data.get('minerFeeInvoice')
lockup_address = data['lockupAddress'] lockup_address = data['lockupAddress']
redeem_script = data['redeemScript'] redeem_script = bytes.fromhex(data['redeemScript'])
locktime = data['timeoutBlockHeight'] locktime = data['timeoutBlockHeight']
onchain_amount = data["onchainAmount"] onchain_amount = data["onchainAmount"]
response_id = data['id'] response_id = data['id']
@ -1052,7 +1051,7 @@ class SwapManager(Logger):
txin.witness_script = witness_script txin.witness_script = witness_script
sig_dummy = b'\x00' * 71 # DER-encoded ECDSA sig, with low S and low R sig_dummy = b'\x00' * 71 # DER-encoded ECDSA sig, with low S and low R
witness = [sig_dummy, preimage, witness_script] witness = [sig_dummy, preimage, witness_script]
txin.witness_sizehint = len(bytes.fromhex(construct_witness(witness))) txin.witness_sizehint = len(construct_witness(witness))
@classmethod @classmethod
def sign_tx(cls, tx: PartialTransaction, swap: SwapData) -> None: def sign_tx(cls, tx: PartialTransaction, swap: SwapData) -> None:
@ -1063,9 +1062,9 @@ class SwapManager(Logger):
assert txin.prevout.txid.hex() == swap.funding_txid assert txin.prevout.txid.hex() == swap.funding_txid
txin.script_sig = b'' txin.script_sig = b''
txin.witness_script = witness_script 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] witness = [sig, preimage, witness_script]
txin.witness = bytes.fromhex(construct_witness(witness)) txin.witness = construct_witness(witness)
@classmethod @classmethod
def _create_and_sign_claim_tx( def _create_and_sign_claim_tx(

195
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, from .bitcoin import (TYPE_ADDRESS, TYPE_SCRIPT, hash_160,
hash160_to_p2sh, hash160_to_p2pkh, hash_to_segwit_addr, hash160_to_p2sh, hash160_to_p2pkh, hash_to_segwit_addr,
var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN, var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN,
int_to_hex, push_script, b58_address_to_hash160, opcodes, base_decode,
opcodes, add_number_to_script, base_decode,
base_encode, construct_witness, construct_script) base_encode, construct_witness, construct_script)
from .crypto import sha256d from .crypto import sha256d
from .logging import get_logger from .logging import get_logger
@ -138,13 +137,13 @@ class TxOutput:
@classmethod @classmethod
def from_address_and_value(cls, address: str, value: Union[int, str]) -> Union['TxOutput', 'PartialTxOutput']: 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) value=value)
def serialize_to_network(self) -> bytes: def serialize_to_network(self) -> bytes:
buf = int.to_bytes(self.value, 8, byteorder="little", signed=False) buf = int.to_bytes(self.value, 8, byteorder="little", signed=False)
script = self.scriptpubkey script = self.scriptpubkey
buf += bfh(var_int(len(script.hex()) // 2)) buf += var_int(len(script))
buf += script buf += script
return buf return buf
@ -212,9 +211,9 @@ class TxOutput:
class BIP143SharedTxDigestFields(NamedTuple): class BIP143SharedTxDigestFields(NamedTuple):
hashPrevouts: str hashPrevouts: bytes
hashSequence: str hashSequence: bytes
hashOutputs: str hashOutputs: bytes
class TxOutpoint(NamedTuple): class TxOutpoint(NamedTuple):
@ -241,7 +240,7 @@ class TxOutpoint(NamedTuple):
return [self.txid.hex(), self.out_idx] return [self.txid.hex(), self.out_idx]
def serialize_to_network(self) -> bytes: 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: def is_coinbase(self) -> bool:
return self.txid == bytes(32) return self.txid == bytes(32)
@ -356,9 +355,9 @@ class TxInput:
# Prev hash and index # Prev hash and index
s = self.prevout.serialize_to_network() s = self.prevout.serialize_to_network()
# Script length, script, sequence # Script length, script, sequence
s += bytes.fromhex(var_int(len(script_sig))) s += var_int(len(script_sig))
s += 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 return s
def witness_elements(self) -> Sequence[bytes]: def witness_elements(self) -> Sequence[bytes]:
@ -714,7 +713,7 @@ def parse_input(vds: BCDataStream) -> TxInput:
def parse_witness(vds: BCDataStream, txin: TxInput) -> None: def parse_witness(vds: BCDataStream, txin: TxInput) -> None:
n = vds.read_compact_size() n = vds.read_compact_size()
witness_elements = list(vds.read_bytes(vds.read_compact_size()) for i in range(n)) 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: def parse_output(vds: BCDataStream) -> TxOutput:
@ -729,7 +728,7 @@ def parse_output(vds: BCDataStream) -> TxOutput:
# pay & redeem scripts # 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) n = len(public_keys)
assert 1 <= m <= n <= 15, f'm {m}, n {n}' assert 1 <= m <= n <= 15, f'm {m}, n {n}'
return construct_script([m, *public_keys, n, opcodes.OP_CHECKMULTISIG]) return construct_script([m, *public_keys, n, opcodes.OP_CHECKMULTISIG])
@ -833,18 +832,18 @@ class Transaction:
raise SerializationError('extra junk at the end') raise SerializationError('extra junk at the end')
@classmethod @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: if txin.witness is not None:
return txin.witness.hex() return txin.witness
if txin.is_coinbase_input(): if txin.is_coinbase_input():
return '' return b""
assert isinstance(txin, PartialTxInput) assert isinstance(txin, PartialTxInput)
if not txin.is_segwit(): if not txin.is_segwit():
return construct_witness([]) return construct_witness([])
if estimate_size and txin.witness_sizehint is not None: if estimate_size and txin.witness_sizehint is not None:
return '00' * txin.witness_sizehint return bytes(txin.witness_sizehint)
dummy_desc = None dummy_desc = None
if estimate_size: if estimate_size:
@ -852,22 +851,22 @@ class Transaction:
if desc := (txin.script_descriptor or dummy_desc): if desc := (txin.script_descriptor or dummy_desc):
sol = desc.satisfy(allow_dummy=estimate_size, sigdata=txin.part_sigs) sol = desc.satisfy(allow_dummy=estimate_size, sigdata=txin.part_sigs)
if sol.witness is not None: if sol.witness is not None:
return sol.witness.hex() return sol.witness
return construct_witness([]) return construct_witness([])
raise UnknownTxinType("cannot construct witness") raise UnknownTxinType("cannot construct witness")
@classmethod @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: if txin.script_sig is not None:
return txin.script_sig.hex() return txin.script_sig
if txin.is_coinbase_input(): if txin.is_coinbase_input():
return '' return b""
assert isinstance(txin, PartialTxInput) assert isinstance(txin, PartialTxInput)
if txin.is_p2sh_segwit() and txin.redeem_script: if txin.is_p2sh_segwit() and txin.redeem_script:
return construct_script([txin.redeem_script]) return construct_script([txin.redeem_script])
if txin.is_native_segwit(): if txin.is_native_segwit():
return '' return b""
dummy_desc = None dummy_desc = None
if estimate_size: if estimate_size:
@ -876,37 +875,39 @@ class Transaction:
if desc.is_segwit(): if desc.is_segwit():
if redeem_script := desc.expand().redeem_script: if redeem_script := desc.expand().redeem_script:
return construct_script([redeem_script]) return construct_script([redeem_script])
return "" return b""
sol = desc.satisfy(allow_dummy=estimate_size, sigdata=txin.part_sigs) sol = desc.satisfy(allow_dummy=estimate_size, sigdata=txin.part_sigs)
if sol.script_sig is not None: if sol.script_sig is not None:
return sol.script_sig.hex() return sol.script_sig
return "" return b""
raise UnknownTxinType("cannot construct scriptSig") raise UnknownTxinType("cannot construct scriptSig")
@classmethod @classmethod
def get_preimage_script(cls, txin: 'PartialTxInput') -> str: def get_preimage_script(cls, txin: 'PartialTxInput') -> bytes:
if txin.witness_script: if txin.witness_script:
if opcodes.OP_CODESEPARATOR in [x[0] for x in script_GetOp(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') 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 not txin.is_segwit() and txin.redeem_script:
if opcodes.OP_CODESEPARATOR in [x[0] for x in script_GetOp(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') raise Exception('OP_CODESEPARATOR black magic is not supported')
return txin.redeem_script.hex() return txin.redeem_script
if desc := txin.script_descriptor: if desc := txin.script_descriptor:
sc = desc.expand() sc = desc.expand()
if script := sc.scriptcode_for_sighash: if script := sc.scriptcode_for_sighash:
return script.hex() return script
raise Exception(f"don't know scriptcode for descriptor: {desc.to_string()}") raise Exception(f"don't know scriptcode for descriptor: {desc.to_string()}")
raise UnknownTxinType(f'cannot construct preimage_script') raise UnknownTxinType(f'cannot construct preimage_script')
def _calc_bip143_shared_txdigest_fields(self) -> BIP143SharedTxDigestFields: def _calc_bip143_shared_txdigest_fields(self) -> BIP143SharedTxDigestFields:
inputs = self.inputs() inputs = self.inputs()
outputs = self.outputs() outputs = self.outputs()
hashPrevouts = sha256d(b''.join(txin.prevout.serialize_to_network() for txin in inputs)).hex() hashPrevouts = sha256d(b''.join(txin.prevout.serialize_to_network() for txin in inputs))
hashSequence = sha256d(bfh(''.join(int_to_hex(txin.nsequence, 4) for txin in inputs))).hex() hashSequence = sha256d(b''.join(
hashOutputs = sha256d(bfh(''.join(o.serialize_to_network().hex() for o in outputs))).hex() 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, return BIP143SharedTxDigestFields(hashPrevouts=hashPrevouts,
hashSequence=hashSequence, hashSequence=hashSequence,
hashOutputs=hashOutputs) hashOutputs=hashOutputs)
@ -934,19 +935,20 @@ class Transaction:
note: (not include_sigs) implies force_legacy note: (not include_sigs) implies force_legacy
""" """
self.deserialize() self.deserialize()
nVersion = int_to_hex(self.version, 4) nVersion = int.to_bytes(self.version, length=4, byteorder="little", signed=True).hex()
nLocktime = int_to_hex(self.locktime, 4) nLocktime = int.to_bytes(self.locktime, length=4, byteorder="little", signed=False).hex()
inputs = self.inputs() inputs = self.inputs()
outputs = self.outputs() outputs = self.outputs()
def create_script_sig(txin: TxInput) -> bytes: def create_script_sig(txin: TxInput) -> bytes:
if include_sigs: if include_sigs:
script_sig = self.input_script(txin, estimate_size=estimate_size) script_sig = self.input_script(txin, estimate_size=estimate_size)
return bytes.fromhex(script_sig) return script_sig
return b"" return b""
txins = var_int(len(inputs)) + ''.join(txin.serialize_to_network(script_sig=create_script_sig(txin)).hex() txins = var_int(len(inputs)).hex() + ''.join(
for txin in inputs) txin.serialize_to_network(script_sig=create_script_sig(txin)).hex()
txouts = var_int(len(outputs)) + ''.join(o.serialize_to_network().hex() for o in outputs) 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_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() 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: if include_sigs and not force_legacy and use_segwit_ser:
marker = '00' marker = '00'
flag = '01' 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 return nVersion + marker + flag + txins + txouts + witness + nLocktime
else: else:
return nVersion + txins + txouts + nLocktime return nVersion + txins + txouts + nLocktime
@ -1073,7 +1075,7 @@ class Transaction:
"""Whether the tx explicitly signals BIP-0125 replace-by-fee.""" """Whether the tx explicitly signals BIP-0125 replace-by-fee."""
return any([txin.nsequence < 0xffffffff - 1 for txin in self.inputs()]) 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. """Return an estimated virtual tx size in vbytes.
BIP-0141 defines 'Virtual transaction size' to be weight/4 rounded up. BIP-0141 defines 'Virtual transaction size' to be weight/4 rounded up.
This definition is only for humans, and has little meaning otherwise. This definition is only for humans, and has little meaning otherwise.
@ -1084,13 +1086,13 @@ class Transaction:
return self.virtual_size_from_weight(weight) return self.virtual_size_from_weight(weight)
@classmethod @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.''' '''Return an estimate of serialized input weight in weight units.'''
script_sig = cls.input_script(txin, estimate_size=True) 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): 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: else:
witness_size = 1 if is_segwit_tx else 0 witness_size = 1 if is_segwit_tx else 0
@ -1103,15 +1105,15 @@ class Transaction:
return cls.estimated_output_size_for_script(script) return cls.estimated_output_size_for_script(script)
@classmethod @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.""" """Return an estimate of serialized output size in bytes."""
# 8 byte value + varint script len + script # 8 byte value + varint script len + script
script_len = len(script) // 2 script_len = len(script)
var_int_len = len(var_int(script_len)) // 2 var_int_len = len(var_int(script_len))
return 8 + var_int_len + script_len return 8 + var_int_len + script_len
@classmethod @classmethod
def virtual_size_from_weight(cls, weight): def virtual_size_from_weight(cls, weight: int) -> int:
return weight // 4 + (weight % 4 > 0) return weight // 4 + (weight % 4 > 0)
@classmethod @classmethod
@ -1132,8 +1134,8 @@ class Transaction:
if not self.is_segwit(guess_for_address=estimate): if not self.is_segwit(guess_for_address=estimate):
return 0 return 0
inputs = self.inputs() inputs = self.inputs()
witness = ''.join(self.serialize_witness(x, estimate_size=estimate) for x in inputs) witness = b"".join(self.serialize_witness(x, estimate_size=estimate) for x in inputs)
witness_size = len(witness) // 2 + 2 # include marker and flag witness_size = len(witness) + 2 # include marker and flag
return witness_size return witness_size
def estimated_base_size(self): def estimated_base_size(self):
@ -1149,17 +1151,16 @@ class Transaction:
def is_complete(self) -> bool: def is_complete(self) -> bool:
return True 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.""" """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 # build cache if there isn't one yet
# note: can become stale and return incorrect data # note: can become stale and return incorrect data
# if the tx is modified later; that's out of scope. # if the tx is modified later; that's out of scope.
if not hasattr(self, '_script_to_output_idx'): if not hasattr(self, '_script_to_output_idx'):
d = defaultdict(set) d = defaultdict(set)
for output_idx, o in enumerate(self.outputs()): for output_idx, o in enumerate(self.outputs()):
o_script = o.scriptpubkey.hex() o_script = o.scriptpubkey
assert isinstance(o_script, str)
d[o_script].add(output_idx) d[o_script].add(output_idx)
self._script_to_output_idx = d self._script_to_output_idx = d
return set(self._script_to_output_idx[script]) # copy return set(self._script_to_output_idx[script]) # copy
@ -1347,9 +1348,9 @@ class PSBTSection:
def create_psbt_writer(cls, fd): def create_psbt_writer(cls, fd):
def wr(key_type: int, val: bytes, key: bytes = b''): def wr(key_type: int, val: bytes, key: bytes = b''):
full_key = cls.get_fullkey_from_keytype_and_key(key_type, key) 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(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 fd.write(val) # val
return wr return wr
@ -1363,7 +1364,7 @@ class PSBTSection:
@classmethod @classmethod
def get_fullkey_from_keytype_and_key(cls, key_type: int, key: bytes) -> bytes: 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 return key_type_bytes + key
def _serialize_psbt_section(self, fd): 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") f"If a redeemScript is provided, the scriptPubKey must be for that redeemScript")
if self.witness_script: if self.witness_script:
if self.redeem_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: " raise PSBTInputConsistencyFailure(f"PSBT input validation: "
f"If a witnessScript is provided, the redeemScript must be for that witnessScript") f"If a witnessScript is provided, the redeemScript must be for that witnessScript")
elif self.address: 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: " raise PSBTInputConsistencyFailure(f"PSBT input validation: "
f"If a witnessScript is provided, the scriptPubKey must be for that witnessScript") 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: if (spk := super().scriptpubkey) is not None:
return spk return spk
if self._trusted_address is not None: 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: if self.witness_utxo:
return self.witness_utxo.scriptpubkey return self.witness_utxo.scriptpubkey
return None return None
@ -1657,8 +1658,8 @@ class PartialTxInput(TxInput, PSBTSection):
clear_fields_when_finalized() clear_fields_when_finalized()
return # already finalized return # already finalized
if self.is_complete(): if self.is_complete():
self.script_sig = bfh(Transaction.input_script(self)) self.script_sig = Transaction.input_script(self)
self.witness = bfh(Transaction.serialize_witness(self)) self.witness = Transaction.serialize_witness(self)
clear_fields_when_finalized() clear_fields_when_finalized()
def combine_with_other_txin(self, other_txin: 'TxInput') -> None: def combine_with_other_txin(self, other_txin: 'TxInput') -> None:
@ -2086,16 +2087,16 @@ class PartialTransaction(Transaction):
self.invalidate_ser_cache() self.invalidate_ser_cache()
def serialize_preimage(self, txin_index: int, *, def serialize_preimage(self, txin_index: int, *,
bip143_shared_txdigest_fields: BIP143SharedTxDigestFields = None) -> str: bip143_shared_txdigest_fields: BIP143SharedTxDigestFields = None) -> bytes:
nVersion = int_to_hex(self.version, 4) nVersion = int.to_bytes(self.version, length=4, byteorder="little", signed=True)
nLocktime = int_to_hex(self.locktime, 4) nLocktime = int.to_bytes(self.locktime, length=4, byteorder="little", signed=False)
inputs = self.inputs() inputs = self.inputs()
outputs = self.outputs() outputs = self.outputs()
txin = inputs[txin_index] txin = inputs[txin_index]
sighash = txin.sighash if txin.sighash is not None else Sighash.ALL sighash = txin.sighash if txin.sighash is not None else Sighash.ALL
if not Sighash.is_valid(sighash): if not Sighash.is_valid(sighash):
raise Exception(f"SIGHASH_FLAG ({sighash}) not supported!") 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) preimage_script = self.get_preimage_script(txin)
if txin.is_segwit(): if txin.is_segwit():
if bip143_shared_txdigest_fields is None: if bip143_shared_txdigest_fields is None:
@ -2103,28 +2104,29 @@ class PartialTransaction(Transaction):
if not (sighash & Sighash.ANYONECANPAY): if not (sighash & Sighash.ANYONECANPAY):
hashPrevouts = bip143_shared_txdigest_fields.hashPrevouts hashPrevouts = bip143_shared_txdigest_fields.hashPrevouts
else: else:
hashPrevouts = '00' * 32 hashPrevouts = bytes(32)
if not (sighash & Sighash.ANYONECANPAY) and (sighash & 0x1f) != Sighash.SINGLE and (sighash & 0x1f) != Sighash.NONE: if not (sighash & Sighash.ANYONECANPAY) and (sighash & 0x1f) != Sighash.SINGLE and (sighash & 0x1f) != Sighash.NONE:
hashSequence = bip143_shared_txdigest_fields.hashSequence hashSequence = bip143_shared_txdigest_fields.hashSequence
else: else:
hashSequence = '00' * 32 hashSequence = bytes(32)
if (sighash & 0x1f) != Sighash.SINGLE and (sighash & 0x1f) != Sighash.NONE: if (sighash & 0x1f) != Sighash.SINGLE and (sighash & 0x1f) != Sighash.NONE:
hashOutputs = bip143_shared_txdigest_fields.hashOutputs hashOutputs = bip143_shared_txdigest_fields.hashOutputs
elif (sighash & 0x1f) == Sighash.SINGLE and txin_index < len(outputs): 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: else:
hashOutputs = '00' * 32 hashOutputs = bytes(32)
outpoint = txin.prevout.serialize_to_network().hex() outpoint = txin.prevout.serialize_to_network()
scriptCode = var_int(len(preimage_script) // 2) + preimage_script scriptCode = var_int(len(preimage_script)) + preimage_script
amount = int_to_hex(txin.value_sats(), 8) amount = int.to_bytes(txin.value_sats(), length=8, byteorder="little", signed=False)
nSequence = int_to_hex(txin.nsequence, 4) nSequence = int.to_bytes(txin.nsequence, length=4, byteorder="little", signed=False)
preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nHashType preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nHashType
else: else:
if sighash != Sighash.ALL: if sighash != Sighash.ALL:
raise Exception(f"SIGHASH_FLAG ({sighash}) not supported! (for legacy sighash)") 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() txins = var_int(len(inputs)) + b"".join(
for k, txin in enumerate(inputs)) txin.serialize_to_network(script_sig=preimage_script if txin_index==k else b"")
txouts = var_int(len(outputs)) + ''.join(o.serialize_to_network().hex() for o in outputs) 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 preimage = nVersion + txins + txouts + nLocktime + nHashType
return preimage return preimage
@ -2141,20 +2143,26 @@ class PartialTransaction(Transaction):
_logger.info(f"adding signature for {pubkey}. spending utxo {txin.prevout.to_str()}") _logger.info(f"adding signature for {pubkey}. spending utxo {txin.prevout.to_str()}")
sec, compressed = keypairs[pubkey] sec, compressed = keypairs[pubkey]
sig = self.sign_txin(i, sec, bip143_shared_txdigest_fields=bip143_shared_txdigest_fields) 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()}") _logger.debug(f"is_complete {self.is_complete()}")
self.invalidate_ser_cache() 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 = self.inputs()[txin_index]
txin.validate_data(for_signing=True) txin.validate_data(for_signing=True)
sighash = txin.sighash if txin.sighash is not None else Sighash.ALL sighash = txin.sighash if txin.sighash is not None else Sighash.ALL
pre_hash = sha256d(bfh(self.serialize_preimage(txin_index, pre_hash = self.serialize_preimage(txin_index, bip143_shared_txdigest_fields=bip143_shared_txdigest_fields)
bip143_shared_txdigest_fields=bip143_shared_txdigest_fields))) msg_hash = sha256d(pre_hash)
privkey = ecc.ECPrivkey(privkey_bytes) privkey = ecc.ECPrivkey(privkey_bytes)
sig = privkey.ecdsa_sign(pre_hash, sigencode=ecc.ecdsa_der_sig_from_r_and_s) sig = privkey.ecdsa_sign(msg_hash, sigencode=ecc.ecdsa_der_sig_from_r_and_s)
sig = sig.hex() + Sighash.to_sigbytes(sighash).hex() sig = sig + Sighash.to_sigbytes(sighash)
return sig return sig
def is_complete(self) -> bool: def is_complete(self) -> bool:
@ -2189,7 +2197,7 @@ class PartialTransaction(Transaction):
raw_bytes = self.serialize_as_bytes() raw_bytes = self.serialize_as_bytes()
return base64.b64encode(raw_bytes).decode('ascii') 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 """Add new signatures to a transaction
`signatures` is expected to be a list of sigs with signatures[i] `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): if len(self.inputs()) != len(signatures):
raise Exception('expected {} signatures; got {}'.format(len(self.inputs()), len(signatures))) raise Exception('expected {} signatures; got {}'.format(len(self.inputs()), len(signatures)))
for i, txin in enumerate(self.inputs()): for i, txin in enumerate(self.inputs()):
pubkeys = [pk.hex() for pk in txin.pubkeys]
sig = signatures[i] sig = signatures[i]
if sig is None: if sig is None:
continue continue
if bfh(sig) in list(txin.part_sigs.values()): if sig in list(txin.part_sigs.values()):
continue continue
pre_hash = sha256d(bfh(self.serialize_preimage(i))) msg_hash = sha256d(self.serialize_preimage(i))
sig_string = ecc.ecdsa_sig64_from_der_sig(bfh(sig[:-2])) sig64 = ecc.ecdsa_sig64_from_der_sig(sig[:-1])
for recid in range(4): for recid in range(4):
try: 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: except ecc.InvalidECPointException:
# the point might not be on the curve for some recid values # the point might not be on the curve for some recid values
continue continue
pubkey_hex = public_key.get_public_key_hex(compressed=True) pubkey_bytes = public_key.get_public_key_bytes(compressed=True)
if pubkey_hex in pubkeys: if pubkey_bytes in txin.pubkeys:
if not public_key.ecdsa_verify(sig_string, pre_hash): if not public_key.ecdsa_verify(sig64, msg_hash):
continue continue
_logger.info(f"adding sig: txin_idx={i}, signing_pubkey={pubkey_hex}, sig={sig}") _logger.info(f"adding sig: txin_idx={i}, signing_pubkey={pubkey_bytes.hex()}, sig={sig.hex()}")
self.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_hex, sig=sig) self.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey_bytes, sig=sig)
break break
# redo raw # redo raw
self.invalidate_ser_cache() 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 = self._inputs[txin_idx]
txin.part_sigs[bfh(signing_pubkey)] = bfh(sig) txin.part_sigs[signing_pubkey] = sig
# force re-serialization # force re-serialization
txin.script_sig = None txin.script_sig = None
txin.witness = None txin.witness = None

16
electrum/wallet.py

@ -111,14 +111,14 @@ async def _append_utxos_to_inputs(
script_descriptor: 'descriptor.Descriptor', script_descriptor: 'descriptor.Descriptor',
imax: int, imax: int,
) -> None: ) -> None:
script = script_descriptor.expand().output_script.hex() script = script_descriptor.expand().output_script
scripthash = bitcoin.script_to_scripthash(script) scripthash = bitcoin.script_to_scripthash(script)
async def append_single_utxo(item): async def append_single_utxo(item):
prev_tx_raw = await network.get_transaction(item['tx_hash']) prev_tx_raw = await network.get_transaction(item['tx_hash'])
prev_tx = Transaction(prev_tx_raw) prev_tx = Transaction(prev_tx_raw)
prev_txout = prev_tx.outputs()[item['tx_pos']] 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') raise Exception('scripthash mismatch when sweeping')
prevout_str = item['tx_hash'] + ':%d' % item['tx_pos'] prevout_str = item['tx_hash'] + ':%d' % item['tx_pos']
prevout = TxOutpoint.from_str(prevout_str) prevout = TxOutpoint.from_str(prevout_str)
@ -182,7 +182,7 @@ async def sweep(
inputs, keypairs = await sweep_preparations(privkeys, network, imax) inputs, keypairs = await sweep_preparations(privkeys, network, imax)
total = sum(txin.value_sats() for txin in inputs) total = sum(txin.value_sats() for txin in inputs)
if fee is None: 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)] value=total)]
tx = PartialTransaction.from_io(inputs, outputs) tx = PartialTransaction.from_io(inputs, outputs)
fee = config.estimate_fee(tx.estimated_size()) fee = config.estimate_fee(tx.estimated_size())
@ -191,7 +191,7 @@ async def sweep(
if total - fee < dust_threshold(network): 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))) 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)] value=total - fee)]
if locktime is None: if locktime is None:
locktime = get_locktime_for_new_transaction(network) locktime = get_locktime_for_new_transaction(network)
@ -1297,7 +1297,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
conf_needed = None # type: Optional[int] conf_needed = None # type: Optional[int]
with self.lock, self.transaction_lock: with self.lock, self.transaction_lock:
for invoice_scriptpubkey, invoice_amt in invoice_amounts.items(): 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) prevouts_and_values = self.db.get_prevouts_by_scripthash(scripthash)
confs_and_values = [] confs_and_values = []
for prevout, v in prevouts_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 delta_total = target_fee - cur_fee
if delta_total <= 0: if delta_total <= 0:
break 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) for (idx, out) in s if idx not in del_out_idxs)
if out_size_total == 0: # no outputs left to decrease if out_size_total == 0: # no outputs left to decrease
raise CannotBumpFee(_('Could not find suitable outputs')) raise CannotBumpFee(_('Could not find suitable outputs'))
for idx, out in s: 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)) delta = int(math.ceil(delta_total * out_size / out_size_total))
if out.value - delta >= self.dust_threshold(): if out.value - delta >= self.dust_threshold():
new_output_value = out.value - delta new_output_value = out.value - delta
@ -3804,7 +3804,7 @@ class Multisig_Wallet(Deterministic_Wallet):
redeem_script = self.pubkeys_to_scriptcode(pubkeys) redeem_script = self.pubkeys_to_scriptcode(pubkeys)
return bitcoin.redeem_script_to_address(self.txin_type, redeem_script) 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) return transaction.multisig_script(sorted(pubkeys), self.m)
def derive_pubkeys(self, c, i): def derive_pubkeys(self, c, i):

4
electrum/wallet_db.py

@ -522,7 +522,7 @@ class WalletDBUpgrader(Logger):
tx = Transaction(raw_tx) tx = Transaction(raw_tx)
for idx, txout in enumerate(tx.outputs()): for idx, txout in enumerate(tx.outputs()):
outpoint = f"{txid}:{idx}" 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)) prevouts_by_scripthash[scripthash].append((outpoint, txout.value))
self.put('prevouts_by_scripthash', prevouts_by_scripthash) self.put('prevouts_by_scripthash', prevouts_by_scripthash)
@ -1126,7 +1126,7 @@ class WalletDBUpgrader(Logger):
tx = Transaction(raw_tx) tx = Transaction(raw_tx)
for idx, txout in enumerate(tx.outputs()): for idx, txout in enumerate(tx.outputs()):
outpoint = f"{txid}:{idx}" outpoint = f"{txid}:{idx}"
scripthash = script_to_scripthash(txout.scriptpubkey.hex()) scripthash = script_to_scripthash(txout.scriptpubkey)
if scripthash not in prevouts_by_scripthash: if scripthash not in prevouts_by_scripthash:
prevouts_by_scripthash[scripthash] = {} prevouts_by_scripthash[scripthash] = {}
prevouts_by_scripthash[scripthash][outpoint] = txout.value prevouts_by_scripthash[scripthash][outpoint] = txout.value

152
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, deserialize_privkey, serialize_privkey, is_segwit_address,
is_b58_address, address_to_scripthash, is_minikey, is_b58_address, address_to_scripthash, is_minikey,
is_compressed_privkey, EncodeBase58Check, DecodeBase58Check, 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) opcodes, base_encode, base_decode, BitcoinException)
from electrum import bip32 from electrum import bip32
from electrum import segwit_addr 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', 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")) 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): def test_var_int(self):
for i in range(0xfd): for i in range(0xfd):
self.assertEqual(var_int(i), "{:02x}".format(i)) self.assertEqual(var_int(i), bfh("{:02x}".format(i)))
self.assertEqual(var_int(0xfd), "fdfd00") self.assertEqual(var_int(0xfd), bfh("fdfd00"))
self.assertEqual(var_int(0xfe), "fdfe00") self.assertEqual(var_int(0xfe), bfh("fdfe00"))
self.assertEqual(var_int(0xff), "fdff00") self.assertEqual(var_int(0xff), bfh("fdff00"))
self.assertEqual(var_int(0x1234), "fd3412") self.assertEqual(var_int(0x1234), bfh("fd3412"))
self.assertEqual(var_int(0xffff), "fdffff") self.assertEqual(var_int(0xffff), bfh("fdffff"))
self.assertEqual(var_int(0x10000), "fe00000100") self.assertEqual(var_int(0x10000), bfh("fe00000100"))
self.assertEqual(var_int(0x12345678), "fe78563412") self.assertEqual(var_int(0x12345678), bfh("fe78563412"))
self.assertEqual(var_int(0xffffffff), "feffffffff") self.assertEqual(var_int(0xffffffff), bfh("feffffffff"))
self.assertEqual(var_int(0x100000000), "ff0000000001000000") self.assertEqual(var_int(0x100000000), bfh("ff0000000001000000"))
self.assertEqual(var_int(0x0123456789abcdef), "ffefcdab8967452301") self.assertEqual(var_int(0x0123456789abcdef), bfh("ffefcdab8967452301"))
def test_op_push(self): def test_op_push(self):
self.assertEqual(_op_push(0x00), '00') self.assertEqual(_op_push(0x00), bfh('00'))
self.assertEqual(_op_push(0x12), '12') self.assertEqual(_op_push(0x12), bfh('12'))
self.assertEqual(_op_push(0x4b), '4b') self.assertEqual(_op_push(0x4b), bfh('4b'))
self.assertEqual(_op_push(0x4c), '4c4c') self.assertEqual(_op_push(0x4c), bfh('4c4c'))
self.assertEqual(_op_push(0xfe), '4cfe') self.assertEqual(_op_push(0xfe), bfh('4cfe'))
self.assertEqual(_op_push(0xff), '4cff') self.assertEqual(_op_push(0xff), bfh('4cff'))
self.assertEqual(_op_push(0x100), '4d0001') self.assertEqual(_op_push(0x100), bfh('4d0001'))
self.assertEqual(_op_push(0x1234), '4d3412') self.assertEqual(_op_push(0x1234), bfh('4d3412'))
self.assertEqual(_op_push(0xfffe), '4dfeff') self.assertEqual(_op_push(0xfffe), bfh('4dfeff'))
self.assertEqual(_op_push(0xffff), '4dffff') self.assertEqual(_op_push(0xffff), bfh('4dffff'))
self.assertEqual(_op_push(0x10000), '4e00000100') self.assertEqual(_op_push(0x10000), bfh('4e00000100'))
self.assertEqual(_op_push(0x12345678), '4e78563412') self.assertEqual(_op_push(0x12345678), bfh('4e78563412'))
def test_script_num_to_hex(self): def test_script_num_to_hex(self):
# test vectors from https://github.com/btcsuite/btcd/blob/fdc2bc867bda6b351191b5872d2da8270df00d13/txscript/scriptnum.go#L77 # 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_bytes(127), bfh('7f'))
self.assertEqual(script_num_to_hex(-127), 'ff') self.assertEqual(script_num_to_bytes(-127), bfh('ff'))
self.assertEqual(script_num_to_hex(128), '8000') self.assertEqual(script_num_to_bytes(128), bfh('8000'))
self.assertEqual(script_num_to_hex(-128), '8080') self.assertEqual(script_num_to_bytes(-128), bfh('8080'))
self.assertEqual(script_num_to_hex(129), '8100') self.assertEqual(script_num_to_bytes(129), bfh('8100'))
self.assertEqual(script_num_to_hex(-129), '8180') self.assertEqual(script_num_to_bytes(-129), bfh('8180'))
self.assertEqual(script_num_to_hex(256), '0001') self.assertEqual(script_num_to_bytes(256), bfh('0001'))
self.assertEqual(script_num_to_hex(-256), '0081') self.assertEqual(script_num_to_bytes(-256), bfh('0081'))
self.assertEqual(script_num_to_hex(32767), 'ff7f') self.assertEqual(script_num_to_bytes(32767), bfh('ff7f'))
self.assertEqual(script_num_to_hex(-32767), 'ffff') self.assertEqual(script_num_to_bytes(-32767), bfh('ffff'))
self.assertEqual(script_num_to_hex(32768), '008000') self.assertEqual(script_num_to_bytes(32768), bfh('008000'))
self.assertEqual(script_num_to_hex(-32768), '008080') self.assertEqual(script_num_to_bytes(-32768), bfh('008080'))
def test_push_script(self): def test_push_script(self):
# https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#push-operators # 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(b""), bytes([opcodes.OP_0]))
self.assertEqual(push_script('07'), bytes([opcodes.OP_7]).hex()) self.assertEqual(push_script(b'\x07'), bytes([opcodes.OP_7]))
self.assertEqual(push_script('10'), bytes([opcodes.OP_16]).hex()) self.assertEqual(push_script(b'\x10'), bytes([opcodes.OP_16]))
self.assertEqual(push_script('81'), bytes([opcodes.OP_1NEGATE]).hex()) self.assertEqual(push_script(b'\x81'), bytes([opcodes.OP_1NEGATE]))
self.assertEqual(push_script('11'), '0111') self.assertEqual(push_script(b'\x11'), bfh('0111'))
self.assertEqual(push_script(75 * '42'), '4b' + 75 * '42') self.assertEqual(push_script(75 * b'\x42'), bfh('4b' + 75 * '42'))
self.assertEqual(push_script(76 * '42'), (bytes([opcodes.OP_PUSHDATA1]) + bfh('4c' + 76 * '42')).hex()) self.assertEqual(push_script(76 * b'\x42'), bytes([opcodes.OP_PUSHDATA1]) + bfh('4c' + 76 * '42'))
self.assertEqual(push_script(100 * '42'), (bytes([opcodes.OP_PUSHDATA1]) + bfh('64' + 100 * '42')).hex()) self.assertEqual(push_script(100 * b'\x42'), bytes([opcodes.OP_PUSHDATA1]) + bfh('64' + 100 * '42'))
self.assertEqual(push_script(255 * '42'), (bytes([opcodes.OP_PUSHDATA1]) + bfh('ff' + 255 * '42')).hex()) self.assertEqual(push_script(255 * b'\x42'), bytes([opcodes.OP_PUSHDATA1]) + bfh('ff' + 255 * '42'))
self.assertEqual(push_script(256 * '42'), (bytes([opcodes.OP_PUSHDATA2]) + bfh('0001' + 256 * '42')).hex()) self.assertEqual(push_script(256 * b'\x42'), bytes([opcodes.OP_PUSHDATA2]) + bfh('0001' + 256 * '42'))
self.assertEqual(push_script(520 * '42'), (bytes([opcodes.OP_PUSHDATA2]) + bfh('0802' + 520 * '42')).hex()) self.assertEqual(push_script(520 * b'\x42'), bytes([opcodes.OP_PUSHDATA2]) + bfh('0802' + 520 * '42'))
def test_add_number_to_script(self): def test_add_number_to_script(self):
# https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#numbers # https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#numbers
@ -528,17 +508,17 @@ class Test_bitcoin(ElectrumTestCase):
# bech32/bech32m native segwit # bech32/bech32m native segwit
# test vectors from BIP-0173 # test vectors from BIP-0173
# note: the ones that are commented out have been invalidated by BIP-0350 # 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('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx'), '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6')
# self.assertEqual(address_to_script('BC1SW50QA3JX3S'), '6002751e') # self.assertEqual(address_to_script('BC1SW50QA3JX3S'), '6002751e')
# self.assertEqual(address_to_script('bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'), '5210751e76e8199196d454941c45d1b3a323') # self.assertEqual(address_to_script('bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj'), '5210751e76e8199196d454941c45d1b3a323')
# bech32/bech32m native segwit # bech32/bech32m native segwit
# test vectors from BIP-0350 # test vectors from BIP-0350
self.assertEqual(address_to_script('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y'), '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6') self.assertEqual(address_to_script('bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y').hex(), '5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6')
self.assertEqual(address_to_script('BC1SW50QGDZ25J'), '6002751e') self.assertEqual(address_to_script('BC1SW50QGDZ25J').hex(), '6002751e')
self.assertEqual(address_to_script('bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs'), '5210751e76e8199196d454941c45d1b3a323') self.assertEqual(address_to_script('bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs').hex(), '5210751e76e8199196d454941c45d1b3a323')
self.assertEqual(address_to_script('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0'), '512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798') self.assertEqual(address_to_script('bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqzk5jj0').hex(), '512079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798')
# invalid addresses (from BIP-0173) # invalid addresses (from BIP-0173)
self.assertFalse(is_address('tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty')) self.assertFalse(is_address('tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty'))
@ -570,12 +550,12 @@ class Test_bitcoin(ElectrumTestCase):
self.assertFalse(is_address('bc1gmk9yu')) self.assertFalse(is_address('bc1gmk9yu'))
# base58 P2PKH # base58 P2PKH
self.assertEqual(address_to_script('14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG'), '76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac') self.assertEqual(address_to_script('14gcRovpkCoGkCNBivQBvw7eso7eiNAbxG').hex(), '76a91428662c67561b95c79d2257d2a93d9d151c977e9188ac')
self.assertEqual(address_to_script('1BEqfzh4Y3zzLosfGhw1AsqbEKVW6e1qHv'), '76a914704f4b81cadb7bf7e68c08cd3657220f680f863c88ac') self.assertEqual(address_to_script('1BEqfzh4Y3zzLosfGhw1AsqbEKVW6e1qHv').hex(), '76a914704f4b81cadb7bf7e68c08cd3657220f680f863c88ac')
# base58 P2SH # base58 P2SH
self.assertEqual(address_to_script('35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT'), 'a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487') self.assertEqual(address_to_script('35ZqQJcBQMZ1rsv8aSuJ2wkC7ohUCQMJbT').hex(), 'a9142a84cf00d47f699ee7bbc1dea5ec1bdecb4ac15487')
self.assertEqual(address_to_script('3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji'), 'a914f47c8954e421031ad04ecd8e7752c9479206b9d387') self.assertEqual(address_to_script('3PyjzJ3im7f7bcV724GR57edKDqoZvH7Ji').hex(), 'a914f47c8954e421031ad04ecd8e7752c9479206b9d387')
def test_address_to_payload(self): def test_address_to_payload(self):
# bech32 P2WPKH # bech32 P2WPKH
@ -698,14 +678,14 @@ class Test_bitcoin_testnet(ElectrumTestCase):
def test_address_to_script(self): def test_address_to_script(self):
# bech32/bech32m native segwit # bech32/bech32m native segwit
# test vectors from BIP-0173 # test vectors from BIP-0173
self.assertEqual(address_to_script('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7'), '00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262') self.assertEqual(address_to_script('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7').hex(), '00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262')
self.assertEqual(address_to_script('tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy'), '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433') self.assertEqual(address_to_script('tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy').hex(), '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433')
# bech32/bech32m native segwit # bech32/bech32m native segwit
# test vectors from BIP-0350 # test vectors from BIP-0350
self.assertEqual(address_to_script('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7'), '00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262') self.assertEqual(address_to_script('tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7').hex(), '00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262')
self.assertEqual(address_to_script('tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy'), '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433') self.assertEqual(address_to_script('tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy').hex(), '0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433')
self.assertEqual(address_to_script('tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c'), '5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433') self.assertEqual(address_to_script('tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c').hex(), '5120000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433')
# invalid addresses (from BIP-0173) # invalid addresses (from BIP-0173)
self.assertFalse(is_address('tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty')) self.assertFalse(is_address('tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty'))
@ -737,12 +717,12 @@ class Test_bitcoin_testnet(ElectrumTestCase):
self.assertFalse(is_address('bc1gmk9yu')) self.assertFalse(is_address('bc1gmk9yu'))
# base58 P2PKH # base58 P2PKH
self.assertEqual(address_to_script('mutXcGt1CJdkRvXuN2xoz2quAAQYQ59bRX'), '76a9149da64e300c5e4eb4aaffc9c2fd465348d5618ad488ac') self.assertEqual(address_to_script('mutXcGt1CJdkRvXuN2xoz2quAAQYQ59bRX').hex(), '76a9149da64e300c5e4eb4aaffc9c2fd465348d5618ad488ac')
self.assertEqual(address_to_script('miqtaRTkU3U8rzwKbEHx3g8FSz8GJtPS3K'), '76a914247d2d5b6334bdfa2038e85b20fc15264f8e5d2788ac') self.assertEqual(address_to_script('miqtaRTkU3U8rzwKbEHx3g8FSz8GJtPS3K').hex(), '76a914247d2d5b6334bdfa2038e85b20fc15264f8e5d2788ac')
# base58 P2SH # base58 P2SH
self.assertEqual(address_to_script('2N3LSvr3hv5EVdfcrxg2Yzecf3SRvqyBE4p'), 'a9146eae23d8c4a941316017946fc761a7a6c85561fb87') self.assertEqual(address_to_script('2N3LSvr3hv5EVdfcrxg2Yzecf3SRvqyBE4p').hex(), 'a9146eae23d8c4a941316017946fc761a7a6c85561fb87')
self.assertEqual(address_to_script('2NE4ZdmxFmUgwu5wtfoN2gVniyMgRDYq1kk'), 'a914e4567743d378957cd2ee7072da74b1203c1a7a0b87') self.assertEqual(address_to_script('2NE4ZdmxFmUgwu5wtfoN2gVniyMgRDYq1kk').hex(), 'a914e4567743d378957cd2ee7072da74b1203c1a7a0b87')
class Test_xprv_xpub(ElectrumTestCase): class Test_xprv_xpub(ElectrumTestCase):

2
tests/test_lnpeer.py

@ -1208,7 +1208,7 @@ class TestPeerDirect(TestPeer):
# shutdown script # shutdown script
bob_uss_pub = lnutil.privkey_to_pubkey(os.urandom(32)) bob_uss_pub = lnutil.privkey_to_pubkey(os.urandom(32))
bob_uss_addr = bitcoin.pubkey_to_address('p2wpkh', bob_uss_pub.hex()) 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 # bob commits to close to bob_uss
alice_channel.config[HTLCOwner.REMOTE].upfront_shutdown_script = bob_uss alice_channel.config[HTLCOwner.REMOTE].upfront_shutdown_script = bob_uss

16
tests/test_lnutil.py

@ -508,7 +508,7 @@ class TestLNUtil(ElectrumTestCase):
htlc_payment_preimage[4] = b"\x04" * 32 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]) 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" output_commit_tx = "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220"
htlc_obj = {} htlc_obj = {}
@ -594,7 +594,7 @@ class TestLNUtil(ElectrumTestCase):
our_htlc_tx_witness = make_htlc_tx_witness( our_htlc_tx_witness = make_htlc_tx_witness(
remotehtlcsig=bfh(remote_htlc_sig) + b"\x01", # 0x01 is SIGHASH_ALL 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 payment_preimage=htlc_payment_preimage if success else b'', # will put 00 on witness if timeout
witness_script=htlc) witness_script=htlc)
our_htlc_tx._inputs[0].witness = our_htlc_tx_witness our_htlc_tx._inputs[0].witness = our_htlc_tx_witness
@ -604,7 +604,7 @@ class TestLNUtil(ElectrumTestCase):
to_local_msat= 6988000000 to_local_msat= 6988000000
to_remote_msat= 3000000000 to_remote_msat= 3000000000
local_feerate_per_kw= 9651181 local_feerate_per_kw= 9651181
remote_signature = "3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e" remote_signature = bfh("3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e")
output_commit_tx= "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" output_commit_tx= "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220"
our_commit_tx = make_commitment( our_commit_tx = make_commitment(
@ -633,7 +633,7 @@ class TestLNUtil(ElectrumTestCase):
to_local_msat= 6988000000 to_local_msat= 6988000000
to_remote_msat= 3000000000 to_remote_msat= 3000000000
local_feerate_per_kw= 9651936 local_feerate_per_kw= 9651936
remote_signature = "3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e" remote_signature = bfh("3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e")
output_commit_tx= "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220" output_commit_tx= "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220"
our_commit_tx = make_commitment( our_commit_tx = make_commitment(
@ -701,7 +701,7 @@ class TestLNUtil(ElectrumTestCase):
# actual commitment transaction fee = 10860 # actual commitment transaction fee = 10860
# to_local amount 6989140 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac # to_local amount 6989140 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac
# to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b) # to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b)
remote_signature = "3045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c0" remote_signature = bfh("3045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c0")
# local_signature = 3044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c3836939 # local_signature = 3044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c3836939
our_commit_tx = make_commitment( our_commit_tx = make_commitment(
ctn=commitment_number, 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): def sign_and_insert_remote_sig(self, tx: PartialTransaction, remote_pubkey, remote_signature, pubkey, privkey):
assert type(remote_pubkey) is bytes assert type(remote_pubkey) is bytes
assert len(remote_pubkey) == 33 assert len(remote_pubkey) == 33
assert type(remote_signature) is str assert type(remote_signature) is bytes
assert type(pubkey) is bytes assert type(pubkey) is bytes
assert type(privkey) is bytes assert type(privkey) is bytes
assert len(pubkey) == 33 assert len(pubkey) == 33
assert len(privkey) == 33 assert len(privkey) == 33
tx.sign({pubkey.hex(): (privkey[:-1], True)}) tx.sign({pubkey.hex(): (privkey[:-1], True)})
sighash = Sighash.to_sigbytes(Sighash.ALL).hex() sighash = Sighash.to_sigbytes(Sighash.ALL)
tx.add_signature_to_txin(txin_idx=0, signing_pubkey=remote_pubkey.hex(), sig=remote_signature + sighash) tx.add_signature_to_txin(txin_idx=0, signing_pubkey=remote_pubkey, sig=remote_signature + sighash)
def test_get_compressed_pubkey_from_bech32(self): 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', self.assertEqual(b'\x03\x84\xef\x87\xd9d\xa2\xaaa7=\xff\xb8\xfe=t8[}>;\n\x13\xa8e\x8eo:\xf5Mi\xb5H',

34
tests/test_transaction.py

@ -18,7 +18,7 @@ signed_blob = '01000000012a5c9a94fcde98f5581cd00162c60a13936ceb75389ea65bf38633b
v2_blob = "0200000001191601a44a81e061502b7bfbc6eaa1cef6d1e6af5308ef96c9342f71dbf4b9b5000000006b483045022100a6d44d0a651790a477e75334adfb8aae94d6612d01187b2c02526e340a7fd6c8022028bdf7a64a54906b13b145cd5dab21a26bd4b85d6044e9b97bceab5be44c2a9201210253e8e0254b0c95776786e40984c1aa32a7d03efa6bdacdea5f421b774917d346feffffff026b20fa04000000001976a914024db2e87dd7cfd0e5f266c5f212e21a31d805a588aca0860100000000001976a91421919b94ae5cefcdf0271191459157cdb41c4cbf88aca6240700" v2_blob = "0200000001191601a44a81e061502b7bfbc6eaa1cef6d1e6af5308ef96c9342f71dbf4b9b5000000006b483045022100a6d44d0a651790a477e75334adfb8aae94d6612d01187b2c02526e340a7fd6c8022028bdf7a64a54906b13b145cd5dab21a26bd4b85d6044e9b97bceab5be44c2a9201210253e8e0254b0c95776786e40984c1aa32a7d03efa6bdacdea5f421b774917d346feffffff026b20fa04000000001976a914024db2e87dd7cfd0e5f266c5f212e21a31d805a588aca0860100000000001976a91421919b94ae5cefcdf0271191459157cdb41c4cbf88aca6240700"
signed_segwit_blob = "01000000000101b66d722484f2db63e827ebf41d02684fed0c6550e85015a6c9d41ef216a8a6f00000000000fdffffff0280c3c90100000000160014b65ce60857f7e7892b983851c2a8e3526d09e4ab64bac30400000000160014c478ebbc0ab2097706a98e10db7cf101839931c4024730440220789c7d47f876638c58d98733c30ae9821c8fa82b470285dcdf6db5994210bf9f02204163418bbc44af701212ad42d884cc613f3d3d831d2d0cc886f767cca6e0235e012103083a6dc250816d771faa60737bfe78b23ad619f6b458e0a1f1688e3a0605e79c00000000" signed_segwit_blob = "01000000000101b66d722484f2db63e827ebf41d02684fed0c6550e85015a6c9d41ef216a8a6f00000000000fdffffff0280c3c90100000000160014b65ce60857f7e7892b983851c2a8e3526d09e4ab64bac30400000000160014c478ebbc0ab2097706a98e10db7cf101839931c4024730440220789c7d47f876638c58d98733c30ae9821c8fa82b470285dcdf6db5994210bf9f02204163418bbc44af701212ad42d884cc613f3d3d831d2d0cc886f767cca6e0235e012103083a6dc250816d771faa60737bfe78b23ad619f6b458e0a1f1688e3a0605e79c00000000"
signed_blob_signatures = ['3046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d98501',] signed_blob_signatures = [bfh('3046022100a82bbc57a0136751e5433f41cf000b3f1a99c6744775e76ec764fb78c54ee100022100f9e80b7de89de861dc6fb0c1429d5da72c2b6b2ee2406bc9bfb1beedd729d98501'),]
class TestBCDataStream(ElectrumTestCase): class TestBCDataStream(ElectrumTestCase):
@ -81,11 +81,11 @@ class TestBCDataStream(ElectrumTestCase):
class TestTransaction(ElectrumTestCase): class TestTransaction(ElectrumTestCase):
def test_match_against_script_template(self): 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)) 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)) 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)) self.assertFalse(match_script_against_template(script, SCRIPTPUBKEY_TEMPLATE_ANYSEGWIT))
def test_tx_update_signatures(self): def test_tx_update_signatures(self):
@ -875,9 +875,9 @@ class TestTransactionTestnet(ElectrumTestCase):
prevout = TxOutpoint(txid=bfh('6d500966f9e494b38a04545f0cea35fc7b3944e341a64b804fed71cdee11d434'), out_idx=1) prevout = TxOutpoint(txid=bfh('6d500966f9e494b38a04545f0cea35fc7b3944e341a64b804fed71cdee11d434'), out_idx=1)
txin = PartialTxInput(prevout=prevout) txin = PartialTxInput(prevout=prevout)
txin.nsequence = 2 ** 32 - 3 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, locktime, opcodes.OP_CHECKLOCKTIMEVERIFY, opcodes.OP_DROP, pubkey, opcodes.OP_CHECKSIG,
])) ])
txin.redeem_script = redeem_script txin.redeem_script = redeem_script
# Build the Transaction Output # Build the Transaction Output
@ -887,7 +887,7 @@ class TestTransactionTestnet(ElectrumTestCase):
# Build and sign the transaction # Build and sign the transaction
tx = PartialTransaction.from_io([txin], [txout], locktime=locktime, version=1) tx = PartialTransaction.from_io([txin], [txout], locktime=locktime, version=1)
sig = tx.sign_txin(0, privkey) 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), # note: in testnet3 chain, signature differs (no low-R grinding),
# so txid there is: a8110bbdd40d65351f615897d98c33cbe33e4ebedb4ba2fc9e8c644423dadc93 # so txid there is: a8110bbdd40d65351f615897d98c33cbe33e4ebedb4ba2fc9e8c644423dadc93
@ -903,10 +903,10 @@ class TestTransactionTestnet(ElectrumTestCase):
# Build the Transaction Input # Build the Transaction Input
_, privkey, compressed = deserialize_privkey(wif) _, privkey, compressed = deserialize_privkey(wif)
pubkey = ECPrivkey(privkey).get_public_key_hex(compressed=compressed) 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, 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) self.assertEqual("tb1q9dn6qke9924xe3zmptmhrdge0s043pjxpjndypgnu2t9fvsd4crs2qjuer", from_addr)
prevout = TxOutpoint(txid=bfh('8680971efd5203025cffe746f8598d0a704fae81f236ffe009c2609ec673d59a'), out_idx=0) prevout = TxOutpoint(txid=bfh('8680971efd5203025cffe746f8598d0a704fae81f236ffe009c2609ec673d59a'), out_idx=0)
txin = PartialTxInput(prevout=prevout) txin = PartialTxInput(prevout=prevout)
@ -922,7 +922,7 @@ class TestTransactionTestnet(ElectrumTestCase):
# Build and sign the transaction # Build and sign the transaction
tx = PartialTransaction.from_io([txin], [txout], locktime=locktime, version=2) tx = PartialTransaction.from_io([txin], [txout], locktime=locktime, version=2)
sig = tx.sign_txin(0, privkey) sig = tx.sign_txin(0, privkey)
txin.witness = bfh(construct_witness([sig, witness_script])) txin.witness = construct_witness([sig, witness_script])
self.assertEqual('1cdb274755b144090c7134b6459e8d4cb6b4552fe620102836d751e8389b2694', self.assertEqual('1cdb274755b144090c7134b6459e8d4cb6b4552fe620102836d751e8389b2694',
tx.txid()) 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) 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) sig = tx.sign_txin(0,privkey)
self.assertEqual('304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01', self.assertEqual('304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01',
sig) sig.hex())
def test_check_sighash_types_sighash_none(self): def test_check_sighash_types_sighash_none(self):
self.txin.sighash=Sighash.NONE 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) 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) sig = tx.sign_txin(0,privkey)
self.assertEqual('3044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502', self.assertEqual('3044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502',
sig) sig.hex())
def test_check_sighash_types_sighash_single(self): def test_check_sighash_types_sighash_single(self):
self.txin.sighash=Sighash.SINGLE 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) 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) sig = tx.sign_txin(0,privkey)
self.assertEqual('3044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403', self.assertEqual('3044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403',
sig) sig.hex())
@disable_ecdsa_r_value_grinding @disable_ecdsa_r_value_grinding
def test_check_sighash_types_sighash_all_anyonecanpay(self): 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) 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) sig = tx.sign_txin(0,privkey)
self.assertEqual('3045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381', self.assertEqual('3045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381',
sig) sig.hex())
@disable_ecdsa_r_value_grinding @disable_ecdsa_r_value_grinding
def test_check_sighash_types_sighash_none_anyonecanpay(self): 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) 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) sig = tx.sign_txin(0,privkey)
self.assertEqual('3045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a0882', self.assertEqual('3045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a0882',
sig) sig.hex())
def test_check_sighash_types_sighash_single_anyonecanpay(self): def test_check_sighash_types_sighash_single_anyonecanpay(self):
self.txin.sighash=Sighash.SINGLE|Sighash.ANYONECANPAY 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) 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) sig = tx.sign_txin(0,privkey)
self.assertEqual('30440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783', self.assertEqual('30440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783',
sig) sig.hex())

Loading…
Cancel
Save