keyfork-mnemonic-generate: split wordlist parsing into its own thing

This commit is contained in:
Ryan Heywood 2023-08-18 01:11:54 -05:00
parent 8ec5dc0dec
commit 8e74c18135
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
1 changed files with 44 additions and 27 deletions

View File

@ -91,17 +91,40 @@ fn ensure_offline() {
} }
} }
// TODO: Can a Mnemonic be formatted using a wordlist, without allocating or without storing the struct Wordlist(Vec<String>);
// entire word list?
// impl Default for Wordlist {
// NOTE: Yes, use a Language and pass a reference to this. fn default() -> Self {
struct Mnemonic { // TODO: English is the only supported language.
pub words: Vec<usize>, let wordlist_file = include_str!("wordlist.txt");
wordlist: Vec<String>, Wordlist(
wordlist_file
.lines()
.skip(1)
.map(|x| x.trim().to_string())
.collect(),
)
}
} }
impl Mnemonic { impl Wordlist {
fn from_entropy(bytes: &[u8]) -> Result<Mnemonic> { fn get_word(&self, word: usize) -> Option<String> {
self.0.get(word).cloned()
}
#[cfg(test)]
fn into_inner(self) -> Vec<String> {
self.0
}
}
struct Mnemonic<'a> {
pub words: Vec<usize>,
wordlist: &'a Wordlist,
}
impl<'a> Mnemonic<'a> {
fn from_entropy(bytes: &[u8], wordlist: &'a Wordlist) -> Result<Mnemonic<'a>> {
let bit_count = bytes.len() * 8; let bit_count = bytes.len() * 8;
let hash = generate_slice_hash(bytes); let hash = generate_slice_hash(bytes);
@ -132,16 +155,15 @@ impl Mnemonic {
.map(|chunk| bitslice_to_usize(chunk.try_into().expect("11 bit chunks"))) .map(|chunk| bitslice_to_usize(chunk.try_into().expect("11 bit chunks")))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let wordlist = build_wordlist();
Ok(Mnemonic { words, wordlist }) Ok(Mnemonic { words, wordlist })
} }
} }
impl Display for Mnemonic { impl<'a> Display for Mnemonic<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut iter = self.words.iter().peekable(); let mut iter = self.words.iter().peekable();
while let Some(word_index) = iter.next() { while let Some(word_index) = iter.next() {
let word = &self.wordlist[*word_index]; let word = self.wordlist.get_word(*word_index).expect("word");
write!(f, "{word}")?; write!(f, "{word}")?;
if iter.peek().is_some() { if iter.peek().is_some() {
write!(f, " ")?; write!(f, " ")?;
@ -157,15 +179,6 @@ fn generate_slice_hash(data: &[u8]) -> Vec<u8> {
hasher.finalize().to_vec() hasher.finalize().to_vec()
} }
fn build_wordlist() -> Vec<String> {
let wordlist_file = include_str!("wordlist.txt");
wordlist_file
.lines()
.skip(1)
.map(|x| x.trim().to_string())
.collect()
}
fn bitslice_to_usize(bitslice: [bool; 11]) -> usize { fn bitslice_to_usize(bitslice: [bool; 11]) -> usize {
let mut index = 0usize; let mut index = 0usize;
for i in 0..11 { for i in 0..11 {
@ -196,7 +209,8 @@ fn main() -> Result<()> {
let entropy = &mut [0u8; 256 / 8]; let entropy = &mut [0u8; 256 / 8];
random_handle.read_exact(&mut entropy[..])?; random_handle.read_exact(&mut entropy[..])?;
let mnemonic = Mnemonic::from_entropy(&entropy[..bit_size / 8])?; let wordlist = Wordlist::default();
let mnemonic = Mnemonic::from_entropy(&entropy[..bit_size / 8], &wordlist)?;
println!("{mnemonic}"); println!("{mnemonic}");
@ -209,7 +223,7 @@ mod tests {
#[test] #[test]
fn has_good_wordlist() { fn has_good_wordlist() {
let wordlist = build_wordlist(); let wordlist = Wordlist::default().into_inner();
assert_eq!( assert_eq!(
wordlist.len(), wordlist.len(),
2usize.pow(11), 2usize.pow(11),
@ -228,7 +242,8 @@ mod tests {
}; };
let hex = hex::decode(hex_.as_str().unwrap()).unwrap(); let hex = hex::decode(hex_.as_str().unwrap()).unwrap();
let mnemonic = Mnemonic::from_entropy(&hex)?; let wordlist = Wordlist::default();
let mnemonic = Mnemonic::from_entropy(&hex, &wordlist)?;
assert_eq!(mnemonic.to_string(), seed.as_str().unwrap()); assert_eq!(mnemonic.to_string(), seed.as_str().unwrap());
} }
@ -240,7 +255,8 @@ mod tests {
let mut random_handle = File::open("/dev/random").unwrap(); let mut random_handle = File::open("/dev/random").unwrap();
let entropy = &mut [0u8; 256 / 8]; let entropy = &mut [0u8; 256 / 8];
random_handle.read_exact(&mut entropy[..]).unwrap(); random_handle.read_exact(&mut entropy[..]).unwrap();
let my_mnemonic = super::Mnemonic::from_entropy(&entropy[..256 / 8]).unwrap(); let wordlist = Wordlist::default();
let my_mnemonic = super::Mnemonic::from_entropy(&entropy[..256 / 8], &wordlist).unwrap();
let their_mnemonic = bip39::Mnemonic::from_entropy(&entropy[..256 / 8]).unwrap(); let their_mnemonic = bip39::Mnemonic::from_entropy(&entropy[..256 / 8]).unwrap();
/* /*
let my_words = my_mnemonic.words.clone(); let my_words = my_mnemonic.words.clone();
@ -252,13 +268,14 @@ mod tests {
#[test] #[test]
fn count_to_get_duplicate_words() { fn count_to_get_duplicate_words() {
let mut count = 0.; let mut count = 0.;
let tests = 10_000; let tests = 100_000;
let mut random_handle = File::open("/dev/random").unwrap(); let mut random_handle = File::open("/dev/random").unwrap();
let entropy = &mut [0u8; 256 / 8]; let entropy = &mut [0u8; 256 / 8];
let wordlist = Wordlist::default();
for _ in 0..tests { for _ in 0..tests {
random_handle.read_exact(&mut entropy[..]).unwrap(); random_handle.read_exact(&mut entropy[..]).unwrap();
// let bits = generate_slice_hash(entropy); // let bits = generate_slice_hash(entropy);
let mnemonic = Mnemonic::from_entropy(&entropy[..256 / 8]).unwrap(); let mnemonic = Mnemonic::from_entropy(&entropy[..256 / 8], &wordlist).unwrap();
let mut words = mnemonic.words.clone(); let mut words = mnemonic.words.clone();
words.sort(); words.sort();
assert_eq!(words.len(), 24); assert_eq!(words.len(), 24);