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