ppad-bolt7-0.0.1: lib/Lightning/Protocol/BOLT7/Types.hs
{-# OPTIONS_HADDOCK prune #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}
-- |
-- Module: Lightning.Protocol.BOLT7.Types
-- Copyright: (c) 2025 Jared Tobin
-- License: MIT
-- Maintainer: Jared Tobin <jared@ppad.tech>
--
-- Core types for BOLT #7 routing gossip.
module Lightning.Protocol.BOLT7.Types (
-- * Identifiers
ChainHash
, chainHash
, getChainHash
, mainnetChainHash
, ShortChannelId
, shortChannelId
, mkShortChannelId
, getShortChannelId
, scidBlockHeight
, scidTxIndex
, scidOutputIndex
, formatScid
, ChannelId
, channelId
, getChannelId
-- * Cryptographic types
, Signature
, signature
, getSignature
, Point
, point
, getPoint
, NodeId
, nodeId
, getNodeId
-- * Node metadata
, RgbColor
, rgbColor
, getRgbColor
, Alias
, alias
, getAlias
, Timestamp
, FeatureBits
, featureBits
, getFeatureBits
-- * Address types
, Address(..)
, IPv4Addr
, ipv4Addr
, getIPv4Addr
, IPv6Addr
, ipv6Addr
, getIPv6Addr
, TorV3Addr
, torV3Addr
, getTorV3Addr
-- * Channel update flags
, MessageFlags(..)
, encodeMessageFlags
, decodeMessageFlags
, ChannelFlags(..)
, encodeChannelFlags
, decodeChannelFlags
-- * Routing parameters
, CltvExpiryDelta(..)
, FeeBaseMsat(..)
, FeeProportionalMillionths(..)
, HtlcMinimumMsat(..)
, HtlcMaximumMsat(..)
-- * Constants
, chainHashLen
, shortChannelIdLen
, channelIdLen
, signatureLen
, pointLen
, nodeIdLen
, rgbColorLen
, aliasLen
, ipv4AddrLen
, ipv6AddrLen
, torV3AddrLen
) where
import Control.DeepSeq (NFData)
import Data.Bits (shiftL, shiftR, (.&.), (.|.))
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Word (Word8, Word16, Word32, Word64)
import GHC.Generics (Generic)
-- Constants -------------------------------------------------------------------
-- | Length of a chain hash (32 bytes).
chainHashLen :: Int
chainHashLen = 32
{-# INLINE chainHashLen #-}
-- | Length of a short channel ID (8 bytes).
shortChannelIdLen :: Int
shortChannelIdLen = 8
{-# INLINE shortChannelIdLen #-}
-- | Length of a channel ID (32 bytes).
channelIdLen :: Int
channelIdLen = 32
{-# INLINE channelIdLen #-}
-- | Length of a signature (64 bytes).
signatureLen :: Int
signatureLen = 64
{-# INLINE signatureLen #-}
-- | Length of a compressed public key (33 bytes).
pointLen :: Int
pointLen = 33
{-# INLINE pointLen #-}
-- | Length of a node ID (33 bytes, same as compressed public key).
nodeIdLen :: Int
nodeIdLen = 33
{-# INLINE nodeIdLen #-}
-- | Length of RGB color (3 bytes).
rgbColorLen :: Int
rgbColorLen = 3
{-# INLINE rgbColorLen #-}
-- | Length of node alias (32 bytes).
aliasLen :: Int
aliasLen = 32
{-# INLINE aliasLen #-}
-- | Length of IPv4 address (4 bytes).
ipv4AddrLen :: Int
ipv4AddrLen = 4
{-# INLINE ipv4AddrLen #-}
-- | Length of IPv6 address (16 bytes).
ipv6AddrLen :: Int
ipv6AddrLen = 16
{-# INLINE ipv6AddrLen #-}
-- | Length of Tor v3 address (35 bytes).
torV3AddrLen :: Int
torV3AddrLen = 35
{-# INLINE torV3AddrLen #-}
-- Identifiers -----------------------------------------------------------------
-- | Chain hash identifying the blockchain (32 bytes).
newtype ChainHash = ChainHash { getChainHash :: ByteString }
deriving (Eq, Show, Generic)
instance NFData ChainHash
-- | Smart constructor for ChainHash. Returns Nothing if not 32 bytes.
chainHash :: ByteString -> Maybe ChainHash
chainHash !bs
| BS.length bs == chainHashLen = Just (ChainHash bs)
| otherwise = Nothing
{-# INLINE chainHash #-}
-- | Bitcoin mainnet chain hash (genesis block hash, little-endian).
--
-- This is the double-SHA256 of the mainnet genesis block header, reversed
-- to little-endian byte order as used in the protocol.
mainnetChainHash :: ChainHash
mainnetChainHash = ChainHash $ BS.pack
[ 0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72
, 0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f
, 0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c
, 0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00
]
-- | Short channel ID (8 bytes): block height (3) + tx index (3) + output (2).
newtype ShortChannelId = ShortChannelId { getShortChannelId :: ByteString }
deriving (Eq, Show, Generic)
instance NFData ShortChannelId
-- | Smart constructor for ShortChannelId. Returns Nothing if not 8 bytes.
shortChannelId :: ByteString -> Maybe ShortChannelId
shortChannelId !bs
| BS.length bs == shortChannelIdLen = Just (ShortChannelId bs)
| otherwise = Nothing
{-# INLINE shortChannelId #-}
-- | Construct ShortChannelId from components.
--
-- Block height and tx index are truncated to 24 bits.
--
-- >>> mkShortChannelId 539268 845 1
-- ShortChannelId {getShortChannelId = "\NUL\131\132\NUL\ETX-\NUL\SOH"}
mkShortChannelId
:: Word32 -- ^ Block height (24 bits)
-> Word32 -- ^ Transaction index (24 bits)
-> Word16 -- ^ Output index
-> ShortChannelId
mkShortChannelId !block !txIdx !outIdx = ShortChannelId $ BS.pack
[ fromIntegral ((block `shiftR` 16) .&. 0xff) :: Word8
, fromIntegral ((block `shiftR` 8) .&. 0xff)
, fromIntegral (block .&. 0xff)
, fromIntegral ((txIdx `shiftR` 16) .&. 0xff)
, fromIntegral ((txIdx `shiftR` 8) .&. 0xff)
, fromIntegral (txIdx .&. 0xff)
, fromIntegral ((outIdx `shiftR` 8) .&. 0xff)
, fromIntegral (outIdx .&. 0xff)
]
{-# INLINE mkShortChannelId #-}
-- | Extract block height from short channel ID (first 3 bytes, big-endian).
scidBlockHeight :: ShortChannelId -> Word32
scidBlockHeight (ShortChannelId bs) =
let b0 = fromIntegral (BS.index bs 0)
b1 = fromIntegral (BS.index bs 1)
b2 = fromIntegral (BS.index bs 2)
in (b0 `shiftL` 16) .|. (b1 `shiftL` 8) .|. b2
{-# INLINE scidBlockHeight #-}
-- | Extract transaction index from short channel ID (bytes 3-5, big-endian).
scidTxIndex :: ShortChannelId -> Word32
scidTxIndex (ShortChannelId bs) =
let b3 = fromIntegral (BS.index bs 3)
b4 = fromIntegral (BS.index bs 4)
b5 = fromIntegral (BS.index bs 5)
in (b3 `shiftL` 16) .|. (b4 `shiftL` 8) .|. b5
{-# INLINE scidTxIndex #-}
-- | Extract output index from short channel ID (last 2 bytes, big-endian).
scidOutputIndex :: ShortChannelId -> Word16
scidOutputIndex (ShortChannelId bs) =
let b6 = fromIntegral (BS.index bs 6)
b7 = fromIntegral (BS.index bs 7)
in (b6 `shiftL` 8) .|. b7
{-# INLINE scidOutputIndex #-}
-- | Format short channel ID as human-readable string.
--
-- Uses the standard "block x tx x output" notation.
--
-- >>> formatScid (mkShortChannelId 539268 845 1)
-- "539268x845x1"
formatScid :: ShortChannelId -> String
formatScid scid =
show (scidBlockHeight scid) ++ "x" ++
show (scidTxIndex scid) ++ "x" ++
show (scidOutputIndex scid)
{-# INLINE formatScid #-}
-- | Channel ID (32 bytes).
newtype ChannelId = ChannelId { getChannelId :: ByteString }
deriving (Eq, Show, Generic)
instance NFData ChannelId
-- | Smart constructor for ChannelId. Returns Nothing if not 32 bytes.
channelId :: ByteString -> Maybe ChannelId
channelId !bs
| BS.length bs == channelIdLen = Just (ChannelId bs)
| otherwise = Nothing
{-# INLINE channelId #-}
-- Cryptographic types ---------------------------------------------------------
-- | Signature (64 bytes).
newtype Signature = Signature { getSignature :: ByteString }
deriving (Eq, Show, Generic)
instance NFData Signature
-- | Smart constructor for Signature. Returns Nothing if not 64 bytes.
signature :: ByteString -> Maybe Signature
signature !bs
| BS.length bs == signatureLen = Just (Signature bs)
| otherwise = Nothing
{-# INLINE signature #-}
-- | Compressed public key (33 bytes).
newtype Point = Point { getPoint :: ByteString }
deriving (Eq, Show, Generic)
instance NFData Point
-- | Smart constructor for Point. Returns Nothing if not 33 bytes.
point :: ByteString -> Maybe Point
point !bs
| BS.length bs == pointLen = Just (Point bs)
| otherwise = Nothing
{-# INLINE point #-}
-- | Node ID (33 bytes, same as compressed public key).
--
-- Has Ord instance for lexicographic comparison (required by spec for
-- channel announcements where node_id_1 < node_id_2).
newtype NodeId = NodeId { getNodeId :: ByteString }
deriving (Eq, Ord, Show, Generic)
instance NFData NodeId
-- | Smart constructor for NodeId. Returns Nothing if not 33 bytes.
nodeId :: ByteString -> Maybe NodeId
nodeId !bs
| BS.length bs == nodeIdLen = Just (NodeId bs)
| otherwise = Nothing
{-# INLINE nodeId #-}
-- Node metadata ---------------------------------------------------------------
-- | RGB color (3 bytes).
newtype RgbColor = RgbColor { getRgbColor :: ByteString }
deriving (Eq, Show, Generic)
instance NFData RgbColor
-- | Smart constructor for RgbColor. Returns Nothing if not 3 bytes.
rgbColor :: ByteString -> Maybe RgbColor
rgbColor !bs
| BS.length bs == rgbColorLen = Just (RgbColor bs)
| otherwise = Nothing
{-# INLINE rgbColor #-}
-- | Node alias (32 bytes, UTF-8 padded with zero bytes).
newtype Alias = Alias { getAlias :: ByteString }
deriving (Eq, Show, Generic)
instance NFData Alias
-- | Smart constructor for Alias. Returns Nothing if not 32 bytes.
alias :: ByteString -> Maybe Alias
alias !bs
| BS.length bs == aliasLen = Just (Alias bs)
| otherwise = Nothing
{-# INLINE alias #-}
-- | Timestamp (Unix epoch seconds).
type Timestamp = Word32
-- | Feature bits (variable length).
newtype FeatureBits = FeatureBits { getFeatureBits :: ByteString }
deriving (Eq, Show, Generic)
instance NFData FeatureBits
-- | Smart constructor for FeatureBits (any length).
featureBits :: ByteString -> FeatureBits
featureBits = FeatureBits
{-# INLINE featureBits #-}
-- Address types ---------------------------------------------------------------
-- | IPv4 address (4 bytes).
newtype IPv4Addr = IPv4Addr { getIPv4Addr :: ByteString }
deriving (Eq, Show, Generic)
instance NFData IPv4Addr
-- | Smart constructor for IPv4Addr. Returns Nothing if not 4 bytes.
ipv4Addr :: ByteString -> Maybe IPv4Addr
ipv4Addr !bs
| BS.length bs == ipv4AddrLen = Just (IPv4Addr bs)
| otherwise = Nothing
{-# INLINE ipv4Addr #-}
-- | IPv6 address (16 bytes).
newtype IPv6Addr = IPv6Addr { getIPv6Addr :: ByteString }
deriving (Eq, Show, Generic)
instance NFData IPv6Addr
-- | Smart constructor for IPv6Addr. Returns Nothing if not 16 bytes.
ipv6Addr :: ByteString -> Maybe IPv6Addr
ipv6Addr !bs
| BS.length bs == ipv6AddrLen = Just (IPv6Addr bs)
| otherwise = Nothing
{-# INLINE ipv6Addr #-}
-- | Tor v3 onion address (35 bytes: 32 pubkey + 2 checksum + 1 version).
newtype TorV3Addr = TorV3Addr { getTorV3Addr :: ByteString }
deriving (Eq, Show, Generic)
instance NFData TorV3Addr
-- | Smart constructor for TorV3Addr. Returns Nothing if not 35 bytes.
torV3Addr :: ByteString -> Maybe TorV3Addr
torV3Addr !bs
| BS.length bs == torV3AddrLen = Just (TorV3Addr bs)
| otherwise = Nothing
{-# INLINE torV3Addr #-}
-- | Network address with port.
data Address
= AddrIPv4 !IPv4Addr !Word16 -- ^ IPv4 address + port
| AddrIPv6 !IPv6Addr !Word16 -- ^ IPv6 address + port
| AddrTorV3 !TorV3Addr !Word16 -- ^ Tor v3 address + port
| AddrDNS !ByteString !Word16 -- ^ DNS hostname + port
deriving (Eq, Show, Generic)
instance NFData Address
-- Channel update flags --------------------------------------------------------
-- | Message flags for channel_update.
--
-- Bit 0: htlc_maximum_msat field is present.
data MessageFlags = MessageFlags
{ mfHtlcMaxPresent :: !Bool -- ^ htlc_maximum_msat is present
}
deriving (Eq, Show, Generic)
instance NFData MessageFlags
-- | Encode MessageFlags to Word8.
encodeMessageFlags :: MessageFlags -> Word8
encodeMessageFlags mf = if mfHtlcMaxPresent mf then 0x01 else 0x00
{-# INLINE encodeMessageFlags #-}
-- | Decode Word8 to MessageFlags.
decodeMessageFlags :: Word8 -> MessageFlags
decodeMessageFlags w = MessageFlags { mfHtlcMaxPresent = w .&. 0x01 /= 0 }
{-# INLINE decodeMessageFlags #-}
-- | Channel flags for channel_update.
--
-- Bit 0: direction (0 = node_id_1 is origin, 1 = node_id_2 is origin).
-- Bit 1: disabled (1 = channel disabled).
data ChannelFlags = ChannelFlags
{ cfDirection :: !Bool -- ^ True = node_id_2 is origin
, cfDisabled :: !Bool -- ^ True = channel is disabled
}
deriving (Eq, Show, Generic)
instance NFData ChannelFlags
-- | Encode ChannelFlags to Word8.
encodeChannelFlags :: ChannelFlags -> Word8
encodeChannelFlags cf =
(if cfDirection cf then 0x01 else 0x00) .|.
(if cfDisabled cf then 0x02 else 0x00)
{-# INLINE encodeChannelFlags #-}
-- | Decode Word8 to ChannelFlags.
decodeChannelFlags :: Word8 -> ChannelFlags
decodeChannelFlags w = ChannelFlags
{ cfDirection = w .&. 0x01 /= 0
, cfDisabled = w .&. 0x02 /= 0
}
{-# INLINE decodeChannelFlags #-}
-- Routing parameters ----------------------------------------------------------
-- | CLTV expiry delta.
newtype CltvExpiryDelta = CltvExpiryDelta { getCltvExpiryDelta :: Word16 }
deriving (Eq, Ord, Show, Generic)
instance NFData CltvExpiryDelta
-- | Base fee in millisatoshis.
newtype FeeBaseMsat = FeeBaseMsat { getFeeBaseMsat :: Word32 }
deriving (Eq, Ord, Show, Generic)
instance NFData FeeBaseMsat
-- | Proportional fee in millionths.
newtype FeeProportionalMillionths = FeeProportionalMillionths
{ getFeeProportionalMillionths :: Word32 }
deriving (Eq, Ord, Show, Generic)
instance NFData FeeProportionalMillionths
-- | Minimum HTLC value in millisatoshis.
newtype HtlcMinimumMsat = HtlcMinimumMsat { getHtlcMinimumMsat :: Word64 }
deriving (Eq, Ord, Show, Generic)
instance NFData HtlcMinimumMsat
-- | Maximum HTLC value in millisatoshis.
newtype HtlcMaximumMsat = HtlcMaximumMsat { getHtlcMaximumMsat :: Word64 }
deriving (Eq, Ord, Show, Generic)
instance NFData HtlcMaximumMsat