Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8fbf5ef

Browse files
authored
Fix poseidon sponge bug (#148)
* demo bug * fix collusion bug cross test with independent implementation
1 parent 6b19555 commit 8fbf5ef

File tree

3 files changed

+242
-7
lines changed

3 files changed

+242
-7
lines changed

crypto-primitives/src/sponge/poseidon/constraints.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,12 +169,13 @@ impl<F: PrimeField> PoseidonSpongeVar<F> {
169169
..(self.parameters.capacity + num_elements_squeezed + rate_start_index)],
170170
);
171171

172+
// Repeat with updated output slices and rate start index
173+
remaining_output = &mut remaining_output[num_elements_squeezed..];
174+
172175
// Unless we are done with squeezing in this call, permute.
173-
if remaining_output.len() != self.parameters.rate {
176+
if !remaining_output.is_empty() {
174177
self.permute()?;
175178
}
176-
// Repeat with updated output slices and rate start index
177-
remaining_output = &mut remaining_output[num_elements_squeezed..];
178179
rate_start_index = 0;
179180
}
180181
}

crypto-primitives/src/sponge/poseidon/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,13 @@ impl<F: PrimeField> PoseidonSponge<F> {
174174
..(self.parameters.capacity + num_elements_squeezed + rate_start_index)],
175175
);
176176

177+
// Repeat with updated output slices
178+
output_remaining = &mut output_remaining[num_elements_squeezed..];
177179
// Unless we are done with squeezing in this call, permute.
178-
if output_remaining.len() != self.parameters.rate {
180+
if !output_remaining.is_empty() {
179181
self.permute();
180182
}
181-
// Repeat with updated output slices
182-
output_remaining = &mut output_remaining[num_elements_squeezed..];
183+
183184
rate_start_index = 0;
184185
}
185186
}

crypto-primitives/src/sponge/poseidon/tests.rs

Lines changed: 234 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,243 @@
1-
use crate::sponge::poseidon::{PoseidonConfig, PoseidonSponge};
1+
use crate::sponge::poseidon::{PoseidonConfig, PoseidonDefaultConfigField, PoseidonSponge};
22
use crate::sponge::test::Fr;
33
use crate::sponge::{Absorb, AbsorbWithLength, CryptographicSponge, FieldBasedCryptographicSponge};
44
use crate::{absorb, collect_sponge_bytes, collect_sponge_field_elements};
55
use ark_ff::{One, PrimeField, UniformRand};
66
use ark_std::test_rng;
77

8+
#[test]
9+
// Remove once this PR matures
10+
fn demo_bug() {
11+
let sponge_params = Fr::get_default_poseidon_parameters(2, false).unwrap();
12+
13+
let rng = &mut test_rng();
14+
let input = (0..3).map(|_| Fr::rand(rng)).collect::<Vec<_>>();
15+
16+
// works good
17+
let e0 = {
18+
let mut sponge = PoseidonSponge::<Fr>::new(&sponge_params);
19+
sponge.absorb(&input);
20+
sponge.squeeze_native_field_elements(3)
21+
};
22+
23+
// works good
24+
let e1 = {
25+
let mut sponge = PoseidonSponge::<Fr>::new(&sponge_params);
26+
sponge.absorb(&input);
27+
let e0 = sponge.squeeze_native_field_elements(1);
28+
let e1 = sponge.squeeze_native_field_elements(1);
29+
let e2 = sponge.squeeze_native_field_elements(1);
30+
e0.iter()
31+
.chain(e1.iter())
32+
.chain(e2.iter())
33+
.cloned()
34+
.collect::<Vec<_>>()
35+
};
36+
37+
// also works good
38+
let e2 = {
39+
let mut sponge = PoseidonSponge::<Fr>::new(&sponge_params);
40+
sponge.absorb(&input);
41+
42+
let e0 = sponge.squeeze_native_field_elements(2);
43+
let e1 = sponge.squeeze_native_field_elements(1);
44+
e0.iter().chain(e1.iter()).cloned().collect::<Vec<_>>()
45+
};
46+
47+
// skips a permutation if sponge
48+
// * in squeezing mode
49+
// * number of elements are equal to rate
50+
let e3 = {
51+
let mut sponge = PoseidonSponge::<Fr>::new(&sponge_params);
52+
sponge.absorb(&input);
53+
let e0 = sponge.squeeze_native_field_elements(1);
54+
let e1 = sponge.squeeze_native_field_elements(2);
55+
e0.iter().chain(e1.iter()).cloned().collect::<Vec<_>>()
56+
};
57+
58+
assert_eq!(e0, e1);
59+
assert_eq!(e0, e2);
60+
assert_eq!(e0, e3); // this will fail
61+
}
62+
63+
// Remove once this PR matures
64+
fn run_cross_test<F: PrimeField + Absorb>(cfg: &PoseidonConfig<F>) {
65+
#[derive(Debug, PartialEq, Eq)]
66+
enum SpongeMode {
67+
Absorbing,
68+
Squeezing,
69+
}
70+
71+
#[derive(Clone, Debug)]
72+
struct Reference<F: PrimeField> {
73+
cfg: PoseidonConfig<F>,
74+
state: Vec<F>,
75+
absorbing: Vec<F>,
76+
squeeze_count: Option<usize>,
77+
}
78+
79+
// workaround to permute a state
80+
fn permute<F: PrimeField>(cfg: &PoseidonConfig<F>, state: &mut [F]) {
81+
let mut sponge = PoseidonSponge::new(&cfg);
82+
sponge.state.copy_from_slice(state);
83+
sponge.permute();
84+
state.copy_from_slice(&sponge.state)
85+
}
86+
87+
impl<F: PrimeField> Reference<F> {
88+
fn new(cfg: &PoseidonConfig<F>) -> Self {
89+
let t = cfg.rate + cfg.capacity;
90+
let state = vec![F::zero(); t];
91+
Self {
92+
cfg: cfg.clone(),
93+
state,
94+
absorbing: Vec::new(),
95+
squeeze_count: None,
96+
}
97+
}
98+
99+
fn mode(&self) -> SpongeMode {
100+
match self.squeeze_count {
101+
Some(_) => {
102+
assert!(self.absorbing.is_empty());
103+
SpongeMode::Squeezing
104+
}
105+
None => SpongeMode::Absorbing,
106+
}
107+
}
108+
109+
fn absorb(&mut self, input: &[F]) {
110+
if !input.is_empty() {
111+
match self.mode() {
112+
SpongeMode::Absorbing => self.absorbing.extend_from_slice(input),
113+
SpongeMode::Squeezing => {
114+
// Wash the state as mode changes
115+
// This is not appied in SAFE sponge
116+
permute(&self.cfg, &mut self.state);
117+
// Append inputs to the absorbing line
118+
self.absorbing.extend_from_slice(input);
119+
// Change mode to absorbing
120+
self.squeeze_count = None;
121+
}
122+
}
123+
}
124+
}
125+
126+
fn _absorb(&mut self) {
127+
let rate = self.cfg.rate;
128+
self.absorbing.chunks(rate).for_each(|chunk| {
129+
self.state
130+
.iter_mut()
131+
.skip(self.cfg.capacity)
132+
.zip(chunk.iter())
133+
.for_each(|(s, c)| *s += *c);
134+
permute(&self.cfg, &mut self.state);
135+
});
136+
137+
// This case can only happen in the begining when the absorbing line is empty
138+
// and user wants to squeeze elements. Notice that after moving to squueze mode
139+
// if user calls absorb again with empty input it will be ignored
140+
self.absorbing
141+
.is_empty()
142+
.then(|| permute(&self.cfg, &mut self.state));
143+
144+
// flush the absorbing line
145+
self.absorbing.clear();
146+
147+
// Change to the squeezing mode
148+
assert_eq!(self.mode(), SpongeMode::Absorbing);
149+
self.squeeze_count = Some(0);
150+
}
151+
152+
pub fn squeeze(&mut self, n: usize) -> Vec<F> {
153+
match self.mode() {
154+
SpongeMode::Absorbing => self._absorb(),
155+
SpongeMode::Squeezing => {
156+
assert!(self.absorbing.is_empty());
157+
assert!(self.squeeze_count.is_some());
158+
159+
// ???
160+
// **This seems nonsense to me**
161+
// If,
162+
// * number of squeeze is zero AND
163+
// * in squeezing mode AND
164+
// * output index is is at `rate`
165+
// it applies a useless permutation.
166+
// This is also not appied in SAFE sponge
167+
168+
if n == 0 {
169+
let squeeze_count = self.squeeze_count.unwrap();
170+
let out_index = self.squeeze_count.unwrap() % self.cfg.rate;
171+
(out_index == 0 && squeeze_count != 0).then(|| {
172+
permute(&self.cfg, &mut self.state);
173+
self.squeeze_count = Some(0);
174+
});
175+
}
176+
}
177+
}
178+
179+
let rate = self.cfg.rate;
180+
let mut output = Vec::new();
181+
for _ in 0..n {
182+
let squeeze_count = self.squeeze_count.unwrap();
183+
let out_index = squeeze_count % rate;
184+
185+
// proceed with a permutation if
186+
// * the rate is full
187+
// * and it is not the first output
188+
(out_index == 0 && squeeze_count != 0).then(|| permute(&self.cfg, &mut self.state));
189+
190+
// skip the capacity elements
191+
let out_index = out_index + self.cfg.capacity;
192+
output.push(self.state[out_index]);
193+
self.squeeze_count.as_mut().map(|c| *c += 1);
194+
}
195+
196+
output
197+
}
198+
}
199+
200+
let mut sponge = PoseidonSponge::new(cfg);
201+
let mut sponge_ref = Reference::new(cfg);
202+
let mut rng = test_rng();
203+
204+
for _ in 0..1000 {
205+
let test = (0..100)
206+
.map(|_| {
207+
use crate::ark_std::rand::Rng;
208+
let do_absorb = rng.gen_bool(0.5);
209+
let do_squeeze = rng.gen_bool(0.5);
210+
211+
(
212+
(do_absorb, rng.gen_range(0..=cfg.rate * 2 + 1)),
213+
(do_squeeze, rng.gen_range(0..=cfg.rate * 2 + 1)),
214+
)
215+
})
216+
.collect::<Vec<_>>();
217+
218+
// fuzz fuzz
219+
for (_i, ((do_absorb, n_absorb), (do_squeeze, n_squeeze))) in test.into_iter().enumerate() {
220+
do_absorb.then(|| {
221+
let inputs = (0..n_absorb).map(|_| F::rand(&mut rng)).collect::<Vec<_>>();
222+
sponge_ref.absorb(&inputs);
223+
sponge.absorb(&inputs);
224+
});
225+
do_squeeze.then(|| {
226+
let out0 = sponge_ref.squeeze(n_squeeze);
227+
let out1 = sponge.squeeze_field_elements(n_squeeze);
228+
assert_eq!(out0, out1);
229+
});
230+
}
231+
}
232+
}
233+
234+
#[test]
235+
// Remove once this PR matures
236+
fn test_cross() {
237+
let cfg = Fr::get_default_poseidon_parameters(2, false).unwrap();
238+
run_cross_test::<Fr>(&cfg);
239+
}
240+
8241
fn assert_different_encodings<F: PrimeField, A: Absorb>(a: &A, b: &A) {
9242
let bytes1 = a.to_sponge_bytes_as_vec();
10243
let bytes2 = b.to_sponge_bytes_as_vec();

0 commit comments

Comments
 (0)