Guides
Accepting a proof
For verifiers: one verifier implementation, three ways to run it. Pick the one that fits where your decision is made.
On this page
#Choose where to verify
| Where your decision is made | Use | Trust added |
|---|---|---|
| A script, a back office, CI | pof-verify CLI | none |
| A web page | pof-wasm in a Web Worker | none |
| A Solana program | an attestor + pof-gate | the attestor allowlist (k of n) |
| A rail’s deposit flow | the rail API on an attestor you run | none, if you run it |
#1 · Ask for a proof
Build a request at /request: the threshold, your identifier, an expiry, and optionally a period (so reuse shows), an unmoved-since block, a response deadline, and a binding to the holder’s Solana account. Hand the link to the holder.
Choosing an identifier
Your identifier is what proofs are addressed to, and only its hash travels. Use something stable and specific, like lender:acme-desk; a proof made for anyone else is refused at check 4.
#2 · Verify with the CLI
curl -s $SITE/api/anchors > anchors.json
curl -s $SITE/api/revocations > revocations.json
pof-verify check proof.pof --audience pof-credit:usdc-pool-1 \
--anchors anchors.json --revocations revocations.json \
--epoch 202610 --seen registry.json --dormant-since 4410000
# Valid · 500.00000000 ZEC at least · anchor 3491040
echo $? # 0 valid · 1 invalid · 2 expired · 3 malformed · 4 could not runWith --json it prints the full result, including the tags to append to your registry. It reads local files only, so it can gate CI or a batch job.
#3 · Verify in the browser
pof-wasm is the same Rust crate compiled with wasm-bindgen. Its verifying keys are embedded (a native test proves they equal a fresh keygen), so it warms up in about 300 ms and verifies in about 90 ms. The boundary is pure: bytes, the anchor table and the revocation list in; a verdict out.
import init, { verify } from '/wasm/pof_wasm.js'
await init()
const anchors = await (await fetch('/api/anchors')).text()
const revoked = JSON.stringify((await (await fetch('/api/revocations')).json()).secrets)
const now = BigInt(Math.floor(Date.now() / 1000))
const result = JSON.parse(verify(bytes, 'pof-credit:usdc-pool-1', now, anchors, revoked))
if (result.verdict.kind !== 'Valid') refuse(result.verdict)Run it in a Web Worker, as the site does, so the warm-up never blocks the page.
#4 · Verify on Solana
A Solana program cannot verify a Halo2 proof over Pallas or read Zcash state. So pof-attest runs the verifier and signs a domain-separated 139-byte message, and pof-gate checks that signature through instruction introspection:
ix 0 Ed25519SigVerify111111111111111111111111111 verify(attestor, message, signature)
ix 1 pof-gate::submit_attestation(subject, message)
1. parse and domain-check; verdict = Valid; claim kind known
2. slot within max_age_slots; expires_at in the future
3. count distinct allowlisted keys that signed exactly this message in
earlier Ed25519 instructions whose offsets all point into themselves
4. count ≥ threshold (k of n)
5. create ClaimReceipt PDA [b"receipt", subject] ← one proof, one receiptA consumer program reads the receipt, checks the audience, the claim value, the expiry and that the signer is the beneficiary, then calls mark_consumed by CPI, signed by the beneficiary and by the consumer program’s own [b"consumer"] PDA. pof-credit is the worked example: see Credit lines.
| Program | Devnet id |
|---|---|
| pof-gate | A6WmRTgEAHC9GHstaj9oD8woVNRw9jgu31bpKVREXn8C |
| pof-credit | J6ewFZAzcTkbVsYNBrcZvzdBtTTUK5UrpqCg9jiNyra1 |
| pof-reserve | 5Q26Tjcd4equ5te5hF15eG4xK6uvkbZtKkEeqdf535h9 |
Read the trust model before you rely on it, and run your own attestor.
#Integration checklist
- Load only the anchors you trust (mainnet only, if you only accept mainnet).
- Fetch the revocation list at decision time, not once at start-up.
- Name a period in your requests and keep a registry of accepted tags, or reuse is invisible to you.
- Set
--tip-heightand--max-anchor-agewhen “unspent” must be recent. - Treat anything but Valid as a refusal, and show the holder the verdict’s reason.
- Ask for a fresh proof at each decision: a proof is “as of” one block.