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.
1#!/usr/bin/env python32# Copyright (c) 2022-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""Test Miniscript descriptors integration in the wallet."""7from test_framework.descriptors import descsum_create8from test_framework.psbt import PSBT, PSBT_IN_SHA2569from test_framework.test_framework import BitcoinTestFramework10from test_framework.util import assert_equal13TPRVS = [14"tprv8ZgxMBicQKsPerQj6m35no46amfKQdjY7AhLnmatHYXs8S4MTgeZYkWAn4edSGwwL3vkSiiGqSZQrmy5D3P5gBoqgvYP2fCUpBwbKTMTAkL",15"tprv8ZgxMBicQKsPd3cbrKjE5GKKJLDEidhtzSSmPVtSPyoHQGL2LZw49yt9foZsN9BeiC5VqRaESUSDV2PS9w7zAVBSK6EQH3CZW9sMKxSKDwD",16"tprv8iF7W37EHnVEtDr9EFeyFjQJFL6SfGby2AnZ2vQARxTQHQXy9tdzZvBBVp8a19e5vXhskczLkJ1AZjqgScqWL4FpmXVp8LLjiorcrFK63Sr",17]18TPUBS = [19"tpubD6NzVbkrYhZ4YPAbyf6urxqqnmJF79PzQtyERAmvkSVS9fweCTjxjDh22Z5St9fGb1a5DUCv8G27nYupKP1Ctr1pkamJossoetzws1moNRn",20"tpubD6NzVbkrYhZ4YMQC15JS7QcrsAyfGrGiykweqMmPxTkEVScu7vCZLNpPXW1XphHwzsgmqdHWDQAfucbM72EEB1ZEyfgZxYvkZjYVXx1xS9p",21"tpubD6NzVbkrYhZ4YU9vM1s53UhD75UyJatx8EMzMZ3VUjR2FciNfLLkAw6a4pWACChzobTseNqdWk4G7ZdBqRDLtLSACKykTScmqibb1ZrCvJu",22"tpubD6NzVbkrYhZ4XRMcMFMMFvzVt6jaDAtjZhD7JLwdPdMm9xa76DnxYYP7w9TZGJDVFkek3ArwVsuacheqqPog8TH5iBCX1wuig8PLXim4n9a",23"tpubD6NzVbkrYhZ4WsqRzDmkL82SWcu42JzUvKWzrJHQ8EC2vEHRHkXj1De93sD3biLrKd8XGnamXURGjMbYavbszVDXpjXV2cGUERucLJkE6cy",24"tpubDEFLeBkKTm8aiYkySz8hXAXPVnPSfxMi7Fxhg9sejUrkwJuRWvPdLEiXjTDbhGbjLKCZUDUUibLxTnK5UP1q7qYrSnPqnNe7M8mvAW1STcc",25"tpubD6NzVbkrYhZ4WR99ygpiJvPMAJiwahjLgGywc5vJx2gUfKUfEPCrbKmQczDPJZmLcyZzRb5Ti6rfUb89S2WFyPH7FDtD6RFDA1hdgTEgEUL",26]27PUBKEYS = [28"02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068",29"030f64b922aee2fd597f104bc6cb3b670f1ca2c6c49b1071a1a6c010575d94fe5a",30"02abe475b199ec3d62fa576faee16a334fdb86ffb26dce75becebaaedf328ac3fe",31"0314f3dc33595b0d016bb522f6fe3a67680723d842c1b9b8ae6b59fdd8ab5cccb4",32"025eba3305bd3c829e4e1551aac7358e4178832c739e4fc4729effe428de0398ab",33"029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0",34"0211c7b2e18b6fd330f322de087da62da92ae2ae3d0b7cec7e616479cce175f183",35]37P2WSH_MINISCRIPTS = [38# One of two keys39f"or_b(pk({TPUBS[0]}/*),s:pk({TPUBS[1]}/*))",40# A script similar (same spending policy) to BOLT3's offered HTLC (with anchor outputs)41f"or_d(pk({TPUBS[0]}/*),and_v(and_v(v:pk({TPUBS[1]}/*),or_c(pk({TPUBS[2]}/*),v:hash160(7f999c905d5e35cefd0a37673f746eb13fba3640))),older(1)))",42# A Revault Unvault policy with the older() replaced by an after()43f"andor(multi(2,{TPUBS[0]}/*,{TPUBS[1]}/*),and_v(v:multi(4,{PUBKEYS[0]},{PUBKEYS[1]},{PUBKEYS[2]},{PUBKEYS[3]}),after(424242)),thresh(4,pkh({TPUBS[2]}/*),a:pkh({TPUBS[3]}/*),a:pkh({TPUBS[4]}/*),a:pkh({TPUBS[5]}/*)))",44# Liquid-like federated pegin with emergency recovery keys45f"or_i(and_b(pk({PUBKEYS[0]}),a:and_b(pk({PUBKEYS[1]}),a:and_b(pk({PUBKEYS[2]}),a:and_b(pk({PUBKEYS[3]}),s:pk({PUBKEYS[4]}))))),and_v(v:thresh(2,pkh({TPUBS[0]}/*),a:pkh({PUBKEYS[5]}),a:pkh({PUBKEYS[6]})),older(4209713)))",46]48DESCS = [49*[f"wsh({ms})" for ms in P2WSH_MINISCRIPTS],50# A Taproot with one of the above scripts as the single script path.51f"tr(4d54bb9928a0683b7e383de72943b214b0716f58aa54c7ba6bcea2328bc9c768,{P2WSH_MINISCRIPTS[0]})",52# A Taproot with two script paths among the above scripts.53f"tr(4d54bb9928a0683b7e383de72943b214b0716f58aa54c7ba6bcea2328bc9c768,{{{P2WSH_MINISCRIPTS[0]},{P2WSH_MINISCRIPTS[1]}}})",54# A Taproot with three script paths among the above scripts.55f"tr(4d54bb9928a0683b7e383de72943b214b0716f58aa54c7ba6bcea2328bc9c768,{{{{{P2WSH_MINISCRIPTS[0]},{P2WSH_MINISCRIPTS[1]}}},{P2WSH_MINISCRIPTS[2].replace('multi', 'multi_a')}}})",56# A Taproot with all above scripts in its tree.57f"tr(4d54bb9928a0683b7e383de72943b214b0716f58aa54c7ba6bcea2328bc9c768,{{{{{P2WSH_MINISCRIPTS[0]},{P2WSH_MINISCRIPTS[1]}}},{{{P2WSH_MINISCRIPTS[2].replace('multi', 'multi_a')},{P2WSH_MINISCRIPTS[3]}}}}})",58]60DESCS_PRIV = [61# One of two keys, of which one private key is known62{63"desc": f"wsh(or_i(pk({TPRVS[0]}/*),pk({TPUBS[0]}/*)))",64"sequence": None,65"locktime": None,66"sigs_count": 1,67"stack_size": 3,68},69# A more complex policy, that can't be satisfied through the first branch (need for a preimage)70{71"desc": f"wsh(andor(ndv:older(2),and_v(v:pk({TPRVS[0]}),sha256(2a8ce30189b2ec3200b47aeb4feaac8fcad7c0ba170389729f4898b0b7933bcb)),and_v(v:pkh({TPRVS[1]}),pk({TPRVS[2]}/*))))",72"sequence": 2,73"locktime": None,74"sigs_count": 3,75"stack_size": 5,76},77# The same policy but we provide the preimage. This path will be chosen as it's a smaller witness.78{79"desc": f"wsh(andor(ndv:older(2),and_v(v:pk({TPRVS[0]}),sha256(61e33e9dbfefc45f6a194187684d278f789fd4d5e207a357e79971b6519a8b12)),and_v(v:pkh({TPRVS[1]}),pk({TPRVS[2]}/*))))",80"sequence": 2,81"locktime": None,82"sigs_count": 3,83"stack_size": 4,84"sha256_preimages": {85"61e33e9dbfefc45f6a194187684d278f789fd4d5e207a357e79971b6519a8b12": "e8774f330f5f330c23e8bbefc5595cb87009ddb7ac3b8deaaa8e9e41702d919c"86},87},88# Signature with a relative timelock89{90"desc": f"wsh(and_v(v:older(2),pk({TPRVS[0]}/*)))",91"sequence": 2,92"locktime": None,93"sigs_count": 1,94"stack_size": 2,95},96# Signature with an absolute timelock97{98"desc": f"wsh(and_v(v:after(20),pk({TPRVS[0]}/*)))",99"sequence": None,100"locktime": 20,101"sigs_count": 1,102"stack_size": 2,103},104# Signature with both105{106"desc": f"wsh(and_v(v:older(4),and_v(v:after(30),pk({TPRVS[0]}/*))))",107"sequence": 4,108"locktime": 30,109"sigs_count": 1,110"stack_size": 2,111},112# We have one key on each branch; Core signs both (can't finalize)113{114"desc": f"wsh(c:andor(pk({TPRVS[0]}/*),pk_k({TPUBS[0]}),and_v(v:pk({TPRVS[1]}),pk_k({TPUBS[1]}))))",115"sequence": None,116"locktime": None,117"sigs_count": 2,118"stack_size": None,119},120# We have all the keys, wallet selects the timeout path to sign since it's smaller and sequence is set121{122"desc": f"wsh(andor(pk({TPRVS[0]}/*),pk({TPRVS[2]}),and_v(v:pk({TPRVS[1]}),older(10))))",123"sequence": 10,124"locktime": None,125"sigs_count": 3,126"stack_size": 3,127},128# We have all the keys, wallet selects the primary path to sign unconditionally since nsequence wasn't set to be valid for timeout path129{130"desc": f"wsh(andor(pk({TPRVS[0]}/*),pk({TPRVS[2]}),and_v(v:pkh({TPRVS[1]}),older(10))))",131"sequence": None,132"locktime": None,133"sigs_count": 3,134"stack_size": 3,135},136# Finalizes to the smallest valid witness, regardless of sequence137{138"desc": f"wsh(or_d(pk({TPRVS[0]}/*),and_v(v:pk({TPRVS[1]}),and_v(v:pk({TPRVS[2]}),older(10)))))",139"sequence": 12,140"locktime": None,141"sigs_count": 3,142"stack_size": 2,143},144# Liquid-like federated pegin with emergency recovery privkeys145{146"desc": f"wsh(or_i(and_b(pk({TPUBS[0]}/*),a:and_b(pk({TPUBS[1]}),a:and_b(pk({TPUBS[2]}),a:and_b(pk({TPUBS[3]}),s:pk({PUBKEYS[0]}))))),and_v(v:thresh(2,pkh({TPRVS[0]}),a:pkh({TPRVS[1]}),a:pkh({TPUBS[4]})),older(42))))",147"sequence": 42,148"locktime": None,149"sigs_count": 2,150"stack_size": 8,151},152# Each leaf needs two sigs. We've got one key on each. Will sign both but can't finalize.153{154"desc": f"tr({TPUBS[0]}/*,{{and_v(v:pk({TPRVS[0]}/*),pk({TPUBS[1]})),and_v(v:pk({TPRVS[1]}/*),pk({TPUBS[2]}))}})",155"sequence": None,156"locktime": None,157"sigs_count": 2,158"stack_size": None,159},160# The same but now the two leaves are identical. Will add a single sig that is valid for both. Can't finalize.161{162"desc": f"tr({TPUBS[0]}/*,{{and_v(v:pk({TPRVS[0]}/*),pk({TPUBS[1]})),and_v(v:pk({TPRVS[0]}/*),pk({TPUBS[1]}))}})",163"sequence": None,164"locktime": None,165"sigs_count": 1,166"stack_size": None,167},168# The same but we have the two necessary privkeys on one of the leaves. Also it uses a pubkey hash.169{170"desc": f"tr({TPUBS[0]}/*,{{and_v(v:pk({TPRVS[0]}/*),pk({TPUBS[1]})),and_v(v:pkh({TPRVS[1]}/*),pk({TPRVS[2]}))}})",171"sequence": None,172"locktime": None,173"sigs_count": 3,174"stack_size": 5,175},176# A key immediately or one of two keys after a timelock. If both paths are available it'll use the177# non-timelocked path because it's a smaller witness.178{179"desc": f"tr({TPUBS[0]}/*,{{pk({TPRVS[0]}/*),and_v(v:older(42),multi_a(1,{TPRVS[1]},{TPRVS[2]}))}})",180"sequence": 42,181"locktime": None,182"sigs_count": 3,183"stack_size": 3,184},185# A key immediately or one of two keys after a timelock. If the "primary" key isn't available though it'll186# use the timelocked path. Same remark for multi_a.187{188"desc": f"tr({TPUBS[0]}/*,{{pk({TPUBS[1]}/*),and_v(v:older(42),multi_a(1,{TPRVS[0]},{TPRVS[1]}))}})",189"sequence": 42,190"locktime": None,191"sigs_count": 2,192"stack_size": 4,193},194# Liquid-like federated pegin with emergency recovery privkeys, but in a Taproot.195{196"desc": f"tr({TPUBS[1]}/*,{{and_b(pk({TPUBS[2]}/*),a:and_b(pk({TPUBS[3]}),a:and_b(pk({TPUBS[4]}),a:and_b(pk({TPUBS[5]}),s:pk({PUBKEYS[0]}))))),and_v(v:thresh(2,pkh({TPRVS[0]}),a:pkh({TPRVS[1]}),a:pkh({TPUBS[6]})),older(42))}})",197"sequence": 42,198"locktime": None,199"sigs_count": 2,200"stack_size": 8,201},202]205class WalletMiniscriptTest(BitcoinTestFramework):206def set_test_params(self):207self.num_nodes = 1208self.rpc_timeout = 180210def skip_test_if_missing_module(self):211self.skip_if_no_wallet()213def watchonly_test(self, desc):214self.log.info(f"Importing descriptor '{desc}'")215desc = descsum_create(f"{desc}")216assert_equal(self.ms_wo_wallet.importdescriptors(217[218{219"desc": desc,220"active": True,221"range": 2,222"next_index": 0,223"timestamp": "now",224}225]226)[0]["success"], True)228self.log.info("Testing we derive new addresses for it")229addr_type = "bech32m" if desc.startswith("tr(") else "bech32"230assert_equal(231self.ms_wo_wallet.getnewaddress(address_type=addr_type),232self.funder.deriveaddresses(desc, 0)[0],233)234assert_equal(235self.ms_wo_wallet.getnewaddress(address_type=addr_type),236self.funder.deriveaddresses(desc, 1)[1],237)239self.log.info("Testing we detect funds sent to one of them")240addr = self.ms_wo_wallet.getnewaddress()241txid = self.funder.sendtoaddress(addr, 0.01)242self.wait_until(243lambda: len(self.ms_wo_wallet.listunspent(minconf=0, addresses=[addr])) == 1244)245utxo = self.ms_wo_wallet.listunspent(minconf=0, addresses=[addr])[0]246assert_equal(utxo["txid"], txid)247assert_equal(utxo["solvable"], True)249def signing_test(250self, desc, sequence, locktime, sigs_count, stack_size, sha256_preimages
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.