Skip to content

AffixIO/ZKCookies-BETA-2025

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

6 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Zero Knowledge Proof Cookie Banner

Production-ready Zero-Knowledge Proof Cookie Consent System with Groth16 on BLS12-381.

🎯 Overview

This system implements a privacy-preserving cookie banner that uses zero-knowledge proofs to verify user consent without storing user preferences on the server. The system is designed to be GDPR-compliant and privacy-first.

πŸ” Cryptography Stack

  • Proof System: Groth16 on BLS12-381 β†’ 192-byte proofs
  • Hash Function: Poseidon (never SHA-256 or Keccak)
  • Identity: Semaphore-style persistent identity with 32-byte identitySecret in localStorage
  • Commitment: Poseidon(oldConsent, timestamp, identitySecret)
  • Nullifier: Poseidon(identitySecret, domainSalt) β†’ prevents double-spending per domain
  • State: Sparse Merkle tree (depth 20) using Poseidon for consent state
  • Enforcement:
    • Monotonic consent: newConsent β‰₯ oldConsent (8-bit bitfield)
    • Max consent age: 2 years enforced in-circuit via currentTime public input

πŸ“¦ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Client (Browser)                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β€’ 32-byte identitySecret (localStorage)                    β”‚
β”‚  β€’ Poseidon hash computation                                β”‚
β”‚  β€’ Groth16 proof generation (snarkjs WASM)                  β”‚
β”‚  β€’ Banner UI (Vite + TypeScript, < 50 KB gzipped)          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
                        β”‚ 192-byte proof + 5 public signals
                        β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              Cloudflare Worker / Node.js Server              β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β€’ Groth16 proof verification                               β”‚
β”‚  β€’ Poseidon Merkle tree management                          β”‚
β”‚  β€’ Nullifier tracking (prevents replay attacks)             β”‚
β”‚  β€’ Returns 200 OK β†’ client hides banner forever             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸš€ Quick Start

Prerequisites

  • Node.js 18+
  • npm or yarn
  • circom compiler (install via npm install -g circom)

Installation

# Clone or navigate to the zkcookies directory
cd zkcookies

# Install dependencies
npm install

# Run setup (compiles circuit, runs phase-2 ceremony)
npm run setup

# Start development server (in one terminal)
npm run dev

# Start verification server (in another terminal)
npm run dev:server

Open http://localhost:5173 in your browser.

Note: The setup process may take several minutes as it compiles the circuit and runs the trusted setup ceremony.

πŸ“ Project Structure

zkcookies/
β”œβ”€β”€ circuits/
β”‚   └── consent.circom          # Circom circuit definition
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ zk.ts                   # ZK proof generation logic
β”‚   β”œβ”€β”€ banner.ts                # Banner UI and interaction
β”‚   └── main.ts                 # Entry point
β”œβ”€β”€ worker/
β”‚   β”œβ”€β”€ verify.ts                # Cloudflare Worker verifier
β”‚   └── wrangler.toml            # Cloudflare Worker config
β”œβ”€β”€ scripts/
β”‚   └── setup.js                 # Setup script (circuit compilation)
β”œβ”€β”€ build/                       # Generated files (circuit, keys)
β”œβ”€β”€ public/                      # Static assets (WASM files)
β”œβ”€β”€ index.html                   # Demo page
β”œβ”€β”€ package.json
β”œβ”€β”€ vite.config.ts
└── README.md

πŸ”§ Setup Process

The npm run setup command:

  1. Compiles the Circom circuit β†’ generates R1CS, WASM, and symbol files
  2. Runs Phase 1 ceremony β†’ generates powers of tau (trusted setup)
  3. Runs Phase 2 ceremony β†’ circuit-specific setup
  4. Exports verification key β†’ for server-side verification
  5. Copies WASM files β†’ to public directory for client use

πŸ’» Usage

Client-Side

import { CookieBanner } from './banner';

const banner = new CookieBanner({
  apiEndpoint: 'https://your-api.com/verify',
  domainSalt: BigInt('0x...'), // Domain-specific salt
  onAccept: () => {
    console.log('Consent accepted!');
  },
  onReject: () => {
    console.log('Consent rejected.');
  },
});

// Show banner if needed
if (banner.shouldShowBanner()) {
  banner.show();
}

Server-Side (Cloudflare Worker)

Deploy the worker:

cd worker
wrangler deploy

Set the verification key as an environment variable:

wrangler secret put VERIFICATION_KEY
# Paste the contents of build/keys/verification_key.json

πŸ”’ Security Properties

  1. Privacy: User preferences never stored on server (only Merkle commitments)
  2. Unlinkability: Each proof uses a nullifier to prevent tracking
  3. Double-spend prevention: Nullifiers prevent replay attacks
  4. Monotonic consent: Consent can only increase, never decrease
  5. Expiry enforcement: 2-year max consent age enforced in-circuit
  6. No wallet required: Works in any browser without extensions

πŸ“Š Public Signals (5 total)

  1. currentTime - Unix timestamp (public)
  2. domainSalt - Domain-specific salt (public)
  3. newConsentCommitment - Poseidon(newConsent, timestamp, identitySecret) (public)
  4. nullifier - Poseidon(identitySecret, domainSalt) (public)
  5. root - Merkle root (public)

🎨 Browser Compatibility

  • βœ… Safari
  • βœ… Chrome
  • βœ… Firefox
  • βœ… Tor Browser
  • ❌ No WebGPU required
  • ❌ No experimental flags required
  • ❌ No browser extensions required

πŸ“ License

MIT License - see LICENSE file for details.

πŸ”— References

⚠️ Production Notes

  1. BLS12-381 vs BN254: The current setup uses BN254 (snarkjs limitation). For production BLS12-381, use a different toolchain (e.g., arkworks, bellman-bn254 with BLS12-381 support).

  2. Trusted Setup: The setup script uses a single-contributor ceremony for MVP. In production, use a multi-party trusted setup ceremony.

  3. Merkle Tree Storage: The worker uses in-memory storage. In production, use Cloudflare KV or Durable Objects for persistent tree state.

  4. Poseidon Implementation: Ensure client and server use the same Poseidon implementation (from circomlib).

πŸ› Troubleshooting

Circuit compilation fails:

  • Ensure circom is installed: npm install -g circom
  • Check that circomlib is installed: npm install

Proof generation fails:

  • Ensure WASM and zkey files are in the public directory
  • Check browser console for errors
  • Verify the circuit was compiled successfully

Server verification fails:

  • Ensure verification key is correctly set in environment
  • Check that proof format matches expected structure
  • Verify nullifier hasn't been used before