keyfork-mnemonic-generate: make bin into lib with bin
This commit is contained in:
parent
ff351b12a9
commit
76d369a6d5
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<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");
|
||||
}
|
||||
}
|
||||
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::<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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue