circuits/test/utils/
general.rs

1use num_bigint::{BigInt, BigUint};
2use std::{ops::AddAssign, path::PathBuf};
3use zkhash::poseidon2::{
4    poseidon2::Poseidon2,
5    poseidon2_instance_bn256::{
6        POSEIDON2_BN256_PARAMS_2, POSEIDON2_BN256_PARAMS_3, POSEIDON2_BN256_PARAMS_4,
7    },
8};
9
10use zkhash::{
11    ark_ff::{BigInteger, PrimeField},
12    fields::bn256::FpBN256 as Scalar,
13};
14
15/// Poseidon2 hash of two field elements using optimized compression mode
16///
17/// # Arguments
18///
19/// * `left` - First field element to hash
20/// * `right` - Second field element to hash
21///
22/// # Returns
23///
24/// Returns the first element of the permutation result after adding the inputs.
25pub fn poseidon2_compression(left: Scalar, right: Scalar) -> Scalar {
26    let h = Poseidon2::new(&POSEIDON2_BN256_PARAMS_2);
27    let mut perm = h.permutation(&[left, right]);
28    perm[0].add_assign(&left);
29    perm[1].add_assign(&right);
30    perm[0] // By default, we truncate to one element
31}
32
33/// Poseidon2 hash of 2 field elements (t = 3, r=2, c=1)
34///
35/// Performs a Poseidon2 permutation on two field elements with an optional
36/// domain separator, returning the first element (state[0]).
37///
38/// # Arguments
39///
40/// * `a` - First field element to hash
41/// * `b` - Second field element to hash
42/// * `dom_sep` - Optional domain separator (uses 0 if None)
43///
44/// # Returns
45///
46/// Returns the first lane (state[0]) of the permutation result.
47pub fn poseidon2_hash2(a: Scalar, b: Scalar, dom_sep: Option<Scalar>) -> Scalar {
48    let h = Poseidon2::new(&POSEIDON2_BN256_PARAMS_3);
49    let perm: Vec<Scalar>;
50    if let Some(dom_sep) = dom_sep {
51        perm = h.permutation(&[a, b, dom_sep]);
52    } else {
53        perm = h.permutation(&[a, b, Scalar::from(0)]);
54    }
55    perm[0]
56}
57
58/// Poseidon2 hash of 3 field elements (t = 4, r=3, c=1)
59///
60/// Performs a Poseidon2 permutation on three field elements with an optional
61/// domain separator, returning the first element (state[0]).
62///
63/// # Arguments
64///
65/// * `a` - First field element to hash
66/// * `b` - Second field element to hash
67/// * `c` - Third field element to hash
68/// * `dom_sep` - Optional domain separator (uses 0 if None)
69///
70/// # Returns
71///
72/// Returns the first element (state[0]) of the permutation result.
73pub fn poseidon2_hash3(a: Scalar, b: Scalar, c: Scalar, dom_sep: Option<Scalar>) -> Scalar {
74    let h = Poseidon2::new(&POSEIDON2_BN256_PARAMS_4);
75    let perm: Vec<Scalar>;
76    if let Some(dom_sep) = dom_sep {
77        perm = h.permutation(&[a, b, c, dom_sep]);
78    } else {
79        perm = h.permutation(&[a, b, c, Scalar::from(0)]);
80    }
81    perm[0]
82}
83
84/// Convert a field `Scalar` into a signed `BigInt`
85///  
86/// # Arguments
87///
88/// * `s` - Field element scalar to convert
89///
90/// # Returns
91///
92/// Returns the scalar as a signed `BigInt` value.
93pub fn scalar_to_bigint(s: Scalar) -> BigInt {
94    let bi = s.into_bigint();
95    let bytes_le = bi.to_bytes_le();
96    let u = BigUint::from_bytes_le(&bytes_le);
97    BigInt::from(u)
98}
99
100/// Load the compiled WASM and R1CS artifacts for a circuit by name.
101/// This expects files to be located under the `CIRCUIT_OUT_DIR` tree
102/// as produced by the build system.
103///
104/// # Arguments
105///
106/// * `name` - Name of the circuit (without file extension)
107///
108/// # Returns
109///
110/// Returns `Ok((wasm_path, r1cs_path))` if both files exist, or an error
111/// if either file is not found at the expected location.
112pub fn load_artifacts(name: &str) -> anyhow::Result<(PathBuf, PathBuf)> {
113    let out_dir = PathBuf::from(env!("CIRCUIT_OUT_DIR"));
114    let wasm = out_dir.join(format!("wasm/{name}_js/{name}.wasm"));
115    let r1cs = out_dir.join(format!("{name}.r1cs"));
116    anyhow::ensure!(wasm.exists(), "WASM file not found at {}", wasm.display());
117    anyhow::ensure!(r1cs.exists(), "R1CS file not found at {}", r1cs.display());
118    Ok((wasm, r1cs))
119}