Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move RpoRandomCoin to crate #237

Merged
merged 1 commit into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
15 changes: 15 additions & 0 deletions src/rand/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
}
188 changes: 188 additions & 0 deletions src/rand/rpo.rs
Original file line number Diff line number Diff line change
@@ -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<E: FieldElement<BaseField = Felt>>(&mut self) -> Result<E, RandomCoinError> {
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<Vec<usize>, 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);
}
}