Anonymous Whistleblower Inbox with Zero-Knowledge Proofs

By depa panjie purnama on Aug 23, 2025. Originally published on DEV.to.
Anonymous Whistleblower Inbox with Zero-Knowledge Proofs

This is a submission for the Midnight Network "Privacy First" Challenge - Protect That Data prompt

What I Built

Midnight Whistleblower is a privacy-first DApp that reimagines secure reporting. Imagine needing to report something sensitive (corruption, safety violations, or misconduct) but fearing retaliation. Traditional systems force you to choose: stay silent or risk exposure. Midnight Whistleblower breaks this dilemma.

The Magic โœจ

Our platform combines three powerful privacy technologies:

  1. Zero-Knowledge Proofs: Prove you're authorized to report without revealing who you are
  2. Rate-Limit Nullifiers (RLN): Prevent spam while maintaining complete anonymity
  3. End-to-End Encryption: Only the designated moderator can decrypt reports, not even our servers can read them

The Journey Through Every Page ๐Ÿ—บ๏ธ

๐Ÿ  Home Page: Your gateway to anonymous truth
home description

๐Ÿ“ Submit Report (Reporter Page): Where courage meets cryptography
submit description

๐Ÿ” Moderator Dashboard: The trusted guardian's toolkit
moderator description

๐Ÿ“Š Metrics Page: Understanding the flow of truth
metrics description

๐Ÿ“ˆ Public Stats: Transparency without compromise
stats description

โš™๏ธ Settings: Your control center
settings description

๐Ÿ›ก๏ธ Privacy Page: Our promises, explained
privacy description

Demo

Architecture Overview

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    USER BROWSER                         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚   React App + MidnightJS + Web Crypto API       โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ”‚                           โ”‚                             โ”‚
โ”‚        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”          โ”‚
โ”‚        โ–ผ                  โ–ผ                  โ–ผ          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”‚
โ”‚  โ”‚ZK Proofs โ”‚      โ”‚Encryptionโ”‚      โ”‚   RLN    โ”‚       โ”‚
โ”‚  โ”‚(Groth16) โ”‚      โ”‚(AES-GCM) โ”‚      โ”‚Nullifiersโ”‚       โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜       โ”‚
โ”‚                           โ”‚                             โ”‚
โ”‚                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”                      โ”‚
โ”‚                    โ”‚  IndexedDB  โ”‚                      โ”‚
โ”‚                    โ”‚(Local First)โ”‚                      โ”‚
โ”‚                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                            โ”‚ (Optional)
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚  Vercel KV     โ”‚
                    โ”‚(Encrypted Only)โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Enter fullscreen mode Exit fullscreen mode

How I Used Midnight's Technology

๐Ÿ”ฎ The Compact Circuit (circuits/membership_rln.compact)

// Simplified view of our RLN circuit
circuit MembershipRLN {
    // Public inputs
    merkleRoot: Field
    epoch: Field
    nullifier: Field
    signalHash: Field

    // Private inputs (never revealed!)
    identitySecret: Field
    merklePath: Field[20]

    // The magic: prove membership without revealing identity
    assert(verifyMerkleProof(identitySecret, merklePath) == merkleRoot)
    assert(hash(identitySecret, epoch) == nullifier)
}
Enter fullscreen mode Exit fullscreen mode

๐ŸŽฏ MidnightJS Integration

Our src/lib/midnightjs.ts orchestrates the privacy symphony:

// Load compiled artifacts (proving key, verification key, WASM)
const artifacts = await ArtifactLoader.loadArtifacts('membership_rln');

// Generate proof (happens in reporter's browser)
const proof = await midnightJS.generateProof('membership_rln', {
    merkleRoot: organizationRoot,
    epoch: currentEpoch,
    identitySecret: userSecret,  // Never leaves the browser!
    merklePath: membershipPath
});

// Verify proof (happens in moderator's browser)
const isValid = await midnightJS.verifyProof('membership_rln', proof);
Enter fullscreen mode Exit fullscreen mode

๐Ÿš€ The Development Experience

We built a two-speed system:

This means you can develop the UX at full speed, then switch to real proofs for testing.

