Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@ name: Audit
on:
push:
paths:
# Run if workflow changes
- '.github/workflows/audit.yml'
# Run on changed dependencies
- '**/Cargo.toml'
- '**/Cargo.lock'
# Run if the configuration file changes
- '**/audit.toml'
schedule:
- cron: '0 0 * * 0' # Once per week
# Run manually
workflow_dispatch:

jobs:

security_audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/audit@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ shlex = { version = "1.3.0", optional = true }
payjoin = { version = "1.0.0-rc.1", features = ["v1", "v2", "io", "_test-utils"], optional = true}
reqwest = { version = "0.12.23", default-features = false, optional = true }
url = { version = "2.5.4", optional = true }
bdk-bip322 = { git = "https://github.com/aagbotemi/bdk-bip322.git", branch = "master", optional = true }

[features]
default = ["repl", "sqlite"]
Expand All @@ -55,6 +56,7 @@ rpc = ["bdk_bitcoind_rpc", "_payjoin-dependencies"]

# Internal features
_payjoin-dependencies = ["payjoin", "reqwest", "url"]
bip322 = ["bdk-bip322"]

# Use this to consensus verify transactions at sync time
verify = []
Expand Down
31 changes: 31 additions & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,37 @@ pub enum OfflineWalletSubCommand {
#[arg(env = "BASE64_PSBT", required = true)]
psbt: Vec<String>,
},
/// Sign a message using BIP322
#[cfg(feature = "bip322")]
SignBip322 {
/// The message to sign
#[arg(long)]
message: String,
/// The signature format (e.g., Legacy, Simple, Full)
#[arg(long, default_value = "simple")]
signature_type: String,
/// Address to sign
#[arg(long)]
address: String,
// Optional list of specific UTXOs for proof-of-funds (only for `FullWithProofOfFunds`) #[arg(long)]
utxos: Option<Vec<OutPoint>>,
},
/// Verify a BIP322 signature
#[cfg(feature = "bip322")]
VerifyBip322 {
/// The signature proof to verify
#[arg(long)]
proof: String,
/// The message that was signed
#[arg(long)]
message: String,
/// The signature format (e.g., Legacy, Simple, Full)
#[arg(long, default_value = "simple")]
signature_type: String,
/// The address associated with the signature
#[arg(long)]
address: String,
},
}

/// Wallet subcommands that needs a blockchain backend.
Expand Down
7 changes: 7 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,10 @@ impl From<ExtractTxError> for BDKCliError {
BDKCliError::PsbtExtractTxError(Box::new(value))
}
}

#[cfg(feature = "bip322")]
impl From<bdk_bip322::error::Error> for BDKCliError {
fn from(e: bdk_bip322::error::Error) -> Self {
BDKCliError::Generic(e.to_string())
}
}
45 changes: 45 additions & 0 deletions src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ use std::str::FromStr;
))]
use std::sync::Arc;

#[cfg(feature = "bip322")]
use crate::error::BDKCliError;
#[cfg(feature = "bip322")]
use bdk_bip322::{BIP322, Bip322Proof, Bip322VerificationResult};

#[cfg(any(
feature = "electrum",
feature = "esplora",
Expand Down Expand Up @@ -591,6 +596,46 @@ pub fn handle_offline_wallet_subcommand(
&json!({ "psbt": BASE64_STANDARD.encode(final_psbt.serialize()) }),
)?)
}
#[cfg(feature = "bip322")]
SignBip322 {
message,
signature_type,
address,
utxos,
} => {
let address: Address = parse_address(&address)?;
let signature_format = parse_signature_format(&signature_type)?;

let proof: Bip322Proof = wallet
.sign_bip322(message.as_str(), signature_format, &address, utxos)
.map_err(|e| {
BDKCliError::Generic(format!("Failed to sign BIP-322 message: {e}"))
})?;

Ok(json!({"proof": proof.to_base64()}).to_string())
}
#[cfg(feature = "bip322")]
VerifyBip322 {
proof,
message,
signature_type,
address,
} => {
let address: Address = parse_address(&address)?;
let signature_format = parse_signature_format(&signature_type)?;

let parsed_proof: Bip322Proof = Bip322Proof::from_base64(&proof)
.map_err(|e| BDKCliError::Generic(format!("Invalid proof: {e}")))?;

let is_valid: Bip322VerificationResult =
wallet.verify_bip322(&parsed_proof, &message, signature_format, &address)?;

Ok(json!({
"valid": is_valid.valid,
"proven_amount": is_valid.proven_amount.map(|a| a.to_sat()) // optional field
})
.to_string())
}
}
}

Expand Down
18 changes: 18 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ use bdk_wallet::descriptor::Segwitv0;
use bdk_wallet::keys::{GeneratableKey, GeneratedKey, bip39::WordCount};
use serde_json::{Value, json};

#[cfg(feature = "bip322")]
use bdk_bip322::SignatureFormat;

/// Parse the recipient (Address,Amount) argument from cli input.
pub(crate) fn parse_recipient(s: &str) -> Result<(ScriptBuf, u64), String> {
let parts: Vec<_> = s.split(':').collect();
Expand Down Expand Up @@ -95,6 +98,21 @@ pub(crate) fn parse_address(address_str: &str) -> Result<Address, Error> {
Ok(unchecked_address.assume_checked())
}

/// 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(),
)),
}
}

/// Prepare bdk-cli home directory
///
/// This function is called to check if [`crate::CliOpts`] datadir is set.
Expand Down
Loading