diff --git a/beacon_chain/utils/shuffling/Cargo.toml b/beacon_chain/utils/shuffling/Cargo.toml index df0a108ee..31f4ce8cf 100644 --- a/beacon_chain/utils/shuffling/Cargo.toml +++ b/beacon_chain/utils/shuffling/Cargo.toml @@ -5,3 +5,6 @@ authors = ["Paul Hauner "] [dependencies] hashing = { path = "../hashing" } + +[dev-dependencies] +yaml-rust = "0.4.2" diff --git a/beacon_chain/utils/shuffling/src/lib.rs b/beacon_chain/utils/shuffling/src/lib.rs index 7acb7408a..9623a1fdc 100644 --- a/beacon_chain/utils/shuffling/src/lib.rs +++ b/beacon_chain/utils/shuffling/src/lib.rs @@ -25,9 +25,15 @@ pub fn shuffle( -> Result, ShuffleErr> { let mut rng = ShuffleRng::new(seed); + if list.len() > rng.rand_max as usize { return Err(ShuffleErr::ExceedsListLength); } + + if list.len() == 0 { + return Ok(list); + } + for i in 0..(list.len() - 1) { let n = list.len() - i; let j = rng.rand_range(n as u32) as usize + i; @@ -39,17 +45,43 @@ pub fn shuffle( #[cfg(test)] mod tests { + extern crate yaml_rust; + use super::*; use super::hashing::canonical_hash; + use std::fs::File; + use std::io::prelude::*; + use self::yaml_rust::yaml; #[test] fn test_shuffling() { - let seed = canonical_hash(b"4kn4driuctg8"); - let list: Vec = (0..12).collect(); - let s = shuffle(&seed, list).unwrap(); - assert_eq!( - s, - vec![7, 3, 2, 5, 11, 9, 1, 0, 4, 6, 10, 8], - ) + let mut file = File::open("./src/specs/shuffle_test_vectors.yaml").unwrap(); + let mut yaml_str = String::new(); + + file.read_to_string(&mut yaml_str).unwrap(); + + let docs = yaml::YamlLoader::load_from_str(&yaml_str).unwrap(); + let doc = &docs[0]; + let test_cases = doc["test_cases"].as_vec(); + + for test_case in test_cases.unwrap() { + let input = test_case["input"].clone().into_vec().unwrap(); + let output = test_case["output"].clone().into_vec().unwrap(); + let seed_bytes = test_case["seed"].as_str().unwrap().as_bytes(); + let mut seed; + + if seed_bytes.len() > 0 { + seed = canonical_hash(seed_bytes); + } else { + seed = vec![]; + } + + let mut s = shuffle(&seed, input).unwrap(); + + assert_eq!( + s, + output, + ); + } } } diff --git a/beacon_chain/utils/shuffling/src/rng.rs b/beacon_chain/utils/shuffling/src/rng.rs index 7ca6f1866..37151825c 100644 --- a/beacon_chain/utils/shuffling/src/rng.rs +++ b/beacon_chain/utils/shuffling/src/rng.rs @@ -2,7 +2,7 @@ use super::hashing::canonical_hash; const SEED_SIZE_BYTES: usize = 32; const RAND_BYTES: usize = 3; // 24 / 8 -const RAND_MAX: u32 = 16_777_216; // 2**24 +const RAND_MAX: u32 = 16_777_215; // 2 ** (rand_bytes * 8) - 1 /// A pseudo-random number generator which given a seed /// uses successive blake2s hashing to generate "entropy". diff --git a/beacon_chain/utils/shuffling/src/specs/shuffle_test_vectors.yaml b/beacon_chain/utils/shuffling/src/specs/shuffle_test_vectors.yaml new file mode 100644 index 000000000..c75b6aeaa --- /dev/null +++ b/beacon_chain/utils/shuffling/src/specs/shuffle_test_vectors.yaml @@ -0,0 +1,169 @@ +# This file was generated with a modified version of shuffling_sandbox +# python3 sandbox.py test_vectors +#diff --git a/sandbox.py b/sandbox.py +#index 99b0ba7..cd7cafe 100644 +#--- a/sandbox.py +#+++ b/sandbox.py +#@@ -126,13 +126,13 @@ elif args.method == "test_vectors": +# results = [] +# +# seeds = [ +#- b"", +#- blake("4kn4driuctg8".encode()), # known to cause conflicts with old shuffler +#- blake("ytre1p".encode()), +#- blake("mytobcffnkvj".encode()), +#- blake("myzu3g7evxp5nkvj".encode()), +#- blake("xdpli1jsx5xb".encode()), +#- blake("oab3mbb3xe8qsx5xb".encode()), +#+ "", +#+ "4kn4driuctg8", # known to cause conflicts with old shuffler +#+ "ytre1p", +#+ "mytobcffnkvj", +#+ "myzu3g7evxp5nkvj", +#+ "xdpli1jsx5xb", +#+ "oab3mbb3xe8qsx5xb", +# ] +# lists = [ +# [], +#@@ -147,7 +147,8 @@ elif args.method == "test_vectors": +# +# for seed in seeds: +# for lst in lists: +#- output = shuffler(lst, seed) +#+ blake_seed = blake(seed.encode()) if len(seed) > 0 else b"" +#+ output = shuffler(lst, blake_seed) +# results.append({"seed": seed, "input": lst, "output": output}) +# +# body = { + +title: Shuffling Algorithm Tests +summary: Test vectors for shuffling a list based upon a seed. +test_suite: Shuffling + +test_cases: +- input: [] + output: [] + seed: '' +- input: [0] + output: [0] + seed: '' +- input: [255] + output: [255] + seed: '' +- input: [4, 6, 2, 6, 1, 4, 6, 2, 1, 5] + output: [1, 6, 4, 1, 6, 6, 2, 2, 4, 5] + seed: '' +- input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] + output: [4, 7, 10, 13, 3, 1, 2, 9, 12, 6, 11, 8, 5] + seed: '' +- input: [65, 6, 2, 6, 1, 4, 6, 2, 1, 5] + output: [1, 6, 65, 1, 6, 6, 2, 2, 4, 5] + seed: '' +- input: [] + output: [] + seed: 4kn4driuctg8 +- input: [0] + output: [0] + seed: 4kn4driuctg8 +- input: [255] + output: [255] + seed: 4kn4driuctg8 +- input: [4, 6, 2, 6, 1, 4, 6, 2, 1, 5] + output: [6, 4, 2, 5, 4, 2, 6, 6, 1, 1] + seed: 4kn4driuctg8 +- input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] + output: [13, 1, 9, 8, 3, 10, 6, 2, 5, 12, 11, 4, 7] + seed: 4kn4driuctg8 +- input: [65, 6, 2, 6, 1, 4, 6, 2, 1, 5] + output: [6, 65, 2, 5, 4, 2, 6, 6, 1, 1] + seed: 4kn4driuctg8 +- input: [] + output: [] + seed: ytre1p +- input: [0] + output: [0] + seed: ytre1p +- input: [255] + output: [255] + seed: ytre1p +- input: [4, 6, 2, 6, 1, 4, 6, 2, 1, 5] + output: [6, 2, 5, 1, 6, 4, 1, 2, 4, 6] + seed: ytre1p +- input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] + output: [3, 8, 10, 4, 7, 11, 6, 1, 2, 5, 13, 9, 12] + seed: ytre1p +- input: [65, 6, 2, 6, 1, 4, 6, 2, 1, 5] + output: [6, 2, 5, 1, 6, 4, 1, 2, 65, 6] + seed: ytre1p +- input: [] + output: [] + seed: mytobcffnkvj +- input: [0] + output: [0] + seed: mytobcffnkvj +- input: [255] + output: [255] + seed: mytobcffnkvj +- input: [4, 6, 2, 6, 1, 4, 6, 2, 1, 5] + output: [5, 6, 2, 1, 6, 4, 6, 4, 1, 2] + seed: mytobcffnkvj +- input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] + output: [12, 4, 11, 6, 13, 10, 9, 2, 3, 7, 8, 1, 5] + seed: mytobcffnkvj +- input: [65, 6, 2, 6, 1, 4, 6, 2, 1, 5] + output: [5, 6, 2, 1, 6, 65, 6, 4, 1, 2] + seed: mytobcffnkvj +- input: [] + output: [] + seed: myzu3g7evxp5nkvj +- input: [0] + output: [0] + seed: myzu3g7evxp5nkvj +- input: [255] + output: [255] + seed: myzu3g7evxp5nkvj +- input: [4, 6, 2, 6, 1, 4, 6, 2, 1, 5] + output: [6, 2, 6, 5, 4, 4, 1, 6, 2, 1] + seed: myzu3g7evxp5nkvj +- input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] + output: [10, 12, 13, 3, 7, 11, 2, 4, 9, 8, 6, 5, 1] + seed: myzu3g7evxp5nkvj +- input: [65, 6, 2, 6, 1, 4, 6, 2, 1, 5] + output: [6, 2, 6, 5, 65, 4, 1, 6, 2, 1] + seed: myzu3g7evxp5nkvj +- input: [] + output: [] + seed: xdpli1jsx5xb +- input: [0] + output: [0] + seed: xdpli1jsx5xb +- input: [255] + output: [255] + seed: xdpli1jsx5xb +- input: [4, 6, 2, 6, 1, 4, 6, 2, 1, 5] + output: [6, 2, 4, 1, 2, 6, 5, 1, 6, 4] + seed: xdpli1jsx5xb +- input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] + output: [11, 8, 12, 9, 2, 1, 10, 4, 13, 5, 7, 3, 6] + seed: xdpli1jsx5xb +- input: [65, 6, 2, 6, 1, 4, 6, 2, 1, 5] + output: [6, 2, 65, 1, 2, 6, 5, 1, 6, 4] + seed: xdpli1jsx5xb +- input: [] + output: [] + seed: oab3mbb3xe8qsx5xb +- input: [0] + output: [0] + seed: oab3mbb3xe8qsx5xb +- input: [255] + output: [255] + seed: oab3mbb3xe8qsx5xb +- input: [4, 6, 2, 6, 1, 4, 6, 2, 1, 5] + output: [2, 5, 1, 6, 1, 2, 6, 6, 4, 4] + seed: oab3mbb3xe8qsx5xb +- input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] + output: [5, 13, 9, 7, 11, 10, 12, 2, 6, 8, 3, 1, 4] + seed: oab3mbb3xe8qsx5xb +- input: [65, 6, 2, 6, 1, 4, 6, 2, 1, 5] + output: [2, 5, 1, 6, 1, 2, 6, 6, 65, 4] + seed: oab3mbb3xe8qsx5xb