示例#1
0
def sign_tx(path,
            multisig_address,
            redeemscript,
            utxo_file,
            output_file,
            testnet=False):
    """
    Sign a spend of a bitcoin 2-of-3 P2SH-multisig address
    using a Trezor One Hardware Wallet

    Args:
        path: BIP32 path of key with which to sign
        multisig_address: Address that is being spent
        redeemscript: redeem script corresponding to multisig_address
        utxo_file: JSON file of UTXOs for multisig_address
                   (see get_utxo_set.py)
        output_file: JSON file of destination addresses and amounts
                     (see generate_outputs.py)
        testnet: Is this a testnet or mainnet address?

    Returns:
        Dictionary with two keys:
        pubkey: public key corresponding to the private key used for signing
        signatures: a list of signatures, one per utxo

    Raises:
        ValueError: If multisig_address is not correct for the given redeemscript

    Example:
       TODO
    """

    with open(utxo_file, 'r') as f:
        utxos = json.load(f)

    with open(output_file, 'r') as f:
        outputs = json.load(f)

    # Verify that Pubkeys and Address match
    check_address = generate_multisig_address(redeemscript, testnet)
    parsed_redeem_script = btc_utils.parse_redeem_script(redeemscript)

    if multisig_address != check_address:
        raise ValueError("Incorrect Redeem Script")

    if testnet:
        coin = 'Testnet'
    else:
        coin = 'Bitcoin'

    input_script_type = proto.InputScriptType.SPENDMULTISIG
    output_script_type = proto.OutputScriptType.PAYTOADDRESS

    tx_api = trezorlib.coins.tx_api[coin]
    client = trezor_utils.get_trezor_client()
    #client.set_tx_api(tx_api)

    # Get signing node:
    expanded_path = trezorlib.tools.parse_path(path)
    signer = trezorbtc.get_public_node(client,
                                       expanded_path,
                                       show_display=True).node

    # blank HDNodes with public_keys
    nodes = [
        proto.HDNodePathType(node=proto.HDNodeType(public_key=bytes.fromhex(h),
                                                   depth=0,
                                                   fingerprint=0,
                                                   child_num=0,
                                                   chain_code=b'0' * 32),
                             address_n=[])
        for h in parsed_redeem_script['pubkeys']
    ]
    trezor_inputs = []
    for utxo in utxos:
        multisig = proto.MultisigRedeemScriptType(pubkeys=nodes,
                                                  m=parsed_redeem_script['m'])

        _input = proto.TxInputType(prev_hash=bytes.fromhex(utxo['txid']),
                                   prev_index=utxo['n'],
                                   amount=utxo['amount'],
                                   address_n=trezorlib.tools.parse_path(path),
                                   script_type=input_script_type,
                                   multisig=multisig)
        trezor_inputs.append(_input)

    txes = {}
    for tx in trezor_inputs:
        tmptx = tx_api[tx.prev_hash]
        txes[tx.prev_hash] = tmptx

    # make this multi-output, probably from file
    trezor_outputs = []
    for output in outputs:
        trezor_outputs.append(
            proto.TxOutputType(
                address=output['address'],
                amount=output['amount'],
                script_type=output_script_type,
            ))

    output_signatures, serialized_tx = trezorbtc.sign_tx(client,
                                                         coin,
                                                         trezor_inputs,
                                                         trezor_outputs,
                                                         prev_txes=txes)

    signature_blob = {
        "pubkey": signer.public_key.hex(),
        "signatures": [s.hex() for s in output_signatures]
    }
    client.close()

    return signature_blob
