Compare commits

...

10 commits

Author SHA1 Message Date
randogoth
e0f244281d Add migration notice 2026-09-10 09:57:27 +02:00
randogoth
249a16536a iOS static lib compatible 2025-08-27 21:40:35 +03:00
randogoth
800d58ea89 fix 2025-01-18 20:24:50 +02:00
randogoth
cb9c346533 typo 2025-01-18 14:44:42 +02:00
randogoth
8ac9e78057 license information 2025-01-18 14:39:22 +02:00
randogoth
e797669fba test files 2025-01-18 14:24:27 +02:00
randogoth
2d6b61b6cf fixes and a PRNG 2025-01-18 14:22:17 +02:00
randogoth
b4c2e5ea9b readme, optimizations 2025-01-17 22:08:46 +02:00
randogoth
f21804ffaf added C and Python bindings 2025-01-17 18:50:37 +02:00
randogoth
dcc612fc98 renamed uncorrelation 2025-01-17 18:49:52 +02:00
29 changed files with 1294 additions and 162 deletions

109
Cargo.lock generated
View file

@ -77,6 +77,18 @@ dependencies = [
"wasi",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "indoc"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]]
name = "kolmogorov_smirnov"
version = "1.1.0"
@ -108,6 +120,15 @@ dependencies = [
"rawpointer",
]
[[package]]
name = "memoffset"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.8.3"
@ -183,12 +204,19 @@ dependencies = [
"libm",
]
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "onod3000"
version = "0.1.0"
dependencies = [
"flate2",
"kolmogorov_smirnov",
"pyo3",
"statrs",
]
@ -198,6 +226,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "portable-atomic"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
@ -216,6 +250,69 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "pyo3"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc"
dependencies = [
"cfg-if",
"indoc",
"libc",
"memoffset",
"once_cell",
"portable-atomic",
"pyo3-build-config",
"pyo3-ffi",
"pyo3-macros",
"unindent",
]
[[package]]
name = "pyo3-build-config"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7"
dependencies = [
"once_cell",
"target-lexicon",
]
[[package]]
name = "pyo3-ffi"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d"
dependencies = [
"libc",
"pyo3-build-config",
]
[[package]]
name = "pyo3-macros"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
"quote",
"syn",
]
[[package]]
name = "pyo3-macros-backend"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4"
dependencies = [
"heck",
"proc-macro2",
"pyo3-build-config",
"quote",
"syn",
]
[[package]]
name = "quote"
version = "1.0.38"
@ -363,6 +460,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "target-lexicon"
version = "0.12.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "typenum"
version = "1.17.0"
@ -375,6 +478,12 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unindent"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"

View file

@ -3,7 +3,24 @@ name = "onod3000"
version = "0.1.0"
edition = "2021"
[lib]
name = "onod3000"
crate-type = ["cdylib", "staticlib", "lib"]
[dependencies]
flate2 = "1.0.35"
kolmogorov_smirnov = "1.1.0"
pyo3 = {version = "0.23.4", optional = true}
statrs = "0.18.0"
[features]
python = ["dep:pyo3"]
[[bin]]
name="onod3000"
file="src/bin/onod3000.rs"
[profile.release]
lto = true
codegen-units = 1
panic = "abort"

26
LICENSE Normal file
View file

@ -0,0 +1,26 @@
MIT License
Copyright (c) 2025 Tobias Raayoni Last (@randogoth)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
This project is a Rust port of the original Java implementation by Paul Uszak.
Original work is licensed under the terms specified in the respective file header.

18
include/onod3000.h Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
// Returns true on success. Writes three doubles: obs, z, p
bool onod_run(const char* test,
const uint8_t* samples,
size_t len,
double* result /* length >= 3 */);
#ifdef __cplusplus
}
#endif

201
readme.md Normal file
View file

@ -0,0 +1,201 @@
> **Migrated to [code.randogoth.com/randogoth/onod3000](https://code.randogoth.com/randogoth/onod3000)**
```
                        ____                   
                   |\   ` //  /\\   /\\   /\\  
                    \\   //  || || || || || || 
 /'\\ \\/\\  /'\\  / \\  \\  || || || || || || 
|| || || || || || || ||   )) || || || || || || 
|| || || || || || || ||  //  || || || || || || 
\\,/  \\ \\ \\,/   \\/  /'    \\/   \\/   \\/  
                                               
