DEV Community

Cover image for Privacy‑First Job Applications with Midnight Network
depa panjie purnama
depa panjie purnama

Posted on

Privacy‑First Job Applications with Midnight Network

Midnight Network Challenge: Protect That Data

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

home

Click the Disclosure Triangle icon / Collapsible Indicator (►) below to see the details

What I Built
I built ZK Job Board, a privacy-first job marketplace where applicants prove they meet job requirements without revealing their personal attributes. Using Midnight's Compact language and MidnightJS SDK, applicants generate zero-knowledge proofs that validate skills, experience, and region without disclosing exact values. Employers get trustworthy eligibility checks, while applicants keep their data private.

Core Features:

  • Anonymous Eligibility Proofs: Prove subset skills, minimum experience, and region membership without revealing specifics
  • On-Chain Verification: Verifier contract integration with mock fallback for development
  • Anti-Spam Protection: Per-job nullifiers prevent duplicate applications while preserving cross-job unlinkability
  • Privacy-First UI: Clear indicators showing what stays private vs. what gets proven

Demo

GitHub Repository: https://github.com/depapp/zk-job-board

Page-by-Page Walkthrough

This section explains every page, its function, and how it supports privacy by design.

1. Home Page (/)

home
Purpose: Landing page introducing the concept of anonymous job applications
Key Actions: Navigate to "Browse Jobs" or "Post a Job"

Privacy Signals: Copy and visuals emphasize that proofs validate eligibility without exposing personal data

Midnight Integration: Educational introduction; no proof generation on this page

2. Browse Jobs (/jobs)

browse
Purpose: List all open positions with summarized requirements

Key Actions: Select a job to view full details

Privacy Signals: Requirements are public; applicant data remains private

Midnight Integration: Displays policy summaries that become public inputs (policyHash) in the circuit

3. Job Detail (/job/:id)

job details
Purpose: Show complete job policy including required skills, minimum experience, and allowed regions

Key Actions: Click "Apply to This Job" to start application

Privacy Signals: Requirements are transparent, but what you'll reveal is not

Midnight Integration: jobId and policyHash form part of the circuit's public inputs

4. Apply to Job (/job/:id/apply)

apply
Purpose: Applicant prepares private attributes and generates ZK proof

Key Actions: Generate proof client-side, then submit application

Privacy Signals: UI marks private fields with 🔒 icons and explains that raw values never leave the device

Midnight Integration:

  • Real Mode (VITE_MIDNIGHT_ENABLED=true):
    • Fetches zk-config for job_eligibility circuit
    • Generates proof via MidnightJS httpClientProofProvider
    • Optionally submits to on-chain verifier
  • Mock Mode: Deterministic proof generation for demo/testing

5. Proof Result (/proof-result)

proof result
Purpose: Confirmation that proof was generated and verified

Key Actions: View "What Was Proven" vs "What Remained Private"

Privacy Signals: Explicit list of public inputs (jobId, policyHash, nullifier) vs hidden attributes

Midnight Integration: Shows verification result (on-chain or mock) with transaction hash in real mode

6. Applicant Status (/status)

status pending
status approved
Purpose: Check application status using applicationId or nullifier reference

Key Actions: View historical submissions per job while preserving anonymity

Privacy Signals: Status checking without identity revelation; nullifier provides rate-limiting

Midnight Integration: Real deployments derive status from on-chain events; mock mode uses localStorage

7. Employer New Job (/employer/new)

post new job
Purpose: Create job policy with required skills, minimum experience, and allowed regions

Key Actions: Submit job → policyHash computed and stored

Privacy Signals: Employers never see raw applicant attributes, only validity results

Midnight Integration: Policy becomes circuit parameterization; policyHash embedded in proofs

8. Employer Applications (/employer/job/:id/applications)

Employer Applications
Purpose: View verified submissions for a specific job

Key Actions: Inspect application validity, timestamps, and anonymized IDs

Privacy Signals: No visibility into applicant skills/years/region—only constraint satisfaction

Midnight Integration: Read-only view of verified results (on-chain or mock storage)

9. Review Application (/employer/application/:applicationId)

review application
Purpose: Inspect single application's verification outcome

