]> Untitled Git - bdk-cli/commitdiff
ref(handlers): rebase bip322 feature
authorVihiga Tyonum <withtvpeter@gmail.com>
Wed, 29 Apr 2026 14:07:54 +0000 (15:07 +0100)
committerVihiga Tyonum <withtvpeter@gmail.com>
Fri, 5 Jun 2026 16:16:36 +0000 (17:16 +0100)
- rebase master for bip322 feature
- update types to use simple table helper

src/handlers/offline.rs
src/handlers/types.rs
src/handlers/wallets.rs
src/utils/common.rs

index 8ce056b1dd480142f8dfca2e779ce9c059c084a2..b75e6ed7d81615d8ec2d1de6ed1908fc7fe8f114 100644 (file)
@@ -14,6 +14,12 @@ use bdk_wallet::{KeychainKind, SignOptions, Wallet};
 use serde_json::json;
 use std::collections::BTreeMap;
 use std::str::FromStr;
+#[cfg(feature = "bip322")]
+use {
+    crate::utils::{parse_address, parse_signature_format},
+    bdk_bip322::{BIP322, MessageProof, MessageVerificationResult},
+    bdk_wallet::bitcoin::Address,
+};
 
 /// Execute an offline wallet sub-command
 ///
@@ -269,5 +275,46 @@ pub fn handle_offline_wallet_subcommand(
             let result = PsbtResult::new(&final_psbt, wallet_opts.verbose, None);
             result.format(pretty)
         }
+        #[cfg(feature = "bip322")]
+        SignMessage {
+            message,
+            signature_type,
+            address,
+            utxos,
+        } => {
+            let address: Address = parse_address(&address)?;
+            let signature_format = parse_signature_format(&signature_type)?;
+
+            if !wallet.is_mine(address.script_pubkey()) {
+                return Err(Error::Generic(format!(
+                    "Address {} does not belong to this wallet.",
+                    address
+                )));
+            }
+
+            let proof: MessageProof =
+                wallet.sign_message(message.as_str(), signature_format, &address, utxos)?;
+
+            Ok(json!({"proof": proof.to_base64()}).to_string())
+        }
+        #[cfg(feature = "bip322")]
+        VerifyMessage {
+            proof,
+            message,
+            address,
+        } => {
+            let address: Address = parse_address(&address)?;
+            let parsed_proof: MessageProof = MessageProof::from_base64(&proof)
+                .map_err(|e| Error::Generic(format!("Invalid proof: {e}")))?;
+
+            let is_valid: MessageVerificationResult =
+                wallet.verify_message(&parsed_proof, &message, &address)?;
+
+            Ok(json!({
+                "valid": is_valid.valid,
+                "proven_amount": is_valid.proven_amount.map(|a| a.to_sat()) // optional field
+            })
+            .to_string())
+        }
     }
 }
index 94aca12359a70083d648cf2d75ac86af97172846..51a1b56a9cc01820d00be1d80b35ce23e1585b36 100644 (file)
@@ -7,7 +7,7 @@ use bdk_wallet::bitcoin::{
     Address, Network, Psbt, Transaction, base64::Engine, consensus::encode::serialize_hex,
 };
 use bdk_wallet::{AddressInfo, Balance, LocalOutput, chain::ChainPosition};
-use cli_table::{Cell, CellStruct, Style, Table, format::Justify};
+use cli_table::{Cell, CellStruct, Style, format::Justify};
 use serde::Serialize;
 use serde_json::json;
 
@@ -83,20 +83,16 @@ impl FormatOutput for TransactionListResult {
             ]);
         }
 
-        let table = rows
-            .table()
-            .title(vec![
-                "Txid".cell().bold(true),
-                "Version".cell().bold(true),
-                "Is RBF".cell().bold(true),
-                "Input Count".cell().bold(true),
-                "Output Count".cell().bold(true),
-                "Total Value (sat)".cell().bold(true),
-            ])
-            .display()
-            .map_err(|e| Error::Generic(e.to_string()))?;
+        let title = vec![
+            "Txid".cell().bold(true),
+            "Version".cell().bold(true),
+            "Is RBF".cell().bold(true),
+            "Input Count".cell().bold(true),
+            "Output Count".cell().bold(true),
+            "Total Value (sat)".cell().bold(true),
+        ];
 
-        Ok(format!("{table}"))
+        simple_table(rows, Some(title))
     }
 }
 
@@ -240,22 +236,17 @@ impl FormatOutput for UnspentListResult {
             ]);
         }
 
