1use electrum_client::ElectrumApi;
2use std::str::FromStr;
3
4use bitcoin::{script::Script as BitcoinScript, secp256k1::Keypair, Witness};
5use elements::{
6 confidential::{self, AssetBlindingFactor, Value, ValueBlindingFactor},
7 hashes::hash160,
8 secp256k1_zkp::{self, Secp256k1, SecretKey},
9 sighash::SighashCache,
10 Address, AssetIssuance, OutPoint, Script, Sequence, Transaction, TxIn, TxInWitness, TxOut,
11 TxOutSecrets, TxOutWitness,
12};
13
14use elements::encode::serialize;
15use elements::secp256k1_zkp::Message;
16
17use crate::{
18 network::{electrum::ElectrumConfig, Chain},
19 swaps::boltz::SwapTxKind,
20 util::{
21 error::{ErrorKind, S5Error},
22 secrets::Preimage,
23 },
24};
25
26use elements::bitcoin::PublicKey;
27use elements::secp256k1_zkp::Keypair as ZKKeyPair;
28use elements::{
29 address::Address as EAddress,
30 opcodes::all::*,
31 script::{Builder as EBuilder, Instruction, Script as EScript},
32 AddressParams, LockTime,
33};
34
35use super::boltz::SwapType;
36
37#[derive(Debug, Clone, PartialEq)]
39pub struct LBtcSwapScript {
40 swap_type: SwapType,
41 pub hashlock: String,
42 pub reciever_pubkey: String,
43 pub timelock: u32,
44 pub sender_pubkey: String,
45 pub blinding_key: ZKKeyPair,
46}
47
48impl LBtcSwapScript {
49 pub fn new(
51 swap_type: SwapType,
52 hashlock: &str,
53 reciever_pubkey: &str,
54 timelock: u32,
55 sender_pubkey: &str,
56 blinding_key: &ZKKeyPair,
57 ) -> Self {
58 LBtcSwapScript {
59 swap_type: swap_type,
60 hashlock: hashlock.to_string(),
61 reciever_pubkey: reciever_pubkey.to_string(),
62 timelock: timelock,
63 sender_pubkey: sender_pubkey.to_string(),
64 blinding_key: blinding_key.clone(),
65 }
66 }
67 pub fn submarine_from_str(
70 redeem_script_str: &str,
71 blinding_str: &str,
72 ) -> Result<Self, S5Error> {
73 let script = match EScript::from_str(&redeem_script_str) {
74 Ok(result) => result,
75 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
76 };
77
78 let instructions = script.instructions();
79 let mut last_op = OP_0NOTEQUAL;
80 let mut hashlock = None;
81 let mut reciever_pubkey = None;
82 let mut timelock = None;
83 let mut sender_pubkey = None;
84
85 for instruction in instructions {
86 match instruction {
87 Ok(Instruction::Op(opcode)) => {
88 last_op = opcode;
89 }
90
91 Ok(Instruction::PushBytes(bytes)) => {
92 if last_op == OP_HASH160 {
93 hashlock = Some(hex::encode(bytes));
94 }
95 if last_op == OP_IF {
96 reciever_pubkey = Some(hex::encode(bytes));
97 }
98 if last_op == OP_ELSE {
99 timelock = Some(bytes_to_u32_little_endian(&bytes));
100 }
101 if last_op == OP_DROP {
102 sender_pubkey = Some(hex::encode(bytes));
103 }
104 }
105 _ => (),
106 }
107 }
108
109 if hashlock.is_some()
110 && sender_pubkey.is_some()
111 && timelock.is_some()
112 && sender_pubkey.is_some()
113 {
114 let zksecp = Secp256k1::new();
115
116 Ok(LBtcSwapScript {
117 swap_type: SwapType::Submarine,
118 hashlock: hashlock.unwrap(),
119 reciever_pubkey: reciever_pubkey.unwrap(),
120 timelock: timelock.unwrap(),
121 sender_pubkey: sender_pubkey.unwrap(),
122 blinding_key: match ZKKeyPair::from_seckey_str(&zksecp, &blinding_str) {
123 Ok(result) => result,
124 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
125 },
126 })
127 } else {
128 Err(S5Error::new(
129 ErrorKind::Input,
130 &format!(
131 "Could not extract all elements: {:?} {:?} {:?} {:?}",
132 hashlock, reciever_pubkey, timelock, sender_pubkey
133 ),
134 ))
135 }
136 }
137
138 pub fn reverse_from_str(redeem_script_str: &str, blinding_str: &str) -> Result<Self, S5Error> {
141 let script = match EScript::from_str(redeem_script_str) {
142 Ok(result) => result.to_owned(),
143 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
144 };
145
146 let instructions = script.instructions();
147 let mut last_op = OP_0NOTEQUAL;
148 let mut hashlock = None;
149 let mut reciever_pubkey = None;
150 let mut timelock = None;
151 let mut sender_pubkey = None;
152
153 for instruction in instructions {
154 match instruction {
155 Ok(Instruction::Op(opcode)) => {
156 last_op = opcode;
157 }
158
159 Ok(Instruction::PushBytes(bytes)) => {
160 if last_op == OP_HASH160 {
161 hashlock = Some(hex::encode(bytes));
162 }
163 if last_op == OP_EQUALVERIFY {
164 reciever_pubkey = Some(hex::encode(bytes));
165 }
166 if last_op == OP_DROP {
167 if bytes.len() == 3 as usize {
168 timelock = Some(bytes_to_u32_little_endian(&bytes));
169 } else {
170 sender_pubkey = Some(hex::encode(bytes));
171 }
172 }
173 }
174 _ => (),
175 }
176 }
177
178 if hashlock.is_some()
179 && sender_pubkey.is_some()
180 && timelock.is_some()
181 && sender_pubkey.is_some()
182 {
183 let zksecp = Secp256k1::new();
184
185 Ok(LBtcSwapScript {
186 swap_type: SwapType::ReverseSubmarine,
187 hashlock: hashlock.unwrap(),
188 reciever_pubkey: reciever_pubkey.unwrap(),
189 timelock: timelock.unwrap(),
190 sender_pubkey: sender_pubkey.unwrap(),
191 blinding_key: match ZKKeyPair::from_seckey_str(&zksecp, &blinding_str) {
192 Ok(result) => result,
193 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
194 },
195 })
196 } else {
197 Err(S5Error::new(
198 ErrorKind::Input,
199 &format!(
200 "Could not extract all elements: {:?} {:?} {:?} {:?}",
201 hashlock, reciever_pubkey, timelock, sender_pubkey
202 ),
203 ))
204 }
205 }
206
207 pub fn to_script(&self) -> Result<EScript, S5Error> {
209 match self.swap_type {
220 SwapType::Submarine => {
221 let reciever_pubkey = match PublicKey::from_str(&self.reciever_pubkey) {
222 Ok(result) => result,
223 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
224 };
225 let sender_pubkey = match PublicKey::from_str(&self.sender_pubkey) {
226 Ok(result) => result,
227 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
228 };
229 let locktime = LockTime::from_consensus(self.timelock);
230 let hashvalue = match hash160::Hash::from_str(&self.hashlock) {
231 Ok(result) => result,
232 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
233 };
234 let hashbytes_slice: &[u8] = hashvalue.as_ref();
235 let hashbytes: [u8; 20] =
236 hashbytes_slice.try_into().expect("Hash must be 20 bytes");
237
238 let script = EBuilder::new()
239 .push_opcode(OP_HASH160)
240 .push_slice(&hashbytes)
241 .push_opcode(OP_EQUAL)
242 .push_opcode(OP_IF)
243 .push_key(&reciever_pubkey)
244 .push_opcode(OP_ELSE)
245 .push_int(locktime.to_consensus_u32() as i64)
246 .push_opcode(OP_CLTV)
247 .push_opcode(OP_DROP)
248 .push_key(&sender_pubkey)
249 .push_opcode(OP_ENDIF)
250 .push_opcode(OP_CHECKSIG)
251 .into_script();
252
253 Ok(script)
254 }
255 SwapType::ReverseSubmarine => {
256 let reciever_pubkey = match PublicKey::from_str(&self.reciever_pubkey) {
271 Ok(result) => result,
272 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
273 };
274 let sender_pubkey = match PublicKey::from_str(&self.sender_pubkey) {
275 Ok(result) => result,
276 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
277 };
278 let locktime = LockTime::from_consensus(self.timelock);
279 let hashvalue = match hash160::Hash::from_str(&self.hashlock) {
280 Ok(result) => result,
281 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
282 };
283 let hashbytes_slice: &[u8] = hashvalue.as_ref();
284 let hashbytes: [u8; 20] = match hashbytes_slice.try_into() {
285 Ok(result) => result,
286 Err(_) => {
287 return Err(S5Error::new(ErrorKind::Input, "Hash160 must be 20 bytes"))
288 }
289 };
290
291 let script = EBuilder::new()
292 .push_opcode(OP_SIZE)
293 .push_slice(&[32])
294 .push_opcode(OP_EQUAL)
295 .push_opcode(OP_IF)
296 .push_opcode(OP_HASH160)
297 .push_slice(&hashbytes)
298 .push_opcode(OP_EQUALVERIFY)
299 .push_key(&reciever_pubkey)
300 .push_opcode(OP_ELSE)
301 .push_opcode(OP_DROP)
302 .push_int(locktime.to_consensus_u32() as i64)
303 .push_opcode(OP_CLTV)
304 .push_opcode(OP_DROP)
305 .push_key(&sender_pubkey)
306 .push_opcode(OP_ENDIF)
307 .push_opcode(OP_CHECKSIG)
308 .into_script();
309
310 Ok(script)
311 }
312 }
313 }
314
315 pub fn to_address(&self, network: Chain) -> Result<EAddress, S5Error> {
319 let script = self.to_script()?;
320 let address_params = match network {
321 Chain::Liquid => &AddressParams::LIQUID,
322 _ => &AddressParams::LIQUID_TESTNET,
323 };
324
325 match self.swap_type {
326 SwapType::Submarine => Ok(EAddress::p2wsh(
327 &script,
328 Some(self.blinding_key.public_key()),
329 address_params,
330 )),
331 SwapType::ReverseSubmarine => Ok(EAddress::p2wsh(
332 &script,
333 Some(self.blinding_key.public_key()),
334 address_params,
335 )),
336 }
337 }
338
339 pub fn get_balance(&self, network_config: &ElectrumConfig) -> Result<(u64, i64), S5Error> {
341 let electrum_client = network_config.clone().build_client()?;
342
343 let _ = match electrum_client.script_subscribe(BitcoinScript::from_bytes(
353 &self
354 .to_address(network_config.network())?
355 .script_pubkey()
356 .as_bytes(),
357 )) {
358 Ok(_t) => (),
359 Err(error) => {
360 return Err(S5Error::new(ErrorKind::Script, &error.to_string()));
362 }
363 };
364
365 let balance = match electrum_client.script_get_balance(BitcoinScript::from_bytes(
375 &self
376 .to_address(network_config.network())?
377 .script_pubkey()
378 .as_bytes(),
379 )) {
380 Ok(t) => t,
381 Err(error) => {
382 return Err(S5Error::new(ErrorKind::Script, &error.to_string()));
384 }
385 };
386
387 let _ = match electrum_client.script_unsubscribe(BitcoinScript::from_bytes(
398 &self
399 .to_address(network_config.network())?
400 .script_pubkey()
401 .as_bytes(),
402 )) {
403 Ok(_t) => (),
404 Err(error) => {
405 return Err(S5Error::new(ErrorKind::Script, &error.to_string()));
407 }
408 };
409 Ok((balance.confirmed, balance.unconfirmed))
410 }
411
412 pub fn fetch_utxo(
414 &self,
415 network_config: &ElectrumConfig,
416 ) -> Result<(OutPoint, u64, Option<Value>, Option<TxOutSecrets>), S5Error> {
417 let electrum_client = network_config.clone().build_client()?;
418 let address = self.to_address(network_config.network())?;
419 let history = match electrum_client.script_get_history(BitcoinScript::from_bytes(
420 self.to_script()?.to_v0_p2wsh().as_bytes(),
421 )) {
422 Ok(result) => result,
423 Err(e) => return Err(S5Error::new(ErrorKind::Network, &e.to_string())),
424 };
425 let bitcoin_txid = match history.last() {
426 Some(result) => result,
427 None => return Err(S5Error::new(ErrorKind::Input, "No Transaction History")),
428 }
429 .tx_hash;
430 println!("{}", bitcoin_txid);
431 let raw_tx = match electrum_client.transaction_get_raw(&bitcoin_txid) {
432 Ok(result) => result,
433 Err(e) => return Err(S5Error::new(ErrorKind::Network, &e.to_string())),
434 };
435 let tx: Transaction = match elements::encode::deserialize(&raw_tx) {
436 Ok(result) => result,
437 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
438 };
439 let mut vout = 0;
440 for output in tx.clone().output {
441 if output.script_pubkey == address.script_pubkey() {
442 let zksecp = Secp256k1::new();
443 let is_blinded = output.asset.is_confidential() && output.value.is_confidential();
444 if !is_blinded {
445 let el_txid = tx.clone().txid();
446 let outpoint_0 = OutPoint::new(el_txid, vout);
447 return Ok((outpoint_0, output.value.explicit().unwrap(), None, None));
448 } else {
449 let unblinded = match output.unblind(&zksecp, self.blinding_key.secret_key()) {
450 Ok(result) => result,
451 Err(e) => return Err(S5Error::new(ErrorKind::Key, &e.to_string())),
452 };
453 let el_txid = tx.clone().txid();
454 let outpoint_0 = OutPoint::new(el_txid, vout);
455 let utxo_value = unblinded.value;
456
457 return Ok((outpoint_0, utxo_value, Some(output.value), Some(unblinded)));
458 }
459 }
460 vout += 1;
461 }
462 return Err(S5Error::new(
463 ErrorKind::Script,
464 "Could not find utxos for script",
465 ));
466 }
467}
468
469fn bytes_to_u32_little_endian(bytes: &[u8]) -> u32 {
470 let mut result = 0u32;
471 for (i, &byte) in bytes.iter().enumerate() {
472 result |= (byte as u32) << (8 * i);
473 }
474 result
475}
476fn _u32_to_bytes_little_endian(value: u32) -> [u8; 4] {
477 let b1: u8 = (value & 0xff) as u8;
478 let b2: u8 = ((value >> 8) & 0xff) as u8;
479 let b3: u8 = ((value >> 16) & 0xff) as u8;
480 let b4: u8 = ((value >> 24) & 0xff) as u8;
481 [b1, b2, b3, b4]
482}
483
484pub type ElementsSig = (secp256k1_zkp::ecdsa::Signature, elements::EcdsaSighashType);
485
486fn elementssig_to_rawsig(sig: &ElementsSig) -> Vec<u8> {
488 let ser_sig = sig.0.serialize_der();
489 let mut raw_sig = Vec::from(&ser_sig[..]);
490 raw_sig.push(sig.1 as u8);
491 raw_sig
492}
493
494#[derive(Debug, Clone)]
496pub struct LBtcSwapTx {
497 kind: SwapTxKind,
498 swap_script: LBtcSwapScript,
499 output_address: Address,
500 utxo: OutPoint,
501 utxo_value: u64, utxo_confidential_value: Option<elements::confidential::Value>,
503 txout_secrets: Option<TxOutSecrets>,
504}
505
506impl LBtcSwapTx {
507 pub fn new_claim(
509 swap_script: LBtcSwapScript,
510 output_address: String,
511 network_config: &ElectrumConfig,
512 ) -> Result<LBtcSwapTx, S5Error> {
513 if swap_script.swap_type == SwapType::Submarine {
514 return Err(S5Error::new(
515 ErrorKind::Script,
516 "Claim transactions can only be constructed for Reverse swaps.",
517 ));
518 }
519 let address = match Address::from_str(&output_address) {
520 Ok(result) => result,
521 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
522 };
523
524 let (utxo, utxo_value, utxo_confidential_value, txout_secrets) =
525 swap_script.fetch_utxo(network_config)?;
526
527 Ok(LBtcSwapTx {
528 kind: SwapTxKind::Claim,
529 swap_script: swap_script,
530 output_address: address,
531 utxo,
532 utxo_value,
533 utxo_confidential_value,
534 txout_secrets,
535 })
536 }
537 pub fn new_refund(
539 swap_script: LBtcSwapScript,
540 output_address: String,
541 network_config: &ElectrumConfig,
542 ) -> Result<LBtcSwapTx, S5Error> {
543 if swap_script.swap_type == SwapType::ReverseSubmarine {
544 return Err(S5Error::new(
545 ErrorKind::Script,
546 "Refund transactions can only be constructed for Submarine swaps.",
547 ));
548 }
549 let address = match Address::from_str(&output_address) {
550 Ok(result) => result,
551 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
552 };
553
554 let (utxo, utxo_value, utxo_confidential_value, txout_secrets) =
555 swap_script.fetch_utxo(network_config)?;
556
557 Ok(LBtcSwapTx {
558 kind: SwapTxKind::Refund,
559 swap_script: swap_script,
560 output_address: address,
561 utxo,
562 utxo_value,
563 utxo_confidential_value,
564 txout_secrets,
565 })
566 }
567
568 fn _is_confidential(&self) -> bool {
570 self.txout_secrets.is_some() && self.utxo_confidential_value.is_some()
571 }
572
573 pub fn sign_claim(
575 &self,
576 keys: &Keypair,
577 preimage: &Preimage,
578 absolute_fees: u64,
579 ) -> Result<Transaction, S5Error> {
580 if self.swap_script.swap_type == SwapType::Submarine {
581 return Err(S5Error::new(
582 ErrorKind::Script,
583 "Claim transactions can only be constructed for Reverse swaps.",
584 ));
585 }
586 if self.kind == SwapTxKind::Refund {
587 return Err(S5Error::new(
588 ErrorKind::Script,
589 "Constructed transaction is a refund. Cannot claim.",
590 ));
591 }
592 let preimage_bytes = if let Some(value) = preimage.bytes {
593 value
594 } else {
595 return Err(S5Error::new(ErrorKind::Input, "No preimage provided"));
596 };
597 let redeem_script = self.swap_script.to_script()?;
598
599 let sequence = Sequence::from_consensus(0xFFFFFFFF);
600 let unsigned_input: TxIn = TxIn {
601 sequence: sequence,
602 previous_output: self.utxo,
603 script_sig: Script::new(),
604 witness: TxInWitness::default(),
605 is_pegin: false,
606 asset_issuance: AssetIssuance::default(),
607 };
608
609 use bitcoin::secp256k1::rand::rngs::OsRng;
610 let mut rng = OsRng::default();
611 let secp = Secp256k1::new();
612
613 let is_explicit_utxo =
614 self.utxo_confidential_value.is_none() && self.txout_secrets.is_none();
615
616 if is_explicit_utxo {
617 todo!()
618 }
619 let txout_secrets = if let Some(value) = self.txout_secrets {
620 value
621 } else {
622 return Err(S5Error::new(
623 ErrorKind::Input,
624 "No txout_secrets in script.",
625 ));
626 };
627
628 let asset_id = txout_secrets.asset;
629 let out_abf = AssetBlindingFactor::new(&mut rng);
630 let exp_asset = confidential::Asset::Explicit(asset_id);
631
632 let (blinded_asset, asset_surjection_proof) =
633 match exp_asset.blind(&mut rng, &secp, out_abf, &[txout_secrets]) {
634 Ok(result) => result,
635 Err(e) => return Err(S5Error::new(ErrorKind::Key, &e.to_string())),
636 };
637
638 let output_value = self.utxo_value - absolute_fees;
639
640 let final_vbf = ValueBlindingFactor::last(
641 &secp,
642 output_value,
643 out_abf,
644 &[(
645 txout_secrets.value,
646 txout_secrets.asset_bf,
647 txout_secrets.value_bf,
648 )],
649 &[(
650 absolute_fees,
651 AssetBlindingFactor::zero(),
652 ValueBlindingFactor::zero(),
653 )],
654 );
655 let explicit_value = elements::confidential::Value::Explicit(output_value);
656 let msg = elements::RangeProofMessage {
657 asset: asset_id,
658 bf: out_abf,
659 };
660 let ephemeral_sk = SecretKey::new(&mut rng);
661 let blinding_key = if let Some(value) = self.output_address.blinding_pubkey {
663 value
664 } else {
665 return Err(S5Error::new(ErrorKind::Input, "No blinding key in tx."));
666 };
667 let (blinded_value, nonce, rangeproof) = match explicit_value.blind(
668 &secp,
669 final_vbf,
670 blinding_key,
671 ephemeral_sk,
672 &self.output_address.script_pubkey(),
673 &msg,
674 ) {
675 Ok(result) => result,
676 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
677 };
678
679 let tx_out_witness = TxOutWitness {
680 surjection_proof: Some(Box::new(asset_surjection_proof)), rangeproof: Some(Box::new(rangeproof)), };
683 let payment_output: TxOut = TxOut {
684 script_pubkey: self.output_address.script_pubkey(),
685 value: blinded_value,
686 asset: blinded_asset,
687 nonce: nonce,
688 witness: tx_out_witness,
689 };
690 let fee_output: TxOut = TxOut::new_fee(absolute_fees, asset_id);
691
692 let unsigned_tx = Transaction {
693 version: 2,
694 lock_time: LockTime::from_consensus(self.swap_script.timelock),
695 input: vec![unsigned_input],
696 output: vec![payment_output.clone(), fee_output.clone()],
697 };
698
699 let utxo_confidential_value = if let Some(value) = self.utxo_confidential_value {
700 value
701 } else {
702 return Err(S5Error::new(
703 ErrorKind::Input,
704 "No utxo confidential value in tx.",
705 ));
706 };
707
708 let hash_type = elements::EcdsaSighashType::All;
710 let sighash = match Message::from_digest_slice(
711 &SighashCache::new(&unsigned_tx).segwitv0_sighash(
712 0,
713 &redeem_script,
714 utxo_confidential_value,
715 hash_type,
716 )[..],
717 ) {
718 Ok(result) => result,
719 Err(e) => return Err(S5Error::new(ErrorKind::Transaction, &e.to_string())),
720 };
721
722 let sig: secp256k1_zkp::ecdsa::Signature =
723 secp.sign_ecdsa_low_r(&sighash, &keys.secret_key());
724 let sig = elementssig_to_rawsig(&(sig, hash_type));
725
726 let mut script_witness = Witness::new();
727 script_witness.push(sig);
728 script_witness.push(preimage_bytes);
729 script_witness.push(redeem_script.as_bytes());
730
731 let witness = TxInWitness {
732 amount_rangeproof: None,
733 inflation_keys_rangeproof: None,
734 script_witness: script_witness.to_vec(),
735 pegin_witness: vec![],
736 };
737
738 let signed_txin = TxIn {
739 previous_output: self.utxo,
740 script_sig: Script::default(),
741 sequence: sequence,
742 witness: witness,
743 is_pegin: false,
744 asset_issuance: AssetIssuance::default(),
745 };
746
747 let signed_tx = Transaction {
748 version: 2,
749 lock_time: LockTime::from_consensus(self.swap_script.timelock),
750 input: vec![signed_txin],
751 output: vec![payment_output, fee_output],
752 };
753 Ok(signed_tx)
754 }
755 pub fn sign_refund(&self, keys: &Keypair, absolute_fees: u64) -> Result<Transaction, S5Error> {
757 if self.swap_script.swap_type == SwapType::ReverseSubmarine {
758 return Err(S5Error::new(
759 ErrorKind::Script,
760 "Refund transactions can only be constructed for Submarine swaps.",
761 ));
762 }
763 if self.kind == SwapTxKind::Claim {
764 return Err(S5Error::new(
765 ErrorKind::Script,
766 "Constructed transaction is a claim. Cannot refund.",
767 ));
768 }
769
770 let redeem_script = self.swap_script.to_script()?;
771 let sequence = Sequence::from_consensus(0xFFFFFFFF);
772 let unsigned_input: TxIn = TxIn {
773 sequence: sequence,
774 previous_output: self.utxo,
775 script_sig: Script::new(),
776 witness: TxInWitness::default(),
777 is_pegin: false,
778 asset_issuance: AssetIssuance::default(),
779 };
780
781 use bitcoin::secp256k1::rand::rngs::OsRng;
782 let mut rng = OsRng::default();
783 let secp = Secp256k1::new();
784
785 let is_explicit_utxo =
786 self.utxo_confidential_value.is_none() && self.txout_secrets.is_none();
787
788 if is_explicit_utxo {
789 todo!()
790 }
791 let txout_secrets = if let Some(value) = self.txout_secrets {
792 value
793 } else {
794 return Err(S5Error::new(
795 ErrorKind::Input,
796 "No txout_secrets in script.",
797 ));
798 };
799 let asset_id = txout_secrets.asset;
800 let out_abf = AssetBlindingFactor::new(&mut rng);
801 let exp_asset = confidential::Asset::Explicit(asset_id);
802
803 let (blinded_asset, asset_surjection_proof) =
804 match exp_asset.blind(&mut rng, &secp, out_abf, &[txout_secrets]) {
805 Ok(result) => result,
806 Err(e) => return Err(S5Error::new(ErrorKind::Key, &e.to_string())),
807 };
808
809 let output_value = self.utxo_value - absolute_fees;
810
811 let final_vbf = ValueBlindingFactor::last(
812 &secp,
813 output_value,
814 out_abf,
815 &[(
816 txout_secrets.value,
817 txout_secrets.asset_bf,
818 txout_secrets.value_bf,
819 )],
820 &[(
821 absolute_fees,
822 AssetBlindingFactor::zero(),
823 ValueBlindingFactor::zero(),
824 )],
825 );
826 let explicit_value = elements::confidential::Value::Explicit(output_value);
827 let msg = elements::RangeProofMessage {
828 asset: asset_id,
829 bf: out_abf,
830 };
831 let ephemeral_sk = SecretKey::new(&mut rng);
832 let blinding_key = if let Some(value) = self.output_address.blinding_pubkey {
834 value
835 } else {
836 return Err(S5Error::new(ErrorKind::Input, "No blinding key in tx."));
837 };
838 let (blinded_value, nonce, rangeproof) = match explicit_value.blind(
839 &secp,
840 final_vbf,
841 blinding_key,
842 ephemeral_sk,
843 &self.output_address.script_pubkey(),
844 &msg,
845 ) {
846 Ok(result) => result,
847 Err(e) => return Err(S5Error::new(ErrorKind::Input, &e.to_string())),
848 };
849
850 let tx_out_witness = TxOutWitness {
851 surjection_proof: Some(Box::new(asset_surjection_proof)), rangeproof: Some(Box::new(rangeproof)), };
854 let payment_output: TxOut = TxOut {
855 script_pubkey: self.output_address.script_pubkey(),
856 value: blinded_value,
857 asset: blinded_asset,
858 nonce: nonce,
859 witness: tx_out_witness,
860 };
861 let fee_output: TxOut = TxOut::new_fee(absolute_fees, asset_id);
862
863 let unsigned_tx = Transaction {
864 version: 2,
865 lock_time: LockTime::from_consensus(self.swap_script.timelock),
866 input: vec![unsigned_input],
867 output: vec![payment_output.clone(), fee_output.clone()],
868 };
869 let utxo_confidential_value = if let Some(value) = self.utxo_confidential_value {
870 value
871 } else {
872 return Err(S5Error::new(
873 ErrorKind::Input,
874 "No utxo confidential value in tx.",
875 ));
876 };
877 let hash_type = elements::EcdsaSighashType::All;
879 let sighash = match Message::from_digest_slice(
880 &SighashCache::new(&unsigned_tx).segwitv0_sighash(
881 0,
882 &redeem_script,
883 utxo_confidential_value,
884 hash_type,
885 )[..],
886 ) {
887 Ok(result) => result,
888 Err(e) => return Err(S5Error::new(ErrorKind::Transaction, &e.to_string())),
889 };
890
891 let sig: secp256k1_zkp::ecdsa::Signature =
892 secp.sign_ecdsa_low_r(&sighash, &keys.secret_key());
893 let sig = elementssig_to_rawsig(&(sig, hash_type));
894
895 let mut script_witness = Witness::new();
896 script_witness.push(sig);
897 script_witness.push([0]);
898 script_witness.push(redeem_script.as_bytes());
899
900 let witness = TxInWitness {
901 amount_rangeproof: None,
902 inflation_keys_rangeproof: None,
903 script_witness: script_witness.to_vec(),
904 pegin_witness: vec![],
905 };
906
907 let signed_txin = TxIn {
908 previous_output: self.utxo,
909 script_sig: Script::default(),
910 sequence: sequence,
911 witness: witness,
912 is_pegin: false,
913 asset_issuance: AssetIssuance::default(),
914 };
915
916 let signed_tx = Transaction {
917 version: 2,
918 lock_time: LockTime::from_consensus(self.swap_script.timelock),
919 input: vec![signed_txin],
920 output: vec![payment_output, fee_output],
921 };
922 Ok(signed_tx)
923 }
924 pub fn size(&self, keys: &Keypair, preimage: &Preimage) -> Result<usize, S5Error> {
928 let dummy_abs_fee = 5_000;
929 let tx = match self.kind {
930 _ => self.sign_claim(keys, preimage, dummy_abs_fee)?,
931 };
932 Ok(tx.size())
933 }
934
935 pub fn broadcast(
937 &mut self,
938 signed_tx: Transaction,
939 network_config: &ElectrumConfig,
940 ) -> Result<String, S5Error> {
941 let electrum_client = network_config.build_client()?;
942 let serialized = serialize(&signed_tx);
943 match electrum_client.transaction_broadcast_raw(&serialized) {
944 Ok(txid) => Ok(txid.to_string()),
945 Err(e) => Err(S5Error::new(ErrorKind::Network, &e.to_string())),
946 }
947 }
948}
949
950#[cfg(test)]
951mod tests {
952 use super::*;
953 #[test]
954 #[ignore]
955 fn test_fetch_utxo_fix() {
956 const _RETURN_ADDRESS: &str =
957 "tlq1qqtc07z9kljll7dk2jyhz0qj86df9gnrc70t0wuexutzkxjavdpht0d4vwhgs2pq2f09zsvfr5nkglc394766w3hdaqrmay4tw";
958 let redeem_script_str = "8201208763a9142bdd03d431251598f46a625f1d3abfcd7f491535882102ccbab5f97c89afb97d814831c5355ef5ba96a18c9dcd1b5c8cfd42c697bfe53c677503715912b1752103fced00385bd14b174a571d88b4b6aced2cb1d532237c29c4ec61338fbb7eff4068ac".to_string();
959 let blinding_str = "02702ae71ec11a895f6255e26395983585a0d791ea1eb83d1aa54a66056469da";
960 let script =
961 LBtcSwapScript::reverse_from_str(&redeem_script_str.clone(), blinding_str).unwrap();
962 let network_config = &ElectrumConfig::default_liquid();
963 let address = script.to_address(network_config.network()).unwrap();
964 println!("{:?}", address.to_string());
965 let _status = network_config
969 .build_client()
970 .unwrap()
971 .script_subscribe(BitcoinScript::from_bytes(
972 &script
973 .to_address(Chain::LiquidTestnet)
974 .unwrap()
975 .script_pubkey()
976 .as_bytes(),
977 ))
978 .unwrap();
979 let utxo = network_config
992 .build_client()
993 .unwrap()
994 .script_list_unspent(BitcoinScript::from_bytes(
995 &script
996 .to_address(Chain::LiquidTestnet)
997 .unwrap()
998 .script_pubkey()
999 .as_bytes(),
1000 ))
1001 .unwrap();
1002 println!("{:#?}", utxo);
1003
1004 let _ =
1005 network_config
1006 .build_client()
1007 .unwrap()
1008 .script_unsubscribe(BitcoinScript::from_bytes(
1009 &script
1010 .to_address(Chain::LiquidTestnet)
1011 .unwrap()
1012 .script_pubkey()
1013 .as_bytes(),
1014 ));
1015
1016 }
1026
1027
1028 #[test]
1029 fn test_script_address(){
1030 let rs = "a91430dd7bf6e97514be2ec0d1368790f763184b7f848763210301798770066e9d93803ced62f169d06567683d26a180f87be736e1af00eaba116703fa0113b1752102c530b4583640ab3df5c75c5ce381c4b747af6bdd6c618db7e5248cb0adcf3a1868ac";
1031 let blinder = "89b7b9e32cb141787ae187f0d7db784eb114ea7e69da7be9bebafee3f3dbb64e";
1032 let exp_addr = "tlq1qqdtkt2czrht3mjy7kwtauq0swtvr5tfxysvcekmrzraayu025wjl8537am2epmhzl40e27mpuxr2cp36emmmtudjquf5lruld437rz0tkqxu72j38yjz";
1033 let script = LBtcSwapScript::submarine_from_str(rs, blinder).unwrap();
1034 assert_eq!(script.to_address(Chain::LiquidTestnet).unwrap().to_string(), exp_addr);
1035 }
1036 #[test]
1037 #[ignore]
1038 fn test_liquid_swap_elements() {
1039 let secp = Secp256k1::new();
1041 const RETURN_ADDRESS: &str =
1042 "tlq1qqtc07z9kljll7dk2jyhz0qj86df9gnrc70t0wuexutzkxjavdpht0d4vwhgs2pq2f09zsvfr5nkglc394766w3hdaqrmay4tw";
1043 let redeem_script_str = "8201208763a9142bdd03d431251598f46a625f1d3abfcd7f491535882102ccbab5f97c89afb97d814831c5355ef5ba96a18c9dcd1b5c8cfd42c697bfe53c677503715912b1752103fced00385bd14b174a571d88b4b6aced2cb1d532237c29c4ec61338fbb7eff4068ac".to_string();
1044 let expected_address = "tlq1qq0gnj2my5tp8r77srvvdmwfrtr8va9mgz9e8ja0rzk75jvsanjvgz5sfvl093l5a7xztrtzhyhfmfyr2exdxtpw7cehfgtzgn62zdzcsgrz8c4pjfvtj";
1045 let expected_timeout = 1202545;
1046 let boltz_blinding_str = "02702ae71ec11a895f6255e26395983585a0d791ea1eb83d1aa54a66056469da";
1047 let boltz_blinding_key = ZKKeyPair::from_seckey_str(&secp, boltz_blinding_str).unwrap();
1048 let preimage_str = "6ef7d91c721ea06b3b65d824ae1d69777cd3892d41090234aef13a572ff0e64f";
1049 let preimage = Preimage::from_str(preimage_str).unwrap();
1050 let _id = "axtHXB";
1051 let my_key_pair = ZKKeyPair::from_seckey_str(
1052 &secp,
1053 "aecbc2bddfcd3fa6953d257a9f369dc20cdc66f2605c73efb4c91b90703506b6",
1054 )
1055 .unwrap();
1056 let network_config = &ElectrumConfig::default_liquid();
1057 let decoded =
1058 LBtcSwapScript::reverse_from_str(&redeem_script_str.clone(), boltz_blinding_str)
1059 .unwrap();
1060 assert_eq!(
1062 decoded.reciever_pubkey,
1063 my_key_pair.public_key().to_string()
1064 );
1065 assert_eq!(decoded.timelock, expected_timeout);
1066
1067 let el_script = LBtcSwapScript {
1068 hashlock: decoded.hashlock,
1069 reciever_pubkey: decoded.reciever_pubkey,
1070 sender_pubkey: decoded.sender_pubkey,
1071 timelock: decoded.timelock,
1072 swap_type: SwapType::ReverseSubmarine,
1073 blinding_key: boltz_blinding_key,
1074 };
1075
1076 let address = el_script.to_address(network_config.network()).unwrap();
1077 println!("ADDRESS FROM ENCODED: {:?}", address.to_string());
1078 println!("Blinding Pub: {:?}", address.blinding_pubkey);
1079
1080 assert_eq!(address.to_string(), expected_address);
1081
1082 let mut liquid_swap_tx =
1083 LBtcSwapTx::new_claim(el_script, RETURN_ADDRESS.to_string(), network_config).unwrap();
1084 println!("{:#?}", liquid_swap_tx);
1086 let final_tx = liquid_swap_tx
1087 .sign_claim(&my_key_pair, &preimage, 5_000)
1088 .unwrap();
1089 println!("FINALIZED TX SIZE: {:?}", final_tx.size());
1090 let txid = liquid_swap_tx.broadcast(final_tx, &network_config).unwrap();
1099 println!("TXID: {}", txid);
1100 }
1101}