From 0e5dd59d35bbf93bbbf5d9750dcdd2e6d95de56e Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sat, 23 Jul 2022 22:27:38 -0700 Subject: [PATCH 1/2] Use a fixed input buffer size in `Challenger`. Alternate implementation of #633, using `SPONGE_RATE` as the buffer size. --- plonky2/src/iop/challenger.rs | 58 ++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/plonky2/src/iop/challenger.rs b/plonky2/src/iop/challenger.rs index 52412409..b9883a18 100644 --- a/plonky2/src/iop/challenger.rs +++ b/plonky2/src/iop/challenger.rs @@ -33,8 +33,8 @@ impl> Challenger { pub fn new() -> Challenger { Challenger { sponge_state: [F::ZERO; SPONGE_WIDTH], - input_buffer: Vec::new(), - output_buffer: Vec::new(), + input_buffer: Vec::with_capacity(SPONGE_RATE), + output_buffer: Vec::with_capacity(SPONGE_RATE), _phantom: Default::default(), } } @@ -44,6 +44,10 @@ impl> Challenger { self.output_buffer.clear(); self.input_buffer.push(element); + + if self.input_buffer.len() == SPONGE_RATE { + self.duplexing(); + } } pub fn observe_extension_element(&mut self, element: &F::Extension) @@ -79,12 +83,10 @@ impl> Challenger { } pub fn get_challenge(&mut self) -> F { - self.absorb_buffered_inputs(); - - if self.output_buffer.is_empty() { - // Evaluate the permutation to produce `r` new outputs. - self.sponge_state = H::Permutation::permute(self.sponge_state); - self.output_buffer = self.sponge_state[0..SPONGE_RATE].to_vec(); + // If we have buffered inputs, we must perform a duplexing so that the challenge will + // reflect them. Or if we've run out of outputs, we must perform a duplexing to get more. + if !self.input_buffer.is_empty() || self.output_buffer.is_empty() { + self.duplexing(); } self.output_buffer @@ -125,27 +127,24 @@ impl> Challenger { .collect() } - /// Absorb any buffered inputs. After calling this, the input buffer will be empty. - fn absorb_buffered_inputs(&mut self) { - if self.input_buffer.is_empty() { - return; + /// Absorb any buffered inputs. After calling this, the input buffer will be empty, and the + /// output buffer will be full. + fn duplexing(&mut self) { + assert!(self.input_buffer.len() < SPONGE_RATE); + + // Overwrite the first r elements with the inputs. This differs from a standard sponge, + // where we would xor or add in the inputs. This is a well-known variant, though, + // sometimes called "overwrite mode". + for (i, input) in self.input_buffer.drain(..).enumerate() { + self.sponge_state[i] = input; } - for input_chunk in self.input_buffer.chunks(SPONGE_RATE) { - // Overwrite the first r elements with the inputs. This differs from a standard sponge, - // where we would xor or add in the inputs. This is a well-known variant, though, - // sometimes called "overwrite mode". - for (i, &input) in input_chunk.iter().enumerate() { - self.sponge_state[i] = input; - } + // Apply the permutation. + self.sponge_state = H::Permutation::permute(self.sponge_state); - // Apply the permutation. - self.sponge_state = H::Permutation::permute(self.sponge_state); - } - - self.output_buffer = self.sponge_state[0..SPONGE_RATE].to_vec(); - - self.input_buffer.clear(); + self.output_buffer.clear(); + self.output_buffer + .extend_from_slice(&self.sponge_state[0..SPONGE_RATE]); } } @@ -155,7 +154,9 @@ impl> Default for Challenger { } } -/// A recursive version of `Challenger`. +/// A recursive version of `Challenger`. The main difference is that `RecursiveChallenger`'s input +/// buffer can grow beyond `SPONGE_RATE`. This is so that `observe_element` etc do not need access +/// to the `CircuitBuilder`. pub struct RecursiveChallenger, H: AlgebraicHasher, const D: usize> { sponge_state: [Target; SPONGE_WIDTH], @@ -248,7 +249,8 @@ impl, H: AlgebraicHasher, const D: usize> self.get_n_challenges(builder, D).try_into().unwrap() } - /// Absorb any buffered inputs. After calling this, the input buffer will be empty. + /// Absorb any buffered inputs. After calling this, the input buffer will be empty, and the + /// output buffer will be full. fn absorb_buffered_inputs(&mut self, builder: &mut CircuitBuilder) { if self.input_buffer.is_empty() { return; From cddc749a7e102b361836ed0d2ddc2055127fe6e9 Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Sun, 24 Jul 2022 08:06:02 -0700 Subject: [PATCH 2/2] Fix comparison --- plonky2/src/iop/challenger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plonky2/src/iop/challenger.rs b/plonky2/src/iop/challenger.rs index b9883a18..97d21197 100644 --- a/plonky2/src/iop/challenger.rs +++ b/plonky2/src/iop/challenger.rs @@ -130,7 +130,7 @@ impl> Challenger { /// Absorb any buffered inputs. After calling this, the input buffer will be empty, and the /// output buffer will be full. fn duplexing(&mut self) { - assert!(self.input_buffer.len() < SPONGE_RATE); + assert!(self.input_buffer.len() <= SPONGE_RATE); // Overwrite the first r elements with the inputs. This differs from a standard sponge, // where we would xor or add in the inputs. This is a well-known variant, though,