bdk_message_signer/lib.rs
1//! A no_std Rust library implementing [BIP‑322: Generic Signed Message Format](https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki).
2//!
3//! This crate provides:
4//! - Construction of virtual `to_spend` and `to_sign` transactions
5//! - Signing and verification for Simple, Full, and Full Proof-of-Funds BIP‑322 formats
6//! - Optional “proof of funds” support via additional UTXO inputs
7#![no_std]
8
9#[macro_use]
10pub extern crate alloc;
11
12#[cfg(feature = "std")]
13extern crate std;
14
15pub mod error;
16pub mod sign;
17pub mod utils;
18pub mod verify;
19
20pub use error::*;
21#[allow(unused_imports)]
22pub use sign::*;
23pub use utils::*;
24pub use verify::*;
25
26use crate::Error;
27use alloc::{
28 string::{String, ToString},
29 vec::Vec,
30};
31use bitcoin::{
32 Address, Amount, OutPoint, Psbt,
33 base64::{Engine, engine::general_purpose},
34};
35
36/// Represents the different formats supported by the message signing protocol.
37///
38/// BIP322 defines multiple formats for signatures to accommodate different use cases
39/// and maintain backward compatibility with legacy signing methods.
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum SignatureFormat {
42 /// Legacy Bitcoin Core message signing format (P2PKH only).
43 Legacy,
44 /// A simplified version of the format that includes only the witness stack.
45 Simple,
46 /// Full format with complete transaction data.
47 Full,
48 /// The Full format with Proof-of-funds capabiility.
49 FullProofOfFunds,
50}
51
52/// Main trait providing signing and verification functionality.
53///
54/// This trait is implemented for `bdk_wallet::Wallet` to provide seamless
55/// integration with BDK wallets.
56///
57/// # Examples
58///
59/// ```no_run
60/// use bdk_wallet::{Wallet, KeychainKind};
61/// use bdk_message_signer::{MessageSigner, SignatureFormat};
62///
63/// # fn main() -> Result<(), bdk_message_signer::error::Error> {
64/// # let mut wallet: Wallet = unimplemented!();
65/// let address = wallet.peek_address(KeychainKind::External, 0).address;
66///
67/// // Sign a message
68/// let proof = wallet.sign_message(
69/// "Hello Bitcoin",
70/// SignatureFormat::Simple,
71/// &address,
72/// None,
73/// )?;
74///
75/// // Verify the signature
76/// let result = wallet.verify_message(
77/// &proof,
78/// "Hello Bitcoin",
79/// &address,
80/// )?;
81///
82/// assert!(result.valid);
83/// # Ok(())
84/// # }
85/// ```
86pub trait MessageSigner {
87 /// Sign a message for a specific address.
88 ///
89 /// # Arguments
90 ///
91 /// * `message` - The message to sign (as UTF-8 text)
92 /// * `signature_type` - The signature format to use
93 /// * `address` - The address to sign with (must be owned by wallet)
94 /// * `utxos` - Optional list of specific UTXOs for proof-of-funds (only for `FullProofOfFunds`)
95 ///
96 /// # Returns
97 ///
98 /// Returns either a complete signature or a PSBT for external signing or [`Error`] when there's an error
99 fn sign_message(
100 &mut self,
101 message: &str,
102 signature_type: SignatureFormat,
103 address: &Address,
104 utxos: Option<Vec<OutPoint>>,
105 ) -> Result<MessageProof, Error>;
106
107 /// Verify message signature.
108 ///
109 /// # Arguments
110 ///
111 /// * `proof` - The signature proof to verify
112 /// * `message` - The original message that was signed
113 /// * `signature_type` - The signature format used
114 /// * `address` - The address that supposedly signed the message
115 ///
116 /// # Returns
117 ///
118 /// Returns verification result with validity and optional proven amount or [`Error`] when there's an error
119 fn verify_message(
120 &self,
121 proof: &MessageProof,
122 message: &str,
123 address: &Address,
124 ) -> Result<MessageVerificationResult, Error>;
125}
126
127/// Result of signature verification.
128pub struct MessageVerificationResult {
129 /// Whether the signature is valid for the given message and address
130 pub valid: bool,
131 /// The total amount proven for FullProofOfFunds signatures.
132 ///
133 /// This is `Some` only when using `FullProofOfFunds` format and
134 /// additional UTXOs were included. For other formats, always `None`.
135 pub proven_amount: Option<Amount>,
136}
137
138/// Result of signing operation.
139///
140/// Signing can result in either a complete signature (when the wallet has
141/// private keys) or a PSBT ready for external signing (e.g., hardware wallets).
142#[derive(Debug)]
143pub enum MessageProof {
144 /// Signature was created successfully.
145 ///
146 /// Contains the base64-encoded signature string ready for sharing.
147 Signed(String),
148 /// PSBT ready for external signing.
149 Psbt(Psbt),
150}
151
152impl MessageProof {
153 /// Converts the proof to a base64-encoded string.
154 pub fn to_base64(&self) -> String {
155 match self {
156 // Signed proofs are already in base64 format, just return the string
157 MessageProof::Signed(s) => s.clone(),
158 // For PSBT proofs, serialize and encode to base64
159 MessageProof::Psbt(psbt) => general_purpose::STANDARD.encode(psbt.serialize()),
160 }
161 }
162
163 /// Parses a base64-encoded string into a [`MessageProof`].
164 pub fn from_base64(s: &str) -> Result<Self, Error> {
165 // Try to decode as PSBT first - this handles the Full and FullProofOfFunds formats
166 if let Ok(bytes) = general_purpose::STANDARD.decode(s) {
167 if let Ok(psbt) = Psbt::deserialize(&bytes) {
168 return Ok(MessageProof::Psbt(psbt));
169 }
170 }
171
172 // Otherwise, treat it as a signed proof (Legacy or Simple format)
173 // The string is already base64 encoded, so we store it as-is
174 Ok(MessageProof::Signed(s.to_string()))
175 }
176}