Skip to content

Commit 90fa317

Browse files
authored
Add ROMode for configurable Poseidon sponge width (#490)
1 parent 2bba8e5 commit 90fa317

8 files changed

Lines changed: 203 additions & 77 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "nova-snark"
3-
version = "0.70.0"
3+
version = "0.71.0"
44
authors = ["Srinath Setty <srinath@microsoft.com>"]
55
edition = "2021"
66
description = "High-speed recursive arguments from folding schemes"

src/frontend/gadgets/num.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl<Scalar: PrimeField> AllocatedNum<Scalar> {
3333
///
3434
/// This is useful when a variable is known to hold a valid field element
3535
/// due to constraints added separately, enabling zero-cost reinterpretation
36-
/// (e.g., wrapping an [`AllocatedBit`](super::boolean::AllocatedBit)'s variable as a number).
36+
/// (e.g., wrapping an [`AllocatedBit`]'s variable as a number).
3737
pub fn from_parts(variable: Variable, value: Option<Scalar>) -> Self {
3838
AllocatedNum { value, variable }
3939
}

src/frontend/gadgets/poseidon/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod serde_impl;
1515
mod sponge;
1616

1717
pub use circuit2::Elt;
18+
pub(crate) use poseidon_inner::Arity;
1819
pub use poseidon_inner::PoseidonConstants;
1920
use round_constants::generate_constants;
2021
use round_numbers::{round_numbers_base, round_numbers_strengthened};

src/frontend/gadgets/poseidon/poseidon_inner.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub trait Arity<T>: ArrayLength {
2222
/// Must be Arity + 1.
2323
type ConstantsSize: ArrayLength;
2424

25+
/// Returns the tag value for this arity.
2526
fn tag() -> T;
2627
}
2728

src/neutron/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ where
105105
/// let ck_hint2 = &*SPrime::<E2>::ck_floor();
106106
///
107107
/// let pp = PublicParams::setup(&circuit, ck_hint1, ck_hint2)?;
108-
/// Ok(())
108+
/// Ok::<(), nova_snark::errors::NovaError>(())
109109
/// ```
110110
pub fn setup(
111111
c: &C,
@@ -544,17 +544,17 @@ mod tests {
544544
fn test_pp_digest() {
545545
test_pp_digest_with::<PallasEngine, VestaEngine, _>(
546546
&TrivialCircuit::<_>::default(),
547-
&expect!["a92fc0374a5f9fc21e5269476b4d978597606fd30e35e7e6a8673152746c3a00"],
547+
&expect!["4d22b1021985b02532b1cc83ab566d503d8db8cf7de1acac525d39e3c2508e03"],
548548
);
549549

550550
test_pp_digest_with::<Bn256EngineIPA, GrumpkinEngine, _>(
551551
&TrivialCircuit::<_>::default(),
552-
&expect!["5bca2e81847be57a6ee39b1e7c11cafb23cd946d9d26658149223f999df44300"],
552+
&expect!["fdea1f44a4d102141c6f31efa72c04606c5e6d3ec9a6b37208238152717a4c03"],
553553
);
554554

555555
test_pp_digest_with::<Secp256k1Engine, Secq256k1Engine, _>(
556556
&TrivialCircuit::<_>::default(),
557-
&expect!["17fbf2a863d82c73e546fee2ca4854818e1c1973531d099af1fee258d91e6703"],
557+
&expect!["bdcf8157e37b5d99c5c7168774e16ec11a24594833b078ebe6312e83fdfda600"],
558558
);
559559
}
560560

src/nova/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,17 +1125,17 @@ mod tests {
11251125
fn test_pp_digest() {
11261126
test_pp_digest_with::<PallasEngine, VestaEngine, _>(
11271127
&TrivialCircuit::<_>::default(),
1128-
&expect!["a2232cea11d185f62c6b5304229be6358cfb90c16ca303bc61ae188f2f49d900"],
1128+
&expect!["5554dcef9f66efdf2477d0ada1f553f0e7edd9191391156edfca338cb270aa02"],
11291129
);
11301130

11311131
test_pp_digest_with::<Bn256EngineIPA, GrumpkinEngine, _>(
11321132
&TrivialCircuit::<_>::default(),
1133-
&expect!["f35d70261a065938981677839685b1e2a91aa2f2526cdf7f676fc908bed1a701"],
1133+
&expect!["a5ad54e26a84517739bde0fd1e56f10aa1f8321bfee234c347af0fb9b14bfb00"],
11341134
);
11351135

11361136
test_pp_digest_with::<Secp256k1Engine, Secq256k1Engine, _>(
11371137
&TrivialCircuit::<_>::default(),
1138-
&expect!["bfec121f93de2faedd0a6602ce0445b3bec9d49f494cf6be01312ddf24fa4d02"],
1138+
&expect!["b403daf596511f975656f8621269c1e885b60863aebd7a095000b599f6ed2802"],
11391139
);
11401140
}
11411141

src/provider/poseidon.rs

Lines changed: 150 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,63 @@
22
use 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
};
1313
use ff::{PrimeField, PrimeFieldBits};
14-
use generic_array::typenum::U24;
14+
use generic_array::typenum::{U24, U5};
1515
use 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

2127
impl<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)]
3039
pub 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

3664
impl<Base> ROTrait<Base> for PoseidonRO<Base>
@@ -44,6 +72,15 @@ where
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

103168
impl<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

Comments
 (0)