packages feed

ppad-bolt3-0.0.1: lib/Lightning/Protocol/BOLT3/Types.hs

{-# OPTIONS_HADDOCK prune #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- |
-- Module: Lightning.Protocol.BOLT3.Types
-- Copyright: (c) 2025 Jared Tobin
-- License: MIT
-- Maintainer: Jared Tobin <jared@ppad.tech>
--
-- Core types for BOLT #3 transaction and script formats.

module Lightning.Protocol.BOLT3.Types (
    -- * Monetary amounts
    Satoshi(..)
  , MilliSatoshi(..)
  , msat_to_sat
  , sat_to_msat

    -- * Keys and points
  , Pubkey(..)
  , pubkey
  , Seckey(..)
  , seckey
  , Point(..)
  , point

    -- * Hashes
  , PaymentHash(..)
  , payment_hash
  , PaymentPreimage(..)
  , payment_preimage

    -- * Transaction primitives
  , TxId(..)
  , txid
  , Outpoint(..)
  , Sequence(..)
  , Locktime(..)

    -- * Channel parameters
  , CommitmentNumber(..)
  , commitment_number
  , ToSelfDelay(..)
  , CltvExpiry(..)
  , DustLimit(..)
  , FeeratePerKw(..)

    -- * HTLC types
  , HTLC(..)
  , HTLCDirection(..)

    -- * Basepoints
  , Basepoints(..)
  , PerCommitmentPoint(..)
  , PerCommitmentSecret(..)
  , per_commitment_secret
  , RevocationBasepoint(..)
  , PaymentBasepoint(..)
  , DelayedPaymentBasepoint(..)
  , HtlcBasepoint(..)

    -- * Derived keys
  , LocalPubkey(..)
  , RemotePubkey(..)
  , LocalDelayedPubkey(..)
  , RemoteDelayedPubkey(..)
  , LocalHtlcPubkey(..)
  , RemoteHtlcPubkey(..)
  , RevocationPubkey(..)
  , FundingPubkey(..)

    -- * Script and witness
  , Script(..)
  , Witness(..)

    -- * Channel options
  , ChannelFeatures(..)
  , has_anchors

    -- * Transaction weights (constants)
  , commitment_weight_no_anchors
  , commitment_weight_anchors
  , htlc_timeout_weight_no_anchors
  , htlc_timeout_weight_anchors
  , htlc_success_weight_no_anchors
  , htlc_success_weight_anchors
  , htlc_output_weight

    -- * Dust thresholds (constants)
  , dust_p2pkh
  , dust_p2sh
  , dust_p2wpkh
  , dust_p2wsh
  , anchor_output_value
  ) where

import Data.Word (Word16, Word32, Word64)
import qualified Data.ByteString as BS
import GHC.Generics (Generic)

-- monetary amounts ------------------------------------------------------------

-- | Amount in satoshis.
newtype Satoshi = Satoshi { unSatoshi :: Word64 }
  deriving (Eq, Ord, Show, Generic, Num)

-- | Amount in millisatoshis.
newtype MilliSatoshi = MilliSatoshi { unMilliSatoshi :: Word64 }
  deriving (Eq, Ord, Show, Generic, Num)

-- | Convert millisatoshis to satoshis (rounds down).
msat_to_sat :: MilliSatoshi -> Satoshi
msat_to_sat (MilliSatoshi m) = Satoshi (m `div` 1000)
{-# INLINE msat_to_sat #-}

-- | Convert satoshis to millisatoshis.
sat_to_msat :: Satoshi -> MilliSatoshi
sat_to_msat (Satoshi s) = MilliSatoshi (s * 1000)
{-# INLINE sat_to_msat #-}

-- keys and points -------------------------------------------------------------

-- | Compressed public key (33 bytes).
newtype Pubkey = Pubkey { unPubkey :: BS.ByteString }
  deriving (Eq, Ord, Show, Generic)

-- | Parse a 33-byte compressed public key.
--
-- Returns Nothing if the input is not exactly 33 bytes.
--
-- >>> pubkey (BS.replicate 33 0x02)
-- Just (Pubkey ...)
-- >>> pubkey (BS.replicate 32 0x02)
-- Nothing
pubkey :: BS.ByteString -> Maybe Pubkey
pubkey bs
  | BS.length bs == 33 = Just (Pubkey bs)
  | otherwise = Nothing
{-# INLINE pubkey #-}

-- | Secret key (32 bytes).
newtype Seckey = Seckey { unSeckey :: BS.ByteString }
  deriving (Eq, Generic)

-- Don't show secret keys
instance Show Seckey where
  show _ = "Seckey <redacted>"

-- | Parse a 32-byte secret key.
--
-- Returns Nothing if the input is not exactly 32 bytes.
seckey :: BS.ByteString -> Maybe Seckey
seckey bs
  | BS.length bs == 32 = Just (Seckey bs)
  | otherwise = Nothing
{-# INLINE seckey #-}

-- | Elliptic curve point (33-byte compressed form).
newtype Point = Point { unPoint :: BS.ByteString }
  deriving (Eq, Ord, Show, Generic)

-- | Parse a 33-byte elliptic curve point.
--
-- Returns Nothing if the input is not exactly 33 bytes.
point :: BS.ByteString -> Maybe Point
point bs
  | BS.length bs == 33 = Just (Point bs)
  | otherwise = Nothing
{-# INLINE point #-}

-- hashes ----------------------------------------------------------------------

-- | Payment hash (32 bytes, SHA256 of preimage).
newtype PaymentHash = PaymentHash { unPaymentHash :: BS.ByteString }
  deriving (Eq, Ord, Show, Generic)

-- | Parse a 32-byte payment hash.
--
-- Returns Nothing if the input is not exactly 32 bytes.
payment_hash :: BS.ByteString -> Maybe PaymentHash
payment_hash bs
  | BS.length bs == 32 = Just (PaymentHash bs)
  | otherwise = Nothing
{-# INLINE payment_hash #-}

-- | Payment preimage (32 bytes).
newtype PaymentPreimage = PaymentPreimage { unPaymentPreimage :: BS.ByteString }
  deriving (Eq, Generic)

instance Show PaymentPreimage where
  show _ = "PaymentPreimage <redacted>"

-- | Parse a 32-byte payment preimage.
--
-- Returns Nothing if the input is not exactly 32 bytes.
payment_preimage :: BS.ByteString -> Maybe PaymentPreimage
payment_preimage bs
  | BS.length bs == 32 = Just (PaymentPreimage bs)
  | otherwise = Nothing
{-# INLINE payment_preimage #-}

-- transaction primitives ------------------------------------------------------

-- | Transaction ID (32 bytes, little-endian hash).
newtype TxId = TxId { unTxId :: BS.ByteString }
  deriving (Eq, Ord, Show, Generic)

-- | Parse a 32-byte transaction ID.
--
-- Returns Nothing if the input is not exactly 32 bytes.
txid :: BS.ByteString -> Maybe TxId
txid bs
  | BS.length bs == 32 = Just (TxId bs)
  | otherwise = Nothing
{-# INLINE txid #-}

-- | Transaction outpoint (txid + output index).
data Outpoint = Outpoint
  { outpoint_txid  :: {-# UNPACK #-} !TxId
  , outpoint_index :: {-# UNPACK #-} !Word32
  } deriving (Eq, Ord, Show, Generic)

-- | Transaction input sequence number.
newtype Sequence = Sequence { unSequence :: Word32 }
  deriving (Eq, Ord, Show, Generic, Num)

-- | Transaction locktime.
newtype Locktime = Locktime { unLocktime :: Word32 }
  deriving (Eq, Ord, Show, Generic, Num)

-- channel parameters ----------------------------------------------------------

-- | 48-bit commitment number.
newtype CommitmentNumber = CommitmentNumber { unCommitmentNumber :: Word64 }
  deriving (Eq, Ord, Show, Generic, Num)

-- | Parse a 48-bit commitment number.
--
-- Returns Nothing if the value exceeds 2^48 - 1.
commitment_number :: Word64 -> Maybe CommitmentNumber
commitment_number n
  | n <= 281474976710655 = Just (CommitmentNumber n)
  | otherwise = Nothing
{-# INLINE commitment_number #-}

-- | CSV delay for to_local outputs.
newtype ToSelfDelay = ToSelfDelay { unToSelfDelay :: Word16 }
  deriving (Eq, Ord, Show, Generic, Num)

-- | CLTV expiry for HTLCs.
newtype CltvExpiry = CltvExpiry { unCltvExpiry :: Word32 }
  deriving (Eq, Ord, Show, Generic, Num)

-- | Dust limit threshold.
newtype DustLimit = DustLimit { unDustLimit :: Satoshi }
  deriving (Eq, Ord, Show, Generic)

-- | Fee rate in satoshis per 1000 weight units.
newtype FeeratePerKw = FeeratePerKw { unFeeratePerKw :: Word32 }
  deriving (Eq, Ord, Show, Generic, Num)

-- HTLC types ------------------------------------------------------------------

-- | Direction of an HTLC from the commitment tx owner's perspective.
data HTLCDirection
  = HTLCOffered   -- ^ We offered this HTLC (outgoing)
  | HTLCReceived  -- ^ We received this HTLC (incoming)
  deriving (Eq, Ord, Show, Generic)

-- | HTLC output details.
--
-- NOTE: No Ord instance is provided. BOLT #3 requires output ordering by
-- amount then scriptPubKey, but scriptPubKey depends on derived keys which
-- are not available here. Use 'sort_outputs' in Tx module for proper BIP69
-- output ordering.
data HTLC = HTLC
  { htlc_direction    :: !HTLCDirection
  , htlc_amount_msat  :: {-# UNPACK #-} !MilliSatoshi
  , htlc_payment_hash :: {-# UNPACK #-} !PaymentHash
  , htlc_cltv_expiry  :: {-# UNPACK #-} !CltvExpiry
  } deriving (Eq, Show, Generic)

-- basepoints ------------------------------------------------------------------

-- | Per-commitment point (used to derive keys).
newtype PerCommitmentPoint = PerCommitmentPoint { unPerCommitmentPoint :: Point }
  deriving (Eq, Ord, Show, Generic)

-- | Per-commitment secret (32 bytes).
newtype PerCommitmentSecret = PerCommitmentSecret
  { unPerCommitmentSecret :: BS.ByteString }
  deriving (Eq, Generic)

instance Show PerCommitmentSecret where
  show _ = "PerCommitmentSecret <redacted>"

-- | Parse a 32-byte per-commitment secret.
--
-- Returns Nothing if the input is not exactly 32 bytes.
per_commitment_secret :: BS.ByteString -> Maybe PerCommitmentSecret
per_commitment_secret bs
  | BS.length bs == 32 = Just (PerCommitmentSecret bs)
  | otherwise = Nothing
{-# INLINE per_commitment_secret #-}

-- | Revocation basepoint.
newtype RevocationBasepoint = RevocationBasepoint
  { unRevocationBasepoint :: Point }
  deriving (Eq, Ord, Show, Generic)

-- | Payment basepoint.
newtype PaymentBasepoint = PaymentBasepoint
  { unPaymentBasepoint :: Point }
  deriving (Eq, Ord, Show, Generic)

-- | Delayed payment basepoint.
newtype DelayedPaymentBasepoint = DelayedPaymentBasepoint
  { unDelayedPaymentBasepoint :: Point }
  deriving (Eq, Ord, Show, Generic)

-- | HTLC basepoint.
newtype HtlcBasepoint = HtlcBasepoint { unHtlcBasepoint :: Point }
  deriving (Eq, Ord, Show, Generic)

-- | Collection of all basepoints for one party.
data Basepoints = Basepoints
  { bp_revocation      :: !RevocationBasepoint
  , bp_payment         :: !PaymentBasepoint
  , bp_delayed_payment :: !DelayedPaymentBasepoint
  , bp_htlc            :: !HtlcBasepoint
  } deriving (Eq, Show, Generic)

-- derived keys ----------------------------------------------------------------

-- | Local pubkey (derived from payment_basepoint + per_commitment_point).
newtype LocalPubkey = LocalPubkey { unLocalPubkey :: Pubkey }
  deriving (Eq, Ord, Show, Generic)

-- | Remote pubkey (simply the remote's payment_basepoint).
newtype RemotePubkey = RemotePubkey { unRemotePubkey :: Pubkey }
  deriving (Eq, Ord, Show, Generic)

-- | Local delayed pubkey.
newtype LocalDelayedPubkey = LocalDelayedPubkey
  { unLocalDelayedPubkey :: Pubkey }
  deriving (Eq, Ord, Show, Generic)

-- | Remote delayed pubkey.
newtype RemoteDelayedPubkey = RemoteDelayedPubkey
  { unRemoteDelayedPubkey :: Pubkey }
  deriving (Eq, Ord, Show, Generic)

-- | Local HTLC pubkey.
newtype LocalHtlcPubkey = LocalHtlcPubkey { unLocalHtlcPubkey :: Pubkey }
  deriving (Eq, Ord, Show, Generic)

-- | Remote HTLC pubkey.
newtype RemoteHtlcPubkey = RemoteHtlcPubkey { unRemoteHtlcPubkey :: Pubkey }
  deriving (Eq, Ord, Show, Generic)

-- | Revocation pubkey (derived from revocation_basepoint + per_commitment).
newtype RevocationPubkey = RevocationPubkey { unRevocationPubkey :: Pubkey }
  deriving (Eq, Ord, Show, Generic)

-- | Funding pubkey (used in 2-of-2 multisig).
newtype FundingPubkey = FundingPubkey { unFundingPubkey :: Pubkey }
  deriving (Eq, Ord, Show, Generic)

-- script and witness ----------------------------------------------------------

-- | Bitcoin script (serialized).
newtype Script = Script { unScript :: BS.ByteString }
  deriving (Eq, Ord, Show, Generic)

-- | Transaction witness stack.
newtype Witness = Witness { unWitness :: [BS.ByteString] }
  deriving (Eq, Ord, Show, Generic)

-- channel options -------------------------------------------------------------

-- | Channel feature flags relevant to BOLT #3.
data ChannelFeatures = ChannelFeatures
  { cf_option_anchors :: !Bool
  } deriving (Eq, Show, Generic)

-- | Check if option_anchors is enabled.
has_anchors :: ChannelFeatures -> Bool
has_anchors = cf_option_anchors
{-# INLINE has_anchors #-}

-- transaction weights (constants from spec) -----------------------------------

-- | Base commitment tx weight without option_anchors.
commitment_weight_no_anchors :: Word64
commitment_weight_no_anchors = 724

-- | Base commitment tx weight with option_anchors.
commitment_weight_anchors :: Word64
commitment_weight_anchors = 1124

-- | HTLC-timeout tx weight without option_anchors.
htlc_timeout_weight_no_anchors :: Word64
htlc_timeout_weight_no_anchors = 663

-- | HTLC-timeout tx weight with option_anchors.
htlc_timeout_weight_anchors :: Word64
htlc_timeout_weight_anchors = 666

-- | HTLC-success tx weight without option_anchors.
htlc_success_weight_no_anchors :: Word64
htlc_success_weight_no_anchors = 703

-- | HTLC-success tx weight with option_anchors.
htlc_success_weight_anchors :: Word64
htlc_success_weight_anchors = 706

-- | Weight added per HTLC output in commitment tx.
htlc_output_weight :: Word64
htlc_output_weight = 172

-- dust thresholds (constants from Bitcoin Core) -------------------------------

-- | P2PKH dust threshold (546 satoshis).
dust_p2pkh :: Satoshi
dust_p2pkh = Satoshi 546

-- | P2SH dust threshold (540 satoshis).
dust_p2sh :: Satoshi
dust_p2sh = Satoshi 540

-- | P2WPKH dust threshold (294 satoshis).
dust_p2wpkh :: Satoshi
dust_p2wpkh = Satoshi 294

-- | P2WSH dust threshold (330 satoshis).
dust_p2wsh :: Satoshi
dust_p2wsh = Satoshi 330

-- | Fixed anchor output value (330 satoshis).
anchor_output_value :: Satoshi
anchor_output_value = Satoshi 330