Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bip39: Add support for different languages #42

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 48 additions & 3 deletions src/bin/hal/cmd/bip39.rs
@@ -1,6 +1,6 @@
use std::io;

use bip39::{Language, Mnemonic};
use bip39::Mnemonic;
use bitcoin::hashes::{sha256, Hash};
use bitcoin::secp256k1::rand::{self, RngCore};
use clap;
Expand All @@ -9,6 +9,19 @@ use hex;
use hal;
use crate::prelude::*;

/// List of languages we support.
const LANGUAGES: &[&'static str] = &[
"english",
"czech",
"french",
"italian",
"japanese",
"korean",
"spanish",
"simplified-chinese",
"traditional-chinese",
];

pub fn subcommand<'a>() -> clap::App<'a, 'a> {
cmd::subcommand_group("bip39", "BIP-39 mnemonics")
.subcommand(cmd_generate())
Expand All @@ -24,16 +37,34 @@ pub fn execute<'a>(args: &clap::ArgMatches<'a>) {
}

fn cmd_generate<'a>() -> clap::App<'a, 'a> {
lazy_static! {
static ref LANGUAGE_HELP: String = format!(
"the language to use for the mnemonic. \
Supported languages are: {}", LANGUAGES.join(", "),
);
}

cmd::subcommand("generate", "generate a new BIP-39 mnemonic")
.unset_setting(clap::AppSettings::ArgRequiredElseHelp)
.arg(args::arg("words", "the number of words").long("words").short("w").default_value("24"))
.arg(args::arg("words", "the number of words")
.long("words").short("w")
.default_value("24"))
.arg(args::arg("language", "the language to use")
.long("language").short("l")
.default_value("english")
.help(&LANGUAGE_HELP))
.arg(args::arg("entropy", "hex-encoded entropy data").long("entropy"))
.arg(args::opt("stdin", "read entropy from stdin"))
}

fn exec_generate<'a>(args: &clap::ArgMatches<'a>) {
let network = args.network();

let language = {
let s = args.value_of("language").unwrap_or("en");
hal::bip39::parse_language(s).need("invalid language string")
};

let word_count = args.value_of("words").unwrap_or("24").parse::<usize>()
.need("invalid number of words");
if word_count < 12 || word_count % 6 != 0 || word_count > 24 {
Expand Down Expand Up @@ -70,7 +101,7 @@ fn exec_generate<'a>(args: &clap::ArgMatches<'a>) {
}

assert!(entropy.len() == nb_entropy_bytes);
let mnemonic = Mnemonic::from_entropy_in(Language::English, &entropy).unwrap();
let mnemonic = Mnemonic::from_entropy_in(language, &entropy).unwrap();
args.print_output(&hal::GetInfo::get_info(&mnemonic, network))
}

Expand All @@ -97,3 +128,17 @@ fn exec_get_seed<'a>(args: &clap::ArgMatches<'a>) {
);
args.print_output(&info)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_languages() {
let mut unique = std::collections::HashSet::new();
for l in LANGUAGES {
let lang = hal::bip39::parse_language(l).unwrap();
assert!(unique.insert(lang));
}
}
}
52 changes: 52 additions & 0 deletions src/bip39.rs
@@ -1,3 +1,6 @@

use std::borrow::Cow;

use bip39lib::{Language, Mnemonic};
use bitcoin::Network;
use bitcoin::util::bip32;
Expand Down Expand Up @@ -68,3 +71,52 @@ impl GetInfo<SeedInfo> for [u8; 64] {
}
}
}

/// Parse a BIP-39 language from string.
///
/// Supported formats are (case-insensitive):
/// - full name in English
/// - full name in English with hyphen instead of space
/// - ISO 639-1 code
/// - except for Simplified Chinese: "sc" or "zhs"
/// - except for Traditional Chinese: "tc" or "zht"
pub fn parse_language(s: &str) -> Option<Language> {
if !s.is_ascii() {
return None;
}

let s = if s.chars().all(|c| c.is_lowercase()) {
Cow::Borrowed(s)
} else {
Cow::Owned(s.to_lowercase())
};
let ret = match s.as_ref() {
"en" | "english" => Language::English,
"sc" | "zhs" | "simplified chinese" | "simplified-chinese"
| "simplifiedchinese" => Language::SimplifiedChinese,
"tc" | "zht" | "traditional chinese"| "traditional-chinese"
| "traditionalchinese" => Language::TraditionalChinese,
"cs" | "czech" => Language::Czech,
"fr" | "french" => Language::French,
"it" | "italian" => Language::Italian,
"ja" | "japanese" => Language::Japanese,
"ko" | "korean" => Language::Korean,
"es" | "spanish" => Language::Spanish,
_ => return None,
};
Some(ret)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parse_language() {
// a simple check all
for l in Language::all() {
assert_eq!(Some(*l), parse_language(&l.to_string()), "lang: {}", l);
assert_eq!(Some(*l), parse_language(&l.to_string().to_uppercase()), "lang: {}", l);
}
}
}