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
|
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
|
||||||
|
|
||||||
|
|
|
@ -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/"
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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.
|
/// 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(" ")?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in New Issue