packages feed

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

{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}

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

module Lightning.Protocol.BOLT3.Types (
    -- * Points and keys
    PerCommitmentPoint(..)
  , RevocationBasepoint(..)
  , PaymentBasepoint(..)
  , DelayedPaymentBasepoint(..)
  , HtlcBasepoint(..)
  , Basepoints(..)
  , RevocationPubkey(..)
  , LocalDelayedPubkey(..)
  , LocalHtlcPubkey(..)
  , RemoteHtlcPubkey(..)
  , RemotePubkey(..)
  , FundingPubkey(..)
  , Seckey(..)
  , seckey
  , un_seckey

    -- * Per-commitment secrets
  , Seed(..)
  , seed
  , un_seed
  , SecretIndex(..)
  , secret_index
  , un_secret_index
  , commitment_secret_index

    -- * Channel parameters
  , CommitmentNumber(..)
  , commitment_number
  , un_commitment_number
  , next_commitment_number
  , ToSelfDelay(..)
  , CltvExpiry(..)
  , DustLimit(..)
  , FeeratePerKw(..)
  , Locktime(..)
  , Sequence(..)
  , CommitmentFormat(..)

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

    -- * Scripts
  , Script(..)

    -- * Dust thresholds
  , dust_p2pkh
  , dust_p2sh
  , dust_p2wpkh
  , dust_p2wsh
  , anchor_output_value

    -- * Internal
  , sat
  , internal_error
  ) where

import Control.DeepSeq (NFData(..))
import qualified Crypto.Curve.Secp256k1 as S
import qualified Data.ByteString as BS
import Data.Word (Word16, Word32, Word64)
import GHC.Generics (Generic)
import Lightning.Protocol.BOLT1 (Point, Satoshi, MilliSatoshi, PaymentHash)
import qualified Lightning.Protocol.BOLT1 as BOLT1

-- internal ------------------------------------------------------------------

