DEV Community

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

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 (3)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.