Inspiration
Every large crypto trade is a trust problem. You either post your order publicly and get front-run by bots, or you call an OTC desk and hope they don't trade against you before settling. Wintermute moved $34 billion in a single month — that volume exists because institutions desperately need a private venue. But "private" today just means "trust us." We wanted to build something where you don't have to.Midnight's architecture — shielded state, ZK proofs, Zswap atomic settlement — is exactly the right foundation for this. We just had to figure out how to wire it together into something that actually works end to end.
What it does
Umbra is a private OTC trading protocol where neither the relayer nor any counterparty ever sees your order details until the moment of settlement. When a maker creates an order, the price, size, and terms are encrypted with AES-256-GCM directly in their browser. What gets broadcast to the network is just a cryptographic commitment — a hash that proves the order exists without revealing what's inside. The direction (buy or sell) and asset pair are exposed so the relayer can match orders without decrypting anything else. When a taker wants to fill an order, they perform an ECDH key exchange directly with the maker through the relayer. The maker encrypts their AES key specifically for that taker's public key and forwards it. The taker decrypts the order details locally, verifies the commitment on-chain, and both sides settle atomically via Zswap — the full trade completes or nothing moves. No operator holds funds. No server ever sees a plaintext order.
How we built it
The stack has three layers: On-chain (Midnight / Compact): A Compact smart contract handles commit, reveal, and settlement. The maker locks escrowed tokens when posting a commitment. The taker reveals the plaintext, the contract verifies it matches the commitment hash, and Zswap executes the atomic swap. Cancellation returns the escrowed funds. Off-chain relayer (Node.js / WebSocket): A lightweight order book aggregator that stores encrypted order blobs and matches on direction bit + asset pair only. It handles the ECDH key forwarding — when a taker expresses interest, the relayer passes their public key to the maker, then routes the encrypted AES key back point-to-point to that specific taker. It never broadcasts the key and never stores it. Frontend (Next.js 14 / TypeScript): The maker flow encrypts the order locally, submits the ciphertext + commitment, and waits for a match signal. The taker flow performs the ECDH handshake, decrypts the order in-browser, verifies the commitment, and triggers settlement. A mock mode (no wallet required) lets you demo the full flow locally. The cryptography is all Web Crypto API — no third-party crypto libraries for the core primitives.
Challenges we ran into
The key handoff problem was the hardest architectural piece. Originally the spec had no mechanism for the maker to get the AES key to the taker after matching — the relayer can't decrypt it and can't broadcast it safely. We solved this with ECDH: the taker generates an ephemeral keypair, sends their public key through the relayer, and the maker encrypts the AES key specifically to that key. The relayer forwards an opaque blob it can't read. On-chain solvency was a spec gap we caught early. Without locking the maker's tokens at commitment time, nothing prevents a maker from posting fake orders and draining a taker who reveals first. We added escrow to the Compact contract — tokens lock when the commitment is posted, not at settlement. Commitment malleability was subtle. A hash of just the ciphertext is malleable — an attacker could substitute a different IV or auth tag and produce a valid-looking but undecryptable order. We fixed the commitment to hash ciphertext ‖ IV ‖ authTag ‖ salt, binding all AES-GCM components. The Midnight JS SDK doesn't yet have a published npm package for smart contract interaction, so we built a dual-mode contract client with a full in-memory mock that mirrors real contract behavior exactly — useful for both development and demo.
Accomplishments that we're proud of
The thing we're most proud of is that the privacy guarantee is real, not rhetorical. The relayer literally cannot learn order details — it only ever sees a hash, a direction bit, and an encrypted blob it has no key for. We didn't just claim privacy; we made it structurally impossible to violate. We also shipped a complete working system: contract, relayer, frontend, and a test suite (14/14 passing) covering the full trade lifecycle including edge cases like commitment replay, auth tag tampering, and expired orders. End to end, in a weekend.
What we learned
Midnight's programming model forces you to think clearly about what state is public versus shielded — which turns out to be exactly the right mental model for building a privacy-preserving trading protocol. The constraint is a feature. ECDH key exchange in the browser via Web Crypto is surprisingly clean once you understand the subtle difference between key agreement (shared secret) and key encryption (wrapping). The Web Crypto API is underused and underrated. We also learned that most "private OTC" solutions just move trust rather than eliminate it. Building something with no trusted operator is harder but it's the only version that holds up under adversarial conditions.
What's next for Umbra
The immediate next step is integrating with a real Midnight wallet and deploying the contract to testnet. The mock mode was essential for speed, but the cryptographic primitives are already production-grade — the swap is straightforward. Beyond that: maker allowlists (restrict which takers can fill your order), partial fills, and W3C Verifiable Credential gating so institutional makers can require identity attestations from takers without doxxing either party. Midnight's ZK identity infrastructure makes that last piece genuinely possible in a way it isn't on other chains. The longer-term vision is a protocol layer — Umbra as infrastructure that other trading venues plug into, not just a standalone interface.
Built With
- aes-256-gcm
- compact-(midnight)
- css
- ecdh-(p-256)
- jest
- next.js-14
- node.js
- tailwind
- typescript
- web-crypto-api
- websocket
- zswap