Key Actions: Approve, shortlist, or reject based on proof validity

Privacy Signals: No PII; only proof validity and job policy reference shown

Midnight Integration: Links proof result to job policy without deanonymizing applicant

10. Privacy Page (/privacy)

privacy
Purpose: Educational page explaining zero-knowledge proofs in this context

Key Actions: Learn what's shared vs. protected, understand nullifiers

Privacy Signals: Teaches users to trust the process and clarifies tradeoffs

Midnight Integration: Conceptual explanation with links to technical documentation

How I Used Midnight's Technology
This is How I Used Midnight's Technology:

1. Compact Circuit Design

The core privacy logic lives in circuits/job_eligibility.cmp:

circuit JobEligibility {
    // Public inputs (visible to verifier)
    public jobId: Field
    public policyHash: Field  
    public nullifier: Field

    // Private inputs (never revealed)
    private skillsBitset: Field[32]
    private experienceYears: Field
    private regionIndex: Field
    private secret: Field

    // Constraints enforce:
    // 1. Skills subset check (applicant ⊇ required)
    // 2. Experience threshold (years ≥ minimum)
    // 3. Region membership (region ∈ allowed)
    // 4. Nullifier derivation (prevents duplicates)
}
Enter fullscreen mode Exit fullscreen mode

2. MidnightJS SDK Integration

The app/src/lib/midnight.ts file orchestrates proof generation:

// Dual-mode design for flexibility
if (VITE_MIDNIGHT_ENABLED === 'true') {
    // Real Midnight Network integration
    const { httpClientProofProvider } = await import('@midnight-ntwrk/midnight-js-http-client-proof-provider');
    const { FetchZkConfigProvider } = await import('@midnight-ntwrk/midnight-js-fetch-zk-config-provider');

    // Generate real proofs via SDK
    const proof = await proofProvider.prove('job_eligibility', witness, zkConfig);
} else {
    // Mock mode for development/demo
    return generateMockProof(publicInputs, privateInputs);
}
Enter fullscreen mode Exit fullscreen mode

3. Build & Deployment Scripts

  • Circuit Compilation (scripts/compile-circuits.ts): Compiles Compact code to proving/verification keys in artifacts/zk/
  • Verifier Deployment (scripts/deploy-verifier.ts): Deploys on-chain verifier and persists contract address

Data Protection as a Core Feature

Privacy isn't an afterthought—it's the foundation of every design decision:

What Stays Private:

  • Exact Skills: Verifier never sees your full skill list, only that you have the required subset
  • Precise Experience: Your exact years remain hidden; only proof of meeting minimum is revealed
  • Specific Location: Your exact region stays private; only membership in allowed regions is proven
  • Personal Identity: No PII is ever exposed or stored

What Gets Proven:

  • ✅ "I have all required skills" (without listing other skills)
  • ✅ "I have enough experience" (without revealing exact years)
  • ✅ "I'm in an allowed region" (without specifying which one)
  • ✅ "This is my only application to this job" (via nullifier)

UI Privacy Reinforcement:

  • 🔒 Lock icons mark all private data fields
  • Clear explanations at each step about what remains hidden
  • Dedicated /privacy page educating users on ZK benefits
  • Success page explicitly lists "What Was Protected" vs "What Was Proven"

Set Up Instructions / Tutorial
This comprehensive tutorial supports both mock mode (no credentials needed) and real Midnight integration.

Prerequisites

  • Node.js 18+ and npm
  • Git
  • Modern web browser

Step 1: Clone and Install

git clone https://github.com/depapp/zk-job-board.git
cd zk-job-board
npm install
Enter fullscreen mode Exit fullscreen mode

Step 2: Configure Environment

cp .env.example .env.local
Enter fullscreen mode Exit fullscreen mode

Option A: Mock Mode (Default - No Credentials Needed)
Leave .env.local as-is or set:

VITE_MIDNIGHT_ENABLED=false
Enter fullscreen mode Exit fullscreen mode

Option B: Real Midnight Integration

