Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
Introduce efficient Hash-based RNG streamer (#4169)
Browse files Browse the repository at this point in the history
* Introduce efficient Hash-based RNG streamer

* Loop in the case of being in bias space.

* Fix tests

* Minor cleanup
  • Loading branch information
gavofyork authored and rphmeier committed Nov 26, 2019
1 parent 2b716b5 commit 6e9c675
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 1 deletion.
3 changes: 3 additions & 0 deletions primitives/sr-primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub mod offchain;
pub mod testing;
pub mod traits;
pub mod transaction_validity;
pub mod random_number_generator;

/// Re-export these since they're only "kind of" generic.
pub use generic::{DigestItem, Digest};
Expand All @@ -70,6 +71,8 @@ pub use arithmetic::helpers_128bit;
/// Re-export big_uint stuff.
pub use arithmetic::biguint;

pub use random_number_generator::RandomNumberGenerator;

/// An abstraction over justification for a block's validity under a consensus algorithm.
///
/// Essentially a finality proof. The exact formulation will vary between consensus
Expand Down
103 changes: 103 additions & 0 deletions primitives/sr-primitives/src/random_number_generator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright 2017-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.

// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.

//! A simple pseudo random number generator that allows a stream of random numbers to be efficiently
//! created from a single initial seed hash.

use codec::{Encode, Decode};
use crate::traits::{Hash, TrailingZeroInput};

/// Pseudo-random number streamer. This retains the state of the random number stream. It's as
/// secure as the combination of the seed with which it is constructed and the hash function it uses
/// to cycle elements.
///
/// It can be saved and later reloaded using the Codec traits.
///
/// Example:
/// ```
/// use sr_primitives::traits::{Hash, BlakeTwo256};
/// use sr_primitives::RandomNumberGenerator;
/// let random_seed = BlakeTwo256::hash(b"Sixty-nine");
/// let mut rng = <RandomNumberGenerator<BlakeTwo256>>::new(random_seed);
/// assert_eq!(rng.pick_u32(100), 59);
/// assert_eq!(rng.pick_item(&[1, 2, 3]), Some(&1));
/// ```
///
/// This can use any cryptographic `Hash` function as the means of entropy-extension, and avoids
/// needless extensions of entropy.
///
/// If you're persisting it over blocks, be aware that the sequence will start to repeat. This won't
/// be a practical issue unless you're using tiny hash types (e.g. 64-bit) and pulling hundred of
/// megabytes of data from it.
#[derive(Encode, Decode)]
pub struct RandomNumberGenerator<Hashing: Hash> {
current: Hashing::Output,
offset: u32,
}

impl<Hashing: Hash> RandomNumberGenerator<Hashing> {
/// A new source of random data.
pub fn new(seed: Hashing::Output) -> Self {
Self {
current: seed,
offset: 0,
}
}

fn offset(&self) -> usize { self.offset as usize }

/// Returns a number at least zero, at most `max`.
pub fn pick_u32(&mut self, max: u32) -> u32 {
let needed = (4 - max.leading_zeros() / 8) as usize;
let top = ((1 << (needed as u64 * 8)) / ((max + 1) as u64) * ((max + 1) as u64) - 1) as u32;
loop {
if self.offset() + needed > self.current.as_ref().len() {
// rehash
self.current = Hashing::hash(self.current.as_ref());
self.offset = 0;
}
let data = &self.current.as_ref()[self.offset()..self.offset() + needed];
self.offset += needed as u32;
let raw = u32::decode(&mut TrailingZeroInput::new(data)).unwrap_or(0);
if raw <= top {
break if max < u32::max_value() {
raw % (max + 1)
} else {
raw
}
}
}
}

/// Returns a number at least zero, at most `max`.
///
/// This returns a `usize`, but internally it only uses `u32` so avoid consensus problems.
pub fn pick_usize(&mut self, max: usize) -> usize {
self.pick_u32(max as u32) as usize
}

/// Pick a random element from an array of `items`.
///
/// This is guaranteed to return `Some` except in the case that the given array `items` is
/// empty.
pub fn pick_item<'a, T>(&mut self, items: &'a [T]) -> Option<&'a T> {
if items.is_empty() {
None
} else {
Some(&items[self.pick_usize(items.len() - 1)])
}
}
}
9 changes: 8 additions & 1 deletion primitives/sr-primitives/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1026,7 +1026,14 @@ pub trait OpaqueKeys: Clone {
}

/// Input that adds infinite number of zero after wrapped input.
struct TrailingZeroInput<'a>(&'a [u8]);
pub struct TrailingZeroInput<'a>(&'a [u8]);

impl<'a> TrailingZeroInput<'a> {
/// Create a new instance from the given byte array.
pub fn new(data: &'a [u8]) -> Self {
Self(data)
}
}

impl<'a> codec::Input for TrailingZeroInput<'a> {
fn remaining_len(&mut self) -> Result<Option<usize>, codec::Error> {
Expand Down

0 comments on commit 6e9c675

Please sign in to comment.