示例#2
0
    def test_send_bch_multisig_wrongchange(self, client):
        nodes = [
            btc.get_public_node(client, parse_path("48'/145'/%d'" % i)).node
            for i in range(1, 4)
        ]

        def getmultisig(chain, nr, signatures=[b"", b"", b""], nodes=nodes):
            return proto.MultisigRedeemScriptType(
                nodes=nodes, address_n=[chain, nr], signatures=signatures, m=2
            )

        correcthorse = proto.HDNodeType(
            depth=1,
            fingerprint=0,
            child_num=0,
            chain_code=bytes.fromhex(
                "0000000000000000000000000000000000000000000000000000000000000000"
            ),
            public_key=bytes.fromhex(
                "0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71"
            ),
        )
        sig = bytes.fromhex(
            "304402207274b5a4d15e75f3df7319a375557b0efba9b27bc63f9f183a17da95a6125c94022000efac57629f1522e2d3958430e2ef073b0706cfac06cce492651b79858f09ae"
        )
        inp1 = proto.TxInputType(
            address_n=parse_path("48'/145'/1'/1/0"),
            multisig=getmultisig(1, 0, [b"", sig, b""]),
            # bitcoincash:pp6kcpkhua7789g2vyj0qfkcux3yvje7euhyhltn0a
            amount=24000,
            prev_hash=bytes.fromhex(
                "f68caf10df12d5b07a34601d88fa6856c6edcbf4d05ebef3486510ae1c293d5f"
            ),
            prev_index=1,
            script_type=proto.InputScriptType.SPENDMULTISIG,
        )
        out1 = proto.TxOutputType(
            address_n=parse_path("48'/145'/1'/1/1"),
            multisig=proto.MultisigRedeemScriptType(
                pubkeys=[
                    proto.HDNodePathType(node=nodes[0], address_n=[1, 1]),
                    proto.HDNodePathType(node=correcthorse, address_n=[]),
                    proto.HDNodePathType(node=correcthorse, address_n=[]),
                ],
                signatures=[b"", b"", b""],
                m=2,
            ),
            script_type=proto.OutputScriptType.PAYTOMULTISIG,
            amount=23000,
        )
        with client:
            client.set_expected_responses(
                [
                    proto.TxRequest(
                        request_type=proto.RequestType.TXINPUT,
                        details=proto.TxRequestDetailsType(request_index=0),
                    ),
                    proto.TxRequest(
                        request_type=proto.RequestType.TXOUTPUT,
                        details=proto.TxRequestDetailsType(request_index=0),
                    ),
                    proto.ButtonRequest(code=proto.ButtonRequestType.ConfirmOutput),
                    proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
                    proto.TxRequest(
                        request_type=proto.RequestType.TXINPUT,
                        details=proto.TxRequestDetailsType(request_index=0),
                    ),
                    proto.TxRequest(
                        request_type=proto.RequestType.TXOUTPUT,
                        details=proto.TxRequestDetailsType(request_index=0),
                    ),
                    proto.TxRequest(request_type=proto.RequestType.TXFINISHED),
                ]
            )
            (signatures1, serialized_tx) = btc.sign_tx(
                client, "Bcash", [inp1], [out1], prev_txes=TX_API
            )
        assert (
            signatures1[0].hex()
            == "304402201badcdcafef4855ed58621f95935efcbc72068510472140f4ec5e252faa0af93022003310a43488288f70aedee96a5af2643a255268a6858cda9ae3001ea5e3c7557"
        )
        assert (
            serialized_tx.hex()
            == "01000000015f3d291cae106548f3be5ed0f4cbedc65668fa881d60347ab0d512df10af8cf601000000fc0047304402201badcdcafef4855ed58621f95935efcbc72068510472140f4ec5e252faa0af93022003310a43488288f70aedee96a5af2643a255268a6858cda9ae3001ea5e3c75574147304402207274b5a4d15e75f3df7319a375557b0efba9b27bc63f9f183a17da95a6125c94022000efac57629f1522e2d3958430e2ef073b0706cfac06cce492651b79858f09ae414c69522102245739b55787a27228a4fe78b3a324366cc645fbaa708cad45da351a334341192102debbdcb0b6970d5ade84a50fdbda1c701cdde5c9925d9b6cd8e05a9a15dbef352102ffe5fa04547b2b0c3cfbc21c08a1ddfb147025fee10274cdcd5c1bdeee88eae253aeffffffff01d85900000000000017a914a23eb2a1ed4003d357770120f5c370e199ee55468700000000"
        )
