dup_crypto/mnemonic/mnemonic_gen.rs
1// Copyright (C) 2019 Eloïs SANCHEZ.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Affero General Public License as
5// published by the Free Software Foundation, either version 3 of the
6// License, or (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU Affero General Public License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16use super::error::MnemonicError;
17use super::language::Language;
18use super::mnemonic_type::MnemonicType;
19use super::utils::{checksum, sha256_first_byte, BitWriter, IterExt};
20use crate::rand::UnspecifiedRandError;
21use std::fmt;
22use zeroize::Zeroize;
23
24/// The primary type in this crate, most tasks require creating or using one.
25///
26/// To create a *new* [`Mnemonic`][Mnemonic] from a randomly generated key, call [`Mnemonic::new()`][Mnemonic::new()].
27///
28/// To get a [`Mnemonic`][Mnemonic] instance for an existing mnemonic phrase, including
29/// those generated by other software or hardware wallets, use [`Mnemonic::from_phrase()`][Mnemonic::from_phrase()].
30///
31/// You can get the HD wallet [`Seed`][Seed] from a [`Mnemonic`][Mnemonic] by calling [`Seed::new()`][Seed::new()].
32/// From there you can either get the raw byte value with [`Seed::as_bytes()`][Seed::as_bytes()], or the hex
33/// representation using Rust formatting: `format!("{:X}", seed)`.
34///
35/// You can also get the original entropy value back from a [`Mnemonic`][Mnemonic] with [`Mnemonic::entropy()`][Mnemonic::entropy()],
36/// but beware that the entropy value is **not the same thing** as an HD wallet seed, and should
37/// *never* be used that way.
38///
39/// [Mnemonic]: ./mnemonic/struct.Mnemonic.html
40/// [Mnemonic::new()]: ./mnemonic/struct.Mnemonic.html#method.new
41/// [Mnemonic::from_phrase()]: ./mnemonic/struct.Mnemonic.html#method.from_phrase
42/// [Mnemonic::entropy()]: ./mnemonic/struct.Mnemonic.html#method.entropy
43/// [Seed]: ./seed/struct.Seed.html
44/// [Seed::new()]: ./seed/struct.Seed.html#method.new
45/// [Seed::as_bytes()]: ./seed/struct.Seed.html#method.as_bytes
46///
47#[derive(PartialEq)]
48pub struct Mnemonic {
49 lang: Language,
50 secret: MnemonicSecret,
51}
52
53#[derive(Default, PartialEq, Zeroize)]
54#[zeroize(drop)]
55struct MnemonicSecret {
56 entropy: Vec<u8>,
57 phrase: String,
58}
59
60impl Mnemonic {
61 /// Generates a new [`Mnemonic`][Mnemonic]
62 ///
63 /// Use [`Mnemonic::phrase()`][Mnemonic::phrase()] to get an `str` slice of the generated phrase.
64 ///
65 /// # Example
66 ///
67 /// ```
68 /// use dup_crypto::mnemonic::{Mnemonic, MnemonicType, Language};
69 ///
70 /// let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English).expect("fail to generate random bytes");
71 /// let phrase = mnemonic.phrase();
72 ///
73 /// println!("phrase: {}", phrase);
74 ///
75 /// assert_eq!(phrase.split(" ").count(), 12);
76 /// ```
77 ///
78 /// [Mnemonic]: ./mnemonic/struct.Mnemonic.html
79 /// [Mnemonic::phrase()]: ./mnemonic/struct.Mnemonic.html#method.phrase
80 pub fn new(mtype: MnemonicType, lang: Language) -> Result<Mnemonic, UnspecifiedRandError> {
81 let mut buffer = vec![0u8; mtype.entropy_bits() / 8];
82 crate::rand::gen_random_bytes(&mut buffer)?;
83
84 Ok(Mnemonic::from_entropy_unchecked(buffer, lang))
85 }
86
87 /// Create a [`Mnemonic`][Mnemonic] from pre-generated entropy
88 ///
89 /// # Example
90 ///
91 /// ```
92 /// use dup_crypto::mnemonic::{Mnemonic, MnemonicType, Language};
93 ///
94 /// let entropy = &[0x33, 0xE4, 0x6B, 0xB1, 0x3A, 0x74, 0x6E, 0xA4, 0x1C, 0xDD, 0xE4, 0x5C, 0x90, 0x84, 0x6A, 0x79];
95 /// let mnemonic = Mnemonic::from_entropy(entropy, Language::English).unwrap();
96 ///
97 /// assert_eq!("crop cash unable insane eight faith inflict route frame loud box vibrant", mnemonic.phrase());
98 /// assert_eq!("33E46BB13A746EA41CDDE45C90846A79", format!("{:X}", mnemonic));
99 /// ```
100 ///
101 /// [Mnemonic]: ../mnemonic/struct.Mnemonic.html
102 pub fn from_entropy(entropy: &[u8], lang: Language) -> Result<Mnemonic, MnemonicError> {
103 // Validate entropy size
104 MnemonicType::for_key_size(entropy.len() * 8)?;
105
106 Ok(Self::from_entropy_unchecked(entropy, lang))
107 }
108
109 fn from_entropy_unchecked<E>(entropy: E, lang: Language) -> Mnemonic
110 where
111 E: Into<Vec<u8>>,
112 {
113 let entropy = entropy.into();
114 let wordlist = lang.wordlist();
115
116 let checksum_byte = sha256_first_byte(&entropy);
117
118 // First, create a byte iterator for the given entropy and the first byte of the
119 // hash of the entropy that will serve as the checksum (up to 8 bits for biggest
120 // entropy source).
121 //
122 // Then we transform that into a bits iterator that returns 11 bits at a
123 // time (as u16), which we can map to the words on the `wordlist`.
124 //
125 // Given the entropy is of correct size, this ought to give us the correct word
126 // count.
127 let phrase = entropy
128 .iter()
129 .chain(Some(&checksum_byte))
130 .bits()
131 .map(|bits| wordlist.get_word(bits))
132 .join(" ");
133
134 Mnemonic {
135 secret: MnemonicSecret { entropy, phrase },
136 lang,
137 }
138 }
139
140 /// Create a [`Mnemonic`][Mnemonic] from an existing mnemonic phrase
141 ///
142 /// The phrase supplied will be checked for word length and validated according to the checksum
143 /// specified in BIP0039
144 ///
145 /// # Example
146 ///
147 /// ```
148 /// use dup_crypto::mnemonic::{Mnemonic, Language};
149 ///
150 /// let phrase = "park remain person kitchen mule spell knee armed position rail grid ankle";
151 /// let mnemonic = Mnemonic::from_phrase(phrase, Language::English).unwrap();
152 ///
153 /// assert_eq!(phrase, mnemonic.phrase());
154 /// ```
155 ///
156 /// [Mnemonic]: ../mnemonic/struct.Mnemonic.html
157 pub fn from_phrase<S>(phrase: S, lang: Language) -> Result<Mnemonic, MnemonicError>
158 where
159 S: Into<String>,
160 {
161 let phrase = phrase.into();
162
163 // this also validates the checksum and phrase length before returning the entropy so we
164 // can store it. We don't use the validate function here to avoid having a public API that
165 // takes a phrase string and returns the entropy directly.
166 let entropy = Mnemonic::phrase_to_entropy(&phrase, lang)?;
167
168 let mnemonic = Mnemonic {
169 secret: MnemonicSecret { entropy, phrase },
170 lang,
171 };
172
173 Ok(mnemonic)
174 }
175
176 /// Validate a mnemonic phrase
177 ///
178 /// The phrase supplied will be checked for word length and validated according to the checksum
179 /// specified in BIP0039.
180 ///
181 /// # Example
182 ///
183 /// ```
184 /// use dup_crypto::mnemonic::{Mnemonic, Language};
185 ///
186 /// let test_mnemonic = "park remain person kitchen mule spell knee armed position rail grid ankle";
187 ///
188 /// assert!(Mnemonic::validate(test_mnemonic, Language::English).is_ok());
189 /// ```
190 pub fn validate(phrase: &str, lang: Language) -> Result<(), MnemonicError> {
191 Mnemonic::phrase_to_entropy(phrase, lang)?;
192
193 Ok(())
194 }
195
196 /// Calculate the checksum, verify it and return the entropy
197 ///
198 /// Only intended for internal use, as returning a `Vec<u8>` that looks a bit like it could be
199 /// used as the seed is likely to cause problems for someone eventually. All the other functions
200 /// that return something like that are explicit about what it is and what to use it for.
201 fn phrase_to_entropy(phrase: &str, lang: Language) -> Result<Vec<u8>, MnemonicError> {
202 let wordmap = lang.wordmap();
203
204 // Preallocate enough space for the longest possible word list
205 let mut bits = BitWriter::with_capacity(264);
206
207 for word in phrase.split(' ') {
208 bits.push(wordmap.get_bits(&word)?);
209 }
210
211 let mtype = MnemonicType::for_word_count(bits.len() / 11)?;
212
213 debug_assert!(
214 bits.len() == mtype.total_bits(),
215 "Insufficient amount of bits to validate"
216 );
217
218 let mut entropy = bits.into_bytes();
219 let entropy_bytes = mtype.entropy_bits() / 8;
220
221 let actual_checksum = checksum(entropy[entropy_bytes], mtype.checksum_bits());
222
223 // Truncate to get rid of the byte containing the checksum
224 entropy.truncate(entropy_bytes);
225
226 let checksum_byte = sha256_first_byte(&entropy);
227 let expected_checksum = checksum(checksum_byte, mtype.checksum_bits());
228
229 if actual_checksum != expected_checksum {
230 Err(MnemonicError::InvalidChecksum)
231 } else {
232 Ok(entropy)
233 }
234 }
235
236 /// Get the mnemonic phrase as a string reference.
237 pub fn phrase(&self) -> &str {
238 &self.secret.phrase
239 }
240
241 /// Get the original entropy value of the mnemonic phrase as a slice.
242 ///
243 /// # Example
244 ///
245 /// ```
246 /// use dup_crypto::mnemonic::{Mnemonic, Language};
247 ///
248 /// let phrase = "park remain person kitchen mule spell knee armed position rail grid ankle";
249 ///
250 /// let mnemonic = Mnemonic::from_phrase(phrase, Language::English).unwrap();
251 ///
252 /// let entropy: &[u8] = mnemonic.entropy();
253 /// ```
254 ///
255 /// **Note:** You shouldn't use the generated entropy as secrets, for that generate a new
256 /// `Seed` from the `Mnemonic`.
257 pub fn entropy(&self) -> &[u8] {
258 &self.secret.entropy
259 }
260
261 /// Get the [`Language`][Language]
262 ///
263 /// [Language]: ../language/struct.Language.html
264 pub fn language(&self) -> Language {
265 self.lang
266 }
267}
268
269impl AsRef<str> for Mnemonic {
270 fn as_ref(&self) -> &str {
271 self.phrase()
272 }
273}
274
275impl fmt::Display for Mnemonic {
276 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
277 fmt::Display::fmt(self.phrase(), f)
278 }
279}
280
281impl fmt::Debug for Mnemonic {
282 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
283 fmt::Debug::fmt(self.phrase(), f)
284 }
285}
286
287impl fmt::LowerHex for Mnemonic {
288 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
289 if f.alternate() {
290 f.write_str("0x")?;
291 }
292
293 for byte in self.entropy() {
294 write!(f, "{:x}", byte)?;
295 }
296
297 Ok(())
298 }
299}
300
301impl fmt::UpperHex for Mnemonic {
302 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
303 if f.alternate() {
304 f.write_str("0x")?;
305 }
306
307 for byte in self.entropy() {
308 write!(f, "{:X}", byte)?;
309 }
310
311 Ok(())
312 }
313}
314
315impl From<Mnemonic> for String {
316 fn from(val: Mnemonic) -> String {
317 val.phrase().to_owned()
318 }
319}
320
321#[cfg(test)]
322mod test {
323 use super::*;
324
325 #[test]
326 fn back_to_back() -> Result<(), MnemonicError> {
327 let m1 = Mnemonic::new(MnemonicType::Words12, Language::English)
328 .map_err(MnemonicError::UnspecifiedRandError)?;
329 let m2 = Mnemonic::from_phrase(m1.phrase(), Language::English)?;
330 let m3 = Mnemonic::from_entropy(m1.entropy(), Language::English)?;
331
332 assert_eq!(m1.entropy(), m2.entropy(), "Entropy must be the same");
333 assert_eq!(m1.entropy(), m3.entropy(), "Entropy must be the same");
334 assert_eq!(m1.phrase(), m2.phrase(), "Phrase must be the same");
335 assert_eq!(m1.phrase(), m3.phrase(), "Phrase must be the same");
336
337 Ok(())
338 }
339
340 #[test]
341 fn mnemonic_from_entropy() -> Result<(), MnemonicError> {
342 let entropy = &[
343 0x33, 0xE4, 0x6B, 0xB1, 0x3A, 0x74, 0x6E, 0xA4, 0x1C, 0xDD, 0xE4, 0x5C, 0x90, 0x84,
344 0x6A, 0x79,
345 ];
346 let phrase = "crop cash unable insane eight faith inflict route frame loud box vibrant";
347
348 let mnemonic = Mnemonic::from_entropy(entropy, Language::English)?;
349
350 assert_eq!(phrase, mnemonic.phrase());
351
352 Ok(())
353 }
354
355 #[test]
356 fn mnemonic_from_phrase() -> Result<(), MnemonicError> {
357 let entropy = &[
358 0x33, 0xE4, 0x6B, 0xB1, 0x3A, 0x74, 0x6E, 0xA4, 0x1C, 0xDD, 0xE4, 0x5C, 0x90, 0x84,
359 0x6A, 0x79,
360 ];
361 let phrase = "crop cash unable insane eight faith inflict route frame loud box vibrant";
362
363 let mnemonic = Mnemonic::from_phrase(phrase, Language::English)?;
364
365 assert_eq!(entropy, mnemonic.entropy());
366
367 Ok(())
368 }
369
370 #[test]
371 fn mnemonic_format() -> Result<(), MnemonicError> {
372 let mnemonic = Mnemonic::new(MnemonicType::Words15, Language::English)
373 .map_err(MnemonicError::UnspecifiedRandError)?;
374
375 assert_eq!(mnemonic.phrase(), format!("{}", mnemonic));
376
377 Ok(())
378 }
379
380 #[test]
381 fn mnemonic_hex_format() -> Result<(), MnemonicError> {
382 let entropy = &[
383 0x33, 0xE4, 0x6B, 0xB1, 0x3A, 0x74, 0x6E, 0xA4, 0x1C, 0xDD, 0xE4, 0x5C, 0x90, 0x84,
384 0x6A, 0x79,
385 ];
386
387 let mnemonic = Mnemonic::from_entropy(entropy, Language::English)?;
388
389 assert_eq!(
390 format!("{:x}", mnemonic),
391 "33e46bb13a746ea41cdde45c90846a79"
392 );
393 assert_eq!(
394 format!("{:X}", mnemonic),
395 "33E46BB13A746EA41CDDE45C90846A79"
396 );
397 assert_eq!(
398 format!("{:#x}", mnemonic),
399 "0x33e46bb13a746ea41cdde45c90846a79"
400 );
401 assert_eq!(
402 format!("{:#X}", mnemonic),
403 "0x33E46BB13A746EA41CDDE45C90846A79"
404 );
405
406 Ok(())
407 }
408}