Inspiration

In April 2021, the personal data of 533 million Facebook users leaked online phone numbers, full names, locations, birthdates, and email addresses. Facebook never notified the victims. In October 2025, Discord confirmed that hackers had stolen government IDs, passports, and driver's licenses belonging to 2.1 million users through a third-party vendor. People trusted these platforms with their identity. The platforms stored it carelessly. Hackers walked away with it.

In Web3, the problem runs even deeper. Around 73% of crypto users reuse wallet addresses, which means every transaction they have ever made is permanently traceable. Your balance, your trades, your entire financial history sitting in plain sight. Every time you connect a wallet to a DeFi app, you hand over your identity as collateral, and front-running bots and data harvesters are waiting on the other side.

Every existing solution asks you to trust something: a server, a company, an encrypted database. We asked a different question: what if the app never learned your identity in the first place?

We built ShadowKey because privacy shouldn't be a premium feature. It should be the default.


What it does

ShadowKey is not a DeFi protocol. It is the privacy infrastructure every DeFi protocol needs.

Current DeFi apps require users to connect wallets, instantly exposing addresses, balances, and full transaction histories to front-running bots and data harvesters. ShadowKey replaces this broken model with zero-knowledge authentication: users prove compliance and legitimacy without revealing identity or financial history. We demonstrate this with a mock lending pool that requires ShadowKey verification before granting borrow access, showing exactly how confidential smart contracts keep sensitive financial data hidden while remaining mathematically verifiable.

Users register a deterministic identity derived from a local secret. They can later prove ownership to generate session nonces, without ever transmitting that secret to the blockchain or a server.

  • Register: Identity fields are SHA256-hashed individually in the browser before anything touches the network. Only a zero-knowledge proof is submitted to Midnight. On-chain, all that's visible is a hash.
  • Documents: Supporting documents are committed via SHA256 hashes. The raw document data never hits the chain.
  • Verify: A Groth16 proof pipeline runs client-side: document hash verification, identity field matching, circuit execution, and on-chain submission. The live operations log shows every step in real time.
  • Login: The contract verifies registration through a ZK circuit without ever learning the user's secret or wallet address. A deterministic session nonce is minted.
  • Dashboard: Any third-party dApp checks the nonce publicly via a single verifySession() call. They get true or false. Access granted. Identity never exposed.

The dApp only ever gets a yes or a no, verified by math and not trust. No database. No passwords. No breach surface.


Trust model

One question worth addressing directly: in the demo, who runs the verifier?

For the hackathon, we simulate a KYC attestor with a mock signing service for speed. In production, ShadowKey supports any attestor architecture: a regulated bank or exchange verifying identity off-chain, a multi-sig council where multiple independent verifiers must agree, or a decentralized oracle network. The critical design is that the attestor never receives or stores the user's secret. They verify documents off-chain, sign a cryptographic attestation, and that's it. Even a fully compromised attestor cannot forge proofs or link users to their on-chain activity.

Component Knows secret? Knows identity? Can forge proofs?
User browser Yes Yes No (needs attestor signature)
KYC attestor No Yes (off-chain only) No (has no secret)
Blockchain No No No (only sees hashes)
Verifier dApp No No No (only checks nonce)

How we built it

ShadowKey required solving hard problems across every layer of the stack at the same time.

  • Smart Contract: Written in Compact, Midnight's zero-knowledge smart contract language. We implemented 9 ZK circuits: submitIdentity, uploadDocument, approveIdentity, rejectIdentity, deleteIdentity, proveIdentityExists, proveField, login, and verifySession. The contract uses witness-derived keypairs, domain-bound hashing with persistentHash and pad(32, ...), and enforces three discrete on-chain states (registered, session-active, verified), each gated by a ZK proof.
  • Proof Generation: Dockerized midnight-proof-server for fully client-side Groth16 proof generation over BLS12-381. The secret is generated in the browser, used to construct the proof locally, and never transmitted anywhere.
  • Frontend: React 19 + TypeScript + Vite + Tailwind CSS + shadcn/ui. The live operations log surfaces real circuit execution details including k values, row counts, and constraint counts, designed to feel like a production tool rather than a hackathon demo.
  • Developer Integration: One-click app registration generates a contract address and API key. The full integration is four lines of TypeScript. No PII flows through developer servers.
  • Wallet Integration: Lace Wallet (Midnight Preview) via the dapp-connector API, with shielded address detection, balance fetching, network validation, and transaction signing.

Security note: localStorage is demo-only

We made a deliberate trade-off for the hackathon: the 32-byte secret is stored in browser localStorage to enable rapid testing without requiring wallet signatures on every action. This is not production architecture, and we documented it explicitly rather than hiding it.

