By Glenn Lyvers, Instant Access · Published 13 September 2026 · Updated 13 September 2026 · ~11 min read
01 What a fork actually is
Bitcoin Core does not know it is running Bitcoin. It knows a set of parameters: four magic bytes that prefix every network message, a port, a genesis block hash, a difficulty floor, a bech32 prefix, and a list of seed nodes. Change those and the same binary refuses to talk to Bitcoin nodes, refuses Bitcoin blocks, and starts a chain of its own. That is the whole of a “code fork” in the sense used here: a new network running Bitcoin's consensus rules, not a chain split that shares Bitcoin's history.
The consequence worth stating at the outset is that a fork inherits Bitcoin Core's review, and only that. The code paths are the most examined in the industry. The parameters you type, and any rule you change, are examined by nobody until you publish them. Spearmint's position is that novelty belongs in parameters and in a small, published set of consensus additions, and nowhere else — the overview sets out why.
02 Build v29 first, unchanged
Bitcoin Core moved its build system from Autotools to CMake in v29.0, so older fork tutorials that begin with
./autogen.sh no longer apply. Build the unmodified tag before touching anything, so that any later
failure is unambiguously yours. These are the commands from the Spearmint build log,
run on Ubuntu 22.04 with the wallet enabled:
Note the last line. cmake --install installs per component, so bitcoin-cli,
bitcoin-wallet, bitcoin-tx and bitcoin-util each need their own
--component invocation. The build log records exactly that omission and the four commands that fixed it.
03 Scaffold the fork
Copy the tree, drop the upstream history, and make an initial commit that says what it is. Keeping the upstream git history is a defensible choice too — it makes rebasing onto v30 easier — but a clean root commit makes the diff between “Bitcoin Core v29.0” and “your chain” a single command to produce, which matters when someone asks what you changed.
Branding comes next and is deliberately shallow. Rename the CMake project and the output binaries; leave the internal target names alone so that upstream patches still apply:
Resist the temptation to search-and-replace “bitcoin” across the tree at this stage. It touches thousands of files, breaks the functional test framework, and buries your real changes in noise. Do it last, if at all.
04 The fields in chainparams.cpp
In v29 the network definitions live in src/kernel/chainparams.cpp (older tutorials say
src/chainparams.cpp; that file is now a thin wrapper). There is one class per network —
CMainParams, CTestNetParams, CTestNet4Params, SigNetParams,
CRegTestParams — and each constructor sets the same fields. These are the ones a fork must change,
with Bitcoin's mainnet values as fetched from the v29.0 tag:
| Field | Bitcoin v29.0 mainnet | What it does / Spearmint |
|---|---|---|
pchMessageStart[0..3] | f9 be b4 d9 | Magic bytes on every P2P message. Spearmint: fa bf b0 dc (test …dd, regtest …de). |
nDefaultPort | 8333 | P2P port. Spearmint: 9333 / 19333 / 19444. |
bech32_hrp | "bc" | Address prefix. Spearmint: spmtc / tsmc / smcrt. |
base58Prefixes[…] | 0, 5, 128, xpub/xprv bytes | Legacy address and key prefixes. Change even if you only issue bech32 addresses, or Bitcoin WIF keys will import. |
genesis = CreateGenesisBlock(nTime, nNonce, nBits, nVersion, reward) | (1231006505, 2083236893, 0x1d00ffff, 1, 50 * COIN) | Your mined values go here, plus a new pszTimestamp string. |
consensus.hashGenesisBlock + the two asserts | the 000000000019d6… hash | Must equal the hash of the block the constructor builds, or the binary aborts on start. |
consensus.powLimit | 00000000ffff… | Easiest allowed target. Keep Bitcoin's on main; regtest uses 7fffff…. |
consensus.nPowTargetSpacing | 600 | Block interval in seconds. Spearmint: 30. |
consensus.nPowTargetTimespan | 1209600 | Retarget window (14 days). Only meaningful if you keep Bitcoin's algorithm — see the retargeting article. |
consensus.nSubsidyHalvingInterval | 210000 | Blocks per halving. Spearmint's schedule is not a single interval (six-monthly for five years, then annual), so GetBlockSubsidy() in validation.cpp changes too. |
consensus.BIP34Height, BIP65Height, BIP66Height, CSVHeight, SegwitHeight, MinBIP9WarningHeight | 227931 … 483840 | Set all to 0 or 1: your chain has these rules from the start. Also clear script_flag_exceptions. |
consensus.vDeployments[TAPROOT] | activated 2021 | Set to always-active from genesis unless you want a second activation drama. |
consensus.nMinimumChainWork | a 256-bit work value | Set to 0 at launch; raise it in later releases to pin history. Spearmint plans to pin the 8% acquisition range this way (transparency). |
consensus.defaultAssumeValid | a recent block hash | Set to null (uint256{}) or the node will skip script checks up to a Bitcoin block that does not exist on your chain. |
checkpointData | 13 Bitcoin checkpoints | Empty the map. Add your own only once you have blocks to point at. |
m_assumeutxo_data | 2 snapshots | Empty it. These are Bitcoin UTXO-set hashes. |
chainTxData | tx count, timestamp, rate | Used for the sync-progress estimate only. Set to zeros. |
vSeeds, vFixedSeeds | 9 DNS seeds, seeds header | Clear both. Bitcoin's seeds will hand you Bitcoin peers who will ban you for bad magic. Regenerate chainparamsseeds.h from contrib/seeds when you have nodes. |
nPruneAfterHeight, m_assumed_blockchain_size, m_assumed_chain_state_size | 100000, 720, 14 | Cosmetic. Set the sizes to 1. |
Two files outside chainparams.cpp also carry identity. src/chainparamsbase.cpp sets the
RPC ports (8332 on Bitcoin mainnet) and data-directory subfolders; change the ports so a fork and a Bitcoin node
can share a host. And src/validation.cpp holds GetBlockSubsidy(), which is where a
non-standard emission schedule such as Spearmint's lives.
05 Mining the genesis blocks
A genesis block is an ordinary block with no predecessor: a coinbase transaction whose input script carries a
text string, a single output paying a public key, and a header that has to satisfy the network's difficulty
floor. Nothing in Core mines it for you. You compute the merkle root from the coinbase, then loop over
nNonce (and nTime if the nonce wraps) until the double-SHA-256 of the 80-byte header is
below the target encoded by nBits.
At Bitcoin's floor of 0x1d00ffff that is on average about 232 hashes — roughly
4.3 billion — which a Python script does in minutes to hours depending on the machine, and which a C
implementation does in seconds. You need three: main, test and regtest each have their own timestamp string,
and regtest's powLimit is so easy that its block is found almost instantly. Spearmint's strings are
published in the build log:
- Main: “Spearmint 25/Oct/2025: From code we trust, not fiat we must.”
- Test: “Spearmint Testnet 25/Oct/2025: Trial by code, not fiat.”
- Regtest: “Spearmint Regtest 25/Oct/2025: Proof in code, not paper.”
The genesis output's public key is conventionally an uncompressed key — 65 bytes, 130 hex characters —
whose private key is discarded, because the genesis coinbase is unspendable in Bitcoin Core regardless (it is
never added to the UTXO set). Once mined, the values go into CreateGenesisBlock(...) and the two
assert lines that follow it are updated with the new block hash and merkle root. Get either wrong
and the daemon aborts before it opens a socket, which is the intended behaviour.
06 A real failure: odd-length string
Spearmint automated the three genesis mines and the chainparams.cpp patch in one script,
contrib/spearmint/spearmintify.sh. Its first run failed:
The cause was mundane and worth recording precisely because it is the kind of thing that never appears in a
tutorial. The script had been assembled from a heredoc, and the heredoc was truncated, which cut the 130-character
genesis_pubkey to an odd number of hex digits. binascii.unhexlify cannot decode an odd-length
string, so the miner aborted before producing a single block. The fix was to write the miner as a clean file with the
full key and run the wrapper again:
07 What breaks after the rename
Most of the effort in a fork is not the edits above; it is the long list of places that assume the parameters have Bitcoin's values. In rough order of how soon each one bites:
| Symptom | Cause | Fix |
|---|---|---|
| Daemon aborts on start with an assertion | hashGenesisBlock or the merkle-root assert does not match the block the constructor builds | Re-check the mined values; the nTime/nNonce/nBits tuple and the timestamp string must all match. |
| Node connects to Bitcoin peers and is banned | Seeds still point at Bitcoin | Clear vSeeds/vFixedSeeds; run with -connect= to your own nodes until you have seeds. |
| Blocks validate suspiciously fast, or a later block is rejected as invalid | defaultAssumeValid or checkpointData still hold Bitcoin hashes | Null them. Reintroduce checkpoints only for your own chain. |
| Wrong coins per block, or a halving at height 210,000 | GetBlockSubsidy() and nSubsidyHalvingInterval unchanged | Implement your schedule in validation.cpp and write a unit test for the total supply. |
| Chain stalls after a miner leaves, or difficulty never moves | Bitcoin's 2,016-block retarget on a chain with a few miners | Replace the algorithm in pow.cpp (next article). |
| Functional tests fail everywhere | test/functional hard-codes ports, regtest genesis, subsidy and the binary names | Update test_framework/util.py port bases and the regtest parameters; expect a day of test triage. |
| GUI, man pages and help text say “Bitcoin” | Strings and PACKAGE_NAME | Cosmetic; do it last and in one commit. |
| Wallets import Bitcoin keys, or your addresses validate on Bitcoin tools | base58Prefixes left at Bitcoin's values | Change all five prefixes even if you only use bech32. |
| Signet/testnet4 code paths reference Bitcoin's signet challenge | SigNetParams unchanged | Either define your own signet or disable the chain type. |
There is no substitute for booting regtest first. Spearmint's build log lists exactly that as the next step
after the genesis patch: regtest, then a private mainnet on the new chain IDs, then ckpool in solo mode against
getblocktemplate — the same sequence the node page describes for anyone
running the software independently.
08 Consensus changes are the risky part
Everything above is configuration. It can be wrong in ways that stop the node from starting, which is embarrassing and cheap. Consensus changes are different: they can be wrong in ways that a node accepts happily for a year and then splits the network, and they are the only part of a fork that Bitcoin Core's reviewers have never seen.
Spearmint makes two. Difficulty retargets every block instead of every 2,016 (ADR), and confirmation depth scales with transaction value (DBF). Both are documented on the overview, and both are treated as the highest-risk code in the project: the September 2026 meeting put AI-assisted review of exactly this logic at the top of the threat model, and the testing programme now includes fuzzing and adversarial simulation of the retarget response before any endpoint is public.
09 Key takeaways
- Bitcoin Core v29 builds with CMake, not Autotools; install each tool with its own
--component. - Network identity lives in
src/kernel/chainparams.cpp: magic bytes, port, bech32 HRP, base58 prefixes, genesis, powLimit, spacing, seeds, checkpoints, assumevalid. - Mine three genesis blocks (main, test, regtest); at
0x1d00ffffexpect ~4.3 billion hashes for main. - Null
defaultAssumeValid, emptycheckpointDataandm_assumeutxo_data, clear the seeds, or your node will trust Bitcoin's history. - A non-standard emission schedule means editing
GetBlockSubsidy()invalidation.cpp, not just the halving interval. - The configuration is cheap to get wrong; the consensus changes are not. Test them as if they were hostile.
10 Questions
Where is chainparams.cpp in Bitcoin Core v29?
src/kernel/chainparams.cpp. Older tutorials refer to src/chainparams.cpp, which is now a thin wrapper. RPC ports and data-directory names are in src/chainparamsbase.cpp; hard-coded seed addresses are generated into src/chainparamsseeds.h from contrib/seeds.How long does it take to mine a genesis block?
nBits 0x1d00ffff) about 4.3 billion hashes on average, which a Python script finds in minutes to a few hours and a C loop in seconds. Regtest's powLimit is far easier and is found almost immediately. You need one block per network.Does forking Bitcoin Core make a secure coin?
Can I keep Bitcoin's difficulty adjustment?
11 Sources
- Bitcoin Core v29.0,
src/kernel/chainparams.cpp— github.com/bitcoin/bitcoin/blob/v29.0/src/kernel/chainparams.cpp (field names and mainnet values quoted above). - Bitcoin Core v29.0,
src/pow.cpp— github.com/bitcoin/bitcoin/blob/v29.0/src/pow.cpp (GetNextWorkRequired,CalculateNextWorkRequired). - Bitcoin Core v29.0 release notes (CMake build system) — github.com/bitcoin/bitcoin/blob/v29.0/doc/release-notes.md.
- Spearmint build log — spearmintcoin.com/progress.html (commands, chain identity, genesis-script failure).
- Spearmint economic policy — spearmintcoin.com/economic-policy.html (emission schedule referenced in section 04).