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