Code Audit: TwistCoin Core vs. Bitcoin Core
Every change TwistCoin Core makes to Bitcoin Core v31.1, what it does, and what reviewing it turned up.
1. Scope and method
The review covers the complete difference between the upstream tag
v31.1 of bitcoin/bitcoin
and commit 8935cd8 of
twistcoin-core:
82 files changed, 392 lines added and 8,552 removed. Most removed lines are Bitcoin's CI
configuration and Bitcoin's list of seed nodes. In src/ itself, 24 files
change.
How the review was done:
- Every hunk of the diff was read and classified (consensus, network identity, user-facing text, build).
- For every changed parameter, the unchanged code that consumes it was traced, to check that the new value is valid there and that removed values are not silently needed.
- The patched node was built from source and started on mainnet.
Not done: fuzzing, Bitcoin Core's own unit and functional tests (they had not been adapted to TwistCoin's parameters at the time of the review, see TC-04, since fixed), and any review of the unchanged Bitcoin Core code beyond the paths that use changed values. The pool, explorer and other TwistCoin repositories are out of scope.
2. Summary
The fork is small and conservative. The consensus code itself is untouched except for the block reward, and all other consensus differences are parameter values. Network identity is fully separated from Bitcoin. The review found one critical issue, a missing mainnet parameter that let any peer crash a node. It was fixed and deployed to all three public nodes on 5 October 2026. The remaining findings are inherited weaknesses, maintenance gaps and cosmetic leftovers.
| ID | Finding | Severity | Status |
|---|---|---|---|
| TC-01 | Missing mainnet headers-sync parameters allow a remote crash | Critical | Fixed |
| TC-02 | Timewarp protection (BIP94) not enforced on mainnet | Medium | Follows Bitcoin |
| TC-03 | Minimum chain work and assumevalid are zero | Low | Open |
| TC-04 | Test suite not adapted, tests and CI disabled | Low | Fixed |
| TC-05 | Currency unit still displayed as "BTC" | Info | Open |
| TC-06 | Testnet4 and signet were still Bitcoin's networks | Info | Signet fixed, testnet4 open |
| TC-07 | MAX_MONEY is below the supply cap | Info | No action needed |
| TC-08 | BIP30 is checked on every block | Info | No action needed |
| TC-09 | Wallet key derivation identical to Bitcoin's | Info | Open |
| TC-10 | New user agent breaks tools that detect "Satoshi" | Info | Acknowledged |
| TC-11 | Testnet difficulty adjustment could overflow | Low | Fixed |
3. What was changed
| Area | Files | Change |
|---|---|---|
| Chain parameters | src/kernel/chainparams.cpp |
New genesis blocks, block interval, retarget window, halving interval, soft-fork heights, network magic, ports, address prefixes, seeds, headers-sync parameters (see section 4). |
| Block reward | src/validation.cpp |
GetBlockSubsidy(): initial subsidy 50 → 20 coins. This is the only change to consensus code. The halving logic itself is unchanged. |
| RPC ports | src/chainparamsbase.cpp |
Default RPC ports 8332 → 8432 (mainnet), and matching changes on the test networks. |
| Seed nodes | src/chainparamsseeds.h, contrib/seeds/nodes_main.txt |
Bitcoin's mainnet fixed seeds replaced by the three TwistCoin nodes. |
| Domain separation | src/clientversion.cpp, src/common/signmessage.cpp, src/key.cpp, src/common/args.cpp |
User agent /TwistCoin:31.1.0/, signed-message prefix TwistCoin Signed Message:, key self-test string, config file twistcoin.conf, data directory ~/.twistcoin. |
| Units and help text | src/policy/feerate.h and 13 other files |
Smallest unit renamed "sat" → "wisp" (CURRENCY_ATOM), plus comments and RPC help strings. |
| Build and packaging | CMakeLists.txt, src/CMakeLists.txt, src/qt/CMakeLists.txt, Dockerfile, .dockerignore, contrib/rename-binaries.sh |
Product name, twistcoind/twistcoin-cli symlinks next to the unchanged binary names, Docker image. |
| Removed | .github/, ci/ |
Bitcoin Core's CI pipelines and scripts. |
| Documentation | README.md, CONTRIBUTING.md, SECURITY.md, doc/README.md |
Rewritten for TwistCoin. |
4. Mainnet parameter review
| Parameter | Bitcoin | TwistCoin | Assessment |
|---|---|---|---|
nPowTargetSpacing | 600 s | 120 s | OK. |
nPowTargetTimespan | 1,209,600 s | 17,280 s | OK: gives a 144-block retarget interval (timespan / spacing). The 4x clamp still applies. |
nSubsidyHalvingInterval | 210,000 | 525,600 | OK: about 2 years at 120 s per block. |
powLimit | 0x1d00ffff | 0x1e0ffff0 | OK: easier than the genesis target, so it is never the binding limit at launch. Allows recovery if hashrate collapses. |
| BIP34/65/66, CSV, SegWit heights | historical heights | 0 | OK: active from genesis. See TC-08 for one side effect. |
| Taproot deployment | BIP9, 2021 | ALWAYS_ACTIVE | OK. |
script_flag_exceptions | 2 Bitcoin blocks | none | OK: they named Bitcoin block hashes that cannot exist on TwistCoin. |
enforce_BIP94 | false | false | Inherited weakness, see TC-02. |
nMinimumChainWork, defaultAssumeValid | recent values | 0 | See TC-03. |
pchMessageStart | f9 be b4 d9 | 7f 87 c0 69 | OK: unique to TwistCoin. |
| P2P / RPC port | 8333 / 8332 | 8433 / 8432 | OK. |
| Address prefixes | bc, 0, 5, 128 | tw, 65, 63, 193 | OK: tw1..., T..., S.... The WIF byte follows the usual "address byte + 128" convention. |
| BIP32 version bytes | xpub/xprv | Tpub/Tprv | OK on mainnet. See TC-09 for derivation paths. |
| Genesis block | 2009 | 2026-08-15 | OK: hash and merkle root are asserted at startup, and the coinbase script is under the 100-byte limit. |
vSeeds, vFixedSeeds | Bitcoin seeders | 3 TwistCoin nodes | OK. |
m_assumeutxo_data, chainTxData | Bitcoin snapshots | empty / 0 | OK: there is no history to snapshot yet. |
m_headers_sync_params | 641 / 15218 | 209 / 5096 (since 8935cd8) | Was missing, see TC-01. |
5. Findings
TC-01 - Missing mainnet headers-sync parameters allow a remote crash Critical Fixed
- Location
src/kernel/chainparams.cpp(CMainParams)- Fixed in
8935cd8, deployed to all three public nodes on 2026-10-05
When the Bitcoin-specific values were removed from the mainnet parameters, the
m_headers_sync_params block went with them, leaving both of its fields at
zero. The test networks kept theirs. Bitcoin Core uses these values when a peer offers a
chain of headers with less total work than the node's anti-DoS threshold (its
protection against memory exhaustion by cheap header chains). The constructor of that
protection asserts that the values are non-zero, and Bitcoin Core is always built with
assertions enabled. A peer able to reach that code path could therefore stop any
TwistCoin mainnet node.
Fix: the parameters were restored, with values computed by Bitcoin
Core's own tool, contrib/devtools/headerssync-params.py, using
TwistCoin's 2-minute block interval and genesis date. This is not a consensus change:
patched and unpatched nodes remain fully compatible. Anyone running TwistCoin Core
built from an earlier commit should upgrade.
TC-02 - Timewarp protection (BIP94) not enforced on mainnet Medium
- Location
src/kernel/chainparams.cpp:consensus.enforce_BIP94 = false- Status
- Follows Bitcoin (see the governance policy)
The difficulty adjustment inherits Bitcoin's timewarp weakness: a miner with a majority of the hashrate can manipulate timestamps at retarget boundaries to drive the difficulty down and issue coins faster than scheduled. Bitcoin Core already contains the BIP94 mitigation (used on testnet4), but it is off on Bitcoin mainnet, and therefore on TwistCoin mainnet as well. The weakness is the same as Bitcoin's. The practical risk is higher on TwistCoin, because a majority of TwistCoin's hashrate is cheap to obtain.
Recommendation: in line with the project's policy of following Bitcoin's consensus decisions, adopt a timewarp fix when Bitcoin activates one. Enabling BIP94 on TwistCoin alone would be a soft fork that Bitcoin has not made.
TC-03 - Minimum chain work and assumevalid are zero Low
- Location
src/kernel/chainparams.cpp:nMinimumChainWork,defaultAssumeValid- Status
- Open
Both values are empty, which is normal for a chain at launch. With
nMinimumChainWork = 0, though, a freshly started node has no work
threshold during initial sync. It can be led to store low-work header chains and
follow a dishonest peer's chain until it hears about the real one. The cost of zero
assumevalid (all scripts verified) is negligible at the current chain size.
Recommendation: set both values to a recent block at each
release, as Bitcoin Core does, and regenerate m_headers_sync_params at
the same time.
TC-04 - Test suite not adapted, tests and CI disabled Low Fixed
- Location
src/test/,test/functional/, build flag-DBUILD_TESTS=OFF, removedci/and.github/- Fixed in
b71051f,10557baandfd4b11e(7 October 2026)
No test file was changed. At least 32 test files still assume Bitcoin's parameters:
Bitcoin's genesis hash, bcrt1 addresses, regtest's 150-block halving, or
a 50-coin subsidy (for example src/test/validation_tests.cpp). All
builds disable the tests, and the CI was removed. As a result, none of the changed
consensus parameters is covered by an automated test, and a regression would only be
noticed in production.
Recommendation: start with the unit tests that cover the changed code (subsidy, chain parameters, difficulty), keep a build that runs them, and adapt the functional tests progressively.
Fix: the whole test suite was adapted to TwistCoin's parameters rather than the other way round (regtest keeps TwistCoin's 20-coin subsidy and halving interval). All unit tests and all functional tests now pass, apart from those that need tools not included in the build, which are skipped as in Bitcoin Core. A GitLab CI pipeline builds the node and runs both suites on every push. Where tests relied on Bitcoin data, it was regenerated for TwistCoin: the difficulty adjustment tests use real TwistCoin retargets, and the mainnet mining test replays a full 144-block retarget period of real TwistCoin mainnet proof-of-work, mined for the purpose by an ASIC.
TC-05 - Currency unit still displayed as "BTC" Info
- Location
src/policy/feerate.h:CURRENCY_UNIT = "BTC"- Status
- Open
The smallest unit was renamed to "wisp", but the main unit is still "BTC" in RPC help
text and formatted amounts (for example BTC/kvB fee rates). This has no effect on behaviour, but can confuse users.
TC-06 - Testnet4 and signet were still Bitcoin's networks Info
- Location
src/kernel/chainparams.cpp:CTestNet4Params,SigNetParams- Status
- Signet fixed in
89485a2(7 October 2026), testnet4 open
These networks were left as they are in Bitcoin, apart from having their seed nodes
removed. Testnet4 keeps Bitcoin's genesis, port and tb prefix. Signet
keeps Bitcoin's genesis, default challenge, port and network magic. A TwistCoin node
started with -signet and pointed at a peer would therefore join
Bitcoin's signet. Neither network is deployed for TwistCoin.
Recommendation: either give them TwistCoin genesis blocks and identities, or refuse to start on them.
Partial fix: signet is now a TwistCoin network, with its own genesis
block, tws address prefix, port 38633 and TwistCoin pacing (2-minute
blocks, 144-block retarget). Its default challenge is a key held by the project, which
signs the blocks of the default signet; anyone can still run their own signet with
-signetchallenge. Testnet4 is unchanged and still not deployed.
TC-07 - MAX_MONEY is below the supply cap Info
- Location
src/consensus/amount.h:MAX_MONEY = 21000000 * COIN- Status
- No action needed
The total supply will eventually reach about 21,024,000 TWIST, slightly above
MAX_MONEY. This constant is only a sanity bound on individual amounts
(a single output, the outputs of one transaction, the fees of one block). It is never
compared to the total supply. In practice, it only means that one transaction cannot
move more than 21 million TWIST. Changing it would be a consensus change.
TC-08 - BIP30 is checked on every block Info
- Location
src/kernel/chainparams.cpp:BIP34Height = 0,BIP34Hashunset; used insrc/validation.cpp(ConnectBlock)- Status
- No action needed
Bitcoin skips the BIP30 duplicate-transaction lookup once BIP34 is known to be active,
by comparing the block at BIP34Height with BIP34Hash. With
the hash left empty, the comparison never matches, so TwistCoin always runs the BIP30
check. This is safe, since it is the stricter path, at a small extra validation cost.
TC-09 - Wallet key derivation identical to Bitcoin's Info
- Location
src/wallet/walletutil.cpp; test-network BIP32 version bytes inchainparams.cpp- Status
- Open
The built-in wallet derives mainnet keys with coin type 0, as Bitcoin does (for
example m/84h/0h/0h), and the test networks reuse Bitcoin testnet's
extended key version bytes. A seed used in both a Bitcoin wallet and a TwistCoin wallet
therefore produces the same private keys on both chains, which links the two wallets.
Recommendation: document that a Bitcoin seed should never be reused for TwistCoin. A distinct coin type could be adopted at the wallet level (not a consensus change), but it would need a migration for existing wallets.
TC-10 - New user agent breaks tools that detect "Satoshi" Info
- Location
src/clientversion.cpp:UA_NAME = "TwistCoin"- Status
- Acknowledged
Some third-party software identifies Bitcoin Core by its /Satoshi: user
agent. The Fulcrum Electrum server, for instance, otherwise assumes it is talking to a
Bitcoin Cash node and uses RPC methods TwistCoin Core does not have. TwistCoin keeps its
own user agent and ships a patched Fulcrum (twistcoin-fulcrum) instead.
Other tools may need the same treatment.
TC-11 - Testnet difficulty adjustment could overflow Low Fixed
- Location
src/kernel/chainparams.cpp:CTestNetParams- Fixed in
89485a2(7 October 2026), found by the re-enabled unit tests
The fork relaxed testnet's powLimit but kept Bitcoin's two-week
retarget timespan. CalculateNextWorkRequired() multiplies the previous
target by the actual timespan, up to four times the target timespan, so with these
values the intermediate result could exceed 256 bits and wrap around, producing a
wrong difficulty. Bitcoin Core's own chain parameter sanity test caught it as soon as
the unit tests were running again. Testnet is not deployed, and mainnet was checked:
its margin is about 2252 against the 2256 limit.
Fix: testnet now uses mainnet's pacing (2-minute blocks, 144-block retarget), like regtest and signet.
6. Verified as correct
-
Block reward and supply:
GetBlockSubsidy()starts at 20 coins and shifts right once per halving. Summed over all halvings, this gives the cap stated in the whitepaper (21,023,999.931672 TWIST). - Difficulty: the retarget interval is derived as timespan / spacing = 144 blocks. The adjustment algorithm and its 4x clamp are unchanged.
- No hidden consensus changes: apart from the subsidy line, no consensus or validation logic differs from Bitcoin Core v31.1. All other changes are parameter values, text, or build files.
- Separation from Bitcoin: TwistCoin has distinct network magic, ports, address prefixes and signed-message prefix. Since the chains share no UTXOs, transactions cannot be replayed between them.
- No contact with Bitcoin infrastructure: Bitcoin's DNS seeds, fixed seeds and assumeutxo snapshots are removed from mainnet. Seeds are cleared on every test network.
- Genesis: the genesis hash and merkle root are asserted at startup. The genesis output can never be spent, because Bitcoin Core never adds the genesis coinbase to the UTXO set.
- Docker image: the node runs as an unprivileged user, and the RPC port is documented as internal only.