1use crate::network::Chain;
2use crate::swaps::bitcoin::BtcSwapScript;
3use crate::swaps::liquid::LBtcSwapScript;
4use crate::util::error::{ErrorKind, S5Error};
5use bip39::Mnemonic;
6use bitcoin::bip32::{DerivationPath, Fingerprint, Xpriv};
7use bitcoin::secp256k1::{Keypair, Secp256k1};
8use elements::secp256k1_zkp::{Keypair as ZKKeyPair, Secp256k1 as ZKSecp256k1};
9
10use bitcoin::secp256k1::hashes::{hash160, ripemd160, sha256, Hash};
11use lightning_invoice::Bolt11Invoice;
12
13use bitcoin::secp256k1::rand::rngs::OsRng;
14use rand_core::RngCore;
15
16use serde::{Deserialize, Serialize};
17use std::fmt::Display;
18use std::fmt::Formatter;
19use std::str::FromStr;
20
21const SUBMARINE_SWAP_ACCOUNT: u32 = 21;
22const REVERSE_SWAP_ACCOUNT: u32 = 42;
23
24const BITCOIN_NETWORK_PATH: u32 = 0;
25const LIQUID_NETWORK_PATH: u32 = 1776;
26const TESTNET_NETWORK_PATH: u32 = 1;
27
28#[derive(Serialize, Deserialize, Debug, Clone)]
32pub struct SwapKey {
33 pub fingerprint: Fingerprint,
34 pub path: DerivationPath,
35 pub keypair: Keypair,
36}
37impl SwapKey {
38 pub fn from_submarine_account(
41 mnemonic: &str,
42 passphrase: &str,
43 network: Chain,
44 index: u64,
45 ) -> Result<SwapKey, S5Error> {
46 let secp = Secp256k1::new();
47 let mnemonic_struct = Mnemonic::from_str(&mnemonic).unwrap();
48 let seed = mnemonic_struct.to_seed(passphrase);
49 let root = match Xpriv::new_master(bitcoin::Network::Testnet, &seed) {
50 Ok(xprv) => xprv,
51 Err(_) => return Err(S5Error::new(ErrorKind::Key, "Invalid Master Key.")),
52 };
53 let fingerprint = root.fingerprint(&secp);
54 let purpose = DerivationPurpose::Compatible;
55 let network_path = match network {
56 Chain::Bitcoin => BITCOIN_NETWORK_PATH,
57 Chain::Liquid => LIQUID_NETWORK_PATH,
58 _ => TESTNET_NETWORK_PATH,
59 };
60 let derivation_path = format!(
61 "m/{}h/{}h/{}h/0/{}",
62 purpose.to_string(),
63 network_path,
64 SUBMARINE_SWAP_ACCOUNT,
65 index
66 );
67 let path = match DerivationPath::from_str(&derivation_path) {
68 Ok(hdpath) => hdpath,
69 Err(_) => {
70 return Err(S5Error::new(
71 ErrorKind::Key,
72 "Invalid purpose or account in derivation path.",
73 ))
74 }
75 };
76 let child_xprv = match root.derive_priv(&secp, &path) {
77 Ok(xprv) => xprv,
78 Err(e) => return Err(S5Error::new(ErrorKind::Key, &e.to_string())),
79 };
80
81 let key_pair = match Keypair::from_seckey_str(
82 &secp,
83 &hex::encode(child_xprv.private_key.secret_bytes()),
84 ) {
85 Ok(kp) => kp,
86 Err(_) => return Err(S5Error::new(ErrorKind::Key, "BAD SECKEY STRING")),
87 };
88
89 Ok(SwapKey {
90 fingerprint: fingerprint,
91 path: path,
92 keypair: key_pair,
93 })
94 }
95 pub fn from_reverse_account(
98 mnemonic: &str,
99 passphrase: &str,
100 network: Chain,
101 index: u64,
102 ) -> Result<SwapKey, S5Error> {
103 let secp = Secp256k1::new();
104 let mnemonic_struct = Mnemonic::from_str(mnemonic).unwrap();
105 let seed = mnemonic_struct.to_seed(passphrase);
106 let root = match Xpriv::new_master(bitcoin::Network::Testnet, &seed) {
107 Ok(xprv) => xprv,
108 Err(_) => return Err(S5Error::new(ErrorKind::Key, "Invalid Master Key.")),
109 };
110 let fingerprint = root.fingerprint(&secp);
111 let purpose = DerivationPurpose::Native;
112 let network_path = match network {
113 Chain::Bitcoin => BITCOIN_NETWORK_PATH,
114 Chain::Liquid => LIQUID_NETWORK_PATH,
115 _ => TESTNET_NETWORK_PATH,
116 };
117 let derivation_path = format!(
119 "m/{}h/{}h/{}h/0/{}",
120 purpose, network_path, REVERSE_SWAP_ACCOUNT, index
121 );
122 let path = match DerivationPath::from_str(&derivation_path) {
123 Ok(hdpath) => hdpath,
124 Err(_) => {
125 return Err(S5Error::new(
126 ErrorKind::Key,
127 "Invalid purpose or account in derivation path.",
128 ))
129 }
130 };
131 let child_xprv = match root.derive_priv(&secp, &path) {
132 Ok(xprv) => xprv,
133 Err(e) => return Err(S5Error::new(ErrorKind::Key, &e.to_string())),
134 };
135
136 let key_pair = match Keypair::from_seckey_str(
137 &secp,
138 &hex::encode(child_xprv.private_key.secret_bytes()),
139 ) {
140 Ok(kp) => kp,
141 Err(_) => return Err(S5Error::new(ErrorKind::Key, "BAD SECKEY STRING")),
142 };
143
144 Ok(SwapKey {
145 fingerprint: fingerprint,
146 path: path,
147 keypair: key_pair,
148 })
149 }
150}
151#[derive(Clone)]
152
153#[derive(Serialize, Deserialize, Debug)]
158pub struct LiquidSwapKey {
159 pub fingerprint: Fingerprint,
160 pub path: DerivationPath,
161 pub keypair: ZKKeyPair,
162}
163impl From<SwapKey> for LiquidSwapKey {
164 fn from(swapkey: SwapKey) -> Self {
165 let secp = ZKSecp256k1::new();
166 let liquid_keypair =
167 ZKKeyPair::from_seckey_str(&secp, &swapkey.keypair.display_secret().to_string())
168 .unwrap();
169
170 LiquidSwapKey {
171 fingerprint: swapkey.fingerprint,
172 path: swapkey.path,
173 keypair: liquid_keypair,
174 }
175 }
176}
177enum DerivationPurpose {
178 _Legacy,
179 Compatible,
180 Native,
181 _Taproot,
182}
183impl Display for DerivationPurpose {
184 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
185 match self {
186 DerivationPurpose::_Legacy => write!(f, "44"),
187 DerivationPurpose::Compatible => write!(f, "49"),
188 DerivationPurpose::Native => write!(f, "84"),
189 DerivationPurpose::_Taproot => write!(f, "86"),
190 }
191 }
192}
193
194fn rng_32b() -> [u8; 32] {
196 let mut bytes = [0u8; 32];
197 OsRng.fill_bytes(&mut bytes);
198 bytes
199}
200
201#[derive(Debug, Clone)]
203pub struct Preimage {
204 pub bytes: Option<[u8; 32]>,
205 pub sha256: sha256::Hash,
206 pub hash160: hash160::Hash,
207}
208
209impl Preimage {
210 pub fn new() -> Preimage {
212 let preimage = rng_32b();
213 let sha256 = sha256::Hash::hash(&preimage);
214 let hash160 = hash160::Hash::hash(&preimage);
215
216 Preimage {
217 bytes: Some(preimage),
218 sha256: sha256,
219 hash160: hash160,
220 }
221 }
222
223 pub fn from_str(preimage: &str) -> Result<Preimage, S5Error> {
225 let decoded = match hex::decode(preimage) {
226 Ok(decoded) => decoded,
227 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
228 };
229 let preimage_bytes: [u8; 32] = match decoded.try_into() {
231 Ok(bytes) => bytes,
232 Err(_) => {
233 return Err(S5Error::new(
234 ErrorKind::Input,
235 "Decoded Preimage input is not 32 bytes",
236 ))
237 }
238 };
239 let sha256 = sha256::Hash::hash(&preimage_bytes);
240 let hash160 = hash160::Hash::hash(&preimage_bytes);
241 Ok(Preimage {
242 bytes: Some(preimage_bytes),
243 sha256: sha256,
244 hash160: hash160,
245 })
246 }
247
248 pub fn from_sha256_str(preimage_sha256: &str) -> Result<Preimage, S5Error> {
251 let sha256 = match sha256::Hash::from_str(preimage_sha256) {
252 Ok(result) => result,
253 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
254 };
255 let hash160 = hash160::Hash::from_slice(
256 ripemd160::Hash::hash(sha256.as_byte_array()).as_byte_array(),
257 )
258 .unwrap();
259 Ok(Preimage {
261 bytes: None,
262 sha256: sha256,
263 hash160: hash160,
264 })
265 }
266
267 pub fn from_invoice_str(invoice_str: &str) -> Result<Preimage, S5Error> {
270 let invoice = match Bolt11Invoice::from_str(&invoice_str) {
271 Ok(invoice) => invoice,
272 Err(e) => {
273 println!("{:?}", e);
274 return Err(S5Error::new(
275 ErrorKind::Input,
276 "Could not parse invoice string.",
277 ));
278 }
279 };
280 Ok(Preimage::from_sha256_str(
281 &invoice.payment_hash().to_string(),
282 )?)
283 }
284
285 pub fn to_string(&self) -> Option<String> {
287 match self.bytes {
288 Some(result) => Some(hex::encode(result)),
289 None => None,
290 }
291 }
292}
293use serde_json;
294use std::fs::File;
295use std::io::{Read, Write};
296use std::path::{Path, PathBuf};
297
298#[derive(Serialize, Deserialize, Debug, Clone)]
300pub struct RefundSwapFile {
301 pub id: String,
302 pub currency: String,
303 pub redeem_script: String,
304 pub private_key: String,
305 pub timeout_block_height: u64,
306}
307impl RefundSwapFile {
308 pub fn file_name(&self) -> String {
309 format!("boltz-{}.json", self.id)
310 }
311 pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), S5Error> {
312 let mut full_path = PathBuf::from(path.as_ref());
313 full_path.push(self.file_name());
314 let mut file = match File::create(&full_path) {
315 Ok(f) => f,
316 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
317 };
318 let json = match serde_json::to_string_pretty(self) {
319 Ok(j) => j,
320 Err(e) => return Err(S5Error::new(ErrorKind::Script, &e.to_string())),
321 };
322 if let Err(e) = writeln!(file, "{}", json) {
323 return Err(S5Error::new(ErrorKind::Input, &e.to_string()));
324 }
325 Ok(())
326 }
327 pub fn read_from_file<P: AsRef<Path>>(path: P) -> Result<Self, S5Error> {
328 let mut file = match File::open(path) {
329 Ok(f) => f,
330 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
331 };
332 let mut contents = String::new();
333 if let Err(e) = file.read_to_string(&mut contents) {
334 return Err(S5Error::new(ErrorKind::Input, &e.to_string()));
335 }
336 match serde_json::from_str(&contents) {
337 Ok(refund_swap_file) => Ok(refund_swap_file),
338 Err(e) => Err(S5Error::new(ErrorKind::Script, &e.to_string())),
339 }
340 }
341}
342
343#[derive(Serialize, Deserialize, Debug, Clone)]
345pub struct BtcSubmarineRecovery {
346 pub id: String,
347 pub refund_key: String,
348 pub redeem_script: String,
349}
350impl BtcSubmarineRecovery {
351 pub fn new(id: &str, refund_key: &Keypair, redeem_script: &str) -> Self {
352 BtcSubmarineRecovery {
353 id: id.to_string(),
354 refund_key: refund_key.display_secret().to_string(),
355 redeem_script: redeem_script.to_string(),
356 }
357 }
358}
359impl Into<RefundSwapFile> for BtcSubmarineRecovery {
360 fn into(self) -> RefundSwapFile {
361 let script = BtcSwapScript::submarine_from_str(&self.redeem_script).unwrap();
362 RefundSwapFile {
363 id: self.id,
364 currency: "BTC".to_string(),
365 redeem_script: self.redeem_script,
366 private_key: self.refund_key,
367 timeout_block_height: script.timelock as u64,
368 }
369 }
370}
371
372impl TryInto<BtcSwapScript> for &BtcSubmarineRecovery {
373 type Error = S5Error;
374 fn try_into(self) -> Result<BtcSwapScript, Self::Error> {
375 Ok(BtcSwapScript::submarine_from_str(&self.redeem_script).unwrap())
376 }
377}
378
379impl TryInto<Keypair> for &BtcSubmarineRecovery {
380 type Error = S5Error;
381 fn try_into(self) -> Result<Keypair, Self::Error> {
382 let secp = Secp256k1::new();
383 Ok(Keypair::from_seckey_str(&secp, &self.refund_key).unwrap())
384 }
385}
386
387#[derive(Serialize, Deserialize, Debug, Clone)]
389pub struct BtcReverseRecovery {
390 pub id: String,
391 pub preimage: String,
392 pub claim_key: String,
393 pub redeem_script: String,
394}
395impl BtcReverseRecovery {
396 pub fn new(id: &str, preimage: &Preimage, claim_key: &Keypair, redeem_script: &str) -> Self {
397 BtcReverseRecovery {
398 id: id.to_string(),
399 claim_key: claim_key.display_secret().to_string(),
400 preimage: preimage.to_string().unwrap(),
401 redeem_script: redeem_script.to_string(),
402 }
403 }
404}
405impl TryInto<BtcSwapScript> for &BtcReverseRecovery {
406 type Error = S5Error;
407 fn try_into(self) -> Result<BtcSwapScript, Self::Error> {
408 Ok(BtcSwapScript::reverse_from_str(&self.redeem_script).unwrap())
409 }
410}
411
412impl TryInto<Keypair> for &BtcReverseRecovery {
413 type Error = S5Error;
414 fn try_into(self) -> Result<Keypair, Self::Error> {
415 let secp = Secp256k1::new();
416 Ok(Keypair::from_seckey_str(&secp, &self.claim_key).unwrap())
417 }
418}
419impl TryInto<Preimage> for &BtcReverseRecovery {
420 type Error = S5Error;
421 fn try_into(self) -> Result<Preimage, Self::Error> {
422 Ok(Preimage::from_str(&self.preimage).unwrap())
423 }
424}
425
426#[derive(Serialize, Deserialize, Debug, Clone)]
428pub struct LBtcSubmarineRecovery {
429 pub id: String,
430 pub refund_key: String,
431 pub blinding_key: String,
432 pub redeem_script: String,
433}
434impl LBtcSubmarineRecovery {
435 pub fn new(
436 id: &str,
437 refund_key: &Keypair,
438 blinding_key: &ZKKeyPair,
439 redeem_script: &str,
440 ) -> Self {
441 LBtcSubmarineRecovery {
442 id: id.to_string(),
443 refund_key: refund_key.display_secret().to_string(),
444 redeem_script: redeem_script.to_string(),
445 blinding_key: blinding_key.display_secret().to_string(),
446 }
447 }
448}
449impl Into<RefundSwapFile> for LBtcSubmarineRecovery {
450 fn into(self) -> RefundSwapFile {
451 let script =
452 LBtcSwapScript::submarine_from_str(&self.redeem_script, &self.blinding_key).unwrap();
453 RefundSwapFile {
454 id: self.id,
455 currency: "L-BTC".to_string(),
456 redeem_script: self.redeem_script,
457 private_key: self.refund_key,
458 timeout_block_height: script.timelock as u64,
459 }
460 }
461}
462#[derive(Serialize, Deserialize, Debug, Clone)]
464pub struct LBtcReverseRecovery {
465 pub id: String,
466 pub preimage: String,
467 pub claim_key: String,
468 pub blinding_key: String,
469 pub redeem_script: String,
470}
471impl LBtcReverseRecovery {
472 pub fn new(
473 id: &str,
474 preimage: &Preimage,
475 claim_key: &Keypair,
476 blinding_key: &ZKKeyPair,
477 redeem_script: &str,
478 ) -> Self {
479 LBtcReverseRecovery {
480 id: id.to_string(),
481 claim_key: claim_key.display_secret().to_string(),
482 blinding_key: blinding_key.display_secret().to_string(),
483 preimage: preimage.to_string().unwrap(),
484 redeem_script: redeem_script.to_string(),
485 }
486 }
487}
488impl TryInto<LBtcSwapScript> for &LBtcReverseRecovery {
489 type Error = S5Error;
490 fn try_into(self) -> Result<LBtcSwapScript, Self::Error> {
491 Ok(LBtcSwapScript::reverse_from_str(&self.redeem_script, &self.blinding_key).unwrap())
492 }
493}
494
495impl TryInto<Keypair> for &LBtcReverseRecovery {
496 type Error = S5Error;
497 fn try_into(self) -> Result<Keypair, Self::Error> {
498 let secp = Secp256k1::new();
499 Ok(Keypair::from_seckey_str(&secp, &self.claim_key).unwrap())
500 }
501}
502impl TryInto<Preimage> for &LBtcReverseRecovery {
503 type Error = S5Error;
504 fn try_into(self) -> Result<Preimage, Self::Error> {
505 Ok(Preimage::from_str(&self.preimage).unwrap())
506 }
507}
508#[cfg(test)]
509mod tests {
510
511 use super::*;
512
513 #[test]
514 fn test_derivation() {
515 let mnemonic: &str = "bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon bacon";
516 let index = 0 as u64; let sk = SwapKey::from_submarine_account(mnemonic, "", Chain::Bitcoin, index).unwrap();
518 let lks: LiquidSwapKey = sk.clone().into();
519 assert!(sk.fingerprint == lks.fingerprint);
520 assert_eq!(&sk.fingerprint.to_string().clone(), "9a6a2580");
522 assert_eq!(
523 &sk.keypair.display_secret().to_string(),
524 "d8d26ab9ba4e2c44f1a1fb9e10dc9d78707aaaaf38b5d42cf5c8bf00306acd85"
525 );
526 }
527
528 #[test]
529 #[ignore]
530 fn test_recover() {
531 let recovery = BtcSubmarineRecovery {
532 id: "y8uGeA".to_string(),
533 refund_key: "5416f1e024c191605502017d066786e294f841e711d3d437d13e9d27e40e066e".to_string(),
534 redeem_script: "a914046fabc17989627f6ca9c1846af8e470263e712d87632102c929edb654bc1da91001ec27d74d42b5d6a8cf8aef2fab7c55f2eb728eed0d1f6703634d27b1752102c530b4583640ab3df5c75c5ce381c4b747af6bdd6c618db7e5248cb0adcf3a1868ac".to_string(),
535 };
536 let file: RefundSwapFile = recovery.into();
537 let base_path = "/tmp/boltz-rust";
538 file.write_to_file(base_path);
539 let file_path = base_path.to_owned() + "/" + &file.file_name();
540 let file_struct = RefundSwapFile::read_from_file(file_path);
541 println!("Refund File: {:?}", file_struct);
542 }
543}