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