Rust port of Paul Uszak's new randomness testing suite for TRNG Makers
generating in the sub 1 MB space. Successor to John Walker’s venerable `ent`.
Pipe binary entropy data into the `onod3000` command line tool or call it
with `-f <filename>` to load entropy from a file.
```
`Onod3000` provides multiple randomness tests, each outputting a statistic, a z-score andd a p-value to assess the conformity of the data to random behavior.
## Testing Methodology
- **Null Hypothesis**: The data conforms to random behavior.
- **P-Value Interpretation**: Low p-values (< 0.01) indicate a significant deviation from randomness.
All tests have been carefully ported from [Paul Uszak's Java code](http://www.reallyreallyrandom.com/gitbucketlabhub/) (ent3000-0.6.0-beta). Java libraries were substituted with Rust crate equivalents when possible. The suite includes the following tests:
- **Monobit Test**: Evaluates the balance of 0s and 1s.
- **Chi-Square Tests**: Tests the uniformity of bits and bytes.
- **MeanByte Test**: Checks if the mean byte value aligns with expected randomness.
- **Kolmogorov-Smirnov (KS) Test**: Assesses uniformity of data distribution.
- **Pi Test**: Uses Monte Carlo methods to approximate π.
- **Shells Test**: Analyzes distances in 3D space.
- **Gaps Test**: Measures gaps between occurrences of a specific value.
- **Avalanche Test**: Analyzes bit-level changes in data chunks.
- **Runs and RunUps Tests**: Checks for sequential patterns in data.
- **Prediction Test**: Assesses the predictability of next bits.
- **UnCorrelation Test**: Evaluates correlation between shifted data.
More details on some individual tests [available here](http://www.reallyreallyrandom.com/ent3000/the-tests/index.html)
### Sample Output
```bash
cat /dev/random | head -c 259200 | onod3000
Testing 259200 bytes from stdin.
--------------------------------------------------------
Randomness Test Value Z-Score P-Value Pass
--------------------------------------------------------
Shannon 7.999 -0.3669 0.7137 ✅
Monobit 0.500 0.9000 0.3681 ✅
ChiBit 4.555 -0.8612 0.8039 ✅
ChiByte 236.288 -0.8286 0.7939 ✅
MeanByte 127.312 -1.2970 0.1946 ✅
Compression 1.000 0.0868 0.9308 ✅
Kolm.-Smirnov 0.004 2.0722 0.0272 ✅
Pi 3.139 -1.3171 0.1878 ✅
Shells 33.593 -0.0493 0.4874 ✅
Gaps 7.402 -0.3767 0.5954 ✅
Avalanche 80.006 0.0009 0.9993 ✅
Runs 129288.000 0.8208 0.4118 ✅
RunUps 2.647 1.1645 0.1038 ✅
Prediction 0.500 -0.0255 0.9796 ✅
UnCorrelation -0.001 -0.3725 0.7096 ✅
--------------------------------------------------------
15/15 tests passed.
--------------------------------------------------------
```
## Rust Library
```rust
use onod3000::Onod;
fn main() {
let data = vec![...]; // Your binary data
let p_value = Onod::monobit(&data);
println!("Monobit Test P-Value: {:.4}", p_value);
}
```
## Bindings
The library ships with FFI and optional Python bindings so it can be used as C or Python library.
### Python
Install using `maturin`:
```bash
maturin build --release --features python
```
## Comparison
To test the implementation three files of binary random data have been analyzed using the original Java program (version 0.6.0-beta) and this Rust port, expecting the same p-values. The test files can be found in the test folder.
### Testing 1024 bytes from test1.bin (/dev/random)
Test | Java ent3000 | Rust onod3000 | Note |
------------- | ------------ | ------------- | ------------- |
Shannon | N/A | 0.0000 | |
Monobit | 0.6746 | 0.6746 | |
ChiBit | 0.9299 | 0.9299 | |
ChiByte | 0.9819 | 0.9819 | |
MeanByte | 0.7964 | 0.7964 | |
Compression | N/A | 0.0146 | |
KS | 0.5313 | 0.2476 | Mismatch |
Pi | 0.3784 | 0.3784 | |
Shells | 0.7559 | 0.7559 | |
Gaps | 0.0000 | 0.0000 | |
Avalanche | 0.8794 | 0.8794 | |
Runs | 0.0850 | 0.0850 | |
RunUps | 0.6537 | 0.6537 | |
Prediction | 0.6164 | 0.6164 | |
UnCorrelation | 0.0921 | 0.0918 | Close enough |
Feel free to provide more results, and I'll continue updating the table!
### Testing 259,200 bytes from test2.bin (/dev/random)
Test | Java ent3000 | Rust onod3000 | Note |
------------- | ------------ | ------------- | ------------- |
Shannon | N/A | 0.6853 | |
Monobit | 0.2103 | 0.2103 | |
ChiBit | 0.5590 | 0.5590 | |
ChiByte | 0.3748 | 0.3748 | |
MeanByte | 0.0183 | 0.0183 | |
Compression | N/A | 0.9308 | |
KS | 0.2900 | 0.0178 | Mismatch |
Pi | 0.9765 | 0.9765 | |
Shells | 0.7235 | 0.7235 | |
Gaps | 0.6425 | 0.6425 | |
Avalanche | 0.9995 | 0.9995 | |
Runs | 0.3310 | 0.3310 | |
RunUps | 0.9393 | 0.9393 | |
Prediction | 0.1519 | 0.1519 | |
UnCorrelation | 0.4497 | 0.4497 | |
### Testing 259,200 bytes from test3.bin (Hardware QRNG)
Test | Java ent3000 | Rust onod3000 | Note |
------------- | ------------ | ------------- | ---- |
Shannon | N/A | 0.6978 | |
Monobit | 0.4926 | 0.4926 | |
ChiBit | 0.8428 | 0.8428 | |
ChiByte | 0.5785 | 0.5785 | |
MeanByte | 0.9601 | 0.9601 | |
Compression | N/A | 0.9308 | |
KS | 0.7395 | 0.0441 | Mismatch |
Pi | 0.2806 | 0.2806 | |
Shells | 0.7711 | 0.7711 | |
Gaps | 0.1937 | 0.1937 | |
Avalanche | 0.9932 | 0.9932 | |
Runs | 0.2500 | 0.2500 | |
RunUps | 0.9710 | 0.9710 | |
Prediction | 0.7173 | 0.7173 | |
UnCorrelation | 0.4674 | 0.4674 | |
### Conclusions
Although the implementation of the randomness tests closely follows the original Java logic, differences in the results arise in the Kolmogorov-Smirnov test. The external libraries used for running the test probably differ in their implementation. Also the uniform distribution to test against is provided by the Apache Commons Math library. Since it would be way beyond the scope of this porting project to try to fully match the functionality of the dependencies used we just accept the minor difference. Another issue might be that Java emphasizes predictability and portability, enforcing strict IEEE 754 behavior across platforms. Rust on the other hand prioritizes performance and flexibility, allowing platform-specific optimizations that may deviate slightly from strict IEEE semantics.
These differences are generally negligible for practical purposes and do not affect the overall functionality or
statistical significance of the test.
### Bonus
We implemented a Well Equidistributed Long-period Linear pseudo-random number generator that is used with a random seed derived from an epoch timestamp that is being used as uniform distribution for the KS test.
## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
### License of Original Java Implementation
```
Copyright (c) 2023 Paul Uszak.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
## Credits
This work was made possible with the invaluable assistance of OpenAI's ChatGPT, which provided guidance, debugging help, and inspiration throughout the development process.

