Browse Source

Merge pull request #9031 from SomberNight/202404_bytes_vs_hex

bitcoin.py/transaction.py: API changes: rm most hex usage
master
ThomasV 2 years ago committed by GitHub
parent
commit
e29a1714ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      electrum/address_synchronizer.py
  2. 6
      electrum/bip32.py
  3. 145
      electrum/bitcoin.py
  4. 35
      electrum/blockchain.py
  5. 12
      electrum/commands.py
  6. 2
      electrum/constants.py
  7. 30
      electrum/descriptor.py
  8. 2
      electrum/ecc.py
  9. 22
      electrum/gui/qt/main_window.py
  10. 4
      electrum/gui/qt/send_tab.py
  11. 6
      electrum/gui/qt/transaction_dialog.py
  12. 2
      electrum/gui/text.py
  13. 14
      electrum/keystore.py
  14. 34
      electrum/lnchannel.py
  15. 20
      electrum/lnpeer.py
  16. 34
      electrum/lnsweep.py
  17. 42
      electrum/lnutil.py
  18. 2
      electrum/lnworker.py
  19. 12
      electrum/payment_identifier.py
  20. 4
      electrum/paymentrequest.py
  21. 4
      electrum/plugins/bitbox02/bitbox02.py
  22. 2
      electrum/plugins/coldcard/coldcard.py
  23. 6
      electrum/plugins/digitalbitbox/digitalbitbox.py
  24. 9
      electrum/plugins/jade/jade.py
  25. 4
      electrum/plugins/keepkey/keepkey.py
  26. 33
      electrum/plugins/ledger/ledger.py
  27. 4
      electrum/plugins/safe_t/safe_t.py
  28. 4
      electrum/plugins/trezor/trezor.py
  29. 2
      electrum/scripts/peers.py
  30. 27
      electrum/submarine_swaps.py
  31. 206
      electrum/transaction.py
  32. 34
      electrum/wallet.py
  33. 4
      electrum/wallet_db.py
  34. 152
      tests/test_bitcoin.py
  35. 2
      tests/test_lnpeer.py
  36. 20
      tests/test_lnutil.py
  37. 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()):
v = txo.value
ser = tx_hash + ':%d'%n
scripthash = bitcoin.script_to_scripthash(txo.scriptpubkey.hex())
scripthash = bitcoin.script_to_scripthash(txo.scriptpubkey)
self.db.add_prevout_by_scripthash(scripthash, prevout=TxOutpoint.from_str(ser), value=v)
addr = txo.address
if addr and self.is_mine(addr):
@ -407,7 +407,7 @@ class AddressSynchronizer(Logger, EventListener):
self.unconfirmed_tx.pop(tx_hash, None)
if tx:
for idx, txo in enumerate(tx.outputs()):
scripthash = bitcoin.script_to_scripthash(txo.scriptpubkey.hex())
scripthash = bitcoin.script_to_scripthash(txo.scriptpubkey)
prevout = TxOutpoint(bfh(tx_hash), idx)
self.db.remove_prevout_by_scripthash(scripthash, prevout=prevout, value=txo.value)
util.trigger_callback('adb_removed_tx', self, tx_hash, tx)

6
electrum/bip32.py

@ -11,7 +11,7 @@ from .util import bfh, BitcoinException
from . import constants
from . import ecc
from .crypto import hash_160, hmac_oneshot
from .bitcoin import rev_hex, int_to_hex, EncodeBase58Check, DecodeBase58Check
from .bitcoin import EncodeBase58Check, DecodeBase58Check
from .logging import get_logger
@ -49,7 +49,7 @@ def CKD_priv(parent_privkey: bytes, parent_chaincode: bytes, child_index: int) -
is_hardened_child = bool(child_index & BIP32_PRIME)
return _CKD_priv(parent_privkey=parent_privkey,
parent_chaincode=parent_chaincode,
child_index=bfh(rev_hex(int_to_hex(child_index, 4))),
child_index=int.to_bytes(child_index, length=4, byteorder="big", signed=False),
is_hardened_child=is_hardened_child)
@ -85,7 +85,7 @@ def CKD_pub(parent_pubkey: bytes, parent_chaincode: bytes, child_index: int) ->
if child_index & BIP32_PRIME: raise Exception('not possible to derive hardened child from parent pubkey')
return _CKD_pub(parent_pubkey=parent_pubkey,
parent_chaincode=parent_chaincode,
child_index=bfh(rev_hex(int_to_hex(child_index, 4))))
child_index=int.to_bytes(child_index, length=4, byteorder="big", signed=False))
# helper function, callable with arbitrary 'child_index' byte-string.

145
electrum/bitcoin.py

