HIGH - Can completely break privacy guarantees
Detects usage of predictable on-chain data (ledger timestamp, sequence numbers) as secret input to ZK commitment or nullifier construction. An attacker can predict or brute-force these values, breaking the privacy or uniqueness guarantee.
// ❌ BAD: Ledger timestamp as commitment secret
let secret = env.ledger().timestamp();
let commitment = poseidon_hash(&env, &[secret.into(), amount.into()]);The attacker can observe the ledger timestamp and reconstruct the commitment, revealing the hidden amount.
// ✅ GOOD: User-supplied cryptographic secret
pub fn create_note(env: Env, secret: BytesN<32>, amount: u64) {
// User provides cryptographically random secret (e.g., from secure wallet)
let commitment = poseidon_hash(&env, &[secret, amount.into()]);
env.storage().set(&COMMITMENTS_KEY, commitment);
}// ✅ GOOD: Proper CSPRNG
pub fn generate_secret(env: Env) -> u64 {
// Use env.prng() which provides cryptographically secure randomness
let secret = env.prng().gen_range(0..u64::MAX);
secret
}// ✅ GOOD: Combine user input with contract-generated entropy
pub fn create_note_hybrid(env: Env, user_seed: BytesN<32>, amount: u64) {
let contract_entropy = env.prng().gen::<[u8; 32]>();
let combined_secret = keccak256(&env, &[user_seed, contract_entropy.into()]);
let commitment = poseidon_hash(&env, &[combined_secret, amount.into()]);
env.storage().set(&COMMITMENTS_KEY, commitment);
}ZK systems rely on secrets being unpredictable. Predictable randomness:
- Breaks privacy: Attacker can reconstruct commitments by trying all possible timestamp values
- Enables double-spending: Attacker can predict nullifiers before they're published
- Defeats purpose: The ZK scheme provides no security if secrets are known
- Undermines anonymity: Linkability analysis becomes trivial
Consider a private payment system:
- Alice creates a commitment using ledger timestamp
T - Commitment =
H(T, 100 tokens) - Bob observes the transaction occurred at time
T - Bob brute-forces:
H(T, amount)for all reasonable amounts - Bob discovers Alice transferred 100 tokens, defeating privacy
This rule uses taint analysis:
-
Taint sources: Track variables derived from:
env.ledger().timestamp()env.ledger().sequence()env.ledger().protocol_version()- Other low-entropy sources
-
Taint propagation: Follow data flow through:
- Variable assignments
- Function calls
- Arithmetic operations
- Type conversions
-
Sink detection: Flag when tainted data reaches:
poseidon_hash,pedersen_commitnullifier_derive,commitment_create- Functions with
commitmentornullifierin name
-
Context verification: Ensure it's a ZK context, not general hashing
- S018 (unsafe-prng): General PRNG security (reuses taint infrastructure)
- Z001: Missing nullifier checks
- Z003: Public input leakage
- #1192: ZK taint-tracking infrastructure foundation
- #1194: Commitment/nullifier sink function identification
- Zero-Knowledge Privacy Best Practices
- Predictable Randomness in Smart Contracts
- Commitment Scheme Security Requirements
See test fixtures in contracts/fixtures/finding-codes/z002_insecure_randomness.rs for comprehensive examples.