SP1 Zero-Knowledge Proofs
Each REGISTER transaction is proven correct inside SP1's RISC-V zkVM. The guest program verifies four things:
- Name validity -- 3-63 chars, lowercase alphanumeric + hyphens
- Fee sufficiency -- payment meets the tiered fee schedule
- Ed25519 signature -- verifies over
"REGISTER:{name}:{address}" - SMT non-membership + insert -- proves the name is not already taken, then computes the new root
Public outputs committed by the proof: name, address, pubkey, fee paid (value_zat), old SMT root, new SMT root.
Architecture
| Component | Description |
|---|---|
zns-prover/program/ | SP1 guest ELF (zns-register-program) -- no_std RISC-V binary |
zns-prover/lib/ | zns-smt crate -- depth-128 SHA-256 SMT with hardcoded empty hashes |
zns-prover/script/ | Host-side prover + prover daemon |
Proof Pipeline
- Scanner processes a REGISTER, captures SMT non-membership proof (128 siblings) + old/new roots, stores a proof record with status
pending - Prover daemon polls the DB for pending records, builds SP1Stdin, generates compressed SP1 proof, verifies it, stores proof bytes with status
proved - API serves the proof via
GET /v1/proof/{name}
Performance (CPU proving, Apple Silicon)
| Metric | Value |
|---|---|
| Ed25519 verification | ~95,000 cycles |
| SMT verify + insert (128 levels) | ~270,000 cycles |
| Total per registration | ~382,000 cycles |
| Compressed proof time (CPU) | ~105 seconds |
Running
# Test prover (standalone, hardcoded test data)
cd zns-prover/script
cargo run --release --bin zns-register # Execute mode (logic check, ~7s)
cargo run --release --bin zns-register -- --prove # Prove mode (compressed proof, ~105s)
# Prover daemon (production, reads from scanner DB)
cd zns-prover/script
cargo run --release --bin zns-prover-daemon -- --db ../../zns/zns.db # Real proofs
cargo run --release --bin zns-prover-daemon -- --db ../../zns/zns.db --execute-only # Logic check only
Proof Chaining
Each registration proof takes the previous root as input and outputs the new root. The chain of proofs creates a verifiable history: root_0 -> root_1 -> root_2 -> ... -> root_current.
SMT Root Persistence
The SMT root is stored in SQLite after every block with state changes:
GET /v1/state/root-- current SMT root + block height + total registrationsGET /v1/status-- includessmt_rootfield
Independent indexers can compare roots. If two indexers process the same blocks and get different roots, one of them is wrong.