boltz_client/swaps/
bitcoin.rs

1use bitcoin::consensus::deserialize;
2use bitcoin::hashes::Hash;
3use bitcoin::hex::DisplayHex;
4use bitcoin::key::rand::rngs::OsRng;
5use bitcoin::key::rand::RngCore;
6use bitcoin::secp256k1::{Keypair, Message, Secp256k1};
7use bitcoin::sighash::Prevouts;
8use bitcoin::taproot::{LeafVersion, Signature, TaprootBuilder, TaprootSpendInfo};
9use bitcoin::transaction::Version;
10use bitcoin::{
11    blockdata::script::{Builder, Instruction, ScriptBuf},
12    opcodes::{all::*, OP_0},
13    Address, OutPoint, PublicKey,
14};
15use bitcoin::{sighash::SighashCache, Network, Sequence, Transaction, TxIn, TxOut, Witness};
16use bitcoin::{Amount, Script, TapLeafHash, TapSighashType, Txid, XOnlyPublicKey};
17use elements::pset::serialize::Serialize;
18use secp256k1_musig::{
19    musig::{self},
20    Scalar,
21};
22use std::str::FromStr;
23
24use crate::util::hex_to_bytes32;
25use crate::util::secrets::rng_32b;
26use crate::{error::Error, util::secrets::Preimage};
27
28use bitcoin::{blockdata::locktime::absolute::LockTime, hashes::hash160};
29
30use super::boltz::{
31    BoltzApiClientV2, ChainSwapDetails, Cooperative, CreateReverseResponse,
32    CreateSubmarineResponse, Side, SwapTxKind, SwapType, ToSign,
33};
34use super::wrappers::SwapScriptCommon;
35
36use crate::network::{BitcoinChain, BitcoinClient};
37use crate::util::fees::{create_tx_with_fee, Fee};
38
39pub(crate) fn find_utxo(tx: &Transaction, script_pubkey: &Script) -> Option<(OutPoint, TxOut)> {
40    for (vout, output) in tx.clone().output.into_iter().enumerate() {
41        if output.script_pubkey == *script_pubkey {
42            let outpoint = OutPoint::new(tx.compute_txid(), vout as u32);
43            return Some((outpoint, output));
44        }
45    }
46    None
47}
48
49/// Bitcoin v2 swap script helper.
50// TODO: This should encode the network at global level.
51#[derive(Debug, PartialEq, Clone)]
52pub struct BtcSwapScript {
53    pub swap_type: SwapType,
54    // pub swap_id: String,
55    pub side: Option<Side>,
56    pub funding_addrs: Option<Address>, // we should not store this as a field, since we have a method
57    // if we are using it just to recognize regtest, we should consider another strategy
58    pub hashlock: hash160::Hash,
59    pub receiver_pubkey: PublicKey,
60    pub locktime: LockTime,
61    pub sender_pubkey: PublicKey,
62}
63
64impl BtcSwapScript {
65    /// Create the struct for a submarine swap from boltz create swap response.
66    pub fn submarine_from_swap_resp(
67        create_swap_response: &CreateSubmarineResponse,
68        our_pubkey: PublicKey,
69    ) -> Result<Self, Error> {
70        let claim_script = ScriptBuf::from_hex(&create_swap_response.swap_tree.claim_leaf.output)?;
71        let refund_script =
72            ScriptBuf::from_hex(&create_swap_response.swap_tree.refund_leaf.output)?;
73
74        let claim_instructions = claim_script.instructions();
75        let refund_instructions = refund_script.instructions();
76
77        let mut last_op = OP_0;
78        let mut hashlock = None;
79        let mut timelock = None;
80
81        for instruction in claim_instructions {
82            match instruction {
83                Ok(Instruction::PushBytes(bytes)) => {
84                    if bytes.len() == 20 {
85                        hashlock = Some(hash160::Hash::from_slice(bytes.as_bytes())?);
86                    } else {
87                        continue;
88                    }
89                }
90                _ => continue,
91            }
92        }
93
94        for instruction in refund_instructions {
95            match instruction {
96                Ok(Instruction::Op(opcode)) => last_op = opcode,
97                Ok(Instruction::PushBytes(bytes)) => {
98                    if last_op == OP_CHECKSIGVERIFY {
99                        timelock = Some(LockTime::from_consensus(bytes_to_u32_little_endian(
100                            bytes.as_bytes(),
101                        )));
102                    } else {
103                        continue;
104                    }
105                }
106                _ => continue,
107            }
108        }
109
110        let hashlock =
111            hashlock.ok_or_else(|| Error::Protocol("No hashlock provided".to_string()))?;
112
113        let timelock =
114            timelock.ok_or_else(|| Error::Protocol("No timelock provided".to_string()))?;
115
116        let funding_addrs = Address::from_str(&create_swap_response.address)?.assume_checked();
117
118        Ok(BtcSwapScript {
119            swap_type: SwapType::Submarine,
120            // swap_id: create_swap_response.id.clone(),
121            side: None,
122            funding_addrs: Some(funding_addrs),
123            hashlock,
124            receiver_pubkey: create_swap_response.claim_public_key,
125            locktime: timelock,
126            sender_pubkey: our_pubkey,
127        })
128    }
129
130    pub fn musig_keyagg_cache(&self) -> musig::KeyAggCache {
131        match (self.swap_type, self.side.clone()) {
132            (SwapType::ReverseSubmarine, _) | (SwapType::Chain, Some(Side::Claim)) => {
133                let pubkeys = [self.sender_pubkey.inner, self.receiver_pubkey.inner];
134                let [a, b] = convert_pubkeys_for_musig(&pubkeys);
135                musig::KeyAggCache::new(&[&a, &b])
136            }
137
138            (SwapType::Submarine, _) | (SwapType::Chain, _) => {
139                let pubkeys = [self.receiver_pubkey.inner, self.sender_pubkey.inner];
140                let [a, b] = convert_pubkeys_for_musig(&pubkeys);
141                musig::KeyAggCache::new(&[&a, &b])
142            }
143        }
144    }
145
146    /// Create the struct for a reverse swap from a boltz create response.
147    pub fn reverse_from_swap_resp(
148        reverse_response: &CreateReverseResponse,
149        our_pubkey: PublicKey,
150    ) -> Result<Self, Error> {
151        let claim_script = ScriptBuf::from_hex(&reverse_response.swap_tree.claim_leaf.output)?;
152        let refund_script = ScriptBuf::from_hex(&reverse_response.swap_tree.refund_leaf.output)?;
153
154        let claim_instructions = claim_script.instructions();
155        let refund_instructions = refund_script.instructions();
156
157        let mut last_op = OP_0;
158        let mut hashlock = None;
159        let mut timelock = None;
160
161        for instruction in claim_instructions {
162            match instruction {
163                Ok(Instruction::PushBytes(bytes)) => {
164                    if bytes.len() == 20 {
165                        hashlock = Some(hash160::Hash::from_slice(bytes.as_bytes())?);
166                    } else {
167                        continue;
168                    }
169                }
170                _ => continue,
171            }
172        }
173
174        for instruction in refund_instructions {
175            match instruction {
176                Ok(Instruction::Op(opcode)) => last_op = opcode,
177                Ok(Instruction::PushBytes(bytes)) => {
178                    if last_op == OP_CHECKSIGVERIFY {
179                        timelock = Some(LockTime::from_consensus(bytes_to_u32_little_endian(
180                            bytes.as_bytes(),
181                        )));
182                    } else {
183                        continue;
184                    }
185                }
186                _ => continue,
187            }
188        }
189
190        let hashlock =
191            hashlock.ok_or_else(|| Error::Protocol("No hashlock provided".to_string()))?;
192
193        let timelock =
194            timelock.ok_or_else(|| Error::Protocol("No timelock provided".to_string()))?;
195
196        let funding_addrs = Address::from_str(&reverse_response.lockup_address)?.assume_checked();
197
198        Ok(BtcSwapScript {
199            swap_type: SwapType::ReverseSubmarine,
200            // swap_id: reverse_response.id.clone(),
201            side: None,
202            funding_addrs: Some(funding_addrs),
203            hashlock,
204            receiver_pubkey: our_pubkey,
205            locktime: timelock,
206            sender_pubkey: reverse_response.refund_public_key,
207        })
208    }
209
210    /// Create the struct for a chain swap from a boltz create response.
211    pub fn chain_from_swap_resp(
212        side: Side,
213        chain_swap_details: ChainSwapDetails,
214        our_pubkey: PublicKey,
215    ) -> Result<Self, Error> {
216        let claim_script = ScriptBuf::from_hex(&chain_swap_details.swap_tree.claim_leaf.output)?;
217        let refund_script = ScriptBuf::from_hex(&chain_swap_details.swap_tree.refund_leaf.output)?;
218
219        let claim_instructions = claim_script.instructions();
220        let refund_instructions = refund_script.instructions();
221
222        let mut last_op = OP_0;
223        let mut hashlock = None;
224        let mut timelock = None;
225
226        for instruction in claim_instructions {
227            match instruction {
228                Ok(Instruction::PushBytes(bytes)) => {
229                    if bytes.len() == 20 {
230                        hashlock = Some(hash160::Hash::from_slice(bytes.as_bytes())?);
231                    } else {
232                        continue;
233                    }
234                }
235                _ => continue,
236            }
237        }
238
239        for instruction in refund_instructions {
240            match instruction {
241                Ok(Instruction::Op(opcode)) => last_op = opcode,
242                Ok(Instruction::PushBytes(bytes)) => {
243                    if last_op == OP_CHECKSIGVERIFY {
244                        timelock = Some(LockTime::from_consensus(bytes_to_u32_little_endian(
245                            bytes.as_bytes(),
246                        )));
247                    } else {
248                        continue;
249                    }
250                }
251                _ => continue,
252            }
253        }
254
255        let hashlock =
256            hashlock.ok_or_else(|| Error::Protocol("No hashlock provided".to_string()))?;
257
258        let timelock =
259            timelock.ok_or_else(|| Error::Protocol("No timelock provided".to_string()))?;
260
261        let funding_addrs = Address::from_str(&chain_swap_details.lockup_address)?.assume_checked();
262
263        let (sender_pubkey, receiver_pubkey) = match side {
264            Side::Lockup => (our_pubkey, chain_swap_details.server_public_key),
265            Side::Claim => (chain_swap_details.server_public_key, our_pubkey),
266        };
267
268        Ok(BtcSwapScript {
269            swap_type: SwapType::Chain,
270            // swap_id: reverse_response.id.clone(),
271            side: Some(side),
272            funding_addrs: Some(funding_addrs),
273            hashlock,
274            receiver_pubkey,
275            locktime: timelock,
276            sender_pubkey,
277        })
278    }
279
280    fn claim_script(&self) -> ScriptBuf {
281        match self.swap_type {
282            SwapType::Submarine => Builder::new()
283                .push_opcode(OP_HASH160)
284                .push_slice(self.hashlock.to_byte_array())
285                .push_opcode(OP_EQUALVERIFY)
286                .push_x_only_key(&self.receiver_pubkey.inner.x_only_public_key().0)
287                .push_opcode(OP_CHECKSIG)
288                .into_script(),
289
290            SwapType::ReverseSubmarine | SwapType::Chain => Builder::new()
291                .push_opcode(OP_SIZE)
292                .push_int(32)
293                .push_opcode(OP_EQUALVERIFY)
294                .push_opcode(OP_HASH160)
295                .push_slice(self.hashlock.to_byte_array())
296                .push_opcode(OP_EQUALVERIFY)
297                .push_x_only_key(&self.receiver_pubkey.inner.x_only_public_key().0)
298                .push_opcode(OP_CHECKSIG)
299                .into_script(),
300        }
301    }
302
303    fn refund_script(&self) -> ScriptBuf {
304        // Refund scripts are same for all swap types
305        Builder::new()
306            .push_x_only_key(&self.sender_pubkey.inner.x_only_public_key().0)
307            .push_opcode(OP_CHECKSIGVERIFY)
308            .push_lock_time(self.locktime)
309            .push_opcode(OP_CLTV)
310            .into_script()
311    }
312
313    /// Internally used to convert struct into a bitcoin::Script type
314    fn taproot_spendinfo(&self) -> Result<TaprootSpendInfo, Error> {
315        let secp = Secp256k1::new();
316
317        // Setup Key Aggregation cache
318        // let pubkeys = [self.receiver_pubkey.inner, self.sender_pubkey.inner];
319
320        let key_agg_cache = self.musig_keyagg_cache();
321
322        // Construct the Taproot
323        let internal_key = key_agg_cache.agg_pk();
324
325        let taproot_builder = TaprootBuilder::new();
326
327        let taproot_builder =
328            taproot_builder.add_leaf_with_ver(1, self.claim_script(), LeafVersion::TapScript)?;
329        let taproot_builder =
330            taproot_builder.add_leaf_with_ver(1, self.refund_script(), LeafVersion::TapScript)?;
331
332        let taproot_spend_info =
333            match taproot_builder.finalize(&secp, convert_xonly_key(internal_key)) {
334                Ok(r) => r,
335                Err(e) => {
336                    return Err(Error::Taproot(format!(
337                        "Could not finalize taproot constructions: {e:?}"
338                    )))
339                }
340            };
341
342        // Verify taproot construction, only if we have funding address previously known.
343        // Which will be None only for regtest integration tests, so verification will be skipped for them.
344        if let Some(funding_address) = &self.funding_addrs {
345            let claim_key = taproot_spend_info.output_key();
346
347            let lockup_spk = funding_address.script_pubkey();
348
349            let pubkey_instruction = lockup_spk
350                .instructions()
351                .last()
352                .ok_or(Error::Protocol(
353                    "Script should contain at least one instruction".to_string(),
354                ))?
355                .map_err(|_| Error::Protocol("Failed to parse script instruction".to_string()))?;
356
357            let lockup_xonly_pubkey_bytes = pubkey_instruction.push_bytes().ok_or(
358                Error::Protocol("Expected push bytes instruction for pubkey".to_string()),
359            )?;
360
361            let lockup_xonly_pubkey =
362                XOnlyPublicKey::from_slice(lockup_xonly_pubkey_bytes.as_bytes())?;
363
364            if lockup_xonly_pubkey != claim_key.to_x_only_public_key() {
365                return Err(Error::Protocol(format!(
366                    "Taproot construction Failed. Lockup Pubkey: {lockup_xonly_pubkey}, Claim Pubkey {claim_key}"
367                )));
368            }
369
370            log::info!("Taproot creation and verification success!");
371        }
372
373        Ok(taproot_spend_info)
374    }
375
376    /// Get taproot address for the swap script.
377    pub fn to_address(&self, network: BitcoinChain) -> Result<Address, Error> {
378        let spend_info = self.taproot_spendinfo()?;
379        let output_key = spend_info.output_key();
380
381        let network = match network {
382            BitcoinChain::Bitcoin => Network::Bitcoin,
383            BitcoinChain::BitcoinRegtest => Network::Regtest,
384            BitcoinChain::BitcoinTestnet => Network::Testnet,
385        };
386
387        Ok(Address::p2tr_tweaked(output_key, network))
388    }
389
390    pub fn validate_address(&self, chain: BitcoinChain, address: String) -> Result<(), Error> {
391        let to_address = self.to_address(chain)?;
392        if to_address.to_string() == address {
393            Ok(())
394        } else {
395            Err(Error::Protocol("Script/LockupAddress Mismatch".to_string()))
396        }
397    }
398
399    /// Get the balance of the script
400    pub async fn get_balance<BC: BitcoinClient + ?Sized>(
401        &self,
402        bitcoin_client: &BC,
403    ) -> Result<(u64, i64), Error> {
404        bitcoin_client
405            .get_address_balance(&self.to_address(bitcoin_client.network())?)
406            .await
407    }
408
409    /// Fetch (utxo,amount) pairs for all utxos of the script_pubkey of this swap.
410    pub async fn fetch_utxos<BC: BitcoinClient + ?Sized>(
411        &self,
412        bitcoin_client: &BC,
413    ) -> Result<Vec<(OutPoint, TxOut)>, Error> {
414        bitcoin_client
415            .get_address_utxos(&self.to_address(bitcoin_client.network())?)
416            .await
417    }
418
419    pub(crate) async fn fetch_swap_utxo<BC: BitcoinClient + ?Sized>(
420        &self,
421        lockup_tx: Option<&Transaction>,
422        bitcoin_client: &BC,
423        boltz_client: &BoltzApiClientV2,
424        swap_id: &str,
425        tx_kind: SwapTxKind,
426    ) -> Result<(OutPoint, TxOut), Error> {
427        let outpoint = match lockup_tx {
428            Some(tx) => self.find_utxo(tx, bitcoin_client.network()),
429            None => match self.fetch_utxos(bitcoin_client).await {
430                Ok(v) => Ok(v.first().cloned()),
431                Err(_) => {
432                    self.fetch_lockup_utxo_boltz(
433                        bitcoin_client.network(),
434                        boltz_client,
435                        swap_id,
436                        tx_kind,
437                    )
438                    .await
439                }
440            },
441        }?;
442
443        outpoint.ok_or(Error::Protocol(
444            "No Bitcoin UTXO detected for this script".to_string(),
445        ))
446    }
447
448    pub(crate) fn find_utxo(
449        &self,
450        tx: &Transaction,
451        network: BitcoinChain,
452    ) -> Result<Option<(OutPoint, TxOut)>, Error> {
453        let address = self.to_address(network)?;
454        Ok(find_utxo(tx, &address.script_pubkey()))
455    }
456
457    /// Fetch utxo for script from BoltzApi
458    pub async fn fetch_lockup_utxo_boltz(
459        &self,
460        network: BitcoinChain,
461        boltz_client: &BoltzApiClientV2,
462        swap_id: &str,
463        tx_kind: SwapTxKind,
464    ) -> Result<Option<(OutPoint, TxOut)>, Error> {
465        let hex = match self.swap_type {
466            SwapType::Chain => match tx_kind {
467                SwapTxKind::Claim => {
468                    let chain_txs = boltz_client.get_chain_txs(swap_id).await?;
469                    chain_txs
470                        .server_lock
471                        .ok_or(Error::Protocol(
472                            "No server_lock transaction for Chain Swap available".to_string(),
473                        ))?
474                        .transaction
475                        .hex
476                }
477                SwapTxKind::Refund => {
478                    let chain_txs = boltz_client.get_chain_txs(swap_id).await?;
479                    chain_txs
480                        .user_lock
481                        .ok_or(Error::Protocol(
482                            "No user_lock transaction for Chain Swap available".to_string(),
483                        ))?
484                        .transaction
485                        .hex
486                }
487            },
488            SwapType::ReverseSubmarine => boltz_client.get_reverse_tx(swap_id).await?.hex,
489            SwapType::Submarine => boltz_client.get_submarine_tx(swap_id).await?.hex,
490        };
491        if hex.is_none() {
492            return Err(Error::Hex(
493                "No transaction hex found in boltz response".to_string(),
494            ));
495        }
496        let tx: Transaction = deserialize(&hex::decode(hex.unwrap())?)?;
497        self.find_utxo(&tx, network)
498    }
499}
500
501pub fn bytes_to_u32_little_endian(bytes: &[u8]) -> u32 {
502    let mut result = 0u32;
503    for (i, &byte) in bytes.iter().enumerate() {
504        result |= (byte as u32) << (8 * i);
505    }
506    result
507}
508
509/// A structure representing either a Claim or a Refund Tx.
510/// This Tx spends from the HTLC.
511#[derive(Debug, Clone)]
512pub struct BtcSwapTx {
513    pub kind: SwapTxKind, // These fields needs to be public to do manual creation in IT.
514    pub swap_script: BtcSwapScript,
515    pub output_address: Address,
516    /// All utxos for the script_pubkey of this swap, at this point in time:
517    /// - the initial lockup utxo, if not yet spent (claimed or refunded)
518    /// - any further utxos, if not yet spent
519    pub utxos: Vec<(OutPoint, TxOut)>,
520}
521
522impl BtcSwapTx {
523    /// Craft a new ClaimTx. Only works for Reverse and Chain Swaps.
524    /// Returns None, if the HTLC utxo doesn't exist for the swap.
525    pub async fn new_claim<BC: BitcoinClient + ?Sized>(
526        swap_script: BtcSwapScript,
527        claim_address: String,
528        bitcoin_client: &BC,
529        boltz_client: &BoltzApiClientV2,
530        swap_id: String,
531    ) -> Result<BtcSwapTx, Error> {
532        let utxo = swap_script
533            .fetch_swap_utxo(
534                None,
535                bitcoin_client,
536                boltz_client,
537                &swap_id,
538                SwapTxKind::Claim,
539            )
540            .await?;
541        Self::new_claim_with_utxo(swap_script, claim_address, bitcoin_client, utxo)
542    }
543
544    pub(crate) fn new_claim_with_utxo<BC: BitcoinClient + ?Sized>(
545        swap_script: BtcSwapScript,
546        claim_address: String,
547        bitcoin_client: &BC,
548        utxo: (OutPoint, TxOut),
549    ) -> Result<BtcSwapTx, Error> {
550        if swap_script.swap_type == SwapType::Submarine {
551            return Err(Error::Protocol(
552                "Claim transactions cannot be constructed for Submarine swaps.".to_string(),
553            ));
554        }
555
556        let address = Address::from_str(&claim_address)?;
557
558        address.is_valid_for_network(bitcoin_client.network().into());
559
560        Ok(BtcSwapTx {
561            kind: SwapTxKind::Claim,
562            swap_script,
563            output_address: address.assume_checked(),
564            utxos: vec![utxo], // When claiming, we only consider the first utxo
565        })
566    }
567
568    /// Construct a RefundTX corresponding to the swap_script. Only works for Submarine and Chain Swaps.
569    /// Returns None, if the HTLC UTXO for the swap doesn't exist in blockhcian.
570    pub async fn new_refund<BC: BitcoinClient + ?Sized>(
571        swap_script: BtcSwapScript,
572        refund_address: &str,
573        bitcoin_client: &BC,
574        boltz_client: &BoltzApiClientV2,
575        swap_id: String,
576    ) -> Result<BtcSwapTx, Error> {
577        if swap_script.swap_type == SwapType::ReverseSubmarine {
578            return Err(Error::Protocol(
579                "Refund Txs cannot be constructed for Reverse Submarine Swaps.".to_string(),
580            ));
581        }
582
583        let address = Address::from_str(refund_address)?;
584        if !address.is_valid_for_network(bitcoin_client.network().into()) {
585            return Err(Error::Address("Address validation failed".to_string()));
586        };
587
588        let utxos = match swap_script.fetch_utxos(bitcoin_client).await {
589            Ok(r) => r,
590            Err(_) => {
591                let lockup_utxo_info = swap_script
592                    .fetch_lockup_utxo_boltz(
593                        bitcoin_client.network(),
594                        boltz_client,
595                        &swap_id,
596                        SwapTxKind::Refund,
597                    )
598                    .await?;
599
600                match lockup_utxo_info {
601                    Some(r) => vec![r],
602                    None => vec![],
603                }
604            }
605        };
606
607        match utxos.is_empty() {
608            true => Err(Error::Protocol(
609                "No Bitcoin UTXO detected for this script".to_string(),
610            )),
611            false => Ok(BtcSwapTx {
612                kind: SwapTxKind::Refund,
613                swap_script,
614                output_address: address.assume_checked(),
615                utxos,
616            }),
617        }
618    }
619
620    /// Compute the Musig partial signature.
621    /// This is used to cooperatively settle a Submarine or Chain Swap.
622    pub fn partial_sign(
623        &self,
624        keys: &Keypair,
625        pub_nonce: &str,
626        transaction_hash: &str,
627    ) -> Result<(musig::PartialSignature, musig::PublicNonce), Error> {
628        self.swap_script
629            .partial_sign(keys, pub_nonce, transaction_hash)
630    }
631
632    /// Sign a claim transaction.
633    /// Errors if called on a Submarine Swap or Refund Tx.
634    /// If the claim is cooperative, provide the other party's partial sigs.
635    /// If this is None, transaction will be claimed via taproot script path.
636    pub async fn sign_claim(
637        &self,
638        keys: &Keypair,
639        preimage: &Preimage,
640        fee: Fee,
641        is_cooperative: Option<Cooperative<'_>>,
642    ) -> Result<Transaction, Error> {
643        if self.swap_script.swap_type == SwapType::Submarine {
644            return Err(Error::Protocol(
645                "Claim Tx signing is not applicable for Submarine Swaps".to_string(),
646            ));
647        }
648
649        if self.kind == SwapTxKind::Refund {
650            return Err(Error::Protocol(
651                "Cannot sign claim with refund-type BtcSwapTx".to_string(),
652            ));
653        }
654
655        if self.utxos.is_empty() {
656            return Err(Error::Protocol(
657                "No Bitcoin UTXO available for claim transaction".to_string(),
658            ));
659        }
660
661        let mut claim_tx = create_tx_with_fee(
662            fee,
663            |fee| self.create_claim(keys, preimage, fee, is_cooperative.is_some()),
664            |tx| tx.vsize(),
665        )?;
666
667        // If it's a cooperative claim, compute the Musig2 Aggregate Signature and use Keypath spending
668        if let Some(Cooperative {
669            boltz_api,
670            swap_id,
671            signature,
672        }) = is_cooperative
673        {
674            let secp = Secp256k1::new();
675
676            // Start the Musig session
677            // Step 1: Get the sighash
678            let claim_tx_taproot_hash = SighashCache::new(claim_tx.clone())
679                .taproot_key_spend_signature_hash(
680                    0,
681                    &Prevouts::All(&[&self.utxos.first().unwrap().1]),
682                    bitcoin::TapSighashType::Default,
683                )?;
684
685            let msg = *claim_tx_taproot_hash.as_byte_array();
686
687            // Step 2: Get the Public and Secret nonces
688            let mut key_agg_cache = self.swap_script.musig_keyagg_cache();
689
690            let tweak = Scalar::from_be_bytes(
691                *self
692                    .swap_script
693                    .taproot_spendinfo()?
694                    .tap_tweak()
695                    .as_byte_array(),
696            )?;
697
698            let _ = key_agg_cache.pubkey_xonly_tweak_add(&tweak)?;
699
700            let session_secret_rand =
701                musig::SessionSecretRand::assume_unique_per_nonce_gen(rng_32b());
702
703            let mut extra_rand = [0u8; 32];
704            OsRng.fill_bytes(&mut extra_rand);
705
706            let (claim_sec_nonce, claim_pub_nonce) = key_agg_cache.nonce_gen(
707                session_secret_rand,
708                convert_public_key(keys.public_key()),
709                &msg,
710                Some(extra_rand),
711            );
712
713            // Step 7: Get boltz's partial sig
714            let claim_tx_hex = claim_tx.serialize().to_lower_hex_string();
715            let partial_sig_resp = match self.swap_script.swap_type {
716                SwapType::Chain => {
717                    boltz_api
718                        .post_chain_claim_tx_details(
719                            &swap_id,
720                            preimage,
721                            signature,
722                            ToSign {
723                                pub_nonce: claim_pub_nonce.serialize().to_lower_hex_string(),
724                                transaction: claim_tx_hex,
725                                index: 0,
726                            },
727                        )
728                        .await
729                }
730                SwapType::ReverseSubmarine => {
731                    boltz_api
732                        .get_reverse_partial_sig(
733                            &swap_id,
734                            preimage,
735                            &claim_pub_nonce,
736                            &claim_tx_hex,
737                        )
738                        .await
739                }
740                _ => Err(Error::Protocol(format!(
741                    "Cannot get partial sig for {:?} Swap",
742                    self.swap_script.swap_type
743                ))),
744            }?;
745
746            let boltz_public_nonce = musig::PublicNonce::from_str(&partial_sig_resp.pub_nonce)?;
747
748            let boltz_partial_sig =
749                musig::PartialSignature::from_str(&partial_sig_resp.partial_signature)?;
750
751            // Aggregate Our's and Other's Nonce and start the Musig session.
752            let agg_nonce = musig::AggregatedNonce::new(&[&boltz_public_nonce, &claim_pub_nonce]);
753
754            let musig_session = musig::Session::new(&key_agg_cache, agg_nonce, &msg);
755
756            // Verify the Boltz's sig.
757            let boltz_partial_sig_verify = musig_session.partial_verify(
758                &key_agg_cache,
759                &boltz_partial_sig,
760                &boltz_public_nonce,
761                convert_public_key(self.swap_script.sender_pubkey.inner),
762            );
763
764            if !boltz_partial_sig_verify {
765                return Err(Error::Protocol(
766                    "Invalid partial-sig received from Boltz".to_string(),
767                ));
768            }
769
770            let our_partial_sig =
771                musig_session.partial_sign(claim_sec_nonce, &convert_keypair(keys), &key_agg_cache);
772
773            let schnorr_sig = musig_session
774                .partial_sig_agg(&[&boltz_partial_sig, &our_partial_sig])
775                .assume_valid();
776
777            let final_schnorr_sig = Signature {
778                signature: convert_schnorr_signature(schnorr_sig),
779                sighash_type: TapSighashType::Default,
780            };
781
782            let output_key = self.swap_script.taproot_spendinfo()?.output_key();
783
784            secp.verify_schnorr(
785                &final_schnorr_sig.signature,
786                &bitcoin::secp256k1::Message::from_digest_slice(&msg)?,
787                &output_key.to_x_only_public_key(),
788            )?;
789
790            let mut witness = Witness::new();
791            witness.push(final_schnorr_sig.to_vec());
792
793            claim_tx.input[0].witness = witness;
794        }
795
796        Ok(claim_tx)
797    }
798
799    fn create_claim(
800        &self,
801        keys: &Keypair,
802        preimage: &Preimage,
803        absolute_fees: u64,
804        is_cooperative: bool,
805    ) -> Result<Transaction, Error> {
806        if preimage.bytes.is_none() {
807            return Err(Error::Protocol(
808                "No preimage provided while signing.".to_string(),
809            ));
810        };
811
812        // For claim, we only consider 1 utxo
813        let utxo = self.utxos.first().ok_or(Error::Protocol(
814            "No Bitcoin UTXO detected for this script".to_string(),
815        ))?;
816
817        let txin = TxIn {
818            previous_output: utxo.0,
819            sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
820            script_sig: ScriptBuf::new(),
821            witness: Witness::new(),
822        };
823
824        let destination_spk = self.output_address.script_pubkey();
825
826        let output_value = utxo
827            .1
828            .value
829            .checked_sub(Amount::from_sat(absolute_fees))
830            .ok_or(Error::Protocol(format!(
831                "Claim output value {} is less than fees {}",
832                utxo.1.value, absolute_fees
833            )))?;
834
835        let txout = TxOut {
836            script_pubkey: destination_spk,
837            value: output_value,
838        };
839
840        let mut claim_tx = Transaction {
841            version: Version::TWO,
842            lock_time: LockTime::ZERO,
843            input: vec![txin],
844            output: vec![txout],
845        };
846
847        if is_cooperative {
848            claim_tx.input[0].witness = Self::stubbed_cooperative_witness();
849        } else {
850            let secp = Secp256k1::new();
851
852            // If Non-Cooperative claim use the Script Path spending
853            claim_tx.input[0].sequence = Sequence::ZERO;
854
855            let leaf_hash =
856                TapLeafHash::from_script(&self.swap_script.claim_script(), LeafVersion::TapScript);
857
858            let sighash = SighashCache::new(claim_tx.clone()).taproot_script_spend_signature_hash(
859                0,
860                &Prevouts::All(&[&utxo.1]),
861                leaf_hash,
862                TapSighashType::Default,
863            )?;
864
865            let msg = Message::from_digest_slice(sighash.as_byte_array())?;
866
867            let signature = secp.sign_schnorr(&msg, keys);
868
869            let final_sig = Signature {
870                signature,
871                sighash_type: TapSighashType::Default,
872            };
873
874            let control_block = self
875                .swap_script
876                .taproot_spendinfo()?
877                .control_block(&(self.swap_script.claim_script(), LeafVersion::TapScript))
878                .ok_or(Error::Taproot(
879                    "Control block calculation failed".to_string(),
880                ))?;
881
882            let mut witness = Witness::new();
883
884            witness.push(final_sig.to_vec());
885            witness.push(preimage.bytes.ok_or(Error::Protocol(
886                "Preimage bytes not available - cannot claim without actual preimage".to_string(),
887            ))?);
888            witness.push(self.swap_script.claim_script().as_bytes());
889            witness.push(control_block.serialize());
890
891            claim_tx.input[0].witness = witness;
892        }
893
894        Ok(claim_tx)
895    }
896
897    /// Sign a refund transaction.
898    /// Errors if called for a Reverse Swap.
899    pub async fn sign_refund(
900        &self,
901        keys: &Keypair,
902        fee: Fee,
903        is_cooperative: Option<Cooperative<'_>>,
904    ) -> Result<Transaction, Error> {
905        if self.swap_script.swap_type == SwapType::ReverseSubmarine {
906            return Err(Error::Protocol(
907                "Refund Tx signing is not applicable for Reverse Submarine Swaps".to_string(),
908            ));
909        }
910
911        if self.kind == SwapTxKind::Claim {
912            return Err(Error::Protocol(
913                "Cannot sign refund with a claim-type BtcSwapTx".to_string(),
914            ));
915        }
916
917        let mut refund_tx = create_tx_with_fee(
918            fee,
919            |fee| self.create_refund(keys, fee, is_cooperative.is_some()),
920            |tx| tx.vsize(),
921        )?;
922
923        if let Some(Cooperative {
924            boltz_api, swap_id, ..
925        }) = is_cooperative
926        {
927            let secp = Secp256k1::new();
928
929            // Start the Musig session
930            refund_tx.lock_time = LockTime::ZERO; // No locktime for cooperative spend
931
932            for input_index in 0..refund_tx.input.len() {
933                // Step 1: Get the sighash
934                let tx_outs: Vec<&TxOut> = self.utxos.iter().map(|(_, out)| out).collect();
935                let refund_tx_taproot_hash = SighashCache::new(refund_tx.clone())
936                    .taproot_key_spend_signature_hash(
937                        input_index,
938                        &Prevouts::All(&tx_outs),
939                        bitcoin::TapSighashType::Default,
940                    )?;
941
942                let msg = *refund_tx_taproot_hash.as_byte_array();
943
944                // Step 2: Get the Public and Secret nonces
945                let mut key_agg_cache = self.swap_script.musig_keyagg_cache();
946
947                let tweak = Scalar::from_be_bytes(
948                    *self
949                        .swap_script
950                        .taproot_spendinfo()?
951                        .tap_tweak()
952                        .as_byte_array(),
953                )?;
954
955                let _ = key_agg_cache.pubkey_xonly_tweak_add(&tweak)?;
956
957                let session_secret_rand =
958                    musig::SessionSecretRand::assume_unique_per_nonce_gen(rng_32b());
959
960                let mut extra_rand = [0u8; 32];
961                OsRng.fill_bytes(&mut extra_rand);
962
963                let (sec_nonce, pub_nonce) = key_agg_cache.nonce_gen(
964                    session_secret_rand,
965                    convert_public_key(keys.public_key()),
966                    &msg,
967                    Some(extra_rand),
968                );
969
970                // Step 7: Get boltz's partial sig
971                let refund_tx_hex = refund_tx.serialize().to_lower_hex_string();
972                let partial_sig_resp = match self.swap_script.swap_type {
973                    SwapType::Chain => {
974                        boltz_api
975                            .get_chain_partial_sig(
976                                &swap_id,
977                                input_index,
978                                &pub_nonce,
979                                &refund_tx_hex,
980                            )
981                            .await
982                    }
983                    SwapType::Submarine => {
984                        boltz_api
985                            .get_submarine_partial_sig(
986                                &swap_id,
987                                input_index,
988                                &pub_nonce,
989                                &refund_tx_hex,
990                            )
991                            .await
992                    }
993                    _ => Err(Error::Protocol(format!(
994                        "Cannot get partial sig for {:?} Swap",
995                        self.swap_script.swap_type
996                    ))),
997                }?;
998
999                let boltz_public_nonce = musig::PublicNonce::from_str(&partial_sig_resp.pub_nonce)?;
1000
1001                let boltz_partial_sig =
1002                    musig::PartialSignature::from_str(&partial_sig_resp.partial_signature)?;
1003
1004                // Aggregate Our's and Other's Nonce and start the Musig session.
1005                let agg_nonce = musig::AggregatedNonce::new(&[&boltz_public_nonce, &pub_nonce]);
1006
1007                let musig_session = musig::Session::new(&key_agg_cache, agg_nonce, &msg);
1008
1009                // Verify the Boltz's sig.
1010                let boltz_partial_sig_verify = musig_session.partial_verify(
1011                    &key_agg_cache,
1012                    &boltz_partial_sig,
1013                    &boltz_public_nonce,
1014                    convert_public_key(self.swap_script.receiver_pubkey.inner), //boltz key
1015                );
1016
1017                if !boltz_partial_sig_verify {
1018                    return Err(Error::Protocol(
1019                        "Invalid partial-sig received from Boltz".to_string(),
1020                    ));
1021                }
1022
1023                let our_partial_sig =
1024                    musig_session.partial_sign(sec_nonce, &convert_keypair(keys), &key_agg_cache);
1025
1026                let schnorr_sig = musig_session
1027                    .partial_sig_agg(&[&boltz_partial_sig, &our_partial_sig])
1028                    .assume_valid();
1029
1030                let final_schnorr_sig = Signature {
1031                    signature: convert_schnorr_signature(schnorr_sig),
1032                    sighash_type: TapSighashType::Default,
1033                };
1034
1035                let output_key = self.swap_script.taproot_spendinfo()?.output_key();
1036
1037                secp.verify_schnorr(
1038                    &final_schnorr_sig.signature,
1039                    &bitcoin::secp256k1::Message::from_digest_slice(&msg)?,
1040                    &output_key.to_x_only_public_key(),
1041                )?;
1042
1043                let mut witness = Witness::new();
1044                witness.push(final_schnorr_sig.to_vec());
1045                refund_tx.input[input_index].witness = witness;
1046            }
1047        }
1048
1049        Ok(refund_tx)
1050    }
1051
1052    fn create_refund(
1053        &self,
1054        keys: &Keypair,
1055        absolute_fees: u64,
1056        is_cooperative: bool,
1057    ) -> Result<Transaction, Error> {
1058        let utxos_amount = self
1059            .utxos
1060            .iter()
1061            .fold(Amount::ZERO, |acc, (_, txo)| acc + txo.value);
1062        let absolute_fees_amount = Amount::from_sat(absolute_fees);
1063        let output_amount =
1064            utxos_amount
1065                .checked_sub(absolute_fees_amount)
1066                .ok_or(Error::Protocol(format!(
1067                    "Refund output value {utxos_amount} is less than fees {absolute_fees_amount}"
1068                )))?;
1069        let output: TxOut = TxOut {
1070            script_pubkey: self.output_address.script_pubkey(),
1071            value: output_amount,
1072        };
1073
1074        let unsigned_inputs = self
1075            .utxos
1076            .iter()
1077            .map(|(outpoint, _txo)| TxIn {
1078                previous_output: *outpoint,
1079                script_sig: ScriptBuf::new(),
1080                sequence: Sequence::MAX,
1081                witness: Witness::new(),
1082            })
1083            .collect();
1084
1085        let lock_time = match self
1086            .swap_script
1087            .refund_script()
1088            .instructions()
1089            .filter_map(|i| {
1090                let ins = i.ok()?;
1091                if let Instruction::PushBytes(bytes) = ins {
1092                    if bytes.len() < 5_usize {
1093                        Some(LockTime::from_consensus(bytes_to_u32_little_endian(
1094                            bytes.as_bytes(),
1095                        )))
1096                    } else {
1097                        None
1098                    }
1099                } else {
1100                    None
1101                }
1102            })
1103            .next()
1104        {
1105            Some(r) => r,
1106            None => {
1107                return Err(Error::Protocol(
1108                    "Error getting timelock from refund script".to_string(),
1109                ))
1110            }
1111        };
1112
1113        let mut refund_tx = Transaction {
1114            version: Version::TWO,
1115            lock_time,
1116            input: unsigned_inputs,
1117            output: vec![output],
1118        };
1119
1120        let tx_outs: Vec<&TxOut> = self.utxos.iter().map(|(_, out)| out).collect();
1121
1122        if is_cooperative {
1123            for index in 0..refund_tx.input.len() {
1124                refund_tx.input[index].witness = Self::stubbed_cooperative_witness();
1125            }
1126        } else {
1127            let leaf_hash =
1128                TapLeafHash::from_script(&self.swap_script.refund_script(), LeafVersion::TapScript);
1129
1130            let control_block = self
1131                .swap_script
1132                .taproot_spendinfo()?
1133                .control_block(&(
1134                    self.swap_script.refund_script().clone(),
1135                    LeafVersion::TapScript,
1136                ))
1137                .ok_or(Error::Protocol(
1138                    "Control block calculation failed".to_string(),
1139                ))?;
1140
1141            // Input sequence has to be set for all inputs before signing
1142            for input_index in 0..refund_tx.input.len() {
1143                refund_tx.input[input_index].sequence = Sequence::ZERO;
1144            }
1145
1146            for input_index in 0..refund_tx.input.len() {
1147                let sighash = SighashCache::new(refund_tx.clone())
1148                    .taproot_script_spend_signature_hash(
1149                        input_index,
1150                        &Prevouts::All(&tx_outs),
1151                        leaf_hash,
1152                        TapSighashType::Default,
1153                    )?;
1154
1155                let msg = Message::from_digest_slice(sighash.as_byte_array())?;
1156
1157                let signature = Secp256k1::new().sign_schnorr(&msg, keys);
1158
1159                let final_sig = Signature {
1160                    signature,
1161                    sighash_type: TapSighashType::Default,
1162                };
1163
1164                let mut witness = Witness::new();
1165                witness.push(final_sig.to_vec());
1166                witness.push(self.swap_script.refund_script().as_bytes());
1167                witness.push(control_block.serialize());
1168                refund_tx.input[input_index].witness = witness;
1169            }
1170        }
1171
1172        Ok(refund_tx)
1173    }
1174
1175    fn stubbed_cooperative_witness() -> Witness {
1176        let mut witness = Witness::new();
1177        // Stub because we don't want to create cooperative signatures here
1178        // but still be able to have an accurate size estimation
1179        witness.push([0; 64]);
1180        witness
1181    }
1182
1183    /// Calculate the size of a transaction.
1184    /// Use this before calling drain to help calculate the absolute fees.
1185    /// Multiply the size by the fee_rate to get the absolute fees.
1186    pub fn size(&self, keys: &Keypair, is_cooperative: bool) -> Result<usize, Error> {
1187        let dummy_abs_fee = 1;
1188        let tx = match self.kind {
1189            SwapTxKind::Claim => {
1190                let preimage = Preimage::from_vec([0; 32].to_vec())?;
1191                self.create_claim(keys, &preimage, dummy_abs_fee, is_cooperative)?
1192            }
1193            SwapTxKind::Refund => self.create_refund(keys, dummy_abs_fee, is_cooperative)?,
1194        };
1195        Ok(tx.vsize())
1196    }
1197
1198    /// Broadcast transaction to the network.
1199    pub async fn broadcast<BC: BitcoinClient + ?Sized>(
1200        &self,
1201        signed_tx: &Transaction,
1202        bitcoin_client: &BC,
1203    ) -> Result<Txid, Error> {
1204        bitcoin_client.broadcast_tx(signed_tx).await
1205    }
1206}
1207
1208impl SwapScriptCommon for BtcSwapScript {
1209    fn swap_type(&self) -> SwapType {
1210        self.swap_type
1211    }
1212
1213    /// Compute the Musig partial signature.
1214    /// This is used to cooperatively settle a Submarine or Chain Swap.
1215    fn partial_sign(
1216        &self,
1217        keys: &Keypair,
1218        pub_nonce: &str,
1219        transaction_hash: &str,
1220    ) -> Result<(musig::PartialSignature, musig::PublicNonce), Error> {
1221        // Step 1: Start with a Musig KeyAgg Cache
1222
1223        let mut key_agg_cache = self.musig_keyagg_cache();
1224
1225        let tweak = Scalar::from_be_bytes(*self.taproot_spendinfo()?.tap_tweak().as_byte_array())?;
1226
1227        let _ = key_agg_cache.pubkey_xonly_tweak_add(&tweak)?;
1228
1229        let session_secret_rand = musig::SessionSecretRand::assume_unique_per_nonce_gen(rng_32b());
1230
1231        let msg = hex_to_bytes32(transaction_hash)?;
1232
1233        // Step 4: Start the Musig2 Signing session
1234        let mut extra_rand = [0u8; 32];
1235        OsRng.fill_bytes(&mut extra_rand);
1236
1237        let (gen_sec_nonce, gen_pub_nonce) = key_agg_cache.nonce_gen(
1238            session_secret_rand,
1239            convert_public_key(keys.public_key()),
1240            &msg,
1241            Some(extra_rand),
1242        );
1243
1244        let boltz_nonce = musig::PublicNonce::from_str(pub_nonce)?;
1245
1246        let agg_nonce = musig::AggregatedNonce::new(&[&boltz_nonce, &gen_pub_nonce]);
1247
1248        let musig_session = musig::Session::new(&key_agg_cache, agg_nonce, &msg);
1249
1250        let partial_sig =
1251            musig_session.partial_sign(gen_sec_nonce, &convert_keypair(keys), &key_agg_cache);
1252
1253        Ok((partial_sig, gen_pub_nonce))
1254    }
1255}
1256
1257fn convert_pubkeys_for_musig(
1258    pubkeys: &[bitcoin::secp256k1::PublicKey; 2],
1259) -> [secp256k1_musig::PublicKey; 2] {
1260    [
1261        convert_public_key(pubkeys[0]),
1262        convert_public_key(pubkeys[1]),
1263    ]
1264}
1265
1266fn convert_xonly_key(key: secp256k1_musig::XOnlyPublicKey) -> bitcoin::XOnlyPublicKey {
1267    bitcoin::XOnlyPublicKey::from_slice(&key.serialize()[..]).expect("xonly key size matches")
1268}
1269
1270fn convert_public_key(key: bitcoin::secp256k1::PublicKey) -> secp256k1_musig::PublicKey {
1271    secp256k1_musig::PublicKey::from_slice(&key.serialize()[..]).expect("public key size matches")
1272}
1273
1274fn convert_keypair(keys: &bitcoin::secp256k1::Keypair) -> secp256k1_musig::Keypair {
1275    secp256k1_musig::Keypair::from_seckey_byte_array(keys.secret_bytes())
1276        .expect("keypair size matches")
1277}
1278
1279fn convert_schnorr_signature(
1280    schnorr_sig: secp256k1_musig::schnorr::Signature,
1281) -> bitcoin::secp256k1::schnorr::Signature {
1282    bitcoin::secp256k1::schnorr::Signature::from_slice(schnorr_sig.as_byte_array())
1283        .expect("signature size matches")
1284}