]> Untitled Git - bdk-cli/commitdiff
fix(bip322): Re-enable bip322 feature
authorVihiga Tyonum <withtvpeter@gmail.com>
Tue, 14 Jul 2026 01:37:57 +0000 (02:37 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Thu, 16 Jul 2026 16:07:07 +0000 (17:07 +0100)
- update bdk_bip322 crate with replacement crate
bdk_message_signer
- re-enable bip322 feature
- update documentation for add_global_xpubs fn

Cargo.lock
Cargo.toml
src/commands.rs
src/error.rs
src/handlers/offline.rs
src/utils/common.rs
src/utils/types.rs
tests/integration/offline.rs
tests/integration/online.rs

index 66273d71e68de92df36fc5ade057c7810cdc3bfb..915b4ba1faaac80f16f121c9fa490fa94026619d 100644 (file)
@@ -251,6 +251,7 @@ dependencies = [
  "bdk_electrum",
  "bdk_esplora",
  "bdk_kyoto",
+ "bdk_message_signer",
  "bdk_redb",
  "bdk_sp",
  "bdk_testenv",
@@ -345,6 +346,16 @@ dependencies = [
  "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"
index 3411ad43740add84829e67111c1228c14bcbff37..e97625ca4b2453b3f4314e964a8ee5560da59fe8 100644 (file)
@@ -39,7 +39,7 @@ shlex = {  version = "1.3.0", optional = true }
 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]
@@ -61,7 +61,7 @@ dns_payment = ["bitcoin-payment-instructions"]
 
 # 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 = []
index 2891162792b2a7490bd231581f3c9d97116bef11..613a4f6b4dad08b01f236ea94826b3254e3dd670 100644 (file)
@@ -13,8 +13,8 @@
 //! 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,
@@ -362,11 +362,11 @@ pub enum OfflineWalletSubCommand {
     /// 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).
index 96af63ad6415ba2f916c3836cfbe10bdc12e1777..dfd9f336e9c9cbdd64ad05ebc7ea793cbe21e414 100644 (file)
@@ -155,9 +155,9 @@ pub enum BDKCliError {
     #[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 {
index 048dcfcb78edc996e9abd1a1aa5fadb4089d3e6d..993d10cbc68a5e3c12822a71b10f253e40ac653b 100644 (file)
@@ -28,12 +28,12 @@ use {
     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> {
@@ -73,14 +73,14 @@ impl OfflineWalletSubCommand {
             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) => {
@@ -204,7 +204,7 @@ pub struct CreateTxCommand {
     #[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,
 
@@ -773,7 +773,6 @@ impl AppCommand<AppContext<OfflineOperations<'_>>> for CombinePsbtCommand {
     }
 }
 
-/**
 #[cfg(feature = "bip322")]
 #[derive(Debug, Parser, Clone, PartialEq)]
 pub struct SignMessageCommand {
@@ -861,12 +860,11 @@ impl AppCommand<AppContext<OfflineOperations<'_>>> for VerifyMessageCommand {
         })
     }
 }
-**/
 
 #[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>,
 }
 
@@ -891,7 +889,7 @@ impl AppCommand<AppContext<OfflineOperations<'_>>> for LockUtxoCommand {
 #[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>,
 }
 
index 0fdc2aa22bb556dd015dc2c031c6a2fa78e7f189..a5f8c15a66d7db47465de58079059c82410fd910 100644 (file)
@@ -1,8 +1,8 @@
 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};
@@ -197,20 +197,20 @@ pub(crate) fn parse_sp_code_value_pairs(s: &str) -> Result<(SilentPaymentCode, u
     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 {
@@ -232,11 +232,11 @@ pub fn command_requires_db(command: &OfflineWalletSubCommand) -> bool {
         | 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")]
index 51855b0dcd67e31da82c1940d3a93405087d5d2e..8a10d03f7eeb441c6e7e1f195e4554a2b03354eb 100644 (file)
@@ -110,18 +110,18 @@ pub struct KeychainPair<T> {
     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 {
index 1c0fed2800aed3de9c56b658d3100afb9c9b90df..f19f623c0baf286112c8cd79dd174d3618289d84 100644 (file)
@@ -150,117 +150,117 @@ mod test_offline {
         .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();
index 1a8f2eefb94a7f373bcb48c3ebecae228f09cb86..e6d7108fa12147a678a063cb5b3e8001fa237141 100644 (file)
@@ -813,111 +813,111 @@ mod test_online {
             );
         }
     }
-    /**
-        #[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]