22use crate :: {
33 frontend:: {
44 gadgets:: poseidon:: {
5- Elt , IOPattern , PoseidonConstants , Simplex , Sponge , SpongeAPI , SpongeCircuit , SpongeOp ,
6- SpongeTrait , Strength ,
5+ Arity , Elt , IOPattern , PoseidonConstants , Simplex , Sponge , SpongeAPI , SpongeCircuit ,
6+ SpongeOp , SpongeTrait , Strength ,
77 } ,
88 num:: AllocatedNum ,
99 AllocatedBit , Boolean , ConstraintSystem , SynthesisError ,
1010 } ,
11- traits:: { ROCircuitTrait , ROTrait } ,
11+ traits:: { ROCircuitTrait , ROMode , ROTrait } ,
1212} ;
1313use ff:: { PrimeField , PrimeFieldBits } ;
14- use generic_array:: typenum:: U24 ;
14+ use generic_array:: typenum:: { U24 , U5 } ;
1515use serde:: { Deserialize , Serialize } ;
1616
17- /// All Poseidon Constants that are used in Nova
17+ /// All Poseidon Constants that are used in Nova.
18+ ///
19+ /// Holds constants for both the wide (U24) and narrow (U5) sponge widths so
20+ /// that the same constants object can be used with either [`ROMode`].
1821#[ derive( Clone , PartialEq , Serialize , Deserialize ) ]
19- pub struct PoseidonConstantsCircuit < Scalar : PrimeField > ( PoseidonConstants < Scalar , U24 > ) ;
22+ pub struct PoseidonConstantsCircuit < Scalar : PrimeField > {
23+ wide : PoseidonConstants < Scalar , U24 > ,
24+ narrow : PoseidonConstants < Scalar , U5 > ,
25+ }
2026
2127impl < Scalar : PrimeField > Default for PoseidonConstantsCircuit < Scalar > {
22- /// Generate Poseidon constants
28+ /// Generate Poseidon constants for both wide and narrow arities.
2329 fn default ( ) -> Self {
24- Self ( Sponge :: < Scalar , U24 > :: api_constants ( Strength :: Standard ) )
30+ Self {
31+ wide : Sponge :: < Scalar , U24 > :: api_constants ( Strength :: Standard ) ,
32+ narrow : Sponge :: < Scalar , U5 > :: api_constants ( Strength :: Standard ) ,
33+ }
2534 }
2635}
2736
28- /// A Poseidon-based RO to use outside circuits
37+ /// A Poseidon-based RO to use outside circuits.
2938#[ derive( Serialize , Deserialize ) ]
3039pub struct PoseidonRO < Base : PrimeField > {
3140 // internal State
3241 state : Vec < Base > ,
3342 constants : PoseidonConstantsCircuit < Base > ,
43+ mode : ROMode ,
44+ }
45+
46+ /// Run through a Poseidon sponge with the given constants and return the hash.
47+ fn poseidon_squeeze_native < Base : PrimeField , A : Arity < Base > > (
48+ constants : & PoseidonConstants < Base , A > ,
49+ state : & [ Base ] ,
50+ ) -> Base {
51+ let mut sponge = Sponge :: new_with_constants ( constants, Simplex ) ;
52+ let acc = & mut ( ) ;
53+ let parameter = IOPattern ( vec ! [
54+ SpongeOp :: Absorb ( state. len( ) as u32 ) ,
55+ SpongeOp :: Squeeze ( 1u32 ) ,
56+ ] ) ;
57+ sponge. start ( parameter, None , acc) ;
58+ SpongeAPI :: absorb ( & mut sponge, state. len ( ) as u32 , state, acc) ;
59+ let hash = SpongeAPI :: squeeze ( & mut sponge, 1 , acc) ;
60+ sponge. finish ( acc) . unwrap ( ) ;
61+ hash[ 0 ]
3462}
3563
3664impl < Base > ROTrait < Base > for PoseidonRO < Base >
4472 Self {
4573 state : Vec :: new ( ) ,
4674 constants,
75+ mode : ROMode :: Wide ,
76+ }
77+ }
78+
79+ fn new_with_mode ( constants : PoseidonConstantsCircuit < Base > , mode : ROMode ) -> Self {
80+ Self {
81+ state : Vec :: new ( ) ,
82+ constants,
83+ mode,
4784 }
4885 }
4986
@@ -54,23 +91,16 @@ where
5491
5592 /// Compute a challenge by hashing the current state
5693 fn squeeze ( & mut self , num_bits : usize , start_with_one : bool ) -> Base {
57- let mut sponge = Sponge :: new_with_constants ( & self . constants . 0 , Simplex ) ;
58- let acc = & mut ( ) ;
59- let parameter = IOPattern ( vec ! [
60- SpongeOp :: Absorb ( self . state. len( ) as u32 ) ,
61- SpongeOp :: Squeeze ( 1u32 ) ,
62- ] ) ;
63-
64- sponge. start ( parameter, None , acc) ;
65- SpongeAPI :: absorb ( & mut sponge, self . state . len ( ) as u32 , & self . state , acc) ;
66- let hash = SpongeAPI :: squeeze ( & mut sponge, 1 , acc) ;
67- sponge. finish ( acc) . unwrap ( ) ;
94+ let hash = match self . mode {
95+ ROMode :: Wide => poseidon_squeeze_native ( & self . constants . wide , & self . state ) ,
96+ ROMode :: Narrow => poseidon_squeeze_native ( & self . constants . narrow , & self . state ) ,
97+ } ;
6898
6999 // reset the state to only contain the squeezed value
70- self . state = vec ! [ hash[ 0 ] ] ;
100+ self . state = vec ! [ hash] ;
71101
72102 // Only return `num_bits`
73- let bits = hash[ 0 ] . to_le_bits ( ) ;
103+ let bits = hash. to_le_bits ( ) ;
74104 let mut res = Base :: ZERO ;
75105 let mut coeff = Base :: ONE ;
76106 for bit in bits[ ..num_bits] . into_iter ( ) {
@@ -98,6 +128,41 @@ pub struct PoseidonROCircuit<Scalar: PrimeField> {
98128 // Internal state
99129 state : Vec < AllocatedNum < Scalar > > ,
100130 constants : PoseidonConstantsCircuit < Scalar > ,
131+ mode : ROMode ,
132+ compact : bool ,
133+ }
134+
135+ /// Sponge circuit squeeze: allocates a Poseidon sponge, absorbs `state`, squeezes one element.
136+ /// Used as a helper inside ROCircuitTrait methods to avoid duplicating the absorb/squeeze logic.
137+ macro_rules! poseidon_squeeze_circuit {
138+ ( $constants: expr, $state: expr, $compact: expr, $ns: expr) => { {
139+ let parameter = IOPattern ( vec![
140+ SpongeOp :: Absorb ( $state. len( ) as u32 ) ,
141+ SpongeOp :: Squeeze ( 1u32 ) ,
142+ ] ) ;
143+
144+ let hash = {
145+ let mut sponge = SpongeCircuit :: new_with_constants( $constants, Simplex ) ;
146+ sponge. set_compact( $compact) ;
147+
148+ sponge. start( parameter, None , $ns) ;
149+ SpongeAPI :: absorb(
150+ & mut sponge,
151+ $state. len( ) as u32 ,
152+ & $state
153+ . iter( )
154+ . map( |e| Elt :: Allocated ( e. clone( ) ) )
155+ . collect:: <Vec <Elt <_>>>( ) ,
156+ $ns,
157+ ) ;
158+
159+ let output = SpongeAPI :: squeeze( & mut sponge, 1 , $ns) ;
160+ sponge. finish( $ns) . unwrap( ) ;
161+ output
162+ } ;
163+
164+ Elt :: ensure_allocated( & hash[ 0 ] , & mut $ns. namespace( || "ensure allocated" ) )
165+ } } ;
101166}
102167
103168impl < Scalar > ROCircuitTrait < Scalar > for PoseidonROCircuit < Scalar >
@@ -112,6 +177,17 @@ where
112177 Self {
113178 state : Vec :: new ( ) ,
114179 constants,
180+ mode : ROMode :: Wide ,
181+ compact : false ,
182+ }
183+ }
184+
185+ fn new_with_mode ( constants : PoseidonConstantsCircuit < Scalar > , mode : ROMode ) -> Self {
186+ Self {
187+ state : Vec :: new ( ) ,
188+ constants,
189+ mode,
190+ compact : false ,
115191 }
116192 }
117193
@@ -127,33 +203,17 @@ where
127203 num_bits : usize ,
128204 start_with_one : bool ,
129205 ) -> Result < Vec < AllocatedBit > , SynthesisError > {
130- let parameter = IOPattern ( vec ! [
131- SpongeOp :: Absorb ( self . state. len( ) as u32 ) ,
132- SpongeOp :: Squeeze ( 1u32 ) ,
133- ] ) ;
134206 let mut ns = cs. namespace ( || "ns" ) ;
135207
136- let hash = {
137- let mut sponge = SpongeCircuit :: new_with_constants ( & self . constants . 0 , Simplex ) ;
138- let acc = & mut ns;
139-
140- sponge. start ( parameter, None , acc) ;
141- SpongeAPI :: absorb (
142- & mut sponge,
143- self . state . len ( ) as u32 ,
144- & ( 0 ..self . state . len ( ) )
145- . map ( |i| Elt :: Allocated ( self . state [ i] . clone ( ) ) )
146- . collect :: < Vec < Elt < Scalar > > > ( ) ,
147- acc,
148- ) ;
149-
150- let output = SpongeAPI :: squeeze ( & mut sponge, 1 , acc) ;
151- sponge. finish ( acc) . unwrap ( ) ;
152- output
208+ let hash = match self . mode {
209+ ROMode :: Wide => {
210+ poseidon_squeeze_circuit ! ( & self . constants. wide, & self . state, self . compact, & mut ns) ?
211+ }
212+ ROMode :: Narrow => {
213+ poseidon_squeeze_circuit ! ( & self . constants. narrow, & self . state, self . compact, & mut ns) ?
214+ }
153215 } ;
154216
155- let hash = Elt :: ensure_allocated ( & hash[ 0 ] , & mut ns. namespace ( || "ensure allocated" ) ) ?;
156-
157217 // reset the state to only contain the squeezed value
158218 self . state = vec ! [ hash. clone( ) ] ;
159219
@@ -186,38 +246,26 @@ where
186246 & mut self ,
187247 mut cs : CS ,
188248 ) -> Result < AllocatedNum < Scalar > , SynthesisError > {
189- let parameter = IOPattern ( vec ! [
190- SpongeOp :: Absorb ( self . state. len( ) as u32 ) ,
191- SpongeOp :: Squeeze ( 1u32 ) ,
192- ] ) ;
193249 let mut ns = cs. namespace ( || "ns" ) ;
194250
195- let hash = {
196- let mut sponge = SpongeCircuit :: new_with_constants ( & self . constants . 0 , Simplex ) ;
197- let acc = & mut ns;
198-
199- sponge. start ( parameter, None , acc) ;
200- SpongeAPI :: absorb (
201- & mut sponge,
202- self . state . len ( ) as u32 ,
203- & ( 0 ..self . state . len ( ) )
204- . map ( |i| Elt :: Allocated ( self . state [ i] . clone ( ) ) )
205- . collect :: < Vec < Elt < Scalar > > > ( ) ,
206- acc,
207- ) ;
208-
209- let output = SpongeAPI :: squeeze ( & mut sponge, 1 , acc) ;
210- sponge. finish ( acc) . unwrap ( ) ;
211- output
251+ let hash = match self . mode {
252+ ROMode :: Wide => {
253+ poseidon_squeeze_circuit ! ( & self . constants. wide, & self . state, self . compact, & mut ns) ?
254+ }
255+ ROMode :: Narrow => {
256+ poseidon_squeeze_circuit ! ( & self . constants. narrow, & self . state, self . compact, & mut ns) ?
257+ }
212258 } ;
213259
214- let hash = Elt :: ensure_allocated ( & hash[ 0 ] , & mut ns. namespace ( || "ensure allocated" ) ) ?;
215-
216260 // reset the state to only contain the squeezed value
217261 self . state = vec ! [ hash. clone( ) ] ;
218262
219263 Ok ( hash)
220264 }
265+
266+ fn set_compact ( & mut self , compact : bool ) {
267+ self . compact = compact;
268+ }
221269}
222270
223271#[ cfg( test) ]
@@ -269,4 +317,38 @@ mod tests {
269317 test_poseidon_ro_with :: < Secp256k1Engine > ( ) ;
270318 test_poseidon_ro_with :: < Secq256k1Engine > ( ) ;
271319 }
320+
321+ fn test_poseidon_ro_narrow_with < E : Engine > ( ) {
322+ let mut csprng: OsRng = OsRng ;
323+ let constants = PoseidonConstantsCircuit :: < E :: Scalar > :: default ( ) ;
324+ let num_absorbs = 4 ;
325+ let mut ro: PoseidonRO < E :: Scalar > =
326+ PoseidonRO :: new_with_mode ( constants. clone ( ) , ROMode :: Narrow ) ;
327+ let mut ro_gadget: PoseidonROCircuit < E :: Scalar > =
328+ PoseidonROCircuit :: new_with_mode ( constants, ROMode :: Narrow ) ;
329+ let mut cs = SatisfyingAssignment :: < E > :: new ( ) ;
330+ for i in 0 ..num_absorbs {
331+ let num = E :: Scalar :: random ( & mut csprng) ;
332+ ro. absorb ( num) ;
333+ let num_gadget = AllocatedNum :: alloc_infallible ( cs. namespace ( || format ! ( "data {i}" ) ) , || num) ;
334+ num_gadget
335+ . inputize ( & mut cs. namespace ( || format ! ( "input {i}" ) ) )
336+ . unwrap ( ) ;
337+ ro_gadget. absorb ( & num_gadget) ;
338+ }
339+ let num = ro. squeeze ( NUM_CHALLENGE_BITS , false ) ;
340+ let num2_bits = ro_gadget
341+ . squeeze ( & mut cs, NUM_CHALLENGE_BITS , false )
342+ . unwrap ( ) ;
343+ let num2 = le_bits_to_num ( & mut cs, & num2_bits) . unwrap ( ) ;
344+ assert_eq ! ( num, num2. get_value( ) . unwrap( ) ) ;
345+ }
346+
347+ #[ test]
348+ fn test_poseidon_ro_narrow ( ) {
349+ test_poseidon_ro_narrow_with :: < PallasEngine > ( ) ;
350+ test_poseidon_ro_narrow_with :: < VestaEngine > ( ) ;
351+ test_poseidon_ro_narrow_with :: < Bn256EngineKZG > ( ) ;
352+ test_poseidon_ro_narrow_with :: < GrumpkinEngine > ( ) ;
353+ }
272354}
0 commit comments