diff --git a/crates/franklin-crypto/src/plonk/circuit/goldilocks/mod.rs b/crates/franklin-crypto/src/plonk/circuit/goldilocks/mod.rs index fad6ba4..c7f75a2 100644 --- a/crates/franklin-crypto/src/plonk/circuit/goldilocks/mod.rs +++ b/crates/franklin-crypto/src/plonk/circuit/goldilocks/mod.rs @@ -46,7 +46,7 @@ impl Hash for GoldilocksField { } } -fn range_check_for_num_bits>(cs: &mut CS, num: &Num, num_bits: usize) -> Result<(), SynthesisError> { +pub fn range_check_for_num_bits>(cs: &mut CS, num: &Num, num_bits: usize) -> Result<(), SynthesisError> { assert!(num_bits % 16 == 0); if let Num::Constant(value) = num { diff --git a/crates/snark-wrapper/Cargo.toml b/crates/snark-wrapper/Cargo.toml index 524e34c..b6f5a70 100644 --- a/crates/snark-wrapper/Cargo.toml +++ b/crates/snark-wrapper/Cargo.toml @@ -12,6 +12,6 @@ description = "ZKsync snark wrapper" [dependencies] rescue_poseidon.workspace = true - +serde = { version = "1", features = ["derive"] } derivative = "2" rand = "0.4" diff --git a/crates/snark-wrapper/src/implementations/poseidon2/mod.rs b/crates/snark-wrapper/src/implementations/poseidon2/mod.rs index 6fbc424..633ab52 100644 --- a/crates/snark-wrapper/src/implementations/poseidon2/mod.rs +++ b/crates/snark-wrapper/src/implementations/poseidon2/mod.rs @@ -15,6 +15,7 @@ use crate::boojum::field::PrimeField as BoojumPrimeField; use derivative::*; +pub mod pow; pub mod transcript; pub mod tree_hasher; diff --git a/crates/snark-wrapper/src/implementations/poseidon2/pow.rs b/crates/snark-wrapper/src/implementations/poseidon2/pow.rs new file mode 100644 index 0000000..68bb3f7 --- /dev/null +++ b/crates/snark-wrapper/src/implementations/poseidon2/pow.rs @@ -0,0 +1,59 @@ +use crate::traits::pow::RecursivePoWRunner; + +use super::*; +use rescue_poseidon::franklin_crypto::bellman::Field; + +pub type ConcretePoseidon2SpongeGadget = CircuitPoseidon2Sponge; + +impl RecursivePoWRunner for ConcretePoseidon2SpongeGadget { + fn verify_from_field_elements>( + cs: &mut CS, + seed: Vec>, + pow_challenge_le_bits: [Boolean; 64], + pow_bits: usize, + ) -> Result<(Boolean, [GoldilocksField; 2]), SynthesisError> { + let mut sponge = ConcretePoseidon2SpongeGadget::new(); + + for el in seed.iter() { + sponge.absorb_single_gl(cs, el)?; + } + + // commit nonce + let mut lc = LinearCombination::zero(); + let mut coeff = E::Fr::one(); + for bit in pow_challenge_le_bits[..32].iter() { + lc.add_assign_boolean_with_coeff(bit, coeff.clone()); + coeff.double(); + } + let low = lc.into_num(cs)?; + let low = GoldilocksField::from_num(cs, low)?; + sponge.absorb_single_gl(cs, &low)?; + + let mut lc = LinearCombination::zero(); + let mut coeff = E::Fr::one(); + for bit in pow_challenge_le_bits[32..].iter() { + lc.add_assign_boolean_with_coeff(bit, coeff.clone()); + coeff.double(); + } + let high = lc.into_num(cs)?; + let high = GoldilocksField::from_num(cs, high)?; + sponge.absorb_single_gl(cs, &high)?; + + // get final pow challenge + let result = sponge.finalize(cs)?[0]; + + // verify that pow challenge has enough zeroes + let allocated_bools = result.into_bits_le(cs, None)?; + + let mut lc = LinearCombination::zero(); + let coeff = E::Fr::one(); + for b in allocated_bools.iter().take(pow_bits) { + lc.add_assign_boolean_with_coeff(b, coeff); + } + let num_zeroes = lc.into_num(cs)?; + let result = num_zeroes.is_zero(cs)?; + Boolean::enforce_equal(cs, &result, &Boolean::constant(true))?; + + Ok((result, [low, high])) + } +} diff --git a/crates/snark-wrapper/src/traits/mod.rs b/crates/snark-wrapper/src/traits/mod.rs index 3007cdc..4f26e03 100644 --- a/crates/snark-wrapper/src/traits/mod.rs +++ b/crates/snark-wrapper/src/traits/mod.rs @@ -7,5 +7,6 @@ use crate::franklin_crypto::plonk::circuit::boolean::Boolean; use crate::franklin_crypto::plonk::circuit::goldilocks::*; pub mod circuit; +pub mod pow; pub mod transcript; pub mod tree_hasher; diff --git a/crates/snark-wrapper/src/traits/pow.rs b/crates/snark-wrapper/src/traits/pow.rs new file mode 100644 index 0000000..80643ba --- /dev/null +++ b/crates/snark-wrapper/src/traits/pow.rs @@ -0,0 +1,23 @@ +use super::*; + +pub trait RecursivePoWRunner { + fn verify_from_field_elements>( + cs: &mut CS, + seed: Vec>, + pow_challenge: [Boolean; 64], + pow_bits: usize, + ) -> Result<(Boolean, [GoldilocksField; 2]), SynthesisError>; +} + +pub struct NoCircuitPow; + +impl RecursivePoWRunner for NoCircuitPow { + fn verify_from_field_elements>( + cs: &mut CS, + seed: Vec>, + pow_challenge: [Boolean; 64], + pow_bits: usize, + ) -> Result<(Boolean, [GoldilocksField; 2]), SynthesisError> { + unimplemented!() + } +} diff --git a/crates/snark-wrapper/src/verifier/fri.rs b/crates/snark-wrapper/src/verifier/fri.rs index c0eab25..12fcaac 100644 --- a/crates/snark-wrapper/src/verifier/fri.rs +++ b/crates/snark-wrapper/src/verifier/fri.rs @@ -1,9 +1,16 @@ use super::*; +use crate::traits::pow::RecursivePoWRunner; use crate::traits::transcript::BoolsBuffer; use crate::verifier_structs::allocated_queries::AllocatedSingleRoundQueries; -pub(crate) fn verify_fri_part + 'static, H: CircuitGLTreeHasher, TR: CircuitGLTranscript>( +pub(crate) fn verify_fri_part< + E: Engine, + CS: ConstraintSystem + 'static, + H: CircuitGLTreeHasher, + TR: CircuitGLTranscript, + POW: RecursivePoWRunner, +>( cs: &mut CS, proof: &AllocatedProof, vk: &AllocatedVerificationKey, @@ -29,20 +36,24 @@ pub(crate) fn verify_fri_part + 'static, H: C transcript.witness_field_elements(cs, &proof.final_fri_monomials[0])?; transcript.witness_field_elements(cs, &proof.final_fri_monomials[1])?; - assert_eq!(constants.new_pow_bits, 0, "PoW not supported yet"); - // if new_pow_bits != 0 { - // log!("Doing PoW verification for {} bits", new_pow_bits); - // // log!("Prover gave challenge 0x{:016x}", proof.pow_challenge); - - // // pull enough challenges from the transcript - // let mut num_challenges = 256 / F::CHAR_BITS; - // if num_challenges % F::CHAR_BITS != 0 { - // num_challenges += 1; - // } - // let _challenges: Vec<_> = transcript.get_multiple_challenges(cs, num_challenges); - - // todo!() - // } + if constants.new_pow_bits != 0 { + // pull enough challenges from the transcript + let mut num_challenges = 256 / GL::CHAR_BITS; + if num_challenges % GL::CHAR_BITS != 0 { + num_challenges += 1; + } + let challenges: Vec<_> = transcript.get_multiple_challenges(cs, num_challenges as usize)?; + let (is_valid, pow_challenge_limbs) = POW::verify_from_field_elements(cs, challenges, proof.pow_challenge_le, constants.new_pow_bits)?; + match is_valid.get_value() { + Some(is_valid) => { + if is_valid == false { + println!("PoW challenge is invalid") + } + } + None => (), + } + transcript.witness_field_elements(cs, &pow_challenge_limbs)?; + } let max_needed_bits = (fixed_parameters.domain_size * fixed_parameters.fri_lde_factor as u64).trailing_zeros() as usize; diff --git a/crates/snark-wrapper/src/verifier/mod.rs b/crates/snark-wrapper/src/verifier/mod.rs index 0f5d3cb..2f46716 100644 --- a/crates/snark-wrapper/src/verifier/mod.rs +++ b/crates/snark-wrapper/src/verifier/mod.rs @@ -24,9 +24,11 @@ use crate::franklin_crypto::plonk::circuit::goldilocks::GoldilocksField; use crate::franklin_crypto::plonk::circuit::linear_combination::LinearCombination; use crate::franklin_crypto::plonk::circuit::Assignment; +use crate::implementations::poseidon2::pow::ConcretePoseidon2SpongeGadget; use crate::traits::circuit::*; use crate::traits::transcript::CircuitGLTranscript; use crate::traits::tree_hasher::CircuitGLTreeHasher; +use crate::traits::*; use crate::verifier_structs::allocated_vk::AllocatedVerificationKey; use crate::verifier_structs::challenges::{ChallengesHolder, EvaluationsHolder}; use crate::verifier_structs::constants::ConstantsHolder; @@ -39,10 +41,11 @@ pub(crate) mod utils; use first_step::*; use fri::*; +use pow::RecursivePoWRunner; use quotient_contributions::*; use utils::*; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, serde::Serialize)] pub struct WrapperCircuit< E: Engine, HS: TreeHasher, @@ -91,7 +94,7 @@ impl< let proof: AllocatedProof = AllocatedProof::allocate_from_witness(cs, &self.witness, &verifier, &fixed_parameters, &proof_config)?; // Verify proof - let correct = crate::verifier::verify::(cs, self.transcript_params.clone(), &proof_config, &proof, &verifier, &fixed_parameters, &vk)?; + let correct = crate::verifier::verify::>(cs, self.transcript_params.clone(), &proof_config, &proof, &verifier, &fixed_parameters, &vk)?; Boolean::enforce_equal(cs, &correct, &Boolean::constant(true))?; // Aggregate PI @@ -101,13 +104,57 @@ impl< } } -pub fn verify< +#[derive(Clone)] +pub struct WrapperCircuitWidth3NoLookupNoCustomGate< E: Engine, - CS: ConstraintSystem + 'static, - H: CircuitGLTreeHasher, + HS: TreeHasher, + H: CircuitGLTreeHasher, NonCircuitSimulator = HS>, TR: CircuitGLTranscript, - // TODO POW ->( + PWF: ProofWrapperFunction, +> { + pub witness: Option>, + pub vk: VerificationKey, + pub fixed_parameters: VerificationKeyCircuitGeometry, + pub transcript_params: TR::TranscriptParameters, + pub wrapper_function: PWF, +} + +impl< + E: Engine, + HS: TreeHasher, + H: CircuitGLTreeHasher, NonCircuitSimulator = HS>, + TR: CircuitGLTranscript, + PWF: ProofWrapperFunction, + > Circuit for WrapperCircuitWidth3NoLookupNoCustomGate +{ + type MainGate = rescue_poseidon::franklin_crypto::bellman::plonk::better_better_cs::gates::naive_main_gate::NaiveMainGate; + + fn declare_used_gates() -> Result>>, SynthesisError> { + Ok(vec![Self::MainGate::default().into_internal()]) + } + + fn synthesize + 'static>(&self, cs: &mut CS) -> Result<(), SynthesisError> { + // Prepare for proof verification + let verifier_builder = self.wrapper_function.builder_for_wrapper(); + let verifier = verifier_builder.create_wrapper_verifier(cs); + + let proof_config = self.wrapper_function.proof_config_for_compression_step(); + let fixed_parameters = self.fixed_parameters.clone(); + + let vk = AllocatedVerificationKey::::allocate_constant(&self.vk, &fixed_parameters); + let proof: AllocatedProof = AllocatedProof::allocate_from_witness(cs, &self.witness, &verifier, &fixed_parameters, &proof_config)?; + // Verify proof + let correct = crate::verifier::verify::>(cs, self.transcript_params.clone(), &proof_config, &proof, &verifier, &fixed_parameters, &vk)?; + Boolean::enforce_equal(cs, &correct, &Boolean::constant(true))?; + + // Aggregate PI + let _pi = aggregate_public_inputs(cs, &proof.public_inputs)?; + + Ok(()) + } +} + +pub fn verify + 'static, H: CircuitGLTreeHasher, TR: CircuitGLTranscript, POW: RecursivePoWRunner>( cs: &mut CS, transcript_params: TR::TranscriptParameters, proof_config: &ProofConfig, @@ -128,8 +175,7 @@ pub fn verify< let public_input_opening_tuples = verify_first_step(cs, proof, vk, &mut challenges, &mut transcript, verifier, fixed_parameters, &constants)?; validity_flags.extend(check_quotient_contributions_in_z(cs, proof, &challenges, verifier, fixed_parameters, &constants)?); - - validity_flags.extend(verify_fri_part::( + validity_flags.extend(verify_fri_part::( cs, proof, vk, @@ -155,10 +201,9 @@ fn aggregate_public_inputs>(cs: &mut CS, publ ); // Firstly we check that public inputs have correct size - use crate::franklin_crypto::plonk::circuit::bigint_new::enforce_range_check_using_bitop_table; + use rescue_poseidon::franklin_crypto::plonk::circuit::goldilocks::range_check_for_num_bits; for pi in public_inputs.iter() { - let table = cs.get_table(BITWISE_LOGICAL_OPS_TABLE_NAME).unwrap(); - enforce_range_check_using_bitop_table(cs, &pi.into_num().get_variable(), chunk_bit_size, table, false)?; + range_check_for_num_bits(cs, &pi.into_num(), 64)?; } // compute aggregated pi value diff --git a/crates/snark-wrapper/src/verifier_structs/allocated_proof.rs b/crates/snark-wrapper/src/verifier_structs/allocated_proof.rs index 6a27d98..9aa3ae9 100644 --- a/crates/snark-wrapper/src/verifier_structs/allocated_proof.rs +++ b/crates/snark-wrapper/src/verifier_structs/allocated_proof.rs @@ -18,7 +18,7 @@ pub struct AllocatedProof> { pub queries_per_fri_repetition: Vec>, - pub pow_challenge: [Boolean; 64], + pub pow_challenge_le: [Boolean; 64], } impl, H: CircuitGLTreeHasher, NonCircuitSimulator = HS>> AllocatedProof { @@ -121,7 +121,7 @@ impl, H: CircuitGLTreeHasher