Inspiration
A man stands outside a bank, unable to withdraw his own deposit. "My mother is dying. I can't afford treatment."
He did nothing wrong. He saved money and put it in a bank. The bank still shows his balance on paper. He simply cannot get it out.
He is not why the bank failed. Large companies and politically connected borrowers took enormous loans from banks like his and never paid them back. When a loan like that goes bad, it usually does not get marked as bad. The bank keeps granting the borrower a new repayment deadline, over and over, or quietly writes the loan off. On paper the bank looks healthy right up until the day it isn't, and by then the people locked out of their savings are depositors, not defaulters.
Here is what made us angry enough to build something. The rules against this already exist. Classification has to be justified in writing over two named signatures. Rescheduling is capped. A write-off needs the board to approve it.
The rules are not what is missing. The record is. That proof of board approval is a form the bank fills in itself, kept in a file the bank controls, and read once a year when an inspector shows up. One outside audit of six banks found roughly four times the bad loans those banks had admitted to.
What it does
Quorum puts those authority rules inside a smart contract on Midnight, so a bank cannot claim an approval it never got.
The obvious version of this fails immediately. If you prove board authorisation by publishing every director's signature to a public chain, you hand competitors your governance record, and no bank will agree to that. It is a fair objection.
So we asked a narrower question. A regulator needs to know that enough of the right people approved. It does not need to know which ones. Those are two different questions, and only one of them requires anybody to publish anything.
Quorum proves two authority rules in zero knowledge:
- Board threshold. A director registers a fingerprint of a secret, never the secret. To approve, they supply the secret privately. The circuit re-derives the hash, checks the director is confirmed and not revoked, rejects duplicates, and discloses only the final count.
- Seniority. A restructure must be authorised one grade above whoever sanctioned the loan. That comparison happens over a private grade, and only the true or false result reaches the ledger. An officer's grade is personnel data, and publishing it on every reclassification would leak the bank's whole internal hierarchy.
Two rules, two different privacy shapes: a count over a set, and a single comparison. One zero-knowledge rule can look like a party trick. Two show the pattern generalises.
Submit a write-off without enough approvals and it is refused. The refusal does not come from the web page. It comes from the proof: the circuit counts the valid approvals, comes up short, and the transaction cannot be built. There is no front-end check to bypass.
How we built it
It is a full stack, not just a contract.
The contract. commitment.compact, six circuits, written in Compact and deployed on Midnight Preview. Loans are an append-only event chain where every event carries the previous state's hash, so history cannot be quietly rewritten behind a supervisor. Directors live in an on-ledger role registry: a bank registers its own directors, but only the Central Authority can confirm them, because a bank that could seat its own board could authorise its own write-offs. Thresholds are not constants we compiled in. They sit in ledger state a Regulatory Council governs, and the bank cannot touch them.
The back end. A Midnight wallet has to replay ledger history before it can sign anything, which takes minutes, so it cannot be constructed per request. That forced the back end into a separate long-lived bridge service in Fastify. It syncs once at boot, caches its synced state to disk so later starts take seconds, holds the funded wallet, talks to the proof server, and serves circuit calls over HTTP.
The front end. A Next.js board room that walks the whole story in five steps: constitute a board, originate a loan, attempt a write-off with too few approvals, watch the circuit refuse it, then submit the same loan with enough approvals and watch it commit with a receipt naming the network, contract and block.
Why this is a real Midnight integration and not a veneer. The privacy boundary is enforced by the compiler. Compact's disclose() analysis means a witness value becomes public ledger state only when you explicitly wrap it, and the contract does not compile otherwise. Director approval secrets and the officer's grade are private witnesses. What reaches the chain is a count and a boolean. Take Midnight out and the project does not degrade, it stops existing, because publishing the credentials is exactly the trade we refuse to make.
Deployed and verified on Midnight Preview
The contract is live on Midnight Preview, confirmed through the Preview indexer rather than only through our own bridge. The address is in the repository README.
We verified both rules end to end against that live contract. The same write-off was refused with too few director approvals and committed with enough, at block 586223. The same restructure was refused at the grade that sanctioned the loan and committed one grade above it, at block 586236. Neither the director secrets nor the officer's grade ever reached the ledger.
An end-to-end test reproduces all of it and fails loudly if a write-off ever commits without board approval. A run where that succeeds is a failure even though nothing threw, because it would mean the rule is not being enforced, which is the entire claim.
How to run it
Requires Docker, Node 20+, and WSL2 on Windows, because the Compact toolchain has no native Windows build.
# 1. Compile the contract (inside WSL on Windows)
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/midnightntwrk/compact/releases/latest/download/compact-installer.sh | sh
compact update 0.31.1 # pin: 0.34.0 speaks a newer language and rejects this contract
cd midnight/contracts/commitment
npm install
npm run compact
# 2. Start the proof server
docker run -d -p 6300:6300 midnightntwrk/proof-server:8.1.0 midnight-proof-server -v
# 3. Deploy to Midnight Preview
# Run with no seed and it generates one, prints an address, and waits.
MIDNIGHT_NETWORK=preview NODE_OPTIONS=--max-old-space-size=6144 npx tsx src/deploy.ts
# 4. Start the bridge, then the front end
MIDNIGHT_NETWORK=preview MIDNIGHT_WALLET_SEED=<seed> npm run bridge
npm --prefix web run dev # then open http://localhost:3000/board
# 5. Verify the rules without clicking anything
node scripts/midnight-smoke.mjs
Two things to expect. Fund the printed address at the Midnight Preview faucet, which is captcha-gated and so cannot be scripted; the deploy resumes on its own afterwards. And the first wallet sync replays the whole ledger and takes tens of minutes, after which the state is cached and every later start takes seconds.
Full setup notes, architecture and toolchain gotchas are in the repository README and the contract README.
What is built, and what we do not claim
Built and verified against the live contract: six circuits with a real k-of-n board threshold proved in zero knowledge and a seniority rule proved over a private grade, the wallet and provider and deploy layer with state caching, and the bridge and board room exercised end to end by the test above.
We would rather write the limits down than let a demo imply more than it delivers. An approval proves knowledge of the preimage of a registered commitment, not a signature over the event, so a bank holding a director's secret could reuse it for a later vote. Director identities are still visible, because looking one up in a public map needs a public key, so what we bought is credential secrecy and not voter anonymity. The bridge asserts its own caller role per endpoint, which is fine for a prototype where one operator drives every party but is not a security boundary. And all data on screen is synthetic. No real borrower, depositor or institution appears anywhere.
Everything here was built inside the hackathon window. An earlier Hyperledger Fabric prototype, where director signatures went in as cleartext transaction arguments, was removed from the repository and survives only in git history for anyone who wants to compare the two approaches.
What's next
Bind each approval to a specific event hash, so a bank holding a director's secret cannot reuse it for a later vote. Derive the caller's role from their own key instead of having the bridge assert it. Then cross-institution exposure aggregation, so a borrower group's system-wide total becomes visible without any bank exposing its book.
The first two need no other participant. That sequencing is deliberate: a supervisory network that must be complete before it is useful is a network that never starts.
Built With
- compact
- docker
- fastify
- midnight
- next.js
- node.js
- react
- rxjs
- smart-contracts
- typescript
- webassembly
- websockets
- zero-knowledge-proofs
- zk-snarks

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