Skip to content

Commit

Permalink
feat: move RpoRandomCoin and define Rng trait
Browse files Browse the repository at this point in the history
nits: minor

chore: update log and readme
  • Loading branch information
Al-Kindi-0 authored and bobbinth committed Dec 21, 2023
1 parent 4758e06 commit af76cb1
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 1 deletion.
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);
}
}

0 comments on commit af76cb1

Please sign in to comment.