diff --git a/.gitignore b/.gitignore index 34c04cd..db94870 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ package-lock.json *.sym # generated contracts at test time -generated +solidity *.sol *.calldata *.inputs diff --git a/Cargo.toml b/Cargo.toml index d915d60..7bb10d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ ark-crypto-primitives = { version = "^0.4.0", default-features = false, features ark-std = "0.4.0" color-eyre = "0.6.2" num-bigint = "0.4.3" -folding-schemes = { git = "https://github.com/privacy-scaling-explorations/sonobe", package = "folding-schemes", branch="fix/circom-frontend", features=["light-test"]} +folding-schemes = { git = "https://github.com/privacy-scaling-explorations/sonobe", package = "folding-schemes", features=["light-test"]} solidity-verifiers = { git = "https://github.com/privacy-scaling-explorations/sonobe", package = "solidity-verifiers"} serde = "1.0.198" serde_json = "1.0.116" @@ -38,3 +38,5 @@ rand = "0.8.5" # this will no longer be needed) ark-bn254 = { git = "https://github.com/arnaucube/ark-curves-cherry-picked", branch="cherry-pick"} ark-grumpkin = { git = "https://github.com/arnaucube/ark-curves-cherry-picked", branch="cherry-pick"} +ark-circom = { git = "https://github.com/arnaucube/circom-compat" } +ark-r1cs-std = { git = "https://github.com/winderica/r1cs-std", branch="cherry-pick" } diff --git a/README.md b/README.md index 3cb1466..1aedf61 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ Repo to test a more complex [Circom](https://github.com/iden3/circom) circuit wi Proves a chain of keccak256 hashes, using the [vocdoni/keccak256-circom](https://github.com/vocdoni/keccak256-circom) circuit. -> WIP - -assuming rust and circom have been installed: +Assuming rust and circom have been installed: - `./compile-circuit.sh` - `cargo test --release -- --nocapture` diff --git a/src/lib.rs b/src/lib.rs index 390a6b0..57ca511 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ mod tests { use ark_std::Zero; use std::path::PathBuf; + use std::rc::Rc; use std::time::Instant; use folding_schemes::{ @@ -37,11 +38,10 @@ mod tests { get_r1cs, Nova, ProverParams, VerifierParams, }, frontend::{circom::CircomFCircuit, FCircuit}, - transcript::poseidon::poseidon_test_config, - Decider, FoldingScheme, + transcript::poseidon::poseidon_canonical_config, + Decider, Error, FoldingScheme, }; use solidity_verifiers::{ - evm::{compile_solidity, Evm}, utils::get_function_selector_for_nova_cyclefold_verifier, verifiers::nova_cyclefold::get_decider_template_for_cyclefold_decider, NovaCycleFoldVerifierKey, @@ -59,7 +59,7 @@ mod tests { KZGVerifierKey, ) { let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_test_config::(); + let poseidon_config = poseidon_canonical_config::(); // get the CM & CF_CM len let (r1cs, cf_r1cs) = @@ -135,16 +135,31 @@ mod tests { let b = f_vec_to_bits(v); BigInteger256::from_bits_le(&b).to_bytes_le() } + fn bytes_to_f_vec(b: Vec) -> Result, Error> { + use num_bigint::BigUint; + let bi = BigUint::from_bytes_le(&b); + let bi = BigInteger256::try_from(bi).unwrap(); + let bits = bi.to_bits_le(); + Ok(bits + .iter() + .map(|&e| if e == true { F::one() } else { F::zero() }) + .collect()) + } // function to compute the next state of the folding via rust-native code (not Circom). Used to // check the Circom values. use tiny_keccak::{Hasher, Keccak}; - fn rust_native_step(z_i: [u8; 32]) -> [u8; 32] { + fn rust_native_step( + _i: usize, + z_i: Vec, + _external_inputs: Vec, + ) -> Result, Error> { + let b = f_vec_to_bytes(z_i.to_vec()); let mut h = Keccak::v256(); - h.update(&z_i); + h.update(&b); let mut z_i1 = [0u8; 32]; h.finalize(&mut z_i1); - z_i1 + bytes_to_f_vec(z_i1.to_vec()) } #[test] @@ -158,7 +173,30 @@ mod tests { let wasm_path = PathBuf::from("./circuit/keccak-chain_js/keccak-chain.wasm"); let f_circuit_params = (r1cs_path, wasm_path, 32 * 8, 0); - let f_circuit = CircomFCircuit::::new(f_circuit_params).unwrap(); + let mut f_circuit = CircomFCircuit::::new(f_circuit_params).unwrap(); + // Note (optional): for more speed, we can set a custom rust-native logic, which will be + // used for the `step_native` method instead of extracting the values from the circom + // witness: + f_circuit.set_custom_step_native(Rc::new(rust_native_step)); + + // ---------------- + // Sanity check + // check that the f_circuit produces valid R1CS constraints + use ark_r1cs_std::alloc::AllocVar; + use ark_r1cs_std::fields::fp::FpVar; + use ark_r1cs_std::R1CSVar; + use ark_relations::r1cs::ConstraintSystem; + let cs = ConstraintSystem::::new_ref(); + let z_0_var = Vec::>::new_witness(cs.clone(), || Ok(z_0.clone())).unwrap(); + let z_1_var = f_circuit + .generate_step_constraints(cs.clone(), 1, z_0_var, vec![]) + .unwrap(); + // check z_1_var against the native z_1 + let z_1_native = f_circuit.step_native(1, z_0.clone(), vec![]).unwrap(); + assert_eq!(z_1_var.value().unwrap(), z_1_native); + // check that the constraint system is satisfied + assert!(cs.is_satisfied().unwrap()); + // ---------------- let (fs_prover_params, kzg_vk, g16_pk, g16_vk) = init_ivc_and_decider_params::>(f_circuit.clone()); @@ -186,18 +224,16 @@ mod tests { println!("Nova::prove_step {}: {:?}", nova.i, start.elapsed()); } - // perform the hash chain natively in rust - let z_0_bytes: [u8; 32] = [0u8; 32]; - let z_1_bytes = rust_native_step(z_0_bytes); - let z_2_bytes = rust_native_step(z_1_bytes); - let z_3_bytes = rust_native_step(z_2_bytes); - - // check that the value of the last folding state (nova.z_i) computed through folding, is equal to the natively - // computed hash using the rust_native_step method - let nova_z_i = f_vec_to_bytes(nova.z_i.clone()); - assert_eq!(nova_z_i, z_3_bytes); + // perform the hash chain natively in rust (which uses a rust Keccak256 library) + let z_1 = rust_native_step(0, z_0.clone(), vec![]).unwrap(); + let z_2 = rust_native_step(0, z_1, vec![]).unwrap(); + let z_3 = rust_native_step(0, z_2, vec![]).unwrap(); + // check that the value of the last folding state (nova.z_i) computed through folding, is + // equal to the natively computed hash using the rust_native_step method + assert_eq!(nova.z_i, z_3); - /* + // ---------------- + // Sanity check // The following lines contain a sanity check that checks the IVC proof (before going into // the zkSNARK proof) let verifier_params = VerifierParams:: { @@ -216,7 +252,7 @@ mod tests { cyclefold_instance, ) .unwrap(); - */ + // ---------------- let rng = rand::rngs::OsRng; let start = Instant::now(); @@ -263,24 +299,35 @@ mod tests { // generate the solidity code let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk); + /* + * Note: since we're proving the Keccak256 (ie. 32 byte size, 256 bits), the number of + * inputs is too big for the contract. In a real world use case we would convert the binary + * representation into a couple of field elements which would be inputs of the Decider + * circuit, and in-circuit we would obtain the binary representation to be used for the + * final proof check. + * + * The following code is commented out for that reason. // verify the proof against the solidity code in the EVM + use solidity_verifiers::evm::{compile_solidity, Evm}; let nova_cyclefold_verifier_bytecode = compile_solidity(&decider_solidity_code, "NovaDecider"); let mut evm = Evm::default(); let verifier_address = evm.create(nova_cyclefold_verifier_bytecode); let (_, output) = evm.call(verifier_address, calldata.clone()); assert_eq!(*output.last().unwrap(), 1); + */ // save smart contract and the calldata println!("storing nova-verifier.sol and the calldata into files"); use std::fs; + fs::create_dir_all("./solidity").unwrap(); fs::write( - "./examples/nova-verifier.sol", + "./solidity/nova-verifier.sol", decider_solidity_code.clone(), ) .unwrap(); - fs::write("./examples/solidity-calldata.calldata", calldata.clone()).unwrap(); + fs::write("./solidity/solidity-calldata.calldata", calldata.clone()).unwrap(); let s = solidity_verifiers::utils::get_formatted_calldata(calldata.clone()); - fs::write("./examples/solidity-calldata.inputs", s.join(",\n")).expect(""); + fs::write("./solidity/solidity-calldata.inputs", s.join(",\n")).expect(""); } }