Skip to content

Commit 5f0acd7

Browse files
authored
Merge branch 'bitcoindevkit:master' into feat/tr-descriptor-with-random-point
2 parents 2a933ce + f80d92a commit 5f0acd7

6 files changed

Lines changed: 113 additions & 3 deletions

File tree

Cargo.lock

Lines changed: 14 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ bdk_redb = { version = "0.1.1", optional = true }
3838
payjoin = { version = "=1.0.0-rc.1", features = ["v1", "v2", "io", "_test-utils"], optional = true}
3939
reqwest = { version = "0.13.2", default-features = false, optional = true }
4040
url = { version = "2.5.8", optional = true }
41+
bdk_bip322 = { version = "0.1.0", optional = true }
4142

4243
[features]
4344
default = ["repl", "sqlite"]
@@ -57,6 +58,7 @@ rpc = ["bdk_bitcoind_rpc", "_payjoin-dependencies"]
5758

5859
# Internal features
5960
_payjoin-dependencies = ["payjoin", "reqwest", "url"]
61+
bip322 = ["bdk_bip322"]
6062

6163
# Use this to consensus verify transactions at sync time
6264
verify = []

src/commands.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,35 @@ pub enum OfflineWalletSubCommand {
461461
#[arg(env = "BASE64_PSBT", required = true)]
462462
psbt: Vec<String>,
463463
},
464+
/// Sign a message using BIP322
465+
#[cfg(feature = "bip322")]
466+
SignMessage {
467+
/// The message to sign
468+
#[arg(long)]
469+
message: String,
470+
/// The signature format (e.g., Legacy, Simple, Full)
471+
#[arg(long, default_value = "simple")]
472+
signature_type: String,
473+
/// Address to sign
474+
#[arg(long)]
475+
address: String,
476+
/// Optional list of specific UTXOs for proof-of-funds (only for `FullWithProofOfFunds`)
477+
#[arg(long)]
478+
utxos: Option<Vec<OutPoint>>,
479+
},
480+
/// Verify a BIP322 signature
481+
#[cfg(feature = "bip322")]
482+
VerifyMessage {
483+
/// The signature proof to verify
484+
#[arg(long)]
485+
proof: String,
486+
/// The message that was signed
487+
#[arg(long)]
488+
message: String,
489+
/// The address associated with the signature
490+
#[arg(long)]
491+
address: String,
492+
},
464493
}
465494

466495
/// Wallet subcommands that needs a blockchain backend.

src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ pub enum BDKCliError {
147147
#[cfg(feature = "payjoin")]
148148
#[error("Payjoin create request error: {0}")]
149149
PayjoinCreateRequest(#[from] payjoin::send::v2::CreateRequestError),
150+
151+
#[cfg(feature = "bip322")]
152+
#[error("BIP-322 error: {0}")]
153+
Bip322Error(#[from] bdk_bip322::error::Error),
150154
}
151155

152156
impl From<ExtractTxError> for BDKCliError {

src/handlers.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ use std::str::FromStr;
7878
))]
7979
use std::sync::Arc;
8080

81+
#[cfg(feature = "bip322")]
82+
use crate::error::BDKCliError;
83+
#[cfg(feature = "bip322")]
84+
use bdk_bip322::{BIP322, MessageProof, MessageVerificationResult};
85+
8186
#[cfg(any(
8287
feature = "electrum",
8388
feature = "esplora",
@@ -600,6 +605,47 @@ pub fn handle_offline_wallet_subcommand(
600605
&json!({ "psbt": BASE64_STANDARD.encode(final_psbt.serialize()) }),
601606
)?)
602607
}
608+
#[cfg(feature = "bip322")]
609+
SignMessage {
610+
message,
611+
signature_type,
612+
address,
613+
utxos,
614+
} => {
615+
let address: Address = parse_address(&address)?;
616+
let signature_format = parse_signature_format(&signature_type)?;
617+
618+
if !wallet.is_mine(address.script_pubkey()) {
619+
return Err(Error::Generic(format!(
620+
"Address {} does not belong to this wallet.",
621+
address
622+
)));
623+
}
624+
625+
let proof: MessageProof =
626+
wallet.sign_message(message.as_str(), signature_format, &address, utxos)?;
627+
628+
Ok(json!({"proof": proof.to_base64()}).to_string())
629+
}
630+
#[cfg(feature = "bip322")]
631+
VerifyMessage {
632+
proof,
633+
message,
634+
address,
635+
} => {
636+
let address: Address = parse_address(&address)?;
637+
let parsed_proof: MessageProof = MessageProof::from_base64(&proof)
638+
.map_err(|e| BDKCliError::Generic(format!("Invalid proof: {e}")))?;
639+
640+
let is_valid: MessageVerificationResult =
641+
wallet.verify_message(&parsed_proof, &message, &address)?;
642+
643+
Ok(json!({
644+
"valid": is_valid.valid,
645+
"proven_amount": is_valid.proven_amount.map(|a| a.to_sat()) // optional field
646+
})
647+
.to_string())
648+
}
603649
}
604650
}
605651

src/utils.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ use bdk_wallet::descriptor::Segwitv0;
5656
use bdk_wallet::keys::{GeneratableKey, GeneratedKey, bip39::WordCount};
5757
use serde_json::{Value, json};
5858

59+
#[cfg(feature = "bip322")]
60+
use bdk_bip322::SignatureFormat;
61+
5962
/// Parse the recipient (Address,Amount) argument from cli input.
6063
pub(crate) fn parse_recipient(s: &str) -> Result<(ScriptBuf, u64), String> {
6164
let parts: Vec<_> = s.split(':').collect();
@@ -95,6 +98,21 @@ pub(crate) fn parse_address(address_str: &str) -> Result<Address, Error> {
9598
Ok(unchecked_address.assume_checked())
9699
}
97100

101+
/// Function to parse the signature format from a string
102+
#[cfg(feature = "bip322")]
103+
pub(crate) fn parse_signature_format(format_str: &str) -> Result<SignatureFormat, Error> {
104+
match format_str.to_lowercase().as_str() {
105+
"legacy" => Ok(SignatureFormat::Legacy),
106+
"simple" => Ok(SignatureFormat::Simple),
107+
"full" => Ok(SignatureFormat::Full),
108+
"fullproofoffunds" => Ok(SignatureFormat::FullProofOfFunds),
109+
_ => Err(Error::Generic(
110+
"Invalid signature format. Use 'legacy', 'simple', 'full', or 'fullproofoffunds'"
111+
.to_string(),
112+
)),
113+
}
114+
}
115+
98116
/// Prepare bdk-cli home directory
99117
///
100118
/// This function is called to check if [`crate::CliOpts`] datadir is set.

0 commit comments

Comments
 (0)