Skip to main content

ddk_dlc/
lib.rs

1//! # Rust DLC Library
2//! Library for creating, signing and verifying transactions for the
3//! Discreet Log Contract protocol.
4//!
5
6// Coding conventions
7#![deny(non_upper_case_globals)]
8#![deny(non_camel_case_types)]
9#![deny(non_snake_case)]
10#![deny(unused_mut)]
11#![deny(dead_code)]
12#![deny(unused_imports)]
13#![deny(missing_docs)]
14
15#[cfg(not(feature = "std"))]
16extern crate alloc;
17extern crate bitcoin;
18extern crate core;
19extern crate miniscript;
20extern crate secp256k1_sys;
21pub extern crate secp256k1_zkp;
22#[cfg(feature = "use-serde")]
23extern crate serde;
24
25use bitcoin::secp256k1::Scalar;
26use bitcoin::transaction::Version;
27use bitcoin::Amount;
28use bitcoin::{
29    absolute::LockTime,
30    blockdata::{
31        opcodes,
32        script::{Builder, Script, ScriptBuf},
33        transaction::{OutPoint, Transaction, TxIn, TxOut},
34    },
35    Sequence, Witness,
36};
37use secp256k1_zkp::schnorr::Signature as SchnorrSignature;
38use secp256k1_zkp::{
39    ecdsa::Signature, EcdsaAdaptorSignature, Message, PublicKey, Secp256k1, SecretKey,
40    Verification, XOnlyPublicKey,
41};
42#[cfg(feature = "use-serde")]
43use serde::{Deserialize, Serialize};
44use std::fmt;
45
46// use crate::dlc_input::calculate_total_dlc_input_amount;
47
48pub mod channel;
49pub mod dlc_input;
50pub mod secp_utils;
51pub mod util;
52
53/// Minimum value that can be included in a transaction output. Under this value,
54/// outputs are discarded
55/// See: https://github.com/discreetlogcontracts/dlcspecs/blob/master/Transactions.md#change-outputs
56const DUST_LIMIT: Amount = Amount::from_sat(1000);
57
58/// Bit 0 of `contract_flags`: redirect all refund proceeds to the accepter.
59pub const REFUND_TO_ACCEPTER_FLAG: u8 = 0x01;
60
61/// The transaction version
62/// See: https://github.com/discreetlogcontracts/dlcspecs/blob/master/Transactions.md#funding-transaction
63const TX_VERSION: Version = Version::TWO;
64
65/// The base weight of a fund transaction
66/// See: https://github.com/discreetlogcontracts/dlcspecs/blob/master/Transactions.md#fees
67const FUND_TX_BASE_WEIGHT: usize = 214;
68
69/// The weight of a CET excluding payout outputs
70/// See: https://github.com/discreetlogcontracts/dlcspecs/blob/master/Transactions.md#fees
71const CET_BASE_WEIGHT: usize = 500;
72
73/// The base weight of a transaction input computed as: (outpoint(36) + sequence(4) + scriptPubKeySize(1)) * 4
74/// See: <https://github.com/discreetlogcontracts/dlcspecs/blob/master/Transactions.md#fees>
75const TX_INPUT_BASE_WEIGHT: usize = 164;
76
77/// The witness size of a P2WPKH input
78/// See: <https://github.com/discreetlogcontracts/dlcspecs/blob/master/Transactions.md#fees>
79pub const P2WPKH_WITNESS_SIZE: usize = 108;
80
81macro_rules! checked_add {
82    ($a: expr, $b: expr) => {
83        $a.checked_add($b).ok_or(Error::InvalidArgument(format!(
84            "Checked add failed: {} + {}",
85            $a, $b
86        )))
87    };
88    ($a: expr, $b: expr, $c: expr) => {
89        checked_add!(checked_add!($a, $b)?, $c)
90    };
91    ($a: expr, $b: expr, $c: expr, $d: expr) => {
92        checked_add!(checked_add!($a, $b, $c)?, $d)
93    };
94}
95
96/// Represents the payouts for a unique contract outcome. Offer party represents
97/// the initiator of the contract while accept party represents the party
98/// accepting the contract.
99#[derive(Eq, PartialEq, Debug, Clone)]
100#[cfg_attr(feature = "use-serde", derive(Serialize, Deserialize))]
101pub struct Payout {
102    /// Payout for the offering party
103    pub offer: Amount,
104    /// Payout for the accepting party
105    pub accept: Amount,
106}
107
108#[derive(Eq, PartialEq, Debug, Clone)]
109/// Representation of a set of contiguous outcomes that share a single payout.
110pub struct RangePayout {
111    /// The start of the range
112    pub start: usize,
113    /// The number of outcomes in the range
114    pub count: usize,
115    /// The payout associated with all outcomes
116    pub payout: Payout,
117}
118
119/// Representation of a payout for an enumeration outcome.
120#[derive(Clone, Debug)]
121#[cfg_attr(feature = "use-serde", derive(Serialize, Deserialize))]
122pub struct EnumerationPayout {
123    /// The outcome value (prior to hashing)
124    pub outcome: String,
125    /// The corresponding payout
126    pub payout: Payout,
127}
128
129/// Contains the necessary transactions for establishing a DLC
130#[derive(Clone)]
131#[cfg_attr(feature = "use-serde", derive(Serialize, Deserialize))]
132pub struct DlcTransactions {
133    /// The fund transaction locking both parties collaterals
134    pub fund: Transaction,
135    /// The contract execution transactions for closing the contract on a
136    /// certain outcome
137    pub cets: Vec<Transaction>,
138    /// The refund transaction for returning the collateral for each party in
139    /// case of an oracle misbehavior
140    pub refund: Transaction,
141
142    /// The script pubkey of the fund output in the fund transaction
143    pub funding_script_pubkey: ScriptBuf,
144
145    /// Pending cooperative close offers.
146    pub pending_close_txs: Vec<Transaction>,
147}
148
149impl DlcTransactions {
150    /// Get the fund output in the fund transaction
151    pub fn get_fund_output(&self) -> &TxOut {
152        let v0_witness_fund_script = self.funding_script_pubkey.to_p2wsh();
153        util::get_output_for_script_pubkey(&self.fund, &v0_witness_fund_script)
154            .unwrap()
155            .1
156    }
157
158    /// Get the fund output in the fund transaction
159    pub fn get_fund_output_index(&self) -> usize {
160        let v0_witness_fund_script = self.funding_script_pubkey.to_p2wsh();
161        util::get_output_for_script_pubkey(&self.fund, &v0_witness_fund_script)
162            .unwrap()
163            .0
164    }
165
166    /// Get the outpoint for the fund output in the fund transaction
167    pub fn get_fund_outpoint(&self) -> OutPoint {
168        OutPoint {
169            txid: self.fund.compute_txid(),
170            vout: self.get_fund_output_index() as u32,
171        }
172    }
173}
174
175/// Contains info about a utxo used for funding a DLC contract
176#[derive(Clone, Debug)]
177#[cfg_attr(
178    feature = "use-serde",
179    derive(Serialize, Deserialize),
180    serde(rename_all = "camelCase")
181)]
182pub struct TxInputInfo {
183    /// The outpoint for the utxo
184    pub outpoint: OutPoint,
185    /// The maximum witness length
186    pub max_witness_len: usize,
187    /// The redeem script
188    pub redeem_script: ScriptBuf,
189    /// The serial id for the input that will be used for ordering inputs of
190    /// the fund transaction
191    pub serial_id: u64,
192}
193
194/// Structure containing oracle information for a single event.
195#[derive(Clone, Debug)]
196pub struct OracleInfo {
197    /// The public key of the oracle.
198    pub public_key: XOnlyPublicKey,
199    /// The nonces that the oracle will use to attest to the event.
200    pub nonces: Vec<XOnlyPublicKey>,
201}
202
203/// An error code.
204#[derive(Debug)]
205pub enum Error {
206    /// Secp256k1 error
207    Secp256k1(secp256k1_zkp::Error),
208    /// An error while computing a p2wpkh signature hash
209    P2wpkh(bitcoin::sighash::P2wpkhError),
210    /// An invalid argument was provided
211    InvalidArgument(String),
212    /// An error occurred in miniscript
213    Miniscript(miniscript::Error),
214    /// Error attempting to do an out of bounds access on the transaction inputs vector.
215    InputsIndex(bitcoin::transaction::InputsIndexError),
216}
217
218impl From<secp256k1_zkp::Error> for Error {
219    fn from(error: secp256k1_zkp::Error) -> Error {
220        Error::Secp256k1(error)
221    }
222}
223
224impl From<secp256k1_zkp::UpstreamError> for Error {
225    fn from(error: secp256k1_zkp::UpstreamError) -> Error {
226        Error::Secp256k1(secp256k1_zkp::Error::Upstream(error))
227    }
228}
229
230impl From<bitcoin::sighash::P2wpkhError> for Error {
231    fn from(error: bitcoin::sighash::P2wpkhError) -> Error {
232        Error::P2wpkh(error)
233    }
234}
235
236impl From<bitcoin::transaction::InputsIndexError> for Error {
237    fn from(error: bitcoin::transaction::InputsIndexError) -> Error {
238        Error::InputsIndex(error)
239    }
240}
241
242impl From<miniscript::Error> for Error {
243    fn from(error: miniscript::Error) -> Error {
244        Error::Miniscript(error)
245    }
246}
247
248impl fmt::Display for Error {
249    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
250        match *self {
251            Error::Secp256k1(ref e) => write!(f, "Secp256k1 error: {e}"),
252            Error::InvalidArgument(ref e) => write!(f, "Invalid argument: {e}"),
253            Error::P2wpkh(ref e) => write!(f, "Error while computing p2wpkh sighash: {e}"),
254            Error::InputsIndex(ref e) => write!(f, "Error ordering inputs: {e}"),
255            Error::Miniscript(_) => write!(f, "Error within miniscript"),
256        }
257    }
258}
259
260#[cfg(feature = "std")]
261impl std::error::Error for Error {
262    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
263        match self {
264            Error::Secp256k1(e) => Some(e),
265            Error::P2wpkh(e) => Some(e),
266            Error::InputsIndex(e) => Some(e),
267            Error::InvalidArgument(_) => None,
268            Error::Miniscript(e) => Some(e),
269        }
270    }
271}
272
273/// Contains the parameters required for creating DLC transactions for a single
274/// party. Specifically these are the common fields between Offer and Accept
275/// messages.
276#[derive(Clone, Debug)]
277#[cfg_attr(
278    feature = "use-serde",
279    derive(Serialize, Deserialize),
280    serde(rename_all = "camelCase")
281)]
282pub struct PartyParams {
283    /// The public key for the fund multisig script
284    pub fund_pubkey: PublicKey,
285    /// An address to receive change
286    pub change_script_pubkey: ScriptBuf,
287    /// Id used to order fund outputs
288    pub change_serial_id: u64,
289    /// An address to receive the outcome amount
290    pub payout_script_pubkey: ScriptBuf,
291    /// Id used to order CET outputs
292    pub payout_serial_id: u64,
293    /// A list of inputs to fund the contract
294    pub inputs: Vec<TxInputInfo>,
295    /// A list of dlc inputs to be used
296    pub dlc_inputs: Vec<dlc_input::DlcInputInfo>,
297    /// The sum of the inputs values.
298    pub input_amount: Amount,
299    /// The collateral put in the contract by the party
300    pub collateral: Amount,
301}
302
303impl PartyParams {
304    /// Returns the change output for a single party as well as the fees that
305    /// they are required to pay for the fund transaction and the cet or refund transaction.
306    /// The change output value already accounts for the required fees.
307    /// If input amount (sum of all input values) is lower than the sum of the collateral
308    /// plus the required fees, an error is returned.
309    pub fn get_change_output_and_fees(
310        &self,
311        total_collateral: Amount,
312        fee_rate_per_vb: u64,
313        extra_fee: Amount,
314    ) -> Result<(TxOut, Amount, Amount), Error> {
315        let mut inputs_weight: usize = 0;
316
317        // first check if a party does not need to fund the contract if so, then it is zero
318        if self.collateral == Amount::ZERO {
319            // We use a zero value output to indicate that the party does not need to fund the contract
320            let change_output = TxOut {
321                value: Amount::ZERO,
322                script_pubkey: self.change_script_pubkey.clone(),
323            };
324            return Ok((change_output, Amount::ZERO, Amount::ZERO));
325        }
326
327        inputs_weight += dlc_input::get_dlc_inputs_weight(&self.dlc_inputs);
328
329        for w in &self.inputs {
330            let script_weight = util::redeem_script_to_script_sig(&w.redeem_script)
331                .len()
332                .checked_mul(4)
333                .ok_or(Error::InvalidArgument(format!(
334                    "Redeem script checked multiplication failed. {} * 4",
335                    w.redeem_script.len()
336                )))?;
337            inputs_weight = checked_add!(
338                inputs_weight,
339                TX_INPUT_BASE_WEIGHT,
340                script_weight,
341                w.max_witness_len
342            )?;
343        }
344
345        // Value size + script length var_int + ouput script pubkey size
346        let change_size = self.change_script_pubkey.len();
347        // Change size is scaled by 4 from vBytes to weight units
348        let change_weight = change_size
349            .checked_mul(4)
350            .ok_or(Error::InvalidArgument(format!(
351                "Change size checked multiplication failed: {} * 4",
352                change_size
353            )))?;
354
355        // If the party is funding the whole contract, then the base weight is the full base weight
356        // otherwise, the base weight (nLocktime, nVersion, ...) is distributed among parties
357        // independently of inputs contributed
358        let this_party_fund_base_weight = if self.collateral == total_collateral {
359            FUND_TX_BASE_WEIGHT
360        } else {
361            FUND_TX_BASE_WEIGHT / 2
362        };
363
364        let total_fund_weight = checked_add!(
365            this_party_fund_base_weight,
366            inputs_weight,
367            change_weight,
368            36
369        )?;
370        let fund_fee = util::weight_to_fee(total_fund_weight, fee_rate_per_vb)?;
371
372        // If the party is funding the whole contract, then the base weight is the full base weight
373        // otherwise, the base weight (nLocktime, nVersion, funding input ...) is distributed
374        // among parties independently of output types
375        let this_party_cet_base_weight = if self.collateral == total_collateral {
376            CET_BASE_WEIGHT
377        } else {
378            CET_BASE_WEIGHT / 2
379        };
380
381        // size of the payout script pubkey scaled by 4 from vBytes to weight units
382        let output_spk_weight =
383            self.payout_script_pubkey
384                .len()
385                .checked_mul(4)
386                .ok_or(Error::InvalidArgument(format!(
387                    "Output spk checked multiplication failed: {} * 4",
388                    self.payout_script_pubkey.len()
389                )))?;
390        let total_cet_weight = checked_add!(this_party_cet_base_weight, output_spk_weight)?;
391        let cet_or_refund_fee = util::weight_to_fee(total_cet_weight, fee_rate_per_vb)?;
392
393        let required_input_funds =
394            checked_add!(self.collateral, fund_fee, cet_or_refund_fee, extra_fee)?;
395        if self.input_amount < required_input_funds {
396            return Err(Error::InvalidArgument(format!(
397                "Input amount is less than required input funds. input_amount={} required_input_funds={}",
398                self.input_amount, required_input_funds
399            )));
400        }
401
402        let change_output = TxOut {
403            value: self.input_amount - required_input_funds,
404            script_pubkey: self.change_script_pubkey.clone(),
405        };
406
407        Ok((change_output, fund_fee, cet_or_refund_fee))
408    }
409
410    fn get_unsigned_tx_inputs_and_serial_ids(&self, sequence: Sequence) -> (Vec<TxIn>, Vec<u64>) {
411        let mut tx_ins = Vec::with_capacity(self.inputs.len());
412        let mut serial_ids = Vec::with_capacity(self.inputs.len());
413
414        for input in &self.inputs {
415            let tx_in = TxIn {
416                previous_output: input.outpoint,
417                script_sig: util::redeem_script_to_script_sig(&input.redeem_script),
418                sequence,
419                witness: Witness::new(),
420            };
421            tx_ins.push(tx_in);
422            serial_ids.push(input.serial_id);
423        }
424
425        (tx_ins, serial_ids)
426    }
427}
428
429/// Create the transactions for a DLC contract based on the provided parameters
430/// This function is used to create the transactions for a DLC contract when the
431/// offer and accept parameters are spliced together.
432#[allow(clippy::too_many_arguments)]
433pub fn create_spliced_dlc_transactions(
434    offer_params: &PartyParams,
435    accept_params: &PartyParams,
436    payouts: &[Payout],
437    refund_lock_time: u32,
438    fee_rate_per_vb: u64,
439    fund_lock_time: u32,
440    cet_lock_time: u32,
441    fund_output_serial_id: u64,
442    contract_flags: u8,
443) -> Result<DlcTransactions, Error> {
444    // Create enhanced party parameters that include DLC inputs as regular inputs
445    let mut enhanced_offer_params = offer_params.clone();
446    let mut enhanced_accept_params = accept_params.clone();
447
448    // Filter out DLC inputs from regular inputs to avoid duplicates
449    // DLC inputs are already included in the inputs[] array, so we need to remove them
450    // before adding them back from dlc_inputs[] to avoid double-counting
451    enhanced_offer_params
452        .inputs
453        .retain(|input| input.max_witness_len <= 108);
454    enhanced_accept_params
455        .inputs
456        .retain(|input| input.max_witness_len <= 108);
457
458    let offer_dlc_tx_inputs = offer_params
459        .dlc_inputs
460        .iter()
461        .map(|input| input.into())
462        .collect::<Vec<TxInputInfo>>();
463
464    let accept_dlc_tx_inputs = accept_params
465        .dlc_inputs
466        .iter()
467        .map(|input| input.into())
468        .collect::<Vec<TxInputInfo>>();
469
470    // Add DLC inputs to regular inputs
471    enhanced_offer_params.inputs.extend(offer_dlc_tx_inputs);
472    enhanced_accept_params.inputs.extend(accept_dlc_tx_inputs);
473
474    // Clear DLC inputs from enhanced params since they're now regular inputs
475    enhanced_offer_params.dlc_inputs.clear();
476    enhanced_accept_params.dlc_inputs.clear();
477
478    create_dlc_transactions(
479        &enhanced_offer_params,
480        &enhanced_accept_params,
481        payouts,
482        refund_lock_time,
483        fee_rate_per_vb,
484        fund_lock_time,
485        cet_lock_time,
486        fund_output_serial_id,
487        contract_flags,
488    )
489}
490
491/// Create the transactions for a DLC contract based on the provided parameters
492#[allow(clippy::too_many_arguments)]
493pub fn create_dlc_transactions(
494    offer_params: &PartyParams,
495    accept_params: &PartyParams,
496    payouts: &[Payout],
497    refund_lock_time: u32,
498    fee_rate_per_vb: u64,
499    fund_lock_time: u32,
500    cet_lock_time: u32,
501    fund_output_serial_id: u64,
502    contract_flags: u8,
503) -> Result<DlcTransactions, Error> {
504    let (fund_tx, funding_script_pubkey) = create_fund_transaction_with_fees(
505        offer_params,
506        accept_params,
507        fee_rate_per_vb,
508        fund_lock_time,
509        fund_output_serial_id,
510        Amount::ZERO,
511    )?;
512    let fund_outpoint = OutPoint {
513        txid: fund_tx.compute_txid(),
514        vout: util::get_output_for_script_pubkey(&fund_tx, &funding_script_pubkey.to_p2wsh())
515            .expect("to find the funding script pubkey")
516            .0 as u32,
517    };
518    let (cets, refund_tx) = create_cets_and_refund_tx(
519        offer_params,
520        accept_params,
521        fund_outpoint,
522        payouts,
523        refund_lock_time,
524        cet_lock_time,
525        None,
526        contract_flags,
527    )?;
528
529    Ok(DlcTransactions {
530        fund: fund_tx,
531        cets,
532        refund: refund_tx,
533        funding_script_pubkey,
534        pending_close_txs: vec![],
535    })
536}
537
538/// Create a funding transaction with fees.
539pub fn create_fund_transaction_with_fees(
540    offer_params: &PartyParams,
541    accept_params: &PartyParams,
542    fee_rate_per_vb: u64,
543    fund_lock_time: u32,
544    fund_output_serial_id: u64,
545    extra_fee: Amount,
546) -> Result<(Transaction, ScriptBuf), Error> {
547    let total_collateral = checked_add!(offer_params.collateral, accept_params.collateral)?;
548
549    let (offer_change_output, offer_fund_fee, offer_cet_fee) =
550        offer_params.get_change_output_and_fees(total_collateral, fee_rate_per_vb, extra_fee)?;
551    let (accept_change_output, accept_fund_fee, accept_cet_fee) =
552        accept_params.get_change_output_and_fees(total_collateral, fee_rate_per_vb, extra_fee)?;
553
554    let fund_output_value = checked_add!(offer_params.input_amount, accept_params.input_amount)?
555        - offer_change_output.value
556        - accept_change_output.value
557        - offer_fund_fee
558        - accept_fund_fee
559        - extra_fee;
560
561    assert_eq!(
562        total_collateral + offer_cet_fee + accept_cet_fee + extra_fee,
563        fund_output_value
564    );
565
566    assert_eq!(
567        offer_params.input_amount + accept_params.input_amount,
568        fund_output_value
569            + offer_change_output.value
570            + accept_change_output.value
571            + offer_fund_fee
572            + accept_fund_fee
573            + extra_fee
574    );
575
576    let fund_sequence = util::get_sequence(fund_lock_time);
577    let (offer_tx_ins, offer_inputs_serial_ids) =
578        offer_params.get_unsigned_tx_inputs_and_serial_ids(fund_sequence);
579    let (accept_tx_ins, accept_inputs_serial_ids) =
580        accept_params.get_unsigned_tx_inputs_and_serial_ids(fund_sequence);
581
582    let funding_script_pubkey =
583        make_funding_redeemscript(&offer_params.fund_pubkey, &accept_params.fund_pubkey);
584
585    let fund_tx = create_funding_transaction(
586        &funding_script_pubkey,
587        fund_output_value,
588        &offer_tx_ins,
589        &offer_inputs_serial_ids,
590        &accept_tx_ins,
591        &accept_inputs_serial_ids,
592        offer_change_output,
593        offer_params.change_serial_id,
594        accept_change_output,
595        accept_params.change_serial_id,
596        fund_output_serial_id,
597        fund_lock_time,
598    );
599
600    Ok((fund_tx, funding_script_pubkey))
601}
602
603/// Create the contract execution transactions and refund transaction.
604#[allow(clippy::too_many_arguments)]
605pub fn create_cets_and_refund_tx(
606    offer_params: &PartyParams,
607    accept_params: &PartyParams,
608    prev_outpoint: OutPoint,
609    payouts: &[Payout],
610    refund_lock_time: u32,
611    cet_lock_time: u32,
612    cet_nsequence: Option<Sequence>,
613    contract_flags: u8,
614) -> Result<(Vec<Transaction>, Transaction), Error> {
615    let total_collateral = checked_add!(offer_params.collateral, accept_params.collateral)?;
616
617    let has_proper_outcomes = payouts.iter().all(|o| {
618        let total = checked_add!(o.offer, o.accept);
619        if let Ok(total) = total {
620            total == total_collateral
621        } else {
622            false
623        }
624    });
625
626    if !has_proper_outcomes {
627        return Err(Error::InvalidArgument(
628            "Payouts do not sum to total collateral".to_string(),
629        ));
630    }
631
632    let cet_input = TxIn {
633        previous_output: prev_outpoint,
634        witness: Witness::default(),
635        script_sig: ScriptBuf::default(),
636        sequence: cet_nsequence.unwrap_or_else(|| util::get_sequence(cet_lock_time)),
637    };
638
639    let cets = create_cets(
640        &cet_input,
641        &offer_params.payout_script_pubkey,
642        offer_params.payout_serial_id,
643        &accept_params.payout_script_pubkey,
644        accept_params.payout_serial_id,
645        payouts,
646        cet_lock_time,
647    );
648
649    let (offer_refund_value, accept_refund_value) = if contract_flags & REFUND_TO_ACCEPTER_FLAG != 0
650    {
651        (Amount::ZERO, total_collateral)
652    } else {
653        (offer_params.collateral, accept_params.collateral)
654    };
655
656    let offer_refund_output = TxOut {
657        value: offer_refund_value,
658        script_pubkey: offer_params.payout_script_pubkey.clone(),
659    };
660
661    let accept_refund_ouput = TxOut {
662        value: accept_refund_value,
663        script_pubkey: accept_params.payout_script_pubkey.clone(),
664    };
665
666    let refund_input = TxIn {
667        previous_output: prev_outpoint,
668        witness: Witness::default(),
669        script_sig: ScriptBuf::default(),
670        sequence: util::ENABLE_LOCKTIME,
671    };
672
673    let refund_tx = create_refund_transaction(
674        offer_refund_output,
675        accept_refund_ouput,
676        refund_input,
677        refund_lock_time,
678    );
679
680    Ok((cets, refund_tx))
681}
682
683/// Create a contract execution transaction
684pub fn create_cet(
685    offer_output: TxOut,
686    offer_payout_serial_id: u64,
687    accept_output: TxOut,
688    accept_payout_serial_id: u64,
689    fund_tx_in: &TxIn,
690    lock_time: u32,
691) -> Transaction {
692    let mut output: Vec<TxOut> = if offer_payout_serial_id < accept_payout_serial_id {
693        vec![offer_output, accept_output]
694    } else {
695        vec![accept_output, offer_output]
696    };
697
698    output = util::discard_dust(output, DUST_LIMIT);
699
700    Transaction {
701        version: TX_VERSION,
702        lock_time: LockTime::from_consensus(lock_time),
703        input: vec![fund_tx_in.clone()],
704        output,
705    }
706}
707
708/// Create a set of contract execution transaction for each provided outcome
709pub fn create_cets(
710    fund_tx_input: &TxIn,
711    offer_payout_script_pubkey: &Script,
712    offer_payout_serial_id: u64,
713    accept_payout_script_pubkey: &Script,
714    accept_payout_serial_id: u64,
715    payouts: &[Payout],
716    lock_time: u32,
717) -> Vec<Transaction> {
718    let mut txs: Vec<Transaction> = Vec::with_capacity(payouts.len());
719    for payout in payouts {
720        let offer_output = TxOut {
721            value: payout.offer,
722            script_pubkey: offer_payout_script_pubkey.to_owned(),
723        };
724        let accept_output = TxOut {
725            value: payout.accept,
726            script_pubkey: accept_payout_script_pubkey.to_owned(),
727        };
728        let tx = create_cet(
729            offer_output,
730            offer_payout_serial_id,
731            accept_output,
732            accept_payout_serial_id,
733            fund_tx_input,
734            lock_time,
735        );
736
737        txs.push(tx);
738    }
739
740    txs
741}
742
743/// Create a funding transaction
744#[allow(clippy::too_many_arguments)]
745pub fn create_funding_transaction(
746    funding_script_pubkey: &Script,
747    output_amount: Amount,
748    offer_inputs: &[TxIn],
749    offer_inputs_serial_ids: &[u64],
750    accept_inputs: &[TxIn],
751    accept_inputs_serial_ids: &[u64],
752    offer_change_output: TxOut,
753    offer_change_serial_id: u64,
754    accept_change_output: TxOut,
755    accept_change_serial_id: u64,
756    fund_output_serial_id: u64,
757    lock_time: u32,
758) -> Transaction {
759    let fund_tx_out = TxOut {
760        value: output_amount,
761        script_pubkey: funding_script_pubkey.to_p2wsh(),
762    };
763
764    let output: Vec<TxOut> = {
765        let serial_ids = vec![
766            fund_output_serial_id,
767            offer_change_serial_id,
768            accept_change_serial_id,
769        ];
770        util::discard_dust(
771            util::order_by_serial_ids(
772                vec![fund_tx_out, offer_change_output, accept_change_output],
773                &serial_ids,
774            ),
775            DUST_LIMIT,
776        )
777    };
778
779    let input = util::order_by_serial_ids(
780        [offer_inputs, accept_inputs].concat(),
781        &[offer_inputs_serial_ids, accept_inputs_serial_ids].concat(),
782    );
783
784    Transaction {
785        version: TX_VERSION,
786        lock_time: LockTime::from_consensus(lock_time),
787        input,
788        output,
789    }
790}
791
792/// Create a refund transaction
793pub fn create_refund_transaction(
794    offer_output: TxOut,
795    accept_output: TxOut,
796    funding_input: TxIn,
797    locktime: u32,
798) -> Transaction {
799    let output = util::discard_dust(vec![offer_output, accept_output], DUST_LIMIT);
800    Transaction {
801        version: TX_VERSION,
802        lock_time: LockTime::from_consensus(locktime),
803        input: vec![funding_input],
804        output,
805    }
806}
807
808/// Create the multisig redeem script for the funding output
809pub fn make_funding_redeemscript(a: &PublicKey, b: &PublicKey) -> ScriptBuf {
810    let (first, second) = if a <= b { (a, b) } else { (b, a) };
811
812    Builder::new()
813        .push_opcode(opcodes::all::OP_PUSHNUM_2)
814        .push_slice(first.serialize())
815        .push_slice(second.serialize())
816        .push_opcode(opcodes::all::OP_PUSHNUM_2)
817        .push_opcode(opcodes::all::OP_CHECKMULTISIG)
818        .into_script()
819}
820
821fn get_oracle_sig_point<C: secp256k1_zkp::Verification>(
822    secp: &Secp256k1<C>,
823    oracle_info: &OracleInfo,
824    msgs: &[Message],
825) -> Result<PublicKey, Error> {
826    if oracle_info.nonces.len() < msgs.len() {
827        return Err(Error::InvalidArgument(format!(
828            "Oracle info nonces length is less than msgs length: {} < {}",
829            oracle_info.nonces.len(),
830            msgs.len()
831        )));
832    }
833
834    let sig_points: Vec<PublicKey> = oracle_info
835        .nonces
836        .iter()
837        .zip(msgs.iter())
838        .map(|(nonce, msg)| {
839            secp_utils::schnorrsig_compute_sig_point(secp, &oracle_info.public_key, nonce, msg)
840        })
841        .collect::<Result<Vec<PublicKey>, Error>>()?;
842    Ok(PublicKey::combine_keys(
843        &sig_points.iter().collect::<Vec<_>>(),
844    )?)
845}
846
847/// Get an adaptor point generated using the given oracle information and messages.
848pub fn get_adaptor_point_from_oracle_info<C: Verification>(
849    secp: &Secp256k1<C>,
850    oracle_infos: &[OracleInfo],
851    msgs: &[Vec<Message>],
852) -> Result<PublicKey, Error> {
853    if oracle_infos.is_empty() || msgs.is_empty() {
854        return Err(Error::InvalidArgument(format!(
855            "Oracle infos or msgs is empty. oracle_infos={} msgs={}",
856            oracle_infos.len(),
857            msgs.len()
858        )));
859    }
860
861    let mut oracle_sigpoints = Vec::with_capacity(msgs[0].len());
862    for (i, info) in oracle_infos.iter().enumerate() {
863        oracle_sigpoints.push(get_oracle_sig_point(secp, info, &msgs[i])?);
864    }
865    Ok(PublicKey::combine_keys(
866        &oracle_sigpoints.iter().collect::<Vec<_>>(),
867    )?)
868}
869
870/// Create an adaptor signature for the given cet using the provided adaptor point.
871pub fn create_cet_adaptor_sig_from_point<C: secp256k1_zkp::Signing>(
872    secp: &secp256k1_zkp::Secp256k1<C>,
873    cet: &Transaction,
874    adaptor_point: &PublicKey,
875    funding_sk: &SecretKey,
876    funding_script_pubkey: &Script,
877    fund_output_value: Amount,
878) -> Result<EcdsaAdaptorSignature, Error> {
879    let sig_hash = util::get_sig_hash_msg(cet, 0, funding_script_pubkey, fund_output_value)?;
880
881    #[cfg(feature = "std")]
882    let res = EcdsaAdaptorSignature::encrypt(secp, &sig_hash, funding_sk, adaptor_point);
883
884    #[cfg(not(feature = "std"))]
885    let res =
886        EcdsaAdaptorSignature::encrypt_no_aux_rand(secp, &sig_hash, funding_sk, adaptor_point);
887
888    Ok(res)
889}
890
891/// Create an adaptor signature for the given cet using the provided oracle infos.
892pub fn create_cet_adaptor_sig_from_oracle_info(
893    secp: &secp256k1_zkp::Secp256k1<secp256k1_zkp::All>,
894    cet: &Transaction,
895    oracle_infos: &[OracleInfo],
896    funding_sk: &SecretKey,
897    funding_script_pubkey: &Script,
898    fund_output_value: Amount,
899    msgs: &[Vec<Message>],
900) -> Result<EcdsaAdaptorSignature, Error> {
901    let adaptor_point = get_adaptor_point_from_oracle_info(secp, oracle_infos, msgs)?;
902    create_cet_adaptor_sig_from_point(
903        secp,
904        cet,
905        &adaptor_point,
906        funding_sk,
907        funding_script_pubkey,
908        fund_output_value,
909    )
910}
911
912/// Crerate a set of adaptor signatures for the given cet/message pairs.
913pub fn create_cet_adaptor_sigs_from_points<C: secp256k1_zkp::Signing>(
914    secp: &secp256k1_zkp::Secp256k1<C>,
915    inputs: &[(&Transaction, &PublicKey)],
916    funding_sk: &SecretKey,
917    funding_script_pubkey: &Script,
918    fund_output_value: Amount,
919) -> Result<Vec<EcdsaAdaptorSignature>, Error> {
920    inputs
921        .iter()
922        .map(|(cet, adaptor_point)| {
923            create_cet_adaptor_sig_from_point(
924                secp,
925                cet,
926                adaptor_point,
927                funding_sk,
928                funding_script_pubkey,
929                fund_output_value,
930            )
931        })
932        .collect()
933}
934
935/// Crerate a set of adaptor signatures for the given cet/message pairs.
936pub fn create_cet_adaptor_sigs_from_oracle_info(
937    secp: &secp256k1_zkp::Secp256k1<secp256k1_zkp::All>,
938    cets: &[Transaction],
939    oracle_infos: &[OracleInfo],
940    funding_sk: &SecretKey,
941    funding_script_pubkey: &Script,
942    fund_output_value: Amount,
943    msgs: &[Vec<Vec<Message>>],
944) -> Result<Vec<EcdsaAdaptorSignature>, Error> {
945    if msgs.len() != cets.len() {
946        return Err(Error::InvalidArgument(format!(
947            "Msgs length is not equal to cets length. msgs={} cets={}",
948            msgs.len(),
949            cets.len()
950        )));
951    }
952
953    cets.iter()
954        .zip(msgs.iter())
955        .map(|(cet, msg)| {
956            create_cet_adaptor_sig_from_oracle_info(
957                secp,
958                cet,
959                oracle_infos,
960                funding_sk,
961                funding_script_pubkey,
962                fund_output_value,
963                msg,
964            )
965        })
966        .collect()
967}
968
969fn signatures_to_secret(signatures: &[Vec<SchnorrSignature>]) -> Result<SecretKey, Error> {
970    let s_values = signatures
971        .iter()
972        .flatten()
973        .map(|x| match secp_utils::schnorrsig_decompose(x) {
974            Ok(v) => Ok(v.1),
975            Err(err) => Err(err),
976        })
977        .collect::<Result<Vec<&[u8]>, Error>>()?;
978    let secret = SecretKey::from_slice(s_values[0])?;
979
980    let result = s_values.iter().skip(1).fold(secret, |accum, s| {
981        let sec = SecretKey::from_slice(s).unwrap();
982        accum.add_tweak(&Scalar::from(sec)).unwrap()
983    });
984
985    Ok(result)
986}
987
988/// Sign the given cet using own private key, adapt the counter party signature
989/// and place both signatures and the funding multi sig script pubkey on the
990/// witness stack
991#[allow(clippy::too_many_arguments)]
992pub fn sign_cet<C: secp256k1_zkp::Signing>(
993    secp: &secp256k1_zkp::Secp256k1<C>,
994    cet: &mut Transaction,
995    adaptor_signature: &EcdsaAdaptorSignature,
996    oracle_signatures: &[Vec<SchnorrSignature>],
997    funding_sk: &SecretKey,
998    other_pk: &PublicKey,
999    funding_script_pubkey: &Script,
1000    fund_output_value: Amount,
1001) -> Result<(), Error> {
1002    let adaptor_secret = signatures_to_secret(oracle_signatures)?;
1003    let adapted_sig = adaptor_signature.decrypt(&adaptor_secret)?;
1004
1005    util::sign_multi_sig_input(
1006        secp,
1007        cet,
1008        &adapted_sig,
1009        other_pk,
1010        funding_sk,
1011        funding_script_pubkey,
1012        fund_output_value,
1013        0,
1014    )?;
1015
1016    Ok(())
1017}
1018
1019/// Verify that a given adaptor signature for a given cet is valid with respect
1020/// to an adaptor point.
1021pub fn verify_cet_adaptor_sig_from_point(
1022    secp: &Secp256k1<secp256k1_zkp::All>,
1023    adaptor_sig: &EcdsaAdaptorSignature,
1024    cet: &Transaction,
1025    adaptor_point: &PublicKey,
1026    pubkey: &PublicKey,
1027    funding_script_pubkey: &Script,
1028    total_collateral: Amount,
1029) -> Result<(), Error> {
1030    let sig_hash = util::get_sig_hash_msg(cet, 0, funding_script_pubkey, total_collateral)?;
1031    adaptor_sig.verify(secp, &sig_hash, pubkey, adaptor_point)?;
1032    Ok(())
1033}
1034
1035/// Verify that a given adaptor signature for a given cet is valid with respect
1036/// to an oracle public key, nonce and a given message.
1037#[allow(clippy::too_many_arguments)]
1038pub fn verify_cet_adaptor_sig_from_oracle_info(
1039    secp: &Secp256k1<secp256k1_zkp::All>,
1040    adaptor_sig: &EcdsaAdaptorSignature,
1041    cet: &Transaction,
1042    oracle_infos: &[OracleInfo],
1043    pubkey: &PublicKey,
1044    funding_script_pubkey: &Script,
1045    total_collateral: Amount,
1046    msgs: &[Vec<Message>],
1047) -> Result<(), Error> {
1048    let adaptor_point = get_adaptor_point_from_oracle_info(secp, oracle_infos, msgs)?;
1049    verify_cet_adaptor_sig_from_point(
1050        secp,
1051        adaptor_sig,
1052        cet,
1053        &adaptor_point,
1054        pubkey,
1055        funding_script_pubkey,
1056        total_collateral,
1057    )
1058}
1059
1060/// Verify a signature for a given transaction input.
1061pub fn verify_tx_input_sig<V: Verification>(
1062    secp: &Secp256k1<V>,
1063    signature: &Signature,
1064    tx: &Transaction,
1065    input_index: usize,
1066    script_pubkey: &Script,
1067    value: Amount,
1068    pk: &PublicKey,
1069) -> Result<(), Error> {
1070    let sig_hash_msg = util::get_sig_hash_msg(tx, input_index, script_pubkey, value)?;
1071    secp.verify_ecdsa(&sig_hash_msg, signature, pk)?;
1072    Ok(())
1073}
1074
1075#[cfg(test)]
1076mod tests {
1077    use super::*;
1078    use bitcoin::blockdata::script::ScriptBuf;
1079    use bitcoin::blockdata::transaction::OutPoint;
1080    use bitcoin::consensus::encode::Encodable;
1081    use bitcoin::hashes::Hash;
1082    use bitcoin::hashes::HashEngine;
1083    use bitcoin::sighash::EcdsaSighashType;
1084    use bitcoin::{Address, CompressedPublicKey, Network, Txid};
1085    use secp256k1_zkp::{
1086        rand::{Rng, RngCore},
1087        Keypair, PublicKey, Secp256k1, SecretKey, Signing,
1088    };
1089    use std::fmt::Write;
1090    use std::str::FromStr;
1091    use util;
1092
1093    fn create_txin_vec(sequence: Sequence) -> Vec<TxIn> {
1094        let mut inputs = Vec::new();
1095        let txin = TxIn {
1096            previous_output: OutPoint::default(),
1097            script_sig: ScriptBuf::new(),
1098            sequence,
1099            witness: Witness::new(),
1100        };
1101        inputs.push(txin);
1102        inputs
1103    }
1104
1105    fn create_multi_party_pub_keys() -> (PublicKey, PublicKey) {
1106        let secp = Secp256k1::new();
1107        let secret_key =
1108            SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000001")
1109                .unwrap();
1110        let pk = PublicKey::from_secret_key(&secp, &secret_key);
1111        let pk1 = pk;
1112
1113        (pk, pk1)
1114    }
1115
1116    fn create_test_tx_io() -> (TxOut, TxOut, TxIn) {
1117        let offer = TxOut {
1118            value: DUST_LIMIT + Amount::from_sat(1),
1119            script_pubkey: ScriptBuf::new(),
1120        };
1121
1122        let accept = TxOut {
1123            value: DUST_LIMIT + Amount::from_sat(2),
1124            script_pubkey: ScriptBuf::new(),
1125        };
1126
1127        let funding = TxIn {
1128            previous_output: OutPoint::default(),
1129            script_sig: ScriptBuf::new(),
1130            sequence: Sequence(3),
1131            witness: Witness::new(),
1132        };
1133
1134        (offer, accept, funding)
1135    }
1136
1137    #[test]
1138    fn create_refund_transaction_test() {
1139        let (offer, accept, funding) = create_test_tx_io();
1140
1141        let refund_transaction = create_refund_transaction(offer, accept, funding, 0);
1142        assert_eq!(Version::TWO, refund_transaction.version);
1143        assert_eq!(0, refund_transaction.lock_time.to_consensus_u32());
1144        assert_eq!(
1145            DUST_LIMIT + Amount::from_sat(1),
1146            refund_transaction.output[0].value
1147        );
1148        assert_eq!(
1149            DUST_LIMIT + Amount::from_sat(2),
1150            refund_transaction.output[1].value
1151        );
1152        assert_eq!(3, refund_transaction.input[0].sequence.0);
1153    }
1154
1155    #[test]
1156    fn create_funding_transaction_test() {
1157        let (pk, pk1) = create_multi_party_pub_keys();
1158
1159        let offer_inputs = create_txin_vec(Sequence::ZERO);
1160        let accept_inputs = create_txin_vec(Sequence(1));
1161
1162        let change = Amount::from_sat(1000);
1163
1164        let total_collateral = Amount::from_sat(31415);
1165
1166        let offer_change_output = TxOut {
1167            value: change,
1168            script_pubkey: ScriptBuf::new(),
1169        };
1170        let accept_change_output = TxOut {
1171            value: change,
1172            script_pubkey: ScriptBuf::new(),
1173        };
1174        let funding_script_pubkey = make_funding_redeemscript(&pk, &pk1);
1175
1176        let transaction = create_funding_transaction(
1177            &funding_script_pubkey,
1178            total_collateral,
1179            &offer_inputs,
1180            &[1],
1181            &accept_inputs,
1182            &[2],
1183            offer_change_output,
1184            0,
1185            accept_change_output,
1186            1,
1187            0,
1188            0,
1189        );
1190
1191        assert_eq!(transaction.input[0].sequence.0, 0);
1192        assert_eq!(transaction.input[1].sequence.0, 1);
1193
1194        assert_eq!(transaction.output[0].value, total_collateral);
1195        assert_eq!(transaction.output[1].value, change);
1196        assert_eq!(transaction.output[2].value, change);
1197        assert_eq!(transaction.output.len(), 3);
1198    }
1199
1200    #[test]
1201    fn create_funding_transaction_with_outputs_less_than_dust_limit_test() {
1202        let (pk, pk1) = create_multi_party_pub_keys();
1203
1204        let offer_inputs = create_txin_vec(Sequence::ZERO);
1205        let accept_inputs = create_txin_vec(Sequence(1));
1206
1207        let total_collateral = Amount::from_sat(31415);
1208        let change = Amount::from_sat(999);
1209
1210        let offer_change_output = TxOut {
1211            value: change,
1212            script_pubkey: ScriptBuf::new(),
1213        };
1214        let accept_change_output = TxOut {
1215            value: change,
1216            script_pubkey: ScriptBuf::new(),
1217        };
1218
1219        let funding_script_pubkey = make_funding_redeemscript(&pk, &pk1);
1220
1221        let transaction = create_funding_transaction(
1222            &funding_script_pubkey,
1223            total_collateral,
1224            &offer_inputs,
1225            &[1],
1226            &accept_inputs,
1227            &[2],
1228            offer_change_output,
1229            0,
1230            accept_change_output,
1231            1,
1232            0,
1233            0,
1234        );
1235
1236        assert_eq!(transaction.output[0].value, total_collateral);
1237        assert_eq!(transaction.output.len(), 1);
1238    }
1239
1240    #[test]
1241    fn create_funding_transaction_serialized_test() {
1242        let secp = Secp256k1::new();
1243        let input_amount = Amount::from_sat(5000000000);
1244        let change = Amount::from_sat(4899999719);
1245        let total_collateral = Amount::from_sat(200000312);
1246        let offer_change_address =
1247            Address::from_str("bcrt1qlgmznucxpdkp5k3ktsct7eh6qrc4tju7ktjukn")
1248                .unwrap()
1249                .assume_checked();
1250        let accept_change_address =
1251            Address::from_str("bcrt1qvh2dvgjctwh4z5w7sc93u7h4sug0yrdz2lgpqf")
1252                .unwrap()
1253                .assume_checked();
1254
1255        let offer_change_output = TxOut {
1256            value: change,
1257            script_pubkey: offer_change_address.script_pubkey(),
1258        };
1259
1260        let accept_change_output = TxOut {
1261            value: change,
1262            script_pubkey: accept_change_address.script_pubkey(),
1263        };
1264
1265        let offer_input = TxIn {
1266            previous_output: OutPoint {
1267                txid: Txid::from_str(
1268                    "83266d6b22a9babf6ee469b88fd0d3a0c690525f7c903aff22ec8ee44214604f",
1269                )
1270                .unwrap(),
1271                vout: 0,
1272            },
1273            script_sig: ScriptBuf::new(),
1274            sequence: Sequence(0xffffffff),
1275            witness: Witness::from_slice(&[ScriptBuf::new().to_bytes()]),
1276        };
1277
1278        let accept_input = TxIn {
1279            previous_output: OutPoint {
1280                txid: Txid::from_str(
1281                    "bc92a22f07ef23c53af343397874b59f5f8c0eb37753af1d1a159a2177d4bb98",
1282                )
1283                .unwrap(),
1284                vout: 0,
1285            },
1286            script_sig: ScriptBuf::new(),
1287            sequence: Sequence(0xffffffff),
1288            witness: Witness::from_slice(&[ScriptBuf::new().to_bytes()]),
1289        };
1290        let offer_fund_sk =
1291            SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000001")
1292                .unwrap();
1293        let offer_fund_pubkey = PublicKey::from_secret_key(&secp, &offer_fund_sk);
1294        let accept_fund_sk =
1295            SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000002")
1296                .unwrap();
1297        let accept_fund_pubkey = PublicKey::from_secret_key(&secp, &accept_fund_sk);
1298        let offer_input_sk =
1299            SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000005")
1300                .unwrap();
1301        let accept_input_sk =
1302            SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000006")
1303                .unwrap();
1304
1305        let expected_serialized = "020000000001024F601442E48EEC22FF3A907C5F5290C6A0D3D08FB869E46EBFBAA9226B6D26830000000000FFFFFFFF98BBD477219A151A1DAF5377B30E8C5F9FB574783943F33AC523EF072FA292BC0000000000FFFFFFFF0338C3EB0B000000002200209B984C7BAE3EFDDC3A3F0A20FF81BFE89ED1FE07FF13E562149EE654BED845DBE70F102401000000160014FA3629F3060B6C1A5A365C30BF66FA00F155CB9EE70F10240100000016001465D4D622585BAF5151DE860B1E7AF58710F20DA20247304402207108DE1563AE311F8D4217E1C0C7463386C1A135BE6AF88CBE8D89A3A08D65090220195A2B0140FB9BA83F20CF45AD6EA088BB0C6860C0D4995F1CF1353739CA65A90121022F8BDE4D1A07209355B4A7250A5C5128E88B84BDDC619AB7CBA8D569B240EFE4024730440220048716EAEE918AEBCB1BFCFAF7564E78293A7BB0164D9A7844E42FCEB5AE393C022022817D033C9DB19C5BDCADD49B7587A810B6FC2264158A59665ABA8AB298455B012103FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A146029755600000000";
1306
1307        let funding_script_pubkey =
1308            make_funding_redeemscript(&offer_fund_pubkey, &accept_fund_pubkey);
1309
1310        let mut fund_tx = create_funding_transaction(
1311            &funding_script_pubkey,
1312            total_collateral,
1313            &[offer_input],
1314            &[1],
1315            &[accept_input],
1316            &[2],
1317            offer_change_output,
1318            0,
1319            accept_change_output,
1320            1,
1321            0,
1322            0,
1323        );
1324
1325        util::sign_p2wpkh_input(
1326            &secp,
1327            &offer_input_sk,
1328            &mut fund_tx,
1329            0,
1330            EcdsaSighashType::All,
1331            input_amount,
1332        )
1333        .expect("to be able to sign the input.");
1334
1335        util::sign_p2wpkh_input(
1336            &secp,
1337            &accept_input_sk,
1338            &mut fund_tx,
1339            1,
1340            EcdsaSighashType::All,
1341            input_amount,
1342        )
1343        .expect("to be able to sign the input.");
1344
1345        let mut writer = Vec::new();
1346        fund_tx.consensus_encode(&mut writer).unwrap();
1347        let mut serialized = String::new();
1348        for x in writer {
1349            write!(&mut serialized, "{:02X}", x).unwrap();
1350        }
1351
1352        assert_eq!(expected_serialized, serialized);
1353    }
1354
1355    fn get_p2wpkh_script_pubkey<C: Signing, R: Rng + ?Sized>(
1356        secp: &Secp256k1<C>,
1357        rng: &mut R,
1358    ) -> ScriptBuf {
1359        let sk = bitcoin::PrivateKey {
1360            inner: SecretKey::new(rng),
1361            network: Network::Testnet.into(),
1362            compressed: true,
1363        };
1364        let pk = CompressedPublicKey::from_private_key(secp, &sk).unwrap();
1365        Address::p2wpkh(&pk, Network::Testnet).script_pubkey()
1366    }
1367
1368    fn get_party_params(
1369        input_amount: Amount,
1370        collateral: Amount,
1371        serial_id: Option<u64>,
1372    ) -> (PartyParams, SecretKey) {
1373        let secp = Secp256k1::new();
1374        let mut rng = secp256k1_zkp::rand::thread_rng();
1375        let fund_privkey = SecretKey::new(&mut rng);
1376        let serial_id = serial_id.unwrap_or(1);
1377        (
1378            PartyParams {
1379                fund_pubkey: PublicKey::from_secret_key(&secp, &fund_privkey),
1380                change_script_pubkey: get_p2wpkh_script_pubkey(&secp, &mut rng),
1381                change_serial_id: serial_id,
1382                payout_script_pubkey: get_p2wpkh_script_pubkey(&secp, &mut rng),
1383                payout_serial_id: serial_id,
1384                input_amount,
1385                collateral,
1386                inputs: vec![TxInputInfo {
1387                    max_witness_len: 108,
1388                    redeem_script: ScriptBuf::new(),
1389                    outpoint: OutPoint {
1390                        txid: Txid::from_str(
1391                            "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456",
1392                        )
1393                        .unwrap(),
1394                        vout: serial_id as u32,
1395                    },
1396                    serial_id,
1397                }],
1398                dlc_inputs: vec![],
1399            },
1400            fund_privkey,
1401        )
1402    }
1403
1404    fn payouts() -> Vec<Payout> {
1405        vec![
1406            Payout {
1407                offer: Amount::from_sat(200_000_000),
1408                accept: Amount::ZERO,
1409            },
1410            Payout {
1411                offer: Amount::ZERO,
1412                accept: Amount::from_sat(200_000_000),
1413            },
1414        ]
1415    }
1416
1417    #[test]
1418    fn get_change_output_and_fees_no_inputs_no_funding() {
1419        let (party_params, _) = get_party_params(Amount::ZERO, Amount::ZERO, None);
1420
1421        let total_collateral = Amount::ONE_BTC;
1422
1423        let (change_out, fund_fee, cet_fee) = party_params
1424            .get_change_output_and_fees(total_collateral, 4, Amount::ZERO)
1425            .unwrap();
1426
1427        assert_eq!(change_out.value, Amount::ZERO);
1428        assert_eq!(fund_fee, Amount::ZERO);
1429        assert_eq!(cet_fee, Amount::ZERO);
1430    }
1431
1432    #[test]
1433    fn get_change_output_and_fees_single_funded_vs_dual_funded() {
1434        let (party_params_single_funded, _) =
1435            get_party_params(Amount::from_sat(150_000_000), Amount::ONE_BTC, None);
1436
1437        let total_collateral = Amount::ONE_BTC;
1438
1439        let (change_out_single_funded, fund_fee_single_funded, cet_fee_single_funded) =
1440            party_params_single_funded
1441                .get_change_output_and_fees(total_collateral, 4, Amount::ZERO)
1442                .unwrap();
1443
1444        let (party_params_dual_funded, _) =
1445            get_party_params(Amount::from_sat(150_000_000), Amount::ONE_BTC, None);
1446        let total_collateral = Amount::ONE_BTC + Amount::ONE_BTC;
1447
1448        let (change_out_dual_funded, fund_fee_dual_funded, cet_fee_dual_funded) =
1449            party_params_dual_funded
1450                .get_change_output_and_fees(total_collateral, 4, Amount::ZERO)
1451                .unwrap();
1452
1453        assert!(change_out_single_funded.value < change_out_dual_funded.value);
1454        assert!(fund_fee_single_funded > fund_fee_dual_funded);
1455        assert!(cet_fee_single_funded > cet_fee_dual_funded);
1456    }
1457
1458    #[test]
1459    fn get_change_output_and_fees_enough_funds() {
1460        // Arrange
1461        let (party_params, _) =
1462            get_party_params(Amount::from_sat(100000), Amount::from_sat(10000), None);
1463
1464        // Act
1465        let total_collateral = Amount::from_sat(100001);
1466        let (change_out, fund_fee, cet_fee) = party_params
1467            .get_change_output_and_fees(total_collateral, 4, Amount::ZERO)
1468            .unwrap();
1469
1470        // Assert
1471        assert!(
1472            change_out.value > Amount::ZERO && fund_fee > Amount::ZERO && cet_fee > Amount::ZERO
1473        );
1474    }
1475
1476    #[test]
1477    fn get_change_output_and_fees_not_enough_funds() {
1478        // Arrange
1479        let (party_params, _) =
1480            get_party_params(Amount::from_sat(100000), Amount::from_sat(100000), None);
1481
1482        let total_collateral = Amount::from_sat(100001);
1483        // Act
1484        let res = party_params.get_change_output_and_fees(total_collateral, 4, Amount::ZERO);
1485
1486        // Assert
1487        assert!(res.is_err());
1488    }
1489
1490    #[test]
1491    fn create_dlc_transactions_no_error() {
1492        // Arrange
1493        let (offer_party_params, _) = get_party_params(
1494            Amount::from_sat(1000000000),
1495            Amount::from_sat(100000000),
1496            None,
1497        );
1498        let (accept_party_params, _) = get_party_params(
1499            Amount::from_sat(1000000000),
1500            Amount::from_sat(100000000),
1501            None,
1502        );
1503
1504        // Act
1505        let dlc_txs = create_dlc_transactions(
1506            &offer_party_params,
1507            &accept_party_params,
1508            &payouts(),
1509            100,
1510            4,
1511            10,
1512            10,
1513            0,
1514            0,
1515        )
1516        .unwrap();
1517
1518        // Assert
1519        assert_eq!(10, dlc_txs.fund.lock_time.to_consensus_u32());
1520        assert_eq!(100, dlc_txs.refund.lock_time.to_consensus_u32());
1521        assert!(dlc_txs
1522            .cets
1523            .iter()
1524            .all(|x| x.lock_time.to_consensus_u32() == 10));
1525    }
1526
1527    #[test]
1528    fn create_cet_adaptor_sig_is_valid() {
1529        // Arrange
1530        let secp = Secp256k1::new();
1531        let mut rng = secp256k1_zkp::rand::thread_rng();
1532        let (offer_party_params, offer_fund_sk) = get_party_params(
1533            Amount::from_sat(1000000000),
1534            Amount::from_sat(100000000),
1535            None,
1536        );
1537        let (accept_party_params, accept_fund_sk) = get_party_params(
1538            Amount::from_sat(1000000000),
1539            Amount::from_sat(100000000),
1540            None,
1541        );
1542
1543        let dlc_txs = create_dlc_transactions(
1544            &offer_party_params,
1545            &accept_party_params,
1546            &payouts(),
1547            100,
1548            4,
1549            10,
1550            10,
1551            0,
1552            0,
1553        )
1554        .unwrap();
1555
1556        let cets = dlc_txs.cets;
1557        const NB_ORACLES: usize = 3;
1558        const NB_OUTCOMES: usize = 2;
1559        const NB_DIGITS: usize = 20;
1560        let mut oracle_infos: Vec<OracleInfo> = Vec::with_capacity(NB_ORACLES);
1561        let mut oracle_sks: Vec<Keypair> = Vec::with_capacity(NB_ORACLES);
1562        let mut oracle_sk_nonce: Vec<Vec<[u8; 32]>> = Vec::with_capacity(NB_ORACLES);
1563        let mut oracle_sigs: Vec<Vec<SchnorrSignature>> = Vec::with_capacity(NB_ORACLES);
1564        let messages: Vec<Vec<Vec<_>>> = (0..NB_OUTCOMES)
1565            .map(|x| {
1566                (0..NB_ORACLES)
1567                    .map(|y| {
1568                        (0..NB_DIGITS)
1569                            .map(|z| {
1570                                let tag_hash = bitcoin::hashes::sha256::Hash::hash(
1571                                    b"DLC/oracle/attestation/v0",
1572                                );
1573                                let outcome_hash =
1574                                    bitcoin::hashes::sha256::Hash::hash(&[(x + y + z) as u8]);
1575                                let mut hash_engine = bitcoin::hashes::sha256::Hash::engine();
1576                                hash_engine.input(&tag_hash[..]);
1577                                hash_engine.input(&tag_hash[..]);
1578                                hash_engine.input(&outcome_hash[..]);
1579                                let hash = bitcoin::hashes::sha256::Hash::from_engine(hash_engine);
1580                                Message::from_digest(hash.to_byte_array())
1581                            })
1582                            .collect()
1583                    })
1584                    .collect()
1585            })
1586            .collect();
1587
1588        for i in 0..NB_ORACLES {
1589            let oracle_kp = Keypair::new(&secp, &mut rng);
1590            let oracle_pubkey = oracle_kp.x_only_public_key().0;
1591            let mut nonces: Vec<XOnlyPublicKey> = Vec::with_capacity(NB_DIGITS);
1592            let mut sk_nonces: Vec<[u8; 32]> = Vec::with_capacity(NB_DIGITS);
1593            oracle_sigs.push(Vec::with_capacity(NB_DIGITS));
1594            for j in 0..NB_DIGITS {
1595                let mut sk_nonce = [0u8; 32];
1596                rng.fill_bytes(&mut sk_nonce);
1597                let oracle_r_kp = Keypair::from_seckey_slice(&secp, &sk_nonce).unwrap();
1598                let nonce = XOnlyPublicKey::from_keypair(&oracle_r_kp).0;
1599                let sig = secp_utils::schnorrsig_sign_with_nonce(
1600                    &secp,
1601                    &messages[0][i][j],
1602                    &oracle_kp,
1603                    &sk_nonce,
1604                );
1605                oracle_sigs[i].push(sig);
1606                nonces.push(nonce);
1607                sk_nonces.push(sk_nonce);
1608            }
1609            oracle_infos.push(OracleInfo {
1610                public_key: oracle_pubkey,
1611                nonces,
1612            });
1613            oracle_sk_nonce.push(sk_nonces);
1614            oracle_sks.push(oracle_kp);
1615        }
1616
1617        let funding_script_pubkey = make_funding_redeemscript(
1618            &offer_party_params.fund_pubkey,
1619            &accept_party_params.fund_pubkey,
1620        );
1621        let fund_output_value = dlc_txs.fund.output[0].value;
1622
1623        // Act
1624        let cet_sigs = create_cet_adaptor_sigs_from_oracle_info(
1625            &secp,
1626            &cets,
1627            &oracle_infos,
1628            &offer_fund_sk,
1629            &funding_script_pubkey,
1630            fund_output_value,
1631            &messages,
1632        )
1633        .unwrap();
1634
1635        let sign_res = sign_cet(
1636            &secp,
1637            &mut cets[0].clone(),
1638            &cet_sigs[0],
1639            &oracle_sigs,
1640            &accept_fund_sk,
1641            &offer_party_params.fund_pubkey,
1642            &funding_script_pubkey,
1643            fund_output_value,
1644        );
1645
1646        let adaptor_secret = signatures_to_secret(&oracle_sigs).unwrap();
1647        let adapted_sig = cet_sigs[0].decrypt(&adaptor_secret).unwrap();
1648
1649        // Assert
1650        assert!(cet_sigs
1651            .iter()
1652            .enumerate()
1653            .all(|(i, x)| verify_cet_adaptor_sig_from_oracle_info(
1654                &secp,
1655                x,
1656                &cets[i],
1657                &oracle_infos,
1658                &offer_party_params.fund_pubkey,
1659                &funding_script_pubkey,
1660                fund_output_value,
1661                &messages[i],
1662            )
1663            .is_ok()));
1664        sign_res.expect("Error signing CET");
1665        verify_tx_input_sig(
1666            &secp,
1667            &adapted_sig,
1668            &cets[0],
1669            0,
1670            &funding_script_pubkey,
1671            fund_output_value,
1672            &offer_party_params.fund_pubkey,
1673        )
1674        .expect("Invalid decrypted adaptor signature");
1675    }
1676
1677    #[test]
1678    fn input_output_ordering_test() {
1679        struct OrderingCase {
1680            serials: [u64; 3],
1681            expected_input_order: [usize; 2],
1682            expected_fund_output_order: [usize; 3],
1683            expected_payout_order: [usize; 2],
1684        }
1685
1686        let cases = vec![
1687            OrderingCase {
1688                serials: [0, 1, 2],
1689                expected_input_order: [0, 1],
1690                expected_fund_output_order: [0, 1, 2],
1691                expected_payout_order: [0, 1],
1692            },
1693            OrderingCase {
1694                serials: [1, 0, 2],
1695                expected_input_order: [0, 1],
1696                expected_fund_output_order: [1, 0, 2],
1697                expected_payout_order: [0, 1],
1698            },
1699            OrderingCase {
1700                serials: [2, 0, 1],
1701                expected_input_order: [0, 1],
1702                expected_fund_output_order: [2, 0, 1],
1703                expected_payout_order: [0, 1],
1704            },
1705            OrderingCase {
1706                serials: [2, 1, 0],
1707                expected_input_order: [1, 0],
1708                expected_fund_output_order: [2, 1, 0],
1709                expected_payout_order: [1, 0],
1710            },
1711        ];
1712
1713        for case in cases {
1714            let (offer_party_params, _) = get_party_params(
1715                Amount::from_sat(1000000000),
1716                Amount::from_sat(100000000),
1717                Some(case.serials[1]),
1718            );
1719            let (accept_party_params, _) = get_party_params(
1720                Amount::from_sat(1000000000),
1721                Amount::from_sat(100000000),
1722                Some(case.serials[2]),
1723            );
1724
1725            let dlc_txs = create_dlc_transactions(
1726                &offer_party_params,
1727                &accept_party_params,
1728                &[Payout {
1729                    offer: Amount::from_sat(100000000),
1730                    accept: Amount::from_sat(100000000),
1731                }],
1732                100,
1733                4,
1734                10,
1735                10,
1736                case.serials[0],
1737                0,
1738            )
1739            .unwrap();
1740
1741            // Check that fund inputs are in correct order
1742            assert!(
1743                dlc_txs.fund.input[case.expected_input_order[0]].previous_output
1744                    == offer_party_params.inputs[0].outpoint
1745            );
1746            assert!(
1747                dlc_txs.fund.input[case.expected_input_order[1]].previous_output
1748                    == accept_party_params.inputs[0].outpoint
1749            );
1750
1751            // Check that fund output are in correct order
1752            assert!(
1753                dlc_txs.fund.output[case.expected_fund_output_order[0]].script_pubkey
1754                    == dlc_txs.funding_script_pubkey.to_p2wsh()
1755            );
1756            assert!(
1757                dlc_txs.fund.output[case.expected_fund_output_order[1]].script_pubkey
1758                    == offer_party_params.change_script_pubkey
1759            );
1760            assert!(
1761                dlc_txs.fund.output[case.expected_fund_output_order[2]].script_pubkey
1762                    == accept_party_params.change_script_pubkey
1763            );
1764
1765            // Check payout output ordering
1766            assert!(
1767                dlc_txs.cets[0].output[case.expected_payout_order[0]].script_pubkey
1768                    == offer_party_params.payout_script_pubkey
1769            );
1770            assert!(
1771                dlc_txs.cets[0].output[case.expected_payout_order[1]].script_pubkey
1772                    == accept_party_params.payout_script_pubkey
1773            );
1774
1775            crate::util::get_output_for_script_pubkey(
1776                &dlc_txs.fund,
1777                &dlc_txs.funding_script_pubkey.to_p2wsh(),
1778            )
1779            .expect("Could not find fund output");
1780        }
1781    }
1782
1783    #[test]
1784    fn refund_to_accepter_flag_redirects_all_funds() {
1785        let secp = Secp256k1::new();
1786        let mut rng = secp256k1_zkp::rand::thread_rng();
1787
1788        let offer_collateral = Amount::from_sat(100000000);
1789        let accept_collateral = Amount::ZERO;
1790        let total_collateral = offer_collateral + accept_collateral;
1791
1792        let (offer_party_params, _) =
1793            get_party_params(Amount::from_sat(1000000000), offer_collateral, None);
1794
1795        let accept_fund_privkey = SecretKey::new(&mut rng);
1796        let accept_party_params = PartyParams {
1797            fund_pubkey: PublicKey::from_secret_key(&secp, &accept_fund_privkey),
1798            change_script_pubkey: get_p2wpkh_script_pubkey(&secp, &mut rng),
1799            change_serial_id: 2,
1800            payout_script_pubkey: get_p2wpkh_script_pubkey(&secp, &mut rng),
1801            payout_serial_id: 2,
1802            input_amount: Amount::ZERO,
1803            collateral: accept_collateral,
1804            inputs: vec![],
1805            dlc_inputs: vec![],
1806        };
1807
1808        let dlc_txs = create_dlc_transactions(
1809            &offer_party_params,
1810            &accept_party_params,
1811            &[Payout {
1812                offer: total_collateral,
1813                accept: Amount::ZERO,
1814            }],
1815            100,
1816            4,
1817            10,
1818            10,
1819            0,
1820            REFUND_TO_ACCEPTER_FLAG,
1821        )
1822        .unwrap();
1823
1824        let refund_tx = &dlc_txs.refund;
1825
1826        assert_eq!(
1827            refund_tx.output.len(),
1828            1,
1829            "Refund TX should have exactly one output when flag is set"
1830        );
1831        assert_eq!(
1832            refund_tx.output[0].value, total_collateral,
1833            "The single output should receive total collateral"
1834        );
1835        assert_eq!(
1836            refund_tx.output[0].script_pubkey, accept_party_params.payout_script_pubkey,
1837            "Output should pay to the accepter's payout SPK"
1838        );
1839    }
1840}