Inspiration
In Buenos Aires, the colonial night watchman — el sereno — walked the streets and called out at midnight: "¡Las doce han dado… y sereno!" — "midnight struck, and all is well." One designated watcher, so nobody else had to live with their lights on.
That's exactly what Latin American remittances are missing. Today families choose between formal rails where every payment is surveilled forever, and informal rails where regulators get a black box. We had built payment corridors on public ledgers before and hit the wall from the other side: everything on-chain is public — a merchant's entire cash flow visible to anyone with an explorer. Midnight's private-by-default model was the missing piece, so we built the watchman.
What it does
Sereno is a shielded remittance corridor: private in the middle, accountable at the edges.
shield(amount) — deposits are public on entry (the accountable edge). The deposit becomes a private Zerocash-style note: a hiding commitment in a HistoricMerkleTree. transfer(recipientPk) — fully private in the middle: the circuit proves Merkle membership of the spent note, publishes a nullifier (double-spend dead), inserts a new commitment. No amounts, no linkable parties on the public ledger. In the same circuit, an exponential-ElGamal ciphertext of the amount is encrypted to one sealed key: the auditor's. Opaque to the public — never opaque to the watchman. discloseToAuditor(requestId) — selective disclosure as a proof, not a backdoor: the note owner proves in-circuit that their commitment opens to an exact amount, recorded on-ledger under that specific audit request only, without spending the note. unshield() — the amount goes public again on exit.
A CLI drives the full corridor (deploy / shield / transfer / receive / unshield / disclose / ledger view), plus an offline demo mode and an AI agent interface (cli/src/agent.ts) that operates the corridor without ever seeing private note data.
How we built it
Compact contract (language 0.23, toolchain 0.31.1): 4 impure circuits + pure circuits for client-side key/commitment derivation. Ledger: HistoricMerkleTree<10> of commitments, nullifier Set, sealed auditorPk (Jubjub point), audit disclosure Map, per-transfer ElGamal ciphertexts, public TVL. Witness layer (TypeScript): private note state — amounts, randomness, secret keys — never leaves the client; the circuits validate everything witnesses return. CLI on Midnight.js + wallet-sdk facade (HD keys, shielded/unshielded/dust wallets), local proof server in Docker under WSL2. Built by studying the official example-counter / example-bboard repos and docs.midnight.network — every API in the codebase traced back to an official source. Cryptographic lineage, credited in the README: Zerocash (notes/commitments/nullifiers), Zcash viewing keys (auditor lane), Privacy Pools (accountable-privacy boundary design).
Challenges we ran into
Public preprod RPC sync: our wallet is funded (faucet tx on-chain, balance visible via indexer in ~3 s) but the node websocket closes right after subscribing (1000 Normal Closure), so isSynced never completes → no DUST registration → no fees → no live finality. We diagnosed the full dependency chain and shipped the demo as an honestly-labeled simulation over the real compiled circuits instead of faking a live run. Fast-moving stack: testnet-02 → preprod migration, indexer v3 vs v4, compact-runtime 0.15 vs 0.16, wallet-sdk facade APIs — docs and examples disagree with each other week to week. Windows development: the Compact toolchain is Linux/macOS only — everything runs through WSL2 + Docker, with its own traps (WSL idle shutdown kills the proof server mid-session). 48-hour scope discipline: cutting native shielded tokens and note splits to ship the cryptographic core.
Accomplishments that we're proud of
All 4 circuits compile and generate proving keys — the full note lifecycle (shield → transfer → unshield) plus selective disclosure in one Compact contract. In-circuit ElGamal to a sealed auditor key on every transfer — the compliance lane is cryptographically enforced, not a policy promise. Passing smoke test over the real pure circuits: address derivation, commitment construction, private-state round-trip. Honest demo: real commitments, real nullifiers, real funded wallet — and a visible "finality simulated" label. No overclaiming. A design regulators can actually evaluate: public edges, private interior, dual auditor lanes (continuous ciphertext + on-request proof).
What we learned
Midnight's model inverts the usual ZK workflow: instead of bolting a verifier onto a transparent contract, privacy is the default and disclose() makes every leak explicit at compile time. The compiler is a privacy reviewer. Witnesses are untrusted inputs — the discipline of validating everything they return in-circuit is the core security skill of Compact. Selective disclosure is a product feature, not just cryptography: "only what was asked, only under this request" is a sentence a compliance officer understands. Ship the honest demo. A simulation labeled as such over real circuits beats a fake live run every time.
What's next for Sereno
Live finality: migrate to the v4 indexer API, per-subwallet sync diagnostics, and a standalone local node as fallback — deploy + callTx end-to-end. Native shielded tokens (Zswap coins) instead of the contract-level note registry; note splits/change for arbitrary amounts. Threshold-based disclosure: amounts above a regulatory threshold auto-enable auditor opening — compliance rules as circuit constraints. KYC-gated entry: blind-issued eligibility credentials at the shield edge. Real corridor integration: we operate Stellar-based payment rails for Argentina today (QR Transferencias 3.0, USDC off-ramps) — Sereno is the privacy layer that corridor is missing. Ley 27.739 / PSAV compliance narrative included in the README. Rematch at Hack Buenos Aires, August 2026 — with live on-chain finality.
Built With
- compact
- docker
- elgamal
- halo2
- merkle-tree
- midnight
- node.js
- react
- remotion
- typescript
- vercel
- wsl2
- zero-knowledge-proofs
- zerocash
Log in or sign up for Devpost to join the conversation.