Beispiel #1
0
 def test_sign_input(self):
     private_key = PrivateKey(secret=8675309)
     tx_ins = []
     prev_tx = bytes.fromhex(
         "0025bc3c0fa8b7eb55b9437fdbd016870d18e0df0ace7bc9864efc38414147c8"
     )
     tx_ins.append(TxIn(prev_tx, 0))
     tx_outs = [
         TxOut.to_address("mzx5YhAH9kNHtcN481u6WkjeHjYtVeKVh2", 99000000),
         TxOut.to_address("mnrVtF8DWjMu839VW3rBfgYaAfKk8983Xf", 10000000),
     ]
     tx = Tx(1, tx_ins, tx_outs, 0, network="testnet")
     self.assertTrue(tx.sign_input(0, private_key))
Beispiel #2
0
 def test_sign_p2wpkh(self):
     private_key = PrivateKey(secret=8675309)
     prev_tx = bytes.fromhex(
         "6bfa079532dd9fad6cfbf218edc294fdfa7dd0cb3956375bc864577fb36fad97"
     )
     prev_index = 0
     fee = 500
     tx_in = TxIn(prev_tx, prev_index)
     amount = tx_in.value(network="testnet") - fee
     tx_out = TxOut.to_address("mqYz6JpuKukHzPg94y4XNDdPCEJrNkLQcv", amount)
     t = Tx(1, [tx_in], [tx_out], 0, network="testnet", segwit=True)
     self.assertTrue(t.sign_input(0, private_key))
     want = "0100000000010197ad6fb37f5764c85b375639cbd07dfafd94c2ed18f2fb6cad9fdd329507fa6b0000000000ffffffff014c400f00000000001976a9146e13971913b9aa89659a9f53d327baa8826f2d7588ac02483045022100feab5b8feefd5e774bdfdc1dc23525b40f1ffaa25a376f8453158614f00fa6cb02204456493d0bc606ebeb3fa008e056bbc96a67cb0c11abcc871bfc2bec60206bf0012103935581e52c354cd2f484fe8ed83af7a3097005b2f9c60bff71d35bd795f54b6700000000"
     self.assertEqual(t.serialize().hex(), want)
Beispiel #3
0
 def test_sign_p2sh_p2wpkh(self):
     private_key = PrivateKey(secret=8675309)
     redeem_script = private_key.point.p2sh_p2wpkh_redeem_script()
     prev_tx = bytes.fromhex(
         "2e19b463bd5c8a3e0f10ae827f5a670f6794fca96394ecf8488321291d1c2ee9"
     )
     prev_index = 1
     fee = 500
     tx_in = TxIn(prev_tx, prev_index)
     amount = tx_in.value(network="testnet") - fee
     tx_out = TxOut.to_address("mqYz6JpuKukHzPg94y4XNDdPCEJrNkLQcv", amount)
     t = Tx(1, [tx_in], [tx_out], 0, network="testnet", segwit=True)
     self.assertTrue(t.sign_input(0, private_key, redeem_script=redeem_script))
     want = "01000000000101e92e1c1d29218348f8ec9463a9fc94670f675a7f82ae100f3e8a5cbd63b4192e0100000017160014d52ad7ca9b3d096a38e752c2018e6fbc40cdf26fffffffff014c400f00000000001976a9146e13971913b9aa89659a9f53d327baa8826f2d7588ac0247304402205e3ae5ac9a0e0a16ae04b0678c5732973ce31051ba9f42193e69843e600d84f2022060a91cbd48899b1bf5d1ffb7532f69ab74bc1701a253a415196b38feb599163b012103935581e52c354cd2f484fe8ed83af7a3097005b2f9c60bff71d35bd795f54b6700000000"
     self.assertEqual(t.serialize().hex(), want)
Beispiel #4
0
 def test_op_cltv(self):
     locktime_0 = Locktime(1234)
     locktime_1 = Locktime(2345)
     sequence = Sequence()
     tx_in = TxIn(b"\x00" * 32, 0, sequence=sequence)
     tx_out = TxOut(1, Script())
     tx_obj = Tx(1, [tx_in], [tx_out], locktime_1)
     stack = []
     self.assertFalse(op_checklocktimeverify(stack, tx_obj, 0))
     tx_in.sequence = Sequence(0xFFFFFFFE)
     self.assertFalse(op_checklocktimeverify(stack, tx_obj, 0))
     stack = [encode_num(-5)]
     self.assertFalse(op_checklocktimeverify(stack, tx_obj, 0))
     stack = [encode_num(locktime_0)]
     self.assertTrue(op_checklocktimeverify(stack, tx_obj, 0))
     tx_obj.locktime = Locktime(1582820194)
     self.assertFalse(op_checklocktimeverify(stack, tx_obj, 0))
     tx_obj.locktime = Locktime(500)
     self.assertFalse(op_checklocktimeverify(stack, tx_obj, 0))
