Merge pull request #11 from rust-bitcoin/indices
Use indices internally instead of &'static str + language
This commit is contained in:
commit
520e15a6e5
|
@ -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
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "bip39"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
authors = ["Steven Roose <steven@stevenroose.org>"]
|
||||
license = "CC0-1.0"
|
||||
homepage = "https://github.com/rust-bitcoin/rust-bip39/"
|
||||
|
|
|
@ -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<usize> {
|
||||
self.word_list().iter().position(|w| *w == word)
|
||||
pub(crate) fn find_word(self, word: &str) -> Option<u16> {
|
||||
self.word_list().iter().position(|w| *w == word).map(|i| i as u16)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
52
src/lib.rs
52
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<Item = &'static str> + '_ {
|
||||
self.0.iter().take_while(|w| !w.is_empty()).map(|w| *w)
|
||||
pub fn word_iter(&self) -> impl Iterator<Item = &'static str> + 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<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())
|
||||
}
|
||||
|
||||
|
@ -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(" ")?;
|
||||
}
|
||||
|
|
|
@ -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<M>(mnemonic: M) -> usize
|
||||
where M: Iterator<Item = &'static str> + 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<M>(mnemonic: M, engine: &mut sha512::HashEngine)
|
||||
where M: Iterator<Item = &'static str> + 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<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.
|
||||
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<sha512::Has
|
|||
} else {
|
||||
// First modify the first elements from the prefix.
|
||||
let mut cursor = 0;
|
||||
for i in 0..mnemonic.len() {
|
||||
let word = &mnemonic[i];
|
||||
for (i, word) in mnemonic.enumerate() {
|
||||
if i > 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<M>(mnemonic: M, unprefixed_salt: &[u8], c: usize, res: &mut [u8])
|
||||
where M: Iterator<Item = &'static str> + Clone,
|
||||
{
|
||||
let prf = create_hmac_engine(mnemonic);
|
||||
|
||||
for (i, chunk) in res.chunks_mut(sha512::Hash::LEN).enumerate() {
|
||||
|
|
Loading…
Reference in New Issue