VITE_MIDNIGHT_ENABLED=true
VITE_MIDNIGHT_RPC_URL=https://testnet.midnight.network/rpc
VITE_MIDNIGHT_NETWORK_ID=testnet-02
VITE_MIDNIGHT_API_KEY=your-api-key
VITE_PROOF_SERVER_URL=http://localhost:6300
VITE_VERIFIER_ADDRESS=  # Set after deployment
Enter fullscreen mode Exit fullscreen mode

💡 Get testnet credentials at midnight.network

Step 3: Compile Circuit

npm run compile-circuits
Enter fullscreen mode Exit fullscreen mode

This compiles circuits/job_eligibility.cmp and generates artifacts in artifacts/zk/.

Step 4: Deploy Verifier (Real Mode Only)

npm run deploy-verifier
Enter fullscreen mode Exit fullscreen mode

Deploys verifier contract and saves address to .env.local.

Step 5: Run the Application

npm run dev
# Visit http://localhost:5173
Enter fullscreen mode Exit fullscreen mode

Step 6: Try the Complete Flow

As an Employer:

  1. Navigate to "Submit Job" (/employer/new)
  2. Create a job with requirements:
    • Required Skills: Select from allowlist
    • Min Experience: Set years
    • Allowed Regions: Choose regions
  3. Submit and note the Job ID

As an Applicant:

  1. Browse jobs (/jobs)
  2. Select a job and click "Apply"
  3. Generate mock credentials that meet requirements
  4. Click "Generate Proof"
  5. Submit application
  6. View "Your Privacy Was Protected" confirmation

Verify Privacy:

  1. Check the Privacy page (/privacy) to understand the system
  2. Try applying twice to same job (nullifier prevents it)
  3. Apply to different jobs (nullifiers don't link identity)

Step 7: Understanding Console Logs

The app provides detailed logging for educational purposes:

// Mock mode
[Midnight] SDK loading skipped (VITE_MIDNIGHT_ENABLED is not true)
[Midnight] Using mock proof generation

// Real mode
[Midnight] Loading SDK modules...
[Midnight] Connected to testnet-02
[Midnight] Using real proof generation via SDK
[Midnight] Proof verified on-chain at 0x...
Enter fullscreen mode Exit fullscreen mode

Step 8: Troubleshooting

Issue Solution
SDK modules failed to load Run npm install again
ZK config not available Run npm run compile-circuits
Verifier address not configured Run npm run deploy-verifier or use mock mode
Connection failed Check API key and network connectivity
Proof generation fails App auto-falls back to mock mode

Step 9: Advanced Customization

Adding New Skills:
Edit config/allowlist.skills.json:

{
  "skills": ["YourNewSkill", ...]
}
Enter fullscreen mode Exit fullscreen mode

Modifying Circuit:

  1. Edit circuits/job_eligibility.cmp
  2. Recompile: npm run compile-circuits
  3. Redeploy: npm run deploy-verifier

Switching Modes:
Toggle VITE_MIDNIGHT_ENABLED and restart dev server.

Step 10: Production Deployment

# Build for production
npm run build

# Preview production build
npm run preview

# Deploy to any static hosting (Vercel, Netlify, etc.)
Enter fullscreen mode Exit fullscreen mode

Architecture Overview

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   Employer  │────▶│  Job Policy  │────▶│  On-Chain   │
│     UI      │     │   Creation   │     │   Storage   │
└─────────────┘     └──────────────┘     └─────────────┘
                                                 │
                                                 ▼
┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│  Applicant  │────▶│ ZK Proof Gen │────▶│  Verifier   │
│     UI      │     │  (Midnight)  │     │  Contract   │
└─────────────┘     └──────────────┘     └─────────────┘
Enter fullscreen mode Exit fullscreen mode

Key Design Decisions:

  1. Dual-Mode Operation: Enables both development (mock) and production (real Midnight) workflows
  2. Client-Side Proof Generation: Sensitive data never leaves user's device
  3. Nullifier Design: Per-job nullifiers prevent spam while maintaining cross-job unlinkability
  4. Bitset Encoding: Efficient skill matching using bitwise operations in circuit



ZK Job Board demonstrates how Midnight Network enables practical privacy-preserving applications. By making privacy the default rather than an option, we can build systems that respect user data while maintaining functionality and trust.

Top comments (0)