diff --git a/CHANGELOG.md b/CHANGELOG.md index c07ed86..17b2b7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,5 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added a simple monobit test. +- Added a block frequency test. [Unreleased]: https://git.chir.rs/ProcyOS/rand_testsuite diff --git a/Cargo.lock b/Cargo.lock index 7fc2810..2c01bb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "extra-math" +version = "0.1.0" +source = "git+https://git.chir.rs/ProcyOS/extra_math.git#38d6abf85718d053f45c6b77c88607f245be55e0" +dependencies = [ + "libm", + "num-traits", +] + [[package]] name = "funty" version = "2.0.0" @@ -139,6 +148,7 @@ name = "rand_testsuite" version = "0.1.0" dependencies = [ "bitvec", + "extra-math", "libm", "num-traits", "rand", diff --git a/Cargo.nix b/Cargo.nix index 6d7ea98..58eaf29 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -22,7 +22,7 @@ args @ { workspaceSrc, ignoreLockHash, }: let - nixifiedLockHash = "0c97699175df8652eb6708b342caa7bcc3794d5a6bd83ecc4362d8cf3b262349"; + nixifiedLockHash = "a2b590ebe54f290db87e552bf935f4bb11098b9ba02841e8ed499aad10c57419"; workspaceSrc = if args.workspaceSrc == null then ./. @@ -113,6 +113,25 @@ in }; }); + "git+https://git.chir.rs/ProcyOS/extra_math.git".extra-math."0.1.0" = overridableMkRustCrate (profileName: rec { + name = "extra-math"; + version = "0.1.0"; + registry = "git+https://git.chir.rs/ProcyOS/extra_math.git"; + src = fetchCrateGit { + url = "https://git.chir.rs/ProcyOS/extra_math.git"; + name = "extra-math"; + version = "0.1.0"; + rev = "38d6abf85718d053f45c6b77c88607f245be55e0"; + }; + features = builtins.concatLists [ + ["default"] + ]; + dependencies = { + libm = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".libm."0.2.8" {inherit profileName;}).out; + num_traits = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-traits."0.2.19" {inherit profileName;}).out; + }; + }); + "registry+https://github.com/rust-lang/crates.io-index".funty."2.0.0" = overridableMkRustCrate (profileName: rec { name = "funty"; version = "2.0.0"; @@ -332,6 +351,7 @@ in ]; dependencies = { bitvec = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".bitvec."1.0.1" {inherit profileName;}).out; + extra_math = (rustPackages."git+https://git.chir.rs/ProcyOS/extra_math.git".extra-math."0.1.0" {inherit profileName;}).out; libm = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".libm."0.2.8" {inherit profileName;}).out; num_traits = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".num-traits."0.2.19" {inherit profileName;}).out; ${ diff --git a/Cargo.toml b/Cargo.toml index 3795b40..eb6c99a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ categories = ["development-tools::testing", "no-std", "no-std::no-alloc"] [dependencies] bitvec = { version = "1.0.1", default-features = false } +extra-math = { git = "https://git.chir.rs/ProcyOS/extra_math.git", version = "0.1.0" } libm = "0.2.8" num-traits = { version = "0.2.19", default-features = false, features = [ "libm", diff --git a/src/tests.rs b/src/tests.rs index e3c898f..dd2a13f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,5 +1,6 @@ //! All supported tests are available in this module. +mod block_frequency; mod monobit; use bitvec::prelude::{BitOrder, BitSlice, BitStore}; diff --git a/src/tests/block_frequency.rs b/src/tests/block_frequency.rs new file mode 100644 index 0000000..5fb0361 --- /dev/null +++ b/src/tests/block_frequency.rs @@ -0,0 +1,65 @@ +//! Block Frequency Test + +use bitvec::prelude::{BitOrder, BitSlice, BitStore}; +use extra_math::gamma::Gamma; +#[allow(unused_imports, reason = "redundant in std use cases")] +use num_traits::Float; + +use crate::RandomTest; + +/// The Block Frequency Test splits the input data into multiple blocks, counts the frequency 1s and 0s within these blocks, and determines whether these are distributed well. +/// +/// The amount of blocks is chosen such that a block is at least 1% of the size of the total input, or at least 20. +/// +/// This test is defined in the [NIST Special Publication 800-22](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-22r1a.pdf). + +#[derive(Copy, Clone, Debug)] +pub struct BlockFrequency; + +impl RandomTest for BlockFrequency { + const RECOMMENDED_BIT_SIZE: usize = 100; + + const MINIMUM_BIT_SIZE: usize = 0; + + fn evaluate(&self, bs: &BitSlice) -> Option { + let block_size = (bs.len() / 100).max(20); + let block_count = bs.len() / block_size; + let mut statistic = 0.0; + for i in 0..block_count { + let one_count: usize = bs[i * block_size..(i + 1) * block_size] + .into_iter() + .map(|v| if *v { 1 } else { 0 }) + .sum(); + let pi = (one_count as f64) / (block_size as f64); + statistic += (pi - 0.5).powi(2); + } + statistic *= (4 * block_size) as f64; + Some(Gamma::upper_gamma_regularized( + block_size as f64 / 2.0, + statistic / 2.0, + )) + } +} + +#[cfg(test)] +mod tests { + use bitvec::prelude::*; + + use crate::RandomTest; + + use super::BlockFrequency; + + #[test] + fn block_frequency_test_vector() { + let epsilon = bitvec![ + 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, + 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, + 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0 + ]; + let res = BlockFrequency.evaluate(epsilon.as_bitslice()); + assert!(res.is_some()); + let res = res.unwrap(); + assert!(res > 0.01, "Data should be evaluated as random"); + } +}