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.
502# Repeat with descriptorprocesspsbt503# Mismatching sighash type fails, including when no type is specified504for sighash in ["DEFAULT", "ALL", "NONE", "SINGLE", "NONE|ANYONECANPAY", "SINGLE|ANYONECANPAY", None]:505assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", self.nodes[0].descriptorprocesspsbt, psbt, descs, sighash)507# Matching sighash type succeeds508proc = self.nodes[0].descriptorprocesspsbt(psbt, descs, "ALL|ANYONECANPAY")509assert_equal(proc["complete"], True)511wallet.unloadwallet()513def test_sighash_adding(self):514self.log.info("Test adding of sighash type field")515self.nodes[0].createwallet("sighash_adding")516wallet = self.nodes[0].get_wallet_rpc("sighash_adding")517def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)519outputs = [{wallet.getnewaddress(address_type="bech32"): 1}]520outputs.append({wallet.getnewaddress(address_type="bech32m"): 1})521descs = wallet.listdescriptors(True)["descriptors"]522def_wallet.send(outputs)523self.generate(self.nodes[0], 6)524utxos = wallet.listunspent()526# Make a PSBT527psbt = wallet.walletcreatefundedpsbt(utxos, [{def_wallet.getnewaddress(): 0.5}])["psbt"]529# Process the PSBT with the wallet530wallet_psbt = wallet.walletprocesspsbt(psbt=psbt, sighashtype="ALL|ANYONECANPAY", finalize=False)["psbt"]532# Separately process the PSBT with descriptors533desc_psbt = self.nodes[0].descriptorprocesspsbt(psbt=psbt, descriptors=descs, sighashtype="ALL|ANYONECANPAY", finalize=False)["psbt"]535for psbt in [wallet_psbt, desc_psbt]:536# Check that the PSBT has a sighash field on all inputs537dec_psbt = self.nodes[0].decodepsbt(psbt)538for input in dec_psbt["inputs"]:539assert_equal(input["sighash"], "ALL|ANYONECANPAY")541# Make sure we can still finalize the transaction542fin_res = self.nodes[0].finalizepsbt(psbt)543assert_equal(fin_res["complete"], True)544fin_hex = fin_res["hex"]545assert_equal(self.nodes[0].testmempoolaccept([fin_hex])[0]["allowed"], True)547# Change the sighash field to a different value and make sure we can no longer finalize548mod_psbt = PSBT.from_base64(psbt)549mod_psbt.i[0].map[PSBT_IN_SIGHASH_TYPE] = (SIGHASH_ALL).to_bytes(4, byteorder="little")550mod_psbt.i[1].map[PSBT_IN_SIGHASH_TYPE] = (SIGHASH_ALL).to_bytes(4, byteorder="little")551psbt = mod_psbt.to_base64()552fin_res = self.nodes[0].finalizepsbt(psbt)553assert_equal(fin_res["complete"], False)555self.nodes[0].sendrawtransaction(fin_hex)556self.generate(self.nodes[0], 1)558wallet.unloadwallet()560def test_decodepsbt_long_sighash_type(self):561self.log.info("Test that decodepsbt rejects invalid trailing bytes in the sighash type field")562node = self.nodes[0]563psbt = PSBT.from_base64(node.createpsbt([{"txid": "00" * 32, "vout": 0}], [{"data": "00"}]))564# The first byte of this sighash type is ALL, but the type itself is not565psbt.i[0].map[PSBT_IN_SIGHASH_TYPE] = (0x101).to_bytes(4, "little")566assert_equal(node.decodepsbt(psbt.to_base64())["inputs"][0]["sighash"], "")568def test_combinepsbt_sighash_type(self):569self.log.info("Test that combining PSBTs preserves the sighash type field regardless of order")570node = self.nodes[0]571node.createwallet("combine_sighash")572wallet = node.get_wallet_rpc("combine_sighash")573def_wallet = node.get_wallet_rpc(self.default_wallet_name)575def_wallet.send([{wallet.getnewaddress(address_type="bech32"): 1}])576self.generate(node, 1)577psbt = wallet.walletcreatefundedpsbt(wallet.listunspent(), [{def_wallet.getnewaddress(): 0.5}])["psbt"]579signed = wallet.walletprocesspsbt(psbt=psbt, sighashtype="ALL|ANYONECANPAY", finalize=False)["psbt"]580assert_equal(node.decodepsbt(signed)["inputs"][0].get("sighash"), "ALL|ANYONECANPAY")581updated = wallet.walletprocesspsbt(psbt=psbt, sign=False)["psbt"]582assert "sighash" not in node.decodepsbt(updated)["inputs"][0]584finalized = []585for psbts in [[signed, updated], [updated, signed]]:586combined = node.combinepsbt(psbts)587assert_equal(node.decodepsbt(combined)["inputs"][0].get("sighash"), "ALL|ANYONECANPAY")588fin_res = node.finalizepsbt(combined)589assert_equal(fin_res["complete"], True)590assert_equal(node.testmempoolaccept([fin_res["hex"]])[0]["allowed"], True)591finalized.append(fin_res["hex"])592assert_equal(finalized[0], finalized[1])594wallet.unloadwallet()596def assert_change_type(self, psbtx, expected_type):597"""Assert that the given PSBT has a change output with the given type."""599# The decodepsbt RPC is stateless and independent of any settings, we can always just call it on the first node600decoded_psbt = self.nodes[0].decodepsbt(psbtx["psbt"])601changepos = psbtx["changepos"]602assert_equal(decoded_psbt["outputs"][changepos]["script"]["type"], expected_type)604def test_psbt_named_parameter_handling(self):605"""Test that PSBT Base64 parameters with '=' padding are handled correctly in -named mode"""606self.log.info("Testing PSBT Base64 parameter handling with '=' padding characters")607node = self.nodes[0]608psbt_with_padding = "cHNidP8BAJoCAAAAAqvNEjSrzRI0q80SNKvNEjSrzRI0q80SNKvNEjSrzRI0AAAAAAD9////NBLNqzQSzas0Es2rNBLNqzQSzas0Es2rNBLNqzQSzasBAAAAAP3///8CoIYBAAAAAAAWABQVQBGVs/sqFAmC8HZ8O+g1htqivkANAwAAAAAAFgAUir7MzgyzDnRMjdkVa7d+Dwr07jsAAAAAAAAAAAA="610# Test decodepsbt with explicit named parameter containing '=' padding611result = node.cli("-named", "decodepsbt", f"psbt={psbt_with_padding}").send_cli()612assert 'tx' in result614# Test decodepsbt with positional argument containing '=' padding615result = node.cli("-named", "decodepsbt", psbt_with_padding).send_cli()616assert 'tx' in result618# Test analyzepsbt with positional argument containing '=' padding619result = node.cli("-named", "analyzepsbt", psbt_with_padding).send_cli()620assert 'inputs' in result622# Test finalizepsbt with positional argument containing '=' padding623result = node.cli("-named", "finalizepsbt", psbt_with_padding, "extract=true").send_cli()624assert 'complete' in result626# Test walletprocesspsbt with positional argument containing '=' padding627result = node.cli("-named", "walletprocesspsbt", psbt_with_padding).send_cli()628assert 'complete' in result630# Test utxoupdatepsbt with positional argument containing '=' padding631result = node.cli("-named", "utxoupdatepsbt", psbt_with_padding).send_cli()632assert isinstance(result, str) and len(result) > 0634# Test that unknown parameter with '=' gets treated as positional and return error635unknown_psbt_param = "unknown_param_data=more_data="636# This should be treated as positional and fail with decode error, not parameter error637assert_raises_rpc_error(-22, "TX decode failed invalid base64", node.cli("-named", "finalizepsbt", unknown_psbt_param).send_cli)639self.log.info("PSBT parameter handling test completed successfully")641def test_psbt_roundtrip(self):642self.log.info("Test that PSBTs roundtrip when RPC does nothing")643utxo = self.nodes[0].listunspent()[0]644for ver in [0, 2]:645psbt = self.nodes[0].walletcreatefundedpsbt(inputs=[utxo], outputs=[{self.nodes[0].getnewaddress(): utxo["amount"] / 2}], psbt_version=ver)["psbt"]647rt_psbts = [648self.nodes[0].combinepsbt([psbt, psbt]),649self.nodes[0].finalizepsbt(psbt)["psbt"],650self.nodes[0].utxoupdatepsbt(psbt),651self.nodes[0].descriptorprocesspsbt(psbt, [])["psbt"],652self.nodes[0].walletprocesspsbt(psbt, sign=False)["psbt"],653]654for p in rt_psbts:655assert_equal(psbt, p)657def test_psbt_version(self):658tobump = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1)659utxo = self.nodes[0].listunspent()[0]660outputs = [{self.nodes[0].getnewaddress(): utxo["amount"] / 2}]661rawtx = self.nodes[0].createrawtransaction(inputs=[utxo], outputs=outputs)662for ver in [0, 2]:663psbt = self.nodes[0].createpsbt(inputs=[utxo], outputs=outputs, psbt_version=ver)664dec = self.nodes[0].decodepsbt(psbt)665assert_equal(ver, dec["psbt_version"])667psbt = self.nodes[0].walletcreatefundedpsbt(inputs=[utxo], outputs=outputs, psbt_version=ver)668dec = self.nodes[0].decodepsbt(psbt["psbt"])669assert_equal(ver, dec["psbt_version"])671psbt = self.nodes[0].converttopsbt(hexstring=rawtx, psbt_version=ver)672dec = self.nodes[0].decodepsbt(psbt)673assert_equal(ver, dec["psbt_version"])675psbt = self.nodes[0].psbtbumpfee(txid=tobump, psbt_version=ver)676dec = self.nodes[0].decodepsbt(psbt["psbt"])677assert_equal(ver, dec["psbt_version"])679assert_raises_rpc_error(-8, "The PSBT version can only be 2 or 0", self.nodes[0].createpsbt, inputs=[utxo], outputs=outputs, psbt_version=1)680assert_raises_rpc_error(-8, "The PSBT version can only be 2 or 0", self.nodes[0].walletcreatefundedpsbt, inputs=[utxo], outputs=outputs, psbt_version=1)681assert_raises_rpc_error(-8, "The PSBT version can only be 2 or 0", self.nodes[0].converttopsbt, hexstring=rawtx, psbt_version=1)682assert_raises_rpc_error(-8, "The PSBT version can only be 2 or 0", self.nodes[0].psbtbumpfee, txid=tobump, psbt_version=1)684def test_psbt_with_invalid_signature(self):685self.log.info("Test descriptorprocesspsbt with invalid signature in signed PSBT")687def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)688outputs = [{def_wallet.getnewaddress(address_type="bech32m"): 1}]689def_wallet.send(outputs)690self.generate(self.nodes[0], 1)692utxos = [utxo for utxo in def_wallet.listunspent() if utxo["desc"].startswith("tr")]693unsigned_psbt = def_wallet.walletcreatefundedpsbt(utxos, [{def_wallet.getnewaddress(): 0.5}])["psbt"]694descs = [desc for desc in def_wallet.listdescriptors(True)["descriptors"] if desc["desc"].startswith("tr")]696# Unload the wallet to avoid using wallet RPC internals in descriptorprocesspsbt.697def_wallet.unloadwallet()699result = self.nodes[0].descriptorprocesspsbt(psbt=unsigned_psbt, descriptors=descs, finalize=True)700assert_equal(result["complete"], True)701assert_equal("hex" in result, True)703valid_sig_psbt = result["psbt"]704flawed_psbt = PSBT.from_base64(valid_sig_psbt)705valid_witness = flawed_psbt.i[0].map.get(PSBT_IN_FINAL_SCRIPTWITNESS)706# The witness format is [num_items: CompactSize] [item1_len: CompactSize] [item1_data] ...707# For taproot key-path spend [0x01] [0x40 or 0x41] [64 or 65 byte signature].708# Skip the first 2 prefix bytes and flip a bit in the signature bytes only.709assert_equal(valid_witness[0], 1)710prefix = valid_witness[:2]711sig_len = valid_witness[1]712signature = valid_witness[2:2 + sig_len]713invalid_sig = bitflipper(signature)714invalid_witness = prefix + invalid_sig + valid_witness[2 + sig_len:]715flawed_psbt.i[0].map[PSBT_IN_FINAL_SCRIPTWITNESS] = invalid_witness716invalid_sig_psbt = flawed_psbt.to_base64()718result = self.nodes[0].descriptorprocesspsbt(psbt=invalid_sig_psbt, descriptors=descs, finalize=True)719assert_equal(result["complete"], False)720assert_equal("hex" in result, False)722# Load the default wallet back for later test cases.723self.nodes[0].loadwallet(self.default_wallet_name)725def run_test(self):726# Create and fund a raw tx for sending 10 BTC727psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.nodes[2].getnewaddress():10})['psbt']729self.log.info("Test for invalid maximum transaction weights")730dest_arg = [{self.nodes[0].getnewaddress(): 1}]731min_tx_weight = MIN_STANDARD_TX_NONWITNESS_SIZE * WITNESS_SCALE_FACTOR732assert_raises_rpc_error(-4, f"Maximum transaction weight must be between {min_tx_weight} and {MAX_STANDARD_TX_WEIGHT}", self.nodes[0].walletcreatefundedpsbt, [], dest_arg, 0, {"max_tx_weight": -1})733assert_raises_rpc_error(-4, f"Maximum transaction weight must be between {min_tx_weight} and {MAX_STANDARD_TX_WEIGHT}", self.nodes[0].walletcreatefundedpsbt, [], dest_arg, 0, {"max_tx_weight": 0})734assert_raises_rpc_error(-4, f"Maximum transaction weight must be between {min_tx_weight} and {MAX_STANDARD_TX_WEIGHT}", self.nodes[0].walletcreatefundedpsbt, [], dest_arg, 0, {"max_tx_weight": MAX_STANDARD_TX_WEIGHT + 1})736# Base transaction vsize: version (4) + locktime (4) + input count (1) + witness overhead (1) = 10 vbytes737base_tx_vsize = 10738# One P2WPKH output vsize: outpoint (31 vbytes)739p2wpkh_output_vsize = 31740# 1 vbyte for output count741output_count = 1742tx_weight_without_inputs = (base_tx_vsize + output_count + p2wpkh_output_vsize) * WITNESS_SCALE_FACTOR743# min_tx_weight is greater than transaction weight without inputs744assert_greater_than(min_tx_weight, tx_weight_without_inputs)746# In order to test for when the passed max weight is less than the transaction weight without inputs747# Define destination with two outputs.748dest_arg_large = [{self.nodes[0].getnewaddress(): 1}, {self.nodes[0].getnewaddress(): 1}]749large_tx_vsize_without_inputs = base_tx_vsize + output_count + (p2wpkh_output_vsize * 2)750large_tx_weight_without_inputs = large_tx_vsize_without_inputs * WITNESS_SCALE_FACTOR
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.