From d52550a6fc3301894bc567fc1e4540760da42fc9 Mon Sep 17 00:00:00 2001 From: DanS Date: Sun, 12 Jul 2026 08:10:14 -0500 Subject: [PATCH] fix(consensus): reject Sprout JoinSplits (unverified proof + vpub_new inflation vector) Sprout JoinSplit proofs/sigs/nullifiers/anchors are never verified (the verifier arg to CheckTransaction is unused), yet vpub_new is counted as transparent value-in -- a forged all-zero JoinSplit mints arbitrary value from nothing. Reproduced on an isolated ac_private=1 chain: 500,000 minted into a z-addr, accepted + mined + verifychain=true. Reject any non-coinbase tx carrying a JoinSplit in ContextualCheckTransaction (covers both mempool acceptance and ConnectBlock). DragonX is Sapling-only from genesis with zero JoinSplits in its history (mainnet supply audit), so this is inert on all legitimate traffic and never invalidates a historical block. Co-Authored-By: Claude Opus 4.8 --- src/main.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 510fe6f18..12b405a95 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1283,6 +1283,16 @@ bool ContextualCheckTransaction(int32_t slowflag,const CBlock *block, CBlockInde const bool overwinterActive = nHeight >=1 ? true : false; //NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER); const bool saplingActive = nHeight >=1 ? true : false; //NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING); + // SECURITY FIX (JoinSplit inflation): reject Sprout JoinSplits at consensus. DragonX is + // Sapling-only from genesis (zero JoinSplits in its entire history, verified by the mainnet + // supply audit); a tx carrying one is illegitimate. Their zk-proof/sig/nullifier/anchor are + // never verified while vpub_new is counted as transparent value-in -> unlimited inflation. + // Coinbase/notary (IsMint) exempt. Unconditional is safe: no historical block has a JoinSplit. + if (!tx.IsMint() && !tx.vjoinsplit.empty()) { + return state.DoS(100, error("ContextualCheckTransaction(): Sprout JoinSplits are disabled (inflation vector)"), + REJECT_INVALID, "bad-txns-joinsplit-disabled"); + } + if (saplingActive) { // Reject transactions with valid version but missing overwintered flag if (tx.nVersion >= SAPLING_MIN_TX_VERSION && !tx.fOverwintered) {