plugins/midnight-core-concepts/skills/privacy-patterns/SKILL.md
Use when implementing privacy-preserving logic in Compact, working with hashes, commitments, Merkle trees, nullifier patterns, or keeping data private on-chain.
npx skillsauth add aaronbassett/midnight-knowledgebase midnight-core-concepts:privacy-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
4 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Fundamental rule: Anything passed to a ledger operation is publicly visible, except MerkleTree types. Use these patterns to keep data private.
| Goal | Pattern | Use When | |------|---------|----------| | Hide data, prove later | Hash | Simple verification needed | | Hide data, prevent correlation | Commitment | Same values must look different | | Prove membership secretly | Merkle Tree | Set membership without revealing which | | Single-use tokens | Commitment + Nullifier | Prevent double-spend privately |
Use persistentHash to hide data while allowing later verification.
import { persistentHash } from "std/compact/hashes";
// Store hash of secret
ledger.stored_hash = persistentHash(secret_data);
// Later, prove knowledge by re-hashing
assert persistentHash(provided_data) == ledger.stored_hash;
Use for: Data that won't repeat and has high entropy.
Use persistentCommit when same values must appear different.
import { persistentCommit } from "std/compact/commitments";
// Commitment with randomness prevents correlation
const commitment = persistentCommit(value, randomness);
ledger.stored_commitment = commitment;
Without randomness, commitments to "yes" always look identical. With randomness:
// WRONG: Correlatable
const bad = persistentCommit(vote, Bytes<32>::zero());
// CORRECT: Unlinkable
const good = persistentCommit(vote, fresh_randomness);
// Option 1: Fresh randomness (ideal)
const r = generateRandomness();
// Option 2: Derived from secret key + counter (deterministic)
const r = persistentHash(secret_key, counter);
Use for: Any value that might repeat (votes, bids, choices).
Use MerkleTree to prove set membership without revealing which element.
ledger authorized_keys: MerkleTree<32, Bytes<32>>;
export circuit authenticate(
secret_key: Bytes<32>,
merkle_path: MerkleTreePath<32, Bytes<32>>
): Void {
// Prove key is in tree without revealing which key
const public_key = persistentHash(secret_key);
assert ledger.authorized_keys.member(public_key, merkle_path);
}
| Type | Use Case |
|------|----------|
| MerkleTree<n, T> | Static set, proofs against current root |
| HistoricMerkleTree<n, T> | Frequent insertions, proofs against past roots |
Use HistoricMerkleTree when: Elements added frequently and users need to prove membership against roots from when they obtained their path.
The most powerful pattern: single-use tokens with complete privacy.
commit(value, secret) in Merkle treenullifier = hash(commitment, secret)ledger commitments: MerkleTree<32, Bytes<32>>;
ledger nullifiers: Set<Bytes<32>>;
export circuit spend(
value: Field,
secret: Bytes<32>,
path: MerkleTreePath<32, Bytes<32>>
): Void {
// Reconstruct commitment
const commitment = persistentCommit(value, secret);
// Prove commitment exists in tree
assert ledger.commitments.member(commitment, path);
// Compute and record nullifier
const nullifier = persistentHash(commitment, secret);
assert !ledger.nullifiers.member(nullifier);
ledger.nullifiers.insert(nullifier);
}
This is the foundation of Zerocash and Zswap's shielded UTXOs.
Prove identity without revealing credentials:
export circuit authenticate(secret_key: Bytes<32>): Void {
const public_key = persistentHash(secret_key);
assert public_key == ledger.authorized_key;
// Authorized! secret_key never revealed
}
// WRONG: Same randomness = correlatable
const r = fixed_value;
commit(vote1, r); commit(vote2, r);
// CORRECT: Fresh randomness each time
commit(vote1, random1); commit(vote2, random2);
// WRONG: Only 2 possible values, easily brute-forced
hash(vote); // vote is "yes" or "no"
// CORRECT: Commitment with randomness
persistentCommit(vote, randomness);
// WRONG: This exposes the secret!
ledger.public_field = secret_value;
// CORRECT: Store only commitment
ledger.public_field = persistentCommit(secret_value, randomness);
For detailed technical information:
references/commitment-schemes.md - Pedersen commitments, binding/hiding propertiesreferences/merkle-tree-usage.md - Tree operations, path generation, historic treesWorking patterns:
examples/private-voting.compact - Complete voting with commitment/nullifierexamples/auth-patterns.compact - Authentication without revealing keystools
Use when setting up Midnight development environment, installing Compact compiler and developer tools, configuring proof server, verifying prerequisites, or getting started with Midnight development.
tools
--- name: midnight-tooling:midnight-debugging description: Use when encountering Midnight errors like "compact: command not found", "ERR_UNSUPPORTED_DIR_IMPORT", version mismatches, proof server failures, "@midnight-ntwrk" package errors, or compilation failures. --- # Midnight Environment Debugging Expert knowledge for identifying and resolving common Midnight development toolchain issues. ## Diagnostic Approach When encountering Midnight-related errors, follow this systematic approach: 1.
tools
Use when checking Midnight version compatibility, understanding pragma language_version, verifying compiler and runtime version relationships, or troubleshooting version mismatch errors between Midnight components.
tools
Use when setting up CI/CD for Midnight projects, configuring GitHub Actions for Compact contract compilation, running TypeScript tests in CI, validating version consistency, or automating contract builds.