#![forbid(unsafe_code)]
#![deny(non_upper_case_globals)]
#![deny(non_camel_case_types)]
#![deny(non_snake_case)]
#![deny(unused_mut)]
#![deny(dead_code)]
#![deny(unused_imports)]
#![deny(missing_docs)]
extern crate bitcoin;
extern crate dlc;
extern crate lightning;
extern crate secp256k1_zkp;
#[macro_use]
pub mod ser_macros;
pub mod ser_impls;
#[cfg(any(test, feature = "use-serde"))]
extern crate serde;
#[cfg(test)]
extern crate serde_json;
pub mod channel;
pub mod contract_msgs;
pub mod message_handler;
pub mod oracle_msgs;
pub mod segmentation;
#[cfg(any(test, feature = "use-serde"))]
pub mod serde_utils;
use std::fmt::Display;
use crate::ser_impls::{read_ecdsa_adaptor_signature, write_ecdsa_adaptor_signature};
use bitcoin::ScriptBuf;
use bitcoin::{consensus::Decodable, OutPoint, Transaction};
use channel::{
AcceptChannel, CollaborativeCloseOffer, OfferChannel, Reject, RenewAccept, RenewConfirm,
RenewFinalize, RenewOffer, RenewRevoke, SettleAccept, SettleConfirm, SettleFinalize,
SettleOffer, SignChannel,
};
use contract_msgs::ContractInfo;
use dlc::{Error, TxInputInfo};
use lightning::ln::msgs::DecodeError;
use lightning::ln::wire::Type;
use lightning::util::ser::{Readable, Writeable, Writer};
use secp256k1_zkp::Verification;
use secp256k1_zkp::{ecdsa::Signature, EcdsaAdaptorSignature, PublicKey, Secp256k1};
use segmentation::{SegmentChunk, SegmentStart};
macro_rules! impl_type {
($const_name: ident, $type_name: ident, $type_val: expr) => {
pub const $const_name: u16 = $type_val;
impl Type for $type_name {
fn type_id(&self) -> u16 {
$const_name
}
}
};
}
impl_type!(OFFER_TYPE, OfferDlc, 42778);
impl_type!(ACCEPT_TYPE, AcceptDlc, 42780);
impl_type!(SIGN_TYPE, SignDlc, 42782);
impl_type!(OFFER_CHANNEL_TYPE, OfferChannel, 43000);
impl_type!(ACCEPT_CHANNEL_TYPE, AcceptChannel, 43002);
impl_type!(SIGN_CHANNEL_TYPE, SignChannel, 43004);
impl_type!(SETTLE_CHANNEL_OFFER_TYPE, SettleOffer, 43006);
impl_type!(SETTLE_CHANNEL_ACCEPT_TYPE, SettleAccept, 43008);
impl_type!(SETTLE_CHANNEL_CONFIRM_TYPE, SettleConfirm, 43010);
impl_type!(SETTLE_CHANNEL_FINALIZE_TYPE, SettleFinalize, 43012);
impl_type!(RENEW_CHANNEL_OFFER_TYPE, RenewOffer, 43014);
impl_type!(RENEW_CHANNEL_ACCEPT_TYPE, RenewAccept, 43016);
impl_type!(RENEW_CHANNEL_CONFIRM_TYPE, RenewConfirm, 43018);
impl_type!(RENEW_CHANNEL_FINALIZE_TYPE, RenewFinalize, 43020);
impl_type!(RENEW_CHANNEL_REVOKE_TYPE, RenewRevoke, 43026);
impl_type!(
COLLABORATIVE_CLOSE_OFFER_TYPE,
CollaborativeCloseOffer,
43022
);
impl_type!(REJECT, Reject, 43024);
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "use-serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct FundingInput {
pub input_serial_id: u64,
#[cfg_attr(
feature = "use-serde",
serde(
serialize_with = "crate::serde_utils::serialize_hex",
deserialize_with = "crate::serde_utils::deserialize_hex_string"
)
)]
pub prev_tx: Vec<u8>,
pub prev_tx_vout: u32,
pub sequence: u32,
pub max_witness_len: u16,
pub redeem_script: ScriptBuf,
}
impl_dlc_writeable!(FundingInput, {
(input_serial_id, writeable),
(prev_tx, vec),
(prev_tx_vout, writeable),
(sequence, writeable),
(max_witness_len, writeable),
(redeem_script, writeable)
});
impl From<&FundingInput> for TxInputInfo {
fn from(funding_input: &FundingInput) -> TxInputInfo {
TxInputInfo {
outpoint: OutPoint {
txid: Transaction::consensus_decode(&mut funding_input.prev_tx.as_slice())
.expect("Transaction Decode Error")
.compute_txid(),
vout: funding_input.prev_tx_vout,
},
max_witness_len: (funding_input.max_witness_len as usize),
redeem_script: funding_input.redeem_script.clone(),
serial_id: funding_input.input_serial_id,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "use-serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct CetAdaptorSignature {
pub signature: EcdsaAdaptorSignature,
}
impl_dlc_writeable!(CetAdaptorSignature, {
(signature, { cb_writeable, write_ecdsa_adaptor_signature, read_ecdsa_adaptor_signature })
});
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "use-serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct CetAdaptorSignatures {
pub ecdsa_adaptor_signatures: Vec<CetAdaptorSignature>,
}
impl From<&[EcdsaAdaptorSignature]> for CetAdaptorSignatures {
fn from(signatures: &[EcdsaAdaptorSignature]) -> Self {
CetAdaptorSignatures {
ecdsa_adaptor_signatures: signatures
.iter()
.map(|x| CetAdaptorSignature { signature: *x })
.collect(),
}
}
}
impl From<&CetAdaptorSignatures> for Vec<EcdsaAdaptorSignature> {
fn from(signatures: &CetAdaptorSignatures) -> Vec<EcdsaAdaptorSignature> {
signatures
.ecdsa_adaptor_signatures
.iter()
.map(|x| x.signature)
.collect::<Vec<_>>()
}
}
impl_dlc_writeable!(CetAdaptorSignatures, { (ecdsa_adaptor_signatures, vec) });
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "use-serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct FundingSignature {
pub witness_elements: Vec<WitnessElement>,
}
impl_dlc_writeable!(FundingSignature, { (witness_elements, vec) });
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "use-serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct FundingSignatures {
pub funding_signatures: Vec<FundingSignature>,
}
impl_dlc_writeable!(FundingSignatures, { (funding_signatures, vec) });
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "use-serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct WitnessElement {
#[cfg_attr(
feature = "use-serde",
serde(
serialize_with = "crate::serde_utils::serialize_hex",
deserialize_with = "crate::serde_utils::deserialize_hex_string"
)
)]
pub witness: Vec<u8>,
}
impl_dlc_writeable!(WitnessElement, { (witness, vec) });
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "use-serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub enum NegotiationFields {
Single(SingleNegotiationFields),
Disjoint(DisjointNegotiationFields),
}
impl_dlc_writeable_enum!(NegotiationFields, (0, Single), (1, Disjoint);;;);
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "use-serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct SingleNegotiationFields {
rounding_intervals: contract_msgs::RoundingIntervals,
}
impl_dlc_writeable!(SingleNegotiationFields, { (rounding_intervals, writeable) });
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "use-serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct DisjointNegotiationFields {
negotiation_fields: Vec<NegotiationFields>,
}
impl_dlc_writeable!(DisjointNegotiationFields, { (negotiation_fields, vec) });
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "use-serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct OfferDlc {
pub protocol_version: u32,
pub contract_flags: u8,
#[cfg_attr(
feature = "use-serde",
serde(
serialize_with = "crate::serde_utils::serialize_hex",
deserialize_with = "crate::serde_utils::deserialize_hex_array"
)
)]
pub chain_hash: [u8; 32],
#[cfg_attr(
feature = "use-serde",
serde(
serialize_with = "crate::serde_utils::serialize_hex",
deserialize_with = "crate::serde_utils::deserialize_hex_array"
)
)]
pub temporary_contract_id: [u8; 32],
pub contract_info: ContractInfo,
pub funding_pubkey: PublicKey,
pub payout_spk: ScriptBuf,
pub payout_serial_id: u64,
pub offer_collateral: u64,
pub funding_inputs: Vec<FundingInput>,
pub change_spk: ScriptBuf,
pub change_serial_id: u64,
pub fund_output_serial_id: u64,
pub fee_rate_per_vb: u64,
pub cet_locktime: u32,
pub refund_locktime: u32,
}
impl OfferDlc {
pub fn get_total_collateral(&self) -> u64 {
match &self.contract_info {
ContractInfo::SingleContractInfo(single) => single.total_collateral,
ContractInfo::DisjointContractInfo(disjoint) => disjoint.total_collateral,
}
}
pub fn validate<C: Verification>(
&self,
secp: &Secp256k1<C>,
min_timeout_interval: u32,
max_timeout_interval: u32,
) -> Result<(), Error> {
match &self.contract_info {
ContractInfo::SingleContractInfo(s) => s.contract_info.oracle_info.validate(secp)?,
ContractInfo::DisjointContractInfo(d) => {
if d.contract_infos.len() < 2 {
return Err(Error::InvalidArgument);
}
for c in &d.contract_infos {
c.oracle_info.validate(secp)?;
}
}
}
let closest_maturity_date = self.contract_info.get_closest_maturity_date();
let valid_dates = self.cet_locktime <= closest_maturity_date
&& closest_maturity_date + min_timeout_interval <= self.refund_locktime
&& self.refund_locktime <= closest_maturity_date + max_timeout_interval;
if !valid_dates {
return Err(Error::InvalidArgument);
}
Ok(())
}
}
impl_dlc_writeable!(OfferDlc, {
(protocol_version, writeable),
(contract_flags, writeable),
(chain_hash, writeable),
(temporary_contract_id, writeable),
(contract_info, writeable),
(funding_pubkey, writeable),
(payout_spk, writeable),
(payout_serial_id, writeable),
(offer_collateral, writeable),
(funding_inputs, vec),
(change_spk, writeable),
(change_serial_id, writeable),
(fund_output_serial_id, writeable),
(fee_rate_per_vb, writeable),
(cet_locktime, writeable),
(refund_locktime, writeable)
});
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "use-serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct AcceptDlc {
pub protocol_version: u32,
#[cfg_attr(
feature = "use-serde",
serde(
serialize_with = "crate::serde_utils::serialize_hex",
deserialize_with = "crate::serde_utils::deserialize_hex_array"
)
)]
pub temporary_contract_id: [u8; 32],
pub accept_collateral: u64,
pub funding_pubkey: PublicKey,
pub payout_spk: ScriptBuf,
pub payout_serial_id: u64,
pub funding_inputs: Vec<FundingInput>,
pub change_spk: ScriptBuf,
pub change_serial_id: u64,
pub cet_adaptor_signatures: CetAdaptorSignatures,
pub refund_signature: Signature,
pub negotiation_fields: Option<NegotiationFields>,
}
impl_dlc_writeable!(AcceptDlc, {
(protocol_version, writeable),
(temporary_contract_id, writeable),
(accept_collateral, writeable),
(funding_pubkey, writeable),
(payout_spk, writeable),
(payout_serial_id, writeable),
(funding_inputs, vec),
(change_spk, writeable),
(change_serial_id, writeable),
(cet_adaptor_signatures, writeable),
(refund_signature, writeable),
(negotiation_fields, option)
});
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "use-serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct SignDlc {
pub protocol_version: u32,
#[cfg_attr(
feature = "use-serde",
serde(
serialize_with = "crate::serde_utils::serialize_hex",
deserialize_with = "crate::serde_utils::deserialize_hex_array"
)
)]
pub contract_id: [u8; 32],
pub cet_adaptor_signatures: CetAdaptorSignatures,
pub refund_signature: Signature,
pub funding_signatures: FundingSignatures,
}
impl_dlc_writeable!(SignDlc, {
(protocol_version, writeable),
(contract_id, writeable),
(cet_adaptor_signatures, writeable),
(refund_signature, writeable),
(funding_signatures, writeable)
});
#[allow(missing_docs)]
#[derive(Debug, Clone)]
pub enum Message {
Offer(OfferDlc),
Accept(AcceptDlc),
Sign(SignDlc),
OfferChannel(OfferChannel),
AcceptChannel(AcceptChannel),
SignChannel(SignChannel),
SettleOffer(SettleOffer),
SettleAccept(SettleAccept),
SettleConfirm(SettleConfirm),
SettleFinalize(SettleFinalize),
RenewOffer(RenewOffer),
RenewAccept(RenewAccept),
RenewConfirm(RenewConfirm),
RenewFinalize(RenewFinalize),
RenewRevoke(RenewRevoke),
CollaborativeCloseOffer(CollaborativeCloseOffer),
Reject(Reject),
}
macro_rules! impl_type_writeable_for_enum {
($type_name: ident, {$($variant_name: ident),*}) => {
impl Type for $type_name {
fn type_id(&self) -> u16 {
match self {
$($type_name::$variant_name(v) => v.type_id(),)*
}
}
}
impl Writeable for $type_name {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ::lightning::io::Error> {
match self {
$($type_name::$variant_name(v) => v.write(writer),)*
}
}
}
};
}
impl_type_writeable_for_enum!(Message,
{
Offer,
Accept,
Sign,
OfferChannel,
AcceptChannel,
SignChannel,
SettleOffer,
SettleAccept,
SettleConfirm,
SettleFinalize,
RenewOffer,
RenewAccept,
RenewConfirm,
RenewFinalize,
RenewRevoke,
CollaborativeCloseOffer,
Reject
});
#[derive(Debug, Clone)]
pub enum WireMessage {
Message(Message),
SegmentStart(SegmentStart),
SegmentChunk(SegmentChunk),
}
impl Display for WireMessage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
Self::Message(_) => "Message",
Self::SegmentStart(_) => "SegmentStart",
Self::SegmentChunk(_) => "SegmentChunk",
};
f.write_str(name)
}
}
impl_type_writeable_for_enum!(WireMessage, { Message, SegmentStart, SegmentChunk });
#[cfg(test)]
mod tests {
use secp256k1_zkp::SECP256K1;
use super::*;
macro_rules! roundtrip_test {
($type: ty, $input: ident) => {
let msg: $type = serde_json::from_str(&$input).unwrap();
test_roundtrip(msg);
};
}
fn test_roundtrip<T: Writeable + Readable + PartialEq + std::fmt::Debug>(msg: T) {
let mut buf = Vec::new();
msg.write(&mut buf).expect("Error writing message");
let mut cursor = lightning::io::Cursor::new(buf);
let deser = Readable::read(&mut cursor).expect("Error reading message");
assert_eq!(msg, deser);
}
#[test]
fn offer_msg_roundtrip() {
let input = include_str!("./test_inputs/offer_msg.json");
roundtrip_test!(OfferDlc, input);
}
#[test]
fn accept_msg_roundtrip() {
let input = include_str!("./test_inputs/accept_msg.json");
roundtrip_test!(AcceptDlc, input);
}
#[test]
fn sign_msg_roundtrip() {
let input = include_str!("./test_inputs/sign_msg.json");
roundtrip_test!(SignDlc, input);
}
#[test]
fn valid_offer_message_passes_validation() {
let input = include_str!("./test_inputs/offer_msg.json");
let valid_offer: OfferDlc = serde_json::from_str(input).unwrap();
valid_offer
.validate(SECP256K1, 86400 * 7, 86400 * 14)
.expect("to validate valid offer messages.");
}
#[test]
fn invalid_offer_messages_fail_validation() {
let input = include_str!("./test_inputs/offer_msg.json");
let offer: OfferDlc = serde_json::from_str(input).unwrap();
let mut invalid_maturity = offer.clone();
invalid_maturity.cet_locktime += 3;
let mut too_short_timeout = offer.clone();
too_short_timeout.refund_locktime -= 100;
let mut too_long_timeout = offer;
too_long_timeout.refund_locktime -= 100;
for invalid in &[invalid_maturity, too_short_timeout, too_long_timeout] {
invalid
.validate(SECP256K1, 86400 * 7, 86400 * 14)
.expect_err("Should not pass validation of invalid offer message.");
}
}
#[test]
fn disjoint_contract_offer_messages_fail_validation() {
let input = include_str!("./test_inputs/offer_msg_disjoint.json");
let offer: OfferDlc = serde_json::from_str(input).unwrap();
let mut no_contract_input = offer.clone();
no_contract_input.contract_info =
ContractInfo::DisjointContractInfo(contract_msgs::DisjointContractInfo {
total_collateral: 100000000,
contract_infos: vec![],
});
let mut single_contract_input = offer.clone();
single_contract_input.contract_info =
if let ContractInfo::DisjointContractInfo(d) = offer.contract_info {
let mut single = d;
single.contract_infos.remove(1);
ContractInfo::DisjointContractInfo(single)
} else {
panic!("Expected disjoint contract info.");
};
for invalid in &[no_contract_input, single_contract_input] {
invalid
.validate(SECP256K1, 86400 * 7, 86400 * 14)
.expect_err("Should not pass validation of invalid offer message.");
}
}
}