Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details.
- Fixed `compile` silently accepting placeholder keys/hashes instead of validating them; added `--placeholders` to opt back into the old permissive behavior
- Fixed `compile` panicking instead of returning an error when a key is valid on its own but the wrong shape for the requested script type (e.g. an x-only key under `sh`/`wsh`/`sh-wsh`, or an uncompressed key under `tr`)
- Updated dependencies to resolve RUSTSEC advisories in rustls, rustls-webpki, webpki-roots, quinn-proto, h2 and zstd-sys; bumped async-hwi to 0.0.35.
- Fixed `key generate` silently producing a 24-word mnemonic for any `--entropy` value other than 12; values other than 12 and 24 are now rejected

## [4.0.0]

Expand Down
34 changes: 25 additions & 9 deletions src/handlers/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use bdk_wallet::keys::{
DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, GeneratedKey, bip39::WordCount,
};
use bdk_wallet::miniscript::{self, Segwitv0};
use clap::Parser;
use clap::{Parser, ValueEnum};

impl KeySubCommand {
pub fn execute(&self, ctx: &mut AppContext<Init>) -> Result<(), Error> {
Expand All @@ -26,16 +26,37 @@ impl KeySubCommand {
}
}
}
/// Mnemonic lengths supported by `key generate`: 12 and 24, the lengths hardware
/// wallets and most BIP39 wallets generate. A local mirror of `WordCount`, which
/// doesn't derive `Clone` and so can't be parsed by clap directly.
#[derive(Clone, Copy, ValueEnum, Debug, Eq, PartialEq)]
pub enum MnemonicWordCount {
#[value(name = "12")]
Words12,
#[value(name = "24")]
Words24,
}

impl From<MnemonicWordCount> for WordCount {
fn from(word_count: MnemonicWordCount) -> Self {
match word_count {
MnemonicWordCount::Words12 => WordCount::Words12,
MnemonicWordCount::Words24 => WordCount::Words24,
}
}
}

#[derive(Parser, Debug, Clone, PartialEq, Eq)]
pub struct GenerateKeyCommand {
/// Entropy level based on number of random seed mnemonic words.
#[arg(
env = "WORD_COUNT",
short = 'e',
long = "entropy",
default_value = "12"
value_enum,
default_value_t = MnemonicWordCount::Words12
)]
word_count: usize,
word_count: MnemonicWordCount,
/// Seed password.
#[arg(env = "PASSWORD", short = 'p', long = "password")]
password: Option<String>,
Expand All @@ -46,13 +67,8 @@ impl AppCommand<AppContext<Init>> for GenerateKeyCommand {

fn execute(&self, ctx: &mut AppContext<Init>) -> Result<Self::Output, Error> {
let secp = Secp256k1::new();
let mnemonic_type = match self.word_count {
12 => WordCount::Words12,
_ => WordCount::Words24,
};

let mnemonic: GeneratedKey<_, miniscript::BareCtx> =
Mnemonic::generate((mnemonic_type, Language::English))
Mnemonic::generate((self.word_count.into(), Language::English))
.map_err(|_| Error::Generic("Mnemonic generation error".to_string()))?;
let mnemonic = mnemonic.into_key();
let xkey: ExtendedKey = (mnemonic.clone(), self.password.clone()).into_extended_key()?;
Expand Down
31 changes: 31 additions & 0 deletions tests/integration/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,37 @@ mod test_key {
.stdout(predicate::str::contains("\"fingerprint\":"));
}

#[test]
fn test_cli_key_generate_word_counts() {
let temp_dir = TempDir::new().unwrap();
let cli = BdkCli::new("testnet", Some(temp_dir.path().to_path_buf()));

for word_count in [12, 24] {
let output = cli
.key_cmd(&["generate", "--entropy", &word_count.to_string()])
.output()
.expect("Failed to execute generate command");
assert!(output.status.success(), "Generate command failed");

let json: Value = serde_json::from_slice(&output.stdout).expect("Invalid JSON");
let mnemonic = json["mnemonic"].as_str().expect("Missing mnemonic");
assert_eq!(mnemonic.split_whitespace().count(), word_count);
}
}

#[test]
fn test_cli_key_generate_rejects_invalid_word_count() {
let temp_dir = TempDir::new().unwrap();
let cli = BdkCli::new("testnet", Some(temp_dir.path().to_path_buf()));

for word_count in ["0", "13", "15", "18", "21", "999"] {
cli.key_cmd(&["generate", "--entropy", word_count])
.assert()
.failure()
.stderr(predicate::str::contains("possible values"));
Comment thread
Olorunshogo marked this conversation as resolved.
Resolved
}
}

#[test]
fn test_cli_key_derive() {
let temp_dir = TempDir::new().unwrap();
Expand Down
Loading