1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
use ::bip39::{Mnemonic, MnemonicType};
use getrandom::getrandom;

use super::*;

/// Tool for generating, validating and parsing BIP39 phrases in different supported languages.
#[derive(Clone, Copy, Debug, Default)]
pub struct Bip39 {
    lang: Bip39Language,
}

impl Bip39 {
    /// Number of words in the BIP39 mnemonics we accept and generate
    pub const MNEMONIC_WORDS: usize = 24;

    /// Creates the right sized entropy using the CSPRNG available on the platform.
    pub fn generate_entropy() -> Result<[u8; 32]> {
        let mut entropy = [0u8; 256 / 8];
        getrandom(&mut entropy)?;
        Ok(entropy)
    }

    /// Creates a new English BIP39 phrase handler
    pub fn new() -> Self {
        Self { lang: Bip39Language::English }
    }

    /// Creates a new BIP39 phrase handler with the given language
    pub fn language(lang: Bip39Language) -> Self {
        Self { lang }
    }

    /// Creates a new BIP39 phrase handler with the given language code
    ///
    /// # Example
    ///
    /// ```
    /// # use iop_keyvault::Bip39;
    /// let bip39 = Bip39::language_code("fr").unwrap();
    /// assert!(bip39.check_word("aménager"));
    /// ```
    pub fn language_code(code: impl AsRef<str>) -> Result<Self> {
        let lang = Bip39Language::from_language_code(code.as_ref())
            .ok_or_else(|| anyhow!("Invalid BIP39 language code"))?;
        Ok(Self { lang })
    }

    /// Creates a new phrase from hardware entropy. Cannot be used from WASM because it uses the system random generator.
    ///
    /// # Example
    /// ```
    /// # use iop_keyvault::{Bip39, Seed};
    /// let phrase: &str = Bip39::language_code("fr").unwrap().generate().as_phrase();
    /// let seed: Seed = Bip39::new().generate().password("");
    pub fn generate(self) -> Bip39Phrase {
        let size = MnemonicType::for_word_count(Self::MNEMONIC_WORDS).unwrap();
        let mnemonic = Mnemonic::new(size, self.lang);
        Bip39Phrase { mnemonic }
    }

    /// Checks if a word is present in the BIP39 dictionary
    ///
    /// # Example
    ///
    /// ```
    /// # use iop_keyvault::Bip39;
    /// let bip39 = Bip39::new();
    /// assert!(bip39.check_word("abandon"));
    /// assert!(!bip39.check_word("Abandon"));
    /// assert!(!bip39.check_word("avalon"));
    /// ```
    pub fn check_word(self, word: &str) -> bool {
        self.lang.wordmap().get_bits(word).is_ok()
    }

    /// Lists all words in the BIP39 dictionary, which start with the given prefix.
    ///
    /// Can be used in 3 different ways:
    /// - When the prefix is empty, the sorted list of all words are returned
    /// - When the prefix is a partial word, the returned list can be used for auto-completion
    /// - When the returned list is empty, the prefix is not a valid word in the dictionary
    ///
    /// # Example
    ///
    /// ```
    /// # use iop_keyvault::{Bip39, Bip39Language};
    /// let bip39 = Bip39::language(Bip39Language::English);
    /// assert_eq!(bip39.list_words("").len(), 2048);
    /// assert_eq!(bip39.list_words("woo"), ["wood", "wool"]);
    /// assert!(bip39.list_words("woof").is_empty());
    /// ```
    pub fn list_words(self, prefix: impl AsRef<str>) -> &'static [&'static str] {
        self.lang.wordlist().get_words_by_prefix(prefix.as_ref())
    }

    /// Validates a whole BIP39 mnemonic phrase. Because the phrase contains some checksum, the
    /// whole phrase can be invalid even when each word itself is valid.
    ///
    /// # Example
    ///
    /// ```
    /// # use iop_keyvault::Bip39;
    /// let bip39 = Bip39::new();
    /// assert!(bip39.validate("type shield target dream feature surface search flee tenant cake taxi shrug").is_ok());
    /// assert!(bip39.validate("abandon abandon about").unwrap_err().to_string().contains("invalid number of words"));
    /// ```
    pub fn validate(self, phrase: impl AsRef<str>) -> Result<()> {
        Mnemonic::validate(phrase.as_ref(), self.lang)
    }

    /// Validates a whole BIP39 mnemonic phrase and returns an intermediate object that can be
    /// later converted into a [`Seed`] with an optional password.
    ///
    /// [`Seed`]: struct.Seed.html
    pub fn phrase(self, phrase: impl AsRef<str>) -> Result<Bip39Phrase> {
        if phrase.as_ref().split(' ').count() != Bip39::MNEMONIC_WORDS {
            bail!("Only {}-word mnemonics are supported", Bip39::MNEMONIC_WORDS)
        }
        self.short_phrase(phrase)
    }

    /// Use the [`phrase`] method whenever possible. This method allows some legacy use-cases to
    /// provide mnemonics shorter than [`MNEMONIC_WORDS`] words.
    ///
    /// [`phrase`]: #method.phrase
    /// [`MNEMONIC_WORDS`]: ../constant.MNEMONIC_WORDS
    pub fn short_phrase(self, phrase: impl AsRef<str>) -> Result<Bip39Phrase> {
        let mnemonic = Mnemonic::from_phrase(phrase.as_ref(), self.lang)?;
        Ok(Bip39Phrase { mnemonic })
    }

    /// Creates a BIP39 phrase based on the 256 bits of entropy provided. This method is needed from WASM
    /// because [`generate`] uses system resources unavailable from WASM.
    pub fn entropy(self, entropy: impl AsRef<[u8]>) -> Result<Bip39Phrase> {
        let size = MnemonicType::for_word_count(Self::MNEMONIC_WORDS).unwrap();
        if entropy.as_ref().len() * 8 != size.entropy_bits() {
            bail!("Only {}-bit entropy is supported", size.entropy_bits())
        }
        self.short_entropy(entropy)
    }

    /// Use the ['entropy'] method whenever possible. This method allows some legacy use-cases to
    /// provide mnemonics with low entropy.
    pub fn short_entropy(self, entropy: impl AsRef<[u8]>) -> Result<Bip39Phrase> {
        let mnemonic = Mnemonic::from_entropy(entropy.as_ref(), self.lang)?;
        Ok(Bip39Phrase { mnemonic })
    }
}

/// A thin wrapper on top of a BIP39 phrase with a known language
pub struct Bip39Phrase {
    mnemonic: Mnemonic,
}

impl Bip39Phrase {
    /// Creates a [`Seed`] from the phrase with the given password. Give empty string when the user did not provide any password.
    pub fn password(&self, password: impl AsRef<str>) -> Seed {
        Seed::from_bip39(&self.mnemonic, password.as_ref())
    }

    /// Returns the phrase as a readable string
    pub fn as_phrase(&self) -> &str {
        self.mnemonic.phrase()
    }
}