diff --git a/tests/device_tests/test_msg_signtx_invalid_path.py b/tests/device_tests/test_msg_signtx_invalid_path.py index e393e74f5..7c4c98a4d 100644 --- a/tests/device_tests/test_msg_signtx_invalid_path.py +++ b/tests/device_tests/test_msg_signtx_invalid_path.py @@ -18,10 +18,12 @@ import pytest from trezorlib import btc, device, messages as proto from trezorlib.exceptions import TrezorFailure -from trezorlib.tools import parse_path +from trezorlib.tools import H_, parse_path from ..tx_cache import TxCache +from .signtx import request_finished, request_input, request_meta, request_output +B = proto.ButtonRequestType TX_CACHE_MAINNET = TxCache("Bitcoin") TX_CACHE_BCASH = TxCache("Bcash") @@ -31,6 +33,12 @@ TXHASH_8cc1f4 = bytes.fromhex( TXHASH_d5f65e = bytes.fromhex( "d5f65ee80147b4bcc70b75e4bbf2d7382021b871bd8867ef8fa525ef50864882" ) +TXHASH_fa80a9 = bytes.fromhex( + "fa80a9949f1094119195064462f54d0e0eabd3139becd4514ae635b8c7fe3a46" +) +TXHASH_5dfd1b = bytes.fromhex( + "5dfd1b037633adc7f84a17b2df31c9994fe50b3ab3e246c44c4ceff3d326f62e" +) class TestMsgSigntxInvalidPath: @@ -117,3 +125,94 @@ class TestMsgSigntxInvalidPath: ) btc.sign_tx(client, "Bcash", [inp1], [out1], prev_txes=TX_CACHE_BCASH) + + def test_attack_path_segwit(self, client): + # Scenario: The attacker falsely claims that the transaction uses Testnet paths to + # avoid the path warning dialog, but in step6_sign_segwit_inputs() uses Bitcoin paths + # to get a valid signature. + + device.apply_settings( + client, safety_checks=proto.SafetyCheckLevel.PromptTemporarily + ) + + inp1 = proto.TxInputType( + # The actual input that the attcker wants to get signed. + address_n=parse_path("84'/0'/0'/0/0"), + amount=9426, + prev_hash=TXHASH_fa80a9, + prev_index=0, + script_type=proto.InputScriptType.SPENDWITNESS, + ) + inp2 = proto.TxInputType( + # The actual input that the attcker wants to get signed. + # We need this one to be from a different account, so that the match checker + # allows the transaction to pass. + address_n=parse_path("84'/0'/1'/0/1"), + amount=7086, + prev_hash=TXHASH_5dfd1b, + prev_index=0, + script_type=proto.InputScriptType.SPENDWITNESS, + ) + + out1 = proto.TxOutputType( + # Attacker's Mainnet address encoded as Testnet. + address="tb1q694ccp5qcc0udmfwgp692u2s2hjpq5h407urtu", + script_type=proto.OutputScriptType.PAYTOADDRESS, + amount=9426 + 7086 - 500, + ) + + attack_count = 6 + + def attack_processor(msg): + nonlocal attack_count + # Make the inputs look like they are coming from Testnet paths until we reach the + # signing phase. + if attack_count > 0 and msg.tx.inputs and msg.tx.inputs[0] in (inp1, inp2): + attack_count -= 1 + msg.tx.inputs[0].address_n[1] = H_(1) + + return msg + + with client: + client.set_filter(proto.TxAck, attack_processor) + client.set_expected_responses( + [ + # Step: process inputs + request_input(0), + # Attacker bypasses warning about non-standard path. + request_input(1), + # Attacker bypasses warning about non-standard path. + # Step: approve outputs + request_output(0), + proto.ButtonRequest(code=B.ConfirmOutput), + proto.ButtonRequest(code=B.SignTx), + # Step: verify inputs + request_input(0), + request_meta(TXHASH_fa80a9), + request_input(0, TXHASH_fa80a9), + request_output(0, TXHASH_fa80a9), + request_output(1, TXHASH_fa80a9), + request_input(1), + request_meta(TXHASH_5dfd1b), + request_input(0, TXHASH_5dfd1b), + request_output(0, TXHASH_5dfd1b), + request_output(1, TXHASH_5dfd1b), + # Step: serialize inputs + request_input(0), + request_input(1), + # Step: serialize outputs + request_output(0), + # Step: sign segwit inputs + request_input(0), + # Trezor must warn about non-standard path before signing. + proto.ButtonRequest(code=B.UnknownDerivationPath), + request_input(1), + # Trezor must warn about non-standard path before signing. + proto.ButtonRequest(code=B.UnknownDerivationPath), + request_finished(), + ] + ) + + btc.sign_tx( + client, "Testnet", [inp1, inp2], [out1], prev_txes=TX_CACHE_MAINNET + ) diff --git a/tests/txcache/bitcoin/5dfd1b037633adc7f84a17b2df31c9994fe50b3ab3e246c44c4ceff3d326f62e.json b/tests/txcache/bitcoin/5dfd1b037633adc7f84a17b2df31c9994fe50b3ab3e246c44c4ceff3d326f62e.json new file mode 100644 index 000000000..16a310d14 --- /dev/null +++ b/tests/txcache/bitcoin/5dfd1b037633adc7f84a17b2df31c9994fe50b3ab3e246c44c4ceff3d326f62e.json @@ -0,0 +1,22 @@ +{ + "bin_outputs": [ + { + "amount": 7086, + "script_pubkey": "0014f6740e521befc61400662d9489a5f744883ca681" + }, + { + "amount": 20120, + "script_pubkey": "001404278d66e85ad1073d6f7a8eeee8b96ca633e6e8" + } + ], + "inputs": [ + { + "prev_hash": "939cda2b4b609be5f5772dea5885431e18a61834bcbe5e0f895d6dfa05d2cc70", + "prev_index": 0, + "script_sig": "", + "sequence": 4294967293 + } + ], + "lock_time": 0, + "version": 1 +} diff --git a/tests/txcache/bitcoin/fa80a9949f1094119195064462f54d0e0eabd3139becd4514ae635b8c7fe3a46.json b/tests/txcache/bitcoin/fa80a9949f1094119195064462f54d0e0eabd3139becd4514ae635b8c7fe3a46.json new file mode 100644 index 000000000..ea31974ea --- /dev/null +++ b/tests/txcache/bitcoin/fa80a9949f1094119195064462f54d0e0eabd3139becd4514ae635b8c7fe3a46.json @@ -0,0 +1,22 @@ +{ + "bin_outputs": [ + { + "amount": 9426, + "script_pubkey": "0014ece6935b2a5a5b5ff997c87370b16fa10f164410" + }, + { + "amount": 309896, + "script_pubkey": "a914dfe58cc93d35fb99e15436f47d3bbfce8203280687" + } + ], + "inputs": [ + { + "prev_hash": "86a6e02943dcd057cfbe349f2c2274478a3a1be908eb788606a6950e727a0d36", + "prev_index": 0, + "script_sig": "16001495f41f5c0e0ec2c7fe27f0ac4bd59a5632a40b5f", + "sequence": 4294967295 + } + ], + "lock_time": 0, + "version": 1 +} diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index 8a3e15ac9..67b776c81 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -661,6 +661,7 @@ "test_msg_signtx_grs.py-test_send_segwit_native_change": "fd8b04e26d71fad1c59f5e548c35f22f2031cfb99f9077824242e264fcbedfe6", "test_msg_signtx_grs.py-test_send_segwit_p2sh": "e59018de5c49f902c6880c2347283b6c1830fcb19e8eab9686938a08abd930b3", "test_msg_signtx_grs.py-test_send_segwit_p2sh_change": "08251e3b7e509264dbd89a5ded2deba51544d0fcfce1dc7466b553b73298d423", +"test_msg_signtx_invalid_path.py-test_attack_path_segwit": "3feaa01d47aa9757e9d74f668927fd1445493c367adff94215405eb8e0a2749b", "test_msg_signtx_invalid_path.py-test_invalid_path_fail": "1c100ce4b7c1e47e72428f390de0846c1ff933e9f07894872644a369a9422738", "test_msg_signtx_invalid_path.py-test_invalid_path_pass_forkid": "ef98eb752ec5fa948c952def7599f57a2bc5240b2d6b1eec0e02cc9be5c3040f", "test_msg_signtx_invalid_path.py-test_invalid_path_prompt": "12e137210397357ed754af0f4618ef03312b3e884930f55727d1b034f969bfd5",