ProofGuard

Privacy-first zk credential attestation on Citrea: batch proofs, instant verification.

ProofGuard

Created At

ETHGlobal New Delhi

Project Description

What it is (one-liner)

A privacy-preserving credential attestation and verification system: issuers batch-commit records to a Bitcoin-secured chain (Citrea) and anyone can verify an individual claim with a zero-knowledge proof—without exposing personal data.

The Problem

Credentials (degrees, certificates, employment letters) are easy to forge and hard to verify globally.

Verifiers (employers, platforms) either trust PDFs/emails or must contact the issuer directly—slow, manual, and privacy-leaking.

On-chain storage of PII is unacceptable; off-chain databases are centralized and reversible.

What ProofGuard Solves

Authenticity: The issuer proves “this record was part of our official batch” using zk-SNARKs.

Privacy: Verifiers get a binary result (Valid/Invalid) without seeing raw personal data.

Global Verifiability: Anyone can independently verify against an immutable on-chain commitment (Merkle root).

Cost Efficiency: Batch attestation compresses 100s/1000s of records into a single on-chain commitment.

Core Concepts (plain English)

Merkle Tree: Off-chain, we hash each credential into a leaf. Leaves form a Merkle tree; its root uniquely commits to the entire batch.

zk-SNARK (Groth16): A tiny proof that a specific leaf (the student’s credential) exists in the committed tree and satisfies the circuit’s rules—without revealing the underlying data.

On-chain Contracts:

ProofGuardRegistry: Stores Merkle tree state (via incremental insert). Emits current root after each batch/insert; exposes getters.

ProofGuardCoreVerifier: Stateless verifier that checks Groth16 proofs (generated off-chain) via Ethereum precompiles (pairing checks).

Citrea (EVM on Bitcoin): We post the root/updates to Citrea, leveraging Bitcoin security while using EVM tooling (Remix, MetaMask, ethers).

Stakeholders & Journeys

  1. Issuer (e.g., a University)

Onboarding: Connect SIS (student information system) to ProofGuard via API or simple upload.

Batch Attestation:

Normalize records (stable JSON) → encrypt each record’s payload (so on-chain storage is never raw PII).

Compute the leaf for each record (consistent hash inputs that match the circuit).

Insert leaves into the on-chain Merkle tree (or call a batch function), which emits a new root.

(Optional) Generate a proof template or pre-compute per-record proofs if the flow needs it.

Result: One or a few on-chain transactions represent a large set of credentials, time-stamped and immutable.

  1. Holder (Student/Graduate)

Gets a Claim Bundle: { registry, verifier, root, publicInputs[], proof }.

publicInputs[] typically includes the Merkle root + disclosed statement hashes.

proof is the Groth16 proof generated from the student’s encrypted record and the tree path.

Can share the claim as a QR code or short JSON.

  1. Verifier (Employer/Platform)

Opens the public dApp:

Pasts/scans the Claim Bundle.

dApp fetches getCurrentMerkleRoot() from ProofGuardRegistry.

Compares provided root vs on-chain root (or verifies against a specific historical root).

Calls ProofGuardCoreVerifier.verifyProof(a,b,c,input) via eth_call.

Sees Valid / Invalid. No PII is exposed at any point.

Repo Components (minimal logic approach)

Contracts

ProofGuardCoreVerifier.sol: Groth16 verifier (IC points hard-coded; generated from the circuit).

ProofGuardRegistry.sol: Ownable + Incremental Merkle Tree (zk-kit library). Functions to store encrypted records and insert leaves; exposes root and leaves.

Circuits

Define the exact leaf hash and public inputs layout (e.g., Poseidon hash of selective fields).

Set depth (e.g., 16) to match the on-chain registry’s TREE_DEPTH.

claimsN (or equivalent) must match your schema so inputs/constraints line up.

Off-chain Tooling

A small batch uploader (CSV/JSON → canonicalize → encrypt → leaf → on-chain insert).

A prover that, for a given holder, computes the Merkle path and generates the Groth16 proof.

A claim packer that exports the Claim Bundle JSON + QR.

Data Model & Privacy

On-chain: Only encrypted strings (ciphertexts), Merkle leaves, events, and roots.

Off-chain: The cleartext record lives with the holder/issuer; proofs and public inputs reveal only what is necessary.

No PII on-chain: All raw data is encrypted before touching the registry.

Selective Disclosure: Circuits can be designed to prove properties (e.g., “degree=BS, year≥2024”) without revealing the entire record.

API/UX Surface (example)

Issuer Admin UI

Upload CSV/JSON → map fields to schema → “Generate leaves” → “Attest batch”

Shows tx status, final root, and per-record status.

Student Portal

“Create Claim” → choose which attributes to disclose → download JSON/QR claim bundle.

Verifier dApp

Textarea / QR scanner → “Verify” → instant Valid/Invalid.

Trust & Threat Model

Trust: You trust the issuer’s policy (they control what is attested) but not their storage; proof + chain root prevents tampering.

Tamper Resistance: Once a root is on-chain, issuer cannot later modify past records without changing the root.

Privacy: Verifier learns only the validity of the claimed statement; no raw data leakage.

Key Management: Issuer/holder encryption keys must be handled securely; losing keys can make claims unverifiable or undecryptable.

Performance & Cost

Batching drastically reduces on-chain cost (one/few transactions per N records).

Groth16 verification gas is low and deterministic; proofs are tiny (a few hundred bytes).

Citrea: Native coin (cBTC) for gas; EVM RPC compatibility streamlines tooling.

Constraints & Assumptions

Circuit/Contract Parity: Merkle depth, leaf hash function, and public input ordering must match exactly across:

the circom circuit,

the generated verifier,

the off-chain leaf builder,

the on-chain registry.

Updates/Revocations:

Easiest pattern: append new leaf representing revocation or updated status and verify against latest root.

A dedicated revocation list circuit can be added later (roadmap).

Availability:

Encrypted data is on-chain (durable), but decryption depends on holder/issuer keys.

Off-chain re-generation of proofs is required when root changes (e.g., periodic batch inserts).

How it's Made

ChatGPT said:

Smart contracts in Solidity: ProofGuardRegistry (incremental Merkle tree via zk-kit) + ProofGuardCoreVerifier (Groth16 verifier with hard-coded VK IC points).

OpenZeppelin (Ownable, ReentrancyGuard) for access + safety; deployed on Citrea testnet (Bitcoin-secured EVM).

Circuits in circom; proofs generated with snarkjs; public inputs include Merkle root + selective claim hashes.

Off-chain Node/TypeScript batch uploader: canonicalizes JSON, encrypts payloads, computes Poseidon leaves, and submits inserts.

Encryption (ECIES/NaCl style): on-chain only stores ciphertext; PII never appears in clear.

Frontend dApp (Next.js/React + ethers.js): paste/scan a “claim bundle” (a,b,c + inputs + root), auto-reads on-chain root, calls verifyProof via eth_call.

Minimal gas by batching many records into one on-chain commitment (root), with per-record proofs verified client-side.

“Hacky but handy”: a single attestBatch wrapper (loop in-contract) to compress UX into one tx without changing semantics.

Tooling lint/format/test: pnpm, TypeScript, eslint/prettier, and a tiny prover CLI for local proof generation.

Partner value: Citrea gives Bitcoin-level security with EVM tooling, so Remix/MetaMask/ethers “just work” while anchoring to BTC.

background image mobile

Join the mailing list

Get the latest news and updates