added chop & stack method, optimizations
This commit is contained in:
parent
c737460cce
commit
9f3d56c50a
4 changed files with 139 additions and 72 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -19,3 +19,5 @@ Cargo.lock
|
|||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
*.bin
|
||||
|
|
@ -4,4 +4,5 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.39"
|
||||
opencv = "0.93.5"
|
||||
|
|
|
|||
63
README.md
63
README.md
|
|
@ -1,41 +1,50 @@
|
|||
# ocelli: Camera-Based TRNG
|
||||
|
||||
**Ocelli** is a Rust application that uses a camera to generate high-quality random entropy by analyzing pixel intensity differences between consecutive frames.
|
||||
**Ocelli** is a Rust application that generates high-quality entropy using a camera feed. The application supports two entropy generation methods (`get_entropy` and `chop_and_stack`) and optionally applies Van Neumann whitening for enhanced randomness. Generated entropy is saved as a binary file.
|
||||
|
||||
## Features
|
||||
|
||||
- **Camera-Based Entropy**: Leverages grayscale pixel intensity differences to produce entropy bits.
|
||||
- **Shannon Entropy Validation**: Filters entropy data to ensure high randomness quality.
|
||||
- **Van Neumann Whitening**: Optional bias removal for unbiased entropy output.
|
||||
- **Dynamic Frame Handling**: Automatically captures frames based on resolution and entropy requirements.
|
||||
|
||||
## Requirements
|
||||
|
||||
- OpenCV (with Rust bindings)
|
||||
- **Entropy Methods**:
|
||||
- `get_entropy`: Compares pixel values between frames.
|
||||
- `chop_and_stack`: Combines and processes pixel data with reversed rows.
|
||||
- **Van Neumann Whitening**: Optional, enabled via the `-w` flag.
|
||||
- **Shannon Entropy Test**: Ensures the randomness quality of generated entropy.
|
||||
|
||||
## Usage
|
||||
|
||||
Run the program with the desired parameters:
|
||||
|
||||
```bash
|
||||
cargo run -- <entropy_length> <resolution_width> <resolution_height> [-w]
|
||||
```
|
||||
### Parameters:
|
||||
- `<entropy_length>`: Number of bytes of entropy to generate.
|
||||
- `<resolution_width>`: Camera resolution width.
|
||||
- `<resolution_height>`: Camera resolution height.
|
||||
- `-w`: (Optional) Enable Van Neumann whitening for unbiased entropy.
|
||||
|
||||
### Example:
|
||||
Generate 1000 bytes of entropy at 1920x1080 resolution with whitening enabled:
|
||||
```bash
|
||||
cargo run -- 1000 1920 1080 -w
|
||||
cargo run --release -- <entropy_length_in_bytes> <resolution_width> <resolution_height> [-w]
|
||||
```
|
||||
|
||||
---
|
||||
### Example
|
||||
|
||||
```bash
|
||||
cargo run --release -- 1024 640 480 -w
|
||||
```
|
||||
|
||||
This generates 1024 bytes of whitened entropy using a 640x480 resolution.
|
||||
|
||||
## Requirements
|
||||
|
||||
- OpenCV 4.x
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install Rust: https://www.rust-lang.org/tools/install
|
||||
2. Install OpenCV: Follow the [official guide](https://docs.opencv.org/).
|
||||
3. Clone the repository:
|
||||
```bash
|
||||
git clone <repository_url>
|
||||
cd ocelli-entropy-generator
|
||||
```
|
||||
4. Run the application:
|
||||
```bash
|
||||
cargo run --release -- <arguments>
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
- **Hexadecimal Entropy**: Generated entropy is output as a hexadecimal string.
|
||||
- **Processing Time**: Reports how long the entropy generation took.
|
||||
- **Shannon Entropy**: Displays the Shannon entropy of the final output.
|
||||
Generated entropy files are saved in the current directory with a name format:
|
||||
```
|
||||
<method>[_whitened]_YYYYMMDD_HHMMSS.bin
|
||||
```
|
||||
141
src/main.rs
141
src/main.rs
|
|
@ -6,6 +6,9 @@ use std::collections::HashSet;
|
|||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::time::Instant;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use chrono::Local;
|
||||
|
||||
struct Ocelli;
|
||||
|
||||
|
|
@ -38,6 +41,43 @@ impl Ocelli {
|
|||
entropy
|
||||
}
|
||||
|
||||
/// Use the chop & stack method on an array of grayscale values
|
||||
fn chop_and_stack(&self, mut data: Vec<u8>) -> Vec<u8> {
|
||||
// Ensure the input length is divisible by 4 by trimming excess bytes
|
||||
let remainder = data.len() % 4;
|
||||
if remainder != 0 {
|
||||
data.truncate(data.len() - remainder);
|
||||
}
|
||||
|
||||
// Determine the length of each fold
|
||||
let fold_len = data.len() / 4;
|
||||
|
||||
// Split the data into four folds
|
||||
let mut folds: Vec<Vec<u8>> = data
|
||||
.chunks(fold_len)
|
||||
.map(|chunk| chunk.to_vec())
|
||||
.collect();
|
||||
|
||||
// Reverse the second and fourth folds
|
||||
if folds.len() > 1 {
|
||||
folds[1].reverse(); // Reverse the second row
|
||||
}
|
||||
if folds.len() > 3 {
|
||||
folds[3].reverse(); // Reverse the fourth row
|
||||
}
|
||||
|
||||
// Combine the folds by summing corresponding elements and applying modulo 256
|
||||
let mut combined: Vec<u8> = vec![0u8; fold_len];
|
||||
for i in 0..fold_len {
|
||||
combined[i] = ((folds[0][i] as u16
|
||||
+ folds[1][i] as u16
|
||||
+ folds[2][i] as u16
|
||||
+ folds[3][i] as u16) % 256) as u8;
|
||||
}
|
||||
|
||||
combined
|
||||
}
|
||||
|
||||
/// Apply Van Neumann whitening to a vector of entropy bits
|
||||
fn whiten(&self, entropy: &Vec<u8>) -> Vec<u8> {
|
||||
let mut whitened_entropy = Vec::new();
|
||||
|
|
@ -90,17 +130,6 @@ impl Ocelli {
|
|||
})
|
||||
}
|
||||
|
||||
/// Calculates the required number of frames for the desired entropy bytes
|
||||
fn required_frames(&self, bytes: usize, width: usize, height: usize) -> usize {
|
||||
let max_bytes = width * height / 8;
|
||||
|
||||
if max_bytes == 0 {
|
||||
panic!("Invalid resolution: too few pixels to generate entropy.");
|
||||
}
|
||||
|
||||
(bytes + max_bytes - 1) / max_bytes + 1
|
||||
}
|
||||
|
||||
/// Determines if the camera is covered based on the unique grayscale values
|
||||
fn is_covered(&self, grayscale: &[u8], threshold: usize) -> bool {
|
||||
let unique_values: HashSet<_> = grayscale.iter().copied().collect();
|
||||
|
|
@ -115,7 +144,6 @@ fn main() -> opencv::Result<()> {
|
|||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Check if the whitening flag is set
|
||||
let whiten_flag = args.contains(&String::from("-w"));
|
||||
|
||||
let length: usize = args[1].parse().expect("Failed to parse entropy length as a number");
|
||||
|
|
@ -127,7 +155,6 @@ fn main() -> opencv::Result<()> {
|
|||
panic!("Failed to open the camera");
|
||||
}
|
||||
|
||||
// Set camera resolution
|
||||
cam.set(opencv::videoio::CAP_PROP_FRAME_WIDTH, width as f64)?;
|
||||
cam.set(opencv::videoio::CAP_PROP_FRAME_HEIGHT, height as f64)?;
|
||||
|
||||
|
|
@ -140,25 +167,24 @@ fn main() -> opencv::Result<()> {
|
|||
cam.read(&mut frame)?;
|
||||
let mut gray_frame = core::Mat::default();
|
||||
imgproc::cvt_color(&frame, &mut gray_frame, imgproc::COLOR_BGR2GRAY, 0)?;
|
||||
let grayscale_data = gray_frame.data_bytes().expect("Failed to get grayscale data");
|
||||
let grayscale_data = gray_frame.data_bytes().expect("Failed to get grayscale data").to_vec();
|
||||
|
||||
if !ocelli.is_covered(grayscale_data, 50) {
|
||||
println!("Camera is not covered. Stopping...");
|
||||
// return Ok(());
|
||||
}
|
||||
let uncovered = !ocelli.is_covered(&grayscale_data, 50);
|
||||
|
||||
// Start timing
|
||||
let start_time = Instant::now();
|
||||
println!(
|
||||
"Starting entropy generation... Using {}",
|
||||
if uncovered {
|
||||
"chop_and_stack"
|
||||
} else {
|
||||
"get_entropy"
|
||||
}
|
||||
);
|
||||
|
||||
// Calculate required frames
|
||||
let required_frames = ocelli.required_frames(length, width, height);
|
||||
println!("Capturing {} frames to generate {} bytes of entropy...", required_frames, length);
|
||||
|
||||
// Generate entropy
|
||||
let mut total_entropy = Vec::new();
|
||||
let shannon_threshold = 4.0;
|
||||
let mut previous_frame_data = grayscale_data.clone();
|
||||
|
||||
let mut previous_frame_data = grayscale_data.to_vec();
|
||||
let start_time = Instant::now();
|
||||
|
||||
while total_entropy.len() < length {
|
||||
cam.read(&mut frame)?;
|
||||
|
|
@ -166,42 +192,71 @@ fn main() -> opencv::Result<()> {
|
|||
imgproc::cvt_color(&frame, &mut gray_frame, imgproc::COLOR_BGR2GRAY, 0)?;
|
||||
let current_frame_data = gray_frame.data_bytes().expect("Failed to get grayscale data").to_vec();
|
||||
|
||||
let entropy = ocelli.get_entropy(¤t_frame_data, &previous_frame_data);
|
||||
let shannon_entropy = ocelli.shannon(&entropy);
|
||||
let mut entropy: Vec<u8> = Vec::new();
|
||||
let mut shannon_entropy = 0.0;
|
||||
|
||||
if uncovered {
|
||||
// Process with chop_and_stack
|
||||
entropy = ocelli.chop_and_stack(current_frame_data.clone());
|
||||
|
||||
} else {
|
||||
// Process with get_entropy
|
||||
entropy = ocelli.get_entropy(¤t_frame_data, &previous_frame_data);
|
||||
|
||||
previous_frame_data = current_frame_data;
|
||||
}
|
||||
|
||||
if whiten_flag {
|
||||
entropy = ocelli.whiten(&entropy);
|
||||
}
|
||||
|
||||
shannon_entropy = ocelli.shannon(&entropy);
|
||||
|
||||
if shannon_entropy >= shannon_threshold {
|
||||
|
||||
total_entropy.extend(entropy);
|
||||
|
||||
} else {
|
||||
println!(
|
||||
"Rejected entropy array (Shannon entropy: {:.3}). Retrying...",
|
||||
"Rejected stacked entropy array (Shannon entropy: {:.3}). Retrying...",
|
||||
shannon_entropy
|
||||
);
|
||||
}
|
||||
|
||||
previous_frame_data = current_frame_data;
|
||||
}
|
||||
|
||||
if whiten_flag {
|
||||
total_entropy = ocelli.whiten(&total_entropy);
|
||||
println!(
|
||||
"Collected {} of {} bytes of entropy...",
|
||||
total_entropy.len(),
|
||||
length
|
||||
);
|
||||
}
|
||||
|
||||
// Convert entropy to hex string
|
||||
let entropy_hex = total_entropy
|
||||
.iter()
|
||||
.take(length)
|
||||
.map(|byte| format!("{:02x}", byte))
|
||||
.collect::<String>();
|
||||
|
||||
println!("Generated entropy (hex): {}", entropy_hex);
|
||||
// let entropy_hex = total_entropy
|
||||
// .iter()
|
||||
// .take(length)
|
||||
// .map(|byte| format!("{:02x}", byte))
|
||||
// .collect::<String>();
|
||||
// println!("Generated entropy (hex): {}", entropy_hex);
|
||||
|
||||
let total_shannon_entropy = ocelli.shannon(&total_entropy);
|
||||
|
||||
// End timing
|
||||
let elapsed_time = start_time.elapsed();
|
||||
println!(
|
||||
"Process completed in {:.3} seconds.\nShannon Entropy {:.3}.",
|
||||
elapsed_time.as_secs_f64(), total_shannon_entropy
|
||||
elapsed_time.as_secs_f64(),
|
||||
total_shannon_entropy
|
||||
);
|
||||
|
||||
// Save the generated entropy to a binary file
|
||||
let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string();
|
||||
let method = if uncovered { "chop_and_stack" } else { "get_entropy" };
|
||||
let whitened = if whiten_flag { "_whitened" } else { "" };
|
||||
let filename = format!("{}{}_{}.bin", method, whitened, timestamp);
|
||||
|
||||
let mut file = File::create(&filename).expect("Failed to create file");
|
||||
file.write_all(&total_entropy).expect("Failed to write data to file");
|
||||
|
||||
println!("Entropy saved to file: {}", filename);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue