Skip to content

Latest commit

 

History

History
102 lines (74 loc) · 3.32 KB

File metadata and controls

102 lines (74 loc) · 3.32 KB

Z011: Commitment-Scheme Reuse Without Domain Separation

Severity

MEDIUM - Enables cross-context collision attacks

Description

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.

Vulnerable Pattern

// ❌ 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 nullifiers

Attack: If hash(amount, secret) == hash(note_id, secret'), attacker can use a commitment as a nullifier or vice versa, breaking privacy assumptions.

Secure Pattern

// ✅ 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
    ])
}

Why This Matters

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.

Detection Method

  1. Find all commitment/hash construction sites across project
  2. Group by hash function used (poseidon, pedersen, keccak256, etc.)
  3. For each group with multiple call sites:
    • Check if first argument is a constant (domain separator)
    • Verify constants differ between call sites
  4. Flag groups with reuse and no domain separation

Heuristics for Commitment Detection

Functions with names containing:

  • commitment, commit, nullifier
  • leaf, node (merkle trees)
  • hash when return value stored/checked

Best Practices

  1. Use constants: Define domain separators as named constants
  2. Document purpose: Comment what each domain represents
  3. Use first slot: Place domain separator as first hash input
  4. Never reuse: Each semantic purpose gets unique domain
  5. Consider strings: Some prefer hash("NULLIFIER", ...) for clarity

Related Rules

  • Z002: Insecure randomness (commitment construction)
  • Z001: Missing nullifier (where this applies)

Dependencies

  • #1192, #1194: ZK infrastructure

References

Examples

See contracts/fixtures/finding-codes/z011_commitment_domain_separation.rs