Skip to main content

breez_sdk_liquid/
signer.rs

1use std::sync::Arc;
2
3use anyhow::anyhow;
4use bip39::Mnemonic;
5use boltz_client::PublicKey;
6use lwk_common::Signer as LwkSigner;
7use lwk_wollet::bitcoin::bip32::Xpriv;
8use lwk_wollet::bitcoin::Network;
9use lwk_wollet::elements_miniscript::{self, ToPublicKey as _};
10use lwk_wollet::elements_miniscript::{
11    bitcoin::{self, bip32::DerivationPath},
12    elements::{
13        bitcoin::bip32::{self, Fingerprint, Xpub},
14        hashes::Hash,
15        pset::PartiallySignedTransaction,
16        secp256k1_zkp::{All, Secp256k1},
17        sighash::SighashCache,
18    },
19    elementssig_to_rawsig,
20    psbt::PsbtExt,
21    slip77::MasterBlindingKey,
22};
23use lwk_wollet::hashes::{sha256, HashEngine, Hmac, HmacEngine};
24use lwk_wollet::secp256k1::ecdsa::Signature;
25use lwk_wollet::secp256k1::Message;
26
27use crate::model::{Signer, SignerError};
28
29#[derive(thiserror::Error, Debug)]
30pub enum SignError {
31    #[error(transparent)]
32    Pset(#[from] elements_miniscript::elements::pset::Error),
33
34    #[error(transparent)]
35    ElementsEncode(#[from] elements_miniscript::elements::encode::Error),
36
37    #[error(transparent)]
38    Sighash(#[from] elements_miniscript::psbt::SighashError),
39
40    #[error(transparent)]
41    PsetParse(#[from] elements_miniscript::elements::pset::ParseError),
42
43    #[error(transparent)]
44    Bip32(#[from] bip32::Error),
45
46    #[error(transparent)]
47    Generic(#[from] anyhow::Error),
48
49    #[error(transparent)]
50    UserSignerError(#[from] crate::model::SignerError),
51}
52
53/// Possible errors when creating a new software signer [`SwSigner`]
54#[derive(thiserror::Error, Debug)]
55pub enum NewError {
56    #[error(transparent)]
57    Bip39(#[from] bip39::Error),
58
59    #[error(transparent)]
60    Bip32(#[from] bip32::Error),
61
62    #[error(transparent)]
63    Seed(#[from] anyhow::Error),
64}
65
66/// A software signer
67pub struct SdkLwkSigner {
68    sdk_signer: Arc<Box<dyn Signer>>,
69}
70
71impl SdkLwkSigner {
72    /// Creates a new software signer from the given mnemonic.
73    ///
74    /// Takes also a flag if the network is mainnet so that generated extended keys are in the
75    /// correct form xpub/tpub (there is no need to discriminate between regtest and testnet)
76    pub fn new(sdk_signer: Arc<Box<dyn Signer>>) -> Result<Self, NewError> {
77        Ok(Self { sdk_signer })
78    }
79
80    pub(crate) fn xpub(&self) -> Result<Xpub, SignError> {
81        let xpub = self.sdk_signer.xpub()?;
82        Ok(Xpub::decode(&xpub)?)
83    }
84
85    pub fn fingerprint(&self) -> Result<Fingerprint, SignError> {
86        let f: Fingerprint = self.xpub()?.identifier()[0..4]
87            .try_into()
88            .map_err(|_| SignError::Generic(anyhow::anyhow!("Wrong fingerprint length")))?;
89        Ok(f)
90    }
91
92    pub(crate) fn sign_ecdsa_recoverable(&self, msg: &Message) -> Result<Vec<u8>, SignError> {
93        let sig_bytes = self
94            .sdk_signer
95            .sign_ecdsa_recoverable(msg.as_ref().to_vec())?;
96        Ok(sig_bytes)
97    }
98}
99
100impl LwkSigner for SdkLwkSigner {
101    type Error = SignError;
102
103    fn sign(&self, pset: &mut PartiallySignedTransaction) -> Result<u32, Self::Error> {
104        let tx = pset.extract_tx()?;
105        let mut sighash_cache = SighashCache::new(&tx);
106        let mut signature_added = 0;
107
108        // genesis hash is not used at all for sighash calculation
109        let genesis_hash = elements_miniscript::elements::BlockHash::all_zeros();
110        let mut messages = vec![];
111        for i in 0..pset.inputs().len() {
112            // computing all the messages to sign, it is not necessary if we are not going to sign
113            // some input, but since the pset is borrowed, we can't do this action in a inputs_mut() for loop
114            let msg = pset
115                .sighash_msg(i, &mut sighash_cache, None, genesis_hash)?
116                .to_secp_msg();
117            messages.push(msg);
118        }
119
120        // Fixme: Take a parameter
121        let hash_ty = elements_miniscript::elements::EcdsaSighashType::All;
122
123        let signer_fingerprint = self.fingerprint()?;
124        for (input, msg) in pset.inputs_mut().iter_mut().zip(messages) {
125            for (want_public_key, (fingerprint, derivation_path)) in input.bip32_derivation.iter() {
126                if &signer_fingerprint == fingerprint {
127                    let xpub = self.derive_xpub(derivation_path)?;
128                    let public_key: PublicKey = xpub.public_key.into();
129                    if want_public_key == &public_key {
130                        // fixme: for taproot use schnorr
131                        let sig_bytes = self
132                            .sdk_signer
133                            .sign_ecdsa(msg.as_ref().to_vec(), derivation_path.to_string())?;
134                        let sig = Signature::from_der(&sig_bytes).map_err(|_| {
135                            SignError::Generic(anyhow::anyhow!("Invalid esda signature"))
136                        })?;
137                        let sig = elementssig_to_rawsig(&(sig, hash_ty));
138
139                        let inserted = input.partial_sigs.insert(public_key, sig);
140                        if inserted.is_none() {
141                            signature_added += 1;
142                        }
143                    }
144                }
145            }
146        }
147
148        Ok(signature_added)
149    }
150
151    fn slip77_master_blinding_key(&self) -> Result<MasterBlindingKey, Self::Error> {
152        let bytes: [u8; 32] = self
153            .sdk_signer
154            .slip77_master_blinding_key()?
155            .try_into()
156            .map_err(|_| {
157                SignError::Generic(anyhow::anyhow!("Wrong slip77 master blinding key length"))
158            })?;
159        Ok(bytes.into())
160    }
161
162    fn derive_xpub(&self, path: &DerivationPath) -> Result<Xpub, Self::Error> {
163        let pubkey_bytes = if path.is_empty() {
164            self.sdk_signer.xpub()?
165        } else {
166            self.sdk_signer.derive_xpub(path.to_string())?
167        };
168        let xpub = Xpub::decode(pubkey_bytes.as_slice())?;
169        Ok(xpub)
170    }
171
172    /// Message signing is not used by the SDK (LWK only invokes this for its own
173    /// AMP0 software/hardware signers, which the SDK does not use) and the SDK's
174    /// `Signer` abstraction does not expose a recoverable-sign-at-path primitive,
175    /// so this is intentionally unsupported.
176    fn sign_message(
177        &self,
178        _message: &str,
179        _path: &DerivationPath,
180    ) -> Result<bitcoin::sign_message::MessageSignature, Self::Error> {
181        Err(SignError::Generic(anyhow!(
182            "sign_message is not supported by the Breez SDK signer"
183        )))
184    }
185}
186
187pub struct SdkSigner {
188    xprv: Xpriv,
189    secp: Secp256k1<All>, // could be sign only, but it is likely the caller already has the All context.
190    seed: Vec<u8>,
191    network: Network,
192}
193
194impl SdkSigner {
195    pub fn new(mnemonic: &str, passphrase: &str, is_mainnet: bool) -> Result<Self, NewError> {
196        let mnemonic: Mnemonic = mnemonic.parse()?;
197        let seed = mnemonic.to_seed(passphrase).to_vec();
198
199        Self::new_with_seed(seed, is_mainnet)
200    }
201
202    pub fn new_with_seed(seed: Vec<u8>, is_mainnet: bool) -> Result<Self, NewError> {
203        if seed.len() < 32 {
204            return Err(NewError::Seed(anyhow!("Seed must be at least 32 bytes")));
205        }
206
207        let secp = Secp256k1::new();
208        let network = if is_mainnet {
209            bitcoin::Network::Bitcoin
210        } else {
211            bitcoin::Network::Testnet
212        };
213
214        let xprv = Xpriv::new_master(network, &seed)?;
215
216        Ok(Self {
217            xprv,
218            secp,
219            seed,
220            network,
221        })
222    }
223}
224
225impl Signer for SdkSigner {
226    fn xpub(&self) -> Result<Vec<u8>, SignerError> {
227        Ok(Xpub::from_priv(&self.secp, &self.xprv).encode().to_vec())
228    }
229
230    fn derive_xpub(&self, derivation_path: String) -> Result<Vec<u8>, SignerError> {
231        let der: DerivationPath = derivation_path.parse()?;
232        let derived = self.xprv.derive_priv(&self.secp, &der)?;
233        Ok(Xpub::from_priv(&self.secp, &derived).encode().to_vec())
234    }
235
236    fn sign_ecdsa(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError> {
237        let der: DerivationPath = derivation_path.parse()?;
238        let ext_derived = self.xprv.derive_priv(&self.secp, &der)?;
239        let sig = self.secp.sign_ecdsa_low_r(
240            &Message::from_digest(
241                msg.try_into()
242                    .map_err(|_| anyhow::anyhow!("failed to sign"))?,
243            ),
244            &ext_derived.private_key,
245        );
246        Ok(sig.serialize_der().to_vec())
247    }
248
249    fn slip77_master_blinding_key(&self) -> Result<Vec<u8>, SignerError> {
250        let master_blinding_key = MasterBlindingKey::from_seed(&self.seed);
251        Ok(master_blinding_key.as_bytes().to_vec())
252    }
253
254    fn sign_ecdsa_recoverable(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError> {
255        let secp = Secp256k1::new();
256        let keypair = Xpriv::new_master(self.network, &self.seed)
257            .map_err(|e| anyhow::anyhow!("Could not get signer keypair: {e}"))?
258            .to_keypair(&secp);
259        let s = msg.as_slice();
260
261        let msg: Message = Message::from_digest_slice(s)
262            .map_err(|e| SignerError::Generic { err: e.to_string() })?;
263        // Get message signature and encode to zbase32
264        let recoverable_sig = secp.sign_ecdsa_recoverable(&msg, &keypair.secret_key());
265        let (recovery_id, sig) = recoverable_sig.serialize_compact();
266        let mut complete_signature = vec![31 + recovery_id.to_i32() as u8];
267        complete_signature.extend_from_slice(&sig);
268        Ok(complete_signature)
269    }
270
271    fn hmac_sha256(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError> {
272        let der: DerivationPath = derivation_path.parse()?;
273        let priv_key = self.xprv.derive_priv(&self.secp, &der)?;
274        let mut engine = HmacEngine::<sha256::Hash>::new(priv_key.to_priv().to_bytes().as_slice());
275
276        engine.input(msg.as_slice());
277        Ok(Hmac::<sha256::Hash>::from_engine(engine)
278            .as_byte_array()
279            .to_vec())
280    }
281
282    fn ecies_encrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError> {
283        let keypair = self.xprv.to_keypair(&self.secp);
284        let rc_pub = keypair.public_key().to_public_key().to_bytes();
285        ecies::encrypt(&rc_pub, &msg).map_err(|err| SignerError::Generic {
286            err: format!("Could not encrypt data: {err}"),
287        })
288    }
289
290    fn ecies_decrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError> {
291        let rc_prv = self.xprv.to_priv().to_bytes();
292        ecies::decrypt(&rc_prv, &msg).map_err(|err| SignerError::Generic {
293            err: format!("Could not decrypt data: {err}"),
294        })
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301    use bip32::KeySource;
302    use bip39::rand::{self, RngCore};
303    use bitcoin::PublicKey;
304    use elements::{
305        pset::{Input, Output, PartiallySignedTransaction},
306        AssetId, TxOut, Txid,
307    };
308    use lwk_common::{singlesig_desc, Singlesig};
309    use lwk_signer::SwSigner;
310    use lwk_wollet::{
311        elements::{self, Script},
312        FakeStore, Network, WolletBuilder, WolletDescriptor,
313    };
314    use std::collections::BTreeMap;
315
316    #[cfg(feature = "browser-tests")]
317    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
318
319    fn get_descriptor<S: LwkSigner>(signer: &S) -> Result<WolletDescriptor, anyhow::Error> {
320        let descriptor_str = singlesig_desc(
321            signer,
322            Singlesig::Wpkh,
323            lwk_common::DescriptorBlindingKey::Slip77,
324        )
325        .map_err(|e| anyhow::anyhow!("Invalid descriptor: {e}"))?;
326        Ok(descriptor_str.parse()?)
327    }
328
329    fn create_signers(mnemonic: &str) -> (SwSigner, SdkLwkSigner) {
330        let sw_signer = SwSigner::new(mnemonic, false).unwrap();
331        let sdk_signer: Box<dyn Signer> = Box::new(SdkSigner::new(mnemonic, "", false).unwrap());
332        let sdk_signer = SdkLwkSigner::new(Arc::new(sdk_signer)).unwrap();
333        (sw_signer, sdk_signer)
334    }
335
336    fn create_pset<S: LwkSigner>(signer: &S) -> PartiallySignedTransaction {
337        // Create a PartiallySignedTransaction
338        let mut pset = PartiallySignedTransaction::new_v2();
339
340        // Add a dummy input
341        let prev_txid = Txid::from_slice(&[0; 32]).unwrap();
342        let prev_vout = 0;
343
344        let derivation_path: DerivationPath = "m/84'/0'/0'/0/0".parse().unwrap();
345        let xpub = signer.derive_xpub(&derivation_path).unwrap();
346        let mut bip32_derivation_map: BTreeMap<PublicKey, KeySource> = BTreeMap::new();
347        bip32_derivation_map.insert(
348            xpub.public_key.into(),
349            (signer.fingerprint().unwrap(), derivation_path),
350        );
351        let input = Input {
352            non_witness_utxo: None,
353            witness_utxo: Some(TxOut::new_fee(
354                100_000_000,
355                AssetId::from_slice(&[1; 32]).unwrap(),
356            )),
357            previous_txid: prev_txid,
358            previous_output_index: prev_vout,
359            bip32_derivation: bip32_derivation_map,
360            ..Default::default()
361        };
362
363        pset.add_input(input);
364
365        // Add a dummy output using new_explicit
366        let output_script = Script::new();
367        let output_amount = 99_000_000;
368        let output_asset = AssetId::from_slice(&[1; 32]).unwrap();
369        let output = Output::new_explicit(
370            output_script,
371            output_amount,
372            output_asset,
373            None, // No blinding key for this example
374        );
375        pset.add_output(output);
376        pset
377    }
378
379    #[sdk_macros::test_all]
380    fn test_invalid_signer() {
381        let mut rng = rand::thread_rng();
382
383        assert!(SwSigner::new("", false).is_err());
384
385        assert!(SdkSigner::new("", "", false).is_err());
386
387        assert!(SdkSigner::new_with_seed(vec![], false).is_err());
388
389        let mut seed1 = [0u8; 31];
390        rng.fill_bytes(&mut seed1);
391        assert!(SdkSigner::new_with_seed(seed1.to_vec(), false).is_err());
392
393        let mut seed2 = [0u8; 32];
394        rng.fill_bytes(&mut seed2);
395        assert!(SdkSigner::new_with_seed(seed2.to_vec(), false).is_ok());
396    }
397
398    #[sdk_macros::test_all]
399    fn test_sign() {
400        let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
401        let (sw_signer, sdk_signer) = create_signers(mnemonic);
402
403        // Clone the PSET for each signer
404        let mut pset_sw = create_pset(&sw_signer);
405        let mut pset_sdk = create_pset(&sdk_signer);
406
407        // Sign with SwSigner
408        let sw_sig_count = sw_signer.sign(&mut pset_sw).unwrap();
409        assert_eq!(sw_sig_count, 1);
410
411        // Sign with SdkLwkSigner
412        let sdk_sig_count = sdk_signer.sign(&mut pset_sdk).unwrap();
413        assert_eq!(sdk_sig_count, 1);
414
415        // Compare the sign results
416        assert_eq!(pset_sw, pset_sdk);
417
418        // Extract and compare the final transactions
419        let tx_sw = pset_sw.extract_tx().unwrap();
420        let tx_sdk = pset_sdk.extract_tx().unwrap();
421        assert_eq!(tx_sw, tx_sdk);
422    }
423
424    #[sdk_macros::test_all]
425    fn test_slip77_master_blinding_key() {
426        let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
427        let (sw_signer, sdk_signer) = create_signers(mnemonic);
428
429        let sw_key = sw_signer.slip77_master_blinding_key().unwrap();
430        let sdk_key = sdk_signer.slip77_master_blinding_key().unwrap();
431
432        assert_eq!(
433            sw_key, sdk_key,
434            "SLIP77 master blinding keys should be identical"
435        );
436    }
437
438    #[sdk_macros::test_all]
439    fn test_derive_xpub() {
440        let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
441        let (sw_signer, sdk_signer) = create_signers(mnemonic);
442
443        let path = "m/84'/0'/0'/0/0".parse().unwrap();
444        let sw_xpub = sw_signer.derive_xpub(&path).unwrap();
445        let sdk_xpub = sdk_signer.derive_xpub(&path).unwrap();
446
447        assert_eq!(sw_xpub, sdk_xpub, "Derived xpubs should be identical");
448    }
449
450    #[sdk_macros::test_all]
451    fn test_identifier() {
452        let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
453        let (sw_signer, sdk_signer) = create_signers(mnemonic);
454
455        let sw_identifier = sw_signer.xpub().identifier();
456        let sdk_identifier = sdk_signer.xpub().unwrap().identifier();
457
458        assert_eq!(
459            sw_identifier, sdk_identifier,
460            "Identifiers should be identical"
461        );
462    }
463
464    #[sdk_macros::test_all]
465    fn test_fingerprint() {
466        let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
467        let (sw_signer, sdk_signer) = create_signers(mnemonic);
468
469        let sw_fingerprint = sw_signer.fingerprint();
470        let sdk_fingerprint = sdk_signer.fingerprint().unwrap();
471        let manual_finger_print = sdk_signer.xpub().unwrap().identifier()[0..4]
472            .try_into()
473            .unwrap();
474        assert_eq!(
475            sw_fingerprint, sdk_fingerprint,
476            "Fingerprints should be identical"
477        );
478
479        assert_eq!(
480            sw_fingerprint, manual_finger_print,
481            "Fingerprints should be identical"
482        );
483    }
484
485    #[sdk_macros::test_all]
486    fn test_sdk_signer_vs_sw_signer() {
487        // Use a test mnemonic (don't use this in production!)
488        let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
489        let network = Network::TestnetLiquid;
490
491        // 1. Create a wallet using SwSigner
492        let sw_signer = SwSigner::new(mnemonic, false).unwrap();
493        let sw_wallet = WolletBuilder::new(network, get_descriptor(&sw_signer).unwrap())
494            .with_updates_store(Arc::new(FakeStore::new()))
495            .build()
496            .unwrap();
497
498        // 2. Create a wallet using SdkLwkSigner
499        let sdk_signer: Box<dyn Signer> = Box::new(SdkSigner::new(mnemonic, "", false).unwrap());
500        let sdk_signer = SdkLwkSigner::new(Arc::new(sdk_signer)).unwrap();
501        let sdk_wallet = WolletBuilder::new(network, get_descriptor(&sdk_signer).unwrap())
502            .with_updates_store(Arc::new(FakeStore::new()))
503            .build()
504            .unwrap();
505
506        // Generate new addresses and compare
507        let sw_address = sw_wallet.address(None).unwrap();
508        let sdk_address = sdk_wallet.address(None).unwrap();
509
510        assert_eq!(
511            sw_address.address().to_string(),
512            sdk_address.address().to_string(),
513            "Addresses should be identical"
514        );
515    }
516}