openvm_verify_stark_circuit/verifier/
air.rs

1use std::{array::from_fn, borrow::Borrow};
2
3use openvm_circuit::arch::{ExitCode, POSEIDON2_WIDTH};
4use openvm_circuit_primitives::{
5    utils::assert_array_eq, ColumnsAir, StructReflection, StructReflectionHelper, SubAir,
6};
7use openvm_continuations::{
8    circuit::{
9        deferral::DeferralCircuitPvs,
10        root::{
11            bus::{
12                DeferralAccPathBus, DeferralAccPathMessage, DeferralMerkleRootsBus,
13                DeferralMerkleRootsMessage, MemoryMerkleCommitBus, MemoryMerkleCommitMessage,
14            },
15            NUM_DIGESTS_IN_VM_COMMIT,
16        },
17        subair::{HashSliceCtx, HashSliceSubAir},
18        utils::{assert_vk_commit_eq, assert_vk_commit_unset, vk_commit_components},
19    },
20    utils::{digests_to_poseidon2_input, pad_slice_to_poseidon2_input},
21    CommitBytes, VkCommitBytes,
22};
23use openvm_recursion_circuit::{
24    bus::{
25        CachedCommitBus, CachedCommitBusMessage, FinalTranscriptStateBus,
26        FinalTranscriptStateMessage, Poseidon2CompressBus, Poseidon2CompressMessage, PreHashBus,
27        PreHashMessage, PublicValuesBus, PublicValuesBusMessage,
28    },
29    primitives::bus::{RangeCheckerBus, RangeCheckerBusMessage},
30    utils::assert_zeros,
31};
32use openvm_recursion_circuit_derive::AlignedBorrow;
33use openvm_stark_backend::{
34    interaction::InteractionBuilder, BaseAirWithPublicValues, PartitionedBaseAir,
35};
36use openvm_stark_sdk::config::baby_bear_poseidon2::DIGEST_SIZE;
37use openvm_verify_stark_host::pvs::{
38    DeferralPvs, VerifierBasePvs, VerifierDefPvs, VkCommit, VmPvs, CONSTRAINT_EVAL_AIR_ID,
39    CONSTRAINT_EVAL_CACHED_INDEX, DEF_PVS_AIR_ID, LOG_MAX_RECURSION_DEPTH, VERIFIER_PVS_AIR_ID,
40    VM_PVS_AIR_ID,
41};
42use p3_air::{Air, AirBuilder, AirBuilderWithPublicValues, BaseAir};
43use p3_field::{Field, PrimeCharacteristicRing};
44use p3_matrix::Matrix;
45
46use crate::{
47    bus::{OutputCommitBus, OutputCommitMessage, OutputValBus, OutputValMessage},
48    output::VALS_IN_DIGEST,
49};
50
51#[repr(C)]
52#[derive(AlignedBorrow, StructReflection)]
53pub struct DeferredVerifyPvsCols<F> {
54    pub child_verifier_pvs: VerifierBasePvs<F>,
55    pub child_vm_pvs: VmPvs<F>,
56
57    pub recursion_depth_minus_one_inv: F,
58
59    pub program_commit_hash: [F; DIGEST_SIZE],
60    pub initial_root_hash: [F; DIGEST_SIZE],
61    pub initial_pc_hash: [F; DIGEST_SIZE],
62
63    pub intermediate_exe_commit: [F; DIGEST_SIZE],
64    pub intermediate_vk_states: [[F; POSEIDON2_WIDTH]; NUM_DIGESTS_IN_VM_COMMIT - 1],
65
66    pub app_exe_commit: [F; DIGEST_SIZE],
67    pub app_vm_commit: [F; DIGEST_SIZE],
68    pub final_transcript_state: [F; POSEIDON2_WIDTH],
69}
70
71#[derive(ColumnsAir)]
72#[columns_via(DeferredVerifyPvsCols<u8>)]
73pub struct DeferredVerifyPvsAir {
74    pub public_values_bus: PublicValuesBus,
75    pub cached_commit_bus: CachedCommitBus,
76    pub pre_hash_bus: PreHashBus,
77    pub range_bus: RangeCheckerBus,
78    pub poseidon2_compress_bus: Poseidon2CompressBus,
79    pub hash_slice_subair: HashSliceSubAir,
80    pub memory_merkle_commit_bus: MemoryMerkleCommitBus,
81    pub output_val_bus: OutputValBus,
82    pub output_commit_bus: OutputCommitBus,
83    pub final_state_bus: FinalTranscriptStateBus,
84
85    pub def_acc_paths_bus: DeferralAccPathBus,
86    pub def_merkle_roots_bus: DeferralMerkleRootsBus,
87
88    pub expected_internal_recursive_vk_commit: VkCommitBytes,
89    pub expected_def_hook_commit: Option<CommitBytes>,
90
91    pub def_idx: usize,
92}
93
94impl<F> BaseAir<F> for DeferredVerifyPvsAir {
95    fn width(&self) -> usize {
96        DeferredVerifyPvsCols::<u8>::width()
97            + if self.expected_def_hook_commit.is_some() {
98                RecursiveDeferredVerifyCols::<u8>::width()
99            } else {
100                0
101            }
102    }
103}
104impl<F> BaseAirWithPublicValues<F> for DeferredVerifyPvsAir {
105    fn num_public_values(&self) -> usize {
106        DeferralCircuitPvs::<u8>::width()
107    }
108}
109impl<F> PartitionedBaseAir<F> for DeferredVerifyPvsAir {}
110
111impl<AB: AirBuilder + InteractionBuilder + AirBuilderWithPublicValues> Air<AB>
112    for DeferredVerifyPvsAir
113{
114    fn eval(&self, builder: &mut AB) {
115        let main = builder.main();
116        let local = main.row_slice(0).expect("window should have one elements");
117
118        let base_cols_width = DeferredVerifyPvsCols::<AB::Var>::width();
119        let (base_local, rec_local) = local.split_at(base_cols_width);
120        let local: &DeferredVerifyPvsCols<AB::Var> = (*base_local).borrow();
121
122        if let Some(def_hook_commit) = self.expected_def_hook_commit {
123            let rec: &RecursiveDeferredVerifyCols<AB::Var> = (*rec_local).borrow();
124            self.eval_deferrals(builder, local, rec, def_hook_commit);
125        }
126
127        /*
128         * DeferralCircuitPvs exposes a reduced/derived subset of the child public values, so
129         * we must constrain required child public values here. We start with is_terminate and
130         * exit code.
131         */
132        let success = AB::F::from_u32(ExitCode::Success as u32);
133        builder.assert_eq(local.child_vm_pvs.exit_code, success);
134        builder.assert_one(local.child_vm_pvs.is_terminate);
135
136        /*
137         * Check that the final proof is computed by the internal recursive prover, i.e.
138         * that internal_flag is 2 and that recursion_depth is in [1, MAX_RECURSION_DEPTH].
139         */
140        builder.assert_eq(local.child_verifier_pvs.internal_flag, AB::F::TWO);
141
142        let depth_minus_one = local.child_verifier_pvs.recursion_depth - AB::F::ONE;
143        let is_depth_not_one = depth_minus_one.clone() * local.recursion_depth_minus_one_inv;
144        let is_depth_one = AB::Expr::ONE - is_depth_not_one.clone();
145
146        builder.assert_bool(is_depth_one.clone());
147        builder
148            .when(is_depth_one.clone())
149            .assert_one(local.child_verifier_pvs.recursion_depth);
150
151        self.range_bus.lookup_key(
152            builder,
153            RangeCheckerBusMessage {
154                value: depth_minus_one,
155                max_bits: AB::Expr::from_u8(LOG_MAX_RECURSION_DEPTH),
156            },
157            AB::F::ONE,
158        );
159
160        /*
161         * UserPvsCommitValuesAir constrains that the Merkle root of the original app user
162         * public values is some user_pvs_commit. We also need to constrain that those
163         * values were part of the final memory state - we do this in UserPvsInMemoryAir,
164         * which has to receive final_root in order to verify a Merkle proof from
165         * user_pvs_commit to it.
166         */
167        self.memory_merkle_commit_bus.send(
168            builder,
169            MemoryMerkleCommitMessage {
170                merkle_root: local.child_vm_pvs.final_root,
171            },
172            AB::F::ONE,
173        );
174
175        /*
176         * We still need to receive public values from ProofShapeModule to ensure the values
177         * being read here are correct. Vm and verifier public values are on separate AIRs.
178         */
179        let vm_pvs_id = AB::Expr::from_usize(VM_PVS_AIR_ID);
180        let verifier_pvs_id = AB::Expr::from_usize(VERIFIER_PVS_AIR_ID);
181
182        for (pv_idx, value) in local.child_vm_pvs.as_slice().iter().enumerate() {
183            self.public_values_bus.receive(
184                builder,
185                AB::F::ZERO,
186                PublicValuesBusMessage {
187                    air_idx: vm_pvs_id.clone(),
188                    pv_idx: AB::Expr::from_usize(pv_idx),
189                    value: (*value).into(),
190                },
191                AB::F::ONE,
192            );
193        }
194
195        for (pv_idx, value) in local.child_verifier_pvs.as_slice().iter().enumerate() {
196            self.public_values_bus.receive(
197                builder,
198                AB::F::ZERO,
199                PublicValuesBusMessage {
200                    air_idx: verifier_pvs_id.clone(),
201                    pv_idx: AB::Expr::from_usize(pv_idx),
202                    value: (*value).into(),
203                },
204                AB::F::ONE,
205            );
206        }
207
208        /*
209         * We also need to receive the cached commit from ProofShapeModule, which is either for
210         * the internal-for-leaf (i.e. if recursion_depth == 1) or internal-recursive layer. In
211         * the former case we constrain child_pvs.internal_recursive_vk_commit to be unset (i.e.
212         * all 0), and in the latter we constrain it to be equal to a pre-generated constant as
213         * it should be the same regardless of app_vk (provided internal system params are the
214         * same).
215         */
216        let cached_commit = from_fn(|i| {
217            local
218                .child_verifier_pvs
219                .internal_for_leaf_vk_commit
220                .cached_commit[i]
221                * is_depth_one.clone()
222                + local
223                    .child_verifier_pvs
224                    .internal_recursive_vk_commit
225                    .cached_commit[i]
226                    * is_depth_not_one.clone()
227        });
228        self.cached_commit_bus.receive(
229            builder,
230            AB::F::ZERO,
231            CachedCommitBusMessage {
232                air_idx: AB::Expr::from_usize(CONSTRAINT_EVAL_AIR_ID),
233                cached_idx: AB::Expr::from_usize(CONSTRAINT_EVAL_CACHED_INDEX),
234                global_cached_idx: AB::Expr::ZERO,
235                cached_commit,
236            },
237            AB::F::ONE,
238        );
239
240        self.pre_hash_bus.receive(
241            builder,
242            AB::F::ZERO,
243            PreHashMessage::<AB::F> {
244                vk_pre_hash: self
245                    .expected_internal_recursive_vk_commit
246                    .vk_pre_hash
247                    .into(),
248            },
249            AB::F::ONE,
250        );
251
252        assert_vk_commit_unset(
253            &mut builder.when(is_depth_one),
254            local.child_verifier_pvs.internal_recursive_vk_commit,
255        );
256
257        assert_vk_commit_eq(
258            &mut builder.when(is_depth_not_one),
259            local.child_verifier_pvs.internal_recursive_vk_commit,
260            VkCommit::<AB::Expr>::from(self.expected_internal_recursive_vk_commit),
261        );
262
263        /*
264         * We need to verify the commits to the app executable and vk. The app_vm_commit is
265         * constrained to be hash_slice of the 6 vk_commit_components (cached_commit and
266         * vk_pre_hash for each of the child's app, leaf, and internal-for-leaf vk commits).
267         */
268        let vk_commit_components: Vec<_> = vk_commit_components(&local.child_verifier_pvs)
269            .into_iter()
270            .map(|c| c.map(Into::into))
271            .collect();
272        self.hash_slice_subair.eval(
273            builder,
274            HashSliceCtx {
275                elements: vk_commit_components.as_slice(),
276                intermediate: local
277                    .intermediate_vk_states
278                    .map(|v| v.map(Into::into))
279                    .as_slice(),
280                result: &local.app_vm_commit.map(Into::into),
281                enabled: &AB::Expr::ONE,
282            },
283        );
284
285        /*
286         * The app_exe_commit is a commit to the app program, initial memory state, and initial
287         * PC. Child public values program_commit, initial_root, and initial_pc are individually
288         * hashed and then permuted together to produce app_exe_commit.
289         */
290        self.poseidon2_compress_bus.lookup_key(
291            builder,
292            Poseidon2CompressMessage {
293                input: pad_slice_to_poseidon2_input(
294                    &local.child_vm_pvs.program_commit.map(Into::into),
295                    AB::Expr::ZERO,
296                ),
297                output: local.program_commit_hash.map(Into::into),
298            },
299            AB::F::ONE,
300        );
301
302        self.poseidon2_compress_bus.lookup_key(
303            builder,
304            Poseidon2CompressMessage {
305                input: pad_slice_to_poseidon2_input(
306                    &local.child_vm_pvs.initial_root.map(Into::into),
307                    AB::Expr::ZERO,
308                ),
309                output: local.initial_root_hash.map(Into::into),
310            },
311            AB::F::ONE,
312        );
313
314        self.poseidon2_compress_bus.lookup_key(
315            builder,
316            Poseidon2CompressMessage {
317                input: pad_slice_to_poseidon2_input(
318                    &[local.child_vm_pvs.initial_pc.into()],
319                    AB::Expr::ZERO,
320                ),
321                output: local.initial_pc_hash.map(Into::into),
322            },
323            AB::F::ONE,
324        );
325
326        self.poseidon2_compress_bus.lookup_key(
327            builder,
328            Poseidon2CompressMessage {
329                input: digests_to_poseidon2_input(
330                    local.program_commit_hash,
331                    local.initial_root_hash,
332                ),
333                output: local.intermediate_exe_commit,
334            },
335            AB::F::ONE,
336        );
337
338        self.poseidon2_compress_bus.lookup_key(
339            builder,
340            Poseidon2CompressMessage {
341                input: digests_to_poseidon2_input(
342                    local.intermediate_exe_commit,
343                    local.initial_pc_hash,
344                ),
345                output: local.app_exe_commit,
346            },
347            AB::F::ONE,
348        );
349
350        /*
351         * Finally, we constrain the public values of this AIR - input_commit should be the
352         * compression of the final verifier sub-circuit transcript state, and output_commit
353         * should be the Poseidon2 sponge hash of the byte representations of app_exe_commit,
354         * app_vm_commit, and the user public values. The latter is constrained by the
355         * DeferralOutputCommitAir, to which we need to send the app commits. def_idx must be
356         * constrained against a constant.
357         */
358        let &DeferralCircuitPvs::<_> {
359            input_commit,
360            output_commit,
361            def_idx,
362        } = builder.public_values().borrow();
363
364        builder.assert_eq(def_idx, AB::Expr::from_usize(self.def_idx));
365
366        self.final_state_bus.receive(
367            builder,
368            AB::Expr::ZERO,
369            FinalTranscriptStateMessage {
370                state: local.final_transcript_state,
371            },
372            AB::F::ONE,
373        );
374
375        self.poseidon2_compress_bus.lookup_key(
376            builder,
377            Poseidon2CompressMessage {
378                input: local.final_transcript_state.map(Into::into),
379                output: input_commit.map(Into::into),
380            },
381            AB::F::ONE,
382        );
383
384        let mut output_idx = 0;
385        for exe_val in local.app_exe_commit.chunks_exact(VALS_IN_DIGEST) {
386            self.output_val_bus.send(
387                builder,
388                OutputValMessage {
389                    values: from_fn(|i| exe_val[i].into()),
390                    idx: AB::Expr::from_u8(output_idx),
391                },
392                AB::F::ONE,
393            );
394            output_idx += 1;
395        }
396
397        for vk_val in local.app_vm_commit.chunks_exact(VALS_IN_DIGEST) {
398            self.output_val_bus.send(
399                builder,
400                OutputValMessage {
401                    values: from_fn(|i| vk_val[i].into()),
402                    idx: AB::Expr::from_u8(output_idx),
403                },
404                AB::F::ONE,
405            );
406            output_idx += 1;
407        }
408
409        self.output_commit_bus.receive(
410            builder,
411            OutputCommitMessage {
412                commit: output_commit,
413            },
414            AB::F::ONE,
415        );
416    }
417}
418
419#[repr(C)]
420#[derive(AlignedBorrow)]
421pub struct RecursiveDeferredVerifyCols<F> {
422    pub child_def_verifier_pvs: VerifierDefPvs<F>,
423    pub child_def_pvs: DeferralPvs<F>,
424}
425
426#[repr(C)]
427#[derive(AlignedBorrow)]
428pub struct DeferredVerifyCombinedPvs<F> {
429    pub base: DeferredVerifyPvsCols<F>,
430    pub rec: RecursiveDeferredVerifyCols<F>,
431}
432
433impl DeferredVerifyPvsAir {
434    fn eval_deferrals<AB>(
435        &self,
436        builder: &mut AB,
437        base: &DeferredVerifyPvsCols<AB::Var>,
438        rec: &RecursiveDeferredVerifyCols<AB::Var>,
439        expected_def_hook_commit: CommitBytes,
440    ) where
441        AB: AirBuilder + InteractionBuilder + AirBuilderWithPublicValues,
442    {
443        let verifier_pvs = rec.child_def_verifier_pvs;
444        let def_pvs = rec.child_def_pvs;
445
446        /*
447         * We need to receive the additional public values here.
448         */
449        let def_pvs_idx = AB::Expr::from_usize(DEF_PVS_AIR_ID);
450        let verifier_pvs_id = AB::Expr::from_usize(VERIFIER_PVS_AIR_ID);
451        let base_verifier_pvs_width = VerifierBasePvs::<u8>::width();
452
453        for (pv_idx, value) in verifier_pvs.as_slice().iter().enumerate() {
454            self.public_values_bus.receive(
455                builder,
456                AB::F::ZERO,
457                PublicValuesBusMessage {
458                    air_idx: verifier_pvs_id.clone(),
459                    pv_idx: AB::Expr::from_usize(pv_idx + base_verifier_pvs_width),
460                    value: (*value).into(),
461                },
462                AB::F::ONE,
463            );
464        }
465
466        for (pv_idx, value) in def_pvs.as_slice().iter().enumerate() {
467            self.public_values_bus.receive(
468                builder,
469                AB::F::ZERO,
470                PublicValuesBusMessage {
471                    air_idx: def_pvs_idx.clone(),
472                    pv_idx: AB::Expr::from_usize(pv_idx),
473                    value: (*value).into(),
474                },
475                AB::F::ONE,
476            );
477        }
478
479        /*
480         * The final internal-recursive proof's deferral_flag must be either 0 or 2, and
481         * in the latter case the def_hook_commit must match the expected one.
482         */
483        builder
484            .assert_zero(verifier_pvs.deferral_flag * (verifier_pvs.deferral_flag - AB::Expr::TWO));
485        assert_array_eq::<_, _, AB::Expr, _>(
486            &mut builder.when(verifier_pvs.deferral_flag),
487            verifier_pvs.def_hook_commit,
488            expected_def_hook_commit.into(),
489        );
490
491        /*
492         * If deferral_flag is 2, there must be a Merkle path from initial_acc_hash to
493         * initial_root and from final_acc_hash to final_root. Else, we constrain that
494         * they are 0, that def_hook_commit is unset, and that the deferral address
495         * space remained unchanged. Either way, node_idx must be 0.
496         */
497        let mut when_no_deferral = builder.when_ne(verifier_pvs.deferral_flag, AB::Expr::TWO);
498        when_no_deferral.assert_zero(def_pvs.depth);
499        assert_zeros(&mut when_no_deferral, def_pvs.initial_acc_hash);
500        assert_zeros(&mut when_no_deferral, def_pvs.final_acc_hash);
501        assert_zeros(&mut when_no_deferral, verifier_pvs.def_hook_commit);
502
503        builder.assert_zero(def_pvs.node_idx);
504
505        self.def_acc_paths_bus.send(
506            builder,
507            DeferralAccPathMessage {
508                initial_acc_hash: def_pvs.initial_acc_hash.map(Into::into),
509                final_acc_hash: def_pvs.final_acc_hash.map(Into::into),
510                depth: def_pvs.depth.into(),
511                is_unset: (AB::Expr::TWO - verifier_pvs.deferral_flag) * AB::F::TWO.inverse(),
512            },
513            AB::F::ONE,
514        );
515
516        self.def_merkle_roots_bus.send(
517            builder,
518            DeferralMerkleRootsMessage {
519                initial_root: base.child_vm_pvs.initial_root,
520                final_root: base.child_vm_pvs.final_root,
521            },
522            AB::F::ONE,
523        );
524    }
525}