Our production roadmap replaces this with wallet-signature-derived secrets:

const secret = keccak256(await wallet.signMessage("shadowkey:derive:v1"));

The secret is deterministically generated from the wallet signature, never stored, and can be re-derived on any device. This eliminates the localStorage attack vector entirely while preserving the zero-knowledge guarantee. The contract architecture itself is production-ready. Only the secret derivation method changes.


Challenges we ran into

These weren't configuration issues. They were fundamental challenges in building on a new ZK stack.

1. Rethinking identity from scratch Compact has no msg.sender. There is no default identity primitive. We had to construct the entire concept of "a user" from zero: a witness-derived keypair, a domain-separated hash commitment, and an explicit ZK proof of knowledge. Every value in the system is either public or witness-tainted, and the compiler enforces that boundary strictly. Getting the disclose() annotations right, knowing exactly where witness data could cross into ledger state, took real time and careful thinking.

2. Proof server on ARM Mac The official midnightnetwork/proof-server Docker image failed on Apple Silicon. We tracked down and validated the community-maintained bricktowers/proof-server:6.1.0-alpha-6 ARM-compatible build and confirmed it produced identical proofs.

3. Silent wallet failures The Lace dapp-connector API fails silently when the wallet is connected to mainnet instead of Midnight Testnet. No error thrown, no feedback to the user. We added explicit network ID validation with a clear user-facing error message, something the broader ecosystem genuinely needs.

4. Map key types in Compact The compiler rejected Map<Bytes<32>, Bool>. The fix was switching to the struct-based Map<PublicKey, Bool> pattern from the ERC20 examples, but finding that pattern meant digging through example contracts rather than documentation.


Accomplishments we're proud of

  • 9 ZK circuits working end-to-end on Midnight's mainnet-era testnet, covering the full identity lifecycle from registration to privacy-preserving deletion.
  • True client-side privacy: no secret, no password, no private key ever touches a server or the blockchain. The live operations log shows real Groth16 circuit execution with constraint counts, not simulated output.
  • Production-quality developer experience: the one-click integration panel generates working credentials and the integration itself is four lines of TypeScript. This is the SDK we would have wanted when we started.
  • Reusable by design: any Midnight developer can fork this contract and add private authentication to their dApp in under ten minutes. The architecture is modular enough to drop into existing projects with minimal changes.

What we learned

  • Midnight's assert in ZK circuits is fundamentally more expressive than Solidity's require. You're not checking a condition, you're proving knowledge of a witness that satisfies one, without ever revealing the witness itself.
  • The persistentHash + pad pattern for domain separation is essential, not optional. Without it, the same secret can be replayed across contracts.
  • Client-side proof generation is the only honest way to build private dApps. The moment a server touches the proof inputs, the zero-knowledge guarantee is broken.
  • Acknowledging limitations clearly and having a documented fix is more valuable than hiding them. The localStorage trade-off is intentional and the production path is already designed.

Business value and what's next

Every lending protocol, DEX, and yield aggregator needs to know users are legitimate. Today they get that by exposing wallet addresses, balances, and full transaction histories. ShadowKey gives DeFi protocols compliance without surveillance. Users prove they're accredited. The protocol gets yes or no. Zero financial data exposed.

Authentication is a solved problem in Web2 and a completely unsolved one in Web3. ShadowKey is the missing primitive.

  • npm install @shadowkey/auth: a single-package integration that gives any Midnight dApp private login in under ten minutes.
  • Merkle-tree user registry: scale from thousands to 10M+ users without linear on-chain storage costs.
  • Nullifier sessions: prevent nonce replay by tracking spent nullifiers, closing the one remaining attack surface in the current design.
  • Wallet-signature-derived secrets: eliminate localStorage entirely, which is already designed and ready to implement.
  • Build Club application: we are applying to the Midnight Build Club accelerator to fund this as an infrastructure company. The addressable market is every dApp that needs to know who its users are without storing who they are.

Privacy infrastructure is not a feature. It is a category. ShadowKey is the foundation.


Built with

  • Compact — Midnight's zero-knowledge smart contract language
  • Midnight Network — privacy-preserving blockchain with client-side ZK proofs
  • React 19 — frontend framework
  • TypeScript — type-safe development
  • Vite — build tool and dev server
  • Tailwind CSS — utility-first styling
  • shadcn/ui — accessible UI components
  • Framer Motion — page transitions and animations
  • Lace Wallet — Midnight-native browser wallet
  • Docker — local proof server containerization
  • GitHub — public repository and version control

Built With

Share this project:

Updates