Beispiel #5
0
 def test_sign_p2sh_multisig(self):
     private_key1 = PrivateKey(secret=8675309)
     private_key2 = PrivateKey(secret=8675310)
     redeem_script = RedeemScript.create_p2sh_multisig(
         quorum_m=2,
         pubkey_hexes=[
             private_key1.point.sec().hex(),
             private_key2.point.sec().hex(),
         ],
         sort_keys=False,
     )
     prev_tx = bytes.fromhex(
         "ded9b3c8b71032d42ea3b2fd5211d75b39a90637f967e637b64dfdb887dd11d7"
     )
     prev_index = 1
     fee_sats = 500
     tx_in = TxIn(prev_tx, prev_index)
     tx_in_sats = 1000000
     amount = tx_in_sats - fee_sats
     tx_out = TxOut.to_address("mqYz6JpuKukHzPg94y4XNDdPCEJrNkLQcv", amount)
     t = Tx(1, [tx_in], [tx_out], 0, network="testnet", segwit=True)
     sig1 = t.get_sig_legacy(0, private_key1, redeem_script=redeem_script)
     sig2 = t.get_sig_legacy(0, private_key2, redeem_script=redeem_script)
     self.assertTrue(
         t.check_sig_legacy(
             0,
             private_key1.point,
             Signature.parse(sig1[:-1]),
             redeem_script=redeem_script,
         )
     )
     self.assertTrue(
         t.check_sig_legacy(
             0,
             private_key2.point,
             Signature.parse(sig2[:-1]),
             redeem_script=redeem_script,
         )
     )
     tx_in.finalize_p2sh_multisig([sig1, sig2], redeem_script)
     want = "01000000000101d711dd87b8fd4db637e667f93706a9395bd71152fdb2a32ed43210b7c8b3d9de01000000da00483045022100c457fa45f63636eb2552cef642116a8363469d60b99dcda19686d30ed2a539bb0220222c7617e3dd9aef37095df52047e9a6bf11254a88eab521aec1b8b4e7913b3401473044022003d3d6a1b232b42d9fb961b42ab6854077a1e195473d952d54e6dcf22ef6dede02206f62a44b65e1dbccbdd54a3fd6f87c05a8d8da39c70e06f5ee07d469e1155e020147522103935581e52c354cd2f484fe8ed83af7a3097005b2f9c60bff71d35bd795f54b672103674944c63d8dc3373a88cd1f8403b39b48be07bdb83d51dbbaa34be070c72e1452aeffffffff014c400f00000000001976a9146e13971913b9aa89659a9f53d327baa8826f2d7588ac0000000000"
     self.assertEqual(t.serialize().hex(), want)
Beispiel #6
0
 def test_op_csv(self):
     sequence_0 = Sequence()
     sequence_1 = Sequence(2345)
     tx_in = TxIn(b"\x00" * 32, 0, sequence=sequence_0)
     tx_out = TxOut(1, Script())
     tx_obj = Tx(1, [tx_in], [tx_out])
     stack = []
     self.assertFalse(op_checksequenceverify(stack, tx_obj, 0))
     tx_in.sequence = sequence_1
     self.assertFalse(op_checksequenceverify(stack, tx_obj, 0))
     stack = [encode_num(-5)]
     self.assertFalse(op_checksequenceverify(stack, tx_obj, 0))
     tx_obj.version = 2
     self.assertFalse(op_checksequenceverify(stack, tx_obj, 0))
     stack = [encode_num(1234 | (1 << 22))]
     self.assertFalse(op_checksequenceverify(stack, tx_obj, 0))
     stack = [encode_num(9999)]
     self.assertFalse(op_checksequenceverify(stack, tx_obj, 0))
     stack = [encode_num(1234)]
     self.assertTrue(op_checksequenceverify(stack, tx_obj, 0))
