1use core::borrow::Borrow;
2use core::marker::PhantomData;
3
4use p3_air::{Air, AirBuilder, BaseAir};
5use p3_field::{PrimeCharacteristicRing, PrimeField};
6use p3_matrix::Matrix;
7use p3_matrix::dense::RowMajorMatrix;
8use p3_poseidon2::GenericPoseidon2LinearLayers;
9use rand::distr::{Distribution, StandardUniform};
10use rand::rngs::SmallRng;
11use rand::{Rng, SeedableRng};
12
13use crate::columns::{Poseidon2Cols, num_cols};
14use crate::constants::RoundConstants;
15use crate::{FullRound, PartialRound, SBox, generate_trace_rows};
16
17#[derive(Debug)]
19pub struct Poseidon2Air<
20 F: PrimeCharacteristicRing,
21 LinearLayers,
22 const WIDTH: usize,
23 const SBOX_DEGREE: u64,
24 const SBOX_REGISTERS: usize,
25 const HALF_FULL_ROUNDS: usize,
26 const PARTIAL_ROUNDS: usize,
27> {
28 pub(crate) constants: RoundConstants<F, WIDTH, HALF_FULL_ROUNDS, PARTIAL_ROUNDS>,
29 _phantom: PhantomData<LinearLayers>,
30}
31
32impl<
33 F: PrimeCharacteristicRing,
34 LinearLayers,
35 const WIDTH: usize,
36 const SBOX_DEGREE: u64,
37 const SBOX_REGISTERS: usize,
38 const HALF_FULL_ROUNDS: usize,
39 const PARTIAL_ROUNDS: usize,
40> Clone
41 for Poseidon2Air<
42 F,
43 LinearLayers,
44 WIDTH,
45 SBOX_DEGREE,
46 SBOX_REGISTERS,
47 HALF_FULL_ROUNDS,
48 PARTIAL_ROUNDS,
49 >
50{
51 fn clone(&self) -> Self {
52 Self {
53 constants: self.constants.clone(),
54 _phantom: PhantomData,
55 }
56 }
57}
58
59impl<
60 F: PrimeCharacteristicRing,
61 LinearLayers,
62 const WIDTH: usize,
63 const SBOX_DEGREE: u64,
64 const SBOX_REGISTERS: usize,
65 const HALF_FULL_ROUNDS: usize,
66 const PARTIAL_ROUNDS: usize,
67>
68 Poseidon2Air<
69 F,
70 LinearLayers,
71 WIDTH,
72 SBOX_DEGREE,
73 SBOX_REGISTERS,
74 HALF_FULL_ROUNDS,
75 PARTIAL_ROUNDS,
76 >
77{
78 pub const fn new(
79 constants: RoundConstants<F, WIDTH, HALF_FULL_ROUNDS, PARTIAL_ROUNDS>,
80 ) -> Self {
81 Self {
82 constants,
83 _phantom: PhantomData,
84 }
85 }
86
87 pub fn generate_trace_rows(
88 &self,
89 num_hashes: usize,
90 extra_capacity_bits: usize,
91 ) -> RowMajorMatrix<F>
92 where
93 F: PrimeField,
94 LinearLayers: GenericPoseidon2LinearLayers<WIDTH>,
95 StandardUniform: Distribution<[F; WIDTH]>,
96 {
97 let mut rng = SmallRng::seed_from_u64(1);
98 let inputs = (0..num_hashes).map(|_| rng.random()).collect();
99 generate_trace_rows::<
100 _,
101 LinearLayers,
102 WIDTH,
103 SBOX_DEGREE,
104 SBOX_REGISTERS,
105 HALF_FULL_ROUNDS,
106 PARTIAL_ROUNDS,
107 >(inputs, &self.constants, extra_capacity_bits)
108 }
109}
110
111impl<
112 F: PrimeCharacteristicRing + Sync,
113 LinearLayers: Sync,
114 const WIDTH: usize,
115 const SBOX_DEGREE: u64,
116 const SBOX_REGISTERS: usize,
117 const HALF_FULL_ROUNDS: usize,
118 const PARTIAL_ROUNDS: usize,
119> BaseAir<F>
120 for Poseidon2Air<
121 F,
122 LinearLayers,
123 WIDTH,
124 SBOX_DEGREE,
125 SBOX_REGISTERS,
126 HALF_FULL_ROUNDS,
127 PARTIAL_ROUNDS,
128 >
129{
130 fn width(&self) -> usize {
131 num_cols::<WIDTH, SBOX_DEGREE, SBOX_REGISTERS, HALF_FULL_ROUNDS, PARTIAL_ROUNDS>()
132 }
133}
134
135pub(crate) fn eval<
136 AB: AirBuilder,
137 LinearLayers: GenericPoseidon2LinearLayers<WIDTH>,
138 const WIDTH: usize,
139 const SBOX_DEGREE: u64,
140 const SBOX_REGISTERS: usize,
141 const HALF_FULL_ROUNDS: usize,
142 const PARTIAL_ROUNDS: usize,
143>(
144 air: &Poseidon2Air<
145 AB::F,
146 LinearLayers,
147 WIDTH,
148 SBOX_DEGREE,
149 SBOX_REGISTERS,
150 HALF_FULL_ROUNDS,
151 PARTIAL_ROUNDS,
152 >,
153 builder: &mut AB,
154 local: &Poseidon2Cols<
155 AB::Var,
156 WIDTH,
157 SBOX_DEGREE,
158 SBOX_REGISTERS,
159 HALF_FULL_ROUNDS,
160 PARTIAL_ROUNDS,
161 >,
162) {
163 let mut state: [_; WIDTH] = local.inputs.clone().map(|x| x.into());
164
165 LinearLayers::external_linear_layer(&mut state);
166
167 for round in 0..HALF_FULL_ROUNDS {
168 eval_full_round::<_, LinearLayers, WIDTH, SBOX_DEGREE, SBOX_REGISTERS>(
169 &mut state,
170 &local.beginning_full_rounds[round],
171 &air.constants.beginning_full_round_constants[round],
172 builder,
173 );
174 }
175
176 for round in 0..PARTIAL_ROUNDS {
177 eval_partial_round::<_, LinearLayers, WIDTH, SBOX_DEGREE, SBOX_REGISTERS>(
178 &mut state,
179 &local.partial_rounds[round],
180 &air.constants.partial_round_constants[round],
181 builder,
182 );
183 }
184
185 for round in 0..HALF_FULL_ROUNDS {
186 eval_full_round::<_, LinearLayers, WIDTH, SBOX_DEGREE, SBOX_REGISTERS>(
187 &mut state,
188 &local.ending_full_rounds[round],
189 &air.constants.ending_full_round_constants[round],
190 builder,
191 );
192 }
193}
194
195impl<
196 AB: AirBuilder,
197 LinearLayers: GenericPoseidon2LinearLayers<WIDTH>,
198 const WIDTH: usize,
199 const SBOX_DEGREE: u64,
200 const SBOX_REGISTERS: usize,
201 const HALF_FULL_ROUNDS: usize,
202 const PARTIAL_ROUNDS: usize,
203> Air<AB>
204 for Poseidon2Air<
205 AB::F,
206 LinearLayers,
207 WIDTH,
208 SBOX_DEGREE,
209 SBOX_REGISTERS,
210 HALF_FULL_ROUNDS,
211 PARTIAL_ROUNDS,
212 >
213{
214 #[inline]
215 fn eval(&self, builder: &mut AB) {
216 let main = builder.main();
217 let local = main.row_slice(0).expect("The matrix is empty?");
218 let local = (*local).borrow();
219
220 eval::<_, _, WIDTH, SBOX_DEGREE, SBOX_REGISTERS, HALF_FULL_ROUNDS, PARTIAL_ROUNDS>(
221 self, builder, local,
222 );
223 }
224}
225
226#[inline]
227fn eval_full_round<
228 AB: AirBuilder,
229 LinearLayers: GenericPoseidon2LinearLayers<WIDTH>,
230 const WIDTH: usize,
231 const SBOX_DEGREE: u64,
232 const SBOX_REGISTERS: usize,
233>(
234 state: &mut [AB::Expr; WIDTH],
235 full_round: &FullRound<AB::Var, WIDTH, SBOX_DEGREE, SBOX_REGISTERS>,
236 round_constants: &[AB::F; WIDTH],
237 builder: &mut AB,
238) {
239 for (i, (s, r)) in state.iter_mut().zip(round_constants.iter()).enumerate() {
240 *s += r.clone();
241 eval_sbox(&full_round.sbox[i], s, builder);
242 }
243 LinearLayers::external_linear_layer(state);
244 for (state_i, post_i) in state.iter_mut().zip(&full_round.post) {
245 builder.assert_eq(state_i.clone(), post_i.clone());
246 *state_i = post_i.clone().into();
247 }
248}
249
250#[inline]
251fn eval_partial_round<
252 AB: AirBuilder,
253 LinearLayers: GenericPoseidon2LinearLayers<WIDTH>,
254 const WIDTH: usize,
255 const SBOX_DEGREE: u64,
256 const SBOX_REGISTERS: usize,
257>(
258 state: &mut [AB::Expr; WIDTH],
259 partial_round: &PartialRound<AB::Var, WIDTH, SBOX_DEGREE, SBOX_REGISTERS>,
260 round_constant: &AB::F,
261 builder: &mut AB,
262) {
263 state[0] += round_constant.clone();
264 eval_sbox(&partial_round.sbox, &mut state[0], builder);
265
266 builder.assert_eq(state[0].clone(), partial_round.post_sbox.clone());
267 state[0] = partial_round.post_sbox.clone().into();
268
269 LinearLayers::internal_linear_layer(state);
270}
271
272#[inline]
280fn eval_sbox<AB, const DEGREE: u64, const REGISTERS: usize>(
281 sbox: &SBox<AB::Var, DEGREE, REGISTERS>,
282 x: &mut AB::Expr,
283 builder: &mut AB,
284) where
285 AB: AirBuilder,
286{
287 *x = match (DEGREE, REGISTERS) {
288 (3, 0) => x.cube(),
289 (5, 0) => x.exp_const_u64::<5>(),
290 (7, 0) => x.exp_const_u64::<7>(),
291 (5, 1) => {
292 let committed_x3 = sbox.0[0].clone().into();
293 let x2 = x.square();
294 builder.assert_eq(committed_x3.clone(), x2.clone() * x.clone());
295 committed_x3 * x2
296 }
297 (7, 1) => {
298 let committed_x3 = sbox.0[0].clone().into();
299 builder.assert_eq(committed_x3.clone(), x.cube());
300 committed_x3.square() * x.clone()
301 }
302 (11, 2) => {
303 let committed_x3 = sbox.0[0].clone().into();
304 let committed_x9 = sbox.0[1].clone().into();
305 let x2 = x.square();
306 builder.assert_eq(committed_x3.clone(), x2.clone() * x.clone());
307 builder.assert_eq(committed_x9.clone(), committed_x3.cube());
308 committed_x9 * x2
309 }
310 _ => panic!(
311 "Unexpected (DEGREE, REGISTERS) of ({}, {})",
312 DEGREE, REGISTERS
313 ),
314 }
315}