Ejemplo n.º 1
0
def test_attack_change_input(client: Client):
    # NOTE: fake input tx used

    inp1 = messages.TxInputType(
        address_n=parse_path("m/44h/156h/0h/0/0"),
        amount=1_252_382_934,
        prev_hash=FAKE_TXHASH_6f0398,
        prev_index=0,
        script_type=messages.InputScriptType.SPENDADDRESS,
    )
    out1 = messages.TxOutputType(
        address_n=parse_path("m/44h/156h/0h/1/0"),
        amount=1_896_050,
        script_type=messages.OutputScriptType.PAYTOADDRESS,
    )
    out2 = messages.TxOutputType(
        address="GfDB1tvjfm3bukeoBTtfNqrJVFohS2kCTe",
        amount=1_252_382_934 - 1_896_050 - 1_000,
        script_type=messages.OutputScriptType.PAYTOADDRESS,
    )

    attack_count = 2

    def attack_processor(msg):
        nonlocal attack_count

        if msg.tx.inputs and msg.tx.inputs[0] == inp1:
            if attack_count > 0:
                attack_count -= 1
            else:
                msg.tx.inputs[0].address_n[2] = H_(1)

        return msg

    with client:
        client.set_filter(messages.TxAck, attack_processor)
        client.set_expected_responses([
            request_input(0),
            request_output(0),
            request_output(1),
            messages.ButtonRequest(code=B.ConfirmOutput),
            messages.ButtonRequest(code=B.SignTx),
            request_input(0),
            request_meta(FAKE_TXHASH_6f0398),
            request_input(0, FAKE_TXHASH_6f0398),
            request_output(0, FAKE_TXHASH_6f0398),
            request_output(1, FAKE_TXHASH_6f0398),
            request_input(0),
            messages.Failure(code=messages.FailureType.ProcessError),
        ])
        with pytest.raises(TrezorFailure):
            btc.sign_tx(client,
                        "Bgold", [inp1], [out1, out2],
                        prev_txes=TX_API)
Ejemplo n.º 2
0
def test_attack_change_input(client: Client):
    # NOTE: fake input tx used

    inp1 = messages.TxInputType(
        address_n=parse_path("m/44h/145h/10h/0/0"),
        amount=1_995_344,
        prev_hash=FAKE_TXHASH_bd32ff,
        prev_index=0,
        script_type=messages.InputScriptType.SPENDADDRESS,
    )
    out1 = messages.TxOutputType(
        address_n=parse_path("m/44h/145h/10h/1/0"),
        amount=1_896_050,
        script_type=messages.OutputScriptType.PAYTOADDRESS,
    )
    out2 = messages.TxOutputType(
        address="bitcoincash:qr23ajjfd9wd73l87j642puf8cad20lfmqdgwvpat4",
        amount=73_452,
        script_type=messages.OutputScriptType.PAYTOADDRESS,
    )

    attack_count = 2

    def attack_processor(msg):
        nonlocal attack_count

        if msg.tx.inputs and msg.tx.inputs[0] == inp1:
            if attack_count > 0:
                attack_count -= 1
            else:
                msg.tx.inputs[0].address_n[2] = H_(1)

        return msg

    with client:
        client.set_filter(messages.TxAck, attack_processor)
        client.set_expected_responses([
            request_input(0),
            request_output(0),
            request_output(1),
            messages.ButtonRequest(code=B.ConfirmOutput),
            messages.ButtonRequest(code=B.SignTx),
            request_input(0),
            request_meta(FAKE_TXHASH_bd32ff),
            request_input(0, FAKE_TXHASH_bd32ff),
            request_output(0, FAKE_TXHASH_bd32ff),
            request_input(0),
            messages.Failure(code=messages.FailureType.ProcessError),
        ])
        with pytest.raises(TrezorFailure):
            btc.sign_tx(client,
                        "Bcash", [inp1], [out1, out2],
                        prev_txes=TX_API)
