Skip to content

Commit 520e15a

Browse files
authored
Merge pull request #11 from rust-bitcoin/indices
Use indices internally instead of &'static str + language
2 parents 1cc986e + 5599c45 commit 520e15a

File tree

5 files changed

+58
-32
lines changed

5 files changed

+58
-32
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
# v1.0.1
5+
6+
- Add `Mnemonic::language` getter.
7+
- Make `Mnemonic::language_of` static method public.
8+
- Change internal representation of `Mnemonic`, making it slightly smaller.
49

510
# v1.0.0
611

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bip39"
3-
version = "1.0.0"
3+
version = "1.0.1"
44
authors = ["Steven Roose <steven@stevenroose.org>"]
55
license = "CC0-1.0"
66
homepage = "https://github.com/rust-bitcoin/rust-bip39/"

src/language/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ impl Language {
146146

147147
/// Get the index of the word in the word list.
148148
#[inline]
149-
pub(crate) fn find_word(self, word: &str) -> Option<usize> {
150-
self.word_list().iter().position(|w| *w == word)
149+
pub(crate) fn find_word(self, word: &str) -> Option<u16> {
150+
self.word_list().iter().position(|w| *w == word).map(|i| i as u16)
151151
}
152152
}
153153

src/lib.rs

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ const MIN_NB_WORDS: usize = 12;
6767
/// The maximum number of words in a mnemonic.
6868
const MAX_NB_WORDS: usize = 24;
6969

70+
/// The index used to indicate the mnemonic ended.
71+
const EOF: u16 = u16::max_value();
72+
7073
/// A structured used in the [Error::AmbiguousLanguages] variant that iterates
7174
/// over the possible languages.
7275
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
@@ -149,7 +152,13 @@ impl error::Error for Error {}
149152
///
150153
/// Supported number of words are 12, 18 and 24.
151154
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
152-
pub struct Mnemonic([&'static str; MAX_NB_WORDS]);
155+
pub struct Mnemonic {
156+
/// The language the mnemonic.
157+
lang: Language,
158+
/// The indiced of the words.
159+
/// Mnemonics with less than the max nb of words are terminated with EOF.
160+
words: [u16; MAX_NB_WORDS],
161+
}
153162

154163
serde_string_impl!(Mnemonic, "a BIP-39 Mnemonic Code");
155164

@@ -194,7 +203,7 @@ impl Mnemonic {
194203
bits[8 * nb_bytes + i] = (check[i / 8] & (1 << (7 - (i % 8)))) > 0;
195204
}
196205

197-
let mut words: [&'static str; MAX_NB_WORDS] = Default::default();
206+
let mut words = [EOF; MAX_NB_WORDS];
198207
let nb_words = nb_bytes * 3 / 4;
199208
for i in 0..nb_words {
200209
let mut idx = 0;
@@ -203,10 +212,13 @@ impl Mnemonic {
203212
idx += 1 << (10 - j);
204213
}
205214
}
206-
words[i] = language.word_list()[idx];
215+
words[i] = idx;
207216
}
208217

209-
Ok(Mnemonic(words))
218+
Ok(Mnemonic {
219+
lang: language,
220+
words: words,
221+
})
210222
}
211223

212224
/// Create a new English [Mnemonic] from the given entropy.
@@ -282,9 +294,15 @@ impl Mnemonic {
282294
Mnemonic::generate_in(Language::English, word_count)
283295
}
284296

297+
/// Get the language of the [Mnemonic].
298+
pub fn language(&self) -> Language {
299+
self.lang
300+
}
301+
285302
/// Get an iterator over the words.
286-
pub fn word_iter(&self) -> impl Iterator<Item = &'static str> + '_ {
287-
self.0.iter().take_while(|w| !w.is_empty()).map(|w| *w)
303+
pub fn word_iter(&self) -> impl Iterator<Item = &'static str> + Clone + '_ {
304+
let list = self.lang.word_list();
305+
self.words.iter().take_while(|w| **w != EOF).map(move |w| list[*w as usize])
288306
}
289307

290308
/// Determine the language of the mnemonic as a word iterator.
@@ -352,7 +370,7 @@ impl Mnemonic {
352370
/// word lists. In the extremely unlikely case that a word list can be
353371
/// interpreted in multiple languages, an [Error::AmbiguousLanguages] is
354372
/// returned, containing the possible languages.
355-
fn language_of<S: AsRef<str>>(mnemonic: S) -> Result<Language, Error> {
373+
pub fn language_of<S: AsRef<str>>(mnemonic: S) -> Result<Language, Error> {
356374
Mnemonic::language_of_iter(mnemonic.as_ref().split_whitespace())
357375
}
358376

@@ -364,7 +382,7 @@ impl Mnemonic {
364382
}
365383

366384
// Here we will store the eventual words.
367-
let mut words: [&'static str; MAX_NB_WORDS] = Default::default();
385+
let mut words = [EOF; MAX_NB_WORDS];
368386

369387
// And here we keep track of the bits to calculate and validate the checksum.
370388
// We only use `nb_words * 11` elements in this array.
@@ -373,7 +391,7 @@ impl Mnemonic {
373391
for (i, word) in s.split_whitespace().enumerate() {
374392
let idx = language.find_word(word).ok_or(Error::UnknownWord(i))?;
375393

376-
words[i] = language.word_list()[idx];
394+
words[i] = idx;
377395

378396
for j in 0..11 {
379397
bits[i * 11 + j] = idx >> (10 - j) & 1 == 1;
@@ -398,7 +416,10 @@ impl Mnemonic {
398416
}
399417
}
400418

401-
Ok(Mnemonic(words))
419+
Ok(Mnemonic {
420+
lang: language,
421+
words: words,
422+
})
402423
}
403424

404425
/// Parse a mnemonic in normalized UTF8.
@@ -435,18 +456,17 @@ impl Mnemonic {
435456

436457
/// Get the number of words in the mnemonic.
437458
pub fn word_count(&self) -> usize {
438-
self.0.iter().take_while(|w| !w.is_empty()).count()
459+
self.words.iter().take_while(|w| **w != EOF).count()
439460
}
440461

441462
/// Convert to seed bytes with a passphrase in normalized UTF8.
442463
pub fn to_seed_normalized(&self, normalized_passphrase: &str) -> [u8; 64] {
443464
const PBKDF2_ROUNDS: usize = 2048;
444465
const PBKDF2_BYTES: usize = 64;
445466

446-
let nb_words = self.word_count();
447467
let mut seed = [0u8; PBKDF2_BYTES];
448468
pbkdf2::pbkdf2(
449-
&self.0[0..nb_words],
469+
self.word_iter(),
450470
normalized_passphrase.as_bytes(),
451471
PBKDF2_ROUNDS,
452472
&mut seed,
@@ -513,11 +533,7 @@ impl Mnemonic {
513533

514534
impl fmt::Display for Mnemonic {
515535
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
516-
for i in 0..self.0.len() {
517-
let word = &self.0[i];
518-
if word.is_empty() {
519-
break;
520-
}
536+
for (i, word) in self.word_iter().enumerate() {
521537
if i > 0 {
522538
f.write_str(" ")?;
523539
}

src/pbkdf2.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ use bitcoin_hashes::{hmac, sha512, Hash, HashEngine};
33
const SALT_PREFIX: &'static str = "mnemonic";
44

55
/// Calculate the binary size of the mnemonic.
6-
fn mnemonic_byte_len(mnemonic: &[&'static str]) -> usize {
6+
fn mnemonic_byte_len<M>(mnemonic: M) -> usize
7+
where M: Iterator<Item = &'static str> + Clone,
8+
{
79
let mut len = 0;
8-
for i in 0..mnemonic.len() {
9-
let word = &mnemonic[i];
10+
for (i, word) in mnemonic.enumerate() {
1011
if i > 0 {
1112
len += 1;
1213
}
@@ -16,9 +17,10 @@ fn mnemonic_byte_len(mnemonic: &[&'static str]) -> usize {
1617
}
1718

1819
/// Wrote the mnemonic in binary form into the hash engine.
19-
fn mnemonic_write_into(mnemonic: &[&'static str], engine: &mut sha512::HashEngine) {
20-
for i in 0..mnemonic.len() {
21-
let word = &mnemonic[i];
20+
fn mnemonic_write_into<M>(mnemonic: M, engine: &mut sha512::HashEngine)
21+
where M: Iterator<Item = &'static str> + Clone,
22+
{
23+
for (i, word) in mnemonic.enumerate() {
2224
if i > 0 {
2325
engine.input(" ".as_bytes());
2426
}
@@ -29,14 +31,16 @@ fn mnemonic_write_into(mnemonic: &[&'static str], engine: &mut sha512::HashEngin
2931
/// Create an HMAC engine from the passphrase.
3032
/// We need a special method because we can't allocate a new byte
3133
/// vector for the entire serialized mnemonic.
32-
fn create_hmac_engine(mnemonic: &[&'static str]) -> hmac::HmacEngine<sha512::Hash> {
34+
fn create_hmac_engine<M>(mnemonic: M) -> hmac::HmacEngine<sha512::Hash>
35+
where M: Iterator<Item = &'static str> + Clone,
36+
{
3337
// Inner code is borrowed from the bitcoin_hashes::hmac::HmacEngine::new method.
3438
let mut ipad = [0x36u8; 128];
3539
let mut opad = [0x5cu8; 128];
3640
let mut iengine = sha512::Hash::engine();
3741
let mut oengine = sha512::Hash::engine();
3842

39-
if mnemonic_byte_len(mnemonic) > sha512::HashEngine::BLOCK_SIZE {
43+
if mnemonic_byte_len(mnemonic.clone()) > sha512::HashEngine::BLOCK_SIZE {
4044
let hash = {
4145
let mut engine = sha512::Hash::engine();
4246
mnemonic_write_into(mnemonic, &mut engine);
@@ -52,8 +56,7 @@ fn create_hmac_engine(mnemonic: &[&'static str]) -> hmac::HmacEngine<sha512::Has
5256
} else {
5357
// First modify the first elements from the prefix.
5458
let mut cursor = 0;
55-
for i in 0..mnemonic.len() {
56-
let word = &mnemonic[i];
59+
for (i, word) in mnemonic.enumerate() {
5760
if i > 0 {
5861
ipad[cursor] ^= ' ' as u8;
5962
opad[cursor] ^= ' ' as u8;
@@ -93,7 +96,9 @@ fn xor(res: &mut [u8], salt: &[u8]) {
9396
}
9497

9598
/// PBKDF2-HMAC-SHA512 implementation using bitcoin_hashes.
96-
pub(crate) fn pbkdf2(mnemonic: &[&'static str], unprefixed_salt: &[u8], c: usize, res: &mut [u8]) {
99+
pub(crate) fn pbkdf2<M>(mnemonic: M, unprefixed_salt: &[u8], c: usize, res: &mut [u8])
100+
where M: Iterator<Item = &'static str> + Clone,
101+
{
97102
let prf = create_hmac_engine(mnemonic);
98103

99104
for (i, chunk) in res.chunks_mut(sha512::Hash::LEN).enumerate() {

0 commit comments

Comments
 (0)