add WASM bindings
arnaucube git@arnaucube.com
Fri, 27 Dec 2024 19:33:08 +0100
5 files changed,
268 insertions(+),
14 deletions(-)
A
README.md
@@ -0,0 +1,7 @@
+# ethdos-fold + +- run native tests: `cargo test --release -- --nocapture` +- build wasm: `wasm-pack build --target web` +- serve the web: `python -m http.server 8080` + - go to http://127.0.0.1:8080/index.html +
M
src/fcircuit.rs
→
src/fcircuit.rs
@@ -83,7 +83,7 @@ // increment the degree
degree = degree.clone() + FpVar::<F>::one(); let pk_i1_xy = external_inputs.pk.to_constraint_field()?; - Ok(vec![vec![pk_0_x, pk_0_y], pk_i1_xy, vec![degree]].concat()) + Ok([vec![pk_0_x, pk_0_y], pk_i1_xy, vec![degree]].concat()) } } #[cfg(test)]
A
src/lib.rs
@@ -0,0 +1,197 @@
+//! This file contains the WASM bindings. +//! +#![allow(non_snake_case)] +#![allow(dead_code)] +#![allow(non_camel_case_types)] +#![allow(clippy::upper_case_acronyms)] + +use ark_bn254::{Fr, G1Projective as G1}; +use ark_ec::AffineRepr; +use ark_grumpkin::Projective as G2; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::Zero; +use base64::{engine::general_purpose::STANDARD as b64, Engine as _}; +use rand::rngs::OsRng; + +use arkeddsa::ed_on_bn254_twist::{constraints::EdwardsVar, EdwardsProjective}; + +use folding_schemes::{ + commitment::pedersen::Pedersen, + folding::nova::{Nova, PreprocessorParam}, + frontend::FCircuit, + transcript::poseidon::poseidon_canonical_config, + FoldingScheme, +}; + +use crate::fcircuit::EthDosCircuit; +use crate::signature::{gen_signatures, SigPk}; +use crate::utils::{dbg, elapsed, get_time}; + +mod fcircuit; +mod fold_ethdos; +mod signature; +mod utils; + +use wasm_bindgen::prelude::*; + +// define type aliases for the FCircuit (FC) and the FoldingScheme (FS), to avoid writting the +// whole type each time. +type FC = EthDosCircuit<Fr, EdwardsProjective, EdwardsVar>; +type FS = Nova<G1, G2, FC, Pedersen<G1>, Pedersen<G2>, false>; + +#[wasm_bindgen] +extern "C" { + fn alert(s: &str); +} + +#[wasm_bindgen] +pub fn gen_params() -> Vec<String> { + let mut rng = OsRng; + let poseidon_config = poseidon_canonical_config::<Fr>(); + + let f_circuit = FC::new(poseidon_config.clone()).unwrap(); + + let nova_preprocess_params = PreprocessorParam::new(poseidon_config.clone(), f_circuit.clone()); + let start = get_time(); + let nova_params = FS::preprocess(&mut rng, &nova_preprocess_params).unwrap(); + dbg(format!("Nova params generated: {:?}ms", elapsed(start))); + + // serialize + let start = get_time(); + let mut prover_params_serialized = vec![]; + nova_params + .0 + .serialize_compressed(&mut prover_params_serialized) + .unwrap(); + dbg(format!( + "Nova prover params serialized: {:?}ms", + elapsed(start) + )); + + let start = get_time(); + let mut verifier_params_serialized = vec![]; + nova_params + .1 + .serialize_compressed(&mut verifier_params_serialized) + .unwrap(); + dbg(format!( + "Nova prover params serialized: {:?}ms", + elapsed(start) + )); + + vec![ + b64.encode(&prover_params_serialized), + b64.encode(&prover_params_serialized), + ] +} + +#[wasm_bindgen] +pub fn gen_sigs(n_steps: usize) -> Vec<String> { + let mut rng = OsRng; + let poseidon_config = poseidon_canonical_config::<Fr>(); + + let sigs: Vec<SigPk<EdwardsProjective>> = gen_signatures(&mut rng, &poseidon_config, n_steps); + let b: Vec<Vec<u8>> = sigs.iter().map(|&s| s.to_bytes()).collect(); + b.iter().map(|s| b64.encode(s)).collect::<Vec<String>>() +} + +#[wasm_bindgen] +pub fn fold_sigs(params: Vec<String>, sigs_pks: Vec<String>) -> String { + dbg("starting fold_sigs (rust)".to_string()); + dbg(format!("received sigs: {:?}", sigs_pks)); + + let poseidon_config = poseidon_canonical_config::<Fr>(); + + // parse sigs_pks + let b: Vec<Vec<u8>> = sigs_pks.iter().map(|s| b64.decode(s).unwrap()).collect(); + let pks_sigs: Vec<SigPk<EdwardsProjective>> = + b.iter().map(|s| SigPk::from_bytes(s.clone())).collect(); + + // parse params + let start = get_time(); + let pp = FS::pp_deserialize_with_mode( + &mut b64.decode(params[0].clone()).unwrap().as_slice(), + ark_serialize::Compress::Yes, + ark_serialize::Validate::Yes, + poseidon_config.clone(), // fcircuit_params + ) + .unwrap(); + let vp = FS::vp_deserialize_with_mode( + &mut b64.decode(params[1].clone()).unwrap().as_slice(), + ark_serialize::Compress::Yes, + ark_serialize::Validate::Yes, + poseidon_config.clone(), // fcircuit_params + ) + .unwrap(); + let fs_params = (pp, vp); + dbg(format!( + "params (prover & verifier) deserialization: {:?}ms", + elapsed(start) + )); + + // set the initial state + let xy = pks_sigs[0].pk.0.xy().unwrap(); + let pk0 = vec![xy.0, xy.1]; + let z_0: Vec<Fr> = [pk0.clone(), pk0, vec![Fr::zero()]].concat(); + + type FC = EthDosCircuit<Fr, EdwardsProjective, EdwardsVar>; + let f_circuit = FC::new(poseidon_config.clone()).unwrap(); + + // initialize the folding scheme engine, in our case we use Nova + let mut nova = FS::init(&fs_params, f_circuit, z_0.clone()).unwrap(); + let rng = OsRng; + let n_steps = sigs_pks.len(); + + let start_full = get_time(); + #[allow(clippy::needless_range_loop)] + for i in 0..n_steps { + let start = get_time(); + nova.prove_step(rng, pks_sigs[i], None).unwrap(); + dbg(format!( + "Nova::prove_step {}: {:?}ms", + nova.i, + elapsed(start) + )); + } + dbg(format!( + "Nova's all {} steps time: {:?}ms", + n_steps, + elapsed(start_full) + )); + + let ivc_proof = nova.ivc_proof(); + let mut ivc_proof_bytes = vec![]; + ivc_proof + .serialize_compressed(&mut ivc_proof_bytes) + .unwrap(); + + b64.encode(ivc_proof_bytes) +} + +#[wasm_bindgen] +pub fn verify_proof(verifier_params: String, ivc_proof: String) -> String { + let poseidon_config = poseidon_canonical_config::<Fr>(); + + let vp = FS::vp_deserialize_with_mode( + &mut b64.decode(verifier_params.clone()).unwrap().as_slice(), + ark_serialize::Compress::Yes, + ark_serialize::Validate::Yes, + poseidon_config.clone(), // fcircuit_params + ) + .unwrap(); + // let proof = + // FS::IVCProof::deserialize_compressed(b64.decode(ivc_proof).unwrap().as_slice()).unwrap(); + let proof = <Nova<G1, G2, FC, Pedersen<G1>, Pedersen<G2>, false> as FoldingScheme< + G1, + G2, + FC, + >>::IVCProof::deserialize_compressed(b64.decode(ivc_proof).unwrap().as_slice()) + .unwrap(); + + FS::verify( + vp, // Nova's verifier params + proof, + ) + .unwrap(); + "verified".to_string() +}
M
src/signature.rs
→
src/signature.rs
@@ -33,10 +33,10 @@ }
} } impl<C: CurveGroup> SigPk<C> { - pub fn to_bytes(&self) -> Vec<u8> { + pub fn to_bytes(self) -> Vec<u8> { let sig_bytes = self.sig.to_bytes(); let pk_bytes = self.pk.to_bytes(); - vec![sig_bytes, pk_bytes].concat() + [sig_bytes, pk_bytes].concat() } pub fn from_bytes(b: Vec<u8>) -> Self { let u_point_size = C::Affine::generator().serialized_size(ark_serialize::Compress::No);@@ -138,20 +138,13 @@ let sk = SigningKey::<C>::generate::<blake2::Blake2b512>(rng).unwrap();
let pk = sk.public_key(); // if prev_pk!=None, use it, else, set the new pk to it - let prev_pk = if prev_pk.is_some() { - prev_pk.unwrap() - } else { - *pk - }; + let prev_pk = if let Some(v) = prev_pk { v } else { *pk }; let msg = hash_pk(poseidon_config, prev_pk); let sig = sk - .sign::<blake2::Blake2b512>(&poseidon_config, &msg) + .sign::<blake2::Blake2b512>(poseidon_config, &msg) .unwrap(); - pk.verify(&poseidon_config, &msg, &sig).unwrap(); - SigPk { - pk: pk.clone(), - sig, - } + pk.verify(poseidon_config, &msg, &sig).unwrap(); + SigPk { pk: *pk, sig } }
A
src/utils.rs
@@ -0,0 +1,57 @@
+#[cfg(target_arch = "wasm32")] +use web_sys::console; + +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +} + +pub fn dbg(s: String) { + #[cfg(target_arch = "wasm32")] + console::log_1(&s.into()); + + #[cfg(not(target_arch = "wasm32"))] + println!("{}", s); +} + +pub fn get_time() -> u64 { + #[cfg(target_arch = "wasm32")] + let start = get_wasm_time() as u64; + + #[cfg(not(target_arch = "wasm32"))] + let start = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + + start +} + +pub fn elapsed(start: u64) -> u64 { + #[cfg(target_arch = "wasm32")] + let end = get_wasm_time() as u64; + + #[cfg(not(target_arch = "wasm32"))] + let end = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + + end - start +} + +#[cfg(target_arch = "wasm32")] +fn get_wasm_time() -> u64 { + use web_sys::{window, Performance}; + let window = window().expect("should have a window in this context"); + let performance = window + .performance() + .expect("performance should be available"); + performance.now() as u64 +}