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.
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.
Full patient record: exact age, diagnosis codes, lab results, medication list, consent forms, identity. Sensitive, protected, and not meant to travel.
Stays inside the hospitalOne 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 / noA 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.
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:
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 →
This is the actual flow running in this project right now. It's been tested end to end.
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 sideThe 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 sideThe 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 sideEach component verified by running real code, not just describing it.
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.
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.
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.
The provider portal, running against the actual prover and verifier services on localhost. These are real screenshots, not mockups.
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.
Both services connected (ports 3001/3002), with live counts of patients stored and proofs generated this session.
Only an ID, a Poseidon commitment, and a timestamp are ever shown. The encrypted record itself never appears here.
When a record doesn't satisfy the circuit, the prover returns "not eligible" with no proof and no explanation of which rule failed.
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.
Every proof request made during the session, success or not, ends up here as a session-local audit trail.
This project proves the core idea works. Here's the honest gap between "working demo" and "something a hospital could actually use."
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.
Separate networks and HTTPS: the hospital and sponsor servers should run on different machines, talking over encrypted connections, not localhost.
Key management: the encryption keys protecting patient data need proper storage (not a plain .env file) and regular rotation.
Audit logging and rate limits: every proof request should be logged, and the APIs need protection against abuse or replay attacks.
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.
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.
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