1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-23 23:08:14 +00:00

feat(core): Add serialize option to SignTx.

This commit is contained in:
Andrew Kozlik 2022-09-07 14:29:02 +02:00 committed by Andrew Kozlik
parent 294c390c96
commit 5b453c88ed
9 changed files with 97 additions and 67 deletions

View File

@ -197,6 +197,7 @@ message SignTx {
optional uint32 branch_id = 10; // only for Zcash, BRANCH_ID
optional AmountUnit amount_unit = 11 [default=BITCOIN]; // show amounts in
optional bool decred_staking_ticket = 12 [default=false]; // only for Decred, this is signing a ticket purchase
optional bool serialize = 13 [default=true]; // serialize the full transaction, as opposed to only outputting the signatures
}
/**

View File

@ -0,0 +1 @@
Add serialize option to SignTx.

View File

@ -111,6 +111,7 @@ class Bitcoin:
# transaction and signature serialization
_SERIALIZED_TX_BUFFER[:] = bytes()
self.serialized_tx = _SERIALIZED_TX_BUFFER
self.serialize = tx.serialize
self.tx_req = TxRequest()
self.tx_req.details = TxRequestDetailsType()
self.tx_req.serialized = TxRequestSerializedType()
@ -247,18 +248,25 @@ class Bitcoin:
await self.verify_original_txs()
async def step4_serialize_inputs(self) -> None:
self.write_tx_header(self.serialized_tx, self.tx_info.tx, bool(self.segwit))
write_compact_size(self.serialized_tx, self.tx_info.tx.inputs_count)
if self.serialize:
self.write_tx_header(self.serialized_tx, self.tx_info.tx, bool(self.segwit))
write_compact_size(self.serialized_tx, self.tx_info.tx.inputs_count)
for i in range(self.tx_info.tx.inputs_count):
progress.advance()
if i in self.external:
await self.serialize_external_input(i)
if self.serialize:
await self.serialize_external_input(i)
elif i in self.segwit:
await self.serialize_segwit_input(i)
if self.serialize:
await self.serialize_segwit_input(i)
else:
await self.sign_nonsegwit_input(i)
async def step5_serialize_outputs(self) -> None:
if not self.serialize:
return
write_compact_size(self.serialized_tx, self.tx_info.tx.outputs_count)
for i in range(self.tx_info.tx.outputs_count):
progress.advance()
@ -273,16 +281,19 @@ class Bitcoin:
progress.advance()
if i in self.segwit:
if i in self.external:
txi = await helpers.request_tx_input(self.tx_req, i, self.coin)
self.serialized_tx.extend(txi.witness or b"\0")
if self.serialize:
txi = await helpers.request_tx_input(self.tx_req, i, self.coin)
self.serialized_tx.extend(txi.witness or b"\0")
else:
await self.sign_segwit_input(i)
else:
# add empty witness for non-segwit inputs
self.serialized_tx.append(0)
if self.serialize:
self.serialized_tx.append(0)
async def step7_finish(self) -> None:
self.write_tx_footer(self.serialized_tx, self.tx_info.tx)
if self.serialize:
self.write_tx_footer(self.serialized_tx, self.tx_info.tx)
await helpers.request_tx_finish(self.tx_req)
async def process_internal_input(self, txi: TxInput) -> None:
@ -596,31 +607,32 @@ class Bitcoin:
if txi.script_type == InputScriptType.SPENDTAPROOT:
signature = self.sign_taproot_input(i, txi)
scripts.write_witness_p2tr(
self.serialized_tx, signature, self.get_sighash_type(txi)
)
if self.serialize:
scripts.write_witness_p2tr(
self.serialized_tx, signature, self.get_sighash_type(txi)
)
else:
public_key, signature = self.sign_bip143_input(i, txi)
if txi.multisig:
# find out place of our signature based on the pubkey
signature_index = multisig.multisig_pubkey_index(
txi.multisig, public_key
)
scripts.write_witness_multisig(
self.serialized_tx,
txi.multisig,
signature,
signature_index,
self.get_sighash_type(txi),
)
else:
scripts.write_witness_p2wpkh(
self.serialized_tx,
signature,
public_key,
self.get_sighash_type(txi),
)
if self.serialize:
if txi.multisig:
# find out place of our signature based on the pubkey
signature_index = multisig.multisig_pubkey_index(
txi.multisig, public_key
)
scripts.write_witness_multisig(
self.serialized_tx,
txi.multisig,
signature,
signature_index,
self.get_sighash_type(txi),
)
else:
scripts.write_witness_p2wpkh(
self.serialized_tx,
signature,
public_key,
self.get_sighash_type(txi),
)
self.set_serialized_signature(i, signature)
@ -707,10 +719,11 @@ class Bitcoin:
# compute the signature from the tx digest
signature = ecdsa_sign(node, tx_digest)
# serialize input with correct signature
self.write_tx_input_derived(
self.serialized_tx, txi, node.public_key(), signature
)
if self.serialize:
# serialize input with correct signature
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:

View File

@ -30,7 +30,8 @@ class Bitcoinlike(Bitcoin):
multisig.multisig_pubkey_index(txi.multisig, public_key)
# serialize input with correct signature
self.write_tx_input_derived(self.serialized_tx, txi, public_key, signature)
if self.serialize:
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:

View File

@ -149,8 +149,11 @@ class Decred(Bitcoin):
approver = DecredApprover(tx, coin)
super().__init__(tx, keychain, coin, approver)
self.write_tx_header(self.serialized_tx, self.tx_info.tx, witness_marker=True)
write_compact_size(self.serialized_tx, self.tx_info.tx.inputs_count)
if self.serialize:
self.write_tx_header(
self.serialized_tx, self.tx_info.tx, witness_marker=True
)
write_compact_size(self.serialized_tx, self.tx_info.tx.inputs_count)
writers.write_uint32(
self.h_prefix, self.tx_info.tx.version | DECRED_SERIALIZE_NO_WITNESS
@ -164,22 +167,25 @@ class Decred(Bitcoin):
return DecredSigHasher(self.h_prefix)
async def step2_approve_outputs(self) -> None:
write_compact_size(self.serialized_tx, self.tx_info.tx.outputs_count)
write_compact_size(self.h_prefix, self.tx_info.tx.outputs_count)
if self.serialize:
write_compact_size(self.serialized_tx, self.tx_info.tx.outputs_count)
if self.tx_info.tx.decred_staking_ticket:
await self.approve_staking_ticket()
else:
await super().step2_approve_outputs()
self.write_tx_footer(self.serialized_tx, self.tx_info.tx)
self.write_tx_footer(self.h_prefix, self.tx_info.tx)
if self.serialize:
self.write_tx_footer(self.serialized_tx, self.tx_info.tx)
async def process_internal_input(self, txi: TxInput) -> None:
await super().process_internal_input(txi)
# Decred serializes inputs early.
self.write_tx_input(self.serialized_tx, txi, bytes())
if self.serialize:
self.write_tx_input(self.serialized_tx, txi, bytes())
async def process_external_input(self, txi: TxInput) -> None:
raise wire.DataError("External inputs not supported")
@ -194,10 +200,12 @@ class Decred(Bitcoin):
orig_txo: TxOutput | None,
) -> None:
await super().approve_output(txo, script_pubkey, orig_txo)
self.write_tx_output(self.serialized_tx, txo, script_pubkey)
if self.serialize:
self.write_tx_output(self.serialized_tx, txo, script_pubkey)
async def step4_serialize_inputs(self) -> None:
write_compact_size(self.serialized_tx, self.tx_info.tx.inputs_count)
if self.serialize:
write_compact_size(self.serialized_tx, self.tx_info.tx.inputs_count)
prefix_hash = self.h_prefix.get_digest()
@ -259,10 +267,11 @@ class Decred(Bitcoin):
signature = ecdsa_sign(key_sign, sig_hash)
# serialize input with correct signature
self.write_tx_input_witness(
self.serialized_tx, txi_sign, key_sign_pub, signature
)
self.set_serialized_signature(i_sign, signature)
if self.serialize:
self.write_tx_input_witness(
self.serialized_tx, txi_sign, key_sign_pub, signature
)
async def step5_serialize_outputs(self) -> None:
pass
@ -325,7 +334,8 @@ class Decred(Bitcoin):
script_pubkey = scripts_decred.output_script_sstxsubmissionpkh(txo.address)
await self.approver.add_decred_sstx_submission(txo, script_pubkey)
self.tx_info.add_output(txo, script_pubkey)
self.write_tx_output(self.serialized_tx, txo, script_pubkey)
if self.serialize:
self.write_tx_output(self.serialized_tx, txo, script_pubkey)
# SSTX commitment
txo = await helpers.request_tx_output(self.tx_req, 1, self.coin)
@ -334,7 +344,8 @@ class Decred(Bitcoin):
script_pubkey = self.process_sstx_commitment_owned(txo)
self.approver.add_change_output(txo, script_pubkey)
self.tx_info.add_output(txo, script_pubkey)
self.write_tx_output(self.serialized_tx, txo, script_pubkey)
if self.serialize:
self.write_tx_output(self.serialized_tx, txo, script_pubkey)
# SSTX change
txo = await helpers.request_tx_output(self.tx_req, 2, self.coin)
@ -350,7 +361,8 @@ class Decred(Bitcoin):
raise wire.DataError("Only zeroed addresses accepted for sstx change.")
self.approver.add_change_output(txo, script_pubkey)
self.tx_info.add_output(txo, script_pubkey)
self.write_tx_output(self.serialized_tx, txo, script_pubkey)
if self.serialize:
self.write_tx_output(self.serialized_tx, txo, script_pubkey)
def write_tx_header(
self,

View File

@ -139,12 +139,13 @@ class ZcashV4(Bitcoinlike):
return Zip243SigHasher()
async def step7_finish(self) -> None:
self.write_tx_footer(self.serialized_tx, self.tx_info.tx)
if self.serialize:
self.write_tx_footer(self.serialized_tx, self.tx_info.tx)
write_uint64(self.serialized_tx, 0) # valueBalance
write_compact_size(self.serialized_tx, 0) # nShieldedSpend
write_compact_size(self.serialized_tx, 0) # nShieldedOutput
write_compact_size(self.serialized_tx, 0) # nJoinSplit
write_uint64(self.serialized_tx, 0) # valueBalance
write_compact_size(self.serialized_tx, 0) # nShieldedSpend
write_compact_size(self.serialized_tx, 0) # nShieldedOutput
write_compact_size(self.serialized_tx, 0) # nJoinSplit
await helpers.request_tx_finish(self.tx_req)

View File

@ -594,6 +594,7 @@ if TYPE_CHECKING:
branch_id: "int | None"
amount_unit: "AmountUnit"
decred_staking_ticket: "bool"
serialize: "bool"
def __init__(
self,
@ -609,6 +610,7 @@ if TYPE_CHECKING:
branch_id: "int | None" = None,
amount_unit: "AmountUnit | None" = None,
decred_staking_ticket: "bool | None" = None,
serialize: "bool | None" = None,
) -> None:
pass

View File

@ -1172,6 +1172,7 @@ class SignTx(protobuf.MessageType):
10: protobuf.Field("branch_id", "uint32", repeated=False, required=False),
11: protobuf.Field("amount_unit", "AmountUnit", repeated=False, required=False),
12: protobuf.Field("decred_staking_ticket", "bool", repeated=False, required=False),
13: protobuf.Field("serialize", "bool", repeated=False, required=False),
}
def __init__(
@ -1189,6 +1190,7 @@ class SignTx(protobuf.MessageType):
branch_id: Optional["int"] = None,
amount_unit: Optional["AmountUnit"] = AmountUnit.BITCOIN,
decred_staking_ticket: Optional["bool"] = False,
serialize: Optional["bool"] = True,
) -> None:
self.outputs_count = outputs_count
self.inputs_count = inputs_count
@ -1202,6 +1204,7 @@ class SignTx(protobuf.MessageType):
self.branch_id = branch_id
self.amount_unit = amount_unit
self.decred_staking_ticket = decred_staking_ticket
self.serialize = serialize
class TxRequest(protobuf.MessageType):

View File

@ -188,19 +188,11 @@ def test_sign_tx(client: Client):
request_output(2),
request_output(3),
request_output(4),
request_input(0),
request_input(0),
request_input(1),
request_output(0),
request_output(1),
request_output(2),
request_output(3),
request_output(4),
request_input(1),
request_finished(),
]
)
_, serialized_tx = btc.sign_tx(
signatures, serialized_tx = btc.sign_tx(
client,
"Testnet",
inputs,
@ -208,12 +200,15 @@ def test_sign_tx(client: Client):
prev_txes=TX_CACHE_TESTNET,
payment_reqs=[payment_req],
preauthorized=True,
serialize=False,
)
# Transaction does not exist on the blockchain, not using assert_tx_matches()
assert serialized_tx == b""
assert len(signatures) == 2
assert signatures[0] is None
assert (
serialized_tx.hex()
== "010000000001028abbd1cf69e00fbf60fa3ba475dccdbdba4a859ffa6bfd1ee820a75b1be2b7e50000000000ffffffff0ab6ad3ba09261cfb4fa1d3680cb19332a8fe4d9de9ea89aa565bd83a2c082f90100000000ffffffff0550c3000000000000225120e0458118b80a08042d84c4f0356d86863fe2bffc034e839c166ad4e8da7e26ef50c3000000000000225120bdb100a4e7ba327d364642dc653b9e6b51783bde6ea0df2ccbc1a78e3cc1329511e56d0000000000225120c5c7c63798b59dc16e97d916011e99da5799d1b3dd81c2f2e93392477417e71e72bf00000000000022512062fdf14323b9ccda6f5b03c5c2c28e35839a3909a2e14d32b595c63d53c7b88f51900000000000001976a914a579388225827d9f2fe9014add644487808c695d88ac000140c017fce789fa8db54a2ae032012d2dd6d7c76cc1c1a6f00e29b86acbf93022da8aa559009a574792c7b09b2535d288d6e03c6ed169902ed8c4c97626a83fbc1100000000"
signatures[1].hex()
== "c017fce789fa8db54a2ae032012d2dd6d7c76cc1c1a6f00e29b86acbf93022da8aa559009a574792c7b09b2535d288d6e03c6ed169902ed8c4c97626a83fbc11"
)
# Test for a second time.
@ -249,7 +244,7 @@ def test_sign_tx_large(client: Client):
own_output_count = 30
total_output_count = 1200
output_denom = 10_000 # sats
max_expected_delay = 250 # seconds
max_expected_delay = 60 # seconds
with client:
btc.authorize_coinjoin(
@ -338,6 +333,7 @@ def test_sign_tx_large(client: Client):
prev_txes=TX_CACHE_TESTNET,
payment_reqs=[payment_req],
preauthorized=True,
serialize=False,
)
delay = time.time() - start
assert delay <= max_expected_delay