ppad-bolt7-0.1.0: lib/Lightning/Protocol/BOLT7/Validate.hs
{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE DeriveGeneric #-}
-- |
-- Module: Lightning.Protocol.BOLT7.Validate
-- Copyright: (c) 2025 Jared Tobin
-- License: MIT
-- Maintainer: Jared Tobin <jared@ppad.tech>
--
-- Stateless checks of BOLT #7 messages.
module Lightning.Protocol.BOLT7.Validate (
ValidationError(..)
, validate_channel_announcement
, validate_node_announcement
, validate_channel_update
, validate_query_short_channel_ids
, validate_query_channel_range
, validate_reply_channel_range
) where
import Control.DeepSeq (NFData)
import Data.Word (Word64)
import GHC.Generics (Generic)
import Lightning.Protocol.BOLT1 (ShortChannelId)
import Lightning.Protocol.BOLT7.Messages
import Lightning.Protocol.BOLT7.Types
-- | A rule a message breaks.
data ValidationError
= ValidateNodeIdOrdering
-- ^ @node_id_1@ is not lexicographically less than @node_id_2@
| ValidateMultipleDns
-- ^ more than one DNS hostname descriptor
| ValidateHtlcAmounts
-- ^ @htlc_minimum_msat@ exceeds @htlc_maximum_msat@
| ValidateZeroBlocks
-- ^ @number_of_blocks@ is 0
| ValidateBlockOverflow
-- ^ the block range extends past block 2^32 - 1
| ValidateScidNotAscending
-- ^ short channel ids are not in strictly ascending order
deriving (Eq, Show, Generic)
instance NFData ValidationError
-- | Check that @node_id_1@ is lexicographically less than @node_id_2@;
-- a receiver must ignore the message otherwise.
--
-- Signatures and feature bits are not checked here; see
-- 'Lightning.Protocol.BOLT7.verify_channel_announcement' and
-- ppad-bolt9's @validate_remote@.
validate_channel_announcement
:: ChannelAnnouncement -> Either ValidationError ()
validate_channel_announcement m
| ca_node_id_1 m < ca_node_id_2 m = Right ()
| otherwise = Left ValidateNodeIdOrdering
-- | Check that at most one DNS hostname is announced; a receiver must
-- not forward the message otherwise.
--
-- Descriptors a receiver should merely ignore (Tor v2, port 0, a
-- hostname that isn't ASCII) are left to
-- 'Lightning.Protocol.BOLT7.usable_addresses'.
validate_node_announcement :: NodeAnnouncement -> Either ValidationError ()
validate_node_announcement m
| length [ () | AddrDNS _ _ <- na_addresses m ] > 1 =
Left ValidateMultipleDns
| otherwise = Right ()
-- | Check that @htlc_minimum_msat@ does not exceed @htlc_maximum_msat@;
-- a receiver should not route through the channel otherwise.
validate_channel_update :: ChannelUpdate -> Either ValidationError ()
validate_channel_update m
| cu_htlc_minimum_msat m > cu_htlc_maximum_msat m = Left ValidateHtlcAmounts
| otherwise = Right ()
-- | Check that the short channel ids are in strictly ascending order, as
-- their encoding requires.
validate_query_short_channel_ids
:: QueryShortChannelIds -> Either ValidationError ()
validate_query_short_channel_ids = ascending . qsci_short_channel_ids
-- | Check that @number_of_blocks@ is at least 1, as the sender must
-- ensure, and that the range ends at or before block 2^32 - 1 (a
-- library rule: such a range can't be represented).
validate_query_channel_range
:: QueryChannelRange -> Either ValidationError ()
validate_query_channel_range m
| num == 0 = Left ValidateZeroBlocks
| first + num > 0x100000000 = Left ValidateBlockOverflow
| otherwise = Right ()
where
first = fromIntegral (qcr_first_blocknum m) :: Word64
num = fromIntegral (qcr_number_of_blocks m) :: Word64
-- | Check that the short channel ids are in strictly ascending order, as
-- their encoding requires.
validate_reply_channel_range
:: ReplyChannelRange -> Either ValidationError ()
validate_reply_channel_range = ascending . rcr_short_channel_ids
ascending :: [ShortChannelId] -> Either ValidationError ()
ascending (a : rest@(b : _))
| a < b = ascending rest
| otherwise = Left ValidateScidNotAscending
ascending _ = Right ()