Architecture
The verification pipeline
One function decides every verdict: pof-verify::verify. This page walks through its six checks, the verdicts they produce, the policy a verifier can add, and the three places it runs.
On this page
#Six checks, cheapest first
| # | Check | Fails as |
|---|---|---|
| 1 | Magic, version, length limits, checksum, decode; HoldsAtLeast in whole 0.125 ZEC units | Malformed { reason } |
| 2 | now < expires_at (equality is expired, as on Solana) | Expired { at } |
| 3 | No published secret hashes to the revocation tag | Revoked |
| 4 | blake2b(identifier) equals audience; the epoch is the one requested, if any | WrongAudience · WrongScope |
| 5 | The tree root at nc_height and the spent-set root at height are both in the table; the spent set fresh enough; the notes unmoved long enough | AnchorNotFound · AnchorTooOld · NotDormantLongEnough |
| 6 | Five distinct tags; the RedPallas signature under rk; the Halo2 proof against the circuit’s key; then no tag in the registry | ProofInvalid · AlreadyUsed |
ProofInvalid says the evidence does not prove the statement, not which constraint failed: verbose failure detail in a security tool is a debugging aid for an attacker.
#Context and policy
pub struct Context<'a> {
audience: &'a str, // your identifier; only its hash is compared
anchors: &'a [AnchorRecord], // the table you trust
revoked_secrets: &'a [String], // published secrets, hex
now: u64,
zk: &'a ZkVerifier, // the embedded verifying keys
policy: Policy,
seen: &'a [String], // your reuse registry: tags accepted in this scope
}
pub struct Policy { // all optional
tip_height: Option<u32>, // with max_anchor_age: refuse a stale spent set
max_anchor_age: Option<u32>,
dormant_since: Option<u32>, // notes unmoved since this block or earlier
epoch: Option<u64>, // the period your request named
}Everything the verifier needs is passed in: it reads no files and makes no network calls, which is what lets it run unchanged in a browser.
#Verdicts
pub enum Verdict {
Valid { claim: Claim, anchor_height: u32, dormant_since: Option<u32> },
Expired { at: u64 },
Revoked,
WrongAudience,
AnchorNotFound,
AnchorTooOld { anchor_height: u32 },
NotDormantLongEnough { dormant_since: u32, required: u32 },
WrongScope { epoch: u64, required: u64 },
AlreadyUsed,
ProofInvalid { detail: String },
Malformed { reason: String },
}#Three hosts
In the browser
The site lazily loads /wasm/pof_wasm.js in a Web Worker. warm() loads the embedded parameters and verifying key (about 330 ms, once); each verification then takes about 90 ms. The page fetches /api/anchors and /api/revocations, calls verify(bytes, audience, now, anchorsJson, revokedJson) and renders the JSON result. A verify link carries the proof after the #, so it never reaches a server.
The CLI
pof-verify check reads local files only and turns the verdict into an exit code: 0 valid, 1 invalid, 2 expired, 3 malformed, 4 could not run.
The attestor
pof-attest runs the same verify under a per-core semaphore (429 when busy), with revocations from a static file and the site’s list (cached for 10 s, a 4 s timeout, keeping the last list on failure). It signs only a Valid verdict bound to a Solana account.
#The JSON result
Every host returns the same JSON: the verdict, the six checks in order, the matched anchor record, the proof’s scope and tags, an exit certificate’s revealed nullifiers, and envelope, the JSON projection of the proof (hex in lowercase, amounts in zatoshi, times in unix seconds). Its JSON Schema is at /schema/pof-v1.json, and CI checks every vector’s result against it.
{ "verdict": { "kind": "Valid", "claim": { "kind": "HoldsAtLeast", "zatoshi": 100000000 }, "anchorHeight": 4410000 },
"checks": [ { "id": "format", "label": "…", "status": "pass", "detail": "…" }, … ],
"envelope": { "version": 1, "claim": { … }, "audience": "412a96…", "binding": "0000…", "anchor": { … },
"issuedAt": 1790622467, "expiresAt": 1800990467, "revocation": "704960c1…", "evidence": { … } },
"sizeBytes": 11884, "checksum": "c82489…", "anchor": { "network": "testnet", "height": 4410000, … },
"now": 1790693805, "verifier": "pof-verify 0.1.0" }#Revocation
#How agreement is tested
| Test | What it proves |
|---|---|
pof-verify tests/fixtures.rs | The native verdict for every committed vector equals expected.json. |
pnpm test:wasm | The committed WASM build gives the same verdicts, and every result matches the JSON Schema. |
pnpm test:fixtures | The TypeScript codec parses the Rust encoder’s bytes; request-link rules agree in both languages. |
embedded_vk_matches_keygen | The embedded verifying keys equal a fresh keygen. |