Area
circuits/shielded_pool/src/main.nr, circuits/compliance/src/main.nr, circuits/disclosure/src/main.nr, circuits/hasher, contracts/pool, contracts/compliance
Problem
Merkle leaves and internal tree nodes both use the same undifferentiated hash2(a, b) (e.g. leaf = hash2(nullifier, secret), internal node = hash2(left, right)). Because commitments are public via get_commitments(), this is the classic "same compression function for leaves and internal nodes" anti-pattern: nothing on-chain or in-circuit distinguishes a genuine deposit leaf from hash2(commitment_i, commitment_{i+1}), a value anyone can compute from two public sibling commitments. The compliance KYC hash (hash2(kyc_preimage, 0)) uses the same undifferentiated form as the nullifier hash, so the pool-nullifier and compliance-KYC hash domains aren't cryptographically separated from each other either. This isn't a currently-known exploitable theft path (an attacker would still need a real deposit at the relevant slot), but it's a real cryptographic hygiene gap worth closing before any external audit or mainnet consideration.
Proposed change
Introduce domain-separated hashing: a distinct tag for leaf hashing vs. internal-node hashing (e.g. hash3(LEAF_DOMAIN, nullifier, secret) for leaves, plain hash2 for internal nodes, or vice versa), and a distinct tag for the KYC-hash construction. This must be changed consistently across circuit, frontend (poseidon2.ts), and contract (soroban_poseidon) call sites, since all three currently have to agree byte-for-byte (per the existing pinned test_poseidon_matches_circuit_zero_hash / test_reconstructed_root_matches_onchain_root tests) — this is a VK-breaking change and needs bb write_vk diff + redeploy, planned deliberately rather than shipped incidentally.
Acceptance Criteria
Open your PR against the dev branch, not main. All active development merges into dev.
Discuss this issue / coordinate work: join the DShield contributor Telegram group: https://t.me/+SiGHH24No9U2MDJk
Area
circuits/shielded_pool/src/main.nr, circuits/compliance/src/main.nr, circuits/disclosure/src/main.nr, circuits/hasher, contracts/pool, contracts/compliance
Problem
Merkle leaves and internal tree nodes both use the same undifferentiated
hash2(a, b)(e.g. leaf =hash2(nullifier, secret), internal node =hash2(left, right)). Because commitments are public viaget_commitments(), this is the classic "same compression function for leaves and internal nodes" anti-pattern: nothing on-chain or in-circuit distinguishes a genuine deposit leaf fromhash2(commitment_i, commitment_{i+1}), a value anyone can compute from two public sibling commitments. The compliance KYC hash (hash2(kyc_preimage, 0)) uses the same undifferentiated form as the nullifier hash, so the pool-nullifier and compliance-KYC hash domains aren't cryptographically separated from each other either. This isn't a currently-known exploitable theft path (an attacker would still need a real deposit at the relevant slot), but it's a real cryptographic hygiene gap worth closing before any external audit or mainnet consideration.Proposed change
Introduce domain-separated hashing: a distinct tag for leaf hashing vs. internal-node hashing (e.g.
hash3(LEAF_DOMAIN, nullifier, secret)for leaves, plainhash2for internal nodes, or vice versa), and a distinct tag for the KYC-hash construction. This must be changed consistently across circuit, frontend (poseidon2.ts), and contract (soroban_poseidon) call sites, since all three currently have to agree byte-for-byte (per the existing pinnedtest_poseidon_matches_circuit_zero_hash/test_reconstructed_root_matches_onchain_roottests) — this is a VK-breaking change and needsbb write_vkdiff + redeploy, planned deliberately rather than shipped incidentally.Acceptance Criteria
just test,pnpm test,cargo test, ornargo testas applicable) pass locally and in CIOpen your PR against the
devbranch, notmain. All active development merges intodev.Discuss this issue / coordinate work: join the DShield contributor Telegram group: https://t.me/+SiGHH24No9U2MDJk