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 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 enhanced_offer_params.inputs.extend(offer_dlc_tx_inputs);
458 enhanced_accept_params.inputs.extend(accept_dlc_tx_inputs);
459
460 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#[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
521pub 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
586pub 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
657pub 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
682pub 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#[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
766pub 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
782pub 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
821pub 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
844pub 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
865pub 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
886pub 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
909pub 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#[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
993pub 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#[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
1034pub 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 let (party_params, _) =
1436 get_party_params(Amount::from_sat(100000), Amount::from_sat(10000), None);
1437
1438 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!(
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 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 let res = party_params.get_change_output_and_fees(total_collateral, 4, Amount::ZERO);
1459
1460 assert!(res.is_err());
1462 }
1463
1464 #[test]
1465 fn create_dlc_transactions_no_error() {
1466 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 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_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 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 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!(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 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 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 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}