|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
|
|
from trezor import utils, wire
|
|
|
|
from trezor.crypto import base58, cashaddr
|
|
|
|
from trezor.crypto.hashlib import sha256
|
|
|
|
from trezor.enums import InputScriptType
|
|
|
|
|
|
|
|
from apps.common import address_type
|
|
|
|
from apps.common.readers import read_compact_size
|
|
|
|
from apps.common.writers import write_compact_size
|
|
|
|
|
|
|
|
from . import common
|
|
|
|
from .common import SigHashType
|
|
|
|
from .multisig import (
|
|
|
|
multisig_get_pubkey_count,
|
|
|
|
multisig_get_pubkeys,
|
|
|
|
multisig_pubkey_index,
|
|
|
|
)
|
|
|
|
from .readers import read_memoryview_prefixed, read_op_push
|
|
|
|
from .writers import (
|
|
|
|
op_push_length,
|
|
|
|
write_bytes_fixed,
|
|
|
|
write_bytes_prefixed,
|
|
|
|
write_bytes_unchecked,
|
|
|
|
write_op_push,
|
|
|
|
)
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
from typing import Sequence
|
|
|
|
|
|
|
|
from trezor.messages import MultisigRedeemScriptType, TxInput
|
|
|
|
|
|
|
|
from apps.common.coininfo import CoinInfo
|
|
|
|
|
|
|
|
from .writers import Writer
|
|
|
|
|
|
|
|
|
|
|
|
def write_input_script_prefixed(
|
|
|
|
w: Writer,
|
|
|
|
script_type: InputScriptType,
|
|
|
|
multisig: MultisigRedeemScriptType | None,
|
|
|
|
coin: CoinInfo,
|
|
|
|
sighash_type: SigHashType,
|
|
|
|
pubkey: bytes,
|
|
|
|
signature: bytes,
|
|
|
|
) -> None:
|
|
|
|
if script_type == InputScriptType.SPENDADDRESS:
|
|
|
|
# p2pkh or p2sh
|
|
|
|
write_input_script_p2pkh_or_p2sh_prefixed(w, pubkey, signature, sighash_type)
|
|
|
|
elif script_type == InputScriptType.SPENDP2SHWITNESS:
|
|
|
|
# p2wpkh or p2wsh using p2sh
|
|
|
|
|
|
|
|
if multisig is not None:
|
|
|
|
# p2wsh in p2sh
|
|
|
|
pubkeys = multisig_get_pubkeys(multisig)
|
|
|
|
witness_script_h = utils.HashWriter(sha256())
|
|
|
|
write_output_script_multisig(witness_script_h, pubkeys, multisig.m)
|
|
|
|
write_input_script_p2wsh_in_p2sh(
|
|
|
|
w, witness_script_h.get_digest(), prefixed=True
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
# p2wpkh in p2sh
|
|
|
|
write_input_script_p2wpkh_in_p2sh(
|
|
|
|
w, common.ecdsa_hash_pubkey(pubkey, coin), prefixed=True
|
|
|
|
)
|
|
|
|
elif script_type in (InputScriptType.SPENDWITNESS, InputScriptType.SPENDTAPROOT):
|
|
|
|
# native p2wpkh or p2wsh or p2tr
|
|
|
|
script_sig = input_script_native_segwit()
|
|
|
|
write_bytes_prefixed(w, script_sig)
|
|
|
|
elif script_type == InputScriptType.SPENDMULTISIG:
|
|
|
|
# p2sh multisig
|
|
|
|
assert multisig is not None # checked in sanitize_tx_input
|
|
|
|
signature_index = multisig_pubkey_index(multisig, pubkey)
|
|
|
|
write_input_script_multisig_prefixed(
|
|
|
|
w, multisig, signature, signature_index, sighash_type, coin
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
raise wire.ProcessError("Invalid script type")
|
|
|
|
|
|
|
|
|
|
|
|
def output_derive_script(address: str, coin: CoinInfo) -> bytes:
|
|
|
|
if coin.bech32_prefix and address.startswith(coin.bech32_prefix):
|
|
|
|
# p2wpkh or p2wsh or p2tr
|
|
|
|
witver, witprog = common.decode_bech32_address(coin.bech32_prefix, address)
|
|
|
|
return output_script_native_segwit(witver, witprog)
|
|
|
|
|
|
|
|
if (
|
|
|
|
not utils.BITCOIN_ONLY
|
|
|
|
and coin.cashaddr_prefix is not None
|
|
|
|
and address.startswith(coin.cashaddr_prefix + ":")
|
|
|
|
):
|
|
|
|
prefix, addr = address.split(":")
|
|
|
|
version, data = cashaddr.decode(prefix, addr)
|
|
|
|
if version == cashaddr.ADDRESS_TYPE_P2KH:
|
|
|
|
version = coin.address_type
|
|
|
|
elif version == cashaddr.ADDRESS_TYPE_P2SH:
|
|
|
|
version = coin.address_type_p2sh
|
|
|
|
else:
|
|
|
|
raise wire.DataError("Unknown cashaddr address type")
|
|
|
|
raw_address = bytes([version]) + data
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
raw_address = base58.decode_check(address, coin.b58_hash)
|
|
|
|
except ValueError:
|
|
|
|
raise wire.DataError("Invalid address")
|
|
|
|
|
|
|
|
if address_type.check(coin.address_type, raw_address):
|
|
|
|
# p2pkh
|
|
|
|
pubkeyhash = address_type.strip(coin.address_type, raw_address)
|
|
|
|
script = output_script_p2pkh(pubkeyhash)
|
|
|
|
return script
|
|
|
|
elif address_type.check(coin.address_type_p2sh, raw_address):
|
|
|
|
# p2sh
|
|
|
|
scripthash = address_type.strip(coin.address_type_p2sh, raw_address)
|
|
|
|
script = output_script_p2sh(scripthash)
|
|
|
|
return script
|
|
|
|
|
|
|
|
raise wire.DataError("Invalid address type")
|
|
|
|
|
|
|
|
|
|
|
|
# see https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#specification
|
|
|
|
# item 5 for details
|
|
|
|
def write_bip143_script_code_prefixed(
|
|
|
|
w: Writer,
|
|
|
|
txi: TxInput,
|
|
|
|
public_keys: Sequence[bytes | memoryview],
|
|
|
|
threshold: int,
|
|
|
|
coin: CoinInfo,
|
|
|
|
) -> None:
|
|
|
|
if len(public_keys) > 1:
|
|
|
|
write_output_script_multisig(w, public_keys, threshold, prefixed=True)
|
|
|
|
return
|
|
|
|
|
|
|
|
p2pkh = txi.script_type in (
|
|
|
|
InputScriptType.SPENDWITNESS,
|
|
|
|
InputScriptType.SPENDP2SHWITNESS,
|
|
|
|
InputScriptType.SPENDADDRESS,
|
|
|
|
InputScriptType.EXTERNAL,
|
|
|
|
)
|
|
|
|
if p2pkh:
|
|
|
|
# for p2wpkh in p2sh or native p2wpkh
|
|
|
|
# the scriptCode is a classic p2pkh
|
|
|
|
write_output_script_p2pkh(
|
|
|
|
w, common.ecdsa_hash_pubkey(public_keys[0], coin), prefixed=True
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
raise wire.DataError("Unknown input script type for bip143 script code")
|
|
|
|
|
|
|
|
|
|
|
|
# P2PKH, P2SH
|
|
|
|
# ===
|
|
|
|
# https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki
|
|
|
|
|
|
|
|
|
|
|
|
def write_input_script_p2pkh_or_p2sh_prefixed(
|
|
|
|
w: Writer, pubkey: bytes, signature: bytes, sighash_type: SigHashType
|
|
|
|
) -> None:
|
|
|
|
write_compact_size(w, 1 + len(signature) + 1 + 1 + len(pubkey))
|
|
|
|
append_signature(w, signature, sighash_type)
|
|
|
|
append_pubkey(w, pubkey)
|
|
|
|
|
|
|
|
|
|
|
|
def parse_input_script_p2pkh(
|
|
|
|
script_sig: bytes,
|
|
|
|
) -> tuple[memoryview, memoryview, SigHashType]:
|
|
|
|
try:
|
|
|
|
r = utils.BufferReader(script_sig)
|
|
|
|
n = read_op_push(r)
|
|
|
|
signature = r.read_memoryview(n - 1)
|
|
|
|
sighash_type = SigHashType.from_int(r.get())
|
|
|
|
|
|
|
|
n = read_op_push(r)
|
|
|
|
pubkey = r.read_memoryview()
|
|
|
|
if len(pubkey) != n:
|
|
|
|
raise ValueError
|
|
|
|
except (ValueError, EOFError):
|
|
|
|
raise wire.DataError("Invalid scriptSig.")
|
|
|
|
|
|
|
|
return pubkey, signature, sighash_type
|
|
|
|
|
|
|
|
|
|
|
|
def write_output_script_p2pkh(
|
|
|
|
w: Writer, pubkeyhash: bytes, prefixed: bool = False
|
|
|
|
) -> None:
|
|
|
|
if prefixed:
|
|
|
|
write_compact_size(w, 25)
|
|
|
|
w.append(0x76) # OP_DUP
|
|
|
|
w.append(0xA9) # OP_HASH160
|
|
|
|
w.append(0x14) # OP_DATA_20
|
|
|
|
write_bytes_fixed(w, pubkeyhash, 20)
|
|
|
|
w.append(0x88) # OP_EQUALVERIFY
|
|
|
|
w.append(0xAC) # OP_CHECKSIG
|
|
|
|
|
|
|
|
|
|
|
|
def output_script_p2pkh(pubkeyhash: bytes) -> bytearray:
|
|
|
|
s = utils.empty_bytearray(25)
|
|
|
|
write_output_script_p2pkh(s, pubkeyhash)
|
|
|
|
return s
|
|
|
|
|
|
|
|
|
|
|
|
def output_script_p2sh(scripthash: bytes) -> bytearray:
|
|
|
|
# A9 14 <scripthash> 87
|
|
|
|
utils.ensure(len(scripthash) == 20)
|
|
|
|
s = bytearray(23)
|
|
|
|
s[0] = 0xA9 # OP_HASH_160
|
|
|
|
s[1] = 0x14 # pushing 20 bytes
|
|
|
|
s[2:22] = scripthash
|
|
|
|
s[22] = 0x87 # OP_EQUAL
|
|
|
|
return s
|
|
|
|
|
|
|
|
|
|
|
|
# SegWit: Native P2WPKH or P2WSH or P2TR
|
|
|
|
# ===
|
|
|
|
#
|
|
|
|
# P2WPKH (Pay-to-Witness-Public-Key-Hash) is native SegWit version 0 P2PKH.
|
|
|
|
# Not backwards compatible.
|
|
|
|
# https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh
|
|
|
|
#
|
|
|
|
# P2WSH (Pay-to-Witness-Script-Hash) is native SegWit version 0 P2SH.
|
|
|
|
# Not backwards compatible.
|
|
|
|
# https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wsh
|
|
|
|
#
|
|
|
|
# P2TR (Pay-to-Taproot) is native SegWit version 1.
|
|
|
|
# Not backwards compatible.
|
|
|
|
# https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#script-validation-rules
|
|
|
|
|
|
|
|
|
|
|
|
def input_script_native_segwit() -> bytearray:
|
|
|
|
# Completely replaced by the witness and therefore empty.
|
|
|
|
return bytearray(0)
|
|
|
|
|
|
|
|
|
|
|
|
def output_script_native_segwit(witver: int, witprog: bytes) -> bytearray:
|
|
|
|
# Either:
|
|
|
|
# 00 14 <20-byte-key-hash>
|
|
|
|
# 00 20 <32-byte-script-hash>
|
|
|
|
# 51 20 <32-byte-taproot-output-key>
|
|
|
|
length = len(witprog)
|
|
|
|
utils.ensure((length == 20 and witver == 0) or length == 32)
|
|
|
|
|
|
|
|
w = utils.empty_bytearray(2 + length)
|
|
|
|
w.append(witver + 0x50 if witver else 0) # witness version byte (OP_witver)
|
|
|
|
w.append(length) # witness program length is 20 (P2WPKH) or 32 (P2WSH, P2TR) bytes
|
|
|
|
write_bytes_fixed(w, witprog, length)
|
|
|
|
return w
|
|
|
|
|
|
|
|
|
|
|
|
def parse_output_script_p2tr(script_pubkey: bytes) -> memoryview:
|
|
|
|
# 51 20 <32-byte-taproot-output-key>
|
|
|
|
try:
|
|
|
|
r = utils.BufferReader(script_pubkey)
|
|
|
|
|
|
|
|
if r.get() != common.OP_1:
|
|
|
|
# P2TR should be SegWit version 1
|
|
|
|
raise ValueError
|
|
|
|
|
|
|
|
if r.get() != 32:
|
|
|
|
# taproot output key should be 32 bytes
|
|
|
|
raise ValueError
|
|
|
|
|
|
|
|
pubkey = r.read_memoryview(32)
|
|
|
|
if r.remaining_count():
|
|
|
|
raise ValueError
|
|
|
|
except (ValueError, EOFError):
|
|
|
|
raise wire.DataError("Invalid scriptPubKey.")
|
|
|
|
|
|
|
|
return pubkey
|
|
|
|
|
|
|
|
|
|
|
|
# SegWit: P2WPKH nested in P2SH
|
|
|
|
# ===
|
|
|
|
# https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#witness-program
|
|
|
|
#
|
|
|
|
# P2WPKH is nested in P2SH to be backwards compatible.
|
|
|
|
# Uses normal P2SH output scripts.
|
|
|
|
|
|
|
|
|
|
|
|
def write_input_script_p2wpkh_in_p2sh(
|
|
|
|
w: Writer, pubkeyhash: bytes, prefixed: bool = False
|
|
|
|
) -> None:
|
|
|
|
# 16 00 14 <pubkeyhash>
|
|
|
|
# Signature is moved to the witness.
|
|
|
|
if prefixed:
|
|
|
|
write_compact_size(w, 23)
|
|
|
|
|
|
|
|
w.append(0x16) # length of the data
|
|
|
|
w.append(0x00) # witness version byte
|
|
|
|
w.append(0x14) # P2WPKH witness program (pub key hash length)
|
|
|
|
write_bytes_fixed(w, pubkeyhash, 20) # pub key hash
|
|
|
|
|
|
|
|
|
|
|
|
# SegWit: P2WSH nested in P2SH
|
|
|
|
# ===
|
|
|
|
# https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wsh-nested-in-bip16-p2sh
|
|
|
|
#
|
|
|
|
# P2WSH is nested in P2SH to be backwards compatible.
|
|
|
|
# Uses normal P2SH output scripts.
|
|
|
|
|
|
|
|
|
|
|
|
def write_input_script_p2wsh_in_p2sh(
|
|
|
|
w: Writer, script_hash: bytes, prefixed: bool = False
|
|
|
|
) -> None:
|
|
|
|
# 22 00 20 <redeem script hash>
|
|
|
|
# Signature is moved to the witness.
|
|
|
|
if prefixed:
|
|
|
|
write_compact_size(w, 35)
|
|
|
|
|
|
|
|
w.append(0x22) # length of the data
|
|
|
|
w.append(0x00) # witness version byte
|
|
|
|
w.append(0x20) # P2WSH witness program (redeem script hash length)
|
|
|
|
write_bytes_fixed(w, script_hash, 32)
|
|
|
|
|
|
|
|
|
|
|
|
# SegWit: Witness getters
|
|
|
|
# ===
|
|
|
|
|
|
|
|
|
|
|
|
def write_witness_p2wpkh(
|
|
|
|
w: Writer, signature: bytes, pubkey: bytes, sighash_type: SigHashType
|
|
|
|
) -> None:
|
|
|
|
write_compact_size(w, 0x02) # num of segwit items, in P2WPKH it's always 2
|
|
|
|
write_signature_prefixed(w, signature, sighash_type)
|
|
|
|
write_bytes_prefixed(w, pubkey)
|
|
|
|
|
|
|
|
|
|
|
|
def parse_witness_p2wpkh(witness: bytes) -> tuple[memoryview, memoryview, SigHashType]:
|
|
|
|
try:
|
|
|
|
r = utils.BufferReader(witness)
|
|
|
|
|
|
|
|
if r.get() != 2:
|
|
|
|
# num of stack items, in P2WPKH it's always 2
|
|
|
|
raise ValueError
|
|
|
|
|
|
|
|
n = read_compact_size(r)
|
|
|
|
signature = r.read_memoryview(n - 1)
|
|
|
|
sighash_type = SigHashType.from_int(r.get())
|
|
|
|
|
|
|
|
pubkey = read_memoryview_prefixed(r)
|
|
|
|
if r.remaining_count():
|
|
|
|
raise ValueError
|
|
|
|
except (ValueError, EOFError):
|
|
|
|
raise wire.DataError("Invalid witness.")
|
|
|
|
|
|
|
|
return pubkey, signature, sighash_type
|
|
|
|
|
|
|
|
|
|
|
|
def write_witness_multisig(
|
|
|
|
w: Writer,
|
|
|
|
multisig: MultisigRedeemScriptType,
|
|
|
|
signature: bytes,
|
|
|
|
signature_index: int,
|
|
|
|
sighash_type: SigHashType,
|
|
|
|
) -> None:
|
|
|
|
# get other signatures, stretch with empty bytes to the number of the pubkeys
|
|
|
|
signatures = multisig.signatures + [b""] * (
|
|
|
|
multisig_get_pubkey_count(multisig) - len(multisig.signatures)
|
|
|
|
)
|
|
|
|
|
|
|
|
# fill in our signature
|
|
|
|
if signatures[signature_index]:
|
|
|
|
raise wire.DataError("Invalid multisig parameters")
|
|
|
|
signatures[signature_index] = signature
|
|
|
|
|
|
|
|
# witness program + signatures + redeem script
|
|
|
|
num_of_witness_items = 1 + sum(1 for s in signatures if s) + 1
|
|
|
|
write_compact_size(w, num_of_witness_items)
|
|
|
|
|
|
|
|
# Starts with OP_FALSE because of an old OP_CHECKMULTISIG bug, which
|
|
|
|
# consumes one additional item on the stack:
|
|
|
|
# https://bitcoin.org/en/developer-guide#standard-transactions
|
|
|
|
write_compact_size(w, 0)
|
|
|
|
|
|
|
|
for s in signatures:
|
|
|
|
if s:
|
|
|
|
write_signature_prefixed(w, s, sighash_type) # size of the witness included
|
|
|
|
|
|
|
|
# redeem script
|
|
|
|
pubkeys = multisig_get_pubkeys(multisig)
|
|
|
|
write_output_script_multisig(w, pubkeys, multisig.m, prefixed=True)
|
|
|
|
|
|
|
|
|
|
|
|
def parse_witness_multisig(
|
|
|
|
witness: bytes,
|
|
|
|
) -> tuple[memoryview, list[tuple[memoryview, SigHashType]]]:
|
|
|
|
try:
|
|
|
|
r = utils.BufferReader(witness)
|
|
|
|
|
|
|
|
# Get number of witness stack items.
|
|
|
|
item_count = read_compact_size(r)
|
|
|
|
|
|
|
|
# Skip over OP_FALSE, which is due to the old OP_CHECKMULTISIG bug.
|
|
|
|
if r.get() != 0:
|
|
|
|
raise ValueError
|
|
|
|
|
|
|
|
signatures = []
|
|
|
|
for _ in range(item_count - 2):
|
|
|
|
n = read_compact_size(r)
|
|
|
|
signature = r.read_memoryview(n - 1)
|
|
|
|
sighash_type = SigHashType.from_int(r.get())
|
|
|
|
signatures.append((signature, sighash_type))
|
|
|
|
|
|
|
|
script = read_memoryview_prefixed(r)
|
|
|
|
if r.remaining_count():
|
|
|
|
raise ValueError
|
|
|
|
except (ValueError, EOFError):
|
|
|
|
raise wire.DataError("Invalid witness.")
|
|
|
|
|
|
|
|
return script, signatures
|
|
|
|
|
|
|
|
|
|
|
|
# Taproot: Witness getters
|
|
|
|
# ===
|
|
|
|
|
|
|
|
|
|
|
|
def write_witness_p2tr(w: Writer, signature: bytes, sighash_type: SigHashType) -> None:
|
|
|
|
# Taproot key path spending without annex.
|
|
|
|
write_compact_size(w, 0x01) # num of segwit items
|
|
|
|
write_signature_prefixed(w, signature, sighash_type)
|
|
|
|
|
|
|
|
|
|
|
|
def parse_witness_p2tr(witness: bytes) -> tuple[memoryview, SigHashType]:
|
|
|
|
try:
|
|
|
|
r = utils.BufferReader(witness)
|
|
|
|
|
|
|
|
if r.get() != 1: # Number of stack items.
|
|
|
|
# Only Taproot key path spending without annex is supported.
|
|
|
|
raise ValueError
|
|
|
|
|
|
|
|
n = read_compact_size(r)
|
|
|
|
if n not in (64, 65):
|
|
|
|
raise ValueError
|
|
|
|
|
|
|
|
signature = r.read_memoryview(64)
|
|
|
|
if n == 65:
|
|
|
|
sighash_type = SigHashType.from_int(r.get())
|
|
|
|
else:
|
|
|
|
sighash_type = SigHashType.SIGHASH_ALL_TAPROOT
|
|
|
|
|
|
|
|
if r.remaining_count():
|
|
|
|
raise ValueError
|
|
|
|
except (ValueError, EOFError):
|
|
|
|
raise wire.DataError("Invalid witness.")
|
|
|
|
|
|
|
|
return signature, sighash_type
|
|
|
|
|
|
|
|
|
|
|
|
# Multisig
|
|
|
|
# ===
|
|
|
|
#
|
|
|
|
# Used either as P2SH, P2WSH, or P2WSH nested in P2SH.
|
|
|
|
|
|
|
|
|
|
|
|
def write_input_script_multisig_prefixed(
|
|
|
|
w: Writer,
|
|
|
|
multisig: MultisigRedeemScriptType,
|
|
|
|
signature: bytes,
|
|
|
|
signature_index: int,
|
|
|
|
sighash_type: SigHashType,
|
|
|
|
coin: CoinInfo,
|
|
|
|
) -> None:
|
|
|
|
signatures = multisig.signatures # other signatures
|
|
|
|
if len(signatures[signature_index]) > 0:
|
|
|
|
raise wire.DataError("Invalid multisig parameters")
|
|
|
|
signatures[signature_index] = signature # our signature
|
|
|
|
|
|
|
|
# length of the redeem script
|
|
|
|
pubkeys = multisig_get_pubkeys(multisig)
|
|
|
|
redeem_script_length = output_script_multisig_length(pubkeys, multisig.m)
|
|
|
|
|
|
|
|
# length of the result
|
|
|
|
total_length = 1 # OP_FALSE
|
|
|
|
for s in signatures:
|
|
|
|
if s:
|
|
|
|
total_length += 1 + len(s) + 1 # length, signature, sighash_type
|
|
|
|
total_length += op_push_length(redeem_script_length) + redeem_script_length
|
|
|
|
write_compact_size(w, total_length)
|
|
|
|
|
|
|
|
# Starts with OP_FALSE because of an old OP_CHECKMULTISIG bug, which
|
|
|
|
# consumes one additional item on the stack:
|
|
|
|
# https://bitcoin.org/en/developer-guide#standard-transactions
|
|
|
|
w.append(0x00)
|
|
|
|
|
|
|
|
for s in signatures:
|
|
|
|
if s:
|
|
|
|
append_signature(w, s, sighash_type)
|
|
|
|
|
|
|
|
# redeem script
|
|
|
|
write_op_push(w, redeem_script_length)
|
|
|
|
write_output_script_multisig(w, pubkeys, multisig.m)
|
|
|
|
|
|
|
|
|
|
|
|
def parse_input_script_multisig(
|
|
|
|
script_sig: bytes,
|
|
|
|
) -> tuple[memoryview, list[tuple[memoryview, SigHashType]]]:
|
|
|
|
try:
|
|
|
|
r = utils.BufferReader(script_sig)
|
|
|
|
|
|
|
|
# Skip over OP_FALSE, which is due to the old OP_CHECKMULTISIG bug.
|
|
|
|
if r.get() != 0:
|
|
|
|
raise ValueError
|
|
|
|
|
|
|
|
signatures = []
|
|
|
|
n = read_op_push(r)
|
|
|
|
while r.remaining_count() > n:
|
|
|
|
signature = r.read_memoryview(n - 1)
|
|
|
|
sighash_type = SigHashType.from_int(r.get())
|
|
|
|
signatures.append((signature, sighash_type))
|
|
|
|
n = read_op_push(r)
|
|
|
|
|
|
|
|
script = r.read_memoryview()
|
|
|
|
if len(script) != n:
|
|
|
|
raise ValueError
|
|
|
|
except (ValueError, EOFError):
|
|
|
|
raise wire.DataError("Invalid scriptSig.")
|
|
|
|
|
|
|
|
return script, signatures
|
|
|
|
|
|
|
|
|
|
|
|
def output_script_multisig(pubkeys: list[bytes], m: int) -> bytearray:
|
|
|
|
w = utils.empty_bytearray(output_script_multisig_length(pubkeys, m))
|
|
|
|
write_output_script_multisig(w, pubkeys, m)
|
|
|
|
return w
|
|
|
|
|
|
|
|
|
|
|
|
def write_output_script_multisig(
|
|
|
|
w: Writer,
|
|
|
|
pubkeys: Sequence[bytes | memoryview],
|
|
|
|
m: int,
|
|
|
|
prefixed: bool = False,
|
|
|
|
) -> None:
|
|
|
|
n = len(pubkeys)
|
|
|
|
if n < 1 or n > 15 or m < 1 or m > 15 or m > n:
|
|
|
|
raise wire.DataError("Invalid multisig parameters")
|
|
|
|
for pubkey in pubkeys:
|
|
|
|
if len(pubkey) != 33:
|
|
|
|
raise wire.DataError("Invalid multisig parameters")
|
|
|
|
|
|
|
|
if prefixed:
|
|
|
|
write_compact_size(w, output_script_multisig_length(pubkeys, m))
|
|
|
|
|
|
|
|
w.append(0x50 + m) # numbers 1 to 16 are pushed as 0x50 + value
|
|
|
|
for p in pubkeys:
|
|
|
|
append_pubkey(w, p)
|
|
|
|
w.append(0x50 + n)
|
|
|
|
w.append(0xAE) # OP_CHECKMULTISIG
|
|
|
|
|
|
|
|
|
|
|
|
def output_script_multisig_length(pubkeys: Sequence[bytes | memoryview], m: int) -> int:
|
|
|
|
return 1 + len(pubkeys) * (1 + 33) + 1 + 1 # see output_script_multisig
|
|
|
|
|
|
|
|
|
|
|
|
def parse_output_script_multisig(script: bytes) -> tuple[list[memoryview], int]:
|
|
|
|
try:
|
|
|
|
r = utils.BufferReader(script)
|
|
|
|
|
|
|
|
threshold = r.get() - 0x50
|
|
|
|
pubkey_count = script[-2] - 0x50
|
|
|
|
|
|
|
|
if (
|
|
|
|
not 1 <= threshold <= 15
|
|
|
|
or not 1 <= pubkey_count <= 15
|
|
|
|
or threshold > pubkey_count
|
|
|
|
):
|
|
|
|
raise ValueError
|
|
|
|
|
|
|
|
public_keys = []
|
|
|
|
for _ in range(pubkey_count):
|
|
|
|
n = read_op_push(r)
|
|
|
|
if n != 33:
|
|
|
|
raise ValueError
|
|
|
|
public_keys.append(r.read_memoryview(n))
|
|
|
|
|
|
|
|
r.get() # ignore pubkey_count
|
|
|
|
if r.get() != 0xAE: # OP_CHECKMULTISIG
|
|
|
|
raise ValueError
|
|
|
|
if r.remaining_count():
|
|
|
|
raise ValueError
|
|
|
|
|
|
|
|
except (ValueError, IndexError, EOFError):
|
|
|
|
raise wire.DataError("Invalid multisig script")
|
|
|
|
|
|
|
|
return public_keys, threshold
|
|
|
|
|
|
|
|
|
|
|
|
# OP_RETURN
|
|
|
|
# ===
|
|
|
|
|
|
|
|
|
|
|
|
def output_script_paytoopreturn(data: bytes) -> bytearray:
|
|
|
|
w = utils.empty_bytearray(1 + 5 + len(data))
|
|
|
|
w.append(0x6A) # OP_RETURN
|
|
|
|
write_op_push(w, len(data))
|
|
|
|
w.extend(data)
|
|
|
|
return w
|
|
|
|
|
|
|
|
|
|
|
|
# BIP-322: SignatureProof container for scriptSig & witness
|
|
|
|
# ===
|
|
|
|
# https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki
|
|
|
|
|
|
|
|
|
|
|
|
def write_bip322_signature_proof(
|
|
|
|
w: Writer,
|
|
|
|
script_type: InputScriptType,
|
|
|
|
multisig: MultisigRedeemScriptType | None,
|
|
|
|
coin: CoinInfo,
|
|
|
|
public_key: bytes,
|
|
|
|
signature: bytes,
|
|
|
|
) -> None:
|
|
|
|
write_input_script_prefixed(
|
|
|
|
w, script_type, multisig, coin, SigHashType.SIGHASH_ALL, public_key, signature
|
|
|
|
)
|
|
|
|
|
|
|
|
if script_type == InputScriptType.SPENDTAPROOT:
|
|
|
|
write_witness_p2tr(w, signature, SigHashType.SIGHASH_ALL_TAPROOT)
|
|
|
|
elif script_type in common.SEGWIT_INPUT_SCRIPT_TYPES:
|
|
|
|
if multisig:
|
|
|
|
# find the place of our signature based on the public key
|
|
|
|
signature_index = multisig_pubkey_index(multisig, public_key)
|
|
|
|
write_witness_multisig(
|
|
|
|
w, multisig, signature, signature_index, SigHashType.SIGHASH_ALL
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
write_witness_p2wpkh(w, signature, public_key, SigHashType.SIGHASH_ALL)
|
|
|
|
else:
|
|
|
|
# Zero entries in witness stack.
|
|
|
|
w.append(0x00)
|
|
|
|
|
|
|
|
|
|
|
|
def read_bip322_signature_proof(r: utils.BufferReader) -> tuple[memoryview, memoryview]:
|
|
|
|
script_sig = read_memoryview_prefixed(r)
|
|
|
|
witness = r.read_memoryview()
|
|
|
|
return script_sig, witness
|
|
|
|
|
|
|
|
|
|
|
|
# Helpers
|
|
|
|
# ===
|
|
|
|
|
|
|
|
|
|
|
|
def write_signature_prefixed(
|
|
|
|
w: Writer, signature: bytes, sighash_type: SigHashType
|
|
|
|
) -> None:
|
|
|
|
length = len(signature)
|
|
|
|
if sighash_type != SigHashType.SIGHASH_ALL_TAPROOT:
|
|
|
|
length += 1
|
|
|
|
|
|
|
|
write_compact_size(w, length)
|
|
|
|
write_bytes_unchecked(w, signature)
|
|
|
|
if sighash_type != SigHashType.SIGHASH_ALL_TAPROOT:
|
|
|
|
w.append(sighash_type)
|
|
|
|
|
|
|
|
|
|
|
|
def append_signature(w: Writer, signature: bytes, sighash_type: SigHashType) -> None:
|
|
|
|
write_op_push(w, len(signature) + 1)
|
|
|
|
write_bytes_unchecked(w, signature)
|
|
|
|
w.append(sighash_type)
|
|
|
|
|
|
|
|
|
|
|
|
def append_pubkey(w: Writer, pubkey: bytes | memoryview) -> None:
|
|
|
|
write_op_push(w, len(pubkey))
|
|
|
|
write_bytes_unchecked(w, pubkey)
|