ShadowMatch :Private AI Resume Screening

The Problem That Keeps Me Up at Night

Last year, a friend applied to 47 jobs. She got 3 interviews and 47 data breaches. Her resume ,containing her home address, phone number, employment history, and salary expectations was scraped, sold, and fed into training datasets she never consented to. By month three, she was getting phishing texts that referenced her exact cover letter paragraphs.

This isn't an edge case. It's the default. Job platforms process millions of resumes through AI scoring systems that store everything in plaintext. The AI sees your depression gap, your career pivot, your immigration status — and so does anyone with database access.

I started this hackathon asking: What if AI could score a resume without ever seeing it?


What We Built

ShadowMatch is a privacy-first AI resume screener built on the Midnight Network. Candidates paste their resumes, an AI model scores job fit from 0–100, but the resume text never touches the blockchain. Only two things go on-chain:

  1. A cryptographic commitment : a hash that proves the AI was run on this resume without revealing a single word of it.
  2. The match score : a public number employers can filter on.

The architecture looks like this:

┌─────────────┐     ┌─────────────────────┐     ┌─────────────────┐
│  Candidate  │────▶│  React Frontend     │────▶│  AI Service     │
│  Browser    │     │  (Lace Wallet)      │     │  (Python/FastAPI│
└─────────────┘     └─────────────────────┘     │   TF-IDF + Cos) │
                                              └─────────────────┘
                                                        │
                                              ┌─────────▼─────────┐
                                              │  ZK Commitment    │
                                              │  hash(resumeHash  │
                                              │  + score + salt)  │
                                              └─────────┬─────────┘
                                                        │
                                              ┌─────────▼─────────┐
                                              │  Midnight Contract│
                                              │  (Compact)        │
                                              │  • commitment     │
                                              │  • score          │
                                              └───────────────────┘

How It Works (The Math)

1. Resume Hashing

The candidate's resume $R$ is hashed locally in the browser using SHA-256:

$$H_R = \text{SHA-256}(R)$$

This hash is never sent to the AI service. It stays in the browser's memory.

2. AI Scoring (Off-Chain)

The resume text is sent to a Python FastAPI service that computes job-match similarity using TF-IDF vectorization and cosine similarity:

$$\text{score} = \left\lfloor \cos(\theta) \times 100 \right\rfloor + \text{keyword_boost}$$

where:

$$\cos(\theta) = \frac{\vec{v}{\text{job}} \cdot \vec{v}{\text{resume}}}{||\vec{v}{\text{job}}|| \; ||\vec{v}{\text{resume}}||}$$

The keyword boost adds up to 20 points for detected skills (React, Solidity, ZK proofs, etc.), capped at 100.

3. Zero-Knowledge Commitment

The AI service generates a random 256-bit salt $s$ and computes a commitment:

$$C = \text{SHA-256}(H_R \;||\; \text{score} \;||\; s)$$

This commitment $C$ is what gets stored on the Midnight blockchain , not the resume, not the score explanation, not even the salt. The contract verifies that $C$ was generated from valid inputs without learning what those inputs were.

4. On-Chain Verification

The Compact smart contract stores:

export ledger commitments: Map<Uint<64>, Bytes<32>>;
export ledger matchScores: Map<Uint<64>, Uint<8>>;

When a candidate calls submitResume(C, score), the proof server generates a zero-knowledge proof that:

  • The score is in range $[0, 100]$
  • The commitment $C$ binds the score to a valid resume hash
  • The caller is the original submitter

All without revealing $R$, $H_R$, or $s$.


What We Learned

1. Privacy Architecture Is Harder Than Privacy Marketing

Every "privacy-first" job platform I researched stores resumes in plaintext databases with an "AI layer" on top. Building real privacy means accepting a constraint: the blockchain cannot see the data. That changes every design decision : from how you index candidates to how employers filter them. We had to rebuild the entire UX around anonymous scores instead of searchable profiles.

2. Compact Is a Different Mindset

Coming from Solidity, I kept wanting to emit events and read storage directly. Compact forces you to think in circuits : every state change needs a ZK proof, and every proof needs a witness. The mental model shift is:

