diff --git a/keyfork-mnemonic-generate/Cargo.toml b/keyfork-mnemonic-generate/Cargo.toml index ff17b9f..e5ab679 100644 --- a/keyfork-mnemonic-generate/Cargo.toml +++ b/keyfork-mnemonic-generate/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "keyfork-mnemonic-generate" -version = "0.1.0" +version = "0.1.1" description = "A tool to generate BIP-0039 mnemonics." license = "GPL-3.0" repository = "https://git.distrust.co/public/keyfork" diff --git a/keyfork-mnemonic-generate/src/lib.rs b/keyfork-mnemonic-generate/src/lib.rs new file mode 100644 index 0000000..5810b6f --- /dev/null +++ b/keyfork-mnemonic-generate/src/lib.rs @@ -0,0 +1,150 @@ +use std::{ + env, + error::Error, + fs::{read_dir, read_to_string, File}, + io::Read, +}; + +use keyfork_mnemonic_util::{Mnemonic, Wordlist}; + +pub type Result> = std::result::Result; + +/// Usage: keyfork-mnemonic-generate [bitsize: 128 or 256] +/// CHECKS: +/// * If the system is online +/// * If a kernel is running post-BLAKE2 +/// +/// TODO: +/// * --features kitchen-sink: load username; system time's most random, precise bits; hostname; +/// kernel version; other env specific shit into a CSPRNG + +pub struct Entropy(File); + +/// An entropy source +impl Entropy { + pub fn new() -> Result { + let file = File::open("/dev/random")?; + Ok(Self(file)) + } + + pub fn read_into(&mut self, bytes: &mut [u8]) -> Result<()> { + self.0.read_exact(bytes)?; + Ok(()) + } +} + +static WARNING_LINKS: [&str; 1] = + ["https://lore.kernel.org/lkml/20211223141113.1240679-2-Jason@zx2c4.com/"]; + +fn ensure_safe_kernel_version() { + let kernel_version = read_to_string("/proc/version").expect("/proc/version"); + let v = kernel_version + .split(' ') + .nth(2) + .expect("Unable to parse kernel version") + .split('.') + .take(2) + .map(str::parse) + .map(|x| x.expect("Unable to parse kernel version number")) + .collect::>(); + let [major, minor, ..] = v.as_slice() else { + panic!("Unable to determine major and minor: {kernel_version}"); + }; + assert!( + [major, minor] > [&5, &4], + "kernel can't generate clean entropy: {}", + WARNING_LINKS[0] + ); +} + +fn ensure_offline() { + let paths = read_dir("/sys/class/net").expect("Unable to read network interfaces"); + for entry in paths { + let mut path = entry.expect("Unable to read directory entry").path(); + if path + .as_os_str() + .to_str() + .expect("Unable to decode UTF-8 filepath") + .split('/') + .last() + .unwrap() + == "lo" + { + continue; + } + path.push("operstate"); + let isup = read_to_string(&path).expect("Unable to read operstate of network interfaces"); + assert_ne!(isup.trim(), "up", "No network interfaces should be up"); + } +} + +pub fn generate_mnemonic() -> Result { + if !env::vars() + .any(|(name, _)| name == "SHOOT_SELF_IN_FOOT" || name == "INSECURE_HARDWARE_ALLOWED") + { + ensure_safe_kernel_version(); + ensure_offline(); + } + + let bit_size: usize = env::args() + .nth(1) + .unwrap_or(String::from("256")) + .parse() + .expect("Expected integer bit size"); + assert!( + bit_size == 128 || bit_size == 256, + "Only 12 or 24 word mnemonics are supported" + ); + + let mut rng = Entropy::new()?; + let entropy = &mut [0u8; 256 / 8]; + rng.read_into(&mut entropy[..])?; + + let wordlist = Wordlist::default().arc(); + Mnemonic::from_entropy(&entropy[..bit_size / 8], wordlist).map_err(From::from) +} + +#[cfg(test)] +mod tests { + use keyfork_mnemonic_util::{Mnemonic, Wordlist}; + use std::collections::HashSet; + + use super::*; + + #[test] + fn count_to_get_duplicate_words() { + let tests = 100_000; + let mut count = 0.; + let entropy = &mut [0u8; 256 / 8]; + let wordlist = Wordlist::default().arc(); + let mut rng = Entropy::new().unwrap(); + let mut hs = HashSet::::with_capacity(24); + + for _ in 0..tests { + rng.read_into(&mut entropy[..]).unwrap(); + let mnemonic = Mnemonic::from_entropy(&entropy[..256 / 8], wordlist.clone()).unwrap(); + let (words, _) = mnemonic.into_inner(); + hs.clear(); + hs.extend(words); + if hs.len() != 24 { + count += 1.; + } + } + + // NOTE: Birthday problem math is: 0.126532 + // Set values to (about) 1 below, 1 above + // Source: https://en.wikipedia.org/wiki/Birthday_problem + let min = 11.5; + let max = 13.5; + assert!( + count > f64::from(tests) * min / 100., + "{count} probability should be more than {min}%: {}", + count / f64::from(tests) + ); + assert!( + count < f64::from(tests) * max / 100., + "{count} probability should be more than {max}%: {}", + count / f64::from(tests) + ); + } +} diff --git a/keyfork-mnemonic-generate/src/main.rs b/keyfork-mnemonic-generate/src/main.rs index e9ca3fe..055576d 100644 --- a/keyfork-mnemonic-generate/src/main.rs +++ b/keyfork-mnemonic-generate/src/main.rs @@ -1,153 +1,9 @@ -use std::{ - env, - error::Error, - fs::{read_dir, read_to_string, File}, - io::Read, -}; - -use keyfork_mnemonic_util::{Mnemonic, Wordlist}; - -type Result> = std::result::Result; - -/// Usage: keyfork-mnemonic-generate [bitsize: 128 or 256] -/// CHECKS: -/// * If the system is online -/// * If a kernel is running post-BLAKE2 -/// -/// TODO: -/// * --features kitchen-sink: load username; system time's most random, precise bits; hostname; -/// kernel version; other env specific shit into a CSPRNG - -struct Entropy(File); - -/// An entropy source -impl Entropy { - fn new() -> Result { - let file = File::open("/dev/random")?; - Ok(Self(file)) - } - - fn read_into(&mut self, bytes: &mut [u8]) -> Result<()> { - self.0.read_exact(bytes)?; - Ok(()) - } -} - -static WARNING_LINKS: [&str; 1] = - ["https://lore.kernel.org/lkml/20211223141113.1240679-2-Jason@zx2c4.com/"]; - -fn ensure_safe_kernel_version() { - let kernel_version = read_to_string("/proc/version").expect("/proc/version"); - let v = kernel_version - .split(' ') - .nth(2) - .expect("Unable to parse kernel version") - .split('.') - .take(2) - .map(str::parse) - .map(|x| x.expect("Unable to parse kernel version number")) - .collect::>(); - let [major, minor, ..] = v.as_slice() else { - panic!("Unable to determine major and minor: {kernel_version}"); - }; - assert!( - [major, minor] > [&5, &4], - "kernel can't generate clean entropy: {}", - WARNING_LINKS[0] - ); -} - -fn ensure_offline() { - let paths = read_dir("/sys/class/net").expect("Unable to read network interfaces"); - for entry in paths { - let mut path = entry.expect("Unable to read directory entry").path(); - if path - .as_os_str() - .to_str() - .expect("Unable to decode UTF-8 filepath") - .split('/') - .last() - .unwrap() - == "lo" - { - continue; - } - path.push("operstate"); - let isup = read_to_string(&path).expect("Unable to read operstate of network interfaces"); - assert_ne!(isup.trim(), "up", "No network interfaces should be up"); - } -} +use keyfork_mnemonic_generate::*; fn main() -> Result<()> { - if !env::vars() - .any(|(name, _)| name == "SHOOT_SELF_IN_FOOT" || name == "INSECURE_HARDWARE_ALLOWED") - { - ensure_safe_kernel_version(); - ensure_offline(); - } - - let bit_size: usize = env::args() - .nth(1) - .unwrap_or(String::from("256")) - .parse() - .expect("Expected integer bit size"); - assert!( - bit_size == 128 || bit_size == 256, - "Only 12 or 24 word mnemonics are supported" - ); - - let mut rng = Entropy::new()?; - let entropy = &mut [0u8; 256 / 8]; - rng.read_into(&mut entropy[..])?; - - let wordlist = Wordlist::default().arc(); - let mnemonic = Mnemonic::from_entropy(&entropy[..bit_size / 8], wordlist)?; + let mnemonic = generate_mnemonic()?; println!("{mnemonic}"); Ok(()) } - -#[cfg(test)] -mod tests { - use std::collections::HashSet; - - use super::*; - - #[test] - fn count_to_get_duplicate_words() { - let tests = 100_000; - let mut count = 0.; - let entropy = &mut [0u8; 256 / 8]; - let wordlist = Wordlist::default().arc(); - let mut rng = Entropy::new().unwrap(); - let mut hs = HashSet::::with_capacity(24); - - for _ in 0..tests { - rng.read_into(&mut entropy[..]).unwrap(); - let mnemonic = Mnemonic::from_entropy(&entropy[..256 / 8], wordlist.clone()).unwrap(); - let (words, _) = mnemonic.into_inner(); - hs.clear(); - hs.extend(words); - if hs.len() != 24 { - count += 1.; - } - } - - // NOTE: Birthday problem math is: 0.126532 - // Set values to (about) 1 below, 1 above - // Source: https://en.wikipedia.org/wiki/Birthday_problem - let min = 11.5; - let max = 13.5; - assert!( - count > f64::from(tests) * min / 100., - "{count} probability should be more than {min}%: {}", - count / f64::from(tests) - ); - assert!( - count < f64::from(tests) * max / 100., - "{count} probability should be more than {max}%: {}", - count / f64::from(tests) - ); - } -}