@@ -9,11 +9,19 @@ use p3_poseidon2_air::{Poseidon2Air, RoundConstants};
99
1010use crate :: { Poseidon2CircuitCols , num_cols} ;
1111
12+ /// Extends the Poseidon2 AIR with recursion circuit-specific columns and constraints.
1213/// Assumes the field size is at least 16 bits.
14+ ///
15+ /// SPECIFIC ASSUMPTIONS:
16+ /// - Memory elements from the witness table are extension elements of degree D.
17+ /// - RATE and CAPACITY are the number of extension elements in the rate/capacity.
18+ /// - WIDTH is the number of field elements in the state, i.e., (RATE + CAPACITY) * D.
19+ /// - `reset` can only be set during an absorb.
1320#[ derive( Debug ) ]
1421pub struct Poseidon2CircuitAir <
1522 F : PrimeCharacteristicRing ,
1623 LinearLayers ,
24+ const D : usize ,
1725 const RATE : usize ,
1826 const CAPACITY : usize ,
1927 const WIDTH : usize ,
@@ -36,6 +44,7 @@ pub struct Poseidon2CircuitAir<
3644impl <
3745 F : PrimeCharacteristicRing ,
3846 LinearLayers ,
47+ const D : usize ,
3948 const RATE : usize ,
4049 const CAPACITY : usize ,
4150 const WIDTH : usize ,
4756 Poseidon2CircuitAir <
4857 F ,
4958 LinearLayers ,
59+ D ,
5060 RATE ,
5161 CAPACITY ,
5262 WIDTH ,
5969 pub const fn new (
6070 constants : RoundConstants < F , WIDTH , HALF_FULL_ROUNDS , PARTIAL_ROUNDS > ,
6171 ) -> Self {
72+ assert ! ( ( CAPACITY + RATE ) * D == WIDTH ) ;
73+ assert ! ( RATE . is_multiple_of( D ) ) ;
74+ assert ! ( CAPACITY . is_multiple_of( D ) ) ;
75+ assert ! ( WIDTH . is_multiple_of( D ) ) ;
76+
6277 Self {
6378 p3_poseidon2 : Poseidon2Air :: new ( constants) ,
6479 }
6883impl <
6984 F : PrimeCharacteristicRing + Sync ,
7085 LinearLayers : Sync ,
86+ const D : usize ,
7187 const RATE : usize ,
7288 const CAPACITY : usize ,
7389 const WIDTH : usize ,
7995 for Poseidon2CircuitAir <
8096 F ,
8197 LinearLayers ,
98+ D ,
8299 RATE ,
83100 CAPACITY ,
84101 WIDTH ,
@@ -89,13 +106,14 @@ impl<
89106 >
90107{
91108 fn width ( & self ) -> usize {
92- num_cols :: < WIDTH , SBOX_DEGREE , SBOX_REGISTERS , HALF_FULL_ROUNDS , PARTIAL_ROUNDS > ( )
109+ num_cols :: < WIDTH , RATE , SBOX_DEGREE , SBOX_REGISTERS , HALF_FULL_ROUNDS , PARTIAL_ROUNDS > ( )
93110 }
94111}
95112
96113pub ( crate ) fn eval <
97114 AB : AirBuilder ,
98115 LinearLayers : GenericPoseidon2LinearLayers < WIDTH > ,
116+ const D : usize ,
99117 const RATE : usize ,
100118 const CAPACITY : usize ,
101119 const WIDTH : usize ,
@@ -107,6 +125,7 @@ pub(crate) fn eval<
107125 air : & Poseidon2CircuitAir <
108126 AB :: F ,
109127 LinearLayers ,
128+ D ,
110129 RATE ,
111130 CAPACITY ,
112131 WIDTH ,
@@ -119,6 +138,7 @@ pub(crate) fn eval<
119138 local : & Poseidon2CircuitCols <
120139 AB :: Var ,
121140 WIDTH ,
141+ RATE ,
122142 SBOX_DEGREE ,
123143 SBOX_REGISTERS ,
124144 HALF_FULL_ROUNDS ,
@@ -127,6 +147,7 @@ pub(crate) fn eval<
127147 next : & Poseidon2CircuitCols <
128148 AB :: Var ,
129149 WIDTH ,
150+ RATE ,
130151 SBOX_DEGREE ,
131152 SBOX_REGISTERS ,
132153 HALF_FULL_ROUNDS ,
@@ -135,39 +156,71 @@ pub(crate) fn eval<
135156) {
136157 air. p3_poseidon2 . eval ( builder) ;
137158
138- // When resetting the state, we just have to clear the capacity. The rate will be overwritten by the input.
139- builder
140- . when ( local. reset . clone ( ) )
141- . assert_zeros :: < CAPACITY , _ > ( array:: from_fn ( |i| local. poseidon2 . inputs [ i + RATE ] . clone ( ) ) ) ;
142-
143- // If the next row doesn't reset, propagate the capacity.
144159 let next_no_reset = AB :: Expr :: ONE - next. reset . clone ( ) ;
145- builder
146- . when ( next_no_reset)
147- . assert_zeros :: < CAPACITY , _ > ( array:: from_fn ( |i| {
148- next. poseidon2 . inputs [ i + RATE ] . clone ( )
149- - local. poseidon2 . ending_full_rounds [ HALF_FULL_ROUNDS - 1 ] . post [ i + RATE ] . clone ( )
150- } ) ) ;
160+ for i in 0 ..( CAPACITY * D ) {
161+ // The first row has capacity zeroed.
162+ builder
163+ . when_first_row ( )
164+ . assert_zero ( local. poseidon2 . inputs [ RATE * D + i] . clone ( ) ) ;
165+
166+ // When resetting the state, we just have to clear the capacity. The rate will be overwritten by the input.
167+ builder
168+ . when ( local. reset . clone ( ) )
169+ . assert_zero ( local. poseidon2 . inputs [ RATE * D + i] . clone ( ) ) ;
170+
171+ // If the next row doesn't reset, propagate the capacity.
172+ builder. when ( next_no_reset. clone ( ) ) . assert_zero (
173+ next. poseidon2 . inputs [ RATE * D + i] . clone ( )
174+ - local. poseidon2 . ending_full_rounds [ HALF_FULL_ROUNDS - 1 ] . post [ RATE * D + i]
175+ . clone ( ) ,
176+ ) ;
177+ }
151178
152- // If the next row is squeezing, then we propagate the entire output state forward.
153- let next_squeeze = AB :: Expr :: ONE - next. absorb . clone ( ) ;
154- builder
155- . when ( next_squeeze)
156- . assert_zeros :: < WIDTH , _ > ( array:: from_fn ( |i| {
157- next. poseidon2 . inputs [ i] . clone ( )
158- - local. poseidon2 . ending_full_rounds [ HALF_FULL_ROUNDS - 1 ] . post [ i] . clone ( )
159- } ) ) ;
179+ let mut next_absorb = [ AB :: Expr :: ZERO ; RATE ] ;
180+ for i in 0 ..RATE {
181+ for col in next_absorb. iter_mut ( ) . take ( RATE ) . skip ( i) {
182+ * col += next. absorb_flags [ i] . clone ( ) ;
183+ }
184+ }
185+ let next_no_absorb = array:: from_fn :: < _ , RATE , _ > ( |i| AB :: Expr :: ONE - next_absorb[ i] . clone ( ) ) ;
186+ // In the next row, each rate element not being absorbed comes from the current row.
187+ for index in 0 ..( RATE * D ) {
188+ let i = index / D ;
189+ let j = index % D ;
190+ builder. when ( next_no_absorb[ i] . clone ( ) ) . assert_zero (
191+ next. poseidon2 . inputs [ i * D + j] . clone ( )
192+ - local. poseidon2 . ending_full_rounds [ HALF_FULL_ROUNDS - 1 ] . post [ i * D + j] . clone ( ) ,
193+ ) ;
194+ }
195+
196+ let mut current_absorb = [ AB :: Expr :: ZERO ; RATE ] ;
197+ for i in 0 ..RATE {
198+ for col in current_absorb. iter_mut ( ) . take ( RATE ) . skip ( i) {
199+ * col += local. absorb_flags [ i] . clone ( ) ;
200+ }
201+ }
202+ let current_no_absorb =
203+ array:: from_fn :: < _ , RATE , _ > ( |i| AB :: Expr :: ONE - current_absorb[ i] . clone ( ) ) ;
204+ // During a reset, the rate elements not being absorbed are zeroed.
205+ for ( i, col) in current_no_absorb. iter ( ) . enumerate ( ) . take ( RATE ) {
206+ let arr = array:: from_fn :: < _ , D , _ > ( |j| local. poseidon2 . inputs [ i * D + j] . clone ( ) . into ( ) ) ;
207+ builder
208+ . when ( local. reset . clone ( ) * col. clone ( ) )
209+ . assert_zeros ( arr) ;
210+ }
160211
212+ let _is_squeeze = AB :: Expr :: ONE - current_absorb[ 0 ] . clone ( ) ;
161213 // TODO: Add all lookups:
162- // - If local.absorb = 1:
163- // * local.rate comes from input lookups.
164- // - If local.absorb = 0 :
214+ // - If current_absorb[i] = 1:
215+ // * local.rate[i] comes from input lookups.
216+ // - If is_squeeze = 1 :
165217 // * local.rate is sent to output lookups.
166218}
167219
168220impl <
169221 AB : AirBuilder ,
170222 LinearLayers : GenericPoseidon2LinearLayers < WIDTH > ,
223+ const D : usize ,
171224 const RATE : usize ,
172225 const CAPACITY : usize ,
173226 const WIDTH : usize ,
@@ -179,6 +232,7 @@ impl<
179232 for Poseidon2CircuitAir <
180233 AB :: F ,
181234 LinearLayers ,
235+ D ,
182236 RATE ,
183237 CAPACITY ,
184238 WIDTH ,
@@ -199,6 +253,7 @@ impl<
199253 eval :: <
200254 _ ,
201255 _ ,
256+ D ,
202257 RATE ,
203258 CAPACITY ,
204259 WIDTH ,
0 commit comments