liana/src/random.rs
Antoine Poinsot e28010915f
lianad: update rust-miniscript (and rust-bitcoin) dependencies
Rust-bitcoin, that we use through rust-miniscript, has seen plenty of
breaking changes in the latest version. I've tried to keep the necessary
changes here minimal, still it had to be a single commit to keep it
hygienic. But i'll try to summarize the main things here. Tobin also
wrote a guide about the release at
https://rust-bitcoin.org/blog/release-0.30.0/.

The most verbose change in this commit is probably due to the `Address`
type overhaul. It's overengineered if you ask me but hey here we are. I
tried to keep network validation in commands, and otherwise passing
around unchecked addresses (to avoid having to pass around a global
state between our various components).

Another non-obvious change was changes in hash types upstream and the
removal of `ToHex`, forcing us to get the hex representation of a txid
through its `Display` implementation. It is however displayed backward
in this case ("little-endian" if you will), and we need a regular hex
encoding for some queries to the database. We needed to make sure we
didn't implement any silent bug here.

The rest (Script type changes, PSBT serialization updates, ..) is
probably self-explanatory.
2023-07-13 09:56:09 +02:00

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(&timestamp.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();
//}
//}
}