diff --git a/tests/device_tests/bitcoin/test_getaddress_segwit_native.py b/tests/device_tests/bitcoin/test_getaddress_segwit_native.py index f141630053..606f98d721 100644 --- a/tests/device_tests/bitcoin/test_getaddress_segwit_native.py +++ b/tests/device_tests/bitcoin/test_getaddress_segwit_native.py @@ -219,6 +219,45 @@ def test_show_multisig_3(client: Client): ) +@pytest.mark.multisig +def test_show_multisig_taproot_3(client: Client): + nodes = [ + btc.get_public_node( + client, parse_path(f"m/86h/1h/{index}h"), coin_name="Testnet" + ).node + for index in range(1, 4) + ] + multisig1 = messages.MultisigRedeemScriptType( + nodes=nodes, address_n=[0, 0], signatures=[b"", b"", b""], m=2 + ) + multisig2 = messages.MultisigRedeemScriptType( + nodes=nodes, address_n=[0, 1], signatures=[b"", b"", b""], m=2 + ) + for i in [1, 2, 3]: + assert ( + btc.get_address( + client, + "Testnet", + parse_path(f"m/86h/1h/{i}h/0/1"), + False, + multisig2, + script_type=messages.InputScriptType.SPENDTAPROOT, + ) + == "tb1p8wk2er5shm22z5mqy3qn2al7gz62csw75uh4ag8wluzptgr387tqeynquu" + ) + assert ( + btc.get_address( + client, + "Testnet", + parse_path(f"m/86h/1h/{i}h/0/0"), + False, + multisig1, + script_type=messages.InputScriptType.SPENDTAPROOT, + ) + == "tb1ph9e923j4w40nhr4v9fqz4pdn09tphdagrcx997qhe2fezgs2dqlqv9r8hl" + ) + + @pytest.mark.multisig @pytest.mark.parametrize("show_display", (True, False)) def test_multisig_missing(client: Client, show_display: bool): diff --git a/tests/device_tests/bitcoin/test_signtx_taproot.py b/tests/device_tests/bitcoin/test_signtx_taproot.py index f548154ae7..46b4a4535b 100644 --- a/tests/device_tests/bitcoin/test_signtx_taproot.py +++ b/tests/device_tests/bitcoin/test_signtx_taproot.py @@ -21,7 +21,9 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client from trezorlib.exceptions import TrezorFailure from trezorlib.tools import H_, parse_path +from ...bip32 import deserialize from ...common import is_core +from ...input_flows import InputFlowConfirmAllWarnings from ...tx_cache import TxCache from .signtx import ( assert_tx_matches, @@ -61,6 +63,18 @@ TXHASH_ec5194 = bytes.fromhex( TXHASH_c96621 = bytes.fromhex( "c96621a96668f7dd505c4deb9ee2b2038503a5daa4888242560e9b640cca8819" ) +TXHASH_e56e8b = bytes.fromhex( + "e56e8bdb23625856c54f5f52e3edc10ebabd72c839eed41a49f8ec2ea3691363" +) +TXHASH_b84fd2 = bytes.fromhex( + "b84fd297347318ff6693513637b11005600f93f4af60a44ffebaea1b5637d06c" +) +TXHASH_bedf7b = bytes.fromhex( + "bedf7b99c7e8e92f64d233c3789ba265671b89b1ab048296243a27da872f6494" +) +TXHASH_d20c2e = bytes.fromhex( + "d20c2e9f00220048a20e9a7240a9f41d57ca29541009d3477316233416946145" +) @pytest.mark.parametrize("chunkify", (True, False)) @@ -416,3 +430,287 @@ def test_send_invalid_address(client: Client, address: str): ] ) btc.sign_tx(client, "Testnet", [inp1], [out1], prev_txes=TX_API) + + +@pytest.mark.multisig +def test_send_multisig_1(client: Client): + # input tx: e56e8bdb23625856c54f5f52e3edc10ebabd72c839eed41a49f8ec2ea3691363 + + nodes = [ + btc.get_public_node( + client, parse_path(f"m/86h/1h/{index}h"), coin_name="Testnet" + ) + for index in range(1, 4) + ] + # tb1ph9e923j4w40nhr4v9fqz4pdn09tphdagrcx997qhe2fezgs2dqlqv9r8hl + multisig = messages.MultisigRedeemScriptType( + nodes=[deserialize(n.xpub) for n in nodes], + address_n=[0, 0], + signatures=[b"", b"", b""], + m=2, + ) + + inp1 = messages.TxInputType( + address_n=parse_path("m/86h/1h/1h/0/0"), + prev_hash=TXHASH_e56e8b, + prev_index=2, + script_type=messages.InputScriptType.SPENDTAPROOT, + multisig=multisig, + amount=4900, + ) + + out1 = messages.TxOutputType( + address="tb1qch62pf820spe9mlq49ns5uexfnl6jzcezp7d328fw58lj0rhlhasge9hzy", + amount=4900 - 2000, + script_type=messages.OutputScriptType.PAYTOADDRESS, + ) + + expected_responses = [ + request_input(0), + request_output(0), + messages.ButtonRequest(code=B.ConfirmOutput), + (is_core(client), messages.ButtonRequest(code=B.ConfirmOutput)), + messages.ButtonRequest(code=B.SignTx), + request_input(0), + request_output(0), + request_input(0), + request_finished(), + ] + + with client: + client.set_expected_responses(expected_responses) + signatures, _ = btc.sign_tx(client, "Testnet", [inp1], [out1]) + + # store signature + inp1.multisig.signatures[0] = signatures[0] + # sign with third key + inp1.address_n[2] = H_(3) + + with client: + client.set_expected_responses(expected_responses) + _, serialized_tx = btc.sign_tx(client, "Testnet", [inp1], [out1]) + + assert_tx_matches( + serialized_tx, + hash_link="https://tbtc1.trezor.io/api/tx/29e67f2b9239a2bea3840a664077bca7df751fcfdd9d962e337e3f6ade15c0a5", + tx_hex="01000000000101631369a32eecf8491ad4ee39c872bdba0ec1ede3525f4fc556586223db8b6ee50200000000ffffffff01540b000000000000220020c5f4a0a4ea7c0392efe0a9670a73264cffa90b19107cd8a8e9750ff93c77fdfb05407947a9308e19a0ec87e89211a5f4a108e69f6ebb2347eb289bfdf531464e97489736730f76131a5b3a9836d3716005ca49fcb4764be54956fab6ce05aad76a69004023c0143ff50681e0d94efcf46aec6cf5a899cf64dea629c5d9239f1061ba0eff6b52f0862e05ec7a313b08e09db3b9651d5345c58711b924fa3f9e51864e2621682061465959e55a05157db83f9785fc1f080495303f501c1ae262d8548beef6f46cac20e468fb01bde3718a826107ad3dff1dfb766f6a37ae880b779c2305601da2dd8aba2022988c4042826838fcb3e95ab0211b207034b1d3abefc0efe49a493dcb975aaaba529c21c14a30b2e461b280c0b13a03799096ab1256589153fc4b9c8cead16dc0b642069700000000", + ) + + +@pytest.mark.multisig +def test_send_multisig_2(client: Client): + # input tx: b84fd297347318ff6693513637b11005600f93f4af60a44ffebaea1b5637d06c + + nodes = [ + btc.get_public_node( + client, parse_path(f"m/86h/1h/{index}h"), coin_name="Testnet" + ) + for index in range(1, 4) + ] + # tb1p8wk2er5shm22z5mqy3qn2al7gz62csw75uh4ag8wluzptgr387tqeynquu + multisig = messages.MultisigRedeemScriptType( + nodes=[deserialize(n.xpub) for n in nodes], + address_n=[0, 1], + signatures=[b"", b"", b""], + m=2, + ) + + inp1 = messages.TxInputType( + address_n=parse_path("m/86h/1h/2h/0/1"), + prev_hash=TXHASH_b84fd2, + prev_index=0, + script_type=messages.InputScriptType.SPENDTAPROOT, + multisig=multisig, + amount=10_551, + ) + + out1 = messages.TxOutputType( + address="tb1qr6xa5v60zyt3ry9nmfew2fk5g9y3gerkjeu6xxdz7qga5kknz2ssld9z2z", + amount=10_551 - 5_000, + script_type=messages.OutputScriptType.PAYTOADDRESS, + ) + + expected_responses = [ + request_input(0), + request_output(0), + messages.ButtonRequest(code=B.ConfirmOutput), + (is_core(client), messages.ButtonRequest(code=B.ConfirmOutput)), + messages.ButtonRequest(code=B.SignTx), + request_input(0), + request_output(0), + request_input(0), + request_finished(), + ] + + with client: + client.set_expected_responses(expected_responses) + signatures, _ = btc.sign_tx(client, "Testnet", [inp1], [out1]) + + # store signature + inp1.multisig.signatures[1] = signatures[0] + # sign with first key + inp1.address_n[2] = H_(1) + + with client: + client.set_expected_responses(expected_responses) + _, serialized_tx = btc.sign_tx(client, "Testnet", [inp1], [out1]) + + assert_tx_matches( + serialized_tx, + hash_link="https://tbtc1.trezor.io/api/tx/18c12c869657f28901df61b2a72499cdbb52a1249552a12041d14fb2ff3b1e64", + tx_hex="010000000001016cd037561beabafe4fa460aff4930f600510b13736519366ff18733497d24fb80000000000ffffffff01af150000000000002200201e8dda334f11171190b3da72e526d441491464769679a319a2f011da5ad312a105004018ef361f578ee11b438c5b95673d38a550f324be69a2a3b133c79926b9a1a48c31d20672c6145ed0579f0478b7997a56be57f2982e6ccabef0a962c54c432e4e4000d5a6434e4a446d75f0e6d185a910998d7bb9511a85f6da46824fe5d197aaf40ca8776a59576fc0ced6044d14ef2e4fc33d85cd067a29335aaf09cc05609cd86820d7a2c22a7e60ea8084288b7c8f400ebb6d815ec09d1cba8aca060ec11506ce8dac20eabf4a439218264d990911f8c74579e8db3a72835c5f4093638badfb8a4bf766ba20a58003e084b3998fbeec35d4f6785873aab37b413e66c45f87943ada3183e645ba529c21c0a6d4674217688a33aaf77d815c52cb6739f8bc7abfb25567759515ab7cd6fc3700000000", + ) + + +@pytest.mark.multisig +def test_send_multisig_3_change(client: Client): + # input tx: bedf7b99c7e8e92f64d233c3789ba265671b89b1ab048296243a27da872f6494 + + nodes = [ + btc.get_public_node( + client, parse_path(f"m/86h/1h/{index}h"), coin_name="Testnet" + ) + for index in range(1, 4) + ] + # tb1pnk26euz34ftr0pmu4ygptk4829rua5s20fvm2ykrn5sfvdkgtwpss4uqax + multisig = messages.MultisigRedeemScriptType( + nodes=[deserialize(n.xpub) for n in nodes], + address_n=[1, 0], + signatures=[b"", b"", b""], + m=2, + ) + multisig2 = messages.MultisigRedeemScriptType( + nodes=[deserialize(n.xpub) for n in nodes], + address_n=[1, 1], + signatures=[b"", b"", b""], + m=2, + ) + + inp1 = messages.TxInputType( + address_n=parse_path("m/86h/1h/1h/1/0"), + prev_hash=TXHASH_bedf7b, + prev_index=0, + script_type=messages.InputScriptType.SPENDTAPROOT, + multisig=multisig, + amount=28_705, + ) + + out1 = messages.TxOutputType( + address_n=parse_path("m/86h/1h/1h/1/1"), + amount=28_705 - 10_000, + multisig=multisig2, + script_type=messages.OutputScriptType.PAYTOTAPROOT, + ) + + expected_responses = [ + request_input(0), + request_output(0), + messages.ButtonRequest(code=B.SignTx), + request_input(0), + request_output(0), + request_input(0), + request_finished(), + ] + + with client: + client.set_expected_responses(expected_responses) + if is_core(client): + IF = InputFlowConfirmAllWarnings(client) + client.set_input_flow(IF.get()) + signatures, _ = btc.sign_tx(client, "Testnet", [inp1], [out1]) + + # store signature + inp1.multisig.signatures[0] = signatures[0] + # sign with third key + inp1.address_n[2] = H_(3) + out1.address_n[2] = H_(3) + + with client: + client.set_expected_responses(expected_responses) + if is_core(client): + IF = InputFlowConfirmAllWarnings(client) + client.set_input_flow(IF.get()) + _, serialized_tx = btc.sign_tx(client, "Testnet", [inp1], [out1]) + + assert_tx_matches( + serialized_tx, + hash_link="https://tbtc1.trezor.io/api/tx/4670d37ad9d8cd92f7712e9ff90503c96d20201ab7c19b9621d3d9c00450c3ac", + tx_hex="0100000000010194642f87da273a24968204abb1891b6765a29b78c333d2642fe9e8c7997bdfbe0000000000ffffffff01114900000000000022512011901dcb350069ceb9d16c30805197f147f4dbad6c59e7c875eb3ddda47182b60540a40d516fe8f52e99d4a25d34b466e31d09fd8a7785a1441d1148d4eb0e767c96f4c78135ddd2cbe412789944631312f7f3ffcb40612d76062bf5cba8f6e676c3004075b5624ccca6ee82fda05f1cd9b72fe181db1b28ec48c2d416d0bb84ef17b8b06d6f7e2167588b1acb9984fbf60b938960f2c5ae2b3d7d1c93aa2c93091761856820852e4ec4dfd12b7414503d2746abc58ffcb626d401e17fe4b8b75e3a2ece8147ac20984a838226f2a655812683b00a26a3d413538daec64e4e135e5e8b0f6c0438d2ba20cd53e7d320770e1edc1619efaeda211d068ad77f00fe8376bb6c5aa6494dd406ba529c21c1a24cedfcfebb0c81e572c3b6a4135c906f47f9a00d162cc26ffd8897e6772c4500000000", + ) + + +@pytest.mark.multisig +def test_send_multisig_4_change(client: Client): + # input tx: d20c2e9f00220048a20e9a7240a9f41d57ca29541009d3477316233416946145 + + nodes = [ + btc.get_public_node( + client, parse_path(f"m/86h/1h/{index}h"), coin_name="Testnet" + ) + for index in range(1, 4) + ] + # tb1pzxgpmje4qp5uaww3dscgq5vh79rlfkadd3v70jr4av7amfr3s2mqw93kgy + multisig = messages.MultisigRedeemScriptType( + nodes=[deserialize(n.xpub) for n in nodes], + address_n=[1, 1], + signatures=[b"", b"", b""], + m=2, + ) + multisig2 = messages.MultisigRedeemScriptType( + nodes=[deserialize(n.xpub) for n in nodes], + address_n=[1, 2], + signatures=[b"", b"", b""], + m=2, + ) + + inp1 = messages.TxInputType( + address_n=parse_path("m/86h/1h/1h/1/1"), + prev_hash=TXHASH_d20c2e, + prev_index=0, + script_type=messages.InputScriptType.SPENDTAPROOT, + multisig=multisig, + amount=4_900, + ) + + out1 = messages.TxOutputType( + address_n=parse_path("m/86h/1h/1h/1/2"), + amount=4_900 - 2_000, + multisig=multisig2, + script_type=messages.OutputScriptType.PAYTOTAPROOT, + ) + + expected_responses = [ + request_input(0), + request_output(0), + messages.ButtonRequest(code=B.SignTx), + request_input(0), + request_output(0), + request_input(0), + request_finished(), + ] + + with client: + client.set_expected_responses(expected_responses) + if is_core(client): + IF = InputFlowConfirmAllWarnings(client) + client.set_input_flow(IF.get()) + signatures, _ = btc.sign_tx(client, "Testnet", [inp1], [out1]) + + # store signature + inp1.multisig.signatures[0] = signatures[0] + # sign with third key + inp1.address_n[2] = H_(3) + out1.address_n[2] = H_(3) + + with client: + client.set_expected_responses(expected_responses) + if is_core(client): + IF = InputFlowConfirmAllWarnings(client) + client.set_input_flow(IF.get()) + _, serialized_tx = btc.sign_tx(client, "Testnet", [inp1], [out1]) + + assert_tx_matches( + serialized_tx, + hash_link="https://tbtc1.trezor.io/api/tx/a23d30e520c9d0c4bccd04052ce18c90144e40dd4c0ac93f245830c5b6a4904b", + tx_hex="01000000000101456194163423167347d309105429ca571df4a940729a0ea2480022009f2e0cd20000000000ffffffff01540b0000000000002251204c9005ce3af7e9e0431f495bff1cf76de4cf26b8fb120ad0e520e5afe184a36c0540f9a77d5b4426e545bf1f34a26ff2407e87a8d5440ab67104eaa2063bb0cf46094e9351a7fa0eefe74b3dc5082855b2586b9cc9c6fe5cd1d3a8ff78687f5be08d00403cf6027e390f6381764c1918498d3f9a18746649e1d9a4661a5ec8ef64c2c41520da013cc1b88f68b7fccbce62714ec8022f09363e749b4e9e77fa2541ea732968208ed908dd8424b93b4b713d1d889e8d3d7c8ac58ddaf5bb47d2172fca760a223bac20575123f284fc0749e9ec186dc5318ea1126431d42cb88b5ea0f3d6dd31ccbe26ba209dc1f7e86ecf62eb89d00a2a16aa37744cbfd52e210779fbe0892246d1b7059eba529c21c14190a45076828ce7bfe18486671eef3f143fb41dd13e0fa0ba24813e33f9a8cf00000000", + )