コード例 #1
0
 def test_coinbase_height(self):
     raw_tx = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff5e03d71b07254d696e656420627920416e74506f6f6c20626a31312f4542312f4144362f43205914293101fabe6d6d678e2c8c34afc36896e7d9402824ed38e856676ee94bfdb0c6c4bcd8b2e5666a0400000000000000c7270000a5e00e00ffffffff01faf20b58000000001976a914338c84849423992471bffb1a54a8d9b1d69dc28a88ac00000000"
     tx = Tx.parse_hex(raw_tx)
     self.assertEqual(tx.coinbase_height(), 465879)
     raw_tx = "0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600"
     tx = Tx.parse_hex(raw_tx)
     self.assertIsNone(tx.coinbase_height())
コード例 #2
0
 def test_fee(self):
     raw_tx = "0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600"
     tx = Tx.parse_hex(raw_tx)
     self.assertEqual(tx.fee(), 40000)
     raw_tx = "010000000456919960ac691763688d3d3bcea9ad6ecaf875df5339e148a1fc61c6ed7a069e010000006a47304402204585bcdef85e6b1c6af5c2669d4830ff86e42dd205c0e089bc2a821657e951c002201024a10366077f87d6bce1f7100ad8cfa8a064b39d4e8fe4ea13a7b71aa8180f012102f0da57e85eec2934a82a585ea337ce2f4998b50ae699dd79f5880e253dafafb7feffffffeb8f51f4038dc17e6313cf831d4f02281c2a468bde0fafd37f1bf882729e7fd3000000006a47304402207899531a52d59a6de200179928ca900254a36b8dff8bb75f5f5d71b1cdc26125022008b422690b8461cb52c3cc30330b23d574351872b7c361e9aae3649071c1a7160121035d5c93d9ac96881f19ba1f686f15f009ded7c62efe85a872e6a19b43c15a2937feffffff567bf40595119d1bb8a3037c356efd56170b64cbcc160fb028fa10704b45d775000000006a47304402204c7c7818424c7f7911da6cddc59655a70af1cb5eaf17c69dadbfc74ffa0b662f02207599e08bc8023693ad4e9527dc42c34210f7a7d1d1ddfc8492b654a11e7620a0012102158b46fbdff65d0172b7989aec8850aa0dae49abfb84c81ae6e5b251a58ace5cfeffffffd63a5e6c16e620f86f375925b21cabaf736c779f88fd04dcad51d26690f7f345010000006a47304402200633ea0d3314bea0d95b3cd8dadb2ef79ea8331ffe1e61f762c0f6daea0fabde022029f23b3e9c30f080446150b23852028751635dcee2be669c2a1686a4b5edf304012103ffd6f4a67e94aba353a00882e563ff2722eb4cff0ad6006e86ee20dfe7520d55feffffff0251430f00000000001976a914ab0c0b2e98b1ab6dbf67d4750b0a56244948a87988ac005a6202000000001976a9143c82d7df364eb6c75be8c80df2b3eda8db57397088ac46430600"
     tx = Tx.parse_hex(raw_tx)
     self.assertEqual(tx.fee(), 140500)
コード例 #3
0
 def test_sig_hash_bip143(self):
     raw_tx = "0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560100000000ffffffff0100b4f505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02483045022100df7b7e5cda14ddf91290e02ea10786e03eb11ee36ec02dd862fe9a326bbcb7fd02203f5b4496b667e6e281cc654a2da9e4f08660c620a1051337fa8965f727eb19190121038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990ac00000000"
     tx = Tx.parse_hex(raw_tx, network="testnet")
     want = int(
         "12bb9e0988736b8d1c3a180acd828b8a7eddae923a6a4bf0b4c14c40cd7327d1", 16
     )
     self.assertEqual(tx.sig_hash_legacy(0), want)
     tx = Tx.parse_hex(raw_tx, network="signet")
     self.assertEqual(tx.sig_hash_legacy(0), want)
コード例 #4
0
 def test_sig_hash(self):
     raw_tx = "0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600"
     tx = Tx.parse_hex(raw_tx)
     want = int(
         "27e0c5994dec7824e56dec6b2fcb342eb7cdb0d0957c2fce9882f715e85d81a6", 16
     )
     self.assertEqual(tx.sig_hash_legacy(0), want)
