diff --git a/CHANGELOG.md b/CHANGELOG.md index aeed3a9..dbe376a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +# v1.0.1 + +- Add `Mnemonic::language` getter. +- Make `Mnemonic::language_of` static method public. +- Change internal representation of `Mnemonic`, making it slightly smaller. # v1.0.0 diff --git a/Cargo.toml b/Cargo.toml index 3836a0e..cd4c2aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bip39" -version = "1.0.0" +version = "1.0.1" authors = ["Steven Roose "] license = "CC0-1.0" homepage = "https://github.com/rust-bitcoin/rust-bip39/" diff --git a/src/language/mod.rs b/src/language/mod.rs index 18c7759..10e0a44 100644 --- a/src/language/mod.rs +++ b/src/language/mod.rs @@ -146,8 +146,8 @@ impl Language { /// Get the index of the word in the word list. #[inline] - pub(crate) fn find_word(self, word: &str) -> Option { - self.word_list().iter().position(|w| *w == word) + pub(crate) fn find_word(self, word: &str) -> Option { + self.word_list().iter().position(|w| *w == word).map(|i| i as u16) } } diff --git a/src/lib.rs b/src/lib.rs index 3f4ffdb..79cb52f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,9 @@ const MIN_NB_WORDS: usize = 12; /// The maximum number of words in a mnemonic. const MAX_NB_WORDS: usize = 24; +/// The index used to indicate the mnemonic ended. +const EOF: u16 = u16::max_value(); + /// A structured used in the [Error::AmbiguousLanguages] variant that iterates /// over the possible languages. #[derive(Debug, Clone, PartialEq, Eq, Copy)] @@ -149,7 +152,13 @@ impl error::Error for Error {} /// /// Supported number of words are 12, 18 and 24. #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct Mnemonic([&'static str; MAX_NB_WORDS]); +pub struct Mnemonic { + /// The language the mnemonic. + lang: Language, + /// The indiced of the words. + /// Mnemonics with less than the max nb of words are terminated with EOF. + words: [u16; MAX_NB_WORDS], +} serde_string_impl!(Mnemonic, "a BIP-39 Mnemonic Code"); @@ -194,7 +203,7 @@ impl Mnemonic { bits[8 * nb_bytes + i] = (check[i / 8] & (1 << (7 - (i % 8)))) > 0; } - let mut words: [&'static str; MAX_NB_WORDS] = Default::default(); + let mut words = [EOF; MAX_NB_WORDS]; let nb_words = nb_bytes * 3 / 4; for i in 0..nb_words { let mut idx = 0; @@ -203,10 +212,13 @@ impl Mnemonic { idx += 1 << (10 - j); } } - words[i] = language.word_list()[idx]; + words[i] = idx; } - Ok(Mnemonic(words)) + Ok(Mnemonic { + lang: language, + words: words, + }) } /// Create a new English [Mnemonic] from the given entropy. @@ -282,9 +294,15 @@ impl Mnemonic { Mnemonic::generate_in(Language::English, word_count) } + /// Get the language of the [Mnemonic]. + pub fn language(&self) -> Language { + self.lang + } + /// Get an iterator over the words. - pub fn word_iter(&self) -> impl Iterator + '_ { - self.0.iter().take_while(|w| !w.is_empty()).map(|w| *w) + pub fn word_iter(&self) -> impl Iterator + Clone + '_ { + let list = self.lang.word_list(); + self.words.iter().take_while(|w| **w != EOF).map(move |w| list[*w as usize]) } /// Determine the language of the mnemonic as a word iterator. @@ -352,7 +370,7 @@ impl Mnemonic { /// word lists. In the extremely unlikely case that a word list can be /// interpreted in multiple languages, an [Error::AmbiguousLanguages] is /// returned, containing the possible languages. - fn language_of>(mnemonic: S) -> Result { + pub fn language_of>(mnemonic: S) -> Result { Mnemonic::language_of_iter(mnemonic.as_ref().split_whitespace()) } @@ -364,7 +382,7 @@ impl Mnemonic { } // Here we will store the eventual words. - let mut words: [&'static str; MAX_NB_WORDS] = Default::default(); + let mut words = [EOF; MAX_NB_WORDS]; // And here we keep track of the bits to calculate and validate the checksum. // We only use `nb_words * 11` elements in this array. @@ -373,7 +391,7 @@ impl Mnemonic { for (i, word) in s.split_whitespace().enumerate() { let idx = language.find_word(word).ok_or(Error::UnknownWord(i))?; - words[i] = language.word_list()[idx]; + words[i] = idx; for j in 0..11 { bits[i * 11 + j] = idx >> (10 - j) & 1 == 1; @@ -398,7 +416,10 @@ impl Mnemonic { } } - Ok(Mnemonic(words)) + Ok(Mnemonic { + lang: language, + words: words, + }) } /// Parse a mnemonic in normalized UTF8. @@ -435,7 +456,7 @@ impl Mnemonic { /// Get the number of words in the mnemonic. pub fn word_count(&self) -> usize { - self.0.iter().take_while(|w| !w.is_empty()).count() + self.words.iter().take_while(|w| **w != EOF).count() } /// Convert to seed bytes with a passphrase in normalized UTF8. @@ -443,10 +464,9 @@ impl Mnemonic { const PBKDF2_ROUNDS: usize = 2048; const PBKDF2_BYTES: usize = 64; - let nb_words = self.word_count(); let mut seed = [0u8; PBKDF2_BYTES]; pbkdf2::pbkdf2( - &self.0[0..nb_words], + self.word_iter(), normalized_passphrase.as_bytes(), PBKDF2_ROUNDS, &mut seed, @@ -513,11 +533,7 @@ impl Mnemonic { impl fmt::Display for Mnemonic { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - for i in 0..self.0.len() { - let word = &self.0[i]; - if word.is_empty() { - break; - } + for (i, word) in self.word_iter().enumerate() { if i > 0 { f.write_str(" ")?; } diff --git a/src/pbkdf2.rs b/src/pbkdf2.rs index f9cbf80..e7d3375 100644 --- a/src/pbkdf2.rs +++ b/src/pbkdf2.rs @@ -3,10 +3,11 @@ use bitcoin_hashes::{hmac, sha512, Hash, HashEngine}; const SALT_PREFIX: &'static str = "mnemonic"; /// Calculate the binary size of the mnemonic. -fn mnemonic_byte_len(mnemonic: &[&'static str]) -> usize { +fn mnemonic_byte_len(mnemonic: M) -> usize + where M: Iterator + Clone, +{ let mut len = 0; - for i in 0..mnemonic.len() { - let word = &mnemonic[i]; + for (i, word) in mnemonic.enumerate() { if i > 0 { len += 1; } @@ -16,9 +17,10 @@ fn mnemonic_byte_len(mnemonic: &[&'static str]) -> usize { } /// Wrote the mnemonic in binary form into the hash engine. -fn mnemonic_write_into(mnemonic: &[&'static str], engine: &mut sha512::HashEngine) { - for i in 0..mnemonic.len() { - let word = &mnemonic[i]; +fn mnemonic_write_into(mnemonic: M, engine: &mut sha512::HashEngine) + where M: Iterator + Clone, +{ + for (i, word) in mnemonic.enumerate() { if i > 0 { engine.input(" ".as_bytes()); } @@ -29,14 +31,16 @@ fn mnemonic_write_into(mnemonic: &[&'static str], engine: &mut sha512::HashEngin /// Create an HMAC engine from the passphrase. /// We need a special method because we can't allocate a new byte /// vector for the entire serialized mnemonic. -fn create_hmac_engine(mnemonic: &[&'static str]) -> hmac::HmacEngine { +fn create_hmac_engine(mnemonic: M) -> hmac::HmacEngine + where M: Iterator + Clone, +{ // Inner code is borrowed from the bitcoin_hashes::hmac::HmacEngine::new method. let mut ipad = [0x36u8; 128]; let mut opad = [0x5cu8; 128]; let mut iengine = sha512::Hash::engine(); let mut oengine = sha512::Hash::engine(); - if mnemonic_byte_len(mnemonic) > sha512::HashEngine::BLOCK_SIZE { + if mnemonic_byte_len(mnemonic.clone()) > sha512::HashEngine::BLOCK_SIZE { let hash = { let mut engine = sha512::Hash::engine(); mnemonic_write_into(mnemonic, &mut engine); @@ -52,8 +56,7 @@ fn create_hmac_engine(mnemonic: &[&'static str]) -> hmac::HmacEngine 0 { ipad[cursor] ^= ' ' as u8; opad[cursor] ^= ' ' as u8; @@ -93,7 +96,9 @@ fn xor(res: &mut [u8], salt: &[u8]) { } /// PBKDF2-HMAC-SHA512 implementation using bitcoin_hashes. -pub(crate) fn pbkdf2(mnemonic: &[&'static str], unprefixed_salt: &[u8], c: usize, res: &mut [u8]) { +pub(crate) fn pbkdf2(mnemonic: M, unprefixed_salt: &[u8], c: usize, res: &mut [u8]) + where M: Iterator + Clone, +{ let prf = create_hmac_engine(mnemonic); for (i, chunk) in res.chunks_mut(sha512::Hash::LEN).enumerate() {