Solidity: "What data do I store?"
Compact: "What can I prove without storing?"

Our witnesses.ts file contains the candidate's actual resume text — it never leaves the browser. The contract only sees the proof that this text produced a valid score. That's not a workaround; it's the whole point.

3. The Proof Server Is the Unsung Hero

I spent the first 6 hours thinking the contract was broken because transactions were "failing." They weren't failing, the proof server was still generating the ZK proof. On a local network, proof generation for our simple circuit takes ~3 seconds. On mainnet, it'll be sub-second. Understanding the async lifecycle of callTx → prove → submit → confirm was the breakthrough moment.


Challenges We Faced

Challenge 1: Windows Build Hell

The Midnight starter template assumes a Unix environment. The counter-cli package uses rm -rf in its build script, which crashes on Windows CMD. Our fix: drop the CLI package entirely and build the contract + frontend separately. We also hit path issues with the Compact compiler not being in PATH on Windows. Solution: use absolute paths or add C:\Users\<user>\.cargo\bin to system environment variables.

Challenge 2: Contract-to-Frontend Type Mismatch

The Compact compiler generates TypeScript bindings, but the generated contract instance export name isn't documented clearly. We had contract: {} as any placeholders in our SDK for hours before realizing the compiled artifact exports shadowMatchContractInstance (camelCase, not PascalCase). One import line. Three hours of debugging.

Challenge 3: Mocking vs. Real Data

Our employer dashboard started with hardcoded mock candidates so we could style it. The challenge was switching to real on-chain data without breaking the UI. We used RxJS BehaviorSubject to bridge the gap mock data flows through the same observable pipeline as real indexer data, so swapping the source is one line change.

Challenge 4: CORS + Local Development

The AI service runs on localhost:8000, the frontend on localhost:5173, and the proof server on localhost:6300. Getting CORS headers right across three origins while keeping ZK proof generation local was a configuration puzzle. FastAPI's CORSMiddleware solved the AI service side; Vite's proxy config handled the rest.


Tech Stack

Layer Technology
Smart Contract Compact (Midnight's ZK-native language)
Blockchain Midnight Preview Network (Cardano sidechain)
Wallet Lace (Chrome extension)
Frontend React 18 + Vite + Tailwind CSS + Lucide icons
State Management RxJS observables + MidnightJS SDK
AI Scoring Python 3.11 + FastAPI + scikit-learn (TF-IDF)
Proof Generation Midnight Proof Server (local Docker)
Local Network Midnight standalone node + Indexer (Docker Compose)

What's Next

  • Selective Disclosure: Let candidates reveal parts of their resume (e.g., "I have 5 years React experience" as a ZK proof) without exposing the full document.
  • Federated Learning: Train the AI model on encrypted resume embeddings from multiple candidates without centralizing data.
  • Credential Verification: Integrate with decentralized identity (DID) so candidates can prove degrees/certificates via ZK proofs instead of uploading PDFs.

Why Midnight?

Midnight is the only blockchain where this project makes sense. Ethereum stores everything publicly, a resume hash on Ethereum is still a public signal. Midnight's selective disclosure means the candidate chooses what to reveal, when, and to whom. The ZK proof guarantees the employer isn't being lied to; the private state guarantees the candidate isn't being exploited.

ShadowMatch proves that AI and privacy aren't tradeoffs, they're multipliers. When candidates trust the system, they share better data. When employers trust the scores, they hire faster. Privacy isn't a feature. It's the foundation.


Built with caffeine, zero-knowledge proofs, and the belief that your resume belongs to you.

Built With

  • ai
  • blockchain
  • compact
  • cosine-similarity
  • data-privacy
  • fastapi
  • hackathon
  • hiring
  • job-search
  • lace-wallet
  • machine-learning
  • midnight
  • privacy
  • python
  • react
  • resume-screening
  • selective-disclosure
  • sklearn
  • smart-contracts
  • tfidf
  • typescript
  • web3
  • zero-knowledge-proofs
  • zk-proofs
Share this project:

Updates

Submission history