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
173pub struct SdkSigner {
174    xprv: Xpriv,
175    secp: Secp256k1<All>, // could be sign only, but it is likely the caller already has the All context.
176    seed: Vec<u8>,
177    network: Network,
178}
179
180impl SdkSigner {
181    pub fn new(mnemonic: &str, passphrase: &str, is_mainnet: bool) -> Result<Self, NewError> {
182        let mnemonic: Mnemonic = mnemonic.parse()?;
183        let seed = mnemonic.to_seed(passphrase).to_vec();
184
185        Self::new_with_seed(seed, is_mainnet)
186    }
187
188    pub fn new_with_seed(seed: Vec<u8>, is_mainnet: bool) -> Result<Self, NewError> {
189        if seed.len() < 32 {
190            return Err(NewError::Seed(anyhow!("Seed must be at least 32 bytes")));
191        }
192
193        let secp = Secp256k1::new();
194        let network = if is_mainnet {
195            bitcoin::Network::Bitcoin
196        } else {
197            bitcoin::Network::Testnet
198        };
199
200        let xprv = Xpriv::new_master(network, &seed)?;
201
202        Ok(Self {
203            xprv,
204            secp,
205            seed,
206            network,
207        })
208    }
209}
210
211impl Signer for SdkSigner {
212    fn xpub(&self) -> Result<Vec<u8>, SignerError> {
213        Ok(Xpub::from_priv(&self.secp, &self.xprv).encode().to_vec())
214    }
215
216    fn derive_xpub(&self, derivation_path: String) -> Result<Vec<u8>, SignerError> {
217        let der: DerivationPath = derivation_path.parse()?;
218        let derived = self.xprv.derive_priv(&self.secp, &der)?;
219        Ok(Xpub::from_priv(&self.secp, &derived).encode().to_vec())
220    }
221
222    fn sign_ecdsa(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError> {
223        let der: DerivationPath = derivation_path.parse()?;
224        let ext_derived = self.xprv.derive_priv(&self.secp, &der)?;
225        let sig = self.secp.sign_ecdsa_low_r(
226            &Message::from_digest(
227                msg.try_into()
228                    .map_err(|_| anyhow::anyhow!("failed to sign"))?,
229            ),
230            &ext_derived.private_key,
231        );
232        Ok(sig.serialize_der().to_vec())
233    }
234
235    fn slip77_master_blinding_key(&self) -> Result<Vec<u8>, SignerError> {
236        let master_blinding_key = MasterBlindingKey::from_seed(&self.seed);
237        Ok(master_blinding_key.as_bytes().to_vec())
238    }
239
240    fn sign_ecdsa_recoverable(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError> {
241        let secp = Secp256k1::new();
242        let keypair = Xpriv::new_master(self.network, &self.seed)
243            .map_err(|e| anyhow::anyhow!("Could not get signer keypair: {e}"))?
244            .to_keypair(&secp);
245        let s = msg.as_slice();
246
247        let msg: Message = Message::from_digest_slice(s)
248            .map_err(|e| SignerError::Generic { err: e.to_string() })?;
249        // Get message signature and encode to zbase32
250        let recoverable_sig = secp.sign_ecdsa_recoverable(&msg, &keypair.secret_key());
251        let (recovery_id, sig) = recoverable_sig.serialize_compact();
252        let mut complete_signature = vec![31 + recovery_id.to_i32() as u8];
253        complete_signature.extend_from_slice(&sig);
254        Ok(complete_signature)
255    }
256
257    fn hmac_sha256(&self, msg: Vec<u8>, derivation_path: String) -> Result<Vec<u8>, SignerError> {
258        let der: DerivationPath = derivation_path.parse()?;
259        let priv_key = self.xprv.derive_priv(&self.secp, &der)?;
260        let mut engine = HmacEngine::<sha256::Hash>::new(priv_key.to_priv().to_bytes().as_slice());
261
262        engine.input(msg.as_slice());
263        Ok(Hmac::<sha256::Hash>::from_engine(engine)
264            .as_byte_array()
265            .to_vec())
266    }
267
268    fn ecies_encrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError> {
269        let keypair = self.xprv.to_keypair(&self.secp);
270        let rc_pub = keypair.public_key().to_public_key().to_bytes();
271        ecies::encrypt(&rc_pub, &msg).map_err(|err| SignerError::Generic {
272            err: format!("Could not encrypt data: {err}"),
273        })
274    }
275
276    fn ecies_decrypt(&self, msg: Vec<u8>) -> Result<Vec<u8>, SignerError> {
277        let rc_prv = self.xprv.to_priv().to_bytes();
278        ecies::decrypt(&rc_prv, &msg).map_err(|err| SignerError::Generic {
279            err: format!("Could not decrypt data: {err}"),
280        })
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287    use bip32::KeySource;
288    use bip39::rand::{self, RngCore};
289    use bitcoin::PublicKey;
290    use elements::{
291        pset::{Input, Output, PartiallySignedTransaction},
292        AssetId, TxOut, Txid,
293    };
294    use lwk_common::{singlesig_desc, Singlesig};
295    use lwk_signer::SwSigner;
296    use lwk_wollet::{
297        elements::{self, Script},
298        ElementsNetwork, NoPersist, Wollet, WolletDescriptor,
299    };
300    use std::collections::BTreeMap;
301
302    #[cfg(feature = "browser-tests")]
303    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
304
305    fn get_descriptor<S: LwkSigner>(signer: &S) -> Result<WolletDescriptor, anyhow::Error> {
306        let descriptor_str = singlesig_desc(
307            signer,
308            Singlesig::Wpkh,
309            lwk_common::DescriptorBlindingKey::Slip77,
310        )
311        .map_err(|e| anyhow::anyhow!("Invalid descriptor: {e}"))?;
312        Ok(descriptor_str.parse()?)
313    }
314
315    fn create_signers(mnemonic: &str) -> (SwSigner, SdkLwkSigner) {
316        let sw_signer = SwSigner::new(mnemonic, false).unwrap();
317        let sdk_signer: Box<dyn Signer> = Box::new(SdkSigner::new(mnemonic, "", false).unwrap());
318        let sdk_signer = SdkLwkSigner::new(Arc::new(sdk_signer)).unwrap();
319        (sw_signer, sdk_signer)
320    }
321
322    fn create_pset<S: LwkSigner>(signer: &S) -> PartiallySignedTransaction {
323        // Create a PartiallySignedTransaction
324        let mut pset = PartiallySignedTransaction::new_v2();
325
326        // Add a dummy input
327        let prev_txid = Txid::from_slice(&[0; 32]).unwrap();
328        let prev_vout = 0;
329
330        let derivation_path: DerivationPath = "m/84'/0'/0'/0/0".parse().unwrap();
331        let xpub = signer.derive_xpub(&derivation_path).unwrap();
332        let mut bip32_derivation_map: BTreeMap<PublicKey, KeySource> = BTreeMap::new();
333        bip32_derivation_map.insert(
334            xpub.public_key.into(),
335            (signer.fingerprint().unwrap(), derivation_path),
336        );
337        let input = Input {
338            non_witness_utxo: None,
339            witness_utxo: Some(TxOut::new_fee(
340                100_000_000,
341                AssetId::from_slice(&[1; 32]).unwrap(),
342            )),
343            previous_txid: prev_txid,
344            previous_output_index: prev_vout,
345            bip32_derivation: bip32_derivation_map,
346            ..Default::default()
347        };
348
349        pset.add_input(input);
350
351        // Add a dummy output using new_explicit
352        let output_script = Script::new();
353        let output_amount = 99_000_000;
354        let output_asset = AssetId::from_slice(&[1; 32]).unwrap();
355        let output = Output::new_explicit(
356            output_script,
357            output_amount,
358            output_asset,
359            None, // No blinding key for this example
360        );
361        pset.add_output(output);
362        pset
363    }
364
365    #[sdk_macros::test_all]
366    fn test_invalid_signer() {
367        let mut rng = rand::thread_rng();
368
369        assert!(SwSigner::new("", false).is_err());
370
371        assert!(SdkSigner::new("", "", false).is_err());
372
373        assert!(SdkSigner::new_with_seed(vec![], false).is_err());
374
375        let mut seed1 = [0u8; 31];
376        rng.fill_bytes(&mut seed1);
377        assert!(SdkSigner::new_with_seed(seed1.to_vec(), false).is_err());
378
379        let mut seed2 = [0u8; 32];
380        rng.fill_bytes(&mut seed2);
381        assert!(SdkSigner::new_with_seed(seed2.to_vec(), false).is_ok());
382    }
383
384    #[sdk_macros::test_all]
385    fn test_sign() {
386        let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
387        let (sw_signer, sdk_signer) = create_signers(mnemonic);
388
389        // Clone the PSET for each signer
390        let mut pset_sw = create_pset(&sw_signer);
391        let mut pset_sdk = create_pset(&sdk_signer);
392
393        // Sign with SwSigner
394        let sw_sig_count = sw_signer.sign(&mut pset_sw).unwrap();
395        assert_eq!(sw_sig_count, 1);
396
397        // Sign with SdkLwkSigner
398        let sdk_sig_count = sdk_signer.sign(&mut pset_sdk).unwrap();
399        assert_eq!(sdk_sig_count, 1);
400
401        // Compare the sign results
402        assert_eq!(pset_sw, pset_sdk);
403
404        // Extract and compare the final transactions
405        let tx_sw = pset_sw.extract_tx().unwrap();
406        let tx_sdk = pset_sdk.extract_tx().unwrap();
407        assert_eq!(tx_sw, tx_sdk);
408    }
409
410    #[sdk_macros::test_all]
411    fn test_slip77_master_blinding_key() {
412        let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
413        let (sw_signer, sdk_signer) = create_signers(mnemonic);
414
415        let sw_key = sw_signer.slip77_master_blinding_key().unwrap();
416        let sdk_key = sdk_signer.slip77_master_blinding_key().unwrap();
417
418        assert_eq!(
419            sw_key, sdk_key,
420            "SLIP77 master blinding keys should be identical"
421        );
422    }
423
424    #[sdk_macros::test_all]
425    fn test_derive_xpub() {
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 path = "m/84'/0'/0'/0/0".parse().unwrap();
430        let sw_xpub = sw_signer.derive_xpub(&path).unwrap();
431        let sdk_xpub = sdk_signer.derive_xpub(&path).unwrap();
432
433        assert_eq!(sw_xpub, sdk_xpub, "Derived xpubs should be identical");
434    }
435
436    #[sdk_macros::test_all]
437    fn test_identifier() {
438        let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
439        let (sw_signer, sdk_signer) = create_signers(mnemonic);
440
441        let sw_identifier = sw_signer.xpub().identifier();
442        let sdk_identifier = sdk_signer.xpub().unwrap().identifier();
443
444        assert_eq!(
445            sw_identifier, sdk_identifier,
446            "Identifiers should be identical"
447        );
448    }
449
450    #[sdk_macros::test_all]
451    fn test_fingerprint() {
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_fingerprint = sw_signer.fingerprint();
456        let sdk_fingerprint = sdk_signer.fingerprint().unwrap();
457        let manual_finger_print = sdk_signer.xpub().unwrap().identifier()[0..4]
458            .try_into()
459            .unwrap();
460        assert_eq!(
461            sw_fingerprint, sdk_fingerprint,
462            "Fingerprints should be identical"
463        );
464
465        assert_eq!(
466            sw_fingerprint, manual_finger_print,
467            "Fingerprints should be identical"
468        );
469    }
470
471    #[sdk_macros::test_all]
472    fn test_sdk_signer_vs_sw_signer() {
473        // Use a test mnemonic (don't use this in production!)
474        let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
475        let network = ElementsNetwork::LiquidTestnet;
476
477        // 1. Create a wallet using SwSigner
478        let sw_signer = SwSigner::new(mnemonic, false).unwrap();
479        let sw_wallet = Wollet::new(
480            network,
481            NoPersist::new(),
482            get_descriptor(&sw_signer).unwrap(),
483        )
484        .unwrap();
485
486        // 2. Create a wallet using SdkLwkSigner
487        let sdk_signer: Box<dyn Signer> = Box::new(SdkSigner::new(mnemonic, "", false).unwrap());
488        let sdk_signer = SdkLwkSigner::new(Arc::new(sdk_signer)).unwrap();
489        let sdk_wallet = Wollet::new(
490            network,
491            NoPersist::new(),
492            get_descriptor(&sdk_signer).unwrap(),
493        )
494        .unwrap();
495
496        // Generate new addresses and compare
497        let sw_address = sw_wallet.address(None).unwrap();
498        let sdk_address = sdk_wallet.address(None).unwrap();
499
500        assert_eq!(
501            sw_address.address().to_string(),
502            sdk_address.address().to_string(),
503            "Addresses should be identical"
504        );
505    }
506}