View file

@ -1,3 +1,31 @@
// MIT License
// Copyright (c) 2025 Tobias Raayoni Last (@randogoth)
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ---
// This project is a Rust port of the original Java implementation by Paul Uszak.
// Original work is licensed under the terms specified in the respective file header.
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
@ -62,7 +90,7 @@ fn main() -> io::Result<()> {
("Runs", Onod::runs(&input_data)),
("RunUps", Onod::run_ups(&input_data)),
("Prediction", Onod::prediction(&input_data)),
("UnCorrelation", Onod::un_correlation(&input_data)),
("UnCorrelation", Onod::uncorrelation(&input_data)),
];
for (test_name, (observation, z_score, p_value)) in &tests {

58
src/chisquaretest.rs Normal file
View file

@ -0,0 +1,58 @@
// MIT License
// Copyright (c) 2025 Tobias Raayoni Last (@randogoth)
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ---
// This project is a Rust port of the original Java implementation by Paul Uszak.
// Original work is licensed under the terms specified in the respective file header.
use statrs::distribution::{ChiSquared, ContinuousCDF};
pub fn chi_square_test(observed: &[u64], expected: &[f64]) -> f64 {
// Preconditions
if observed.len() != expected.len() || observed.len() < 2 {
panic!("Observed and expected arrays must have the same length and length >= 2.");
}
if expected.iter().any(|&e| e <= 0.0) {
panic!("Expected array must contain only strictly positive values.");
}
// Rescale expected array if necessary
let sum_observed: f64 = observed.iter().map(|&o| o as f64).sum();
let sum_expected: f64 = expected.iter().sum();
let rescaled_expected: Vec<f64> = expected.iter().map(|&e| e * sum_observed / sum_expected).collect();
// Calculate chi-squared statistic
let chi_squared_stat: f64 = observed
.iter()
.zip(rescaled_expected.iter())
.map(|(&o, &e)| (o as f64 - e).powi(2) / e)
.sum();
// Perform chi-squared test
let degrees_of_freedom = observed.len() as f64 - 1.0;
let chi_squared_dist = ChiSquared::new(degrees_of_freedom).expect("Failed to create ChiSquared distribution");
let p_value = 1.0 - chi_squared_dist.cdf(chi_squared_stat);
p_value
}

71
src/ffi.rs Normal file
View file

@ -0,0 +1,71 @@
// MIT License
// Copyright (c) 2025 Tobias Raayoni Last (@randogoth)
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ---
// This project is a Rust port of the original Java implementation by Paul Uszak.
// Original work is licensed under the terms specified in the respective file header.
use std::ffi::{c_char, c_double, CStr};
use crate::Onod;
#[no_mangle]
pub extern "C" fn onod_run(
test: *const c_char,
samples: *const u8,
len: usize,
result: *mut c_double,
) -> bool {
// Check for null pointers
if test.is_null() || samples.is_null() || result.is_null() {
eprintln!("Error: Null pointer passed to onod_run.");
return false;
}
// Convert C string to Rust string
let test = unsafe {
match CStr::from_ptr(test).to_str() {
Ok(s) => s,
Err(_) => {
eprintln!("Error: Invalid UTF-8 string passed to onod_run.");
return false;
}
}
};
// Convert samples to a Rust slice
let samples = unsafe { std::slice::from_raw_parts(samples, len as usize) };
// Call the Rust `run` function
let (obs, z, p) = Onod::run(test, samples);
// Write results to the output buffer
unsafe {
*result.offset(0) = obs;
*result.offset(1) = z;
*result.offset(2) = p;
}
true
}

View file

@ -1,3 +1,63 @@
// MIT License
// Copyright (c) 2025 Tobias Raayoni Last (@randogoth)
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ---
// This project is a Rust port of the original Java implementation by Paul Uszak.
// Original work is licensed under the terms specified in the respective file header.
pub struct Onod;
mod uniformity;
mod uniformity;
pub mod chisquaretest;
pub mod well19937c;
pub mod ffi;
#[cfg(feature = "python")]
pub mod python;
impl Onod {
pub fn run(test: &str, samples: &[u8]) -> (f64, f64, f64) {
match test {
"avalanche" => Onod::avalanche(samples),
"chi_bit" => Onod::chi_bit(samples),
"chi_byte" => Onod::chi_byte(samples),
"compression" => Onod::compression(samples),
"gaps" => Onod::gaps(samples),
"ks" => Onod::ks(samples),
"mean_byte" => Onod::mean_byte(samples),
"monobit" => Onod::monobit(samples),
"pi" => Onod::pi(samples),
"prediction" => Onod::prediction(samples),
"runs" => Onod::runs(samples),
"run_ups" => Onod::run_ups(samples),
"shannon" => Onod::shannon(samples),
"shells" => Onod::shells(samples),
"uncorrelation" => Onod::uncorrelation(samples),
_ => {
eprintln!("Error: Unknown test '{}'", test);
(-1.0, 0.0, 0.0)
}
}
}
}

47
src/python.rs Normal file
View file

@ -0,0 +1,47 @@
// MIT License
// Copyright (c) 2025 Tobias Raayoni Last (@randogoth)
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ---
// This project is a Rust port of the original Java implementation by Paul Uszak.
// Original work is licensed under the terms specified in the respective file header.
use pyo3::prelude::*;
use super::Onod as O;
#[pyclass]
struct Onod;
#[pymethods]
impl Onod {
#[staticmethod]
pub fn run(test: &str, samples: Vec<u8>) -> (f64, f64, f64) {
O::run(test, &samples)
}
}
#[pymodule]
fn onod(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Onod>()?;
Ok(())
}

View file

@ -1,3 +1,28 @@
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use statrs::distribution::{Normal, ContinuousCDF};
use crate::Onod;

View file

@ -1,3 +1,28 @@
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use statrs::distribution::{ChiSquared, ContinuousCDF};
use crate::Onod;

View file

@ -1,3 +1,28 @@
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use std::collections::HashMap;
use statrs::distribution::{ChiSquared, ContinuousCDF};

View file

@ -1,3 +1,28 @@
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use flate2::{write::DeflateEncoder, Compression};
use std::io::Write;
use statrs::distribution::{Normal, ContinuousCDF};

View file

@ -1,3 +1,28 @@
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use statrs::distribution::{ChiSquared, ContinuousCDF};
use crate::Onod;

View file

@ -1,47 +1,101 @@
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use kolmogorov_smirnov::test_f64;
use std::time::{SystemTime, UNIX_EPOCH};
use statrs::distribution::ContinuousCDF;
use crate::Onod;
use crate::well19937c::Well19937c;
impl Onod {
/// KS randomness test
/// Performs the Kolmogorov-Smirnov test to evaluate uniformity of data distribution and returns a p-value.
// Note: This implementation of the Kolmogorov-Smirnov (KS) test differs slightly from the
// Java implementation due to differences in library behavior and floating-point handling.
// The Java implementation (Apache Commons Math) adds random jitter to handle ties in small
// datasets, uses strict inequality for small sample sizes, and applies specific precision
// rules based on the IEEE 754 standard for `double`. The Rust implementation, using the
// `kolmogorov_smirnov` crate, does not add jitter or handle ties in the same way, and
// adheres to the crate's internal handling of floating-point comparisons. These differences
// may result in slight variations in p-values or KS statistics between the two versions.
/// Performs the Kolmogorov-Smirnov test to evaluate the uniformity of data distribution
/// and returns the test statistic (D-statistic), z-score, and p-value.
pub fn ks(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return (-1.0, 0.0, 1.0); // empty data
return (-1.0, 0.0, 1.0); // Invalid input
}
// Normalize the input samples to [0, 1] range
let normalized_samples: Vec<f64> = samples.iter().map(|&x| x as f64 / 255.0).collect();
// Normalize the input samples to [0, 1) range
let mut normalized_samples: Vec<f64> = samples.iter().map(|&x| x as f64 / 255.0).collect();
// Generate a uniform distribution for comparison
let uniform_distribution: Vec<f64> = (0..normalized_samples.len())
.map(|i| i as f64 / (normalized_samples.len() as f64 - 1.0))
.collect();
let mut uniform_distribution: Vec<f64> = Self::generate_uniform_distribution(normalized_samples.len(), Self::get_timestamp_seed());
// Sort both distributions
normalized_samples.sort_by(|a, b| a.partial_cmp(b).unwrap());
uniform_distribution.sort_by(|a, b| a.partial_cmp(b).unwrap());
let debug = false;
// Debugging: Optional print sorted values
if debug {
println!("Sorted Normalized Samples: {:?}", normalized_samples);
println!("Sorted Uniform Distribution: {:?}", uniform_distribution);
}
// Perform the Kolmogorov-Smirnov test
let confidence = 0.01; // Significance level
let confidence = 0.05; // Significance level
let result = test_f64(&normalized_samples, &uniform_distribution, confidence);
// Extract the KS statistic (D-statistic)
let ks_statistic = result.statistic;
// Debugging: Print ECDF differences and max difference (D-statistic)
if debug {
for (i, (&sample, &uniform)) in normalized_samples.iter().zip(&uniform_distribution).enumerate() {
let diff = (sample - uniform).abs();
println!(
"Index: {}, Sample: {:.6}, Uniform: {:.6}, Difference: {:.6}",
i, sample, uniform, diff
);
}
println!("D-Statistic: {:.6}", ks_statistic);
}
// Calculate the z-score
let sample_size = normalized_samples.len() as f64;
let z_score = ks_statistic * sample_size.sqrt();
// Extract the p-value
let p_value = 1.0 - result.reject_probability;
// Calculate the p-value
let p_value = 2.0 * (1.0 - statrs::distribution::Normal::new(0.0, 1.0).unwrap().cdf(z_score.abs()));
(ks_statistic, z_score, p_value)
}
/// Generates a uniform distribution of the same length as the input data.
fn generate_uniform_distribution(len: usize, seed: u32) -> Vec<f64> {
let mut rng = Well19937c::new(seed);
(0..len).map(|_| rng.next_f64()).collect()
}
fn get_timestamp_seed() -> u32 {
let duration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");
// Use seconds or nanoseconds as the seed
(duration.as_secs() as u32) ^ (duration.subsec_nanos())
}
}

