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:
parent
294c390c96
commit
5b453c88ed
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
1
core/.changelog.d/2507.added
Normal file
1
core/.changelog.d/2507.added
Normal file
@ -0,0 +1 @@
|
||||
Add serialize option to SignTx.
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user