Inspiration
Verifying a degree today means one of two bad options.
Send the whole document. To prove you hold a computer science degree, you email a PDF transcript containing your student number, every grade you ever received, and your date of birth. The employer keeps that file. So does their applicant tracking system.
Or call the university. Slow, manual, and only works while someone answers the phone.
Both leak far more than the question required. The question was "does this person hold this degree." The answer given is an entire academic history.
The case that stuck with us was the nursing diploma scandals. Thousands of forged credentials circulated for years without detection. When institutions finally started checking, the people who suffered were the nurses with real degrees — suspended without pay, "pending verification," while small schools were slow to answer the phone. A system that couldn't spot thousands of fakes also couldn't confirm one real credential in under a month.
What it does
A university issues a degree. The graduate decides who sees what. An employer verifies it in about a tenth of a second and learns only what the graduate chose to share.
Nothing about the student goes on the chain — not their name, not their student number, not their grades, not even a hash of any of it. And a forged credential isn't caught after the fact. It simply cannot produce a proof at all.
Beyond the degree, a university can attest individual courses, honors, extracurriculars and certifications. Each becomes its own credential on the chain, so a graduate can prove they took one specific course to one specific employer without revealing the rest of their transcript.
How we built it
- Compact contract — 4 circuits:
authorizeIssuer,issue,revokeCredential,proveCredential - chain-service — Node 22 + Midnight.js, holding private state and driving the proof server
- Backend — FastAPI, with an off-chain index that deliberately stores no grades and no student identity
- Frontend — Next.js, with the Lace wallet as the university's identity
- Observability — Prometheus, Loki, Grafana, and an anomaly detector that correlates verification logs
The privacy design lives in two structs. CredentialData is a witness — a
private input to the proof, never written to the chain. DisclosedClaim is
what a verification returns, and it has four fields, none of which is
studentId.
That absence is the whole design. Hiding the holder's identity isn't a runtime check someone could forget or a setting an admin could misconfigure. There is no field to put it in.
Challenges we ran into
The verifier had a toggle it should never have had. Our verify page shipped a "show GPA" button any visitor could click. We had described that as consent. It wasn't — asking is not consent. We deleted it and rebuilt disclosure around share links the graduate mints, so a verifier has no way to request more.
The student had no surface at all. The university issued, the employer verified, and the graduate — the person the product is about — was the row in between. We built them a portal reached by a secret link, with no account and no password for us to store.
A grade of zero isn't the absence of a grade. The chain stores
gpaTimes100 as a non-nullable integer, so an ungraded honor arrives as 0.
Rendering that as "0.00" would libel the graduate, so attestations read it as
absent — while a degree keeps the strict reading, because a 0.00 GPA there is
real data.
Our logging pipeline silently dropped everything. The proof server writes log lines over 256KB while proving. Loki rejected them, and Promtail drops the entire batch when one entry is rejected — so every service's logs vanished. It only appeared once we switched from the mock adapter to real proofs.
Accomplishments we're proud of
The privacy guarantee is enforced by the compiler, not by our discipline. Add one line writing the student's ID to the ledger and the build fails — Compact names the witness, the ledger operation, and the exact path the value would have taken.
We ship a script that demonstrates this in 13 seconds.
What we learned
Zero-knowledge changes what a failure means. In a normal system a bad
credential is caught by comparison. In a circuit, assert failing doesn't
throw an error you can catch — no proof exists. There is nothing to submit.
We also learned that privacy is mostly a design discipline about what you don't store. The hardest decisions weren't cryptographic; they were about refusing convenient fields. There is no "resend access link" because we keep only a one-way hash, and the student's email is used once and discarded.
What's next
Deploy to a public Midnight network, address the credential-ID linkability we document as a known limitation, and let a graduate hold credentials from multiple institutions behind one access link.
Tested
121 backend tests · 66 chain-service tests · 4 browser tests · contract compiles to 4 circuits with prover and verifier keys.

Log in or sign up for Devpost to join the conversation.