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    let offer_dlc_tx_inputs = offer_params
445        .dlc_inputs
446        .iter()
447        .map(|input| input.into())
448        .collect::<Vec<TxInputInfo>>();
449
450    let accept_dlc_tx_inputs = accept_params
451        .dlc_inputs
452        .iter()
453        .map(|input| input.into())
454        .collect::<Vec<TxInputInfo>>();
455
456    // Add DLC inputs to regular inputs
457    enhanced_offer_params.inputs.extend(offer_dlc_tx_inputs);
458    enhanced_accept_params.inputs.extend(accept_dlc_tx_inputs);
459
460    // Clear DLC inputs from enhanced params since they're now regular inputs
461    enhanced_offer_params.dlc_inputs.clear();
462    enhanced_accept_params.dlc_inputs.clear();
463
464    create_dlc_transactions(
465        &enhanced_offer_params,
466        &enhanced_accept_params,
467        payouts,
468        refund_lock_time,
469        fee_rate_per_vb,
470        fund_lock_time,
471        cet_lock_time,
472        fund_output_serial_id,
473    )
474}
475
476/// Create the transactions for a DLC contract based on the provided parameters
477#[allow(clippy::too_many_arguments)]
478pub fn create_dlc_transactions(
479    offer_params: &PartyParams,
480    accept_params: &PartyParams,
481    payouts: &[Payout],
482    refund_lock_time: u32,
483    fee_rate_per_vb: u64,
484    fund_lock_time: u32,
485    cet_lock_time: u32,
486    fund_output_serial_id: u64,
487) -> Result<DlcTransactions, Error> {
488    let (fund_tx, funding_script_pubkey) = create_fund_transaction_with_fees(
489        offer_params,
490        accept_params,
491        fee_rate_per_vb,
492        fund_lock_time,
493        fund_output_serial_id,
494        Amount::ZERO,
495    )?;
496    let fund_outpoint = OutPoint {
497        txid: fund_tx.compute_txid(),
498        vout: util::get_output_for_script_pubkey(&fund_tx, &funding_script_pubkey.to_p2wsh())
499            .expect("to find the funding script pubkey")
500            .0 as u32,
501    };
502    let (cets, refund_tx) = create_cets_and_refund_tx(
503        offer_params,
504        accept_params,
505        fund_outpoint,
506        payouts,
507        refund_lock_time,
508        cet_lock_time,
509        None,
510    )?;
511
512    Ok(DlcTransactions {
513        fund: fund_tx,
514        cets,
515        refund: refund_tx,
516        funding_script_pubkey,
517        pending_close_txs: vec![],
518    })
519}
520
521/// Create a funding transaction with fees.
522pub fn create_fund_transaction_with_fees(
523    offer_params: &PartyParams,
524    accept_params: &PartyParams,
525    fee_rate_per_vb: u64,
526    fund_lock_time: u32,
527    fund_output_serial_id: u64,
528    extra_fee: Amount,
529) -> Result<(Transaction, ScriptBuf), Error> {
530    let total_collateral = checked_add!(offer_params.collateral, accept_params.collateral)?;
531
532    let (offer_change_output, offer_fund_fee, offer_cet_fee) =
533        offer_params.get_change_output_and_fees(total_collateral, fee_rate_per_vb, extra_fee)?;
534    let (accept_change_output, accept_fund_fee, accept_cet_fee) =
535        accept_params.get_change_output_and_fees(total_collateral, fee_rate_per_vb, extra_fee)?;
536
537    let fund_output_value = checked_add!(offer_params.input_amount, accept_params.input_amount)?
538        - offer_change_output.value
539        - accept_change_output.value
540        - offer_fund_fee
541        - accept_fund_fee
542        - extra_fee;
543
544    assert_eq!(
545        total_collateral + offer_cet_fee + accept_cet_fee + extra_fee,
546        fund_output_value
547    );
548
549    assert_eq!(
550        offer_params.input_amount + accept_params.input_amount,
551        fund_output_value
552            + offer_change_output.value
553            + accept_change_output.value
554            + offer_fund_fee
555            + accept_fund_fee
556            + extra_fee
557    );
558
559    let fund_sequence = util::get_sequence(fund_lock_time);
560    let (offer_tx_ins, offer_inputs_serial_ids) =
561        offer_params.get_unsigned_tx_inputs_and_serial_ids(fund_sequence);
562    let (accept_tx_ins, accept_inputs_serial_ids) =
563        accept_params.get_unsigned_tx_inputs_and_serial_ids(fund_sequence);
564
565    let funding_script_pubkey =
566        make_funding_redeemscript(&offer_params.fund_pubkey, &accept_params.fund_pubkey);
567
568    let fund_tx = create_funding_transaction(
569        &funding_script_pubkey,
570        fund_output_value,
571        &offer_tx_ins,
572        &offer_inputs_serial_ids,
573        &accept_tx_ins,
574        &accept_inputs_serial_ids,
575        offer_change_output,
576        offer_params.change_serial_id,
577        accept_change_output,
578        accept_params.change_serial_id,
579        fund_output_serial_id,
580        fund_lock_time,
581    );
582
583    Ok((fund_tx, funding_script_pubkey))
584}
585
586/// Create the contract execution transactions and refund transaction.
587pub fn create_cets_and_refund_tx(
588    offer_params: &PartyParams,
589    accept_params: &PartyParams,
590    prev_outpoint: OutPoint,
591    payouts: &[Payout],
592    refund_lock_time: u32,
593    cet_lock_time: u32,
594    cet_nsequence: Option<Sequence>,
595) -> Result<(Vec<Transaction>, Transaction), Error> {
596    let total_collateral = checked_add!(offer_params.collateral, accept_params.collateral)?;
597
598    let has_proper_outcomes = payouts.iter().all(|o| {
599        let total = checked_add!(o.offer, o.accept);
600        if let Ok(total) = total {
601            total == total_collateral
602        } else {
603            false
604        }
605    });
606
607    if !has_proper_outcomes {
608        return Err(Error::InvalidArgument(
609            "Payouts do not sum to total collateral".to_string(),
610        ));
611    }
612
613    let cet_input = TxIn {
614        previous_output: prev_outpoint,
615        witness: Witness::default(),
616        script_sig: ScriptBuf::default(),
617        sequence: cet_nsequence.unwrap_or_else(|| util::get_sequence(cet_lock_time)),
618    };
619
620    let cets = create_cets(
621        &cet_input,
622        &offer_params.payout_script_pubkey,
623        offer_params.payout_serial_id,
624        &accept_params.payout_script_pubkey,
625        accept_params.payout_serial_id,
626        payouts,
627        cet_lock_time,
628    );
629
630    let offer_refund_output = TxOut {
631        value: offer_params.collateral,
632        script_pubkey: offer_params.payout_script_pubkey.clone(),
633    };
634
635    let accept_refund_ouput = TxOut {
636        value: accept_params.collateral,
637        script_pubkey: accept_params.payout_script_pubkey.clone(),
638    };
639
640    let refund_input = TxIn {
641        previous_output: prev_outpoint,
642        witness: Witness::default(),
643        script_sig: ScriptBuf::default(),
644        sequence: util::ENABLE_LOCKTIME,
645    };
646
647    let refund_tx = create_refund_transaction(
648        offer_refund_output,
649        accept_refund_ouput,
650        refund_input,
651        refund_lock_time,
652    );
653
654    Ok((cets, refund_tx))
655}
656
657/// Create a contract execution transaction
658pub fn create_cet(
659    offer_output: TxOut,
660    offer_payout_serial_id: u64,
661    accept_output: TxOut,
662    accept_payout_serial_id: u64,
663    fund_tx_in: &TxIn,
664    lock_time: u32,
665) -> Transaction {
666    let mut output: Vec<TxOut> = if offer_payout_serial_id < accept_payout_serial_id {
667        vec![offer_output, accept_output]
668    } else {
669        vec![accept_output, offer_output]
670    };
671
672    output = util::discard_dust(output, DUST_LIMIT);
673
674    Transaction {
675        version: TX_VERSION,
676        lock_time: LockTime::from_consensus(lock_time),
677        input: vec![fund_tx_in.clone()],
678        output,
679    }
680}
681
682/// Create a set of contract execution transaction for each provided outcome
683pub fn create_cets(
684    fund_tx_input: &TxIn,
685    offer_payout_script_pubkey: &Script,
686    offer_payout_serial_id: u64,
687    accept_payout_script_pubkey: &Script,
688    accept_payout_serial_id: u64,
689    payouts: &[Payout],
690    lock_time: u32,
691) -> Vec<Transaction> {
692    let mut txs: Vec<Transaction> = Vec::with_capacity(payouts.len());
693    for payout in payouts {
694        let offer_output = TxOut {
695            value: payout.offer,
696            script_pubkey: offer_payout_script_pubkey.to_owned(),
697        };
698        let accept_output = TxOut {
699            value: payout.accept,
700            script_pubkey: accept_payout_script_pubkey.to_owned(),
701        };
702        let tx = create_cet(
703            offer_output,
704            offer_payout_serial_id,
705            accept_output,
706            accept_payout_serial_id,
707            fund_tx_input,
708            lock_time,
709        );
710
711        txs.push(tx);
712    }
713
714    txs
715}
716
717/// Create a funding transaction
718#[allow(clippy::too_many_arguments)]
719pub fn create_funding_transaction(
720    funding_script_pubkey: &Script,
721    output_amount: Amount,
722    offer_inputs: &[TxIn],
723    offer_inputs_serial_ids: &[u64],
724    accept_inputs: &[TxIn],
725    accept_inputs_serial_ids: &[u64],
726    offer_change_output: TxOut,
727    offer_change_serial_id: u64,
728    accept_change_output: TxOut,
729    accept_change_serial_id: u64,
730    fund_output_serial_id: u64,
731    lock_time: u32,
732) -> Transaction {
733    let fund_tx_out = TxOut {
734        value: output_amount,
735        script_pubkey: funding_script_pubkey.to_p2wsh(),
736    };
737
738    let output: Vec<TxOut> = {
739        let serial_ids = vec![
740            fund_output_serial_id,
741            offer_change_serial_id,
742            accept_change_serial_id,
743        ];
744        util::discard_dust(
745            util::order_by_serial_ids(
746                vec![fund_tx_out, offer_change_output, accept_change_output],
747                &serial_ids,
748            ),
749            DUST_LIMIT,
750        )
751    };
752
753    let input = util::order_by_serial_ids(
754        [offer_inputs, accept_inputs].concat(),
755        &[offer_inputs_serial_ids, accept_inputs_serial_ids].concat(),
756    );
757
758    Transaction {
759        version: TX_VERSION,
760        lock_time: LockTime::from_consensus(lock_time),
761        input,
762        output,
763    }
764}
765
766/// Create a refund transaction
767pub fn create_refund_transaction(
768    offer_output: TxOut,
769    accept_output: TxOut,
770    funding_input: TxIn,
771    locktime: u32,
772) -> Transaction {
773    let output = util::discard_dust(vec![offer_output, accept_output], DUST_LIMIT);
774    Transaction {
775        version: TX_VERSION,
776        lock_time: LockTime::from_consensus(locktime),
777        input: vec![funding_input],
778        output,
779    }
780}
781
782/// Create the multisig redeem script for the funding output
783pub fn make_funding_redeemscript(a: &PublicKey, b: &PublicKey) -> ScriptBuf {
784    let (first, second) = if a <= b { (a, b) } else { (b, a) };
785
786    Builder::new()
787        .push_opcode(opcodes::all::OP_PUSHNUM_2)
788        .push_slice(first.serialize())
789        .push_slice(second.serialize())
790        .push_opcode(opcodes::all::OP_PUSHNUM_2)
791        .push_opcode(opcodes::all::OP_CHECKMULTISIG)
792        .into_script()
793}
794
795fn get_oracle_sig_point<C: secp256k1_zkp::Verification>(
796    secp: &Secp256k1<C>,
797    oracle_info: &OracleInfo,
798    msgs: &[Message],
799) -> Result<PublicKey, Error> {
800    if oracle_info.nonces.len() < msgs.len() {
801        return Err(Error::InvalidArgument(format!(
802            "Oracle info nonces length is less than msgs length: {} < {}",
803            oracle_info.nonces.len(),
804            msgs.len()
805        )));
806    }
807
808    let sig_points: Vec<PublicKey> = oracle_info
809        .nonces
810        .iter()
811        .zip(msgs.iter())
812        .map(|(nonce, msg)| {
813            secp_utils::schnorrsig_compute_sig_point(secp, &oracle_info.public_key, nonce, msg)
814        })
815        .collect::<Result<Vec<PublicKey>, Error>>()?;
816    Ok(PublicKey::combine_keys(
817        &sig_points.iter().collect::<Vec<_>>(),
818    )?)
819}
820
821/// Get an adaptor point generated using the given oracle information and messages.
822pub fn get_adaptor_point_from_oracle_info<C: Verification>(
823    secp: &Secp256k1<C>,
824    oracle_infos: &[OracleInfo],
825    msgs: &[Vec<Message>],
826) -> Result<PublicKey, Error> {
827    if oracle_infos.is_empty() || msgs.is_empty() {
828        return Err(Error::InvalidArgument(format!(
829            "Oracle infos or msgs is empty. oracle_infos={} msgs={}",
830            oracle_infos.len(),
831            msgs.len()
832        )));
833    }
834
835    let mut oracle_sigpoints = Vec::with_capacity(msgs[0].len());
836    for (i, info) in oracle_infos.iter().enumerate() {
837        oracle_sigpoints.push(get_oracle_sig_point(secp, info, &msgs[i])?);
838    }
839    Ok(PublicKey::combine_keys(
840        &oracle_sigpoints.iter().collect::<Vec<_>>(),
841    )?)
842}
843
844/// Create an adaptor signature for the given cet using the provided adaptor point.
845pub fn create_cet_adaptor_sig_from_point<C: secp256k1_zkp::Signing>(
846    secp: &secp256k1_zkp::Secp256k1<C>,
847    cet: &Transaction,
848    adaptor_point: &PublicKey,
849    funding_sk: &SecretKey,
850    funding_script_pubkey: &Script,
851    fund_output_value: Amount,
852) -> Result<EcdsaAdaptorSignature, Error> {
853    let sig_hash = util::get_sig_hash_msg(cet, 0, funding_script_pubkey, fund_output_value)?;
854
855    #[cfg(feature = "std")]
856    let res = EcdsaAdaptorSignature::encrypt(secp, &sig_hash, funding_sk, adaptor_point);
857
858    #[cfg(not(feature = "std"))]
859    let res =
860        EcdsaAdaptorSignature::encrypt_no_aux_rand(secp, &sig_hash, funding_sk, adaptor_point);
861
862    Ok(res)
863}
864
865/// Create an adaptor signature for the given cet using the provided oracle infos.
866pub fn create_cet_adaptor_sig_from_oracle_info(
867    secp: &secp256k1_zkp::Secp256k1<secp256k1_zkp::All>,
868    cet: &Transaction,
869    oracle_infos: &[OracleInfo],
870    funding_sk: &SecretKey,
871    funding_script_pubkey: &Script,
872    fund_output_value: Amount,
873    msgs: &[Vec<Message>],
874) -> Result<EcdsaAdaptorSignature, Error> {
875    let adaptor_point = get_adaptor_point_from_oracle_info(secp, oracle_infos, msgs)?;
876    create_cet_adaptor_sig_from_point(
877        secp,
878        cet,
879        &adaptor_point,
880        funding_sk,
881        funding_script_pubkey,
882        fund_output_value,
883    )
884}
885
886/// Crerate a set of adaptor signatures for the given cet/message pairs.
887pub fn create_cet_adaptor_sigs_from_points<C: secp256k1_zkp::Signing>(
888    secp: &secp256k1_zkp::Secp256k1<C>,
889    inputs: &[(&Transaction, &PublicKey)],
890    funding_sk: &SecretKey,
891    funding_script_pubkey: &Script,
892    fund_output_value: Amount,
893) -> Result<Vec<EcdsaAdaptorSignature>, Error> {
894    inputs
895        .iter()
896        .map(|(cet, adaptor_point)| {
897            create_cet_adaptor_sig_from_point(
898                secp,
899                cet,
900                adaptor_point,
901                funding_sk,
902                funding_script_pubkey,
903                fund_output_value,
904            )
905        })
906        .collect()
907}
908
909/// Crerate a set of adaptor signatures for the given cet/message pairs.
910pub fn create_cet_adaptor_sigs_from_oracle_info(
911    secp: &secp256k1_zkp::Secp256k1<secp256k1_zkp::All>,
912    cets: &[Transaction],
913    oracle_infos: &[OracleInfo],
914    funding_sk: &SecretKey,
915    funding_script_pubkey: &Script,
916    fund_output_value: Amount,
917    msgs: &[Vec<Vec<Message>>],
918) -> Result<Vec<EcdsaAdaptorSignature>, Error> {
919    if msgs.len() != cets.len() {
920        return Err(Error::InvalidArgument(format!(
921            "Msgs length is not equal to cets length. msgs={} cets={}",
922            msgs.len(),
923            cets.len()
924        )));
925    }
926
927    cets.iter()
928        .zip(msgs.iter())
929        .map(|(cet, msg)| {
930            create_cet_adaptor_sig_from_oracle_info(
931                secp,
932                cet,
933                oracle_infos,
934                funding_sk,
935                funding_script_pubkey,
936                fund_output_value,
937                msg,
938            )
939        })
940        .collect()
941}
942
943fn signatures_to_secret(signatures: &[Vec<SchnorrSignature>]) -> Result<SecretKey, Error> {
944    let s_values = signatures
945        .iter()
946        .flatten()
947        .map(|x| match secp_utils::schnorrsig_decompose(x) {
948            Ok(v) => Ok(v.1),
949            Err(err) => Err(err),
950        })
951        .collect::<Result<Vec<&[u8]>, Error>>()?;
952    let secret = SecretKey::from_slice(s_values[0])?;
953
954    let result = s_values.iter().skip(1).fold(secret, |accum, s| {
955        let sec = SecretKey::from_slice(s).unwrap();
956        accum.add_tweak(&Scalar::from(sec)).unwrap()
957    });
958
959    Ok(result)
960}
961
962/// Sign the given cet using own private key, adapt the counter party signature
963/// and place both signatures and the funding multi sig script pubkey on the
964/// witness stack
965#[allow(clippy::too_many_arguments)]
966pub fn sign_cet<C: secp256k1_zkp::Signing>(
967    secp: &secp256k1_zkp::Secp256k1<C>,
968    cet: &mut Transaction,
969    adaptor_signature: &EcdsaAdaptorSignature,
970    oracle_signatures: &[Vec<SchnorrSignature>],
971    funding_sk: &SecretKey,
972    other_pk: &PublicKey,
973    funding_script_pubkey: &Script,
974    fund_output_value: Amount,
975) -> Result<(), Error> {
976    let adaptor_secret = signatures_to_secret(oracle_signatures)?;
977    let adapted_sig = adaptor_signature.decrypt(&adaptor_secret)?;
978
979    util::sign_multi_sig_input(
980        secp,
981        cet,
982        &adapted_sig,
983        other_pk,
984        funding_sk,
985        funding_script_pubkey,
986        fund_output_value,
987        0,
988    )?;
989
990    Ok(())
991}
992
993/// Verify that a given adaptor signature for a given cet is valid with respect
994/// to an adaptor point.
995pub fn verify_cet_adaptor_sig_from_point(
996    secp: &Secp256k1<secp256k1_zkp::All>,
997    adaptor_sig: &EcdsaAdaptorSignature,
998    cet: &Transaction,
999    adaptor_point: &PublicKey,
1000    pubkey: &PublicKey,
1001    funding_script_pubkey: &Script,
1002    total_collateral: Amount,
1003) -> Result<(), Error> {
1004    let sig_hash = util::get_sig_hash_msg(cet, 0, funding_script_pubkey, total_collateral)?;
1005    adaptor_sig.verify(secp, &sig_hash, pubkey, adaptor_point)?;
1006    Ok(())
1007}
1008
1009/// Verify that a given adaptor signature for a given cet is valid with respect
1010/// to an oracle public key, nonce and a given message.
1011#[allow(clippy::too_many_arguments)]
1012pub fn verify_cet_adaptor_sig_from_oracle_info(
1013    secp: &Secp256k1<secp256k1_zkp::All>,
1014    adaptor_sig: &EcdsaAdaptorSignature,
1015    cet: &Transaction,
1016    oracle_infos: &[OracleInfo],
1017    pubkey: &PublicKey,
1018    funding_script_pubkey: &Script,
1019    total_collateral: Amount,
1020    msgs: &[Vec<Message>],
1021) -> Result<(), Error> {
1022    let adaptor_point = get_adaptor_point_from_oracle_info(secp, oracle_infos, msgs)?;
1023    verify_cet_adaptor_sig_from_point(
1024        secp,
1025        adaptor_sig,
1026        cet,
1027        &adaptor_point,
1028        pubkey,
1029        funding_script_pubkey,
1030        total_collateral,
1031    )
1032}
1033
1034/// Verify a signature for a given transaction input.
1035pub fn verify_tx_input_sig<V: Verification>(
1036    secp: &Secp256k1<V>,
1037    signature: &Signature,
1038    tx: &Transaction,
1039    input_index: usize,
1040    script_pubkey: &Script,
1041    value: Amount,
1042    pk: &PublicKey,
1043) -> Result<(), Error> {
1044    let sig_hash_msg = util::get_sig_hash_msg(tx, input_index, script_pubkey, value)?;
1045    secp.verify_ecdsa(&sig_hash_msg, signature, pk)?;
1046    Ok(())
1047}
1048
1049#[cfg(test)]
1050mod tests {
1051    use super::*;
1052    use bitcoin::blockdata::script::ScriptBuf;
1053    use bitcoin::blockdata::transaction::OutPoint;
1054    use bitcoin::consensus::encode::Encodable;
1055    use bitcoin::hashes::Hash;
1056    use bitcoin::hashes::HashEngine;
1057    use bitcoin::sighash::EcdsaSighashType;
1058    use bitcoin::{Address, CompressedPublicKey, Network, Txid};
1059    use secp256k1_zkp::{
1060        rand::{Rng, RngCore},
1061        Keypair, PublicKey, Secp256k1, SecretKey, Signing,
1062    };
1063    use std::fmt::Write;
1064    use std::str::FromStr;
1065    use util;
1066
1067    fn create_txin_vec(sequence: Sequence) -> Vec<TxIn> {
1068        let mut inputs = Vec::new();
1069        let txin = TxIn {
1070            previous_output: OutPoint::default(),
1071            script_sig: ScriptBuf::new(),
1072            sequence,
1073            witness: Witness::new(),
1074        };
1075        inputs.push(txin);
1076        inputs
1077    }
1078
1079    fn create_multi_party_pub_keys() -> (PublicKey, PublicKey) {
1080        let secp = Secp256k1::new();
1081        let secret_key =
1082            SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000001")
1083                .unwrap();
1084        let pk = PublicKey::from_secret_key(&secp, &secret_key);
1085        let pk1 = pk;
1086
1087        (pk, pk1)
1088    }
1089
1090    fn create_test_tx_io() -> (TxOut, TxOut, TxIn) {
1091        let offer = TxOut {
1092            value: DUST_LIMIT + Amount::from_sat(1),
1093            script_pubkey: ScriptBuf::new(),
1094        };
1095
1096        let accept = TxOut {
1097            value: DUST_LIMIT + Amount::from_sat(2),
1098            script_pubkey: ScriptBuf::new(),
1099        };
1100
1101        let funding = TxIn {
1102            previous_output: OutPoint::default(),
1103            script_sig: ScriptBuf::new(),
1104            sequence: Sequence(3),
1105            witness: Witness::new(),
1106        };
1107
1108        (offer, accept, funding)
1109    }
1110
1111    #[test]
1112    fn create_refund_transaction_test() {
1113        let (offer, accept, funding) = create_test_tx_io();
1114
1115        let refund_transaction = create_refund_transaction(offer, accept, funding, 0);
1116        assert_eq!(Version::TWO, refund_transaction.version);
1117        assert_eq!(0, refund_transaction.lock_time.to_consensus_u32());
1118        assert_eq!(
1119            DUST_LIMIT + Amount::from_sat(1),
1120            refund_transaction.output[0].value
1121        );
1122        assert_eq!(
1123            DUST_LIMIT + Amount::from_sat(2),
1124            refund_transaction.output[1].value
1125        );
1126        assert_eq!(3, refund_transaction.input[0].sequence.0);
1127    }
1128
1129    #[test]
1130    fn create_funding_transaction_test() {
1131        let (pk, pk1) = create_multi_party_pub_keys();
1132
1133        let offer_inputs = create_txin_vec(Sequence::ZERO);
1134        let accept_inputs = create_txin_vec(Sequence(1));
1135
1136        let change = Amount::from_sat(1000);
1137
1138        let total_collateral = Amount::from_sat(31415);
1139
1140        let offer_change_output = TxOut {
1141            value: change,
1142            script_pubkey: ScriptBuf::new(),
1143        };
1144        let accept_change_output = TxOut {
1145            value: change,
1146            script_pubkey: ScriptBuf::new(),
1147        };
1148        let funding_script_pubkey = make_funding_redeemscript(&pk, &pk1);
1149
1150        let transaction = create_funding_transaction(
1151            &funding_script_pubkey,
1152            total_collateral,
1153            &offer_inputs,
1154            &[1],
1155            &accept_inputs,
1156            &[2],
1157            offer_change_output,
1158            0,
1159            accept_change_output,
1160            1,
1161            0,
1162            0,
1163        );
1164
1165        assert_eq!(transaction.input[0].sequence.0, 0);
1166        assert_eq!(transaction.input[1].sequence.0, 1);
1167
1168        assert_eq!(transaction.output[0].value, total_collateral);
1169        assert_eq!(transaction.output[1].value, change);
1170        assert_eq!(transaction.output[2].value, change);
1171        assert_eq!(transaction.output.len(), 3);
1172    }
1173
1174    #[test]
1175    fn create_funding_transaction_with_outputs_less_than_dust_limit_test() {
1176        let (pk, pk1) = create_multi_party_pub_keys();
1177
1178        let offer_inputs = create_txin_vec(Sequence::ZERO);
1179        let accept_inputs = create_txin_vec(Sequence(1));
1180
1181        let total_collateral = Amount::from_sat(31415);
1182        let change = Amount::from_sat(999);
1183
1184        let offer_change_output = TxOut {
1185            value: change,
1186            script_pubkey: ScriptBuf::new(),
1187        };
1188        let accept_change_output = TxOut {
1189            value: change,
1190            script_pubkey: ScriptBuf::new(),
1191        };
1192
1193        let funding_script_pubkey = make_funding_redeemscript(&pk, &pk1);
1194
1195        let transaction = create_funding_transaction(
1196            &funding_script_pubkey,
1197            total_collateral,
1198            &offer_inputs,
1199            &[1],
1200            &accept_inputs,
1201            &[2],
1202            offer_change_output,
1203            0,
1204            accept_change_output,
1205            1,
1206            0,
1207            0,
1208        );
1209
1210        assert_eq!(transaction.output[0].value, total_collateral);
1211        assert_eq!(transaction.output.len(), 1);
1212    }
1213
1214    #[test]
1215    fn create_funding_transaction_serialized_test() {
1216        let secp = Secp256k1::new();
1217        let input_amount = Amount::from_sat(5000000000);
1218        let change = Amount::from_sat(4899999719);
1219        let total_collateral = Amount::from_sat(200000312);
1220        let offer_change_address =
1221            Address::from_str("bcrt1qlgmznucxpdkp5k3ktsct7eh6qrc4tju7ktjukn")
1222                .unwrap()
1223                .assume_checked();
1224        let accept_change_address =
1225            Address::from_str("bcrt1qvh2dvgjctwh4z5w7sc93u7h4sug0yrdz2lgpqf")
1226                .unwrap()
1227                .assume_checked();
1228
1229        let offer_change_output = TxOut {
1230            value: change,
1231            script_pubkey: offer_change_address.script_pubkey(),
1232        };
1233
1234        let accept_change_output = TxOut {
1235            value: change,
1236            script_pubkey: accept_change_address.script_pubkey(),
1237        };
1238
1239        let offer_input = TxIn {
1240            previous_output: OutPoint {
1241                txid: Txid::from_str(
1242                    "83266d6b22a9babf6ee469b88fd0d3a0c690525f7c903aff22ec8ee44214604f",
1243                )
1244                .unwrap(),
1245                vout: 0,
1246            },
1247            script_sig: ScriptBuf::new(),
1248            sequence: Sequence(0xffffffff),
1249            witness: Witness::from_slice(&[ScriptBuf::new().to_bytes()]),
1250        };
1251
1252        let accept_input = TxIn {
1253            previous_output: OutPoint {
1254                txid: Txid::from_str(
1255                    "bc92a22f07ef23c53af343397874b59f5f8c0eb37753af1d1a159a2177d4bb98",
1256                )
1257                .unwrap(),
1258                vout: 0,
1259            },
1260            script_sig: ScriptBuf::new(),
1261            sequence: Sequence(0xffffffff),
1262            witness: Witness::from_slice(&[ScriptBuf::new().to_bytes()]),
1263        };
1264        let offer_fund_sk =
1265            SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000001")
1266                .unwrap();
1267        let offer_fund_pubkey = PublicKey::from_secret_key(&secp, &offer_fund_sk);
1268        let accept_fund_sk =
1269            SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000002")
1270                .unwrap();
1271        let accept_fund_pubkey = PublicKey::from_secret_key(&secp, &accept_fund_sk);
1272        let offer_input_sk =
1273            SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000005")
1274                .unwrap();
1275        let accept_input_sk =
1276            SecretKey::from_str("0000000000000000000000000000000000000000000000000000000000000006")
1277                .unwrap();
1278
1279        let expected_serialized = "020000000001024F601442E48EEC22FF3A907C5F5290C6A0D3D08FB869E46EBFBAA9226B6D26830000000000FFFFFFFF98BBD477219A151A1DAF5377B30E8C5F9FB574783943F33AC523EF072FA292BC0000000000FFFFFFFF0338C3EB0B000000002200209B984C7BAE3EFDDC3A3F0A20FF81BFE89ED1FE07FF13E562149EE654BED845DBE70F102401000000160014FA3629F3060B6C1A5A365C30BF66FA00F155CB9EE70F10240100000016001465D4D622585BAF5151DE860B1E7AF58710F20DA20247304402207108DE1563AE311F8D4217E1C0C7463386C1A135BE6AF88CBE8D89A3A08D65090220195A2B0140FB9BA83F20CF45AD6EA088BB0C6860C0D4995F1CF1353739CA65A90121022F8BDE4D1A07209355B4A7250A5C5128E88B84BDDC619AB7CBA8D569B240EFE4024730440220048716EAEE918AEBCB1BFCFAF7564E78293A7BB0164D9A7844E42FCEB5AE393C022022817D033C9DB19C5BDCADD49B7587A810B6FC2264158A59665ABA8AB298455B012103FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A146029755600000000";
1280
1281        let funding_script_pubkey =
1282            make_funding_redeemscript(&offer_fund_pubkey, &accept_fund_pubkey);
1283
1284        let mut fund_tx = create_funding_transaction(
1285            &funding_script_pubkey,
1286            total_collateral,
1287            &[offer_input],
1288            &[1],
1289            &[accept_input],
1290            &[2],
1291            offer_change_output,
1292            0,
1293            accept_change_output,
1294            1,
1295            0,
1296            0,
1297        );
1298
1299        util::sign_p2wpkh_input(
1300            &secp,
1301            &offer_input_sk,
1302            &mut fund_tx,
1303            0,
1304            EcdsaSighashType::All,
1305            input_amount,
1306        )
1307        .expect("to be able to sign the input.");
1308
1309        util::sign_p2wpkh_input(
1310            &secp,
1311            &accept_input_sk,
1312            &mut fund_tx,
1313            1,
1314            EcdsaSighashType::All,
1315            input_amount,
1316        )
1317        .expect("to be able to sign the input.");
1318
1319        let mut writer = Vec::new();
1320        fund_tx.consensus_encode(&mut writer).unwrap();
1321        let mut serialized = String::new();
1322        for x in writer {
1323            write!(&mut serialized, "{:02X}", x).unwrap();
1324        }
1325
1326        assert_eq!(expected_serialized, serialized);
1327    }
1328
1329    fn get_p2wpkh_script_pubkey<C: Signing, R: Rng + ?Sized>(
1330        secp: &Secp256k1<C>,
1331        rng: &mut R,
1332    ) -> ScriptBuf {
1333        let sk = bitcoin::PrivateKey {
1334            inner: SecretKey::new(rng),
1335            network: Network::Testnet.into(),
1336            compressed: true,
1337        };
1338        let pk = CompressedPublicKey::from_private_key(secp, &sk).unwrap();
1339        Address::p2wpkh(&pk, Network::Testnet).script_pubkey()
1340    }
1341
1342    fn get_party_params(
1343        input_amount: Amount,
1344        collateral: Amount,
1345        serial_id: Option<u64>,
1346    ) -> (PartyParams, SecretKey) {
1347        let secp = Secp256k1::new();
1348        let mut rng = secp256k1_zkp::rand::thread_rng();
1349        let fund_privkey = SecretKey::new(&mut rng);
1350        let serial_id = serial_id.unwrap_or(1);
1351        (
1352            PartyParams {
1353                fund_pubkey: PublicKey::from_secret_key(&secp, &fund_privkey),
1354                change_script_pubkey: get_p2wpkh_script_pubkey(&secp, &mut rng),
1355                change_serial_id: serial_id,
1356                payout_script_pubkey: get_p2wpkh_script_pubkey(&secp, &mut rng),
1357                payout_serial_id: serial_id,
1358                input_amount,
1359                collateral,
1360                inputs: vec![TxInputInfo {
1361                    max_witness_len: 108,
1362                    redeem_script: ScriptBuf::new(),
1363                    outpoint: OutPoint {
1364                        txid: Txid::from_str(
1365                            "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456",
1366                        )
1367                        .unwrap(),
1368                        vout: serial_id as u32,
1369                    },
1370                    serial_id,
1371                }],
1372                dlc_inputs: vec![],
1373            },
1374            fund_privkey,
1375        )
1376    }
1377
1378    fn payouts() -> Vec<Payout> {
1379        vec![
1380            Payout {
1381                offer: Amount::from_sat(200_000_000),
1382                accept: Amount::ZERO,
1383            },
1384            Payout {
1385                offer: Amount::ZERO,
1386                accept: Amount::from_sat(200_000_000),
1387            },
1388        ]
1389    }
1390
1391    #[test]
1392    fn get_change_output_and_fees_no_inputs_no_funding() {
1393        let (party_params, _) = get_party_params(Amount::ZERO, Amount::ZERO, None);
1394
1395        let total_collateral = Amount::ONE_BTC;
1396
1397        let (change_out, fund_fee, cet_fee) = party_params
1398            .get_change_output_and_fees(total_collateral, 4, Amount::ZERO)
1399            .unwrap();
1400
1401        assert_eq!(change_out.value, Amount::ZERO);
1402        assert_eq!(fund_fee, Amount::ZERO);
1403        assert_eq!(cet_fee, Amount::ZERO);
1404    }
1405
1406    #[test]
1407    fn get_change_output_and_fees_single_funded_vs_dual_funded() {
1408        let (party_params_single_funded, _) =
1409            get_party_params(Amount::from_sat(150_000_000), Amount::ONE_BTC, None);
1410
1411        let total_collateral = Amount::ONE_BTC;
1412
1413        let (change_out_single_funded, fund_fee_single_funded, cet_fee_single_funded) =
1414            party_params_single_funded
1415                .get_change_output_and_fees(total_collateral, 4, Amount::ZERO)
1416                .unwrap();
1417
1418        let (party_params_dual_funded, _) =
1419            get_party_params(Amount::from_sat(150_000_000), Amount::ONE_BTC, None);
1420        let total_collateral = Amount::ONE_BTC + Amount::ONE_BTC;
1421
1422        let (change_out_dual_funded, fund_fee_dual_funded, cet_fee_dual_funded) =
1423            party_params_dual_funded
1424                .get_change_output_and_fees(total_collateral, 4, Amount::ZERO)
1425                .unwrap();
1426
1427        assert!(change_out_single_funded.value < change_out_dual_funded.value);
1428        assert!(fund_fee_single_funded > fund_fee_dual_funded);
1429        assert!(cet_fee_single_funded > cet_fee_dual_funded);
1430    }
1431
1432    #[test]
1433    fn get_change_output_and_fees_enough_funds() {
1434        // Arrange
1435        let (party_params, _) =
1436            get_party_params(Amount::from_sat(100000), Amount::from_sat(10000), None);
1437
1438        // Act
1439        let total_collateral = Amount::from_sat(100001);
1440        let (change_out, fund_fee, cet_fee) = party_params
1441            .get_change_output_and_fees(total_collateral, 4, Amount::ZERO)
1442            .unwrap();
1443
1444        // Assert
1445        assert!(
1446            change_out.value > Amount::ZERO && fund_fee > Amount::ZERO && cet_fee > Amount::ZERO
1447        );
1448    }
1449
1450    #[test]
1451    fn get_change_output_and_fees_not_enough_funds() {
1452        // Arrange
1453        let (party_params, _) =
1454            get_party_params(Amount::from_sat(100000), Amount::from_sat(100000), None);
1455
1456        let total_collateral = Amount::from_sat(100001);
1457        // Act
1458        let res = party_params.get_change_output_and_fees(total_collateral, 4, Amount::ZERO);
1459
1460        // Assert
1461        assert!(res.is_err());
1462    }
1463
1464    #[test]
1465    fn create_dlc_transactions_no_error() {
1466        // Arrange
1467        let (offer_party_params, _) = get_party_params(
1468            Amount::from_sat(1000000000),
1469            Amount::from_sat(100000000),
1470            None,
1471        );
1472        let (accept_party_params, _) = get_party_params(
1473            Amount::from_sat(1000000000),
1474            Amount::from_sat(100000000),
1475            None,
1476        );
1477
1478        // Act
1479        let dlc_txs = create_dlc_transactions(
1480            &offer_party_params,
1481            &accept_party_params,
1482            &payouts(),
1483            100,
1484            4,
1485            10,
1486            10,
1487            0,
1488        )
1489        .unwrap();
1490
1491        // Assert
1492        assert_eq!(10, dlc_txs.fund.lock_time.to_consensus_u32());
1493        assert_eq!(100, dlc_txs.refund.lock_time.to_consensus_u32());
1494        assert!(dlc_txs
1495            .cets
1496            .iter()
1497            .all(|x| x.lock_time.to_consensus_u32() == 10));
1498    }
1499
1500    #[test]
1501    fn create_cet_adaptor_sig_is_valid() {
1502        // Arrange
1503        let secp = Secp256k1::new();
1504        let mut rng = secp256k1_zkp::rand::thread_rng();
1505        let (offer_party_params, offer_fund_sk) = get_party_params(
1506            Amount::from_sat(1000000000),
1507            Amount::from_sat(100000000),
1508            None,
1509        );
1510        let (accept_party_params, accept_fund_sk) = get_party_params(
1511            Amount::from_sat(1000000000),
1512            Amount::from_sat(100000000),
1513            None,
1514        );
1515
1516        let dlc_txs = create_dlc_transactions(
1517            &offer_party_params,
1518            &accept_party_params,
1519            &payouts(),
1520            100,
1521            4,
1522            10,
1523            10,
1524            0,
1525        )
1526        .unwrap();
1527
1528        let cets = dlc_txs.cets;
1529        const NB_ORACLES: usize = 3;
1530        const NB_OUTCOMES: usize = 2;
1531        const NB_DIGITS: usize = 20;
1532        let mut oracle_infos: Vec<OracleInfo> = Vec::with_capacity(NB_ORACLES);
1533        let mut oracle_sks: Vec<Keypair> = Vec::with_capacity(NB_ORACLES);
1534        let mut oracle_sk_nonce: Vec<Vec<[u8; 32]>> = Vec::with_capacity(NB_ORACLES);
1535        let mut oracle_sigs: Vec<Vec<SchnorrSignature>> = Vec::with_capacity(NB_ORACLES);
1536        let messages: Vec<Vec<Vec<_>>> = (0..NB_OUTCOMES)
1537            .map(|x| {
1538                (0..NB_ORACLES)
1539                    .map(|y| {
1540                        (0..NB_DIGITS)
1541                            .map(|z| {
1542                                let tag_hash = bitcoin::hashes::sha256::Hash::hash(
1543                                    b"DLC/oracle/attestation/v0",
1544                                );
1545                                let outcome_hash =
1546                                    bitcoin::hashes::sha256::Hash::hash(&[(x + y + z) as u8]);
1547                                let mut hash_engine = bitcoin::hashes::sha256::Hash::engine();
1548                                hash_engine.input(&tag_hash[..]);
1549                                hash_engine.input(&tag_hash[..]);
1550                                hash_engine.input(&outcome_hash[..]);
1551                                let hash = bitcoin::hashes::sha256::Hash::from_engine(hash_engine);
1552                                Message::from_digest(hash.to_byte_array())
1553                            })
1554                            .collect()
1555                    })
1556                    .collect()
1557            })
1558            .collect();
1559
1560        for i in 0..NB_ORACLES {
1561            let oracle_kp = Keypair::new(&secp, &mut rng);
1562            let oracle_pubkey = oracle_kp.x_only_public_key().0;
1563            let mut nonces: Vec<XOnlyPublicKey> = Vec::with_capacity(NB_DIGITS);
1564            let mut sk_nonces: Vec<[u8; 32]> = Vec::with_capacity(NB_DIGITS);
1565            oracle_sigs.push(Vec::with_capacity(NB_DIGITS));
1566            for j in 0..NB_DIGITS {
1567                let mut sk_nonce = [0u8; 32];
1568                rng.fill_bytes(&mut sk_nonce);
1569                let oracle_r_kp = Keypair::from_seckey_slice(&secp, &sk_nonce).unwrap();
1570                let nonce = XOnlyPublicKey::from_keypair(&oracle_r_kp).0;
1571                let sig = secp_utils::schnorrsig_sign_with_nonce(
1572                    &secp,
1573                    &messages[0][i][j],
1574                    &oracle_kp,
1575                    &sk_nonce,
1576                );
1577                oracle_sigs[i].push(sig);
1578                nonces.push(nonce);
1579                sk_nonces.push(sk_nonce);
1580            }
1581            oracle_infos.push(OracleInfo {
1582                public_key: oracle_pubkey,
1583                nonces,
1584            });
1585            oracle_sk_nonce.push(sk_nonces);
1586            oracle_sks.push(oracle_kp);
1587        }
1588
1589        let funding_script_pubkey = make_funding_redeemscript(
1590            &offer_party_params.fund_pubkey,
1591            &accept_party_params.fund_pubkey,
1592        );
1593        let fund_output_value = dlc_txs.fund.output[0].value;
1594
1595        // Act
1596        let cet_sigs = create_cet_adaptor_sigs_from_oracle_info(
1597            &secp,
1598            &cets,
1599            &oracle_infos,
1600            &offer_fund_sk,
1601            &funding_script_pubkey,
1602            fund_output_value,
1603            &messages,
1604        )
1605        .unwrap();
1606
1607        let sign_res = sign_cet(
1608            &secp,
1609            &mut cets[0].clone(),
1610            &cet_sigs[0],
1611            &oracle_sigs,
1612            &accept_fund_sk,
1613            &offer_party_params.fund_pubkey,
1614            &funding_script_pubkey,
1615            fund_output_value,
1616        );
1617
1618        let adaptor_secret = signatures_to_secret(&oracle_sigs).unwrap();
1619        let adapted_sig = cet_sigs[0].decrypt(&adaptor_secret).unwrap();
1620
1621        // Assert
1622        assert!(cet_sigs
1623            .iter()
1624            .enumerate()
1625            .all(|(i, x)| verify_cet_adaptor_sig_from_oracle_info(
1626                &secp,
1627                x,
1628                &cets[i],
1629                &oracle_infos,
1630                &offer_party_params.fund_pubkey,
1631                &funding_script_pubkey,
1632                fund_output_value,
1633                &messages[i],
1634            )
1635            .is_ok()));
1636        sign_res.expect("Error signing CET");
1637        verify_tx_input_sig(
1638            &secp,
1639            &adapted_sig,
1640            &cets[0],
1641            0,
1642            &funding_script_pubkey,
1643            fund_output_value,
1644            &offer_party_params.fund_pubkey,
1645        )
1646        .expect("Invalid decrypted adaptor signature");
1647    }
1648
1649    #[test]
1650    fn input_output_ordering_test() {
1651        struct OrderingCase {
1652            serials: [u64; 3],
1653            expected_input_order: [usize; 2],
1654            expected_fund_output_order: [usize; 3],
1655            expected_payout_order: [usize; 2],
1656        }
1657
1658        let cases = vec![
1659            OrderingCase {
1660                serials: [0, 1, 2],
1661                expected_input_order: [0, 1],
1662                expected_fund_output_order: [0, 1, 2],
1663                expected_payout_order: [0, 1],
1664            },
1665            OrderingCase {
1666                serials: [1, 0, 2],
1667                expected_input_order: [0, 1],
1668                expected_fund_output_order: [1, 0, 2],
1669                expected_payout_order: [0, 1],
1670            },
1671            OrderingCase {
1672                serials: [2, 0, 1],
1673                expected_input_order: [0, 1],
1674                expected_fund_output_order: [2, 0, 1],
1675                expected_payout_order: [0, 1],
1676            },
1677            OrderingCase {
1678                serials: [2, 1, 0],
1679                expected_input_order: [1, 0],
1680                expected_fund_output_order: [2, 1, 0],
1681                expected_payout_order: [1, 0],
1682            },
1683        ];
1684
1685        for case in cases {
1686            let (offer_party_params, _) = get_party_params(
1687                Amount::from_sat(1000000000),
1688                Amount::from_sat(100000000),
1689                Some(case.serials[1]),
1690            );
1691            let (accept_party_params, _) = get_party_params(
1692                Amount::from_sat(1000000000),
1693                Amount::from_sat(100000000),
1694                Some(case.serials[2]),
1695            );
1696
1697            let dlc_txs = create_dlc_transactions(
1698                &offer_party_params,
1699                &accept_party_params,
1700                &[Payout {
1701                    offer: Amount::from_sat(100000000),
1702                    accept: Amount::from_sat(100000000),
1703                }],
1704                100,
1705                4,
1706                10,
1707                10,
1708                case.serials[0],
1709            )
1710            .unwrap();
1711
1712            // Check that fund inputs are in correct order
1713            assert!(
1714                dlc_txs.fund.input[case.expected_input_order[0]].previous_output
1715                    == offer_party_params.inputs[0].outpoint
1716            );
1717            assert!(
1718                dlc_txs.fund.input[case.expected_input_order[1]].previous_output
1719                    == accept_party_params.inputs[0].outpoint
1720            );
1721
1722            // Check that fund output are in correct order
1723            assert!(
1724                dlc_txs.fund.output[case.expected_fund_output_order[0]].script_pubkey
1725                    == dlc_txs.funding_script_pubkey.to_p2wsh()
1726            );
1727            assert!(
1728                dlc_txs.fund.output[case.expected_fund_output_order[1]].script_pubkey
1729                    == offer_party_params.change_script_pubkey
1730            );
1731            assert!(
1732                dlc_txs.fund.output[case.expected_fund_output_order[2]].script_pubkey
1733                    == accept_party_params.change_script_pubkey
1734            );
1735
1736            // Check payout output ordering
1737            assert!(
1738                dlc_txs.cets[0].output[case.expected_payout_order[0]].script_pubkey
1739                    == offer_party_params.payout_script_pubkey
1740            );
1741            assert!(
1742                dlc_txs.cets[0].output[case.expected_payout_order[1]].script_pubkey
1743                    == accept_party_params.payout_script_pubkey
1744            );
1745
1746            crate::util::get_output_for_script_pubkey(
1747                &dlc_txs.fund,
1748                &dlc_txs.funding_script_pubkey.to_p2wsh(),
1749            )
1750            .expect("Could not find fund output");
1751        }
1752    }
1753}