Beispiel #7
0
 def test_sign_p2pkh(self):
     private_key = PrivateKey(secret=8675309)
     tx_ins = []
     prev_tx = bytes.fromhex(
         "448c1cf931cb8a35d648b75a63c7dbdc6d81a8b82be07c055d599a4ce810a20a"
     )
     tx_ins.append(TxIn(prev_tx, 0))
     tx_outs = [
         TxOut.to_address("mzx5YhAH9kNHtcN481u6WkjeHjYtVeKVh2", 5999000),
         TxOut.to_address("tb1qjfavna0z7r484w674f723w7g4jpeaplttt464w", 1000000),
         TxOut.to_address(
             "tb1qdhd06yyf7pazh2vx3hm37c3gq8lpra2993hlr784z4e3xwpgksmsceq9wc",
             1000000,
         ),
         TxOut.to_address("2MyJsxLnxj7DsNch4xE7B3nMpB94kDPoE2s", 1000000),
         TxOut.to_address(
             "tb1p9gpzhc5fhlwlf49ze00fgjszxh5pl2p7az76758xwarweq08gcas8qa0r7",
             1000000,
         ),
     ]
     tx_obj = Tx(1, tx_ins, tx_outs, 0, network="signet")
     self.assertTrue(tx_obj.sign_p2pkh(0, private_key))
Beispiel #8
0
    def test_sign_p2sh_p2wsh_multisig(self):
        private_key1 = PrivateKey(secret=8675309)
        private_key2 = PrivateKey(secret=8675310)
        witness_script = WitnessScript(
            [0x52, private_key1.point.sec(), private_key2.point.sec(), 0x52, 0xAE]
        )
        prev_tx = bytes.fromhex(
            "f92c8c8e40296c6a94539b6d22d8994a56dd8ff2d6018d07a8371fef1f66efee"
        )
        prev_index = 0
        fee = 500
        tx_in = TxIn(prev_tx, prev_index)
        amount = tx_in.value(network="testnet") - fee
        tx_out = TxOut.to_address("mqYz6JpuKukHzPg94y4XNDdPCEJrNkLQcv", amount)
        t = Tx(1, [tx_in], [tx_out], 0, network="testnet", segwit=True)
        sig1 = t.get_sig_segwit(0, private_key1, witness_script=witness_script)
        sig2 = t.get_sig_segwit(0, private_key2, witness_script=witness_script)
        self.assertTrue(
            t.check_sig_segwit(
                0,
                private_key1.point,
                Signature.parse(sig1[:-1]),
                witness_script=witness_script,
            )
        )
        self.assertTrue(
            t.check_sig_segwit(
                0,
                private_key2.point,
                Signature.parse(sig2[:-1]),
                witness_script=witness_script,
            )
        )
        tx_in.finalize_p2sh_p2wsh_multisig([sig1, sig2], witness_script)

        want = "01000000000101eeef661fef1f37a8078d01d6f28fdd564a99d8226d9b53946a6c29408e8c2cf900000000232200206ddafd1089f07a2ba9868df71f622801fe11f5452c6ff1f8f51573133828b437ffffffff014c400f00000000001976a9146e13971913b9aa89659a9f53d327baa8826f2d7588ac0400483045022100d31433973b7f8014a4e17d46c4720c6c9bed1ee720dc1f0839dd847fa6972553022039278e98a3c18f4748a2727b99acd41eb1534dcf041a3abefd0c7546c868f55801473044022027be7d616b0930c1edf7ed39cc99edf5975e7b859d3224fe340d55c595c2798f02206c05662d39e5b05cc13f936360d62a482b122ad9791074bbdafec3ddc221b8c00147522103935581e52c354cd2f484fe8ed83af7a3097005b2f9c60bff71d35bd795f54b672103674944c63d8dc3373a88cd1f8403b39b48be07bdb83d51dbbaa34be070c72e1452ae00000000"
        self.assertEqual(t.serialize().hex(), want)
