1use log::{info, trace};
18use std::collections::HashSet;
19
20use edit_distance::edit_distance;
21use parity_wordlist;
22
23use super::{Address, Brain, KeyPairGenerator};
24
25pub fn brain_recover(
30 address: &Address, known_phrase: &str, expected_words: usize,
31) -> Option<String> {
32 let it = PhrasesIterator::from_known_phrase(known_phrase, expected_words);
33 for phrase in it {
34 let keypair = Brain::new(phrase.clone())
35 .generate()
36 .expect("Brain wallets are infallible; qed");
37 trace!("Testing: {}, got: {:?}", phrase, keypair.address());
38 if &keypair.address() == address {
39 return Some(phrase);
40 }
41 }
42
43 None
44}
45
46fn generate_substitutions(word: &str) -> Vec<&'static str> {
47 let mut words = parity_wordlist::WORDS
48 .iter()
49 .cloned()
50 .map(|w| (edit_distance(w, word), w))
51 .collect::<Vec<_>>();
52 words.sort_by(|a, b| a.0.cmp(&b.0));
53
54 words.into_iter().map(|pair| pair.1).collect()
55}
56
57pub struct PhrasesIterator {
59 words: Vec<Vec<&'static str>>,
60 combinations: u64,
61 indexes: Vec<usize>,
62 has_next: bool,
63}
64
65impl PhrasesIterator {
66 pub fn from_known_phrase(
67 known_phrase: &str, expected_words: usize,
68 ) -> Self {
69 let known_words = parity_wordlist::WORDS
70 .iter()
71 .cloned()
72 .collect::<HashSet<_>>();
73 let mut words = known_phrase.split(' ')
74 .map(|word| match known_words.get(word) {
75 None => {
76 info!("Invalid word '{}', looking for potential substitutions.", word);
77 let substitutions = generate_substitutions(word);
78 info!("Closest words: {:?}", &substitutions[..10]);
79 substitutions
80 },
81 Some(word) => vec![*word],
82 })
83 .collect::<Vec<_>>();
84
85 if words.len() < expected_words {
87 let to_add = expected_words - words.len();
88 info!("Number of words is insuficcient adding {} more.", to_add);
89 for _ in 0..to_add {
90 words.push(parity_wordlist::WORDS.to_vec());
91 }
92 }
93
94 PhrasesIterator::new(words)
96 }
97
98 pub fn new(words: Vec<Vec<&'static str>>) -> Self {
99 let combinations =
100 words.iter().fold(1u64, |acc, x| acc * x.len() as u64);
101 let indexes = words.iter().map(|_| 0).collect();
102 info!("Starting to test {} possible combinations.", combinations);
103
104 PhrasesIterator {
105 words,
106 combinations,
107 indexes,
108 has_next: combinations > 0,
109 }
110 }
111
112 pub fn combinations(&self) -> u64 { self.combinations }
113
114 fn current(&self) -> String {
115 let mut s = self.words[0][self.indexes[0]].to_owned();
116 for i in 1..self.indexes.len() {
117 s.push(' ');
118 s.push_str(self.words[i][self.indexes[i]]);
119 }
120 s
121 }
122
123 fn next_index(&mut self) -> bool {
124 let mut pos = self.indexes.len();
125 while pos > 0 {
126 pos -= 1;
127 self.indexes[pos] += 1;
128 if self.indexes[pos] >= self.words[pos].len() {
129 self.indexes[pos] = 0;
130 } else {
131 return true;
132 }
133 }
134
135 false
136 }
137}
138
139impl Iterator for PhrasesIterator {
140 type Item = String;
141
142 fn next(&mut self) -> Option<String> {
143 if !self.has_next {
144 return None;
145 }
146
147 let phrase = self.current();
148 self.has_next = self.next_index();
149 Some(phrase)
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::PhrasesIterator;
156
157 #[test]
158 fn should_generate_possible_combinations() {
159 let mut it = PhrasesIterator::new(vec![
160 vec!["1", "2", "3"],
161 vec!["test"],
162 vec!["a", "b", "c"],
163 ]);
164
165 assert_eq!(it.combinations(), 9);
166 assert_eq!(it.next(), Some("1 test a".to_owned()));
167 assert_eq!(it.next(), Some("1 test b".to_owned()));
168 assert_eq!(it.next(), Some("1 test c".to_owned()));
169 assert_eq!(it.next(), Some("2 test a".to_owned()));
170 assert_eq!(it.next(), Some("2 test b".to_owned()));
171 assert_eq!(it.next(), Some("2 test c".to_owned()));
172 assert_eq!(it.next(), Some("3 test a".to_owned()));
173 assert_eq!(it.next(), Some("3 test b".to_owned()));
174 assert_eq!(it.next(), Some("3 test c".to_owned()));
175 assert_eq!(it.next(), None);
176 }
177}