From c528da4e66f2afff30a20acd6a613b9b4651f754 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Wed, 23 Nov 2022 14:24:09 -0800 Subject: [PATCH] Cyclic recursion tweaks - Have the caller to `cyclic_recursion` create and pass in the virtual proof - Split `dummy_proof` into preprocessing and proving, so that we don't need to redo the preprocessing work in each `set_cyclic_recursion_data_target` call - Have the caller update `num_public_inputs` instead of doing it in `cyclic_recursion`. This is a little less convenient but I think it's best not to modify the caller's config (principle of least surprise) - Have `set_cyclic_recursion_data_target` take a sparse set of public inputs. Taking some PIs with the lowest indices didn't seem very general. I still have some reservations about this part of the API - I think it would seem cleaner if PIs of a proof which wasn't selected for verification were simply ignored - but perhaps there are some optimization reasons to keep using them. --- plonky2/src/fri/proof.rs | 2 +- plonky2/src/gadgets/polynomial.rs | 2 +- plonky2/src/hash/hash_types.rs | 37 ++++ plonky2/src/plonk/circuit_builder.rs | 9 +- plonky2/src/plonk/proof.rs | 4 +- .../conditional_recursive_verifier.rs | 62 +----- plonky2/src/recursion/cyclic_recursion.rs | 187 ++++++++++-------- plonky2/src/recursion/dummy_circuit.rs | 69 +++++++ plonky2/src/recursion/mod.rs | 1 + 9 files changed, 229 insertions(+), 144 deletions(-) create mode 100644 plonky2/src/recursion/dummy_circuit.rs diff --git a/plonky2/src/fri/proof.rs b/plonky2/src/fri/proof.rs index 4c5f65d8..f841b274 100644 --- a/plonky2/src/fri/proof.rs +++ b/plonky2/src/fri/proof.rs @@ -112,7 +112,7 @@ pub struct FriProof, H: Hasher, const D: usize> pub pow_witness: F, } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct FriProofTarget { pub commit_phase_merkle_caps: Vec, pub query_round_proofs: Vec>, diff --git a/plonky2/src/gadgets/polynomial.rs b/plonky2/src/gadgets/polynomial.rs index f7a59192..80beb62f 100644 --- a/plonky2/src/gadgets/polynomial.rs +++ b/plonky2/src/gadgets/polynomial.rs @@ -7,7 +7,7 @@ use crate::iop::target::Target; use crate::plonk::circuit_builder::CircuitBuilder; use crate::util::reducing::ReducingFactorTarget; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct PolynomialCoeffsExtTarget(pub Vec>); impl PolynomialCoeffsExtTarget { diff --git a/plonky2/src/hash/hash_types.rs b/plonky2/src/hash/hash_types.rs index b95a2113..c725c45c 100644 --- a/plonky2/src/hash/hash_types.rs +++ b/plonky2/src/hash/hash_types.rs @@ -1,5 +1,6 @@ use alloc::vec::Vec; +use anyhow::ensure; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use crate::field::goldilocks_field::GoldilocksField; @@ -25,6 +26,7 @@ impl HashOut { elements: [F::ZERO; 4], }; + // TODO: Switch to a TryFrom impl. pub fn from_vec(elements: Vec) -> Self { debug_assert!(elements.len() == 4); Self { @@ -39,6 +41,23 @@ impl HashOut { } } +impl From<[F; 4]> for HashOut { + fn from(elements: [F; 4]) -> Self { + Self { elements } + } +} + +impl TryFrom<&[F]> for HashOut { + type Error = anyhow::Error; + + fn try_from(elements: &[F]) -> Result { + ensure!(elements.len() == 4); + Ok(Self { + elements: elements.try_into().unwrap(), + }) + } +} + impl Sample for HashOut where F: Field, @@ -97,6 +116,7 @@ pub struct HashOutTarget { } impl HashOutTarget { + // TODO: Switch to a TryFrom impl. pub fn from_vec(elements: Vec) -> Self { debug_assert!(elements.len() == 4); Self { @@ -111,6 +131,23 @@ impl HashOutTarget { } } +impl From<[Target; 4]> for HashOutTarget { + fn from(elements: [Target; 4]) -> Self { + Self { elements } + } +} + +impl TryFrom<&[Target]> for HashOutTarget { + type Error = anyhow::Error; + + fn try_from(elements: &[Target]) -> Result { + ensure!(elements.len() == 4); + Ok(Self { + elements: elements.try_into().unwrap(), + }) + } +} + #[derive(Clone, Debug)] pub struct MerkleCapTarget(pub Vec); diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index 8bd1d994..c9f2edf0 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -244,9 +244,14 @@ impl, const D: usize> CircuitBuilder { self.register_public_input(t); t } + /// Add a virtual verifier data, register it as a public input and set it to `self.verifier_data_public_input`. /// WARNING: Do not register any public input after calling this! TODO: relax this - pub(crate) fn add_verifier_data_public_input(&mut self) { + pub fn add_verifier_data_public_inputs(&mut self) { + if self.verifier_data_public_input.is_some() { + return; + } + let verifier_data = VerifierCircuitTarget { constants_sigmas_cap: self.add_virtual_cap(self.config.fri_config.cap_height), circuit_digest: self.add_virtual_hash(), @@ -886,7 +891,7 @@ impl, const D: usize> CircuitBuilder { num_partial_products, }; if let Some(goal_data) = self.goal_common_data { - assert_eq!(goal_data, common); + assert_eq!(goal_data, common, "The expected circuit data passed to cyclic recursion method did not match the actual circuit"); } let prover_only = ProverOnlyCircuitData { diff --git a/plonky2/src/plonk/proof.rs b/plonky2/src/plonk/proof.rs index caf3a7f8..fb9e6cde 100644 --- a/plonky2/src/plonk/proof.rs +++ b/plonky2/src/plonk/proof.rs @@ -40,7 +40,7 @@ pub struct Proof, C: GenericConfig, const pub opening_proof: FriProof, } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ProofTarget { pub wires_cap: MerkleCapTarget, pub plonk_zs_partial_products_cap: MerkleCapTarget, @@ -283,7 +283,7 @@ pub(crate) struct FriInferredElements, const D: usi pub Vec, ); -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ProofWithPublicInputsTarget { pub proof: ProofTarget, pub public_inputs: Vec, diff --git a/plonky2/src/recursion/conditional_recursive_verifier.rs b/plonky2/src/recursion/conditional_recursive_verifier.rs index 6cbce94a..e7b727b5 100644 --- a/plonky2/src/recursion/conditional_recursive_verifier.rs +++ b/plonky2/src/recursion/conditional_recursive_verifier.rs @@ -1,7 +1,5 @@ -use alloc::vec; use alloc::vec::Vec; -use anyhow::{ensure, Result}; use itertools::Itertools; use crate::field::extension::Extendable; @@ -9,66 +7,16 @@ use crate::fri::proof::{ FriInitialTreeProofTarget, FriProofTarget, FriQueryRoundTarget, FriQueryStepTarget, }; use crate::gadgets::polynomial::PolynomialCoeffsExtTarget; -use crate::gates::noop::NoopGate; use crate::hash::hash_types::{HashOutTarget, MerkleCapTarget, RichField}; use crate::hash::merkle_proofs::MerkleProofTarget; use crate::iop::ext_target::ExtensionTarget; use crate::iop::target::{BoolTarget, Target}; -use crate::iop::witness::{PartialWitness, Witness}; use crate::plonk::circuit_builder::CircuitBuilder; -use crate::plonk::circuit_data::{ - CommonCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData, -}; +use crate::plonk::circuit_data::{CommonCircuitData, VerifierCircuitTarget}; use crate::plonk::config::{AlgebraicHasher, GenericConfig}; -use crate::plonk::proof::{ - OpeningSetTarget, ProofTarget, ProofWithPublicInputs, ProofWithPublicInputsTarget, -}; -use crate::util::ceil_div_usize; +use crate::plonk::proof::{OpeningSetTarget, ProofTarget, ProofWithPublicInputsTarget}; use crate::with_context; -/// Generate a proof having a given `CommonCircuitData`. -pub(crate) fn dummy_proof< - F: RichField + Extendable, - C: GenericConfig, - const D: usize, ->( - common_data: &CommonCircuitData, -) -> Result<( - ProofWithPublicInputs, - VerifierOnlyCircuitData, -)> { - let config = common_data.config.clone(); - - let mut pw = PartialWitness::new(); - let mut builder = CircuitBuilder::::new(config); - - ensure!( - !common_data.config.zero_knowledge, - "Degree calculation can be off if zero-knowledge is on." - ); - let degree = common_data.degree(); - // Number of `NoopGate`s to add to get a circuit of size `degree` in the end. - // Need to account for public input hashing, a `PublicInputGate` and a `ConstantGate`. - let num_noop_gate = degree - ceil_div_usize(common_data.num_public_inputs, 8) - 2; - - for _ in 0..num_noop_gate { - builder.add_gate(NoopGate, vec![]); - } - for gate in &common_data.gates { - builder.add_gate_to_gate_set(gate.clone()); - } - for _ in 0..common_data.num_public_inputs { - let t = builder.add_virtual_public_input(); - pw.set_target(t, F::ZERO); - } - - let data = builder.build::(); - assert_eq!(&data.common, common_data); - let proof = data.prove(pw)?; - - Ok((proof, data.verifier_only)) -} - impl, const D: usize> CircuitBuilder { /// Verify `proof0` if `condition` else verify `proof1`. /// `proof0` and `proof1` are assumed to use the same `CommonCircuitData`. @@ -376,6 +324,7 @@ mod tests { use crate::iop::witness::{PartialWitness, Witness}; use crate::plonk::circuit_data::CircuitConfig; use crate::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; + use crate::recursion::dummy_circuit::{dummy_circuit, dummy_proof}; #[test] fn test_conditional_recursive_verifier() -> Result<()> { @@ -400,7 +349,8 @@ mod tests { data.verify(proof.clone())?; // Generate dummy proof with the same `CommonCircuitData`. - let (dummy_proof, dummy_data) = dummy_proof(&data.common)?; + let dummy_data = dummy_circuit(&data.common); + let dummy_proof = dummy_proof(&dummy_data, &[])?; // Conditionally verify the two proofs. let mut builder = CircuitBuilder::::new(config); @@ -418,7 +368,7 @@ mod tests { constants_sigmas_cap: builder.add_virtual_cap(data.common.config.fri_config.cap_height), circuit_digest: builder.add_virtual_hash(), }; - pw.set_verifier_data_target(&dummy_inner_data, &dummy_data); + pw.set_verifier_data_target(&dummy_inner_data, &dummy_data.verifier_only); let b = builder.constant_bool(F::rand().0 % 2 == 0); builder.conditionally_verify_proof::( b, diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs index 2e4f2613..b466c034 100644 --- a/plonky2/src/recursion/cyclic_recursion.rs +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -13,11 +13,11 @@ use crate::iop::target::{BoolTarget, Target}; use crate::iop::witness::{PartialWitness, Witness}; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::circuit_data::{ - CommonCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData, + CircuitData, CommonCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData, }; use crate::plonk::config::{AlgebraicHasher, GenericConfig}; use crate::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}; -use crate::recursion::conditional_recursive_verifier::dummy_proof; +use crate::recursion::dummy_circuit::{dummy_circuit, dummy_proof}; pub struct CyclicRecursionData< 'a, @@ -30,12 +30,17 @@ pub struct CyclicRecursionData< common_data: &'a CommonCircuitData, } -pub struct CyclicRecursionTarget { - pub proof: ProofWithPublicInputsTarget, - pub verifier_data: VerifierCircuitTarget, - pub dummy_proof: ProofWithPublicInputsTarget, - pub dummy_verifier_data: VerifierCircuitTarget, - pub condition: BoolTarget, +pub struct CyclicRecursionTarget +where + F: RichField + Extendable, + C: GenericConfig, +{ + pub(crate) proof: ProofWithPublicInputsTarget, + pub(crate) verifier_data: VerifierCircuitTarget, + pub(crate) dummy_proof: ProofWithPublicInputsTarget, + pub(crate) dummy_verifier_data: VerifierCircuitTarget, + pub(crate) condition: BoolTarget, + pub(crate) dummy_circuit: CircuitData, } impl, const D: usize> VerifierOnlyCircuitData { @@ -107,17 +112,16 @@ impl, const D: usize> CircuitBuilder { pub fn cyclic_recursion>( &mut self, condition: BoolTarget, - previous_virtual_public_inputs: &[Target], - common_data: &mut CommonCircuitData, - ) -> Result> + proof_with_pis: &ProofWithPublicInputsTarget, + common_data: &CommonCircuitData, + ) -> Result> where C::Hasher: AlgebraicHasher, { - if self.verifier_data_public_input.is_none() { - self.add_verifier_data_public_input(); - } - let verifier_data = self.verifier_data_public_input.clone().unwrap(); - common_data.num_public_inputs = self.num_public_inputs(); + let verifier_data = self + .verifier_data_public_input + .clone() + .expect("Must call add_verifier_data_public_inputs before cyclic recursion"); self.goal_common_data = Some(common_data.clone()); let dummy_verifier_data = VerifierCircuitTarget { @@ -125,10 +129,12 @@ impl, const D: usize> CircuitBuilder { circuit_digest: self.add_virtual_hash(), }; - let proof = self.add_virtual_proof_with_pis::(common_data); let dummy_proof = self.add_virtual_proof_with_pis::(common_data); - let pis = VerifierCircuitTarget::from_slice::(&proof.public_inputs, common_data)?; + let pis = VerifierCircuitTarget::from_slice::( + &proof_with_pis.public_inputs, + common_data, + )?; // Connect previous verifier data to current one. This guarantees that every proof in the cycle uses the same verifier data. self.connect_hashes(pis.circuit_digest, verifier_data.circuit_digest); for (h0, h1) in pis @@ -140,17 +146,10 @@ impl, const D: usize> CircuitBuilder { self.connect_hashes(*h0, *h1); } - for (x, y) in previous_virtual_public_inputs - .iter() - .zip(&proof.public_inputs) - { - self.connect(*x, *y); - } - // Verify the real proof if `condition` is set to true, otherwise verify the dummy proof. self.conditionally_verify_proof::( condition, - &proof, + proof_with_pis, &verifier_data, &dummy_proof, &dummy_verifier_data, @@ -167,11 +166,12 @@ impl, const D: usize> CircuitBuilder { } Ok(CyclicRecursionTarget { - proof, - verifier_data: verifier_data.clone(), + proof: proof_with_pis.clone(), + verifier_data, dummy_proof, dummy_verifier_data, condition, + dummy_circuit: dummy_circuit(common_data), }) } } @@ -183,10 +183,10 @@ pub fn set_cyclic_recursion_data_target< const D: usize, >( pw: &mut PartialWitness, - cyclic_recursion_data_target: &CyclicRecursionTarget, + cyclic_recursion_data_target: &CyclicRecursionTarget, cyclic_recursion_data: &CyclicRecursionData, // Public inputs to set in the base case to seed some initial data. - public_inputs: &[F], + public_inputs: &[(usize, F)], ) -> Result<()> where C::Hasher: AlgebraicHasher, @@ -204,36 +204,42 @@ where cyclic_recursion_data.verifier_data, ); } else { - let (dummy_proof, dummy_data) = dummy_proof::(cyclic_recursion_data.common_data)?; pw.set_bool_target(cyclic_recursion_data_target.condition, false); - let mut proof = dummy_proof.clone(); - proof.public_inputs[0..public_inputs.len()].copy_from_slice(public_inputs); - let pis_len = proof.public_inputs.len(); - // The circuit checks that the verifier data is the same throughout the cycle, so - // we set the verifier data to the "real" verifier data even though it's unused in the base case. - let num_cap = cyclic_recursion_data + + let pis_len = cyclic_recursion_data_target + .dummy_circuit + .common + .num_public_inputs; + let cap_elements = cyclic_recursion_data .common_data .config .fri_config .num_cap_elements(); - let s = pis_len - 4 - 4 * num_cap; - proof.public_inputs[s..s + 4] - .copy_from_slice(&cyclic_recursion_data.verifier_data.circuit_digest.elements); - for i in 0..num_cap { - proof.public_inputs[s + 4 * (1 + i)..s + 4 * (2 + i)].copy_from_slice( - &cyclic_recursion_data.verifier_data.constants_sigmas_cap.0[i].elements, - ); + let start_vk_pis = pis_len - 4 - 4 * cap_elements; + + // The circuit checks that the verifier data is the same throughout the cycle, so + // we set the verifier data to the "real" verifier data even though it's unused in the base case. + let mut public_inputs = public_inputs.to_vec(); + let verifier_data = &cyclic_recursion_data.verifier_data; + public_inputs.extend((start_vk_pis..).zip(verifier_data.circuit_digest.elements)); + + for i in 0..cap_elements { + let start = start_vk_pis + 4 + 4 * i; + public_inputs.extend((start..).zip(verifier_data.constants_sigmas_cap.0[i].elements)); } + let proof = dummy_proof(&cyclic_recursion_data_target.dummy_circuit, &public_inputs)?; pw.set_proof_with_pis_target(&cyclic_recursion_data_target.proof, &proof); pw.set_verifier_data_target( &cyclic_recursion_data_target.verifier_data, cyclic_recursion_data.verifier_data, ); - pw.set_proof_with_pis_target(&cyclic_recursion_data_target.dummy_proof, &dummy_proof); + + let dummy_p = dummy_proof(&cyclic_recursion_data_target.dummy_circuit, &[])?; + pw.set_proof_with_pis_target(&cyclic_recursion_data_target.dummy_proof, &dummy_p); pw.set_verifier_data_target( &cyclic_recursion_data_target.dummy_verifier_data, - &dummy_data, + &cyclic_recursion_data_target.dummy_circuit.verifier_only, ); } @@ -264,11 +270,12 @@ where #[cfg(test)] mod tests { use anyhow::Result; + use itertools::Itertools; use crate::field::extension::Extendable; use crate::field::types::{Field, PrimeField64}; use crate::gates::noop::NoopGate; - use crate::hash::hash_types::RichField; + use crate::hash::hash_types::{HashOutTarget, RichField}; use crate::hash::hashing::hash_n_to_hash_no_pad; use crate::hash::poseidon::{PoseidonHash, PoseidonPermutation}; use crate::iop::witness::PartialWitness; @@ -315,6 +322,12 @@ mod tests { builder.build::().common } + /// Uses cyclic recursion to build a hash chain. + /// The circuit has the following public input structure: + /// - Initial hash (4) + /// - Output for the tip of the hash chain (4) + /// - Chain length, i.e. the number of times the hash has been applied (1) + /// - VK for cyclic recursion (?) #[test] fn test_cyclic_recursion() -> Result<()> { const D: usize = 2; @@ -322,55 +335,62 @@ mod tests { type F = >::F; let config = CircuitConfig::standard_recursion_config(); - let mut pw = PartialWitness::new(); let mut builder = CircuitBuilder::::new(config); + let one = builder.one(); // Circuit that computes a repeated hash. let initial_hash = builder.add_virtual_hash(); builder.register_public_inputs(&initial_hash.elements); - // Hash from the previous proof. - let old_hash = builder.add_virtual_hash(); - // The input hash is either the previous hash or the initial hash depending on whether - // the last proof was a base case. - let input_hash = builder.add_virtual_hash(); - let h = builder.hash_n_to_hash_no_pad::(input_hash.elements.to_vec()); - builder.register_public_inputs(&h.elements); - // Previous counter. - let old_counter = builder.add_virtual_target(); - let new_counter = builder.add_virtual_public_input(); - let old_pis = [ - initial_hash.elements.as_slice(), - old_hash.elements.as_slice(), - [old_counter].as_slice(), - ] - .concat(); + let current_hash_in = builder.add_virtual_hash(); + let current_hash_out = + builder.hash_n_to_hash_no_pad::(current_hash_in.elements.to_vec()); + builder.register_public_inputs(¤t_hash_out.elements); + let counter = builder.add_virtual_public_input(); let mut common_data = common_data_for_recursion::(); + builder.add_verifier_data_public_inputs(); + common_data.num_public_inputs = builder.num_public_inputs(); let condition = builder.add_virtual_bool_target_safe(); - // Add cyclic recursion gadget. + + // Unpack inner proof's public inputs. + let inner_proof_with_pis = builder.add_virtual_proof_with_pis::(&common_data); + let inner_pis = &inner_proof_with_pis.public_inputs; + let inner_initial_hash = HashOutTarget::try_from(&inner_pis[0..4]).unwrap(); + let inner_latest_hash = HashOutTarget::try_from(&inner_pis[4..8]).unwrap(); + let inner_counter = inner_pis[8]; + + // Connect our initial hash to that of our inner proof. (If there is no inner proof, the + // initial hash will be unconstrained, which is intentional.) + builder.connect_hashes(initial_hash, inner_initial_hash); + + // The input hash is the previous hash output if we have an inner proof, or the initial hash + // if this is the base case. + let actual_hash_in = builder.select_hash(condition, inner_latest_hash, initial_hash); + builder.connect_hashes(current_hash_in, actual_hash_in); + + // Our chain length will be inner_counter + 1 if we have an inner proof, or 1 if not. + let new_counter = builder.mul_add(condition.target, inner_counter, one); + builder.connect(counter, new_counter); + let cyclic_data_target = - builder.cyclic_recursion::(condition, &old_pis, &mut common_data)?; - let input_hash_bis = - builder.select_hash(cyclic_data_target.condition, old_hash, initial_hash); - builder.connect_hashes(input_hash, input_hash_bis); - // New counter is the previous counter +1 if the previous proof wasn't a base case. - let new_counter_bis = builder.add(old_counter, condition.target); - builder.connect(new_counter, new_counter_bis); + builder.cyclic_recursion::(condition, &inner_proof_with_pis, &common_data)?; let cyclic_circuit_data = builder.build::(); + let mut pw = PartialWitness::new(); let cyclic_recursion_data = CyclicRecursionData { proof: &None, // Base case: We don't have a proof to put here yet. verifier_data: &cyclic_circuit_data.verifier_only, common_data: &cyclic_circuit_data.common, }; let initial_hash = [F::ZERO, F::ONE, F::TWO, F::from_canonical_usize(3)]; + let initial_hash_pis = initial_hash.into_iter().enumerate().collect_vec(); set_cyclic_recursion_data_target( &mut pw, &cyclic_data_target, &cyclic_recursion_data, - &initial_hash, + &initial_hash_pis, )?; let proof = cyclic_circuit_data.prove(pw)?; check_cyclic_proof_verifier_data( @@ -425,17 +445,20 @@ mod tests { let initial_hash = &proof.public_inputs[..4]; let hash = &proof.public_inputs[4..8]; let counter = proof.public_inputs[8]; - let mut h: [F; 4] = initial_hash.try_into().unwrap(); - assert_eq!( - hash, - core::iter::repeat_with(|| { - h = hash_n_to_hash_no_pad::(&h).elements; - h - }) - .nth(counter.to_canonical_u64() as usize) - .unwrap() + let expected_hash: [F; 4] = iterate_poseidon( + initial_hash.try_into().unwrap(), + counter.to_canonical_u64() as usize, ); + assert_eq!(hash, expected_hash); cyclic_circuit_data.verify(proof) } + + fn iterate_poseidon(initial_state: [F; 4], n: usize) -> [F; 4] { + let mut current = initial_state; + for _ in 0..n { + current = hash_n_to_hash_no_pad::(¤t).elements; + } + current + } } diff --git a/plonky2/src/recursion/dummy_circuit.rs b/plonky2/src/recursion/dummy_circuit.rs new file mode 100644 index 00000000..049f5540 --- /dev/null +++ b/plonky2/src/recursion/dummy_circuit.rs @@ -0,0 +1,69 @@ +use alloc::vec; + +use plonky2_field::extension::Extendable; +use plonky2_util::ceil_div_usize; + +use crate::gates::noop::NoopGate; +use crate::hash::hash_types::RichField; +use crate::iop::witness::{PartialWitness, Witness}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::{CircuitData, CommonCircuitData}; +use crate::plonk::config::GenericConfig; +use crate::plonk::proof::ProofWithPublicInputs; + +/// Generate a proof for a dummy circuit. The `public_inputs` parameter let the caller specify +/// certain public inputs (identified by their indices) which should be given specific values. +/// The rest will default to zero. +pub(crate) fn dummy_proof( + circuit: &CircuitData, + nonzero_public_inputs: &[(usize, F)], +) -> anyhow::Result> +where + F: RichField + Extendable, + C: GenericConfig, +{ + let mut pw = PartialWitness::new(); + for i in 0..circuit.common.num_public_inputs { + let pi = nonzero_public_inputs + .iter() + .find_map(|(j, pi)| (i == *j).then_some(*pi)) + .unwrap_or_default(); + pw.set_target(circuit.prover_only.public_inputs[i], pi); + } + circuit.prove(pw) +} + +/// Generate a circuit matching a given `CommonCircuitData`. +pub(crate) fn dummy_circuit< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + common_data: &CommonCircuitData, +) -> CircuitData { + let config = common_data.config.clone(); + assert!( + !common_data.config.zero_knowledge, + "Degree calculation can be off if zero-knowledge is on." + ); + + // Number of `NoopGate`s to add to get a circuit of size `degree` in the end. + // Need to account for public input hashing, a `PublicInputGate` and a `ConstantGate`. + let degree = common_data.degree(); + let num_noop_gate = degree - ceil_div_usize(common_data.num_public_inputs, 8) - 2; + + let mut builder = CircuitBuilder::::new(config); + for _ in 0..num_noop_gate { + builder.add_gate(NoopGate, vec![]); + } + for gate in &common_data.gates { + builder.add_gate_to_gate_set(gate.clone()); + } + for _ in 0..common_data.num_public_inputs { + builder.add_virtual_public_input(); + } + + let circuit = builder.build::(); + assert_eq!(&circuit.common, common_data); + circuit +} diff --git a/plonky2/src/recursion/mod.rs b/plonky2/src/recursion/mod.rs index 33e8212e..3aba4ffd 100644 --- a/plonky2/src/recursion/mod.rs +++ b/plonky2/src/recursion/mod.rs @@ -1,3 +1,4 @@ pub mod conditional_recursive_verifier; pub mod cyclic_recursion; +pub(crate) mod dummy_circuit; pub mod recursive_verifier;