-- Abort on a state that the surrounding code rules out.
internal_error :: a
internal_error = error "ppad-bolt3: internal error, please report a bug!"
{-# NOINLINE internal_error #-}

-- A 'Satoshi' amount known to be at most 21 million BTC (a constant,
-- or a value bounded by construction).
sat :: Word64 -> Satoshi
sat w = case BOLT1.satoshi w of
  Just s  -> s
  Nothing -> internal_error
{-# INLINE sat #-}

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

-- | A per-commitment point, from which a commitment's keys are
--   derived.
newtype PerCommitmentPoint = PerCommitmentPoint Point
  deriving (Eq, Ord, Show, Generic)

instance NFData PerCommitmentPoint

-- | A @revocation_basepoint@.
newtype RevocationBasepoint = RevocationBasepoint Point
  deriving (Eq, Ord, Show, Generic)

instance NFData RevocationBasepoint

-- | A @payment_basepoint@.
newtype PaymentBasepoint = PaymentBasepoint Point
  deriving (Eq, Ord, Show, Generic)

instance NFData PaymentBasepoint

-- | A @delayed_payment_basepoint@.
newtype DelayedPaymentBasepoint = DelayedPaymentBasepoint Point
  deriving (Eq, Ord, Show, Generic)

instance NFData DelayedPaymentBasepoint

-- | An @htlc_basepoint@.
newtype HtlcBasepoint = HtlcBasepoint Point
  deriving (Eq, Ord, Show, Generic)

instance NFData HtlcBasepoint

-- | The four basepoints a party announces in @open_channel@ or
--   @accept_channel@.
data Basepoints = Basepoints
  { bp_revocation      :: !RevocationBasepoint
  , bp_payment         :: !PaymentBasepoint
  , bp_delayed_payment :: !DelayedPaymentBasepoint
  , bp_htlc            :: !HtlcBasepoint
  } deriving (Eq, Show, Generic)

instance NFData Basepoints

-- | A commitment's @revocationpubkey@.
newtype RevocationPubkey = RevocationPubkey Point
  deriving (Eq, Ord, Show, Generic)

instance NFData RevocationPubkey

-- | A commitment's @local_delayedpubkey@: the owner's key for its
--   delayed outputs.
newtype LocalDelayedPubkey = LocalDelayedPubkey Point
  deriving (Eq, Ord, Show, Generic)

instance NFData LocalDelayedPubkey

-- | A commitment's @local_htlcpubkey@: the owner's HTLC key.
newtype LocalHtlcPubkey = LocalHtlcPubkey Point
  deriving (Eq, Ord, Show, Generic)

instance NFData LocalHtlcPubkey

-- | A commitment's @remote_htlcpubkey@: the other party's HTLC key.
newtype RemoteHtlcPubkey = RemoteHtlcPubkey Point
  deriving (Eq, Ord, Show, Generic)

instance NFData RemoteHtlcPubkey

-- | A commitment's @remotepubkey@: the other party's
--   @payment_basepoint@, which its @to_remote@ output pays.
newtype RemotePubkey = RemotePubkey Point
  deriving (Eq, Ord, Show, Generic)

instance NFData RemotePubkey

-- | A @funding_pubkey@.
newtype FundingPubkey = FundingPubkey Point
  deriving (Eq, Ord, Show, Generic)

instance NFData FundingPubkey

-- | A secp256k1 secret key: 32 bytes encoding an integer in
--   [1, n), for n the group order.
--
--   This is secret material: its 'Show' instance is redacted, and it
--   has no 'Eq' instance.
newtype Seckey = Seckey BS.ByteString

instance Show Seckey where
  showsPrec d _ = showParen (d > 10) $ showString "Seckey <redacted>"

instance NFData Seckey where
  rnf (Seckey bs) = rnf bs

-- | Construct a 'Seckey' from 32 big-endian bytes. Fails unless the
--   bytes encode an integer in [1, n).
--
--   >>> fmap (BS.length . un_seckey) (seckey (BS.replicate 32 0x01))
--   Just 32
--   >>> seckey (BS.replicate 32 0x00)
--   Nothing
--   >>> seckey (BS.replicate 32 0xff)
--   Nothing
seckey :: BS.ByteString -> Maybe Seckey
seckey bs = do
  w <- S.parse_int256 bs
  if   S.ge w
  then Just (Seckey bs)
  else Nothing

-- | The bytes of a 'Seckey'.
un_seckey :: Seckey -> BS.ByteString
un_seckey (Seckey bs) = bs
{-# INLINE un_seckey #-}

-- per-commitment secrets -----------------------------------------------------

-- | The 32-byte seed from which a party generates its per-commitment
--   secrets.
--
--   This is secret material: its 'Show' instance is redacted, and it
--   has no 'Eq' instance.
newtype Seed = Seed BS.ByteString

instance Show Seed where
  showsPrec d _ = showParen (d > 10) $ showString "Seed <redacted>"

instance NFData Seed where
  rnf (Seed bs) = rnf bs

-- | Construct a 'Seed' from exactly 32 bytes.
--
--   >>> fmap (BS.length . un_seed) (seed (BS.replicate 32 0xff))
--   Just 32
--   >>> seed (BS.replicate 31 0xff)
--   Nothing
seed :: BS.ByteString -> Maybe Seed
seed bs
  | BS.length bs == 32 = Just (Seed bs)
  | otherwise          = Nothing
{-# INLINE seed #-}

-- | The bytes of a 'Seed'.
un_seed :: Seed -> BS.ByteString
un_seed (Seed bs) = bs
{-# INLINE un_seed #-}

-- | The 48-bit index /I/ of a per-commitment secret. Secrets are
--   used from index 2^48 - 1 downwards.
newtype SecretIndex = SecretIndex Word64
  deriving (Eq, Ord, Show, Generic)

instance NFData SecretIndex

-- | Construct a 'SecretIndex'. Fails at or above 2^48.
--
--   >>> secret_index 281474976710655
--   Just (SecretIndex 281474976710655)
--   >>> secret_index 281474976710656
--   Nothing
secret_index :: Word64 -> Maybe SecretIndex
secret_index w
  | w <= 0xFFFFFFFFFFFF = Just (SecretIndex w)
  | otherwise           = Nothing
{-# INLINE secret_index #-}

-- | The value of a 'SecretIndex'.
un_secret_index :: SecretIndex -> Word64
un_secret_index (SecretIndex w) = w
{-# INLINE un_secret_index #-}

-- | The index of the per-commitment secret for a commitment number:
--   2^48 - 1 - n.
--
--   >>> fmap commitment_secret_index (commitment_number 0)
--   Just (SecretIndex 281474976710655)
commitment_secret_index :: CommitmentNumber -> SecretIndex
commitment_secret_index (CommitmentNumber n) = SecretIndex (0xFFFFFFFFFFFF - n)
{-# INLINE commitment_secret_index #-}

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

-- | A 48-bit commitment number.
newtype CommitmentNumber = CommitmentNumber Word64
  deriving (Eq, Ord, Show, Generic)

instance NFData CommitmentNumber

-- | Construct a 'CommitmentNumber'. Fails at or above 2^48.
--
--   >>> commitment_number 42
--   Just (CommitmentNumber 42)
--   >>> commitment_number 281474976710656
--   Nothing
commitment_number :: Word64 -> Maybe CommitmentNumber
commitment_number n
  | n <= 0xFFFFFFFFFFFF = Just (CommitmentNumber n)
  | otherwise           = Nothing
{-# INLINE commitment_number #-}

-- | The value of a 'CommitmentNumber'.
un_commitment_number :: CommitmentNumber -> Word64
un_commitment_number (CommitmentNumber n) = n
{-# INLINE un_commitment_number #-}

-- | The next commitment number. Fails past 2^48 - 1.
--
--   >>> commitment_number 0 >>= next_commitment_number
--   Just (CommitmentNumber 1)
--   >>> commitment_number 281474976710655 >>= next_commitment_number
--   Nothing
next_commitment_number :: CommitmentNumber -> Maybe CommitmentNumber
next_commitment_number (CommitmentNumber n)
  | n < 0xFFFFFFFFFFFF = Just (CommitmentNumber (n + 1))
  | otherwise          = Nothing
{-# INLINE next_commitment_number #-}

-- | The CSV delay (@to_self_delay@) on a commitment owner's outputs.
newtype ToSelfDelay = ToSelfDelay Word16
  deriving (Eq, Ord, Show, Generic)

instance NFData ToSelfDelay

-- | An HTLC's absolute CLTV expiry.
newtype CltvExpiry = CltvExpiry Word32
  deriving (Eq, Ord, Show, Generic)

instance NFData CltvExpiry

-- | A @dust_limit_satoshis@ threshold.
newtype DustLimit = DustLimit Satoshi
  deriving (Eq, Ord, Show, Generic)

instance NFData DustLimit

-- | A fee rate, in satoshis per 1000 weight units.
newtype FeeratePerKw = FeeratePerKw Word32
  deriving (Eq, Ord, Show, Generic)

instance NFData FeeratePerKw

-- | A transaction locktime.
newtype Locktime = Locktime Word32
  deriving (Eq, Ord, Show, Generic)

instance NFData Locktime

-- | A transaction input's sequence number.
newtype Sequence = Sequence Word32
  deriving (Eq, Ord, Show, Generic)

instance NFData Sequence

-- | The commitment format a channel uses, as fixed by its
--   @channel_type@.
data CommitmentFormat
  = StaticRemotekey
    -- ^ @option_static_remotekey@ without anchors: @to_remote@ is
    --   P2WPKH and HTLC transactions pay their own fees.
  | Anchors
    -- ^ @option_anchors@: two anchor outputs, a CSV-locked
    --   @to_remote@, and zero-fee HTLC transactions.
  deriving (Eq, Ord, Show, Generic)

instance NFData CommitmentFormat

-- HTLCs ----------------------------------------------------------------------

-- | The direction of an HTLC, from the commitment owner's point of
--   view.
data HTLCDirection
  = HTLCOffered   -- ^ offered by the owner
  | HTLCReceived  -- ^ received by the owner
  deriving (Eq, Ord, Show, Generic)

instance NFData HTLCDirection

-- | An HTLC committed to a commitment transaction.
data HTLC = HTLC
  { htlc_direction    :: !HTLCDirection
  , htlc_amount_msat  :: {-# UNPACK #-} !MilliSatoshi
  , htlc_payment_hash :: !PaymentHash
  , htlc_cltv_expiry  :: {-# UNPACK #-} !CltvExpiry
  } deriving (Eq, Show, Generic)

instance NFData HTLC

-- scripts --------------------------------------------------------------------

-- | A serialized Bitcoin script.
newtype Script = Script BS.ByteString
  deriving (Eq, Ord, Show, Generic)

instance NFData Script

-- dust thresholds ------------------------------------------------------------

-- | Bitcoin Core's P2PKH dust threshold (546 satoshis).
dust_p2pkh :: Satoshi
dust_p2pkh = sat 546

-- | Bitcoin Core's P2SH dust threshold (540 satoshis).
dust_p2sh :: Satoshi
dust_p2sh = sat 540

-- | Bitcoin Core's P2WPKH dust threshold (294 satoshis).
dust_p2wpkh :: Satoshi
dust_p2wpkh = sat 294

-- | Bitcoin Core's P2WSH dust threshold (330 satoshis).
dust_p2wsh :: Satoshi
dust_p2wsh = sat 330

-- | The value of each anchor output (330 satoshis).
anchor_output_value :: Satoshi
anchor_output_value = sat 330