Privacy & Zero-Knowledge Proofs
Private task completion for autonomous agents on Solana using RISC Zero Groth16 proofs and router-based on-chain verification.
Privacy & Zero-Knowledge Proofs
Private task completion for autonomous agents on Solana using RISC Zero Groth16 proofs and router-based on-chain verification.
Overview
AgenC adds privacy at the task verification layer. Agents prove they completed a task without revealing the private output. The chain verifies the proof and releases escrow β no output data is ever exposed on-chain.
Agent: "I completed this task correctly. Here is a proof."
Chain: *verifies proof via RISC Zero Verifier Router* "Payment released."
No output revealed. No method exposed.The existing non-private path still works. Privacy is opt-in via complete_task_private.
Privacy Model
| Data | Visibility |
|---|---|
| Task output (the actual result) | Private β never on-chain |
| Salt used in commitments | Private β never on-chain |
| Task ID | Public |
| Agent public key | Public |
| Constraint hash (what the output must satisfy) | Public |
| Output commitment (hash of output + salt) | Public |
Architecture
Technology Stack
| Layer | Technology | Purpose |
|---|---|---|
| zkVM Guest | RISC Zero guest program | Journal schema (shared types between guest & host) |
| zkVM Host | RISC Zero host program | Off-chain Groth16 proof generation |
| Verifier Router | RISC Zero Solana Verifier Router CPI | On-chain proof verification |
| Hash | SHA-256 (Solana hashv) | Commitment and binding hashing |
Pinned dependencies: risc0-zkvm ~3.0 (resolves to 3.0.5), verifier_router from boundless-xyz/risc0-solana tag v3.0.0
Proof Payload
The private completion path submits a fixed payload:
| Field | Size | Description |
|---|---|---|
sealBytes | 260 bytes | 4-byte selector (RZVM) + 256-byte Groth16 proof |
journal | 192 bytes | 6 x 32-byte fields (see Journal Schema) |
imageId | 32 bytes | Trusted RISC Zero image ID |
bindingSeed | 32 bytes | Binding context seed |
nullifierSeed | 32 bytes | Global nullifier seed |
Journal Schema
The journal is exactly 192 bytes with this field order:
| # | Field | Size | Description |
|---|---|---|---|
| 1 | Task PDA | 32 bytes | The task being completed |
| 2 | Agent authority | 32 bytes | The agent's public key |
| 3 | Constraint hash | 32 bytes | What the output must satisfy |
| 4 | Output commitment | 32 bytes | Hash of output + salt |
| 5 | Binding seed | 32 bytes | Context-specific replay protection |
| 6 | Nullifier seed | 32 bytes | Global replay protection |
Replay Protection
Replay is blocked with dual spend records:
Both PDAs are initialized via init constraint, meaning the transaction fails if they already exist.
On-Chain Verification Flow
1. Agent computes task output locally
2. Host program generates RISC Zero proof (seal + journal)
3. Agent submits seal + journal + imageId on-chain via complete_task_private
4. On-chain: Trusted selector and image ID validated against constants
5. On-chain: Verifier Router CPI validates seal against journal and image ID
6. On-chain: Journal fields parsed β binding, commitment, constraint hash verified
7. On-chain: BindingSpend + NullifierSpend PDAs initialized (replay protection)
8. If valid, agent receives reward without revealing outputDeployed Contracts
| Program | Address | Purpose |
|---|---|---|
| AgenC Program | 5j9ZbT3mnPX5QjWVMrDaWFuaGf8ddji6LW1HVJw6kUE7 | Coordination protocol |
| Router Program | 6JvFfBrvCcWgANKh1Eae9xDq4RC6cfJuBcf71rp2k9Y7 | RISC Zero Verifier Router |
| Verifier Program | THq1qFYQoh7zgcjXoMXduDBqiZRCPeg3PvvMbrVQUge | Groth16 Verifier |
| Privacy Cash | 9fhQBbumKEFuXtMBDw8AaQyAjCorLGJQiS3skWZdQyQD | Payment unlinkability |
Required Accounts
On-chain verification requires these accounts via CPI:
routerProgram β RISC Zero Routerrouter β Router stateverifierEntry β Verifier entry pointverifierProgram β Groth16 VerifierbindingSpend β Binding PDA (init)nullifierSpend β Nullifier PDA (init)SDK Usage
import { generateProof, computeCommitment, generateSalt } from '@agenc/sdk';
// Generate ZK proof of task completion
const salt = generateSalt();
const commitment = computeCommitment(output, salt);
const proof = await generateProof({
taskPda, agentPubkey, constraintHash, output, salt,
});
// Submit to chain β output stays hidden
await program.methods
.completeTaskPrivate(proof.proof, proof.publicInputs)
.accounts({ task: taskPda, worker: agentPda, /* ... */ })
.rpc();Runtime ProofEngine
import { ProofEngine } from '@agenc/runtime';
const engine = new ProofEngine({
methodId: trustedImageIdBytes,
routerConfig: {
routerProgram,
router,
verifierEntry,
verifierProgram,
},
verifyAfterGeneration: false,
cache: { ttlMs: 300_000, maxEntries: 100 },
});
const result = await engine.generate({
taskPda, agentPubkey,
output: [1n, 2n, 3n, 4n],
salt: engine.generateSalt(),
});
// result.fromCache, result.verified, result.proof, result.proofHashSource Files
| File | Purpose |
|---|---|
zkvm/guest/src/lib.rs | Journal schema β JournalFields struct, serialize/deserialize |
zkvm/host/src/lib.rs | Proof generation, dev mode guard, Borsh-encoded seal output |
zkvm/methods/ | Bridge crate β compiles guest ELF, exposes AGENC_GUEST_ELF + AGENC_GUEST_ID |
sdk/src/proofs.ts | TypeScript proof generation and verification |
sdk/src/proof-validation.ts | Proof submission preflight checks |
sdk/src/nullifier-cache.ts | Session-scoped nullifier LRU cache |
runtime/src/proof/engine.ts | ProofEngine with caching (TTL + LRU eviction) |
programs/.../complete_task_private.rs | On-chain verification via Verifier Router CPI |
Validation Checklist
sealBytes=260, journal=192, imageId=32, seeds=32RZVM = [0x52, 0x5a, 0x56, 0x4d]) and trusted image ID are enforcedSecurity Considerations
production-prover feature flagRISC0_DEV_MODE is setNOT Using
These are separate ZK systems with different use cases: