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