Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
251tx.vin = [CTxIn(outpoint=COutPoint(hash=int('ee' * 32, 16), n=0), scriptSig=b"")]252tx.vout = [CTxOut(nValue=0, scriptPubKey=b"")]253psbt = PSBT()254psbt.g = PSBTMap({PSBT_GLOBAL_UNSIGNED_TX: tx.serialize()})255participant1_keydata = in_pubkey1 + in_fake_agg_pubkey + fake_leaf_hash256psbt.i = [PSBTMap({257bytes([PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS]) + in_fake_agg_pubkey: [in_pubkey1, in_pubkey2],258bytes([PSBT_IN_MUSIG2_PUB_NONCE]) + participant1_keydata: fake_pubnonce,259bytes([PSBT_IN_MUSIG2_PARTIAL_SIG]) + participant1_keydata: fake_partialsig,260})]261_, out_pubkey1 = generate_keypair()262_, out_pubkey2 = generate_keypair()263_, out_fake_agg_pubkey = generate_keypair()264psbt.o = [PSBTMap({265bytes([PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS]) + out_fake_agg_pubkey: [out_pubkey1, out_pubkey2],266})]267res = self.nodes[0].decodepsbt(psbt.to_base64())268assert_equal(len(res["inputs"]), 1)269res_input = res["inputs"][0]270assert_equal(len(res["outputs"]), 1)271res_output = res["outputs"][0]273assert "musig2_participant_pubkeys" in res_input274in_participant_pks = res_input["musig2_participant_pubkeys"][0]275assert "aggregate_pubkey" in in_participant_pks276assert_equal(in_participant_pks["aggregate_pubkey"], in_fake_agg_pubkey.hex())277assert "participant_pubkeys" in in_participant_pks278assert_equal(in_participant_pks["participant_pubkeys"], [in_pubkey1.hex(), in_pubkey2.hex()])280assert "musig2_pubnonces" in res_input281in_pubnonce = res_input["musig2_pubnonces"][0]282assert "participant_pubkey" in in_pubnonce283assert_equal(in_pubnonce["participant_pubkey"], in_pubkey1.hex())284assert "aggregate_pubkey" in in_pubnonce285assert_equal(in_pubnonce["aggregate_pubkey"], in_fake_agg_pubkey.hex())286assert "leaf_hash" in in_pubnonce287assert_equal(in_pubnonce["leaf_hash"], fake_leaf_hash.hex())288assert "pubnonce" in in_pubnonce289assert_equal(in_pubnonce["pubnonce"], fake_pubnonce.hex())291assert "musig2_partial_sigs" in res_input292in_partialsig = res_input["musig2_partial_sigs"][0]293assert "participant_pubkey" in in_partialsig294assert_equal(in_partialsig["participant_pubkey"], in_pubkey1.hex())295assert "aggregate_pubkey" in in_partialsig296assert_equal(in_partialsig["aggregate_pubkey"], in_fake_agg_pubkey.hex())297assert "leaf_hash" in in_partialsig298assert_equal(in_partialsig["leaf_hash"], fake_leaf_hash.hex())299assert "partial_sig" in in_partialsig300assert_equal(in_partialsig["partial_sig"], fake_partialsig.hex())302assert "musig2_participant_pubkeys" in res_output303out_participant_pks = res_output["musig2_participant_pubkeys"][0]304assert "aggregate_pubkey" in out_participant_pks305assert_equal(out_participant_pks["aggregate_pubkey"], out_fake_agg_pubkey.hex())306assert "participant_pubkeys" in out_participant_pks307assert_equal(out_participant_pks["participant_pubkeys"], [out_pubkey1.hex(), out_pubkey2.hex()])309def test_musig2_untrusted_derivation(self):310self.log.info("Test MuSig2 aggregate derivation from untrusted PSBT fields")311node = self.nodes[0]313script_pubkey = bytes.fromhex(H_POINT)314_, aggregate_pubkey = generate_keypair()315_, participant_pubkey = generate_keypair()317# Both have a matching aggregate fingerprint but cannot derive the script pubkey: 0 derives a different key, hardened(0) cannot be derived at all318for index in [0, hardened(0)]:319psbt = self.create_psbt(inputs={320PSBT_IN_WITNESS_UTXO: CTxOut(nValue=1, scriptPubKey=output_key_to_p2tr_script(script_pubkey)).serialize(),321bytes([PSBT_IN_TAP_BIP32_DERIVATION]) + script_pubkey: ser_compact_size(0) + hash160(aggregate_pubkey)[:4] + index.to_bytes(4, "little"),322PSBT_IN_TAP_INTERNAL_KEY: script_pubkey,323bytes([PSBT_IN_MUSIG2_PARTICIPANT_PUBKEYS]) + aggregate_pubkey: [participant_pubkey],324}).to_base64()325assert_equal(node.analyzepsbt(psbt)["inputs"][0]["is_final"], False)326assert_equal(node.finalizepsbt(psbt)["complete"], False)328def test_combinepsbt_preserves_proprietary_fields(self):329self.log.info("Test that combining PSBTs preserves proprietary fields")331def proprietary_key(type_byte, identifier, subtype, key_data=b""):332return bytes([type_byte]) + ser_compact_size(len(identifier)) + identifier + ser_compact_size(subtype) + key_data334def proprietary_entry(key, value, identifier, subtype):335return {"identifier": identifier.hex(), "subtype": subtype, "key": key.hex(), "value": value.hex()}337tx = CTransaction()338tx.vin = [CTxIn(outpoint=COutPoint(hash=int('aa' * 32, 16), n=0), scriptSig=b"")]339tx.vout = [CTxOut(nValue=0, scriptPubKey=b"")]341global_key_a = proprietary_key(type_byte=PSBT_GLOBAL_PROPRIETARY, identifier=b"gc", subtype=1, key_data=b"\x01")342global_key_b = proprietary_key(type_byte=PSBT_GLOBAL_PROPRIETARY, identifier=b"gc", subtype=2, key_data=b"\x02")343input_key_a = proprietary_key(type_byte=PSBT_IN_PROPRIETARY, identifier=b"in", subtype=3, key_data=b"\x03")344input_key_b = proprietary_key(type_byte=PSBT_IN_PROPRIETARY, identifier=b"in", subtype=4, key_data=b"\x04")345output_key_a = proprietary_key(type_byte=PSBT_OUT_PROPRIETARY, identifier=b"out", subtype=5, key_data=b"\x05")346output_key_b = proprietary_key(type_byte=PSBT_OUT_PROPRIETARY, identifier=b"out", subtype=6, key_data=b"\x06")348psbt1 = PSBT(349g=PSBTMap({350PSBT_GLOBAL_UNSIGNED_TX: tx.serialize(),351global_key_a: b"\xaa",352}),353i=[PSBTMap({354input_key_a: b"\xbb",355})],356o=[PSBTMap({357output_key_a: b"\xcc",358})],359).to_base64()360psbt2 = PSBT(361g=PSBTMap({362PSBT_GLOBAL_UNSIGNED_TX: tx.serialize(),363global_key_b: b"\xdd",364}),365i=[PSBTMap({366input_key_b: b"\xee",367})],368o=[PSBTMap({369output_key_b: b"\xff",370})],371).to_base64()373decoded = self.nodes[0].decodepsbt(self.nodes[0].combinepsbt([psbt1, psbt2]))374assert_equal(decoded["proprietary"], [375proprietary_entry(key=global_key_a, value=b"\xaa", identifier=b"gc", subtype=1),376proprietary_entry(key=global_key_b, value=b"\xdd", identifier=b"gc", subtype=2),377])378assert_equal(decoded["inputs"][0]["proprietary"], [379proprietary_entry(key=input_key_a, value=b"\xbb", identifier=b"in", subtype=3),380proprietary_entry(key=input_key_b, value=b"\xee", identifier=b"in", subtype=4),381])382assert_equal(decoded["outputs"][0]["proprietary"], [383proprietary_entry(key=output_key_a, value=b"\xcc", identifier=b"out", subtype=5),384proprietary_entry(key=output_key_b, value=b"\xff", identifier=b"out", subtype=6),385])387def test_combinepsbt_global_xpub_origin_conflict(self):388self.log.info("Test that combining PSBTs with conflicting origins for the same xpub keeps a single record")390tx = CTransaction()391tx.vin = [CTxIn(outpoint=COutPoint(hash=int('aa' * 32, 16), n=0), scriptSig=b"")]392tx.vout = [CTxOut(nValue=0, scriptPubKey=b"")]394xpub = "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp"395xpub_data, xpub_version = base58_to_byte(xpub)396xpub_key = bytes([PSBT_GLOBAL_XPUB]) + bytes([xpub_version]) + xpub_data398def psbt_with_origin(fingerprint):399return PSBT(400g=PSBTMap({401PSBT_GLOBAL_UNSIGNED_TX: tx.serialize(),402xpub_key: fingerprint,403}),404i=[PSBTMap({})],405o=[PSBTMap({})],406).to_base64()408combined = self.nodes[0].combinepsbt([psbt_with_origin(b"\x00\x00\x00\x00"), psbt_with_origin(b"\x11\x11\x11\x11")])409# The same xpub under both origins would serialize as duplicate keys, making the combined PSBT unparseable410decoded = self.nodes[0].decodepsbt(combined)411assert_equal(decoded["global_xpubs"], [{"xpub": xpub, "master_fingerprint": "00000000", "path": "m"}])413def test_combinepsbt_tap_leaf_script_conflict(self):414self.log.info("Test that combining PSBTs with conflicting leaf scripts for the same control block keeps a single record")416tx = CTransaction()417tx.vin = [CTxIn(outpoint=COutPoint(hash=int('aa' * 32, 16), n=0), scriptSig=b"")]418tx.vout = [CTxOut(nValue=0, scriptPubKey=b"")]420def psbt_with_leaf_scripts(*records):421return PSBT(422g=PSBTMap({PSBT_GLOBAL_UNSIGNED_TX: tx.serialize()}),423i=[PSBTMap({424bytes([PSBT_IN_TAP_LEAF_SCRIPT]) + control_block: bytes(leaf_script) + bytes([LEAF_VERSION_TAPSCRIPT])425for leaf_script, control_block in records426})],427o=[PSBTMap({})],428).to_base64()430def combined_tap_scripts(psbts):431return self.nodes[0].decodepsbt(self.nodes[0].combinepsbt(psbts))["inputs"][0]["taproot_scripts"]433def tap_script(leaf_script, control_blocks):434return {"script": leaf_script.hex(), "leaf_ver": LEAF_VERSION_TAPSCRIPT, "control_blocks": [cb.hex() for cb in control_blocks]}436control_block = bytes([LEAF_VERSION_TAPSCRIPT]) + bytes.fromhex(H_POINT)437control_block_with_path = control_block + bytes(32)438control_block_with_longer_path = control_block + bytes(64)439leaf_script_a = CScript([OP_TRUE])440leaf_script_b = CScript([OP_TRUE, OP_TRUE])441leaf_script_c = CScript([OP_TRUE, OP_TRUE, OP_TRUE])443psbt_a = psbt_with_leaf_scripts((leaf_script_a, control_block))444psbt_b = psbt_with_leaf_scripts((leaf_script_b, control_block))445psbt_c = psbt_with_leaf_scripts((leaf_script_c, control_block))447# The same control block under two leaf scripts would serialize as duplicate keys448assert_equal(combined_tap_scripts([psbt_a, psbt_b]), [tap_script(leaf_script_a, [control_block])])449# Reversed, so the leaf script kept is decided by the argument order and not by its content450assert_equal(combined_tap_scripts([psbt_b, psbt_a]), [tap_script(leaf_script_b, [control_block])])451# A third PSBT conflicting with what the first merge kept is dropped the same way452assert_equal(combined_tap_scripts([psbt_a, psbt_b, psbt_c]), [tap_script(leaf_script_a, [control_block])])453# Combining a PSBT with itself leaves it untouched454assert_equal(self.nodes[0].combinepsbt([psbt_a, psbt_a]), psbt_a)456# Records that do not conflict are all kept, whether or not they share a leaf script457psbt_same_leaf = psbt_with_leaf_scripts((leaf_script_a, control_block_with_path))458assert_equal(combined_tap_scripts([psbt_a, psbt_same_leaf]), [tap_script(leaf_script_a, [control_block, control_block_with_path])])459psbt_other_leaf = psbt_with_leaf_scripts((leaf_script_b, control_block_with_path))460assert_equal(combined_tap_scripts([psbt_a, psbt_other_leaf]), [461tap_script(leaf_script_a, [control_block]),462tap_script(leaf_script_b, [control_block_with_path]),463])465# Only the conflicting control block of an incoming leaf script is dropped, not all of them466psbt_leaf_a_two_blocks = psbt_with_leaf_scripts((leaf_script_a, control_block), (leaf_script_a, control_block_with_path))467psbt_leaf_b_two_blocks = psbt_with_leaf_scripts((leaf_script_b, control_block_with_path), (leaf_script_b, control_block_with_longer_path))468assert_equal(combined_tap_scripts([psbt_leaf_a_two_blocks, psbt_leaf_b_two_blocks]), [469tap_script(leaf_script_a, [control_block, control_block_with_path]),470tap_script(leaf_script_b, [control_block_with_longer_path]),471])473def test_sighash_mismatch(self):474self.log.info("Test sighash type mismatches")475self.nodes[0].createwallet("sighash_mismatch")476wallet = self.nodes[0].get_wallet_rpc("sighash_mismatch")477def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)479addr = wallet.getnewaddress(address_type="bech32")480def_wallet.sendtoaddress(addr, 5)481self.generate(self.nodes[0], 6)483# Retrieve the descriptors so we can do all of the tests with descriptorprocesspsbt as well484descs = wallet.listdescriptors(True)["descriptors"]486# Make a PSBT487psbt = wallet.walletcreatefundedpsbt([], [{def_wallet.getnewaddress(): 1}])["psbt"]489# Modify the PSBT and insert a sighash field for ALL|ANYONECANPAY on input 0490mod_psbt = PSBT.from_base64(psbt)491mod_psbt.i[0].map[PSBT_IN_SIGHASH_TYPE] = (SIGHASH_ALL | SIGHASH_ANYONECANPAY).to_bytes(4, byteorder="little")492psbt = mod_psbt.to_base64()494# Mismatching sighash type fails, including when no type is specified495for sighash in ["DEFAULT", "ALL", "NONE", "SINGLE", "NONE|ANYONECANPAY", "SINGLE|ANYONECANPAY", None]:496assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", wallet.walletprocesspsbt, psbt, True, sighash)498# Matching sighash type succeeds499proc = wallet.walletprocesspsbt(psbt, True, "ALL|ANYONECANPAY")500assert_equal(proc["complete"], True)
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.