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