Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions docs/redacted/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: Overview | Redacted
sidebar_label: Overview
description: Redacted is a zero-knowledge email verification tool that lets users prove an email is authentic while selectively hiding sensitive content using ZK proofs.
keywords: [redacted, zk-email, email masking, zero-knowledge proofs, DKIM verification, email privacy, selective disclosure, Noir circuits]
---

import DocCardList from '@theme/DocCardList';

# Overview

Redacted is a zero-knowledge email verification tool. Users upload an email, selectively mask sensitive fields, and generate a cryptographic proof that the unmasked parts are authentic. Anyone can verify the proof without seeing the original email.

**Live app:** [redacted.zk.email](https://redacted.zk.email)

## How it works

1. User uploads a `.eml` file in the browser
2. The app parses the email and verifies the DKIM signature
3. User selects which fields to reveal and which to mask
4. A ZK proof is generated entirely client-side (Noir + UltraHonk)
5. The proof is uploaded — the original email never leaves the device
6. Anyone with the link can verify the proof and see only the revealed content

## Key properties

- **Client-side proving** — the email never leaves the browser
- **No email storage** — only the proof and masked output are persisted
- **Cryptographic masking** — masked content is mathematically removed, not just hidden
- **DKIM-based** — leverages existing [email authentication infrastructure](/architecture/dkim-verification)

## How Redacted relates to the ZK Email ecosystem

Redacted is a standalone application built on top of core ZK Email libraries:

- **[`@zk-email/helpers`](/zk-email-verifier/packages/zk-email-helpers)** — used for DKIM signature verification and generating circuit inputs from raw emails
- **[`@zk-email/zkemail-nr`](https://github.com/zkemail/zkemail.nr)** — the Noir library that provides RSA/DKIM verification, body hash extraction, and masking primitives inside the circuit
- Uses the same [DKIM verification](/architecture/dkim-verification) and [ZK proof](/architecture/zk-proofs) concepts as the rest of the ecosystem

Unlike the [Blueprint SDK](/zk-email-sdk/README), which uses Circom circuits compiled server-side, Redacted uses **Noir circuits** with the **UltraHonk** proving system. This enables the entire proof to be generated client-side in the browser via WASM — no remote prover needed.

## Documentation

<DocCardList />
118 changes: 118 additions & 0 deletions docs/redacted/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
title: Architecture | Redacted
sidebar_label: Architecture
description: System architecture for Redacted — component overview, data flow, and key design decisions for the ZK email masking tool.
keywords: [redacted architecture, system design, client-side proving, GCS storage, email verification]
---

# Architecture

## System Overview

```text
┌──────────────────────────────────────────────────────────────────┐
│ Browser (React 19 + Vite) │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌─────────────────────────────┐ │
│ │ Email │ │ Interactive │ │ ZK Proving Layer │ │
│ │ Upload & │→ │ Masking UI │→ │ │ │
│ │ Parsing │ │ (per-char) │ │ Noir circuit (WASM) │ │
│ └──────────┘ └──────────────┘ │ + Barretenberg UltraHonk │ │
│ ↑ │ → proof + public inputs │ │
│ postal-mime └──────────────┬──────────────┘ │
│ + @zk-email/helpers (DKIM) │ │
└─────────────────────────────────────────────────┼────────────────┘
│
Upload proof via signed URL
│
▼
┌──────────────────────────────────┐
│ Express Backend (Node.js) │
│ │
│ POST /api/get-proof-upload-url │
│ GET /api/get-data/:uuid │
│ POST /api/generate-uuid │
│ │
│ Serves static frontend (prod) │
└──────────────┬───────────────────┘
│
▼
┌──────────────────────────────────┐
│ Google Cloud Storage │
│ │
│ eml/{uuid}/proof.json │
│ eml/{uuid}/metadata.json │
│ │
│ (original email NEVER stored) │
└──────────────────────────────────┘
```

## Key Design Decisions

**Client-side proving.** The entire ZK proof is generated in the browser. The original email never leaves the user's device. This is the core privacy guarantee.

**No email storage.** The backend only stores proof outputs (masked bytes + cryptographic proof) and mask metadata. The original `.eml` file exists only in browser memory during the session.

**Cross-origin isolation.** The Express server sets `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp` headers. This enables `SharedArrayBuffer`, which Barretenberg uses for multi-threaded proof generation via Web Workers.

**Circuit variants.** Four pre-compiled Noir circuits cover the 2x2 matrix of RSA key sizes (1024/2048-bit) and email body sizes (4KB/8.4KB). The app auto-selects the smallest circuit that fits. See the [ZK Proving System](./zk-proving) page for details on each variant.

## File Structure

```text
src/
├── lib.ts # Core: proof generation, verification, circuit loading
├── App.tsx # Main app state, proof flow orchestration
├── circuit-configs.json # Circuit variant definitions
├── circuit/
│ ├── src/main.nr # Noir circuit source
│ ├── Nargo.toml # Noir project config + dependencies
│ └── target/ # Pre-compiled circuit JSON (4 variants)
│ ├── email_mask_1024_small.json
│ ├── email_mask_1024_mid.json
│ ├── email_mask_2048_small.json
│ └── email_mask_2048_mid.json
├── utils/
│ └── emlParser.ts # Email parsing, DKIM, field range extraction
├── components/
│ ├── EmailCard.tsx # Email display with masking UI
│ ├── EmailField.tsx # Individual field (From, To, etc.) with mask toggle
│ ├── ActionBar.tsx # Bottom bar: generate proof, undo/redo
│ ├── UploadModal.tsx # Drag-and-drop .eml upload
│ ├── MaskedText.tsx # Renders masked content as black blocks
│ └── ...
├── pages/
│ ├── Home.tsx # Landing page
│ └── VerifyPage.tsx # Proof verification page
server/
└── index.js # Express API + GCS integration
```

## Data Flow

### Proof Generation

```text
1. User drops .eml file
2. postal-mime parses email → {from, to, subject, body, raw}
3. @zk-email/helpers verifyDKIMSignature → DKIMResult {modulusLength, signature, ...}
4. User masks fields → headerMask[], bodyMask[] (1=reveal, 0=hide)
5. Select circuit by keyBits + bodyLength
6. generateEmailVerifierInputsFromDKIMResult(dkimResult, {masks, lengths})
7. Noir witness execution → witness
8. UltraHonkBackend.generateProof(witness) → ProofData {publicInputs, proof}
9. Upload proof.json + metadata.json to GCS via signed URL
10. Redirect to /verify?id={uuid}
```

### Proof Verification

```text
1. Verifier opens /verify?id={uuid}
2. GET /api/get-data/{uuid} → {proof, headerMask, bodyMask}
3. Extract masked header/body from publicInputs
4. Display masked email (null bytes → black blocks)
5. User clicks "Verify Proof"
6. UltraHonkBackend.verifyProof(proofData) → boolean
7. Show green (valid) or red (invalid) banner
```
169 changes: 169 additions & 0 deletions docs/redacted/backend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
title: Backend & Storage | Redacted
sidebar_label: Backend & Storage
description: Express.js backend API and Google Cloud Storage integration for Redacted — proof upload, retrieval, and storage architecture.
keywords: [Express API, Google Cloud Storage, proof storage, signed URLs, CORS, cross-origin isolation]
---

# Backend & Storage

## Overview

The backend is a lightweight Express.js server that handles proof storage and retrieval via Google Cloud Storage. It intentionally does **not** handle proof generation or email processing — those happen entirely in the browser.

**Source:** `server/index.js`

## API Endpoints

### `POST /api/generate-uuid`

Generate a random UUID for a new proof.

**Response:**
```json
{ "uuid": "6c4d4f5f-859f-4221-8ba4-53055ed006f3" }
```

---

### `POST /api/get-proof-upload-url`

Get a signed URL for the client to upload proof data directly to GCS.

**Request:**
```json
{
"uuid": "6c4d4f5f-...",
"headerMask": [1, 1, 0, 0],
"bodyMask": [1, 1, 1, 0]
}
```

**Response:**
```json
{
"uploadUrl": "https://storage.googleapis.com/bucket/eml/uuid/proof.json?X-Goog-Signature=...",
"publicUrl": "https://storage.googleapis.com/bucket/eml/uuid/proof.json",
"filename": "eml/uuid/proof.json",
"uuid": "6c4d4f5f-..."
}
```

**What happens:**
1. Generates a signed PUT URL (15-minute expiry) for `eml/{uuid}/proof.json`
2. Stores `eml/{uuid}/metadata.json` with mask arrays + timestamp
3. Client uploads proof directly to GCS using the signed URL (no proxy through backend)

---

### `GET /api/get-data/:uuid`

Retrieve proof and metadata for verification.

**Response:**
```json
{
"proof": {
"publicInputs": ["0x1a2b...", "0x3c4d..."],
"proof": [12, 34, 56]
},
"headerMask": [1, 1, 0, 0],
"bodyMask": [1, 1, 1, 0],
"createdAt": "2025-12-18T13:22:19.000Z"
}
```

**Data normalization:** The endpoint normalizes proof data on retrieval:
- `publicInputs` elements are ensured to be hex strings (converts arrays if stored incorrectly)
- `proof` elements are ensured to be numbers 0-255 (converts string representations)

---

### `GET /health`

```json
{ "status": "ok" }
```

## GCS Storage Structure

```text
bucket/
└── eml/
└── {uuid}/
├── proof.json # ProofData: {publicInputs: string[], proof: number[]}
└── metadata.json # {headerMask: number[], bodyMask: number[], createdAt: string}
```

**No original email is ever stored.** The proof's public inputs contain the masked header and body — sufficient for display and verification.

## GCS Configuration

The server supports two credential modes:

**Option 1 — JSON string** (recommended for deployment):
```bash
GCS_CREDENTIALS='{"type":"service_account","project_id":"...","private_key":"..."}'
```

**Option 2 — Key file path:**
```bash
GCS_KEY_FILE=./path/to/service-account.json
```

Required environment variables:
```bash
GCS_PROJECT_ID=your-project-id
GCS_BUCKET_NAME=your-bucket-name
```

### Bucket CORS Setup

The bucket needs CORS configured for client-side uploads. Use `server/setup-cors.js`:

```bash
npm run setup-cors
```

This sets:
- Allowed methods: `PUT`, `GET`, `HEAD`
- Allowed headers: `Content-Type`, `Content-Length`
- Allowed origins: configured from `ALLOWED_ORIGINS` env var

## Cross-Origin Isolation

The server sets two critical headers on all responses:

```text
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
```

These enable `SharedArrayBuffer` in the browser, which Barretenberg's `UltraHonkBackend` uses for multi-threaded proof generation via Web Workers. Without these headers, proving falls back to single-threaded mode.

## Static File Serving

In production (`NODE_ENV=production`), the server also serves the built Vite frontend:

```text
GET / → dist/index.html
GET /generate-proof → dist/index.html (SPA fallback)
GET /verify → dist/index.html (SPA fallback)
GET /assets/* → dist/assets/* (static files)
```

API routes (`/api/*`) are registered first and take priority.

## Proof Lifecycle

```text
1. Client generates proof in browser
2. Client calls POST /api/get-proof-upload-url → gets signed URL + uuid
3. Client uploads proof.json directly to GCS via signed PUT URL
4. Client stores proof in localStorage as fallback
5. Client redirects to /verify?id={uuid}
6. Verifier's browser calls GET /api/get-data/{uuid}
7. Verifier's browser verifies proof locally (no server computation)
```

The server is stateless — it only brokers storage. All cryptographic operations happen client-side.
Loading
Loading