From 9ee3d8742ab7f79d0bf445b362dbf21a0aec1906 Mon Sep 17 00:00:00 2001 From: Al-Kindi-0 <82364884+Al-Kindi-0@users.noreply.github.com> Date: Mon, 18 Dec 2023 12:30:37 +0100 Subject: [PATCH] feat: move RpoRandomCoin and define Rng trait nits: minor chore: update log and readme --- CHANGELOG.md | 1 + README.md | 8 ++- src/rand/mod.rs | 15 ++++ src/rand/rpo.rs | 188 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 src/rand/rpo.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b8a6559d..02bb66f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * Implemented the `PartialMmr` data structure (#195). * Updated Winterfell dependency to v0.7 (#200) * Implemented RPX hash function (#201). +* Added `FeltRng` and `RpoRandomCoin` (#237). ## 0.7.1 (2023-10-10) diff --git a/README.md b/README.md index f4de47ed..f43a7429 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,18 @@ For performance benchmarks of these hash functions and their comparison to other The module also contains additional supporting components such as `NodeIndex`, `MerklePath`, and `MerkleError` to assist with tree indexation, opening proofs, and reporting inconsistent arguments/state. ## Signatures -[DAS module](./src/dsa) provides a set of digital signature schemes supported by default in the Miden VM. Currently, these schemes are: +[DSA module](./src/dsa) provides a set of digital signature schemes supported by default in the Miden VM. Currently, these schemes are: * `RPO Falcon512`: a variant of the [Falcon](https://falcon-sign.info/) signature scheme. This variant differs from the standard in that instead of using SHAKE256 hash function in the *hash-to-point* algorithm we use RPO256. This makes the signature more efficient to verify in Miden VM. For the above signatures, key generation and signing is available only in the `std` context (see [crate features](#crate-features) below), while signature verification is available in `no_std` context as well. +## Pseudo-Random Element Generator +[Pseudo random element generator module](./src/rand/) provides a set of traits and data structures that facilitate generating pseudo-random elements in the context of Miden VM and Miden rollup. The module currently includes: + +* `FeltRng`: a trait for generating random field elements and random 4 field elements. +* `RpoRandomCoin`: a struct implementing `FeltRng` as well as the [`RandomCoin`](https://github.com/facebook/winterfell/blob/main/crypto/src/random/mod.rs) trait. + ## Crate features This crate can be compiled with the following features: diff --git a/src/rand/mod.rs b/src/rand/mod.rs index 2633527e..0de7658c 100644 --- a/src/rand/mod.rs +++ b/src/rand/mod.rs @@ -1,3 +1,18 @@ //! Pseudo-random element generation. pub use winter_crypto::{RandomCoin, RandomCoinError}; + +use crate::{Felt, Word, ZERO}; + +mod rpo; + +/// Pseudo-random element generator. +/// +/// An instance can be used to draw, uniformly at random, basefield elements as well as `Word`s. +pub trait FeltRng { + /// Draw, uniformly at random, a basefield element. + fn draw_element(&mut self) -> Felt; + + /// Draw, uniformly at random, a `Word`. + fn draw_word(&mut self) -> Word; +} diff --git a/src/rand/rpo.rs b/src/rand/rpo.rs new file mode 100644 index 00000000..3af74b4e --- /dev/null +++ b/src/rand/rpo.rs @@ -0,0 +1,188 @@ +pub use winter_crypto::{RandomCoin, RandomCoinError}; +use winter_math::{FieldElement, StarkField}; + +use super::{Felt, FeltRng, Word, ZERO}; +use crate::hash::rpo::{Rpo256, RpoDigest}; +use crate::utils::collections::Vec; +use crate::utils::vec; + +// CONSTANTS +// ================================================================================================ + +const STATE_WIDTH: usize = Rpo256::STATE_WIDTH; +const RATE_START: usize = Rpo256::RATE_RANGE.start; +const RATE_END: usize = Rpo256::RATE_RANGE.end; +const HALF_RATE_WIDTH: usize = (Rpo256::RATE_RANGE.end - Rpo256::RATE_RANGE.start) / 2; + +// RPO RANDOM COIN +// ================================================================================================ +/// A simplified version of the `SPONGE_PRG` reseedable pseudo-random number generator algorithm +/// described in https://eprint.iacr.org/2011/499.pdf. The simplification is related to +/// the following facts: +/// 1. A call to the reseed method implies one and only one call to the permutation function. +/// This is possible because in our case we never reseed with more than 4 field elements. +/// 2. As a result of the previous point, we dont make use of an input buffer to accumulate seed +/// material. +pub struct RpoRandomCoin { + state: [Felt; STATE_WIDTH], + current: usize, +} + +impl RpoRandomCoin { + fn draw_basefield(&mut self) -> Felt { + if self.current == RATE_END { + Rpo256::apply_permutation(&mut self.state); + self.current = RATE_START; + } + + self.current += 1; + self.state[self.current - 1] + } +} + +impl RandomCoin for RpoRandomCoin { + type BaseField = Felt; + type Hasher = Rpo256; + + fn new(seed: &[Self::BaseField]) -> Self { + let mut state = [ZERO; STATE_WIDTH]; + let digest: Word = Rpo256::hash_elements(seed).into(); + + for i in 0..HALF_RATE_WIDTH { + state[RATE_START + i] += digest[i]; + } + + // Absorb + Rpo256::apply_permutation(&mut state); + + RpoRandomCoin { state, current: RATE_START } + } + + fn reseed(&mut self, data: RpoDigest) { + // Reset buffer + self.current = RATE_START; + + // Add the new seed material to the first half of the rate portion of the RPO state + let data: Word = data.into(); + + self.state[RATE_START] += data[0]; + self.state[RATE_START + 1] += data[1]; + self.state[RATE_START + 2] += data[2]; + self.state[RATE_START + 3] += data[3]; + + // Absorb + Rpo256::apply_permutation(&mut self.state); + } + + fn check_leading_zeros(&self, value: u64) -> u32 { + let value = Felt::new(value); + let mut state_tmp = self.state; + + state_tmp[RATE_START] += value; + + Rpo256::apply_permutation(&mut state_tmp); + + let first_rate_element = state_tmp[RATE_START].as_int(); + first_rate_element.trailing_zeros() + } + + fn draw>(&mut self) -> Result { + let ext_degree = E::EXTENSION_DEGREE; + let mut result = vec![ZERO; ext_degree]; + for r in result.iter_mut().take(ext_degree) { + *r = self.draw_basefield(); + } + + let result = E::slice_from_base_elements(&result); + Ok(result[0]) + } + + fn draw_integers( + &mut self, + num_values: usize, + domain_size: usize, + nonce: u64, + ) -> Result, RandomCoinError> { + assert!(domain_size.is_power_of_two(), "domain size must be a power of two"); + assert!(num_values < domain_size, "number of values must be smaller than domain size"); + + // absorb the nonce + let nonce = Felt::new(nonce); + self.state[RATE_START] += nonce; + Rpo256::apply_permutation(&mut self.state); + + // reset the buffer + self.current = RATE_START; + + // determine how many bits are needed to represent valid values in the domain + let v_mask = (domain_size - 1) as u64; + + // draw values from PRNG until we get as many unique values as specified by num_queries + let mut values = Vec::new(); + for _ in 0..1000 { + // get the next pseudo-random field element + let value = self.draw_basefield().as_int(); + + // use the mask to get a value within the range + let value = (value & v_mask) as usize; + + values.push(value); + if values.len() == num_values { + break; + } + } + + if values.len() < num_values { + return Err(RandomCoinError::FailedToDrawIntegers(num_values, values.len(), 1000)); + } + + Ok(values) + } +} + +impl FeltRng for RpoRandomCoin { + fn draw_element(&mut self) -> Felt { + self.draw_basefield() + } + + fn draw_word(&mut self) -> Word { + let mut output = [ZERO; 4]; + for o in output.iter_mut() { + *o = self.draw_basefield(); + } + output + } +} + +// TESTS +// ================================================================================================ + +#[cfg(all(test, feature = "std"))] +mod tests { + use super::{FeltRng, RandomCoin, RpoRandomCoin, ZERO}; + + #[test] + fn test_randfeltsgen_felt() { + let mut rpocoin = RpoRandomCoin::new(&[ZERO; 4]); + let output = rpocoin.draw_element(); + + let mut rpocoin = RpoRandomCoin::new(&[ZERO; 4]); + let expected = rpocoin.draw_basefield(); + + assert_eq!(output, expected); + } + + #[test] + fn test_randfeltsgen_word() { + let mut rpocoin = RpoRandomCoin::new(&[ZERO; 4]); + let output = rpocoin.draw_word(); + + let mut rpocoin = RpoRandomCoin::new(&[ZERO; 4]); + let mut expected = [ZERO; 4]; + for o in expected.iter_mut() { + *o = rpocoin.draw_basefield(); + } + + assert_eq!(output, expected); + } +}