ppad-bolt2-0.0.1: lib/Lightning/Protocol/BOLT2/Types.hs
{-# OPTIONS_HADDOCK prune #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- |
-- Module: Lightning.Protocol.BOLT2.Types
-- Copyright: (c) 2025 Jared Tobin
-- License: MIT
-- Maintainer: Jared Tobin <jared@ppad.tech>
--
-- Core types for BOLT #2 peer protocol.
--
-- This module provides newtypes for identifiers, amounts, hashes, and
-- keys used in the Lightning Network peer protocol.
module Lightning.Protocol.BOLT2.Types (
-- * Identifiers
ChannelId
, channelId
, unChannelId
-- * Amounts
, Satoshis(..)
, MilliSatoshis(..)
, satoshisToMsat
, msatToSatoshis
-- * Cryptographic types
, Signature
, signature
, unSignature
, Point
, point
, unPoint
, PaymentHash
, paymentHash
, unPaymentHash
, PaymentPreimage
, paymentPreimage
, unPaymentPreimage
, Secret
, secret
, unSecret
-- * Transaction types
, TxId
, txId
, unTxId
, Outpoint(..)
, ScriptPubKey
, scriptPubKey
, unScriptPubKey
-- * Chain types
, ChainHash
, chainHash
, unChainHash
, ShortChannelId(..)
, shortChannelId
, scidBlockHeight
, scidTxIndex
, scidOutputIndex
-- * Protocol types
, FeatureBits
, featureBits
, unFeatureBits
, OnionPacket
, onionPacket
, unOnionPacket
-- * Constants
, channelIdLen
, signatureLen
, pointLen
, txIdLen
, chainHashLen
, shortChannelIdLen
, paymentHashLen
, paymentPreimageLen
, onionPacketLen
, secretLen
) where
import Control.DeepSeq (NFData)
import Data.Bits (unsafeShiftL, unsafeShiftR, (.&.), (.|.))
import qualified Data.ByteString as BS
import Data.Word (Word16, Word32, Word64)
import GHC.Generics (Generic)
-- constants -------------------------------------------------------------------
-- | Length of a channel_id in bytes (32).
channelIdLen :: Int
channelIdLen = 32
{-# INLINE channelIdLen #-}
-- | Length of a signature in bytes (64, compact format).
signatureLen :: Int
signatureLen = 64
{-# INLINE signatureLen #-}
-- | Length of a compressed secp256k1 public key in bytes (33).
pointLen :: Int
pointLen = 33
{-# INLINE pointLen #-}
-- | Length of a transaction ID in bytes (32).
txIdLen :: Int
txIdLen = 32
{-# INLINE txIdLen #-}
-- | Length of a chain hash in bytes (32).
chainHashLen :: Int
chainHashLen = 32
{-# INLINE chainHashLen #-}
-- | Length of a short_channel_id in bytes (8).
shortChannelIdLen :: Int
shortChannelIdLen = 8
{-# INLINE shortChannelIdLen #-}
-- | Length of a payment hash in bytes (32).
paymentHashLen :: Int
paymentHashLen = 32
{-# INLINE paymentHashLen #-}
-- | Length of a payment preimage in bytes (32).
paymentPreimageLen :: Int
paymentPreimageLen = 32
{-# INLINE paymentPreimageLen #-}
-- | Length of an onion routing packet in bytes (1366).
onionPacketLen :: Int
onionPacketLen = 1366
{-# INLINE onionPacketLen #-}
-- | Length of a per-commitment secret in bytes (32).
secretLen :: Int
secretLen = 32
{-# INLINE secretLen #-}
-- identifiers -----------------------------------------------------------------
-- | A 32-byte channel identifier.
--
-- Derived from the funding transaction by XORing @funding_txid@ with
-- @funding_output_index@ (big-endian, altering the last 2 bytes).
--
-- For v2 channels, derived as @SHA256(lesser-revocation-basepoint ||
-- greater-revocation-basepoint)@.
newtype ChannelId = ChannelId BS.ByteString
deriving stock (Eq, Ord, Show, Generic)
deriving newtype NFData
-- | Construct a 'ChannelId' from a 32-byte 'BS.ByteString'.
--
-- Returns 'Nothing' if the input is not exactly 32 bytes.
--
-- >>> channelId (BS.replicate 32 0x00)
-- Just (ChannelId ...)
-- >>> channelId (BS.replicate 31 0x00)
-- Nothing
channelId :: BS.ByteString -> Maybe ChannelId
channelId !bs
| BS.length bs == channelIdLen = Just $! ChannelId bs
| otherwise = Nothing
{-# INLINABLE channelId #-}
-- | Extract the underlying 'BS.ByteString' from a 'ChannelId'.
unChannelId :: ChannelId -> BS.ByteString
unChannelId (ChannelId bs) = bs
{-# INLINE unChannelId #-}
-- amounts ---------------------------------------------------------------------
-- | Amount in satoshis (1/100,000,000 of a bitcoin).
--
-- Stored as a 'Word64'. Maximum valid value is 21,000,000 * 100,000,000
-- = 2,100,000,000,000,000 satoshis.
newtype Satoshis = Satoshis { unSatoshis :: Word64 }
deriving stock (Eq, Ord, Show, Generic)
deriving newtype (NFData, Num, Enum, Real, Integral)
-- | Amount in millisatoshis (1/1000 of a satoshi).
--
-- Stored as a 'Word64'. Used for HTLC amounts and channel balances.
newtype MilliSatoshis = MilliSatoshis { unMilliSatoshis :: Word64 }
deriving stock (Eq, Ord, Show, Generic)
deriving newtype (NFData, Num, Enum, Real, Integral)
-- | Convert 'Satoshis' to 'MilliSatoshis'.
--
-- >>> satoshisToMsat (Satoshis 1)
-- MilliSatoshis 1000
satoshisToMsat :: Satoshis -> MilliSatoshis
satoshisToMsat (Satoshis !s) = MilliSatoshis $! s * 1000
{-# INLINE satoshisToMsat #-}
-- | Convert 'MilliSatoshis' to 'Satoshis', rounding down.
--
-- >>> msatToSatoshis (MilliSatoshis 1500)
-- Satoshis 1
msatToSatoshis :: MilliSatoshis -> Satoshis
msatToSatoshis (MilliSatoshis !m) = Satoshis $! m `div` 1000
{-# INLINE msatToSatoshis #-}
-- cryptographic types ---------------------------------------------------------
-- | A 64-byte compact ECDSA signature.
--
-- Used for commitment transaction signatures, HTLC signatures, and
-- closing transaction signatures.
newtype Signature = Signature BS.ByteString
deriving stock (Eq, Ord, Show, Generic)
deriving newtype NFData
-- | Construct a 'Signature' from a 64-byte 'BS.ByteString'.
--
-- Returns 'Nothing' if the input is not exactly 64 bytes.
signature :: BS.ByteString -> Maybe Signature
signature !bs
| BS.length bs == signatureLen = Just $! Signature bs
| otherwise = Nothing
{-# INLINABLE signature #-}
-- | Extract the underlying 'BS.ByteString' from a 'Signature'.
unSignature :: Signature -> BS.ByteString
unSignature (Signature bs) = bs
{-# INLINE unSignature #-}
-- | A 33-byte compressed secp256k1 public key.
--
-- Used for funding pubkeys, basepoints, and per-commitment points.
newtype Point = Point BS.ByteString
deriving stock (Eq, Ord, Show, Generic)
deriving newtype NFData
-- | Construct a 'Point' from a 33-byte 'BS.ByteString'.
--
-- Returns 'Nothing' if the input is not exactly 33 bytes.
--
-- Note: This only validates the length. Use secp256k1 libraries for
-- full point validation.
point :: BS.ByteString -> Maybe Point
point !bs
| BS.length bs == pointLen = Just $! Point bs
| otherwise = Nothing
{-# INLINABLE point #-}
-- | Extract the underlying 'BS.ByteString' from a 'Point'.
unPoint :: Point -> BS.ByteString
unPoint (Point bs) = bs
{-# INLINE unPoint #-}
-- | A 32-byte SHA256 payment hash.
--
-- Used to identify HTLCs. The preimage that hashes to this value is
-- required to claim the HTLC.
newtype PaymentHash = PaymentHash BS.ByteString
deriving stock (Eq, Ord, Show, Generic)
deriving newtype NFData
-- | Construct a 'PaymentHash' from a 32-byte 'BS.ByteString'.
--
-- Returns 'Nothing' if the input is not exactly 32 bytes.
paymentHash :: BS.ByteString -> Maybe PaymentHash
paymentHash !bs
| BS.length bs == paymentHashLen = Just $! PaymentHash bs
| otherwise = Nothing
{-# INLINABLE paymentHash #-}
-- | Extract the underlying 'BS.ByteString' from a 'PaymentHash'.
unPaymentHash :: PaymentHash -> BS.ByteString
unPaymentHash (PaymentHash bs) = bs
{-# INLINE unPaymentHash #-}
-- | A 32-byte payment preimage.
--
-- The SHA256 hash of this value produces the corresponding 'PaymentHash'.
-- Knowledge of the preimage allows claiming an HTLC.
newtype PaymentPreimage = PaymentPreimage BS.ByteString
deriving stock (Eq, Ord, Show, Generic)
deriving newtype NFData
-- | Construct a 'PaymentPreimage' from a 32-byte 'BS.ByteString'.
--
-- Returns 'Nothing' if the input is not exactly 32 bytes.
paymentPreimage :: BS.ByteString -> Maybe PaymentPreimage
paymentPreimage !bs
| BS.length bs == paymentPreimageLen = Just $! PaymentPreimage bs
| otherwise = Nothing
{-# INLINABLE paymentPreimage #-}
-- | Extract the underlying 'BS.ByteString' from a 'PaymentPreimage'.
unPaymentPreimage :: PaymentPreimage -> BS.ByteString
unPaymentPreimage (PaymentPreimage bs) = bs
{-# INLINE unPaymentPreimage #-}
-- | A 32-byte per-commitment secret.
--
-- Used in revoke_and_ack and channel_reestablish messages to revoke
-- old commitment transactions.
newtype Secret = Secret BS.ByteString
deriving stock (Eq, Ord, Show, Generic)
deriving newtype NFData
-- | Construct a 'Secret' from a 32-byte 'BS.ByteString'.
--
-- Returns 'Nothing' if the input is not exactly 32 bytes.
secret :: BS.ByteString -> Maybe Secret
secret !bs
| BS.length bs == secretLen = Just $! Secret bs
| otherwise = Nothing
{-# INLINABLE secret #-}
-- | Extract the underlying 'BS.ByteString' from a 'Secret'.
unSecret :: Secret -> BS.ByteString
unSecret (Secret bs) = bs
{-# INLINE unSecret #-}
-- transaction types -----------------------------------------------------------
-- | A 32-byte transaction identifier.
--
-- The double-SHA256 hash of a serialized transaction.
newtype TxId = TxId BS.ByteString
deriving stock (Eq, Ord, Show, Generic)
deriving newtype NFData
-- | Construct a 'TxId' from a 32-byte 'BS.ByteString'.
--
-- Returns 'Nothing' if the input is not exactly 32 bytes.
txId :: BS.ByteString -> Maybe TxId
txId !bs
| BS.length bs == txIdLen = Just $! TxId bs
| otherwise = Nothing
{-# INLINABLE txId #-}
-- | Extract the underlying 'BS.ByteString' from a 'TxId'.
unTxId :: TxId -> BS.ByteString
unTxId (TxId bs) = bs
{-# INLINE unTxId #-}
-- | A transaction outpoint (txid + output index).
--
-- Identifies a specific output of a transaction.
data Outpoint = Outpoint
{ outpointTxId :: {-# UNPACK #-} !TxId
, outpointVout :: {-# UNPACK #-} !Word32
}
deriving stock (Eq, Ord, Show, Generic)
instance NFData Outpoint
-- | A script pubkey (output script).
--
-- Variable length; used in shutdown messages, closing transactions, etc.
newtype ScriptPubKey = ScriptPubKey BS.ByteString
deriving stock (Eq, Ord, Show, Generic)
deriving newtype NFData
-- | Construct a 'ScriptPubKey' from a 'BS.ByteString'.
--
-- Accepts any length; validation of script structure is left to higher
-- layers.
scriptPubKey :: BS.ByteString -> ScriptPubKey
scriptPubKey = ScriptPubKey
{-# INLINE scriptPubKey #-}
-- | Extract the underlying 'BS.ByteString' from a 'ScriptPubKey'.
unScriptPubKey :: ScriptPubKey -> BS.ByteString
unScriptPubKey (ScriptPubKey bs) = bs
{-# INLINE unScriptPubKey #-}
-- chain types -----------------------------------------------------------------
-- | A 32-byte chain hash.
--
-- Identifies the blockchain (typically the genesis block hash).
-- Used in @open_channel@ to specify which chain the channel will reside on.
newtype ChainHash = ChainHash BS.ByteString
deriving stock (Eq, Ord, Show, Generic)
deriving newtype NFData
-- | Construct a 'ChainHash' from a 32-byte 'BS.ByteString'.
--
-- Returns 'Nothing' if the input is not exactly 32 bytes.
chainHash :: BS.ByteString -> Maybe ChainHash
chainHash !bs
| BS.length bs == chainHashLen = Just $! ChainHash bs
| otherwise = Nothing
{-# INLINABLE chainHash #-}
-- | Extract the underlying 'BS.ByteString' from a 'ChainHash'.
unChainHash :: ChainHash -> BS.ByteString
unChainHash (ChainHash bs) = bs
{-# INLINE unChainHash #-}
-- | A short channel identifier (8 bytes).
--
-- Encodes the block height (3 bytes), transaction index (3 bytes), and
-- output index (2 bytes) of the funding transaction output.
--
-- This is a compact representation for referencing channels in gossip
-- and routing.
data ShortChannelId = ShortChannelId
{ scidBytes :: {-# UNPACK #-} !Word64
}
deriving stock (Eq, Ord, Show, Generic)
instance NFData ShortChannelId
-- | Construct a 'ShortChannelId' from block height, tx index, and
-- output index.
--
-- Returns 'Nothing' if any component exceeds its maximum value:
--
-- * block height: max 16,777,215 (2^24 - 1)
-- * tx index: max 16,777,215 (2^24 - 1)
-- * output index: max 65,535 (2^16 - 1)
--
-- >>> shortChannelId 800000 1234 0
-- Just (ShortChannelId ...)
shortChannelId
:: Word32 -- ^ Block height (24 bits max)
-> Word32 -- ^ Transaction index (24 bits max)
-> Word16 -- ^ Output index
-> Maybe ShortChannelId
shortChannelId !blockHeight !txIndex !outputIndex
| blockHeight > 0xFFFFFF = Nothing
| txIndex > 0xFFFFFF = Nothing
| otherwise = Just $! ShortChannelId scid
where
!scid = (fromIntegral blockHeight `unsafeShiftL` 40)
.|. (fromIntegral txIndex `unsafeShiftL` 16)
.|. fromIntegral outputIndex
{-# INLINABLE shortChannelId #-}
-- | Extract the block height from a 'ShortChannelId'.
scidBlockHeight :: ShortChannelId -> Word32
scidBlockHeight (ShortChannelId !w) =
fromIntegral $! (w `unsafeShiftR` 40) .&. 0xFFFFFF
{-# INLINE scidBlockHeight #-}
-- | Extract the transaction index from a 'ShortChannelId'.
scidTxIndex :: ShortChannelId -> Word32
scidTxIndex (ShortChannelId !w) =
fromIntegral $! (w `unsafeShiftR` 16) .&. 0xFFFFFF
{-# INLINE scidTxIndex #-}
-- | Extract the output index from a 'ShortChannelId'.
scidOutputIndex :: ShortChannelId -> Word16
scidOutputIndex (ShortChannelId !w) = fromIntegral $! w .&. 0xFFFF
{-# INLINE scidOutputIndex #-}
-- protocol types --------------------------------------------------------------
-- | Feature bits (variable length).
--
-- Encodes supported/required features. Even bits indicate required
-- features; odd bits indicate optional features.
newtype FeatureBits = FeatureBits BS.ByteString
deriving stock (Eq, Ord, Show, Generic)
deriving newtype NFData
-- | Construct 'FeatureBits' from a 'BS.ByteString'.
--
-- Accepts any length; feature bit parsing is left to higher layers.
featureBits :: BS.ByteString -> FeatureBits
featureBits = FeatureBits
{-# INLINE featureBits #-}
-- | Extract the underlying 'BS.ByteString' from 'FeatureBits'.
unFeatureBits :: FeatureBits -> BS.ByteString
unFeatureBits (FeatureBits bs) = bs
{-# INLINE unFeatureBits #-}
-- | A 1366-byte onion routing packet.
--
-- Contains encrypted routing information for HTLC forwarding, as
-- specified in BOLT #4.
newtype OnionPacket = OnionPacket BS.ByteString
deriving stock (Eq, Ord, Show, Generic)
deriving newtype NFData
-- | Construct an 'OnionPacket' from a 1366-byte 'BS.ByteString'.
--
-- Returns 'Nothing' if the input is not exactly 1366 bytes.
onionPacket :: BS.ByteString -> Maybe OnionPacket
onionPacket !bs
| BS.length bs == onionPacketLen = Just $! OnionPacket bs
| otherwise = Nothing
{-# INLINABLE onionPacket #-}
-- | Extract the underlying 'BS.ByteString' from an 'OnionPacket'.
unOnionPacket :: OnionPacket -> BS.ByteString
unOnionPacket (OnionPacket bs) = bs
{-# INLINE unOnionPacket #-}