Merge pull request #11 from rust-bitcoin/indices

Use indices internally instead of &'static str + language
This commit is contained in:
Steven Roose 2021-04-01 18:55:28 +01:00 committed by GitHub
commit 520e15a6e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 58 additions and 32 deletions

View File

@ -1,6 +1,11 @@
CHANGELOG 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 # v1.0.0

View File

@ -1,6 +1,6 @@
[package] [package]
name = "bip39" name = "bip39"
version = "1.0.0" version = "1.0.1"
authors = ["Steven Roose <steven@stevenroose.org>"] authors = ["Steven Roose <steven@stevenroose.org>"]
license = "CC0-1.0" license = "CC0-1.0"
homepage = "https://github.com/rust-bitcoin/rust-bip39/" homepage = "https://github.com/rust-bitcoin/rust-bip39/"

View File

@ -146,8 +146,8 @@ impl Language {
/// Get the index of the word in the word list. /// Get the index of the word in the word list.
#[inline] #[inline]
pub(crate) fn find_word(self, word: &str) -> Option<usize> { pub(crate) fn find_word(self, word: &str) -> Option<u16> {
self.word_list().iter().position(|w| *w == word) self.word_list().iter().position(|w| *w == word).map(|i| i as u16)
} }
} }

View File

@ -67,6 +67,9 @@ const MIN_NB_WORDS: usize = 12;
/// The maximum number of words in a mnemonic. /// The maximum number of words in a mnemonic.
const MAX_NB_WORDS: usize = 24; 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 /// A structured used in the [Error::AmbiguousLanguages] variant that iterates
/// over the possible languages. /// over the possible languages.
#[derive(Debug, Clone, PartialEq, Eq, Copy)] #[derive(Debug, Clone, PartialEq, Eq, Copy)]
@ -149,7 +152,13 @@ impl error::Error for Error {}
/// ///
/// Supported number of words are 12, 18 and 24. /// Supported number of words are 12, 18 and 24.
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] #[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"); 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; 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; let nb_words = nb_bytes * 3 / 4;
for i in 0..nb_words { for i in 0..nb_words {
let mut idx = 0; let mut idx = 0;
@ -203,10 +212,13 @@ impl Mnemonic {
idx += 1 << (10 - j); 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. /// Create a new English [Mnemonic] from the given entropy.
@ -282,9 +294,15 @@ impl Mnemonic {
Mnemonic::generate_in(Language::English, word_count) 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. /// Get an iterator over the words.
pub fn word_iter(&self) -> impl Iterator<Item = &'static str> + '_ { pub fn word_iter(&self) -> impl Iterator<Item = &'static str> + Clone + '_ {
self.0.iter().take_while(|w| !w.is_empty()).map(|w| *w) 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. /// 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 /// word lists. In the extremely unlikely case that a word list can be
/// interpreted in multiple languages, an [Error::AmbiguousLanguages] is /// interpreted in multiple languages, an [Error::AmbiguousLanguages] is
/// returned, containing the possible languages. /// returned, containing the possible languages.
fn language_of<S: AsRef<str>>(mnemonic: S) -> Result<Language, Error> { pub fn language_of<S: AsRef<str>>(mnemonic: S) -> Result<Language, Error> {
Mnemonic::language_of_iter(mnemonic.as_ref().split_whitespace()) Mnemonic::language_of_iter(mnemonic.as_ref().split_whitespace())
} }
@ -364,7 +382,7 @@ impl Mnemonic {
} }
// Here we will store the eventual words. // 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. // And here we keep track of the bits to calculate and validate the checksum.
// We only use `nb_words * 11` elements in this array. // We only use `nb_words * 11` elements in this array.
@ -373,7 +391,7 @@ impl Mnemonic {
for (i, word) in s.split_whitespace().enumerate() { for (i, word) in s.split_whitespace().enumerate() {
let idx = language.find_word(word).ok_or(Error::UnknownWord(i))?; 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 { for j in 0..11 {
bits[i * 11 + j] = idx >> (10 - j) & 1 == 1; 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. /// Parse a mnemonic in normalized UTF8.
@ -435,7 +456,7 @@ impl Mnemonic {
/// Get the number of words in the mnemonic. /// Get the number of words in the mnemonic.
pub fn word_count(&self) -> usize { 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. /// Convert to seed bytes with a passphrase in normalized UTF8.
@ -443,10 +464,9 @@ impl Mnemonic {
const PBKDF2_ROUNDS: usize = 2048; const PBKDF2_ROUNDS: usize = 2048;
const PBKDF2_BYTES: usize = 64; const PBKDF2_BYTES: usize = 64;
let nb_words = self.word_count();
let mut seed = [0u8; PBKDF2_BYTES]; let mut seed = [0u8; PBKDF2_BYTES];
pbkdf2::pbkdf2( pbkdf2::pbkdf2(
&self.0[0..nb_words], self.word_iter(),
normalized_passphrase.as_bytes(), normalized_passphrase.as_bytes(),
PBKDF2_ROUNDS, PBKDF2_ROUNDS,
&mut seed, &mut seed,
@ -513,11 +533,7 @@ impl Mnemonic {
impl fmt::Display for Mnemonic { impl fmt::Display for Mnemonic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for i in 0..self.0.len() { for (i, word) in self.word_iter().enumerate() {
let word = &self.0[i];
if word.is_empty() {
break;
}
if i > 0 { if i > 0 {
f.write_str(" ")?; f.write_str(" ")?;
} }

View File

@ -3,10 +3,11 @@ use bitcoin_hashes::{hmac, sha512, Hash, HashEngine};
const SALT_PREFIX: &'static str = "mnemonic"; const SALT_PREFIX: &'static str = "mnemonic";
/// Calculate the binary size of the mnemonic. /// Calculate the binary size of the mnemonic.
fn mnemonic_byte_len(mnemonic: &[&'static str]) -> usize { fn mnemonic_byte_len<M>(mnemonic: M) -> usize
where M: Iterator<Item = &'static str> + Clone,
{
let mut len = 0; let mut len = 0;
for i in 0..mnemonic.len() { for (i, word) in mnemonic.enumerate() {
let word = &mnemonic[i];
if i > 0 { if i > 0 {
len += 1; len += 1;
} }
@ -16,9 +17,10 @@ fn mnemonic_byte_len(mnemonic: &[&'static str]) -> usize {
} }
/// Wrote the mnemonic in binary form into the hash engine. /// Wrote the mnemonic in binary form into the hash engine.
fn mnemonic_write_into(mnemonic: &[&'static str], engine: &mut sha512::HashEngine) { fn mnemonic_write_into<M>(mnemonic: M, engine: &mut sha512::HashEngine)
for i in 0..mnemonic.len() { where M: Iterator<Item = &'static str> + Clone,
let word = &mnemonic[i]; {
for (i, word) in mnemonic.enumerate() {
if i > 0 { if i > 0 {
engine.input(" ".as_bytes()); 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. /// Create an HMAC engine from the passphrase.
/// We need a special method because we can't allocate a new byte /// We need a special method because we can't allocate a new byte
/// vector for the entire serialized mnemonic. /// vector for the entire serialized mnemonic.
fn create_hmac_engine(mnemonic: &[&'static str]) -> hmac::HmacEngine<sha512::Hash> { fn create_hmac_engine<M>(mnemonic: M) -> hmac::HmacEngine<sha512::Hash>
where M: Iterator<Item = &'static str> + Clone,
{
// Inner code is borrowed from the bitcoin_hashes::hmac::HmacEngine::new method. // Inner code is borrowed from the bitcoin_hashes::hmac::HmacEngine::new method.
let mut ipad = [0x36u8; 128]; let mut ipad = [0x36u8; 128];
let mut opad = [0x5cu8; 128]; let mut opad = [0x5cu8; 128];
let mut iengine = sha512::Hash::engine(); let mut iengine = sha512::Hash::engine();
let mut oengine = 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 hash = {
let mut engine = sha512::Hash::engine(); let mut engine = sha512::Hash::engine();
mnemonic_write_into(mnemonic, &mut engine); mnemonic_write_into(mnemonic, &mut engine);
@ -52,8 +56,7 @@ fn create_hmac_engine(mnemonic: &[&'static str]) -> hmac::HmacEngine<sha512::Has
} else { } else {
// First modify the first elements from the prefix. // First modify the first elements from the prefix.
let mut cursor = 0; let mut cursor = 0;
for i in 0..mnemonic.len() { for (i, word) in mnemonic.enumerate() {
let word = &mnemonic[i];
if i > 0 { if i > 0 {
ipad[cursor] ^= ' ' as u8; ipad[cursor] ^= ' ' as u8;
opad[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. /// 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<M>(mnemonic: M, unprefixed_salt: &[u8], c: usize, res: &mut [u8])
where M: Iterator<Item = &'static str> + Clone,
{
let prf = create_hmac_engine(mnemonic); let prf = create_hmac_engine(mnemonic);
for (i, chunk) in res.chunks_mut(sha512::Hash::LEN).enumerate() { for (i, chunk) in res.chunks_mut(sha512::Hash::LEN).enumerate() {