1use std::borrow::Borrow;
2
3use itertools::Itertools;
4use p3_baby_bear::BabyBear;
5use p3_bn254_fr::Bn254Fr;
6use p3_field::AbstractField;
7use p3_fri::TwoAdicFriPcsProof;
8use sp1_recursion_compiler::{
9 config::OuterConfig,
10 constraints::{Constraint, ConstraintCompiler},
11 ir::{Builder, Config, Ext, Felt, SymbolicExt, Var},
12};
13use sp1_recursion_core_v2::{
14 air::RecursionPublicValues,
15 machine::RecursionAir,
16 stark::config::{
17 BabyBearPoseidon2Outer, OuterChallenge, OuterChallengeMmcs, OuterFriProof, OuterVal,
18 OuterValMmcs,
19 },
20};
21use sp1_stark::{
22 AirOpenedValues, ChipOpenedValues, ShardCommitment, ShardOpenedValues, ShardProof,
23 StarkMachine, StarkVerifyingKey,
24};
25
26use crate::{
27 challenger::{CanObserveVariable, MultiField32ChallengerVariable},
28 stark::{ShardProofVariable, StarkVerifier},
29 utils::{felt_bytes_to_bn254_var, felts_to_bn254_var, words_to_bytes},
30 witness::Witnessable,
31 BatchOpeningVariable, FriCommitPhaseProofStepVariable, FriProofVariable, FriQueryProofVariable,
32 TwoAdicPcsProofVariable, VerifyingKeyVariable,
33};
34
35pub const DIGEST_SIZE: usize = 1;
36
37type OuterSC = BabyBearPoseidon2Outer;
38type OuterC = OuterConfig;
39type OuterDigestVariable = [Var<<OuterC as Config>::N>; DIGEST_SIZE];
40
41pub fn build_wrap_circuit_v2<F, const DEGREE: usize>(
45 wrap_vk: &StarkVerifyingKey<OuterSC>,
46 template_proof: ShardProof<OuterSC>,
47 outer_machine: StarkMachine<BabyBearPoseidon2Outer, RecursionAir<BabyBear, DEGREE, 0>>,
48) -> Vec<Constraint>
49where
50{
51 let mut builder = Builder::<OuterConfig>::default();
52 let mut challenger = MultiField32ChallengerVariable::new(&mut builder);
53
54 let preprocessed_commit_val: [Bn254Fr; 1] = wrap_vk.commit.into();
55 let preprocessed_commit: OuterDigestVariable = [builder.eval(preprocessed_commit_val[0])];
56 challenger.observe_commitment(&mut builder, preprocessed_commit);
57 let pc_start = builder.eval(wrap_vk.pc_start);
58 challenger.observe(&mut builder, pc_start);
59
60 let proof = template_proof.read(&mut builder);
64 let commited_values_digest = builder.constant(<C as Config>::N::zero());
67 builder.commit_commited_values_digest_circuit(commited_values_digest);
68 let vkey_hash = builder.constant(<C as Config>::N::zero());
69 builder.commit_vkey_hash_circuit(vkey_hash);
70
71 let pv: &RecursionPublicValues<_> = proof.public_values.as_slice().borrow();
79
80 let pv_vkey_hash = felts_to_bn254_var(&mut builder, &pv.sp1_vk_digest);
88 builder.assert_var_eq(pv_vkey_hash, vkey_hash);
90
91 let pv_committed_values_digest_bytes: [Felt<_>; 32] =
93 words_to_bytes(&pv.committed_value_digest).try_into().unwrap();
94 let pv_committed_values_digest: Var<_> =
95 felt_bytes_to_bn254_var(&mut builder, &pv_committed_values_digest_bytes);
96
97 builder.assert_var_eq(pv_committed_values_digest, commited_values_digest);
99
100 let ShardCommitment { main_commit, .. } = &proof.commitment;
101 challenger.observe_commitment(&mut builder, *main_commit);
102
103 challenger.observe_slice(&mut builder, proof.clone().public_values);
104
105 let StarkVerifyingKey { commit, pc_start, chip_information, chip_ordering } = wrap_vk;
106
107 let wrap_vk = VerifyingKeyVariable {
108 commitment: commit
109 .into_iter()
110 .map(|elem| builder.eval(elem))
111 .collect_vec()
112 .try_into()
113 .unwrap(),
114 pc_start: builder.eval(*pc_start),
115 chip_information: chip_information.clone(),
116 chip_ordering: chip_ordering.clone(),
117 };
118
119 StarkVerifier::<OuterC, OuterSC, _>::verify_shard(
120 &mut builder,
121 &wrap_vk,
122 &outer_machine,
123 &mut challenger.clone(),
124 &proof,
125 );
126
127 let zero_ext: Ext<_, _> = builder.constant(<OuterConfig as Config>::EF::zero());
128 let cumulative_sum: Ext<_, _> = builder.eval(zero_ext);
129 for chip in proof.opened_values.chips {
130 builder.assign(cumulative_sum, cumulative_sum + chip.cumulative_sum);
131 }
132 builder.assert_ext_eq(cumulative_sum, zero_ext);
133
134 let mut backend = ConstraintCompiler::<OuterConfig>::default();
144 backend.emit(builder.operations)
145}
146
147pub fn const_shard_proof(
150 builder: &mut Builder<OuterConfig>,
151 proof: &ShardProof<OuterSC>,
152) -> ShardProofVariable<OuterConfig, BabyBearPoseidon2Outer> {
153 let opening_proof = const_two_adic_pcs_proof(builder, proof.opening_proof.clone());
154 let opened_values = proof
155 .opened_values
156 .chips
157 .iter()
158 .map(|chip| {
159 let ChipOpenedValues {
160 preprocessed,
161 main,
162 permutation,
163 quotient,
164 cumulative_sum,
165 log_degree,
166 } = chip;
167 let AirOpenedValues { local: prepr_local, next: prepr_next } = preprocessed;
168 let AirOpenedValues { local: main_local, next: main_next } = main;
169 let AirOpenedValues { local: perm_local, next: perm_next } = permutation;
170
171 let quotient =
172 quotient.iter().map(|q| q.iter().map(|x| builder.constant(*x)).collect()).collect();
173 let cumulative_sum = builder.constant(*cumulative_sum);
174
175 let preprocessed = AirOpenedValues {
176 local: prepr_local.iter().map(|x| builder.constant(*x)).collect(),
177 next: prepr_next.iter().map(|x| builder.constant(*x)).collect(),
178 };
179
180 let main = AirOpenedValues {
181 local: main_local.iter().map(|x| builder.constant(*x)).collect(),
182 next: main_next.iter().map(|x| builder.constant(*x)).collect(),
183 };
184
185 let permutation = AirOpenedValues {
186 local: perm_local.iter().map(|x| builder.constant(*x)).collect(),
187 next: perm_next.iter().map(|x| builder.constant(*x)).collect(),
188 };
189
190 ChipOpenedValues {
191 preprocessed,
192 main,
193 permutation,
194 quotient,
195 cumulative_sum,
196 log_degree: *log_degree,
197 }
198 })
199 .collect();
200 let opened_values = ShardOpenedValues { chips: opened_values };
201 let ShardCommitment { main_commit, permutation_commit, quotient_commit } = proof.commitment;
202 let main_commit: [Bn254Fr; 1] = main_commit.into();
203 let permutation_commit: [Bn254Fr; 1] = permutation_commit.into();
204 let quotient_commit: [Bn254Fr; 1] = quotient_commit.into();
205
206 let main_commit = core::array::from_fn(|i| builder.eval(main_commit[i]));
207 let permutation_commit = core::array::from_fn(|i| builder.eval(permutation_commit[i]));
208 let quotient_commit = core::array::from_fn(|i| builder.eval(quotient_commit[i]));
209
210 let commitment = ShardCommitment { main_commit, permutation_commit, quotient_commit };
211 ShardProofVariable {
212 commitment,
213 public_values: proof.public_values.iter().map(|x| builder.constant(*x)).collect(),
214 opened_values,
215 opening_proof,
216 chip_ordering: proof.chip_ordering.clone(),
217 }
218}
219
220type C = OuterConfig;
221type SC = BabyBearPoseidon2Outer;
222type N = <C as Config>::N;
223
224fn const_fri_proof(
226 builder: &mut Builder<C>,
227 fri_proof: OuterFriProof,
228) -> FriProofVariable<OuterConfig, SC> {
229 let commit_phase_commits = fri_proof
231 .commit_phase_commits
232 .iter()
233 .map(|commit| {
234 let commit: [N; DIGEST_SIZE] = (*commit).into();
235 commit.map(|x| builder.eval(x))
236 })
237 .collect::<Vec<_>>();
238
239 let query_proofs = fri_proof
241 .query_proofs
242 .iter()
243 .map(|query_proof| {
244 let commit_phase_openings = query_proof
245 .commit_phase_openings
246 .iter()
247 .map(|commit_phase_opening| {
248 let sibling_value =
249 builder.eval(SymbolicExt::from_f(commit_phase_opening.sibling_value));
250 let opening_proof = commit_phase_opening
251 .opening_proof
252 .iter()
253 .map(|sibling| sibling.map(|x| builder.eval(x)))
254 .collect::<Vec<_>>();
255 FriCommitPhaseProofStepVariable { sibling_value, opening_proof }
256 })
257 .collect::<Vec<_>>();
258 FriQueryProofVariable { commit_phase_openings }
259 })
260 .collect::<Vec<_>>();
261
262 FriProofVariable {
264 commit_phase_commits,
265 query_proofs,
266 final_poly: builder.eval(SymbolicExt::from_f(fri_proof.final_poly)),
267 pow_witness: builder.eval(fri_proof.pow_witness),
268 }
269}
270
271pub fn const_two_adic_pcs_proof(
272 builder: &mut Builder<OuterConfig>,
273 proof: TwoAdicFriPcsProof<OuterVal, OuterChallenge, OuterValMmcs, OuterChallengeMmcs>,
274) -> TwoAdicPcsProofVariable<OuterConfig, SC> {
275 let fri_proof = const_fri_proof(builder, proof.fri_proof);
276 let query_openings = proof
277 .query_openings
278 .iter()
279 .map(|query_opening| {
280 query_opening
281 .iter()
282 .map(|opening| BatchOpeningVariable {
283 opened_values: opening
284 .opened_values
285 .iter()
286 .map(|opened_value| {
287 opened_value
288 .iter()
289 .map(|value| vec![builder.eval::<Felt<_>, _>(*value)])
290 .collect::<Vec<_>>()
291 })
292 .collect::<Vec<_>>(),
293 opening_proof: opening
294 .opening_proof
295 .iter()
296 .map(|opening_proof| opening_proof.map(|x| builder.eval(x)))
297 .collect::<Vec<_>>(),
298 })
299 .collect::<Vec<_>>()
300 })
301 .collect::<Vec<_>>();
302 TwoAdicPcsProofVariable { fri_proof, query_openings }
303}
304#[cfg(test)]
305pub mod tests {
306
307 use std::{borrow::Borrow, iter::once, sync::Arc};
308
309 use p3_baby_bear::{BabyBear, DiffusionMatrixBabyBear};
310 use p3_challenger::{CanObserve, FieldChallenger};
311 use p3_commit::{Pcs, TwoAdicMultiplicativeCoset};
312 use p3_field::{extension::BinomialExtensionField, AbstractField};
313 use p3_matrix::dense::RowMajorMatrix;
314 use rand::{rngs::StdRng, SeedableRng};
315 use sp1_core_machine::utils::{log2_strict_usize, run_test_machine, setup_logger};
316 use sp1_recursion_compiler::{
317 config::OuterConfig,
318 constraints::ConstraintCompiler,
319 ir::{Builder, Config, Ext, SymbolicExt},
320 };
321 use sp1_recursion_core_v2::{
322 air::RecursionPublicValues,
323 instruction as instr,
324 machine::RecursionAir,
325 stark::config::{
326 outer_fri_config, outer_perm, BabyBearPoseidon2Outer, OuterChallenge, OuterChallenger,
327 OuterCompress, OuterDft, OuterHash, OuterPcs, OuterVal, OuterValMmcs,
328 },
329 BaseAluOpcode, MemAccessKind, RecursionProgram, Runtime,
330 };
331 use sp1_recursion_gnark_ffi::{Groth16Bn254Prover, PlonkBn254Prover};
332 use sp1_stark::{BabyBearPoseidon2Inner, StarkMachine};
333
334 use crate::{
335 challenger::CanObserveVariable,
336 fri::verify_two_adic_pcs,
337 hash::BN254_DIGEST_SIZE,
338 utils::{babybear_bytes_to_bn254, babybears_to_bn254, words_to_bytes},
339 witness::{OuterWitness, Witnessable},
340 Digest, TwoAdicPcsMatsVariable, TwoAdicPcsRoundVariable,
341 };
342
343 use super::{build_wrap_circuit_v2, const_two_adic_pcs_proof};
344
345 fn test_machine<F, const DEGREE: usize>(machine_maker: F)
346 where
347 F: Fn() -> StarkMachine<BabyBearPoseidon2Outer, RecursionAir<BabyBear, DEGREE, 0>>,
348 {
349 setup_logger();
350 let n = 10;
351 let instructions = once(instr::mem(MemAccessKind::Write, 1, 0, 0))
353 .chain(once(instr::mem(MemAccessKind::Write, 2, 1, 1)))
354 .chain((2..=n).map(|i| instr::base_alu(BaseAluOpcode::AddF, 2, i, i - 2, i - 1)))
355 .chain(once(instr::mem(MemAccessKind::Read, 1, n - 1, 34)))
356 .chain(once(instr::mem(MemAccessKind::Read, 2, n, 55)))
357 .collect::<Vec<_>>();
358
359 let machine = machine_maker();
360 let program = RecursionProgram { instructions, ..Default::default() };
361 let mut runtime =
362 Runtime::<BabyBear, BinomialExtensionField<BabyBear, 4>, DiffusionMatrixBabyBear>::new(
363 Arc::new(program.clone()),
364 BabyBearPoseidon2Inner::new().perm,
365 );
366 runtime.run().unwrap();
367
368 let (pk, vk) = machine.setup(&program);
369 let result = run_test_machine(vec![runtime.record], machine, pk, vk.clone()).unwrap();
370
371 let machine = machine_maker();
372 let constraints =
373 build_wrap_circuit_v2::<BabyBear, DEGREE>(&vk, result.shard_proofs[0].clone(), machine);
374
375 let pv: &RecursionPublicValues<_> =
376 result.shard_proofs[0].public_values.as_slice().borrow();
377 let vkey_hash = babybears_to_bn254(&pv.sp1_vk_digest);
378 let committed_values_digest_bytes: [BabyBear; 32] =
379 words_to_bytes(&pv.committed_value_digest).try_into().unwrap();
380 let committed_values_digest = babybear_bytes_to_bn254(&committed_values_digest_bytes);
381
382 let mut witness = OuterWitness::default();
384 result.shard_proofs[0].write(&mut witness);
385 witness.write_commited_values_digest(committed_values_digest);
386 witness.write_vkey_hash(vkey_hash);
387
388 PlonkBn254Prover::test::<OuterConfig>(constraints.clone(), witness.clone());
389 Groth16Bn254Prover::test::<OuterConfig>(constraints, witness);
390 }
391
392 pub fn machine_with_all_chips<const DEGREE: usize>(
393 log_erbl_rows: usize,
394 log_p2_rows: usize,
395 log_frifold_rows: usize,
396 ) -> StarkMachine<BabyBearPoseidon2Outer, RecursionAir<BabyBear, DEGREE, 0>> {
397 let config = SC::new_with_log_blowup(log2_strict_usize(DEGREE - 1));
398 RecursionAir::<BabyBear, DEGREE, 0>::machine_with_padding(
399 config,
400 log_frifold_rows,
401 log_p2_rows,
402 log_erbl_rows,
403 )
404 }
405
406 #[test]
407 fn test_build_wrap() {
408 let machine_maker = || machine_with_all_chips::<17>(3, 3, 3);
409 test_machine(machine_maker);
410 }
411 type C = OuterConfig;
412 type SC = BabyBearPoseidon2Outer;
413
414 #[allow(clippy::type_complexity)]
415 pub fn const_two_adic_pcs_rounds(
416 builder: &mut Builder<OuterConfig>,
417 commit: [<C as Config>::N; BN254_DIGEST_SIZE],
418 os: Vec<(TwoAdicMultiplicativeCoset<OuterVal>, Vec<(OuterChallenge, Vec<OuterChallenge>)>)>,
419 ) -> (Digest<OuterConfig, SC>, Vec<TwoAdicPcsRoundVariable<OuterConfig, SC>>) {
420 let commit: Digest<OuterConfig, SC> = commit.map(|x| builder.eval(x));
421
422 let mut domains_points_and_opens = Vec::new();
423 for (domain, poly) in os.into_iter() {
424 let points: Vec<Ext<OuterVal, OuterChallenge>> =
425 poly.iter().map(|(p, _)| builder.eval(SymbolicExt::from_f(*p))).collect::<Vec<_>>();
426 let values: Vec<Vec<Ext<OuterVal, OuterChallenge>>> = poly
427 .iter()
428 .map(|(_, v)| {
429 v.clone()
430 .iter()
431 .map(|t| builder.eval(SymbolicExt::from_f(*t)))
432 .collect::<Vec<_>>()
433 })
434 .collect::<Vec<_>>();
435 let domain_points_and_values = TwoAdicPcsMatsVariable { domain, points, values };
436 domains_points_and_opens.push(domain_points_and_values);
437 }
438
439 (commit, vec![TwoAdicPcsRoundVariable { batch_commit: commit, domains_points_and_opens }])
440 }
441
442 #[test]
443 fn test_verify_two_adic_pcs_outer() {
444 let mut rng = StdRng::seed_from_u64(0xDEADBEEF);
445 let log_degrees = &[19, 19];
446 let perm = outer_perm();
447 let mut fri_config = outer_fri_config();
448
449 fri_config.log_blowup = 2;
451 let hash = OuterHash::new(perm.clone()).unwrap();
452 let compress = OuterCompress::new(perm.clone());
453 let val_mmcs = OuterValMmcs::new(hash, compress);
454 let dft = OuterDft {};
455 let pcs: OuterPcs =
456 OuterPcs::new(log_degrees.iter().copied().max().unwrap(), dft, val_mmcs, fri_config);
457
458 let domains_and_polys = log_degrees
460 .iter()
461 .map(|&d| {
462 (
463 <OuterPcs as Pcs<OuterChallenge, OuterChallenger>>::natural_domain_for_degree(
464 &pcs,
465 1 << d,
466 ),
467 RowMajorMatrix::<OuterVal>::rand(&mut rng, 1 << d, 100),
468 )
469 })
470 .collect::<Vec<_>>();
471 let (commit, data) = <OuterPcs as Pcs<OuterChallenge, OuterChallenger>>::commit(
472 &pcs,
473 domains_and_polys.clone(),
474 );
475 let mut challenger = OuterChallenger::new(perm.clone()).unwrap();
476 challenger.observe(commit);
477 let zeta = challenger.sample_ext_element::<OuterChallenge>();
478 let points = domains_and_polys.iter().map(|_| vec![zeta]).collect::<Vec<_>>();
479 let (opening, proof) = pcs.open(vec![(&data, points)], &mut challenger);
480
481 let mut challenger = OuterChallenger::new(perm.clone()).unwrap();
483 challenger.observe(commit);
484 let x1 = challenger.sample_ext_element::<OuterChallenge>();
485 let os = domains_and_polys
486 .iter()
487 .zip(&opening[0])
488 .map(|((domain, _), mat_openings)| (*domain, vec![(zeta, mat_openings[0].clone())]))
489 .collect::<Vec<_>>();
490 pcs.verify(vec![(commit, os.clone())], &proof, &mut challenger).unwrap();
491
492 let mut builder = Builder::<OuterConfig>::default();
494 let mut config = outer_fri_config();
495
496 config.log_blowup = 2;
498 let proof = const_two_adic_pcs_proof(&mut builder, proof);
499 let (commit, rounds) = const_two_adic_pcs_rounds(&mut builder, commit.into(), os);
500 let mut challenger = crate::challenger::MultiField32ChallengerVariable::new(&mut builder);
501 challenger.observe_slice(&mut builder, commit);
502 let x2 = challenger.sample_ext(&mut builder);
503 let x1: Ext<_, _> = builder.constant(x1);
504 builder.assert_ext_eq(x1, x2);
505 verify_two_adic_pcs::<_, BabyBearPoseidon2Outer>(
506 &mut builder,
507 &config,
508 &proof,
509 &mut challenger,
510 rounds,
511 );
512 let mut backend = ConstraintCompiler::<OuterConfig>::default();
513 let constraints = backend.emit(builder.operations);
514 let witness = OuterWitness::default();
515 PlonkBn254Prover::test::<OuterConfig>(constraints, witness);
516 }
517}