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
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" )
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" )
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" )