Data Protection as a Core Feature

๐Ÿ” The Privacy Stack

Layer 1: Anonymous Identity

Layer 2: Anti-Spam Without Tracking

Layer 3: End-to-End Encryption

// In the reporter's browser
const encrypted = await encryptMessage(reportContent, moderatorPublicKey);
// Now it's gibberish to everyone except the moderator

// In the moderator's browser (and nowhere else!)
const decrypted = await decryptMessage(encrypted, moderatorPrivateKey);
// The report is revealed, but the identity remains hidden
Enter fullscreen mode Exit fullscreen mode

Layer 4: Local-First Architecture

๐Ÿ›ก๏ธ What Makes This Different

Traditional whistleblower systems:

Midnight Whistleblower:

Set Up Instructions / Tutorial

๐ŸŽฏ Quick Start (5 minutes)

Prerequisites: Node.js 18+, Git

# 1. Clone the repository
git clone https://github.com/depapp/midnight-whistleblower.git
cd midnight-whistleblower

# 2. Install dependencies
npm install

# 3. Compile the Midnight circuits (this is the magic!)
npm run compile-circuits

# 4. Start the development server
npm run dev

# 5. Open http://localhost:5173
Enter fullscreen mode Exit fullscreen mode

๐ŸŽญ Playing Both Roles (Tutorial Mode)

Act 1: Become the Moderator

  1. Navigate to /moderator
  2. Click "Generate New Key Pair"
  3. Copy your public key (this is what reporters need)
  4. Optional: Export your keys with a password for backup

Act 2: Submit a Report

  1. Open a new incognito window (for the full experience)
  2. Go to /submit
  3. Paste the moderator's public key
  4. Write your report: "The coffee machine is plotting against us"
  5. Attach evidence (optional): photo_of_suspicious_coffee_machine.jpg
  6. Click Submit and watch the magic:
    • Console shows: [MidnightJS] Generating proof...
    • Progress bar indicates encryption
    • You get a transaction hash (mock)

Act 3: Moderate the Report

  1. Return to the moderator dashboard
  2. See your encrypted report (looks like gibberish)
  3. Click to decrypt (proof is verified first)
  4. Read the shocking coffee machine revelation
  5. Mark as "Reviewed" and watch the stats update

๐Ÿš€ Production Deployment (10 minutes)

Deploy to Vercel with Cloud Sync:

# 1. Install Vercel CLI
npm i -g vercel

# 2. Deploy (follow prompts)
vercel

# 3. Add KV Storage in Vercel Dashboard
# Project Settings โ†’ Storage โ†’ Create Database โ†’ KV

# 4. Environment variables are auto-configured!
Enter fullscreen mode Exit fullscreen mode

In your deployed app:

  1. Go to Settings
  2. Toggle "Cloud Sync" ON
  3. That's it! Multi-device sync with zero configuration

๐Ÿ”ง Advanced Configuration

Development Modes:

# Fast iteration mode (stub proofs)
VITE_USE_REAL_MIDNIGHT=false npm run dev

# Disable mock contract
VITE_USE_CONTRACT=false npm run dev

# Custom sync endpoint (for self-hosting)
VITE_SYNC_BASE_URL=https://your-api.com npm run dev
Enter fullscreen mode Exit fullscreen mode

Circuit Customization:

// Modify circuits/membership_rln.compact
// Recompile with: npm run compile-circuits
// New artifacts are auto-loaded!
Enter fullscreen mode Exit fullscreen mode

๐Ÿ› Troubleshooting Guide

Problem Solution
"Artifacts not found" Run npm run compile-circuits
"Proof generation failed" Check browser console for [MidnightJS] logs
"Can't decrypt report" Verify you're using the correct private key
"Stats not updating" Wait 500ms for cloud sync, then refresh
"CORS errors in dev" Use VITE_SYNC_BASE_URL for cross-origin

๐Ÿ“š Learning Resources

Understand the Code:

The Vision ๐ŸŒŸ

Imagine a world where:


Midnight Whistleblower isn't just a demo, it's a blueprint for privacy-respecting applications that can change the world.