use std::collections::BTreeMap;
use std::str::FromStr;
use bitcoin::secp256k1::XOnlyPublicKey;
use bitcoin::{Amount, Txid};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::{
decode_hex, encode_hex, ProtocolError, MAX_HUB_FEE, MAX_SATS, MAX_STATUS_LEN, MAX_SYNC_ITEMS,
MEMO_MAX, MIN_HTLC, MIN_OPEN,
};
use crate::{
Htlc, LeafSignature, PopSignature, Side, MAX_DELAY, MAX_EXPIRY, MAX_FEE, MIN_DELAY, MIN_FEE,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ChannelId(pub [u8; 8]);
impl ChannelId {
pub fn from_hex(text: &str) -> Result<Self, ProtocolError> {
let mut bytes = [0u8; 8];
decode_hex(text, &mut bytes).ok_or(ProtocolError::Malformed("bad id"))?;
Ok(Self(bytes))
}
pub fn to_hex(self) -> String {
encode_hex(&self.0)
}
pub fn from_txid(txid: Txid) -> Self {
let text = txid.to_string();
Self::from_hex(&text[..16]).expect("a txid begins with sixteen hex characters")
}
}
impl std::fmt::Display for ChannelId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.to_hex())
}
}
impl Serialize for ChannelId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_hex())
}
}
impl<'de> Deserialize<'de> for ChannelId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let text = String::deserialize(deserializer)?;
Self::from_hex(&text).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeId(pub XOnlyPublicKey);
impl Serialize for NodeId {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.0.to_string())
}
}
impl<'de> Deserialize<'de> for NodeId {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let text = String::deserialize(deserializer)?;
if !lower_hex(&text, 64) {
return Err(serde::de::Error::custom("bad x-only key"));
}
XOnlyPublicKey::from_str(&text)
.map(Self)
.map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Bytes32(pub [u8; 32]);
impl Serialize for Bytes32 {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&encode_hex(&self.0))
}
}
impl<'de> Deserialize<'de> for Bytes32 {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let text = String::deserialize(deserializer)?;
let mut bytes = [0u8; 32];
decode_hex(&text, &mut bytes).ok_or_else(|| serde::de::Error::custom("bad 32-byte hex"))?;
Ok(Self(bytes))
}
}
impl Serialize for LeafSignature {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_hex())
}
}
impl<'de> Deserialize<'de> for LeafSignature {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let text = String::deserialize(deserializer)?;
LeafSignature::from_hex(&text).map_err(serde::de::Error::custom)
}
}
impl Serialize for PopSignature {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_hex())
}
}
impl<'de> Deserialize<'de> for PopSignature {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let text = String::deserialize(deserializer)?;
PopSignature::from_hex(&text).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct Route {
pub to: NodeId,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProtocolHtlc {
pub id: u64,
pub from: WireSide,
pub amount: u64,
pub hash: Bytes32,
pub expiry: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub route: Option<Route>,
}
impl ProtocolHtlc {
pub(crate) fn channel_htlc(&self) -> Htlc {
Htlc {
id: self.id,
from: self.from.into(),
amount: Amount::from_sat(self.amount),
payment_hash: self.hash.0,
expiry: self.expiry,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OfferedHtlc {
pub id: u64,
pub amount: u64,
pub hash: Bytes32,
pub expiry: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub route: Option<Route>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum WireSide {
A,
B,
}
impl From<Side> for WireSide {
fn from(side: Side) -> Self {
match side {
Side::A => Self::A,
Side::B => Self::B,
}
}
}
impl From<WireSide> for Side {
fn from(side: WireSide) -> Self {
match side {
WireSide::A => Self::A,
WireSide::B => Self::B,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum Update {
Pay {
amount: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
memo: Option<String>,
},
Add {
htlc: OfferedHtlc,
#[serde(default, skip_serializing_if = "Option::is_none")]
memo: Option<String>,
},
Settle {
#[serde(rename = "htlcId")]
htlc_id: u64,
preimage: Bytes32,
},
Fail {
#[serde(rename = "htlcId")]
htlc_id: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
reason: Option<String>,
},
}
impl Update {
pub fn label(&self) -> String {
match self {
Self::Pay { amount, .. } => format!("pay of {amount} sat"),
Self::Add { htlc, .. } => format!("add of htlc {} ({} sat)", htlc.id, htlc.amount),
Self::Settle { htlc_id, .. } => format!("settle of htlc {htlc_id}"),
Self::Fail { htlc_id, .. } => format!("fail of htlc {htlc_id}"),
}
}
pub fn added_hash(&self) -> Option<Bytes32> {
match self {
Self::Add { htlc, .. } => Some(htlc.hash),
_ => None,
}
}
pub(crate) fn well_formed(&self) -> Result<(), ProtocolError> {
let text_ok =
|text: &Option<String>| text.as_ref().is_none_or(|t| utf16_len(t) <= MEMO_MAX);
match self {
Self::Pay { amount, memo } => {
if !text_ok(memo) {
return Err(ProtocolError::Malformed("bad update"));
}
if !(1..=MAX_SATS).contains(amount) {
return Err(ProtocolError::Malformed("bad pay"));
}
}
Self::Add { htlc, memo } => {
if !text_ok(memo) {
return Err(ProtocolError::Malformed("bad update"));
}
if htlc.id < 1
|| !(MIN_HTLC..=MAX_SATS).contains(&htlc.amount)
|| !(1..MAX_EXPIRY).contains(&htlc.expiry)
{
return Err(ProtocolError::Malformed("bad add"));
}
}
Self::Settle { htlc_id, .. } => {
if *htlc_id < 1 {
return Err(ProtocolError::Malformed("bad settle"));
}
}
Self::Fail { htlc_id, reason } => {
if *htlc_id < 1 || !text_ok(reason) {
return Err(ProtocolError::Malformed("bad fail"));
}
}
}
Ok(())
}
}
macro_rules! tag {
($(#[$doc:meta])* $name:ident, $variant:ident, $text:literal, $vdoc:literal) => {
$(#[$doc])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum $name {
#[doc = $vdoc]
#[serde(rename = $text)]
$variant,
}
};
}
tag!(
OpenTag, Open, "open", "Opening proposal."
);
tag!(
AcceptTag, Accept, "accept", "Opening acceptance."
);
tag!(
CommitTag, Commit, "commit", "Commitment signature."
);
tag!(
ReadyTag, Ready, "ready", "Initial commitments are ready."
);
tag!(
UpdateTag, Update, "update", "Update message."
);
tag!(
AckTag, Ack, "ack", "Acknowledgement message."
);
tag!(
RevokeTag, Revoke, "revoke", "Revocation message."
);
tag!(
RejectTag, Reject, "reject", "Refusal of an update."
);
tag!(
CloseTag, Close, "close", "Cooperative close request."
);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SyncTag {
#[serde(rename = "sync")]
Sync,
#[serde(rename = "synced")]
Synced,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct FundingOffer {
#[serde(
serialize_with = "serialize_txid",
deserialize_with = "deserialize_txid"
)]
pub txid: Txid,
pub vout: u32,
pub value: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct RevocationProofs {
pub base: PopSignature,
pub rev: [PopSignature; 2],
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OpenMessage {
pub t: OpenTag,
pub id: ChannelId,
pub funding: FundingOffer,
#[serde(default)]
pub push: u64,
pub delay: u16,
pub fee: u64,
pub a: NodeId,
pub b: NodeId,
pub rev: [NodeId; 2],
#[serde(rename = "revBase")]
pub rev_base: NodeId,
pub pop: RevocationProofs,
#[serde(rename = "hubFee", default, skip_serializing_if = "Option::is_none")]
pub hub_fee: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AcceptMessage {
pub t: AcceptTag,
pub id: ChannelId,
pub rev: [NodeId; 2],
#[serde(rename = "revBase")]
pub rev_base: NodeId,
pub pop: RevocationProofs,
pub sig: LeafSignature,
#[serde(rename = "hubFee", default, skip_serializing_if = "Option::is_none")]
pub hub_fee: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CommitMessage {
pub t: CommitTag,
pub id: ChannelId,
pub sig: LeafSignature,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReadyMessage {
pub t: ReadyTag,
pub id: ChannelId,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UpdateMessage {
pub t: UpdateTag,
pub id: ChannelId,
pub n: u64,
#[serde(flatten)]
pub update: Update,
pub sig: LeafSignature,
#[serde(rename = "nextRev")]
pub next_rev: NodeId,
#[serde(rename = "nextRevPop")]
pub next_rev_pop: PopSignature,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AckMessage {
pub t: AckTag,
pub id: ChannelId,
pub n: u64,
pub sig: LeafSignature,
pub reveal: Bytes32,
#[serde(rename = "nextRev")]
pub next_rev: NodeId,
#[serde(rename = "nextRevPop")]
pub next_rev_pop: PopSignature,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RevokeMessage {
pub t: RevokeTag,
pub id: ChannelId,
pub n: u64,
pub reveal: Bytes32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RejectMessage {
pub t: RejectTag,
pub id: ChannelId,
pub n: u64,
pub sig: LeafSignature,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CloseMessage {
pub t: CloseTag,
pub id: ChannelId,
pub n: u64,
pub sig: LeafSignature,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SyncMessage {
pub t: SyncTag,
pub id: ChannelId,
pub n: u64,
#[serde(default)]
pub status: Option<String>,
#[serde(rename = "pendingN", default)]
pub pending_n: Option<u64>,
#[serde(default)]
pub reveal: Option<Bytes32>,
#[serde(default)]
pub missing: Vec<u64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_reveals"
)]
pub reveals: Option<BTreeMap<u64, Bytes32>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PeerMessage {
Open(OpenMessage),
Accept(AcceptMessage),
Commit(CommitMessage),
Ready(ReadyMessage),
Update(UpdateMessage),
Ack(AckMessage),
Revoke(RevokeMessage),
Reject(RejectMessage),
Close(CloseMessage),
Sync(SyncMessage),
}
impl PeerMessage {
pub fn channel(&self) -> ChannelId {
match self {
Self::Open(m) => m.id,
Self::Accept(m) => m.id,
Self::Commit(m) => m.id,
Self::Ready(m) => m.id,
Self::Update(m) => m.id,
Self::Ack(m) => m.id,
Self::Revoke(m) => m.id,
Self::Reject(m) => m.id,
Self::Close(m) => m.id,
Self::Sync(m) => m.id,
}
}
pub fn well_formed(&self) -> Result<(), ProtocolError> {
match self {
Self::Open(m) => m.well_formed(),
Self::Accept(m) => m.well_formed(),
Self::Commit(_) | Self::Ready(_) => Ok(()),
Self::Update(m) => m.well_formed(),
Self::Ack(m) => m.well_formed(),
Self::Revoke(m) => m.well_formed(),
Self::Reject(m) => m.well_formed(),
Self::Close(_) => Ok(()),
Self::Sync(m) => m.well_formed(),
}
}
}
macro_rules! into_peer_message {
($($ty:ident => $variant:ident),* $(,)?) => {$(
impl From<$ty> for PeerMessage {
fn from(message: $ty) -> Self {
Self::$variant(message)
}
}
)*};
}
into_peer_message!(
OpenMessage => Open,
AcceptMessage => Accept,
CommitMessage => Commit,
ReadyMessage => Ready,
UpdateMessage => Update,
AckMessage => Ack,
RevokeMessage => Revoke,
RejectMessage => Reject,
CloseMessage => Close,
SyncMessage => Sync,
);
fn hub_fee_ok(fee: Option<u64>) -> bool {
fee.is_none_or(|fee| fee <= MAX_HUB_FEE)
}
impl OpenMessage {
pub fn well_formed(&self) -> Result<(), ProtocolError> {
let ok = self.funding.vout <= u32::from(u16::MAX)
&& (MIN_OPEN..=MAX_SATS).contains(&self.funding.value)
&& self.id == ChannelId::from_txid(self.funding.txid)
&& (MIN_DELAY..=MAX_DELAY).contains(&self.delay)
&& (MIN_FEE..=MAX_FEE).contains(&self.fee)
&& self.a != self.b
&& hub_fee_ok(self.hub_fee);
if ok {
Ok(())
} else {
Err(ProtocolError::Malformed("bad open"))
}
}
}
impl AcceptMessage {
pub fn well_formed(&self) -> Result<(), ProtocolError> {
if hub_fee_ok(self.hub_fee) {
Ok(())
} else {
Err(ProtocolError::Malformed("bad accept"))
}
}
}
impl UpdateMessage {
pub fn well_formed(&self) -> Result<(), ProtocolError> {
if self.n < 1 {
return Err(ProtocolError::Malformed("bad update"));
}
self.update.well_formed()
}
}
impl AckMessage {
pub fn well_formed(&self) -> Result<(), ProtocolError> {
if self.n < 1 {
return Err(ProtocolError::Malformed("bad ack"));
}
Ok(())
}
}
impl RevokeMessage {
pub fn well_formed(&self) -> Result<(), ProtocolError> {
if self.n < 1 {
return Err(ProtocolError::Malformed("bad revoke"));
}
Ok(())
}
}
impl RejectMessage {
pub fn well_formed(&self) -> Result<(), ProtocolError> {
if self.n < 1
|| self
.reason
.as_ref()
.is_some_and(|r| utf16_len(r) > MEMO_MAX)
{
return Err(ProtocolError::Malformed("bad reject"));
}
Ok(())
}
}
impl SyncMessage {
pub fn well_formed(&self) -> Result<(), ProtocolError> {
let ok = self.pending_n.is_none_or(|n| n >= 1)
&& self.missing.len() <= MAX_SYNC_ITEMS
&& self
.reveals
.as_ref()
.is_none_or(|reveals| reveals.len() <= MAX_SYNC_ITEMS)
&& self
.status
.as_ref()
.is_none_or(|status| utf16_len(status) <= MAX_STATUS_LEN);
if ok {
Ok(())
} else {
Err(ProtocolError::Malformed("bad sync"))
}
}
}
pub(crate) fn utf16_len(text: &str) -> usize {
text.encode_utf16().count()
}
pub(crate) fn truncate_utf16(text: &str, limit: usize) -> String {
let mut used = 0;
let mut out = String::new();
for c in text.chars() {
used += c.len_utf16();
if used > limit {
break;
}
out.push(c);
}
out
}
fn lower_hex(text: &str, len: usize) -> bool {
text.len() == len
&& text
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
}
fn serialize_txid<S: Serializer>(txid: &Txid, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&txid.to_string())
}
fn deserialize_reveals<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<BTreeMap<u64, Bytes32>>, D::Error> {
let Some(raw) = Option::<BTreeMap<String, Bytes32>>::deserialize(deserializer)? else {
return Ok(None);
};
raw.into_iter()
.map(|(k, v)| {
let canonical = !k.is_empty()
&& k.bytes().all(|b| b.is_ascii_digit())
&& (k == "0" || !k.starts_with('0'));
if !canonical {
return Err(serde::de::Error::custom("bad reveals key"));
}
k.parse::<u64>()
.map(|n| (n, v))
.map_err(|_| serde::de::Error::custom("bad reveals key"))
})
.collect::<Result<BTreeMap<_, _>, _>>()
.map(Some)
}
fn deserialize_txid<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Txid, D::Error> {
let text = String::deserialize(deserializer)?;
if !lower_hex(&text, 64) {
return Err(serde::de::Error::custom("bad txid"));
}
Txid::from_str(&text).map_err(serde::de::Error::custom)
}