mod bit_accumulator;
mod entropy;
mod phrase;
mod seed;
#[cfg(not(feature = "std"))]
use alloc::{
borrow::Cow,
string::{String, ToString},
vec::Vec,
};
use core::{fmt, marker::PhantomData, mem, str};
#[cfg(feature = "std")]
use std::borrow::Cow;
use unicode_normalization::{IsNormalized, UnicodeNormalization, is_nfkd_quick};
use zeroize::Zeroizing;
use self::{
bit_accumulator::BitAccumulator,
entropy::{encode_entropy, encode_entropy_with},
phrase::{DecodeMode, decode_phrase},
seed::to_seed,
};
use crate::{
error::Error,
language::{AnyLanguage, English, Language},
};
const BITS_PER_WORD: usize = 11;
const BITS_PER_BYTE: usize = 8;
#[derive(Copy, Clone, Debug, Default, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum Count {
#[default]
Words12,
Words15,
Words18,
Words21,
Words24,
}
impl fmt::Display for Count {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} words (entropy {} bits + checksum {} bits)",
self.word_count(),
self.entropy_bit_length(),
self.checksum_bit_length()
)
}
}
impl From<Count> for usize {
fn from(count: Count) -> Self {
match count {
Count::Words12 => 12,
Count::Words15 => 15,
Count::Words18 => 18,
Count::Words21 => 21,
Count::Words24 => 24,
}
}
}
impl TryFrom<usize> for Count {
type Error = Error;
fn try_from(count: usize) -> Result<Self, Self::Error> {
Self::from_word_count(count)
}
}
impl Count {
const fn from_word_count(count: usize) -> Result<Self, Error> {
Ok(match count {
12 => Self::Words12,
15 => Self::Words15,
18 => Self::Words18,
21 => Self::Words21,
24 => Self::Words24,
others => return Err(Error::BadWordCount(others)),
})
}
const fn from_key_size(size: usize) -> Result<Self, Error> {
Ok(match size {
128 => Self::Words12,
160 => Self::Words15,
192 => Self::Words18,
224 => Self::Words21,
256 => Self::Words24,
others => return Err(Error::BadEntropyBitCount(others)),
})
}
fn from_phrase<P: AsRef<str>>(phrase: P) -> Result<Self, Error> {
let word_count = phrase.as_ref().split_whitespace().count();
Self::from_word_count(word_count)
}
pub const fn word_count(&self) -> usize {
match self {
Self::Words12 => 12,
Self::Words15 => 15,
Self::Words18 => 18,
Self::Words21 => 21,
Self::Words24 => 24,
}
}
pub const fn total_bit_length(&self) -> usize {
self.entropy_bit_length() + self.checksum_bit_length()
}
pub const fn entropy_bit_length(&self) -> usize {
match self {
Self::Words12 => 128,
Self::Words15 => 160,
Self::Words18 => 192,
Self::Words21 => 224,
Self::Words24 => 256,
}
}
pub const fn checksum_bit_length(&self) -> usize {
match self {
Self::Words12 => 4,
Self::Words15 => 5,
Self::Words18 => 6,
Self::Words21 => 7,
Self::Words24 => 8,
}
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct Mnemonic<L = English> {
lang: PhantomData<L>,
phrase: Zeroizing<String>,
entropy: Zeroizing<Vec<u8>>,
}
impl<L: Language> fmt::Debug for Mnemonic<L> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.phrase())
}
}
impl<L: Language> fmt::Display for Mnemonic<L> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.phrase())
}
}
impl<L: Language> str::FromStr for Mnemonic<L> {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_phrase(s)
}
}
impl<L: Language> AsRef<str> for Mnemonic<L> {
fn as_ref(&self) -> &str {
self.phrase()
}
}
impl<L: Language> Mnemonic<L> {
#[cfg_attr(
feature = "chinese-simplified",
doc = r##"
```rust
use bip0039::{ChineseSimplified, Count, Mnemonic};
let mnemonic = <Mnemonic<ChineseSimplified>>::generate(Count::Words24);
let phrase = mnemonic.phrase();
```
"##
)]
#[cfg(feature = "rand")]
pub fn generate(word_count: Count) -> Self {
use rand::Rng;
let mut rng = rand::rng();
match word_count {
Count::Words12 => {
let mut entropy = [0u8; 16];
rng.fill_bytes(&mut entropy);
Self::from_entropy(entropy)
},
Count::Words15 => {
let mut entropy = [0u8; 20];
rng.fill_bytes(&mut entropy);
Self::from_entropy(entropy)
},
Count::Words18 => {
let mut entropy = [0u8; 24];
rng.fill_bytes(&mut entropy);
Self::from_entropy(entropy)
},
Count::Words21 => {
let mut entropy = [0u8; 28];
rng.fill_bytes(&mut entropy);
Self::from_entropy(entropy)
},
Count::Words24 => {
let mut entropy = [0u8; 32];
rng.fill_bytes(&mut entropy);
Self::from_entropy(entropy)
},
}
.expect("valid entropy length won't fail to generate the mnemonic")
}
pub fn from_entropy<E: Into<Vec<u8>>>(entropy: E) -> Result<Self, Error> {
let entropy = entropy.into();
let phrase = encode_entropy::<L>(&entropy)?;
Ok(Self {
lang: PhantomData::<L>,
phrase: Zeroizing::new(phrase),
entropy: Zeroizing::new(entropy),
})
}
pub fn from_phrase<'a, P: Into<Cow<'a, str>>>(phrase: P) -> Result<Self, Error> {
let mut phrase = phrase.into();
normalize_utf8(&mut phrase);
Self::from_normalized_phrase(phrase)
}
pub fn from_normalized_phrase<'a, P: Into<Cow<'a, str>>>(phrase: P) -> Result<Self, Error> {
let phrase = phrase.into();
let decoded = decode_phrase::<L>(&phrase, DecodeMode::BuildNormalizedPhrase)?;
let entropy = decoded.entropy;
let normalized_phrase = decoded
.normalized_phrase
.expect("BuildNormalizedPhrase always constructs a normalized phrase");
Ok(Mnemonic {
lang: PhantomData::<L>,
phrase: Zeroizing::new(normalized_phrase),
entropy: Zeroizing::new(entropy),
})
}
#[cfg_attr(
feature = "japanese",
doc = r##"
```rust
use bip0039::{Error, Japanese, Mnemonic};
use unicode_normalization::UnicodeNormalization;
let phrase = "そつう れきだい ほんやく わかす りくつ ばいか ろせん やちん そつう れきだい ほんやく わかめ";
let result = <Mnemonic<Japanese>>::validate(phrase);
assert!(result.is_ok());
let phrase = "そつう れきだい ほんやく わかす りくつ ばいか ろせん やちん そつう れきだい ほんやく ばか";
let result = <Mnemonic<Japanese>>::validate(phrase);
assert_eq!(result.unwrap_err(), Error::UnknownWord("ばか".nfkd().to_string()));
```
"##
)]
pub fn validate<'a, P: Into<Cow<'a, str>>>(phrase: P) -> Result<(), Error> {
let mut phrase = phrase.into();
normalize_utf8(&mut phrase);
let _decoded = decode_phrase::<L>(&phrase, DecodeMode::ValidateOnly)?;
Ok(())
}
pub fn to_seed<P: AsRef<str>>(&self, passphrase: P) -> [u8; 64] {
to_seed(self.phrase(), passphrase.as_ref())
}
pub fn phrase(&self) -> &str {
&self.phrase
}
pub fn into_phrase(mut self) -> String {
mem::take(&mut self.phrase)
}
pub fn entropy(&self) -> &[u8] {
&self.entropy
}
pub fn into_entropy(mut self) -> Vec<u8> {
mem::take(&mut self.entropy)
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct AnyMnemonic {
language: AnyLanguage,
phrase: Zeroizing<String>,
entropy: Zeroizing<Vec<u8>>,
}
impl fmt::Debug for AnyMnemonic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.phrase())
}
}
impl fmt::Display for AnyMnemonic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.phrase())
}
}
impl AsRef<str> for AnyMnemonic {
fn as_ref(&self) -> &str {
self.phrase()
}
}
impl AnyMnemonic {
#[cfg(feature = "rand")]
pub fn generate(language: impl Into<AnyLanguage>, word_count: Count) -> Self {
let language: AnyLanguage = language.into();
use rand::Rng;
let mut rng = rand::rng();
match word_count {
Count::Words12 => {
let mut entropy = [0u8; 16];
rng.fill_bytes(&mut entropy);
Self::from_entropy(language, entropy)
},
Count::Words15 => {
let mut entropy = [0u8; 20];
rng.fill_bytes(&mut entropy);
Self::from_entropy(language, entropy)
},
Count::Words18 => {
let mut entropy = [0u8; 24];
rng.fill_bytes(&mut entropy);
Self::from_entropy(language, entropy)
},
Count::Words21 => {
let mut entropy = [0u8; 28];
rng.fill_bytes(&mut entropy);
Self::from_entropy(language, entropy)
},
Count::Words24 => {
let mut entropy = [0u8; 32];
rng.fill_bytes(&mut entropy);
Self::from_entropy(language, entropy)
},
}
.expect("valid entropy length won't fail to generate the mnemonic")
}
pub fn from_entropy<E: Into<Vec<u8>>>(
language: impl Into<AnyLanguage>,
entropy: E,
) -> Result<Self, Error> {
let language: AnyLanguage = language.into();
let entropy = entropy.into();
let phrase = encode_entropy_with(language, &entropy)?;
Ok(Self { language, phrase: Zeroizing::new(phrase), entropy: Zeroizing::new(entropy) })
}
pub fn from_phrase<'a, P: Into<Cow<'a, str>>>(
language: impl Into<AnyLanguage>,
phrase: P,
) -> Result<Self, Error> {
let language = language.into();
let mut phrase = phrase.into();
normalize_utf8(&mut phrase);
Self::from_normalized_phrase(language, phrase)
}
pub fn from_normalized_phrase<'a, P: Into<Cow<'a, str>>>(
language: impl Into<AnyLanguage>,
phrase: P,
) -> Result<Self, Error> {
let language = language.into();
let phrase = phrase.into();
let decoded =
phrase::decode_phrase_with(language, &phrase, DecodeMode::BuildNormalizedPhrase)?;
let entropy = decoded.entropy;
let normalized_phrase = decoded
.normalized_phrase
.expect("BuildNormalizedPhrase always constructs a normalized phrase");
Ok(Self {
language,
phrase: Zeroizing::new(normalized_phrase),
entropy: Zeroizing::new(entropy),
})
}
#[cfg_attr(
feature = "japanese",
doc = r##"
```rust
use bip0039::{Error, Japanese, Mnemonic};
use unicode_normalization::UnicodeNormalization;
let phrase = "そつう れきだい ほんやく わかす りくつ ばいか ろせん やちん そつう れきだい ほんやく わかめ";
let result = <Mnemonic<Japanese>>::validate(phrase);
assert!(result.is_ok());
let phrase = "そつう れきだい ほんやく わかす りくつ ばいか ろせん やちん そつう れきだい ほんやく ばか";
let result = <Mnemonic<Japanese>>::validate(phrase);
assert_eq!(result.unwrap_err(), Error::UnknownWord("ばか".nfkd().to_string()));
```
"##
)]
pub fn validate<'a, P: Into<Cow<'a, str>>>(
language: impl Into<AnyLanguage>,
phrase: P,
) -> Result<(), Error> {
let language = language.into();
let mut phrase = phrase.into();
normalize_utf8(&mut phrase);
let _decoded = phrase::decode_phrase_with(language, &phrase, DecodeMode::ValidateOnly)?;
Ok(())
}
pub fn to_seed<P: AsRef<str>>(&self, passphrase: P) -> [u8; 64] {
to_seed(self.phrase(), passphrase.as_ref())
}
#[inline]
pub fn language(&self) -> AnyLanguage {
self.language
}
pub fn phrase(&self) -> &str {
&self.phrase
}
pub fn into_phrase(mut self) -> String {
mem::take(&mut self.phrase)
}
pub fn entropy(&self) -> &[u8] {
&self.entropy
}
pub fn into_entropy(mut self) -> Vec<u8> {
mem::take(&mut self.entropy)
}
}
impl<L: Language> From<Mnemonic<L>> for AnyMnemonic {
fn from(mut value: Mnemonic<L>) -> Self {
let language = AnyLanguage::of::<L>();
Self {
language,
phrase: Zeroizing::new(mem::take(&mut value.phrase)),
entropy: Zeroizing::new(mem::take(&mut value.entropy)),
}
}
}
#[inline]
fn normalize_utf8(s: &mut Cow<'_, str>) {
if is_nfkd_quick(s.as_ref().chars()) != IsNormalized::Yes {
*s = Cow::Owned(s.as_ref().nfkd().to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mnemonic_roundtrip() {
let mnemonic = <Mnemonic>::generate(Count::Words12);
let entropy = mnemonic.entropy();
let phrase = mnemonic.phrase();
{
let m = <Mnemonic>::from_entropy(entropy).unwrap();
assert_eq!(phrase, m.phrase());
}
{
let m = <Mnemonic>::from_phrase(phrase).unwrap();
assert_eq!(entropy, m.entropy());
}
}
#[test]
fn test_any_mnemonic_roundtrip() {
let english = AnyLanguage::of::<English>();
let mnemonic = AnyMnemonic::generate(english, Count::Words12);
let entropy = mnemonic.entropy();
let phrase = mnemonic.phrase();
{
let m = AnyMnemonic::from_entropy(english, entropy).unwrap();
assert_eq!(phrase, m.phrase());
}
{
let m = <AnyMnemonic>::from_phrase(english, phrase).unwrap();
assert_eq!(entropy, m.entropy());
}
}
#[test]
fn test_mnemonic_zeroize_on_drop_mechanism() {
let m = <Mnemonic>::from_entropy([1u8; 16]).unwrap();
fn assert_phrase(_: &Zeroizing<String>) {}
fn assert_entropy(_: &Zeroizing<Vec<u8>>) {}
assert_phrase(&m.phrase);
assert_entropy(&m.entropy);
drop(m);
#[derive(Clone, Debug, PartialEq)]
struct PanicOnNonZeroDrop(u8);
impl zeroize::Zeroize for PanicOnNonZeroDrop {
fn zeroize(&mut self) {
self.0 = 0;
}
}
impl Drop for PanicOnNonZeroDrop {
fn drop(&mut self) {
assert_eq!(self.0, 0, "dropped non-zeroized data");
}
}
let wrapped = Zeroizing::new(vec![PanicOnNonZeroDrop(42); 16]);
drop(wrapped);
}
#[test]
fn test_mnemonic_consume() {
#[derive(Clone, Debug, PartialEq)]
struct DropCheckVec(Vec<u8>);
impl zeroize::Zeroize for DropCheckVec {
fn zeroize(&mut self) {
for byte in &mut self.0 {
*byte = 0;
}
}
}
impl Drop for DropCheckVec {
fn drop(&mut self) {
assert!(self.0.iter().all(|&b| b == 0), "dropped non-zeroized data");
}
}
#[derive(Clone, Debug, PartialEq)]
struct DropCheckString {
s: String,
was_zeroized: bool,
}
impl DropCheckString {
fn new(s: impl Into<String>) -> Self {
DropCheckString { s: s.into(), was_zeroized: false }
}
}
impl zeroize::Zeroize for DropCheckString {
fn zeroize(&mut self) {
self.s.zeroize();
self.was_zeroized = true;
}
}
impl Drop for DropCheckString {
fn drop(&mut self) {
assert!(self.was_zeroized, "string was not zeroized before drop");
assert!(self.s.is_empty(), "zeroized string should be empty");
}
}
struct ConsumeMnemonic {
phrase: Zeroizing<DropCheckString>,
entropy: Zeroizing<DropCheckVec>,
}
impl ConsumeMnemonic {
fn into_phrase(mut self) -> String {
mem::take(&mut self.phrase.s)
}
fn into_entropy(mut self) -> Vec<u8> {
mem::take(&mut self.entropy.0)
}
}
let expected_entropy = vec![1u8; 16];
let expected_phrase =
"absurd amount doctor acoustic avoid letter advice cage absurd amount doctor adjust";
let phrase = {
ConsumeMnemonic {
phrase: Zeroizing::new(DropCheckString::new(expected_phrase)),
entropy: Zeroizing::new(DropCheckVec(expected_entropy.clone())),
}
.into_phrase()
};
assert_eq!(phrase, expected_phrase);
let entropy = {
ConsumeMnemonic {
phrase: Zeroizing::new(DropCheckString::new(expected_phrase)),
entropy: Zeroizing::new(DropCheckVec(expected_entropy.clone())),
}
.into_entropy()
};
assert_eq!(entropy, expected_entropy);
}
}