コード例 #5
0
    def test_serialize(self):
        raw_tx = "0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600"
        tx = Tx.parse_hex(raw_tx)
        self.assertEqual(tx.serialize().hex(), raw_tx)

        # simple test to show repr works (otherwise this would throw an error)
        str(tx)
コード例 #6
0
 def test_parse_segwit(self):
     raw_tx = "01000000000101c70c4ede5731f1b47a89d133be9244927fa12e15778ec78a7e071273c0c58a870400000000ffffffff02809698000000000017a9144f34d55c56f827169921df008e8dfdc23678fc1787d464da1f00000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d0400473044022050a5a50e78e6f9c65b5d94c78f8e4b339848456ff7c2231702b4a37439e2a3bd02201569cbf1c672bbb1608d6e9feea28705d8d6e54aa51d9fa396469be6ffc83c2d0147304402200b69a83cc3e3e1694037ef639049b0ece00f15718a03e9038aa42ac9d1bd0ea50220780c510821cd5205e5d178e6277005f4dd61a7fcccd4f8fae9e2d2adc355e728016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000"
     tx = Tx.parse_hex(raw_tx)
     self.assertTrue(tx.segwit)
     self.assertEqual(tx.version, 1)
     self.assertEqual(tx.tx_ins[0].prev_index, 4)
     self.assertEqual(tx.tx_outs[0].amount, 10000000)
     self.assertEqual(tx.locktime, 0)
コード例 #7
0
 def test_parse_outputs(self):
     raw_tx = "0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600"
     tx = Tx.parse_hex(raw_tx)
     self.assertEqual(len(tx.tx_outs), 2)
     want = 32454049
     self.assertEqual(tx.tx_outs[0].amount, want)
     want = bytes.fromhex("1976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac")
     self.assertEqual(tx.tx_outs[0].script_pubkey.serialize(), want)
     want = 10011545
     self.assertEqual(tx.tx_outs[1].amount, want)
     want = bytes.fromhex("1976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac")
     self.assertEqual(tx.tx_outs[1].script_pubkey.serialize(), want)
コード例 #8
0
 def test_parse_inputs(self):
     raw_tx = "0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600"
     tx = Tx.parse_hex(raw_tx)
     self.assertEqual(len(tx.tx_ins), 1)
     want = bytes.fromhex(
         "d1c789a9c60383bf715f3f6ad9d14b91fe55f3deb369fe5d9280cb1a01793f81"
     )
     self.assertEqual(tx.tx_ins[0].prev_tx, want)
     self.assertEqual(tx.tx_ins[0].prev_index, 0)
     want = bytes.fromhex(
         "6b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a"
     )
     self.assertEqual(tx.tx_ins[0].script_sig.serialize(), want)
     self.assertEqual(tx.tx_ins[0].sequence, 0xFFFFFFFE)
コード例 #9
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,
    )
コード例 #10
0
 def test_is_coinbase(self):
     raw_tx = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff5e03d71b07254d696e656420627920416e74506f6f6c20626a31312f4542312f4144362f43205914293101fabe6d6d678e2c8c34afc36896e7d9402824ed38e856676ee94bfdb0c6c4bcd8b2e5666a0400000000000000c7270000a5e00e00ffffffff01faf20b58000000001976a914338c84849423992471bffb1a54a8d9b1d69dc28a88ac00000000"
     tx = Tx.parse_hex(raw_tx)
     self.assertTrue(tx.is_coinbase())
コード例 #11
0
 def test_parse_tricky(self):
     raw_tx = "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2f0315230e0004ae03ca57043e3d1e1d0c8796bf579aef0c0000000000122f4e696e6a61506f6f6c2f5345475749542fffffffff038427a112000000001976a914876fbb82ec05caa6af7a3b5e5a983aae6c6cc6d688ac0000000000000000266a24aa21a9ed5c748e121c0fe146d973a4ac26fa4a68b0549d46ee22d25f50a5e46fe1b377ee00000000000000002952534b424c4f434b3acd16772ad61a3c5f00287480b720f6035d5e54c9efc71be94bb5e3727f10909000000000"
     tx_obj = Tx.parse_hex(raw_tx)
     self.assertEqual(tx_obj.serialize().hex(), raw_tx)