A privacy chain built from midstate's networking and consensus plus pluribit's MimbleWimble transactions, with receiver-only spend authority. It is merge-mineable with midstate, and anchored to midstate for post-quantum disaster recovery.
Status: devnet-quality research code. Not audited. Do not use for real value.
About 23k lines of Rust. See docs/ANCHORING.md for the anchoring and
recovery design.
| Area | Source | Notes |
|---|---|---|
core/finality.rs, core/extension.rs, core/wots_simd.rs |
midstate | verbatim |
core/mmr.rs, core/simd_mining.rs |
midstate | plus marked additions (MMR prefix truncation, multi-seed SIMD verification) |
core/wots.rs, core/mss.rs |
midstate | key-reuse punishment section dropped; MSS checkpoint file keyed by seed (see Upstream bug) |
core/gpu_mining.rs |
midstate | wgpu 30 fix and opt-in software adapters (for testing only) |
network/amino.rs |
midstate | verbatim except the rendezvous label |
network/*, core/state.rs, core/types.rs, core/filter.rs |
midstate | MW payloads, /midwimble/* protocol ids, no activation heights |
sync.rs, node.rs, mempool.rs, storage.rs, miner.rs, pool.rs |
midstate (design) | re-implemented around the MW core |
core/mw/* |
pluribit | hardened, with the ownership fix |
core/auxpow.rs, merge_mine.rs |
new | merged mining with unmodified midstate nodes |
core/anchor.rs, core/recovery.rs, anchor.rs |
new | anchoring and dormant post-quantum recovery |
core/snapshot.rs, finality.rs |
new | anchored pruning: finalized checkpoints, snapshots, checkpoint sync |
wallet/, rpc.rs, bin/main.rs |
new | pluribit-style wallet, midstate-style RPC |
- Proof of work: sequential BLAKE3 (
H^N(H(header ‖ nonce)), N = 1,000,000) belowWALK_ACTIVATION_HEIGHT(86,400), then Midstate Walk (core/walk.rs, shared with midstate), a CPU-friendly walk over a 256 KiB scratchpad, natively and merged. Relayed and anchored midstate headers follow midstate's own switch at its height 420,000 (MIDSTATE_WALK_ACTIVATION_HEIGHT, which must match midstate's). - Difficulty and time: ASERT with a 4-hour half-life and a 60-second target. Timestamps must beat the median of the last 11 blocks and may be at most 15 minutes ahead.
- Fork choice: cumulative work, less what two caps withhold, tie-broken by
the lower midstate. Each bond's blocks count in full up to its share of the
day (four blocks once 360 bonds are eligible), and no block counts for more
than a quarter above the lowest of the week before it (
docs/BONDED_POW.md). Each header claims its chain's weight under its proof of work, so sync compares chains by it too. A block must be signed whenever a registered bond could sign it; while none can (before the first registration), an unsigned block counts for 1/1,024 of its work. - State root: covers the UTXO set, the kernel set, the chain MMR, the running excess sum, the total offset, the supply, the bond set and the midstate relay.
- Supply: at most 1,000,000.00000000 coins (8 decimals). The schedule reaches that exactly at about block 49.7 million (~95 years), but it is a ceiling rather than a promise: a block whose signer is over its per-bond cap mints less than the schedule allows and the difference is burned, so the chain's supply parts from the curve and its final total settles below the cap (see Over-cap taper).
- Slow start: the reward ramps linearly over the first 30 days, so the blocks mined while the network is small and least known are worth little.
- Halvings: 0.23932616 per block in era 0, halving every 2,100,000 blocks, the same wall-clock era as Bitcoin's. ASERT's genesis anchoring keeps the halving dates predictable to within a day or two.
- Over-cap taper: from the mainnet switch, a bond that has signed
nof the last 1,440 blocks against a cap ofcapmints onlyreward x cap / nof a block's subsidy, rounded down; fees are always paid in full. What the taper withholds is never minted by anyone -- it is a burn, not a redistribution -- so earning the whole reward means spreading hashrate over enough bonds, and the capital locked grows with the hashrate it covers. It is the samen > captest that already drops an over-cap block's fork-choice weight to 1/1024 of its work, so pay and weight agree. - Checkable anywhere: the schedule is defined by its running total, so any node can check that a supply claimed at any height is one the schedule could have produced -- never above it, and below it by whatever has been burned; checkpoint-synced nodes check the snapshot they start from. The Pedersen audit ties the figure to the coins that actually exist.
- After the cap: a block pays only its fees, and a block with none carries no coinbase.
-
Commitments: Pedersen commitments on Ristretto255 with aggregated Bulletproofs, padded to a power of two and bound to their outputs' metadata.
-
Stealth outputs: each carries a one-time owner key
Ko = H(t)·G + B, so only the recipient can spend. -
Balance rules:
Pedersen: Σ C_out − Σ C_in + fee·G = Σ E + o·H Owner: Σ Ko_in = Σ E' + x·G Coinbase: Σ C_cb − (subsidy + fees)·G = E_cb -
Further rules:
- Kernel ids are unique across the chain (replay protection).
- Coinbase outputs mature after 100 blocks.
- Blocks list everything in canonical order.
- A supply audit is available on any node.
- Commitment: a miner puts
H(NETWORK_MAGIC, midwimble mining hash)in the salt of the last output of a midstate coinbase. Midstate's own/block_templatecomputes the state root, so midstate needs no change. - Acceptance: a midwimble block whose work is in such a midstate block is valid when the midstate block meets midwimble's target.
- Proof size: usually about 220 bytes. Proofs are checked during header sync in the same SIMD batches as native blocks.
- Safety check: the miner refuses to hash unless its reconstruction of midstate's fold reproduces the midstate node's mining hash exactly.
- Payouts (PPLNS): every block pays the newest shares worth two blocks, directly in its coinbase. Miners entitled to at least 1/31 of the block are paid exactly; smaller ones share the remaining outputs by systematic sampling, so everyone's expected payout is exactly their share and splitting work across addresses gains nothing. Nothing is deducted, so there is nothing to settle or restore after a reorganisation.
- Commitment: every job commits the window it pays and the window it
freezes for the next tip (in the coinbase
extrafield). The sampling is seeded by the block the job builds on, and a job on a new tip must pay the window miners saw frozen on the previous one, so the pool cannot steer it, short of holding a new tip back until it has seen the next block. A miner run with--node <own node's RPC>refuses jobs for a height its own node passed more than 15 s earlier, which is how such a delay shows. - Ownership: each miner hashes its own copy of the job, whose
extrabinds the commitment to the miner's address and which the node re-signs under the pool's bond (/mining/bind). Work found on one miner's copy is worthless to anyone else, so shares cannot be stolen, even by a man in the middle. A block with nothing to pay (no reward and no fees, after issuance ends) has no coinbase to bind, so work on it is credited to nobody. - Miner audit: before hashing, a miner checks the template hash, the coinbase total for the claimed height, that the job is bound to its own address and to the published windows, that every share the pool accepted from it is counted, and, with its wallet's view key, that the coinbase pays it exactly what the rule gives it, in outputs it can claim.
- Privacy: the window names owners by a hash of their address, and the pool publishes nothing that links a coinbase output to anyone. The view key stays with the miner; it sees payments but cannot spend them.
- Spot checks: every window commits to each share, ordered by its proof
of work's output. Each miner picks random points of each window it is paid
from (four by default,
--spot-checks), weighted by weight, and has the pool prove the share covering each and the job it was mined on, then recomputes that share's proof of work on its owner's own copy of the job. Weight nobody mined, or real work counted twice, fails a check that lands on it. - TLS:
pool --stratum-tls 0.0.0.0:3334 --tls-cert cert.pem --tls-key key.pemadds a TLS listener and prints the certificate's SHA-256; miners connect tostratum+tls://host:3334, pinning a self-signed certificate with--tls-pin <sha256>or relying on a web root authority otherwise. - Verification: each connection's difficulty aims at one share per 15 s, with shares weighted by difficulty. Shares queue rather than being dropped; connections that have proven work are verified first, in SIMD batches; an invalid share bans its sender's address; connections and message sizes are capped.
Anchoring. A checkpoint (height, header, state root, work, previous checkpoint) is anchored in midstate without changing midstate, in one of two ways:
- as an ordinary Commit transaction, with midstate's commit proof of work;
- for free, through a merged-mined parent block.
Evidence (the fold proof plus confirmations) is verified SPV-style and stored on the midwimble side.
Recovery commitments. Every output carries
rc = H(R, value, blinding, salt)
Ris the recipient's midstate-style MSS key, which is part of the v1 address.saltcomes from the stealth shared secret.- Because
rcfixes the commitment's opening while the commitment is still binding, a later recovery can pin the value even after a discrete-log break.
Recovery epoch (dormant). A future hard fork activates it at an anchored checkpoint.
- Claims: accepted only with an SMT proof of the UTXO leaf, the frozen opening, and an MSS signature.
- Limits: each MSS leaf is used once, each output is claimed once, and the total is capped at the checkpoint's supply.
- No elliptic curves: elliptic-curve keys are never consulted.
MimbleWimble cut-through cannot coexist with one-sided ownership: removing a spent output would remove the owner key and signature the rules depend on. Midstate anchors serve as the horizon instead.
- Finalized checkpoints. A checkpoint counts once its Midstate evidence is deep enough, it describes this node's chain, the tip is far enough past it, and no conflicting anchored checkpoint is known.
- Floor. No reorg below it, whatever the competing chain's work.
- Pruning. Block bodies below the floor can be deleted. Headers, kernels (with signatures), UTXO records and range proofs stay.
- Grouped proofs keep aggregation. A spent output shrinks to its commitment and metadata hash, which is all its group's Bulletproof needs. A group is deleted once all its outputs are spent.
- UTXO records commit to their whole output, so a snapshot cannot alter scanning data.
- Wallets restoring from a pruned node scan the unspent-output set
(
GET /utxos) instead of block bodies.
- Transports: TCP and QUIC (WebRTC-direct for browser wallets behind a feature), with Noise and Yamux.
- Discovery and reachability: Kademlia on a private protocol id, identify, relay, DCUtR hole punching, AutoNAT, peer exchange, and the IPFS-DHT rendezvous.
- Limits: connection caps per /24 subnet and on inbound peers.
- Address book: an address is saved only once this node has dialled it
and the peer there has identified itself as a midwimble node. Midstate's nodes share our transports
and often our machines; one is dropped as soon as it identifies, forgotten,
and not dialled or let in for a day. Addresses heard in peer exchange are
tried and dropped at their first failure; saved ones survive a few; the
operator's
--peers are redialled whenever they are not connected. - Bans end: an hour at first, twice as long for each further ban of the
same peer, up to a day.
/statelists the peers banned now, and why. - Light clients: a light-client protocol with a reputation-based rate
limiter. Wallets read each block's scan data (
src/core/scan.rs: its outputs' ephemeral keys and view tags and its inputs' prefixes, about 100 bytes) rather than the block, fetch the few blocks that concern them from any node, checked against it, and learn from a node the WebRTC addresses of the nodes connected to it. - Dandelion++: expired stem transactions are aggregated before fluffing.
- Sync:
- headers first, with SIMD proof-of-work checks, comparing chains by the weight fork choice uses;
- while a session is only fetching and checking a peer's headers, the node keeps taking blocks and mining; it stops only to download a chain whose verified headers outweigh its own;
- a claimed heavier chain has to be backed: a peer whose headers fall short of its claim, or that goes quiet before they reach it, is struck and barred for a while (longer each time) and banned at the third strike, and one that answers a sync request with anything else is banned at once. A peer that goes quiet after its headers have backed its claim is only paused: a slow seed is not a liar. Peers this node dialed are preferred to peers that dialed it;
- blocks downloaded from several peers at once, with resume from verified headers after a restart;
- reorgs committed atomically;
- fork states rebuilt from on-disk undo records.
- Storage: incremental redb tables, with group and kernel bookkeeping for pruning. The state is checked against the tip header on load.
- Sync modes: full verification from genesis (default), or checkpoint
verification from a trusted checkpoint id (
--checkpoint), which verifies a snapshot and its Midstate anchor before validating everything after it in full.
cargo build --release # node, wallet, pool, merged miner, anchoring
cargo build --release --features gpu # + midstate's wgpu miner
cargo test --features fast-mining # 190 unit, 16 integration, 11 doc tests
cargo test --lib # production constants (165 tests)
cargo test --no-default-features --features fast-mining --lib # core only (159 tests)fast-mining makes proof of work trivial, moves genesis to 2023, sets
coinbase maturity to 3 and skips the slow start. Never ship a binary built with
it. midwimble params prints a build's launch parameters, emission calendar and
genesis block id; a build whose launch parameters are still placeholders says
so there and at node startup. Release builds have
not been compiled in this environment.
The integration tests run real nodes on localhost:
| Test | What it shows |
|---|---|
network (15; 16 with webrtc) |
gossip sync and a Dandelion++ payment; shallow and deep reorgs; late-joiner sync with restart; resumed sync; multi-peer download; forged-block and lying peers banned; bans that end and grow; a slow seed waited for, not banned; midstate-style foreign peers kept out; --peers redialled until they are back |
merge_mining (1) |
one search mines both chains against a midstate stand-in built from midstate's own types; merged-mining anchors recorded and verified; a second node syncs the merged-mined chain |
pool (1) |
two auditing miners paid in coinbases; fee to the operator; supply audit holds |
quantum_recovery (3) |
an attacker holding every elliptic-curve secret cannot claim anchored coins while owners can; a genuinely broken commitment binding still cannot inflate a claim; anchoring round trip with chained checkpoints and tamper detection |
pruning (3) |
an anchored checkpoint finalizes, bodies are pruned and a new node bootstraps from the snapshot to the same state root; a heavier chain cannot reorganise below the floor; a wallet restores from a pruned node |
GPU. midwimble gpu-info runs midstate's shader self-test. It was
verified here on Mesa llvmpipe (software Vulkan), including chains split across
many dispatches, via MIDWIMBLE_GPU_ALLOW_SOFTWARE=1. It has not been tested
on real GPU hardware.
export MIDWIMBLE_PASSWORD=...
midwimble wallet --wallet w.mww create # prints a v1 address + 24 words (recovery key: ~seconds to a minute)
midwimble wallet --wallet w.mww phrase # prints the 24 words again
midwimble node --data-dir ./node --mine-to <address> [--backend gpu] [--prune]
midwimble node --data-dir ./node --checkpoint <checkpoint id> # checkpoint verification
midwimble wallet --wallet w.mww balance # incoming: payments to it still waiting for a block
midwimble wallet --wallet w.mww sync # what changed: payments received and confirmed, reorgs
midwimble wallet --wallet w.mww send --to <address> --amount 0.25 # amounts in coins
midwimble wallet --wallet w.mww cancel --tx <hash> # release a payment that will never confirm
midwimble wallet --wallet w.mww rescan [--from <height>] # scan again; starts over on a relaunched network
midwimble bond new --file bond.json # mining requires a bond: docs/BONDED_POW.md
midwimble node --mining-bond bond.json --mine-to <address>
python3 scripts/rehearse.py --midstate-rpc 127.0.0.1:8545 # full rehearsal: docs/REHEARSAL.md
python3 scripts/join_testnet.py [--mine] # join the network (mainnet from 31 Oct): docs/TESTNET.md
# merged mining against a midstate node
midwimble merge-mine --midstate-rpc 127.0.0.1:8545 --midstate-address <hex> \
--midwimble-address <address> --anchor-store anchors.jsonl
# pool and pool miners
midwimble pool --address <operator address> --stratum 0.0.0.0:3333 --api 0.0.0.0:8081
MIDWIMBLE_VIEW_KEY="$(midwimble wallet view-key)" \
midwimble pool-mine --pool stratum+tcp://host:3333 # the view key never leaves the miner
midwimble pool-mine --pool stratum+tls://host:3334 --tls-pin <sha256> --node 127.0.0.1:9434
# anchoring checkpoints in midstate
midwimble anchor --midstate-rpc 127.0.0.1:8545 --depth 6
midwimble anchor-verify --depth 6Browser wallet. web-wallet/ is the wallet compiled to WebAssembly, with
a page in the explorer's style: create, restore or open a .mww file, send,
receive, history. It reaches nodes built with --features webrtc through the
light protocol, three at a time, follows the chain by its scan data, and
shares the CLI's phrases, addresses and wallet file format. See
web-wallet/README.md and docs/TESTNET.md.
RPC. The node serves these endpoints on loopback, with no authentication:
GET /stateGET /blocks/{start}/{count}GET /headers/{start}/{count}GET /utxo/{commitment}GET /mempoolPOST /mempool/txs(those transactions, for wallets to see payments before a block confirms them)POST /txPOST /miningPOST /mining/template(weighted payouts,extra, receipts)POST /mining/bind(copies of a template with their ownextra, re-signed, one per pool miner)POST /mining/submitPOST /peersGET /utxos(unspent outputs, for wallets on pruned nodes)GET /anchors,POST /anchorsGET /finality
It also serves the block explorer (/, /explorer/...) for the machine it
runs on. The RPC answers only requests addressed to an IP address,
localhost, or a name given with --rpc-allow-host (a proxy's or container
network's name for the node), so a web page cannot reach it by pointing a
domain of its own at 127.0.0.1. To publish the explorer, serve it on a
listener of its own, which has nothing on it that changes the node:
--explorer 0.0.0.0:9435. Never publish the RPC port: anyone who can reach it
can redirect the node's mining rewards.
mss::keygen resumes from mss_h{height}.checkpoint whatever seed wrote it.
If a generation is interrupted and a different seed later generates a key of
the same height in that directory, the new key loads the old leaves. Its tree
then no longer matches its seed, and signatures from those leaves fail to
verify. The vendored copy keys the file by a seed fingerprint.
- Emission schedule, supply cap and decimals (
core/types.rs). - Run
scripts/set_launch_params.pyon launch day: network magic, a Bitcoin anchor mined after the code freeze, the genesis time, and a genesis target calibrated from midstate's live target (docs/LAUNCH.md). Done for the testnet (16 October 2026), which becomes mainnet in place when its early phase ends on 31 October 2026 (docs/TESTNET.md). - Get a cryptographic review of:
- the ownership scheme (owner sum, kernel AND-proof, transcript binding);
- the merged-mining proof;
- the recovery commitments.
- Anchor evidence: 1,000 midstate confirmations, each header worth at
least 2^19 attempts (
finality.rs). Forging costs about an hour of midstate's whole network, with room for its difficulty to fall eightfold. - Finality depth: 100 midwimble blocks, fixed. It is a reorg floor, so it
must be identical everywhere;
core/finality.rs's estimator advises wallets instead. - Anchoring cadence: about every 10,000 blocks (a week). Archival nodes
assumed; pruning stays off (
docs/ANCHORING.md). -
EXTENSION_ITERATIONSstays 1,000,000: midstate compatibility, and so merged mining. - Midstate Walk: confirm
WALK_ACTIVATION_HEIGHT(expected a few hours before midstate's switch, so this chain stops taking GPU-friendly work first),WALK_TARGET_SHIFT, and thatMIDSTATE_WALK_ACTIVATION_HEIGHTandMIDSTATE_WALK_TARGET_SHIFTequal midstate's release. Merged mining pauses between the two switches; the merge miner and pools mine midstate alone meanwhile. - Consensus encodings pinned: block hashes cover bincode 1 encodings, so
bincode is pinned to
=1.3.3, andcore/golden.rsand the protocol tests hold vectors for every hashed encoding and the wire format. Build releases with--locked. - Rehearse against a real midstate node: bonded mining and merged mining
(
scripts/rehearse.py,docs/REHEARSAL.md). - Run the public testnet (
docs/TESTNET.md): publishscripts/testnet.json, run a seed, and get a midstate pool merge-mining it. - Before 31 October 2026: every node on a build with the early phase,
mainnet bonds registered ahead of the switch, and a checkpoint anchored
after it (
docs/TESTNET.md, "The switch").
- Not post-quantum in normal operation. Anchoring plus recovery commitments make a recovery possible after a curve break, but the recovery epoch is a future hard fork and is not wired into the node.
- Recovery commitments are written by senders. Wallets flag bad ones
(
unrecoverable_coins); consensus cannot check them without heavy zero-knowledge proofs. - Anchor verification is SPV-level. Cross-check with a trusted midstate node where that matters. Everything midstate-facing has been tested against a stand-in built from midstate's source, not a live midstate node.
- Pruned nodes cannot serve history. A network needs archive nodes for new full-verification peers; checkpoint-synced nodes also cannot serve headers below their snapshot.
- Conflicting anchors stop finalization rather than resolving by "earliest anchored wins".
- Full verification does not use snapshots yet; it downloads every block.
- Wallets restored from a pruned node lose outgoing history (they see only unspent outputs).
- Staged reorgs are held in memory (at most 1,000 blocks), and startup scans the UTXO set once.
- Linked outputs: outputs in one range-proof group are visibly linked (the cost of keeping aggregation; one proof per output would remove it).
- Pool: a coinbase pays at most 31 miners, so miners with less than 1/31 of a block are paid by sampling: exactly their share on average, but in fewer, larger payouts. The WebRTC browser pool from midstate is not ported.
- Wallet: the CLI records its payments (and keeps the browser wallet's)
but has no history command (the browser wallet shows them), and the
password prompt echoes (use
MIDWIMBLE_PASSWORD).
GPL-3.0-or-later (see LICENSE), matching midstate and pluribit.