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) 2020-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.6import argparse7import json8import logging9import math10import os11import re12import shlex13import sys14import time15import subprocess17PATH_BASE_CONTRIB_SIGNET = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))18PATH_BASE_TEST_FUNCTIONAL = os.path.abspath(os.path.join(PATH_BASE_CONTRIB_SIGNET, "..", "..", "test", "functional"))19sys.path.insert(0, PATH_BASE_TEST_FUNCTIONAL)21from test_framework.blocktools import get_witness_script, script_BIP34_coinbase_height, SIGNET_HEADER # noqa: E40222from test_framework.messages import CBlock, CBlockHeader, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, from_binary, from_hex, ser_string, ser_uint256, tx_from_hex, MAX_SEQUENCE_NONFINAL # noqa: E40223from test_framework.psbt import PSBT, PSBTMap, PSBT_GLOBAL_UNSIGNED_TX, PSBT_IN_FINAL_SCRIPTSIG, PSBT_IN_FINAL_SCRIPTWITNESS, PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_SIGHASH_TYPE # noqa: E40224from test_framework.script import CScript, CScriptOp # noqa: E40226logging.basicConfig(27format='%(asctime)s %(levelname)s %(message)s',28level=logging.INFO,29datefmt='%Y-%m-%d %H:%M:%S')31PSBT_SIGNET_BLOCK = b"\xfc\x06signetb" # proprietary PSBT global field holding the block being signed32RE_MULTIMINER = re.compile(r"^(\d+)(-(\d+))?/(\d+)$")34def signet_txs(block, challenge):35# assumes signet solution has not been added yet so does not need36# to be removed38txs = block.vtx[:]39txs[0] = CTransaction(txs[0])40txs[0].vout[-1].scriptPubKey += CScriptOp.encode_op_pushdata(SIGNET_HEADER)41hashes = []42for tx in txs:43hashes.append(ser_uint256(tx.txid_int))44mroot = block.get_merkle_root(hashes)46sd = b""47sd += block.nVersion.to_bytes(4, "little", signed=True)48sd += ser_uint256(block.hashPrevBlock)49sd += ser_uint256(mroot)50sd += block.nTime.to_bytes(4, "little")52to_spend = CTransaction()53to_spend.version = 054to_spend.nLockTime = 055to_spend.vin = [CTxIn(COutPoint(0, 0xFFFFFFFF), b"\x00" + CScriptOp.encode_op_pushdata(sd), 0)]56to_spend.vout = [CTxOut(0, challenge)]58spend = CTransaction()59spend.version = 060spend.nLockTime = 061spend.vin = [CTxIn(COutPoint(to_spend.txid_int, 0), b"", 0)]62spend.vout = [CTxOut(0, b"\x6a")]64return spend, to_spend66def decode_challenge_psbt(b64psbt):67psbt = PSBT.from_base64(b64psbt)69assert len(psbt.i) == 170assert len(psbt.o) == 171assert PSBT_SIGNET_BLOCK in psbt.g.map72return psbt74def get_block_from_psbt(psbt):75return from_binary(CBlock, psbt.g.map[PSBT_SIGNET_BLOCK])77def get_solution_from_psbt(psbt, emptyok=False):78scriptSig = psbt.i[0].map.get(PSBT_IN_FINAL_SCRIPTSIG, b"")79scriptWitness = psbt.i[0].map.get(PSBT_IN_FINAL_SCRIPTWITNESS, b"\x00")80if emptyok and len(scriptSig) == 0 and scriptWitness == b"\x00":81return None82return ser_string(scriptSig) + scriptWitness84def finish_block(block, signet_solution, grind_cmd):85if signet_solution is None:86pass # Don't need to add a signet commitment if there's no signet signature needed87else:88block.vtx[0].vout[-1].scriptPubKey += CScriptOp.encode_op_pushdata(SIGNET_HEADER + signet_solution)89block.hashMerkleRoot = block.calc_merkle_root()90if grind_cmd is None:91block.solve()92else:93headhex = CBlockHeader.serialize(block).hex()94cmd = shlex.split(grind_cmd) + [headhex]95newheadhex = subprocess.run(cmd, stdout=subprocess.PIPE, input=b"", check=True).stdout.strip()96newhead = from_hex(CBlockHeader(), newheadhex.decode('utf8'))97block.nNonce = newhead.nNonce98return block100def new_block(tmpl, reward_spk, *, blocktime=None, poolid=None):101scriptSig = script_BIP34_coinbase_height(tmpl["height"])102if poolid is not None:103scriptSig = CScript(b"" + scriptSig + CScriptOp.encode_op_pushdata(poolid))105cbtx = CTransaction()106cbtx.nLockTime = tmpl["height"] - 1107cbtx.vin = [CTxIn(COutPoint(0, 0xffffffff), scriptSig, MAX_SEQUENCE_NONFINAL)]108cbtx.vout = [CTxOut(tmpl["coinbasevalue"], reward_spk)]109cbtx.vin[0].nSequence = 2**32-2111block = CBlock()112block.nVersion = tmpl["version"]113block.hashPrevBlock = int(tmpl["previousblockhash"], 16)114block.nTime = tmpl["curtime"] if blocktime is None else blocktime115if block.nTime < tmpl["mintime"]:116block.nTime = tmpl["mintime"]117block.nBits = int(tmpl["bits"], 16)118block.nNonce = 0119block.vtx = [cbtx] + [tx_from_hex(t["data"]) for t in tmpl["transactions"]]121witnonce = 0122witroot = block.calc_witness_merkle_root()123cbwit = CTxInWitness()124cbwit.scriptWitness.stack = [ser_uint256(witnonce)]125block.vtx[0].wit.vtxinwit = [cbwit]126block.vtx[0].vout.append(CTxOut(0, bytes(get_witness_script(witroot, witnonce))))128block.hashMerkleRoot = block.calc_merkle_root()130return block132def generate_psbt(block, signet_spk):133signet_spk_bin = bytes.fromhex(signet_spk)134signme, spendme = signet_txs(block, signet_spk_bin)135psbt = PSBT()136psbt.g = PSBTMap( {PSBT_GLOBAL_UNSIGNED_TX: signme.serialize(),137PSBT_SIGNET_BLOCK: block.serialize()138} )139psbt.i = [ PSBTMap( {PSBT_IN_NON_WITNESS_UTXO: spendme.serialize(),140PSBT_IN_SIGHASH_TYPE: bytes([1,0,0,0])})141]142psbt.o = [ PSBTMap() ]143return psbt.to_base64()145def get_poolid(args):146if args.poolid is not None:147return args.poolid.encode('utf8')148elif args.poolnum is not None:149return b"/signet:%d/" % (args.poolnum)150else:151return None153def get_reward_addr_spk(args, height):154assert args.address is not None or args.descriptor is not None156if hasattr(args, "reward_spk"):157return args.address, args.reward_spk159if args.address is not None:160reward_addr = args.address161elif '*' not in args.descriptor:162reward_addr = args.address = json.loads(args.bcli("deriveaddresses", args.descriptor))[0]163else:164remove = [k for k in args.derived_addresses.keys() if k+20 <= height]165for k in remove:166del args.derived_addresses[k]167if height not in args.derived_addresses:168addrs = json.loads(args.bcli("deriveaddresses", args.descriptor, "[%d,%d]" % (height, height+20)))169for k, a in enumerate(addrs):170args.derived_addresses[height+k] = a171reward_addr = args.derived_addresses[height]173reward_spk = bytes.fromhex(json.loads(args.bcli("getaddressinfo", reward_addr))["scriptPubKey"])174if args.address is not None:175# will always be the same, so cache176args.reward_spk = reward_spk178return reward_addr, reward_spk180def do_genpsbt(args):181poolid = get_poolid(args)182tmpl = json.load(sys.stdin)183signet_spk = tmpl["signet_challenge"]184_, reward_spk = get_reward_addr_spk(args, tmpl["height"])185block = new_block(tmpl, reward_spk, poolid=poolid)186psbt = generate_psbt(block, signet_spk)187print(psbt)189def do_solvepsbt(args):190psbt = decode_challenge_psbt(sys.stdin.read())191block = get_block_from_psbt(psbt)192signet_solution = get_solution_from_psbt(psbt, emptyok=True)193block = finish_block(block, signet_solution, args.grind_cmd)194print(block.serialize().hex())196def nbits_to_target(nbits):197shift = (nbits >> 24) & 0xff198return (nbits & 0x00ffffff) * 2**(8*(shift - 3))200def target_to_nbits(target):201tstr = "{0:x}".format(target)202if len(tstr) < 6:203tstr = ("000000"+tstr)[-6:]204if len(tstr) % 2 != 0:205tstr = "0" + tstr206if int(tstr[0],16) >= 0x8:207# avoid "negative"208tstr = "00" + tstr209fix = int(tstr[:6], 16)210sz = len(tstr)//2211if tstr[6:] != "0"*(sz*2-6):212fix += 1214return int("%02x%06x" % (sz,fix), 16)216def seconds_to_hms(s):217if s == 0:218return "0s"219neg = (s < 0)220if neg:221s = -s222out = ""223if s % 60 > 0:224out = "%ds" % (s % 60)225s //= 60226if s % 60 > 0:227out = "%dm%s" % (s % 60, out)228s //= 60229if s > 0:230out = "%dh%s" % (s, out)231if neg:232out = "-" + out233return out235def trivial_challenge(spkhex):236"""237BIP325 allows omitting the signet commitment when scriptSig and238scriptWitness are both empty. This is the case for trivial239challenges such as OP_TRUE or a single data push.240"""241spk = bytes.fromhex(spkhex)242if len(spk) == 1 and 0x51 <= spk[0] <= 0x60:243# OP_TRUE/OP_1...OP_16244return True245elif 2 <= len(spk) <= 76 and spk[0] + 1 == len(spk):246# Single fixed push of 1-75 bytes247return True248return False250class Generate:
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.