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 (3)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.