forked from wizardsardine/liana
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrandom.rs
125 lines (109 loc) · 4.08 KB
/
random.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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).to_byte_array())
}
/// 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).to_byte_array())
}
#[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();
//}
//}
}