diff --git a/README.md b/README.md new file mode 100644 index 0000000..83d0b40 --- /dev/null +++ b/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 + diff --git a/src/fcircuit.rs b/src/fcircuit.rs index bed45f8..cf538bd 100644 --- a/src/fcircuit.rs +++ b/src/fcircuit.rs @@ -83,7 +83,7 @@ where degree = degree.clone() + FpVar::::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)] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5155b11 --- /dev/null +++ b/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; +type FS = Nova, Pedersen, false>; + +#[wasm_bindgen] +extern "C" { + fn alert(s: &str); +} + +#[wasm_bindgen] +pub fn gen_params() -> Vec { + let mut rng = OsRng; + let poseidon_config = poseidon_canonical_config::(); + + 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 { + let mut rng = OsRng; + let poseidon_config = poseidon_canonical_config::(); + + let sigs: Vec> = gen_signatures(&mut rng, &poseidon_config, n_steps); + let b: Vec> = sigs.iter().map(|&s| s.to_bytes()).collect(); + b.iter().map(|s| b64.encode(s)).collect::>() +} + +#[wasm_bindgen] +pub fn fold_sigs(params: Vec, sigs_pks: Vec) -> String { + dbg("starting fold_sigs (rust)".to_string()); + dbg(format!("received sigs: {:?}", sigs_pks)); + + let poseidon_config = poseidon_canonical_config::(); + + // parse sigs_pks + let b: Vec> = sigs_pks.iter().map(|s| b64.decode(s).unwrap()).collect(); + let pks_sigs: Vec> = + 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 = [pk0.clone(), pk0, vec![Fr::zero()]].concat(); + + type FC = EthDosCircuit; + 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::(); + + 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 = , Pedersen, 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() +} diff --git a/src/signature.rs b/src/signature.rs index 694dc25..63aa18c 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -33,10 +33,10 @@ impl Default for SigPk { } } impl SigPk { - pub fn to_bytes(&self) -> Vec { + pub fn to_bytes(self) -> Vec { 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) -> Self { let u_point_size = C::Affine::generator().serialized_size(ark_serialize::Compress::No); @@ -138,20 +138,13 @@ where 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::(&poseidon_config, &msg) + .sign::(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 } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..8197dda --- /dev/null +++ b/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 +}