mirror of
https://github.com/vacp2p/zerokit.git
synced 2026-01-09 21:58:06 -05:00
# Bring Back WebAssembly Support for ZeroKit - Update minor versions of all dependencies. - Update documentation to reflect these changes. - ~~Vendor `wasmer` v4.4.0 in [my git repository](https://github.com/vinhtc27/wasmer) for `ark-circom` v0.5.0.~~ - Resolve `wasm-pack` build failures (`os error 2`) caused by a Node.js version mismatch. - Restore the previous CI pipeline for the `rln-wasm` feature and update to the stable toolchain. - ~~Use `ark-circom` with the `wasm` feature for WebAssembly compatibility and the `rln.wasm` file for witness calculation.~~ - ~~Fix dependency issues related to `ark-circom` v0.5.0, which currently uses `wasmer` v4.4.0 and is affected by this [issue](https://github.com/rust-lang/rust/issues/91632#issuecomment-1477914703).~~ - Install WABT with `brew` and `apt-get` instead of cloning to fix `wasm-strip not found` issue in the CI workflow. - Install `wasm-pack` with `curl` instead of using `wasm-pack-action` to fix parse exception error in the CI workflow. - Use the `.wasm` file with JS bindings for witness calculation, which is generated from [`iden3/circom`](https://github.com/iden3/circom) during circuit compilation. This allows witness computation outside RLN instance. - Refactor the `rln` module by moving circuit-related files to the `src/circuit` folder for better organization. - Remove `ark-circom` and `wasmer` by cloning the [CircomReduction](3c95ed98e2/src/circom/qap.rs (L12)) struct and the [read_zkey](3c95ed98e2/src/zkey.rs (L53)) function into the `rln` module, which reduces the repository's build size and speeds up compilation time and the CI workflow duration. - These change also address [#282](https://github.com/vacp2p/zerokit/issues/282) by removing `wasmer` and `wasmer-wasix`, which lack x32 system support. - Benchmark `rln-wasm` with `wasm_bindgen_test`, covering RLN instance creation, key generation, witness calculation, proving, and verification. Also, add them to `v0.6.1` in [benchmark-v0.6.1](https://github.com/vacp2p/zerokit/tree/benchmark-v0.6.1) for comparison. - Add `arkzkey` feature for rln-wasm, including tests, benchmarks, CI workflow updates, and related documentation. - Benchmark rln-wasm in the browser using HTML, covering initialization, RLN instance creation, proving, and verification; fork to the `benchmark-v0.7.0` branch for later use [here](https://github.com/vacp2p/zerokit/tree/benchmark-v0.7.0). - Fix clippy error: "this `repeat().take()` can be written more concisely" on CI workflow for `utils` module. ([error](https://github.com/vacp2p/zerokit/actions/runs/14258579070/job/39965568013)) - Update Makefile.toml to be able to run `make build`, `make test`, and `make bench` from root and inside each modules.
262 lines
8.5 KiB
Rust
262 lines
8.5 KiB
Rust
// This crate provides an implementation to compute the Poseidon hash round constants and MDS matrices.
|
|
|
|
// SECURITY NOTE: The MDS matrices are generated interatively using the Grain LFSR until certain criteria are met.
|
|
// According to the paper, such matrices have to respect some conditions which are checked by 3 different algorithms in the reference implementation.
|
|
// At the moment such algorithms are not implemented, however *for the hardcoded parameters* the first random matrix generated satisfy such conditions.
|
|
// If different parameters are implemented, it should be checked against the reference implementation how many matrices are generated before outputting
|
|
// the right one, and pass this number to the skip_matrices parameter of find_poseidon_ark_and_mds function in order to output the correct one.
|
|
// Poseidon reference implementation: https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/generate_parameters_grain.sage (algorithm_1, algorithm_2, algorithm_3)
|
|
|
|
// The following implementation was adapted from https://github.com/arkworks-rs/sponge/blob/7d9b3a474c9ddb62890014aeaefcb142ac2b3776/src/poseidon/grain_lfsr.rs
|
|
|
|
use ark_ff::PrimeField;
|
|
use num_bigint::BigUint;
|
|
|
|
pub struct PoseidonGrainLFSR {
|
|
pub prime_num_bits: u64,
|
|
pub state: [bool; 80],
|
|
pub head: usize,
|
|
}
|
|
|
|
impl PoseidonGrainLFSR {
|
|
pub fn new(
|
|
is_field: u64,
|
|
is_sbox_an_inverse: u64,
|
|
prime_num_bits: u64,
|
|
state_len: u64,
|
|
num_full_rounds: u64,
|
|
num_partial_rounds: u64,
|
|
) -> Self {
|
|
let mut state = [false; 80];
|
|
|
|
// Only fields are supported for now
|
|
assert!(is_field == 1);
|
|
|
|
// b0, b1 describes the field
|
|
state[1] = is_field == 1;
|
|
|
|
assert!(is_sbox_an_inverse == 0 || is_sbox_an_inverse == 1);
|
|
|
|
// b2, ..., b5 describes the S-BOX
|
|
state[5] = is_sbox_an_inverse == 1;
|
|
|
|
// b6, ..., b17 are the binary representation of n (prime_num_bits)
|
|
{
|
|
let mut cur = prime_num_bits;
|
|
for i in (6..=17).rev() {
|
|
state[i] = cur & 1 == 1;
|
|
cur >>= 1;
|
|
}
|
|
}
|
|
|
|
// b18, ..., b29 are the binary representation of t (state_len, rate + capacity)
|
|
{
|
|
let mut cur = state_len;
|
|
for i in (18..=29).rev() {
|
|
state[i] = cur & 1 == 1;
|
|
cur >>= 1;
|
|
}
|
|
}
|
|
|
|
// b30, ..., b39 are the binary representation of R_F (the number of full rounds)
|
|
{
|
|
let mut cur = num_full_rounds;
|
|
for i in (30..=39).rev() {
|
|
state[i] = cur & 1 == 1;
|
|
cur >>= 1;
|
|
}
|
|
}
|
|
|
|
// b40, ..., b49 are the binary representation of R_P (the number of partial rounds)
|
|
{
|
|
let mut cur = num_partial_rounds;
|
|
for i in (40..=49).rev() {
|
|
state[i] = cur & 1 == 1;
|
|
cur >>= 1;
|
|
}
|
|
}
|
|
|
|
// b50, ..., b79 are set to 1
|
|
for item in state.iter_mut().skip(50) {
|
|
*item = true;
|
|
}
|
|
|
|
let head = 0;
|
|
|
|
let mut res = Self {
|
|
prime_num_bits,
|
|
state,
|
|
head,
|
|
};
|
|
res.init();
|
|
res
|
|
}
|
|
|
|
pub fn get_bits(&mut self, num_bits: usize) -> Vec<bool> {
|
|
let mut res = Vec::new();
|
|
|
|
for _ in 0..num_bits {
|
|
// Obtain the first bit
|
|
let mut new_bit = self.update();
|
|
|
|
// Loop until the first bit is true
|
|
while !new_bit {
|
|
// Discard the second bit
|
|
let _ = self.update();
|
|
// Obtain another first bit
|
|
new_bit = self.update();
|
|
}
|
|
|
|
// Obtain the second bit
|
|
res.push(self.update());
|
|
}
|
|
|
|
res
|
|
}
|
|
|
|
pub fn get_field_elements_rejection_sampling<F: PrimeField>(
|
|
&mut self,
|
|
num_elems: usize,
|
|
) -> Vec<F> {
|
|
assert_eq!(F::MODULUS_BIT_SIZE as u64, self.prime_num_bits);
|
|
let modulus: BigUint = F::MODULUS.into();
|
|
|
|
let mut res = Vec::new();
|
|
for _ in 0..num_elems {
|
|
// Perform rejection sampling
|
|
loop {
|
|
// Obtain n bits and make it most-significant-bit first
|
|
let mut bits = self.get_bits(self.prime_num_bits as usize);
|
|
bits.reverse();
|
|
|
|
let bytes = bits
|
|
.chunks(8)
|
|
.map(|chunk| {
|
|
let mut result = 0u8;
|
|
for (i, bit) in chunk.iter().enumerate() {
|
|
result |= u8::from(*bit) << i
|
|
}
|
|
result
|
|
})
|
|
.collect::<Vec<u8>>();
|
|
|
|
let value = BigUint::from_bytes_le(&bytes);
|
|
|
|
if value < modulus {
|
|
res.push(F::from(value.clone()));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
res
|
|
}
|
|
|
|
pub fn get_field_elements_mod_p<F: PrimeField>(&mut self, num_elems: usize) -> Vec<F> {
|
|
assert_eq!(F::MODULUS_BIT_SIZE as u64, self.prime_num_bits);
|
|
|
|
let mut res = Vec::new();
|
|
for _ in 0..num_elems {
|
|
// Obtain n bits and make it most-significant-bit first
|
|
let mut bits = self.get_bits(self.prime_num_bits as usize);
|
|
bits.reverse();
|
|
|
|
let bytes = bits
|
|
.chunks(8)
|
|
.map(|chunk| {
|
|
let mut result = 0u8;
|
|
for (i, bit) in chunk.iter().enumerate() {
|
|
result |= u8::from(*bit) << i
|
|
}
|
|
result
|
|
})
|
|
.collect::<Vec<u8>>();
|
|
|
|
res.push(F::from_le_bytes_mod_order(&bytes));
|
|
}
|
|
|
|
res
|
|
}
|
|
|
|
#[inline]
|
|
fn update(&mut self) -> bool {
|
|
let new_bit = self.state[(self.head + 62) % 80]
|
|
^ self.state[(self.head + 51) % 80]
|
|
^ self.state[(self.head + 38) % 80]
|
|
^ self.state[(self.head + 23) % 80]
|
|
^ self.state[(self.head + 13) % 80]
|
|
^ self.state[self.head];
|
|
self.state[self.head] = new_bit;
|
|
self.head += 1;
|
|
self.head %= 80;
|
|
|
|
new_bit
|
|
}
|
|
|
|
fn init(&mut self) {
|
|
for _ in 0..160 {
|
|
let new_bit = self.state[(self.head + 62) % 80]
|
|
^ self.state[(self.head + 51) % 80]
|
|
^ self.state[(self.head + 38) % 80]
|
|
^ self.state[(self.head + 23) % 80]
|
|
^ self.state[(self.head + 13) % 80]
|
|
^ self.state[self.head];
|
|
self.state[self.head] = new_bit;
|
|
self.head += 1;
|
|
self.head %= 80;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn find_poseidon_ark_and_mds<F: PrimeField>(
|
|
is_field: u64,
|
|
is_sbox_an_inverse: u64,
|
|
prime_bits: u64,
|
|
rate: usize,
|
|
full_rounds: u64,
|
|
partial_rounds: u64,
|
|
skip_matrices: usize,
|
|
) -> (Vec<F>, Vec<Vec<F>>) {
|
|
let mut lfsr = PoseidonGrainLFSR::new(
|
|
is_field,
|
|
is_sbox_an_inverse,
|
|
prime_bits,
|
|
rate as u64,
|
|
full_rounds,
|
|
partial_rounds,
|
|
);
|
|
|
|
let mut ark = Vec::<F>::with_capacity((full_rounds + partial_rounds) as usize);
|
|
for _ in 0..(full_rounds + partial_rounds) {
|
|
let values = lfsr.get_field_elements_rejection_sampling::<F>(rate);
|
|
for el in values {
|
|
ark.push(el);
|
|
}
|
|
}
|
|
|
|
let mut mds = Vec::<Vec<F>>::with_capacity(rate);
|
|
mds.resize(rate, vec![F::zero(); rate]);
|
|
|
|
// Note that we build the MDS matrix generating 2*rate elements. If the matrix built is not secure (see checks with algorithm 1, 2, 3 in reference implementation)
|
|
// it has to be skipped. Since here we do not implement such algorithm we allow to pass a parameter to skip generations of elements giving unsecure matrixes.
|
|
// At the moment, the skip_matrices parameter has to be generated from the reference implementation and passed to this function
|
|
for _ in 0..skip_matrices {
|
|
let _ = lfsr.get_field_elements_mod_p::<F>(2 * (rate));
|
|
}
|
|
|
|
// a qualifying matrix must satisfy the following requirements
|
|
// - there is no duplication among the elements in x or y
|
|
// - there is no i and j such that x[i] + y[j] = p
|
|
// - the resultant MDS passes all the three tests
|
|
|
|
let xs = lfsr.get_field_elements_mod_p::<F>(rate);
|
|
let ys = lfsr.get_field_elements_mod_p::<F>(rate);
|
|
|
|
for i in 0..(rate) {
|
|
for (j, ys_item) in ys.iter().enumerate().take(rate) {
|
|
mds[i][j] = (xs[i] + ys_item).inverse().unwrap();
|
|
}
|
|
}
|
|
|
|
(ark, mds)
|
|
}
|