- 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]
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> {
}
}
}
+/// 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.
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>,
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()?;
.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();