|
| 1 | +# Verify ZK Email Proofs in Aztec Contracts |
| 2 | + |
| 3 | +Proves that an email was sent from a specific domain (`icloud.com`) using DKIM signature verification in a Noir circuit, then verifies that proof on-chain inside an Aztec private smart contract. |
| 4 | + |
| 5 | +Built to validate the [zkemail.nr](https://github.com/zkemail/zkemail.nr) library's compatibility with Aztec 4.2.0. |
| 6 | + |
| 7 | +## Overview |
| 8 | + |
| 9 | +This project implements: |
| 10 | + |
| 11 | +- **Noir Circuit** (`circuit/`): Verifies a 2048-bit RSA DKIM signature, checks the body hash, extracts the sender address, and asserts the sender domain is `icloud.com`. Returns three public outputs: two public key hashes (root of trust) and an email nullifier. |
| 12 | +- **Aztec Contract** (`contract/`): A private smart contract that verifies the Noir proof on-chain using `verify_honk_proof` and tracks a per-user verification count. |
| 13 | +- **Proof Generation** (`scripts/generate_data.ts`): Generates an UltraHonk proof from hardcoded test email data and verifies it off-chain before writing `data.json`. |
| 14 | +- **On-chain Verification** (`scripts/run_testnet.ts`): Deploys the contract and submits the proof for verification on the Aztec testnet. |
| 15 | + |
| 16 | +**Aztec Version**: `4.2.0-aztecnr-rc.2` (compatible with testnet `4.2.0-rc.1`) |
| 17 | + |
| 18 | +## Testnet Deployment |
| 19 | + |
| 20 | +Successfully deployed and verified on the Aztec testnet (`https://rpc.testnet.aztec-labs.com`) with real proofs enabled. |
| 21 | + |
| 22 | +| Step | Transaction Hash | |
| 23 | +|------|-----------------| |
| 24 | +| Account deployment | `0x190a55042b4a4bd063150c0b1d2a233c07e8b4b7decb3ba57e2527c646acd2be` | |
| 25 | +| Contract deployment | `0x258f5c271e67ce681cf7db8641984d78d6e7718268d6cb4b9897d8ac624709bc` | |
| 26 | +| Email proof verification | `0x2c053bad6f58bfcdea0b2bc4b918e0e31bb3d38aed740ac836e227cd5b7bca4e` | |
| 27 | + |
| 28 | +**Contract address**: `0x1bbf99d2acd54c9dc9ef58fd05892fec9d5916fa66949aead6877964879a142b` |
| 29 | + |
| 30 | +## How It Works |
| 31 | + |
| 32 | +``` |
| 33 | +Raw Email ──> [Noir Circuit] ──> UltraHonk Proof ──> [Aztec Contract] ──> On-chain Verification |
| 34 | + │ │ |
| 35 | + │ DKIM verify │ verify_honk_proof() |
| 36 | + │ Body hash check │ Increment counter |
| 37 | + │ Domain = icloud.com │ |
| 38 | + │ │ |
| 39 | + └──> 3 public outputs: └──> VK hash stored in |
| 40 | + pubkey_hash[0] PublicImmutable storage |
| 41 | + pubkey_hash[1] |
| 42 | + email_nullifier |
| 43 | +``` |
| 44 | + |
| 45 | +1. The **inner circuit** (`circuit/src/main.nr`) uses the [zkemail.nr](https://github.com/zkemail/zkemail.nr) library to: |
| 46 | + - Verify the DKIM RSA signature over the email header |
| 47 | + - Extract and verify the body hash from the DKIM-Signature header against a SHA256 hash of the body |
| 48 | + - Extract the sender email address from the `From:` header |
| 49 | + - Assert the sender domain is `icloud.com` |
| 50 | + - Output the public key hash (Poseidon), redc parameter hash, and email nullifier (Pedersen) |
| 51 | + |
| 52 | +2. An **UltraHonk proof** is generated off-chain using Barretenberg (`@aztec/bb.js`), then verified off-chain to confirm validity. |
| 53 | + |
| 54 | +3. The **Aztec contract** (`contract/src/main.nr`) calls `verify_honk_proof(vk, proof, public_inputs, vk_hash)` inside a private function. The VK hash is stored at deployment to bind the contract to the specific circuit. On successful verification, a public counter is incremented. |
| 55 | + |
| 56 | +4. With `proverEnabled: true`, the PXE generates real ClientIVC proofs that enforce the `verify_honk_proof` constraint during private kernel execution — the inner proof is cryptographically verified, not skipped. |
| 57 | + |
| 58 | +## Prerequisites |
| 59 | + |
| 60 | +- [Node.js](https://nodejs.org/) (v22+) and [Yarn](https://yarnpkg.com/) |
| 61 | +- [Aztec CLI](https://docs.aztec.network/getting_started/quickstart) (version 4.2.0-aztecnr-rc.2) |
| 62 | + |
| 63 | +```bash |
| 64 | +# Install Aztec CLI |
| 65 | +bash -i <(curl -s https://install.aztec.network) |
| 66 | +aztec-up 4.2.0-aztecnr-rc.2 |
| 67 | + |
| 68 | +# Verify nargo (bundled with Aztec CLI) |
| 69 | +nargo --version # 1.0.0-beta.18 |
| 70 | +``` |
| 71 | + |
| 72 | +## Project Structure |
| 73 | + |
| 74 | +``` |
| 75 | +. |
| 76 | +├── circuit/ # Inner Noir circuit (vanilla bin, not Aztec contract) |
| 77 | +│ ├── src/main.nr # DKIM verify + domain check + public outputs |
| 78 | +│ └── Nargo.toml # Depends on zkemail.nr and sha256 |
| 79 | +├── contract/ # Aztec smart contract |
| 80 | +│ ├── src/main.nr # verify_honk_proof + counter storage |
| 81 | +│ ├── artifacts/ # Generated TypeScript bindings |
| 82 | +│ └── Nargo.toml # Depends on aztec-nr and bb_proof_verification |
| 83 | +├── scripts/ |
| 84 | +│ ├── generate_data.ts # Build circuit inputs, generate + verify proof |
| 85 | +│ ├── run_verification.ts # Deploy + verify on local network |
| 86 | +│ ├── run_testnet.ts # Deploy + verify on Aztec testnet |
| 87 | +│ └── sponsored_fpc.ts # SponsoredFPC fee payment utility |
| 88 | +├── tests/ |
| 89 | +│ └── zkemail_verification.test.ts # Vitest integration tests |
| 90 | +├── data.json # Generated proof data (created by yarn data) |
| 91 | +├── package.json |
| 92 | +├── tsconfig.json |
| 93 | +└── vitest.config.ts |
| 94 | +``` |
| 95 | + |
| 96 | +## Quick Start |
| 97 | + |
| 98 | +```bash |
| 99 | +# Install dependencies |
| 100 | +yarn install |
| 101 | + |
| 102 | +# Compile the inner Noir circuit |
| 103 | +cd circuit && nargo compile && cd .. |
| 104 | + |
| 105 | +# Compile the Aztec contract and generate TypeScript bindings |
| 106 | +yarn ccc |
| 107 | + |
| 108 | +# Generate proof data (generates + verifies proof, writes data.json) |
| 109 | +yarn data |
| 110 | +``` |
| 111 | + |
| 112 | +### Deploy to Testnet |
| 113 | + |
| 114 | +```bash |
| 115 | +yarn testnet |
| 116 | +``` |
| 117 | + |
| 118 | +### Deploy to Local Network |
| 119 | + |
| 120 | +```bash |
| 121 | +# Start local network in a separate terminal |
| 122 | +aztec start --local-network |
| 123 | + |
| 124 | +# Deploy and verify |
| 125 | +yarn verify |
| 126 | + |
| 127 | +# Or run the full test suite |
| 128 | +yarn test |
| 129 | +``` |
| 130 | + |
| 131 | +## Scripts |
| 132 | + |
| 133 | +| Command | Description | |
| 134 | +|---------|-------------| |
| 135 | +| `yarn ccc` | Compile Aztec contract + generate TypeScript bindings | |
| 136 | +| `yarn data` | Generate UltraHonk proof from test email data, verify off-chain, write `data.json` | |
| 137 | +| `yarn testnet` | Deploy contract and verify proof on the Aztec testnet | |
| 138 | +| `yarn verify` | Deploy contract and verify proof on local network | |
| 139 | +| `yarn test` | Run Vitest integration tests against local network | |
| 140 | + |
| 141 | +## Circuit Details |
| 142 | + |
| 143 | +The inner circuit verifies a DKIM-signed email from `icloud.com` using ~222K constraints: |
| 144 | + |
| 145 | +| Component | Constraints | Description | |
| 146 | +|-----------|------------|-------------| |
| 147 | +| DKIM signature verification | ~86,500 | RSA-2048 PKCS#1 v1.5 over SHA256 header hash | |
| 148 | +| Body hash (SHA256) | ~114,000 | SHA256 over email body, compared to DKIM `bh=` field | |
| 149 | +| Address extraction | ~16,000 | Extract and validate `From:` email address | |
| 150 | +| Domain check | ~100 | Assert domain bytes match `icloud.com` | |
| 151 | +| Key + nullifier hashing | ~10,200 | Poseidon hash of pubkey, Pedersen hash of signature | |
| 152 | + |
| 153 | +**Public outputs** (3 fields): |
| 154 | +- `pubkey_hash[0]` — Poseidon hash of RSA modulus (root of trust) |
| 155 | +- `pubkey_hash[1]` — Poseidon hash of RSA redc parameter |
| 156 | +- `email_nullifier` — Pedersen hash of DKIM signature (prevents double-use) |
| 157 | + |
| 158 | +## Contract Details |
| 159 | + |
| 160 | +The `ZKEmailVerifier` contract stores a verification key hash at deployment and exposes: |
| 161 | + |
| 162 | +- `verify_email(owner, vk, proof, public_inputs)` — **private function** that verifies the UltraHonk proof and enqueues a public state update |
| 163 | +- `get_verification_count(owner)` — **public view** that returns how many emails have been verified for an address |
| 164 | + |
| 165 | +The contract uses the hybrid private/public execution pattern: proof verification happens privately (the email content is never revealed on-chain), while the verification count is updated publicly. |
| 166 | + |
| 167 | +## Troubleshooting |
| 168 | + |
| 169 | +**"Cannot find module '../contract/artifacts/ZKEmailVerifier'"** |
| 170 | +Run `yarn ccc` to compile the contract and generate TypeScript bindings. |
| 171 | + |
| 172 | +**"Cannot find module '../data.json'"** |
| 173 | +Run `yarn data` to generate the proof data. |
| 174 | + |
| 175 | +**"Failed to connect" on testnet** |
| 176 | +Check testnet status: `curl https://rpc.testnet.aztec-labs.com/status` |
| 177 | + |
| 178 | +**Proof verification fails off-chain** |
| 179 | +Ensure the circuit was compiled with `nargo compile` after any changes, then re-run `yarn data`. |
| 180 | + |
| 181 | +## Dependencies |
| 182 | + |
| 183 | +- [zkemail.nr](https://github.com/zkemail/zkemail.nr) — Noir library for DKIM email verification |
| 184 | +- [aztec-nr](https://github.com/AztecProtocol/aztec-nr/) v4.2.0-aztecnr-rc.2 — Aztec smart contract framework |
| 185 | +- [bb_proof_verification](https://github.com/AztecProtocol/aztec-packages/) — Barretenberg proof verification for Aztec contracts |
| 186 | +- [@aztec/bb.js](https://www.npmjs.com/package/@aztec/bb.js) 4.2.0-aztecnr-rc.2 — UltraHonk proving backend |
0 commit comments