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:
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,