refactor(core/bitcoin): Change scripts to use writer semantics.

pull/1723/head
Andrew Kozlik 3 years ago committed by Andrew Kozlik
parent 8538f55edc
commit 27e6f35f78

@ -0,0 +1 @@
Memory optimization of BTC signing and CBOR decoding.

@ -15,6 +15,7 @@ from .multisig import (
)
from .readers import read_bytes_prefixed, read_op_push
from .writers import (
op_push_length,
write_bytes_fixed,
write_bytes_prefixed,
write_bytes_unchecked,
@ -29,40 +30,44 @@ if False:
from .writers import Writer
def input_derive_script(
def write_input_script_prefixed(
w: Writer,
script_type: InputScriptType,
multisig: MultisigRedeemScriptType | None,
coin: CoinInfo,
hash_type: int,
pubkey: bytes,
signature: bytes,
) -> bytes:
) -> None:
if script_type == InputScriptType.SPENDADDRESS:
# p2pkh or p2sh
return input_script_p2pkh_or_p2sh(pubkey, signature, hash_type)
if script_type == InputScriptType.SPENDP2SHWITNESS:
write_input_script_p2pkh_or_p2sh_prefixed(w, pubkey, signature, hash_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_hasher = utils.HashWriter(sha256())
write_output_script_multisig(witness_script_hasher, pubkeys, multisig.m)
witness_script_hash = witness_script_hasher.get_digest()
return input_script_p2wsh_in_p2sh(witness_script_hash)
# p2wpkh in p2sh
return input_script_p2wpkh_in_p2sh(common.ecdsa_hash_pubkey(pubkey, coin))
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 == InputScriptType.SPENDWITNESS:
# native p2wpkh or p2wsh
return input_script_native_p2wpkh_or_p2wsh()
script_sig = input_script_native_p2wpkh_or_p2wsh()
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)
return input_script_multisig(
multisig, signature, signature_index, hash_type, coin
write_input_script_multisig_prefixed(
w, multisig, signature, signature_index, hash_type, coin
)
else:
raise wire.ProcessError("Invalid script type")
@ -110,11 +115,12 @@ def output_derive_script(address: str, coin: CoinInfo) -> bytes:
# see https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki#specification
# item 5 for details
def bip143_derive_script_code(
txi: TxInput, public_keys: list[bytes], threshold: int, coin: CoinInfo
) -> bytearray:
def write_bip143_script_code_prefixed(
w: Writer, txi: TxInput, public_keys: list[bytes], threshold: int, coin: CoinInfo
) -> None:
if len(public_keys) > 1:
return output_script_multisig(public_keys, threshold)
write_output_script_multisig(w, public_keys, threshold, prefixed=True)
return
p2pkh = (
txi.script_type == InputScriptType.SPENDWITNESS
@ -122,11 +128,13 @@ def bip143_derive_script_code(
or txi.script_type == InputScriptType.SPENDADDRESS
or txi.script_type == InputScriptType.EXTERNAL
)
if p2pkh:
# for p2wpkh in p2sh or native p2wpkh
# the scriptCode is a classic p2pkh
return output_script_p2pkh(common.ecdsa_hash_pubkey(public_keys[0], coin))
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")
@ -136,13 +144,12 @@ def bip143_derive_script_code(
# https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki
def input_script_p2pkh_or_p2sh(
pubkey: bytes, signature: bytes, hash_type: int
) -> bytearray:
w = utils.empty_bytearray(5 + len(signature) + 1 + 5 + len(pubkey))
def write_input_script_p2pkh_or_p2sh_prefixed(
w: Writer, pubkey: bytes, signature: bytes, hash_type: int
) -> None:
write_bitcoin_varint(w, 1 + len(signature) + 1 + 1 + len(pubkey))
append_signature(w, signature, hash_type)
append_pubkey(w, pubkey)
return w
def parse_input_script_p2pkh(script_sig: bytes) -> tuple[bytes, bytes, int]:
@ -162,15 +169,22 @@ def parse_input_script_p2pkh(script_sig: bytes) -> tuple[bytes, bytes, int]:
return pubkey, signature, hash_type
def write_output_script_p2pkh(
w: Writer, pubkeyhash: bytes, prefixed: bool = False
) -> None:
if prefixed:
write_bitcoin_varint(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:
utils.ensure(len(pubkeyhash) == 20)
s = bytearray(25)
s[0] = 0x76 # OP_DUP
s[1] = 0xA9 # OP_HASH_160
s[2] = 0x14 # pushing 20 bytes
s[3:23] = pubkeyhash
s[23] = 0x88 # OP_EQUALVERIFY
s[24] = 0xAC # OP_CHECKSIG
s = utils.empty_bytearray(25)
write_output_script_p2pkh(s, pubkeyhash)
return s
@ -224,17 +238,18 @@ def output_script_native_p2wpkh_or_p2wsh(witprog: bytes) -> bytearray:
# Uses normal P2SH output scripts.
def input_script_p2wpkh_in_p2sh(pubkeyhash: bytes) -> bytearray:
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.
utils.ensure(len(pubkeyhash) == 20)
if prefixed:
write_bitcoin_varint(w, 23)
w = utils.empty_bytearray(3 + len(pubkeyhash))
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
return w
# SegWit: P2WSH nested in P2SH
@ -245,31 +260,30 @@ def input_script_p2wpkh_in_p2sh(pubkeyhash: bytes) -> bytearray:
# Uses normal P2SH output scripts.
def input_script_p2wsh_in_p2sh(script_hash: bytes) -> bytearray:
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_bitcoin_varint(w, 35)
if len(script_hash) != 32:
raise wire.DataError("Redeem script hash should be 32 bytes long")
w = utils.empty_bytearray(3 + len(script_hash))
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)
return w
# SegWit: Witness getters
# ===
def witness_p2wpkh(signature: bytes, pubkey: bytes, hash_type: int) -> bytearray:
w = utils.empty_bytearray(1 + 5 + len(signature) + 1 + 5 + len(pubkey))
def write_witness_p2wpkh(
w: Writer, signature: bytes, pubkey: bytes, hash_type: int
) -> None:
write_bitcoin_varint(w, 0x02) # num of segwit items, in P2WPKH it's always 2
write_signature_prefixed(w, signature, hash_type)
write_bytes_prefixed(w, pubkey)
return w
def parse_witness_p2wpkh(witness: bytes) -> tuple[bytes, bytes, int]:
@ -293,53 +307,39 @@ def parse_witness_p2wpkh(witness: bytes) -> tuple[bytes, bytes, int]:
return pubkey, signature, hash_type
def witness_multisig(
def write_witness_multisig(
w: Writer,
multisig: MultisigRedeemScriptType,
signature: bytes,
signature_index: int,
hash_type: int,
) -> bytearray:
) -> 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
# filter empty
signatures = [s for s in signatures if s]
# witness program + signatures + redeem script
num_of_witness_items = 1 + len(signatures) + 1
# 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 + 1 # number of items, OP_FALSE
for s in signatures:
total_length += 1 + len(s) + 1 # length, signature, hash_type
total_length += 1 + redeem_script_length # length, script
w = utils.empty_bytearray(total_length)
num_of_witness_items = 1 + sum(1 for s in signatures if s) + 1
write_bitcoin_varint(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_bitcoin_varint(w, 0)
for s in signatures:
write_signature_prefixed(w, s, hash_type) # size of the witness included
if s:
write_signature_prefixed(w, s, hash_type) # size of the witness included
# redeem script
write_bitcoin_varint(w, redeem_script_length)
write_output_script_multisig(w, pubkeys, multisig.m)
return w
pubkeys = multisig_get_pubkeys(multisig)
write_output_script_multisig(w, pubkeys, multisig.m, prefixed=True)
def parse_witness_multisig(witness: bytes) -> tuple[bytes, list[tuple[bytes, int]]]:
@ -375,13 +375,14 @@ def parse_witness_multisig(witness: bytes) -> tuple[bytes, list[tuple[bytes, int
# Used either as P2SH, P2WSH, or P2WSH nested in P2SH.
def input_script_multisig(
def write_input_script_multisig_prefixed(
w: Writer,
multisig: MultisigRedeemScriptType,
signature: bytes,
signature_index: int,
hash_type: int,
coin: CoinInfo,
) -> bytearray:
) -> None:
signatures = multisig.signatures # other signatures
if len(signatures[signature_index]) > 0:
raise wire.DataError("Invalid multisig parameters")
@ -394,10 +395,10 @@ def input_script_multisig(
# length of the result
total_length = 1 # OP_FALSE
for s in signatures:
total_length += 1 + len(s) + 1 # length, signature, hash_type
total_length += 1 + redeem_script_length # length, script
w = utils.empty_bytearray(total_length)
if s:
total_length += 1 + len(s) + 1 # length, signature, hash_type
total_length += op_push_length(redeem_script_length) + redeem_script_length
write_bitcoin_varint(w, total_length)
# Starts with OP_FALSE because of an old OP_CHECKMULTISIG bug, which
# consumes one additional item on the stack:
@ -405,15 +406,13 @@ def input_script_multisig(
w.append(0x00)
for s in signatures:
if len(s):
if s:
append_signature(w, s, hash_type)
# redeem script
write_op_push(w, redeem_script_length)
write_output_script_multisig(w, pubkeys, multisig.m)
return w
def parse_input_script_multisig(
script_sig: bytes,
@ -448,7 +447,12 @@ def output_script_multisig(pubkeys: list[bytes], m: int) -> bytearray:
return w
def write_output_script_multisig(w: Writer, pubkeys: list[bytes], m: int) -> None:
def write_output_script_multisig(
w: Writer,
pubkeys: list[bytes],
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")
@ -456,6 +460,9 @@ def write_output_script_multisig(w: Writer, pubkeys: list[bytes], m: int) -> Non
if len(pubkey) != 33:
raise wire.DataError("Invalid multisig parameters")
if prefixed:
write_bitcoin_varint(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)
@ -525,24 +532,22 @@ def write_bip322_signature_proof(
public_key: bytes,
signature: bytes,
) -> None:
script_sig = input_derive_script(
script_type, multisig, coin, common.SIGHASH_ALL, public_key, signature
write_input_script_prefixed(
w, script_type, multisig, coin, common.SIGHASH_ALL, public_key, signature
)
if 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)
witness = witness_multisig(
multisig, signature, signature_index, common.SIGHASH_ALL
write_witness_multisig(
w, multisig, signature, signature_index, common.SIGHASH_ALL
)
else:
witness = witness_p2wpkh(signature, public_key, common.SIGHASH_ALL)
write_witness_p2wpkh(w, signature, public_key, common.SIGHASH_ALL)
else:
# Zero entries in witness stack.
witness = bytearray(b"\x00")
write_bytes_prefixed(w, script_sig)
w.extend(witness)
w.append(0x00)
def read_bip322_signature_proof(r: utils.BufferReader) -> tuple[bytes, bytes]:

@ -8,47 +8,53 @@ from apps.common.writers import write_bytes_fixed, write_uint64_le
from . import scripts
from .multisig import multisig_get_pubkeys, multisig_pubkey_index
from .scripts import ( # noqa: F401
output_script_multisig,
output_script_p2pkh,
output_script_paytoopreturn,
write_output_script_multisig,
write_output_script_p2pkh,
)
from .writers import write_op_push
from .writers import op_push_length, write_bitcoin_varint, write_op_push
if False:
from trezor.messages import MultisigRedeemScriptType
from apps.common.coininfo import CoinInfo
from .writers import Writer
def input_derive_script(
def write_input_script_prefixed(
w: Writer,
script_type: InputScriptType,
multisig: MultisigRedeemScriptType | None,
coin: CoinInfo,
hash_type: int,
pubkey: bytes,
signature: bytes,
) -> bytes:
) -> None:
if script_type == InputScriptType.SPENDADDRESS:
# p2pkh or p2sh
return scripts.input_script_p2pkh_or_p2sh(pubkey, signature, hash_type)
scripts.write_input_script_p2pkh_or_p2sh_prefixed(
w, pubkey, signature, hash_type
)
elif script_type == InputScriptType.SPENDMULTISIG:
# p2sh multisig
assert multisig is not None # checked in sanitize_tx_input
signature_index = multisig_pubkey_index(multisig, pubkey)
return input_script_multisig(
multisig, signature, signature_index, hash_type, coin
return write_input_script_multisig_prefixed(
w, multisig, signature, signature_index, hash_type, coin
)
else:
raise wire.ProcessError("Invalid script type")
def input_script_multisig(
def write_input_script_multisig_prefixed(
w: Writer,
multisig: MultisigRedeemScriptType,
signature: bytes,
signature_index: int,
hash_type: int,
coin: CoinInfo,
) -> bytearray:
) -> None:
signatures = multisig.signatures # other signatures
if len(signatures[signature_index]) > 0:
raise wire.DataError("Invalid multisig parameters")
@ -61,21 +67,19 @@ def input_script_multisig(
# length of the result
total_length = 0
for s in signatures:
total_length += 1 + len(s) + 1 # length, signature, hash_type
total_length += 1 + redeem_script_length # length, script
w = utils.empty_bytearray(total_length)
if s:
total_length += 1 + len(s) + 1 # length, signature, hash_type
total_length += op_push_length(redeem_script_length) + redeem_script_length
write_bitcoin_varint(w, total_length)
for s in signatures:
if len(s):
if s:
scripts.append_signature(w, s, hash_type)
# redeem script
write_op_push(w, redeem_script_length)
scripts.write_output_script_multisig(w, pubkeys, multisig.m)
return w
# A ticket purchase submission for an address hash.
def output_script_sstxsubmissionpkh(addr: str) -> bytearray:
@ -86,12 +90,7 @@ def output_script_sstxsubmissionpkh(addr: str) -> bytearray:
w = utils.empty_bytearray(26)
w.append(0xBA) # OP_SSTX
w.append(0x76) # OP_DUP
w.append(0xA9) # OP_HASH160
w.append(0x14) # OP_DATA_20
write_bytes_fixed(w, raw_address[2:], 20)
w.append(0x88) # OP_EQUALVERIFY
w.append(0xAC) # OP_CHECKSIG
scripts.write_output_script_p2pkh(w, raw_address[2:])
return w
@ -104,44 +103,27 @@ def output_script_sstxchange(addr: str) -> bytearray:
w = utils.empty_bytearray(26)
w.append(0xBD) # OP_SSTXCHANGE
w.append(0x76) # OP_DUP
w.append(0xA9) # OP_HASH160
w.append(0x14) # OP_DATA_20
write_bytes_fixed(w, raw_address[2:], 20)
w.append(0x88) # OP_EQUALVERIFY
w.append(0xAC) # OP_CHECKSIG
scripts.write_output_script_p2pkh(w, raw_address[2:])
return w
# Spend from a stake revocation.
def output_script_ssrtx(pkh: bytes) -> bytearray:
def write_output_script_ssrtx_prefixed(w: Writer, pkh: bytes) -> None:
utils.ensure(len(pkh) == 20)
s = bytearray(26)
s[0] = 0xBC # OP_SSRTX
s[1] = 0x76 # OP_DUP
s[2] = 0xA9 # OP_HASH160
s[3] = 0x14 # OP_DATA_20
s[4:24] = pkh
s[24] = 0x88 # OP_EQUALVERIFY
s[25] = 0xAC # OP_CHECKSIG
return s
write_bitcoin_varint(w, 26)
w.append(0xBC) # OP_SSRTX
scripts.write_output_script_p2pkh(w, pkh)
# Spend from a stake generation.
def output_script_ssgen(pkh: bytes) -> bytearray:
def write_output_script_ssgen_prefixed(w: Writer, pkh: bytes) -> None:
utils.ensure(len(pkh) == 20)
s = bytearray(26)
s[0] = 0xBB # OP_SSGEN
s[1] = 0x76 # OP_DUP
s[2] = 0xA9 # OP_HASH160
s[3] = 0x14 # OP_DATA_20
s[4:24] = pkh
s[24] = 0x88 # OP_EQUALVERIFY
s[25] = 0xAC # OP_CHECKSIG
return s
# Retrieve pkh bytes from a stake commitment OPRETURN.
write_bitcoin_varint(w, 26)
w.append(0xBB) # OP_SSGEN
scripts.write_output_script_p2pkh(w, pkh)
# Stake commitment OPRETURN.
def sstxcommitment_pkh(pkh: bytes, amount: int) -> bytes:
w = utils.empty_bytearray(30)
write_bytes_fixed(w, pkh, 20)

@ -459,8 +459,7 @@ class Bitcoin:
node = self.keychain.derive(txi.address_n)
key_sign_pub = node.public_key()
script_sig = self.input_derive_script(txi, key_sign_pub, b"")
self.write_tx_input(self.serialized_tx, txi, script_sig)
self.write_tx_input_derived(self.serialized_tx, txi, key_sign_pub, b"")
def sign_bip143_input(self, txi: TxInput) -> tuple[bytes, bytes]:
self.tx_info.check_input(txi)
@ -500,14 +499,16 @@ class Bitcoin:
if txi.multisig:
# find out place of our signature based on the pubkey
signature_index = multisig.multisig_pubkey_index(txi.multisig, public_key)
self.serialized_tx.extend(
scripts.witness_multisig(
txi.multisig, signature, signature_index, self.get_hash_type(txi)
)
scripts.write_witness_multisig(
self.serialized_tx,
txi.multisig,
signature,
signature_index,
self.get_hash_type(txi),
)
else:
self.serialized_tx.extend(
scripts.witness_p2wpkh(signature, public_key, self.get_hash_type(txi))
scripts.write_witness_p2wpkh(
self.serialized_tx, signature, public_key, self.get_hash_type(txi)
)
async def get_legacy_tx_digest(
@ -585,8 +586,9 @@ class Bitcoin:
signature = ecdsa_sign(node, tx_digest)
# serialize input with correct signature
script_sig = self.input_derive_script(txi, node.public_key(), signature)
self.write_tx_input(self.serialized_tx, txi, script_sig)
self.write_tx_input_derived(
self.serialized_tx, txi, node.public_key(), signature
)
self.set_serialized_signature(i, signature)
async def serialize_output(self, i: int) -> None:
@ -659,6 +661,26 @@ class Bitcoin:
# the fork ID value.
return self.get_sighash_type(txi) & 0xFF
def write_tx_input_derived(
self,
w: writers.Writer,
txi: TxInput,
pubkey: bytes,
signature: bytes,
) -> None:
writers.write_bytes_reversed(w, txi.prev_hash, writers.TX_HASH_SIZE)
writers.write_uint32(w, txi.prev_index)
scripts.write_input_script_prefixed(
w,
txi.script_type,
txi.multisig,
self.coin,
self.get_hash_type(txi),
pubkey,
signature,
)
writers.write_uint32(w, txi.sequence)
@staticmethod
def write_tx_input(
w: writers.Writer,
@ -726,18 +748,3 @@ class Bitcoin:
assert txo.address is not None # checked in sanitize_tx_output
return scripts.output_derive_script(txo.address, self.coin)
# Tx Inputs
# ===
def input_derive_script(
self, txi: TxInput, pubkey: bytes, signature: bytes
) -> bytes:
return scripts.input_derive_script(
txi.script_type,
txi.multisig,
self.coin,
self.get_hash_type(txi),
pubkey,
signature,
)

@ -29,8 +29,7 @@ class Bitcoinlike(Bitcoin):
multisig.multisig_pubkey_index(txi.multisig, public_key)
# serialize input with correct signature
script_sig = self.input_derive_script(txi, public_key, signature)
self.write_tx_input(self.serialized_tx, txi, script_sig)
self.write_tx_input_derived(self.serialized_tx, txi, public_key, signature)
self.set_serialized_signature(i_sign, signature)
async def sign_nonsegwit_input(self, i_sign: int) -> None:

@ -147,27 +147,6 @@ class Decred(Bitcoin):
key_sign = self.keychain.derive(txi_sign.address_n)
key_sign_pub = key_sign.public_key()
if txi_sign.decred_staking_spend == DecredStakingSpendType.SSRTX:
prev_pkscript = scripts_decred.output_script_ssrtx(
ecdsa_hash_pubkey(key_sign_pub, self.coin)
)
elif txi_sign.decred_staking_spend == DecredStakingSpendType.SSGen:
prev_pkscript = scripts_decred.output_script_ssgen(
ecdsa_hash_pubkey(key_sign_pub, self.coin)
)
elif txi_sign.script_type == InputScriptType.SPENDMULTISIG:
assert txi_sign.multisig is not None
prev_pkscript = scripts_decred.output_script_multisig(
multisig.multisig_get_pubkeys(txi_sign.multisig),
txi_sign.multisig.m,
)
elif txi_sign.script_type == InputScriptType.SPENDADDRESS:
prev_pkscript = scripts_decred.output_script_p2pkh(
ecdsa_hash_pubkey(key_sign_pub, self.coin)
)
else:
raise wire.DataError("Unsupported input script type")
h_witness = self.create_hash_writer()
writers.write_uint32(
h_witness, self.tx_info.tx.version | DECRED_SERIALIZE_WITNESS_SIGNING
@ -176,7 +155,30 @@ class Decred(Bitcoin):
for ii in range(self.tx_info.tx.inputs_count):
if ii == i_sign:
writers.write_bytes_prefixed(h_witness, prev_pkscript)
if txi_sign.decred_staking_spend == DecredStakingSpendType.SSRTX:
scripts_decred.write_output_script_ssrtx_prefixed(
h_witness, ecdsa_hash_pubkey(key_sign_pub, self.coin)
)
elif txi_sign.decred_staking_spend == DecredStakingSpendType.SSGen:
scripts_decred.write_output_script_ssgen_prefixed(
h_witness, ecdsa_hash_pubkey(key_sign_pub, self.coin)
)
elif txi_sign.script_type == InputScriptType.SPENDMULTISIG:
assert txi_sign.multisig is not None
scripts_decred.write_output_script_multisig(
h_witness,
multisig.multisig_get_pubkeys(txi_sign.multisig),
txi_sign.multisig.m,
prefixed=True,
)
elif txi_sign.script_type == InputScriptType.SPENDADDRESS:
scripts_decred.write_output_script_p2pkh(
h_witness,
ecdsa_hash_pubkey(key_sign_pub, self.coin),
prefixed=True,
)
else:
raise wire.DataError("Unsupported input script type")
else:
write_bitcoin_varint(h_witness, 0)
@ -193,8 +195,9 @@ class Decred(Bitcoin):
signature = ecdsa_sign(key_sign, sig_hash)
# serialize input with correct signature
script_sig = self.input_derive_script(txi_sign, key_sign_pub, signature)
self.write_tx_input_witness(self.serialized_tx, txi_sign, script_sig)
self.write_tx_input_witness(
self.serialized_tx, txi_sign, key_sign_pub, signature
)
self.set_serialized_signature(i_sign, signature)
async def step5_serialize_outputs(self) -> None:
@ -306,17 +309,13 @@ class Decred(Bitcoin):
writers.write_uint32(w, tx.expiry)
def write_tx_input_witness(
self, w: writers.Writer, i: TxInput, script_sig: bytes
self, w: writers.Writer, txi: TxInput, pubkey: bytes, signature: bytes
) -> None:
writers.write_uint64(w, i.amount)
writers.write_uint64(w, txi.amount)
writers.write_uint32(w, 0) # block height fraud proof
writers.write_uint32(w, 0xFFFF_FFFF) # block index fraud proof
writers.write_bytes_prefixed(w, script_sig)
def input_derive_script(
self, txi: TxInput, pubkey: bytes, signature: bytes
) -> bytes:
return scripts_decred.input_derive_script(
scripts_decred.write_input_script_prefixed(
w,
txi.script_type,
txi.multisig,
self.coin,

@ -76,10 +76,9 @@ class Bip143Hash:
writers.write_uint32(h_preimage, txi.prev_index)
# scriptCode
script_code = scripts.bip143_derive_script_code(
txi, public_keys, threshold, coin
scripts.write_bip143_script_code_prefixed(
h_preimage, txi, public_keys, threshold, coin
)
writers.write_bytes_prefixed(h_preimage, script_code)
# amount
writers.write_uint64(h_preimage, txi.amount)

@ -11,8 +11,6 @@ from .matchcheck import MultisigFingerprintChecker, WalletPathChecker
if False:
from typing import Protocol
from trezor.messages import (
PrevInput,
PrevOutput,
PrevTx,
SignTx,
TxInput,
@ -39,22 +37,6 @@ if False:
) -> None:
...
@staticmethod
def write_tx_input(
w: writers.Writer,
txi: TxInput | PrevInput,
script: bytes,
) -> None:
...
@staticmethod
def write_tx_output(
w: writers.Writer,
txo: TxOutput | PrevOutput,
script_pubkey: bytes,
) -> None:
...
async def write_prev_tx_footer(
self, w: writers.Writer, tx: PrevTx, prev_hash: bytes
) -> None:
@ -167,7 +149,7 @@ class OriginalTxInfo(TxInfoBase):
def add_input(self, txi: TxInput) -> None:
super().add_input(txi)
self.signer.write_tx_input(self.h_tx, txi, txi.script_sig or bytes())
writers.write_tx_input(self.h_tx, txi, txi.script_sig or bytes())
# For verification use the first original input that specifies address_n.
if not self.verification_input and txi.address_n:
@ -180,7 +162,7 @@ class OriginalTxInfo(TxInfoBase):
if self.index == 0:
writers.write_bitcoin_varint(self.h_tx, self.tx.outputs_count)
self.signer.write_tx_output(self.h_tx, txo, script_pubkey)
writers.write_tx_output(self.h_tx, txo, script_pubkey)
async def finalize_tx_hash(self) -> None:
await self.signer.write_prev_tx_footer(self.h_tx, self.tx, self.orig_hash)

@ -3,7 +3,6 @@ from micropython import const
from trezor import wire
from trezor.crypto.hashlib import blake2b
from trezor.enums import InputScriptType
from trezor.messages import PrevTx, SignTx, TxInput, TxOutput
from trezor.utils import HashWriter, ensure
@ -11,13 +10,11 @@ from apps.common.coininfo import CoinInfo
from apps.common.keychain import Keychain
from apps.common.writers import write_bitcoin_varint
from ..common import ecdsa_hash_pubkey
from ..scripts import output_script_multisig, output_script_p2pkh
from ..scripts import write_bip143_script_code_prefixed
from ..writers import (
TX_HASH_SIZE,
get_tx_hash,
write_bytes_fixed,
write_bytes_prefixed,
write_bytes_reversed,
write_tx_output,
write_uint32,
@ -97,8 +94,7 @@ class Zip243Hash:
write_bytes_reversed(h_preimage, txi.prev_hash, TX_HASH_SIZE)
write_uint32(h_preimage, txi.prev_index)
# 13b. scriptCode
script_code = derive_script_code(txi, public_keys, threshold, coin)
write_bytes_prefixed(h_preimage, script_code)
write_bip143_script_code_prefixed(h_preimage, txi, public_keys, threshold, coin)
# 13c. value
write_uint64(h_preimage, txi.amount)
# 13d. nSequence
@ -174,17 +170,3 @@ class Zcashlike(Bitcoinlike):
write_uint32(w, tx.lock_time)
if tx.version >= 3:
write_uint32(w, tx.expiry) # expiryHeight
def derive_script_code(
txi: TxInput, public_keys: list[bytes], threshold: int, coin: CoinInfo
) -> bytearray:
if len(public_keys) > 1:
return output_script_multisig(public_keys, threshold)
p2pkh = txi.script_type in (InputScriptType.SPENDADDRESS, InputScriptType.EXTERNAL)
if p2pkh:
return output_script_p2pkh(ecdsa_hash_pubkey(public_keys[0], coin))
else:
raise wire.DataError("Unknown input script type for zip143 script code")

@ -1,12 +1,10 @@
from trezor import wire
from trezor import utils, wire
from trezor.crypto import der
from trezor.crypto.curve import secp256k1
from trezor.crypto.hashlib import sha256
from .common import ecdsa_hash_pubkey
from .scripts import (
input_script_p2wpkh_in_p2sh,
input_script_p2wsh_in_p2sh,
output_script_native_p2wpkh_or_p2wsh,
output_script_p2pkh,
output_script_p2sh,
@ -15,6 +13,8 @@ from .scripts import (
parse_output_script_multisig,
parse_witness_multisig,
parse_witness_p2wpkh,
write_input_script_p2wpkh_in_p2sh,
write_input_script_p2wsh_in_p2sh,
)
if False:
@ -56,7 +56,9 @@ class SignatureVerifier:
if len(script_sig) == 23: # P2WPKH nested in BIP16 P2SH
public_key, signature, hash_type = parse_witness_p2wpkh(witness)
pubkey_hash = ecdsa_hash_pubkey(public_key, coin)
if input_script_p2wpkh_in_p2sh(pubkey_hash) != script_sig:
w = utils.empty_bytearray(23)
write_input_script_p2wpkh_in_p2sh(w, pubkey_hash)
if w != script_sig:
raise wire.DataError("Invalid public key hash")
script_hash = coin.script_hash(script_sig[1:])
if output_script_p2sh(script_hash) != script_pubkey:
@ -66,7 +68,9 @@ class SignatureVerifier:
elif len(script_sig) == 35: # P2WSH nested in BIP16 P2SH
script, self.signatures = parse_witness_multisig(witness)
script_hash = sha256(script).digest()
if input_script_p2wsh_in_p2sh(script_hash) != script_sig:
w = utils.empty_bytearray(35)
write_input_script_p2wsh_in_p2sh(w, script_hash)
if w != script_sig:
raise wire.DataError("Invalid script hash")
script_hash = coin.script_hash(script_sig[1:])
if output_script_p2sh(script_hash) != script_pubkey:

@ -79,6 +79,18 @@ def write_op_push(w: Writer, n: int) -> None:
w.append((n >> 24) & 0xFF)
def op_push_length(n: int) -> int:
ensure(n >= 0 and n <= 0xFFFF_FFFF)
if n < 0x4C:
return 1
elif n < 0xFF:
return 2
elif n < 0xFFFF:
return 3
else:
return 4
def get_tx_hash(w: HashWriter, double: bool = False, reverse: bool = False) -> bytes:
d = w.get_digest()
if double:

Loading…
Cancel
Save