Field
BN254 scalar
p =
21888242871839275222246405745257275088548364400416034343698204186575808495617 All field elements are members of Fp. Every external input is validated < p before circuit execution.
Specification · Version 1.0.0 · 2026-04-21
The full cryptographic construction of Commons. Every proof, hash, and domain separator an auditor needs to reproduce our claims.
UltraHonk over BN254 Aztec Ignition SRS, 1-of-N honesty 0 live circuits three-tree membership · position note · debate weight · bubble membership
Canonical source CRYPTOGRAPHY-SPEC.mdFour layers. The integrity ceiling is set by the weakest.
UltraHonk on BN254 provides computational soundness under the algebraic group model and the hardness of discrete log on BN254. If a proof verifies, the prover demonstrably knows a witness satisfying every circuit constraint. No off-chain component is trusted.
Three immutable append-only registries on Scroll L2. Lifecycle transitions require 7-day timelocks; verifier upgrades require 14 days. The guarantee is transparency with exit rights — malicious governance action is visible on-chain before execution.
The Shadow Atlas operator downloads public Census TIGER data and builds Poseidon2 Merkle trees. The operator cannot forge proofs (user secrets are client-side only) but can poison the tree (mis-map an address) or censor (omit a user). Mitigations documented; walkaway roadmap published.
Census TIGER/Line boundary data is public, free, and published with SHA-256 checksums. Anyone can download the same shapefiles and verify them. The trust assumption is that the Census Bureau publishes accurate boundaries.
Three primitives, nested. A prime field; a hash that operates on it; a sponge that chains the hash.
Field
p =
21888242871839275222246405745257275088548364400416034343698204186575808495617 All field elements are members of Fp. Every external input is validated < p before circuit execution.
Hash, over Fp
~156× cheaper in-circuit. Field-native; no bit decomposition.
Security level 128. Implementation: Noir stdlib. Parameters match the reference Aztec / ZCash specification.
Sponge, over Poseidon2
The += is load-bearing. Overwriting rate slots would break the cryptographic chain — each permutation output must carry forward into the next absorb step. A cross-language golden-vector check runs in CI: Noir, TypeScript, and Rust implementations must produce bit-identical digests.
Every hash output carries a tag at a fixed position in the Poseidon2 state. Seven tags define the protocol’s typology. No tag can change without invalidating all historical proofs.
Arity separation
One tag per input count. The tag’s position shifts right as arity grows — visible across the cluster.
"H1M"
= 0x48314d
"H2M"
= 0x48324d
"H3M"
= 0x48334d
"H4M"
= 0x48344d
2-round sponge — state width exceeded, tag carried across permutations
Purpose separation
Same arity, different semantic domain. PCM and PNL both consume three inputs into the final slot; distinct tag values prevent cross-purpose collisions.
"PCM"
= 0x50434d
"PNL"
= 0x504e4c
Capacity initialization
Tag seeds the capacity slot, making the entire sponge computation depend on the domain from its first permutation.
"SONGE_$"
= 0x534f4e47455f24
Four Merkle trees carry the protocol state. Three persistent trees link through shared witnesses; one is debate-scoped and cryptographically isolated.
Core · persistent, linked
binary Merkle
Leaf
H4(user_secret, cell_id, registration_salt, authority_level) cross-tree cell_id binds to Tree 2; user_secret derives the identity_commitment that feeds Tree 3 and the nullifier (§5). authority_level bound in leaf (BR5-001).
sparse Merkle, key-derived
Leaf
H2(cell_id, district_commitment) cross-tree cell_id is the join key with Tree 1. Sparse path means proof size is logarithmic in occupied keys, not in depth.
binary Merkle
Leaf
H2(ic, H3(tier, action_count, diversity_score)) cross-tree ic is the same private identity_commitment that feeds the nullifier. The cross-tree binding in §5 enforces that no engagement leaf can be claimed by a foreign identity.
Debate-scoped · ephemeral
binary Merkle
Leaf
H_PCM(argument_index, weighted_amount, randomness) cross-tree Leaf uses the PCM domain tag (§3), not H3M — preventing cross-circuit commitment aliasing with engagement data. Consumed by position_note circuit (§6).
A single private input feeds two derivations.
Zero-knowledge
The proof reveals none of ic. An observer sees nullifier and engagement_leaf, both one-way derived. Recovering ic reduces to breaking the discrete-log assumption on BN254.
Equality assertion
The circuit asserts the ic consumed by the nullifier derivation equals the ic consumed by the leaf derivation. Rotating between them fails at witness generation — no satisfying proof exists.
Attacker cannot extract ic, and cannot substitute a different one. Engagement data cannot be claimed by a foreign identity.
This grammar repeats across every circuit. A private witness enters a hash inside the prover, producing a value that the proof equation binds to a public receipt on-chain.
When you must prove membership in a known set, the grammar extends. Your value hashes into a leaf. Each climb combines it with a sibling — another hash at that tree level — until the ladder of twenty climbs reaches the root, the set's on-chain commitment. The chain of siblings is the proof.
Civic action
identity ⟶ action
I'm a verified person in one of these 24 districts, at engagement tier ≥ N, and this is my one action.
Private · stays with you
Public · anyone can verify
Identity proves this is a real, uniquely registered person. The deterministic output is the identity_commitment — abbreviated ic — a stable per-person value that never reveals who.
user_root ≡ merkle( H4(user_secret, cell_id, registration_salt, authority_level), path, idx )
Location proves the person lives in one of the 24 accepted districts.
cell_map_root ≡ smt( H2(cell_id, sponge₂₄(districts)), path, bits )
Identity and location bind to this one action — and only this one. The same ic from ① forks into a nullifier (prevents double-spend) and into the engagement record.
nullifier ≡ H2(ic, action_domain) · engagement_root ≡ merkle( H2(ic, H3(tier, count, diversity)), path, idx )
identity ⟶ community field
I'm a verified person who lives in these specific map cells.
Private · stays with you
Public · anyone can verify
engagement_root ≡ merkle₂₀( H2(ic, H3(tier, count, div)), path, idx ) · epoch_nullifier = H2(ic, epoch_domain)
cell_set_root ≡ merkle₄( cell_ids[16] ) · sorted, zero-padded, MAX_CELLS = 16
Debate market
stake ⟶ weight
My influence is √stake × 2^tier — without revealing stake or tier.
Private · stays with you
Public · anyone can verify
sqrt_stake² ≤ stake < (sqrt_stake + 1)² · the interval traps exactly one integer root
weighted_amount ≡ sqrt_stake × 2^tier
note_commitment ≡ H3(stake, tier, randomness) · consumed by position_note
commitment ⟶ settlement
I own a position in the winning argument and I'm claiming its payout.
Private · stays with you
Public · anyone can verify
commitment = H_PCM( argument_index, weighted_amount, randomness )
position_root ≡ merkle₂₀( commitment, position_path, position_index )
argument_index ≡ winning_argument_index · weighted_amount ≡ claimed_weighted_amount
nullifier ≡ H_PNL( nullifier_key, commitment, debate_id ) · pre: nullifier_key ≠ 0
A natural first cut: hash a per-registration secret and call it a nullifier. But nothing binds that secret to a person — register again with a new secret, the hash changes, one person gets two nullifiers for the same action. Binding the nullifier to a verified identity instead closes the attack before it opens.
user_secret varies with each registration. Two secrets → two hash outputs → two valid nullifiers for the same action_domain. The attacker casts two votes as one person.
H_id is deterministic on the mDL's stable fields. Same person, same identity_commitment — regardless of user_secret, registration_salt, or cell_id. Feeding it into H2 with a contract-fixed action_domain collapses all re-registrations onto the same output.
Formal statement
nullifier = H2(, action_domain) Sybil via re-registrationnullifier = H2(identity_commitment, action_domain) deterministic per verified personidentity_commitment privateH_id(mDL signed fields)user_secret, registration_salt, cell_idaction_domain publickeccak256(protocol, country, jurisdictionType, recipientSubdivision, templateId, sessionId) mod BN254DistrictGate.allowedActionDomains — governance-whitelistedThe protocol's zero-knowledge machinery rests on a public reference string. If the party who generated it kept a secret, every proof could be forged. Here's how that didn't happen.
Load-bearing for the walkaway roadmap. None block shipping. Ordered worst-first.
The Shadow Atlas operator can poison or censor tree construction. Walkaway roadmap target. See TRUST-MODEL-AND-OPERATOR-INTEGRITY.md §5.
A cryptographer cannot today reproduce the verifier contract bytecode from the nargo source without insider knowledge of toolchain versions and flags. This undermines the 14-day verifier upgrade timelock's community-verification property. Remediation: pin toolchain in Docker / Nix.
New roots are registered without timelock (fast UX). A compromised governance key can register a poisoned root instantly; the poisoning must still pass Census-based independent verification.
Three internal review waves have occurred. Findings documented in docs/wave-4xR-*-review.md. No external firm has audited the circuits or contracts.
No circuit is currently formally verified. Domain-separation non-collision is asserted by property tests, not by a formal proof.