Anchor Worker
The AdPriva Anchor Worker is the service that batches cryptographic engagement proofs, generates zero-knowledge proofs (zk-SNARKs), and anchors Merkle roots to Horizen mainnet — making every ad interaction permanently verifiable on-chain.
What It Does
At a high level, the Anchor Worker runs a continuous three-stage pipeline every 5 minutes:
- Batch — Query the database for unprocessed (
pending) engagement proofs, group them into a Merkle tree, and compute the Merkle root. - Verify — Generate a Groth16 zk-SNARK for the batch and submit it to zkVerify for attestation on the zkVerify blockchain.
- Anchor — Submit the attested Merkle root to the
Anchor.solsmart contract on Horizen mainnet, emitting anAnchoredevent that creates a permanent, public record.
This pipeline runs as a single Node.js worker process on DigitalOcean App Platform, replacing the previous three-Lambda AWS architecture.
Architecture
Go Server
│
▼ INSERT anchor_jobs (job_type='zkverify_submit', status='pending')
MySQL (anchor_jobs table)
│
▼ Poll every 5 minutes
DO Anchor Worker (Node.js)
│
├─► Cloudflare R2 ──► Download circuit WASM + zkey
│
├─► zkVerify (Kurier API)
│ └─► Submit Groth16 proof
│ └─► Poll until attestationId finalized
│
└─► Horizen Mainnet (Anchor.sol)
└─► anchorBatch(merkleRoot)
└─► Anchored event emitted (permanent on-chain record)
│
▼ UPDATE anchor_jobs (status='completed', result={txHash})
MySQL
Why a Single Worker?
The previous AWS Lambda approach required three separate services (batch creator, zkVerify submitter, Horizen anchor) connected by SQS queues. This added operational complexity and latency.
The Anchor Worker consolidates all three stages into a single stateful process:
| Before (AWS Lambda) | After (DO Anchor Worker) |
|---|---|
| 3 Lambda functions | 1 Node.js process |
| 3 SQS queues + 3 DLQs | No queues — DB polling |
| CloudWatch + Secrets Manager | DO App Platform env vars |
| Cold-start circuit downloads | In-memory circuit cache |
| ~$3.56/month (10K proofs) | ~$6/month (DO plan) |
The simplification reduces failure surface area, makes logs centralized, and eliminates SQS propagation delays.
Proof Lifecycle
Every proof follows this path through the Anchor Worker:
Engagement event → Go server captures view/click/conversion, creates HMAC-signed proof, inserts row into
proofstable withzkverify_status = 'pending'.Batch → Worker queries unbatched proofs from the
proofstable, organizes them into a Poseidon Merkle tree, computes the root, and creates aproof_batchesrow + ananchor_jobsrow withjob_type = 'zkverify_submit'.ZK proof generation → Worker claims the
zkverify_submitjob, downloads circuit artifacts (proof_verification_v2.0.0.wasm,*.zkey) from Cloudflare R2 if not already cached, and generates a Groth16 zk-SNARK over the Merkle tree.zkVerify submission → Worker calls the Kurier API with the Groth16 proof and waits for an
attestationId. The proof is verified on the zkVerify blockchain (~25 seconds, ~$0.05/batch). On success, creates ananchor_submitjob.On-chain anchoring → Worker claims the
anchor_submitjob and callsAnchor.sol.anchorBatch(merkleRoot)on Horizen mainnet. The contract emitsAnchored(merkleRoot)and stores nothing (gas-optimized, ~50k gas/anchor, ~$0.001).Status update → Worker marks anchor job as
status = 'completed'with thetxHashin the result field. Proofs in the batch are updated tozkverify_status = 'verified'.
Public Verification
Anyone can verify a proof by:
- Looking up the proof ID in the AdPriva Explorer.
- Tracing the Merkle root to the on-chain
Anchoredevent on Horizen mainnet. - Cross-referencing the zkVerify
attestationIdfor the zero-knowledge proof attestation.
The Anchor contract (Anchor.sol) is deployed on Horizen mainnet (Chain ID: 26514, RPC: https://horizen.calderachain.xyz/http).
Zero-Knowledge Proof Details
AdPriva uses Groth16 zk-SNARKs with a custom Circom circuit (proof_verification_v2.0.0):
- Circuit inputs: proof leaf hashes, Merkle path, Merkle root, timestamp
- Output: single bit — proof is valid and belongs to the committed Merkle root
- Proving key: stored in Cloudflare R2 (
circuits/v2.0.0/) - Verification: performed by zkVerify’s on-chain verifier
This means that even the Anchor Worker never sees raw user data — it only processes cryptographic hashes produced by the Go server.
Deployment
The Anchor Worker runs on DigitalOcean App Platform as a background worker (no inbound HTTP). Infrastructure is managed via OpenTofu in the infrastructure/ repository.
Key environment variables:
| Variable | Description |
|---|---|
DATABASE_URL | MySQL connection string (private VPC endpoint) |
WORKER_START_DATE | Process only proofs after this date (migration cutoff) |
KURIER_API_KEY | zkVerify Kurier API key |
HORIZEN_ANCHOR_SUBMITTER_KEY | Horizen wallet private key for anchoring |
HORIZEN_ANCHOR_CONTRACT_ADDRESS | Anchor.sol address on Horizen mainnet |
R2_PUBLIC_ENDPOINT | Cloudflare R2 endpoint for circuit artifacts |
CIRCUIT_VERSION | Circuit version to use (e.g. v2.0.0) |
CI/CD deployment is triggered automatically on push to the main branch of AdPriva/proofs via GitHub Actions (.github/workflows/deploy-anchor-worker.yml).