From 325bab113a735aac869902dd9fad26c1ae8e613e Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 12 Oct 2022 17:43:50 +0200 Subject: [PATCH 01/18] test --- plonky2/src/fri/mod.rs | 2 +- plonky2/src/plonk/circuit_builder.rs | 2 +- plonky2/src/plonk/circuit_data.rs | 2 +- plonky2/src/plonk/recursive_verifier.rs | 42 +++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/plonky2/src/fri/mod.rs b/plonky2/src/fri/mod.rs index 87c4c2aa..d362420e 100644 --- a/plonky2/src/fri/mod.rs +++ b/plonky2/src/fri/mod.rs @@ -54,7 +54,7 @@ impl FriConfig { /// FRI parameters, including generated parameters which are specific to an instance size, in /// contrast to `FriConfig` which is user-specified and independent of instance size. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct FriParams { /// User-specified FRI configuration. pub config: FriConfig, diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index bfa012da..3a2cccb0 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -50,7 +50,7 @@ pub struct CircuitBuilder, const D: usize> { pub config: CircuitConfig, /// The types of gates used in this circuit. - gates: HashSet>, + pub(crate) gates: HashSet>, /// The concrete placement of each gate. pub(crate) gate_instances: Vec>, diff --git a/plonky2/src/plonk/circuit_data.rs b/plonky2/src/plonk/circuit_data.rs index 7e69ef31..879ed6d3 100644 --- a/plonky2/src/plonk/circuit_data.rs +++ b/plonky2/src/plonk/circuit_data.rs @@ -263,7 +263,7 @@ pub struct VerifierOnlyCircuitData, const D: usize> { } /// Circuit data required by both the prover and the verifier. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CommonCircuitData< F: RichField + Extendable, C: GenericConfig, diff --git a/plonky2/src/plonk/recursive_verifier.rs b/plonky2/src/plonk/recursive_verifier.rs index ecce34e1..487373e3 100644 --- a/plonky2/src/plonk/recursive_verifier.rs +++ b/plonky2/src/plonk/recursive_verifier.rs @@ -445,4 +445,46 @@ mod tests { fn init_logger() { let _ = env_logger::builder().format_timestamp(None).try_init(); } + + #[test] + fn test_cyclic_recursion() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + // type FF = >::FE; + + let config = CircuitConfig::standard_recursion_config(); + let (proof, vd, cd) = dummy_proof::(&config, 1 << 13)?; + + let (proof, vd, cd) = + recursive_proof::(proof, vd, cd, &config, None, false, false)?; + let (_proof, _vd, cd) = + recursive_proof::(proof, vd, cd, &config, None, false, false)?; + + let config = CircuitConfig::standard_recursion_config(); + let mut pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let verifier_data = VerifierCircuitTarget { + constants_sigmas_cap: builder.add_virtual_cap(builder.config.fri_config.cap_height), + }; + for i in 0..1 << builder.config.fri_config.cap_height { + builder.register_public_inputs(&verifier_data.constants_sigmas_cap.0[i].elements); + } + let pt = builder.add_virtual_proof_with_pis(&cd); + builder.verify_proof(pt, &verifier_data, &cd); + + let inner_gates = cd.gates.iter().map(|g| g.0.id()).collect::>(); + for gate in &builder.gates { + assert!(inner_gates.contains(&gate.0.id()), "{}", gate.0.id()); + } + for _ in builder.num_gates()..(1 << 13) - 100 { + builder.add_gate(NoopGate, vec![]); + } + let data = builder.build::(); + dbg!(data.common.degree_bits); + let proof = data.prove(pw)?; + + data.verify(proof) + } } From 66a0e7725101d43b21ccabc7e7374ca950ae6f4b Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Thu, 13 Oct 2022 14:28:08 +0200 Subject: [PATCH 02/18] Working in test --- plonky2/src/plonk/circuit_data.rs | 2 +- plonky2/src/plonk/recursive_verifier.rs | 179 ++++++++++++++++++++++-- 2 files changed, 167 insertions(+), 14 deletions(-) diff --git a/plonky2/src/plonk/circuit_data.rs b/plonky2/src/plonk/circuit_data.rs index 4a37f665..1a73d7b6 100644 --- a/plonky2/src/plonk/circuit_data.rs +++ b/plonky2/src/plonk/circuit_data.rs @@ -276,7 +276,7 @@ pub struct ProverOnlyCircuitData< } /// Circuit data required by the verifier, but not the prover. -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct VerifierOnlyCircuitData, const D: usize> { /// A commitment to each constant polynomial and each permutation polynomial. pub constants_sigmas_cap: MerkleCap, diff --git a/plonky2/src/plonk/recursive_verifier.rs b/plonky2/src/plonk/recursive_verifier.rs index 373ed69e..29d89c92 100644 --- a/plonky2/src/plonk/recursive_verifier.rs +++ b/plonky2/src/plonk/recursive_verifier.rs @@ -464,37 +464,190 @@ mod tests { // type FF = >::FE; let config = CircuitConfig::standard_recursion_config(); - let (proof, vd, cd) = dummy_proof::(&config, 1 << 13)?; + let (proof, vd, cd) = dummy_proof::(&config, 1 << 14)?; let (proof, vd, cd) = recursive_proof::(proof, vd, cd, &config, None, false, false)?; - let (_proof, _vd, cd) = - recursive_proof::(proof, vd, cd, &config, None, false, false)?; + let (_proof, _vd, mut cd) = + recursive_proof::(proof, vd, cd, &config, Some(14), false, false)?; + cd.num_public_inputs = 69; + // First proof let config = CircuitConfig::standard_recursion_config(); let mut pw = PartialWitness::new(); let mut builder = CircuitBuilder::::new(config); let verifier_data = VerifierCircuitTarget { constants_sigmas_cap: builder.add_virtual_cap(builder.config.fri_config.cap_height), + circuit_digest: builder.add_virtual_hash(), }; + builder.register_public_inputs(&verifier_data.circuit_digest.elements); for i in 0..1 << builder.config.fri_config.cap_height { builder.register_public_inputs(&verifier_data.constants_sigmas_cap.0[i].elements); } - let pt = builder.add_virtual_proof_with_pis(&cd); - builder.verify_proof(pt, &verifier_data, &cd); + let dummy_verifier_data = VerifierCircuitTarget { + constants_sigmas_cap: builder.add_virtual_cap(builder.config.fri_config.cap_height), + circuit_digest: builder.add_virtual_hash(), + }; + let condition = builder.add_virtual_bool_target(); + builder.register_public_input(condition.target); + pw.set_bool_target(condition, false); - let inner_gates = cd.gates.iter().map(|g| g.0.id()).collect::>(); - for gate in &builder.gates { - assert!(inner_gates.contains(&gate.0.id()), "{}", gate.0.id()); - } - for _ in builder.num_gates()..(1 << 13) - 100 { + let (dummy_proof, dummy_data) = + crate::plonk::conditional_recursive_verifier::dummy_proof(&cd)?; + let pt0 = builder.add_virtual_proof_with_pis(&cd); + let pt1 = builder.add_virtual_proof_with_pis(&cd); + + pw.set_proof_with_pis_target(&pt0, &dummy_proof); + pw.set_proof_with_pis_target(&pt1, &dummy_proof); + pw.set_hash_target( + dummy_verifier_data.circuit_digest, + dummy_data.verifier_only.circuit_digest, + ); + pw.set_cap_target( + &dummy_verifier_data.constants_sigmas_cap, + &dummy_data.verifier_only.constants_sigmas_cap, + ); + + builder.conditionally_verify_proof( + condition, + pt0, + &verifier_data, + pt1, + &dummy_verifier_data, + &cd, + ); + + while builder.num_gates() < 1 << 13 { builder.add_gate(NoopGate, vec![]); } - let data = builder.build::(); - dbg!(data.common.degree_bits); - let proof = data.prove(pw)?; + let data = builder.build::(); + dbg!(cd.degree_bits); + dbg!(data.common.degree_bits); + assert_eq!(&data.common, &cd); + pw.set_verifier_data_target(&verifier_data, &data.verifier_only); + let proof = data.prove(pw)?; + assert_eq!( + data.verifier_only.circuit_digest.elements[0], + proof.public_inputs[0] + ); + data.verify(proof.clone())?; + + // Second proof + let config = CircuitConfig::standard_recursion_config(); + let mut pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let verifier_data = VerifierCircuitTarget { + constants_sigmas_cap: builder.add_virtual_cap(builder.config.fri_config.cap_height), + circuit_digest: builder.add_virtual_hash(), + }; + builder.register_public_inputs(&verifier_data.circuit_digest.elements); + for i in 0..1 << builder.config.fri_config.cap_height { + builder.register_public_inputs(&verifier_data.constants_sigmas_cap.0[i].elements); + } + let dummy_verifier_data = VerifierCircuitTarget { + constants_sigmas_cap: builder.add_virtual_cap(builder.config.fri_config.cap_height), + circuit_digest: builder.add_virtual_hash(), + }; + let condition = builder.add_virtual_bool_target(); + builder.register_public_input(condition.target); + pw.set_bool_target(condition, true); + + let pt0 = builder.add_virtual_proof_with_pis(&data.common); + let pt1 = builder.add_virtual_proof_with_pis(&data.common); + + pw.set_proof_with_pis_target(&pt0, &proof); + pw.set_proof_with_pis_target(&pt1, &proof); + + builder.conditionally_verify_proof( + condition, + pt0, + &verifier_data, + pt1, + &dummy_verifier_data, + &data.common, + ); + + while builder.num_gates() < 1 << 13 { + builder.add_gate(NoopGate, vec![]); + } + + let data1 = builder.build::(); + assert_eq!(data.common, data1.common); + assert_eq!(data.verifier_only, data1.verifier_only); + dbg!(cd.degree_bits); + dbg!(data1.common.degree_bits); + pw.set_verifier_data_target(&verifier_data, &data.verifier_only); + pw.set_verifier_data_target(&dummy_verifier_data, &data.verifier_only); + let proof = data.prove(pw)?; + assert_eq!( + data.verifier_only.circuit_digest.elements[0], + proof.public_inputs[0] + ); + assert_eq!( + data1.verifier_only.circuit_digest.elements[0], + proof.public_inputs[0] + ); + data.verify(proof.clone())?; + + // Second proof + let config = CircuitConfig::standard_recursion_config(); + let mut pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + let verifier_data = VerifierCircuitTarget { + constants_sigmas_cap: builder.add_virtual_cap(builder.config.fri_config.cap_height), + circuit_digest: builder.add_virtual_hash(), + }; + builder.register_public_inputs(&verifier_data.circuit_digest.elements); + for i in 0..1 << builder.config.fri_config.cap_height { + builder.register_public_inputs(&verifier_data.constants_sigmas_cap.0[i].elements); + } + let dummy_verifier_data = VerifierCircuitTarget { + constants_sigmas_cap: builder.add_virtual_cap(builder.config.fri_config.cap_height), + circuit_digest: builder.add_virtual_hash(), + }; + let condition = builder.add_virtual_bool_target(); + builder.register_public_input(condition.target); + pw.set_bool_target(condition, true); + + let pt0 = builder.add_virtual_proof_with_pis(&data.common); + let pt1 = builder.add_virtual_proof_with_pis(&data.common); + + pw.set_proof_with_pis_target(&pt0, &proof); + pw.set_proof_with_pis_target(&pt1, &proof); + + builder.conditionally_verify_proof( + condition, + pt0, + &verifier_data, + pt1, + &dummy_verifier_data, + &data.common, + ); + + while builder.num_gates() < 1 << 13 { + builder.add_gate(NoopGate, vec![]); + } + + let data2 = builder.build::(); + assert_eq!(data.common, data2.common); + assert_eq!(data.verifier_only, data2.verifier_only); + dbg!(cd.degree_bits); + dbg!(data1.common.degree_bits); + pw.set_verifier_data_target(&verifier_data, &data.verifier_only); + pw.set_verifier_data_target(&dummy_verifier_data, &data.verifier_only); + let proof = data.prove(pw)?; + assert_eq!( + data.verifier_only.circuit_digest.elements[0], + proof.public_inputs[0] + ); + assert_eq!( + data1.verifier_only.circuit_digest.elements[0], + proof.public_inputs[0] + ); data.verify(proof) } } From 29e0aef376ed13e6fc7cda0b3b44b6ab87c9c2e8 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Thu, 13 Oct 2022 14:39:08 +0200 Subject: [PATCH 03/18] New recursion folder --- plonky2/src/lib.rs | 1 + plonky2/src/plonk/mod.rs | 2 -- .../src/{plonk => recursion}/conditional_recursive_verifier.rs | 0 plonky2/src/recursion/mod.rs | 2 ++ plonky2/src/{plonk => recursion}/recursive_verifier.rs | 2 +- 5 files changed, 4 insertions(+), 3 deletions(-) rename plonky2/src/{plonk => recursion}/conditional_recursive_verifier.rs (100%) create mode 100644 plonky2/src/recursion/mod.rs rename plonky2/src/{plonk => recursion}/recursive_verifier.rs (99%) diff --git a/plonky2/src/lib.rs b/plonky2/src/lib.rs index 64acfe12..8a517a11 100644 --- a/plonky2/src/lib.rs +++ b/plonky2/src/lib.rs @@ -18,4 +18,5 @@ pub mod gates; pub mod hash; pub mod iop; pub mod plonk; +pub mod recursion; pub mod util; diff --git a/plonky2/src/plonk/mod.rs b/plonky2/src/plonk/mod.rs index 8cd7443f..604c1f79 100644 --- a/plonky2/src/plonk/mod.rs +++ b/plonky2/src/plonk/mod.rs @@ -1,6 +1,5 @@ pub mod circuit_builder; pub mod circuit_data; -pub mod conditional_recursive_verifier; pub mod config; pub(crate) mod copy_constraint; mod get_challenges; @@ -8,7 +7,6 @@ pub(crate) mod permutation_argument; pub mod plonk_common; pub mod proof; pub mod prover; -pub mod recursive_verifier; mod validate_shape; pub(crate) mod vanishing_poly; pub mod vars; diff --git a/plonky2/src/plonk/conditional_recursive_verifier.rs b/plonky2/src/recursion/conditional_recursive_verifier.rs similarity index 100% rename from plonky2/src/plonk/conditional_recursive_verifier.rs rename to plonky2/src/recursion/conditional_recursive_verifier.rs diff --git a/plonky2/src/recursion/mod.rs b/plonky2/src/recursion/mod.rs new file mode 100644 index 00000000..b83c4a85 --- /dev/null +++ b/plonky2/src/recursion/mod.rs @@ -0,0 +1,2 @@ +pub mod conditional_recursive_verifier; +pub mod recursive_verifier; diff --git a/plonky2/src/plonk/recursive_verifier.rs b/plonky2/src/recursion/recursive_verifier.rs similarity index 99% rename from plonky2/src/plonk/recursive_verifier.rs rename to plonky2/src/recursion/recursive_verifier.rs index 29d89c92..c35f21e2 100644 --- a/plonky2/src/plonk/recursive_verifier.rs +++ b/plonky2/src/recursion/recursive_verifier.rs @@ -494,7 +494,7 @@ mod tests { pw.set_bool_target(condition, false); let (dummy_proof, dummy_data) = - crate::plonk::conditional_recursive_verifier::dummy_proof(&cd)?; + crate::recursion::conditional_recursive_verifier::dummy_proof(&cd)?; let pt0 = builder.add_virtual_proof_with_pis(&cd); let pt1 = builder.add_virtual_proof_with_pis(&cd); From f194553345f460d9faf27f9c08ade481be21ea46 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Thu, 13 Oct 2022 18:13:57 +0200 Subject: [PATCH 04/18] Test not working --- plonky2/src/plonk/circuit_builder.rs | 4 + .../conditional_recursive_verifier.rs | 124 +++++----- plonky2/src/recursion/cyclic_recursion.rs | 227 ++++++++++++++++++ plonky2/src/recursion/mod.rs | 1 + plonky2/src/recursion/recursive_verifier.rs | 195 --------------- 5 files changed, 294 insertions(+), 257 deletions(-) create mode 100644 plonky2/src/recursion/cyclic_recursion.rs diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index 16d42685..d5a748e5 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -144,6 +144,10 @@ impl, const D: usize> CircuitBuilder { targets.iter().for_each(|&t| self.register_public_input(t)); } + pub fn num_public_inputs(&self) -> usize { + self.public_inputs.len() + } + /// Adds a new "virtual" target. This is not an actual wire in the witness, but just a target /// that help facilitate witness generation. In particular, a generator can assign a values to a /// virtual target, which can then be copied to other (virtual or concrete) targets. When we diff --git a/plonky2/src/recursion/conditional_recursive_verifier.rs b/plonky2/src/recursion/conditional_recursive_verifier.rs index 8ac0a4f2..8f53046c 100644 --- a/plonky2/src/recursion/conditional_recursive_verifier.rs +++ b/plonky2/src/recursion/conditional_recursive_verifier.rs @@ -66,9 +66,9 @@ impl, const D: usize> CircuitBuilder { pub fn conditionally_verify_proof>( &mut self, condition: BoolTarget, - proof_with_pis0: ProofWithPublicInputsTarget, + proof_with_pis0: &ProofWithPublicInputsTarget, inner_verifier_data0: &VerifierCircuitTarget, - proof_with_pis1: ProofWithPublicInputsTarget, + proof_with_pis1: &ProofWithPublicInputsTarget, inner_verifier_data1: &VerifierCircuitTarget, inner_common_data: &CommonCircuitData, ) where @@ -124,8 +124,8 @@ impl, const D: usize> CircuitBuilder { let selected_verifier_data = VerifierCircuitTarget { constants_sigmas_cap: self.select_cap( condition, - inner_verifier_data0.constants_sigmas_cap.clone(), - inner_verifier_data1.constants_sigmas_cap.clone(), + &inner_verifier_data0.constants_sigmas_cap, + &inner_verifier_data1.constants_sigmas_cap, ), circuit_digest: self.select_hash( condition, @@ -137,10 +137,10 @@ impl, const D: usize> CircuitBuilder { self.verify_proof(selected_proof, &selected_verifier_data, inner_common_data); } - fn select_vec(&mut self, b: BoolTarget, v0: Vec, v1: Vec) -> Vec { - v0.into_iter() + fn select_vec(&mut self, b: BoolTarget, v0: &[Target], v1: &[Target]) -> Vec { + v0.iter() .zip_eq(v1) - .map(|(t0, t1)| self.select(b, t0, t1)) + .map(|(t0, t1)| self.select(b, *t0, *t1)) .collect() } @@ -158,15 +158,15 @@ impl, const D: usize> CircuitBuilder { fn select_cap( &mut self, b: BoolTarget, - cap0: MerkleCapTarget, - cap1: MerkleCapTarget, + cap0: &MerkleCapTarget, + cap1: &MerkleCapTarget, ) -> MerkleCapTarget { assert_eq!(cap0.0.len(), cap1.0.len()); MerkleCapTarget( cap0.0 - .into_iter() - .zip_eq(cap1.0) - .map(|(h0, h1)| self.select_hash(b, h0, h1)) + .iter() + .zip_eq(&cap1.0) + .map(|(h0, h1)| self.select_hash(b, *h0, *h1)) .collect(), ) } @@ -174,10 +174,10 @@ impl, const D: usize> CircuitBuilder { fn select_vec_cap( &mut self, b: BoolTarget, - v0: Vec, - v1: Vec, + v0: &[MerkleCapTarget], + v1: &[MerkleCapTarget], ) -> Vec { - v0.into_iter() + v0.iter() .zip_eq(v1) .map(|(c0, c1)| self.select_cap(b, c0, c1)) .collect() @@ -186,53 +186,53 @@ impl, const D: usize> CircuitBuilder { fn select_opening_set( &mut self, b: BoolTarget, - os0: OpeningSetTarget, - os1: OpeningSetTarget, + os0: &OpeningSetTarget, + os1: &OpeningSetTarget, ) -> OpeningSetTarget { OpeningSetTarget { - constants: self.select_vec_ext(b, os0.constants, os1.constants), - plonk_sigmas: self.select_vec_ext(b, os0.plonk_sigmas, os1.plonk_sigmas), - wires: self.select_vec_ext(b, os0.wires, os1.wires), - plonk_zs: self.select_vec_ext(b, os0.plonk_zs, os1.plonk_zs), - plonk_zs_next: self.select_vec_ext(b, os0.plonk_zs_next, os1.plonk_zs_next), - partial_products: self.select_vec_ext(b, os0.partial_products, os1.partial_products), - quotient_polys: self.select_vec_ext(b, os0.quotient_polys, os1.quotient_polys), + constants: self.select_vec_ext(b, &os0.constants, &os1.constants), + plonk_sigmas: self.select_vec_ext(b, &os0.plonk_sigmas, &os1.plonk_sigmas), + wires: self.select_vec_ext(b, &os0.wires, &os1.wires), + plonk_zs: self.select_vec_ext(b, &os0.plonk_zs, &os1.plonk_zs), + plonk_zs_next: self.select_vec_ext(b, &os0.plonk_zs_next, &os1.plonk_zs_next), + partial_products: self.select_vec_ext(b, &os0.partial_products, &os1.partial_products), + quotient_polys: self.select_vec_ext(b, &os0.quotient_polys, &os1.quotient_polys), } } fn select_vec_ext( &mut self, b: BoolTarget, - v0: Vec>, - v1: Vec>, + v0: &[ExtensionTarget], + v1: &[ExtensionTarget], ) -> Vec> { - v0.into_iter() + v0.iter() .zip_eq(v1) - .map(|(e0, e1)| self.select_ext(b, e0, e1)) + .map(|(e0, e1)| self.select_ext(b, *e0, *e1)) .collect() } fn select_opening_proof( &mut self, b: BoolTarget, - proof0: FriProofTarget, - proof1: FriProofTarget, + proof0: &FriProofTarget, + proof1: &FriProofTarget, ) -> FriProofTarget { FriProofTarget { commit_phase_merkle_caps: self.select_vec_cap( b, - proof0.commit_phase_merkle_caps, - proof1.commit_phase_merkle_caps, + &proof0.commit_phase_merkle_caps, + &proof1.commit_phase_merkle_caps, ), query_round_proofs: self.select_vec_query_round( b, - proof0.query_round_proofs, - proof1.query_round_proofs, + &proof0.query_round_proofs, + &proof1.query_round_proofs, ), final_poly: PolynomialCoeffsExtTarget(self.select_vec_ext( b, - proof0.final_poly.0, - proof1.final_poly.0, + &proof0.final_poly.0, + &proof1.final_poly.0, )), pow_witness: self.select(b, proof0.pow_witness, proof1.pow_witness), } @@ -241,26 +241,26 @@ impl, const D: usize> CircuitBuilder { fn select_query_round( &mut self, b: BoolTarget, - qr0: FriQueryRoundTarget, - qr1: FriQueryRoundTarget, + qr0: &FriQueryRoundTarget, + qr1: &FriQueryRoundTarget, ) -> FriQueryRoundTarget { FriQueryRoundTarget { initial_trees_proof: self.select_initial_tree_proof( b, - qr0.initial_trees_proof, - qr1.initial_trees_proof, + &qr0.initial_trees_proof, + &qr1.initial_trees_proof, ), - steps: self.select_vec_query_step(b, qr0.steps, qr1.steps), + steps: self.select_vec_query_step(b, &qr0.steps, &qr1.steps), } } fn select_vec_query_round( &mut self, b: BoolTarget, - v0: Vec>, - v1: Vec>, + v0: &[FriQueryRoundTarget], + v1: &[FriQueryRoundTarget], ) -> Vec> { - v0.into_iter() + v0.iter() .zip_eq(v1) .map(|(qr0, qr1)| self.select_query_round(b, qr0, qr1)) .collect() @@ -269,14 +269,14 @@ impl, const D: usize> CircuitBuilder { fn select_initial_tree_proof( &mut self, b: BoolTarget, - proof0: FriInitialTreeProofTarget, - proof1: FriInitialTreeProofTarget, + proof0: &FriInitialTreeProofTarget, + proof1: &FriInitialTreeProofTarget, ) -> FriInitialTreeProofTarget { FriInitialTreeProofTarget { evals_proofs: proof0 .evals_proofs - .into_iter() - .zip_eq(proof1.evals_proofs) + .iter() + .zip_eq(&proof1.evals_proofs) .map(|((v0, p0), (v1, p1))| { ( self.select_vec(b, v0, v1), @@ -290,15 +290,15 @@ impl, const D: usize> CircuitBuilder { fn select_merkle_proof( &mut self, b: BoolTarget, - proof0: MerkleProofTarget, - proof1: MerkleProofTarget, + proof0: &MerkleProofTarget, + proof1: &MerkleProofTarget, ) -> MerkleProofTarget { MerkleProofTarget { siblings: proof0 .siblings - .into_iter() - .zip_eq(proof1.siblings) - .map(|(h0, h1)| self.select_hash(b, h0, h1)) + .iter() + .zip_eq(&proof1.siblings) + .map(|(h0, h1)| self.select_hash(b, *h0, *h1)) .collect(), } } @@ -306,22 +306,22 @@ impl, const D: usize> CircuitBuilder { fn select_query_step( &mut self, b: BoolTarget, - qs0: FriQueryStepTarget, - qs1: FriQueryStepTarget, + qs0: &FriQueryStepTarget, + qs1: &FriQueryStepTarget, ) -> FriQueryStepTarget { FriQueryStepTarget { - evals: self.select_vec_ext(b, qs0.evals, qs1.evals), - merkle_proof: self.select_merkle_proof(b, qs0.merkle_proof, qs1.merkle_proof), + evals: self.select_vec_ext(b, &qs0.evals, &qs1.evals), + merkle_proof: self.select_merkle_proof(b, &qs0.merkle_proof, &qs1.merkle_proof), } } fn select_vec_query_step( &mut self, b: BoolTarget, - v0: Vec>, - v1: Vec>, + v0: &[FriQueryStepTarget], + v1: &[FriQueryStepTarget], ) -> Vec> { - v0.into_iter() + v0.iter() .zip_eq(v1) .map(|(qs0, qs1)| self.select_query_step(b, qs0, qs1)) .collect() @@ -384,9 +384,9 @@ mod tests { let b = builder.constant_bool(F::rand().0 % 2 == 0); builder.conditionally_verify_proof( b, - pt, + &pt, &inner_data, - dummy_pt, + &dummy_pt, &dummy_inner_data, &data.common, ); diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs new file mode 100644 index 00000000..d39bfcf7 --- /dev/null +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -0,0 +1,227 @@ +use anyhow::Result; +use plonky2_field::extension::Extendable; + +use crate::gates::noop::NoopGate; +use crate::hash::hash_types::RichField; +use crate::iop::target::BoolTarget; +use crate::iop::witness::{PartialWitness, Witness}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::circuit_data::{ + CircuitData, CommonCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData, +}; +use crate::plonk::config::Hasher; +use crate::plonk::config::{AlgebraicHasher, GenericConfig}; +use crate::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}; +use crate::recursion::conditional_recursive_verifier::dummy_proof; + +pub struct CyclicRecursionData< + 'a, + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +> { + proof: &'a Option>, + verifier_data: &'a VerifierOnlyCircuitData, + common_data: &'a CommonCircuitData, +} + +pub struct CyclicRecursionTarget { + pub proof: ProofWithPublicInputsTarget, + pub verifier_data: VerifierCircuitTarget, + pub dummy_proof: ProofWithPublicInputsTarget, + pub dummy_verifier_data: VerifierCircuitTarget, + pub base_case: BoolTarget, +} + +impl, const D: usize> CircuitBuilder { + pub fn cyclic_recursion>( + mut self, + mut common_data: CommonCircuitData, + ) -> Result<(CircuitData, CyclicRecursionTarget)> + where + C::Hasher: AlgebraicHasher, + [(); C::Hasher::HASH_SIZE]:, + { + let verifier_data = VerifierCircuitTarget { + constants_sigmas_cap: self.add_virtual_cap(self.config.fri_config.cap_height), + circuit_digest: self.add_virtual_hash(), + }; + self.register_public_inputs(&verifier_data.circuit_digest.elements); + for i in 0..self.config.fri_config.num_cap_elements() { + self.register_public_inputs(&verifier_data.constants_sigmas_cap.0[i].elements); + } + let dummy_verifier_data = VerifierCircuitTarget { + constants_sigmas_cap: self.add_virtual_cap(self.config.fri_config.cap_height), + circuit_digest: self.add_virtual_hash(), + }; + let base_case = self.add_virtual_bool_target(); + self.register_public_input(base_case.target); + + common_data.num_public_inputs = self.num_public_inputs(); + common_data.degree_bits = common_data.degree_bits.max(13); + dbg!(common_data.degree_bits); + + let proof = self.add_virtual_proof_with_pis(&common_data); + let dummy_proof = self.add_virtual_proof_with_pis(&common_data); + + self.conditionally_verify_proof( + base_case, + &dummy_proof, + &dummy_verifier_data, + &proof, + &verifier_data, + &common_data, + ); + + while self.num_gates() < 1 << (common_data.degree_bits - 1) { + self.add_gate(NoopGate, vec![]); + } + + let data = self.build::(); + dbg!(&data.common.degree_bits, common_data.degree_bits); + assert_eq!(&data.common, &common_data); + Ok(( + data, + CyclicRecursionTarget { + proof, + verifier_data, + dummy_proof, + dummy_verifier_data, + base_case, + }, + )) + } +} + +/// Set the targets in a `ProofTarget` to their corresponding values in a `Proof`. +pub fn set_cyclic_recursion_data_target< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + pw: &mut PartialWitness, + cyclic_recursion_data_target: &CyclicRecursionTarget, + cyclic_recursion_data: &CyclicRecursionData, +) -> Result<()> +where + F: RichField + Extendable, + C::Hasher: AlgebraicHasher, + [(); C::Hasher::HASH_SIZE]:, +{ + if let Some(proof) = cyclic_recursion_data.proof { + pw.set_bool_target(cyclic_recursion_data_target.base_case, false); + 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, proof); + pw.set_verifier_data_target( + &cyclic_recursion_data_target.dummy_verifier_data, + 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.base_case, true); + pw.set_proof_with_pis_target(&cyclic_recursion_data_target.proof, &dummy_proof); + pw.set_verifier_data_target( + &cyclic_recursion_data_target.verifier_data, + &dummy_data.verifier_only, + ); + pw.set_proof_with_pis_target(&cyclic_recursion_data_target.dummy_proof, &dummy_proof); + pw.set_verifier_data_target( + &cyclic_recursion_data_target.dummy_verifier_data, + &dummy_data.verifier_only, + ); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use plonky2_field::extension::Extendable; + + use crate::field::types::Field; + use crate::hash::hash_types::RichField; + use crate::hash::poseidon::PoseidonHash; + use crate::iop::witness::{PartialWitness, Witness}; + use crate::plonk::circuit_builder::CircuitBuilder; + use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData, VerifierCircuitTarget}; + use crate::plonk::config::{AlgebraicHasher, GenericConfig, Hasher, PoseidonGoldilocksConfig}; + use crate::recursion::cyclic_recursion::{ + set_cyclic_recursion_data_target, CyclicRecursionData, + }; + + fn common_data_for_recursion< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, + >() -> CommonCircuitData + where + C::Hasher: AlgebraicHasher, + [(); C::Hasher::HASH_SIZE]:, + { + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + let data = builder.build::(); + let config = CircuitConfig::standard_recursion_config(); + let mut pw = PartialWitness::::new(); + let mut builder = CircuitBuilder::::new(config); + let proof = builder.add_virtual_proof_with_pis(&data.common); + let verifier_data = VerifierCircuitTarget { + constants_sigmas_cap: builder.add_virtual_cap(data.common.config.fri_config.cap_height), + circuit_digest: builder.add_virtual_hash(), + }; + builder.verify_proof(proof, &verifier_data, &data.common); + let data = builder.build::(); + + let config = CircuitConfig::standard_recursion_config(); + let mut builder = CircuitBuilder::::new(config); + let data = builder.build::(); + let config = CircuitConfig::standard_recursion_config(); + let mut pw = PartialWitness::::new(); + let mut builder = CircuitBuilder::::new(config); + let proof = builder.add_virtual_proof_with_pis(&data.common); + let verifier_data = VerifierCircuitTarget { + constants_sigmas_cap: builder.add_virtual_cap(data.common.config.fri_config.cap_height), + circuit_digest: builder.add_virtual_hash(), + }; + builder.verify_proof(proof, &verifier_data, &data.common); + builder.build::().common + } + + #[test] + fn test_cyclic_recursion() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + + let config = CircuitConfig::standard_recursion_config(); + let mut pw = PartialWitness::new(); + let mut builder = CircuitBuilder::::new(config); + + // Build realistic circuit + let t = builder.add_virtual_target(); + pw.set_target(t, F::rand()); + let t_inv = builder.inverse(t); + let h = builder.hash_n_to_hash_no_pad::(vec![t_inv]); + builder.register_public_inputs(&h.elements); + + let common_data = common_data_for_recursion::(); + dbg!(common_data.degree_bits); + + let (cyclic_circuit_data, cyclic_data_target) = builder.cyclic_recursion(common_data)?; + let cyclic_recursion_data = CyclicRecursionData { + proof: &None, + verifier_data: &cyclic_circuit_data.verifier_only, + common_data: &cyclic_circuit_data.common, + }; + set_cyclic_recursion_data_target(&mut pw, &cyclic_data_target, &cyclic_recursion_data)?; + let proof = cyclic_circuit_data.prove(pw)?; + cyclic_circuit_data.verify(proof); + + Ok(()) + } +} diff --git a/plonky2/src/recursion/mod.rs b/plonky2/src/recursion/mod.rs index b83c4a85..33e8212e 100644 --- a/plonky2/src/recursion/mod.rs +++ b/plonky2/src/recursion/mod.rs @@ -1,2 +1,3 @@ pub mod conditional_recursive_verifier; +pub mod cyclic_recursion; pub mod recursive_verifier; diff --git a/plonky2/src/recursion/recursive_verifier.rs b/plonky2/src/recursion/recursive_verifier.rs index c35f21e2..7d901236 100644 --- a/plonky2/src/recursion/recursive_verifier.rs +++ b/plonky2/src/recursion/recursive_verifier.rs @@ -455,199 +455,4 @@ mod tests { fn init_logger() { let _ = env_logger::builder().format_timestamp(None).try_init(); } - - #[test] - fn test_cyclic_recursion() -> Result<()> { - const D: usize = 2; - type C = PoseidonGoldilocksConfig; - type F = >::F; - // type FF = >::FE; - - let config = CircuitConfig::standard_recursion_config(); - let (proof, vd, cd) = dummy_proof::(&config, 1 << 14)?; - - let (proof, vd, cd) = - recursive_proof::(proof, vd, cd, &config, None, false, false)?; - let (_proof, _vd, mut cd) = - recursive_proof::(proof, vd, cd, &config, Some(14), false, false)?; - cd.num_public_inputs = 69; - - // First proof - let config = CircuitConfig::standard_recursion_config(); - let mut pw = PartialWitness::new(); - let mut builder = CircuitBuilder::::new(config); - - let verifier_data = VerifierCircuitTarget { - constants_sigmas_cap: builder.add_virtual_cap(builder.config.fri_config.cap_height), - circuit_digest: builder.add_virtual_hash(), - }; - builder.register_public_inputs(&verifier_data.circuit_digest.elements); - for i in 0..1 << builder.config.fri_config.cap_height { - builder.register_public_inputs(&verifier_data.constants_sigmas_cap.0[i].elements); - } - let dummy_verifier_data = VerifierCircuitTarget { - constants_sigmas_cap: builder.add_virtual_cap(builder.config.fri_config.cap_height), - circuit_digest: builder.add_virtual_hash(), - }; - let condition = builder.add_virtual_bool_target(); - builder.register_public_input(condition.target); - pw.set_bool_target(condition, false); - - let (dummy_proof, dummy_data) = - crate::recursion::conditional_recursive_verifier::dummy_proof(&cd)?; - let pt0 = builder.add_virtual_proof_with_pis(&cd); - let pt1 = builder.add_virtual_proof_with_pis(&cd); - - pw.set_proof_with_pis_target(&pt0, &dummy_proof); - pw.set_proof_with_pis_target(&pt1, &dummy_proof); - pw.set_hash_target( - dummy_verifier_data.circuit_digest, - dummy_data.verifier_only.circuit_digest, - ); - pw.set_cap_target( - &dummy_verifier_data.constants_sigmas_cap, - &dummy_data.verifier_only.constants_sigmas_cap, - ); - - builder.conditionally_verify_proof( - condition, - pt0, - &verifier_data, - pt1, - &dummy_verifier_data, - &cd, - ); - - while builder.num_gates() < 1 << 13 { - builder.add_gate(NoopGate, vec![]); - } - - let data = builder.build::(); - dbg!(cd.degree_bits); - dbg!(data.common.degree_bits); - assert_eq!(&data.common, &cd); - pw.set_verifier_data_target(&verifier_data, &data.verifier_only); - let proof = data.prove(pw)?; - assert_eq!( - data.verifier_only.circuit_digest.elements[0], - proof.public_inputs[0] - ); - data.verify(proof.clone())?; - - // Second proof - let config = CircuitConfig::standard_recursion_config(); - let mut pw = PartialWitness::new(); - let mut builder = CircuitBuilder::::new(config); - - let verifier_data = VerifierCircuitTarget { - constants_sigmas_cap: builder.add_virtual_cap(builder.config.fri_config.cap_height), - circuit_digest: builder.add_virtual_hash(), - }; - builder.register_public_inputs(&verifier_data.circuit_digest.elements); - for i in 0..1 << builder.config.fri_config.cap_height { - builder.register_public_inputs(&verifier_data.constants_sigmas_cap.0[i].elements); - } - let dummy_verifier_data = VerifierCircuitTarget { - constants_sigmas_cap: builder.add_virtual_cap(builder.config.fri_config.cap_height), - circuit_digest: builder.add_virtual_hash(), - }; - let condition = builder.add_virtual_bool_target(); - builder.register_public_input(condition.target); - pw.set_bool_target(condition, true); - - let pt0 = builder.add_virtual_proof_with_pis(&data.common); - let pt1 = builder.add_virtual_proof_with_pis(&data.common); - - pw.set_proof_with_pis_target(&pt0, &proof); - pw.set_proof_with_pis_target(&pt1, &proof); - - builder.conditionally_verify_proof( - condition, - pt0, - &verifier_data, - pt1, - &dummy_verifier_data, - &data.common, - ); - - while builder.num_gates() < 1 << 13 { - builder.add_gate(NoopGate, vec![]); - } - - let data1 = builder.build::(); - assert_eq!(data.common, data1.common); - assert_eq!(data.verifier_only, data1.verifier_only); - dbg!(cd.degree_bits); - dbg!(data1.common.degree_bits); - pw.set_verifier_data_target(&verifier_data, &data.verifier_only); - pw.set_verifier_data_target(&dummy_verifier_data, &data.verifier_only); - let proof = data.prove(pw)?; - assert_eq!( - data.verifier_only.circuit_digest.elements[0], - proof.public_inputs[0] - ); - assert_eq!( - data1.verifier_only.circuit_digest.elements[0], - proof.public_inputs[0] - ); - data.verify(proof.clone())?; - - // Second proof - let config = CircuitConfig::standard_recursion_config(); - let mut pw = PartialWitness::new(); - let mut builder = CircuitBuilder::::new(config); - - let verifier_data = VerifierCircuitTarget { - constants_sigmas_cap: builder.add_virtual_cap(builder.config.fri_config.cap_height), - circuit_digest: builder.add_virtual_hash(), - }; - builder.register_public_inputs(&verifier_data.circuit_digest.elements); - for i in 0..1 << builder.config.fri_config.cap_height { - builder.register_public_inputs(&verifier_data.constants_sigmas_cap.0[i].elements); - } - let dummy_verifier_data = VerifierCircuitTarget { - constants_sigmas_cap: builder.add_virtual_cap(builder.config.fri_config.cap_height), - circuit_digest: builder.add_virtual_hash(), - }; - let condition = builder.add_virtual_bool_target(); - builder.register_public_input(condition.target); - pw.set_bool_target(condition, true); - - let pt0 = builder.add_virtual_proof_with_pis(&data.common); - let pt1 = builder.add_virtual_proof_with_pis(&data.common); - - pw.set_proof_with_pis_target(&pt0, &proof); - pw.set_proof_with_pis_target(&pt1, &proof); - - builder.conditionally_verify_proof( - condition, - pt0, - &verifier_data, - pt1, - &dummy_verifier_data, - &data.common, - ); - - while builder.num_gates() < 1 << 13 { - builder.add_gate(NoopGate, vec![]); - } - - let data2 = builder.build::(); - assert_eq!(data.common, data2.common); - assert_eq!(data.verifier_only, data2.verifier_only); - dbg!(cd.degree_bits); - dbg!(data1.common.degree_bits); - pw.set_verifier_data_target(&verifier_data, &data.verifier_only); - pw.set_verifier_data_target(&dummy_verifier_data, &data.verifier_only); - let proof = data.prove(pw)?; - assert_eq!( - data.verifier_only.circuit_digest.elements[0], - proof.public_inputs[0] - ); - assert_eq!( - data1.verifier_only.circuit_digest.elements[0], - proof.public_inputs[0] - ); - data.verify(proof) - } } From 861b66a34a9aa89c305881f623293f3427724cc1 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 14 Oct 2022 10:32:40 +0200 Subject: [PATCH 05/18] Test passes for base proof --- plonky2/src/recursion/cyclic_recursion.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs index d39bfcf7..0a3308e6 100644 --- a/plonky2/src/recursion/cyclic_recursion.rs +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -59,7 +59,7 @@ impl, const D: usize> CircuitBuilder { common_data.num_public_inputs = self.num_public_inputs(); common_data.degree_bits = common_data.degree_bits.max(13); - dbg!(common_data.degree_bits); + common_data.fri_params.degree_bits = common_data.fri_params.degree_bits.max(13); let proof = self.add_virtual_proof_with_pis(&common_data); let dummy_proof = self.add_virtual_proof_with_pis(&common_data); @@ -76,10 +76,13 @@ impl, const D: usize> CircuitBuilder { while self.num_gates() < 1 << (common_data.degree_bits - 1) { self.add_gate(NoopGate, vec![]); } + for g in &common_data.gates { + self.add_gate_to_gate_set(g.clone()); + } let data = self.build::(); - dbg!(&data.common.degree_bits, common_data.degree_bits); assert_eq!(&data.common, &common_data); + Ok(( data, CyclicRecursionTarget { @@ -179,7 +182,6 @@ mod tests { let config = CircuitConfig::standard_recursion_config(); let mut builder = CircuitBuilder::::new(config); - let data = builder.build::(); let config = CircuitConfig::standard_recursion_config(); let mut pw = PartialWitness::::new(); let mut builder = CircuitBuilder::::new(config); @@ -210,7 +212,6 @@ mod tests { builder.register_public_inputs(&h.elements); let common_data = common_data_for_recursion::(); - dbg!(common_data.degree_bits); let (cyclic_circuit_data, cyclic_data_target) = builder.cyclic_recursion(common_data)?; let cyclic_recursion_data = CyclicRecursionData { From fce7a4797ad400b0e86130afde2fa11850af537f Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 14 Oct 2022 14:59:59 +0200 Subject: [PATCH 06/18] Working --- ecdsa/src/gadgets/glv.rs | 4 +- ecdsa/src/gadgets/nonnative.rs | 4 +- plonky2/src/gadgets/arithmetic.rs | 2 +- plonky2/src/plonk/circuit_builder.rs | 3 +- plonky2/src/recursion/cyclic_recursion.rs | 195 ++++++++++++++++++++-- 5 files changed, 191 insertions(+), 17 deletions(-) diff --git a/ecdsa/src/gadgets/glv.rs b/ecdsa/src/gadgets/glv.rs index 4302023e..539b5de3 100644 --- a/ecdsa/src/gadgets/glv.rs +++ b/ecdsa/src/gadgets/glv.rs @@ -55,8 +55,8 @@ impl, const D: usize> CircuitBuilderGlv ) { let k1 = self.add_virtual_nonnative_target_sized::(4); let k2 = self.add_virtual_nonnative_target_sized::(4); - let k1_neg = self.add_virtual_bool_target(); - let k2_neg = self.add_virtual_bool_target(); + let k1_neg = self.add_virtual_bool_target_unsafe(); + let k2_neg = self.add_virtual_bool_target_unsafe(); self.add_simple_generator(GLVDecompositionGenerator:: { k: k.clone(), diff --git a/ecdsa/src/gadgets/nonnative.rs b/ecdsa/src/gadgets/nonnative.rs index c6ff4753..29520bed 100644 --- a/ecdsa/src/gadgets/nonnative.rs +++ b/ecdsa/src/gadgets/nonnative.rs @@ -183,7 +183,7 @@ impl, const D: usize> CircuitBuilderNonNative b: &NonNativeTarget, ) -> NonNativeTarget { let sum = self.add_virtual_nonnative_target::(); - let overflow = self.add_virtual_bool_target(); + let overflow = self.add_virtual_bool_target_unsafe(); self.add_simple_generator(NonNativeAdditionGenerator:: { a: a.clone(), @@ -282,7 +282,7 @@ impl, const D: usize> CircuitBuilderNonNative b: &NonNativeTarget, ) -> NonNativeTarget { let diff = self.add_virtual_nonnative_target::(); - let overflow = self.add_virtual_bool_target(); + let overflow = self.add_virtual_bool_target_unsafe(); self.add_simple_generator(NonNativeSubtractionGenerator:: { a: a.clone(), diff --git a/plonky2/src/gadgets/arithmetic.rs b/plonky2/src/gadgets/arithmetic.rs index f4722df4..33facd74 100644 --- a/plonky2/src/gadgets/arithmetic.rs +++ b/plonky2/src/gadgets/arithmetic.rs @@ -345,7 +345,7 @@ impl, const D: usize> CircuitBuilder { pub fn is_equal(&mut self, x: Target, y: Target) -> BoolTarget { let zero = self.zero(); - let equal = self.add_virtual_bool_target(); + let equal = self.add_virtual_bool_target_unsafe(); let not_equal = self.not(equal); let inv = self.add_virtual_target(); self.add_simple_generator(EqualityGenerator { x, y, equal, inv }); diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index d5a748e5..24e83d01 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -202,8 +202,7 @@ impl, const D: usize> CircuitBuilder { PolynomialCoeffsExtTarget(coeffs) } - // TODO: Unsafe - pub fn add_virtual_bool_target(&mut self) -> BoolTarget { + pub fn add_virtual_bool_target_unsafe(&mut self) -> BoolTarget { BoolTarget::new_unsafe(self.add_virtual_target()) } diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs index 0a3308e6..abf7f7a2 100644 --- a/plonky2/src/recursion/cyclic_recursion.rs +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -1,9 +1,12 @@ -use anyhow::Result; +use anyhow::{ensure, Result}; +use itertools::Itertools; use plonky2_field::extension::Extendable; +use plonky2_field::types::Field; use crate::gates::noop::NoopGate; -use crate::hash::hash_types::RichField; -use crate::iop::target::BoolTarget; +use crate::hash::hash_types::{HashOut, HashOutTarget, MerkleCapTarget, RichField}; +use crate::hash::merkle_tree::MerkleCap; +use crate::iop::target::{BoolTarget, Target}; use crate::iop::witness::{PartialWitness, Witness}; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::circuit_data::{ @@ -33,6 +36,85 @@ pub struct CyclicRecursionTarget { pub base_case: BoolTarget, } +pub struct CyclicPublicInputs< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +> { + pub circuit_digest: HashOut, + pub constants_sigmas_cap: MerkleCap, + pub base_case: bool, +} + +impl, C: GenericConfig, const D: usize> + CyclicPublicInputs +{ + fn from_slice(slice: &[F], common_data: &CommonCircuitData) -> Result + where + C::Hasher: AlgebraicHasher, + { + // The structure of the public inputs is `[...,circuit_digest, constants_sigmas_cap, base_case]`. + let cap_len = common_data.config.fri_config.num_cap_elements(); + let len = slice.len(); + ensure!(len >= 4 + 4 * cap_len + 1, "Not enough public inputs"); + let base_case = slice[len - 1]; + ensure!( + base_case.is_one() || base_case.is_zero(), + "Base case flag {:?} is not binary", + base_case + ); + let constants_sigmas_cap = MerkleCap( + (0..cap_len) + .map(|i| HashOut { + elements: std::array::from_fn(|j| slice[len - 1 - 4 * (cap_len - i) + j]), + }) + .collect(), + ); + let circuit_digest = + HashOut::from_partial(&slice[len - 5 - 4 * cap_len..len - 1 - 4 * cap_len]); + + Ok(Self { + circuit_digest, + constants_sigmas_cap, + base_case: base_case.is_one(), + }) + } +} + +pub struct CyclicPublicInputsTarget { + pub circuit_digest: HashOutTarget, + pub constants_sigmas_cap: MerkleCapTarget, + pub base_case: Target, +} + +impl CyclicPublicInputsTarget { + fn from_slice, C: GenericConfig, const D: usize>( + slice: &[Target], + common_data: &CommonCircuitData, + ) -> Result { + let cap_len = common_data.config.fri_config.num_cap_elements(); + let len = slice.len(); + ensure!(len >= 4 + 4 * cap_len + 1, "Not enough public inputs"); + let base_case = slice[len - 1]; + let constants_sigmas_cap = MerkleCapTarget( + (0..cap_len) + .map(|i| HashOutTarget { + elements: std::array::from_fn(|j| slice[len - 1 - 4 * (cap_len - i) + j]), + }) + .collect(), + ); + let circuit_digest = HashOutTarget { + elements: std::array::from_fn(|i| slice[len - 5 - 4 * cap_len + i]), + }; + + Ok(Self { + circuit_digest, + constants_sigmas_cap, + base_case, + }) + } +} + impl, const D: usize> CircuitBuilder { pub fn cyclic_recursion>( mut self, @@ -46,24 +128,39 @@ impl, const D: usize> CircuitBuilder { constants_sigmas_cap: self.add_virtual_cap(self.config.fri_config.cap_height), circuit_digest: self.add_virtual_hash(), }; + // The verifier data are public inputs. self.register_public_inputs(&verifier_data.circuit_digest.elements); for i in 0..self.config.fri_config.num_cap_elements() { self.register_public_inputs(&verifier_data.constants_sigmas_cap.0[i].elements); } + let dummy_verifier_data = VerifierCircuitTarget { constants_sigmas_cap: self.add_virtual_cap(self.config.fri_config.cap_height), circuit_digest: self.add_virtual_hash(), }; - let base_case = self.add_virtual_bool_target(); + // Unsafe is ok since `base_case` is a public input and its booleaness should be checked in the verifier. + let base_case = self.add_virtual_bool_target_unsafe(); self.register_public_input(base_case.target); common_data.num_public_inputs = self.num_public_inputs(); + // The `conditionally_verify_proof` gadget below takes 2^12 gates, so `degree_bits` cannot be smaller than 13. common_data.degree_bits = common_data.degree_bits.max(13); common_data.fri_params.degree_bits = common_data.fri_params.degree_bits.max(13); let proof = self.add_virtual_proof_with_pis(&common_data); let dummy_proof = self.add_virtual_proof_with_pis(&common_data); + let pis = CyclicPublicInputsTarget::from_slice(&proof.public_inputs, &common_data)?; + self.connect_hashes(pis.circuit_digest, verifier_data.circuit_digest); + for (h0, h1) in pis + .constants_sigmas_cap + .0 + .iter() + .zip_eq(&verifier_data.constants_sigmas_cap.0) + { + self.connect_hashes(*h0, *h1); + } + self.conditionally_verify_proof( base_case, &dummy_proof, @@ -107,7 +204,6 @@ pub fn set_cyclic_recursion_data_target< cyclic_recursion_data: &CyclicRecursionData, ) -> Result<()> where - F: RichField + Extendable, C::Hasher: AlgebraicHasher, [(); C::Hasher::HASH_SIZE]:, { @@ -124,12 +220,29 @@ where cyclic_recursion_data.verifier_data, ); } else { + dbg!("hi"); let (dummy_proof, dummy_data) = dummy_proof(cyclic_recursion_data.common_data)?; pw.set_bool_target(cyclic_recursion_data_target.base_case, true); - pw.set_proof_with_pis_target(&cyclic_recursion_data_target.proof, &dummy_proof); + let mut dummy_proof_real_vd = dummy_proof.clone(); + let pis_len = dummy_proof_real_vd.public_inputs.len(); + let num_cap = cyclic_recursion_data + .common_data + .config + .fri_config + .num_cap_elements(); + let s = pis_len - 5 - 4 * num_cap; + dummy_proof_real_vd.public_inputs[s..s + 4] + .copy_from_slice(&cyclic_recursion_data.verifier_data.circuit_digest.elements); + for i in 0..num_cap { + dummy_proof_real_vd.public_inputs[s + 4 * (1 + i)..s + 4 * (2 + i)].copy_from_slice( + &cyclic_recursion_data.verifier_data.constants_sigmas_cap.0[i].elements, + ); + } + pw.set_proof_with_pis_target(&cyclic_recursion_data_target.proof, &dummy_proof_real_vd); + dbg!(cyclic_recursion_data.verifier_data.circuit_digest); pw.set_verifier_data_target( &cyclic_recursion_data_target.verifier_data, - &dummy_data.verifier_only, + cyclic_recursion_data.verifier_data, ); pw.set_proof_with_pis_target(&cyclic_recursion_data_target.dummy_proof, &dummy_proof); pw.set_verifier_data_target( @@ -141,6 +254,29 @@ where Ok(()) } +pub fn check_cyclic_proof_verifier_data< + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + proof: &ProofWithPublicInputs, + verifier_data: &VerifierOnlyCircuitData, + common_data: &CommonCircuitData, +) -> Result<()> +where + C::Hasher: AlgebraicHasher, +{ + let pis = CyclicPublicInputs::from_slice(&proof.public_inputs, common_data)?; + dbg!(pis.circuit_digest); + dbg!(verifier_data.circuit_digest); + if !pis.base_case { + ensure!(verifier_data.constants_sigmas_cap == pis.constants_sigmas_cap); + ensure!(verifier_data.circuit_digest == pis.circuit_digest); + } + + Ok(()) +} + #[cfg(test)] mod tests { use anyhow::Result; @@ -154,7 +290,7 @@ mod tests { use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData, VerifierCircuitTarget}; use crate::plonk::config::{AlgebraicHasher, GenericConfig, Hasher, PoseidonGoldilocksConfig}; use crate::recursion::cyclic_recursion::{ - set_cyclic_recursion_data_target, CyclicRecursionData, + check_cyclic_proof_verifier_data, set_cyclic_recursion_data_target, CyclicRecursionData, }; fn common_data_for_recursion< @@ -183,7 +319,7 @@ mod tests { let config = CircuitConfig::standard_recursion_config(); let mut builder = CircuitBuilder::::new(config); let config = CircuitConfig::standard_recursion_config(); - let mut pw = PartialWitness::::new(); + let pw = PartialWitness::::new(); let mut builder = CircuitBuilder::::new(config); let proof = builder.add_virtual_proof_with_pis(&data.common); let verifier_data = VerifierCircuitTarget { @@ -220,8 +356,47 @@ mod tests { common_data: &cyclic_circuit_data.common, }; set_cyclic_recursion_data_target(&mut pw, &cyclic_data_target, &cyclic_recursion_data)?; + dbg!("yo"); let proof = cyclic_circuit_data.prove(pw)?; - cyclic_circuit_data.verify(proof); + check_cyclic_proof_verifier_data( + &proof, + &cyclic_recursion_data.verifier_data, + cyclic_recursion_data.common_data, + )?; + cyclic_circuit_data.verify(proof.clone())?; + + let mut pw = PartialWitness::new(); + pw.set_target(t, F::rand()); + let cyclic_recursion_data = CyclicRecursionData { + proof: &Some(proof), + verifier_data: &cyclic_circuit_data.verifier_only, + common_data: &cyclic_circuit_data.common, + }; + set_cyclic_recursion_data_target(&mut pw, &cyclic_data_target, &cyclic_recursion_data)?; + dbg!("yo"); + let proof = cyclic_circuit_data.prove(pw)?; + check_cyclic_proof_verifier_data( + &proof, + &cyclic_recursion_data.verifier_data, + cyclic_recursion_data.common_data, + )?; + cyclic_circuit_data.verify(proof.clone())?; + + let mut pw = PartialWitness::new(); + pw.set_target(t, F::rand()); + let cyclic_recursion_data = CyclicRecursionData { + proof: &Some(proof), + verifier_data: &cyclic_circuit_data.verifier_only, + common_data: &cyclic_circuit_data.common, + }; + set_cyclic_recursion_data_target(&mut pw, &cyclic_data_target, &cyclic_recursion_data)?; + let proof = cyclic_circuit_data.prove(pw)?; + check_cyclic_proof_verifier_data( + &proof, + &cyclic_recursion_data.verifier_data, + cyclic_recursion_data.common_data, + )?; + cyclic_circuit_data.verify(proof.clone())?; Ok(()) } From 51cea8d98b82cd94b016edeb7fe57247f85c9a6f Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 14 Oct 2022 15:34:08 +0200 Subject: [PATCH 07/18] `base_case` is decreasing --- plonky2/src/recursion/cyclic_recursion.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs index abf7f7a2..cc08d4eb 100644 --- a/plonky2/src/recursion/cyclic_recursion.rs +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -84,7 +84,7 @@ impl, C: GenericConfig, const D: usize> pub struct CyclicPublicInputsTarget { pub circuit_digest: HashOutTarget, pub constants_sigmas_cap: MerkleCapTarget, - pub base_case: Target, + pub base_case: BoolTarget, } impl CyclicPublicInputsTarget { @@ -95,7 +95,7 @@ impl CyclicPublicInputsTarget { let cap_len = common_data.config.fri_config.num_cap_elements(); let len = slice.len(); ensure!(len >= 4 + 4 * cap_len + 1, "Not enough public inputs"); - let base_case = slice[len - 1]; + let base_case = BoolTarget::new_unsafe(slice[len - 1]); let constants_sigmas_cap = MerkleCapTarget( (0..cap_len) .map(|i| HashOutTarget { @@ -151,6 +151,12 @@ impl, const D: usize> CircuitBuilder { let dummy_proof = self.add_virtual_proof_with_pis(&common_data); let pis = CyclicPublicInputsTarget::from_slice(&proof.public_inputs, &common_data)?; + // Check that the previous base case flag was boolean. + self.assert_bool(pis.base_case); + // Check that we cannot go from a non-base case to a base case by checking `previous_base_case - base_case \in {0,1}`. + let decrease = BoolTarget::new_unsafe(self.sub(pis.base_case.target, base_case.target)); + self.assert_bool(decrease); + // 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 .constants_sigmas_cap @@ -220,11 +226,11 @@ where cyclic_recursion_data.verifier_data, ); } else { - dbg!("hi"); let (dummy_proof, dummy_data) = dummy_proof(cyclic_recursion_data.common_data)?; pw.set_bool_target(cyclic_recursion_data_target.base_case, true); let mut dummy_proof_real_vd = dummy_proof.clone(); let pis_len = dummy_proof_real_vd.public_inputs.len(); + dummy_proof_real_vd.public_inputs[pis_len - 1] = F::ONE; let num_cap = cyclic_recursion_data .common_data .config @@ -239,7 +245,6 @@ where ); } pw.set_proof_with_pis_target(&cyclic_recursion_data_target.proof, &dummy_proof_real_vd); - dbg!(cyclic_recursion_data.verifier_data.circuit_digest); pw.set_verifier_data_target( &cyclic_recursion_data_target.verifier_data, cyclic_recursion_data.verifier_data, @@ -267,8 +272,6 @@ where C::Hasher: AlgebraicHasher, { let pis = CyclicPublicInputs::from_slice(&proof.public_inputs, common_data)?; - dbg!(pis.circuit_digest); - dbg!(verifier_data.circuit_digest); if !pis.base_case { ensure!(verifier_data.constants_sigmas_cap == pis.constants_sigmas_cap); ensure!(verifier_data.circuit_digest == pis.circuit_digest); @@ -356,7 +359,6 @@ mod tests { common_data: &cyclic_circuit_data.common, }; set_cyclic_recursion_data_target(&mut pw, &cyclic_data_target, &cyclic_recursion_data)?; - dbg!("yo"); let proof = cyclic_circuit_data.prove(pw)?; check_cyclic_proof_verifier_data( &proof, @@ -373,7 +375,6 @@ mod tests { common_data: &cyclic_circuit_data.common, }; set_cyclic_recursion_data_target(&mut pw, &cyclic_data_target, &cyclic_recursion_data)?; - dbg!("yo"); let proof = cyclic_circuit_data.prove(pw)?; check_cyclic_proof_verifier_data( &proof, From 35b173ed347116064da36b27cb42d2f1474dea15 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 14 Oct 2022 16:26:05 +0200 Subject: [PATCH 08/18] Comments --- plonky2/src/recursion/cyclic_recursion.rs | 56 +++++++++++++---------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs index cc08d4eb..9e4dcb4e 100644 --- a/plonky2/src/recursion/cyclic_recursion.rs +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -1,7 +1,7 @@ +#![allow(clippy::int_plus_one)] // Makes more sense for some inequalities below. use anyhow::{ensure, Result}; use itertools::Itertools; use plonky2_field::extension::Extendable; -use plonky2_field::types::Field; use crate::gates::noop::NoopGate; use crate::hash::hash_types::{HashOut, HashOutTarget, MerkleCapTarget, RichField}; @@ -138,6 +138,8 @@ impl, const D: usize> CircuitBuilder { constants_sigmas_cap: self.add_virtual_cap(self.config.fri_config.cap_height), circuit_digest: self.add_virtual_hash(), }; + + // Flag set to true for the base case of the cycle where we verify a dummy proof to bootstrap the cycle. Set to false otherwise. // Unsafe is ok since `base_case` is a public input and its booleaness should be checked in the verifier. let base_case = self.add_virtual_bool_target_unsafe(); self.register_public_input(base_case.target); @@ -167,6 +169,7 @@ impl, const D: usize> CircuitBuilder { self.connect_hashes(*h0, *h1); } + // Verify the dummy proof if `base_case` is set to true, otherwise verify the "real" proof. self.conditionally_verify_proof( base_case, &dummy_proof, @@ -176,15 +179,17 @@ impl, const D: usize> CircuitBuilder { &common_data, ); + // Make sure we have enough gates to match `common_data`. while self.num_gates() < 1 << (common_data.degree_bits - 1) { self.add_gate(NoopGate, vec![]); } + // Make sure we have every gate to match `common_data`. for g in &common_data.gates { self.add_gate_to_gate_set(g.clone()); } let data = self.build::(); - assert_eq!(&data.common, &common_data); + ensure!(data.common == common_data, "Common data does not match."); Ok(( data, @@ -199,7 +204,7 @@ impl, const D: usize> CircuitBuilder { } } -/// Set the targets in a `ProofTarget` to their corresponding values in a `Proof`. +/// Set the targets in a `CyclicRecursionTarget` to their corresponding values in a `CyclicRecursionData`. pub fn set_cyclic_recursion_data_target< F: RichField + Extendable, C: GenericConfig, @@ -228,23 +233,27 @@ where } else { let (dummy_proof, dummy_data) = dummy_proof(cyclic_recursion_data.common_data)?; pw.set_bool_target(cyclic_recursion_data_target.base_case, true); - let mut dummy_proof_real_vd = dummy_proof.clone(); - let pis_len = dummy_proof_real_vd.public_inputs.len(); - dummy_proof_real_vd.public_inputs[pis_len - 1] = F::ONE; + let mut proof = dummy_proof.clone(); + let pis_len = proof.public_inputs.len(); + // A base case must be following another base case. + proof.public_inputs[pis_len - 1] = F::ONE; + // 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 .common_data .config .fri_config .num_cap_elements(); let s = pis_len - 5 - 4 * num_cap; - dummy_proof_real_vd.public_inputs[s..s + 4] + proof.public_inputs[s..s + 4] .copy_from_slice(&cyclic_recursion_data.verifier_data.circuit_digest.elements); for i in 0..num_cap { - dummy_proof_real_vd.public_inputs[s + 4 * (1 + i)..s + 4 * (2 + i)].copy_from_slice( + 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, ); } - pw.set_proof_with_pis_target(&cyclic_recursion_data_target.proof, &dummy_proof_real_vd); + + 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, @@ -259,6 +268,9 @@ where Ok(()) } +/// Additional checks to be performed on a cyclic recursive proof in addition to verifying the proof. +/// Checks that the `base_case` flag is boolean and that the purported verifier data in the public inputs +/// match the real verifier data. pub fn check_cyclic_proof_verifier_data< F: RichField + Extendable, C: GenericConfig, @@ -296,6 +308,7 @@ mod tests { check_cyclic_proof_verifier_data, set_cyclic_recursion_data_target, CyclicRecursionData, }; + // Generates `CommonCircuitData` usable for recursion. fn common_data_for_recursion< F: RichField + Extendable, C: GenericConfig, @@ -306,10 +319,9 @@ mod tests { [(); C::Hasher::HASH_SIZE]:, { let config = CircuitConfig::standard_recursion_config(); - let mut builder = CircuitBuilder::::new(config); + let builder = CircuitBuilder::::new(config); let data = builder.build::(); let config = CircuitConfig::standard_recursion_config(); - let mut pw = PartialWitness::::new(); let mut builder = CircuitBuilder::::new(config); let proof = builder.add_virtual_proof_with_pis(&data.common); let verifier_data = VerifierCircuitTarget { @@ -321,9 +333,6 @@ mod tests { let config = CircuitConfig::standard_recursion_config(); let mut builder = CircuitBuilder::::new(config); - let config = CircuitConfig::standard_recursion_config(); - let pw = PartialWitness::::new(); - let mut builder = CircuitBuilder::::new(config); let proof = builder.add_virtual_proof_with_pis(&data.common); let verifier_data = VerifierCircuitTarget { constants_sigmas_cap: builder.add_virtual_cap(data.common.config.fri_config.cap_height), @@ -352,9 +361,10 @@ mod tests { let common_data = common_data_for_recursion::(); + // Add cyclic recursion gadget. let (cyclic_circuit_data, cyclic_data_target) = builder.cyclic_recursion(common_data)?; let cyclic_recursion_data = CyclicRecursionData { - proof: &None, + 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, }; @@ -362,15 +372,16 @@ mod tests { let proof = cyclic_circuit_data.prove(pw)?; check_cyclic_proof_verifier_data( &proof, - &cyclic_recursion_data.verifier_data, + cyclic_recursion_data.verifier_data, cyclic_recursion_data.common_data, )?; cyclic_circuit_data.verify(proof.clone())?; + // 1st recursive layer. let mut pw = PartialWitness::new(); pw.set_target(t, F::rand()); let cyclic_recursion_data = CyclicRecursionData { - proof: &Some(proof), + proof: &Some(proof), // Input previous proof. verifier_data: &cyclic_circuit_data.verifier_only, common_data: &cyclic_circuit_data.common, }; @@ -378,15 +389,16 @@ mod tests { let proof = cyclic_circuit_data.prove(pw)?; check_cyclic_proof_verifier_data( &proof, - &cyclic_recursion_data.verifier_data, + cyclic_recursion_data.verifier_data, cyclic_recursion_data.common_data, )?; cyclic_circuit_data.verify(proof.clone())?; + // 2nd recursive layer. let mut pw = PartialWitness::new(); pw.set_target(t, F::rand()); let cyclic_recursion_data = CyclicRecursionData { - proof: &Some(proof), + proof: &Some(proof), // Input previous proof. verifier_data: &cyclic_circuit_data.verifier_only, common_data: &cyclic_circuit_data.common, }; @@ -394,11 +406,9 @@ mod tests { let proof = cyclic_circuit_data.prove(pw)?; check_cyclic_proof_verifier_data( &proof, - &cyclic_recursion_data.verifier_data, + cyclic_recursion_data.verifier_data, cyclic_recursion_data.common_data, )?; - cyclic_circuit_data.verify(proof.clone())?; - - Ok(()) + cyclic_circuit_data.verify(proof) } } From 366567935c6182ee4f3760ab6e384655567ac13e Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 17 Oct 2022 10:34:08 +0200 Subject: [PATCH 09/18] Fix conflict --- plonky2/src/recursion/conditional_recursive_verifier.rs | 1 - plonky2/src/recursion/cyclic_recursion.rs | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/plonky2/src/recursion/conditional_recursive_verifier.rs b/plonky2/src/recursion/conditional_recursive_verifier.rs index 2c406904..b4da993e 100644 --- a/plonky2/src/recursion/conditional_recursive_verifier.rs +++ b/plonky2/src/recursion/conditional_recursive_verifier.rs @@ -24,7 +24,6 @@ use crate::plonk::proof::{ use crate::with_context; /// Generate a proof having a given `CommonCircuitData`. -#[allow(unused)] // TODO: should be used soon. pub(crate) fn dummy_proof< F: RichField + Extendable, C: GenericConfig, diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs index 9e4dcb4e..6260a584 100644 --- a/plonky2/src/recursion/cyclic_recursion.rs +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -146,7 +146,6 @@ impl, const D: usize> CircuitBuilder { common_data.num_public_inputs = self.num_public_inputs(); // The `conditionally_verify_proof` gadget below takes 2^12 gates, so `degree_bits` cannot be smaller than 13. - common_data.degree_bits = common_data.degree_bits.max(13); common_data.fri_params.degree_bits = common_data.fri_params.degree_bits.max(13); let proof = self.add_virtual_proof_with_pis(&common_data); @@ -180,7 +179,7 @@ impl, const D: usize> CircuitBuilder { ); // Make sure we have enough gates to match `common_data`. - while self.num_gates() < 1 << (common_data.degree_bits - 1) { + while self.num_gates() < (common_data.degree() / 2) { self.add_gate(NoopGate, vec![]); } // Make sure we have every gate to match `common_data`. @@ -261,7 +260,7 @@ where pw.set_proof_with_pis_target(&cyclic_recursion_data_target.dummy_proof, &dummy_proof); pw.set_verifier_data_target( &cyclic_recursion_data_target.dummy_verifier_data, - &dummy_data.verifier_only, + &dummy_data, ); } From 09cee22d1f47037be3d6c9ae4a7761cc2393546b Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 17 Oct 2022 14:56:16 +0200 Subject: [PATCH 10/18] Better test --- .../conditional_recursive_verifier.rs | 2 +- plonky2/src/recursion/cyclic_recursion.rs | 103 +++++++++++++++--- 2 files changed, 91 insertions(+), 14 deletions(-) diff --git a/plonky2/src/recursion/conditional_recursive_verifier.rs b/plonky2/src/recursion/conditional_recursive_verifier.rs index b4da993e..510bb438 100644 --- a/plonky2/src/recursion/conditional_recursive_verifier.rs +++ b/plonky2/src/recursion/conditional_recursive_verifier.rs @@ -182,7 +182,7 @@ impl, const D: usize> CircuitBuilder { .collect() } - fn select_hash( + pub(crate) fn select_hash( &mut self, b: BoolTarget, h0: HashOutTarget, diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs index 6260a584..e968e5e7 100644 --- a/plonky2/src/recursion/cyclic_recursion.rs +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -118,12 +118,19 @@ impl CyclicPublicInputsTarget { impl, const D: usize> CircuitBuilder { pub fn cyclic_recursion>( mut self, + previous_virtual_public_inputs: &[Target], + previous_base_case: Target, mut common_data: CommonCircuitData, ) -> Result<(CircuitData, CyclicRecursionTarget)> where C::Hasher: AlgebraicHasher, [(); C::Hasher::HASH_SIZE]:, { + ensure!( + previous_virtual_public_inputs.len() == self.num_public_inputs(), + "Incorrect number of public inputs." + ); + let verifier_data = VerifierCircuitTarget { constants_sigmas_cap: self.add_virtual_cap(self.config.fri_config.cap_height), circuit_digest: self.add_virtual_hash(), @@ -168,6 +175,14 @@ impl, const D: usize> CircuitBuilder { self.connect_hashes(*h0, *h1); } + self.connect(previous_base_case, pis.base_case.target); + for (x, y) in previous_virtual_public_inputs + .iter() + .zip(&proof.public_inputs) + { + self.connect(*x, *y); + } + // Verify the dummy proof if `base_case` is set to true, otherwise verify the "real" proof. self.conditionally_verify_proof( base_case, @@ -212,6 +227,8 @@ pub fn set_cyclic_recursion_data_target< pw: &mut PartialWitness, 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], ) -> Result<()> where C::Hasher: AlgebraicHasher, @@ -233,6 +250,7 @@ where let (dummy_proof, dummy_data) = dummy_proof(cyclic_recursion_data.common_data)?; pw.set_bool_target(cyclic_recursion_data_target.base_case, true); 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(); // A base case must be following another base case. proof.public_inputs[pis_len - 1] = F::ONE; @@ -293,13 +311,17 @@ where #[cfg(test)] mod tests { + use anyhow::Result; use plonky2_field::extension::Extendable; + use plonky2_field::types::PrimeField64; use crate::field::types::Field; use crate::hash::hash_types::RichField; - use crate::hash::poseidon::PoseidonHash; - use crate::iop::witness::{PartialWitness, Witness}; + use crate::hash::hashing::hash_n_to_hash_no_pad; + use crate::hash::poseidon::{PoseidonHash, PoseidonPermutation}; + use crate::iop::target::BoolTarget; + use crate::iop::witness::PartialWitness; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData, VerifierCircuitTarget}; use crate::plonk::config::{AlgebraicHasher, GenericConfig, Hasher, PoseidonGoldilocksConfig}; @@ -351,23 +373,54 @@ mod tests { let mut pw = PartialWitness::new(); let mut builder = CircuitBuilder::::new(config); - // Build realistic circuit - let t = builder.add_virtual_target(); - pw.set_target(t, F::rand()); - let t_inv = builder.inverse(t); - let h = builder.hash_n_to_hash_no_pad::(vec![t_inv]); + // 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(); + // Flag set to true if the last proof was a base case. + let old_base_case = builder.add_virtual_target(); + // 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.select_hash( + BoolTarget::new_unsafe(old_base_case), + initial_hash, + old_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 one = builder.one(); + let old_not_base_case = builder.sub(one, old_base_case); + // New counter is the previous counter +1 if the previous proof wasn't a base case. + let new_counter = builder.add(old_counter, old_not_base_case); + builder.register_public_input(new_counter); + let old_pis = [ + initial_hash.elements.as_slice(), + old_hash.elements.as_slice(), + [old_counter].as_slice(), + ] + .concat(); let common_data = common_data_for_recursion::(); // Add cyclic recursion gadget. - let (cyclic_circuit_data, cyclic_data_target) = builder.cyclic_recursion(common_data)?; + let (cyclic_circuit_data, cyclic_data_target) = + builder.cyclic_recursion(&old_pis, old_base_case, common_data)?; + 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, }; - set_cyclic_recursion_data_target(&mut pw, &cyclic_data_target, &cyclic_recursion_data)?; + let initial_hash = [F::ZERO, F::ONE, F::TWO, F::from_canonical_usize(3)]; + set_cyclic_recursion_data_target( + &mut pw, + &cyclic_data_target, + &cyclic_recursion_data, + &initial_hash, + )?; let proof = cyclic_circuit_data.prove(pw)?; check_cyclic_proof_verifier_data( &proof, @@ -378,13 +431,17 @@ mod tests { // 1st recursive layer. let mut pw = PartialWitness::new(); - pw.set_target(t, F::rand()); let cyclic_recursion_data = CyclicRecursionData { proof: &Some(proof), // Input previous proof. verifier_data: &cyclic_circuit_data.verifier_only, common_data: &cyclic_circuit_data.common, }; - set_cyclic_recursion_data_target(&mut pw, &cyclic_data_target, &cyclic_recursion_data)?; + set_cyclic_recursion_data_target( + &mut pw, + &cyclic_data_target, + &cyclic_recursion_data, + &[], + )?; let proof = cyclic_circuit_data.prove(pw)?; check_cyclic_proof_verifier_data( &proof, @@ -395,19 +452,39 @@ mod tests { // 2nd recursive layer. let mut pw = PartialWitness::new(); - pw.set_target(t, F::rand()); let cyclic_recursion_data = CyclicRecursionData { proof: &Some(proof), // Input previous proof. verifier_data: &cyclic_circuit_data.verifier_only, common_data: &cyclic_circuit_data.common, }; - set_cyclic_recursion_data_target(&mut pw, &cyclic_data_target, &cyclic_recursion_data)?; + set_cyclic_recursion_data_target( + &mut pw, + &cyclic_data_target, + &cyclic_recursion_data, + &[], + )?; let proof = cyclic_circuit_data.prove(pw)?; check_cyclic_proof_verifier_data( &proof, cyclic_recursion_data.verifier_data, cyclic_recursion_data.common_data, )?; + + // Verify that the proof correctly computes a repeated hash. + 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, + std::iter::repeat_with(|| { + h = hash_n_to_hash_no_pad::(&h).elements; + h + }) + .nth(counter.to_canonical_u64() as usize) + .unwrap() + ); + cyclic_circuit_data.verify(proof) } } From c3e9827b5e54ecbf9eb713a3001fdf7d3df001b7 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 17 Oct 2022 14:59:51 +0200 Subject: [PATCH 11/18] Minor --- plonky2/src/plonk/circuit_builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index fffe22a8..82a72504 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -50,7 +50,7 @@ pub struct CircuitBuilder, const D: usize> { pub config: CircuitConfig, /// The types of gates used in this circuit. - pub(crate) gates: HashSet>, + gates: HashSet>, /// The concrete placement of each gate. pub(crate) gate_instances: Vec>, From f28ff42fe3aa1fe36b017b83c47f9a930265e875 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 24 Oct 2022 13:35:16 +0200 Subject: [PATCH 12/18] PR feedback --- plonky2/src/recursion/cyclic_recursion.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs index e968e5e7..8a70e28a 100644 --- a/plonky2/src/recursion/cyclic_recursion.rs +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -152,8 +152,6 @@ impl, const D: usize> CircuitBuilder { self.register_public_input(base_case.target); common_data.num_public_inputs = self.num_public_inputs(); - // The `conditionally_verify_proof` gadget below takes 2^12 gates, so `degree_bits` cannot be smaller than 13. - common_data.fri_params.degree_bits = common_data.fri_params.degree_bits.max(13); let proof = self.add_virtual_proof_with_pis(&common_data); let dummy_proof = self.add_virtual_proof_with_pis(&common_data); @@ -203,7 +201,12 @@ impl, const D: usize> CircuitBuilder { } let data = self.build::(); - ensure!(data.common == common_data, "Common data does not match."); + ensure!( + data.common == common_data, + "Common data does not match. Final circuit has common data {:?} instead of {:?}.", + data.common, + common_data + ); Ok(( data, @@ -317,6 +320,7 @@ mod tests { use plonky2_field::types::PrimeField64; use crate::field::types::Field; + use crate::gates::noop::NoopGate; use crate::hash::hash_types::RichField; use crate::hash::hashing::hash_n_to_hash_no_pad; use crate::hash::poseidon::{PoseidonHash, PoseidonPermutation}; @@ -360,6 +364,9 @@ mod tests { circuit_digest: builder.add_virtual_hash(), }; builder.verify_proof(proof, &verifier_data, &data.common); + while builder.num_gates() < 1 << 12 { + builder.add_gate(NoopGate, vec![]); + } builder.build::().common } From e8fd8bd1d8196afabf976aea0f123a109648ac0e Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Tue, 25 Oct 2022 22:51:24 +0200 Subject: [PATCH 13/18] Add type hints --- plonky2/src/recursion/cyclic_recursion.rs | 35 ++++++++++++----------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs index 8a70e28a..10fe9dd0 100644 --- a/plonky2/src/recursion/cyclic_recursion.rs +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -25,7 +25,7 @@ pub struct CyclicRecursionData< > { proof: &'a Option>, verifier_data: &'a VerifierOnlyCircuitData, - common_data: &'a CommonCircuitData, + common_data: &'a CommonCircuitData, } pub struct CyclicRecursionTarget { @@ -49,7 +49,7 @@ pub struct CyclicPublicInputs< impl, C: GenericConfig, const D: usize> CyclicPublicInputs { - fn from_slice(slice: &[F], common_data: &CommonCircuitData) -> Result + fn from_slice(slice: &[F], common_data: &CommonCircuitData) -> Result where C::Hasher: AlgebraicHasher, { @@ -90,7 +90,7 @@ pub struct CyclicPublicInputsTarget { impl CyclicPublicInputsTarget { fn from_slice, C: GenericConfig, const D: usize>( slice: &[Target], - common_data: &CommonCircuitData, + common_data: &CommonCircuitData, ) -> Result { let cap_len = common_data.config.fri_config.num_cap_elements(); let len = slice.len(); @@ -120,7 +120,7 @@ impl, const D: usize> CircuitBuilder { mut self, previous_virtual_public_inputs: &[Target], previous_base_case: Target, - mut common_data: CommonCircuitData, + mut common_data: CommonCircuitData, ) -> Result<(CircuitData, CyclicRecursionTarget)> where C::Hasher: AlgebraicHasher, @@ -153,10 +153,11 @@ impl, const D: usize> CircuitBuilder { common_data.num_public_inputs = self.num_public_inputs(); - let proof = self.add_virtual_proof_with_pis(&common_data); - let dummy_proof = self.add_virtual_proof_with_pis(&common_data); + let proof = self.add_virtual_proof_with_pis::(&common_data); + let dummy_proof = self.add_virtual_proof_with_pis::(&common_data); - let pis = CyclicPublicInputsTarget::from_slice(&proof.public_inputs, &common_data)?; + let pis = + CyclicPublicInputsTarget::from_slice::(&proof.public_inputs, &common_data)?; // Check that the previous base case flag was boolean. self.assert_bool(pis.base_case); // Check that we cannot go from a non-base case to a base case by checking `previous_base_case - base_case \in {0,1}`. @@ -182,7 +183,7 @@ impl, const D: usize> CircuitBuilder { } // Verify the dummy proof if `base_case` is set to true, otherwise verify the "real" proof. - self.conditionally_verify_proof( + self.conditionally_verify_proof::( base_case, &dummy_proof, &dummy_verifier_data, @@ -250,7 +251,7 @@ where cyclic_recursion_data.verifier_data, ); } else { - let (dummy_proof, dummy_data) = dummy_proof(cyclic_recursion_data.common_data)?; + let (dummy_proof, dummy_data) = dummy_proof::(cyclic_recursion_data.common_data)?; pw.set_bool_target(cyclic_recursion_data_target.base_case, true); let mut proof = dummy_proof.clone(); proof.public_inputs[0..public_inputs.len()].copy_from_slice(public_inputs); @@ -298,12 +299,12 @@ pub fn check_cyclic_proof_verifier_data< >( proof: &ProofWithPublicInputs, verifier_data: &VerifierOnlyCircuitData, - common_data: &CommonCircuitData, + common_data: &CommonCircuitData, ) -> Result<()> where C::Hasher: AlgebraicHasher, { - let pis = CyclicPublicInputs::from_slice(&proof.public_inputs, common_data)?; + let pis = CyclicPublicInputs::::from_slice(&proof.public_inputs, common_data)?; if !pis.base_case { ensure!(verifier_data.constants_sigmas_cap == pis.constants_sigmas_cap); ensure!(verifier_data.circuit_digest == pis.circuit_digest); @@ -338,7 +339,7 @@ mod tests { F: RichField + Extendable, C: GenericConfig, const D: usize, - >() -> CommonCircuitData + >() -> CommonCircuitData where C::Hasher: AlgebraicHasher, [(); C::Hasher::HASH_SIZE]:, @@ -348,22 +349,22 @@ mod tests { let data = builder.build::(); let config = CircuitConfig::standard_recursion_config(); let mut builder = CircuitBuilder::::new(config); - let proof = builder.add_virtual_proof_with_pis(&data.common); + let proof = builder.add_virtual_proof_with_pis::(&data.common); let verifier_data = VerifierCircuitTarget { constants_sigmas_cap: builder.add_virtual_cap(data.common.config.fri_config.cap_height), circuit_digest: builder.add_virtual_hash(), }; - builder.verify_proof(proof, &verifier_data, &data.common); + builder.verify_proof::(proof, &verifier_data, &data.common); let data = builder.build::(); let config = CircuitConfig::standard_recursion_config(); let mut builder = CircuitBuilder::::new(config); - let proof = builder.add_virtual_proof_with_pis(&data.common); + let proof = builder.add_virtual_proof_with_pis::(&data.common); let verifier_data = VerifierCircuitTarget { constants_sigmas_cap: builder.add_virtual_cap(data.common.config.fri_config.cap_height), circuit_digest: builder.add_virtual_hash(), }; - builder.verify_proof(proof, &verifier_data, &data.common); + builder.verify_proof::(proof, &verifier_data, &data.common); while builder.num_gates() < 1 << 12 { builder.add_gate(NoopGate, vec![]); } @@ -414,7 +415,7 @@ mod tests { // Add cyclic recursion gadget. let (cyclic_circuit_data, cyclic_data_target) = - builder.cyclic_recursion(&old_pis, old_base_case, common_data)?; + builder.cyclic_recursion::(&old_pis, old_base_case, common_data)?; let cyclic_recursion_data = CyclicRecursionData { proof: &None, // Base case: We don't have a proof to put here yet. From 7afbddb0b6b082a3f99be449d7998019a107a577 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Tue, 25 Oct 2022 22:57:36 +0200 Subject: [PATCH 14/18] Add `goal_common_data` to `CircuitBuilder` --- plonky2/src/plonk/circuit_builder.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index 82a72504..5327fc9b 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -83,6 +83,11 @@ pub struct CircuitBuilder, const D: usize> { /// List of constant generators used to fill the constant wires. constant_generators: Vec>, + + /// Optional common data. When it is `Some(goal_data)`, the `build` function panics if the resulting + /// common data doesn't equal `goal_data`. + /// This is used in cyclic recursion. + pub(crate) goal_common_data: Option>, } impl, const D: usize> CircuitBuilder { @@ -102,6 +107,7 @@ impl, const D: usize> CircuitBuilder { arithmetic_results: HashMap::new(), current_slots: HashMap::new(), constant_generators: Vec::new(), + goal_common_data: None, }; builder.check_config(); builder @@ -830,6 +836,9 @@ impl, const D: usize> CircuitBuilder { k_is, num_partial_products, }; + if let Some(goal_data) = self.goal_common_data { + assert_eq!(goal_data, common); + } let prover_only = ProverOnlyCircuitData { generators: self.generators, From 341e1ebeec714c79f3af2ec144d161eb5236f9ed Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 26 Oct 2022 10:58:38 +0200 Subject: [PATCH 15/18] Working --- plonky2/src/recursion/cyclic_recursion.rs | 116 ++++++++-------------- 1 file changed, 40 insertions(+), 76 deletions(-) diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs index 10fe9dd0..2b3693e1 100644 --- a/plonky2/src/recursion/cyclic_recursion.rs +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -10,7 +10,7 @@ use crate::iop::target::{BoolTarget, Target}; use crate::iop::witness::{PartialWitness, Witness}; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::circuit_data::{ - CircuitData, CommonCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData, + CommonCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData, }; use crate::plonk::config::Hasher; use crate::plonk::config::{AlgebraicHasher, GenericConfig}; @@ -43,7 +43,6 @@ pub struct CyclicPublicInputs< > { pub circuit_digest: HashOut, pub constants_sigmas_cap: MerkleCap, - pub base_case: bool, } impl, C: GenericConfig, const D: usize> @@ -53,30 +52,23 @@ impl, C: GenericConfig, const D: usize> where C::Hasher: AlgebraicHasher, { - // The structure of the public inputs is `[...,circuit_digest, constants_sigmas_cap, base_case]`. + // The structure of the public inputs is `[..., circuit_digest, constants_sigmas_cap]`. let cap_len = common_data.config.fri_config.num_cap_elements(); let len = slice.len(); - ensure!(len >= 4 + 4 * cap_len + 1, "Not enough public inputs"); - let base_case = slice[len - 1]; - ensure!( - base_case.is_one() || base_case.is_zero(), - "Base case flag {:?} is not binary", - base_case - ); + ensure!(len >= 4 + 4 * cap_len, "Not enough public inputs"); let constants_sigmas_cap = MerkleCap( (0..cap_len) .map(|i| HashOut { - elements: std::array::from_fn(|j| slice[len - 1 - 4 * (cap_len - i) + j]), + elements: std::array::from_fn(|j| slice[len - 4 * (cap_len - i) + j]), }) .collect(), ); let circuit_digest = - HashOut::from_partial(&slice[len - 5 - 4 * cap_len..len - 1 - 4 * cap_len]); + HashOut::from_partial(&slice[len - 4 - 4 * cap_len..len - 4 * cap_len]); Ok(Self { circuit_digest, constants_sigmas_cap, - base_case: base_case.is_one(), }) } } @@ -84,7 +76,6 @@ impl, C: GenericConfig, const D: usize> pub struct CyclicPublicInputsTarget { pub circuit_digest: HashOutTarget, pub constants_sigmas_cap: MerkleCapTarget, - pub base_case: BoolTarget, } impl CyclicPublicInputsTarget { @@ -94,34 +85,31 @@ impl CyclicPublicInputsTarget { ) -> Result { let cap_len = common_data.config.fri_config.num_cap_elements(); let len = slice.len(); - ensure!(len >= 4 + 4 * cap_len + 1, "Not enough public inputs"); - let base_case = BoolTarget::new_unsafe(slice[len - 1]); + ensure!(len >= 4 + 4 * cap_len, "Not enough public inputs"); let constants_sigmas_cap = MerkleCapTarget( (0..cap_len) .map(|i| HashOutTarget { - elements: std::array::from_fn(|j| slice[len - 1 - 4 * (cap_len - i) + j]), + elements: std::array::from_fn(|j| slice[len - 4 * (cap_len - i) + j]), }) .collect(), ); let circuit_digest = HashOutTarget { - elements: std::array::from_fn(|i| slice[len - 5 - 4 * cap_len + i]), + elements: std::array::from_fn(|i| slice[len - 4 - 4 * cap_len + i]), }; Ok(Self { circuit_digest, constants_sigmas_cap, - base_case, }) } } impl, const D: usize> CircuitBuilder { pub fn cyclic_recursion>( - mut self, + &mut self, previous_virtual_public_inputs: &[Target], - previous_base_case: Target, - mut common_data: CommonCircuitData, - ) -> Result<(CircuitData, CyclicRecursionTarget)> + common_data: &mut CommonCircuitData, + ) -> Result> where C::Hasher: AlgebraicHasher, [(); C::Hasher::HASH_SIZE]:, @@ -147,22 +135,15 @@ impl, const D: usize> CircuitBuilder { }; // Flag set to true for the base case of the cycle where we verify a dummy proof to bootstrap the cycle. Set to false otherwise. - // Unsafe is ok since `base_case` is a public input and its booleaness should be checked in the verifier. - let base_case = self.add_virtual_bool_target_unsafe(); - self.register_public_input(base_case.target); + let base_case = self.add_virtual_bool_target_safe(); common_data.num_public_inputs = self.num_public_inputs(); - let proof = self.add_virtual_proof_with_pis::(&common_data); - let dummy_proof = self.add_virtual_proof_with_pis::(&common_data); + let proof = self.add_virtual_proof_with_pis::(common_data); + let dummy_proof = self.add_virtual_proof_with_pis::(common_data); let pis = - CyclicPublicInputsTarget::from_slice::(&proof.public_inputs, &common_data)?; - // Check that the previous base case flag was boolean. - self.assert_bool(pis.base_case); - // Check that we cannot go from a non-base case to a base case by checking `previous_base_case - base_case \in {0,1}`. - let decrease = BoolTarget::new_unsafe(self.sub(pis.base_case.target, base_case.target)); - self.assert_bool(decrease); + CyclicPublicInputsTarget::from_slice::(&proof.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 @@ -174,7 +155,6 @@ impl, const D: usize> CircuitBuilder { self.connect_hashes(*h0, *h1); } - self.connect(previous_base_case, pis.base_case.target); for (x, y) in previous_virtual_public_inputs .iter() .zip(&proof.public_inputs) @@ -189,7 +169,7 @@ impl, const D: usize> CircuitBuilder { &dummy_verifier_data, &proof, &verifier_data, - &common_data, + common_data, ); // Make sure we have enough gates to match `common_data`. @@ -201,24 +181,13 @@ impl, const D: usize> CircuitBuilder { self.add_gate_to_gate_set(g.clone()); } - let data = self.build::(); - ensure!( - data.common == common_data, - "Common data does not match. Final circuit has common data {:?} instead of {:?}.", - data.common, - common_data - ); - - Ok(( - data, - CyclicRecursionTarget { - proof, - verifier_data, - dummy_proof, - dummy_verifier_data, - base_case, - }, - )) + Ok(CyclicRecursionTarget { + proof, + verifier_data, + dummy_proof, + dummy_verifier_data, + base_case, + }) } } @@ -256,8 +225,6 @@ where 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(); - // A base case must be following another base case. - proof.public_inputs[pis_len - 1] = F::ONE; // 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 @@ -265,7 +232,7 @@ where .config .fri_config .num_cap_elements(); - let s = pis_len - 5 - 4 * num_cap; + 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 { @@ -305,10 +272,8 @@ where C::Hasher: AlgebraicHasher, { let pis = CyclicPublicInputs::::from_slice(&proof.public_inputs, common_data)?; - if !pis.base_case { - ensure!(verifier_data.constants_sigmas_cap == pis.constants_sigmas_cap); - ensure!(verifier_data.circuit_digest == pis.circuit_digest); - } + ensure!(verifier_data.constants_sigmas_cap == pis.constants_sigmas_cap); + ensure!(verifier_data.circuit_digest == pis.circuit_digest); Ok(()) } @@ -325,7 +290,6 @@ mod tests { use crate::hash::hash_types::RichField; use crate::hash::hashing::hash_n_to_hash_no_pad; use crate::hash::poseidon::{PoseidonHash, PoseidonPermutation}; - use crate::iop::target::BoolTarget; use crate::iop::witness::PartialWitness; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData, VerifierCircuitTarget}; @@ -386,24 +350,15 @@ mod tests { builder.register_public_inputs(&initial_hash.elements); // Hash from the previous proof. let old_hash = builder.add_virtual_hash(); - // Flag set to true if the last proof was a base case. - let old_base_case = builder.add_virtual_target(); // 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.select_hash( - BoolTarget::new_unsafe(old_base_case), - initial_hash, - old_hash, - ); + 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 one = builder.one(); - let old_not_base_case = builder.sub(one, old_base_case); - // New counter is the previous counter +1 if the previous proof wasn't a base case. - let new_counter = builder.add(old_counter, old_not_base_case); - builder.register_public_input(new_counter); + let new_counter = builder.add_virtual_public_input(); let old_pis = [ initial_hash.elements.as_slice(), old_hash.elements.as_slice(), @@ -411,11 +366,19 @@ mod tests { ] .concat(); - let common_data = common_data_for_recursion::(); + let mut common_data = common_data_for_recursion::(); // Add cyclic recursion gadget. - let (cyclic_circuit_data, cyclic_data_target) = - builder.cyclic_recursion::(&old_pis, old_base_case, common_data)?; + let cyclic_data_target = builder.cyclic_recursion::(&old_pis, &mut common_data)?; + let input_hash_bis = + builder.select_hash(cyclic_data_target.base_case, initial_hash, old_hash); + builder.connect_hashes(input_hash, input_hash_bis); + let not_base_case = builder.sub(one, cyclic_data_target.base_case.target); + // New counter is the previous counter +1 if the previous proof wasn't a base case. + let new_counter_bis = builder.add(old_counter, not_base_case); + builder.connect(new_counter, new_counter_bis); + + let cyclic_circuit_data = builder.build::(); let cyclic_recursion_data = CyclicRecursionData { proof: &None, // Base case: We don't have a proof to put here yet. @@ -482,6 +445,7 @@ mod tests { let initial_hash = &proof.public_inputs[..4]; let hash = &proof.public_inputs[4..8]; let counter = proof.public_inputs[8]; + dbg!(counter); let mut h: [F; 4] = initial_hash.try_into().unwrap(); assert_eq!( hash, From 73e9e6119b232f43df96e756fbdca3ed4fe870cd Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 26 Oct 2022 11:02:45 +0200 Subject: [PATCH 16/18] `CyclicPublicInputs` is just `VerifierOnlyCircuitData` --- plonky2/src/recursion/cyclic_recursion.rs | 29 +++++------------------ 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs index 2b3693e1..0dd3b730 100644 --- a/plonky2/src/recursion/cyclic_recursion.rs +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -36,21 +36,10 @@ pub struct CyclicRecursionTarget { pub base_case: BoolTarget, } -pub struct CyclicPublicInputs< - F: RichField + Extendable, - C: GenericConfig, - const D: usize, -> { - pub circuit_digest: HashOut, - pub constants_sigmas_cap: MerkleCap, -} - -impl, C: GenericConfig, const D: usize> - CyclicPublicInputs -{ - fn from_slice(slice: &[F], common_data: &CommonCircuitData) -> Result +impl, const D: usize> VerifierOnlyCircuitData { + fn from_slice(slice: &[C::F], common_data: &CommonCircuitData) -> Result where - C::Hasher: AlgebraicHasher, + C::Hasher: AlgebraicHasher, { // The structure of the public inputs is `[..., circuit_digest, constants_sigmas_cap]`. let cap_len = common_data.config.fri_config.num_cap_elements(); @@ -73,12 +62,7 @@ impl, C: GenericConfig, const D: usize> } } -pub struct CyclicPublicInputsTarget { - pub circuit_digest: HashOutTarget, - pub constants_sigmas_cap: MerkleCapTarget, -} - -impl CyclicPublicInputsTarget { +impl VerifierCircuitTarget { fn from_slice, C: GenericConfig, const D: usize>( slice: &[Target], common_data: &CommonCircuitData, @@ -142,8 +126,7 @@ impl, const D: usize> CircuitBuilder { let proof = self.add_virtual_proof_with_pis::(common_data); let dummy_proof = self.add_virtual_proof_with_pis::(common_data); - let pis = - CyclicPublicInputsTarget::from_slice::(&proof.public_inputs, common_data)?; + let pis = VerifierCircuitTarget::from_slice::(&proof.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 @@ -271,7 +254,7 @@ pub fn check_cyclic_proof_verifier_data< where C::Hasher: AlgebraicHasher, { - let pis = CyclicPublicInputs::::from_slice(&proof.public_inputs, common_data)?; + let pis = VerifierOnlyCircuitData::::from_slice(&proof.public_inputs, common_data)?; ensure!(verifier_data.constants_sigmas_cap == pis.constants_sigmas_cap); ensure!(verifier_data.circuit_digest == pis.circuit_digest); From 8a60fe19eae81764e9152be287f213df514a0b18 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Wed, 26 Oct 2022 15:04:47 +0200 Subject: [PATCH 17/18] Cyclic recursion gadget doesn't move circuit builder --- plonky2/src/plonk/circuit_data.rs | 1 + plonky2/src/recursion/cyclic_recursion.rs | 41 ++++++++++++++--------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/plonky2/src/plonk/circuit_data.rs b/plonky2/src/plonk/circuit_data.rs index 66dbf3f2..b5e411f1 100644 --- a/plonky2/src/plonk/circuit_data.rs +++ b/plonky2/src/plonk/circuit_data.rs @@ -488,6 +488,7 @@ impl, const D: usize> CommonCircuitData { /// is intentionally missing certain fields, such as `CircuitConfig`, because we support only a /// limited form of dynamic inner circuits. We can't practically make things like the wire count /// dynamic, at least not without setting a maximum wire count and paying for the worst case. +#[derive(Clone)] pub struct VerifierCircuitTarget { /// A commitment to each constant polynomial and each permutation polynomial. pub constants_sigmas_cap: MerkleCapTarget, diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs index 0dd3b730..98661a92 100644 --- a/plonky2/src/recursion/cyclic_recursion.rs +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -89,20 +89,15 @@ impl VerifierCircuitTarget { } impl, const D: usize> CircuitBuilder { - pub fn cyclic_recursion>( + /// Add verifier data and register it as public inputs. + /// WARNING: Do not register any public input after calling this! + pub fn verifier_data_for_cyclic_recursion>( &mut self, - previous_virtual_public_inputs: &[Target], - common_data: &mut CommonCircuitData, - ) -> Result> + ) -> VerifierCircuitTarget where C::Hasher: AlgebraicHasher, [(); C::Hasher::HASH_SIZE]:, { - ensure!( - previous_virtual_public_inputs.len() == self.num_public_inputs(), - "Incorrect number of public inputs." - ); - let verifier_data = VerifierCircuitTarget { constants_sigmas_cap: self.add_virtual_cap(self.config.fri_config.cap_height), circuit_digest: self.add_virtual_hash(), @@ -113,6 +108,21 @@ impl, const D: usize> CircuitBuilder { self.register_public_inputs(&verifier_data.constants_sigmas_cap.0[i].elements); } + verifier_data + } + + /// Cyclic recursion gadget. + /// WARNING: Do not register any public input after calling this! + pub fn cyclic_recursion>( + &mut self, + previous_virtual_public_inputs: &[Target], + verifier_data: &VerifierCircuitTarget, // should be registered as public inputs already + common_data: &CommonCircuitData, + ) -> Result> + where + C::Hasher: AlgebraicHasher, + [(); C::Hasher::HASH_SIZE]:, + { let dummy_verifier_data = VerifierCircuitTarget { constants_sigmas_cap: self.add_virtual_cap(self.config.fri_config.cap_height), circuit_digest: self.add_virtual_hash(), @@ -121,8 +131,6 @@ impl, const D: usize> CircuitBuilder { // Flag set to true for the base case of the cycle where we verify a dummy proof to bootstrap the cycle. Set to false otherwise. let base_case = self.add_virtual_bool_target_safe(); - common_data.num_public_inputs = self.num_public_inputs(); - let proof = self.add_virtual_proof_with_pis::(common_data); let dummy_proof = self.add_virtual_proof_with_pis::(common_data); @@ -151,7 +159,7 @@ impl, const D: usize> CircuitBuilder { &dummy_proof, &dummy_verifier_data, &proof, - &verifier_data, + verifier_data, common_data, ); @@ -166,7 +174,7 @@ impl, const D: usize> CircuitBuilder { Ok(CyclicRecursionTarget { proof, - verifier_data, + verifier_data: verifier_data.clone(), dummy_proof, dummy_verifier_data, base_case, @@ -351,8 +359,12 @@ mod tests { let mut common_data = common_data_for_recursion::(); + let verifier_data = builder.verifier_data_for_cyclic_recursion::(); + common_data.num_public_inputs = builder.num_public_inputs(); + // Add cyclic recursion gadget. - let cyclic_data_target = builder.cyclic_recursion::(&old_pis, &mut common_data)?; + let cyclic_data_target = + builder.cyclic_recursion::(&old_pis, &verifier_data, &common_data)?; let input_hash_bis = builder.select_hash(cyclic_data_target.base_case, initial_hash, old_hash); builder.connect_hashes(input_hash, input_hash_bis); @@ -428,7 +440,6 @@ mod tests { let initial_hash = &proof.public_inputs[..4]; let hash = &proof.public_inputs[4..8]; let counter = proof.public_inputs[8]; - dbg!(counter); let mut h: [F; 4] = initial_hash.try_into().unwrap(); assert_eq!( hash, From c47f767fc57e53aa8dc15004a2c0d8279fd6e800 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Thu, 27 Oct 2022 15:45:14 +0200 Subject: [PATCH 18/18] PR feedback --- plonky2/src/plonk/circuit_builder.rs | 22 ++++++++++- plonky2/src/recursion/cyclic_recursion.rs | 47 +++++++---------------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index 5327fc9b..dfd23426 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -34,7 +34,7 @@ use crate::iop::target::{BoolTarget, Target}; use crate::iop::wire::Wire; use crate::plonk::circuit_data::{ CircuitConfig, CircuitData, CommonCircuitData, ProverCircuitData, ProverOnlyCircuitData, - VerifierCircuitData, VerifierOnlyCircuitData, + VerifierCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData, }; use crate::plonk::config::{GenericConfig, Hasher}; use crate::plonk::copy_constraint::CopyConstraint; @@ -88,6 +88,10 @@ pub struct CircuitBuilder, const D: usize> { /// common data doesn't equal `goal_data`. /// This is used in cyclic recursion. pub(crate) goal_common_data: Option>, + + /// Optional verifier data that is registered as public inputs. + /// This is used in cyclic recursion to hold the circuit's own verifier key. + pub(crate) verifier_data_public_input: Option, } impl, const D: usize> CircuitBuilder { @@ -108,6 +112,7 @@ impl, const D: usize> CircuitBuilder { current_slots: HashMap::new(), constant_generators: Vec::new(), goal_common_data: None, + verifier_data_public_input: None, }; builder.check_config(); builder @@ -224,6 +229,21 @@ 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) { + let verifier_data = VerifierCircuitTarget { + constants_sigmas_cap: self.add_virtual_cap(self.config.fri_config.cap_height), + circuit_digest: self.add_virtual_hash(), + }; + // The verifier data are public inputs. + self.register_public_inputs(&verifier_data.circuit_digest.elements); + for i in 0..self.config.fri_config.num_cap_elements() { + self.register_public_inputs(&verifier_data.constants_sigmas_cap.0[i].elements); + } + + self.verifier_data_public_input = Some(verifier_data); + } /// Adds a gate to the circuit, and returns its index. pub fn add_gate>(&mut self, gate_type: G, mut constants: Vec) -> usize { diff --git a/plonky2/src/recursion/cyclic_recursion.rs b/plonky2/src/recursion/cyclic_recursion.rs index 98661a92..f2ad7eb9 100644 --- a/plonky2/src/recursion/cyclic_recursion.rs +++ b/plonky2/src/recursion/cyclic_recursion.rs @@ -89,48 +89,31 @@ impl VerifierCircuitTarget { } impl, const D: usize> CircuitBuilder { - /// Add verifier data and register it as public inputs. - /// WARNING: Do not register any public input after calling this! - pub fn verifier_data_for_cyclic_recursion>( - &mut self, - ) -> VerifierCircuitTarget - where - C::Hasher: AlgebraicHasher, - [(); C::Hasher::HASH_SIZE]:, - { - let verifier_data = VerifierCircuitTarget { - constants_sigmas_cap: self.add_virtual_cap(self.config.fri_config.cap_height), - circuit_digest: self.add_virtual_hash(), - }; - // The verifier data are public inputs. - self.register_public_inputs(&verifier_data.circuit_digest.elements); - for i in 0..self.config.fri_config.num_cap_elements() { - self.register_public_inputs(&verifier_data.constants_sigmas_cap.0[i].elements); - } - - verifier_data - } - /// Cyclic recursion gadget. - /// WARNING: Do not register any public input after calling this! + /// WARNING: Do not register any public input after calling this! TODO: relax this pub fn cyclic_recursion>( &mut self, + // Flag set to true for the base case of the cycle where we verify a dummy proof to bootstrap the cycle. Set to false otherwise. + base_case: BoolTarget, previous_virtual_public_inputs: &[Target], - verifier_data: &VerifierCircuitTarget, // should be registered as public inputs already - common_data: &CommonCircuitData, + common_data: &mut CommonCircuitData, ) -> Result> where C::Hasher: AlgebraicHasher, [(); C::Hasher::HASH_SIZE]:, { + 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(); + self.goal_common_data = Some(common_data.clone()); + let dummy_verifier_data = VerifierCircuitTarget { constants_sigmas_cap: self.add_virtual_cap(self.config.fri_config.cap_height), circuit_digest: self.add_virtual_hash(), }; - // Flag set to true for the base case of the cycle where we verify a dummy proof to bootstrap the cycle. Set to false otherwise. - let base_case = self.add_virtual_bool_target_safe(); - let proof = self.add_virtual_proof_with_pis::(common_data); let dummy_proof = self.add_virtual_proof_with_pis::(common_data); @@ -159,7 +142,7 @@ impl, const D: usize> CircuitBuilder { &dummy_proof, &dummy_verifier_data, &proof, - verifier_data, + &verifier_data, common_data, ); @@ -359,12 +342,10 @@ mod tests { let mut common_data = common_data_for_recursion::(); - let verifier_data = builder.verifier_data_for_cyclic_recursion::(); - common_data.num_public_inputs = builder.num_public_inputs(); - + let base_case = builder.add_virtual_bool_target_safe(); // Add cyclic recursion gadget. let cyclic_data_target = - builder.cyclic_recursion::(&old_pis, &verifier_data, &common_data)?; + builder.cyclic_recursion::(base_case, &old_pis, &mut common_data)?; let input_hash_bis = builder.select_hash(cyclic_data_target.base_case, initial_hash, old_hash); builder.connect_hashes(input_hash, input_hash_bis);