From 7f6b18ff21798a3b0c641be088d3535c108f10ef Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 27 Oct 2023 13:27:11 +0200 Subject: [PATCH 1/2] sha2 w/o default features + version bump: 1.0.1 --- Cargo.toml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3910f6d..502b945 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tiny-bip39" -version = "1.0.0" +version = "1.0.1" authors = [ "Stephen Oliver ", "Maciej Hirsz ", @@ -27,15 +27,22 @@ italian = [] japanese = [] korean = [] spanish = [] -default-langs = ["chinese-simplified", "chinese-traditional", "french", "italian", "japanese", "korean", "spanish"] - -default = ["default-langs", "rand"] +default-langs = [ + "chinese-simplified", + "chinese-traditional", + "french", + "italian", + "japanese", + "korean", + "spanish", +] +default = [ "default-langs", "rand", "sha2/std" ] [dependencies] anyhow = "1.0.57" thiserror = "1.0.31" rustc-hash = "1.1.0" -sha2 = "0.10.2" +sha2 = { version = "0.10.2", default-features = false } hmac = "0.12.1" pbkdf2 = { version = "0.11.0", default-features = false } rand = { version = "0.8.5", optional = true } From ca2eac6fb41bd9b017a47d81151c7451e31fc77b Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Fri, 27 Oct 2023 18:52:18 +0200 Subject: [PATCH 2/2] Support for no_std --- Cargo.toml | 13 +++--- src/crypto.rs | 1 + src/error.rs | 25 +++++++--- src/language.rs | 106 +++++++++++++++++++++++-------------------- src/lib.rs | 12 +++++ src/mnemonic.rs | 10 ++-- src/mnemonic_type.rs | 6 +-- src/seed.rs | 18 ++++++-- src/util.rs | 6 ++- 9 files changed, 121 insertions(+), 76 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 502b945..db4214b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,19 +36,20 @@ default-langs = [ "korean", "spanish", ] -default = [ "default-langs", "rand", "sha2/std" ] +default = [ "default-langs", "rand", "std" ] +std = ["sha2/std", "anyhow/std", "rustc-hash/std", "unicode-normalization/std" ] + [dependencies] -anyhow = "1.0.57" -thiserror = "1.0.31" -rustc-hash = "1.1.0" +anyhow = {version = "1.0.57", default-features = false } +rustc-hash = { version = "1.1.0", default-features = false } sha2 = { version = "0.10.2", default-features = false } hmac = "0.12.1" pbkdf2 = { version = "0.11.0", default-features = false } rand = { version = "0.8.5", optional = true } -once_cell = "1.12.0" -unicode-normalization = "0.1.19" +unicode-normalization = { version = "0.1.19", default-features = false } zeroize = { version = "1.5.5", features = ["zeroize_derive"] } +lazy_static = { version = "1.4.0", default-features = false, features = [ "spin_no_std" ] } [dev-dependencies] hex = "0.4.3" diff --git a/src/crypto.rs b/src/crypto.rs index 519369a..d6708f0 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -9,6 +9,7 @@ use hmac::Hmac; #[cfg(feature = "rand")] use rand::{thread_rng, RngCore}; use sha2::Digest; +use crate::Vec; const PBKDF2_ROUNDS: u32 = 2048; const PBKDF2_BYTES: usize = 64; diff --git a/src/error.rs b/src/error.rs index 30fd2ed..3374c3d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,20 +1,31 @@ use crate::mnemonic_type::MnemonicType; -use thiserror::Error; +use core::fmt; -#[derive(Debug, Error)] +#[derive(Debug)] pub enum ErrorKind { - #[error("invalid checksum")] InvalidChecksum, - #[error("invalid word in phrase")] InvalidWord, - #[error("invalid keysize: {0}")] InvalidKeysize(usize), - #[error("invalid number of words in phrase: {0}")] InvalidWordLength(usize), - #[error("invalid entropy length {0}bits for mnemonic type {1:?}")] InvalidEntropyLength(usize, MnemonicType), } +impl fmt::Display for ErrorKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidChecksum => write!(f, "invalid checksum"), + Self::InvalidWord => write!(f, "invalid word in phrase"), + Self::InvalidKeysize(u) => write!(f, "invalid keysize: {0}", u), + Self::InvalidWordLength(u) => write!(f, "invalid number of words in phrase: {0}", u), + Self::InvalidEntropyLength(u, m) => write!( + f, + "invalid entropy length {0}bits for mnemonic type {1:?}", + u, m + ), + } + } +} + #[cfg(test)] mod test { use super::*; diff --git a/src/language.rs b/src/language.rs index ce0b7c6..cb0e500 100644 --- a/src/language.rs +++ b/src/language.rs @@ -1,6 +1,12 @@ -use crate::error::ErrorKind; -use crate::util::{Bits, Bits11}; +use crate::{ + error::ErrorKind, + util::{Bits, Bits11}, + Vec, +}; +#[cfg(feature = "std")] use rustc_hash::FxHashMap; +#[cfg(not(feature = "std"))] +type FxHashMap = alloc::collections::BTreeMap; pub struct WordMap { inner: FxHashMap<&'static str, Bits11>, @@ -25,10 +31,9 @@ impl WordList { } pub fn get_words_by_prefix(&self, prefix: &str) -> &[&'static str] { - let start = self.inner - .binary_search(&prefix) - .unwrap_or_else(|idx| idx); - let count = self.inner[start..].iter() + let start = self.inner.binary_search(&prefix).unwrap_or_else(|idx| idx); + let count = self.inner[start..] + .iter() .take_while(|word| word.starts_with(prefix)) .count(); @@ -38,7 +43,7 @@ impl WordList { mod lazy { use super::{Bits11, WordList, WordMap}; - use once_cell::sync::Lazy; + use crate::Vec; /// lazy generation of the word list fn gen_wordlist(lang_words: &'static str) -> WordList { @@ -60,48 +65,49 @@ mod lazy { WordMap { inner } } - - pub static WORDLIST_ENGLISH: Lazy = - Lazy::new(|| gen_wordlist(include_str!("langs/english.txt"))); - #[cfg(feature = "chinese-simplified")] - pub static WORDLIST_CHINESE_SIMPLIFIED: Lazy = - Lazy::new(|| gen_wordlist(include_str!("langs/chinese_simplified.txt"))); - #[cfg(feature = "chinese-traditional")] - pub static WORDLIST_CHINESE_TRADITIONAL: Lazy = - Lazy::new(|| gen_wordlist(include_str!("langs/chinese_traditional.txt"))); - #[cfg(feature = "french")] - pub static WORDLIST_FRENCH: Lazy = - Lazy::new(|| gen_wordlist(include_str!("langs/french.txt"))); - #[cfg(feature = "italian")] - pub static WORDLIST_ITALIAN: Lazy = - Lazy::new(|| gen_wordlist(include_str!("langs/italian.txt"))); - #[cfg(feature = "japanese")] - pub static WORDLIST_JAPANESE: Lazy = - Lazy::new(|| gen_wordlist(include_str!("langs/japanese.txt"))); - #[cfg(feature = "korean")] - pub static WORDLIST_KOREAN: Lazy = - Lazy::new(|| gen_wordlist(include_str!("langs/korean.txt"))); - #[cfg(feature = "spanish")] - pub static WORDLIST_SPANISH: Lazy = - Lazy::new(|| gen_wordlist(include_str!("langs/spanish.txt"))); - - pub static WORDMAP_ENGLISH: Lazy = Lazy::new(|| gen_wordmap(&WORDLIST_ENGLISH)); - #[cfg(feature = "chinese-simplified")] - pub static WORDMAP_CHINESE_SIMPLIFIED: Lazy = - Lazy::new(|| gen_wordmap(&WORDLIST_CHINESE_SIMPLIFIED)); - #[cfg(feature = "chinese-traditional")] - pub static WORDMAP_CHINESE_TRADITIONAL: Lazy = - Lazy::new(|| gen_wordmap(&WORDLIST_CHINESE_TRADITIONAL)); - #[cfg(feature = "french")] - pub static WORDMAP_FRENCH: Lazy = Lazy::new(|| gen_wordmap(&WORDLIST_FRENCH)); - #[cfg(feature = "italian")] - pub static WORDMAP_ITALIAN: Lazy = Lazy::new(|| gen_wordmap(&WORDLIST_ITALIAN)); - #[cfg(feature = "japanese")] - pub static WORDMAP_JAPANESE: Lazy = Lazy::new(|| gen_wordmap(&WORDLIST_JAPANESE)); - #[cfg(feature = "korean")] - pub static WORDMAP_KOREAN: Lazy = Lazy::new(|| gen_wordmap(&WORDLIST_KOREAN)); - #[cfg(feature = "spanish")] - pub static WORDMAP_SPANISH: Lazy = Lazy::new(|| gen_wordmap(&WORDLIST_SPANISH)); + lazy_static::lazy_static! { + pub static ref WORDLIST_ENGLISH: WordList = + gen_wordlist(include_str!("langs/english.txt")); + #[cfg(feature = "chinese-simplified")] + pub static ref WORDLIST_CHINESE_SIMPLIFIED: WordList = + gen_wordlist(include_str!("langs/chinese_simplified.txt")); + #[cfg(feature = "chinese-traditional")] + pub static ref WORDLIST_CHINESE_TRADITIONAL: WordList = + gen_wordlist(include_str!("langs/chinese_traditional.txt")); + #[cfg(feature = "french")] + pub static ref WORDLIST_FRENCH: WordList = + gen_wordlist(include_str!("langs/french.txt")); + #[cfg(feature = "italian")] + pub static ref WORDLIST_ITALIAN: WordList = + gen_wordlist(include_str!("langs/italian.txt")); + #[cfg(feature = "japanese")] + pub static ref WORDLIST_JAPANESE: WordList = + gen_wordlist(include_str!("langs/japanese.txt")); + #[cfg(feature = "korean")] + pub static ref WORDLIST_KOREAN: WordList = + gen_wordlist(include_str!("langs/korean.txt")); + #[cfg(feature = "spanish")] + pub static ref WORDLIST_SPANISH: WordList = + gen_wordlist(include_str!("langs/spanish.txt")); + + pub static ref WORDMAP_ENGLISH: WordMap = gen_wordmap(&WORDLIST_ENGLISH); + #[cfg(feature = "chinese-simplified")] + pub static ref WORDMAP_CHINESE_SIMPLIFIED: WordMap = + gen_wordmap(&WORDLIST_CHINESE_SIMPLIFIED); + #[cfg(feature = "chinese-traditional")] + pub static ref WORDMAP_CHINESE_TRADITIONAL: WordMap = + gen_wordmap(&WORDLIST_CHINESE_TRADITIONAL); + #[cfg(feature = "french")] + pub static ref WORDMAP_FRENCH: WordMap = gen_wordmap(&WORDLIST_FRENCH); + #[cfg(feature = "italian")] + pub static ref WORDMAP_ITALIAN: WordMap = gen_wordmap(&WORDLIST_ITALIAN); + #[cfg(feature = "japanese")] + pub static ref WORDMAP_JAPANESE: WordMap = gen_wordmap(&WORDLIST_JAPANESE); + #[cfg(feature = "korean")] + pub static ref WORDMAP_KOREAN: WordMap = gen_wordmap(&WORDLIST_KOREAN); + #[cfg(feature = "spanish")] + pub static ref WORDMAP_SPANISH: WordMap = gen_wordmap(&WORDLIST_SPANISH); + } } /// The language determines which words will be used in a mnemonic phrase, but also indirectly @@ -220,7 +226,7 @@ mod test { fn words_by_prefix() { let wl = &lazy::WORDLIST_ENGLISH; let res = wl.get_words_by_prefix("woo"); - assert_eq!(res, ["wood","wool"]); + assert_eq!(res, ["wood", "wool"]); } #[cfg_attr(all(target_arch = "wasm32"), wasm_bindgen_test)] diff --git a/src/lib.rs b/src/lib.rs index 6b19194..4c36ce5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,18 @@ //! println!("{:X}", seed); //! ``` //! + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +#[macro_use] +extern crate alloc; + +#[cfg(not(feature = "std"))] +pub(crate) use alloc::{string::String, vec::Vec}; +#[cfg(feature = "std")] +pub(crate) use {String, Vec}; + mod error; mod language; mod mnemonic; diff --git a/src/mnemonic.rs b/src/mnemonic.rs index dd659db..c2aaad9 100644 --- a/src/mnemonic.rs +++ b/src/mnemonic.rs @@ -5,9 +5,11 @@ use crate::error::ErrorKind; use crate::language::Language; use crate::mnemonic_type::MnemonicType; use crate::util::{checksum, BitWriter, IterExt}; +use crate::String; +use crate::Vec; use anyhow::Error; -use std::fmt; -use std::mem; +use core::fmt; +use core::mem; use unicode_normalization::UnicodeNormalization; use zeroize::Zeroizing; @@ -197,7 +199,7 @@ impl Mnemonic { let mut bits = BitWriter::with_capacity(264); for word in phrase.split(" ") { - bits.push(wordmap.get_bits(&word)?); + bits.push(wordmap.get_bits(&word).map_err(Error::msg)?); } let mtype = MnemonicType::for_word_count(bits.len() / 11)?; @@ -219,7 +221,7 @@ impl Mnemonic { let expected_checksum = checksum(checksum_byte, mtype.checksum_bits()); if actual_checksum != expected_checksum { - Err(ErrorKind::InvalidChecksum)?; + Err(Error::msg(ErrorKind::InvalidChecksum))?; } Ok(entropy) diff --git a/src/mnemonic_type.rs b/src/mnemonic_type.rs index 83e78b9..c36baf3 100644 --- a/src/mnemonic_type.rs +++ b/src/mnemonic_type.rs @@ -1,4 +1,4 @@ -use std::fmt; +use core::fmt; use anyhow::Error; use crate::error::ErrorKind; @@ -58,7 +58,7 @@ impl MnemonicType { 18 => MnemonicType::Words18, 21 => MnemonicType::Words21, 24 => MnemonicType::Words24, - _ => Err(ErrorKind::InvalidWordLength(size))?, + _ => Err(Error::msg(ErrorKind::InvalidWordLength(size)))?, }; Ok(mnemonic_type) @@ -82,7 +82,7 @@ impl MnemonicType { 192 => MnemonicType::Words18, 224 => MnemonicType::Words21, 256 => MnemonicType::Words24, - _ => Err(ErrorKind::InvalidKeysize(size))?, + _ => Err(Error::msg(ErrorKind::InvalidKeysize(size)))?, }; Ok(mnemonic_type) diff --git a/src/seed.rs b/src/seed.rs index aff7201..c1396c4 100644 --- a/src/seed.rs +++ b/src/seed.rs @@ -1,8 +1,13 @@ -use std::fmt; -use unicode_normalization::UnicodeNormalization; -use zeroize::{Zeroize, Zeroizing}; +use core::fmt; +extern crate alloc; +#[cfg(not(feature = "std"))] +use crate::alloc::string::ToString; use crate::crypto::pbkdf2; use crate::mnemonic::Mnemonic; +use crate::Vec; +use alloc::format; +use unicode_normalization::UnicodeNormalization; +use zeroize::{Zeroize, Zeroizing}; /// The secret value used to derive HD wallet addresses from a [`Mnemonic`][Mnemonic] phrase. /// @@ -108,7 +113,12 @@ mod test { assert_eq!(format!("{:#X}", seed), "0x0BDE96F14C35A66235478E0C16C152FCAF6301E4D9A81D3FEBC50879FE7E5438E6A8DD3E39BDF3AB7B12D6B44218710E17D7A2844EE9633FAB0E03D9A6C8569B"); } - fn test_unicode_normalization(lang: Language, phrase: &str, password: &str, expected_seed_hex: &str) { + fn test_unicode_normalization( + lang: Language, + phrase: &str, + password: &str, + expected_seed_hex: &str, + ) { let mnemonic = Mnemonic::from_phrase(phrase, lang).unwrap(); let seed = Seed::new(&mnemonic, password); assert_eq!(format!("{:x}", seed), expected_seed_hex); diff --git a/src/util.rs b/src/util.rs index df756ee..9f26f47 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,3 +1,5 @@ +use crate::String; +use crate::Vec; use unicode_normalization::Decompositions; pub(crate) trait IterExt: Iterator { @@ -148,7 +150,7 @@ impl BitWriter { } pub(crate) struct BitIter + Sized> { - _phantom: ::std::marker::PhantomData, + _phantom: core::marker::PhantomData, source: I, read: usize, buffer: u64, @@ -164,7 +166,7 @@ where let source = source.into_iter(); BitIter { - _phantom: ::std::marker::PhantomData, + _phantom: core::marker::PhantomData, source, read: 0, buffer: 0,