The reference full node for the Rand Protocol chain and its command-line wallet, written in Rust. The chain's native token is RAND, and it lives in a fully shielded pool: there are no accounts and no balances, only note commitments and nullifiers, and a transfer is a zero-knowledge proof that some two notes became some other two. The same token pays for confidential arbitrary computation — programs that run off-chain inside a zero-knowledge virtual machine and settle on chain with a proof instead of their inputs.
| Consensus | chained HotStuff BFT, stake-weighted quorums (more than 2/3), round-robin leaders, three-chain commit, view synchronisation, exponential timeouts |
| Signatures / hashes | Dilithium2 (post-quantum) / BLAKE3 for validators and blocks; Poseidon2 for notes, the tree and nullifiers; ML-KEM-768 + ChaCha20-Poly1305 for note envelopes |
| Networking | libp2p 0.54: TCP + Noise + Yamux, gossipsub, Kademlia + bootstrap list, mDNS on LANs, request-response block sync, ping keepalive, automatic redial |
| Ledger | shielded note pool: a depth-32 Poseidon2 commitment tree, a nullifier set, 4-in-4-out hidden-asset proved bundles (one bundle moves any asset — RAND, a bridged coin or an RPL token — and nobody without a key can tell which), public fees to the proposer's register entry, BLAKE3 Merkle state root over tree, nullifiers, validators and programs, plus the bridge and the RPL token registry on a chain that carries them |
| Staking | a public validator register — bond out of a bundle's burn, unbond over two epochs, withdraw into a shielded note — epochs that re-derive the validator set from it, and a supply audit that adds the register and the pool back up to what the chain issued |
| Wallet keys | a 256-bit spend key; viewing key, note-owner field, nullifier key, outgoing viewing key, ML-KEM-768 decapsulation key and rand1… address all derived from it |
| Bridged assets | the guardian bridge as notes: a bridged holding is a note whose asset word is the registry's index, an attestation deposits one note the chain computes itself, and a burn is a single hidden-asset bundle — the same bundle spends the bridged asset from slots 0–1 and pays the RAND fee from slots 2–3 (docs/bridge.md) |
| Confidential computation | Rand zkVM: RV32I under a Plonky3 batch STARK (Goldilocks, Poseidon2, ZK-hiding FRI); programs deployed on chain, calls carry a proof + 8 public outputs, gas by tier, and pay through a bundle like everything else |
| Storage | one RocksDB per node with column families for blocks, certificates, indexes, notes, nullifiers, anchors, validators, programs and receipts; fsynced commits; startup integrity check with truncate-and-resync |
| Interfaces | JSON-RPC 2.0 over HTTP with batch requests, a WebSocket newHeads / receipts / transaction subscription on the same port (rand-node), rand wallet CLI with a local prover, Rust client library |
Status: an experimental testnet runs chain 14 (v0.5, live since 2026-09-20; genesis
1cff3b7d…, pinned build b3c594c) on 16 DigitalOcean droplets and a laptop validator, with 18
validators in the register — RPL tokens and a hardened, zUSD-backed guardian bridge (see
deploy/README.md). Not audited; not for real value.
- Repository layout
- Build and test
- Run a node
- Use the wallet
- The shielded pool
- Confidential computation
- Operating a node
- How it works
- v0.5: RPL, zUSD and bridge hardening
- Documentation
- Roadmap
crates/randprotocol-core pure logic, no I/O: crypto, types, notes and the commitment tree, ledger
rules, gas, genesis, the HotStuff state machine (tested with a simulated network)
crates/randprotocol-zkvm the Rand zkVM (vendored from circuits/research; resync with deploy/sync-zkvm.sh)
plus the chain-side proof verifier with its verifier-key cache
crates/randprotocol-node RocksDB storage, libp2p networking, mempool, block sync, JSON-RPC server,
the node event loop, and the rand-node binary
crates/randprotocol-client the rand wallet binary and the RpcClient library (no RocksDB/libp2p dependency)
deploy/ testnet genesis, test keys, run scripts, cloud provisioning and rebuild scripts
docs/ reference documentation and the design spec / plan
scripts/ local two-validator testnet
Requirements: Rust 1.98.1 (pinned in rust-toolchain.toml; rustup installs it), a C++ compiler
and cmake (RocksDB). First build: 10 to 20 minutes (RocksDB and Plonky3 from source).
cargo build --release # target/release/rand-node, target/release/rand
cargo test --release # all crates; release because STARK proving is slow in debugTest coverage: 155 core tests (crypto, notes and the tree, ledger admission rules, the staking register and its epochs, the supply audit, gas, genesis, a deterministic multi-replica HotStuff simulation with partitions, restarts and epoch rollovers), node unit tests (storage, corruption cases, the conflict mempool, the redacted RPC), wallet tests (key file, scanning, coin selection), the zkVM suite (the upstream tests wholesale — including, since constraint set 6, the EVM, sBPF and SHA-256 suites and a tier-16 EVM-call proof — plus executor tests with real proofs), one wallet-flow test against a real one-node chain (mint, scan, send, spend the change, bond, and a confidential call whose input transcript it opens back), and 18 cluster tests that start real nodes over TCP: a shielded transfer between wallets, a double-spend race between two validators, a deploy-and-call paid by bundles, a call whose input envelope only its caller and its auditor open, a guardian-attested bridge deposit and a burn, a fifth validator that registers and bonds itself into the next epoch, a validator that unbonds out of the set and withdraws into a note its payout wallet spends, late joiners, restart cycles, quorum loss and recovery, corrupted database recovery, and the faucet.
678 tests, all green. cargo test --workspace --release measured ~43½ minutes on 2026-09-14
from a cold release target, on a machine that was also running other sessions' work — most of it
proving. The proofs dominate: the wallet flow 9m37s (six bundle proofs and a call proof), the
cluster suite 22m44s (18 tests, 12 bundle proofs and 2 program proofs serialised through the
proving slot), and the zkVM end-to-end file 7m25s (a tier-16 EVM-call proof among its 24). Six
upstream tests are #[ignore]d: three production-profile measurement harnesses, the sBPF cycle
breakdown, and the two interpreter guests' exit proofs (memory-bound; they want ≥ 64 GB).
scripts/local-testnet.sh
target/release/rand --rpc http://127.0.0.1:8545 statusEach machine creates a key:
rand-node keygen --out node.key.json # prints the address
rand-node address --key node.key.json # address, public key, libp2p peer idOne machine writes the genesis with every validator's key file or hex public key, then the file is copied to all machines unchanged (the genesis hash must match everywhere):
rand-node genesis --chain-id 6 \
--validator a.key.json,1000,rand1<a's payout address> \
--validator <hex public key of b>,1000,rand1<b's payout address> \
--alloc rand1<address>=1000 \
[--epoch-blocks 1000] \
[--faucet] [--no-confidential] [--fri-profile production] \
--out genesis.jsonA --validator is one register entry, so it carries all three of its fields at once: the key, the
stake in RAND, and the payout address its block rewards and unbonded stake are paid to (phase S2).
All three are part of the genesis hash. 1000 RAND is the minimum a validator needs to be in an
epoch's validator set at all; genesis refuses less. --epoch-blocks is how often the set is
re-derived from the register (spec §8) — the default is 1000 blocks.
Each --alloc creates one shielded deposit note: there is no per-validator allocation, because
value exists only as a note someone holds the spend key for. The addresses come from
rand keygen + rand address on whichever machines will hold the funds. deploy/README.md
has a worked example, and deploy/genesis-shielded.example.json is one such file.
A validator that joins an existing chain registers instead of appearing in genesis:
rand-node register --key node.key.json --payout rand1<payout address> # prints a Registration (hex)
rand-node unbond 1000 --key node.key.json # two epochs to release
rand-node withdraw 1000 --key node.key.json # into a note at the payout addressThe bond itself is a wallet transaction — it burns the stake out of shielded notes, which a node
holds none of — and takes the hex register printed. unbond and withdraw need no wallet: they
are signed by the node's key and carry no bundle at all, exactly as a faucet mint does, and the
register's nonce is what keeps them from being replayed. So there is nothing to prove and each
commits in a block's time.
Their fee model is its own, for the same reason: unbond moves stake inside the public register
and pays nothing at all, while withdraw pays the 0.001 RAND bundle base out of the amount it
withdraws, to the proposer of the block that applies it. A withdrawal of 1000 RAND therefore
creates a note worth 999.999, and an amount that cannot cover the base is refused. docs/staking.md
walks the whole join-and-leave through, including when bonded stake starts counting as weight.
Each machine initialises and runs:
rand-node init --datadir ./data --genesis genesis.json
rand-node run --datadir ./data --key node.key.json --validator \
--listen /ip4/0.0.0.0/tcp/30303 --rpc 127.0.0.1:8545 \
[--bootstrap /ip4/<ip>/tcp/30303/p2p/<peer id>]...Nodes on one LAN discover each other over mDNS; across networks, machines behind NAT dial out to a
node with a public address. --validator means "this node holds a validator key": a key that is in
no current epoch's set observes until an epoch admits it, which is how a validator that bonds in
after genesis joins without a restart (rand_status reports is_validator for the key and
active_validator for being in the current set). Omit --validator to run an observer on purpose. Restarting from the same --datadir resumes from
the persisted head. A validator set of n needs more than 2/3 of stake online: 2 of 2, 3 of 4, 5 of 6.
export RAND_RPC=http://127.0.0.1:8545 # or --rpc on each call
rand keygen # wallet.key.json (or --key <file>, RAND_KEY)
rand address # rand1… — about 1.6 KB of base58
rand balance # scans the tree with this key; nobody else can
rand send <rand1 address> 1.5 # proves a bundle locally (~100 s), submits, waits
rand bond <validator address> 1000 # stake: the bundle burns it out of this wallet's notes
rand faucet [address] # testnet chains only: mint up to 100 RAND
rand notes | rand history
rand tx <hash> | rand block <height|hash> | rand head | rand status | rand peers | rand validatorsAmounts are decimal RAND (1 RAND = 10^9 units). The fee floor is 0.001 RAND per bundle and
goes to the proposer of the block that includes the transaction. There is no
rand balance <address>: a balance is a fact about your key file, not about the chain.
Value on this chain is a set of notes. A note's plaintext — owner, amount, asset, randomness — never appears on chain; what appears is its Poseidon2 commitment, appended as a leaf of a depth-32 tree, and an envelope carrying the plaintext sealed to the owner's address (ML-KEM-768
- ChaCha20-Poly1305). Spending a note publishes its nullifier
H_NF(nk, cm), which nobody can link back to the commitment without the owner's viewing key.
Every transfer is one fixed 4-in-4-out bundle — the hidden-asset bundle, since chain 14 — proved by a pinned zkVM guest. One bundle moves any asset (RAND, a bridged coin, an RPL token) and nobody without a key can tell which:
Bundle { anchor, nullifiers[4], commitments[4], fee, burn_a, burn_r, burn_asset, time, envelopes[4], proof }
Slots 0–1 carry the private asset moved (dummies on a RAND-only transfer); a token or bridge burn
accounts for it there, in burn_a/burn_asset. Slots 2–3 always carry RAND — the fee, and, on
a bond or an aggregator registration, the stake burned in burn_r. The proof says: all four
inputs are leaves under anchor, their nullifiers are the published ones, each pair's inputs
balance its outputs plus whatever it burns or pays as fee, and the spender holds the keys —
without revealing which leaves, which amounts, which asset, or who. A wallet finds its own notes
by trial-decrypting every envelope on
the chain with its viewing key, so a node answers "here is the whole tree" and never "here is your
balance". docs/howto.md (five questions, end to end) and docs/shielded.md is the full guide, including the public/hidden table per action and
what still leaks (a witness request names the leaf you are about to spend).
rand faucet # testnet: a validator mints 100 RAND into a note only you can open
rand sync && rand notes # scan the tree; list what this key can open
rand send rand1q9f… 1.5 # ~100 s of local proving, then the commitValue enters the pool through a faucet mint, a genesis alloc note, or a validator's withdraw,
and every one of those amounts is public — the same one-hop visibility a transparent-to-shielded
deposit has anywhere. It leaves as a bundle's fee or a bond's burn, both public too, which is what
lets rand_getSupply account for a chain nobody can add up (docs/supply.md). Staking is where
those public amounts live: docs/staking.md. Bridged assets are phase S3.
A program is RV32I code for the Rand zkVM. You deploy it once (its content hash is its id), then call it with private inputs: the wallet runs the program and proves it locally, and only the proof and eight public output words reach the chain. Every node verifies the proof, charges gas by tier, and stores a receipt. The gas is paid by a shielded bundle like any other transaction, so the chain does not learn who called the program either.
rand program build --guest private_payment --arg 1000 --out pp.json # assemble a built-in guest
rand program deploy pp.json # prints the program id
rand call <program id> --input 400 --input 250 --input 300 --input 75
rand receipt <tx>private_payment reads four private balances and, if they sum to at least the threshold,
publishes the surplus in its outputs — [1, 0, 25, 0, ...]. The chain sees the proof, the tier and
those eight words; it never sees the balances. What it does not do any more is move money on
its own: effect kind 1, the program-driven transfer to an account, was deleted with the accounts,
so the outputs are data and any payment is made by the caller in a bundle. Measured on Apple
Silicon: proving 21 s, proof 0.9 MB, on-chain verification 19 ms with a cached verifier key (the
key costs 2 s per program on a laptop, 7 s on a 2-vCPU server, computed once in the background
when the program is deployed).
Gas: every bundle 0.001 RAND, plus 100,000 units per code word to deploy, plus 0.001 RAND at
tier 10 rising 0.0001 per two tiers to call. See docs/confidential.md.
rand-node status # height, view, peers, mempool, programs, sync state
rand-node verify --datadir ./data # full integrity check: hashes, QCs, signatures, proofs, replay
rand-node verify --datadir ./data --repair # truncate a damaged tail; peers resync the rest
RUST_LOG=debug rand-node run ... # verbose logsAt startup a node checks its chain (--verify-chain quick|full|off), truncates anything
inconsistent while keeping its vote-safety state, and refetches the missing blocks from peers.
deploy/ has scripts to provision a Linux server as a systemd service and to rebuild it on new
commits; docs/deploy.md describes the rollout and the fault tests that have been run.
- A wallet scans the commitment tree, picks at most two of its own notes per asset it is moving,
proves a 4-in-4-out hidden-asset bundle locally, and submits
{ chain_id, bundle, action }over RPC. There is no signature and no sender: the proof is the authorisation. A faucet mint is the one exception — a validator signs it with its own key. - The node validates against the tip state — cheap checks first, the bundle's STARK last — refuses anything conflicting with a pending transaction over a nullifier or a commitment, and gossips the rest.
- The leader of the current view proposes a block extending the highest quorum certificate it knows, choosing transactions by fee within a 4 MiB budget. Validators vote if the block is safe with respect to their lock; votes from more than 2/3 of stake form the next certificate.
- A block is committed once three consecutive certificates chain on top of it. Commits are one fsynced RocksDB batch: blocks, certificates, indexes, the new notes and nullifiers, the block-end anchor, the proposer's rewards, programs and receipts.
- A node that falls behind requests committed blocks with their certificates from a peer, verifies every certificate, re-executes every block (including every bundle and call proof), and compares receipts before accepting.
Full detail in docs/architecture.md.
v0.5 adds RPL, RandProtocol's own token standard, and hardens the bridge for its first real
asset, zUSD (backed by USDT and USDC bridged from Ethereum, BSC, Tron and Solana). Merged to
main and live on chain 14 since 2026-09-20, pinned build b3c594c (see AGENTS.md for the
full launch record, the mainnet round-trip evidence table, and what is still pending). Highlights,
all hard forks together as chain 14:
- A token is a registry entry, not a contract — a shielded native asset with an id, an index,
and a checksummed
rpl1…text form; creation is permissionless, under a mint authority fixed at registration (none, a key, or the bridge); symbols are not unique, as with ERC-20 and SPL (docs/tokens.md). - Every transfer, of RAND or any RPL token, is one hidden-asset bundle — a fixed 4-in/4-out
proof that hides not only the amount and the parties but which asset moved at all
(
docs/confidential.md, "The hidden-asset bundle guest"). A token transfer publishes exactly what a RAND payment does. - A transaction is bound to its whole self. Every bundle proof is now made over, and verified
against, a hash of the entire transaction — closing a redirect attack that let a copied,
unmodified proof be resubmitted under a changed destination, validator or memo
(
docs/confidential.md, "Transaction binding";docs/bridge.md§1). - One bridged token can have many backings. zUSD is one token backed by seven source coins
across four chains, with
total_supply == Σ backings.lockedheld by construction and a burn refused unless the named backing has enough locked and the amount is a whole release unit (docs/bridge.md§13). - Bridge hardening: a per-backing daily mint cap and an operator pause key (B1); a forward
bound on block timestamps, which makes NTP a requirement for a bridged chain's validators (B2); a
second, post-quantum (Dilithium2) co-signature quorum on every mint (B3); and listing a new
bridged token or backing after genesis under a PQ guardian quorum, with no chain cut and no wire
change (B4) —
docs/bridge.md§§14–20.
| document | contents |
|---|---|
| docs/cli.md | every rand-node and rand command, argument, and default |
| docs/rpc.md | JSON-RPC methods, parameters, result shapes, error codes |
| docs/shielded.md | the shielded pool: keys, what is public, the wallet, the RPC, admission, what still leaks |
| docs/staking.md | the validator register, epochs, and the four staking commands: register, bond, unbond, withdraw |
| docs/supply.md | the supply audit: the counters, the invariant a node checks, and how exact it is |
| docs/confidential.md | programs, calls, outputs, gas, privacy |
| docs/tokens.md | RPL, the token standard (v0.5): a token as a registry entry, asset ids and rpl1…, mint authorities, creation, hidden-asset transfers, burning, the CLI and RPC, ERC-20/SPL comparison |
| docs/guests.md | writing and deploying a RISC-V program: the Rand ISA, the syscall ABI, the image container, rand-guest build/check/run/pack, hc versus program id, the program-size cap |
| docs/translators.md | the Solana (sbpf2rv) and Ethereum (evm2rv) translators: trust model, parity, the ERC-20 and SPL Token walkthroughs, measured cycles, limits |
| docs/architecture.md | how the node works end to end: consensus, ledger, storage, networking, sync, and one confidential transaction followed from wallet to receipt |
| docs/zkvm-milestones.md | the Rand zkVM milestone by milestone (M1–M4, CUDA backend): what was built and why |
| docs/zkvm-m4-m5-progress.md | M4 and M5 as delivered: constraint sets 4–6, the recursion VM (M5.1–M5.4) with all measured numbers, what is deferred to which hardware |
| docs/bridge.md | the guardian bridge: trust model, wire format, guardian sets, state, the two bridge actions, what stays public, and (v0.5) one token with many backings, the mint cap and pause, bounded timestamps, the post-quantum co-signature, and listing a token after genesis |
| docs/deploy.md | multi-machine and cloud deployment, rebuilds, fault tests |
| docs/node-hardware.md | what validators, wallets and aggregators compute; measured RAM, disk and prover memory per tier; DigitalOcean sizes; setup |
| deploy/README.md | the live testnet: nodes, addresses, peer ids |
| docs/superpowers/specs | design specs (node, confidential computation, fully shielded pool) |
The shielded pool lands in three phases, each a hard fork (docs/shielded.md §7). S1 is the
pool itself: notes, bundles, the wallet, the redacted RPC. S2 adds staking on top of it — Bond,
Unbond and Withdraw, epochs that re-derive the validator set from the register, and the validator
rewards S1 accrued but could not pay out (docs/staking.md). S3 brings the bridge back as
notes — a deposit is one note the chain computes from the amount the guardians signed, a burn is
one hidden-asset bundle since chain 14 (docs/bridge.md) — and gives call inputs their own
envelopes, so a
caller can disclose what a program ran on to an auditor, or to itself later, without publishing it.
Both are here, in this release. Still outstanding from S2's own plan: the local wallet commitment
tree, so a wallet stops telling its node which leaf it is about to spend.
Not yet implemented beyond that: persistent per-program state and cross-program calls; slashing and jailing; a nullifier accumulator in place of the per-block recomputation; block rewards; the hash-sortition leader beacon; proof pruning after finality; fee markets.
Two user-owned hardware tasks gate the next milestones. The full checklist with exact commands is
in docs/deploy.md ("Deferred proof runs and hardware tasks"); both were accepted by the user on
2026-09-15 and are scheduled for 2026-09-16.
- A ≥ 64 GB machine, one batch session — runs the deferred production proofs (runbook rows
1–6 in
circuits/recursion/docs/03-gpu-and-self-recursion.mdAppendix A): the ERC-20 and SPL token guest proofs, the rVM tier-21 exit, the N=2/N=3 aggregate twins, the production N=1 aggregate. Its measurements fill chain-9'sgenesis.aggregation.admitted_shapes[0]— chain-9 activation is blocked on this batch (the 2026-09-15 ruling: production proofs execute on ≥ 64 GB after chain-side aggregation lands). - A fleet GPU node — for the rVM CUDA backend's remaining tasks (the first PTX build +
hardware bring-up per
circuitsPTX_BUILD.md, then the production N re-measurement). Spec: Linux, R580+ driver, CUDA 13, LLVM 21, sm_80+, 80 GB device (H100/A100-80G), ≥ 160 GB host. None exists today.
GNU General Public License v3.0 (GPL-3.0-only) — see LICENSE.