dlc_messages/
lib.rs

1//! Data structure and functions related to peer communication.
2
3// Coding conventions
4#![forbid(unsafe_code)]
5#![deny(non_upper_case_globals)]
6#![deny(non_camel_case_types)]
7#![deny(non_snake_case)]
8#![deny(unused_mut)]
9#![deny(dead_code)]
10#![deny(unused_imports)]
11#![deny(missing_docs)]
12
13extern crate bitcoin;
14extern crate dlc;
15extern crate lightning;
16extern crate secp256k1_zkp;
17#[macro_use]
18pub mod ser_macros;
19pub mod ser_impls;
20
21#[cfg(any(test, feature = "use-serde"))]
22extern crate serde;
23
24#[cfg(test)]
25extern crate serde_json;
26
27pub mod channel;
28pub mod contract_msgs;
29pub mod message_handler;
30pub mod oracle_msgs;
31pub mod segmentation;
32
33#[cfg(any(test, feature = "use-serde"))]
34pub mod serde_utils;
35
36use std::fmt::Display;
37
38use crate::ser_impls::{read_ecdsa_adaptor_signature, write_ecdsa_adaptor_signature};
39use bitcoin::{consensus::Decodable, OutPoint, Transaction};
40use bitcoin::{Amount, ScriptBuf};
41use channel::{
42    AcceptChannel, CollaborativeCloseOffer, OfferChannel, Reject, RenewAccept, RenewConfirm,
43    RenewFinalize, RenewOffer, RenewRevoke, SettleAccept, SettleConfirm, SettleFinalize,
44    SettleOffer, SignChannel,
45};
46use contract_msgs::ContractInfo;
47use dlc::{Error, TxInputInfo};
48use lightning::ln::msgs::DecodeError;
49use lightning::ln::wire::Type;
50use lightning::util::ser::{Readable, Writeable, Writer};
51use secp256k1_zkp::Verification;
52use secp256k1_zkp::{ecdsa::Signature, EcdsaAdaptorSignature, PublicKey, Secp256k1};
53use segmentation::{SegmentChunk, SegmentStart};
54
55macro_rules! impl_type {
56    ($const_name: ident, $type_name: ident, $type_val: expr) => {
57        /// The type prefix for an [`$type_name`] message.
58        pub const $const_name: u16 = $type_val;
59
60        impl Type for $type_name {
61            fn type_id(&self) -> u16 {
62                $const_name
63            }
64        }
65    };
66}
67
68impl_type!(OFFER_TYPE, OfferDlc, 42778);
69impl_type!(ACCEPT_TYPE, AcceptDlc, 42780);
70impl_type!(SIGN_TYPE, SignDlc, 42782);
71impl_type!(OFFER_CHANNEL_TYPE, OfferChannel, 43000);
72impl_type!(ACCEPT_CHANNEL_TYPE, AcceptChannel, 43002);
73impl_type!(SIGN_CHANNEL_TYPE, SignChannel, 43004);
74impl_type!(SETTLE_CHANNEL_OFFER_TYPE, SettleOffer, 43006);
75impl_type!(SETTLE_CHANNEL_ACCEPT_TYPE, SettleAccept, 43008);
76impl_type!(SETTLE_CHANNEL_CONFIRM_TYPE, SettleConfirm, 43010);
77impl_type!(SETTLE_CHANNEL_FINALIZE_TYPE, SettleFinalize, 43012);
78impl_type!(RENEW_CHANNEL_OFFER_TYPE, RenewOffer, 43014);
79impl_type!(RENEW_CHANNEL_ACCEPT_TYPE, RenewAccept, 43016);
80impl_type!(RENEW_CHANNEL_CONFIRM_TYPE, RenewConfirm, 43018);
81impl_type!(RENEW_CHANNEL_FINALIZE_TYPE, RenewFinalize, 43020);
82impl_type!(RENEW_CHANNEL_REVOKE_TYPE, RenewRevoke, 43026);
83impl_type!(
84    COLLABORATIVE_CLOSE_OFFER_TYPE,
85    CollaborativeCloseOffer,
86    43022
87);
88impl_type!(REJECT, Reject, 43024);
89
90#[derive(Clone, Debug, PartialEq, Eq)]
91#[cfg_attr(
92    feature = "use-serde",
93    derive(serde::Serialize, serde::Deserialize),
94    serde(rename_all = "camelCase")
95)]
96/// Contains information about a specific input to be used in a funding transaction,
97/// as well as its corresponding on-chain UTXO.
98pub struct FundingInput {
99    /// Serial id used for input ordering in the funding transaction.
100    pub input_serial_id: u64,
101    #[cfg_attr(
102        feature = "use-serde",
103        serde(
104            serialize_with = "crate::serde_utils::serialize_hex",
105            deserialize_with = "crate::serde_utils::deserialize_hex_string"
106        )
107    )]
108    /// The previous transaction used by the associated input in serialized format.
109    pub prev_tx: Vec<u8>,
110    /// The vout of the output used by the associated input.
111    pub prev_tx_vout: u32,
112    /// The sequence number to use for the input.
113    pub sequence: u32,
114    /// The maximum witness length that can be used to spend the previous UTXO.
115    pub max_witness_len: u16,
116    /// The redeem script of the previous UTXO.
117    pub redeem_script: ScriptBuf,
118}
119
120impl_dlc_writeable!(FundingInput, {
121    (input_serial_id, writeable),
122    (prev_tx, vec),
123    (prev_tx_vout, writeable),
124    (sequence, writeable),
125    (max_witness_len, writeable),
126    (redeem_script, writeable)
127});
128
129impl From<&FundingInput> for TxInputInfo {
130    fn from(funding_input: &FundingInput) -> TxInputInfo {
131        TxInputInfo {
132            outpoint: OutPoint {
133                txid: Transaction::consensus_decode(&mut funding_input.prev_tx.as_slice())
134                    .expect("Transaction Decode Error")
135                    .compute_txid(),
136                vout: funding_input.prev_tx_vout,
137            },
138            max_witness_len: (funding_input.max_witness_len as usize),
139            redeem_script: funding_input.redeem_script.clone(),
140            serial_id: funding_input.input_serial_id,
141        }
142    }
143}
144
145#[derive(Clone, Debug, PartialEq, Eq)]
146#[cfg_attr(
147    feature = "use-serde",
148    derive(serde::Serialize, serde::Deserialize),
149    serde(rename_all = "camelCase")
150)]
151/// Contains an adaptor signature for a CET input and its associated DLEQ proof.
152pub struct CetAdaptorSignature {
153    /// The signature.
154    pub signature: EcdsaAdaptorSignature,
155}
156
157impl_dlc_writeable!(CetAdaptorSignature, {
158     (signature, { cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature })
159});
160
161#[derive(Clone, Debug, PartialEq, Eq)]
162#[cfg_attr(
163    feature = "use-serde",
164    derive(serde::Serialize, serde::Deserialize),
165    serde(rename_all = "camelCase")
166)]
167/// Contains a list of adaptor signature for a number of CET inputs.
168pub struct CetAdaptorSignatures {
169    /// The set of signatures.
170    pub ecdsa_adaptor_signatures: Vec<CetAdaptorSignature>,
171}
172
173impl From<&[EcdsaAdaptorSignature]> for CetAdaptorSignatures {
174    fn from(signatures: &[EcdsaAdaptorSignature]) -> Self {
175        CetAdaptorSignatures {
176            ecdsa_adaptor_signatures: signatures
177                .iter()
178                .map(|x| CetAdaptorSignature { signature: *x })
179                .collect(),
180        }
181    }
182}
183
184impl From<&CetAdaptorSignatures> for Vec<EcdsaAdaptorSignature> {
185    fn from(signatures: &CetAdaptorSignatures) -> Vec<EcdsaAdaptorSignature> {
186        signatures
187            .ecdsa_adaptor_signatures
188            .iter()
189            .map(|x| x.signature)
190            .collect::<Vec<_>>()
191    }
192}
193
194impl_dlc_writeable!(CetAdaptorSignatures, { (ecdsa_adaptor_signatures, vec) });
195
196#[derive(Clone, Debug, PartialEq, Eq)]
197#[cfg_attr(
198    feature = "use-serde",
199    derive(serde::Serialize, serde::Deserialize),
200    serde(rename_all = "camelCase")
201)]
202/// Contains the witness elements to use to make a funding transaction input valid.
203pub struct FundingSignature {
204    /// The set of witness elements.
205    pub witness_elements: Vec<WitnessElement>,
206}
207
208impl_dlc_writeable!(FundingSignature, { (witness_elements, vec) });
209
210#[derive(Clone, Debug, PartialEq, Eq)]
211#[cfg_attr(
212    feature = "use-serde",
213    derive(serde::Serialize, serde::Deserialize),
214    serde(rename_all = "camelCase")
215)]
216/// Contains a list of witness elements to satisfy the spending conditions of
217/// funding inputs.
218pub struct FundingSignatures {
219    /// The set of funding signatures.
220    pub funding_signatures: Vec<FundingSignature>,
221}
222
223impl_dlc_writeable!(FundingSignatures, { (funding_signatures, vec) });
224
225#[derive(Clone, Debug, PartialEq, Eq)]
226#[cfg_attr(
227    feature = "use-serde",
228    derive(serde::Serialize, serde::Deserialize),
229    serde(rename_all = "camelCase")
230)]
231/// Contains serialized data representing a single witness stack element.
232pub struct WitnessElement {
233    #[cfg_attr(
234        feature = "use-serde",
235        serde(
236            serialize_with = "crate::serde_utils::serialize_hex",
237            deserialize_with = "crate::serde_utils::deserialize_hex_string"
238        )
239    )]
240    /// The serialized witness data.
241    pub witness: Vec<u8>,
242}
243
244impl_dlc_writeable!(WitnessElement, { (witness, vec) });
245
246#[derive(Clone, Debug, PartialEq, Eq)]
247#[cfg_attr(
248    feature = "use-serde",
249    derive(serde::Serialize, serde::Deserialize),
250    serde(rename_all = "camelCase")
251)]
252/// Fields used to negotiate contract information.
253pub enum NegotiationFields {
254    /// Negotiation for single event based contract.
255    Single(SingleNegotiationFields),
256    /// Negotiation for multiple event based contract.
257    Disjoint(DisjointNegotiationFields),
258}
259
260impl_dlc_writeable_enum!(NegotiationFields, (0, Single), (1, Disjoint);;;);
261
262#[derive(Clone, Debug, PartialEq, Eq)]
263#[cfg_attr(
264    feature = "use-serde",
265    derive(serde::Serialize, serde::Deserialize),
266    serde(rename_all = "camelCase")
267)]
268/// Negotiation fields for contract based on a single event.
269pub struct SingleNegotiationFields {
270    /// Proposed rounding intervals.
271    rounding_intervals: contract_msgs::RoundingIntervals,
272}
273
274impl_dlc_writeable!(SingleNegotiationFields, { (rounding_intervals, writeable) });
275
276#[derive(Clone, Debug, PartialEq, Eq)]
277#[cfg_attr(
278    feature = "use-serde",
279    derive(serde::Serialize, serde::Deserialize),
280    serde(rename_all = "camelCase")
281)]
282/// Negotiation fields for contract based on multiple events.
283pub struct DisjointNegotiationFields {
284    /// The negotiation fields for each contract event.
285    negotiation_fields: Vec<NegotiationFields>,
286}
287
288impl_dlc_writeable!(DisjointNegotiationFields, { (negotiation_fields, vec) });
289
290#[derive(Clone, Debug, PartialEq)]
291#[cfg_attr(
292    feature = "use-serde",
293    derive(serde::Serialize, serde::Deserialize),
294    serde(rename_all = "camelCase")
295)]
296/// Contains information about a party wishing to enter into a DLC with
297/// another party. The contained information is sufficient for any other party
298/// to create a set of transactions representing the contract and its terms.
299pub struct OfferDlc {
300    /// The version of the protocol used by the peer.
301    pub protocol_version: u32,
302    /// Feature flags to be used for the offered contract.
303    pub contract_flags: u8,
304    #[cfg_attr(
305        feature = "use-serde",
306        serde(
307            serialize_with = "crate::serde_utils::serialize_hex",
308            deserialize_with = "crate::serde_utils::deserialize_hex_array"
309        )
310    )]
311    /// The identifier of the chain on which the contract will be settled.
312    pub chain_hash: [u8; 32],
313    #[cfg_attr(
314        feature = "use-serde",
315        serde(
316            serialize_with = "crate::serde_utils::serialize_hex",
317            deserialize_with = "crate::serde_utils::deserialize_hex_array"
318        )
319    )]
320    /// Temporary contract id to identify the contract.
321    pub temporary_contract_id: [u8; 32],
322    /// Information about the contract event, payouts and oracles.
323    pub contract_info: ContractInfo,
324    /// The public key of the offerer to be used to lock the collateral.
325    pub funding_pubkey: PublicKey,
326    /// The SPK where the offerer will receive their payout.
327    pub payout_spk: ScriptBuf,
328    /// Serial id to order CET outputs.
329    pub payout_serial_id: u64,
330    /// Collateral of the offer party.
331    pub offer_collateral: Amount,
332    /// Inputs used by the offer party to fund the contract.
333    pub funding_inputs: Vec<FundingInput>,
334    /// The SPK where the offer party will receive their change.
335    pub change_spk: ScriptBuf,
336    /// Serial id to order funding transaction outputs.
337    pub change_serial_id: u64,
338    /// Serial id to order funding transaction outputs.
339    pub fund_output_serial_id: u64,
340    /// The fee rate to use to compute transaction fees for this contract.
341    pub fee_rate_per_vb: u64,
342    /// The lock time for the CETs.
343    pub cet_locktime: u32,
344    /// The lock time for the refund transactions.
345    pub refund_locktime: u32,
346}
347
348impl OfferDlc {
349    /// Returns the total collateral locked in the contract.
350    pub fn get_total_collateral(&self) -> Amount {
351        match &self.contract_info {
352            ContractInfo::SingleContractInfo(single) => single.total_collateral,
353            ContractInfo::DisjointContractInfo(disjoint) => disjoint.total_collateral,
354        }
355    }
356
357    /// Returns whether the message satisfies validity requirements.
358    pub fn validate<C: Verification>(
359        &self,
360        secp: &Secp256k1<C>,
361        min_timeout_interval: u32,
362        max_timeout_interval: u32,
363    ) -> Result<(), Error> {
364        match &self.contract_info {
365            ContractInfo::SingleContractInfo(s) => s.contract_info.oracle_info.validate(secp)?,
366            ContractInfo::DisjointContractInfo(d) => {
367                if d.contract_infos.len() < 2 {
368                    return Err(Error::InvalidArgument);
369                }
370
371                for c in &d.contract_infos {
372                    c.oracle_info.validate(secp)?;
373                }
374            }
375        }
376
377        let closest_maturity_date = self.contract_info.get_closest_maturity_date();
378        let valid_dates = self.cet_locktime <= closest_maturity_date
379            && closest_maturity_date + min_timeout_interval <= self.refund_locktime
380            && self.refund_locktime <= closest_maturity_date + max_timeout_interval;
381        if !valid_dates {
382            return Err(Error::InvalidArgument);
383        }
384
385        Ok(())
386    }
387}
388
389impl_dlc_writeable!(OfferDlc, {
390        (protocol_version, writeable),
391        (contract_flags, writeable),
392        (chain_hash, writeable),
393        (temporary_contract_id, writeable),
394        (contract_info, writeable),
395        (funding_pubkey, writeable),
396        (payout_spk, writeable),
397        (payout_serial_id, writeable),
398        (offer_collateral, writeable),
399        (funding_inputs, vec),
400        (change_spk, writeable),
401        (change_serial_id, writeable),
402        (fund_output_serial_id, writeable),
403        (fee_rate_per_vb, writeable),
404        (cet_locktime, writeable),
405        (refund_locktime, writeable)
406});
407
408/// Contains information about a party wishing to accept a DLC offer. The contained
409/// information is sufficient for the offering party to re-build the set of
410/// transactions representing the contract and its terms, and guarantees the offering
411/// party that they can safely provide signatures for their funding input.
412#[derive(Clone, Debug, PartialEq, Eq)]
413#[cfg_attr(
414    feature = "use-serde",
415    derive(serde::Serialize, serde::Deserialize),
416    serde(rename_all = "camelCase")
417)]
418pub struct AcceptDlc {
419    /// The version of the protocol used by the peer.
420    pub protocol_version: u32,
421    #[cfg_attr(
422        feature = "use-serde",
423        serde(
424            serialize_with = "crate::serde_utils::serialize_hex",
425            deserialize_with = "crate::serde_utils::deserialize_hex_array"
426        )
427    )]
428    /// The temporary contract id for the contract.
429    pub temporary_contract_id: [u8; 32],
430    /// The collateral input by the accept party.
431    pub accept_collateral: Amount,
432    /// The public key of the accept party to be used to lock the collateral.
433    pub funding_pubkey: PublicKey,
434    /// The SPK where the accept party will receive their payout.
435    pub payout_spk: ScriptBuf,
436    /// Serial id to order CET outputs.
437    pub payout_serial_id: u64,
438    /// Inputs used by the accept party to fund the contract.
439    pub funding_inputs: Vec<FundingInput>,
440    /// The SPK where the accept party will receive their change.
441    pub change_spk: ScriptBuf,
442    /// Serial id to order funding transaction outputs.
443    pub change_serial_id: u64,
444    /// The set of adaptor signatures from the accept party.
445    pub cet_adaptor_signatures: CetAdaptorSignatures,
446    /// The refund signature of the accept party.
447    pub refund_signature: Signature,
448    /// The negotiation fields from the accept party.
449    pub negotiation_fields: Option<NegotiationFields>,
450}
451
452impl_dlc_writeable!(AcceptDlc, {
453    (protocol_version, writeable),
454    (temporary_contract_id, writeable),
455    (accept_collateral, writeable),
456    (funding_pubkey, writeable),
457    (payout_spk, writeable),
458    (payout_serial_id, writeable),
459    (funding_inputs, vec),
460    (change_spk, writeable),
461    (change_serial_id, writeable),
462    (cet_adaptor_signatures, writeable),
463    (refund_signature, writeable),
464    (negotiation_fields, option)
465});
466
467/// Contains all the required signatures for the DLC transactions from the offering
468/// party.
469#[derive(Clone, Debug, PartialEq, Eq)]
470#[cfg_attr(
471    feature = "use-serde",
472    derive(serde::Serialize, serde::Deserialize),
473    serde(rename_all = "camelCase")
474)]
475pub struct SignDlc {
476    /// The version of the protocol used by the peer.
477    pub protocol_version: u32,
478    #[cfg_attr(
479        feature = "use-serde",
480        serde(
481            serialize_with = "crate::serde_utils::serialize_hex",
482            deserialize_with = "crate::serde_utils::deserialize_hex_array"
483        )
484    )]
485    /// The id of the contract referred to by this message.
486    pub contract_id: [u8; 32],
487    /// The set of adaptor signatures from the offer party.
488    pub cet_adaptor_signatures: CetAdaptorSignatures,
489    /// The refund signature from the offer party.
490    pub refund_signature: Signature,
491    /// The set of funding signatures from the offer party.
492    pub funding_signatures: FundingSignatures,
493}
494
495impl_dlc_writeable!(SignDlc, {
496    (protocol_version, writeable),
497    (contract_id, writeable),
498    (cet_adaptor_signatures, writeable),
499    (refund_signature, writeable),
500    (funding_signatures, writeable)
501});
502
503#[allow(missing_docs)]
504#[derive(Debug, Clone)]
505pub enum Message {
506    Offer(OfferDlc),
507    Accept(AcceptDlc),
508    Sign(SignDlc),
509    OfferChannel(OfferChannel),
510    AcceptChannel(AcceptChannel),
511    SignChannel(SignChannel),
512    SettleOffer(SettleOffer),
513    SettleAccept(SettleAccept),
514    SettleConfirm(SettleConfirm),
515    SettleFinalize(SettleFinalize),
516    RenewOffer(RenewOffer),
517    RenewAccept(RenewAccept),
518    RenewConfirm(RenewConfirm),
519    RenewFinalize(RenewFinalize),
520    RenewRevoke(RenewRevoke),
521    CollaborativeCloseOffer(CollaborativeCloseOffer),
522    Reject(Reject),
523}
524
525macro_rules! impl_type_writeable_for_enum {
526    ($type_name: ident, {$($variant_name: ident),*}) => {
527       impl Type for $type_name {
528           fn type_id(&self) -> u16 {
529               match self {
530                   $($type_name::$variant_name(v) => v.type_id(),)*
531               }
532           }
533       }
534
535       impl Writeable for $type_name {
536            fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::lightning::io::Error> {
537                match self {
538                   $($type_name::$variant_name(v) => v.write(writer),)*
539                }
540            }
541       }
542    };
543}
544
545impl_type_writeable_for_enum!(Message,
546{
547    Offer,
548    Accept,
549    Sign,
550    OfferChannel,
551    AcceptChannel,
552    SignChannel,
553    SettleOffer,
554    SettleAccept,
555    SettleConfirm,
556    SettleFinalize,
557    RenewOffer,
558    RenewAccept,
559    RenewConfirm,
560    RenewFinalize,
561    RenewRevoke,
562    CollaborativeCloseOffer,
563    Reject
564});
565
566#[derive(Debug, Clone)]
567/// Wrapper for DLC related message and segmentation related messages.
568pub enum WireMessage {
569    /// Message related to establishment of a DLC contract.
570    Message(Message),
571    /// Message indicating an incoming segmented message.
572    SegmentStart(SegmentStart),
573    /// Message providing a chunk of a segmented message.
574    SegmentChunk(SegmentChunk),
575}
576
577impl Display for WireMessage {
578    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
579        let name = match self {
580            Self::Message(_) => "Message",
581            Self::SegmentStart(_) => "SegmentStart",
582            Self::SegmentChunk(_) => "SegmentChunk",
583        };
584        f.write_str(name)
585    }
586}
587
588impl_type_writeable_for_enum!(WireMessage, { Message, SegmentStart, SegmentChunk });
589
590#[cfg(test)]
591mod tests {
592    use secp256k1_zkp::SECP256K1;
593
594    use super::*;
595
596    macro_rules! roundtrip_test {
597        ($type: ty, $input: ident) => {
598            let msg: $type = serde_json::from_str(&$input).unwrap();
599            test_roundtrip(msg);
600        };
601    }
602
603    fn test_roundtrip<T: Writeable + Readable + PartialEq + std::fmt::Debug>(msg: T) {
604        let mut buf = Vec::new();
605        msg.write(&mut buf).expect("Error writing message");
606        let mut cursor = lightning::io::Cursor::new(buf);
607        let deser = Readable::read(&mut cursor).expect("Error reading message");
608        assert_eq!(msg, deser);
609    }
610
611    #[test]
612    fn offer_msg_roundtrip() {
613        let input = include_str!("./test_inputs/offer_msg.json");
614        roundtrip_test!(OfferDlc, input);
615    }
616
617    #[test]
618    fn accept_msg_roundtrip() {
619        let input = include_str!("./test_inputs/accept_msg.json");
620        roundtrip_test!(AcceptDlc, input);
621    }
622
623    #[test]
624    fn sign_msg_roundtrip() {
625        let input = include_str!("./test_inputs/sign_msg.json");
626        roundtrip_test!(SignDlc, input);
627    }
628
629    #[test]
630    fn valid_offer_message_passes_validation() {
631        let input = include_str!("./test_inputs/offer_msg.json");
632        let valid_offer: OfferDlc = serde_json::from_str(input).unwrap();
633        valid_offer
634            .validate(SECP256K1, 86400 * 7, 86400 * 14)
635            .expect("to validate valid offer messages.");
636    }
637
638    #[test]
639    fn invalid_offer_messages_fail_validation() {
640        let input = include_str!("./test_inputs/offer_msg.json");
641        let offer: OfferDlc = serde_json::from_str(input).unwrap();
642
643        let mut invalid_maturity = offer.clone();
644        invalid_maturity.cet_locktime += 3;
645
646        let mut too_short_timeout = offer.clone();
647        too_short_timeout.refund_locktime -= 100;
648
649        let mut too_long_timeout = offer;
650        too_long_timeout.refund_locktime -= 100;
651
652        for invalid in &[invalid_maturity, too_short_timeout, too_long_timeout] {
653            invalid
654                .validate(SECP256K1, 86400 * 7, 86400 * 14)
655                .expect_err("Should not pass validation of invalid offer message.");
656        }
657    }
658
659    #[test]
660    fn disjoint_contract_offer_messages_fail_validation() {
661        let input = include_str!("./test_inputs/offer_msg_disjoint.json");
662        let offer: OfferDlc = serde_json::from_str(input).unwrap();
663
664        let mut no_contract_input = offer.clone();
665        no_contract_input.contract_info =
666            ContractInfo::DisjointContractInfo(contract_msgs::DisjointContractInfo {
667                total_collateral: Amount::ONE_BTC,
668                contract_infos: vec![],
669            });
670
671        let mut single_contract_input = offer.clone();
672        single_contract_input.contract_info =
673            if let ContractInfo::DisjointContractInfo(d) = offer.contract_info {
674                let mut single = d;
675                single.contract_infos.remove(1);
676                ContractInfo::DisjointContractInfo(single)
677            } else {
678                panic!("Expected disjoint contract info.");
679            };
680
681        for invalid in &[no_contract_input, single_contract_input] {
682            invalid
683                .validate(SECP256K1, 86400 * 7, 86400 * 14)
684                .expect_err("Should not pass validation of invalid offer message.");
685        }
686    }
687}