Inspiration
On a public blockchain, every bid in an auction is visible — which invites front-running, last-second sniping, and competitors reverse-engineering exactly how you value an asset. The usual workaround is a trusted auctioneer who keeps bids secret, but then you have to trust them not to lie about who won. Midnight's selective disclosure and zero-knowledge proofs looked like the perfect way to get privacy and verifiability at once — so we built a sealed-bid auction that needs neither full transparency nor blind trust.
What it does
DarkBid is a sealed-bid auction on Midnight. Each bid is committed on-chain as an opaque hash — the ledger never sees the amount. When bidding closes, a single zero-knowledge proof settles the winner and the clearing price without ever opening the losing bids.
Two implementations:
- Core — a fixed 3-bidder auction that proves the winner directly (unconditionally correct).
- Merkle v2 — unbounded, dynamic bidders via a
HistoricMerkleTreeplus an optimistic-challenge settlement, driven by an interactive web UI.
How we built it
The contracts are written in Compact, using persistentCommit(bid, salt) for hiding + binding commitments. Version two stores commitments in a HistoricMerkleTree and proves membership with merkleTreePathRoot + checkRoot, using blockTime for the challenge window. We deployed and ran everything on a local Midnight devnet with Midnight.js — real zk-SNARKs generated by the proof server (~20–25s each) and verified on-chain. In-process unit tests cover the core logic; three on-chain deployments prove the full pipeline end to end. Every Compact construct was cross-checked against Midnight's documentation via the Kapa docs assistant.
Challenges we ran into
- Standing up the ZK toolchain (Debian/WSL, AVX2, Docker proof server) and the in-process test harness (
createCircuitContext). - Understanding explicit disclosure — a Merkle membership check needed the tree root disclosed (safe, since the root is already public), which the compiler flagged until we placed
disclose()correctly. - The realization that ZK circuits are fixed-size, so "prove the maximum over unbounded, hidden bids" is impossible in a single proof — which is exactly what pushed us to the Merkle + optimistic-challenge design.
Accomplishments that we're proud of
- Two contracts, three on-chain deployments, all proven with real ZK proofs.
- A genuinely scalable design: the full challenge flow works on-chain — an initial leader (420) is overturned by a higher sealed bid (690), then finalized after the block-time window.
- Being honest about the trust model instead of overclaiming.
What we learned
- Midnight's private-by-default model and selective disclosure — and how
disclose()makes privacy the default and exposure the exception. - That the hard part of a ZK product usually isn't the headline idea; it's the trust/oracle layer and knowing exactly what your proof does and does not guarantee.
What's next for DarkBid
- Escrow-backed bids (range proof
bid ≤ escrow) so bids are provably funded, with uniform deposits so escrow size doesn't leak a bid's upper bound. - Remove the operator's view of bids via MPC / threshold decryption.
- Vickrey (second-price) pricing once all bids are registered and escrowed (prevents undetectable shill bids).
- A batch-auction "dark pool" DEX built on the same sealed-input primitive.
Trust model
What's guaranteed: the public and losing bidders learn nothing about losing bid amounts, and the operator cannot lie about the winner or price — the proof binds the result to the published commitments. Not yet guaranteed: privacy from the operator (needs MPC) and escrow-backed bids. First-price reveals the winner's own bid; only losing bids stay hidden.
Built With
- blockchain
- commitment-scheme
- compact
- defi
- merkle-tree
- midnight
- midnight.js
- privacy
- typescript
- zero-knowledge
- zk-snark

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