MEDIUM - Enables cross-context collision attacks
Detects multiple semantically distinct commitment constructions (e.g., both nullifiers and leaf commitments) using the same hash function without domain-separation tags. This allows cross-context collision attacks where values from one domain can be substituted into another.
// ❌ BAD: Same hash function, no domain separation
fn create_note_commitment(amount: u64, secret: BytesN<32>) -> BytesN<32> {
poseidon_hash(&env, &[amount.into(), secret])
}
fn create_nullifier(note_id: u64, secret: BytesN<32>) -> BytesN<32> {
poseidon_hash(&env, &[note_id.into(), secret]) // Same pattern!
}
// Attacker can find: commitment(X, Y) == nullifier(A, B)
// Causes confusion between note commitments and nullifiersAttack: If hash(amount, secret) == hash(note_id, secret'), attacker can use a commitment as a nullifier or vice versa, breaking privacy assumptions.
// ✅ GOOD: Domain separation tags distinguish contexts
const DOMAIN_NOTE_COMMITMENT: u64 = 0;
const DOMAIN_NULLIFIER: u64 = 1;
const DOMAIN_MERKLE_LEAF: u64 = 2;
fn create_note_commitment(amount: u64, secret: BytesN<32>) -> BytesN<32> {
poseidon_hash(&env, &[
DOMAIN_NOTE_COMMITMENT.into(), // Domain separator
amount.into(),
secret
])
}
fn create_nullifier(note_id: u64, secret: BytesN<32>) -> BytesN<32> {
poseidon_hash(&env, &[
DOMAIN_NULLIFIER.into(), // Different domain
note_id.into(),
secret
])
}Without domain separation:
- Collision attacks: Values from one context accepted in another
- Privacy leaks: Linkability between commitments and nullifiers
- Protocol confusion: Components interact in unintended ways
- Cryptographic weakness: Violates hash function collision-resistance assumptions
This is a cryptographic best practice violation that's easy to miss in reviews.
- Find all commitment/hash construction sites across project
- Group by hash function used (poseidon, pedersen, keccak256, etc.)
- For each group with multiple call sites:
- Check if first argument is a constant (domain separator)
- Verify constants differ between call sites
- Flag groups with reuse and no domain separation
Functions with names containing:
commitment,commit,nullifierleaf,node(merkle trees)hashwhen return value stored/checked
- Use constants: Define domain separators as named constants
- Document purpose: Comment what each domain represents
- Use first slot: Place domain separator as first hash input
- Never reuse: Each semantic purpose gets unique domain
- Consider strings: Some prefer
hash("NULLIFIER", ...)for clarity
- Z002: Insecure randomness (commitment construction)
- Z001: Missing nullifier (where this applies)
- #1192, #1194: ZK infrastructure
See contracts/fixtures/finding-codes/z011_commitment_domain_separation.rs