From fe798a8141aae65222b3901cd4d56db23af5acf7 Mon Sep 17 00:00:00 2001 From: Thomas Chataigner Date: Fri, 23 Feb 2024 17:24:58 +0100 Subject: [PATCH] feat(circom): wip working on evaluation values --- Cargo.toml | 1 + examples/circom.rs | 174 +++++++++++++++++++++++++++------- examples/keccak.rs | 126 +++++++++++++++--------- src/coprocessor/circom/mod.rs | 1 + src/coprocessor/mod.rs | 1 + src/lem/circuit.rs | 5 +- 6 files changed, 228 insertions(+), 80 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2adea55f79..fae479bdcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,6 +95,7 @@ criterion = "0.5" expect-test = "1.4.1" hex = "0.4.3" statrs = "0.16.0" +tiny-keccak = { version = "2.0.2", features = ["keccak"] } tap = "1.0.1" tempfile = { workspace = true } diff --git a/examples/circom.rs b/examples/circom.rs index 4b797f7c5e..20c4e11144 100644 --- a/examples/circom.rs +++ b/examples/circom.rs @@ -38,20 +38,27 @@ use lurk::circuit::gadgets::pointer::AllocatedPtr; #[cfg(not(target_arch = "wasm32"))] use lurk::coprocessor::circom::non_wasm::CircomCoprocessor; -use halo2curves::bn256::Fr as Bn; use lurk::eval::lang::Lang; use lurk::field::LurkField; use lurk::lem::{pointers::Ptr, store::Store}; -use lurk::proof::{nova::NovaProver, Prover, RecursiveSNARKTrait}; +use lurk::proof::RecursiveSNARKTrait; use lurk::public_parameters::{ instance::{Instance, Kind}, - public_params, + supernova_public_params, }; use lurk::Symbol; use lurk_macros::Coproc; +use pasta_curves::pallas::{Scalar as Fr, Scalar}; use anyhow::Result; use circom_scotia::r1cs::CircomInput; +use ff::PrimeField; +use lurk::lem::eval::{ + evaluate, make_cprocs_funcs_from_lang, make_eval_step_from_config, EvalConfig, +}; +use lurk::proof::supernova::SuperNovaProver; +use lurk::state::user_sym; +use sha2::{Digest, Sha256}; const REDUCTION_COUNT: usize = 1; @@ -72,24 +79,97 @@ impl CircomSha256 { } } +pub fn from_vec_u32(arr: Vec) -> F { + let mut res = F::ZERO; + let radix = F::from(0x0001_0000_0000_u64); + for &val in &arr { + res = res * radix + F::from(u64::from(val)); + } + res +} + +/// Helper function to convert a vector of [`u32`] values to a [`PrimeField`] element. Assumes little endian representation. +/// Compatible with Circom version 2. +pub fn to_vec_u32(f: F) -> Result> { + let repr = F::to_repr(&f); + let repr = repr.as_ref(); + + let (pre, res, suf) = unsafe { repr.align_to::() }; + + if !pre.is_empty() || !suf.is_empty() { + panic!("nope") + } + + Ok(res.into()) +} + impl CircomGadget for CircomSha256 { fn reference(&self) -> &CircomGadgetReference { &self.reference } fn into_circom_input(self, input: &[AllocatedPtr]) -> Vec> { - dbg!(input.len()); - dbg!(input.get(0).unwrap().hash().get_value()); - dbg!(input.get(0).unwrap().hash().get_variable().get_unchecked()); - dbg!(input.get(1).unwrap().hash().get_value()); - dbg!(input.get(1).unwrap().hash().get_variable().get_unchecked()); - // TODO: actually use the lurk inputs - let a = CircomInput::new("a".into(), vec![F::ZERO]); - let b = CircomInput::new("b".into(), vec![F::ZERO]); + dbg!("------------------------------INTO CIRCOM INPUT--------------------------------"); + dbg!(input + .get(0) + .unwrap() + .hash() + .get_value() + .or_else(|| Some(F::ZERO)) + .unwrap() + .to_bytes()); + dbg!(input + .get(1) + .unwrap() + .hash() + .get_value() + .or_else(|| Some(F::ZERO)) + .unwrap() + .to_bytes()); + let a = CircomInput::new( + "a".into(), + vec![input + .get(0) + .unwrap() + .hash() + .get_value() + .or_else(|| Some(F::ZERO)) + .unwrap()], + ); + let b = CircomInput::new( + "b".into(), + vec![input + .get(1) + .unwrap() + .hash() + .get_value() + .or_else(|| Some(F::ZERO)) + .unwrap()], + ); vec![a, b] } - fn evaluate_simple(&self, s: &Store, _args: &[Ptr]) -> Ptr { + fn evaluate_simple(&self, s: &Store, args: &[Ptr]) -> Ptr { + dbg!("------------------------------EVALUATE SIMPLE--------------------------------"); + + let a_ptr = &args[0]; + let b_ptr = &args[1]; + let a_scalar = *s.hash_ptr(a_ptr).value(); + let b_scalar = *s.hash_ptr(b_ptr).value(); + + dbg!(s.fetch_string(a_ptr)); + dbg!(s.fetch_string(b_ptr)); + + // Create a Sha256 object + let mut hasher = Sha256::new(); + + // Write input message + hasher.update([a_scalar.to_bytes(), b_scalar.to_bytes()].concat()); + + // Read hash digest and consume hasher + let result: Vec = hasher.finalize().to_vec(); + dbg!(F::from_bytes(&result)); + // TODO hash to get proper ptr // TODO: actually use the lurk inputs s.num( F::from_str_vartime( @@ -105,7 +185,7 @@ impl CircomGadget for CircomSha256 { } #[derive(Clone, Debug, Coproc)] -enum Sha256Coproc { +enum CircomCoproc { SC(CircomCoprocessor>), } @@ -114,50 +194,74 @@ enum Sha256Coproc { /// `cargo run --release --example circom` fn main() { let store = &Store::default(); - let sym_str = Symbol::new(&[".circom_sha256_2"], false); // two inputs + let circom_sym = user_sym(&format!("circom_sha256_2")); - let circom_sha256: CircomSha256 = CircomSha256::new(0).unwrap(); - let mut lang = Lang::>::new(); + let expr = "(circom_sha256_2 \"a\" \"b\")".to_string(); + let ptr = store.read_with_default_state(&expr).unwrap(); - lang.add_coprocessor(sym_str, CircomCoprocessor::new(circom_sha256)); - let lang_rc = Arc::new(lang); + let circom_sha256: CircomSha256 = CircomSha256::new(0).unwrap(); + let mut lang = Lang::>::new(); - let expr = "(.circom_sha256_2 \"b\" \"a\")".to_string(); - let ptr = store.read_with_default_state(&expr).unwrap(); + lang.add_coprocessor(circom_sym, CircomCoprocessor::new(circom_sha256)); + let lang_rc = Arc::new(lang.clone()); - let nova_prover = NovaProver::>::new(REDUCTION_COUNT, lang_rc.clone()); + let lurk_step = make_eval_step_from_config(&EvalConfig::new_nivc(&lang)); + let cprocs = make_cprocs_funcs_from_lang(&lang); + let frames = evaluate(Some((&lurk_step, &cprocs, &lang)), ptr, store, 1000).unwrap(); - println!("Setting up public parameters..."); + // TODO is reduction count alright + let supernova_prover = SuperNovaProver::>::new(10, lang_rc.clone()); let pp_start = Instant::now(); - let instance = Instance::new(REDUCTION_COUNT, lang_rc, true, Kind::NovaPublicParams); - let pp = public_params(&instance).unwrap(); - let pp_end = pp_start.elapsed(); - println!("Public parameters took {pp_end:?}"); + println!("Setting up running claim parameters (rc = 10)..."); - println!("Beginning proof step..."); + let instance_primary = Instance::new(10, lang_rc, true, Kind::SuperNovaAuxParams); + let pp = supernova_public_params(&instance_primary).unwrap(); + let pp_end = pp_start.elapsed(); + println!("Running claim parameters took {:?}", pp_end); + + println!("Beginning proof step..."); let proof_start = Instant::now(); - let (proof, z0, zi, _num_steps) = nova_prover - .evaluate_and_prove(&pp, ptr, store.intern_empty_env(), store, 10000) - .unwrap(); + let (proof, z0, zi, _num_steps) = tracing_texray::examine(tracing::info_span!("bang!")) + .in_scope(|| { + supernova_prover + .prove_from_frames(&pp, &frames, store) + .unwrap() + }); let proof_end = proof_start.elapsed(); - println!("Proofs took {proof_end:?}"); + println!("Proofs took {:?}", proof_end); println!("Verifying proof..."); let verify_start = Instant::now(); - let res = proof.verify(&pp, &z0, &zi).unwrap(); + assert!(proof.verify(&pp, &z0, &zi).unwrap()); let verify_end = verify_start.elapsed(); - println!("Verify took {verify_end:?}"); + println!("Verify took {:?}", verify_end); + + println!("Compressing proof.."); + let compress_start = Instant::now(); + let compressed_proof = proof.compress(&pp).unwrap(); + let compress_end = compress_start.elapsed(); + + println!("Compression took {:?}", compress_end); + + let buf = bincode::serialize(&compressed_proof).unwrap(); + println!("proof size : {:}B", buf.len()); + + let compressed_verify_start = Instant::now(); + let res = compressed_proof.verify(&pp, &z0, &zi).unwrap(); + let compressed_verify_end = compressed_verify_start.elapsed(); + + println!("Final verification took {:?}", compressed_verify_end); if res { println!( - "Congratulations! You proved and verified a CIRCOM-SHA256 hash calculation in {:?} time!", - pp_end + proof_end + verify_end + "Congratulations! You proved, verified, compressed, and verified (again!) an NIVC SHA256 hash calculation in {:?} time!", + verify_end + proof_end + verify_end + compress_end ); } } diff --git a/examples/keccak.rs b/examples/keccak.rs index bd26b7eb20..50cdccafee 100644 --- a/examples/keccak.rs +++ b/examples/keccak.rs @@ -17,12 +17,14 @@ //! //! Hooray! Now we can use a [CircomKeccak] coprocessor just like a normal one. +use circom_scotia::r1cs::CircomInput; use std::fmt::Debug; +use std::hash::Hash; use std::marker::PhantomData; use std::sync::Arc; use std::time::Instant; -use lurk::circuit::gadgets::circom::CircomGadget; +use lurk::circuit::gadgets::circom::{CircomGadget, CircomGadgetReference}; use lurk::circuit::gadgets::pointer::AllocatedPtr; use lurk::lem::multiframe::MultiFrame; @@ -31,15 +33,22 @@ use lurk::coprocessor::circom::non_wasm::CircomCoprocessor; use lurk::eval::lang::Lang; use lurk::field::LurkField; +use lurk::lem::eval::{ + evaluate, make_cprocs_funcs_from_lang, make_eval_step_from_config, EvalConfig, +}; +use lurk::lem::pointers::RawPtr; use lurk::lem::{pointers::Ptr, store::Store}; +use lurk::proof::supernova::SuperNovaProver; use lurk::proof::{nova::NovaProver, Prover, RecursiveSNARKTrait}; use lurk::public_parameters::{ instance::{Instance, Kind}, - public_params, + public_params, supernova_public_params, }; +use lurk::state::user_sym; use lurk::Symbol; use lurk_macros::Coproc; use pasta_curves::pallas::Scalar as Fr; +use tiny_keccak::{Hasher, Keccak}; const REDUCTION_COUNT: usize = 1; @@ -47,6 +56,7 @@ const REDUCTION_COUNT: usize = 1; pub struct CircomKeccak { _n: usize, pub(crate) _p: PhantomData, + reference: CircomGadgetReference, } impl CircomKeccak { @@ -54,21 +64,22 @@ impl CircomKeccak { CircomKeccak { _n: n, _p: PhantomData, + reference: CircomGadgetReference::new("lurk-lab/keccak-circom-gadget").unwrap(), } } } impl CircomGadget for CircomKeccak { - fn reference(&self) -> &str { - "lurk-lab/keccak-circom-gadget" + fn reference(&self) -> &CircomGadgetReference { + &self.reference } fn version(&self) -> Option<&str> { Some("v0.1.0") } - fn into_circom_input(self, input: &[AllocatedPtr]) -> Vec<(String, Vec)> { - dbg!(input); + fn into_circom_input(self, input: &[AllocatedPtr]) -> Vec> { + dbg!(input.get(0).unwrap().hash().get_value()); // TODO: actually use the lurk inputs let input_bytes = [ 116, 101, 115, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -77,7 +88,7 @@ impl CircomGadget for CircomKeccak { let input_bits = bytes_to_bits(&input_bytes); - vec![( + vec![CircomInput::new( "in".into(), input_bits.clone().iter().map(|_| F::ZERO).collect(), )] @@ -87,19 +98,23 @@ impl CircomGadget for CircomKeccak { )]*/ } - fn evaluate_simple(&self, s: &Store, _args: &[Ptr]) -> Ptr { - // TODO: actually use the lurk inputs - let _expected_output = [ - 37, 17, 98, 135, 161, 178, 88, 97, 125, 150, 143, 65, 228, 211, 170, 133, 153, 9, 88, - 212, 4, 212, 175, 238, 249, 210, 214, 116, 170, 85, 45, 21, - ]; + fn evaluate_simple(&self, s: &Store, args: &[Ptr]) -> Ptr { + let string_ptr = &args[0]; + let string_scalar = *s.hash_ptr(string_ptr).value(); + dbg!(string_scalar); + let string_scalar_bytes = string_scalar.to_bytes(); - s.num( - F::from_str_vartime( - "55165702627807990590530466439275329993482327026534454077267643456", - ) - .unwrap(), - ) + let mut hasher = Keccak::v256(); + let mut output_bytes = [0u8; 32]; + hasher.update(&string_scalar_bytes); + + hasher.finalize(&mut output_bytes); + + s.num(F::from_bytes(&output_bytes).unwrap()) + } + + fn arity(&self) -> usize { + 1 } } @@ -109,52 +124,75 @@ enum KeccakCoproc { } fn main() { - let store = &Store::::default(); - let sym_str = Symbol::new(&["circom_keccak"], false); // two inputs - let circom_keccak: CircomKeccak = CircomKeccak::new(0); - let mut lang = Lang::>::new(); - lang.add_coprocessor(sym_str, CircomCoprocessor::new(circom_keccak)); - let lang_rc = Arc::new(lang); + let store = &Store::default(); + let circom_sym = user_sym(&format!("circom_keccak")); - let expr = "(cons 'circom_keccak \"aaa\")".to_string(); + let expr = "(circom_keccak \"test\")".to_string(); let ptr = store.read_with_default_state(&expr).unwrap(); - let nova_prover = NovaProver::, MultiFrame<'_, _, _>>::new( - REDUCTION_COUNT, - lang_rc.clone(), - ); + let circom_sha256: CircomKeccak = CircomKeccak::new(0); + let mut lang = Lang::>::new(); + + lang.add_coprocessor(circom_sym, CircomCoprocessor::new(circom_sha256)); + let lang_rc = Arc::new(lang.clone()); + + let lurk_step = make_eval_step_from_config(&EvalConfig::new_nivc(&lang)); + let cprocs = make_cprocs_funcs_from_lang(&lang); + let frames = evaluate(Some((&lurk_step, &cprocs, &lang)), ptr, store, 1000).unwrap(); - println!("Setting up public parameters..."); + // TODO is reduction count alright + let supernova_prover = SuperNovaProver::>::new(10, lang_rc.clone()); let pp_start = Instant::now(); - let instance = Instance::new(REDUCTION_COUNT, lang_rc, true, Kind::NovaPublicParams); - let pp = public_params::<_, _, MultiFrame<'_, _, _>>(&instance).unwrap(); - let pp_end = pp_start.elapsed(); - println!("Public parameters took {pp_end:?}"); + println!("Setting up running claim parameters (rc = 10)..."); - println!("Beginning proof step..."); + let instance_primary = Instance::new(10, lang_rc, true, Kind::SuperNovaAuxParams); + let pp = supernova_public_params(&instance_primary).unwrap(); + let pp_end = pp_start.elapsed(); + println!("Running claim parameters took {:?}", pp_end); + + println!("Beginning proof step..."); let proof_start = Instant::now(); - let (proof, z0, zi, _num_steps) = nova_prover - .evaluate_and_prove(&pp, ptr, store.intern_nil(), store, 10000) - .unwrap(); + let (proof, z0, zi, _num_steps) = tracing_texray::examine(tracing::info_span!("bang!")) + .in_scope(|| { + supernova_prover + .prove_from_frames(&pp, &frames, store) + .unwrap() + }); let proof_end = proof_start.elapsed(); - println!("Proofs took {proof_end:?}"); + println!("Proofs took {:?}", proof_end); println!("Verifying proof..."); let verify_start = Instant::now(); - let res = proof.verify(&pp, &z0, &zi).unwrap(); + assert!(proof.verify(&pp, &z0, &zi).unwrap()); let verify_end = verify_start.elapsed(); - println!("Verify took {verify_end:?}"); + println!("Verify took {:?}", verify_end); + + println!("Compressing proof.."); + let compress_start = Instant::now(); + let compressed_proof = proof.compress(&pp).unwrap(); + let compress_end = compress_start.elapsed(); + + println!("Compression took {:?}", compress_end); + + let buf = bincode::serialize(&compressed_proof).unwrap(); + println!("proof size : {:}B", buf.len()); + + let compressed_verify_start = Instant::now(); + let res = compressed_proof.verify(&pp, &z0, &zi).unwrap(); + let compressed_verify_end = compressed_verify_start.elapsed(); + + println!("Final verification took {:?}", compressed_verify_end); if res { println!( - "Congratulations! You proved and verified a Keccak hash calculation in {:?} time!", - pp_end + proof_end + verify_end + "Congratulations! You proved, verified, compressed, and verified (again!) an NIVC SHA256 hash calculation in {:?} time!", + verify_end + proof_end + verify_end + compress_end ); } } diff --git a/src/coprocessor/circom/mod.rs b/src/coprocessor/circom/mod.rs index b76a67d052..97aac106eb 100644 --- a/src/coprocessor/circom/mod.rs +++ b/src/coprocessor/circom/mod.rs @@ -9,6 +9,7 @@ pub mod error; pub mod non_wasm { use core::fmt::Debug; use std::fs; + use std::hash::Hash; use std::io::Write; use ansi_term::Colour::Red; diff --git a/src/coprocessor/mod.rs b/src/coprocessor/mod.rs index abcdd1b903..c2476ac8f2 100644 --- a/src/coprocessor/mod.rs +++ b/src/coprocessor/mod.rs @@ -44,6 +44,7 @@ pub trait Coprocessor: Clone + Debug + Sync + Send + CoCircuit } fn evaluate(&self, s: &Store, args: &[Ptr], env: &Ptr, cont: &Ptr) -> Vec { + dbg!("COPROCESSOR EVALUATE"); vec![self.evaluate_simple(s, args), *env, *cont] } diff --git a/src/lem/circuit.rs b/src/lem/circuit.rs index 869b9a3dc7..c06655585b 100644 --- a/src/lem/circuit.rs +++ b/src/lem/circuit.rs @@ -597,7 +597,6 @@ fn synthesize_block, C: Coprocessor>( if cproc.has_circuit() { // call the coprocessor's synthesize and then make sure that // the output matches the data collected during interpretation - dbg!(&inp); let inp_ptrs = bound_allocations.get_many_ptr(inp)?; let synthesize_output = cproc.synthesize_internal( &mut cs.namespace(|| format!("Coprocessor {sym}")), @@ -610,8 +609,12 @@ fn synthesize_block, C: Coprocessor>( bail!("Incompatible output length for coprocessor {sym}") } for ((i, var), ptr) in out.iter().enumerate().zip(synthesize_output) { + // TODO COMPARE COPROC RESULT CIRCUIT + if not_dummy_and_not_blank { let z_ptr = &collected_z_ptrs[i]; + dbg!(ptr.hash().get_value()); + dbg!(*z_ptr.value()); if ptr.tag().get_value() != Some(z_ptr.tag_field()) || ptr.hash().get_value() != Some(*z_ptr.value()) {