circuits/test/utils/
transaction.rs

1use zkhash::fields::bn256::FpBN256 as Scalar;
2
3use super::general::poseidon2_hash3;
4
5/// Compute a commitment using Poseidon2 hash
6///
7/// Computes `commitment = Poseidon2(amount, pubkey, blinding)` with
8/// domain separation value 1.
9///
10/// # Arguments
11///
12/// * `amount` - Transaction amount
13/// * `pubkey` - Public key
14/// * `blinding` - Blinding factor
15///
16/// # Returns
17///
18/// Returns the commitment scalar value.
19#[inline]
20pub fn commitment(amount: Scalar, pubkey: Scalar, blinding: Scalar) -> Scalar {
21    poseidon2_hash3(amount, pubkey, blinding, Some(Scalar::from(1))) // We use 1 as domain separation for Commitment
22}
23
24/// Compute a nullifier using Poseidon2 hash
25///
26/// Computes `nullifier = Poseidon2(commitment, pathIndices, signature)` with
27/// domain separation value 2.
28///
29/// # Arguments
30///
31/// * `commitment` - Commitment scalar value
32/// * `path_indices` - Merkle path indices
33/// * `signature` - Signature scalar value
34///
35/// # Returns
36///
37/// Returns the nullifier scalar value.
38#[inline]
39pub(crate) fn nullifier(commitment: Scalar, path_indices: Scalar, signature: Scalar) -> Scalar {
40    poseidon2_hash3(commitment, path_indices, signature, Some(Scalar::from(2))) // We use 2 as domain separation for Nullifier
41}
42
43// --- tiny deterministic RNG (xorshift64) ---
44#[derive(Clone)]
45struct Rng64(u64);
46impl Rng64 {
47    fn new(seed: u64) -> Self {
48        Self(seed)
49    }
50
51    fn next(&mut self) -> u64 {
52        let mut x = self.0;
53        x ^= x << 13;
54        x ^= x >> 7;
55        x ^= x << 17;
56        self.0 = x;
57        x
58    }
59}
60
61/// Generate a random-looking commitment (not tied to a real private key)
62///
63/// Creates a commitment using random values
64/// Suitable for filler leaves in test scenarios.
65///
66/// # Arguments
67///
68/// * `rng` - Mutable reference to the random number generator
69///
70/// # Returns
71///
72/// Returns a randomly generated commitment scalar value.
73fn rand_commitment(rng: &mut Rng64) -> Scalar {
74    let amount = Scalar::from(rng.next() % 1_000_000); // keep small-ish
75    let pubkey = Scalar::from(rng.next());
76    let blinding = Scalar::from(rng.next());
77    // Reuse your commitment function
78    commitment(amount, pubkey, blinding)
79}
80
81/// Build a pre-populated leaves vector of length 2^levels
82///
83/// Creates a vector of leaves for a Merkle tree, pre-populating some positions
84/// with random commitments while excluding specified indices. The excluded
85/// indices are reserved for overwriting with test case inputs.
86///
87/// # Arguments
88///
89/// * `levels` - Number of tree levels
90/// * `seed` - Seed value for the random number generator
91/// * `exclude_indices` - Indices to leave empty (will be overwritten with test
92///   inputs)
93/// * `fill_count` - Number of random commitments to place in the tree for
94///   testing cases
95///
96/// # Returns
97///
98/// Returns a vector of scalar values representing the leaves, with zeros for
99/// empty positions and random commitments for filled positions.
100pub fn prepopulated_leaves(
101    levels: usize,
102    seed: u64,
103    exclude_indices: &[usize],
104    fill_count: usize,
105) -> Vec<Scalar> {
106    let n = 1usize << levels;
107    let mut leaves = vec![Scalar::from(0u64); n];
108
109    let capacity = n.saturating_sub(exclude_indices.len());
110    assert!(
111        fill_count <= capacity,
112        "prepopulated_leaves: fill_count ({fill_count}) exceeds available capacity ({capacity}), causing an infinite loop",
113    );
114
115    let mut rng = Rng64::new(seed);
116    let mut placed = 0usize;
117
118    while placed < fill_count {
119        let idx = usize::try_from(rng.next())
120            .expect("cast to usize failed in prepopulated_leaves")
121            .checked_rem(n)
122            .expect("n must not be zero");
123        if exclude_indices.contains(&idx) || leaves[idx] != Scalar::from(0u64) {
124            continue;
125        }
126
127        leaves[idx] = rand_commitment(&mut rng);
128        placed = placed.checked_add(1).expect("placed counter overflowed");
129    }
130
131    leaves
132}