pub mod cli;
pub mod file_reader;
pub mod separators;
pub mod unicode_normalization_check;
use crate::cli::ListChoice;
use crate::separators::make_separator;
use include_lines::include_lines;
use rand::prelude::*;
use rand::rng;
use rand::seq::IndexedRandom;
pub fn calculate_number_words_needed(
number_of_words: Option<usize>,
minimum_entropy: Option<usize>,
strength_count: u8,
list_length: usize,
) -> usize {
if let Some(number_of_words) = number_of_words {
return number_of_words;
}
const DEFAULT_MINIMUM_ENTROPY: usize = 80;
if strength_count > 0 {
let minimum_entropy = DEFAULT_MINIMUM_ENTROPY + (strength_count as usize) * 20;
return convert_minimum_entropy_to_number_of_words(minimum_entropy, list_length);
}
match minimum_entropy {
Some(minimum_entropy) => {
convert_minimum_entropy_to_number_of_words(minimum_entropy, list_length)
}
None => convert_minimum_entropy_to_number_of_words(DEFAULT_MINIMUM_ENTROPY, list_length),
}
}
pub fn convert_minimum_entropy_to_number_of_words(
minimum_entropy: usize,
list_length: usize,
) -> usize {
let entropy_per_word_from_this_list = (list_length as f64).log2();
(minimum_entropy as f64 / entropy_per_word_from_this_list).ceil() as usize
}
pub fn fetch_list(list_choice: ListChoice) -> &'static [&'static str] {
match list_choice {
ListChoice::Long => &include_lines!("word-lists/orchard-street-long.txt"),
ListChoice::Medium => &include_lines!("word-lists/orchard-street-medium.txt"),
ListChoice::Qwerty => &include_lines!("word-lists/orchard-street-qwerty.txt"),
ListChoice::Alpha => &include_lines!("word-lists/orchard-street-alpha.txt"),
ListChoice::Eff => &include_lines!("word-lists/eff-long.txt"),
ListChoice::Effshort => &include_lines!("word-lists/eff-short-1.txt"),
ListChoice::Mnemonicode => &include_lines!("word-lists/mnemonicode.txt"),
}
}
pub fn generate_a_passphrase<T: AsRef<str> + std::fmt::Display>(
number_of_words_to_put_in_passphrase: usize,
separator: &str,
title_case: bool,
list: &[T], ) -> String {
let mut rng = rng();
let mut passphrase = String::new();
for i in 0..number_of_words_to_put_in_passphrase {
let random_word = if title_case {
make_title_case(&get_random_element(&mut rng, list))
} else {
get_random_element(&mut rng, list)
};
passphrase += &random_word;
if i != number_of_words_to_put_in_passphrase - 1 {
passphrase += &make_separator(&mut rng, separator);
}
}
passphrase.to_string()
}
fn get_random_element<T>(rng: &mut impl Rng, word_list: &[T]) -> String
where
T: std::fmt::Display + AsRef<str>,
{
match word_list.choose(rng) {
Some(word) => word.to_string(),
None => panic!("Couldn't pick a random word"),
}
}
fn make_title_case(s: &str) -> String {
let s = s.to_lowercase();
let mut c = s.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
}
}
#[test]
fn can_make_word_title_case() {
let test_word = "alpha";
assert_eq!(make_title_case(test_word), "Alpha".to_string());
let test_word = "ALPHA";
assert_eq!(make_title_case(test_word), "Alpha".to_string());
let test_word = "aLPHA";
assert_eq!(make_title_case(test_word), "Alpha".to_string());
let test_word = "aLPhA";
assert_eq!(make_title_case(test_word), "Alpha".to_string());
}
pub fn print_entropy(number_of_words: usize, list_length: usize, n_passphrases: usize) {
let passphrase_entropy = (list_length as f64).log2() * number_of_words as f64;
if n_passphrases == 1 {
eprintln!(
"Passphrase has an estimated {:.2} bits of entropy ({} words from a list of {} words)",
passphrase_entropy, number_of_words, list_length,
);
} else {
eprintln!(
"Each passphrase has an estimated {:.2} bits of entropy ({} words from a list of {} words)",
passphrase_entropy, number_of_words, list_length
);
}
}