This is a submission for the Midnight Network "Privacy First" Challenge - Protect That Data prompt
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:
This section explains every page, its function, and how it supports privacy by design.Demo
GitHub Repository: https://github.com/depapp/zk-job-board
Page-by-Page Walkthrough
1. Home Page (
/
)
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
)
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
)
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
)
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:
VITE_MIDNIGHT_ENABLED=true
):
job_eligibility
circuithttpClientProofProvider
5. 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
)
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
)
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
)
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
)
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
)
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
The core privacy logic lives in The How I Used Midnight's Technology
This is How I Used Midnight's Technology:
1. Compact Circuit Design
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)
}
2. MidnightJS SDK Integration
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);
}
3. Build & Deployment Scripts
scripts/compile-circuits.ts
): Compiles Compact code to proving/verification keys in artifacts/zk/
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"
Option A: Mock Mode (Default - No Credentials Needed) Option B: Real Midnight Integration 💡 Get testnet credentials at midnight.network This compiles Deploys verifier contract and saves address to The app provides detailed logging for educational purposes: Adding New Skills: Modifying Circuit: Switching Modes:Set Up Instructions / Tutorial
This comprehensive tutorial supports both mock mode (no credentials needed) and real Midnight integration.
Prerequisites
Step 1: Clone and Install
git clone https://github.com/depapp/zk-job-board.git
cd zk-job-board
npm install
Step 2: Configure Environment
cp .env.example .env.local
Leave .env.local
as-is or set:
VITE_MIDNIGHT_ENABLED=false
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
Step 3: Compile Circuit
npm run compile-circuits
circuits/job_eligibility.cmp
and generates artifacts in artifacts/zk/
.
Step 4: Deploy Verifier (Real Mode Only)
npm run deploy-verifier
.env.local
.
Step 5: Run the Application
npm run dev
# Visit http://localhost:5173
Step 6: Try the Complete Flow
As an Employer:
/employer/new
)
As an Applicant:
/jobs
)
Verify Privacy:
/privacy
) to understand the system
Step 7: Understanding Console Logs
// 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...
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
Edit config/allowlist.skills.json
:
{
"skills": ["YourNewSkill", ...]
}
circuits/job_eligibility.cmp
npm run compile-circuits
npm run deploy-verifier
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.)
Architecture Overview
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Employer │────▶│ Job Policy │────▶│ On-Chain │
│ UI │ │ Creation │ │ Storage │
└─────────────┘ └──────────────┘ └─────────────┘
│
▼
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Applicant │────▶│ ZK Proof Gen │────▶│ Verifier │
│ UI │ │ (Midnight) │ │ Contract │
└─────────────┘ └──────────────┘ └─────────────┘
Key Design Decisions:
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)