Architecture
System architecture
Vouch is a file format and the small set of programs that write it, check it and act on it. This page maps every component, what it depends on, what it talks to, and where it runs.
On this page
#Components
| Component | Role | Runs where | Network |
|---|---|---|---|
pof-anchor | Rebuilds the note tree and nullifier IMT from compact blocks; writes snapshots; publishes anchors. | Whoever publishes anchors | lightwalletd (gRPC) |
pof-prove | Finds the holder’s notes in a snapshot, proves the claim, signs it, writes the .pof. | The holder’s machine | none (library); revoke only |
pof-verify | The verifier library and CLI: six checks, typed verdicts. | Anywhere | none |
pof-wasm | pof-verify compiled to WebAssembly with the verifying key embedded. | The browser, in a Web Worker | none |
pof-attest | HTTP service: verify, sign attestations for Solana, the rail API, reserves attestations. | Render (hosted), or anyone | Solana RPC, revocation list, lightwalletd |
pof-gate | Solana program: attestations in, receipts out. | Solana devnet | — |
pof-credit | Solana program: credit lines against receipts. | Solana devnet | — |
pof-reserve | Solana program: reserves feed and Secure Mint. | Solana devnet | — |
| The site | Next.js: verifier, request builder, prover console, rail console, reserves, the live demo, these docs, and a thin API. | Vercel | the attestor, Solana RPC, Redis |
#The Rust workspace
| Crate | Key entry points | Notes |
|---|---|---|
pof-core | encode · decode · statement_hash · signing_message · audience_hash · revocation_tag · subject_hash | The format. Decoding is injective: canonical varints, checked lengths, timestamps ≤ 2⁵³ − 1. |
pof-zk | Verifier::new · verify_holding · instance_for · round_id · NoteTree · prove_holding (feature prove) | Embedded params and verifying keys; refuses repeated tags before Halo2. |
pof-verify | verify(bytes, &Context) → VerificationResult · verify_batch · exit_matches | The single source of verdicts. |
pof-prove | select_notes · envelope_for · prove · wallet::find_notes · DemoWorld · history | A test fails the build if the library links a network client. |
pof-anchor | Snapshot::{read, write, anchor} · Lightwalletd::scan · publish | The only crate with a gRPC client. |
pof-attest | attest · verify_only · rail::{submit, match_deposit, batch} · demo::prove | Axum; per-core semaphore; 64 KiB body limit (2 MiB for batches). |
pof-wasm | warm · verify · version | wasm-bindgen, about 4.6 MB, committed so CI tests exactly what ships. |
The vendored crates are in backend/vendor/; every Vouch change is listed in THIRD-PARTY.md and in each crate’s VOUCH-MODIFICATIONS.md. The Solana programs are a separate Anchor workspace in backend/solana/.
#Repository layout
| Path | What it is |
|---|---|
frontend/ | The site (Next.js 15): pages, the API routes, the committed WASM verifier, these docs. |
backend/crates/ | The seven Rust crates above. |
backend/vendor/ | voting-circuits and its dependencies, with marked Vouch modifications. |
backend/solana/ | pof-gate, pof-credit, pof-reserve (Anchor) and the LiteSVM tests. |
fixtures/ | Real proofs with their expected verdicts, anchor tables, the demo ledger, request vectors. |
scripts/ | WASM build, fixture sync, the WASM parity check, end-to-end test, local stack, devnet deploy. |
.github/workflows/ | CI (every test but e2e), release builds of the CLIs, the attestor keepalive. |
#Data flows
| Flow | From → to | Carries | Never carries |
|---|---|---|---|
| Anchors | lightwalletd → pof-anchor → anchor table → every verifier | heights, roots, block hashes | — |
| Request | verifier → holder (a link) | claim, audience id, expiry, period, policy | anything about the holder |
| Proof | holder → verifier (a file or link) | the statement and the evidence | balance, notes, addresses, keys |
| Verify link | holder → anyone | the proof after the # | nothing reaches a server log |
| Attestation | attestor → Solana | 139 signed bytes | proof bytes |
| Rail API store | attestor ↔ rail | ids, tags, revealed nullifiers, verdicts, times | proof bytes |
| Revocation | holder → list → verifiers | a 32-byte secret | which proof it was, to anyone else |
#The hosted deployment
| Piece | Where | Notes |
|---|---|---|
| Site and API | Vercel | Deployed from frontend/. Upstash Redis holds revocations, used demo proofs, rate limits and hourly relayer budgets. |
| Attestor | Render, free plan | render.yaml builds pof-attest without the demo prover (one proof takes about 2 minutes on 0.1 CPU); /demo uses 120 pre-made single-use proofs instead. |
| Programs | Solana devnet | Fixed program ids; the admin keeps the upgrade authority, which initialize requires. |
| CLIs | GitHub Releases | Built for Linux, macOS (arm64) and Windows from each v* tag. |
Free Render instances sleep after 15 minutes idle. The site’s /api/status reports a sleeping attestor as waking, and /demo waits for it rather than falling back to the simulated chain.
#Design principles
- The proof is a file, not an API call. Offline verification, a testable boundary, a format that outlives any implementation.
- One verifier. The CLI, the browser and the attestor run the same code; CI proves they agree.
- The verifier never trusts a field it can derive. Roots, round id, domain and threshold are recomputed or looked up.
- Keys stay put. The prover links no network client; nothing is broadcast.
- Boring, plural attestors. Stateless, re-runnable, allowlisted, k-of-n.
- Honest labels. Demo, testnet and simulated are said wherever they apply.