keyfork-mnemonic-generate: make bin into lib with bin

This commit is contained in:
Ryan Heywood 2023-09-11 23:55:01 -05:00
parent ff351b12a9
commit 76d369a6d5
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
3 changed files with 153 additions and 147 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "keyfork-mnemonic-generate" name = "keyfork-mnemonic-generate"
version = "0.1.0" version = "0.1.1"
description = "A tool to generate BIP-0039 mnemonics." description = "A tool to generate BIP-0039 mnemonics."
license = "GPL-3.0" license = "GPL-3.0"
repository = "https://git.distrust.co/public/keyfork" repository = "https://git.distrust.co/public/keyfork"

View File

@ -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<T, E = Box<dyn Error>> = std::result::Result<T, E>;
/// 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<Self> {
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::<Vec<u32>>();
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<Mnemonic> {
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::<usize>::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)
);
}
}

View File

@ -1,153 +1,9 @@
use std::{ use keyfork_mnemonic_generate::*;
env,
error::Error,
fs::{read_dir, read_to_string, File},
io::Read,
};
use keyfork_mnemonic_util::{Mnemonic, Wordlist};
type Result<T, E = Box<dyn Error>> = std::result::Result<T, E>;
/// 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<Self> {
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::<Vec<u32>>();
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");
}
}
fn main() -> Result<()> { fn main() -> Result<()> {
if !env::vars() let mnemonic = generate_mnemonic()?;
.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)?;
println!("{mnemonic}"); println!("{mnemonic}");
Ok(()) 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::<usize>::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)
);
}
}