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