arnaucube
arnaucube/hash-chain-sonobePublic Self-hosted repository
update to last Sonobe version
arnaucube root@arnaucube.com
Tue, 11 Jun 2024 15:52:20 +0200
commit

5c3c0e099aeb127ba73c17497c6dc80534f1cc57

parent

f038c9540b0b17bf1bdda1e24c867336ae30d0ca

4 files changed, 75 insertions(+), 28 deletions(-)

jump to
M .gitignore → .gitignore

@@ -9,7 +9,7 @@ *.r1cs

*.sym # generated contracts at test time -generated +solidity *.sol *.calldata *.inputs
M Cargo.toml → Cargo.toml

@@ -24,7 +24,7 @@ ] }

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 @@ # bn254::constraints & grumpkin for v0.4.0 (once arkworks v0.5.0 is released

# 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" }
M README.md → README.md

@@ -4,8 +4,6 @@ Repo to test a more complex [Circom](https://github.com/iden3/circom) circuit with [Sonobe](https://github.com/privacy-scaling-explorations/sonobe).

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`
M src/lib.rs → src/lib.rs

@@ -23,6 +23,7 @@ use ark_ff::{BigInteger, BigInteger256, PrimeField};

use ark_std::Zero; use std::path::PathBuf; + use std::rc::Rc; use std::time::Instant; use folding_schemes::{

@@ -37,11 +38,10 @@ decider_eth_circuit::DeciderEthCircuit,

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 @@ VerifierParams<G1, G2>,

KZGVerifierKey<Bn254>, ) { let mut rng = ark_std::test_rng(); - let poseidon_config = poseidon_test_config::<Fr>(); + let poseidon_config = poseidon_canonical_config::<Fr>(); // get the CM & CF_CM len let (r1cs, cf_r1cs) =

@@ -135,16 +135,31 @@ fn f_vec_to_bytes<F: PrimeField>(v: Vec<F>) -> Vec<u8> {

let b = f_vec_to_bits(v); BigInteger256::from_bits_le(&b).to_bytes_le() } + fn bytes_to_f_vec<F: PrimeField>(b: Vec<u8>) -> Result<Vec<F>, 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<F: PrimeField>( + _i: usize, + z_i: Vec<F>, + _external_inputs: Vec<F>, + ) -> Result<Vec<F>, 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 @@ let r1cs_path = PathBuf::from("./circuit/keccak-chain.r1cs");

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::<Fr>::new(f_circuit_params).unwrap(); + let mut f_circuit = CircomFCircuit::<Fr>::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::<Fr>::new_ref(); + let z_0_var = Vec::<FpVar<Fr>>::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::<CircomFCircuit<Fr>>(f_circuit.clone());

@@ -186,18 +224,16 @@ nova.prove_step(vec![]).unwrap();

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::<G1, G2> {

@@ -216,7 +252,7 @@ incoming_instance,

cyclefold_instance, ) .unwrap(); - */ + // ---------------- let rng = rand::rngs::OsRng; let start = Instant::now();

@@ -263,24 +299,35 @@

// 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(""); } }