@ -197,34 +197,14 @@ class opcodes(IntEnum):
return bytes([self]).hex()
def rev_hex(s: str) -> str:
return bfh(s)[::-1].hex()
def int_to_hex(i: int, length: int=1) -> str:
"""Converts int to little-endian hex string.
`length` is the number of bytes available
"""
if not isinstance(i, int):
raise TypeError('{} instead of int'.format(i))
range_size = pow(256, length)
if i < -(range_size//2) or i >= range_size:
raise OverflowError('cannot convert int {} to hex ({} bytes)'.format(i, length))
if i < 0:
# two's complement
i = range_size + i
s = hex(i)[2:].rstrip('L')
s = "0"*(2*length - len(s)) + s
return rev_hex(s)
def script_num_to_hex(i: int) -> str:
def script_num_to_bytes(i: int) -> bytes:
"""See CScriptNum in Bitcoin Core.
Encodes an integer as hex, to be used in script.
Encodes an integer as bytes, to be used in script.
ported from https://github.com/bitcoin/bitcoin/blob/8cbc5c4be4be22aca228074f087a374a7ec38be8/src/script/script.h#L326
"""
if i == 0:
return ''
return b""
result = bytearray()
neg = i < 0
@ -238,104 +218,102 @@ def script_num_to_hex(i: int) -> str:
elif neg:
result[-1] |= 0x80
return result.hex()
return bytes(result)
def var_int(i: int) -> str:
def var_int(i: int) -> bytes:
# https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer
# https://github.com/bitcoin/bitcoin/blob/efe1ee0d8d7f82150789f1f6840f139289628a2b/src/serialize.h#L247
# "CompactSize"
assert i >= 0, i
if i<0xfd:
return int_to_hex(i)
elif i<=0xffff:
return "fd"+int_to_hex(i,2)
elif i<=0xffffffff:
return "fe"+int_to_hex(i,4)
if i < 0xfd:
return int.to_bytes(i, length=1, byteorder="little", signed=False)
elif i <= 0xffff:
return b"\xfd" + int.to_bytes(i, length=2, byteorder="little", signed=False)
elif i <= 0xffffffff:
return b"\xfe" + int.to_bytes(i, length=4, byteorder="little", signed=False)
else:
return "ff"+int_to_hex(i,8)
return b"\xff" + int.to_bytes(i, length=8, byteorder="little", signed=False)
def witness_push(item: str) -> str:
"""Returns data in the form it should be present in the witness.
hex -> hex
"""
return var_int(len(item) // 2) + item
def witness_push(item: bytes) -> bytes:
"""Returns data in the form it should be present in the witness."""
return var_int(len(item)) + item
def _op_push(i: int) -> str:
def _op_push(i: int) -> bytes:
if i < opcodes.OP_PUSHDATA1:
return int_to_hex(i)
return int.to_bytes(i, length=1, byteorder="little", signed=False)
elif i <= 0xff:
return opcodes.OP_PUSHDATA1.hex() + int_to_hex(i, 1)
return bytes([opcodes.OP_PUSHDATA1]) + int.to_bytes(i, length=1, byteorder="little", signed=False)
elif i <= 0xffff:
return opcodes.OP_PUSHDATA2.hex() + int_to_hex(i, 2)
return bytes([opcodes.OP_PUSHDATA2]) + int.to_bytes(i, length=2, byteorder="little", signed=False)
else:
return opcodes.OP_PUSHDATA4.hex() + int_to_hex(i, 4)
return bytes([opcodes.OP_PUSHDATA4]) + int.to_bytes(i, length=4, byteorder="little", signed=False)
def push_script(data: str) -> str:
def push_script(data: bytes) -> bytes:
"""Returns pushed data to the script, automatically
choosing canonical opcodes depending on the length of the data.
hex -> hex
ported from https://github.com/btcsuite/btcd/blob/fdc2bc867bda6b351191b5872d2da8270df00d13/txscript/scriptbuilder.go#L128
"""
data = bfh(data)
data_len = len(data)
# "small integer" opcodes
if data_len == 0 or data_len == 1 and data[0] == 0:
return opcodes.OP_0.hex()
return bytes([opcodes.OP_0])
elif data_len == 1 and data[0] <= 16:
return bytes([opcodes.OP_1 - 1 + data[0]]).hex()
return bytes([opcodes.OP_1 - 1 + data[0]])
elif data_len == 1 and data[0] == 0x81:
return opcodes.OP_1NEGATE.hex()
return bytes([opcodes.OP_1NEGATE])
return _op_push(data_len) + data.hex()
return _op_push(data_len) + data
def make_op_return(x:bytes) -> bytes:
return bytes([opcodes.OP_RETURN]) + bytes.fromhex(push_script(x.hex()))
def make_op_return(x: bytes) -> bytes:
return bytes([opcodes.OP_RETURN]) + push_script(x)
def add_number_to_script(i: int) -> bytes:
return bfh(push_script(script_num_to_hex(i)))
return push_script(script_num_to_bytes(i))
def construct_witness(items: Sequence[Union[str, int, bytes]]) -> str:
def construct_witness(items: Sequence[Union[str, int, bytes]]) -> bytes:
"""Constructs a witness from the given stack items."""
witness = var_int(len(items))
witness = bytearray()
witness += var_int(len(items))
for item in items:
if type(item) is int:
item = script_num_to_hex(item)
item = script_num_to_bytes(item)
elif isinstance(item, (bytes, bytearray)):
item = item.hex()
pass # use as-is
else:
assert is_hex_str(item)
assert is_hex_str(item), repr(item)
item = bfh(item)
witness += witness_push(item)
return witness
return bytes(witness)
def construct_script(items: Sequence[Union[str, int, bytes, opcodes]], values=None) -> str:
def construct_script(items: Sequence[Union[str, int, bytes, opcodes]], values=None) -> bytes:
"""Constructs bitcoin script from given items."""
script = ''
script = bytearray()
values = values or {}
for i, item in enumerate(items):
if i in values:
item = values[i]
if isinstance(item, opcodes):
script += item.hex()
script += bytes([item])
elif type(item) is int:
script += add_number_to_script(item).hex()
script += add_number_to_script(item)
elif isinstance(item, (bytes, bytearray)):
script += push_script(item.hex())
script += push_script(item)
elif isinstance(item, str):
assert is_hex_str(item)
script += push_script(item)
script += push_script(bfh(item))
else:
raise Exception(f'unexpected item for script: {item!r}')
return script
return bytes(script)
def relayfee(network: 'Network' = None) -> int:
@ -412,15 +390,11 @@ def hash_to_segwit_addr(h: bytes, witver: int, *, net=None) -> str:
def public_key_to_p2wpkh(public_key: bytes, *, net=None) -> str:
return hash_to_segwit_addr(hash_160(public_key), witver=0, net=net)
def script_to_p2wsh(script: str, *, net=None) -> str:
return hash_to_segwit_addr(sha256(bfh(script)), witver=0, net=net)
def p2wpkh_nested_script(pubkey: str) -> str:
pkh = hash_160(bfh(pubkey))
return construct_script([0, pkh])
def script_to_p2wsh(script: bytes, *, net=None) -> str:
return hash_to_segwit_addr(sha256(script), witver=0, net=net)
def p2wsh_nested_script(witness_script: str) -> str:
wsh = sha256(bfh(witness_script))
def p2wsh_nested_script(witness_script: bytes) -> bytes:
wsh = sha256(witness_script)
return construct_script([0, wsh])
def pubkey_to_address(txin_type: str, pubkey: str, *, net=None) -> str:
@ -430,27 +404,28 @@ def pubkey_to_address(txin_type: str, pubkey: str, *, net=None) -> str:
# TODO this method is confusingly named
def redeem_script_to_address(txin_type: str, scriptcode: str, *, net=None) -> str:
def redeem_script_to_address(txin_type: str, scriptcode: bytes, *, net=None) -> str:
assert isinstance(scriptcode, bytes)
if txin_type == 'p2sh':
# given scriptcode is a redeem_script
return hash160_to_p2sh(hash_160(bfh(scriptcode)), net=net)
return hash160_to_p2sh(hash_160(scriptcode), net=net)
elif txin_type == 'p2wsh':
# given scriptcode is a witness_script
return script_to_p2wsh(scriptcode, net=net)
elif txin_type == 'p2wsh-p2sh':
# given scriptcode is a witness_script
redeem_script = p2wsh_nested_script(scriptcode)
return hash160_to_p2sh(hash_160(bfh(redeem_script)), net=net)
return hash160_to_p2sh(hash_160(redeem_script), net=net)
else:
raise NotImplementedError(txin_type)
def script_to_address(script: str, *, net=None) -> Optional[str]:
def script_to_address(script: bytes, *, net=None) -> Optional[str]:
from .transaction import get_address_from_output_script
return get_address_from_output_script(bfh(script), net=net)
return get_address_from_output_script(script, net=net)
def address_to_script(addr: str, *, net=None) -> str:
def address_to_script(addr: str, *, net=None) -> bytes:
if net is None: net = constants.net
if not is_address(addr, net=net):
raise BitcoinException(f"invalid bitcoin address: {addr}")
@ -461,7 +436,7 @@ def address_to_script(addr: str, *, net=None) -> str:
return construct_script([witver, bytes(witprog)])
addrtype, hash_160_ = b58_address_to_hash160(addr)
if addrtype == net.ADDRTYPE_P2PKH:
script = pubkeyhash_to_p2pkh_script(hash_160_.hex())
script = pubkeyhash_to_p2pkh_script(hash_160_)
elif addrtype == net.ADDRTYPE_P2SH:
script = construct_script([opcodes.OP_HASH160, hash_160_, opcodes.OP_EQUAL])
else:
@ -514,14 +489,12 @@ def address_to_scripthash(addr: str, *, net=None) -> str:
return script_to_scripthash(script)
def script_to_scripthash(script: str) -> str:
h = sha256(bfh(script))[0:32]
def script_to_scripthash(script: bytes) -> str:
h = sha256(script)
return h[::-1].hex()
def public_key_to_p2pk_script(pubkey: str) -> str:
return construct_script([pubkey, opcodes.OP_CHECKSIG])
def pubkeyhash_to_p2pkh_script(pubkey_hash160: str) -> str:
def pubkeyhash_to_p2pkh_script(pubkey_hash160: bytes) -> bytes:
return construct_script([
opcodes.OP_DUP,
opcodes.OP_HASH160,
@ -756,7 +729,7 @@ def minikey_to_private_key(text: str) -> bytes:
def _get_dummy_address(purpose: str) -> str:
return redeem_script_to_address('p2wsh', sha256(bytes(purpose, "utf8")).hex())
return redeem_script_to_address('p2wsh', sha256(bytes(purpose, "utf8")))
_dummy_addr_funcs = set()
class DummyAddress:

35
electrum/blockchain.py

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

12
electrum/commands.py

@ -422,9 +422,9 @@ class Commands:
sec = txin_dict.get('privkey')
if sec:
txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)
pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
keypairs[pubkey] = privkey, compressed
desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey, script_type=txin_type)
pubkey = ecc.ECPrivkey(privkey).get_public_key_bytes(compressed=compressed)
keypairs[pubkey] = privkey
desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey.hex(), script_type=txin_type)
txin.script_descriptor = desc
inputs.append(txin)
@ -465,7 +465,7 @@ class Commands:
if address in txins_dict.keys():
for txin in txins_dict[address]:
txin.script_descriptor = desc
tx.sign({pubkey.hex(): (priv2, compressed)})
tx.sign({pubkey: priv2})
return tx.serialize()
@ -494,8 +494,8 @@ class Commands:
"""Create multisig address"""
assert isinstance(pubkeys, list), (type(num), type(pubkeys))
redeem_script = multisig_script(pubkeys, num)
address = bitcoin.hash160_to_p2sh(hash_160(bfh(redeem_script)))
return {'address':address, 'redeemScript':redeem_script}
address = bitcoin.hash160_to_p2sh(hash_160(redeem_script))
return {'address': address, 'redeemScript': redeem_script.hex()}
@command('w')
async def freeze(self, address: str, wallet: Abstract_Wallet = None):

2
electrum/constants.py

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

30
electrum/descriptor.py

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

2
electrum/ecc.py

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

22
electrum/gui/qt/main_window.py

@ -34,7 +34,7 @@ import base64
from functools import partial
import queue
import asyncio
from typing import Optional, TYPE_CHECKING, Sequence, List, Union, Dict, Set
from typing import Optional, TYPE_CHECKING, Sequence, List, Union, Dict, Set, Mapping
import concurrent.futures
from PyQt5.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont, QFontMetrics
@ -1106,7 +1106,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self,
tx: Transaction,
*,
external_keypairs=None,
external_keypairs: Mapping[bytes, bytes] = None,
payment_identifier: PaymentIdentifier = None,
):
show_transaction(tx, parent=self, external_keypairs=external_keypairs, payment_identifier=payment_identifier)
@ -1269,10 +1269,24 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self.send_tab.broadcast_transaction(tx, payment_identifier=payment_identifier)
@protected
def sign_tx(self, tx, *, callback, external_keypairs, password):
def sign_tx(
self,
tx: PartialTransaction,
*,
callback,
external_keypairs: Optional[Mapping[bytes, bytes]],
password,
):
self.sign_tx_with_password(tx, callback=callback, password=password, external_keypairs=external_keypairs)
def sign_tx_with_password(self, tx: PartialTransaction, *, callback, password, external_keypairs=None):
def sign_tx_with_password(
self,
tx: PartialTransaction,
*,
callback,
password,
external_keypairs: Mapping[bytes, bytes] = None,
):
'''Sign the transaction in a separate thread. When done, calls
the callback with a success code of True or False.
'''

4
electrum/gui/qt/send_tab.py

@ -3,7 +3,7 @@
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
from decimal import Decimal
from typing import Optional, TYPE_CHECKING, Sequence, List, Callable, Union
from typing import Optional, TYPE_CHECKING, Sequence, List, Callable, Union, Mapping
from PyQt5.QtCore import pyqtSignal, QPoint, QSize, Qt
from PyQt5.QtWidgets import (QLabel, QVBoxLayout, QGridLayout, QHBoxLayout,
QWidget, QToolTip, QPushButton, QApplication)
@ -295,7 +295,7 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
outputs: List[PartialTxOutput],
*,
nonlocal_only=False,
external_keypairs=None,
external_keypairs: Mapping[bytes, bytes] = None,
get_coins: Callable[..., Sequence[PartialTxInput]] = None,
invoice: Optional[Invoice] = None
) -> None:

6
electrum/gui/qt/transaction_dialog.py

@ -30,7 +30,7 @@ import copy
import datetime
import traceback
import time
from typing import TYPE_CHECKING, Callable, Optional, List, Union, Tuple
from typing import TYPE_CHECKING, Callable, Optional, List, Union, Tuple, Mapping
from functools import partial
from decimal import Decimal
@ -410,7 +410,7 @@ def show_transaction(
*,
parent: 'ElectrumWindow',
prompt_if_unsaved: bool = False,
external_keypairs=None,
external_keypairs: Mapping[bytes, bytes] = None,
payment_identifier: 'PaymentIdentifier' = None,
):
try:
@ -438,7 +438,7 @@ class TxDialog(QDialog, MessageBoxMixin):
*,
parent: 'ElectrumWindow',
prompt_if_unsaved: bool,
external_keypairs=None,
external_keypairs: Mapping[bytes, bytes] = None,
payment_identifier: 'PaymentIdentifier' = None,
):
'''Transactions in the wallet will show their description.

2
electrum/gui/text.py

@ -624,7 +624,7 @@ class ElectrumGui(BaseElectrumGui, EventListener):
return
elif is_address(self.str_recipient):
amount_sat = self.parse_amount(self.str_amount)
scriptpubkey = bytes.fromhex(address_to_script(self.str_recipient))
scriptpubkey = address_to_script(self.str_recipient)
outputs = [PartialTxOutput(scriptpubkey=scriptpubkey, value=amount_sat)]
invoice = self.wallet.create_invoice(
outputs=outputs,

14
electrum/keystore.py

@ -107,13 +107,13 @@ class KeyStore(Logger, ABC):
"""Returns whether the keystore can be encrypted with a password."""
pass
def _get_tx_derivations(self, tx: 'PartialTransaction') -> Dict[str, Union[Sequence[int], str]]:
def _get_tx_derivations(self, tx: 'PartialTransaction') -> Dict[bytes, Union[Sequence[int], str]]:
keypairs = {}
for txin in tx.inputs():
keypairs.update(self._get_txin_derivations(txin))
return keypairs
def _get_txin_derivations(self, txin: 'PartialTxInput') -> Dict[str, Union[Sequence[int], str]]:
def _get_txin_derivations(self, txin: 'PartialTxInput') -> Dict[bytes, Union[Sequence[int], str]]:
if txin.is_complete():
return {}
keypairs = {}
@ -124,7 +124,7 @@ class KeyStore(Logger, ABC):
derivation = self.get_pubkey_derivation(pubkey, txin)
if not derivation:
continue
keypairs[pubkey.hex()] = derivation
keypairs[pubkey] = derivation
return keypairs
def can_sign(self, tx: 'Transaction', *, ignore_watching_only=False) -> bool:
@ -237,9 +237,11 @@ class Software_KeyStore(KeyStore):
# Raise if password is not correct.
self.check_password(password)
# Add private keys
keypairs = self._get_tx_derivations(tx)
for k, v in keypairs.items():
keypairs[k] = self.get_private_key(v, password)
keypairs = {}
pubkey_to_deriv_map = self._get_tx_derivations(tx)
for pubkey, deriv in pubkey_to_deriv_map.items():
privkey, is_compressed = self.get_private_key(deriv, password)
keypairs[pubkey] = privkey
# Sign
if keypairs:
tx.sign(keypairs)

34
electrum/lnchannel.py

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

20
electrum/lnpeer.py

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

34
electrum/lnsweep.py

@ -55,7 +55,7 @@ def create_sweeptxs_for_watchtower(chan: 'Channel', ctx: Transaction, per_commit
# to_local
revocation_pubkey = ecc.ECPrivkey(other_revocation_privkey).get_public_key_bytes(compressed=True)
witness_script = make_commitment_output_to_local_witness_script(
revocation_pubkey, to_self_delay, this_delayed_pubkey).hex()
revocation_pubkey, to_self_delay, this_delayed_pubkey)
to_local_address = redeem_script_to_address('p2wsh', witness_script)
output_idxs = ctx.get_output_idxs_from_address(to_local_address)
if output_idxs:
@ -64,7 +64,7 @@ def create_sweeptxs_for_watchtower(chan: 'Channel', ctx: Transaction, per_commit
sweep_address=sweep_address,
ctx=ctx,
output_idx=output_idx,
witness_script=bfh(witness_script),
witness_script=witness_script,
privkey=other_revocation_privkey,
is_revocation=True,
config=chan.lnworker.config)
@ -122,7 +122,7 @@ def create_sweeptx_for_their_revoked_ctx(
# to_local
revocation_pubkey = ecc.ECPrivkey(other_revocation_privkey).get_public_key_bytes(compressed=True)
witness_script = make_commitment_output_to_local_witness_script(
revocation_pubkey, to_self_delay, this_delayed_pubkey).hex()
revocation_pubkey, to_self_delay, this_delayed_pubkey)
to_local_address = redeem_script_to_address('p2wsh', witness_script)
output_idxs = ctx.get_output_idxs_from_address(to_local_address)
if output_idxs:
@ -131,7 +131,7 @@ def create_sweeptx_for_their_revoked_ctx(
sweep_address=sweep_address,
ctx=ctx,
output_idx=output_idx,
witness_script=bfh(witness_script),
witness_script=witness_script,
privkey=other_revocation_privkey,
is_revocation=True,
config=chan.lnworker.config)
@ -162,7 +162,7 @@ def create_sweeptx_for_their_revoked_htlc(
# same witness script as to_local
revocation_pubkey = ecc.ECPrivkey(other_revocation_privkey).get_public_key_bytes(compressed=True)
witness_script = make_commitment_output_to_local_witness_script(
revocation_pubkey, to_self_delay, this_delayed_pubkey).hex()
revocation_pubkey, to_self_delay, this_delayed_pubkey)
htlc_address = redeem_script_to_address('p2wsh', witness_script)
# check that htlc_tx is a htlc
if htlc_tx.outputs()[0].address != htlc_address:
@ -170,7 +170,7 @@ def create_sweeptx_for_their_revoked_htlc(
gen_tx = lambda: create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx(
sweep_address=sweep_address,
htlc_tx=htlc_tx,
htlctx_witness_script=bfh(witness_script),
htlctx_witness_script=witness_script,
privkey=other_revocation_privkey,
is_revocation=True,
config=chan.lnworker.config)
@ -204,7 +204,7 @@ def create_sweeptxs_for_our_ctx(
per_commitment_point=our_pcp).to_bytes(32, 'big')
our_localdelayed_pubkey = our_localdelayed_privkey.get_public_key_bytes(compressed=True)
to_local_witness_script = make_commitment_output_to_local_witness_script(
their_revocation_pubkey, to_self_delay, our_localdelayed_pubkey).hex()
their_revocation_pubkey, to_self_delay, our_localdelayed_pubkey)
to_local_address = redeem_script_to_address('p2wsh', to_local_witness_script)
to_remote_address = None
# test if this is our_ctx
@ -231,7 +231,7 @@ def create_sweeptxs_for_our_ctx(
sweep_address=sweep_address,
ctx=ctx,
output_idx=output_idx,
witness_script=bfh(to_local_witness_script),
witness_script=to_local_witness_script,
privkey=our_localdelayed_privkey.get_secret_bytes(),
is_revocation=False,
to_self_delay=to_self_delay,
@ -358,7 +358,7 @@ def create_sweeptxs_for_their_ctx(
our_revocation_pubkey = derive_blinded_pubkey(our_conf.revocation_basepoint.pubkey, their_pcp)
their_delayed_pubkey = derive_pubkey(their_conf.delayed_basepoint.pubkey, their_pcp)
witness_script = make_commitment_output_to_local_witness_script(
our_revocation_pubkey, our_conf.to_self_delay, their_delayed_pubkey).hex()
our_revocation_pubkey, our_conf.to_self_delay, their_delayed_pubkey)
to_local_address = redeem_script_to_address('p2wsh', witness_script)
to_remote_address = None
# test if this is their ctx
@ -469,9 +469,9 @@ def create_htlctx_that_spends_from_our_ctx(
ctx_output_idx=ctx_output_idx,
name=f'our_ctx_{ctx_output_idx}_htlc_tx_{htlc.payment_hash.hex()}')
remote_htlc_sig = chan.get_remote_htlc_sig_for_htlc(htlc_relative_idx=htlc_relative_idx)
local_htlc_sig = bfh(htlc_tx.sign_txin(0, local_htlc_privkey))
local_htlc_sig = htlc_tx.sign_txin(0, local_htlc_privkey)
txin = htlc_tx.inputs()[0]
witness_program = bfh(Transaction.get_preimage_script(txin))
witness_program = Transaction.get_preimage_script(txin)
txin.witness = make_htlc_tx_witness(remote_htlc_sig, local_htlc_sig, preimage, witness_program)
return witness_script, htlc_tx
@ -496,13 +496,13 @@ def create_sweeptx_their_ctx_htlc(
if outvalue <= dust_threshold(): return None
sweep_outputs = [PartialTxOutput.from_address_and_value(sweep_address, outvalue)]
tx = PartialTransaction.from_io(sweep_inputs, sweep_outputs, version=2, locktime=cltv_abs)
sig = bfh(tx.sign_txin(0, privkey))
sig = tx.sign_txin(0, privkey)
if not is_revocation:
witness = construct_witness([sig, preimage, witness_script])
else:
revocation_pubkey = privkey_to_pubkey(privkey)
witness = construct_witness([sig, revocation_pubkey, witness_script])
tx.inputs()[0].witness = bfh(witness)
tx.inputs()[0].witness = witness
assert tx.is_complete()
return tx
@ -511,12 +511,12 @@ def create_sweeptx_their_ctx_to_remote(
sweep_address: str, ctx: Transaction, output_idx: int,
our_payment_privkey: ecc.ECPrivkey,
config: SimpleConfig) -> Optional[PartialTransaction]:
our_payment_pubkey = our_payment_privkey.get_public_key_hex(compressed=True)
our_payment_pubkey = our_payment_privkey.get_public_key_bytes(compressed=True)
val = ctx.outputs()[output_idx].value
prevout = TxOutpoint(txid=bfh(ctx.txid()), out_idx=output_idx)
txin = PartialTxInput(prevout=prevout)
txin._trusted_value_sats = val
desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=our_payment_pubkey, script_type='p2wpkh')
desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=our_payment_pubkey.hex(), script_type='p2wpkh')
txin.script_descriptor = desc
sweep_inputs = [txin]
tx_size_bytes = 110 # approx size of p2wpkh->p2wpkh
@ -526,7 +526,7 @@ def create_sweeptx_their_ctx_to_remote(
sweep_outputs = [PartialTxOutput.from_address_and_value(sweep_address, outvalue)]
sweep_tx = PartialTransaction.from_io(sweep_inputs, sweep_outputs)
sweep_tx.set_rbf(True)
sweep_tx.sign({our_payment_pubkey: (our_payment_privkey.get_secret_bytes(), True)})
sweep_tx.sign({our_payment_pubkey: our_payment_privkey.get_secret_bytes()})
if not sweep_tx.is_complete():
raise Exception('channel close sweep tx is not complete')
return sweep_tx
@ -561,7 +561,7 @@ def create_sweeptx_ctx_to_local(
sweep_tx = PartialTransaction.from_io(sweep_inputs, sweep_outputs, version=2)
sig = sweep_tx.sign_txin(0, privkey)
witness = construct_witness([sig, int(is_revocation), witness_script])
sweep_tx.inputs()[0].witness = bfh(witness)
sweep_tx.inputs()[0].witness = witness
return sweep_tx

42
electrum/lnutil.py

@ -24,7 +24,7 @@ from .transaction import (Transaction, PartialTransaction, PartialTxInput, TxOut
from .ecc import CURVE_ORDER, ecdsa_sig64_from_der_sig, ECPubkey, string_to_number
from . import ecc, bitcoin, crypto, transaction
from . import descriptor
from .bitcoin import (push_script, redeem_script_to_address, address_to_script,
from .bitcoin import (redeem_script_to_address, address_to_script,
construct_witness, construct_script)
from . import segwit_addr
from .i18n import _
@ -591,7 +591,9 @@ def derive_blinded_privkey(basepoint_secret: bytes, per_commitment_secret: bytes
return int.to_bytes(sum, length=32, byteorder='big', signed=False)
def make_htlc_tx_output(amount_msat, local_feerate, revocationpubkey, local_delayedpubkey, success, to_self_delay):
def make_htlc_tx_output(
amount_msat, local_feerate, revocationpubkey, local_delayedpubkey, success, to_self_delay,
) -> Tuple[bytes, PartialTxOutput]:
assert type(amount_msat) is int
assert type(local_feerate) is int
script = make_commitment_output_to_local_witness_script(
@ -600,7 +602,7 @@ def make_htlc_tx_output(amount_msat, local_feerate, revocationpubkey, local_dela
delayed_pubkey=local_delayedpubkey,
)
p2wsh = bitcoin.redeem_script_to_address('p2wsh', script.hex())
p2wsh = bitcoin.redeem_script_to_address('p2wsh', script)
weight = HTLC_SUCCESS_WEIGHT if success else HTLC_TIMEOUT_WEIGHT
fee = local_feerate * weight
fee = fee // 1000 * 1000
@ -615,7 +617,7 @@ def make_htlc_tx_witness(remotehtlcsig: bytes, localhtlcsig: bytes,
assert type(localhtlcsig) is bytes
assert type(payment_preimage) is bytes
assert type(witness_script) is bytes
return bfh(construct_witness([0, remotehtlcsig, localhtlcsig, payment_preimage, witness_script]))
return construct_witness([0, remotehtlcsig, localhtlcsig, payment_preimage, witness_script])
def make_htlc_tx_inputs(htlc_output_txid: str, htlc_output_index: int,
amount_msat: int, witness_script: str) -> List[PartialTxInput]:
@ -648,7 +650,7 @@ def make_offered_htlc(
assert type(remote_htlcpubkey) is bytes
assert type(local_htlcpubkey) is bytes
assert type(payment_hash) is bytes
script = bfh(construct_script([
script = construct_script([
opcodes.OP_DUP,
opcodes.OP_HASH160,
bitcoin.hash_160(revocation_pubkey),
@ -675,7 +677,7 @@ def make_offered_htlc(
opcodes.OP_CHECKSIG,
opcodes.OP_ENDIF,
opcodes.OP_ENDIF,
]))
])
return script
def make_received_htlc(
@ -690,7 +692,7 @@ def make_received_htlc(
assert type(i) is bytes
assert type(cltv_abs) is int
script = bfh(construct_script([
script = construct_script([
opcodes.OP_DUP,
opcodes.OP_HASH160,
bitcoin.hash_160(revocation_pubkey),
@ -720,7 +722,7 @@ def make_received_htlc(
opcodes.OP_CHECKSIG,
opcodes.OP_ENDIF,
opcodes.OP_ENDIF,
]))
])
return script
WITNESS_TEMPLATE_OFFERED_HTLC = [
@ -830,7 +832,7 @@ def possible_output_idxs_of_htlc_in_ctx(*, chan: 'Channel', pcp: bytes, subject:
local_htlc_pubkey=htlc_pubkey,
payment_hash=payment_hash,
cltv_abs=cltv_abs)
htlc_address = redeem_script_to_address('p2wsh', preimage_script.hex())
htlc_address = redeem_script_to_address('p2wsh', preimage_script)
candidates = ctx.get_output_idxs_from_address(htlc_address)
return {output_idx for output_idx in candidates
if ctx.outputs()[output_idx].value == htlc.amount_msat // 1000}
@ -938,20 +940,20 @@ LOCAL = HTLCOwner.LOCAL
REMOTE = HTLCOwner.REMOTE
def make_commitment_outputs(*, fees_per_participant: Mapping[HTLCOwner, int], local_amount_msat: int, remote_amount_msat: int,
local_script: str, remote_script: str, htlcs: List[ScriptHtlc], dust_limit_sat: int) -> Tuple[List[PartialTxOutput], List[PartialTxOutput]]:
local_script: bytes, remote_script: bytes, htlcs: List[ScriptHtlc], dust_limit_sat: int) -> Tuple[List[PartialTxOutput], List[PartialTxOutput]]:
# BOLT-03: "Base commitment transaction fees are extracted from the funder's amount;
# if that amount is insufficient, the entire amount of the funder's output is used."
# -> if funder cannot afford feerate, their output might go negative, so take max(0, x) here:
to_local_amt = max(0, local_amount_msat - fees_per_participant[LOCAL])
to_local = PartialTxOutput(scriptpubkey=bfh(local_script), value=to_local_amt // 1000)
to_local = PartialTxOutput(scriptpubkey=local_script, value=to_local_amt // 1000)
to_remote_amt = max(0, remote_amount_msat - fees_per_participant[REMOTE])
to_remote = PartialTxOutput(scriptpubkey=bfh(remote_script), value=to_remote_amt // 1000)
to_remote = PartialTxOutput(scriptpubkey=remote_script, value=to_remote_amt // 1000)
non_htlc_outputs = [to_local, to_remote]
htlc_outputs = []
for script, htlc in htlcs:
addr = bitcoin.redeem_script_to_address('p2wsh', script.hex())
htlc_outputs.append(PartialTxOutput(scriptpubkey=bfh(address_to_script(addr)),
addr = bitcoin.redeem_script_to_address('p2wsh', script)
htlc_outputs.append(PartialTxOutput(scriptpubkey=address_to_script(addr),
value=htlc.amount_msat // 1000))
# trim outputs
@ -1060,7 +1062,7 @@ def make_commitment_output_to_local_witness_script(
assert type(revocation_pubkey) is bytes
assert type(to_self_delay) is int
assert type(delayed_pubkey) is bytes
script = bfh(construct_script([
script = construct_script([
opcodes.OP_IF,
revocation_pubkey,
opcodes.OP_ELSE,
@ -1070,27 +1072,27 @@ def make_commitment_output_to_local_witness_script(
delayed_pubkey,
opcodes.OP_ENDIF,
opcodes.OP_CHECKSIG,
]))
])
return script
def make_commitment_output_to_local_address(
revocation_pubkey: bytes, to_self_delay: int, delayed_pubkey: bytes) -> str:
local_script = make_commitment_output_to_local_witness_script(revocation_pubkey, to_self_delay, delayed_pubkey)
return bitcoin.redeem_script_to_address('p2wsh', local_script.hex())
return bitcoin.redeem_script_to_address('p2wsh', local_script)
def make_commitment_output_to_remote_address(remote_payment_pubkey: bytes) -> str:
return bitcoin.pubkey_to_address('p2wpkh', remote_payment_pubkey.hex())
def sign_and_get_sig_string(tx: PartialTransaction, local_config, remote_config):
tx.sign({local_config.multisig_key.pubkey.hex(): (local_config.multisig_key.privkey, True)})
tx.sign({local_config.multisig_key.pubkey: local_config.multisig_key.privkey})
sig = tx.inputs()[0].part_sigs[local_config.multisig_key.pubkey]
sig_64 = ecdsa_sig64_from_der_sig(sig[:-1])
return sig_64
def funding_output_script(local_config, remote_config) -> str:
def funding_output_script(local_config: 'LocalConfig', remote_config: 'RemoteConfig') -> bytes:
return funding_output_script_from_keys(local_config.multisig_key.pubkey, remote_config.multisig_key.pubkey)
def funding_output_script_from_keys(pubkey1: bytes, pubkey2: bytes) -> str:
def funding_output_script_from_keys(pubkey1: bytes, pubkey2: bytes) -> bytes:
pubkeys = sorted([pubkey1.hex(), pubkey2.hex()])
return transaction.multisig_script(pubkeys, 2)

2
electrum/lnworker.py

@ -58,7 +58,7 @@ from .lnrater import LNRater
from . import lnutil
from .lnutil import funding_output_script
from .lnutil import serialize_htlc_key, deserialize_htlc_key
from .bitcoin import redeem_script_to_address, DummyAddress
from .bitcoin import DummyAddress
from .lnutil import (Outpoint, LNPeerAddr,
get_compressed_pubkey_from_bech32, extract_nodeid,
PaymentFailure, split_host_port, ConnStringFormatError,

12
electrum/payment_identifier.py

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

4
electrum/paymentrequest.py

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

4
electrum/plugins/bitbox02/bitbox02.py

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

2
electrum/plugins/coldcard/coldcard.py

@ -600,7 +600,7 @@ class ColdcardPlugin(HW_PluginBase):
xfp_int = xfp_int_from_xfp_bytes(fp_bytes)
xfp_paths.append([xfp_int] + list(der_full))
script = bfh(wallet.pubkeys_to_scriptcode(pubkey_hexes))
script = wallet.pubkeys_to_scriptcode(pubkey_hexes)
keystore.show_p2sh_address(wallet.m, script, xfp_paths, txin_type)

6
electrum/plugins/digitalbitbox/digitalbitbox.py

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

9
electrum/plugins/jade/jade.py

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

4
electrum/plugins/keepkey/keepkey.py

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

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

4
electrum/plugins/safe_t/safe_t.py

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

4
electrum/plugins/trezor/trezor.py

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

2
electrum/scripts/peers.py

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

27
electrum/submarine_swaps.py

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

206
electrum/transaction.py

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

34
electrum/wallet.py

@ -40,7 +40,7 @@ from functools import partial
from collections import defaultdict
from numbers import Number
from decimal import Decimal
from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequence, Dict, Any, Set, Iterable
from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequence, Dict, Any, Set, Iterable, Mapping
from abc import ABC, abstractmethod
import itertools
import threading
@ -111,14 +111,14 @@ async def _append_utxos_to_inputs(
script_descriptor: 'descriptor.Descriptor',
imax: int,
) -> None:
script = script_descriptor.expand().output_script.hex()
script = script_descriptor.expand().output_script
scripthash = bitcoin.script_to_scripthash(script)
async def append_single_utxo(item):
prev_tx_raw = await network.get_transaction(item['tx_hash'])
prev_tx = Transaction(prev_tx_raw)
prev_txout = prev_tx.outputs()[item['tx_pos']]
if scripthash != bitcoin.script_to_scripthash(prev_txout.scriptpubkey.hex()):
if scripthash != bitcoin.script_to_scripthash(prev_txout.scriptpubkey):
raise Exception('scripthash mismatch when sweeping')
prevout_str = item['tx_hash'] + ':%d' % item['tx_pos']
prevout = TxOutpoint.from_str(prevout_str)
@ -136,20 +136,22 @@ async def _append_utxos_to_inputs(
await group.spawn(append_single_utxo(item))
async def sweep_preparations(privkeys, network: 'Network', imax=100):
async def sweep_preparations(
privkeys: Iterable[str], network: 'Network', imax=100,
) -> Tuple[Sequence[PartialTxInput], Mapping[bytes, bytes]]:
async def find_utxos_for_privkey(txin_type, privkey, compressed):
pubkey = ecc.ECPrivkey(privkey).get_public_key_hex(compressed=compressed)
desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey, script_type=txin_type)
async def find_utxos_for_privkey(txin_type: str, privkey: bytes, compressed: bool):
pubkey = ecc.ECPrivkey(privkey).get_public_key_bytes(compressed=compressed)
desc = descriptor.get_singlesig_descriptor_from_legacy_leaf(pubkey=pubkey.hex(), script_type=txin_type)
await _append_utxos_to_inputs(
inputs=inputs,
network=network,
script_descriptor=desc,
imax=imax)
keypairs[pubkey] = privkey, compressed
keypairs[pubkey] = privkey
inputs = [] # type: List[PartialTxInput]
keypairs = {}
keypairs = {} # type: Dict[bytes, bytes]
async with OldTaskGroup() as group:
for sec in privkeys:
txin_type, privkey, compressed = bitcoin.deserialize_privkey(sec)
@ -169,7 +171,7 @@ async def sweep_preparations(privkeys, network: 'Network', imax=100):
async def sweep(
privkeys,
privkeys: Iterable[str],
*,
network: 'Network',
config: 'SimpleConfig',
@ -182,7 +184,7 @@ async def sweep(
inputs, keypairs = await sweep_preparations(privkeys, network, imax)
total = sum(txin.value_sats() for txin in inputs)
if fee is None:
outputs = [PartialTxOutput(scriptpubkey=bfh(bitcoin.address_to_script(to_address)),
outputs = [PartialTxOutput(scriptpubkey=bitcoin.address_to_script(to_address),
value=total)]
tx = PartialTransaction.from_io(inputs, outputs)
fee = config.estimate_fee(tx.estimated_size())
@ -191,7 +193,7 @@ async def sweep(
if total - fee < dust_threshold(network):
raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, dust_threshold(network)))
outputs = [PartialTxOutput(scriptpubkey=bfh(bitcoin.address_to_script(to_address)),
outputs = [PartialTxOutput(scriptpubkey=bitcoin.address_to_script(to_address),
value=total - fee)]
if locktime is None:
locktime = get_locktime_for_new_transaction(network)
@ -1297,7 +1299,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
conf_needed = None # type: Optional[int]
with self.lock, self.transaction_lock:
for invoice_scriptpubkey, invoice_amt in invoice_amounts.items():
scripthash = bitcoin.script_to_scripthash(invoice_scriptpubkey.hex())
scripthash = bitcoin.script_to_scripthash(invoice_scriptpubkey)
prevouts_and_values = self.db.get_prevouts_by_scripthash(scripthash)
confs_and_values = []
for prevout, v in prevouts_and_values:
@ -2266,12 +2268,12 @@ class Abstract_Wallet(ABC, Logger, EventListener):
delta_total = target_fee - cur_fee
if delta_total <= 0:
break
out_size_total = sum(Transaction.estimated_output_size_for_script(out.scriptpubkey.hex())
out_size_total = sum(Transaction.estimated_output_size_for_script(out.scriptpubkey)
for (idx, out) in s if idx not in del_out_idxs)
if out_size_total == 0: # no outputs left to decrease
raise CannotBumpFee(_('Could not find suitable outputs'))
for idx, out in s:
out_size = Transaction.estimated_output_size_for_script(out.scriptpubkey.hex())
out_size = Transaction.estimated_output_size_for_script(out.scriptpubkey)
delta = int(math.ceil(delta_total * out_size / out_size_total))
if out.value - delta >= self.dust_threshold():
new_output_value = out.value - delta
@ -3804,7 +3806,7 @@ class Multisig_Wallet(Deterministic_Wallet):
redeem_script = self.pubkeys_to_scriptcode(pubkeys)
return bitcoin.redeem_script_to_address(self.txin_type, redeem_script)
def pubkeys_to_scriptcode(self, pubkeys: Sequence[str]) -> str:
def pubkeys_to_scriptcode(self, pubkeys: Sequence[str]) -> bytes:
return transaction.multisig_script(sorted(pubkeys), self.m)
def derive_pubkeys(self, c, i):

4
electrum/wallet_db.py

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

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

2
tests/test_lnpeer.py

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

20
tests/test_lnutil.py

@ -508,7 +508,7 @@ class TestLNUtil(ElectrumTestCase):
htlc_payment_preimage[4] = b"\x04" * 32
htlc[4] = make_received_htlc(**htlc_pubkeys, payment_hash=bitcoin.sha256(htlc_payment_preimage[4]), cltv_abs=htlc_cltv_timeout[4])
remote_signature = "304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b70606"
remote_signature = bfh("304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b70606")
output_commit_tx = "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8007e80300000000000022002052bfef0479d7b293c27e0f1eb294bea154c63a3294ef092c19af51409bce0e2ad007000000000000220020403d394747cae42e98ff01734ad5c08f82ba123d3d9a620abda88989651e2ab5d007000000000000220020748eba944fedc8827f6b06bc44678f93c0f9e6078b35c6331ed31e75f8ce0c2db80b000000000000220020c20b5d1f8584fd90443e7b7b720136174fa4b9333c261d04dbbd012635c0f419a00f0000000000002200208c48d15160397c9731df9bc3b236656efb6665fbfe92b4a6878e88a499f741c4c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de843110e0a06a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e04004730440220275b0c325a5e9355650dc30c0eccfbc7efb23987c24b556b9dfdd40effca18d202206caceb2c067836c51f296740c7ae807ffcbfbf1dd3a0d56b6de9a5b247985f060147304402204fd4928835db1ccdfc40f5c78ce9bd65249b16348df81f0c44328dcdefc97d630220194d3869c38bc732dd87d13d2958015e2fc16829e74cd4377f84d215c0b7060601475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220"
htlc_obj = {}
@ -594,7 +594,7 @@ class TestLNUtil(ElectrumTestCase):
our_htlc_tx_witness = make_htlc_tx_witness(
remotehtlcsig=bfh(remote_htlc_sig) + b"\x01", # 0x01 is SIGHASH_ALL
localhtlcsig=bfh(local_sig),
localhtlcsig=local_sig,
payment_preimage=htlc_payment_preimage if success else b'', # will put 00 on witness if timeout
witness_script=htlc)
our_htlc_tx._inputs[0].witness = our_htlc_tx_witness
@ -604,7 +604,7 @@ class TestLNUtil(ElectrumTestCase):
to_local_msat= 6988000000
to_remote_msat= 3000000000
local_feerate_per_kw= 9651181
remote_signature = "3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e"
remote_signature = bfh("3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e")
output_commit_tx= "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220"
our_commit_tx = make_commitment(
@ -633,7 +633,7 @@ class TestLNUtil(ElectrumTestCase):
to_local_msat= 6988000000
to_remote_msat= 3000000000
local_feerate_per_kw= 9651936
remote_signature = "3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e"
remote_signature = bfh("3044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e")
output_commit_tx= "02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8001c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de8431100400473044022031a82b51bd014915fe68928d1abf4b9885353fb896cac10c3fdd88d7f9c7f2e00220716bda819641d2c63e65d3549b6120112e1aeaf1742eed94a471488e79e206b101473044022064901950be922e62cbe3f2ab93de2b99f37cff9fc473e73e394b27f88ef0731d02206d1dfa227527b4df44a07599289e207d6fd9cca60c0365682dcd3deaf739567e01475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220"
our_commit_tx = make_commitment(
@ -701,7 +701,7 @@ class TestLNUtil(ElectrumTestCase):
# actual commitment transaction fee = 10860
# to_local amount 6989140 wscript 63210212a140cd0c6539d07cd08dfe09984dec3251ea808b892efeac3ede9402bf2b1967029000b2752103fd5960528dc152014952efdb702a88f71e3c1653b2314431701ec77e57fde83c68ac
# to_remote amount 3000000 P2WPKH(0394854aa6eab5b2a8122cc726e9dded053a2184d88256816826d6231c068d4a5b)
remote_signature = "3045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c0"
remote_signature = bfh("3045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c0")
# local_signature = 3044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c3836939
our_commit_tx = make_commitment(
ctn=commitment_number,
@ -725,17 +725,17 @@ class TestLNUtil(ElectrumTestCase):
ref_commit_tx_str = '02000000000101bef67e4e2fb9ddeeb3461973cd4c62abb35050b1add772995b820b584a488489000000000038b02b8002c0c62d0000000000160014ccf1af2f2aabee14bb40fa3851ab2301de84311054a56a00000000002200204adb4e2f00643db396dd120d4e7dc17625f5f2c11a40d857accc862d6b7dd80e0400473044022051b75c73198c6deee1a875871c3961832909acd297c6b908d59e3319e5185a46022055c419379c5051a78d00dbbce11b5b664a0c22815fbcc6fcef6b1937c383693901483045022100f51d2e566a70ba740fc5d8c0f07b9b93d2ed741c3c0860c613173de7d39e7968022041376d520e9c0e1ad52248ddf4b22e12be8763007df977253ef45a4ca3bdb7c001475221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae3e195220'
self.assertEqual(str(our_commit_tx), ref_commit_tx_str)
def sign_and_insert_remote_sig(self, tx: PartialTransaction, remote_pubkey, remote_signature, pubkey, privkey):
def sign_and_insert_remote_sig(self, tx: PartialTransaction, remote_pubkey: bytes, remote_signature: bytes, pubkey: bytes, privkey: bytes):
assert type(remote_pubkey) is bytes
assert len(remote_pubkey) == 33
assert type(remote_signature) is str
assert type(remote_signature) is bytes
assert type(pubkey) is bytes
assert type(privkey) is bytes
assert len(pubkey) == 33
assert len(privkey) == 33
tx.sign({pubkey.hex(): (privkey[:-1], True)})
sighash = Sighash.to_sigbytes(Sighash.ALL).hex()
tx.add_signature_to_txin(txin_idx=0, signing_pubkey=remote_pubkey.hex(), sig=remote_signature + sighash)
tx.sign({pubkey: privkey[:-1]})
sighash = Sighash.to_sigbytes(Sighash.ALL)
tx.add_signature_to_txin(txin_idx=0, signing_pubkey=remote_pubkey, sig=remote_signature + sighash)
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',

34
tests/test_transaction.py

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

Loading…
Cancel
Save