Version: 1.1 Date: November 2025 Status: Architectural Specification (Buildable v0.1) (c) Oliver Hirst 2025
This document specifies a blockchain where:
- Consensus is achieved via deterministic tournaments between miners in a cellular automaton (CA) arena.
- Miner eligibility and long-term behaviour are governed by protocol-local EBSL (Evidence-Based Subjective Logic) using only on-chain events.
- Transactions and smart contracts are privacy-preserving, enforced by modular ZK-SNARK circuits.
- The chain uses a longest-chain / heaviest-chain fork choice with deterministic work per block, not hash lottery PoW.
Core design choices:
-
Tournament PoW: Each block height has a CA tournament. Eligible miners commit to glider patterns, then reveal and battle in a deterministic bracket. The winner proposes the block.
-
Anti-cartel properties: Random pairings (derived from VRF-based per-epoch randomness), ring signatures, and pairwise battles make cartel coordination unstable and economically irrational.
-
Protocol-local EBSL gating: Miners must have:
- A bond ≥
B_MIN(economic Sybil barrier), and - A protocol-local EBSL trust score
T ≥ T_MINderived from their on-chain behaviour. Negative events hit fast, forgiveness is slow.
- A bond ≥
-
Full verification mandatory: Every validator must verify all battle proofs and contract proofs before a block is valid. No sampling in the consensus rule.
-
Optional sampling mode with explicit finality: Implementations may use sampling as a fast pre-check for gossip / local UX, with quantified detection probability and a recommended safe finality depth (e.g. 100+ blocks).
-
Privacy-native smart contracts: A ZKVM runs private contract logic, with only commitments and proofs on-chain. State is never decrypted in consensus.
This is a deliberately L1-focussed, security-first design targeting high-value, privacy-sensitive use cases. Throughput is acceptable (Bitcoin-ish, ~100 TPS), but not the primary goal.
- System Architecture
- Participants, Identity, and Protocol-local EBSL
- Consensus Mechanism and Fork Choice
- Tournament Protocol (Commit–Reveal, Randomness, Liveness)
- Cellular Automaton Engine
- Zero-Knowledge Proving Architecture
- Smart Contract Execution (ZKVM)
- Cryptographic Primitives
- Network Protocol
- Economic Model
- Light Clients
- Governance and Upgradability
- Security Analysis
- Performance Characteristics
- Implementation Roadmap
At a high level:
- Consensus layer: CA tournament + heaviest-chain rule.
- State layer: Privacy-preserving state commitments (balances, contract storage, nullifiers).
- Execution layer: ZKVM for contract logic; ZK-SNARK proofs for execution validity.
- Reputation layer: Protocol-local EBSL for miner trust and eligibility.
- Network layer: P2P gossip, compact blocks, standard inventory/headers.
Rough stack:
Application Layer
├─ Wallets / dApps / DeFi protocols
└─ Explorer & monitoring
Smart Contract Layer
├─ ZKVM (RISC-like instruction set)
├─ Private state commitments + nullifiers
└─ Contract libraries / standards
Consensus Layer
├─ Protocol-local EBSL + bonded miner set
├─ CA tournament per block height
└─ Heaviest-chain fork choice (sum of per-block tournament work)
CA Layer
├─ 2D grid, Conway-style rules (tuned for cryptographic determinism)
└─ Glider patterns, collisions, battle outcomes
ZK Layer
├─ Battle verification circuit
├─ ZKVM execution circuit
└─ State transition / Merkle / nullifier circuit
Network Layer
├─ P2P gossip
├─ Block / tx / proof propagation
└─ Light-client support (headers + proofs)
Each miner is identified by:
MinerID = hash(pubkey)pubkeyfor ECDSA/BLS signatures- Associated bond account that locks tokens as a Sybil barrier
To join the candidate miner set, an address must:
- Lock at least
B_MINtokens into a bond contract. - Agree to slashing conditions (equivocation, invalid blocks, etc).
Bond information is stored on-chain and is part of the global state.
We define per-miner evidence counters:
r_m= positive evidence (honest behaviour)s_m= negative evidence (dishonest behaviour)
Evidence updates:
Positive:
GOOD_BLOCK(m) → r_m += 1.0
HONEST_PARTICIPATION(m) → r_m += 0.25
Negative:
INVALID_BLOCK(m) → s_m += 6.0
INVALID_TOURNAMENT(m) → s_m += 10.0
PROOF_FAILURE(m) → s_m += 12.0
EQUIVOCATION(m) → s_m += 20.0
Per epoch we apply slow decay:
r_m *= POS_DECAY (e.g. 0.99)
s_m *= NEG_DECAY (e.g. 0.999)
Mapping to subjective logic opinion:
R = r_m + s_mK = 2(binary: honest/dishonest)
b_m = r_m / (R + K)
d_m = s_m / (R + K)
u_m = K / (R + K)
T_m = b_m + ALPHA · u_m (trust score)
Typical parameters:
ALPHA = 0.4T_MIN ≈ 0.75eligibility thresholdT_KILL ≈ 0.2“you’re basically done here”
At epoch/block height h, the active miner set is:
def get_active_miners(state, epoch):
candidates = [m for m in state.miners if m.bond >= B_MIN]
active = []
for m in candidates:
T = compute_trust(m) # as above
if T >= T_MIN and recently_active(m, epoch):
active.append(m)
return activerecently_active enforces liveness (e.g. must have participated in some of the last L tournaments).
For severe offences (especially equivocation), we add:
def handle_negative_event(m, event):
apply_negative_weight(m, event)
T = compute_trust(m)
if event == EQUIVOCATION:
slash(m.bond, 1.0) # full slash
m.banned_until = epoch + ∞ # effectively permanent
elif T < T_KILL:
slash(m.bond, SLASH_RATIO) # e.g. 0.5–1.0
m.banned_until = epoch + BAN_EPOCHSProtocol-local EBSL and bonds together give a fast-punish, slow-forgive regime with an absolute guillotine for certain attacks.
Per block height h:
- Take snapshot of eligible miners
M_husing EBSL + bond rules. - Derive randomness
R_hfrom prior blocks via VRF. - Run tournament among
M_hwith commit–reveal gliders in CA. - Winner
w_hbecomes block proposer. - Proposer executes contracts, assembles block, generates all required proofs.
- Validators verify all proofs; if valid, block is appended.
Unlike Bitcoin, block generation is not a probabilistic hash lottery. Work per block is deterministic, governed by protocol parameters:
N_h = |M_h|active minersRND_h = ceil(log2(N_h))tournament roundsBATTLE_STEPS= CA steps per battleGRID_COST= fixed cost factor for grid size / update rule
We define block work:
work_h = (N_h - 1) * BATTLE_STEPS * GRID_COST
Every block at a given N_h has the same notional work. There’s no “difficulty adjustment” in the lottery sense; instead:
- Chain weight is the sum of
work_halong the chain. - Block time is set by tuning CA/ZK parameters during design.
Nodes adopt the chain with highest cumulative work:
def chain_weight(chain):
return sum(block.work for block in chain)
def select_best_chain(candidates):
return max(candidates, key=chain_weight)Since work_h is deterministic given the block contents (and the activity snapshot), this is objective and verifiable.
This is where we fix the gaping liveness/fairness issues.
Each block height h has a tournament frame with phases:
- Eligibility snapshot
- Commit phase (glider commitments)
- Reveal + battle phase
- Block assembly
We define a VRF output per block, vrf_h, derived by the previous block proposer:
vrf_h = VRF_sk(prev_proposer, message = prev_block_hash || h)
The VRF proof is in the header; verifiable by all.
For height h, the tournament seed seed_h is:
seed_h = H(vrf_{h-1} || vrf_{h-2} || ... || vrf_{h-k})
for some small k (e.g. 8–32). This means:
- No single block proposer fully controls
seed_h. - Grinding
vrfto biasseed_hwould require controlling multiple consecutive blocks.
At the start of height h:
- Nodes compute
M_h = get_active_miners(state_{h-1}, epoch(h)). - This set is fixed for the tournament at
h.
Each m ∈ M_h must broadcast a glider commitment within a commit window Δ_commit:
commit_m = H(glider_pattern_m || nonce_m)
ring_sig_m = RingSign(sk_m, ring=M_h_pubkeys, msg=commit_m || h)
Commitment object:
message GliderCommit {
bytes ring_pubkeys_hash = 1; // hash of sorted pubkeys in M_h
bytes commitment = 2; // H(pattern || nonce)
bytes ring_signature = 3;
uint64 height = 4;
}Rules:
- If a miner fails to commit within
Δ_commit, they are excluded fromM_hfor this height and receive a small negative evidence hit (HONESTNESS_MISStype). - Commitments are anonymous within the ring, but membership is provable.
Once the commit phase closes:
def generate_pairings(commits, seed_h):
# commits correspond 1-1 with M_h elements
miners = [c.miner_id for c in commits] # implicit mapping
rng = PRNG(seed_h)
shuffled = miners.copy()
for i in range(len(shuffled) - 1, 0, -1):
j = rng.randint(0, i)
shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
pairs = [(shuffled[i], shuffled[i+1]) for i in range(0, len(shuffled)-1, 2)]
# odd one out, if any, auto-advances
return pairsThe pairing is fully determined by seed_h and the sorted list of committed miner IDs.
Each committed miner reveals:
reveal_m = (glider_pattern_m, nonce_m)
The block proposer collects reveals, verifies they hash to commitments:
H(glider_pattern_m || nonce_m) == commit_mFor each battle pair (A, B):
-
Place
glider_A,glider_Bin CA grid with fixed positions. -
Simulate
BATTLE_STEPSiterations. -
Determine collision and winner by the CA outcome rules.
-
Generate a battle SNARK proof that:
- The CA evolution was correct.
- Winner declared matches outcome.
- Commitment consistency holds.
Non-revealing miners:
- If a miner committed but fails to reveal, they forfeit that round and get a negative EBSL event.
- The opponent auto-advances.
- Repeated non-reveals can be escalated to slashing / ban via EBSL.
The tournament proceeds in log₂(N) rounds until a single winner remains.
The final winner w_h:
-
Executes contract calls and state transitions locally.
-
Generates ZK proofs for:
- all battles,
- all contract executions,
- state transitions.
-
Packages everything into block
B_h.
All nodes independently recompute:
- Eligibility
M_h, - Expected commits count,
- Pairings,
- Bracket structure.
They verify:
- Each battle proof corresponds to the right pair & commitments.
- Tournament structure leads to the declared
w_h.
If anything diverges from locally recomputed bracket: block invalid.
Per-block timing budget is split roughly:
Δ_commit– commit windowΔ_reveal– reveal windowΔ_compute– CA + provingΔ_propagate– propagation / validation
All of this is bounded by the target block time (e.g. 600 seconds).
Rules:
- Non-commit → excluded for that height, mild negative EBSL.
- Non-reveal → forfeit, stronger negative EBSL.
- Serial non-participation → trust decays, eventually miner drops out of
M_h.
Same as your original concept, but we formalise the parameters.
- Grid:
1024 × 1024toroidal grid. - Cell state: 8-bit (
0–255). - Update rule: Conway-like, with energy.
Pseudocode (unchanged in spirit):
def evolve_cell(cell, neighbors):
live_neighbors = count_live(neighbors)
cell_energy = cell.state
if cell.is_alive():
if live_neighbors < 2 or live_neighbors > 3:
return Cell(state=0)
else:
return Cell(state=cell_energy)
else:
if live_neighbors == 3:
energy = sum(n.state for n in neighbors if n.is_alive()) // 3
return Cell(state=energy)
else:
return Cell(state=0)Battles:
-
Fixed spawn positions
-
BATTLE_STEPSiterations -
Outcome based on:
- Whether a recognisable glider pattern persists
- Fallback to energy tie-break
This logic is embedded both:
- in the execution of the CA engine, and
- as arithmetic constraints inside the Battle SNARK circuit.
We drop the “single mega-circuit” idea. Instead:
-
Battle Circuit
C_battle-
Verifies: commitment consistency + CA evolution + declared winner.
-
Inputs:
- Public: commitments, winner id, seed, positions.
- Private: initial grid state, glider patterns, nonce.
-
-
Execution Circuit
C_exec-
Verifies: ZKVM execution for a single contract invocation.
-
Inputs:
- Public: old state root, new state root, contract address, function selector, gas used.
- Private: witness (plaintext state, arguments, internal steps).
-
-
State Transition Circuit
C_state-
Verifies: commitment/merkle updates, nullifier correctness.
-
Inputs:
- Public: old global root, new global root, nullifiers, commitments.
- Private: merkle paths, underlying cleartext.
-
Each block carries:
- One
C_battleproof per battle (N_h - 1proofs). - One
C_execproof per executed contract call. - One or more
C_stateproofs for state root updates.
Future versions can add recursive/aggregated SNARKs to compress these; v0.1 keeps it simple but modular.
Consensus rule: A block is invalid unless every referenced proof verifies under the appropriate circuit.
The ZKVM is a RISC-ish VM with:
- 256-bit word size, field-friendly arithmetic.
- Finite instruction set: arithmetic, logic, memory, control flow, crypto ops.
The critical point:
- Only provers have access to plaintext state.
- Validators never decrypt anything; they only verify proofs.
So the correct mental model is:
def private_state_transition(old_commitment, function, args, prover_secret):
old_state = decrypt_with_user_key(old_commitment, prover_secret) # OFF-CHAIN
new_state = run_function(function, args, old_state) # OFF-CHAIN
new_commitment = commit(new_state, random_nonce) # OFF-CHAIN
proof_exec = prove_C_exec(old_state, new_state, function_code, args)
proof_state = prove_C_state(old_commitment, new_commitment, ...)
return (new_commitment, proof_exec, proof_state)On-chain / in consensus:
verify_C_exec(proof_exec, public_inputs)
verify_C_state(proof_state, public_inputs)
update_global_state_root(new_root)No decrypt_state() in consensus code, ever.
Mostly as you wrote, with emphasis:
-
Hash: SHA-256 for general use, Poseidon/Rescue inside circuits if needed.
-
Commitments: Pedersen or similar EC commitments, circuit-friendly.
-
Merkle trees: binary, SHA-256 or Poseidon, depending on context.
-
Signatures:
- ECDSA secp256k1 for normal signatures.
- Linkable ring signatures for tournament anonymity.
- Optional BLS for aggregate signatures.
-
ZK System: Groth16 initially, modular per circuit. Later: Plonk / STARK with aggregator.
Standard Bitcoin-esque P2P:
-
inv,getdata,block,tx, plus:glider_commitglider_reveal- optional
battle_proof/exec_proofgossip.
Compact blocks used for efficiency.
Nodes must:
- Propagate candidate blocks.
- Fully verify before marking as valid (consensus rule).
- May use sampling as a local fast filter (see below).
Per block:
block_reward = base_subsidy(h) + tx_fees + contract_fees
Distribution:
60%tow_h(tournament winner / proposer)30%to all participants, weighted by deepest round reached and optional trust factor10%to treasury / dev fund
To avoid proposers cheating on rewards:
- The payout schedule is deterministically computable from the tournament bracket and trust scores.
- The coinbase transaction is checked as part of block validation:
def validate_coinbase(block, bracket, trust_scores):
expected_outputs = compute_expected_payouts(bracket, trust_scores)
return outputs_equal(block.coinbase.outputs, expected_outputs)If coinbase doesn’t match the deterministic formula: block invalid.
No “winner discretion” on paying other participants.
Same structure as before:
- Base fee + tip.
- Privacy multiplier + ZK overhead baked into gas costs.
- Optionally burn part of base fee.
Light clients:
-
Track only headers + chain weight.
-
Verify:
- header structure,
- VRF proofs,
work_hconsistency,- cumulative chain weight.
For transaction inclusion / correctness:
-
They request:
- Merkle inclusion proof for the tx / contract call.
- Corresponding battle/exec/state proofs from full nodes.
They do not re-simulate CA or recompute tournaments; they trust the heaviest header chain and verify individual proofs on demand.
We separate:
-
Hard consensus rules:
- CA rule definition.
- Tournament protocol (phases, randomness, timeouts).
- ZK circuits’ semantics (though they can be versioned).
- Fork-choice rule.
Changes to these require hard forks and explicit client upgrades.
-
Soft parameters:
- EBSL weights / decay rates.
- Reward split percentages.
- Gas pricing.
- Treasury allocation.
Governed via on-chain voting, with parameter changes scheduled far in advance (
activation_height = current_height + N).
For CA rule updates (e.g. if an exploit is found), a special governance process should be defined (e.g. higher quorum, longer delay, signalling via signed messages and on-chain votes).
Key points with your fixes integrated:
-
Randomness / grinding:
- Tournament seed depends on multiple prior VRF outputs → grinding requires capturing several consecutive blocks, not just one.
-
Cartel resistance:
- Commit–reveal + ring signatures + random pairings still give the anti-cartel game dynamics we discussed.
- Protocol-local EBSL further suppresses chronic misbehaviour.
-
Proof soundness:
- Full verification is mandatory: every battle & contract proof is checked before block is valid.
- This removes sampling-based consensus risk.
-
Optional sampling mode & finality depth:
Implementations can optionally:
- Sample K battles/contracts as a pre-filter before spending CPU on full verification.
If an attacker corrupts at least a fraction f of proofs in a block:
- Probability a sample of size
Kmisses all bad proofs:
[ P_\text{miss} = (1 - f)^K ]
Example:
- If
f ≥ 0.1(≥10% proofs are bogus) andK=60:
[ P_\text{miss} = 0.9^{60} \approx 0.0015 \ (\sim 0.15%) ]
Crank K up to 100:
[ P_\text{miss} = 0.9^{100} \approx 2.7 \times 10^{-5} ]
So you can tune a client to “almost never relay invalid garbage” before investing in full verification.
But consensus acceptance still requires full check; sampling is a performance hack, not a rule.
For external users (bridges, exchanges), we can recommend a safe finality depth:
-
E.g. require 100+ confirmations before treating a transaction as economically final.
-
This covers:
- normal reorg risk,
- plus the worst-case where some nodes are slow to validate and a malicious chain gets pruned later.
-
Re-org behaviour:
- Since full verification is required before a block is considered valid, deep reorgs due to late verification failures are avoided at the consensus level.
- Any node that cuts corners (sampling-only) is outside the spec and accepts the risk.
You already did the back-of-envelope; the big change is:
- Full verification is on the critical path.
Target regime:
-
CA steps / circuit design chosen such that:
- Battle proof verification: ~5–10ms
- Exec/state proof verification: ~5–10ms
-
For
N_h ~ 1000miners:N_h - 1 ≈ 999battle proofs → ~10 seconds of verification on a modest machine.
-
Contract proofs: smaller, optional.
So a realistic full validation time per block:
- 10–15 seconds on commodity hardware.
This is still fine with a 10-minute block time, and you can tune N_h, BATTLE_STEPS, and circuit constraints to keep it there.
Phase 1 now explicitly includes:
- Protocol-local EBSL module.
- Tournament phases with commit–reveal.
- VRF-based randomness.
Phase 2:
- Modular circuits (
C_battle,C_exec,C_state) instead of god-circuit.
Phase 3:
- Full verification path optimised; optional sampling path as a client setting, not consensus.
Phase 4:
-
Security audits must specifically check:
- tournament orchestration,
- VRF randomness,
- deterministic payout checking,
- EBSL slashing integration.