View file

@ -1,3 +1,28 @@
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use statrs::distribution::{Normal, ContinuousCDF};
use crate::Onod;

View file

@ -1,3 +1,28 @@
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use statrs::distribution::{Normal, ContinuousCDF};
use crate::Onod;

View file

@ -1,67 +1,82 @@
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
/// Pi randomness test
/// Uses a Monte Carlo simulation to estimate randomness by calculating the approximation of Pi.
use statrs::distribution::{Normal, ContinuousCDF};
use crate::Onod;
impl Onod {
/// Pi randomness test
/// Uses a Monte Carlo simulation to estimate randomness by calculating the approximation of Pi.
/// This implementation of the Pi randomness test closely follows the logic of the original Java implementation.
/// However, minor differences in the results may arise due to the following reasons:
///
/// 1. **Floating-Point Precision**:
/// Rust and Java both use 64-bit floating-point numbers (`double` in Java, `f64` in Rust), but slight differences
/// in their implementations (e.g., rounding modes, intermediate representations) can lead to small deviations.
///
/// 2. **Math Libraries**:
/// Java uses Apache Commons Math for statistical computations, which may implement certain calculations
/// (e.g., Z-scores and normal distribution CDFs) differently compared to the `statrs` crate used in Rust.
///
/// 3. **Bit Accuracy**:
/// The Java implementation notes the significance of bit accuracy in floating-point computations,
/// as defined in the IEEE 754 standard. Differences in handling edge cases (e.g., subnormal values,
/// precision limits) could lead to slight variations.
///
/// These differences are generally negligible for practical purposes and do not affect the overall functionality or
/// statistical significance of the test.
/// Pi randomness test using nalgebra for vectorized operations.
pub fn pi(samples: &[u8]) -> (f64, f64, f64) {
if samples.len() < 4 {
return (-1.0, 0.0, 1.0); // Not enough data
}
if samples.is_empty() {
let normalized_samples: Vec<f32> = get_floats(samples);
if normalized_samples.is_empty() {
return (-1.0, 0.0, 1.0);
}
// Normalize samples to [0.0, 1.0)
let normalized_samples: Vec<f64> = samples.iter().map(|&x| x as f64 / 255.0).collect();
// Initialize variables for summary statistics
let mut sum_y = 0.0;
let mut count = 0.0;
let count = normalized_samples.len() as f64;
// Compute y-values (sqrt(1 - x^2)) and update summary statistics
for &x in &normalized_samples {
let y = (1.0 - x * x).sqrt();
sum_y += y;
count += 1.0;
let y = (1.0 - x.powi(2)).sqrt();
sum_y += y as f64;
}
// Calculate mean of y-values
let mean_y = sum_y / count;
// Calculate the test statistic
let test_statistic = 4.0 * mean_y;
// Calculate variance and standard deviation
let variance = (16.0 / count) * ((2.0 / 3.0) - (std::f64::consts::PI / 4.0).powi(2));
let variance = compute_variance(count);
let std_dev = variance.sqrt();
// Calculate Z-score
let z_score = (test_statistic - std::f64::consts::PI) / std_dev;
// Use normal distribution to calculate p-value
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs()));
(test_statistic, z_score, p_value)
}
}
fn get_floats(samples: &[u8]) -> Vec<f32> {
let mut floats = Vec::new();
for chunk in samples.chunks_exact(4) {
let int_val = i32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
let unsigned_val = (int_val as u32) >> 1; // Discard sign bit
let normalized = unsigned_val as f32 / i32::MAX as f32;
floats.push(normalized);
}
floats
}
fn compute_variance(n: f64) -> f64 {
let term = (2.0 / 3.0) - (std::f64::consts::PI / 4.0).powi(2);
(16.0 / n) * term
}

View file

@ -1,51 +1,91 @@
use statrs::distribution::{Normal, ContinuousCDF};
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use statrs::distribution::{ChiSquared, ContinuousCDF};
/*
* Blatantly copied from David Sexton's battery.
*
* An algorithm is used to predict the value of each byte of the sequence from
* the beginning of the sequence to the end. In a random sequence the
* probability of success of any such algorithm is 1/256. The number of successes
* is counted. A chi-squared statistic is calculated. The degrees-of-freedom is 1.
* The algorithm is as follows: the next byte is predicted to be equal to all the
* previous bytes bitwise XORed together.
*/
use crate::Onod;
impl Onod {
/// Prediction randomness test
/// Evaluates the predictability of the next bit based on current data and returns a p-value.
/// Evaluates the predictability of the next byte based on XORing the previous bytes
/// and returns the total predictions, z-score, and p-value.
pub fn prediction(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return (-1.0, 0.0, 1.0);
if samples.len() < 3 {
return (-1.0, 0.0, 1.0); // Not enough data for meaningful calculation
}
let mut correct_predictions = 0;
let mut total_predictions = 0;
for window in samples.windows(2) {
if let [current, next] = window {
let predicted = if current & 0x01 == 0 { 0 } else { 1 }; // Predict next bit based on LSB
let actual = next & 0x01; // Check LSB of the next byte
let mut prediction = samples[0]; // Start with the first byte
for i in 2..samples.len() {
prediction ^= samples[i - 1]; // XOR all preceding bytes
if predicted == actual {
correct_predictions += 1;
}
total_predictions += 1;
if prediction == samples[i] {
correct_predictions += 1;
}
total_predictions += 1;
}
if total_predictions == 0 {
return (-1.0, 0.0, 1.0); // No predictions possible
}
// Calculate expected and observed frequencies
let expected = vec![
(1.0 / 256.0) * samples.len() as f64, // Probability of correct prediction
(255.0 / 256.0) * samples.len() as f64, // Probability of incorrect prediction
];
let observed = vec![
correct_predictions as f64, // Actual correct predictions
(samples.len() - correct_predictions) as f64, // Actual incorrect predictions
];
// Calculate observed proportion of correct predictions
let observed_proportion = correct_predictions as f64 / total_predictions as f64;
// Calculate chi-squared statistic
let chi_squared_stat: f64 = observed
.iter()
.zip(expected.iter())
.map(|(o, e)| (o - e).powi(2) / e)
.sum();
// Expected proportion for random data
let expected_proportion = 0.5;
let std_dev = (0.5 * 0.5 / total_predictions as f64).sqrt(); // Standard deviation for a binomial distribution
// Use Chi-Squared distribution to calculate p-value
let chi_squared_dist = ChiSquared::new(1.0).unwrap(); // Degrees of freedom = 1
let p_value = 1.0 - chi_squared_dist.cdf(chi_squared_stat);
// Calculate the z-score
let z_score = (observed_proportion - expected_proportion) / std_dev;
// Calculate z-score (optional, for diagnostics)
let mean = 1.0; // Mean of chi-squared distribution
let std_dev = (2.0 as f64).sqrt(); // Standard deviation of chi-squared distribution
let z_score = (chi_squared_stat - mean) / std_dev;
// Use normal distribution to calculate p-value
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs()));
(observed_proportion, z_score, p_value)
(total_predictions as f64, z_score, p_value)
}
}
}

