transaction.py: impl taproot key-spends
Add support for key-path-spending taproot utxos into transaction.py. - no wallet support yet - add some psbt, and minimal descriptor support - preliminary work towards script-path spends
This commit is contained in:
+86
-1
@@ -24,7 +24,7 @@
|
||||
# SOFTWARE.
|
||||
|
||||
import hashlib
|
||||
from typing import List, Tuple, TYPE_CHECKING, Optional, Union, Sequence
|
||||
from typing import List, Tuple, TYPE_CHECKING, Optional, Union, Sequence, Any
|
||||
import enum
|
||||
from enum import IntEnum, Enum
|
||||
|
||||
@@ -686,6 +686,14 @@ def is_segwit_address(addr: str, *, net=None) -> bool:
|
||||
return False
|
||||
return witprog is not None
|
||||
|
||||
def is_taproot_address(addr: str, *, net=None) -> bool:
|
||||
if net is None: net = constants.net
|
||||
try:
|
||||
witver, witprog = segwit_addr.decode_segwit_address(net.SEGWIT_HRP, addr)
|
||||
except Exception as e:
|
||||
return False
|
||||
return witver == 1
|
||||
|
||||
def is_b58_address(addr: str, *, net=None) -> bool:
|
||||
if net is None: net = constants.net
|
||||
try:
|
||||
@@ -753,3 +761,80 @@ class DummyAddress:
|
||||
|
||||
|
||||
class DummyAddressUsedInTxException(Exception): pass
|
||||
|
||||
|
||||
def taproot_tweak_pubkey(pubkey32: bytes, h: bytes) -> Tuple[int, bytes]:
|
||||
assert isinstance(pubkey32, bytes), type(pubkey32)
|
||||
assert isinstance(h, bytes), type(h)
|
||||
assert len(pubkey32) == 32, len(pubkey32)
|
||||
int_from_bytes = lambda x: int.from_bytes(x, byteorder="big", signed=False)
|
||||
|
||||
tweak = int_from_bytes(ecc.bip340_tagged_hash(b"TapTweak", pubkey32 + h))
|
||||
if tweak >= ecc.CURVE_ORDER:
|
||||
raise ValueError
|
||||
P = ecc.ECPubkey(b"\x02" + pubkey32)
|
||||
Q = P + (ecc.GENERATOR * tweak)
|
||||
return 0 if Q.has_even_y() else 1, Q.get_public_key_bytes(compressed=True)[1:]
|
||||
|
||||
|
||||
def taproot_tweak_seckey(seckey0: bytes, h: bytes) -> bytes:
|
||||
assert isinstance(seckey0, bytes), type(seckey0)
|
||||
assert isinstance(h, bytes), type(h)
|
||||
assert len(seckey0) == 32, len(seckey0)
|
||||
int_from_bytes = lambda x: int.from_bytes(x, byteorder="big", signed=False)
|
||||
|
||||
P = ecc.ECPrivkey(seckey0)
|
||||
seckey = P.secret_scalar if P.has_even_y() else ecc.CURVE_ORDER - P.secret_scalar
|
||||
pubkey32 = P.get_public_key_bytes(compressed=True)[1:]
|
||||
tweak = int_from_bytes(ecc.bip340_tagged_hash(b"TapTweak", pubkey32 + h))
|
||||
if tweak >= ecc.CURVE_ORDER:
|
||||
raise ValueError
|
||||
return int.to_bytes((seckey + tweak) % ecc.CURVE_ORDER, length=32, byteorder="big", signed=False)
|
||||
|
||||
|
||||
# a TapTree is either:
|
||||
# - a (leaf_version, script) tuple (leaf_version is 0xc0 for BIP-0342 scripts)
|
||||
# - a list of two elements, each with the same structure as TapTree itself
|
||||
TapTreeLeaf = Tuple[int, bytes]
|
||||
TapTree = Union[TapTreeLeaf, Sequence['TapTree']]
|
||||
|
||||
|
||||
def taproot_tree_helper(script_tree: TapTree):
|
||||
if isinstance(script_tree, tuple):
|
||||
leaf_version, script = script_tree
|
||||
h = ecc.bip340_tagged_hash(b"TapLeaf", bytes([leaf_version]) + witness_push(script))
|
||||
return ([((leaf_version, script), bytes())], h)
|
||||
left, left_h = taproot_tree_helper(script_tree[0])
|
||||
right, right_h = taproot_tree_helper(script_tree[1])
|
||||
ret = [(l, c + right_h) for l, c in left] + [(l, c + left_h) for l, c in right]
|
||||
if right_h < left_h:
|
||||
left_h, right_h = right_h, left_h
|
||||
return (ret, ecc.bip340_tagged_hash(b"TapBranch", left_h + right_h))
|
||||
|
||||
|
||||
def taproot_output_script(internal_pubkey: bytes, *, script_tree: Optional[TapTree]) -> bytes:
|
||||
"""Given an internal public key and a tree of scripts, compute the output script."""
|
||||
assert isinstance(internal_pubkey, bytes), type(internal_pubkey)
|
||||
assert len(internal_pubkey) == 32, len(internal_pubkey)
|
||||
if script_tree is None:
|
||||
merkle_root = bytes()
|
||||
else:
|
||||
_, merkle_root = taproot_tree_helper(script_tree)
|
||||
_, output_pubkey = taproot_tweak_pubkey(internal_pubkey, merkle_root)
|
||||
return construct_script([1, output_pubkey])
|
||||
|
||||
|
||||
def control_block_for_taproot_script_spend(
|
||||
*, internal_pubkey: bytes, script_tree: TapTree, script_num: int,
|
||||
) -> Tuple[bytes, bytes]:
|
||||
"""Constructs the control block necessary for spending a taproot UTXO using a script.
|
||||
script_num indicates which script to use, which indexes into (flattened) script_tree.
|
||||
"""
|
||||
assert isinstance(internal_pubkey, bytes), type(internal_pubkey)
|
||||
assert len(internal_pubkey) == 32, len(internal_pubkey)
|
||||
info, merkle_root = taproot_tree_helper(script_tree)
|
||||
(leaf_version, leaf_script), merkle_path = info[script_num]
|
||||
output_pubkey_y_parity, _ = taproot_tweak_pubkey(internal_pubkey, merkle_root)
|
||||
pubkey_data = bytes([output_pubkey_y_parity + leaf_version]) + internal_pubkey
|
||||
control_block = pubkey_data + merkle_path
|
||||
return (leaf_script, control_block)
|
||||
|
||||
+90
-61
@@ -25,18 +25,19 @@ from typing import (
|
||||
Sequence,
|
||||
Mapping,
|
||||
Set,
|
||||
Union,
|
||||
)
|
||||
|
||||
from .bip32 import convert_bip32_strpath_to_intpath, BIP32Node, KeyOriginInfo, BIP32_PRIME
|
||||
from . import bitcoin
|
||||
from .bitcoin import construct_script, opcodes, construct_witness
|
||||
from .bitcoin import construct_script, opcodes, construct_witness, taproot_output_script
|
||||
from . import constants
|
||||
from .crypto import hash_160, sha256
|
||||
from . import ecc
|
||||
from . import segwit_addr
|
||||
|
||||
|
||||
MAX_TAPROOT_NODES = 128
|
||||
MAX_TAPROOT_DEPTH = 128
|
||||
|
||||
# we guess that signatures will be 72 bytes long
|
||||
# note: DER-encoded ECDSA signatures are 71 or 72 bytes in practice
|
||||
@@ -413,6 +414,9 @@ class Descriptor(object):
|
||||
def is_segwit(self) -> bool:
|
||||
return any([desc.is_segwit() for desc in self.subdescriptors])
|
||||
|
||||
def is_taproot(self) -> bool:
|
||||
return False
|
||||
|
||||
def get_all_pubkeys(self) -> Set[bytes]:
|
||||
"""Returns set of pubkeys that appear at any level in this descriptor."""
|
||||
assert not self.is_range()
|
||||
@@ -752,43 +756,78 @@ class TRDescriptor(Descriptor):
|
||||
def __init__(
|
||||
self,
|
||||
internal_key: 'PubkeyProvider',
|
||||
subdescriptors: List['Descriptor'] = None,
|
||||
depths: List[int] = None,
|
||||
desc_tree: List[Union['Descriptor', List]] = None,
|
||||
) -> None:
|
||||
r"""
|
||||
:param internal_key: The :class:`PubkeyProvider` that is the internal key for this descriptor
|
||||
:param subdescriptors: The :class:`Descriptor`\ s that are the leaf scripts for this descriptor
|
||||
:param depths: The depths of the leaf scripts in the same order as `subdescriptors`
|
||||
:param desc_tree: Taproot script binary tree, as a nested list of Descriptors
|
||||
"""
|
||||
if subdescriptors is None:
|
||||
subdescriptors = []
|
||||
if depths is None:
|
||||
depths = []
|
||||
super().__init__([internal_key], subdescriptors, "tr")
|
||||
self.depths = depths
|
||||
if desc_tree is None:
|
||||
desc_tree = []
|
||||
self.desc_tree = desc_tree
|
||||
desc_list = []
|
||||
if desc_tree:
|
||||
if self.get_max_tree_depth() > MAX_TAPROOT_DEPTH:
|
||||
raise ValueError(f"tr() supports at most {MAX_TAPROOT_DEPTH} nesting levels")
|
||||
def flatten(tree_node):
|
||||
if isinstance(tree_node, Descriptor):
|
||||
return [tree_node]
|
||||
assert len(tree_node) == 2, len(tree_node)
|
||||
return flatten(tree_node[0]) + flatten(tree_node[1])
|
||||
desc_list = flatten(desc_tree)
|
||||
super().__init__(
|
||||
pubkeys=[internal_key],
|
||||
subdescriptors=desc_list, # FIXME we could do without the flattened list (dupl)
|
||||
name="tr",
|
||||
)
|
||||
|
||||
def to_string_no_checksum(self) -> str:
|
||||
r = f"{self.name}({self.pubkeys[0].to_string()}"
|
||||
path: List[bool] = [] # Track left or right for each depth
|
||||
for p, depth in enumerate(self.depths):
|
||||
r += ","
|
||||
while len(path) <= depth:
|
||||
if len(path) > 0:
|
||||
r += "{"
|
||||
path.append(False)
|
||||
r += self.subdescriptors[p].to_string_no_checksum()
|
||||
while len(path) > 0 and path[-1]:
|
||||
if len(path) > 0:
|
||||
r += "}"
|
||||
path.pop()
|
||||
if len(path) > 0:
|
||||
path[-1] = True
|
||||
r += ")"
|
||||
return r
|
||||
ret = f"{self.name}({self.pubkeys[0].to_string()}"
|
||||
if self.desc_tree:
|
||||
ret += ","
|
||||
def tree_to_str(tree_node):
|
||||
if isinstance(tree_node, Descriptor):
|
||||
return tree_node.to_string_no_checksum()
|
||||
assert len(tree_node) == 2, len(tree_node)
|
||||
return "{" + tree_to_str(tree_node[0]) + "," + tree_to_str(tree_node[1]) + "}"
|
||||
ret += tree_to_str(self.desc_tree)
|
||||
ret += ")"
|
||||
return ret
|
||||
|
||||
def is_segwit(self) -> bool:
|
||||
return True
|
||||
|
||||
def is_taproot(self) -> bool:
|
||||
return True
|
||||
|
||||
# TODO add more test vectors from BIP-0386
|
||||
def expand(self, *, pos: Optional[int] = None) -> "ExpandedScripts":
|
||||
internal_pubkey = self.pubkeys[0].get_pubkey_bytes(pos=pos)
|
||||
script_tree = None
|
||||
if self.desc_tree:
|
||||
def transform(tree_node):
|
||||
if isinstance(tree_node, Descriptor):
|
||||
leaf_version = 0xc0
|
||||
leaf_script = tree_node.expand(pos=pos).scriptcode_for_sighash # FIXME maybe rename scriptcode_for_sighash
|
||||
return (leaf_version, leaf_script)
|
||||
assert len(tree_node) == 2, len(tree_node)
|
||||
return [transform(tree_node[0]), transform(tree_node[1])]
|
||||
script_tree = transform(self.desc_tree)
|
||||
output_script = taproot_output_script(internal_pubkey, script_tree=script_tree)
|
||||
return ExpandedScripts(
|
||||
output_script=output_script,
|
||||
)
|
||||
|
||||
def get_max_tree_depth(self) -> Optional[int]:
|
||||
if not self.desc_tree:
|
||||
return None
|
||||
def depth(tree_node) -> int:
|
||||
if isinstance(tree_node, Descriptor):
|
||||
return 0
|
||||
assert len(tree_node) == 2, len(tree_node)
|
||||
return 1 + max(depth(tree_node[0]), depth(tree_node[1]))
|
||||
return depth(self.desc_tree)
|
||||
|
||||
|
||||
def _get_func_expr(s: str) -> Tuple[str, str]:
|
||||
"""
|
||||
@@ -798,9 +837,12 @@ def _get_func_expr(s: str) -> Tuple[str, str]:
|
||||
:return: The function name as the first element of the tuple, and the expression contained within the function as the second element
|
||||
:raises: ValueError: if a matching pair of parentheses cannot be found
|
||||
"""
|
||||
try:
|
||||
start = s.index("(")
|
||||
end = s.rindex(")")
|
||||
return s[0:start], s[start + 1:end]
|
||||
except ValueError:
|
||||
raise ValueError("A matching pair of parentheses cannot be found")
|
||||
|
||||
|
||||
def _get_const(s: str, const: str) -> str:
|
||||
@@ -836,6 +878,8 @@ def _get_expr(s: str) -> Tuple[str, str]:
|
||||
level -= 1
|
||||
elif level == 0 and c in [")", "}", ","]:
|
||||
break
|
||||
else:
|
||||
return s, ""
|
||||
return s[0:i], s[i:]
|
||||
|
||||
def parse_pubkey(expr: str, *, ctx: '_ParseDescriptorContext') -> Tuple['PubkeyProvider', str]:
|
||||
@@ -939,39 +983,24 @@ def _parse_descriptor(desc: str, *, ctx: '_ParseDescriptorContext') -> 'Descript
|
||||
if ctx != _ParseDescriptorContext.TOP:
|
||||
raise ValueError("Can only have tr at top level")
|
||||
internal_key, expr = parse_pubkey(expr, ctx=ctx)
|
||||
subscripts = []
|
||||
depths = []
|
||||
desc_tree = []
|
||||
if expr:
|
||||
# Path from top of the tree to what we're currently processing.
|
||||
# branches[i] == False: left branch in the i'th step from the top
|
||||
# branches[i] == true: right branch
|
||||
branches = []
|
||||
while True:
|
||||
# Process open braces
|
||||
while True:
|
||||
try:
|
||||
expr = _get_const(expr, "{")
|
||||
branches.append(False)
|
||||
except ValueError:
|
||||
break
|
||||
if len(branches) > MAX_TAPROOT_NODES:
|
||||
raise ValueError(f"tr() supports at most {MAX_TAPROOT_NODES} nesting levels") # TODO xxxx fixed upstream bug here
|
||||
# Process script expression
|
||||
sarg, expr = _get_expr(expr)
|
||||
subscripts.append(_parse_descriptor(sarg, ctx=_ParseDescriptorContext.P2TR))
|
||||
depths.append(len(branches))
|
||||
# Process closing braces
|
||||
while len(branches) > 0 and branches[-1]:
|
||||
expr = _get_const(expr, "}")
|
||||
branches.pop()
|
||||
# If we're at the end of a left branch, expect a comma
|
||||
if len(branches) > 0 and not branches[-1]:
|
||||
expr = _get_const(expr, ",")
|
||||
branches[-1] = True
|
||||
|
||||
if len(branches) == 0:
|
||||
break
|
||||
return TRDescriptor(internal_key, subscripts, depths)
|
||||
def parse_tree(tree_str):
|
||||
if len(tree_str) == 0:
|
||||
raise ValueError("Invalid Taproot tree expression")
|
||||
if tree_str[0] != "{": # leaf
|
||||
sarg, remaining = _get_expr(tree_str)
|
||||
return _parse_descriptor(sarg, ctx=_ParseDescriptorContext.P2TR), remaining
|
||||
if len(tree_str) < len("{x,y}") or tree_str[-1] != "}":
|
||||
raise ValueError("Invalid Taproot tree expression")
|
||||
left, remaining = parse_tree(tree_str[1:])
|
||||
if remaining[0] != ",": raise ValueError
|
||||
right, remaining = parse_tree(remaining[1:])
|
||||
if remaining[0] != "}": raise ValueError
|
||||
return [left, right], remaining[1:]
|
||||
desc_tree, _remaining = parse_tree(expr)
|
||||
if len(_remaining) != 0: raise ValueError
|
||||
return TRDescriptor(internal_key, desc_tree)
|
||||
if ctx == _ParseDescriptorContext.P2SH:
|
||||
raise ValueError("A function is needed within P2SH")
|
||||
elif ctx == _ParseDescriptorContext.P2WSH:
|
||||
|
||||
@@ -414,6 +414,9 @@ class ECPubkey(object):
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def has_even_y(self) -> bool:
|
||||
return self.y() % 2 == 0
|
||||
|
||||
|
||||
GENERATOR = ECPubkey(bytes.fromhex('0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'
|
||||
'483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8'))
|
||||
|
||||
@@ -118,7 +118,7 @@ class KeyStore(Logger, ABC):
|
||||
return {}
|
||||
keypairs = {}
|
||||
for pubkey in txin.pubkeys:
|
||||
if pubkey in txin.part_sigs:
|
||||
if pubkey in txin.sigs_ecdsa:
|
||||
# this pubkey already signed
|
||||
continue
|
||||
derivation = self.get_pubkey_derivation(pubkey, txin)
|
||||
|
||||
+1
-1
@@ -1089,7 +1089,7 @@ def make_commitment_output_to_remote_address(remote_payment_pubkey: bytes) -> st
|
||||
|
||||
def sign_and_get_sig_string(tx: PartialTransaction, local_config, remote_config):
|
||||
tx.sign({local_config.multisig_key.pubkey: local_config.multisig_key.privkey})
|
||||
sig = tx.inputs()[0].part_sigs[local_config.multisig_key.pubkey]
|
||||
sig = tx.inputs()[0].sigs_ecdsa[local_config.multisig_key.pubkey]
|
||||
sig_64 = ecdsa_sig64_from_der_sig(sig[:-1])
|
||||
return sig_64
|
||||
|
||||
|
||||
+216
-60
@@ -48,8 +48,9 @@ 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,
|
||||
opcodes, base_decode,
|
||||
base_encode, construct_witness, construct_script)
|
||||
from .crypto import sha256d
|
||||
base_encode, construct_witness, construct_script,
|
||||
taproot_tweak_seckey)
|
||||
from .crypto import sha256d, sha256
|
||||
from .logging import get_logger
|
||||
from .util import ShortID, OldTaskGroup
|
||||
from .bitcoin import DummyAddress
|
||||
@@ -107,21 +108,26 @@ class TxinDataFetchProgress(NamedTuple):
|
||||
class Sighash(IntEnum):
|
||||
# note: this is not an IntFlag, as ALL|NONE != SINGLE
|
||||
|
||||
DEFAULT = 0 # taproot only (bip-0341)
|
||||
ALL = 1
|
||||
NONE = 2
|
||||
SINGLE = 3
|
||||
ANYONECANPAY = 0x80
|
||||
|
||||
@classmethod
|
||||
def is_valid(cls, sighash: int) -> bool:
|
||||
for flag in Sighash:
|
||||
for base_flag in [Sighash.ALL, Sighash.NONE, Sighash.SINGLE]:
|
||||
if (flag & ~0x1f | base_flag) == sighash:
|
||||
return True
|
||||
return False
|
||||
def is_valid(cls, sighash: int, *, is_taproot: bool = False) -> bool:
|
||||
valid_flags = {
|
||||
0x01, 0x02, 0x03,
|
||||
0x81, 0x82, 0x83,
|
||||
}
|
||||
if is_taproot:
|
||||
valid_flags.add(0x00)
|
||||
return sighash in valid_flags
|
||||
|
||||
@classmethod
|
||||
def to_sigbytes(cls, sighash: int) -> bytes:
|
||||
if sighash == Sighash.DEFAULT:
|
||||
return b""
|
||||
return sighash.to_bytes(length=1, byteorder="big")
|
||||
|
||||
|
||||
@@ -210,11 +216,74 @@ class TxOutput:
|
||||
return d
|
||||
|
||||
|
||||
class BIP143SharedTxDigestFields(NamedTuple):
|
||||
class BIP143SharedTxDigestFields(NamedTuple): # witness v0
|
||||
hashPrevouts: bytes
|
||||
hashSequence: bytes
|
||||
hashOutputs: bytes
|
||||
|
||||
@classmethod
|
||||
def from_tx(cls, tx: 'PartialTransaction') -> 'BIP143SharedTxDigestFields':
|
||||
inputs = tx.inputs()
|
||||
outputs = tx.outputs()
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
class BIP341SharedTxDigestFields(NamedTuple): # witness v1
|
||||
sha_prevouts: bytes
|
||||
sha_amounts: bytes
|
||||
sha_scriptpubkeys: bytes
|
||||
sha_sequences: bytes
|
||||
sha_outputs: bytes
|
||||
|
||||
@classmethod
|
||||
def from_tx(cls, tx: 'PartialTransaction') -> 'BIP341SharedTxDigestFields':
|
||||
inputs = tx.inputs()
|
||||
outputs = tx.outputs()
|
||||
sha_prevouts = sha256(b''.join(txin.prevout.serialize_to_network() for txin in inputs))
|
||||
sha_amounts = sha256(b''.join(
|
||||
int.to_bytes(txin.value_sats(), length=8, byteorder="little", signed=False)
|
||||
for txin in inputs))
|
||||
sha_scriptpubkeys = sha256(b''.join(
|
||||
var_int(len(txin.scriptpubkey)) + txin.scriptpubkey
|
||||
for txin in inputs))
|
||||
sha_sequences = sha256(b''.join(
|
||||
int.to_bytes(txin.nsequence, length=4, byteorder="little", signed=False)
|
||||
for txin in inputs))
|
||||
sha_outputs = sha256(b''.join(o.serialize_to_network() for o in outputs))
|
||||
return BIP341SharedTxDigestFields(
|
||||
sha_prevouts=sha_prevouts,
|
||||
sha_amounts=sha_amounts,
|
||||
sha_scriptpubkeys=sha_scriptpubkeys,
|
||||
sha_sequences=sha_sequences,
|
||||
sha_outputs=sha_outputs,
|
||||
)
|
||||
|
||||
|
||||
class SighashCache:
|
||||
|
||||
def __init__(self):
|
||||
self._witver0 = None # type: Optional[BIP143SharedTxDigestFields]
|
||||
self._witver1 = None # type: Optional[BIP341SharedTxDigestFields]
|
||||
|
||||
def get_witver0_data_for_tx(self, tx: 'PartialTransaction') -> BIP143SharedTxDigestFields:
|
||||
if self._witver0 is None:
|
||||
self._witver0 = BIP143SharedTxDigestFields.from_tx(tx)
|
||||
return self._witver0
|
||||
|
||||
def get_witver1_data_for_tx(self, tx: 'PartialTransaction') -> BIP341SharedTxDigestFields:
|
||||
if self._witver1 is None:
|
||||
self._witver1 = BIP341SharedTxDigestFields.from_tx(tx)
|
||||
return self._witver1
|
||||
|
||||
|
||||
class TxOutpoint(NamedTuple):
|
||||
txid: bytes # endianness same as hex string displayed; reverse of tx serialization order
|
||||
@@ -849,7 +918,7 @@ class Transaction:
|
||||
if estimate_size:
|
||||
dummy_desc = create_dummy_descriptor_from_address(txin.address)
|
||||
if desc := (txin.script_descriptor or dummy_desc):
|
||||
sol = desc.satisfy(allow_dummy=estimate_size, sigdata=txin.part_sigs)
|
||||
sol = desc.satisfy(allow_dummy=estimate_size, sigdata=txin.sigs_ecdsa)
|
||||
if sol.witness is not None:
|
||||
return sol.witness
|
||||
return construct_witness([])
|
||||
@@ -876,7 +945,7 @@ class Transaction:
|
||||
if redeem_script := desc.expand().redeem_script:
|
||||
return construct_script([redeem_script])
|
||||
return b""
|
||||
sol = desc.satisfy(allow_dummy=estimate_size, sigdata=txin.part_sigs)
|
||||
sol = desc.satisfy(allow_dummy=estimate_size, sigdata=txin.sigs_ecdsa)
|
||||
if sol.script_sig is not None:
|
||||
return sol.script_sig
|
||||
return b""
|
||||
@@ -900,18 +969,6 @@ class Transaction:
|
||||
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))
|
||||
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)
|
||||
|
||||
def is_segwit(self, *, guess_for_address=False):
|
||||
return any(txin.is_segwit(guess_for_address=guess_for_address)
|
||||
for txin in self.inputs())
|
||||
@@ -1290,6 +1347,8 @@ class PSBTInputType(IntEnum):
|
||||
BIP32_DERIVATION = 6
|
||||
FINAL_SCRIPTSIG = 7
|
||||
FINAL_SCRIPTWITNESS = 8
|
||||
TAP_KEY_SIG = 0x13
|
||||
TAP_MERKLE_ROOT = 0x18
|
||||
SLIP19_OWNERSHIP_PROOF = 0x19
|
||||
|
||||
|
||||
@@ -1301,6 +1360,7 @@ class PSBTOutputType(IntEnum):
|
||||
|
||||
# Serialization/deserialization tools
|
||||
def deser_compact_size(f) -> Optional[int]:
|
||||
# note: ~inverse of bitcoin.var_int
|
||||
try:
|
||||
nit = f.read(1)[0]
|
||||
except IndexError:
|
||||
@@ -1383,11 +1443,13 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
def __init__(self, *args, **kwargs):
|
||||
TxInput.__init__(self, *args, **kwargs)
|
||||
self._witness_utxo = None # type: Optional[TxOutput]
|
||||
self.part_sigs = {} # type: Dict[bytes, bytes] # pubkey -> sig
|
||||
self.sigs_ecdsa = {} # type: Dict[bytes, bytes] # pubkey -> sig
|
||||
self.tap_key_sig = None # type: Optional[bytes] # sig for taproot key-path-spending
|
||||
self.sighash = None # type: Optional[int]
|
||||
self.bip32_paths = {} # type: Dict[bytes, Tuple[bytes, Sequence[int]]] # pubkey -> (xpub_fingerprint, path)
|
||||
self.redeem_script = None # type: Optional[bytes]
|
||||
self.witness_script = None # type: Optional[bytes]
|
||||
self.tap_merkle_root = None # type: Optional[bytes]
|
||||
self.slip_19_ownership_proof = None # type: Optional[bytes]
|
||||
self._unknown = {} # type: Dict[bytes, bytes]
|
||||
|
||||
@@ -1397,6 +1459,7 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
self._trusted_address = None # type: Optional[str]
|
||||
self._is_p2sh_segwit = None # type: Optional[bool] # None means unknown
|
||||
self._is_native_segwit = None # type: Optional[bool] # None means unknown
|
||||
self._is_taproot = None # type: Optional[bool] # None means unknown
|
||||
self.witness_sizehint = None # type: Optional[int] # byte size of serialized complete witness, for tx size est
|
||||
|
||||
@property
|
||||
@@ -1439,7 +1502,9 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
'sighash': self.sighash,
|
||||
'redeem_script': self.redeem_script.hex() if self.redeem_script else None,
|
||||
'witness_script': self.witness_script.hex() if self.witness_script else None,
|
||||
'part_sigs': {pubkey.hex(): sig.hex() for pubkey, sig in self.part_sigs.items()},
|
||||
'sigs_ecdsa': {pubkey.hex(): sig.hex() for pubkey, sig in self.sigs_ecdsa.items()},
|
||||
'tap_key_sig': self.tap_key_sig.hex() if self.tap_key_sig else None,
|
||||
'tap_merkle_root': self.tap_merkle_root.hex() if self.tap_merkle_root else None,
|
||||
'bip32_paths': {pubkey.hex(): (xfp.hex(), bip32.convert_bip32_intpath_to_strpath(path))
|
||||
for pubkey, (xfp, path) in self.bip32_paths.items()},
|
||||
'slip_19_ownership_proof': self.slip_19_ownership_proof.hex() if self.slip_19_ownership_proof else None,
|
||||
@@ -1519,11 +1584,25 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
self.witness_utxo = TxOutput.from_network_bytes(val)
|
||||
if key: raise SerializationError(f"key for {repr(kt)} must be empty")
|
||||
elif kt == PSBTInputType.PARTIAL_SIG:
|
||||
if key in self.part_sigs:
|
||||
if key in self.sigs_ecdsa:
|
||||
raise SerializationError(f"duplicate key: {repr(kt)}")
|
||||
if len(key) not in (33, 65): # TODO also allow 32? one of the tests in the BIP is "supposed to" fail with len==32...
|
||||
if len(key) not in (33, 65):
|
||||
raise SerializationError(f"key for {repr(kt)} has unexpected length: {len(key)}")
|
||||
self.part_sigs[key] = val
|
||||
self.sigs_ecdsa[key] = val
|
||||
elif kt == PSBTInputType.TAP_KEY_SIG:
|
||||
if self.tap_key_sig is not None:
|
||||
raise SerializationError(f"duplicate key: {repr(kt)}")
|
||||
if len(val) not in (64, 65):
|
||||
raise SerializationError(f"value for {repr(kt)} has unexpected length: {len(val)}")
|
||||
self.tap_key_sig = val
|
||||
if key: raise SerializationError(f"key for {repr(kt)} must be empty")
|
||||
elif kt == PSBTInputType.TAP_MERKLE_ROOT:
|
||||
if self.tap_merkle_root is not None:
|
||||
raise SerializationError(f"duplicate key: {repr(kt)}")
|
||||
if len(val) != 32:
|
||||
raise SerializationError(f"value for {repr(kt)} has unexpected length: {len(val)}")
|
||||
self.tap_merkle_root = val
|
||||
if key: raise SerializationError(f"key for {repr(kt)} must be empty")
|
||||
elif kt == PSBTInputType.SIGHASH_TYPE:
|
||||
if self.sighash is not None:
|
||||
raise SerializationError(f"duplicate key: {repr(kt)}")
|
||||
@@ -1534,7 +1613,7 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
elif kt == PSBTInputType.BIP32_DERIVATION:
|
||||
if key in self.bip32_paths:
|
||||
raise SerializationError(f"duplicate key: {repr(kt)}")
|
||||
if len(key) not in (33, 65): # TODO also allow 32? one of the tests in the BIP is "supposed to" fail with len==32...
|
||||
if len(key) not in (33, 65):
|
||||
raise SerializationError(f"key for {repr(kt)} has unexpected length: {len(key)}")
|
||||
self.bip32_paths[key] = unpack_bip32_root_fingerprint_and_int_path(val)
|
||||
elif kt == PSBTInputType.REDEEM_SCRIPT:
|
||||
@@ -1573,8 +1652,12 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
wr(PSBTInputType.WITNESS_UTXO, self.witness_utxo.serialize_to_network())
|
||||
if self.utxo:
|
||||
wr(PSBTInputType.NON_WITNESS_UTXO, bfh(self.utxo.serialize_to_network(include_sigs=True)))
|
||||
for pk, val in sorted(self.part_sigs.items()):
|
||||
for pk, val in sorted(self.sigs_ecdsa.items()):
|
||||
wr(PSBTInputType.PARTIAL_SIG, val, pk)
|
||||
if self.tap_key_sig is not None:
|
||||
wr(PSBTInputType.TAP_KEY_SIG, self.tap_key_sig)
|
||||
if self.tap_merkle_root is not None:
|
||||
wr(PSBTInputType.TAP_MERKLE_ROOT, self.tap_merkle_root)
|
||||
if self.sighash is not None:
|
||||
wr(PSBTInputType.SIGHASH_TYPE, struct.pack('<I', self.sighash))
|
||||
if self.redeem_script is not None:
|
||||
@@ -1632,7 +1715,7 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
return True
|
||||
if desc := self.script_descriptor:
|
||||
try:
|
||||
desc.satisfy(allow_dummy=False, sigdata=self.part_sigs)
|
||||
desc.satisfy(allow_dummy=False, sigdata=self.sigs_ecdsa)
|
||||
except MissingSolutionPiece:
|
||||
pass
|
||||
else:
|
||||
@@ -1641,14 +1724,16 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
|
||||
def get_satisfaction_progress(self) -> Tuple[int, int]:
|
||||
if desc := self.script_descriptor:
|
||||
return desc.get_satisfaction_progress(sigdata=self.part_sigs)
|
||||
return desc.get_satisfaction_progress(sigdata=self.sigs_ecdsa)
|
||||
return 0, 0
|
||||
|
||||
def finalize(self) -> None:
|
||||
def clear_fields_when_finalized():
|
||||
# BIP-174: "All other data except the UTXO and unknown fields in the
|
||||
# input key-value map should be cleared from the PSBT"
|
||||
self.part_sigs = {}
|
||||
self.sigs_ecdsa = {}
|
||||
self.tap_key_sig = None
|
||||
self.tap_merkle_root = None
|
||||
self.sighash = None
|
||||
self.bip32_paths = {}
|
||||
self.redeem_script = None
|
||||
@@ -1673,9 +1758,13 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
self.witness_utxo = other_txin.witness_utxo
|
||||
if other_txin.utxo:
|
||||
self.utxo = other_txin.utxo
|
||||
self.part_sigs.update(other_txin.part_sigs)
|
||||
self.sigs_ecdsa.update(other_txin.sigs_ecdsa)
|
||||
if other_txin.sighash is not None:
|
||||
self.sighash = other_txin.sighash
|
||||
if other_txin.tap_key_sig is not None:
|
||||
self.tap_key_sig = other_txin.tap_key_sig
|
||||
if other_txin.tap_merkle_root is not None:
|
||||
self.tap_merkle_root = other_txin.tap_merkle_root
|
||||
self.bip32_paths.update(other_txin.bip32_paths)
|
||||
if other_txin.redeem_script is not None:
|
||||
self.redeem_script = other_txin.redeem_script
|
||||
@@ -1692,7 +1781,7 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
self._utxo = None # type: Optional[Transaction]
|
||||
|
||||
def is_native_segwit(self) -> Optional[bool]:
|
||||
"""Whether this input is native segwit. None means inconclusive."""
|
||||
"""Whether this input is native segwit (any witness version). None means inconclusive."""
|
||||
if self._is_native_segwit is None:
|
||||
if self.address:
|
||||
self._is_native_segwit = bitcoin.is_segwit_address(self.address)
|
||||
@@ -1726,6 +1815,7 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
return self._is_p2sh_segwit
|
||||
|
||||
def is_segwit(self, *, guess_for_address=False) -> bool:
|
||||
"""Whether this input is segwit (any witness version)."""
|
||||
if super().is_segwit():
|
||||
return True
|
||||
if self.is_native_segwit() or self.is_p2sh_segwit():
|
||||
@@ -1741,9 +1831,18 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
return dummy_desc.is_segwit()
|
||||
return False # can be false-negative
|
||||
|
||||
def is_taproot(self) -> bool:
|
||||
if self._is_taproot is None:
|
||||
if self.address:
|
||||
self._is_taproot = bitcoin.is_taproot_address(self.address)
|
||||
if desc := self.script_descriptor:
|
||||
return desc.is_taproot()
|
||||
return self._is_taproot
|
||||
|
||||
def already_has_some_signatures(self) -> bool:
|
||||
"""Returns whether progress has been made towards completing this input."""
|
||||
return (self.part_sigs
|
||||
return (self.sigs_ecdsa
|
||||
or self.tap_key_sig is not None
|
||||
or self.script_sig is not None
|
||||
or self.witness is not None)
|
||||
|
||||
@@ -1816,7 +1915,7 @@ class PartialTxOutput(TxOutput, PSBTSection):
|
||||
elif kt == PSBTOutputType.BIP32_DERIVATION:
|
||||
if key in self.bip32_paths:
|
||||
raise SerializationError(f"duplicate key: {repr(kt)}")
|
||||
if len(key) not in (33, 65): # TODO also allow 32? one of the tests in the BIP is "supposed to" fail with len==32...
|
||||
if len(key) not in (33, 65):
|
||||
raise SerializationError(f"key for {repr(kt)} has unexpected length: {len(key)}")
|
||||
self.bip32_paths[key] = unpack_bip32_root_fingerprint_and_int_path(val)
|
||||
else:
|
||||
@@ -2086,53 +2185,101 @@ class PartialTransaction(Transaction):
|
||||
self._outputs.sort(key = lambda o: (o.value, o.scriptpubkey))
|
||||
self.invalidate_ser_cache()
|
||||
|
||||
def serialize_preimage(self, txin_index: int, *,
|
||||
bip143_shared_txdigest_fields: BIP143SharedTxDigestFields = None) -> bytes:
|
||||
def serialize_preimage(
|
||||
self,
|
||||
txin_index: int,
|
||||
*,
|
||||
sighash_cache: SighashCache = 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):
|
||||
sighash = txin.sighash
|
||||
if sighash is None:
|
||||
sighash = Sighash.DEFAULT if txin.is_taproot() else Sighash.ALL
|
||||
if not Sighash.is_valid(sighash, is_taproot=txin.is_taproot()):
|
||||
raise Exception(f"SIGHASH_FLAG ({sighash}) not supported!")
|
||||
nHashType = int.to_bytes(sighash, length=4, byteorder="little", signed=False)
|
||||
preimage_script = self.get_preimage_script(txin)
|
||||
if sighash_cache is None:
|
||||
sighash_cache = SighashCache()
|
||||
if txin.is_segwit():
|
||||
if bip143_shared_txdigest_fields is None:
|
||||
bip143_shared_txdigest_fields = self._calc_bip143_shared_txdigest_fields()
|
||||
if txin.is_taproot():
|
||||
scache = sighash_cache.get_witver1_data_for_tx(self)
|
||||
sighash_epoch = b"\x00"
|
||||
hash_type = int.to_bytes(sighash, length=1, byteorder="little", signed=False)
|
||||
# txdata
|
||||
preimage_txdata = bytearray()
|
||||
preimage_txdata += nVersion
|
||||
preimage_txdata += nLocktime
|
||||
if sighash & 0x80 != Sighash.ANYONECANPAY:
|
||||
preimage_txdata += scache.sha_prevouts
|
||||
preimage_txdata += scache.sha_amounts
|
||||
preimage_txdata += scache.sha_scriptpubkeys
|
||||
preimage_txdata += scache.sha_sequences
|
||||
if sighash & 3 not in (Sighash.NONE, Sighash.SINGLE):
|
||||
preimage_txdata += scache.sha_outputs
|
||||
# inputdata
|
||||
preimage_inputdata = bytearray()
|
||||
spend_type = bytes([0]) # (ext_flag * 2) + annex_present
|
||||
preimage_inputdata += spend_type
|
||||
if sighash & 0x80 == Sighash.ANYONECANPAY:
|
||||
preimage_inputdata += txin.prevout.serialize_to_network()
|
||||
preimage_inputdata += int.to_bytes(txin.value_sats(), length=8, byteorder="little", signed=False)
|
||||
preimage_inputdata += var_int(len(txin.scriptpubkey)) + txin.scriptpubkey
|
||||
preimage_inputdata += int.to_bytes(txin.nsequence, length=4, byteorder="little", signed=False)
|
||||
else:
|
||||
preimage_inputdata += int.to_bytes(txin_index, length=4, byteorder="little", signed=False)
|
||||
# TODO sha_annex
|
||||
# outputdata
|
||||
preimage_outputdata = bytearray()
|
||||
if sighash & 3 == Sighash.SINGLE:
|
||||
try:
|
||||
txout = outputs[txin_index]
|
||||
except IndexError:
|
||||
raise Exception("Using SIGHASH_SINGLE without a corresponding output") from None
|
||||
preimage_outputdata += sha256(txout.serialize_to_network())
|
||||
return bytes(sighash_epoch + hash_type + preimage_txdata + preimage_inputdata + preimage_outputdata)
|
||||
else: # segwit (witness v0)
|
||||
scache = sighash_cache.get_witver0_data_for_tx(self)
|
||||
if not (sighash & Sighash.ANYONECANPAY):
|
||||
hashPrevouts = bip143_shared_txdigest_fields.hashPrevouts
|
||||
hashPrevouts = scache.hashPrevouts
|
||||
else:
|
||||
hashPrevouts = bytes(32)
|
||||
if not (sighash & Sighash.ANYONECANPAY) and (sighash & 0x1f) != Sighash.SINGLE and (sighash & 0x1f) != Sighash.NONE:
|
||||
hashSequence = bip143_shared_txdigest_fields.hashSequence
|
||||
hashSequence = scache.hashSequence
|
||||
else:
|
||||
hashSequence = bytes(32)
|
||||
if (sighash & 0x1f) != Sighash.SINGLE and (sighash & 0x1f) != Sighash.NONE:
|
||||
hashOutputs = bip143_shared_txdigest_fields.hashOutputs
|
||||
hashOutputs = scache.hashOutputs
|
||||
elif (sighash & 0x1f) == Sighash.SINGLE and txin_index < len(outputs):
|
||||
hashOutputs = sha256d(outputs[txin_index].serialize_to_network())
|
||||
else:
|
||||
hashOutputs = bytes(32)
|
||||
outpoint = txin.prevout.serialize_to_network()
|
||||
preimage_script = self.get_preimage_script(txin)
|
||||
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)
|
||||
nHashType = int.to_bytes(sighash, length=4, byteorder="little", signed=False)
|
||||
preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nHashType
|
||||
else:
|
||||
return preimage
|
||||
else: # legacy sighash (pre-segwit)
|
||||
if sighash != Sighash.ALL:
|
||||
raise Exception(f"SIGHASH_FLAG ({sighash}) not supported! (for legacy sighash)")
|
||||
preimage_script = self.get_preimage_script(txin)
|
||||
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)
|
||||
nHashType = int.to_bytes(sighash, length=4, byteorder="little", signed=False)
|
||||
preimage = nVersion + txins + txouts + nLocktime + nHashType
|
||||
return preimage
|
||||
raise Exception("should not reach this")
|
||||
|
||||
def sign(self, keypairs: Mapping[bytes, bytes]) -> None:
|
||||
# keypairs: pubkey_bytes -> secret_bytes
|
||||
bip143_shared_txdigest_fields = self._calc_bip143_shared_txdigest_fields()
|
||||
sighash_cache = SighashCache()
|
||||
for i, txin in enumerate(self.inputs()):
|
||||
for pubkey in txin.pubkeys:
|
||||
if txin.is_complete():
|
||||
@@ -2141,7 +2288,7 @@ class PartialTransaction(Transaction):
|
||||
continue
|
||||
_logger.info(f"adding signature for {pubkey}. spending utxo {txin.prevout.to_str()}")
|
||||
sec = keypairs[pubkey]
|
||||
sig = self.sign_txin(i, sec, bip143_shared_txdigest_fields=bip143_shared_txdigest_fields)
|
||||
sig = self.sign_txin(i, sec, sighash_cache=sighash_cache)
|
||||
self.add_signature_to_txin(txin_idx=i, signing_pubkey=pubkey, sig=sig)
|
||||
|
||||
_logger.debug(f"tx.sign() finished. is_complete={self.is_complete()}")
|
||||
@@ -2152,17 +2299,25 @@ class PartialTransaction(Transaction):
|
||||
txin_index: int,
|
||||
privkey_bytes: bytes,
|
||||
*,
|
||||
bip143_shared_txdigest_fields=None,
|
||||
sighash_cache: SighashCache = 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 = self.serialize_preimage(txin_index, bip143_shared_txdigest_fields=bip143_shared_txdigest_fields)
|
||||
msg_hash = sha256d(pre_hash)
|
||||
pre_hash = self.serialize_preimage(txin_index, sighash_cache=sighash_cache)
|
||||
if txin.is_taproot():
|
||||
# note: privkey_bytes is the internal key
|
||||
merkle_root = txin.tap_merkle_root or bytes()
|
||||
output_privkey_bytes = taproot_tweak_seckey(privkey_bytes, merkle_root)
|
||||
output_privkey = ecc.ECPrivkey(output_privkey_bytes)
|
||||
msg_hash = ecc.bip340_tagged_hash(b"TapSighash", pre_hash)
|
||||
sig = output_privkey.schnorr_sign(msg_hash)
|
||||
sighash = txin.sighash if txin.sighash is not None else Sighash.DEFAULT
|
||||
else:
|
||||
privkey = ecc.ECPrivkey(privkey_bytes)
|
||||
msg_hash = sha256d(pre_hash)
|
||||
sig = privkey.ecdsa_sign(msg_hash, sigencode=ecc.ecdsa_der_sig_from_r_and_s)
|
||||
sig = sig + Sighash.to_sigbytes(sighash)
|
||||
return sig
|
||||
sighash = txin.sighash if txin.sighash is not None else Sighash.ALL
|
||||
return sig + Sighash.to_sigbytes(sighash)
|
||||
|
||||
def is_complete(self) -> bool:
|
||||
return all([txin.is_complete() for txin in self.inputs()])
|
||||
@@ -2211,7 +2366,7 @@ class PartialTransaction(Transaction):
|
||||
sig = signatures[i]
|
||||
if sig is None:
|
||||
continue
|
||||
if sig in list(txin.part_sigs.values()):
|
||||
if sig in list(txin.sigs_ecdsa.values()):
|
||||
continue
|
||||
msg_hash = sha256d(self.serialize_preimage(i))
|
||||
sig64 = ecc.ecdsa_sig64_from_der_sig(sig[:-1])
|
||||
@@ -2233,7 +2388,7 @@ class PartialTransaction(Transaction):
|
||||
|
||||
def add_signature_to_txin(self, *, txin_idx: int, signing_pubkey: bytes, sig: bytes) -> None:
|
||||
txin = self._inputs[txin_idx]
|
||||
txin.part_sigs[signing_pubkey] = sig
|
||||
txin.sigs_ecdsa[signing_pubkey] = sig
|
||||
# force re-serialization
|
||||
txin.script_sig = None
|
||||
txin.witness = None
|
||||
@@ -2316,7 +2471,8 @@ class PartialTransaction(Transaction):
|
||||
|
||||
def remove_signatures(self):
|
||||
for txin in self.inputs():
|
||||
txin.part_sigs = {}
|
||||
txin.sigs_ecdsa = {}
|
||||
txin.tap_key_sig = None
|
||||
txin.script_sig = None
|
||||
txin.witness = None
|
||||
assert not self.is_complete()
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
{
|
||||
"version": 1,
|
||||
"scriptPubKey": [
|
||||
{
|
||||
"given": {
|
||||
"internalPubkey": "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d",
|
||||
"scriptTree": null
|
||||
},
|
||||
"intermediary": {
|
||||
"merkleRoot": null,
|
||||
"tweak": "b86e7be8f39bab32a6f2c0443abbc210f0edac0e2c53d501b36b64437d9c6c70",
|
||||
"tweakedPubkey": "53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343"
|
||||
},
|
||||
"expected": {
|
||||
"scriptPubKey": "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343",
|
||||
"bip350Address": "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"internalPubkey": "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27",
|
||||
"scriptTree": {
|
||||
"id": 0,
|
||||
"script": "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac",
|
||||
"leafVersion": 192
|
||||
}
|
||||
},
|
||||
"intermediary": {
|
||||
"leafHashes": [
|
||||
"5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21"
|
||||
],
|
||||
"merkleRoot": "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21",
|
||||
"tweak": "cbd8679ba636c1110ea247542cfbd964131a6be84f873f7f3b62a777528ed001",
|
||||
"tweakedPubkey": "147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3"
|
||||
},
|
||||
"expected": {
|
||||
"scriptPubKey": "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3",
|
||||
"bip350Address": "bc1pz37fc4cn9ah8anwm4xqqhvxygjf9rjf2resrw8h8w4tmvcs0863sa2e586",
|
||||
"scriptPathControlBlocks": [
|
||||
"c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"internalPubkey": "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820",
|
||||
"scriptTree": {
|
||||
"id": 0,
|
||||
"script": "20b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007ac",
|
||||
"leafVersion": 192
|
||||
}
|
||||
},
|
||||
"intermediary": {
|
||||
"leafHashes": [
|
||||
"c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b"
|
||||
],
|
||||
"merkleRoot": "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b",
|
||||
"tweak": "6af9e28dbf9d6aaf027696e2598a5b3d056f5fd2355a7fd5a37a0e5008132d30",
|
||||
"tweakedPubkey": "e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e"
|
||||
},
|
||||
"expected": {
|
||||
"scriptPubKey": "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e",
|
||||
"bip350Address": "bc1punvppl2stp38f7kwv2u2spltjuvuaayuqsthe34hd2dyy5w4g58qqfuag5",
|
||||
"scriptPathControlBlocks": [
|
||||
"c093478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"internalPubkey": "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592",
|
||||
"scriptTree": [
|
||||
{
|
||||
"id": 0,
|
||||
"script": "20387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48ac",
|
||||
"leafVersion": 192
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"script": "06424950333431",
|
||||
"leafVersion": 250
|
||||
}
|
||||
]
|
||||
},
|
||||
"intermediary": {
|
||||
"leafHashes": [
|
||||
"8ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7",
|
||||
"f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a"
|
||||
],
|
||||
"merkleRoot": "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef",
|
||||
"tweak": "9e0517edc8259bb3359255400b23ca9507f2a91cd1e4250ba068b4eafceba4a9",
|
||||
"tweakedPubkey": "712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5"
|
||||
},
|
||||
"expected": {
|
||||
"scriptPubKey": "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5",
|
||||
"bip350Address": "bc1pwyjywgrd0ffr3tx8laflh6228dj98xkjj8rum0zfpd6h0e930h6saqxrrm",
|
||||
"scriptPathControlBlocks": [
|
||||
"c0ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592f224a923cd0021ab202ab139cc56802ddb92dcfc172b9212261a539df79a112a",
|
||||
"faee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865928ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"internalPubkey": "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8",
|
||||
"scriptTree": [
|
||||
{
|
||||
"id": 0,
|
||||
"script": "2044b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fdac",
|
||||
"leafVersion": 192
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"script": "07546170726f6f74",
|
||||
"leafVersion": 192
|
||||
}
|
||||
]
|
||||
},
|
||||
"intermediary": {
|
||||
"leafHashes": [
|
||||
"64512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89",
|
||||
"2cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb"
|
||||
],
|
||||
"merkleRoot": "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc",
|
||||
"tweak": "639f0281b7ac49e742cd25b7f188657626da1ad169209078e2761cefd91fd65e",
|
||||
"tweakedPubkey": "77e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220"
|
||||
},
|
||||
"expected": {
|
||||
"scriptPubKey": "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220",
|
||||
"bip350Address": "bc1pwl3s54fzmk0cjnpl3w9af39je7pv5ldg504x5guk2hpecpg2kgsqaqstjq",
|
||||
"scriptPathControlBlocks": [
|
||||
"c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd82cb2b90daa543b544161530c925f285b06196940d6085ca9474d41dc3822c5cb",
|
||||
"c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd864512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f",
|
||||
"scriptTree": [
|
||||
{
|
||||
"id": 0,
|
||||
"script": "2072ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69ac",
|
||||
"leafVersion": 192
|
||||
},
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"script": "202352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8ac",
|
||||
"leafVersion": 192
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"script": "207337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186aac",
|
||||
"leafVersion": 192
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"intermediary": {
|
||||
"leafHashes": [
|
||||
"2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817",
|
||||
"ba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c",
|
||||
"9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf6"
|
||||
],
|
||||
"merkleRoot": "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2",
|
||||
"tweak": "b57bfa183d28eeb6ad688ddaabb265b4a41fbf68e5fed2c72c74de70d5a786f4",
|
||||
"tweakedPubkey": "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605"
|
||||
},
|
||||
"expected": {
|
||||
"scriptPubKey": "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605",
|
||||
"bip350Address": "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e",
|
||||
"scriptPathControlBlocks": [
|
||||
"c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fffe578e9ea769027e4f5a3de40732f75a88a6353a09d767ddeb66accef85e553",
|
||||
"c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf62645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817",
|
||||
"c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d",
|
||||
"scriptTree": [
|
||||
{
|
||||
"id": 0,
|
||||
"script": "2071981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2ac",
|
||||
"leafVersion": 192
|
||||
},
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"script": "20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac",
|
||||
"leafVersion": 192
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"script": "20c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4cac",
|
||||
"leafVersion": 192
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"intermediary": {
|
||||
"leafHashes": [
|
||||
"f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d",
|
||||
"737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711",
|
||||
"d7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7"
|
||||
],
|
||||
"merkleRoot": "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def",
|
||||
"tweak": "6579138e7976dc13b6a92f7bfd5a2fc7684f5ea42419d43368301470f3b74ed9",
|
||||
"tweakedPubkey": "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831"
|
||||
},
|
||||
"expected": {
|
||||
"scriptPubKey": "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831",
|
||||
"bip350Address": "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe",
|
||||
"scriptPathControlBlocks": [
|
||||
"c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d3cd369a528b326bc9d2133cbd2ac21451acb31681a410434672c8e34fe757e91",
|
||||
"c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312dd7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d",
|
||||
"c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"keyPathSpending": [
|
||||
{
|
||||
"given": {
|
||||
"rawUnsignedTx": "02000000097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000fffffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a418420000000000fffffffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffffffaa5202bdf6d8ccd2ee0f0202afbbb7461d9264a25e5bfd3c5a52ee1239e0ba6c0000000000feffffff956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd050000000000000000000e664b9773b88c09c32cb70a2a3e4da0ced63b7ba3b22f848531bbb1d5d5f4c94010000000000000000e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf0000000000ffffffffa778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af10100000000ffffffff0200ca9a3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0065cd1d",
|
||||
"utxosSpent": [
|
||||
{
|
||||
"scriptPubKey": "512053a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343",
|
||||
"amountSats": 420000000
|
||||
},
|
||||
{
|
||||
"scriptPubKey": "5120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3",
|
||||
"amountSats": 462000000
|
||||
},
|
||||
{
|
||||
"scriptPubKey": "76a914751e76e8199196d454941c45d1b3a323f1433bd688ac",
|
||||
"amountSats": 294000000
|
||||
},
|
||||
{
|
||||
"scriptPubKey": "5120e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e",
|
||||
"amountSats": 504000000
|
||||
},
|
||||
{
|
||||
"scriptPubKey": "512091b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605",
|
||||
"amountSats": 630000000
|
||||
},
|
||||
{
|
||||
"scriptPubKey": "00147dd65592d0ab2fe0d0257d571abf032cd9db93dc",
|
||||
"amountSats": 378000000
|
||||
},
|
||||
{
|
||||
"scriptPubKey": "512075169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831",
|
||||
"amountSats": 672000000
|
||||
},
|
||||
{
|
||||
"scriptPubKey": "5120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5",
|
||||
"amountSats": 546000000
|
||||
},
|
||||
{
|
||||
"scriptPubKey": "512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220",
|
||||
"amountSats": 588000000
|
||||
}
|
||||
]
|
||||
},
|
||||
"intermediary": {
|
||||
"hashAmounts": "58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde6",
|
||||
"hashOutputs": "a2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc5",
|
||||
"hashPrevouts": "e3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f",
|
||||
"hashScriptPubkeys": "23ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e21",
|
||||
"hashSequences": "18959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957e"
|
||||
},
|
||||
"inputSpending": [
|
||||
{
|
||||
"given": {
|
||||
"txinIndex": 0,
|
||||
"internalPrivkey": "6b973d88838f27366ed61c9ad6367663045cb456e28335c109e30717ae0c6baa",
|
||||
"merkleRoot": null,
|
||||
"hashType": 3
|
||||
},
|
||||
"intermediary": {
|
||||
"internalPubkey": "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d",
|
||||
"tweak": "b86e7be8f39bab32a6f2c0443abbc210f0edac0e2c53d501b36b64437d9c6c70",
|
||||
"tweakedPrivkey": "2405b971772ad26915c8dcdf10f238753a9b837e5f8e6a86fd7c0cce5b7296d9",
|
||||
"sigMsg": "0003020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957e0000000000d0418f0e9a36245b9a50ec87f8bf5be5bcae434337b87139c3a5b1f56e33cba0",
|
||||
"precomputedUsed": [
|
||||
"hashAmounts",
|
||||
"hashPrevouts",
|
||||
"hashScriptPubkeys",
|
||||
"hashSequences"
|
||||
],
|
||||
"sigHash": "2514a6272f85cfa0f45eb907fcb0d121b808ed37c6ea160a5a9046ed5526d555"
|
||||
},
|
||||
"expected": {
|
||||
"witness": [
|
||||
"ed7c1647cb97379e76892be0cacff57ec4a7102aa24296ca39af7541246d8ff14d38958d4cc1e2e478e4d4a764bbfd835b16d4e314b72937b29833060b87276c03"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"txinIndex": 1,
|
||||
"internalPrivkey": "1e4da49f6aaf4e5cd175fe08a32bb5cb4863d963921255f33d3bc31e1343907f",
|
||||
"merkleRoot": "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21",
|
||||
"hashType": 131
|
||||
},
|
||||
"intermediary": {
|
||||
"internalPubkey": "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27",
|
||||
"tweak": "cbd8679ba636c1110ea247542cfbd964131a6be84f873f7f3b62a777528ed001",
|
||||
"tweakedPrivkey": "ea260c3b10e60f6de018455cd0278f2f5b7e454be1999572789e6a9565d26080",
|
||||
"sigMsg": "0083020000000065cd1d00d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd9900000000808f891b00000000225120147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3ffffffffffcef8fb4ca7efc5433f591ecfc57391811ce1e186a3793024def5c884cba51d",
|
||||
"precomputedUsed": [],
|
||||
"sigHash": "325a644af47e8a5a2591cda0ab0723978537318f10e6a63d4eed783b96a71a4d"
|
||||
},
|
||||
"expected": {
|
||||
"witness": [
|
||||
"052aedffc554b41f52b521071793a6b88d6dbca9dba94cf34c83696de0c1ec35ca9c5ed4ab28059bd606a4f3a657eec0bb96661d42921b5f50a95ad33675b54f83"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"txinIndex": 3,
|
||||
"internalPrivkey": "d3c7af07da2d54f7a7735d3d0fc4f0a73164db638b2f2f7c43f711f6d4aa7e64",
|
||||
"merkleRoot": "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b",
|
||||
"hashType": 1
|
||||
},
|
||||
"intermediary": {
|
||||
"internalPubkey": "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820",
|
||||
"tweak": "6af9e28dbf9d6aaf027696e2598a5b3d056f5fd2355a7fd5a37a0e5008132d30",
|
||||
"tweakedPrivkey": "97323385e57015b75b0339a549c56a948eb961555973f0951f555ae6039ef00d",
|
||||
"sigMsg": "0001020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957ea2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc50003000000",
|
||||
"precomputedUsed": [
|
||||
"hashAmounts",
|
||||
"hashOutputs",
|
||||
"hashPrevouts",
|
||||
"hashScriptPubkeys",
|
||||
"hashSequences"
|
||||
],
|
||||
"sigHash": "bf013ea93474aa67815b1b6cc441d23b64fa310911d991e713cd34c7f5d46669"
|
||||
},
|
||||
"expected": {
|
||||
"witness": [
|
||||
"ff45f742a876139946a149ab4d9185574b98dc919d2eb6754f8abaa59d18b025637a3aa043b91817739554f4ed2026cf8022dbd83e351ce1fabc272841d2510a01"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"txinIndex": 4,
|
||||
"internalPrivkey": "f36bb07a11e469ce941d16b63b11b9b9120a84d9d87cff2c84a8d4affb438f4e",
|
||||
"merkleRoot": "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2",
|
||||
"hashType": 0
|
||||
},
|
||||
"intermediary": {
|
||||
"internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f",
|
||||
"tweak": "b57bfa183d28eeb6ad688ddaabb265b4a41fbf68e5fed2c72c74de70d5a786f4",
|
||||
"tweakedPrivkey": "a8e7aa924f0d58854185a490e6c41f6efb7b675c0f3331b7f14b549400b4d501",
|
||||
"sigMsg": "0000020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957ea2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc50004000000",
|
||||
"precomputedUsed": [
|
||||
"hashAmounts",
|
||||
"hashOutputs",
|
||||
"hashPrevouts",
|
||||
"hashScriptPubkeys",
|
||||
"hashSequences"
|
||||
],
|
||||
"sigHash": "4f900a0bae3f1446fd48490c2958b5a023228f01661cda3496a11da502a7f7ef"
|
||||
},
|
||||
"expected": {
|
||||
"witness": [
|
||||
"b4010dd48a617db09926f729e79c33ae0b4e94b79f04a1ae93ede6315eb3669de185a17d2b0ac9ee09fd4c64b678a0b61a0a86fa888a273c8511be83bfd6810f"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"txinIndex": 6,
|
||||
"internalPrivkey": "415cfe9c15d9cea27d8104d5517c06e9de48e2f986b695e4f5ffebf230e725d8",
|
||||
"merkleRoot": "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def",
|
||||
"hashType": 2
|
||||
},
|
||||
"intermediary": {
|
||||
"internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d",
|
||||
"tweak": "6579138e7976dc13b6a92f7bfd5a2fc7684f5ea42419d43368301470f3b74ed9",
|
||||
"tweakedPrivkey": "241c14f2639d0d7139282aa6abde28dd8a067baa9d633e4e7230287ec2d02901",
|
||||
"sigMsg": "0002020000000065cd1de3b33bb4ef3a52ad1fffb555c0d82828eb22737036eaeb02a235d82b909c4c3f58a6964a4f5f8f0b642ded0a8a553be7622a719da71d1f5befcefcdee8e0fde623ad0f61ad2bca5ba6a7693f50fce988e17c3780bf2b1e720cfbb38fbdd52e2118959c7221ab5ce9e26c3cd67b22c24f8baa54bac281d8e6b05e400e6c3a957e0006000000",
|
||||
"precomputedUsed": [
|
||||
"hashAmounts",
|
||||
"hashPrevouts",
|
||||
"hashScriptPubkeys",
|
||||
"hashSequences"
|
||||
],
|
||||
"sigHash": "15f25c298eb5cdc7eb1d638dd2d45c97c4c59dcaec6679cfc16ad84f30876b85"
|
||||
},
|
||||
"expected": {
|
||||
"witness": [
|
||||
"a3785919a2ce3c4ce26f298c3d51619bc474ae24014bcdd31328cd8cfbab2eff3395fa0a16fe5f486d12f22a9cedded5ae74feb4bbe5351346508c5405bcfee002"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"txinIndex": 7,
|
||||
"internalPrivkey": "c7b0e81f0a9a0b0499e112279d718cca98e79a12e2f137c72ae5b213aad0d103",
|
||||
"merkleRoot": "6c2dc106ab816b73f9d07e3cd1ef2c8c1256f519748e0813e4edd2405d277bef",
|
||||
"hashType": 130
|
||||
},
|
||||
"intermediary": {
|
||||
"internalPubkey": "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592",
|
||||
"tweak": "9e0517edc8259bb3359255400b23ca9507f2a91cd1e4250ba068b4eafceba4a9",
|
||||
"tweakedPrivkey": "65b6000cd2bfa6b7cf736767a8955760e62b6649058cbc970b7c0871d786346b",
|
||||
"sigMsg": "0082020000000065cd1d00e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf00000000804c8b2000000000225120712447206d7a5238acc7ff53fbe94a3b64539ad291c7cdbc490b7577e4b17df5ffffffff",
|
||||
"precomputedUsed": [],
|
||||
"sigHash": "cd292de50313804dabe4685e83f923d2969577191a3e1d2882220dca88cbeb10"
|
||||
},
|
||||
"expected": {
|
||||
"witness": [
|
||||
"ea0c6ba90763c2d3a296ad82ba45881abb4f426b3f87af162dd24d5109edc1cdd11915095ba47c3a9963dc1e6c432939872bc49212fe34c632cd3ab9fed429c482"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"txinIndex": 8,
|
||||
"internalPrivkey": "77863416be0d0665e517e1c375fd6f75839544eca553675ef7fdf4949518ebaa",
|
||||
"merkleRoot": "ab179431c28d3b68fb798957faf5497d69c883c6fb1e1cd9f81483d87bac90cc",
|
||||
"hashType": 129
|
||||
},
|
||||
"intermediary": {
|
||||
"internalPubkey": "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8",
|
||||
"tweak": "639f0281b7ac49e742cd25b7f188657626da1ad169209078e2761cefd91fd65e",
|
||||
"tweakedPrivkey": "ec18ce6af99f43815db543f47b8af5ff5df3b2cb7315c955aa4a86e8143d2bf5",
|
||||
"sigMsg": "0081020000000065cd1da2e6dab7c1f0dcd297c8d61647fd17d821541ea69c3cc37dcbad7f90d4eb4bc500a778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af101000000002b0c230000000022512077e30a5522dd9f894c3f8b8bd4c4b2cf82ca7da8a3ea6a239655c39c050ab220ffffffff",
|
||||
"precomputedUsed": [
|
||||
"hashOutputs"
|
||||
],
|
||||
"sigHash": "cccb739eca6c13a8a89e6e5cd317ffe55669bbda23f2fd37b0f18755e008edd2"
|
||||
},
|
||||
"expected": {
|
||||
"witness": [
|
||||
"bbc9584a11074e83bc8c6759ec55401f0ae7b03ef290c3139814f545b58a9f8127258000874f44bc46db7646322107d4d86aec8e73b8719a61fff761d75b5dd981"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"auxiliary": {
|
||||
"fullySignedTx": "020000000001097de20cbff686da83a54981d2b9bab3586f4ca7e48f57f5b55963115f3b334e9c010000000000000000d7b7cab57b1393ace2d064f4d4a2cb8af6def61273e127517d44759b6dafdd990000000000fffffffff8e1f583384333689228c5d28eac13366be082dc57441760d957275419a41842000000006b4830450221008f3b8f8f0537c420654d2283673a761b7ee2ea3c130753103e08ce79201cf32a022079e7ab904a1980ef1c5890b648c8783f4d10103dd62f740d13daa79e298d50c201210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798fffffffff0689180aa63b30cb162a73c6d2a38b7eeda2a83ece74310fda0843ad604853b0100000000feffffffaa5202bdf6d8ccd2ee0f0202afbbb7461d9264a25e5bfd3c5a52ee1239e0ba6c0000000000feffffff956149bdc66faa968eb2be2d2faa29718acbfe3941215893a2a3446d32acd050000000000000000000e664b9773b88c09c32cb70a2a3e4da0ced63b7ba3b22f848531bbb1d5d5f4c94010000000000000000e9aa6b8e6c9de67619e6a3924ae25696bb7b694bb677a632a74ef7eadfd4eabf0000000000ffffffffa778eb6a263dc090464cd125c466b5a99667720b1c110468831d058aa1b82af10100000000ffffffff0200ca9a3b000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac807840cb0000000020ac9a87f5594be208f8532db38cff670c450ed2fea8fcdefcc9a663f78bab962b0141ed7c1647cb97379e76892be0cacff57ec4a7102aa24296ca39af7541246d8ff14d38958d4cc1e2e478e4d4a764bbfd835b16d4e314b72937b29833060b87276c030141052aedffc554b41f52b521071793a6b88d6dbca9dba94cf34c83696de0c1ec35ca9c5ed4ab28059bd606a4f3a657eec0bb96661d42921b5f50a95ad33675b54f83000141ff45f742a876139946a149ab4d9185574b98dc919d2eb6754f8abaa59d18b025637a3aa043b91817739554f4ed2026cf8022dbd83e351ce1fabc272841d2510a010140b4010dd48a617db09926f729e79c33ae0b4e94b79f04a1ae93ede6315eb3669de185a17d2b0ac9ee09fd4c64b678a0b61a0a86fa888a273c8511be83bfd6810f0247304402202b795e4de72646d76eab3f0ab27dfa30b810e856ff3a46c9a702df53bb0d8cc302203ccc4d822edab5f35caddb10af1be93583526ccfbade4b4ead350781e2f8adcd012102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f90141a3785919a2ce3c4ce26f298c3d51619bc474ae24014bcdd31328cd8cfbab2eff3395fa0a16fe5f486d12f22a9cedded5ae74feb4bbe5351346508c5405bcfee0020141ea0c6ba90763c2d3a296ad82ba45881abb4f426b3f87af162dd24d5109edc1cdd11915095ba47c3a9963dc1e6c432939872bc49212fe34c632cd3ab9fed429c4820141bbc9584a11074e83bc8c6759ec55401f0ae7b03ef290c3139814f545b58a9f8127258000874f44bc46db7646322107d4d86aec8e73b8719a61fff761d75b5dd9810065cd1d"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+50
-1
@@ -1,7 +1,10 @@
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from electrum import bitcoin
|
||||
from electrum.bitcoin import (public_key_to_p2pkh, address_from_private_key,
|
||||
is_address, is_private_key,
|
||||
var_int, _op_push, address_to_script, OnchainOutputType, address_to_payload,
|
||||
@@ -9,7 +12,9 @@ from electrum.bitcoin import (public_key_to_p2pkh, address_from_private_key,
|
||||
is_b58_address, address_to_scripthash, is_minikey,
|
||||
is_compressed_privkey, EncodeBase58Check, DecodeBase58Check,
|
||||
script_num_to_bytes, push_script, add_number_to_script,
|
||||
opcodes, base_encode, base_decode, BitcoinException)
|
||||
opcodes, base_encode, base_decode, BitcoinException,
|
||||
taproot_tweak_pubkey, taproot_tweak_seckey, taproot_output_script,
|
||||
control_block_for_taproot_script_spend)
|
||||
from electrum import bip32
|
||||
from electrum import segwit_addr
|
||||
from electrum.segwit_addr import DecodedBech32
|
||||
@@ -1229,3 +1234,47 @@ class TestBaseEncode(ElectrumTestCase):
|
||||
data_base58check)
|
||||
self.assertEqual(data_bytes,
|
||||
DecodeBase58Check(data_base58check))
|
||||
|
||||
|
||||
class TestTaprootHelpers(ElectrumTestCase):
|
||||
|
||||
def test_taproot_tweak_homomorphism(self):
|
||||
# For any byte string h it holds that
|
||||
# taproot_tweak_pubkey(pubkey_gen(seckey), h)[1] == pubkey_gen(taproot_tweak_seckey(seckey, h)).
|
||||
for secret_scalar in (8, 11, 99999):
|
||||
privkey = ecc.ECPrivkey.from_secret_scalar(secret_scalar)
|
||||
pubkey32 = privkey.get_public_key_bytes(compressed=True)[1:]
|
||||
for tree_hash in (b"", b"satoshi", b"1234"*8, bytes(range(100)), ):
|
||||
tweaked_pubkey = taproot_tweak_pubkey(pubkey32, tree_hash)[1]
|
||||
tweaked_seckey = taproot_tweak_seckey(privkey.get_secret_bytes(), tree_hash)
|
||||
self.assertEqual(tweaked_pubkey, ecc.ECPrivkey(tweaked_seckey).get_public_key_bytes(compressed=True)[1:])
|
||||
|
||||
def test_taproot_output_script(self):
|
||||
# test vectors from https://github.com/bitcoin/bips/blob/70d9b07ab80ab3c267ece48f74e4e2250226d0cc/bip-0341/wallet-test-vectors.json
|
||||
test_vector_file = os.path.join(os.path.dirname(__file__), "bip-0341", "wallet-test-vectors.json")
|
||||
with open(test_vector_file, "r") as f:
|
||||
vectors = json.load(f)
|
||||
def transform_tree(tree_node):
|
||||
if isinstance(tree_node, dict):
|
||||
return (tree_node["leafVersion"], bfh(tree_node["script"]))
|
||||
assert len(tree_node) == 2, len(tree_node)
|
||||
return [transform_tree(tree_node[0]), transform_tree(tree_node[1])]
|
||||
def flatten_tree(tree_node):
|
||||
if isinstance(tree_node, tuple):
|
||||
return [tree_node]
|
||||
assert len(tree_node) == 2, len(tree_node)
|
||||
return flatten_tree(tree_node[0]) + flatten_tree(tree_node[1])
|
||||
assert len(vectors["scriptPubKey"]) > 0, "test vectors missing"
|
||||
for tcase in vectors["scriptPubKey"]:
|
||||
script_tree = transform_tree(tcase["given"]["scriptTree"]) if tcase["given"]["scriptTree"] else None
|
||||
internal_pubkey = bfh(tcase["given"]["internalPubkey"])
|
||||
spk = taproot_output_script(internal_pubkey, script_tree=script_tree)
|
||||
self.assertEqual(bfh(tcase["expected"]["scriptPubKey"]), spk)
|
||||
self.assertEqual(tcase["expected"]["bip350Address"], bitcoin.script_to_address(spk))
|
||||
if script_tree:
|
||||
flat_tree = flatten_tree(script_tree)
|
||||
for script_num, jcontrol_block in enumerate(tcase["expected"]["scriptPathControlBlocks"]):
|
||||
leaf_script, control_block = control_block_for_taproot_script_spend(
|
||||
internal_pubkey=internal_pubkey, script_tree=script_tree, script_num=script_num)
|
||||
self.assertEqual(jcontrol_block, control_block.hex())
|
||||
self.assertEqual(flat_tree[script_num][1].hex(), leaf_script.hex())
|
||||
|
||||
@@ -227,19 +227,36 @@ class TestDescriptor(ElectrumTestCase):
|
||||
self.assertEqual(desc.pubkeys[0].origin.get_derivation_path(), "m/84h/1h/0h")
|
||||
self.assertEqual(desc.pubkeys[0].pubkey, "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B")
|
||||
self.assertEqual(desc.pubkeys[0].deriv_path, "/0/0")
|
||||
self.assertEqual(desc.get_max_tree_depth(), None)
|
||||
self.assertEqual(desc.to_string_no_checksum(), d)
|
||||
|
||||
d = "tr([00000001/84h/1h/0h]tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/0/0,{pk(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B),{{pk(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B),pk(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B)},pk(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B)}})"
|
||||
desc = parse_descriptor(d)
|
||||
self.assertTrue(isinstance(desc, TRDescriptor))
|
||||
self.assertEqual(len(desc.subdescriptors), 4)
|
||||
self.assertEqual(len(desc.desc_tree), 2)
|
||||
self.assertEqual(len(desc.pubkeys), 1)
|
||||
self.assertEqual(desc.pubkeys[0].origin.fingerprint.hex(), "00000001")
|
||||
self.assertEqual(desc.pubkeys[0].origin.get_derivation_path(), "m/84h/1h/0h")
|
||||
self.assertEqual(desc.pubkeys[0].pubkey, "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B")
|
||||
self.assertEqual(desc.pubkeys[0].deriv_path, "/0/0")
|
||||
self.assertEqual(desc.depths, [1, 3, 3, 2])
|
||||
self.assertEqual(desc.desc_tree[0].to_string_no_checksum(), "pk(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B)")
|
||||
self.assertEqual(desc.desc_tree[1][0][0].to_string_no_checksum(), "pk(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B)")
|
||||
self.assertEqual(desc.desc_tree[1][0][1].to_string_no_checksum(), "pk(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B)")
|
||||
self.assertEqual(desc.desc_tree[1][1].to_string_no_checksum(), "pk(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B)")
|
||||
self.assertEqual(desc.get_max_tree_depth(), 3)
|
||||
self.assertEqual(desc.to_string_no_checksum(), d)
|
||||
|
||||
def test_tr_descriptor_bip386(self):
|
||||
# test vectors from https://github.com/bitcoin/bips/blob/e2f7481a132e1c5863f5ffcbff009964d7c2af20/bip-0386.mediawiki#test-vectors
|
||||
# TODO add missing tests
|
||||
self.assertEqual(
|
||||
"512077aab6e066f8a7419c5ab714c12c67d25007ed55a43cadcacb4d7a970a093f11",
|
||||
parse_descriptor("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)").expand().output_script.hex())
|
||||
self.assertEqual(
|
||||
"512017cf18db381d836d8923b1bdb246cfcd818da1a9f0e6e7907f187f0b2f937754",
|
||||
parse_descriptor("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pk(669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0))").expand().output_script.hex())
|
||||
|
||||
@as_testnet
|
||||
def test_parse_descriptor_with_range(self):
|
||||
d = "wpkh([00000001/84h/1h/0h]tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/0/*)"
|
||||
|
||||
+2
-2
@@ -58,7 +58,7 @@ class TestValidPSBT(ElectrumTestCase):
|
||||
self.assertTrue(tx.inputs()[0].redeem_script is not None)
|
||||
self.assertTrue(tx.inputs()[0].witness_script is not None)
|
||||
self.assertEqual(2, len(tx.inputs()[0].bip32_paths))
|
||||
self.assertEqual(1, len(tx.inputs()[0].part_sigs))
|
||||
self.assertEqual(1, len(tx.inputs()[0].sigs_ecdsa))
|
||||
|
||||
def test_valid_psbt_006(self):
|
||||
# Case: PSBT with one P2WSH input of a 2-of-2 multisig. witnessScript, keypaths, and global xpubs are available. Contains no signatures. Outputs filled.
|
||||
@@ -70,7 +70,7 @@ class TestValidPSBT(ElectrumTestCase):
|
||||
self.assertTrue(tx.inputs()[0].witness_script is not None)
|
||||
self.assertEqual(2, len(tx.inputs()[0].bip32_paths))
|
||||
self.assertEqual(2, len(tx.xpubs))
|
||||
self.assertEqual(0, len(tx.inputs()[0].part_sigs))
|
||||
self.assertEqual(0, len(tx.inputs()[0].sigs_ecdsa))
|
||||
|
||||
def test_valid_psbt_007(self):
|
||||
# Case: PSBT with unknown types in the inputs.
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import json
|
||||
import os
|
||||
from typing import NamedTuple, Union
|
||||
|
||||
from electrum import transaction, bitcoin
|
||||
from electrum.transaction import (convert_raw_tx_to_hex, tx_from_any, Transaction,
|
||||
PartialTransaction, TxOutpoint, PartialTxInput,
|
||||
PartialTxOutput, Sighash, match_script_against_template,
|
||||
SCRIPTPUBKEY_TEMPLATE_ANYSEGWIT)
|
||||
SCRIPTPUBKEY_TEMPLATE_ANYSEGWIT, TxOutput)
|
||||
from electrum.util import bfh
|
||||
from electrum.bitcoin import (deserialize_privkey, opcodes,
|
||||
construct_script, construct_witness)
|
||||
@@ -929,7 +931,7 @@ class TestTransactionTestnet(ElectrumTestCase):
|
||||
self.assertEqual('020000000001019ad573c69e60c209e0ff36f281ae4f700a8d59f846e7ff5c020352fd1e97808600000000000000000001fa840100000000001600145a209b202bc19b3d345a75cf8ab51cb471913a790247304402207b191c1e3ff1a2d3541770b496c9f871406114746b3aa7347ec4ef0423d3a975022043d3a746fa7a794d97e95d74b6d17d618dfc4cd7644476813e08006f271e51bd012a046c4f855fb1752102aec53aa5f347219a7378b13006eb16ce48125f9cf14f04a5509a565ad5e51507ac6c4f855f',
|
||||
tx.serialize())
|
||||
|
||||
class TestSighashTypes(ElectrumTestCase):
|
||||
class TestSighashBIP143(ElectrumTestCase):
|
||||
#These tests are taken from bip143, https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
|
||||
#Input of transaction
|
||||
locktime=0
|
||||
@@ -992,3 +994,36 @@ class TestSighashTypes(ElectrumTestCase):
|
||||
sig = tx.sign_txin(0,privkey)
|
||||
self.assertEqual('30440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783',
|
||||
sig.hex())
|
||||
|
||||
|
||||
class TestSighashBIP341(ElectrumTestCase):
|
||||
|
||||
def test_taproot_keypath_spending(self):
|
||||
test_vector_file = os.path.join(os.path.dirname(__file__), "bip-0341", "wallet-test-vectors.json")
|
||||
with open(test_vector_file, "r") as f:
|
||||
vectors = json.load(f)
|
||||
assert len(vectors["keyPathSpending"]) > 0, "test vectors missing"
|
||||
for tcase in vectors["keyPathSpending"]:
|
||||
unsigned_tx = Transaction(tcase["given"]["rawUnsignedTx"])
|
||||
self.assertEqual(len(tcase["given"]["utxosSpent"]), len(unsigned_tx.inputs()))
|
||||
tx = PartialTransaction.from_tx(unsigned_tx)
|
||||
# add utxo data
|
||||
for txin, json_utxo in zip(tx.inputs(), tcase["given"]["utxosSpent"]):
|
||||
txin.witness_utxo = TxOutput(scriptpubkey=bfh(json_utxo["scriptPubKey"]), value=int(json_utxo["amountSats"]))
|
||||
for txin_test in tcase["inputSpending"]:
|
||||
txin_idx = txin_test["given"]["txinIndex"]
|
||||
txin = tx.inputs()[txin_idx]
|
||||
txin.sighash = int(txin_test["given"]["hashType"])
|
||||
txin.tap_merkle_root = bfh(txin_test["given"]["merkleRoot"]) if txin_test["given"]["merkleRoot"] else None
|
||||
pre_hash = tx.serialize_preimage(txin_idx)
|
||||
self.assertEqual(txin_test["intermediary"]["sigMsg"], pre_hash.hex())
|
||||
privkey = bfh(txin_test["given"]["internalPrivkey"])
|
||||
sig = tx.sign_txin(txin_idx, privkey)
|
||||
assert len(txin_test["expected"]["witness"]) == 1
|
||||
self.assertEqual(txin_test["expected"]["witness"][0], sig.hex())
|
||||
txin.witness = construct_witness([sig])
|
||||
txin.script_sig = b""
|
||||
self.assertTrue(txin.is_complete())
|
||||
# note: some input utxos are not taproot, and there is no key data for them
|
||||
# - txin_idx=2, addr 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
|
||||
# - txin_idx=5, addr bc1q0ht9tyks4vh7p5p904t340cr9nvahy7u3re7zg
|
||||
|
||||
Reference in New Issue
Block a user