boltz_client/swaps/
liquid.rs

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/// Liquid swap script helper.
38#[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    /// Create the struct from raw elements
50    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    /// Create the struct from a submarine swap redeem_script string.
68    ///Usually created from the string provided by boltz api response.
69    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    /// Create the struct from a reverse swap redeem_script string.
139    /// Usually created from the string provided by boltz api response.
140    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    /// Internally used to convert struct into a bitcoin::Script type
208    pub fn to_script(&self) -> Result<EScript, S5Error> {
209        /*
210            HASH160 <hash of the preimage>
211            EQUAL
212            IF <reciever public key>
213            ELSE <timeout block height>
214            CHECKLOCKTIMEVERIFY
215            DROP <sender public key>
216            ENDIF
217            CHECKSIG
218        */
219        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                /*
257                    OP_SIZE
258                    [32]
259                    OP_EQUAL
260                    OP_IF
261                    OP_HASH160 <hash of the preimage>
262                    OP_EQUALVERIFY <reciever public key>
263                    OP_ELSE
264                    OP_DROP <timeout block height>
265                    OP_CLTV
266                    OP_DROP <sender public key>
267                    OP_ENDIF
268                    OP_CHECKSIG
269                */
270                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    /// Get address for the swap script.
316    /// Submarine swaps use p2shwsh. Reverse swaps use p2wsh.
317    /// Always returns a confidential address
318    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    /// Get balance for the swap script
340    pub fn get_balance(&self, network_config: &ElectrumConfig) -> Result<(u64, i64), S5Error> {
341        let electrum_client = network_config.clone().build_client()?;
342
343        // let _ = electrum_client
344        //     .script_subscribe(BitcoinScript::from_bytes(
345        //         &self
346        //             .to_address(network_config.network())?
347        //             .script_pubkey()
348        //             .as_bytes(),
349        //     ))
350        //     .unwrap();
351
352        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                // Handle the error here, you can convert it to S5Error if needed
361                return Err(S5Error::new(ErrorKind::Script, &error.to_string()));
362            }
363        };
364
365        // let balance = electrum_client
366        //     .script_get_balance(BitcoinScript::from_bytes(
367        //         &self
368        //             .to_address(network_config.network())?
369        //             .script_pubkey()
370        //             .as_bytes(),
371        //     ))
372        //     .unwrap();
373
374        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                // Handle the error here, you can convert it to S5Error if needed
383                return Err(S5Error::new(ErrorKind::Script, &error.to_string()));
384            }
385        };
386
387        // let _ = electrum_client
388        //     .script_unsubscribe(BitcoinScript::from_bytes(
389        //         &self
390        //             .to_address(network_config.network())?
391        //             .script_pubkey()
392        //             .as_bytes(),
393        //     ))
394        //     .unwrap();
395        // Ok((balance.confirmed, balance.unconfirmed))
396
397        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                // Handle the error here, you can convert it to S5Error if needed
406                return Err(S5Error::new(ErrorKind::Script, &error.to_string()));
407            }
408        };
409        Ok((balance.confirmed, balance.unconfirmed))
410    }
411
412    /// Fetch utxo for script
413    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
486/// Internal elements signature helper
487fn 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/// Liquid swap transaction helper.
495#[derive(Debug, Clone)]
496pub struct LBtcSwapTx {
497    kind: SwapTxKind,
498    swap_script: LBtcSwapScript,
499    output_address: Address,
500    utxo: OutPoint,
501    utxo_value: u64, // there should only ever be one outpoint in a swap
502    utxo_confidential_value: Option<elements::confidential::Value>,
503    txout_secrets: Option<TxOutSecrets>,
504}
505
506impl LBtcSwapTx {
507    /// Required to claim reverse swaps only. This is never used for submarine swaps.
508    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    /// Required to claim submarine swaps only. This is never used for reverse swaps.
538    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    /// Internally used to check if utxos are present in the struct to build the transaction.
569    fn _is_confidential(&self) -> bool {
570        self.txout_secrets.is_some() && self.utxo_confidential_value.is_some()
571    }
572
573    /// Sign a claim transaction for a reverse swap
574    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        // assuming we always use a blinded address that has an extractable blinding pub
662        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)), // from asset blinding
681            rangeproof: Some(Box::new(rangeproof)),                   // from value blinding
682        };
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        // SIGN TRANSACTION
709        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    /// Sign a refund transaction for a submarine swap
756    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        // assuming we always use a blinded address that has an extractable blinding pub
833        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)), // from asset blinding
852            rangeproof: Some(Box::new(rangeproof)),                   // from value blinding
853        };
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        // SIGN TRANSACTION
878        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    /// Calculate the size of a transaction.
925    /// Use this before calling drain to help calculate the absolute fees.
926    /// Multiply the size by the fee_rate to get the absolute fees.
927    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    /// Broadcast transaction to the network
936    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 balance = script.get_balance(network_config.clone()).unwrap();
966        // println!("BALANCE: {:?}", balance);
967
968        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        // println!("Sub status: {:#?}",_status);
980
981        // let utxo_from_raw = network_config
982        // .build_client()
983        // .unwrap()
984        // .raw_call("blockchain.address.listunspent", [Param::String(script
985        //         .to_address(Chain::LiquidTestnet)
986        //         .unwrap()
987        //         .to_string())]
988        // )
989        // .unwrap();
990        // println!("{:#?}",utxo_from_raw);
991        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        // println!("ATTEMPTING TO GET BLOCKHEIGHT FROM ELECTRUM CLIENT");
1017
1018        // let blockheight = network_config.build_client().unwrap().block_headers_subscribe().unwrap();
1019        // println!("{:?}", blockheight);
1020
1021        // let mut liquid_swap_tx =
1022        //     LBtcSwapTx::new_claim(script, RETURN_ADDRESS.to_string()).unwrap();
1023        // // let _ = liquid_swap_tx.fetch_utxo(network_config.clone()).unwrap();
1024        // let _ = liquid_swap_tx.fetch_utxo_raw(network_config.clone()).unwrap();
1025    }
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();
1040        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        // println!("{:?}", decoded);
1061        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        //let _ = liquid_swap_tx.fetch_utxo(&network_config).unwrap();
1085        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 manifest_dir = env!("CARGO_MANIFEST_DIR");
1091
1092        // let file_path = Path::new(manifest_dir).join("tx.constructed");
1093        // let mut file = File::create(file_path).unwrap();
1094        // use std::io::Write;
1095        // writeln!(file, "{:#?}", final_tx).unwrap();
1096        // println!("CHECK FILE tx.hex!");
1097
1098        let txid = liquid_swap_tx.broadcast(final_tx, &network_config).unwrap();
1099        println!("TXID: {}", txid);
1100    }
1101}