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}