"bdk_electrum",
"bdk_esplora",
"bdk_kyoto",
+ "bdk_message_signer",
"bdk_redb",
"bdk_sp",
"bdk_testenv",
"bip157",
]
+[[package]]
+name = "bdk_message_signer"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2e0c999b3dcc448ceb6337658c363df3197bbed987b663810497bd9c20ea4ef"
+dependencies = [
+ "bdk_wallet",
+ "bitcoin",
+]
+
[[package]]
name = "bdk_redb"
version = "0.2.0"
payjoin = { version = "0.25.0", features = ["v1", "v2", "io", "_test-utils"], optional = true}
reqwest = { version = "0.13.2", default-features = false, features = ["rustls"], optional = true }
url = { version = "2.5.8", optional = true }
-# bdk_bip322 = { version = "0.1.0", optional = true }
+bdk_message_signer = { version = "0.2.0", optional = true }
bitcoin-payment-instructions = { version = "0.7.0", optional = true}
[features]
# Internal features
_payjoin-dependencies = ["payjoin", "reqwest", "url", "sqlite"]
-# bip322 = ["bdk_bip322"]
+bip322 = ["bdk_message_signer"]
# Use this to consensus verify transactions at sync time
verify = []
//! All subcommands are defined in the below enums.
#![allow(clippy::large_enum_variant)]
-// #[cfg(feature = "bip322")]
-// use crate::handlers::offline::{SignMessageCommand, VerifyMessageCommand};
+#[cfg(feature = "bip322")]
+use crate::handlers::offline::{SignMessageCommand, VerifyMessageCommand};
use crate::handlers::{
config::{ListWalletsCommand, SaveConfigCommand},
descriptor::DescriptorCommand,
/// Combines multiple PSBTs into one.
CombinePsbt(CombinePsbtCommand),
/// Sign a message using BIP322
- // #[cfg(feature = "bip322")]
- // SignMessage(SignMessageCommand),
+ #[cfg(feature = "bip322")]
+ SignMessage(SignMessageCommand),
/// Verify a BIP322 signature
- // #[cfg(feature = "bip322")]
- // VerifyMessage(VerifyMessageCommand),
+ #[cfg(feature = "bip322")]
+ VerifyMessage(VerifyMessageCommand),
/// Lock UTXO(s) so they're excluded from coin selection.
LockUtxo(LockUtxoCommand),
/// Unlock previously locked UTXO(s).
#[cfg(feature = "payjoin")]
#[error("Payjoin database error: {0}")]
PayjoinDb(#[from] crate::handlers::payjoin::db::Error),
- // #[cfg(feature = "bip322")]
- // #[error("BIP-322 error: {0}")]
- // Bip322Error(#[from] bdk_bip322::error::Error),
+ #[cfg(feature = "bip322")]
+ #[error("BIP-322 error: {0}")]
+ Bip322Error(#[from] bdk_message_signer::error::Error),
}
impl From<ExtractTxError> for BDKCliError {
bdk_wallet::keys::{DescriptorPublicKey, DescriptorSecretKey, SinglePubKey},
std::collections::HashMap,
};
-// #[cfg(feature = "bip322")]
-// use {
-// crate::utils::parse_signature_format,
-// crate::utils::types::MessageResult,
-// bdk_bip322::{BIP322, MessageProof},
-// };
+#[cfg(feature = "bip322")]
+use {
+ crate::utils::parse_signature_format,
+ crate::utils::types::MessageResult,
+ bdk_message_signer::{MessageProof, MessageSigner},
+};
impl OfflineWalletSubCommand {
pub fn execute(&self, ctx: &mut AppContext<OfflineOperations<'_>>) -> Result<(), Error> {
Self::CombinePsbt(combine_psbt_command) => combine_psbt_command
.execute(ctx)?
.write_out(std::io::stdout()),
- // #[cfg(feature = "bip322")]
- // Self::SignMessage(sign_message_command) => sign_message_command
- // .execute(ctx)?
- // .write_out(std::io::stdout()),
- // #[cfg(feature = "bip322")]
- // Self::VerifyMessage(verify_message_command) => verify_message_command
- // .execute(ctx)?
- // .write_out(std::io::stdout()),
+ #[cfg(feature = "bip322")]
+ Self::SignMessage(sign_message_command) => sign_message_command
+ .execute(ctx)?
+ .write_out(std::io::stdout()),
+ #[cfg(feature = "bip322")]
+ Self::VerifyMessage(verify_message_command) => verify_message_command
+ .execute(ctx)?
+ .write_out(std::io::stdout()),
Self::LockUtxo(lock_utxo) => lock_utxo.execute(ctx)?.write_out(std::io::stdout()),
Self::UnlockUtxo(unlock_utxo) => unlock_utxo.execute(ctx)?.write_out(std::io::stdout()),
Self::LockedUtxos(locked_utxos) => {
#[arg(long = "enable_rbf", short = 'r', default_value_t = true)]
pub enable_rbf: bool,
- /// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output.
+ /// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `PSBT_GLOBAL_XPUB` and more details to let the signer identify the change output.
#[arg(long = "offline_signer")]
pub offline_signer: bool,
}
}
-/**
#[cfg(feature = "bip322")]
#[derive(Debug, Parser, Clone, PartialEq)]
pub struct SignMessageCommand {
})
}
}
-**/
#[derive(Parser, Debug, Clone, PartialEq)]
pub struct LockUtxoCommand {
/// Outpoint(s) to lock, format TXID:VOUT.
- #[arg(env = "TXID:VOUT", long = "utxo", required = true, value_parser = parse_outpoint)]
+ #[arg(env = "TXID", long = "utxo", required = true, value_parser = parse_outpoint)]
pub utxos: Vec<OutPoint>,
}
#[derive(Parser, Debug, Clone, PartialEq)]
pub struct UnlockUtxoCommand {
/// Outpoint(s) to unlock, format TXID:VOUT.
- #[arg(env = "TXID:VOUT", long = "utxo", required = true, value_parser = parse_outpoint)]
+ #[arg(env = "TXID", long = "utxo", required = true, value_parser = parse_outpoint)]
pub utxos: Vec<OutPoint>,
}
use crate::{commands::WalletOpts, config::WalletConfig, error::BDKCliError as Error};
-// #[cfg(feature = "bip322")]
-// use bdk_bip322::SignatureFormat;
#[cfg(feature = "cbf")]
use bdk_kyoto::{Info, Receiver, UnboundedReceiver, Warning};
+#[cfg(feature = "bip322")]
+use bdk_message_signer::SignatureFormat;
#[cfg(feature = "silent-payments")]
use bdk_sp::encoding::SilentPaymentCode;
use bdk_wallet::bitcoin::{Address, Network, OutPoint, ScriptBuf};
Ok((key, value))
}
-// /// Function to parse the signature format from a string
-// #[cfg(feature = "bip322")]
-// pub(crate) fn parse_signature_format(format_str: &str) -> Result<SignatureFormat, Error> {
-// match format_str.to_lowercase().as_str() {
-// "legacy" => Ok(SignatureFormat::Legacy),
-// "simple" => Ok(SignatureFormat::Simple),
-// "full" => Ok(SignatureFormat::Full),
-// "fullproofoffunds" => Ok(SignatureFormat::FullProofOfFunds),
-// _ => Err(Error::Generic(
-// "Invalid signature format. Use 'legacy', 'simple', 'full', or 'fullproofoffunds'"
-// .to_string(),
-// )),
-// }
-// }
+/// Function to parse the signature format from a string
+#[cfg(feature = "bip322")]
+pub(crate) fn parse_signature_format(format_str: &str) -> Result<SignatureFormat, Error> {
+ match format_str.to_lowercase().as_str() {
+ "legacy" => Ok(SignatureFormat::Legacy),
+ "simple" => Ok(SignatureFormat::Simple),
+ "full" => Ok(SignatureFormat::Full),
+ "fullproofoffunds" => Ok(SignatureFormat::FullProofOfFunds),
+ _ => Err(Error::Generic(
+ "Invalid signature format. Use 'legacy', 'simple', 'full', or 'fullproofoffunds'"
+ .to_string(),
+ )),
+ }
+}
pub fn command_requires_db(command: &OfflineWalletSubCommand) -> bool {
match command {
| OfflineWalletSubCommand::FinalizePsbt(_)
| OfflineWalletSubCommand::CombinePsbt(_) => false,
- // #[cfg(feature = "bip322")]
- // OfflineWalletSubCommand::SignMessage(_) => true,
+ #[cfg(feature = "bip322")]
+ OfflineWalletSubCommand::SignMessage(_) => true,
- // #[cfg(feature = "bip322")]
- // OfflineWalletSubCommand::VerifyMessage(_) => true,
+ #[cfg(feature = "bip322")]
+ OfflineWalletSubCommand::VerifyMessage(_) => true,
#[cfg(feature = "silent-payments")]
OfflineWalletSubCommand::CreateSpTx(_) => true,
#[cfg(feature = "dns_payment")]
pub internal: T,
}
-// #[cfg(feature = "bip322")]
-// #[derive(Serialize, Debug, Default)]
-// pub struct MessageResult {
-// #[serde(skip_serializing_if = "Option::is_none")]
-// pub proof: Option<String>,
-
-// #[serde(skip_serializing_if = "Option::is_none")]
-// pub valid: Option<bool>,
-
-// #[serde(skip_serializing_if = "Option::is_none")]
-// pub proven_amount: Option<u64>,
-// }
+#[cfg(feature = "bip322")]
+#[derive(Serialize, Debug, Default)]
+pub struct MessageResult {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub proof: Option<String>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub valid: Option<bool>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub proven_amount: Option<u64>,
+}
#[derive(Serialize, Debug)]
pub struct StatusResult {
.failure()
.stderr(predicate::str::contains("Invalid"));
}
- /**
- #[cfg(feature = "bip322")]
- // #[test]
- fn test_sign_message_and_verify_message() {
- let (cli, mut cmd_init) = setup_wallet_config();
- cmd_init.assert().success();
-
- let message = "bdk-cli integration test";
-
- // Reveal exactly one address and reuse it for both sign and verify.
- let addr_output = cli
- .wallet_cmd(&["--wallet", WALLET_NAME, "new_address"])
- .output()
- .expect("Failed to generate address");
- assert!(
- addr_output.status.success(),
- "new_address failed: {}",
- String::from_utf8_lossy(&addr_output.stderr)
- );
- let addr_json: Value = serde_json::from_slice(&addr_output.stdout).unwrap();
- let address = addr_json["address"]
- .as_str()
- .expect("missing address")
- .to_string();
- println!("DEBUG signing address: {address}");
-
- // Sign (default signature_type = "simple").
- let sign_output = cli
- .wallet_cmd(&[
- "--wallet",
- WALLET_NAME,
- "sign_message",
- "--message",
- message,
- "--address",
- &address,
- ])
- .output()
- .expect("Failed to sign message");
- assert!(
- sign_output.status.success(),
- "sign_message failed: {}",
- String::from_utf8_lossy(&sign_output.stderr)
- );
- let sign_json: Value = serde_json::from_slice(&sign_output.stdout).unwrap();
- let proof = sign_json["proof"].as_str().expect("missing proof");
- println!("DEBUG proof: {proof}");
- println!("DEBUG verifying same address: {address}");
-
- // Verify with the identical address + message.
- cli.wallet_cmd(&[
+
+ #[cfg(feature = "bip322")]
+ #[test]
+ fn test_sign_message_and_verify_message() {
+ let (cli, mut cmd_init) = setup_wallet_config();
+ cmd_init.assert().success();
+
+ let message = "bdk-cli integration test";
+
+ // Reveal exactly one address and reuse it for both sign and verify.
+ let addr_output = cli
+ .wallet_cmd(&["--wallet", WALLET_NAME, "new_address"])
+ .output()
+ .expect("Failed to generate address");
+ assert!(
+ addr_output.status.success(),
+ "new_address failed: {}",
+ String::from_utf8_lossy(&addr_output.stderr)
+ );
+ let addr_json: Value = serde_json::from_slice(&addr_output.stdout).unwrap();
+ let address = addr_json["address"]
+ .as_str()
+ .expect("missing address")
+ .to_string();
+ println!("DEBUG signing address: {address}");
+
+ // Sign (default signature_type = "simple").
+ let sign_output = cli
+ .wallet_cmd(&[
"--wallet",
WALLET_NAME,
- "verify_message",
- "--address",
- &address,
+ "sign_message",
"--message",
message,
- "--proof",
- proof,
+ "--address",
+ &address,
])
- .assert()
- .success()
- .stdout(predicate::str::contains("\"valid\": true"));
- }
-
- #[cfg(feature = "bip322")]
- // #[test]
- fn test_verify_message_rejects_tampered_message() {
- let (cli, mut cmd_init) = setup_wallet_config();
- cmd_init.assert().success();
-
- let addr_output = cli
- .wallet_cmd(&["--wallet", WALLET_NAME, "new_address"])
- .output()
- .expect("Failed to generate address");
- let addr_json: Value = serde_json::from_slice(&addr_output.stdout).unwrap();
- let address = addr_json["address"].as_str().unwrap();
-
- let sign_output = cli
- .wallet_cmd(&[
- "--wallet",
- WALLET_NAME,
- "sign_message",
- "--message",
- "original message",
- "--address",
- address,
- ])
- .output()
- .expect("Failed to sign message");
- let sign_json: Value = serde_json::from_slice(&sign_output.stdout).unwrap();
- let proof = sign_json["proof"].as_str().unwrap();
-
- // A tampered message should fail
- cli.wallet_cmd(&[
+ .output()
+ .expect("Failed to sign message");
+ assert!(
+ sign_output.status.success(),
+ "sign_message failed: {}",
+ String::from_utf8_lossy(&sign_output.stderr)
+ );
+ let sign_json: Value = serde_json::from_slice(&sign_output.stdout).unwrap();
+ let proof = sign_json["proof"].as_str().expect("missing proof");
+ println!("DEBUG proof: {proof}");
+ println!("DEBUG verifying same address: {address}");
+
+ // Verify with the identical address + message.
+ cli.wallet_cmd(&[
+ "--wallet",
+ WALLET_NAME,
+ "verify_message",
+ "--address",
+ &address,
+ "--message",
+ message,
+ "--proof",
+ proof,
+ ])
+ .assert()
+ .success()
+ .stdout(predicate::str::contains("\"valid\": true"));
+ }
+
+ #[cfg(feature = "bip322")]
+ #[test]
+ fn test_verify_message_rejects_tampered_message() {
+ let (cli, mut cmd_init) = setup_wallet_config();
+ cmd_init.assert().success();
+
+ let addr_output = cli
+ .wallet_cmd(&["--wallet", WALLET_NAME, "new_address"])
+ .output()
+ .expect("Failed to generate address");
+ let addr_json: Value = serde_json::from_slice(&addr_output.stdout).unwrap();
+ let address = addr_json["address"].as_str().unwrap();
+
+ let sign_output = cli
+ .wallet_cmd(&[
"--wallet",
WALLET_NAME,
- "verify_message",
- "--proof",
- proof,
+ "sign_message",
"--message",
- "tampered message",
+ "original message",
"--address",
address,
])
- .assert()
- .success()
- .stdout(predicate::str::contains("\"valid\": false"));
- }
- **/
+ .output()
+ .expect("Failed to sign message");
+ let sign_json: Value = serde_json::from_slice(&sign_output.stdout).unwrap();
+ let proof = sign_json["proof"].as_str().unwrap();
+
+ // A tampered message should fail
+ cli.wallet_cmd(&[
+ "--wallet",
+ WALLET_NAME,
+ "verify_message",
+ "--proof",
+ proof,
+ "--message",
+ "tampered message",
+ "--address",
+ address,
+ ])
+ .assert()
+ .success()
+ .stdout(predicate::str::contains("\"valid\": false"));
+ }
+
#[test]
fn test_create_tx_send_all_rejects_multiple_recipients() {
let (cli, mut cmd_init) = setup_wallet_config();
);
}
}
- /**
- #[cfg(feature = "bip322")]
- #[test]
- fn test_verify_message_proof_of_funds_uses_persisted_utxos() {
- let env = TestEnv::new().expect("Failed to start bdk_testenv");
- let server_url = env.electrsd.electrum_url.as_str();
- let temp_dir = TempDir::new().unwrap();
- let cli = BdkCli::new("regtest", Some(temp_dir.path().to_path_buf()));
- // A wpkh wallet
- let desc = cli.cmd("descriptor", &["--type", "wpkh"]).output().unwrap();
- let desc_val: Value = serde_json::from_slice(&desc.stdout).unwrap();
- let ext_desc = desc_val["private_descriptors"]["external"]
- .as_str()
- .unwrap();
- let int_desc = desc_val["private_descriptors"]["internal"]
- .as_str()
- .unwrap();
- cli.build_base_cmd()
- .arg("wallet")
- .arg("--wallet")
- .arg(WALLET_NAME)
- .arg("config")
- .arg("--ext-descriptor")
- .arg(ext_desc)
- .arg("--int-descriptor")
- .arg(int_desc)
- .arg("--client-type")
- .arg("electrum")
- .arg("--database-type")
- .arg("sqlite")
- .arg("--url")
- .arg(server_url)
- .assert()
- .success();
+ #[cfg(feature = "bip322")]
+ #[test]
+ fn test_verify_message_proof_of_funds_uses_persisted_utxos() {
+ let env = TestEnv::new().expect("Failed to start bdk_testenv");
+ let server_url = env.electrsd.electrum_url.as_str();
+ let temp_dir = TempDir::new().unwrap();
+ let cli = BdkCli::new("regtest", Some(temp_dir.path().to_path_buf()));
- // Fund a single small UTXO
- let address = cli_new_address(&cli);
- let node_addr = env
- .rpc_client()
- .get_new_address(None, None)
- .unwrap()
- .assume_checked();
- env.mine_blocks(101, Some(node_addr)).unwrap();
- env.wait_until_electrum_sees_block(Duration::from_secs(10))
- .unwrap();
- let txid = env.send(&address, Amount::from_sat(5_000)).unwrap();
- env.wait_until_electrum_sees_txid(txid, Duration::from_secs(10))
- .unwrap();
- env.mine_blocks(3, None).unwrap();
- env.wait_until_electrum_sees_block(Duration::from_secs(10))
- .unwrap();
- cli_full_scan(&cli);
+ // A wpkh wallet
+ let desc = cli.cmd("descriptor", &["--type", "wpkh"]).output().unwrap();
+ let desc_val: Value = serde_json::from_slice(&desc.stdout).unwrap();
+ let ext_desc = desc_val["private_descriptors"]["external"]
+ .as_str()
+ .unwrap();
+ let int_desc = desc_val["private_descriptors"]["internal"]
+ .as_str()
+ .unwrap();
+ cli.build_base_cmd()
+ .arg("wallet")
+ .arg("--wallet")
+ .arg(WALLET_NAME)
+ .arg("config")
+ .arg("--ext-descriptor")
+ .arg(ext_desc)
+ .arg("--int-descriptor")
+ .arg(int_desc)
+ .arg("--client-type")
+ .arg("electrum")
+ .arg("--database-type")
+ .arg("sqlite")
+ .arg("--url")
+ .arg(server_url)
+ .assert()
+ .success();
- // The funded outpoint to prove control of.
- let unspent = run_wallet_json(&cli, &["unspent"]);
- let outpoint = unspent["items"][0]["outpoint"]
- .as_str()
- .expect("funded wallet should have one UTXO")
- .to_string();
- let addr = address.to_string();
-
- // Produce a proof-of-funds over that UTXO.
- let proof = run_wallet_json(
- &cli,
- &[
- "sign_message",
- "--message",
- "proof-of-funds",
- "--address",
- &addr,
- "--signature-type",
- "fullproofoffunds",
- "--utxos",
- &outpoint,
- ],
- )["proof"]
- .as_str()
- .expect("sign_message should return a proof")
- .to_string();
+ // Fund a single small UTXO
+ let address = cli_new_address(&cli);
+ let node_addr = env
+ .rpc_client()
+ .get_new_address(None, None)
+ .unwrap()
+ .assume_checked();
+ env.mine_blocks(101, Some(node_addr)).unwrap();
+ env.wait_until_electrum_sees_block(Duration::from_secs(10))
+ .unwrap();
+ let txid = env.send(&address, Amount::from_sat(5_000)).unwrap();
+ env.wait_until_electrum_sees_txid(txid, Duration::from_secs(10))
+ .unwrap();
+ env.mine_blocks(3, None).unwrap();
+ env.wait_until_electrum_sees_block(Duration::from_secs(10))
+ .unwrap();
+ cli_full_scan(&cli);
+
+ // The funded outpoint to prove control of.
+ let unspent = run_wallet_json(&cli, &["unspent"]);
+ let outpoint = unspent["items"][0]["outpoint"]
+ .as_str()
+ .expect("funded wallet should have one UTXO")
+ .to_string();
+ let addr = address.to_string();
+
+ // Produce a proof-of-funds over that UTXO.
+ let proof = run_wallet_json(
+ &cli,
+ &[
+ "sign_message",
+ "--message",
+ "proof-of-funds",
+ "--address",
+ &addr,
+ "--signature-type",
+ "fullproofoffunds",
+ "--utxos",
+ &outpoint,
+ ],
+ )["proof"]
+ .as_str()
+ .expect("sign_message should return a proof")
+ .to_string();
+
+ let result = run_wallet_json(
+ &cli,
+ &[
+ "verify_message",
+ "--proof",
+ &proof,
+ "--message",
+ "proof-of-funds",
+ "--address",
+ &addr,
+ ],
+ );
+ assert_eq!(
+ result["valid"].as_bool(),
+ Some(true),
+ "proof-of-funds should verify against the persisted wallet: {result}"
+ );
+ assert_eq!(
+ result["proven_amount"].as_u64(),
+ Some(5_000),
+ "proven_amount should equal the funded UTXO value: {result}"
+ );
+ }
- let result = run_wallet_json(
- &cli,
- &[
- "verify_message",
- "--proof",
- &proof,
- "--message",
- "proof-of-funds",
- "--address",
- &addr,
- ],
- );
- assert_eq!(
- result["valid"].as_bool(),
- Some(true),
- "proof-of-funds should verify against the persisted wallet: {result}"
- );
- assert_eq!(
- result["proven_amount"].as_u64(),
- Some(5_000),
- "proven_amount should equal the funded UTXO value: {result}"
- );
- }
- */
// `create_dns_tx` with a plain `--to` recipient
#[cfg(feature = "dns_payment")]
#[test]