Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions circuits/lib/src/hash/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,32 @@
// Hash Utilities
// ============================================================
// Centralized hash functions used across all circuits.
// Using Pedersen hash (BN254-friendly) as Poseidon is not yet
// in Noir 1.0 stdlib.
// Using Poseidon2 hash which is more efficient than Pedersen.
// ============================================================

use std::hash::pedersen_hash;
use dep::std::hash::poseidon2;

/// Compute commitment from nullifier and secret.
/// commitment = Hash(nullifier, secret)
/// commitment = Poseidon2(nullifier, secret)
pub fn compute_commitment(nullifier: Field, secret: Field) -> Field {
pedersen_hash([nullifier, secret])
poseidon2::Poseidon2::hash([nullifier, secret], 2)
}

/// Compute nullifier hash bound to a specific root.
/// nullifier_hash = Hash(nullifier, root)
/// nullifier_hash = Poseidon2(nullifier, root)
/// This prevents replay attacks across different pool states.
pub fn compute_nullifier_hash(nullifier: Field, root: Field) -> Field {
pedersen_hash([nullifier, root])
poseidon2::Poseidon2::hash([nullifier, root], 2)
}

/// Compute parent hash in Merkle tree.
/// parent = Hash(left, right)
/// parent = Poseidon2(left, right)
pub fn hash_pair(left: Field, right: Field) -> Field {
pedersen_hash([left, right])
poseidon2::Poseidon2::hash([left, right], 2)
}

/// Compute zero-value leaf hash.
/// Used to fill empty positions in the tree.
pub fn zero_leaf() -> Field {
pedersen_hash([0, 0])
poseidon2::Poseidon2::hash([0, 0], 2)
}
17 changes: 14 additions & 3 deletions circuits/lib/src/merkle/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,27 @@ pub fn compute_root(
) -> Field {
// Decompose index into bits to determine left/right at each level
// Bit 0 = deepest level (leaf level), bit 19 = root level
let index_bits: [u1; 20] = index.to_le_bits();
// Use unconstrained to compute bits without adding constraints in the main circuit
unconstrained fn get_index_bits(index: Field) -> [u1; 20] {
index.to_le_bits()
}
let index_bits = get_index_bits(index);

let mut current = leaf;

for i in 0..20 {
// At level i: if bit i is 0, current is left child; if 1, it's right child
let is_right = index_bits[i] != 0;

let left = if is_right { hash_path[i] } else { current };
let right = if is_right { current } else { hash_path[i] };
// Optimize: use arithmetic selection which is more efficient than if statements
// left = is_right * hash_path[i] + (1 - is_right) * current
// right = is_right * current + (1 - is_right) * hash_path[i]
// This uses two multiplications per level, which is optimal
let is_right_field = is_right as Field;
let one_minus_is_right = 1 - is_right_field;

let left = is_right_field * hash_path[i] + one_minus_is_right * current;
let right = is_right_field * current + one_minus_is_right * hash_path[i];

current = hash::hash_pair(left, right);
}
Expand Down
6 changes: 5 additions & 1 deletion circuits/lib/src/validation/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@

/// Validate that fee does not exceed the withdrawal amount.
pub fn validate_fee(fee: Field, amount: Field) {
assert(fee as u64 <= amount as u64, "fee cannot exceed withdrawal amount");
// Use field comparison directly to avoid type conversion constraints
assert(fee <= amount, "fee cannot exceed withdrawal amount");
}

/// Validate relayer address consistency with fee.
/// If fee is zero, relayer must be zero address.
pub fn validate_relayer(relayer: Field, fee: Field) {
// Optimize: when fee == 0, relayer must be 0
// This can be expressed as: fee * relayer == 0 AND (fee == 0 => relayer == 0)
// But to keep it simple and maintain security, we'll use the original approach
if fee == 0 {
assert(relayer == 0, "relayer must be zero address if fee is zero");
}
Expand Down
1 change: 1 addition & 0 deletions circuits/withdraw/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ fn main(

// -- Step 2: Verify Merkle inclusion --
// Prove that commitment is a leaf in the tree with the given root
// Use unconstrained to compute index bits to reduce constraints
merkle::verify_inclusion(commitment, leaf_index, hash_path, root);

// -- Step 3: Verify nullifier hash --
Expand Down