Beispiel #9
0
 def test_sign_p2wsh_multisig(self):
     private_key1 = PrivateKey(secret=8675309)
     private_key2 = PrivateKey(secret=8675310)
     witness_script = WitnessScript(
         [0x52, private_key1.point.sec(), private_key2.point.sec(), 0x52, 0xAE]
     )
     prev_tx = bytes.fromhex(
         "61cd20e3ffdf9216cee9cd607e1a65d3096513c4df3a63d410c047379b54a94a"
     )
     prev_index = 1
     fee = 500
     tx_in = TxIn(prev_tx, prev_index)
     amount = tx_in.value(network="testnet") - fee
     tx_out = TxOut.to_address("mqYz6JpuKukHzPg94y4XNDdPCEJrNkLQcv", amount)
     t = Tx(1, [tx_in], [tx_out], 0, network="testnet", segwit=True)
     sig1 = t.get_sig_segwit(0, private_key1, witness_script=witness_script)
     sig2 = t.get_sig_segwit(0, private_key2, witness_script=witness_script)
     self.assertTrue(
         t.check_sig_segwit(
             0,
             private_key1.point,
             Signature.parse(sig1[:-1]),
             witness_script=witness_script,
         )
     )
     self.assertTrue(
         t.check_sig_segwit(
             0,
             private_key2.point,
             Signature.parse(sig2[:-1]),
             witness_script=witness_script,
         )
     )
     tx_in.finalize_p2wsh_multisig([sig1, sig2], witness_script)
     want = "010000000001014aa9549b3747c010d4633adfc4136509d3651a7e60cde9ce1692dfffe320cd610100000000ffffffff014c400f00000000001976a9146e13971913b9aa89659a9f53d327baa8826f2d7588ac04004730440220325e9f389c4835dab74d644e8c8e295535d9b082d28aefc3fa127e23538051bd022050d68dcecda660d4c01a8443c2b30bd0b3e4b1a405b0f352dcb068210862f6810147304402201abceabfc94903644cf7be836876eaa418cb226e03554c17a71c65b232f4507302202105a8344abae9632d1bc8249a52cf651c4ea02ca5259e20b50d8169c949f5a20147522103935581e52c354cd2f484fe8ed83af7a3097005b2f9c60bff71d35bd795f54b672103674944c63d8dc3373a88cd1f8403b39b48be07bdb83d51dbbaa34be070c72e1452ae00000000"
     self.assertEqual(t.serialize().hex(), want)