-        let table = rows
-            .table()
-            .title(vec![
-                "Outpoint".cell().bold(true),
-                "Output (sat)".cell().bold(true),
-                "Output Address".cell().bold(true),
-                "Keychain".cell().bold(true),
-                "Is Spent".cell().bold(true),
-                "Index".cell().bold(true),
-                "Block Height".cell().bold(true),
-                "Block Hash".cell().bold(true),
-            ])
-            .display()
-            .map_err(|e| Error::Generic(e.to_string()))?;
-
-        Ok(format!("{table}"))
+        let title = vec![
+            "Outpoint".cell().bold(true),
+            "Output (sat)".cell().bold(true),
+            "Output Address".cell().bold(true),
+            "Keychain".cell().bold(true),
+            "Is Spent".cell().bold(true),
+            "Index".cell().bold(true),
+            "Block Height".cell().bold(true),
+            "Block Hash".cell().bold(true),
+        ];
+        simple_table(rows, Some(title))
     }
 }
 
@@ -302,11 +293,7 @@ impl FormatOutput for PsbtResult {
             ]);
         }
 
-        let table = rows
-            .table()
-            .display()
-            .map_err(|e| Error::Generic(e.to_string()))?;
-        Ok(format!("{table}"))
+        simple_table(rows, None)
     }
 }
 
@@ -325,15 +312,11 @@ impl RawPsbt {
 
 impl FormatOutput for RawPsbt {
     fn to_table(&self) -> Result<String, Error> {
-        let table = vec![vec![
+        let rows = vec![vec![
             "Raw Transaction".cell().bold(true),
             self.raw_tx.clone().cell(),
-        ]]
-        .table()
-        .display()
-        .map_err(|e| Error::Generic(e.to_string()))?;
-
-        Ok(format!("{table}"))
+        ]];
+        simple_table(rows, None)
     }
 }
 
@@ -407,7 +390,6 @@ impl FormatOutput for KeyResult {
     }
 }
 
-
 #[derive(Serialize)]
 #[serde(transparent)]
 pub struct WalletsListResult(pub HashMap<String, WalletConfigInner>);
@@ -444,3 +426,48 @@ impl FormatOutput for WalletsListResult {
         )
     }
 }
+
+
+#[derive(Serialize)]
+pub struct DescriptorResult {
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub descriptor: Option<String>,
+    
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub multipath_descriptor: Option<String>,
+    
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub public_descriptors: Option<KeychainPair<String>>,
+    
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub private_descriptors: Option<KeychainPair<String>>,
+    
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub mnemonic: Option<String>,
+}
+
+impl FormatOutput for DescriptorResult {
+    fn to_table(&self) -> Result<String, Error> {
+        let mut rows: Vec<Vec<CellStruct>> = vec![];
+        
+        if let Some(desc) = &self.descriptor {
+            rows.push(vec!["Descriptor".cell().bold(true), desc.clone().cell()]);
+        }
+        if let Some(desc) = &self.multipath_descriptor {
+            rows.push(vec!["Multipath Descriptor".cell().bold(true), desc.clone().cell()]);
+        }
+        if let Some(pub_desc) = &self.public_descriptors {
+            rows.push(vec!["External Public".cell().bold(true), pub_desc.external.clone().cell()]);
+            rows.push(vec!["Internal Public".cell().bold(true), pub_desc.internal.clone().cell()]);
+        }
+        if let Some(priv_desc) = &self.private_descriptors {
+            rows.push(vec!["External Private".cell().bold(true), priv_desc.external.clone().cell()]);
+            rows.push(vec!["Internal Private".cell().bold(true), priv_desc.internal.clone().cell()]);
+        }
+        if let Some(mnemonic) = &self.mnemonic {
+            rows.push(vec!["Mnemonic".cell().bold(true), mnemonic.clone().cell()]);
+        }
+
+        simple_table(rows, None)
+    }
+}
index 9f2b32175300ae9a47bdc7929c4bf0cdbceb5d5f..fdfdff2a12084287721abe4f3da41b56f4645328 100644 (file)
@@ -1,6 +1,6 @@
+use crate::error::BDKCliError as Error;
 use crate::utils::output::FormatOutput;
 use crate::{config::WalletConfig, handlers::types::WalletsListResult};
-use crate::error::BDKCliError as Error;
 use std::path::Path;
 
 /// Handle the top-level `wallets` command (lists all saved wallets)
index 9d7f76f8fd28a22d34ed0ab67a4f2fee227706d9..03e62db35512efffb48c262cb2731e6c49ae3d6a 100644 (file)
@@ -1,4 +1,6 @@
 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(any(
@@ -193,8 +195,7 @@ pub fn load_wallet_config(
     Ok((wallet_opts, network))
 }
 
-
-// #[cfg(feature = "silent-payments")]
+#[cfg(feature = "silent-payments")]
 pub(crate) fn parse_sp_code_value_pairs(s: &str) -> Result<(SilentPaymentCode, u64), Error> {
     let parts: Vec<&str> = s.split(':').collect();
     if parts.len() != 2 {
@@ -214,3 +215,18 @@ 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(),
+        )),
+    }
+}