diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d244600..091bd2a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.3 (2024-04-24) + +* Added `RpxRandomCoin` struct (#307). + ## 0.9.2 (2024-04-21) * Implemented serialization for the `Smt` struct (#304). diff --git a/Cargo.lock b/Cargo.lock index a7185b55..03b6600d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,9 +443,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685a7d121ee3f65ae4fddd72b25a04bb36b6af81bc0828f7d5434c0fe60fa3a2" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -506,7 +506,7 @@ checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "miden-crypto" -version = "0.9.2" +version = "0.9.3" dependencies = [ "blake3", "cc", @@ -786,9 +786,9 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags", "errno", @@ -1029,37 +1029,15 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "134306a13c5647ad6453e8deaec55d3a44d6021970129e6188735e74bf546697" dependencies = [ - "winapi", + "windows-sys", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index ce34b1b6..e0c86526 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "miden-crypto" -version = "0.9.2" +version = "0.9.3" description = "Miden Cryptographic primitives" authors = ["miden contributors"] readme = "README.md" license = "MIT" repository = "https://github.com/0xPolygonMiden/crypto" -documentation = "https://docs.rs/miden-crypto/0.9.2" +documentation = "https://docs.rs/miden-crypto/0.9.3" categories = ["cryptography", "no-std"] keywords = ["miden", "crypto", "hash", "merkle"] edition = "2021" diff --git a/README.md b/README.md index d82af815..f5a554f1 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,8 @@ For the above signatures, key generation, signing, and signature verification ar [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. +* `RpoRandomCoin`: a struct implementing `FeltRng` as well as the [`RandomCoin`](https://github.com/facebook/winterfell/blob/main/crypto/src/random/mod.rs) trait using RPO hash function. +* `RpxRandomCoin`: a struct implementing `FeltRng` as well as the [`RandomCoin`](https://github.com/facebook/winterfell/blob/main/crypto/src/random/mod.rs) trait using RPX hash function. ## Crate features This crate can be compiled with the following features: diff --git a/src/rand/mod.rs b/src/rand/mod.rs index cc846056..6e371a2d 100644 --- a/src/rand/mod.rs +++ b/src/rand/mod.rs @@ -7,7 +7,9 @@ pub use winter_utils::Randomizable; use crate::{Felt, FieldElement, Word, ZERO}; mod rpo; +mod rpx; pub use rpo::RpoRandomCoin; +pub use rpx::RpxRandomCoin; /// Pseudo-random element generator. /// diff --git a/src/rand/rpx.rs b/src/rand/rpx.rs new file mode 100644 index 00000000..a2a0641d --- /dev/null +++ b/src/rand/rpx.rs @@ -0,0 +1,292 @@ +use super::{Felt, FeltRng, FieldElement, RandomCoin, RandomCoinError, RngCore, Word, ZERO}; +use crate::{ + hash::rpx::{Rpx256, RpxDigest}, + utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, +}; +use alloc::{string::ToString, vec::Vec}; +use rand_core::impls; + +// CONSTANTS +// ================================================================================================ + +const STATE_WIDTH: usize = Rpx256::STATE_WIDTH; +const RATE_START: usize = Rpx256::RATE_RANGE.start; +const RATE_END: usize = Rpx256::RATE_RANGE.end; +const HALF_RATE_WIDTH: usize = (Rpx256::RATE_RANGE.end - Rpx256::RATE_RANGE.start) / 2; + +// RPX RANDOM COIN +// ================================================================================================ +/// A simplified version of the `SPONGE_PRG` reseedable pseudo-random number generator algorithm +/// described in . +/// +/// 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 don't make use of an input buffer to accumulate seed +/// material. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct RpxRandomCoin { + state: [Felt; STATE_WIDTH], + current: usize, +} + +impl RpxRandomCoin { + /// Returns a new [RpxRandomCoin] initialize with the specified seed. + pub fn new(seed: Word) -> Self { + let mut state = [ZERO; STATE_WIDTH]; + + for i in 0..HALF_RATE_WIDTH { + state[RATE_START + i] += seed[i]; + } + + // Absorb + Rpx256::apply_permutation(&mut state); + + RpxRandomCoin { state, current: RATE_START } + } + + /// Returns an [RpxRandomCoin] instantiated from the provided components. + /// + /// # Panics + /// Panics if `current` is smaller than 4 or greater than or equal to 12. + pub fn from_parts(state: [Felt; STATE_WIDTH], current: usize) -> Self { + assert!( + (RATE_START..RATE_END).contains(¤t), + "current value outside of valid range" + ); + Self { state, current } + } + + /// Returns components of this random coin. + pub fn into_parts(self) -> ([Felt; STATE_WIDTH], usize) { + (self.state, self.current) + } + + /// Fills `dest` with random data. + pub fn fill_bytes(&mut self, dest: &mut [u8]) { + ::fill_bytes(self, dest) + } + + fn draw_basefield(&mut self) -> Felt { + if self.current == RATE_END { + Rpx256::apply_permutation(&mut self.state); + self.current = RATE_START; + } + + self.current += 1; + self.state[self.current - 1] + } +} + +// RANDOM COIN IMPLEMENTATION +// ------------------------------------------------------------------------------------------------ + +impl RandomCoin for RpxRandomCoin { + type BaseField = Felt; + type Hasher = Rpx256; + + fn new(seed: &[Self::BaseField]) -> Self { + let digest: Word = Rpx256::hash_elements(seed).into(); + Self::new(digest) + } + + fn reseed(&mut self, data: RpxDigest) { + // Reset buffer + self.current = RATE_START; + + // Add the new seed material to the first half of the rate portion of the RPX 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 + Rpx256::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; + + Rpx256::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; + Rpx256::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) + } +} + +// FELT RNG IMPLEMENTATION +// ------------------------------------------------------------------------------------------------ + +impl FeltRng for RpxRandomCoin { + 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 + } +} + +// RNGCORE IMPLEMENTATION +// ------------------------------------------------------------------------------------------------ + +impl RngCore for RpxRandomCoin { + fn next_u32(&mut self) -> u32 { + self.draw_basefield().as_int() as u32 + } + + fn next_u64(&mut self) -> u64 { + impls::next_u64_via_u32(self) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + impls::fill_bytes_via_next(self, dest) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +// SERIALIZATION +// ------------------------------------------------------------------------------------------------ + +impl Serializable for RpxRandomCoin { + fn write_into(&self, target: &mut W) { + self.state.iter().for_each(|v| v.write_into(target)); + // casting to u8 is OK because `current` is always between 4 and 12. + target.write_u8(self.current as u8); + } +} + +impl Deserializable for RpxRandomCoin { + fn read_from(source: &mut R) -> Result { + let state = [ + Felt::read_from(source)?, + Felt::read_from(source)?, + Felt::read_from(source)?, + Felt::read_from(source)?, + Felt::read_from(source)?, + Felt::read_from(source)?, + Felt::read_from(source)?, + Felt::read_from(source)?, + Felt::read_from(source)?, + Felt::read_from(source)?, + Felt::read_from(source)?, + Felt::read_from(source)?, + ]; + let current = source.read_u8()? as usize; + if !(RATE_START..RATE_END).contains(¤t) { + return Err(DeserializationError::InvalidValue( + "current value outside of valid range".to_string(), + )); + } + Ok(Self { state, current }) + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use super::{Deserializable, FeltRng, RpxRandomCoin, Serializable, ZERO}; + use crate::ONE; + + #[test] + fn test_feltrng_felt() { + let mut rpxcoin = RpxRandomCoin::new([ZERO; 4]); + let output = rpxcoin.draw_element(); + + let mut rpxcoin = RpxRandomCoin::new([ZERO; 4]); + let expected = rpxcoin.draw_basefield(); + + assert_eq!(output, expected); + } + + #[test] + fn test_feltrng_word() { + let mut rpxcoin = RpxRandomCoin::new([ZERO; 4]); + let output = rpxcoin.draw_word(); + + let mut rpocoin = RpxRandomCoin::new([ZERO; 4]); + let mut expected = [ZERO; 4]; + for o in expected.iter_mut() { + *o = rpocoin.draw_basefield(); + } + + assert_eq!(output, expected); + } + + #[test] + fn test_feltrng_serialization() { + let coin1 = RpxRandomCoin::from_parts([ONE; 12], 5); + + let bytes = coin1.to_bytes(); + let coin2 = RpxRandomCoin::read_from_bytes(&bytes).unwrap(); + assert_eq!(coin1, coin2); + } +}