126 lines
4.1 KiB
Rust
126 lines
4.1 KiB
Rust
use miniscript::bitcoin::hashes::{sha256, Hash, HashEngine};
|
|
use std::{
|
|
collections::hash_map,
|
|
error, fmt,
|
|
hash::{BuildHasher, Hasher},
|
|
time::{SystemTime, UNIX_EPOCH},
|
|
};
|
|
|
|
#[derive(Debug)]
|
|
pub enum RandomnessError {
|
|
Hardware(String),
|
|
Os(String),
|
|
ContextualInfo(String),
|
|
}
|
|
|
|
impl fmt::Display for RandomnessError {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match self {
|
|
Self::Hardware(s) => write!(f, "Error when getting randomness from hardware: {}", s),
|
|
Self::Os(s) => write!(f, "Error when getting randomness from the OS: {}", s),
|
|
Self::ContextualInfo(s) => write!(f, "Error when getting contextual info: {}", s),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl error::Error for RandomnessError {}
|
|
|
|
// Get some entrop from RDRAND when available.
|
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
fn cpu_randomness() -> Result<Option<[u8; 32]>, RandomnessError> {
|
|
if let Ok(mut rand_gen) = rdrand::RdRand::new() {
|
|
let mut buf = [0; 32];
|
|
rand_gen
|
|
.try_fill_bytes(&mut buf)
|
|
.map_err(|e| RandomnessError::Hardware(e.to_string()))?;
|
|
assert_ne!(buf, [0; 32]);
|
|
Ok(Some(buf))
|
|
} else {
|
|
// Not available.
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
// OS-generated randomness. See https://docs.rs/getrandom/latest/getrandom/#supported-targets
|
|
// (basically this calls `getrandom()` or polls `/dev/urandom` on Linux, `BCryptGenRandom` on
|
|
// Windows, and `getentropy()` / `/dev/random` on Mac.
|
|
fn system_randomness() -> Result<[u8; 32], RandomnessError> {
|
|
let mut buf = [0; 32];
|
|
getrandom::getrandom(&mut buf).map_err(|e| RandomnessError::Os(e.to_string()))?;
|
|
assert_ne!(buf, [0; 32]);
|
|
Ok(buf)
|
|
}
|
|
|
|
// Some more contextual data to try to get at least a slight bit of additional entropy.
|
|
fn additional_data() -> Result<[u8; 32], RandomnessError> {
|
|
let mut engine = sha256::HashEngine::default();
|
|
|
|
let timestamp: u16 = (SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.map_err(|e| RandomnessError::ContextualInfo(e.to_string()))?
|
|
.as_secs()
|
|
% u16::MAX as u64) as u16;
|
|
engine.input(×tamp.to_be_bytes());
|
|
let hasher_number = hash_map::RandomState::new().build_hasher().finish();
|
|
engine.input(&hasher_number.to_be_bytes());
|
|
let pid = std::process::id();
|
|
engine.input(&pid.to_be_bytes());
|
|
// TODO: get some more contextual information
|
|
|
|
Ok(*sha256::Hash::from_engine(engine).as_inner())
|
|
}
|
|
|
|
/// Get 32 random bytes. This is mainly based on OS-provided randomness (`getrandom` or
|
|
/// `/dev/urandom` on Linux, `getentropy` / `/dev/random` on MacOS, and `BCryptGenRandom` on
|
|
/// Windows. In addition some randomness may be taken directly from the CPU if it is
|
|
/// available, and some contextual information are added to the mix as well.
|
|
pub fn random_bytes() -> Result<[u8; 32], RandomnessError> {
|
|
let mut engine = sha256::HashEngine::default();
|
|
|
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
if let Some(bytes) = cpu_randomness()? {
|
|
engine.input(&bytes);
|
|
}
|
|
|
|
engine.input(&system_randomness()?);
|
|
engine.input(&additional_data()?);
|
|
// TODO: add more sources of randomness
|
|
|
|
Ok(*sha256::Hash::from_engine(engine).as_inner())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::collections::HashSet;
|
|
|
|
// This does not test the quality of the randomness but at least sanity checks it's
|
|
// not obviously broken.
|
|
#[test]
|
|
fn randomness_sanity_check() {
|
|
let mut set = HashSet::with_capacity(100);
|
|
|
|
for _ in 0..100 {
|
|
let rand = random_bytes().unwrap();
|
|
assert!(!set.contains(&rand));
|
|
set.insert(rand);
|
|
}
|
|
}
|
|
|
|
// I used this to perform statistical tests of the random generation function using ENT
|
|
// (https://fourmilab.ch/random/).
|
|
//#[test]
|
|
//fn write_to_file() {
|
|
//use std::io::Write;
|
|
//let mut f = std::fs::OpenOptions::new()
|
|
//.write(true)
|
|
//.create(true)
|
|
//.append(true)
|
|
//.open("random_out")
|
|
//.unwrap();
|
|
//for _ in 0..10_000_000 {
|
|
//f.write(&random_bytes().unwrap()).unwrap();
|
|
//}
|
|
//}
|
|
}
|