View file

@ -1,3 +1,28 @@
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use statrs::distribution::{Normal, ContinuousCDF};
use crate::Onod;

View file

@ -1,3 +1,28 @@
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use statrs::distribution::{ChiSquared, ContinuousCDF};
use crate::Onod;

View file

@ -1,3 +1,28 @@
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use statrs::distribution::{Normal, ContinuousCDF};
use crate::Onod;

View file

@ -1,101 +1,127 @@
use statrs::distribution::{ChiSquared, ContinuousCDF};
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use crate::Onod;
use crate::chisquaretest::chi_square_test;
impl Onod {
/// Shells randomness test
/// Evaluates the uniformity of distances between identical byte values and returns a p-value.
/// Evaluates the uniformity of distances in a 3D sphere and returns the chi-squared statistic, z-score, and p-value.
pub fn shells(input: &[u8]) -> (f64, f64, f64) {
// Define shell radii (precomputed to ensure equal volumes)
const SHELL_RADII: [f64; 35] = [
1., 0.990384019787941, 0.980577593308067, 0.970571001281035, 0.960353705642329,
0.949914251592996, 0.939240154232372, 0.928317766722556, 0.91713212619864,
0.905666772691187, 0.893903535096568, 0.881822276616739, 0.869400589952457,
0.856613429672063, 0.843432665301749, 0.829826533366243, 0.815758959214771,
0.801188709029197, 0.786068317431936, 0.770342714221672, 0.753947441129154,
0.736806299728077, 0.718828193851318, 0.699902804775202, 0.67989452969576,
0.65863375600835, 0.635903899768996, 0.61142141746576, 0.584803547642573,
0.555513224287824, 0.52275795857471, 0.485285500640517, 0.440911138308369,
0.385171357110836, 0.30571070873288
1., 0.990384019787941, 0.980577593308067,
0.970571001281035, 0.960353705642329, 0.949914251592996,
0.939240154232372, 0.928317766722556, 0.91713212619864,
0.905666772691187, 0.893903535096568, 0.881822276616739,
0.869400589952457, 0.856613429672063, 0.843432665301749,
0.829826533366243, 0.815758959214771, 0.801188709029197,
0.786068317431936, 0.770342714221672, 0.753947441129154,
0.736806299728077, 0.718828193851318, 0.699902804775202,
0.67989452969576, 0.65863375600835, 0.635903899768996,
0.61142141746576, 0.584803547642573, 0.555513224287824,
0.52275795857471, 0.485285500640517, 0.440911138308369,
0.385171357110836, 0.30571070873288,
];
let samples = convert_to_3d_points(input);
if samples.len() < 25000 {
// eprintln!("---------------------------------------------------------------");
// eprintln!("ERROR: Shells test requires at least 25,000 points for statistical validity. Skipping.");
// eprintln!("---------------------------------------------------------------");
return (-1.0, 0.0, 1.0); // Skip the test for small datasets
}
let samples = convert_to_3d_points(&input);
let sphere_radius = SHELL_RADII[0];
let no_shells = SHELL_RADII.len();
// Calculate sphere and cube volume proportions
let num_shells = SHELL_RADII.len();
// Calculate sphere and cube proportions
let cube_side = 2.0 * sphere_radius;
let cube_volume = cube_side.powi(3);
let sphere_volume = (4.0 / 3.0) * std::f64::consts::PI * sphere_radius.powi(3);
let sphere_proportion = sphere_volume / cube_volume; // Theoretical value: π/6
let no_points = samples.len() as f64;
let no_points_per_shell = sphere_proportion * no_points / no_shells as f64;
let mut observed = vec![0u64; no_shells];
let expected: Vec<f64> = vec![no_points_per_shell; no_shells];
let sphere_proportion = sphere_volume / cube_volume;
let num_points = samples.len() as f64;
let num_points_per_shell = sphere_proportion * num_points / num_shells as f64;
// Initialize observed and expected frequencies
let mut observed = vec![0u64; num_shells];
let expected: Vec<f64> = vec![num_points_per_shell; num_shells];
// Assign points to shells
for (x, y, z) in samples {
// Compute radius from origin
let radius = (x.powi(2) + y.powi(2) + z.powi(2)).sqrt();
// Ignore points outside the sphere
// Skip points outside the sphere
if radius > sphere_radius {
continue;
}
// Assign to the correct shell
for j in 1..SHELL_RADII.len() {
for j in 1..num_shells {
if radius > SHELL_RADII[j] {
observed[j - 1] += 1;
break;
}
}
if radius < SHELL_RADII[no_shells - 1] {
observed[no_shells - 1] += 1;
// Assign to the last shell if radius <= SHELL_RADII[num_shells - 1]
if radius < SHELL_RADII[num_shells - 1] {
observed[num_shells - 1] += 1;
}
}
// Perform Chi-Square Test
let chi_squared_stat: f64 = observed.iter()
// Calculate chi-squared statistic
let chi_squared_stat: f64 = observed
.iter()
.zip(expected.iter())
.map(|(&o, &e)| (o as f64 - e).powi(2) / e)
.sum();
let degrees_of_freedom = no_shells as f64 - 1.0;
let chi_squared_dist = ChiSquared::new(degrees_of_freedom).expect("Failed to create ChiSquared distribution");
let p_value = 1.0 - chi_squared_dist.cdf(chi_squared_stat);
// Z-score calculation (standardization of the chi-squared statistic)
let mean = degrees_of_freedom; // Mean of the chi-squared distribution
let std_dev = (2.0 * degrees_of_freedom).sqrt(); // Standard deviation of the chi-squared distribution
// Perform chi-squared test
let degrees_of_freedom = num_shells as f64 - 1.0;
let p_value = chi_square_test(&observed, &expected);
// Calculate z-score
let mean = degrees_of_freedom;
let std_dev = (2.0 * degrees_of_freedom).sqrt();
let z_score = (chi_squared_stat - mean) / std_dev;
// Return the results
(chi_squared_stat, z_score, p_value)
}
}
}
fn convert_to_3d_points(data: &[u8]) -> Vec<(f64, f64, f64)> {
let mut points = Vec::new();
for chunk in data.chunks(3) {
if chunk.len() == 3 {
// Normalize the bytes to [0.0, 1.0) range
let x = chunk[0] as f64 / 255.0;
let y = chunk[1] as f64 / 255.0;
let z = chunk[2] as f64 / 255.0;
points.push((x, y, z));
for chunk in data.chunks(12) {
if chunk.len() == 12 {
let x = u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]) >> 1;
let y = u32::from_be_bytes([chunk[4], chunk[5], chunk[6], chunk[7]]) >> 1;
let z = u32::from_be_bytes([chunk[8], chunk[9], chunk[10], chunk[11]]) >> 1;
points.push((
x as f64 / (i32::MAX as f64),
y as f64 / (i32::MAX as f64),
z as f64 / (i32::MAX as f64),
));
}
}

View file

@ -1,3 +1,28 @@
// This file is a Rust port of the original Java implementation by Paul Uszak.
// Original Java code:
// http://www.reallyreallyrandom.com/gitbucketlabhub/
//
// Copyright (c) 2023 Paul Uszak. Port (C) 2025 by Tobias Raayoni Last (@randogoth)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
use statrs::distribution::{Normal, ContinuousCDF};
use crate::Onod;
@ -6,7 +31,7 @@ impl Onod {
/// UnCorrelation randomness test
/// Computes the Pearson correlation between the sequence and its shifted version, returning a p-value.
pub fn un_correlation(input: &[u8]) -> (f64, f64, f64) {
pub fn uncorrelation(input: &[u8]) -> (f64, f64, f64) {
let samples = input.iter().map(|&x| x as i32).collect::<Vec<i32>>();

87
src/well19937c.rs Normal file
View file

@ -0,0 +1,87 @@
// MIT License
// Copyright (c) 2025 Tobias Raayoni Last (@randogoth)
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// ---
pub struct Well19937c {
state: [u32; 624],
index: usize,
}
impl Well19937c {
/// Creates a new instance of Well19937c with a given seed.
pub fn new(seed: u32) -> Self {
let mut state = [0u32; 624];
state[0] = seed;
for i in 1..624 {
state[i] = 1812433253u32
.wrapping_mul(state[i - 1] ^ (state[i - 1] >> 30))
.wrapping_add(i as u32);
}
Well19937c { state, index: 0 }
}
/// Updates the internal state.
fn twist(&mut self) {
const M: usize = 397;
const MATRIX_A: u32 = 0x9908b0df; // Constant matrix A
const UPPER_MASK: u32 = 0x80000000; // Most significant w-r bits
const LOWER_MASK: u32 = 0x7fffffff; // Least significant r bits
for i in 0..624 {
let x = (self.state[i] & UPPER_MASK) + (self.state[(i + 1) % 624] & LOWER_MASK);
let mut x_a = x >> 1;
if x % 2 != 0 {
x_a ^= MATRIX_A;
}
self.state[i] = self.state[(i + M) % 624] ^ x_a;
}
self.index = 0;
}
/// Generates the next random number in the sequence.
pub fn next_u32(&mut self) -> u32 {
if self.index == 0 {
self.twist();
}
let mut y = self.state[self.index];
self.index = (self.index + 1) % 624;
// Matsumoto-Kurita tempering
y ^= (y << 7) & 0xe46e1700;
y ^= (y << 15) & 0x9b868000;
y
}
/// Generates the next random `f64` in [0, 1).
pub fn next_f64(&mut self) -> f64 {
self.next_u32() as f64 / u32::MAX as f64
}
}

BIN
test/test1.bin Normal file

Binary file not shown.

BIN
test/test2.bin Normal file

Binary file not shown.

BIN
test/test3.bin Normal file

Binary file not shown.