breez_sdk_liquid/
signer.rs

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