from common import * from trezor.messages import MultisigRedeemScriptType from trezor.messages import TxInput from trezor.messages import TxOutput from trezor.messages import HDNodeType from trezor.enums import OutputScriptType from trezor.crypto import bip39 from apps.common import coins from apps.common.keychain import Keychain from apps.common.paths import AlwaysMatchingSchema from apps.bitcoin.sign_tx.tx_weight import * from apps.bitcoin.scripts import output_derive_script, output_script_paytoopreturn class TestCalculateTxWeight(unittest.TestCase): # pylint: disable=C0301 def test_p2pkh_txweight(self): coin = coins.by_name('Bitcoin') inp1 = TxInput(address_n=[0], # 14LmW5k4ssUrtbAB4255zdqv3b4w1TuX9e # amount=390000, prev_hash=unhexlify('d5f65ee80147b4bcc70b75e4bbf2d7382021b871bd8867ef8fa525ef50864882'), prev_index=0, amount=None, script_type=InputScriptType.SPENDADDRESS, sequence=0xffff_ffff, multisig=None) out1 = TxOutput(address='1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1', amount=390000 - 10000, script_type=OutputScriptType.PAYTOADDRESS, address_n=[], multisig=None) calculator = TxWeightCalculator() calculator.add_input(inp1) calculator.add_output(output_derive_script(out1.address, coin)) serialized_tx = '010000000182488650ef25a58fef6788bd71b8212038d7f2bbe4750bc7bcb44701e85ef6d5000000006b4830450221009a0b7be0d4ed3146ee262b42202841834698bb3ee39c24e7437df208b8b7077102202b79ab1e7736219387dffe8d615bbdba87e11477104b867ef47afed1a5ede7810121023230848585885f63803a0a8aecdd6538792d5c539215c91698e315bf0253b43dffffffff0160cc0500000000001976a914de9b2a8da088824e8fe51debea566617d851537888ac00000000' tx_weight = len(serialized_tx) / 2 * 4 # non-segwit tx's weight is simple length*4 self.assertEqual(calculator.get_weight(), tx_weight) def test_p2wpkh_in_p2sh_txweight(self): coin = coins.by_name('Testnet') inp1 = TxInput( # 49'/1'/0'/1/0" - 2N1LGaGg836mqSQqiuUBLfcyGBhyZbremDX address_n=[49 | 0x80000000, 1 | 0x80000000, 0 | 0x80000000, 1, 0], amount=123456789, prev_hash=unhexlify('20912f98ea3ed849042efed0fdac8cb4fc301961c5988cba56902d8ffb61c337'), prev_index=0, script_type=InputScriptType.SPENDP2SHWITNESS, sequence=0xffffffff, multisig=None, ) out1 = TxOutput( address='mhRx1CeVfaayqRwq5zgRQmD7W5aWBfD5mC', amount=12300000, script_type=OutputScriptType.PAYTOADDRESS, address_n=[], multisig=None, ) out2 = TxOutput( address='2N1LGaGg836mqSQqiuUBLfcyGBhyZbremDX', script_type=OutputScriptType.PAYTOADDRESS, amount=123456789 - 11000 - 12300000, address_n=[], multisig=None, ) calculator = TxWeightCalculator() calculator.add_input(inp1) calculator.add_output(output_derive_script(out1.address, coin)) calculator.add_output(output_derive_script(out2.address, coin)) self.assertEqual(calculator.get_weight(), 670) # non-segwit: header, inputs, outputs, locktime 4*(4+65+67+4) = 560 # segwit: segwit header, witness stack item count, witness 1*(2+1+107) = 110 # total 670 def test_native_p2wpkh_txweight(self): coin = coins.by_name('Testnet') inp1 = TxInput( # 49'/1'/0'/0/0" - tb1qqzv60m9ajw8drqulta4ld4gfx0rdh82un5s65s address_n=[49 | 0x80000000, 1 | 0x80000000, 0 | 0x80000000, 0, 0], amount=12300000, prev_hash=unhexlify('09144602765ce3dd8f4329445b20e3684e948709c5cdcaf12da3bb079c99448a'), prev_index=0, script_type=InputScriptType.SPENDWITNESS, sequence=0xffffffff, multisig=None, ) out1 = TxOutput( address='2N4Q5FhU2497BryFfUgbqkAJE87aKHUhXMp', amount=5000000, script_type=OutputScriptType.PAYTOADDRESS, address_n=[], multisig=None, ) out2 = TxOutput( address='tb1q694ccp5qcc0udmfwgp692u2s2hjpq5h407urtu', script_type=OutputScriptType.PAYTOADDRESS, amount=12300000 - 11000 - 5000000, address_n=[], multisig=None, ) calculator = TxWeightCalculator() calculator.add_input(inp1) calculator.add_output(output_derive_script(out1.address, coin)) calculator.add_output(output_derive_script(out2.address, coin)) self.assertEqual(calculator.get_weight(), 566) # non-segwit: header, inputs, outputs, locktime 4*(4+42+64+4) = 456 # segwit: segwit header, witness stack item count, witness 1*(2+1+107) = 110 # total 566 def test_taproot_txweight(self): coin = coins.by_name('Testnet') inp1 = TxInput( address_n=[86 | 0x80000000, 1 | 0x80000000, 0 | 0x80000000, 1, 0], amount=4600, prev_hash=unhexlify('7956f1de3e7362b04115b64a31f0b6822c50dd6c08d78398f392a0ac3f0e357b'), prev_index=1, script_type=InputScriptType.SPENDTAPROOT, ) out1 = TxOutput( address="tb1paxhjl357yzctuf3fe58fcdx6nul026hhh6kyldpfsf3tckj9a3wslqd7zd", amount=4450, script_type=OutputScriptType.PAYTOADDRESS, ) calculator = TxWeightCalculator() calculator.add_input(inp1) calculator.add_output(output_derive_script(out1.address, coin)) # 010000000001017b350e3faca092f39883d7086cdd502c82b6f0314ab61541b062733edef156790100000000ffffffff016211000000000000225120e9af2fc69e20b0be2629cd0e9c34da9f3ef56af7beac4fb4298262bc5a45ec5d0140493145b992dacbd7ea579a415efc2cba20c3bf0f7827d1bcf999109c0d11783fe96f91ddb04a889faa17ad21ecc5c81a578009744e95c7e721aff2a5c442916600000000 self.assertEqual(calculator.get_weight(), 4*94 + 68) def test_legacy_multisig_txweight(self): coin = coins.by_name('Bitcoin') seed = bip39.seed(' '.join(['all'] * 12), '') keychain = Keychain(seed, coin.curve_name, [AlwaysMatchingSchema]) nodes = [] for index in range(1, 4): node = keychain.derive([48 | 0x80000000, 0 | 0x80000000, index | 0x80000000, 0 | 0x80000000]) nodes.append(HDNodeType( depth=node.depth(), child_num=node.child_num(), fingerprint=node.fingerprint(), chain_code=node.chain_code(), public_key=node.public_key(), )) multisig = MultisigRedeemScriptType( nodes=nodes, address_n=[0, 0], signatures=[b"", b"", b""], m=2 ) inp1 = TxInput( address_n=[48 | 0x80000000, 0 | 0x80000000, 1 | 0x80000000, 0, 0], amount=100000, prev_hash=unhexlify('c6091adf4c0c23982a35899a6e58ae11e703eacd7954f588ed4b9cdefc4dba52'), prev_index=1, script_type=InputScriptType.SPENDMULTISIG, multisig=multisig, ) out1 = TxOutput( address="12iyMbUb4R2K3gre4dHSrbu5azG5KaqVss", amount=100000, script_type=OutputScriptType.PAYTOADDRESS, ) calculator = TxWeightCalculator() calculator.add_input(inp1) calculator.add_output(output_derive_script(out1.address, coin)) # 010000000152ba4dfcde9c4bed88f55479cdea03e711ae586e9a89352a98230c4cdf1a09c601000000fdfe00004830450221009276eea820aa54a24bd9f1a056cb09a15f50c0816570a7c7878bd1c5ee7248540220677d200aec5e2f25bcf4000bdfab3faa9e1746d7f80c4ae4bfa1f5892eb5dcbf01483045022100c2a9fbfbff1be87036d8a6a22745512b158154f7f3d8f4cad4ba7ed130b37b83022058f5299b4c26222588dcc669399bd88b6f2bc6e04b48276373683853187a4fd6014c69522103dc0ff15b9c85c0d2c87099758bf47d36229c2514aeefcf8dea123f0f93c679762102bfe426e8671601ad46d54d09ee15aa035610d36d411961c87474908d403fbc122102a5d57129c6c96df663ad29492aa18605dad97231e043be8a92f9406073815c5d53aeffffffff01a0860100000000001976a91412e8391ad256dcdc023365978418d658dfecba1c88ac00000000 self.assertEqual(calculator.get_weight(), 4*341) def test_segwit_multisig_txweight(self): coin = coins.by_name('Testnet') seed = bip39.seed(' '.join(['all'] * 12), '') keychain = Keychain(seed, coin.curve_name, [AlwaysMatchingSchema]) nodes = [] for index in range(1, 4): node = keychain.derive([49 | 0x80000000, 1 | 0x80000000, index | 0x80000000]) nodes.append(HDNodeType( depth=node.depth(), child_num=node.child_num(), fingerprint=node.fingerprint(), chain_code=node.chain_code(), public_key=node.public_key(), )) multisig = MultisigRedeemScriptType( nodes=nodes, address_n=[0, 0], signatures=[b"", b"", b""], m=2 ) inp1 = TxInput( address_n=[49 | 0x80000000, 1 | 0x80000000, 1 | 0x80000000, 0, 0], prev_hash=unhexlify('c9348040bbc2024e12dcb4a0b4806b0398646b91acf314da028c3f03dd0179fc'), prev_index=1, script_type=InputScriptType.SPENDP2SHWITNESS, multisig=multisig, amount=1610436, ) out1 = TxOutput( address="tb1qch62pf820spe9mlq49ns5uexfnl6jzcezp7d328fw58lj0rhlhasge9hzy", amount=1605000, script_type=OutputScriptType.PAYTOADDRESS, ) calculator = TxWeightCalculator() calculator.add_input(inp1) calculator.add_output(output_derive_script(out1.address, coin)) # 01000000000101be0210025c5be68a473f6a38bf53b53bc88d5c46567616026dc056e72b92319c01000000232200208d398cfb58a1d9cdb59ccbce81559c095e8c6f4a3e64966ca385078d9879f95effffffff01887d180000000000220020c5f4a0a4ea7c0392efe0a9670a73264cffa90b19107cd8a8e9750ff93c77fdfb0400483045022100dd6342c65197af27d7894d8b8b88b16b568ee3b5ebfdc55fdfb7caa9650e3b4c02200c7074a5bcb0068f63d9014c7cd2b0490aba75822d315d41aad444e9b86adf5201483045022100e7e6c2d21109512ba0609e93903e84bfb7731ac3962ee2c1cad54a7a30ff99a20220421497930226c39fc3834e8d6da3fc876516239518b0e82e2dc1e3c46271a17c01695221021630971f20fa349ba940a6ba3706884c41579cd760c89901374358db5dd545b92102f2ff4b353702d2bb03d4c494be19d77d0ab53d16161b53fbcaf1afeef4ad0cb52103e9b6b1c691a12ce448f1aedbbd588e064869c79fbd760eae3b8cd8a5f1a224db53ae00000000 self.assertEqual(calculator.get_weight(), 4*129 + 256) def test_mixed_txweight(self): coin = coins.by_name('Testnet') inp1 = TxInput( address_n=[49 | 0x80000000, 1 | 0x80000000, 1 | 0x80000000, 0, 0], amount=20000, prev_hash=unhexlify('8c3ea7a10ab6d289119b722ec8c27b70c17c722334ced31a0370d782e4b6775d'), prev_index=0, script_type=InputScriptType.SPENDP2SHWITNESS, ) inp2 = TxInput( address_n=[84 | 0x80000000, 1 | 0x80000000, 1 | 0x80000000, 0, 0], amount=15000, prev_hash=unhexlify('7956f1de3e7362b04115b64a31f0b6822c50dd6c08d78398f392a0ac3f0e357b'), prev_index=0, script_type=InputScriptType.SPENDWITNESS, ) inp3 = TxInput( address_n=[86 | 0x80000000, 1 | 0x80000000, 1 | 0x80000000, 0, 0], amount=4450, prev_hash=unhexlify('7956f1de3e7362b04115b64a31f0b6822c50dd6c08d78398f392a0ac3f0e357b'), prev_index=0, script_type=InputScriptType.SPENDTAPROOT, ) inp4 = TxInput( address_n=[44 | 0x80000000, 1 | 0x80000000, 1 | 0x80000000, 0, 0], amount=10000, prev_hash=unhexlify('3ac32e90831d79385eee49d6030a2123cd9d009fe8ffc3d470af9a6a777a119b'), prev_index=2, script_type=InputScriptType.SPENDADDRESS, ) out1 = TxOutput( address="tb1q6xnnna3g7lk22h5tn8nlx2ezmndlvuk556w4w3", amount=25000, script_type=OutputScriptType.PAYTOWITNESS, ) out2 = TxOutput( address="mfnMbVFC1rH4p9GNbjkMfrAjyKRLycFAzA", script_type=OutputScriptType.PAYTOADDRESS, amount=7000, ) out3 = TxOutput( address="2MvAG8m2xSf83FgeR4ZpUtaubpLNjAMMoka", amount=6900, script_type=OutputScriptType.PAYTOP2SHWITNESS, ) out4 = TxOutput( op_return_data=b"test of op_return data", amount=0, script_type=OutputScriptType.PAYTOOPRETURN, ) out5 = TxOutput( address="tb1ptgp9w0mm89ms43flw0gkrhyx75gyc6qjhtpf0jmt5sv0dufpnsrsyv9nsz", amount=10000, script_type=OutputScriptType.PAYTOTAPROOT, ) calculator = TxWeightCalculator() calculator.add_input(inp1) calculator.add_input(inp2) calculator.add_input(inp3) calculator.add_input(inp4) calculator.add_output(output_derive_script(out1.address, coin)) calculator.add_output(output_derive_script(out2.address, coin)) calculator.add_output(output_derive_script(out3.address, coin)) calculator.add_output(output_script_paytoopreturn(out4.op_return_data)) calculator.add_output(output_derive_script(out5.address, coin)) # 010000000001045d77b6e482d770031ad3ce3423727cc1707bc2c82e729b1189d2b60aa1a73e8c0000000017160014a33c6e24c99e108b97bc411e7e9ef31e9d5d6164ffffffff7b350e3faca092f39883d7086cdd502c82b6f0314ab61541b062733edef156790000000000ffffffff852e125137abca2dd7a42837dccfc34edc358c72eefd62978d6747d3be9315900000000000ffffffff9b117a776a9aaf70d4c3ffe89f009dcd23210a03d649ee5e38791d83902ec33a020000006b483045022100f6bd64136839b49822cf7e2050bc5c91346fc18b5cf97a945d4fd6c502f712d002207d1859e66d218f705b704f3cfca0c75410349bb1f50623f4fc2d09d5d8df0a3f012103bae960983f83e28fcb8f0e5f3dc1f1297b9f9636612fd0835b768e1b7275fb9dffffffff05a861000000000000160014d1a739f628f7eca55e8b99e7f32b22dcdbf672d4581b0000000000001976a91402e9b094fd98e2a26e805894eb78f7ff3fef199b88acf41a00000000000017a9141ff816cbeb74817050de585ceb2c772ebf71147a870000000000000000186a1674657374206f66206f705f72657475726e206461746110270000000000002251205a02573f7b39770ac53f73d161dc86f5104c6812bac297cb6ba418f6f1219c070247304402205fae7fa2b5141548593d5623ce5bd82ee18dfc751c243526039c91848efd603702200febfbe3467a68c599245ff89055514f26e146c79b58d932ced2325e6dad1b1a0121021630971f20fa349ba940a6ba3706884c41579cd760c89901374358db5dd545b90247304402201b21212100c84207697cebb852374669c382ed97cbd08afbbdfe1b302802161602206b32b2140d094cf5b7e758135961c95478c8e82fea0df30f56ccee284b79eaea012103f6b2377d52960a6094ec158cf19dcf9e33b3da4798c2302aa5806483ed4187ae01404a81e4b7f55d6d4a26923c5e2daf3cc86ed6030f83ea6e7bb16d7b81b988b34585be21a64ab45ddcc2fb9f17be2dfeff6b22cf943bc3fc8f125a7f463af428ed0000000000 # The witness data is 283 bytes, but two of the DER signatures are one byte below the # average length, so the caculator should estimate 285 bytes of witness data. self.assertEqual(calculator.get_weight(), 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_weight(), 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()