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#[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
65pub struct SdkLwkSigner {
67 sdk_signer: Arc<Box<dyn Signer>>,
68}
69
70impl SdkLwkSigner {
71 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 let genesis_hash = elements_miniscript::elements::BlockHash::all_zeros();
109 let mut messages = vec![];
110 for i in 0..pset.inputs().len() {
111 let msg = pset
114 .sighash_msg(i, &mut sighash_cache, None, genesis_hash)?
115 .to_secp_msg();
116 messages.push(msg);
117 }
118
119 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 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>, 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 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>(signer: &S) -> Result<WolletDescriptor, anyhow::Error> {
305 let descriptor_str = singlesig_desc(
306 signer,
307 Singlesig::Wpkh,
308 lwk_common::DescriptorBlindingKey::Slip77,
309 )
310 .map_err(|e| anyhow::anyhow!("Invalid descriptor: {e}"))?;
311 Ok(descriptor_str.parse()?)
312 }
313
314 fn create_signers(mnemonic: &str) -> (SwSigner, SdkLwkSigner) {
315 let sw_signer = SwSigner::new(mnemonic, false).unwrap();
316 let sdk_signer: Box<dyn Signer> = Box::new(SdkSigner::new(mnemonic, "", false).unwrap());
317 let sdk_signer = SdkLwkSigner::new(Arc::new(sdk_signer)).unwrap();
318 (sw_signer, sdk_signer)
319 }
320
321 fn create_pset<S: LwkSigner>(signer: &S) -> PartiallySignedTransaction {
322 let mut pset = PartiallySignedTransaction::new_v2();
324
325 let prev_txid = Txid::from_slice(&[0; 32]).unwrap();
327 let prev_vout = 0;
328
329 let derivation_path: DerivationPath = "m/84'/0'/0'/0/0".parse().unwrap();
330 let xpub = signer.derive_xpub(&derivation_path).unwrap();
331 let mut bip32_derivation_map: BTreeMap<PublicKey, KeySource> = BTreeMap::new();
332 bip32_derivation_map.insert(
333 xpub.public_key.into(),
334 (signer.fingerprint().unwrap(), derivation_path),
335 );
336 let input = Input {
337 non_witness_utxo: None,
338 witness_utxo: Some(TxOut::new_fee(
339 100_000_000,
340 AssetId::from_slice(&[1; 32]).unwrap(),
341 )),
342 previous_txid: prev_txid,
343 previous_output_index: prev_vout,
344 bip32_derivation: bip32_derivation_map,
345 ..Default::default()
346 };
347
348 pset.add_input(input);
349
350 let output_script = Script::new();
352 let output_amount = 99_000_000;
353 let output_asset = AssetId::from_slice(&[1; 32]).unwrap();
354 let output = Output::new_explicit(
355 output_script,
356 output_amount,
357 output_asset,
358 None, );
360 pset.add_output(output);
361 pset
362 }
363
364 #[sdk_macros::test_all]
365 fn test_invalid_signer() {
366 let mut rng = rand::thread_rng();
367
368 assert!(SwSigner::new("", false).is_err());
369
370 assert!(SdkSigner::new("", "", false).is_err());
371
372 assert!(SdkSigner::new_with_seed(vec![], false).is_err());
373
374 let mut seed1 = [0u8; 31];
375 rng.fill_bytes(&mut seed1);
376 assert!(SdkSigner::new_with_seed(seed1.to_vec(), false).is_err());
377
378 let mut seed2 = [0u8; 32];
379 rng.fill_bytes(&mut seed2);
380 assert!(SdkSigner::new_with_seed(seed2.to_vec(), false).is_ok());
381 }
382
383 #[sdk_macros::test_all]
384 fn test_sign() {
385 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
386 let (sw_signer, sdk_signer) = create_signers(mnemonic);
387
388 let mut pset_sw = create_pset(&sw_signer);
390 let mut pset_sdk = create_pset(&sdk_signer);
391
392 let sw_sig_count = sw_signer.sign(&mut pset_sw).unwrap();
394 assert_eq!(sw_sig_count, 1);
395
396 let sdk_sig_count = sdk_signer.sign(&mut pset_sdk).unwrap();
398 assert_eq!(sdk_sig_count, 1);
399
400 assert_eq!(pset_sw, pset_sdk);
402
403 let tx_sw = pset_sw.extract_tx().unwrap();
405 let tx_sdk = pset_sdk.extract_tx().unwrap();
406 assert_eq!(tx_sw, tx_sdk);
407 }
408
409 #[sdk_macros::test_all]
410 fn test_slip77_master_blinding_key() {
411 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
412 let (sw_signer, sdk_signer) = create_signers(mnemonic);
413
414 let sw_key = sw_signer.slip77_master_blinding_key().unwrap();
415 let sdk_key = sdk_signer.slip77_master_blinding_key().unwrap();
416
417 assert_eq!(
418 sw_key, sdk_key,
419 "SLIP77 master blinding keys should be identical"
420 );
421 }
422
423 #[sdk_macros::test_all]
424 fn test_derive_xpub() {
425 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
426 let (sw_signer, sdk_signer) = create_signers(mnemonic);
427
428 let path = "m/84'/0'/0'/0/0".parse().unwrap();
429 let sw_xpub = sw_signer.derive_xpub(&path).unwrap();
430 let sdk_xpub = sdk_signer.derive_xpub(&path).unwrap();
431
432 assert_eq!(sw_xpub, sdk_xpub, "Derived xpubs should be identical");
433 }
434
435 #[sdk_macros::test_all]
436 fn test_identifier() {
437 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
438 let (sw_signer, sdk_signer) = create_signers(mnemonic);
439
440 let sw_identifier = sw_signer.xpub().identifier();
441 let sdk_identifier = sdk_signer.xpub().unwrap().identifier();
442
443 assert_eq!(
444 sw_identifier, sdk_identifier,
445 "Identifiers should be identical"
446 );
447 }
448
449 #[sdk_macros::test_all]
450 fn test_fingerprint() {
451 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
452 let (sw_signer, sdk_signer) = create_signers(mnemonic);
453
454 let sw_fingerprint = sw_signer.fingerprint();
455 let sdk_fingerprint = sdk_signer.fingerprint().unwrap();
456 let manual_finger_print = sdk_signer.xpub().unwrap().identifier()[0..4]
457 .try_into()
458 .unwrap();
459 assert_eq!(
460 sw_fingerprint, sdk_fingerprint,
461 "Fingerprints should be identical"
462 );
463
464 assert_eq!(
465 sw_fingerprint, manual_finger_print,
466 "Fingerprints should be identical"
467 );
468 }
469
470 #[sdk_macros::test_all]
471 fn test_sdk_signer_vs_sw_signer() {
472 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
474 let network = ElementsNetwork::LiquidTestnet;
475
476 let sw_signer = SwSigner::new(mnemonic, false).unwrap();
478 let sw_wallet = Wollet::new(
479 network,
480 NoPersist::new(),
481 get_descriptor(&sw_signer).unwrap(),
482 )
483 .unwrap();
484
485 let sdk_signer: Box<dyn Signer> = Box::new(SdkSigner::new(mnemonic, "", false).unwrap());
487 let sdk_signer = SdkLwkSigner::new(Arc::new(sdk_signer)).unwrap();
488 let sdk_wallet = Wollet::new(
489 network,
490 NoPersist::new(),
491 get_descriptor(&sdk_signer).unwrap(),
492 )
493 .unwrap();
494
495 let sw_address = sw_wallet.address(None).unwrap();
497 let sdk_address = sdk_wallet.address(None).unwrap();
498
499 assert_eq!(
500 sw_address.address().to_string(),
501 sdk_address.address().to_string(),
502 "Addresses should be identical"
503 );
504 }
505}