Personal project: zero-knowledge proofs meet healthcare

Prove a patient qualifies for a clinical trial, without showing anyone their medical record.

HIPAA-ZKP is a small working system that lets a hospital answer "does this patient qualify for Trial X?" with just yes or no. The patient's age, diagnosis codes, lab results, medications, and consent never leave the hospital's side.

The Problem

Hospitals can't just hand over patient files.

Clinical trial sponsors need to know who qualifies. Hospitals hold the records that could answer that, but HIPAA (and basic patient privacy) means that data isn't supposed to leave the building. Today, that gap is usually closed by trust, paperwork, or risky data-sharing agreements.

What the hospital has

Full patient record: exact age, diagnosis codes, lab results, medication list, consent forms, identity. Sensitive, protected, and not meant to travel.

Stays inside the hospital
VS

What the trial sponsor needs

One bit of information: is this person age 18-65, diagnosed with both E11.9 and I10, has an HbA1c in range, not on warfarin, and did they consent? That's it.

Just yes / no
In Plain English

Think of it like a bouncer checking your ID.

You don't hand over your wallet.

A bouncer at a club doesn't need your home address, your full name, or your driver's license number. They need one fact: are you over 21?

A zero-knowledge proof is the math version of that. The hospital's computer looks at the patient's real record and produces a small "proof" (think of it as a sealed, tamper-proof note) that says "yes, this patient meets all the trial's rules."

The trial sponsor can check that the note is genuine using math, and gets a true or false. They never see the age, the diagnosis codes, the lab results, the medication list, the consent form, or who the patient is.

Sponsor sees
Age: 34
×
Sponsor sees
Diagnosis: E11.9
Sponsor sees
Consent form
×
Sponsor sees
Patient identity
Sponsor sees
HbA1c: 8.2%
×
Sponsor sees
Medication list
What the sponsor actually receives
ELIGIBLE: TRUE
A Tiny Taste Of The Math

Every rule becomes "multiply, add, then check."

Before the bouncer analogy, here's the actual shape of the math underneath it. Say a circuit needs to check that z = x×x + y for some hidden numbers x, y, and z. That single check gets broken into three tiny steps, called gates:

1
t1 = x × x
Multiplication gate
2
t2 = t1 + y
Addition gate
3
t2 == z
Equality constraint

Step 1 takes the hidden number x, multiplies it by itself, and stores the result as t1. Step 2 takes t1 and adds the hidden number y, storing the result as t2. Step 3 doesn't compute anything new, it just checks that t2 and the hidden number z come out equal. If they don't, the proof fails right there, no exceptions and no partial credit.

The circuit in this project chains together close to a thousand steps like these three, to check things like "is this patient's age between 18 and 65" or "does this hash match the stored commitment." However complicated a rule sounds, underneath it's just a long sequence of multiply, add, and equality-check gates like the ones above. See the full chain on the math page →

How It Works

Three steps, two computers, zero shared records.

This is the actual flow running in this project right now. It's been tested end to end.

01

Record stays home

The hospital stores the patient's age, diagnosis codes, lab values, medications, and consent, encrypted, on its own server. This data never travels anywhere.

Hospital side
02

A proof gets generated

The hospital's server runs the trial's eligibility rules against the real record and produces a cryptographic proof: a small bundle of numbers that's useless on its own but mathematically tied to a true answer.

Hospital side
03

Sponsor checks the proof

The trial sponsor's server runs a quick check on the proof and gets back eligible: true or false. No age, codes, or identity ever arrive on this side.

Sponsor side
What's Actually Built

Not a mockup. A working system, tested end to end.

Each component verified by running real code, not just describing it.

Working

The proof engine

A Circom circuit checks eight constraints: age range, two required diagnosis codes, consent, a lab value range, and a medication exclusion. They're tied together by a two-stage Poseidon commitment and fed into a Groth16 zk-SNARK proof. Verified against 10 test patients.

Working

Two live servers

A prover service (hospital side, port 3001) and a verifier service (sponsor side, port 3002) running as real APIs, tested with both eligible and ineligible patients.

Working

Patient intake app

A simple Flutter web form where a patient's info is entered, stored, and checked against a trial, with a live "eligible" badge shown right in the browser.

See It Run

Screenshots from a real local session.

The provider portal, running against the actual prover and verifier services on localhost. These are real screenshots, not mockups.

Patient intake

New intake form showing age, two diagnosis codes, consent, lab value, and a medication chip-input

The intake form now also takes a lab value and a medication list, all hashed into the two-stage Poseidon commitment before the record is stored.

Dashboard overview

Provider portal overview showing connected prover and verifier services, 5 patients stored, 23 proofs generated

Both services connected (ports 3001/3002), with live counts of patients stored and proofs generated this session.

Patient roster

Patients table showing patient IDs, Poseidon commitments, and timestamps

Only an ID, a Poseidon commitment, and a timestamp are ever shown. The encrypted record itself never appears here.

A rejected check

Verification check form showing a not eligible result with no proof produced

When a record doesn't satisfy the circuit, the prover returns "not eligible" with no proof and no explanation of which rule failed.

A successful proof

Verification check form showing a successful proof generated against the expanded six-signal circuit, ready to send to the verifier

Same flow, but this record satisfies all eight constraints (both diagnosis codes, the lab range, the medication exclusion, age, and consent), so it produces a real Groth16 proof and six public signals, ready to hand to the verifier.

Activity log

Activity log listing proof generation attempts and their results

Every proof request made during the session, success or not, ends up here as a session-local audit trail.

Why This Isn't Just a Toy

What would make this real-world ready.

This project proves the core idea works. Here's the honest gap between "working demo" and "something a hospital could actually use."

01

Real EHR data: right now, patient records are entered by hand. A real version would pull from the hospital's existing electronic health record system.

02

Separate networks and HTTPS: the hospital and sponsor servers should run on different machines, talking over encrypted connections, not localhost.

03

Key management: the encryption keys protecting patient data need proper storage (not a plain .env file) and regular rotation.

04

Audit logging and rate limits: every proof request should be logged, and the APIs need protection against abuse or replay attacks.

05

Audited trusted setup: the cryptographic setup ceremony for the proof system should be reviewed by someone outside this project before it ever touches real patient data.

06

Scaling to many trials: the eligibility rules are hardcoded for one trial right now. A real system would support many trials, each with its own rule set.

For the curious

What's actually inside that "proof"?

Underneath the bouncer analogy is a real cryptographic construction: arithmetic circuits, a Groth16 zk-SNARK over the BN128 curve, and a Poseidon hash that binds the proof to one patient record. If you want the full derivation (constraints, polynomials, elliptic curve pairings, and all), it's written out step by step.

Open the math walkthrough
e(A, B) = e(α, β) · e(IC, γ) · e(C, δ)