From 56ac79962619e0e232091ae2a42dba5719ca8bbd Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Mon, 1 Nov 2021 17:36:15 +0100 Subject: [PATCH] feat(core): Support external inputs in TxWeightCalculator. --- core/src/apps/bitcoin/ownership.py | 22 ++++++++- core/src/apps/bitcoin/sign_tx/tx_weight.py | 20 +++++++- core/tests/test_apps.bitcoin.txweight.py | 54 ++++++++++++++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/core/src/apps/bitcoin/ownership.py b/core/src/apps/bitcoin/ownership.py index 532280b8ea..83ca4efa7f 100644 --- a/core/src/apps/bitcoin/ownership.py +++ b/core/src/apps/bitcoin/ownership.py @@ -92,7 +92,7 @@ def verify_nonownership( # Verify the BIP-322 SignatureProof. - proof_body = proof[: r.offset] + proof_body = memoryview(proof)[: r.offset] sighash = hashlib.sha256(proof_body) sighash.update(script_pubkey) if commitment_data: @@ -110,6 +110,26 @@ def verify_nonownership( return not_owned +def read_scriptsig_witness(ownership_proof: bytes) -> tuple[memoryview, memoryview]: + try: + r = utils.BufferReader(ownership_proof) + if r.read_memoryview(4) != _VERSION_MAGIC: + raise wire.DataError("Unknown format of proof of ownership") + + flags = r.get() + if flags & 0b1111_1110: + raise wire.DataError("Unknown flags in proof of ownership") + + # Skip ownership IDs. + id_count = read_bitcoin_varint(r) + r.read_memoryview(_OWNERSHIP_ID_LEN * id_count) + + return read_bip322_signature_proof(r) + + except (ValueError, EOFError): + raise wire.DataError("Invalid proof of ownership") + + def get_identifier(script_pubkey: bytes, keychain: Keychain) -> bytes: # k = Key(m/"SLIP-0019"/"Ownership identification key") node = keychain.derive_slip21(_OWNERSHIP_ID_KEY_PATH) diff --git a/core/src/apps/bitcoin/sign_tx/tx_weight.py b/core/src/apps/bitcoin/sign_tx/tx_weight.py index d7fc710375..14c18b3969 100644 --- a/core/src/apps/bitcoin/sign_tx/tx_weight.py +++ b/core/src/apps/bitcoin/sign_tx/tx_weight.py @@ -10,7 +10,7 @@ from micropython import const from trezor import wire from trezor.enums import InputScriptType -from .. import common +from .. import common, ownership if False: from trezor.messages import TxInput @@ -87,6 +87,24 @@ class TxWeightCalculator: else: self.counter += 4 # empty script_sig (1 byte) self.counter += 1 + input_script_size # discounted witness + elif i.script_type == InputScriptType.EXTERNAL: + if i.ownership_proof: + script_sig, witness = ownership.read_scriptsig_witness( + i.ownership_proof + ) + script_sig_size = len(script_sig) + witness_size = len(witness) + else: + script_sig_size = len(i.script_sig or b"") + witness_size = len(i.witness or b"") + + if witness_size > 1: + self.segwit_inputs_count += 1 + + self.counter += 4 * (self.varint_size(script_sig_size) + script_sig_size) + self.counter += witness_size + else: + raise wire.DataError("Invalid script type") def add_output(self, script: bytes) -> None: self.outputs_count += 1 diff --git a/core/tests/test_apps.bitcoin.txweight.py b/core/tests/test_apps.bitcoin.txweight.py index 5a635c674a..62bc040313 100644 --- a/core/tests/test_apps.bitcoin.txweight.py +++ b/core/tests/test_apps.bitcoin.txweight.py @@ -304,5 +304,59 @@ class TestCalculateTxWeight(unittest.TestCase): # average length, so the caculator should estimate 285 bytes of witness data. self.assertEqual(calculator.get_total(), 4*477 + 285) + def test_external_txweight(self): + coin = coins.by_name('Testnet') + + inp1 = TxInput( + amount=100000, + prev_hash=unhexlify('e5b7e21b5ba720e81efd6bfa9f854ababdcddc75a43bfa60bf0fe069cfd1bb8a'), + prev_index=0, + script_type=InputScriptType.EXTERNAL, + script_pubkey=unhexlify('00149c02608d469160a92f40fdf8c6ccced029493088'), + ownership_proof=unhexlify( + '534c001900016b2055d8190244b2ed2d46513c40658a574d3bc2deb6969c0535bb818b44d2c40002483045022100d4ad0374c922848c71d913fba59c81b9075e0d33e884d953f0c4b4806b8ffd0c022024740e6717a2b6a5aa03148c3a28b02c713b4e30fc8aeae67fa69eb20e8ddcd9012103505f0d82bbdd251511591b34f36ad5eea37d3220c2b81a1189084431ddb3aa3d' + ), + ) + + inp2 = TxInput( + address_n=[84 | 0x80000000, 1 | 0x80000000, 0 | 0x80000000, 0, 0], + prev_hash=unhexlify('70f9871eb03a38405cfd7a01e0e1448678132d815e2c9f552ad83ae23969509e'), + prev_index=0, + amount=100000, + script_type=InputScriptType.SPENDWITNESS, + ) + + inp3 = TxInput( + # tb1qldlynaqp0hy4zc2aag3pkenzvxy65saesxw3wd + # address_n=parse_path("m/84h/1h/0h/0/1"), + prev_hash=unhexlify('65b768dacccfb209eebd95a1fb80a04f1dd6a3abc6d7b41d5e9d9f91605b37d9'), + prev_index=0, + amount=10000, + script_type=InputScriptType.EXTERNAL, + script_pubkey=unhexlify('0014fb7e49f4017dc951615dea221b66626189aa43b9'), + script_sig=bytes(0), + witness=unhexlify( + '024730440220432ac60461de52713ad543cbb1484f7eca1a72c615d539b3f42f5668da4501d2022063786a6d6940a5c1ed9c2d2fd02cef90b6c01ddd84829c946561e15be6c0aae1012103dcf3bc936ecb2ec57b8f468050abce8c8756e75fd74273c9977744b1a0be7d03' + ), + ) + + out1 = TxOutput( + address="tb1qnspxpr2xj9s2jt6qlhuvdnxw6q55jvygcf89r2", + amount=100000 + 100000 + 10000, + script_type=OutputScriptType.PAYTOWITNESS, + ) + + calculator = TxWeightCalculator() + calculator.add_input(inp1) + calculator.add_input(inp2) + calculator.add_input(inp3) + calculator.add_output(output_derive_script(out1.address, coin)) + + self.assertEqual(calculator.get_total(), 4*164 + 325) + # non-segwit: header, inputs, outputs, locktime 4*(4+1+3*41+1+31+4) = 4*164 + # segwit: segwit header, 2x estimated witness (including stack item count) + # and 1x exact witness (including stack item count) 1*(2+108+108+107) = 325 + + if __name__ == '__main__': unittest.main()