Skip to main content

sp1_core_machine/syscall/instructions/
air.rs

1use std::borrow::Borrow;
2
3use itertools::Itertools;
4use slop_air::{Air, AirBuilder};
5use slop_algebra::AbstractField;
6use slop_matrix::Matrix;
7use sp1_core_executor::{Opcode, SyscallCode, CLK_INC, HALT_PC};
8use sp1_hypercube::{
9    air::{
10        BaseAirBuilder, InteractionScope, PublicValues, SP1AirBuilder, POSEIDON_NUM_WORDS,
11        PV_DIGEST_NUM_WORDS, SP1_PROOF_NUM_PV_ELTS,
12    },
13    Word,
14};
15
16use crate::{
17    adapter::{register::r_type::RTypeReader, state::CPUState},
18    air::{SP1CoreAirBuilder, SP1Operation, WordAirBuilder},
19    operations::{
20        IsZeroOperation, IsZeroOperationInput, SP1FieldWordRangeChecker, TrapOperation,
21        U16toU8OperationSafe, U16toU8OperationSafeInput,
22    },
23    TrustMode, UserMode,
24};
25
26use super::{columns::SyscallInstrColumns, SyscallInstrsChip};
27
28impl<AB, M> Air<AB> for SyscallInstrsChip<M>
29where
30    AB: SP1CoreAirBuilder,
31    AB::Var: Sized,
32    M: TrustMode,
33{
34    #[inline(never)]
35    fn eval(&self, builder: &mut AB) {
36        let main = builder.main();
37        let local = main.row_slice(0);
38        let local: &SyscallInstrColumns<AB::Var, M> = (*local).borrow();
39
40        let public_values_slice: [AB::PublicVar; SP1_PROOF_NUM_PV_ELTS] =
41            core::array::from_fn(|i| builder.public_values()[i]);
42        let public_values: &PublicValues<
43            [AB::PublicVar; 4],
44            [AB::PublicVar; 3],
45            [AB::PublicVar; 4],
46            AB::PublicVar,
47        > = public_values_slice.as_slice().borrow();
48
49        // Convert the syscall code to 8 bytes using the safe API.
50        let a_input = U16toU8OperationSafeInput::new(
51            local.adapter.prev_a().0.map(Into::into),
52            local.a_low_bytes,
53            local.is_real.into(),
54        );
55        let a = U16toU8OperationSafe::eval(builder, a_input);
56
57        // SAFETY: Only `ECALL` opcode can be received in this chip.
58        // `is_real` is checked to be boolean, and the `opcode` matches the corresponding opcode.
59        builder.assert_bool(local.is_real);
60
61        // Verify that local.is_halt is correct.
62        self.eval_is_halt_syscall(builder, &a, local);
63
64        // Constrain the state of the CPU.
65        // The extra timestamp increment is `256` always.
66        // The `next_pc` is constrained in the AIR.
67        CPUState::<AB::F>::eval(
68            builder,
69            local.state,
70            local.next_pc.map(Into::into),
71            AB::Expr::from_canonical_u32(CLK_INC + 256),
72            local.is_real.into(),
73        );
74
75        #[allow(unused_variables)]
76        {
77            let funct3 = AB::Expr::from_canonical_u8(Opcode::ECALL.funct3().unwrap_or(0));
78            let funct7 = AB::Expr::from_canonical_u8(Opcode::ECALL.funct7().unwrap_or(0));
79            let base_opcode = AB::Expr::from_canonical_u32(Opcode::ECALL.base_opcode().0);
80            let instr_type =
81                AB::Expr::from_canonical_u32(Opcode::ECALL.instruction_type().0 as u32);
82        }
83
84        // Constrain the program and register reads.
85        RTypeReader::<AB::F>::eval(
86            builder,
87            local.state.clk_high::<AB>(),
88            local.state.clk_low::<AB>(),
89            local.state.pc,
90            AB::Expr::from_canonical_u32(Opcode::ECALL as u32),
91            local.op_a_value,
92            local.adapter,
93            local.is_real.into(),
94            local.is_real.into(), // is_trusted - syscalls are only for trusted programs
95        );
96        builder.when(local.is_real).assert_zero(local.adapter.op_a_0);
97
98        #[allow(unused_variables)]
99        let (is_sig_return, is_trap, trap_code) = if !M::IS_TRUSTED {
100            let local = main.row_slice(0);
101            let local: &SyscallInstrColumns<AB::Var, UserMode> = (*local).borrow();
102            #[cfg(not(feature = "mprotect"))]
103            builder.assert_zero(local.is_real);
104            let is_sig_return = self.eval_sig_return(builder, &a, local);
105            let (is_trap, trap_code) = self.eval_trap(builder, local);
106            builder.assert_bool(local.is_halt + is_sig_return.clone() + is_trap.clone());
107            // PAGE_PROTECT ecall instruction.
108            self.eval_page_protect(builder, local, &a, public_values.is_untrusted_programs_enabled);
109            (is_sig_return, is_trap, trap_code)
110        } else {
111            (AB::Expr::zero(), AB::Expr::zero(), AB::Expr::zero())
112        };
113
114        #[cfg(feature = "mprotect")]
115        {
116            builder.assert_eq(
117                public_values.is_untrusted_programs_enabled,
118                AB::Expr::from_bool(!M::IS_TRUSTED),
119            );
120        }
121
122        #[cfg(not(feature = "mprotect"))]
123        let jump: AB::Expr = local.is_halt.into();
124        #[cfg(feature = "mprotect")]
125        let jump: AB::Expr = local.is_halt.into() + is_sig_return.clone() + is_trap.clone();
126
127        // If the syscall is not halt, then next_pc should be pc + 4.
128        // `next_pc` is constrained for the case where `is_halt` is false to be `pc + 4`.
129        builder
130            .when(local.is_real)
131            .when(AB::Expr::one() - jump.clone())
132            .assert_eq(local.next_pc[0], local.state.pc[0] + AB::Expr::from_canonical_u32(4));
133        builder
134            .when(local.is_real)
135            .when(AB::Expr::one() - jump.clone())
136            .assert_eq(local.next_pc[1], local.state.pc[1]);
137        builder
138            .when(local.is_real)
139            .when(AB::Expr::one() - jump.clone())
140            .assert_eq(local.next_pc[2], local.state.pc[2]);
141
142        // ECALL instruction.
143        self.eval_ecall(builder, &a, local, trap_code);
144
145        // COMMIT/COMMIT_DEFERRED_PROOFS ecall instruction.
146        self.eval_commit(
147            builder,
148            &a,
149            local,
150            public_values.commit_syscall,
151            public_values.commit_deferred_syscall,
152            public_values.committed_value_digest,
153            public_values.deferred_proofs_digest,
154        );
155
156        // HALT ecall and UNIMPL instruction.
157        self.eval_halt_unimpl(builder, local, public_values);
158    }
159}
160
161impl<M: TrustMode> SyscallInstrsChip<M> {
162    /// Constraints related to the ECALL opcode.
163    ///
164    /// This method will do the following:
165    /// 1. Send the syscall to the precompile table, if needed.
166    /// 2. Check for valid op_a values.
167    pub(crate) fn eval_ecall<AB: SP1CoreAirBuilder>(
168        &self,
169        builder: &mut AB,
170        prev_a_byte: &[AB::Expr; 8],
171        local: &SyscallInstrColumns<AB::Var, M>,
172        trap_code: AB::Expr,
173    ) {
174        // We interpret the syscall_code as little-endian bytes and interpret each byte as a u8
175        // with different information.
176        let syscall_id = prev_a_byte[0].clone();
177        let send_to_table = prev_a_byte[1].clone();
178
179        // SAFETY: Assert that for padding rows, the interactions from `send_syscall` and
180        // KoalaBearWordRangeChecker do not have non-zero multiplicities.
181        builder.when_not(local.is_real).assert_zero(send_to_table.clone());
182        builder.when_not(local.is_real).assert_zero(local.is_halt);
183        builder.when_not(local.is_real).assert_zero(local.is_commit_deferred_proofs.result);
184        builder.when(send_to_table.clone()).assert_zero(local.adapter.b()[3]);
185        builder.when(send_to_table.clone()).assert_zero(local.adapter.c()[3]);
186        builder.assert_bool(send_to_table.clone());
187        if !M::IS_TRUSTED {
188            builder.when_not(send_to_table.clone()).assert_zero(trap_code.clone());
189        }
190
191        let b_address = [local.adapter.b()[0], local.adapter.b()[1], local.adapter.b()[2]];
192        let c_address = [local.adapter.c()[0], local.adapter.c()[1], local.adapter.c()[2]];
193
194        builder.send_syscall(
195            local.state.clk_high::<AB>(),
196            local.state.clk_low::<AB>(),
197            syscall_id.clone(),
198            trap_code.clone(),
199            b_address,
200            c_address,
201            send_to_table.clone(),
202            InteractionScope::Local,
203        );
204
205        // Check if `op_b` and `op_c` are a valid SP1Field words.
206        // SAFETY: The multiplicities are zero when `is_real = 0`.
207        // `op_b` value is already known to be a valid Word, as it is read from a register.
208        SP1FieldWordRangeChecker::<AB::F>::range_check::<AB>(
209            builder,
210            local.adapter.b().map(Into::into),
211            local.op_b_range_check,
212            local.is_halt.into(),
213        );
214
215        // Check if `op_c` is a valid SP1Field word.
216        // `op_c` value is already known to be a valid Word, as it is read from a register.
217        SP1FieldWordRangeChecker::<AB::F>::range_check::<AB>(
218            builder,
219            local.adapter.c().map(Into::into),
220            local.op_c_range_check,
221            local.is_commit_deferred_proofs.result.into(),
222        );
223
224        // Compute whether this ecall is ENTER_UNCONSTRAINED.
225        let is_enter_unconstrained = {
226            IsZeroOperation::<AB::F>::eval(
227                builder,
228                IsZeroOperationInput::new(
229                    syscall_id.clone()
230                        - AB::Expr::from_canonical_u32(
231                            SyscallCode::ENTER_UNCONSTRAINED.syscall_id(),
232                        ),
233                    local.is_enter_unconstrained,
234                    local.is_real.into(),
235                ),
236            );
237            local.is_enter_unconstrained.result
238        };
239
240        // Compute whether this ecall is HINT_LEN.
241        let is_hint_len = {
242            IsZeroOperation::<AB::F>::eval(
243                builder,
244                IsZeroOperationInput::new(
245                    syscall_id.clone()
246                        - AB::Expr::from_canonical_u32(SyscallCode::HINT_LEN.syscall_id()),
247                    local.is_hint_len,
248                    local.is_real.into(),
249                ),
250            );
251            local.is_hint_len.result
252        };
253
254        // `op_a_val` is constrained.
255        // When syscall_id is ENTER_UNCONSTRAINED, the new value of op_a should be 0.
256        let zero_word = Word::<AB::F>::from(0u64);
257        builder
258            .when(local.is_real)
259            .when(is_enter_unconstrained)
260            .assert_word_eq(local.op_a_value, zero_word);
261
262        // When the syscall is not one of ENTER_UNCONSTRAINED or HINT_LEN, op_a shouldn't change.
263        builder
264            .when(local.is_real)
265            .when_not(is_enter_unconstrained + is_hint_len)
266            .assert_word_eq(local.op_a_value, *local.adapter.prev_a());
267
268        // SAFETY: This leaves the case where syscall is `HINT_LEN`.
269        // In this case, `op_a`'s value can be arbitrary, but it still must be a valid word.
270        // As this is a syscall for HINT, the value itself being arbitrary is fine, as long as it is
271        // a valid word.
272        builder.slice_range_check_u16(&local.op_a_value.0, local.is_real);
273    }
274
275    /// Constraints related to the COMMIT and COMMIT_DEFERRED_PROOFS instructions.
276    #[allow(clippy::too_many_arguments)]
277    pub(crate) fn eval_commit<AB: SP1CoreAirBuilder>(
278        &self,
279        builder: &mut AB,
280        prev_a_byte: &[AB::Expr; 8],
281        local: &SyscallInstrColumns<AB::Var, M>,
282        commit_syscall: AB::PublicVar,
283        commit_deferred_syscall: AB::PublicVar,
284        commit_digest: [[AB::PublicVar; 4]; PV_DIGEST_NUM_WORDS],
285        deferred_proofs_digest: [AB::PublicVar; POSEIDON_NUM_WORDS],
286    ) {
287        let (is_commit, is_commit_deferred_proofs) = self.get_is_commit_related_syscall(
288            builder,
289            prev_a_byte,
290            commit_syscall,
291            commit_deferred_syscall,
292            local,
293        );
294
295        // Verify the index bitmap.
296        let mut bitmap_sum = AB::Expr::zero();
297        // They should all be bools.
298        for bit in local.index_bitmap.iter() {
299            builder.when(local.is_real).assert_bool(*bit);
300            bitmap_sum = bitmap_sum.clone() + (*bit).into();
301        }
302        // When the syscall is COMMIT or COMMIT_DEFERRED_PROOFS, there should be one set bit.
303        builder
304            .when(local.is_real)
305            .when(is_commit.clone() + is_commit_deferred_proofs.clone())
306            .assert_one(bitmap_sum.clone());
307        // When it's some other syscall, there should be no set bits.
308        builder
309            .when(local.is_real)
310            .when(AB::Expr::one() - (is_commit.clone() + is_commit_deferred_proofs.clone()))
311            .assert_zero(bitmap_sum);
312
313        // Verify that word_idx corresponds to the set bit in index bitmap.
314        for (i, bit) in local.index_bitmap.iter().enumerate() {
315            builder
316                .when(local.is_real)
317                .when(*bit)
318                .assert_eq(local.adapter.b()[0], AB::Expr::from_canonical_u32(i as u32));
319        }
320        // Verify that the upper limb of the word_idx is 0.
321        // SAFETY: Since the limbs are u16s, one can sum them all and test that the sum is zero.
322        builder
323            .when(local.is_real)
324            .when(is_commit.clone() + is_commit_deferred_proofs.clone())
325            .assert_zero(local.adapter.b()[1] + local.adapter.b()[2] + local.adapter.b()[3]);
326
327        // Retrieve the expected public values digest word to check against the one passed into the
328        // commit ecall. Note that for the interaction builder, it will not have any digest words,
329        // since it's used during AIR compilation time to parse for all send/receives. Since
330        // that interaction builder will ignore the other constraints of the air, it is safe
331        // to not include the verification check of the expected public values digest word.
332
333        // First, get the expected public value digest from the bitmap and public values.
334        let mut expected_pv_digest =
335            [AB::Expr::zero(), AB::Expr::zero(), AB::Expr::zero(), AB::Expr::zero()];
336        for i in 0..4 {
337            expected_pv_digest[i] = builder.index_array(
338                commit_digest.iter().map(|word| word[i]).collect_vec().as_slice(),
339                &local.index_bitmap,
340            );
341        }
342        // Then, combine the bytes into u16 Word form, which will be compared to `op_c` value.
343        let expected_pv_digest_word = Word([
344            expected_pv_digest[0].clone()
345                + expected_pv_digest[1].clone() * AB::F::from_canonical_u32(1 << 8),
346            expected_pv_digest[2].clone()
347                + expected_pv_digest[3].clone() * AB::F::from_canonical_u32(1 << 8),
348            AB::Expr::zero(),
349            AB::Expr::zero(),
350        ]);
351
352        // Assert that the expected public value digest are valid bytes.
353        builder.assert_bool(is_commit.clone());
354        for i in 0..4 {
355            builder
356                .when(is_commit.clone())
357                .assert_eq(expected_pv_digest[i].clone(), local.expected_public_values_digest[i]);
358        }
359        builder.slice_range_check_u8(&local.expected_public_values_digest, is_commit.clone());
360
361        let digest_word: Word<AB::Expr> = local.adapter.c().map(Into::into);
362
363        // Verify the public_values_digest_word.
364        builder
365            .when(local.is_real)
366            .when(is_commit.clone())
367            .assert_word_eq(expected_pv_digest_word, digest_word.clone());
368
369        let expected_deferred_proofs_digest_element =
370            builder.index_array(&deferred_proofs_digest, &local.index_bitmap);
371
372        builder
373            .when(local.is_real)
374            .when(is_commit_deferred_proofs.clone())
375            .assert_eq(expected_deferred_proofs_digest_element, digest_word.reduce::<AB>());
376    }
377
378    /// Constraints on `SIG_RETURN` syscall.
379    pub(crate) fn eval_sig_return<AB: SP1CoreAirBuilder>(
380        &self,
381        builder: &mut AB,
382        prev_a_byte: &[AB::Expr; 8],
383        local: &SyscallInstrColumns<AB::Var, UserMode>,
384    ) -> AB::Expr {
385        let syscall_id = prev_a_byte[0].clone();
386        let is_sig_return = {
387            IsZeroOperation::<AB::F>::eval(
388                builder,
389                IsZeroOperationInput::new(
390                    syscall_id.clone()
391                        - AB::Expr::from_canonical_u32(SyscallCode::SIG_RETURN.syscall_id()),
392                    local.user_mode_cols.is_sig_return,
393                    local.is_real.into(),
394                ),
395            );
396            local.user_mode_cols.is_sig_return.result
397        };
398
399        builder.assert_bool(is_sig_return);
400        builder.when_not(local.is_real).assert_zero(is_sig_return);
401
402        builder.eval_memory_access_read(
403            local.state.clk_high::<AB>(),
404            local.state.clk_low::<AB>(),
405            &[
406                local.adapter.b()[0].into(),
407                local.adapter.b()[1].into(),
408                local.adapter.b()[2].into(),
409            ],
410            local.user_mode_cols.next_pc_record,
411            is_sig_return,
412        );
413
414        let next_pc = local.user_mode_cols.next_pc_record.prev_value;
415        builder.when(is_sig_return).assert_eq(local.next_pc[0], next_pc[0]);
416        builder.when(is_sig_return).assert_eq(local.next_pc[1], next_pc[1]);
417        builder.when(is_sig_return).assert_eq(local.next_pc[2], next_pc[2]);
418        builder.when(is_sig_return).assert_zero(next_pc[3]);
419
420        is_sig_return.into()
421    }
422
423    /// Constraints on trapping in a syscall.
424    pub(crate) fn eval_trap<AB: SP1CoreAirBuilder>(
425        &self,
426        builder: &mut AB,
427        local: &SyscallInstrColumns<AB::Var, UserMode>,
428    ) -> (AB::Expr, AB::Expr) {
429        let is_not_trap = {
430            IsZeroOperation::<AB::F>::eval(
431                builder,
432                IsZeroOperationInput::new(
433                    local.user_mode_cols.trap_code.into(),
434                    local.user_mode_cols.is_not_trap,
435                    local.is_real.into(),
436                ),
437            );
438            local.user_mode_cols.is_not_trap.result
439        };
440
441        builder.assert_bool(is_not_trap);
442        builder.when_not(local.is_real).assert_zero(is_not_trap);
443
444        let is_trap = local.is_real.into() - is_not_trap.into();
445        builder.assert_bool(is_trap.clone());
446
447        let next_pc = TrapOperation::<AB::F>::eval(
448            builder,
449            local.user_mode_cols.trap_operation,
450            local.state.clk_high::<AB>(),
451            local.state.clk_low::<AB>(),
452            local.user_mode_cols.trap_code.into(),
453            local.state.pc.map(Into::into),
454            local.user_mode_cols.addresses,
455            is_trap.clone(),
456        );
457
458        builder.when(is_trap.clone()).assert_eq(local.next_pc[0], next_pc[0]);
459        builder.when(is_trap.clone()).assert_eq(local.next_pc[1], next_pc[1]);
460        builder.when(is_trap.clone()).assert_eq(local.next_pc[2], next_pc[2]);
461
462        (is_trap, local.user_mode_cols.trap_code.into())
463    }
464
465    /// Constraint related to the halt and unimpl instruction.
466    pub(crate) fn eval_halt_unimpl<AB: SP1AirBuilder>(
467        &self,
468        builder: &mut AB,
469        local: &SyscallInstrColumns<AB::Var, M>,
470        public_values: &PublicValues<
471            [AB::PublicVar; 4],
472            [AB::PublicVar; 3],
473            [AB::PublicVar; 4],
474            AB::PublicVar,
475        >,
476    ) {
477        // `next_pc` is constrained for the case where `is_halt` is true to be `HALT_PC`.
478        builder
479            .when(local.is_halt)
480            .assert_eq(local.next_pc[0], AB::Expr::from_canonical_u64(HALT_PC));
481        builder.when(local.is_halt).assert_zero(local.next_pc[1]);
482        builder.when(local.is_halt).assert_zero(local.next_pc[2]);
483
484        // Check that the `op_b` value reduced is the `public_values.exit_code`.
485        builder
486            .when(local.is_halt)
487            .assert_eq(local.adapter.b().map(Into::into).reduce::<AB>(), public_values.exit_code);
488    }
489
490    pub(crate) fn eval_page_protect<AB: SP1CoreAirBuilder>(
491        &self,
492        builder: &mut AB,
493        local: &SyscallInstrColumns<AB::Var, UserMode>,
494        prev_a_byte: &[AB::Expr; 8],
495        is_page_protect_active: AB::PublicVar,
496    ) {
497        let syscall_id = prev_a_byte[0].clone();
498
499        // Compute whether this ecall is mprotect.
500        let is_mprotect = {
501            IsZeroOperation::<AB::F>::eval(
502                builder,
503                IsZeroOperationInput::new(
504                    syscall_id.clone()
505                        - AB::Expr::from_canonical_u32(SyscallCode::MPROTECT.syscall_id()),
506                    local.user_mode_cols.is_page_protect,
507                    local.is_real.into(),
508                ),
509            );
510            local.user_mode_cols.is_page_protect.result
511        };
512
513        builder.when(is_mprotect).assert_one(is_page_protect_active);
514    }
515
516    /// Returns a boolean expression indicating whether the instruction is a HALT instruction.
517    pub(crate) fn eval_is_halt_syscall<AB: SP1CoreAirBuilder>(
518        &self,
519        builder: &mut AB,
520        prev_a_byte: &[AB::Expr; 8],
521        local: &SyscallInstrColumns<AB::Var, M>,
522    ) {
523        // `is_halt` is checked to be correct in `eval_is_halt_syscall`.
524        let syscall_id = prev_a_byte[0].clone();
525
526        // Compute whether this ecall is HALT.
527        let is_halt = {
528            IsZeroOperation::<AB::F>::eval(
529                builder,
530                IsZeroOperationInput::new(
531                    syscall_id.clone()
532                        - AB::Expr::from_canonical_u32(SyscallCode::HALT.syscall_id()),
533                    local.is_halt_check,
534                    local.is_real.into(),
535                ),
536            );
537            local.is_halt_check.result
538        };
539
540        // Verify that the `is_halt` flag is correct.
541        // If `is_real = 0`, then `local.is_halt = 0`.
542        // If `is_real = 1`, then `is_halt_check.result` is correct, so `local.is_halt` is as well.
543        builder.assert_eq(local.is_halt, is_halt * local.is_real);
544    }
545
546    /// Returns two boolean expression indicating whether the instruction is a COMMIT or
547    /// COMMIT_DEFERRED_PROOFS instruction, and constrain public values based on it.
548    pub(crate) fn get_is_commit_related_syscall<AB: SP1CoreAirBuilder>(
549        &self,
550        builder: &mut AB,
551        prev_a_byte: &[AB::Expr; 8],
552        commit_syscall: AB::PublicVar,
553        commit_deferred_syscall: AB::PublicVar,
554        local: &SyscallInstrColumns<AB::Var, M>,
555    ) -> (AB::Expr, AB::Expr) {
556        let syscall_id = prev_a_byte[0].clone();
557
558        // Compute whether this ecall is COMMIT.
559        let is_commit = {
560            IsZeroOperation::<AB::F>::eval(
561                builder,
562                IsZeroOperationInput::new(
563                    syscall_id.clone()
564                        - AB::Expr::from_canonical_u32(SyscallCode::COMMIT.syscall_id()),
565                    local.is_commit,
566                    local.is_real.into(),
567                ),
568            );
569            local.is_commit.result
570        };
571
572        // Compute whether this ecall is COMMIT_DEFERRED_PROOFS.
573        let is_commit_deferred_proofs = {
574            IsZeroOperation::<AB::F>::eval(
575                builder,
576                IsZeroOperationInput::new(
577                    syscall_id.clone()
578                        - AB::Expr::from_canonical_u32(
579                            SyscallCode::COMMIT_DEFERRED_PROOFS.syscall_id(),
580                        ),
581                    local.is_commit_deferred_proofs,
582                    local.is_real.into(),
583                ),
584            );
585            local.is_commit_deferred_proofs.result
586        };
587
588        // If the `COMMIT` syscall is called in the shard, then `pv.commit_syscall == 1`.
589        builder.when(is_commit).assert_one(commit_syscall);
590        // If the `COMMIT_DEFERRED_PROOFS` syscall was called, `pv.commit_deferred_syscall == 1`.
591        builder.when(is_commit_deferred_proofs).assert_one(commit_deferred_syscall);
592
593        (is_commit.into(), is_commit_deferred_proofs.into())
594    }
595}