openvm_recursion_circuit/transcript/merkle_verify/
air.rs1use core::{array, borrow::Borrow};
2
3use openvm_circuit_primitives::{ColumnsAir, StructReflection, StructReflectionHelper};
4pub use openvm_poseidon2_air::POSEIDON2_WIDTH;
5use openvm_recursion_circuit_derive::AlignedBorrow;
6use openvm_stark_backend::{
7 interaction::InteractionBuilder, BaseAirWithPublicValues, PartitionedBaseAir,
8};
9use openvm_stark_sdk::config::baby_bear_poseidon2::{CHUNK, DIGEST_SIZE};
10use p3_air::{Air, AirBuilder, BaseAir};
11use p3_field::{Field, PrimeCharacteristicRing};
12use p3_matrix::Matrix;
13
14use crate::{
15 bus::{
16 CommitmentsBus, CommitmentsBusMessage, MerkleVerifyBus, MerkleVerifyBusMessage,
17 Poseidon2CompressBus, Poseidon2CompressMessage,
18 },
19 primitives::bus::{RightShiftBus, RightShiftMessage},
20};
21
22#[repr(C)]
42#[derive(AlignedBorrow, StructReflection)]
43pub struct MerkleVerifyCols<T> {
44 pub proof_idx: T,
46 pub is_valid: T,
48 pub is_last_merkle: T,
50
51 pub is_combining_leaves: T,
53 pub is_last_leaf: T,
55 pub leaf_sub_idx: T,
57
58 pub merkle_idx_bit_src: T,
60 pub current_idx_bit_src: T,
62 pub total_depth: T,
64 pub height: T,
66
67 pub left: [T; DIGEST_SIZE],
68 pub right: [T; DIGEST_SIZE],
69
70 pub recv_flag: T,
72
73 pub commit_major: T,
74 pub commit_minor: T,
75
76 pub compression_output: [T; DIGEST_SIZE],
77}
78
79#[derive(Clone, Debug)]
80pub(super) struct MerkleVerifyLog {
81 pub merkle_idx: usize,
82 pub depth: usize,
83 pub query_idx: usize,
84 pub commit_major: usize,
86 pub commit_minor: usize,
88}
89
90#[derive(ColumnsAir)]
91#[columns_via(MerkleVerifyCols<u8>)]
92pub struct MerkleVerifyAir {
93 pub poseidon2_compress_bus: Poseidon2CompressBus,
94 pub merkle_verify_bus: MerkleVerifyBus,
95 pub commitments_bus: CommitmentsBus,
96 pub right_shift_bus: RightShiftBus,
97 pub k: usize,
98}
99
100impl<F: Field> BaseAir<F> for MerkleVerifyAir {
101 fn width(&self) -> usize {
102 MerkleVerifyCols::<F>::width()
103 }
104}
105
106impl<F: Field> BaseAirWithPublicValues<F> for MerkleVerifyAir {}
107impl<F: Field> PartitionedBaseAir<F> for MerkleVerifyAir {}
108
109impl<AB: AirBuilder + InteractionBuilder> Air<AB> for MerkleVerifyAir {
110 fn eval(&self, builder: &mut AB) {
111 let main = builder.main();
112 let local = main.row_slice(0).expect("window should have two elements");
113 let local: &MerkleVerifyCols<AB::Var> = (*local).borrow();
114
115 builder.assert_bool(local.is_valid);
119 builder.assert_bool(local.is_last_merkle);
120 builder.assert_bool(local.is_combining_leaves);
121 builder.assert_bool(local.is_last_leaf);
122 builder.assert_tern(local.recv_flag);
123
124 for i in 0..DIGEST_SIZE {
126 builder
127 .when(local.is_last_merkle)
128 .assert_eq(local.left[i], local.right[i]);
129 }
130
131 builder
133 .when(local.is_last_merkle)
134 .assert_eq(local.height + AB::Expr::ONE, local.total_depth);
135
136 builder
138 .when(AB::Expr::ONE - local.is_combining_leaves)
139 .assert_zero(local.leaf_sub_idx);
140
141 builder
143 .when(local.is_last_leaf)
144 .assert_one(local.is_combining_leaves);
145 builder
146 .when(local.is_last_leaf)
147 .assert_zero(local.leaf_sub_idx);
148 builder
149 .when(local.is_last_leaf)
150 .assert_eq(local.height + AB::Expr::ONE, AB::Expr::from_usize(self.k));
151
152 builder
154 .when(local.is_combining_leaves)
155 .assert_one(local.is_valid);
156 builder
157 .when(local.is_combining_leaves)
158 .assert_eq(local.recv_flag, AB::Expr::TWO);
159 builder
160 .when_ne(local.is_combining_leaves, AB::Expr::ONE)
161 .assert_bool(local.recv_flag);
162
163 builder
165 .when(local.is_combining_leaves)
166 .assert_eq(local.merkle_idx_bit_src, local.current_idx_bit_src);
167
168 let left_leaf_sub_idx = local.is_combining_leaves * local.leaf_sub_idx * AB::Expr::TWO;
174 let right_leaf_sub_idx =
175 local.is_combining_leaves * (local.leaf_sub_idx * AB::Expr::TWO + AB::Expr::ONE);
176
177 let recv_left = (local.recv_flag - AB::Expr::ONE).square();
178 let recv_right =
179 local.recv_flag * (AB::Expr::from_u8(3) - local.recv_flag) * AB::F::TWO.inverse();
180
181 let left_child_current_idx =
182 local.current_idx_bit_src * (AB::Expr::TWO - local.is_combining_leaves);
183 let right_child_current_idx =
184 left_child_current_idx.clone() + AB::Expr::ONE - local.is_combining_leaves;
185
186 self.merkle_verify_bus.receive(
187 builder,
188 local.proof_idx,
189 MerkleVerifyBusMessage {
190 value: local.left.map(Into::into),
191 merkle_idx_bit_src: local.merkle_idx_bit_src.into(),
192 current_idx_bit_src: left_child_current_idx,
193 total_depth: local.total_depth.into(),
194 height: local.height.into(),
195 is_leaf: local.is_combining_leaves.into(),
196 leaf_sub_idx: left_leaf_sub_idx,
197 commit_major: local.commit_major.into(),
198 commit_minor: local.commit_minor.into(),
199 },
200 local.is_valid * recv_left,
201 );
202 self.merkle_verify_bus.receive(
203 builder,
204 local.proof_idx,
205 MerkleVerifyBusMessage {
206 value: local.right.map(Into::into),
207 merkle_idx_bit_src: local.merkle_idx_bit_src.into(),
208 current_idx_bit_src: right_child_current_idx,
209 total_depth: local.total_depth.into(),
210 height: local.height.into(),
211 is_leaf: local.is_combining_leaves.into(),
212 leaf_sub_idx: right_leaf_sub_idx,
213 commit_major: local.commit_major.into(),
214 commit_minor: local.commit_minor.into(),
215 },
216 local.is_valid * recv_right,
217 );
218
219 self.merkle_verify_bus.send(
220 builder,
221 local.proof_idx,
222 MerkleVerifyBusMessage {
223 value: local.compression_output.map(Into::into),
224 merkle_idx_bit_src: local.merkle_idx_bit_src.into(),
225 current_idx_bit_src: local.current_idx_bit_src.into(),
226 total_depth: local.total_depth.into(),
227 height: local.height + AB::Expr::ONE,
228 is_leaf: local.is_combining_leaves - local.is_last_leaf,
229 leaf_sub_idx: local.leaf_sub_idx.into(),
230 commit_major: local.commit_major.into(),
231 commit_minor: local.commit_minor.into(),
232 },
233 (AB::Expr::ONE - local.is_last_merkle) * local.is_valid,
234 );
235
236 self.commitments_bus.lookup_key(
237 builder,
238 local.proof_idx,
239 CommitmentsBusMessage {
240 major_idx: local.commit_major,
241 minor_idx: local.commit_minor,
242 commitment: local.left, },
244 local.is_valid * local.is_last_merkle,
245 );
246
247 self.right_shift_bus.lookup_key(
248 builder,
249 RightShiftMessage {
250 input: local.merkle_idx_bit_src.into(),
251 shift_bits: local.total_depth - AB::Expr::from_usize(self.k),
252 result: local.current_idx_bit_src.into(),
253 },
254 local.is_valid * local.is_last_merkle,
255 );
256
257 let poseidon2_input = array::from_fn(|i| {
258 if i < CHUNK {
259 local.left[i].into()
260 } else {
261 local.right[i - CHUNK].into()
262 }
263 });
264 self.poseidon2_compress_bus.lookup_key(
265 builder,
266 Poseidon2CompressMessage {
267 input: poseidon2_input,
268 output: local.compression_output.map(Into::into),
269 },
270 local.is_valid,
271 );
272 }
273}
274
275pub(super) fn compute_cum_sum<T>(values: &[T], f: impl Fn(&T) -> usize) -> Vec<usize> {
276 values
277 .iter()
278 .scan(0usize, |acc, x| {
279 *acc += f(x);
280 Some(*acc)
281 })
282 .collect()
283}