Search projects, hackathons, sponsors and tech

    ← All projects

    ProofGuard

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

    Demo

    About

    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. 2) 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. 3) 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.