From f34e2382fa79211b7f03e4a7c0b80cdc9825389e Mon Sep 17 00:00:00 2001 From: Tomas Susanka Date: Fri, 20 Mar 2020 10:24:04 +0000 Subject: [PATCH] tests: add segwit attack test for mixed inputs --- tests/device_tests/test_msg_signtx_segwit.py | 122 +++++++++++++++++++ tests/ui_tests/fixtures.json | 1 + 2 files changed, 123 insertions(+) diff --git a/tests/device_tests/test_msg_signtx_segwit.py b/tests/device_tests/test_msg_signtx_segwit.py index 18ad6d01c..7c1bccdc9 100644 --- a/tests/device_tests/test_msg_signtx_segwit.py +++ b/tests/device_tests/test_msg_signtx_segwit.py @@ -32,6 +32,34 @@ TXHASH_20912f = bytes.fromhex( TXHASH_9c3192 = bytes.fromhex( "9c31922be756c06d02167656465c8dc83bb553bf386a3f478ae65b5c021002be" ) +TXHASH_e5040e = bytes.fromhex( + "e5040e1bc1ae7667ffb9e5248e90b2fb93cd9150234151ce90e14ab2f5933bcd" +) + + +def request_input(n, tx_hash=None): + return proto.TxRequest( + request_type=proto.RequestType.TXINPUT, + details=proto.TxRequestDetailsType(request_index=n, tx_hash=tx_hash), + ) + + +def request_output(n, tx_hash=None): + return proto.TxRequest( + request_type=proto.RequestType.TXOUTPUT, + details=proto.TxRequestDetailsType(request_index=n, tx_hash=tx_hash), + ) + + +def request_meta(tx_hash): + return proto.TxRequest( + request_type=proto.RequestType.TXMETA, + details=proto.TxRequestDetailsType(tx_hash=tx_hash), + ) + + +def request_finished(): + return proto.TxRequest(request_type=proto.RequestType.TXFINISHED) class TestMsgSigntxSegwit: @@ -305,3 +333,97 @@ class TestMsgSigntxSegwit: assert exc.value.message.endswith( "Transaction has changed during signing" ) + + def test_attack_mixed_inputs(self, client): + TRUE_AMOUNT = 12345678 + FAKE_AMOUNT = 123 + + inp1 = proto.TxInputType( + address_n=parse_path("44'/1'/0'/0/0"), + amount=31000000, + prev_hash=TXHASH_e5040e, + prev_index=0, + script_type=proto.InputScriptType.SPENDADDRESS, + sequence=0xFFFFFFFD, + ) + inp2 = proto.TxInputType( + address_n=parse_path("49'/1'/0'/1/0"), + amount=TRUE_AMOUNT, + prev_hash=TXHASH_20912f, + prev_index=0, + script_type=proto.InputScriptType.SPENDP2SHWITNESS, + sequence=0xFFFFFFFD, + ) + out1 = proto.TxOutputType( + address="mhRx1CeVfaayqRwq5zgRQmD7W5aWBfD5mC", + amount=30998000, + script_type=proto.OutputScriptType.PAYTOADDRESS, + ) + + expected_responses = [ + request_input(0), + request_meta(TXHASH_e5040e), + request_input(0, TXHASH_e5040e), + request_output(0, TXHASH_e5040e), + request_output(1, TXHASH_e5040e), + request_input(1), + request_output(0), + proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput), + proto.ButtonRequest(code=proto.ButtonRequestType.FeeOverThreshold), + proto.ButtonRequest(code=proto.ButtonRequestType.SignTx), + request_input(0), + request_input(1), + request_output(0), + request_input(1), + request_output(0), + request_input(1), + ] + + if client.features.model == "1": + # T1 asks for first input for witness again + expected_responses.insert(-1, request_input(0)) + pass + + with client: + # Sign unmodified transaction. + # "Fee over threshold" warning is displayed - fee is the whole TRUE_AMOUNT + client.set_expected_responses(expected_responses + [request_finished()]) + btc.sign_tx( + client, "Testnet", [inp1, inp2], [out1], prev_txes=TX_API, + ) + + # In Phase 1 make the user confirm a lower value of the segwit input. + inp2.amount = FAKE_AMOUNT + + ctr = 3 + + def attack(msg): + nonlocal ctr + + if msg.tx.inputs and msg.tx.inputs[0] == inp2: + ctr -= 1 + if ctr <= 0: + # after Phase 1 is done and input 1 is signed, let Trezor sign + # the true amount in Phase 2 + msg.tx.inputs[0].amount = TRUE_AMOUNT + + return msg + + # Sign with attacker. + with pytest.raises(TrezorFailure) as e, client: + # "Fee over threshold" is NOT displayed, because the calculated fee + # in phase 1 is small. + assert expected_responses[8] == proto.ButtonRequest( + code=proto.ButtonRequestType.FeeOverThreshold + ) + del expected_responses[8] + + client.set_filter(proto.TxAck, attack) + client.set_expected_responses(expected_responses + [proto.Failure()]) + btc.sign_tx( + client, "Testnet", [inp1, inp2], [out1], prev_txes=TX_API, + ) + + assert e.value.failure.message.endswith( + "Transaction has changed during signing" + ) diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index 48ecf0061..9cdad56f9 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -273,6 +273,7 @@ "test_msg_signtx_komodo.py-test_one_one_rewards_claim": "e53f221fda81469027e39e21877a81a8fafbffbece0a45aeda12aae8873b0464", "test_msg_signtx_peercoin.py::test_timestamp_included": "825b9bdf5238c5c6415a254a6bae4b2bd9df8fc5cb31f66f0c20145cb4e60bbb", "test_msg_signtx_segwit.py-test_attack_change_input_address": "5ae71202c062ef7942626a80a4ceeb8d8c4ea5065a97f0de6a97505e9cb82c2c", +"test_msg_signtx_segwit.py-test_attack_mixed_inputs": "5bc10a7cc5fab955f0e9003cf1eea603b5a55917e344ced106870bb3d5bb0306", "test_msg_signtx_segwit.py-test_send_multisig_1": "958a0741070e057dcb889b2000e5487d391bc513e4a5d86193a355261c5f361b", "test_msg_signtx_segwit.py-test_send_p2sh": "ca593e31e919b9e920289b13e4c70b9607f34b93d06ace69835e3d08ecf046c8", "test_msg_signtx_segwit.py-test_send_p2sh_change": "562c7ee5a2e264c9f93387dd165403dab32bb305a4c3a6143a902c4a4c9e5950",