#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![deny(unused_must_use)]
#![deny(unused_mut)]
use anyhow::{bail, Context, Error, Result};
use dictionary_1024::{word_at_index, index_of_word};
pub fn binary_to_phrase(data: &[u8]) -> String {
let mut phrase = "".to_string();
if data.len() == 0 {
return phrase;
}
let mut i = 0;
while i+1 < data.len() {
let mut word_index = data[i] as u16;
word_index *= 4;
let word_bits = data[i+1] / 64;
word_index += word_bits as u16;
let word = word_at_index(word_index as usize);
let num = data[i+1] % 64;
if phrase.len() != 0 {
phrase += " ";
}
phrase += &word;
phrase += &format!("{}", num);
i += 2;
}
if data.len() % 2 == 1 {
let word = word_at_index(data[i] as usize);
if phrase.len() != 0 {
phrase += " ";
}
phrase += &word;
phrase += "64";
}
phrase
}
pub fn phrase_to_binary(phrase: &str) -> Result<Vec<u8>, Error> {
if phrase == "" {
return Ok(vec![0u8; 0]);
}
let mut finalized = false;
let mut result: Vec<u8> = Vec::new();
let words = phrase.split(" ");
for word in words {
if finalized {
bail!("only the last word may contain the number '64'");
}
let mut digits = 0;
for c in word.chars() {
if digits > 0 && !c.is_ascii_digit() {
bail!("number must appear as suffix only");
}
if digits > 1 {
bail!("number must be at most 2 digits");
}
if c.is_ascii_digit() {
digits += 1;
}
}
if digits == 0 {
bail!("word must have a numerical suffix");
}
let numerical_suffix;
if digits == 1 {
numerical_suffix = &word[word.len()-1..];
} else {
numerical_suffix = &word[word.len()-2..];
}
if numerical_suffix == "64" {
finalized = true;
let word_index = index_of_word(word).context(format!("invalid word {} in phrase", word))?;
if word_index > 255 {
bail!("final word is invalid, needs to be among the first 255 words in the dictionary");
}
result.push(word_index as u8);
} else {
let mut bits = index_of_word(word).context(format!("invalid word {} in phrase", word))? as u16;
bits *= 64;
let numerical_bits: u16 = numerical_suffix.parse().unwrap();
if numerical_bits > 64 {
bail!("numerical suffix must have a value [0, 64]");
}
bits += numerical_bits;
result.push((bits / 256) as u8);
result.push((bits % 256) as u8);
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use userspace_rng::Csprng;
use rand_core::RngCore;
#[test]
fn check_seed_phrases() {
let basic = [0u8; 0];
let phrase = binary_to_phrase(&basic);
let result = phrase_to_binary(&phrase).unwrap();
assert!(basic[..] == result[..]);
for i in 0..=255 {
let basic = [i as u8; 1];
let phrase = binary_to_phrase(&basic);
let result = phrase_to_binary(&phrase).unwrap();
assert!(basic[..] == result[..]);
}
for i in 0..=255 {
let basic = vec![0u8; i];
let phrase = binary_to_phrase(&basic);
let result = phrase_to_binary(&phrase).unwrap();
assert!(basic[..] == result[..]);
}
let mut rng = Csprng {};
for _ in 0..8 {
for i in 0..=255 {
let mut basic = vec![0u8; i];
rng.fill_bytes(&mut basic);
let phrase = binary_to_phrase(&basic);
let result = phrase_to_binary(&phrase).unwrap();
assert!(basic[..] == result[..]);
}
}
for i in 0..=255 {
for j in 0..=255 {
let mut basic = [0u8; 2];
basic[0] = i;
basic[1] = j;
let phrase = binary_to_phrase(&basic);
let result = phrase_to_binary(&phrase).unwrap();
assert!(basic[..] == result[..]);
}
}
}
#[test]
fn check_bad_phrases() {
phrase_to_binary("a").unwrap_err();
phrase_to_binary("a64").unwrap_err();
phrase_to_binary("abbey").unwrap_err();
phrase_to_binary("abbey65").unwrap_err();
phrase_to_binary("yacht64").unwrap_err();
phrase_to_binary("sugar21 ab55 mob32").unwrap_err();
phrase_to_binary("sugar21 toffee mob32").unwrap_err();
phrase_to_binary("sug21 tof21 mob32").unwrap();
}
}