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