Ejemplo n.º 3
0
def test_invalid_prev_hash_attack(client: Client, prev_hash):
    # prepare input with a valid prev-hash
    inp1 = messages.TxInputType(
        address_n=tools.parse_path("m/44h/0h/0h/0/0"),
        amount=100_000_000,
        prev_hash=PREV_HASH,
        prev_index=0,
        script_type=messages.InputScriptType.SPENDP2SHWITNESS,
    )
    out1 = messages.TxOutputType(
        address="1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1",
        amount=100_000_000 - 10_000,
        script_type=messages.OutputScriptType.PAYTOADDRESS,
    )

    counter = 1

    def attack_filter(msg):
        nonlocal counter

        if not msg.tx.inputs:
            return msg

        # on first attempt, send unmodified input
        if counter > 0:
            counter -= 1
            return msg

        # on second request, send modified input
        msg.tx.inputs[0].prev_hash = prev_hash
        return msg

    with client, pytest.raises(TrezorFailure) as e:
        client.set_filter(messages.TxAck, attack_filter)
        btc.sign_tx(client, "Bitcoin", [inp1], [out1], prev_txes=PREV_TXES)

    # check that injection was performed
    assert counter == 0
    _check_error_message(prev_hash, client.features.model, e.value.message)
Ejemplo n.º 4
0
def test_attack_change_input(client: Client):
    """
    In Phases 1 and 2 the attacker replaces a non-multisig input
    `input_real` with a multisig input `input_fake`, which allows the
    attacker to provide a 1-of-2 multisig change address. When `input_real`
    is provided in the signing phase, an error must occur.
    """
    address_n = parse_path(
        "m/48h/1h/0h/1h/0/0")  # 2NErUdruXmM8o8bQySrzB3WdBRcmc5br4E8
    attacker_multisig_public_key = bytes.fromhex(
        "03653a148b68584acb97947344a7d4fd6a6f8b8485cad12987ff8edac874268088")

    input_real = messages.TxInputType(
        address_n=address_n,
        prev_hash=TXHASH_509e08,
        prev_index=0,
        script_type=messages.InputScriptType.SPENDP2SHWITNESS,
        amount=61_093,
    )

    multisig_fake = messages.MultisigRedeemScriptType(
        m=1,
        nodes=[
            btc.get_public_node(client, address_n, coin_name="Testnet").node,
            messages.HDNodeType(
                depth=0,
                fingerprint=0,
                child_num=0,
                chain_code=bytes(32),
                public_key=attacker_multisig_public_key,
            ),
        ],
        address_n=[],
    )

    input_fake = messages.TxInputType(
        address_n=address_n,
        prev_hash=input_real.prev_hash,
        prev_index=input_real.prev_index,
        script_type=input_real.script_type,
        multisig=multisig_fake,
        amount=input_real.amount,
    )

    output_payee = messages.TxOutputType(
        address="n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi",
        amount=10_000,
        script_type=messages.OutputScriptType.PAYTOADDRESS,
    )

    output_change = messages.TxOutputType(
        address_n=address_n,
        amount=input_real.amount - output_payee.amount - 1_000,
        script_type=messages.OutputScriptType.PAYTOP2SHWITNESS,
        multisig=multisig_fake,
    )

    # Transaction can be signed without the attack processor
    with client:
        btc.sign_tx(
            client,
            "Testnet",
            [input_real],
            [output_payee, output_change],
            prev_txes=TX_API_TESTNET,
        )

    attack_count = 3

    def attack_processor(msg):
        nonlocal attack_count
        # replace the first input_real with input_fake
        if attack_count > 0 and msg.tx.inputs and msg.tx.inputs[
                0] == input_real:
            msg.tx.inputs[0] = input_fake
            attack_count -= 1
        return msg

    with client:
        client.set_filter(messages.TxAck, attack_processor)
        with pytest.raises(TrezorFailure):
            btc.sign_tx(
                client,
                "Testnet",
                [input_real],
                [output_payee, output_change],
                prev_txes=TX_API_TESTNET,
            )
Ejemplo n.º 5
0
def test_attack_change_input_address(client: Client):
    # Simulates an attack where the user is coerced into unknowingly
    # transferring funds from one account to another one of their accounts,
    # potentially resulting in privacy issues.

    inp1 = messages.TxInputType(
        address_n=parse_path("m/49h/1h/0h/1/0"),
        # 2N1LGaGg836mqSQqiuUBLfcyGBhyZbremDX
        amount=123_456_789,
        prev_hash=TXHASH_20912f,
        prev_index=0,
        script_type=messages.InputScriptType.SPENDP2SHWITNESS,
    )
    out1 = messages.TxOutputType(
        address="mhRx1CeVfaayqRwq5zgRQmD7W5aWBfD5mC",
        amount=12_300_000,
        script_type=messages.OutputScriptType.PAYTOADDRESS,
    )
    out2 = messages.TxOutputType(
        address_n=parse_path("m/49h/1h/12h/1/0"),
        script_type=messages.OutputScriptType.PAYTOP2SHWITNESS,
        amount=123_456_789 - 11_000 - 12_300_000,
    )

    # Test if the transaction can be signed normally.
    with client:
        client.set_expected_responses([
            request_input(0),
            request_output(0),
            # The user is required to confirm transfer to another account.
            messages.ButtonRequest(code=B.ConfirmOutput),
            request_output(1),
            messages.ButtonRequest(code=B.ConfirmOutput),
            messages.ButtonRequest(code=B.SignTx),
            request_input(0),
            request_meta(TXHASH_20912f),
            request_input(0, TXHASH_20912f),
            request_output(0, TXHASH_20912f),
            request_output(1, TXHASH_20912f),
            request_input(0),
            request_output(0),
            request_output(1),
            request_input(0),
            request_finished(),
        ])
        _, serialized_tx = btc.sign_tx(client,
                                       "Testnet", [inp1], [out1, out2],
                                       prev_txes=TX_API_TESTNET)

    assert (
        serialized_tx.hex() ==
        "0100000000010137c361fb8f2d9056ba8c98c5611930fcb48cacfdd0fe2e0449d83eea982f91200000000017160014d16b8c0680c61fc6ed2e407455715055e41052f5ffffffff02e0aebb00000000001976a91414fdede0ddc3be652a0ce1afbc1b509a55b6b94888ac3df39f060000000017a9142f98413cb83ff8b3eaf1926192e68973cbd68a3a8702473044022013cbce7c575337ca05dbe03b5920a0805b510cd8dfd3180bd7c5d01cec6439cd0220050001be4bcefb585caf973caae0ffec682347f2127cc22f26efd93ee54fd852012103e7bfe10708f715e8538c92d46ca50db6f657bbc455b7494e6a0303ccdb868b7900000000"
    )

    attack_count = 2

    def attack_processor(msg):
        nonlocal attack_count

        if attack_count > 0 and msg.tx.inputs and msg.tx.inputs[0] == inp1:
            attack_count -= 1
            msg.tx.inputs[0].address_n[2] = H_(12)

        return msg

    # Now run the attack, must trigger the exception
    with client:
        client.set_filter(messages.TxAck, attack_processor)
        with pytest.raises(TrezorFailure):
            btc.sign_tx(client,
                        "Testnet", [inp1], [out1, out2],
                        prev_txes=TX_API_TESTNET)
Ejemplo n.º 6
0
def test_attack_script_type(client: Client):
    # Scenario: The attacker falsely claims that the transaction is Taproot-only to
    # avoid prev tx streaming and gives a lower amount for one of the inputs. The
    # correct input types and amounts are revelaled only in step6_sign_segwit_inputs()
    # to get a valid signature. This results in a transaction which pays a fee much
    # larger than what the user confirmed.

    inp1 = messages.TxInputType(
        address_n=parse_path("m/84h/1h/0h/1/0"),
        amount=7_289_000,
        prev_hash=TXHASH_65b811,
        prev_index=1,
        script_type=messages.InputScriptType.SPENDWITNESS,
    )
    inp2 = messages.TxInputType(
        address_n=parse_path("m/84h/1h/1h/0/0"),
        amount=12_300_000,
        prev_hash=TXHASH_091446,
        prev_index=0,
        script_type=messages.InputScriptType.SPENDWITNESS,
    )

    out1 = messages.TxOutputType(
        address="tb1q694ccp5qcc0udmfwgp692u2s2hjpq5h407urtu",
        script_type=messages.OutputScriptType.PAYTOADDRESS,
        amount=7_289_000 + 10_000 - 1_000,
    )

    attack_count = 5

    def attack_processor(msg):
        nonlocal attack_count

        if attack_count > 0 and msg.tx.inputs:
            attack_count -= 1
            if msg.tx.inputs[0] == inp2:
                msg.tx.inputs[0].amount = 10000
            msg.tx.inputs[0].address_n[0] = H_(86)
            msg.tx.inputs[0].script_type = messages.InputScriptType.SPENDTAPROOT

        return msg

    with client:
        client.set_filter(messages.TxAck, attack_processor)
        client.set_expected_responses(
            [
                request_input(0),
                request_input(1),
                request_output(0),
                messages.ButtonRequest(code=B.ConfirmOutput),
                messages.ButtonRequest(code=B.SignTx),
                request_input(0),
                request_input(1),
                request_output(0),
                request_input(0),
                request_input(1),
                messages.Failure(code=messages.FailureType.ProcessError),
            ]
        )
        with pytest.raises(TrezorFailure) as exc:
            btc.sign_tx(client, "Testnet", [inp1, inp2], [out1], prev_txes=TX_API)
        assert exc.value.code == messages.FailureType.ProcessError
        assert exc.value.message.endswith("Transaction has changed during signing")
Ejemplo n.º 7
0
def test_attack_path_segwit(client: 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=messages.SafetyCheckLevel.PromptTemporarily)

    # Generate keys
    address_a = btc.get_address(
        client,
        "Testnet",
        parse_path("m/84h/0h/0h/0/0"),
        script_type=messages.InputScriptType.SPENDWITNESS,
    )
    address_b = btc.get_address(
        client,
        "Testnet",
        parse_path("m/84h/0h/1h/0/1"),
        script_type=messages.InputScriptType.SPENDWITNESS,
    )
    prev_hash, prev_tx = forge_prevtx([(address_a, 9_426), (address_b, 7_086)],
                                      network="testnet")

    inp1 = messages.TxInputType(
        # The actual input that the attacker wants to get signed.
        address_n=parse_path("m/84h/0h/0h/0/0"),
        amount=9_426,
        prev_hash=prev_hash,
        prev_index=0,
        script_type=messages.InputScriptType.SPENDWITNESS,
    )
    inp2 = messages.TxInputType(
        # The actual input that the attacker 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("m/84h/0h/1h/0/1"),
        amount=7_086,
        prev_hash=prev_hash,
        prev_index=1,
        script_type=messages.InputScriptType.SPENDWITNESS,
    )

    out1 = messages.TxOutputType(
        # Attacker's Mainnet address encoded as Testnet.
        address="tb1q694ccp5qcc0udmfwgp692u2s2hjpq5h407urtu",
        script_type=messages.OutputScriptType.PAYTOADDRESS,
        amount=9_426 + 7_086 - 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(messages.TxAck, attack_processor)
        with pytest.raises(TrezorFailure):
            btc.sign_tx(client,
                        "Testnet", [inp1, inp2], [out1],
                        prev_txes={prev_hash: prev_tx})