Beispiel #10
0
def create_multisig_psbt(
    public_key_records,
    input_dicts,
    output_dicts,
    fee_sats,
    script_type="p2sh",
):
    """
    Helper method to create a multisig PSBT whose change can be validated.

    network (testnet/mainnet/signet) will be inferred from xpubs/tpubs.

    public_key_records are a list of entries that loom like this: [xfp_hex, xpub_b58, base_path]
    # TODO: turn this into a new object?
    """
    if script_type != "p2sh":
        raise NotImplementedError(f"script_type {script_type} not yet implemented")

    # initialize variables
    network = None
    tx_lookup, pubkey_lookup, redeem_lookup, hd_pubs = {}, {}, {}, {}

    # Use a nested default dict for increased readability
    # It's possible (though nonstandard) for one xfp to have multiple public_key_records in a multisig wallet
    # https://stackoverflow.com/a/19189356
    recursive_defaultdict = lambda: defaultdict(recursive_defaultdict)  # noqa: E731
    xfp_dict = recursive_defaultdict()

    # This at the child pubkey lookup that each input will traverse off of
    for xfp_hex, xpub_b58, base_path in public_key_records:
        hd_pubkey_obj = HDPublicKey.parse(xpub_b58)

        # We will use this dict/list structure for each input/ouput in the for-loops below
        xfp_dict[xfp_hex][base_path] = hd_pubkey_obj

        named_global_hd_pubkey_obj = NamedHDPublicKey.from_hd_pub(
            child_hd_pub=hd_pubkey_obj,
            xfp_hex=xfp_hex,
            # we're only going to base path level
            path=base_path,
        )
        hd_pubs[named_global_hd_pubkey_obj.serialize()] = named_global_hd_pubkey_obj

        if network is None:
            # Set the initial value
            network = hd_pubkey_obj.network
        else:
            # Confirm it hasn't changed
            if network != hd_pubkey_obj.network:
                raise MixedNetwork(
                    f"Mixed networks in public key records: {public_key_records}"
                )

    tx_ins, total_input_sats = [], 0
    for cnt, input_dict in enumerate(input_dicts):

        # Prev tx stuff
        prev_tx_dict = input_dict["prev_tx_dict"]
        prev_tx_obj = Tx.parse_hex(prev_tx_dict["hex"], network=network)
        tx_lookup[prev_tx_obj.hash()] = prev_tx_obj

        if prev_tx_dict["hash_hex"] != prev_tx_obj.hash().hex():
            raise ValueError(
                f"Hash digest mismatch for input #{cnt}: {prev_tx_dict['hash_hex']} != {prev_tx_obj.hash().hex()}"
            )

        if "path_dict" in input_dict:
            # Standard BIP67 unordered list of pubkeys (will be sorted lexicographically)
            iterator = input_dict["path_dict"].items()
            sort_keys = True
        elif "path_list" in input_dict:
            # Caller supplied ordering of pubkeys (will not be sorted)
            iterator = input_dict["path_list"]
            sort_keys = False
        else:
            raise RuntimeError(
                f"input_dict has no `path_dict` nor a `path_list`: {input_dict}"
            )

        input_pubkey_hexes = []
        for xfp_hex, root_path in iterator:
            # Get the correct xpub/path
            child_hd_pubkey = _safe_get_child_hdpubkey(
                xfp_dict=xfp_dict,
                xfp_hex=xfp_hex,
                root_path=root_path,
                cnt=cnt,
            )
            input_pubkey_hexes.append(child_hd_pubkey.sec().hex())

            # Enhance the PSBT
            named_hd_pubkey_obj = NamedHDPublicKey.from_hd_pub(
                child_hd_pub=child_hd_pubkey,
                xfp_hex=xfp_hex,
                path=root_path,
            )
            # pubkey lookups needed for validation
            pubkey_lookup[named_hd_pubkey_obj.sec()] = named_hd_pubkey_obj

        utxo = prev_tx_obj.tx_outs[prev_tx_dict["output_idx"]]

        # Grab amount as developer safety check
        if prev_tx_dict["output_sats"] != utxo.amount:
            raise ValueError(
                f"Wrong number of sats for input #{cnt}! Expecting {prev_tx_dict['output_sats']} but got {utxo.amount}"
            )
        total_input_sats += utxo.amount

        redeem_script = RedeemScript.create_p2sh_multisig(
            quorum_m=input_dict["quorum_m"],
            pubkey_hexes=input_pubkey_hexes,
            sort_keys=sort_keys,
            expected_addr=utxo.script_pubkey.address(network=network),
            expected_addr_network=network,
        )

        # Confirm address matches previous ouput
        if redeem_script.address(network=network) != utxo.script_pubkey.address(
            network=network
        ):
            raise ValueError(
                f"Invalid redeem script for input #{cnt}. Expecting {redeem_script.address(network=network)} but got {utxo.script_pubkey.address(network=network)}"
            )

        tx_in = TxIn(prev_tx=prev_tx_obj.hash(), prev_index=prev_tx_dict["output_idx"])
        tx_ins.append(tx_in)

        # For enhancing the PSBT for HWWs:
        redeem_lookup[redeem_script.hash160()] = redeem_script

    tx_outs = []
    for cnt, output_dict in enumerate(output_dicts):
        tx_out = TxOut(
            amount=output_dict["sats"],
            script_pubkey=address_to_script_pubkey(output_dict["address"]),
        )
        tx_outs.append(tx_out)

        if output_dict.get("path_dict"):
            # This output claims to be change, so we must validate it here
            output_pubkey_hexes = []
            for xfp_hex, root_path in output_dict["path_dict"].items():
                child_hd_pubkey = _safe_get_child_hdpubkey(
                    xfp_dict=xfp_dict,
                    xfp_hex=xfp_hex,
                    root_path=root_path,
                    cnt=cnt,
                )
                output_pubkey_hexes.append(child_hd_pubkey.sec().hex())

                # Enhance the PSBT
                named_hd_pubkey_obj = NamedHDPublicKey.from_hd_pub(
                    child_hd_pub=child_hd_pubkey,
                    xfp_hex=xfp_hex,
                    path=root_path,
                )
                pubkey_lookup[named_hd_pubkey_obj.sec()] = named_hd_pubkey_obj

            redeem_script = RedeemScript.create_p2sh_multisig(
                quorum_m=output_dict["quorum_m"],
                pubkey_hexes=output_pubkey_hexes,
                # We intentionally only allow change addresses to be lexicographically sorted
                sort_keys=True,
            )
            # Confirm address matches previous ouput
            if redeem_script.address(network=network) != output_dict["address"]:
                raise ValueError(
                    f"Invalid redeem script for output #{cnt}. Expecting {redeem_script.address(network=network)} but got {output_dict['address']}"
                )

            # For enhancing the PSBT for HWWs:
            redeem_lookup[redeem_script.hash160()] = redeem_script

    tx_obj = Tx(
        version=1,
        tx_ins=tx_ins,
        tx_outs=tx_outs,
        locktime=0,
        network=network,
        segwit=False,
    )

    # Safety check to try and prevent footgun

    calculated_fee_sats = total_input_sats - sum([tx_out.amount for tx_out in tx_outs])
    if fee_sats != calculated_fee_sats:
        raise ValueError(
            f"TX fee of {fee_sats} sats supplied != {calculated_fee_sats} sats calculated"
        )

    return PSBT.create(
        tx_obj=tx_obj,
        validate=True,
        tx_lookup=tx_lookup,
        pubkey_lookup=pubkey_lookup,
        redeem_lookup=redeem_lookup,
        witness_lookup={},
        hd_pubs=hd_pubs,
    )