1#![allow(clippy::int_plus_one)] #[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5
6use anyhow::{ensure, Result};
7
8use crate::field::extension::Extendable;
9use crate::hash::hash_types::{HashOut, HashOutTarget, MerkleCapTarget, RichField};
10use crate::hash::merkle_tree::MerkleCap;
11use crate::iop::target::{BoolTarget, Target};
12use crate::plonk::circuit_builder::CircuitBuilder;
13use crate::plonk::circuit_data::{
14 CommonCircuitData, VerifierCircuitTarget, VerifierOnlyCircuitData,
15};
16use crate::plonk::config::{AlgebraicHasher, GenericConfig};
17use crate::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget};
18use crate::util::serialization::{Buffer, IoResult, Read, Write};
19
20impl<C: GenericConfig<D>, const D: usize> VerifierOnlyCircuitData<C, D> {
21 fn from_slice(slice: &[C::F], common_data: &CommonCircuitData<C::F, D>) -> Result<Self>
22 where
23 C::Hasher: AlgebraicHasher<C::F>,
24 {
25 let cap_len = common_data.config.fri_config.num_cap_elements();
27 let len = slice.len();
28 ensure!(len >= 4 + 4 * cap_len, "Not enough public inputs");
29 let constants_sigmas_cap = MerkleCap(
30 (0..cap_len)
31 .map(|i| HashOut {
32 elements: core::array::from_fn(|j| slice[len - 4 * (cap_len - i) + j]),
33 })
34 .collect(),
35 );
36 let circuit_digest =
37 HashOut::from_partial(&slice[len - 4 - 4 * cap_len..len - 4 * cap_len]);
38
39 Ok(Self {
40 circuit_digest,
41 constants_sigmas_cap,
42 })
43 }
44}
45
46impl VerifierCircuitTarget {
47 pub fn to_bytes(&self) -> IoResult<Vec<u8>> {
48 let mut buffer = Vec::new();
49 buffer.write_target_merkle_cap(&self.constants_sigmas_cap)?;
50 buffer.write_target_hash(&self.circuit_digest)?;
51 Ok(buffer)
52 }
53
54 pub fn from_bytes(bytes: Vec<u8>) -> IoResult<Self> {
55 let mut buffer = Buffer::new(&bytes);
56 let constants_sigmas_cap = buffer.read_target_merkle_cap()?;
57 let circuit_digest = buffer.read_target_hash()?;
58 Ok(Self {
59 constants_sigmas_cap,
60 circuit_digest,
61 })
62 }
63
64 fn from_slice<F: RichField + Extendable<D>, const D: usize>(
65 slice: &[Target],
66 common_data: &CommonCircuitData<F, D>,
67 ) -> Result<Self> {
68 let cap_len = common_data.config.fri_config.num_cap_elements();
69 let len = slice.len();
70 ensure!(len >= 4 + 4 * cap_len, "Not enough public inputs");
71 let constants_sigmas_cap = MerkleCapTarget(
72 (0..cap_len)
73 .map(|i| HashOutTarget {
74 elements: core::array::from_fn(|j| slice[len - 4 * (cap_len - i) + j]),
75 })
76 .collect(),
77 );
78 let circuit_digest = HashOutTarget {
79 elements: core::array::from_fn(|i| slice[len - 4 - 4 * cap_len + i]),
80 };
81
82 Ok(Self {
83 circuit_digest,
84 constants_sigmas_cap,
85 })
86 }
87}
88
89impl<F: RichField + Extendable<D>, const D: usize> CircuitBuilder<F, D> {
90 pub fn conditionally_verify_cyclic_proof<C: GenericConfig<D, F = F>>(
104 &mut self,
105 condition: BoolTarget,
106 cyclic_proof_with_pis: &ProofWithPublicInputsTarget<D>,
107 other_proof_with_pis: &ProofWithPublicInputsTarget<D>,
108 other_verifier_data: &VerifierCircuitTarget,
109 common_data: &CommonCircuitData<F, D>,
110 ) -> Result<()>
111 where
112 C::Hasher: AlgebraicHasher<F>,
113 {
114 let verifier_data = self
115 .verifier_data_public_input
116 .clone()
117 .expect("Must call add_verifier_data_public_inputs before cyclic recursion");
118
119 if let Some(existing_common_data) = self.goal_common_data.as_ref() {
120 assert_eq!(existing_common_data, common_data);
121 } else {
122 self.goal_common_data = Some(common_data.clone());
123 }
124
125 let inner_cyclic_pis = VerifierCircuitTarget::from_slice::<F, D>(
126 &cyclic_proof_with_pis.public_inputs,
127 common_data,
128 )?;
129 self.connect_hashes(
131 inner_cyclic_pis.circuit_digest,
132 verifier_data.circuit_digest,
133 );
134 self.connect_merkle_caps(
135 &inner_cyclic_pis.constants_sigmas_cap,
136 &verifier_data.constants_sigmas_cap,
137 );
138
139 self.conditionally_verify_proof::<C>(
141 condition,
142 cyclic_proof_with_pis,
143 &verifier_data,
144 other_proof_with_pis,
145 other_verifier_data,
146 common_data,
147 );
148
149 for g in &common_data.gates {
151 self.add_gate_to_gate_set(g.clone());
152 }
153
154 Ok(())
155 }
156
157 pub fn conditionally_verify_cyclic_proof_or_dummy<C: GenericConfig<D, F = F> + 'static>(
158 &mut self,
159 condition: BoolTarget,
160 cyclic_proof_with_pis: &ProofWithPublicInputsTarget<D>,
161 common_data: &CommonCircuitData<F, D>,
162 ) -> Result<()>
163 where
164 C::Hasher: AlgebraicHasher<F>,
165 {
166 let (dummy_proof_with_pis_target, dummy_verifier_data_target) =
167 self.dummy_proof_and_vk::<C>(common_data)?;
168 self.conditionally_verify_cyclic_proof::<C>(
169 condition,
170 cyclic_proof_with_pis,
171 &dummy_proof_with_pis_target,
172 &dummy_verifier_data_target,
173 common_data,
174 )?;
175 Ok(())
176 }
177}
178
179pub fn check_cyclic_proof_verifier_data<
182 F: RichField + Extendable<D>,
183 C: GenericConfig<D, F = F>,
184 const D: usize,
185>(
186 proof: &ProofWithPublicInputs<F, C, D>,
187 verifier_data: &VerifierOnlyCircuitData<C, D>,
188 common_data: &CommonCircuitData<F, D>,
189) -> Result<()>
190where
191 C::Hasher: AlgebraicHasher<F>,
192{
193 let pis = VerifierOnlyCircuitData::<C, D>::from_slice(&proof.public_inputs, common_data)?;
194 ensure!(verifier_data.constants_sigmas_cap == pis.constants_sigmas_cap);
195 ensure!(verifier_data.circuit_digest == pis.circuit_digest);
196
197 Ok(())
198}
199
200#[cfg(test)]
201mod tests {
202 #[cfg(not(feature = "std"))]
203 use alloc::vec;
204
205 use anyhow::Result;
206
207 use crate::field::extension::Extendable;
208 use crate::field::types::{Field, PrimeField64};
209 use crate::gates::noop::NoopGate;
210 use crate::hash::hash_types::{HashOutTarget, RichField};
211 use crate::hash::hashing::hash_n_to_hash_no_pad;
212 use crate::hash::poseidon::{PoseidonHash, PoseidonPermutation};
213 use crate::iop::witness::{PartialWitness, WitnessWrite};
214 use crate::plonk::circuit_builder::CircuitBuilder;
215 use crate::plonk::circuit_data::{CircuitConfig, CommonCircuitData};
216 use crate::plonk::config::{AlgebraicHasher, GenericConfig, PoseidonGoldilocksConfig};
217 use crate::recursion::cyclic_recursion::check_cyclic_proof_verifier_data;
218 use crate::recursion::dummy_circuit::cyclic_base_proof;
219
220 fn common_data_for_recursion<
222 F: RichField + Extendable<D>,
223 C: GenericConfig<D, F = F>,
224 const D: usize,
225 >() -> CommonCircuitData<F, D>
226 where
227 C::Hasher: AlgebraicHasher<F>,
228 {
229 let config = CircuitConfig::standard_recursion_config();
230 let builder = CircuitBuilder::<F, D>::new(config);
231 let data = builder.build::<C>();
232 let config = CircuitConfig::standard_recursion_config();
233 let mut builder = CircuitBuilder::<F, D>::new(config);
234 let proof = builder.add_virtual_proof_with_pis(&data.common);
235 let verifier_data =
236 builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
237 builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
238 let data = builder.build::<C>();
239
240 let config = CircuitConfig::standard_recursion_config();
241 let mut builder = CircuitBuilder::<F, D>::new(config);
242 let proof = builder.add_virtual_proof_with_pis(&data.common);
243 let verifier_data =
244 builder.add_virtual_verifier_data(data.common.config.fri_config.cap_height);
245 builder.verify_proof::<C>(&proof, &verifier_data, &data.common);
246 while builder.num_gates() < 1 << 12 {
247 builder.add_gate(NoopGate, vec![]);
248 }
249 builder.build::<C>().common
250 }
251
252 #[test]
259 fn test_cyclic_recursion() -> Result<()> {
260 const D: usize = 2;
261 type C = PoseidonGoldilocksConfig;
262 type F = <C as GenericConfig<D>>::F;
263
264 let config = CircuitConfig::standard_recursion_config();
265 let mut builder = CircuitBuilder::<F, D>::new(config);
266 let one = builder.one();
267
268 let initial_hash_target = builder.add_virtual_hash();
270 builder.register_public_inputs(&initial_hash_target.elements);
271 let current_hash_in = builder.add_virtual_hash();
272 let current_hash_out =
273 builder.hash_n_to_hash_no_pad::<PoseidonHash>(current_hash_in.elements.to_vec());
274 builder.register_public_inputs(¤t_hash_out.elements);
275 let counter = builder.add_virtual_public_input();
276
277 let mut common_data = common_data_for_recursion::<F, C, D>();
278 let verifier_data_target = builder.add_verifier_data_public_inputs();
279 common_data.num_public_inputs = builder.num_public_inputs();
280
281 let condition = builder.add_virtual_bool_target_safe();
282
283 let inner_cyclic_proof_with_pis = builder.add_virtual_proof_with_pis(&common_data);
285 let inner_cyclic_pis = &inner_cyclic_proof_with_pis.public_inputs;
286 let inner_cyclic_initial_hash = HashOutTarget::try_from(&inner_cyclic_pis[0..4]).unwrap();
287 let inner_cyclic_latest_hash = HashOutTarget::try_from(&inner_cyclic_pis[4..8]).unwrap();
288 let inner_cyclic_counter = inner_cyclic_pis[8];
289
290 builder.connect_hashes(initial_hash_target, inner_cyclic_initial_hash);
293
294 let actual_hash_in =
297 builder.select_hash(condition, inner_cyclic_latest_hash, initial_hash_target);
298 builder.connect_hashes(current_hash_in, actual_hash_in);
299
300 let new_counter = builder.mul_add(condition.target, inner_cyclic_counter, one);
302 builder.connect(counter, new_counter);
303
304 builder.conditionally_verify_cyclic_proof_or_dummy::<C>(
305 condition,
306 &inner_cyclic_proof_with_pis,
307 &common_data,
308 )?;
309
310 let cyclic_circuit_data = builder.build::<C>();
311
312 let mut pw = PartialWitness::new();
313 let initial_hash = [F::ZERO, F::ONE, F::TWO, F::from_canonical_usize(3)];
314 let initial_hash_pis = initial_hash.into_iter().enumerate().collect();
315 pw.set_bool_target(condition, false)?;
316 pw.set_proof_with_pis_target::<C, D>(
317 &inner_cyclic_proof_with_pis,
318 &cyclic_base_proof(
319 &common_data,
320 &cyclic_circuit_data.verifier_only,
321 initial_hash_pis,
322 ),
323 )?;
324 pw.set_verifier_data_target(&verifier_data_target, &cyclic_circuit_data.verifier_only)?;
325 let proof = cyclic_circuit_data.prove(pw)?;
326 check_cyclic_proof_verifier_data(
327 &proof,
328 &cyclic_circuit_data.verifier_only,
329 &cyclic_circuit_data.common,
330 )?;
331 cyclic_circuit_data.verify(proof.clone())?;
332
333 let mut pw = PartialWitness::new();
335 pw.set_bool_target(condition, true)?;
336 pw.set_proof_with_pis_target(&inner_cyclic_proof_with_pis, &proof)?;
337 pw.set_verifier_data_target(&verifier_data_target, &cyclic_circuit_data.verifier_only)?;
338 let proof = cyclic_circuit_data.prove(pw)?;
339 check_cyclic_proof_verifier_data(
340 &proof,
341 &cyclic_circuit_data.verifier_only,
342 &cyclic_circuit_data.common,
343 )?;
344 cyclic_circuit_data.verify(proof.clone())?;
345
346 let mut pw = PartialWitness::new();
348 pw.set_bool_target(condition, true)?;
349 pw.set_proof_with_pis_target(&inner_cyclic_proof_with_pis, &proof)?;
350 pw.set_verifier_data_target(&verifier_data_target, &cyclic_circuit_data.verifier_only)?;
351 let proof = cyclic_circuit_data.prove(pw)?;
352 check_cyclic_proof_verifier_data(
353 &proof,
354 &cyclic_circuit_data.verifier_only,
355 &cyclic_circuit_data.common,
356 )?;
357
358 let initial_hash = &proof.public_inputs[..4];
360 let hash = &proof.public_inputs[4..8];
361 let counter = proof.public_inputs[8];
362 let expected_hash: [F; 4] = iterate_poseidon(
363 initial_hash.try_into().unwrap(),
364 counter.to_canonical_u64() as usize,
365 );
366 assert_eq!(hash, expected_hash);
367
368 cyclic_circuit_data.verify(proof)
369 }
370
371 fn iterate_poseidon<F: RichField>(initial_state: [F; 4], n: usize) -> [F; 4] {
372 let mut current = initial_state;
373 for _ in 0..n {
374 current = hash_n_to_hash_no_pad::<F, PoseidonPermutation<F>>(¤t).elements;
375 }
376 current
377 }
378}