1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3#![deny(unused_must_use)]
4#![deny(unused_mut)]
5
6use anyhow::{bail, Context, Error, Result};
25use dictionary_1024::{word_at_index, index_of_word};
26
27pub fn binary_to_phrase(data: &[u8]) -> String {
29 let mut phrase = "".to_string();
31 if data.len() == 0 {
32 return phrase;
33 }
34
35 let mut i = 0;
37 while i+1 < data.len() {
38 let mut word_index = data[i] as u16;
40 word_index *= 4;
41 let word_bits = data[i+1] / 64;
42 word_index += word_bits as u16;
43 let word = word_at_index(word_index as usize);
44
45 let num = data[i+1] % 64;
47
48 if phrase.len() != 0 {
50 phrase += " ";
51 }
52 phrase += &word;
53 phrase += &format!("{}", num);
54 i += 2;
55 }
56
57 if data.len() % 2 == 1 {
59 let word = word_at_index(data[i] as usize);
60 if phrase.len() != 0 {
61 phrase += " ";
62 }
63 phrase += &word;
64 phrase += "64";
65 }
66
67 phrase
68}
69
70pub fn phrase_to_binary(phrase: &str) -> Result<Vec<u8>, Error> {
73 if phrase == "" {
74 return Ok(vec![0u8; 0]);
75 }
76
77 let mut finalized = false;
79 let mut result: Vec<u8> = Vec::new();
80 let words = phrase.split(" ");
81 for word in words {
82 if finalized {
83 bail!("only the last word may contain the number '64'");
84 }
85
86 let mut digits = 0;
88 for c in word.chars() {
89 if digits > 0 && !c.is_ascii_digit() {
90 bail!("number must appear as suffix only");
91 }
92 if digits > 1 {
93 bail!("number must be at most 2 digits");
94 }
95 if c.is_ascii_digit() {
96 digits += 1;
97 }
98 }
99 if digits == 0 {
100 bail!("word must have a numerical suffix");
101 }
102
103 let numerical_suffix;
106 if digits == 1 {
107 numerical_suffix = &word[word.len()-1..];
108 } else {
109 numerical_suffix = &word[word.len()-2..];
110 }
111
112 if numerical_suffix == "64" {
114 finalized = true;
115 let word_index = index_of_word(word).context(format!("invalid word {} in phrase", word))?;
116 if word_index > 255 {
117 bail!("final word is invalid, needs to be among the first 255 words in the dictionary");
118 }
119 result.push(word_index as u8);
120 } else {
121 let mut bits = index_of_word(word).context(format!("invalid word {} in phrase", word))? as u16;
122 bits *= 64;
123 let numerical_bits: u16 = numerical_suffix.parse().unwrap();
124 if numerical_bits > 64 {
125 bail!("numerical suffix must have a value [0, 64]");
126 }
127 bits += numerical_bits;
128 result.push((bits / 256) as u8);
129 result.push((bits % 256) as u8);
130 }
131 }
132
133 Ok(result)
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use userspace_rng::Csprng;
140 use rand_core::RngCore;
141
142 #[test]
143 fn check_seed_phrases() {
146 let basic = [0u8; 0];
148 let phrase = binary_to_phrase(&basic);
149 let result = phrase_to_binary(&phrase).unwrap();
150 assert!(basic[..] == result[..]);
151
152 for i in 0..=255 {
154 let basic = [i as u8; 1];
155 let phrase = binary_to_phrase(&basic);
156 let result = phrase_to_binary(&phrase).unwrap();
157 assert!(basic[..] == result[..]);
158 }
159
160 for i in 0..=255 {
162 let basic = vec![0u8; i];
163 let phrase = binary_to_phrase(&basic);
164 let result = phrase_to_binary(&phrase).unwrap();
165 assert!(basic[..] == result[..]);
166 }
167
168 let mut rng = Csprng {};
170 for _ in 0..8 {
171 for i in 0..=255 {
172 let mut basic = vec![0u8; i];
173 rng.fill_bytes(&mut basic);
174 let phrase = binary_to_phrase(&basic);
175 let result = phrase_to_binary(&phrase).unwrap();
176 assert!(basic[..] == result[..]);
177 }
178 }
179
180 for i in 0..=255 {
182 for j in 0..=255 {
183 let mut basic = [0u8; 2];
184 basic[0] = i;
185 basic[1] = j;
186 let phrase = binary_to_phrase(&basic);
187 let result = phrase_to_binary(&phrase).unwrap();
188 assert!(basic[..] == result[..]);
189 }
190 }
191 }
192
193 #[test]
194 fn check_bad_phrases() {
196 phrase_to_binary("a").unwrap_err();
197 phrase_to_binary("a64").unwrap_err();
198 phrase_to_binary("abbey").unwrap_err();
199 phrase_to_binary("abbey65").unwrap_err();
200 phrase_to_binary("yacht64").unwrap_err();
201 phrase_to_binary("sugar21 ab55 mob32").unwrap_err();
202 phrase_to_binary("sugar21 toffee mob32").unwrap_err();
203
204 phrase_to_binary("sug21 tof21 mob32").unwrap();
206 }
207}