1pub use bip39::{Error, Language};
40use std::{
41 fmt,
42 fmt::Display,
43 ops::{BitXor, BitXorAssign, Deref, DerefMut},
44 str::FromStr,
45};
46
47pub trait SeedXor {
49 fn xor(&self, rhs: &Self) -> Self;
51
52 fn xor_all(slice: &[Self]) -> Option<Self>
53 where
54 Self: Sized + Clone,
55 {
56 let first = slice.get(0)?;
57 let first = first.clone();
60 Some(slice.iter().skip(1).fold(first, |x, y| x.xor(y)))
61 }
62}
63
64impl SeedXor for bip39::Mnemonic {
65 fn xor(&self, rhs: &Self) -> Self {
67 let (mut entropy, entropy_len) = self.to_entropy_array();
68 let (xor_values, xor_values_len) = rhs.to_entropy_array();
69 let entropy = &mut entropy[0..entropy_len];
70 let xor_values = &xor_values[0..xor_values_len];
71
72 entropy
74 .iter_mut()
75 .zip(xor_values.iter())
76 .for_each(|(a, b)| *a ^= b);
77
78 if entropy.len() < xor_values.len() {
80 let mut entropy = entropy.to_vec();
81 entropy.extend(xor_values.iter().skip(entropy.len()));
82
83 bip39::Mnemonic::from_entropy(&entropy).unwrap()
84 } else {
85 bip39::Mnemonic::from_entropy(&entropy).unwrap()
88 }
89 }
90}
91
92#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
94pub struct Mnemonic {
95 pub inner: bip39::Mnemonic,
97}
98
99impl Mnemonic {
100 pub fn split(&self) -> Result<[Self; 2], Error> {
101 let random = Self::generate_in(self.language(), self.word_count())?;
102 let calc = self.xor(&random);
103 Ok([calc, random])
104 }
105
106 pub fn splitn(self, n: usize) -> Result<Vec<Self>, Error> {
107 let mut ret: Vec<Self> = Vec::with_capacity(n);
108 if n == 1 {
109 ret.push(self);
110 } else {
111 ret.extend_from_slice(&self.split()?);
112 for _ in 0..n - 2 {
113 let split = ret.pop().expect("cannot be empty").split()?;
114 ret.extend_from_slice(&split);
115 }
116 }
117 Ok(ret)
118 }
119
120 pub fn generate_in(language: Language, word_count: usize) -> Result<Self, Error> {
121 let mut inner = vec![0u8; (word_count / 3) * 4];
123 getrandom::getrandom(&mut inner)
124 .map_err(|e| Error::BadEntropyBitCount(e.code().get() as usize))?;
125 bip39::Mnemonic::from_entropy_in(language, &inner).map(|m| m.into())
126 }
127
128 pub fn from_entropy(entropy: &[u8]) -> Result<Self, Error> {
130 bip39::Mnemonic::from_entropy(entropy).map(|m| m.into())
131 }
132
133 pub fn parse_normalized_without_checksum_check(s: &str) -> Result<Mnemonic, Error> {
134 let lang = bip39::Mnemonic::language_of(s).unwrap_or(Language::English);
135 Self::parse_in_normalized_without_checksum_check(lang, s)
136 }
137
138 pub fn parse_in_normalized_without_checksum_check(
139 language: Language,
140 s: &str,
141 ) -> Result<Mnemonic, Error> {
142 bip39::Mnemonic::parse_in_normalized_without_checksum_check(
143 language,
144 &expand_words_in(language, s)?,
145 )
146 .map(|m| m.into())
147 }
148
149 pub fn to_short_string(&self) -> String {
150 let mut ret = self.word_iter().fold(String::new(), |mut s, w| {
151 if w.len() == 3 {
152 s.push_str(w);
153 s.push_str(" ");
154 } else {
155 w.chars().take(4).for_each(|c| s.push(c));
156 s.push(' ');
157 }
158 s
159 });
160 while ret.chars().last() == Some(' ') {
161 ret.pop();
162 }
163 ret
164 }
165
166 pub fn to_display_string(&self, short: bool) -> String {
167 if short {
168 self.to_short_string()
169 } else {
170 self.to_string()
171 }
172 }
173}
174
175pub fn expand_words(seed: &str) -> Result<String, Error> {
176 let lang = bip39::Mnemonic::language_of(seed).unwrap_or(Language::English);
177 expand_words_in(lang, seed)
178}
179
180pub fn expand_words_in(language: Language, seed: &str) -> Result<String, Error> {
181 let mut ret = String::new();
182 for (i, prefix) in seed.to_lowercase().split_whitespace().enumerate() {
183 let words = language.words_by_prefix(prefix);
184 let word = if words.len() == 1 {
185 words[0]
186 } else if words.contains(&prefix) {
187 prefix
188 } else {
189 return Err(Error::UnknownWord(i));
192 };
193 ret.push_str(word);
194 ret.push(' ');
195 }
196 ret.pop();
197 Ok(ret)
198}
199
200impl SeedXor for Mnemonic {
201 fn xor(&self, rhs: &Self) -> Self {
204 self.inner.xor(&rhs.inner).into()
205 }
206}
207
208impl Deref for Mnemonic {
209 type Target = bip39::Mnemonic;
210
211 fn deref(&self) -> &Self::Target {
212 &self.inner
213 }
214}
215
216impl DerefMut for Mnemonic {
217 fn deref_mut(&mut self) -> &mut Self::Target {
218 &mut self.inner
219 }
220}
221
222impl From<bip39::Mnemonic> for Mnemonic {
223 fn from(inner: bip39::Mnemonic) -> Self {
224 Self { inner }
225 }
226}
227
228impl FromStr for Mnemonic {
229 type Err = bip39::Error;
230
231 fn from_str(mnemonic: &str) -> Result<Self, <Self as FromStr>::Err> {
232 bip39::Mnemonic::from_str(&expand_words(mnemonic)?).map(|m| m.into())
233 }
234}
235
236impl fmt::Display for Mnemonic {
237 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
238 for (i, word) in self.inner.word_iter().enumerate() {
239 if i > 0 {
240 f.write_str(" ")?;
241 }
242 f.write_str(word)?;
243 }
244 Ok(())
245 }
246}
247
248impl fmt::Debug for Mnemonic {
249 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
250 <Mnemonic as Display>::fmt(self, f)
251 }
252}
253
254impl BitXor for Mnemonic {
255 type Output = Self;
256
257 fn bitxor(self, rhs: Self) -> Self::Output {
258 self.xor(&rhs)
259 }
260}
261
262impl BitXorAssign for Mnemonic {
263 fn bitxor_assign(&mut self, rhs: Self) {
264 *self = self.xor(&rhs)
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use crate::*;
271 use std::str::FromStr;
272
273 #[test]
274 fn seed_xor_works() {
275 let a_str = "romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room";
277 let b_str = "lion misery divide hurry latin fluid camp advance illegal lab pyramid unaware eager fringe sick camera series noodle toy crowd jeans select depth lounge";
278 let c_str = "vault nominee cradle silk own frown throw leg cactus recall talent worry gadget surface shy planet purpose coffee drip few seven term squeeze educate";
279 let result_str = "silent toe meat possible chair blossom wait occur this worth option bag nurse find fish scene bench asthma bike wage world quit primary indoor";
280
281 let a = Mnemonic::from_str(a_str).unwrap();
282 let b = Mnemonic::from_str(b_str).unwrap();
283 let c = Mnemonic::from_str(c_str).unwrap();
284 let result = Mnemonic::from_str(result_str).unwrap();
285
286 assert_eq!(result, a.clone() ^ b.clone() ^ c.clone());
287 assert_eq!(result, b ^ c ^ a); }
289
290 #[test]
291 fn seed_xor_assignment_works() {
292 let a_str = "romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room";
294 let b_str = "lion misery divide hurry latin fluid camp advance illegal lab pyramid unaware eager fringe sick camera series noodle toy crowd jeans select depth lounge";
295 let c_str = "vault nominee cradle silk own frown throw leg cactus recall talent worry gadget surface shy planet purpose coffee drip few seven term squeeze educate";
296 let result_str = "silent toe meat possible chair blossom wait occur this worth option bag nurse find fish scene bench asthma bike wage world quit primary indoor";
297
298 let a = Mnemonic::from_str(a_str).unwrap();
299 let b = Mnemonic::from_str(b_str).unwrap();
300 let c = Mnemonic::from_str(c_str).unwrap();
301 let result = Mnemonic::from_str(result_str).unwrap();
302
303 let mut assigned = a.xor(&b); assigned ^= c;
305
306 assert_eq!(result, assigned);
307 }
308
309 #[test]
310 fn seed_xor_with_different_lengths_works() {
311 let str_24 = "romance wink lottery autumn shop bring dawn tongue range crater truth ability miss spice fitness easy legal release recall obey exchange recycle dragon room";
314 let str_16 = "lion misery divide hurry latin fluid camp advance illegal lab pyramid unaware eager fringe sick camera series number";
315 let str_12 = "vault nominee cradle silk own frown throw leg cactus recall talent wisdom";
316 let result_str = "silent toe meat possible chair blossom wait occur this worth option aware since milk mother grace rocket cement recall obey exchange recycle dragon rocket";
317
318 let w_24 = Mnemonic::from_str(str_24).unwrap();
319 let w_16 = Mnemonic::from_str(str_16).unwrap();
320 let w_12 = Mnemonic::from_str(str_12).unwrap();
321 let result = Mnemonic::from_str(result_str).unwrap();
322
323 assert_eq!(result, w_24.clone() ^ w_16.clone() ^ w_12.clone());
324 assert_eq!(result, w_12 ^ w_24 ^ w_16); }
326
327 #[test]
328 fn seed_xor_works_12() {
329 let a_str = "romance wink lottery autumn shop bring dawn tongue range crater truth ability";
331 let b_str = "lion misery divide hurry latin fluid camp advance illegal lab pyramid unhappy";
332 let c_str = "vault nominee cradle silk own frown throw leg cactus recall talent wait";
333 let result_str = "silent toe meat possible chair blossom wait occur this worth option boy";
334
335 let a = Mnemonic::from_str(a_str).unwrap();
336 let b = Mnemonic::from_str(b_str).unwrap();
337 let c = Mnemonic::from_str(c_str).unwrap();
338 let result = Mnemonic::from_str(result_str).unwrap();
339
340 assert_eq!(result, a.clone() ^ b.clone() ^ c.clone());
341 assert_eq!(result, b ^ c ^ a); }
343
344 #[test]
345 fn test_electrum_seed() {
346 let electrum_seed =
347 "ramp exotic resource icon sun addict equip sand leisure spare swing tobacco";
348 let expected = "ramp exotic resource icon sun addict equip sand leisure spare swing toast";
350
351 let seed = Mnemonic::from(
353 bip39::Mnemonic::parse_in_normalized_without_checksum_check(
354 Language::English,
355 electrum_seed,
356 )
357 .unwrap(),
358 );
359
360 assert_eq!(electrum_seed, seed.to_string());
361
362 let expected = Mnemonic::from_str(expected).unwrap();
363
364 let split = seed.clone().split().unwrap();
365 println!("1split: '{split:?}'");
366 let result = Mnemonic::xor_all(&split).unwrap();
367 println!("result: '{}'", result);
368 if seed != result {
369 assert_eq!(expected, result);
370 }
371
372 for x in 1..=5 {
373 let split = seed.clone().splitn(x).unwrap();
374 assert_eq!(x, split.len());
375 println!("split: '{split:?}'");
376 let result = Mnemonic::xor_all(&split).unwrap();
377 println!("result: '{}'", result);
378 if seed != result {
379 assert_eq!(expected, result);
380 }
381 }
382 }
383
384 #[test]
385 fn derive_from_seed() {
386 let seed = "silent toe meat possible chair blossom wait occur this worth option boy";
389 let seed = Mnemonic::from_str(seed).unwrap();
390
391 let split = seed.clone().split().unwrap();
392 println!("split: '{split:?}'");
393 assert_eq!(seed.clone(), Mnemonic::xor_all(&split).unwrap());
394
395 for x in 1..=5 {
396 let split = seed.clone().splitn(x).unwrap();
397 assert_eq!(x, split.len());
398 println!("split: '{split:?}'");
399 assert_eq!(seed.clone(), Mnemonic::xor_all(&split).unwrap());
400 }
401 }
402
403 #[test]
404 fn expand_seed() {
405 let orig_seed = "silent toe meat possible chair blossom wait occur this worth option boy";
406 let seed = Mnemonic::from_str(orig_seed).unwrap();
407
408 let short_string = seed.to_short_string();
409 assert_eq!(
410 "sile toe meat poss chai blos wait occu this wort opti boy",
411 short_string
412 );
413 assert_eq!(orig_seed, expand_words(&short_string).unwrap());
416
417 let orig_seed = "song vanish mistake night drink add modify lens average cool evil chest";
419 let seed = Mnemonic::from_str(orig_seed).unwrap();
420
421 let short_string = seed.to_short_string();
422 assert_eq!(
423 "song vani mist nigh drin add modi lens aver cool evil ches",
424 short_string
425 );
426 assert_eq!(
427 Language::English,
428 bip39::Mnemonic::language_of(&short_string).unwrap()
429 );
430
431 assert_eq!(orig_seed, expand_words(&short_string).unwrap());
432
433 let orig_seed = "ramp exotic resource icon sun addict equip sand leisure spare swing toast";
434 let seed = Mnemonic::from_str(orig_seed).unwrap();
435
436 let short_string = seed.to_short_string();
437 assert_eq!(
438 "ramp exot reso icon sun addi equi sand leis spar swin toas",
439 short_string
440 );
441 assert_eq!(
442 Language::English,
443 bip39::Mnemonic::language_of(&short_string).unwrap()
444 );
445
446 assert_eq!(orig_seed, expand_words(&short_string).unwrap());
447 }
448}