prover/
crypto.rs

1//! Cryptographic utilities for input preparation
2//!
3//! Provides Poseidon2 hashing and key derivation functions matching
4//! the Circom circuit implementations.
5
6use crate::serialization::{bytes_to_scalar, scalar_to_bytes, scalar_to_hex};
7use alloc::{string::String, vec, vec::Vec};
8use core::ops::Add;
9use wasm_bindgen::prelude::*;
10use zkhash::{
11    fields::bn256::FpBN256 as Scalar,
12    poseidon2::{
13        poseidon2::Poseidon2,
14        poseidon2_instance_bn256::{
15            POSEIDON2_BN256_PARAMS_2, POSEIDON2_BN256_PARAMS_3, POSEIDON2_BN256_PARAMS_4,
16        },
17    },
18};
19
20// Useful constants
21/// BN256 modulus as Big Endian bytes
22pub const BN256_MOD_BYTES: [u8; 32] = [
23    48, 100, 78, 114, 225, 49, 160, 41, 184, 80, 69, 182, 129, 129, 88, 93, 40, 51, 232, 72, 121,
24    185, 112, 145, 67, 225, 245, 147, 240, 0, 0, 1,
25];
26
27/// Zero leaf value as Big Endian bytes
28pub const ZERO_LEAF_BYTES: [u8; 32] = [
29    37, 48, 34, 136, 219, 153, 53, 3, 68, 151, 65, 131, 206, 49, 13, 99, 181, 58, 187, 158, 240,
30    248, 87, 87, 83, 238, 211, 110, 1, 24, 249, 206,
31];
32
33/// Poseidon2 hash with 2 inputs and optional domain separation (t=3, r=2, c=1)
34///
35/// This is the core hash function used throughout the crate for merkle trees
36/// and other cryptographic operations.
37pub(crate) fn poseidon2_hash2_internal(a: Scalar, b: Scalar, domain: Option<Scalar>) -> Scalar {
38    let poseidon2 = Poseidon2::new(&POSEIDON2_BN256_PARAMS_3);
39    let input = match domain {
40        Some(d) => vec![a, b, d],
41        None => vec![a, b, Scalar::from(0u64)],
42    };
43    let perm = poseidon2.permutation(&input);
44    perm[0]
45}
46
47/// Poseidon2 hash with 3 inputs and optional domain separation
48///
49/// Used for leaf hashing in sparse merkle trees and commitment generation.
50pub(crate) fn poseidon2_hash3_internal(
51    a: Scalar,
52    b: Scalar,
53    c: Scalar,
54    domain: Option<Scalar>,
55) -> Scalar {
56    let poseidon2 = Poseidon2::new(&POSEIDON2_BN256_PARAMS_4);
57    let input = match domain {
58        Some(d) => vec![a, b, c, d],
59        None => vec![a, b, c, Scalar::from(0u64)],
60    };
61    let perm = poseidon2.permutation(&input);
62    perm[0]
63}
64
65/// Poseidon2 compression (2 inputs, no domain separation)
66///
67/// Used for internal nodes in merkle trees.
68pub(crate) fn poseidon2_compression(left: Scalar, right: Scalar) -> Scalar {
69    let poseidon2 = Poseidon2::new(&POSEIDON2_BN256_PARAMS_2);
70    let input = [left, right];
71    let perm = poseidon2.permutation(&input);
72    // Feed-forward: add inputs back to permutation output
73    perm[0].add(input[0])
74}
75
76/// Poseidon2 hash with 2 inputs as compression mode
77
78#[wasm_bindgen]
79pub fn poseidon2_compression_wasm(input0: &[u8], input1: &[u8]) -> Result<Vec<u8>, JsValue> {
80    let a = bytes_to_scalar(input0)?;
81    let b = bytes_to_scalar(input1)?;
82
83    let result = poseidon2_compression(a, b);
84    Ok(scalar_to_bytes(&result))
85}
86
87/// Poseidon2 hash with 2 inputs and domain separation
88///
89/// Matches the Circom Poseidon2(2) template
90#[wasm_bindgen]
91pub fn poseidon2_hash2(
92    input0: &[u8],
93    input1: &[u8],
94    domain_separation: u8,
95) -> Result<Vec<u8>, JsValue> {
96    let a = bytes_to_scalar(input0)?;
97    let b = bytes_to_scalar(input1)?;
98    let domain = Scalar::from(domain_separation);
99
100    let result = poseidon2_hash2_internal(a, b, Some(domain));
101    Ok(scalar_to_bytes(&result))
102}
103
104/// Derive public key from private key
105///
106/// publicKey = Poseidon2(privateKey, 0, domain=0x03)
107#[wasm_bindgen]
108pub fn derive_public_key(private_key: &[u8]) -> Result<Vec<u8>, JsValue> {
109    let sk = bytes_to_scalar(private_key)?;
110    let pk = derive_public_key_internal(sk);
111    Ok(scalar_to_bytes(&pk))
112}
113
114/// Derive public key and return as hex string (for JS BigInt)
115#[wasm_bindgen]
116pub fn derive_public_key_hex(private_key: &[u8]) -> Result<String, JsValue> {
117    let sk = bytes_to_scalar(private_key)?;
118    let pk = derive_public_key_internal(sk);
119    Ok(scalar_to_hex(&pk))
120}
121
122/// Compute commitment: hash(amount, publicKey, blinding)
123///
124/// Uses domain separation 0x01 for leaf commitments
125#[wasm_bindgen]
126pub fn compute_commitment(
127    amount: &[u8],
128    public_key: &[u8],
129    blinding: &[u8],
130) -> Result<Vec<u8>, JsValue> {
131    let amt = bytes_to_scalar(amount)?;
132    let pk = bytes_to_scalar(public_key)?;
133    let blind = bytes_to_scalar(blinding)?;
134
135    // Domain separation 0x01 for leaf commitment
136    let commitment = poseidon2_hash3_internal(amt, pk, blind, Some(Scalar::from(1u64)));
137    Ok(scalar_to_bytes(&commitment))
138}
139
140/// Compute signature: hash(privateKey, commitment, merklePath)
141#[wasm_bindgen]
142pub fn compute_signature(
143    private_key: &[u8],
144    commitment: &[u8],
145    merkle_path: &[u8],
146) -> Result<Vec<u8>, JsValue> {
147    let sk = bytes_to_scalar(private_key)?;
148    let comm = bytes_to_scalar(commitment)?;
149    let path = bytes_to_scalar(merkle_path)?;
150
151    let sig = poseidon2_hash3_internal(sk, comm, path, Some(Scalar::from(4u64)));
152    Ok(scalar_to_bytes(&sig))
153}
154
155/// Compute nullifier: hash(commitment, pathIndices, signature)
156///
157/// Uses domain separation 0x02 for nullifiers
158#[wasm_bindgen]
159pub fn compute_nullifier(
160    commitment: &[u8],
161    path_indices: &[u8],
162    signature: &[u8],
163) -> Result<Vec<u8>, JsValue> {
164    let comm = bytes_to_scalar(commitment)?;
165    let indices = bytes_to_scalar(path_indices)?;
166    let sig = bytes_to_scalar(signature)?;
167
168    // Domain separation 0x02 for nullifier
169    let nullifier = poseidon2_hash3_internal(comm, indices, sig, Some(Scalar::from(2u64)));
170    Ok(scalar_to_bytes(&nullifier))
171}
172
173/// Returns BN256 modulus as Big Endian bytes
174#[wasm_bindgen]
175pub fn bn256_modulus() -> Vec<u8> {
176    BN256_MOD_BYTES.to_vec()
177}
178
179/// Returns Zero leaf used in merkle trees as Big Endian bytes
180#[wasm_bindgen]
181pub fn zero_leaf() -> Vec<u8> {
182    ZERO_LEAF_BYTES.to_vec()
183}
184
185/// Internal public key derivation
186/// Uses domain separation 0x03 (matching Keypair template in circom)
187pub(crate) fn derive_public_key_internal(private_key: Scalar) -> Scalar {
188    poseidon2_hash2_internal(private_key, Scalar::from(0u64), Some(Scalar::from(3u64)))
189}