From: Vihiga Tyonum Date: Wed, 29 Apr 2026 14:07:54 +0000 (+0100) Subject: ref(handlers): rebase bip322 feature X-Git-Url: http://internal-gitweb-vhost/blockdata/script/encode/-script/display/struct.VarInt.html?a=commitdiff_plain;h=90017cdf87538b8f4f14496e55c9ee92f7f96d74;p=bdk-cli ref(handlers): rebase bip322 feature - rebase master for bip322 feature - update types to use simple table helper --- diff --git a/src/handlers/offline.rs b/src/handlers/offline.rs index 8ce056b..b75e6ed 100644 --- a/src/handlers/offline.rs +++ b/src/handlers/offline.rs @@ -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()) + } } } diff --git a/src/handlers/types.rs b/src/handlers/types.rs index 94aca12..51a1b56 100644 --- a/src/handlers/types.rs +++ b/src/handlers/types.rs @@ -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 { - 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); @@ -444,3 +426,48 @@ impl FormatOutput for WalletsListResult { ) } } + + +#[derive(Serialize)] +pub struct DescriptorResult { + #[serde(skip_serializing_if = "Option::is_none")] + pub descriptor: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub multipath_descriptor: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub public_descriptors: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub private_descriptors: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub mnemonic: Option, +} + +impl FormatOutput for DescriptorResult { + fn to_table(&self) -> Result { + let mut rows: Vec> = 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) + } +} diff --git a/src/handlers/wallets.rs b/src/handlers/wallets.rs index 9f2b321..fdfdff2 100644 --- a/src/handlers/wallets.rs +++ b/src/handlers/wallets.rs @@ -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) diff --git a/src/utils/common.rs b/src/utils/common.rs index 9d7f76f..03e62db 100644 --- a/src/utils/common.rs +++ b/src/utils/common.rs @@ -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 { + 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(), + )), + } +}