For an implementation as phone-camera based TRNG we thoroughly tested variants of some of the algorithms above but eventually settled with a different approach:
pub fn pick_and_flip(&self, data: &[u8], low: u8, high: u8, current_frame_index: usize) -> Vec<u8> {
// Extracts the least significant bit (LSB) of each pixel brightness, flipping it based on the frame index.
// Generates entropy by combining these bits into bytes.
let mut entropy = Vec::new();
for &pixel in data {
if (low..=high).contains(&pixel) { // filter bias
let mut lsb = pixel & 1;
if current_frame_index % 2 == 0 { // flip bits
lsb ^= 1;
}
entropy.push(lsb);
}
}
self.bits_to_bytes(&entropy)
}
This really simple solution extracts entropy from an array of 8-bit values (camera frame pixel brightness) by analyzing the least significant bit (LSB) of each value. This process leverages the natural variability in pixel brightness across a frame. Entropy is derived by first examining whether the brightness value of a pixel falls within the range of allowed values, to avoid bias. The LSB of qualifying pixel values is then used to form a bitstream. To optionally avoid correlations the bits of every second array can be flipped.
This even works without covering the camera lens and produces the highest amount of entropy when the image is a neutral background without too much contrast.
This RNG can be tested live in the Android version of the Randonautica app under Games > Camera Entropy Meter where you can also monitor the qualitative evaluation of the randomness live. It will also be implemented as randomness source for randonauting some time this year.
Here is an open Rust library that ports Andy’s original algorithm and the one I mentioned here