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