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>(
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 let mut pset = PartiallySignedTransaction::new_v2();
328
329 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 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, );
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 let mut pset_sw = create_pset(&sw_signer);
394 let mut pset_sdk = create_pset(&sdk_signer);
395
396 let sw_sig_count = sw_signer.sign(&mut pset_sw).unwrap();
398 assert_eq!(sw_sig_count, 1);
399
400 let sdk_sig_count = sdk_signer.sign(&mut pset_sdk).unwrap();
402 assert_eq!(sdk_sig_count, 1);
403
404 assert_eq!(pset_sw, pset_sdk);
406
407 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 let mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
478 let network = ElementsNetwork::LiquidTestnet;
479
480 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 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 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}