rusty-diceware

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit f0d716b591bb6bc3d0a7c7d58730c4210dcd7ff0
parent c8350320dbdb877ad4a155776da15e5436c3dafe
Author: Yuval Langer <yuvallangerontheroad@gmail.com>
Date:   Sun, 18 Sep 2022 18:01:20 +0300

Add physical dice rolls flag `-r` / `--dicerolls`.

Diffstat:
MCHANGELOG.md | 10++++++++++
MCargo.lock | 2+-
MCargo.toml | 2+-
MREADME.md | 24++++++++++++++++++------
Msrc/bin/diceware.rs | 224+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/lib.rs | 6+++---
6 files changed, 183 insertions(+), 85 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -1,3 +1,13 @@ +## v0.5.1 + +* Add physical dice roll input flag (`-r` / `--dicerolls`). Accepts input from standard input. + +## v0.5.0 + +* Add the EFF wordlists as shown in <https://www.eff.org/dice>. +* Make EFF Long Wordlist the default. +* Remove the `--beale` / `--reinhold` / `--minilock` flags. + ## v0.4.0 * Add the `-l` / `--wordlist` option. diff --git a/Cargo.lock b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "diceware" -version = "0.5.0" +version = "0.5.1" dependencies = [ "getopts", "rand", diff --git a/Cargo.toml b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "diceware" -version = "0.5.0" +version = "0.5.1" authors = ["Yuval Langer <yuvallangerontheroad@gmail.com>"] license = "AGPL-3.0" repository = "https://gitlab.com/yuvallanger/rusty-diceware" diff --git a/README.md b/README.md @@ -15,15 +15,18 @@ Inspired by the great passphrase generating solution [Diceware][diceware] ([Wayb Usage: diceware [options] Options: - -h, --help this help message - -e, --entropy display number of entropy bits - -n, --nword NWORD number of words in a passphrase + -h, --help This help message. + -e, --entropy Display number of entropy bits. + -r, --dicerolls Provide results of physical dice rolls. Word per line, + same digit order as in the files, digits between and + including 1 and 6. + -n, --nword NWORD Number of words in a passphrase. -d, --delimiter DELIM - the delimiter character used to separate the words + The delimiter character used to separate the words. -f, --wordlist-file FILE - path to a wordlist file + Path to a wordlist file. -l, --wordlist WORDLIST - Wordlist to use (efflong (default), effshort1, + Wordlist to use. (efflong (default), effshort1, effshort2, minilock, reinhold, or beale) ``` @@ -37,6 +40,15 @@ Options: * The [Beale wordlist][beale-wordlist-asc] ([Wayback Machine mirror][beale-wordlist-asc-wayback]). * The [MiniLock][minilock] ([github][minilock-github])wordlist. (found in the [phrase.js][minilock-phrase-js] file) * The all new `--wordlist-file` command line option which loads and uses your very own newline delimited wordlist file. Inquire within! +* Physical dice roll! You can (don't use echo, it will show up in `ps` and show your rolls to other users): + + ``` + $ cat | diceware -l efflong -r + 111111 + 111112 + ^D + abacus abdomen + ``` ## Mirrors diff --git a/src/bin/diceware.rs b/src/bin/diceware.rs @@ -1,13 +1,15 @@ extern crate getopts; extern crate rand; +use std::io::BufRead; use std::process::exit; use getopts::Options; use rand::thread_rng; +use diceware::entropyn; use diceware::load_wordlist_file; -use diceware::print_words; +use diceware::print_words_rng; use diceware::wordlists::BEALE_WORDLIST; use diceware::wordlists::EFF_LONG_WORDLIST; use diceware::wordlists::EFF_SHORT_WORDLIST_1; @@ -17,20 +19,21 @@ use diceware::wordlists::REINHOLD_WORDLIST; fn make_options() -> Options { let mut opts = Options::new(); - opts.optflag("h", "help", "this help message"); - opts.optflag("e", "entropy", "display number of entropy bits"); - opts.optopt("n", "nword", "number of words in a passphrase", "NWORD"); + opts.optflag("h", "help", "This help message."); + opts.optflag("e", "entropy", "Display number of entropy bits."); + opts.optflag("r", "dicerolls", "Provide results of physical dice rolls. Word per line, same digit order as in the files, digits between and including 1 and 6."); + opts.optopt("n", "nword", "Number of words in a passphrase.", "NWORD"); opts.optopt( "d", "delimiter", - "the delimiter character used to separate the words", + "The delimiter character used to separate the words.", "DELIM", ); - opts.optopt("f", "wordlist-file", "path to a wordlist file", "FILE"); + opts.optopt("f", "wordlist-file", "Path to a wordlist file.", "FILE"); opts.optopt( "l", "wordlist", - "Wordlist to use (efflong (default), effshort1, effshort2, minilock, reinhold, or beale)", + "Wordlist to use. (efflong (default), effshort1, effshort2, minilock, reinhold, or beale)", "WORDLIST", ); opts @@ -49,6 +52,98 @@ fn unknown_wordlist(wordlist_name: &str) -> ! { exit(-1) } +fn find_number_of_rolls_needed(wordlist_length: u32) -> u32 { + let mut x = wordlist_length as u64; + let mut nrolls: u32 = 0; + while x != 0 { + x /= 6; + nrolls += 1; + } + nrolls +} + +fn rolls_to_word_index(number_of_rolls_needed: u32, rolls: &[u8]) -> usize { + if number_of_rolls_needed == rolls.len() as u32 { + let mut word_number = 0; + for (i, roll) in rolls.iter().rev().enumerate() { + word_number += (roll + .checked_sub(1) + .expect("Must be a die roll result between and including 1 and 6.") + as usize) + * 6_usize.pow(i as u32); + } + word_number + } else { + panic!( + "Wrong number of die casts: {}. Needs to be {} die casts.", + rolls.len(), + number_of_rolls_needed + ) + } +} + +fn print_words_rolls( + wordlist: &[&str], + delimiter: &char, + is_entropy_printed: &bool, + rolls: &[&[u8]], +) { + let number_of_rolls_needed = find_number_of_rolls_needed(wordlist.len() as u32); + for roll_index in 0..(rolls.len() - 1) { + let roll_sum = rolls_to_word_index( + number_of_rolls_needed, + rolls + .get(roll_index) + .unwrap_or_else(|| panic!("Bad roll index: {}", roll_index)), + ); + let word = wordlist + .get(roll_sum) + .unwrap_or_else(|| panic!("Wrong word index: {}", roll_sum)); + print!("{}{}", &word, delimiter); + } + let roll_sum = rolls_to_word_index(number_of_rolls_needed, rolls.last().unwrap()); + let word = wordlist.get(roll_sum).unwrap(); + print!("{}", &word); + + println!(); + if *is_entropy_printed { + println!("{}", entropyn(wordlist, rolls.len() as u64)) + } +} + +fn read_rolls() -> Vec<Vec<u8>> { + let stdin = std::io::stdin(); + let mut rolls: Vec<Vec<u8>> = Vec::new(); + let mut last_number_of_dice = None; + for line in stdin.lock().lines() { + let line_value = line.unwrap(); + let line_value_trimmed = line_value.trim(); + if line_value_trimmed.is_empty() { + continue; + } + let current_number_of_dice = line_value_trimmed.len(); + if let Some(last_number_of_dice_value) = last_number_of_dice { + if last_number_of_dice_value != current_number_of_dice { + panic!("Not all dice rolls were of the same number of die."); + } else { + last_number_of_dice = Some(current_number_of_dice); + } + } + + rolls.push( + line_value_trimmed + .chars() + .map(|c| { + c.to_string() + .parse() + .unwrap_or_else(|e| panic!("Not a digit: \"{}\". Error: {}", c, e)) + }) + .collect(), + ); + } + rolls +} + fn main() { let args: Vec<String> = std::env::args().collect(); let program = &args[0]; @@ -69,6 +164,8 @@ fn main() { return; }; + let is_physical_rolls = matches.opt_present("r"); + let word_num: u64 = matches .opt_str("n") .map_or(8, |n_str| n_str.parse::<u64>().ok().unwrap()); @@ -91,81 +188,60 @@ fn main() { let mut rng = thread_rng(); - if word_num != 0 { - if let Some(wordlist_filepath) = matches.opt_str("f") { - let wordlist_string = load_wordlist_file(&wordlist_filepath); + if let Some(wordlist_filepath) = matches.opt_str("f") { + let wordlist_string = load_wordlist_file(&wordlist_filepath); + let wordlist = wordlist_string + .split('\n') + .map(|x| x.trim()) + .filter(|x| x != &"") + .collect::<Vec<&str>>(); - let wordlist = wordlist_string - .split('\n') - .map(|x| x.trim()) - .filter(|x| x != &"") - .collect::<Vec<&str>>(); + if is_physical_rolls { + let rolls = read_rolls(); - print_words( + print_words_rolls( + &wordlist, + &delimiter, + &is_entropy_printed, + &rolls.iter().map(|x| x.as_ref()).collect::<Vec<&[u8]>>(), + ); + } else if word_num != 0 { + print_words_rng( &wordlist, &word_num, &delimiter, &is_entropy_printed, &mut rng, ); - } else { - match wordlist_name.as_ref() { - "efflong" => { - print_words( - EFF_LONG_WORDLIST.as_ref(), - &word_num, - &delimiter, - &is_entropy_printed, - &mut rng, - ); - } - "reinhold" => { - print_words( - REINHOLD_WORDLIST.as_ref(), - &word_num, - &delimiter, - &is_entropy_printed, - &mut rng, - ); - } - "beale" => { - print_words( - BEALE_WORDLIST.as_ref(), - &word_num, - &delimiter, - &is_entropy_printed, - &mut rng, - ); - } - "minilock" => { - print_words( - MINILOCK_WORDLIST.as_ref(), - &word_num, - &delimiter, - &is_entropy_printed, - &mut rng, - ); - } - "effshort1" => { - print_words( - EFF_SHORT_WORDLIST_1.as_ref(), - &word_num, - &delimiter, - &is_entropy_printed, - &mut rng, - ); - } - "effshort2" => { - print_words( - EFF_SHORT_WORDLIST_2_0.as_ref(), - &word_num, - &delimiter, - &is_entropy_printed, - &mut rng, - ); - } - _ => unknown_wordlist(&wordlist_name), - } + }; + } else { + let wordlist = match wordlist_name.as_ref() { + "efflong" => EFF_LONG_WORDLIST.as_ref(), + "reinhold" => REINHOLD_WORDLIST.as_ref(), + "beale" => BEALE_WORDLIST.as_ref(), + "minilock" => MINILOCK_WORDLIST.as_ref(), + "effshort1" => EFF_SHORT_WORDLIST_1.as_ref(), + "effshort2" => EFF_SHORT_WORDLIST_2_0.as_ref(), + _ => unknown_wordlist(&wordlist_name), + }; + + if is_physical_rolls { + let rolls = read_rolls(); + + print_words_rolls( + wordlist, + &delimiter, + &is_entropy_printed, + &rolls.iter().map(|x| x.as_ref()).collect::<Vec<&[u8]>>(), + ); + } else if word_num != 0 { + print_words_rng( + wordlist, + &word_num, + &delimiter, + &is_entropy_printed, + &mut rng, + ); }; }; } diff --git a/src/lib.rs b/src/lib.rs @@ -10,15 +10,15 @@ pub mod wordlists { include!(concat!(env!("OUT_DIR"), "/diceware.rs")); } -fn entropy(wordlist: &[&str]) -> f64 { +pub fn entropy(wordlist: &[&str]) -> f64 { (wordlist.len() as f64).log2() } -fn entropyn(wordlist: &[&str], n: u64) -> f64 { +pub fn entropyn(wordlist: &[&str], n: u64) -> f64 { entropy(wordlist) * (n as f64) } -pub fn print_words( +pub fn print_words_rng( wordlist: &[&str], word_num: &u64, delimiter: &char,