示例#3
0
    def test_attack_change_input(self, 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("48'/1'/0'/0/0")
        attacker_multisig_public_key = bytes.fromhex(
            "03653a148b68584acb97947344a7d4fd6a6f8b8485cad12987ff8edac874268088"
        )

        input_real = proto.TxInputType(
            address_n=address_n,
            prev_hash=TXHASH_fbbff7,
            prev_index=1,
            script_type=proto.InputScriptType.SPENDP2SHWITNESS,
            amount=1000000,
        )

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

        input_fake = proto.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 = proto.TxOutputType(
            address="n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi",
            amount=1000,
            script_type=proto.OutputScriptType.PAYTOADDRESS,
        )

        output_change = proto.TxOutputType(
            address_n=address_n,
            amount=input_real.amount - output_payee.amount - 1000,
            script_type=proto.OutputScriptType.PAYTOP2SHWITNESS,
            multisig=multisig_fake,
        )

        attack_count = 2

        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

        client.set_filter(proto.TxAck, attack_processor)
        with client:
            client.set_expected_responses([
                request_input(0),
                request_meta(TXHASH_fbbff7),
                request_input(0, TXHASH_fbbff7),
                request_output(0, TXHASH_fbbff7),
                request_output(1, TXHASH_fbbff7),
                request_output(0),
                proto.ButtonRequest(code=B.ConfirmOutput),
                request_output(1),
                proto.ButtonRequest(code=B.SignTx),
                request_input(0),
                request_output(0),
                request_output(1),
                request_input(0),
                proto.Failure(code=proto.FailureType.ProcessError),
            ])

            with pytest.raises(TrezorFailure) as exc:
                btc.sign_tx(
                    client,
                    "Testnet",
                    [input_real],
                    [output_payee, output_change],
                    prev_txes=TxCache("Testnet"),
                )
                # must not produce this tx:
                # 01000000000101396e2c107427f9eaece56a37539983adb8efd52b067c3d4567805fc8f3f7bffb01000000171600147a876a07b366f79000b441335f2907f777a0280bffffffff02e8030000000000001976a914e7c1345fc8f87c68170b3aa798a956c2fe6a9eff88ac703a0f000000000017a914a1261837f1b40e84346b1504ffe294e402965f2687024830450221009ff835e861be4e36ca1f2b6224aee2f253dfb9f456b13e4b1724bb4aaff4c9c802205e10679c2ead85743119f468cba5661f68b7da84dd2d477a7215fef98516f1f9012102af12ddd0d55e4fa2fcd084148eaf5b0b641320d0431d63d1e9a90f3cbd0d540700000000

            assert exc.value.code == proto.FailureType.ProcessError
            if client.features.model == "1":
                assert exc.value.message.endswith("Failed to compile input")
            else:
                assert exc.value.message.endswith(
                    "Transaction has changed during signing")
    def test_send_bch_multisig_wrongchange(self):
        self.setup_mnemonic_allallall()
        xpubs = []
        for n in map(
                lambda index: btc.get_public_node(
                    self.client, parse_path("44'/145'/%d'" % index)),
                range(1, 4),
        ):
            xpubs.append(n.xpub)

        def getmultisig(chain, nr, signatures=[b"", b"", b""], xpubs=xpubs):
            return proto.MultisigRedeemScriptType(
                pubkeys=list(
                    map(
                        lambda xpub: proto.HDNodePathType(
                            node=deserialize(xpub), address_n=[chain, nr]),
                        xpubs,
                    )),
                signatures=signatures,
                m=2,
            )

        correcthorse = proto.HDNodeType(
            depth=1,
            fingerprint=0,
            child_num=0,
            chain_code=bytes.fromhex(
                "0000000000000000000000000000000000000000000000000000000000000000"
            ),
            public_key=bytes.fromhex(
                "0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71"
            ),
        )
        sig = bytes.fromhex(
            "304402207274b5a4d15e75f3df7319a375557b0efba9b27bc63f9f183a17da95a6125c94022000efac57629f1522e2d3958430e2ef073b0706cfac06cce492651b79858f09ae"
        )
        inp1 = proto.TxInputType(
            address_n=parse_path("44'/145'/1'/1/0"),
            multisig=getmultisig(1, 0, [b"", sig, b""]),
            # bitcoincash:pp6kcpkhua7789g2vyj0qfkcux3yvje7euhyhltn0a
            amount=24000,
            prev_hash=bytes.fromhex(
                "f68caf10df12d5b07a34601d88fa6856c6edcbf4d05ebef3486510ae1c293d5f"
            ),
            prev_index=1,
            script_type=proto.InputScriptType.SPENDMULTISIG,
        )
        out1 = proto.TxOutputType(
            address_n=parse_path("44'/145'/1'/1/1"),
            multisig=proto.MultisigRedeemScriptType(
                pubkeys=[
                    proto.HDNodePathType(node=deserialize(xpubs[0]),
                                         address_n=[1, 1]),
                    proto.HDNodePathType(node=correcthorse, address_n=[]),
                    proto.HDNodePathType(node=correcthorse, address_n=[]),
                ],
                signatures=[b"", b"", b""],
                m=2,
            ),
            script_type=proto.OutputScriptType.PAYTOMULTISIG,
            amount=23000,
        )
        with self.client:
            self.client.set_expected_responses([
                proto.TxRequest(
                    request_type=proto.RequestType.TXINPUT,
                    details=proto.TxRequestDetailsType(request_index=0),
                ),
                proto.TxRequest(
                    request_type=proto.RequestType.TXOUTPUT,
                    details=proto.TxRequestDetailsType(request_index=0),
                ),
                proto.ButtonRequest(
                    code=proto.ButtonRequestType.ConfirmOutput),
                proto.ButtonRequest(code=proto.ButtonRequestType.SignTx),
                proto.TxRequest(
                    request_type=proto.RequestType.TXINPUT,
                    details=proto.TxRequestDetailsType(request_index=0),
                ),
                proto.TxRequest(
                    request_type=proto.RequestType.TXOUTPUT,
                    details=proto.TxRequestDetailsType(request_index=0),
                ),
                proto.TxRequest(request_type=proto.RequestType.TXFINISHED),
            ])
            (signatures1, serialized_tx) = btc.sign_tx(self.client,
                                                       "Bcash", [inp1], [out1],
                                                       prev_txes=TX_API)
        assert (
            signatures1[0].hex() ==
            "3044022052ccf022b3684ecce9f961ce8828387b97267c86bedf0ce16a24bf014e62e42c022035d315ddbeeef7ab3456bd09aed8b625ea58852216b60e4b84ba9f85827d305c"
        )
        assert (
            serialized_tx.hex() ==
            "01000000015f3d291cae106548f3be5ed0f4cbedc65668fa881d60347ab0d512df10af8cf601000000fc00473044022052ccf022b3684ecce9f961ce8828387b97267c86bedf0ce16a24bf014e62e42c022035d315ddbeeef7ab3456bd09aed8b625ea58852216b60e4b84ba9f85827d305c4147304402207274b5a4d15e75f3df7319a375557b0efba9b27bc63f9f183a17da95a6125c94022000efac57629f1522e2d3958430e2ef073b0706cfac06cce492651b79858f09ae414c69522103d62b2af2272bbd67cbe30eeaf4226c7f2d57d2a0ed1aab5ab736fb40bb2f5ffe21036d5e0d7ca3589465711eec91436249d7234d3a994c219024fc75cec98fc02ae221024f58378a69b68e89301a6ff882116e0fa35446ec9bfd86532eeb05941ec1f8c853aeffffffff01d85900000000000017a9140bb11de6558871f49fc241341992ece9986f7c5c8700000000"
        )
示例#5
0
def test_send_bch_multisig_wrongchange(client: Client):
    # NOTE: fake input tx used

    nodes = [
        btc.get_public_node(client,
                            parse_path(f"m/48h/145h/{i}h/0h"),
                            coin_name="Bcash").node for i in range(1, 4)
    ]

    def getmultisig(chain, nr, signatures):
        return messages.MultisigRedeemScriptType(nodes=nodes,
                                                 address_n=[chain, nr],
                                                 signatures=signatures,
                                                 m=2)

    correcthorse = messages.HDNodeType(
        depth=1,
        fingerprint=0,
        child_num=0,
        chain_code=bytes.fromhex(
            "0000000000000000000000000000000000000000000000000000000000000000"
        ),
        public_key=bytes.fromhex(
            "0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71"
        ),
    )
    sig = bytes.fromhex(
        "304402207274b5a4d15e75f3df7319a375557b0efba9b27bc63f9f183a17da95a6125c94022000efac57629f1522e2d3958430e2ef073b0706cfac06cce492651b79858f09ae"
    )
    inp1 = messages.TxInputType(
        address_n=parse_path("m/48h/145h/1h/0h/1/0"),
        multisig=getmultisig(1, 0, [b"", sig, b""]),
        # bitcoincash:pp6kcpkhua7789g2vyj0qfkcux3yvje7euhyhltn0a
        amount=24_000,
        prev_hash=FAKE_TXHASH_062fbd,
        prev_index=1,
        script_type=messages.InputScriptType.SPENDMULTISIG,
    )
    out1 = messages.TxOutputType(
        address_n=parse_path("m/48h/145h/1h/0h/1/1"),
        multisig=messages.MultisigRedeemScriptType(
            pubkeys=[
                messages.HDNodePathType(node=nodes[0], address_n=[1, 1]),
                messages.HDNodePathType(node=correcthorse, address_n=[]),
                messages.HDNodePathType(node=correcthorse, address_n=[]),
            ],
            signatures=[b"", b"", b""],
            m=2,
        ),
        script_type=messages.OutputScriptType.PAYTOMULTISIG,
        amount=23_000,
    )
    with client:
        client.set_expected_responses([
            request_input(0),
            request_output(0),
            messages.ButtonRequest(code=B.ConfirmOutput),
            messages.ButtonRequest(code=B.SignTx),
            request_input(0),
            request_meta(FAKE_TXHASH_062fbd),
            request_input(0, FAKE_TXHASH_062fbd),
            request_output(0, FAKE_TXHASH_062fbd),
            request_output(1, FAKE_TXHASH_062fbd),
            request_input(0),
            request_output(0),
            request_finished(),
        ])
        (signatures1, serialized_tx) = btc.sign_tx(client,
                                                   "Bcash", [inp1], [out1],
                                                   prev_txes=TX_API)
    assert (
        signatures1[0].hex() ==
        "3044022044d6faf6cca46c368a24220079863e5fb608192eb33e0726d8f529980465122302202903f91c1cc32dee5530f1fc5b88aad2563b44ba74471fd4903b974124db25da"
    )
    assert (
        serialized_tx.hex() ==
        "01000000015d681a1a78ec93860765b43a15e58ac40b399e894881f4dab40da691f4bd2f0601000000fc00473044022044d6faf6cca46c368a24220079863e5fb608192eb33e0726d8f529980465122302202903f91c1cc32dee5530f1fc5b88aad2563b44ba74471fd4903b974124db25da4147304402207274b5a4d15e75f3df7319a375557b0efba9b27bc63f9f183a17da95a6125c94022000efac57629f1522e2d3958430e2ef073b0706cfac06cce492651b79858f09ae414c69522102962724052105f03332ab700812afc5ca665d264b13339be1fe7f7fdd3a2a685821024364cd1fdc2aa05bc8b09874a57aa1082a47ac9062d35f22ed5f4afefb3f67fc21024d375b44804f3b0c3493ea0806eb25cc85f51e0d616d6bd6e4ef0388e71cd29e53aeffffffff01d85900000000000017a9140d5566bfc721e6c3d5ab583841d387f3939ffed38700000000"
    )