cs_mwc_bch/wallet/
mnemonic.rs

1//! Functions to convert data to and from mnemonic words
2
3use ring::digest::{digest, SHA256};
4use std::str;
5use util::{Bits, Error, Result};
6
7/// Wordlist language
8pub enum Wordlist {
9    ChineseSimplified,
10    ChineseTraditional,
11    English,
12    French,
13    Italian,
14    Japanese,
15    Korean,
16    Spanish,
17}
18
19/// Loads the word list for a given language
20pub fn load_wordlist(wordlist: Wordlist) -> Vec<String> {
21    match wordlist {
22        Wordlist::ChineseSimplified => {
23            load_wordlist_internal(include_bytes!("wordlists/chinese_simplified.txt"))
24        }
25        Wordlist::ChineseTraditional => {
26            load_wordlist_internal(include_bytes!("wordlists/chinese_traditional.txt"))
27        }
28        Wordlist::English => load_wordlist_internal(include_bytes!("wordlists/english.txt")),
29        Wordlist::French => load_wordlist_internal(include_bytes!("wordlists/french.txt")),
30        Wordlist::Italian => load_wordlist_internal(include_bytes!("wordlists/italian.txt")),
31        Wordlist::Japanese => load_wordlist_internal(include_bytes!("wordlists/japanese.txt")),
32        Wordlist::Korean => load_wordlist_internal(include_bytes!("wordlists/korean.txt")),
33        Wordlist::Spanish => load_wordlist_internal(include_bytes!("wordlists/spanish.txt")),
34    }
35}
36
37fn load_wordlist_internal(bytes: &[u8]) -> Vec<String> {
38    let text: String = str::from_utf8(bytes).unwrap().to_string();
39    text.lines().map(|s| s.to_string()).collect()
40}
41
42/// Encodes data into a mnemonic using BIP-39
43pub fn mnemonic_encode(data: &[u8], word_list: &[String]) -> Vec<String> {
44    let hash = digest(&SHA256, &data);
45    let mut words = Vec::with_capacity((data.len() * 8 + data.len() / 32 + 10) / 11);
46    let mut bits = Bits::from_slice(data, data.len() * 8);
47    bits.append(&Bits::from_slice(hash.as_ref(), data.len() / 4));
48    for i in 0..bits.len / 11 {
49        words.push(word_list[bits.extract(i * 11, 11) as usize].clone());
50    }
51    let rem = bits.len % 11;
52    if rem != 0 {
53        let n = bits.extract(bits.len / 11 * 11, rem) << (8 - rem);
54        words.push(word_list[n as usize].clone());
55    }
56    words
57}
58
59/// Decodes a mnemonic into data using BIP-39
60pub fn mnemonic_decode(mnemonic: &[String], word_list: &[String]) -> Result<Vec<u8>> {
61    let mut bits = Bits::with_capacity(mnemonic.len() * 11);
62    for word in mnemonic {
63        let value = match word_list.binary_search(word) {
64            Ok(value) => value,
65            Err(_) => return Err(Error::BadArgument(format!("Bad word: {}", word))),
66        };
67        let word_bits = Bits::from_slice(&[(value >> 3) as u8, ((value & 7) as u8) << 5], 11);
68        bits.append(&word_bits);
69    }
70    let data_len = bits.len * 32 / 33;
71    let cs_len = bits.len / 33;
72    let cs = digest(&SHA256, &bits.data[0..data_len / 8]);
73    let cs_bits = Bits::from_slice(cs.as_ref(), cs_len);
74    if cs_bits.extract(0, cs_len) != bits.extract(data_len, cs_len) {
75        return Err(Error::BadArgument("Invalid checksum".to_string()));
76    }
77    Ok(bits.data[0..data_len / 8].to_vec())
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use hex;
84
85    #[test]
86    fn wordlists() {
87        assert!(load_wordlist(Wordlist::ChineseSimplified).len() == 2048);
88        assert!(load_wordlist(Wordlist::ChineseTraditional).len() == 2048);
89        assert!(load_wordlist(Wordlist::English).len() == 2048);
90        assert!(load_wordlist(Wordlist::French).len() == 2048);
91        assert!(load_wordlist(Wordlist::Italian).len() == 2048);
92        assert!(load_wordlist(Wordlist::Japanese).len() == 2048);
93        assert!(load_wordlist(Wordlist::Korean).len() == 2048);
94        assert!(load_wordlist(Wordlist::Spanish).len() == 2048);
95    }
96
97    #[test]
98    fn encode_decode() {
99        let mut data = Vec::new();
100        for i in 0..16 {
101            data.push(i);
102        }
103        let wordlist = load_wordlist(Wordlist::English);
104        assert!(mnemonic_decode(&mnemonic_encode(&data, &wordlist), &wordlist).unwrap() == data);
105    }
106
107    #[test]
108    fn invalid() {
109        let wordlist = load_wordlist(Wordlist::English);
110        assert!(mnemonic_encode(&[], &wordlist).len() == 0);
111        assert!(mnemonic_decode(&[], &wordlist).unwrap().len() == 0);
112
113        let mut data = Vec::new();
114        for i in 0..16 {
115            data.push(i);
116        }
117        let mnemonic = mnemonic_encode(&data, &wordlist);
118
119        let mut bad_checksum = mnemonic.clone();
120        bad_checksum[0] = "hello".to_string();
121        assert!(mnemonic_decode(&bad_checksum, &wordlist).is_err());
122
123        let mut bad_word = mnemonic.clone();
124        bad_word[0] = "123".to_string();
125        assert!(mnemonic_decode(&bad_word, &wordlist).is_err());
126    }
127
128    #[test]
129    fn test_vectors() {
130        let wordlist = load_wordlist(Wordlist::English);
131
132        let h = hex::decode("00000000000000000000000000000000").unwrap();
133        let n = mnemonic_encode(&h, &wordlist).join(" ");
134        assert!(n == "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about");
135
136        let h = hex::decode("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f").unwrap();
137        let n = mnemonic_encode(&h, &wordlist).join(" ");
138        assert!(n == "legal winner thank year wave sausage worth useful legal winner thank yellow");
139
140        let h = hex::decode("80808080808080808080808080808080").unwrap();
141        let n = mnemonic_encode(&h, &wordlist).join(" ");
142        assert!(
143            n == "letter advice cage absurd amount doctor acoustic avoid letter advice cage above"
144        );
145
146        let h = hex::decode("ffffffffffffffffffffffffffffffff").unwrap();
147        let n = mnemonic_encode(&h, &wordlist).join(" ");
148        assert!(n == "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong");
149
150        let h = hex::decode("000000000000000000000000000000000000000000000000").unwrap();
151        let n = mnemonic_encode(&h, &wordlist).join(" ");
152        assert!(n == "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon agent");
153
154        let h = hex::decode("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f").unwrap();
155        let n = mnemonic_encode(&h, &wordlist).join(" ");
156        assert!(n == "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will");
157
158        let h = hex::decode("808080808080808080808080808080808080808080808080").unwrap();
159        let n = mnemonic_encode(&h, &wordlist).join(" ");
160        assert!(n == "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter always");
161
162        let h = hex::decode("ffffffffffffffffffffffffffffffffffffffffffffffff").unwrap();
163        let n = mnemonic_encode(&h, &wordlist).join(" ");
164        assert!(n == "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo when");
165
166        let h = hex::decode("0000000000000000000000000000000000000000000000000000000000000000")
167            .unwrap();
168        let n = mnemonic_encode(&h, &wordlist).join(" ");
169        assert!(n == "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art");
170
171        let h = hex::decode("7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f")
172            .unwrap();
173        let n = mnemonic_encode(&h, &wordlist).join(" ");
174        assert!(n == "legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title");
175
176        let h = hex::decode("8080808080808080808080808080808080808080808080808080808080808080")
177            .unwrap();
178        let n = mnemonic_encode(&h, &wordlist).join(" ");
179        assert!(n == "letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless");
180
181        let h = hex::decode("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
182            .unwrap();
183        let n = mnemonic_encode(&h, &wordlist).join(" ");
184        assert!(n == "zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote");
185
186        let h = hex::decode("9e885d952ad362caeb4efe34a8e91bd2").unwrap();
187        let n = mnemonic_encode(&h, &wordlist).join(" ");
188        assert!(
189            n == "ozone drill grab fiber curtain grace pudding thank cruise elder eight picnic"
190        );
191
192        let h = hex::decode("6610b25967cdcca9d59875f5cb50b0ea75433311869e930b").unwrap();
193        let n = mnemonic_encode(&h, &wordlist).join(" ");
194        assert!(n == "gravity machine north sort system female filter attitude volume fold club stay feature office ecology stable narrow fog");
195
196        let h = hex::decode("68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c")
197            .unwrap();
198        let n = mnemonic_encode(&h, &wordlist).join(" ");
199        assert!(n == "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length");
200
201        let h = hex::decode("c0ba5a8e914111210f2bd131f3d5e08d").unwrap();
202        let n = mnemonic_encode(&h, &wordlist).join(" ");
203        assert!(n == "scheme spot photo card baby mountain device kick cradle pact join borrow");
204
205        let h = hex::decode("6d9be1ee6ebd27a258115aad99b7317b9c8d28b6d76431c3").unwrap();
206        let n = mnemonic_encode(&h, &wordlist).join(" ");
207        assert!(n == "horn tenant knee talent sponsor spell gate clip pulse soap slush warm silver nephew swap uncle crack brave");
208
209        let h = hex::decode("9f6a2878b2520799a44ef18bc7df394e7061a224d2c33cd015b157d746869863")
210            .unwrap();
211        let n = mnemonic_encode(&h, &wordlist).join(" ");
212        assert!(n == "panda eyebrow bullet gorilla call smoke muffin taste mesh discover soft ostrich alcohol speed nation flash devote level hobby quick inner drive ghost inside");
213
214        let h = hex::decode("23db8160a31d3e0dca3688ed941adbf3").unwrap();
215        let n = mnemonic_encode(&h, &wordlist).join(" ");
216        assert!(n == "cat swing flag economy stadium alone churn speed unique patch report train");
217
218        let h = hex::decode("8197a4a47f0425faeaa69deebc05ca29c0a5b5cc76ceacc0").unwrap();
219        let n = mnemonic_encode(&h, &wordlist).join(" ");
220        assert!(n == "light rule cinnamon wrap drastic word pride squirrel upgrade then income fatal apart sustain crack supply proud access");
221
222        let h = hex::decode("066dca1a2bb7e8a1db2832148ce9933eea0f3ac9548d793112d9a95c9407efad")
223            .unwrap();
224        let n = mnemonic_encode(&h, &wordlist).join(" ");
225        assert!(n == "all hour make first leader extend hole alien behind guard gospel lava path output census museum junior mass reopen famous sing advance salt reform");
226
227        let h = hex::decode("f30f8c1da665478f49b001d94c5fc452").unwrap();
228        let n = mnemonic_encode(&h, &wordlist).join(" ");
229        assert!(
230            n == "vessel ladder alter error federal sibling chat ability sun glass valve picture"
231        );
232
233        let h = hex::decode("c10ec20dc3cd9f652c7fac2f1230f7a3c828389a14392f05").unwrap();
234        let n = mnemonic_encode(&h, &wordlist).join(" ");
235        assert!(n == "scissors invite lock maple supreme raw rapid void congress muscle digital elegant little brisk hair mango congress clump");
236
237        let h = hex::decode("f585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f")
238            .unwrap();
239        let n = mnemonic_encode(&h, &wordlist).join(" ");
240        assert!(n == "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold");
241    }
242}