Merge pull request #8 from rust-bitcoin/sebas-comments
Fixing minor things
This commit is contained in:
commit
1786f6168e
|
@ -19,8 +19,8 @@ before_install:
|
||||||
script:
|
script:
|
||||||
- cargo build --verbose
|
- cargo build --verbose
|
||||||
- cargo test --verbose
|
- cargo test --verbose
|
||||||
- cargo build --verbose --no-default-features
|
- cargo build --verbose --no-default-features --features all-languages
|
||||||
- cargo test --verbose --no-default-features
|
- cargo test --verbose --no-default-features --features all-languages
|
||||||
- cargo build --verbose --features rand,all-languages
|
- cargo build --verbose --features rand,all-languages
|
||||||
- cargo test --verbose --features rand,all-languages
|
- cargo test --verbose --features rand,all-languages
|
||||||
# benchmarks
|
# benchmarks
|
||||||
|
|
|
@ -16,7 +16,7 @@ path = "src/lib.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "std" ]
|
default = [ "std" ]
|
||||||
std = []
|
std = [ "unicode-normalization", "serde/std" ]
|
||||||
|
|
||||||
# Note: English is the standard for bip39 so always included
|
# Note: English is the standard for bip39 so always included
|
||||||
chinese-simplified = []
|
chinese-simplified = []
|
||||||
|
@ -41,10 +41,11 @@ all-languages = [
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitcoin_hashes = "0.9.4"
|
bitcoin_hashes = "0.9.4"
|
||||||
unicode-normalization = "=0.1.9"
|
rand_core = "0.4.0"
|
||||||
|
|
||||||
|
unicode-normalization = { version = "=0.1.9", optional = true }
|
||||||
rand = { version = "0.6.0", optional = true }
|
rand = { version = "0.6.0", optional = true }
|
||||||
serde = { version = "1.0", optional = true }
|
serde = { version = "1.0", default-features = false, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = { version = "0.6.0", optional = false }
|
rand = { version = "0.6.0", optional = false }
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
use core::fmt;
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
mod english;
|
mod english;
|
||||||
#[cfg(feature = "chinese-simplified")]
|
#[cfg(feature = "chinese-simplified")]
|
||||||
|
@ -153,7 +152,6 @@ impl Language {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
impl fmt::Display for Language {
|
impl fmt::Display for Language {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
fmt::Debug::fmt(self, f)
|
fmt::Debug::fmt(self, f)
|
||||||
|
|
141
src/lib.rs
141
src/lib.rs
|
@ -27,9 +27,13 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
#![cfg_attr(all(not(test), not(feature = "std")), no_std)]
|
#![cfg_attr(all(not(test), not(feature = "std")), no_std)]
|
||||||
#[cfg(any(test, feature = "std"))] pub extern crate core;
|
|
||||||
|
#[cfg(any(test, feature = "std"))]
|
||||||
|
pub extern crate core;
|
||||||
|
|
||||||
extern crate bitcoin_hashes;
|
extern crate bitcoin_hashes;
|
||||||
|
extern crate rand_core;
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
extern crate unicode_normalization;
|
extern crate unicode_normalization;
|
||||||
|
|
||||||
|
@ -38,8 +42,10 @@ extern crate rand;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
pub extern crate serde;
|
pub extern crate serde;
|
||||||
|
|
||||||
|
use core::{fmt, str};
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::{error, fmt, str};
|
use std::error;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
@ -75,7 +81,6 @@ impl AmbiguousLanguages {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator over the possible languages.
|
/// An iterator over the possible languages.
|
||||||
#[cfg(feature = "std")]
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = Language> + '_ {
|
pub fn iter(&self) -> impl Iterator<Item = Language> + '_ {
|
||||||
Language::all().iter().enumerate().filter(move |(i, _)| self.0[*i]).map(|(_, l)| *l)
|
Language::all().iter().enumerate().filter(move |(i, _)| self.0[*i]).map(|(_, l)| *l)
|
||||||
}
|
}
|
||||||
|
@ -106,7 +111,6 @@ pub enum Error {
|
||||||
AmbiguousLanguages(AmbiguousLanguages),
|
AmbiguousLanguages(AmbiguousLanguages),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
|
@ -120,7 +124,17 @@ impl fmt::Display for Error {
|
||||||
"entropy was not between 128-256 bits or not a multiple of 32 bits: {} bits", c,
|
"entropy was not between 128-256 bits or not a multiple of 32 bits: {} bits", c,
|
||||||
),
|
),
|
||||||
Error::InvalidChecksum => write!(f, "the mnemonic has an invalid checksum"),
|
Error::InvalidChecksum => write!(f, "the mnemonic has an invalid checksum"),
|
||||||
Error::AmbiguousLanguages(a) => write!(f, "ambiguous word list: {:?}", a.to_vec()),
|
Error::AmbiguousLanguages(a) => {
|
||||||
|
write!(f, "ambiguous word list: ")?;
|
||||||
|
for (i, lang) in a.iter().enumerate() {
|
||||||
|
if i == 0 {
|
||||||
|
write!(f, "{}", lang)?;
|
||||||
|
} else {
|
||||||
|
write!(f, ", {}", lang)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,7 +144,7 @@ impl error::Error for Error {}
|
||||||
|
|
||||||
/// A mnemonic code.
|
/// A mnemonic code.
|
||||||
///
|
///
|
||||||
/// The [std::str::FromStr] implementation will try to determine the language of the
|
/// The [core::str::FromStr] implementation will try to determine the language of the
|
||||||
/// mnemonic from all the supported languages. (Languages have to be explicitly enabled using
|
/// mnemonic from all the supported languages. (Languages have to be explicitly enabled using
|
||||||
/// the Cargo features.)
|
/// the Cargo features.)
|
||||||
///
|
///
|
||||||
|
@ -202,23 +216,63 @@ impl Mnemonic {
|
||||||
Mnemonic::from_entropy_in(Language::English, entropy)
|
Mnemonic::from_entropy_in(Language::English, entropy)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a new [Mnemonic] in the given language.
|
/// Generate a new [Mnemonic] in the given language
|
||||||
|
/// with the given randomness source.
|
||||||
/// For the different supported word counts, see documentation on [Mnemonic].
|
/// For the different supported word counts, see documentation on [Mnemonic].
|
||||||
#[cfg(feature = "rand")]
|
///
|
||||||
pub fn generate_in(language: Language, word_count: usize) -> Result<Mnemonic, Error> {
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// extern crate rand;
|
||||||
|
/// extern crate bip39;
|
||||||
|
///
|
||||||
|
/// use bip39::{Mnemonic, Language};
|
||||||
|
///
|
||||||
|
/// let mut rng = rand::thread_rng();
|
||||||
|
/// let m = Mnemonic::generate_in_with(&mut rng, Language::English, 24).unwrap();
|
||||||
|
/// ```
|
||||||
|
pub fn generate_in_with<R>(rng: &mut R, language: Language, word_count: usize) -> Result<Mnemonic, Error>
|
||||||
|
where R: rand_core::RngCore + rand_core::CryptoRng,
|
||||||
|
{
|
||||||
if word_count < MIN_NB_WORDS || word_count % 6 != 0 || word_count > MAX_NB_WORDS {
|
if word_count < MIN_NB_WORDS || word_count % 6 != 0 || word_count > MAX_NB_WORDS {
|
||||||
return Err(Error::BadWordCount(word_count));
|
return Err(Error::BadWordCount(word_count));
|
||||||
}
|
}
|
||||||
|
|
||||||
let entropy_bytes = (word_count / 3) * 4;
|
let entropy_bytes = (word_count / 3) * 4;
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
let mut entropy = [0u8; (MAX_NB_WORDS / 3) * 4];
|
let mut entropy = [0u8; (MAX_NB_WORDS / 3) * 4];
|
||||||
rand::RngCore::fill_bytes(&mut rng, &mut entropy[0..entropy_bytes]);
|
rand_core::RngCore::fill_bytes(rng, &mut entropy[0..entropy_bytes]);
|
||||||
Mnemonic::from_entropy_in(language, &entropy[0..entropy_bytes])
|
Mnemonic::from_entropy_in(language, &entropy[0..entropy_bytes])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a new [Mnemonic] in the given language.
|
||||||
|
/// For the different supported word counts, see documentation on [Mnemonic].
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// extern crate bip39;
|
||||||
|
///
|
||||||
|
/// use bip39::{Mnemonic, Language};
|
||||||
|
///
|
||||||
|
/// let m = Mnemonic::generate_in(Language::English, 24).unwrap();
|
||||||
|
/// ```
|
||||||
|
#[cfg(feature = "rand")]
|
||||||
|
pub fn generate_in(language: Language, word_count: usize) -> Result<Mnemonic, Error> {
|
||||||
|
Mnemonic::generate_in_with(&mut rand::thread_rng(), language, word_count)
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate a new [Mnemonic] in English.
|
/// Generate a new [Mnemonic] in English.
|
||||||
/// For the different supported word counts, see documentation on [Mnemonic].
|
/// For the different supported word counts, see documentation on [Mnemonic].
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// extern crate bip39;
|
||||||
|
///
|
||||||
|
/// use bip39::{Mnemonic,};
|
||||||
|
///
|
||||||
|
/// let m = Mnemonic::generate(24).unwrap();
|
||||||
|
/// ```
|
||||||
#[cfg(feature = "rand")]
|
#[cfg(feature = "rand")]
|
||||||
pub fn generate(word_count: usize) -> Result<Mnemonic, Error> {
|
pub fn generate(word_count: usize) -> Result<Mnemonic, Error> {
|
||||||
Mnemonic::generate_in(Language::English, word_count)
|
Mnemonic::generate_in(Language::English, word_count)
|
||||||
|
@ -240,18 +294,8 @@ impl Mnemonic {
|
||||||
return Err(Error::BadWordCount(0));
|
return Err(Error::BadWordCount(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// For efficiency reasons, we add a special case for when there's
|
// We first try find the first word in wordlists that
|
||||||
// only a single language enabled.
|
// have guaranteed unique words.
|
||||||
if langs.len() == 1 {
|
|
||||||
let lang = langs[0];
|
|
||||||
return if lang.find_word(first_word).is_some() {
|
|
||||||
Ok(lang)
|
|
||||||
} else {
|
|
||||||
Err(Error::UnknownWord(0))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise we first try wordlists that have guaranteed unique words.
|
|
||||||
for language in langs.iter().filter(|l| l.unique_words()) {
|
for language in langs.iter().filter(|l| l.unique_words()) {
|
||||||
if language.find_word(first_word).is_some() {
|
if language.find_word(first_word).is_some() {
|
||||||
return Ok(*language);
|
return Ok(*language);
|
||||||
|
@ -271,20 +315,22 @@ impl Mnemonic {
|
||||||
for (idx, word) in words.enumerate() {
|
for (idx, word) in words.enumerate() {
|
||||||
// Scrap languages that don't have this word.
|
// Scrap languages that don't have this word.
|
||||||
for (i, lang) in langs.iter().enumerate() {
|
for (i, lang) in langs.iter().enumerate() {
|
||||||
if possible[i] {
|
possible[i] &= lang.find_word(word).is_some();
|
||||||
possible[i] = lang.find_word(word).is_some();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is just one language left, return it.
|
// Get an iterator over remaining possible languages.
|
||||||
let nb_possible = possible.iter().filter(|p| **p).count();
|
let mut iter = possible.iter().zip(langs.iter()).filter(|(p, _)| **p).map(|(_, l)| l);
|
||||||
if nb_possible == 1 {
|
|
||||||
return Ok(*possible.iter().zip(langs.iter()).find(|&(p, _)| *p).map(|(_, l)| l).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
match iter.next() {
|
||||||
// If all languages were eliminated, it's an invalid word.
|
// If all languages were eliminated, it's an invalid word.
|
||||||
if nb_possible == 0 {
|
None => return Err(Error::UnknownWord(idx)),
|
||||||
return Err(Error::UnknownWord(idx));
|
// If not, see if there is a second one remaining.
|
||||||
|
Some(remaining) => {
|
||||||
|
if iter.next().is_none() {
|
||||||
|
// No second remaining, we found our language.
|
||||||
|
return Ok(*remaining);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,8 +435,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(&self.0, normalized_passphrase.as_bytes(), PBKDF2_ROUNDS, &mut seed);
|
pbkdf2::pbkdf2(&self.0[0..nb_words], normalized_passphrase.as_bytes(), PBKDF2_ROUNDS, &mut seed);
|
||||||
seed
|
seed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,7 +498,6 @@ impl Mnemonic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
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 in 0..self.0.len() {
|
||||||
|
@ -468,12 +514,14 @@ impl fmt::Display for Mnemonic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
impl str::FromStr for Mnemonic {
|
impl str::FromStr for Mnemonic {
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Mnemonic, Error> {
|
fn from_str(s: &str) -> Result<Mnemonic, Error> {
|
||||||
Mnemonic::parse(s)
|
#[cfg(feature = "std")]
|
||||||
|
{ Mnemonic::parse(s) }
|
||||||
|
#[cfg(not(feature = "std"))]
|
||||||
|
{ Mnemonic::parse_normalized(s) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -483,17 +531,6 @@ mod tests {
|
||||||
|
|
||||||
use bitcoin_hashes::hex::FromHex;
|
use bitcoin_hashes::hex::FromHex;
|
||||||
|
|
||||||
#[cfg(feature = "rand")]
|
|
||||||
#[test]
|
|
||||||
fn test_bit_counts() {
|
|
||||||
let m = Mnemonic::generate(12).unwrap();
|
|
||||||
assert_eq!(m.word_count(), 12);
|
|
||||||
let m = Mnemonic::generate(18).unwrap();
|
|
||||||
assert_eq!(m.word_count(), 18);
|
|
||||||
let m = Mnemonic::generate(24).unwrap();
|
|
||||||
assert_eq!(m.word_count(), 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "rand")]
|
#[cfg(feature = "rand")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_language_of() {
|
fn test_language_of() {
|
||||||
|
@ -524,6 +561,14 @@ mod tests {
|
||||||
assert_eq!(amb.iter().collect::<Vec<_>>(), present_vec);
|
assert_eq!(amb.iter().collect::<Vec<_>>(), present_vec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rand")]
|
||||||
|
#[test]
|
||||||
|
fn test_generate() {
|
||||||
|
let _ = Mnemonic::generate(24).unwrap();
|
||||||
|
let _ = Mnemonic::generate_in(Language::English, 24).unwrap();
|
||||||
|
let _ = Mnemonic::generate_in_with(&mut rand::thread_rng(), Language::English, 24).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vectors_english() {
|
fn test_vectors_english() {
|
||||||
// These vectors are tuples of
|
// These vectors are tuples of
|
||||||
|
|
|
@ -8,9 +8,6 @@ fn mnemonic_byte_len(mnemonic: &[&'static str]) -> usize {
|
||||||
let mut len = 0;
|
let mut len = 0;
|
||||||
for i in 0..mnemonic.len() {
|
for i in 0..mnemonic.len() {
|
||||||
let word = &mnemonic[i];
|
let word = &mnemonic[i];
|
||||||
if word.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
len += 1;
|
len += 1;
|
||||||
}
|
}
|
||||||
|
@ -23,9 +20,6 @@ fn mnemonic_byte_len(mnemonic: &[&'static str]) -> usize {
|
||||||
fn mnemonic_write_into(mnemonic: &[&'static str], engine: &mut sha512::HashEngine) {
|
fn mnemonic_write_into(mnemonic: &[&'static str], engine: &mut sha512::HashEngine) {
|
||||||
for i in 0..mnemonic.len() {
|
for i in 0..mnemonic.len() {
|
||||||
let word = &mnemonic[i];
|
let word = &mnemonic[i];
|
||||||
if word.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
engine.input(" ".as_bytes());
|
engine.input(" ".as_bytes());
|
||||||
}
|
}
|
||||||
|
@ -61,9 +55,6 @@ fn create_hmac_engine(mnemonic: &[&'static str]) -> hmac::HmacEngine<sha512::Has
|
||||||
let mut cursor = 0;
|
let mut cursor = 0;
|
||||||
for i in 0..mnemonic.len() {
|
for i in 0..mnemonic.len() {
|
||||||
let word = &mnemonic[i];
|
let word = &mnemonic[i];
|
||||||
if word.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
ipad[cursor] ^= ' ' as u8;
|
ipad[cursor] ^= ' ' as u8;
|
||||||
opad[cursor] ^= ' ' as u8;
|
opad[cursor] ^= ' ' as u8;
|
||||||
|
@ -88,8 +79,6 @@ fn create_hmac_engine(mnemonic: &[&'static str]) -> hmac::HmacEngine<sha512::Has
|
||||||
// Method borrowed from rust-bitcoin's endian module.
|
// Method borrowed from rust-bitcoin's endian module.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn u32_to_array_be(val: u32) -> [u8; 4] {
|
fn u32_to_array_be(val: u32) -> [u8; 4] {
|
||||||
debug_assert_eq!(::core::mem::size_of::<u32>(), 4); // size_of isn't a constfn in 1.22
|
|
||||||
|
|
||||||
let mut res = [0; 4];
|
let mut res = [0; 4];
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
res[i] = ((val >> (4 - i - 1)*8) & 0xff) as u8;
|
res[i] = ((val >> (4 - i - 1)*8) & 0xff) as u8;
|
||||||
|
|
Loading…
Reference in New Issue