From: Olorunshogo Moses BAMTEFA Date: Sat, 24 Oct 2026 10:39:18 +0000 (+0100) Subject: fix(key): reject word counts other than 12 and 24 in key generate X-Git-Url: http://internal-gitweb-vhost/-script/src/example_cli/scripts/FromScriptException.WitnessProgram.html?a=commitdiff_plain;h=3dec0e02967832ed0728c98f138f52b02599088f;p=bdk-cli fix(key): reject word counts other than 12 and 24 in key generate --- diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d0bdf5..ccc29bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details. - 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 `create_tx`, `create_dns_tx`, `create_sp_tx` and `bump_fee` building transactions over Bitcoin Core's 400,000 wu (100,000 vB) standard weight limit, which no default node relays +- 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] diff --git a/src/handlers/key.rs b/src/handlers/key.rs index e7bb645..4abf3cd 100644 --- a/src/handlers/key.rs +++ b/src/handlers/key.rs @@ -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) -> Result<(), Error> { @@ -26,6 +26,26 @@ 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 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. @@ -33,9 +53,10 @@ pub struct GenerateKeyCommand { 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, @@ -46,13 +67,8 @@ impl AppCommand> for GenerateKeyCommand { fn execute(&self, ctx: &mut AppContext) -> Result { 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()?; diff --git a/tests/integration/init.rs b/tests/integration/init.rs index e0fcea2..3454882 100644 --- a/tests/integration/init.rs +++ b/tests/integration/init.rs @@ -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")); + } + } + #[test] fn test_cli_key_derive() { let temp_dir = TempDir::new().unwrap();