Add new shuffling function
This commit is contained in:
parent
c36e00a1f6
commit
486865e803
@ -4,7 +4,9 @@ version = "0.0.1"
|
|||||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# TODO: remove "blake2" in favor of "blake2-rfc"
|
||||||
blake2 = "^0.7.1"
|
blake2 = "^0.7.1"
|
||||||
|
blake2-rfc = "0.2.18"
|
||||||
bls = { git = "https://github.com/sigp/bls" }
|
bls = { git = "https://github.com/sigp/bls" }
|
||||||
bytes = ""
|
bytes = ""
|
||||||
crypto-mac = "^0.6.2"
|
crypto-mac = "^0.6.2"
|
||||||
|
@ -6,6 +6,7 @@ extern crate clap;
|
|||||||
extern crate network_libp2p;
|
extern crate network_libp2p;
|
||||||
|
|
||||||
pub mod pubkeystore;
|
pub mod pubkeystore;
|
||||||
|
pub mod shuffling;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
52
src/shuffling/mod.rs
Normal file
52
src/shuffling/mod.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
extern crate blake2_rfc;
|
||||||
|
|
||||||
|
mod rng;
|
||||||
|
|
||||||
|
use self::rng::ShuffleRng;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ShuffleErr {
|
||||||
|
ExceedsListLength,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a deterministic, in-place shuffle of a vector of bytes.
|
||||||
|
/// The final order of the shuffle is determined by successive hashes
|
||||||
|
/// of the supplied `seed`.
|
||||||
|
pub fn shuffle(
|
||||||
|
seed: &[u8],
|
||||||
|
mut list: Vec<usize>)
|
||||||
|
-> Result<Vec<usize>, ShuffleErr>
|
||||||
|
{
|
||||||
|
let mut rng = ShuffleRng::new(seed);
|
||||||
|
if list.len() > rng.rand_max as usize {
|
||||||
|
return Err(ShuffleErr::ExceedsListLength);
|
||||||
|
}
|
||||||
|
for i in 0..(list.len() - 1) {
|
||||||
|
let n = list.len() - i;
|
||||||
|
let j = rng.rand_range(n as u32) as usize + i;
|
||||||
|
list.swap(i, j);
|
||||||
|
}
|
||||||
|
Ok(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use super::blake2_rfc::blake2s::{ blake2s, Blake2sResult };
|
||||||
|
|
||||||
|
fn hash(seed: &[u8]) -> Blake2sResult {
|
||||||
|
blake2s(32, &[], seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shuffling() {
|
||||||
|
let seed = hash(b"4kn4driuctg8");
|
||||||
|
let list: Vec<usize> = (0..12).collect();
|
||||||
|
let s = shuffle(seed.as_bytes(), list).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
s,
|
||||||
|
vec![7, 4, 8, 6, 5, 3, 0, 11, 1, 2, 10, 9],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
132
src/shuffling/rng.rs
Normal file
132
src/shuffling/rng.rs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
use super::blake2_rfc::blake2s::{ Blake2s, Blake2sResult };
|
||||||
|
|
||||||
|
const SEED_SIZE_BYTES: usize = 32;
|
||||||
|
const RAND_BYTES: usize = 3; // 24 / 8
|
||||||
|
const RAND_MAX: u32 = 16777216; // 2**24
|
||||||
|
|
||||||
|
/// A pseudo-random number generator which given a seed
|
||||||
|
/// uses successive blake2s hashing to generate "entropy".
|
||||||
|
pub struct ShuffleRng {
|
||||||
|
seed: Blake2sResult,
|
||||||
|
idx: usize,
|
||||||
|
pub rand_max: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShuffleRng {
|
||||||
|
/// Create a new instance given some "seed" bytes.
|
||||||
|
pub fn new(initial_seed: &[u8]) -> Self {
|
||||||
|
Self {
|
||||||
|
seed: hash(initial_seed),
|
||||||
|
idx: 0,
|
||||||
|
rand_max: RAND_MAX,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "Regenerates" the seed by hashing it.
|
||||||
|
fn rehash_seed(&mut self) {
|
||||||
|
self.seed = hash(self.seed.as_bytes());
|
||||||
|
self.idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts 3 bytes from the `seed`. Rehashes seed if required.
|
||||||
|
fn rand(&mut self) -> u32 {
|
||||||
|
self.idx += RAND_BYTES;
|
||||||
|
match self.idx >= SEED_SIZE_BYTES {
|
||||||
|
true => {
|
||||||
|
self.rehash_seed();
|
||||||
|
self.rand()
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
int_from_byte_slice(
|
||||||
|
self.seed.as_bytes(),
|
||||||
|
self.idx - RAND_BYTES,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a random u32 below the specified maximum `n`.
|
||||||
|
///
|
||||||
|
/// Provides a filtered result from a higher-level rng, by discarding
|
||||||
|
/// results which may bias the output. Because of this, execution time is
|
||||||
|
/// not linear and may potentially be infinite.
|
||||||
|
pub fn rand_range(&mut self, n: u32) -> u32 {
|
||||||
|
assert!(n < RAND_MAX, "RAND_MAX exceed");
|
||||||
|
let mut x = self.rand();
|
||||||
|
while x >= self.rand_max - (self.rand_max % n) {
|
||||||
|
x = self.rand();
|
||||||
|
}
|
||||||
|
x % n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the next three bytes of `source`, starting from `offset` and
|
||||||
|
/// interprets those bytes as a 24 bit big-endian integer.
|
||||||
|
/// Returns that integer.
|
||||||
|
fn int_from_byte_slice(source: &[u8], offset: usize) -> u32 {
|
||||||
|
(
|
||||||
|
source[offset + 2] as u32) |
|
||||||
|
((source[offset + 1] as u32) << 8) |
|
||||||
|
((source[offset ] as u32) << 16
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Peform a blake2s hash on the given bytes.
|
||||||
|
fn hash(bytes: &[u8]) -> Blake2sResult {
|
||||||
|
let mut hasher = Blake2s::new(SEED_SIZE_BYTES);
|
||||||
|
hasher.update(bytes);
|
||||||
|
hasher.finalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shuffling_int_from_slice() {
|
||||||
|
let mut x = int_from_byte_slice(
|
||||||
|
&[0, 0, 1],
|
||||||
|
0);
|
||||||
|
assert_eq!((x as u32), 1);
|
||||||
|
|
||||||
|
x = int_from_byte_slice(
|
||||||
|
&[0, 1, 1],
|
||||||
|
0);
|
||||||
|
assert_eq!(x, 257);
|
||||||
|
|
||||||
|
x = int_from_byte_slice(
|
||||||
|
&[1, 1, 1],
|
||||||
|
0);
|
||||||
|
assert_eq!(x, 65793);
|
||||||
|
|
||||||
|
x = int_from_byte_slice(
|
||||||
|
&[255, 1, 1],
|
||||||
|
0);
|
||||||
|
assert_eq!(x, 16711937);
|
||||||
|
|
||||||
|
x = int_from_byte_slice(
|
||||||
|
&[255, 255, 255],
|
||||||
|
0);
|
||||||
|
assert_eq!(x, 16777215);
|
||||||
|
|
||||||
|
x = int_from_byte_slice(
|
||||||
|
&[0x8f, 0xbb, 0xc7],
|
||||||
|
0);
|
||||||
|
assert_eq!(x, 9419719);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shuffling_hash_fn() {
|
||||||
|
let digest = hash(hash(b"4kn4driuctg8").as_bytes()); // double-hash is intentional
|
||||||
|
let digest_bytes = digest.as_bytes();
|
||||||
|
let expected = [
|
||||||
|
0xff, 0xff, 0xff, 0x8f, 0xbb, 0xc7, 0xab, 0x64, 0x43, 0x9a,
|
||||||
|
0xe5, 0x12, 0x44, 0xd8, 0x70, 0xcf, 0xe5, 0x79, 0xf6, 0x55,
|
||||||
|
0x6b, 0xbd, 0x81, 0x43, 0xc5, 0xcd, 0x70, 0x2b, 0xbe, 0xe3,
|
||||||
|
0x87, 0xc7,
|
||||||
|
];
|
||||||
|
assert_eq!(digest_bytes.len(), expected.len());
|
||||||
|
assert_eq!(digest_bytes, expected)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user