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
///
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())
+ }
}
}
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;
]);
}
- 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))
}
}
]);
}
- 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))
}
}
]);
}
- let table = rows
- .table()
- .display()
- .map_err(|e| Error::Generic(e.to_string()))?;
- Ok(format!("{table}"))
+ simple_table(rows, None)
}
}
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)
}
}
}
}
-
#[derive(Serialize)]
#[serde(transparent)]
pub struct WalletsListResult(pub HashMap<String, WalletConfigInner>);
)
}
}
+
+
+#[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)
+ }
+}
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(
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 {
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(),
+ )),
+ }
+}