Privacy Patterns for Midnight
Design and implement privacy-preserving applications using zero-knowledge proofs.
Core Privacy Model
| Concept | Description | Visibility |
|---|---|---|
| Public | Ledger state | Everyone |
| Private | Circuit inputs | Only prover |
| Witness | Prover-provided data | Only prover |
| Disclosed | Explicitly revealed | Everyone |
Reference Files
| Topic | Resource |
|---|---|
| Zero-Knowledge Basics | references/zk-fundamentals.md |
| Commitment Schemes | references/commitments.md |
| Nullifier Patterns | references/nullifiers.md |
| Selective Disclosure | references/selective-disclosure.md |
Pattern Overview
1. Commitment Scheme
Hide a value while binding to it:
compact
commitment = persistentCommit(value, randomness); // Later: prove you know the opening
2. Nullifier Pattern
Prevent double-use without revealing identity:
compact
nullifier = transientHash(secret, commitment); // Can only be generated once per commitment
3. Selective Disclosure
Prove properties without revealing data:
compact
// Prove over 18 without revealing actual age disclose(age >= 18); // Only boolean is public
4. Merkle Membership
Prove membership in a set without revealing position:
compact
// Verify path from leaf to root assert verifyMerklePath(leaf, proof, root);
Quick Examples
Private Balance Check
compact
// Only reveal if balance is sufficient, not actual amount
export circuit checkFunds(balance: Uint<64>, required: Uint<64>): Boolean {
return disclose(balance >= required);
}
Anonymous Voting
compact
export circuit vote(voter: Bytes<32>, choice: Boolean): [] {
// Voter identity disclosed (prevents double voting)
hasVoted.insert(voter);
// Choice remains private, only totals change
if (choice) { yesCount = yesCount + 1; }
}
Commitment-Reveal
compact
witness randomness: Field;
// Phase 1: Commit
export circuit commit(value: Uint<64>): Field {
return persistentCommit(value, randomness);
}
// Phase 2: Reveal
export circuit reveal(value: Uint<64>, commitment: Field): [] {
assert persistentCommit(value, randomness) == commitment;
disclose(value);
}
Privacy Best Practices
- •✅ Use
witnessfor data that should never appear on-chain - •✅ Use
persistentCommit(with randomness) to hide values - •✅ Use nullifiers to prevent double-actions
- •✅ Disclose only what's necessary (prefer booleans)
- •❌ Don't store unhashed sensitive data on ledger
- •❌ Don't use predictable randomness in commitments
- •❌ Don't reveal intermediate values unnecessarily
Privacy Levels
text
┌────────────────────────────────────────────────┐ │ Level 0: Fully Public │ │ - All data visible on-chain │ ├────────────────────────────────────────────────┤ │ Level 1: Hidden Values │ │ - Commitments on-chain, values private │ ├────────────────────────────────────────────────┤ │ Level 2: Unlinkable Actions │ │ - Nullifiers prevent linking actions │ ├────────────────────────────────────────────────┤ │ Level 3: Anonymous Membership │ │ - Merkle proofs hide set position │ └────────────────────────────────────────────────┘
When to Use Each Pattern
| Pattern | Use Case |
|---|---|
| Commitment | Sealed bids, hidden votes before reveal |
| Nullifier | Preventing double-spend, one-time tokens |
| Merkle Proof | Membership in allowlist without revealing identity |
| Selective Disclosure | Age verification, credential proofs |