generated from ProcyOS/rust-template
add block frequency test #3
9 changed files with 147 additions and 25 deletions
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[registries]
|
||||
procyos = { index = "sparse+https://git.chir.rs/api/packages/ProcyOS/cargo/" }
|
|
@ -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
|
||||
|
|
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -32,6 +32,16 @@ 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 = "sparse+https://git.chir.rs/api/packages/ProcyOS/cargo/"
|
||||
checksum = "fa1061c295d9aba46636f4a0b16372ea314fcd6c8dfaf447cc719a6a9dce63aa"
|
||||
dependencies = [
|
||||
"libm",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
|
@ -139,6 +149,7 @@ name = "rand_testsuite"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"extra-math",
|
||||
"libm",
|
||||
"num-traits",
|
||||
"rand",
|
||||
|
|
22
Cargo.nix
22
Cargo.nix
|
@ -22,7 +22,7 @@ args @ {
|
|||
workspaceSrc,
|
||||
ignoreLockHash,
|
||||
}: let
|
||||
nixifiedLockHash = "0c97699175df8652eb6708b342caa7bcc3794d5a6bd83ecc4362d8cf3b262349";
|
||||
nixifiedLockHash = "74300319bd44e956ec1895afc04c903cca3100257d7c627ba18911908b487b47";
|
||||
workspaceSrc =
|
||||
if args.workspaceSrc == null
|
||||
then ./.
|
||||
|
@ -113,6 +113,25 @@ in
|
|||
};
|
||||
});
|
||||
|
||||
"sparse+https://git.chir.rs/api/packages/ProcyOS/cargo/".extra-math."0.1.0" = overridableMkRustCrate (profileName: rec {
|
||||
name = "extra-math";
|
||||
version = "0.1.0";
|
||||
registry = "sparse+https://git.chir.rs/api/packages/ProcyOS/cargo/";
|
||||
src = fetchCrateAlternativeRegistry {
|
||||
index = sparse+https://git.chir.rs/api/packages/ProcyOS/cargo/;
|
||||
name = "extra-math";
|
||||
version = "0.1.0";
|
||||
sha256 = "fa1061c295d9aba46636f4a0b16372ea314fcd6c8dfaf447cc719a6a9dce63aa";
|
||||
};
|
||||
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."sparse+https://git.chir.rs/api/packages/ProcyOS/cargo/".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;
|
||||
${
|
||||
|
|
|
@ -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 = { version = "0.1.0", registry = "procyos" }
|
||||
libm = "0.2.8"
|
||||
num-traits = { version = "0.2.19", default-features = false, features = [
|
||||
"libm",
|
||||
|
|
12
flake.lock
12
flake.lock
|
@ -14,16 +14,16 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1713199118,
|
||||
"narHash": "sha256-MlLdAvk+zXCFUy280sY6LqtykqWXIkKVXo72J7a6HlU=",
|
||||
"owner": "cargo2nix",
|
||||
"lastModified": 1725812914,
|
||||
"narHash": "sha256-xYdN9kbpLScjJGll6IE7Cnnn1UhIMKUwGyrzKjQvPgI=",
|
||||
"owner": "DarkKirb",
|
||||
"repo": "cargo2nix",
|
||||
"rev": "1efb03f2f794ad5eed17e807e858c4da001dbc3e",
|
||||
"rev": "7bb1ee00029ab2e7bd8ba03f5563cdbb998ab6f7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cargo2nix",
|
||||
"ref": "main",
|
||||
"owner": "DarkKirb",
|
||||
"ref": "master",
|
||||
"repo": "cargo2nix",
|
||||
"type": "github"
|
||||
}
|
||||
|
|
16
flake.nix
16
flake.nix
|
@ -12,7 +12,7 @@
|
|||
};
|
||||
|
||||
cargo2nix = {
|
||||
url = github:cargo2nix/cargo2nix/main;
|
||||
url = github:DarkKirb/cargo2nix/master;
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.flake-utils.follows = "flake-utils";
|
||||
inputs.rust-overlay.follows = "rust-overlay";
|
||||
|
@ -36,6 +36,9 @@
|
|||
pkgs = import nixpkgs {
|
||||
inherit system overlays;
|
||||
};
|
||||
registryDownloadPaths = {
|
||||
"sparse+https://git.chir.rs/api/packages/ProcyOS/cargo/" = "https://git.chir.rs/api/packages/ProcyOS/cargo/api/v1/crates";
|
||||
};
|
||||
rustPkgs = pkgs.rustBuilder.makePackageSet {
|
||||
packageFun = import ./Cargo.nix;
|
||||
rustChannel = "nightly";
|
||||
|
@ -45,6 +48,17 @@
|
|||
"rand_testsuite/default"
|
||||
"rand_testsuite/full"
|
||||
];
|
||||
fetchCrateAlternativeRegistry = {
|
||||
index,
|
||||
name,
|
||||
version,
|
||||
sha256,
|
||||
}:
|
||||
pkgs.fetchurl {
|
||||
name = "${name}-${version}.tar.gz";
|
||||
url = "${registryDownloadPaths.${index}}/${name}/${version}/download";
|
||||
inherit sha256;
|
||||
};
|
||||
};
|
||||
in rec {
|
||||
devShells.default = with pkgs;
|
||||
|
|
42
src/tests.rs
42
src/tests.rs
|
@ -1,38 +1,46 @@
|
|||
//! All supported tests are available in this module.
|
||||
|
||||
mod block_frequency;
|
||||
mod monobit;
|
||||
|
||||
use core::f64;
|
||||
|
||||
use bitvec::prelude::{BitOrder, BitSlice, BitStore};
|
||||
pub use block_frequency::*;
|
||||
pub use monobit::*;
|
||||
|
||||
use crate::RandomTest;
|
||||
|
||||
/// This combines all supported tests into one
|
||||
///
|
||||
/// Specifically it returns the lowest metric and stops if it goes below the p-value threshold.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct CombinedRandomTest(f64);
|
||||
pub struct CombinedRandomTest;
|
||||
|
||||
impl CombinedRandomTest {
|
||||
/// Creates a new combined random test
|
||||
pub fn new(p_threshold: f64) -> Self {
|
||||
Self(p_threshold)
|
||||
const fn max_of<const N: usize>(v: [usize; N]) -> usize {
|
||||
let mut m = v[0];
|
||||
let mut i = 1;
|
||||
while i < v.len() {
|
||||
if m < v[i] {
|
||||
m = v[i];
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
m
|
||||
}
|
||||
|
||||
impl RandomTest for CombinedRandomTest {
|
||||
const RECOMMENDED_BIT_SIZE: usize = Monobit::RECOMMENDED_BIT_SIZE;
|
||||
const RECOMMENDED_BIT_SIZE: usize = max_of([
|
||||
Monobit::RECOMMENDED_BIT_SIZE,
|
||||
BlockFrequency::RECOMMENDED_BIT_SIZE,
|
||||
]);
|
||||
|
||||
const MINIMUM_BIT_SIZE: usize = Monobit::MINIMUM_BIT_SIZE;
|
||||
const MINIMUM_BIT_SIZE: usize =
|
||||
max_of([Monobit::MINIMUM_BIT_SIZE, BlockFrequency::MINIMUM_BIT_SIZE]);
|
||||
|
||||
fn evaluate<T: BitStore, O: BitOrder>(&self, bs: &BitSlice<T, O>) -> Option<f64> {
|
||||
Monobit.evaluate(bs)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CombinedRandomTest {
|
||||
fn default() -> Self {
|
||||
Self(0.01)
|
||||
let mut worst_test_score = f64::MAX;
|
||||
worst_test_score = worst_test_score.min(Monobit.evaluate(bs)?);
|
||||
worst_test_score = worst_test_score.min(BlockFrequency.evaluate(bs)?);
|
||||
Some(worst_test_score)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,7 +54,7 @@ mod tests {
|
|||
use super::CombinedRandomTest;
|
||||
let mut rng = rand::thread_rng();
|
||||
assert!(
|
||||
CombinedRandomTest::default()
|
||||
CombinedRandomTest
|
||||
.evaluate_rng(&mut rng)
|
||||
.unwrap_or_default()
|
||||
> 0.01
|
||||
|
|
65
src/tests/block_frequency.rs
Normal file
65
src/tests/block_frequency.rs
Normal file
|
@ -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<T: BitStore, O: BitOrder>(&self, bs: &BitSlice<T, O>) -> Option<f64> {
|
||||
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");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue