packages feed

ppad-bolt5-0.0.1: lib/Lightning/Protocol/BOLT5/Types.hs

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

-- |
-- Module: Lightning.Protocol.BOLT5.Types
-- Copyright: (c) 2025 Jared Tobin
-- License: MIT
-- Maintainer: Jared Tobin <jared@ppad.tech>
--
-- Types for BOLT #5 on-chain transaction handling.

module Lightning.Protocol.BOLT5.Types (
    -- * Close identification
    CloseType(..)

    -- * Output classification
  , UnresolvedOutput(..)
  , OutputResolution(..)

    -- * Spending transactions
  , SpendingTx(..)

    -- * Penalty batching
  , PenaltyContext(..)

    -- * Weight constants (Appendix A)
  , to_local_penalty_witness_weight
  , offered_htlc_penalty_witness_weight
  , accepted_htlc_penalty_witness_weight
  , to_local_penalty_input_weight
  , offered_htlc_penalty_input_weight
  , accepted_htlc_penalty_input_weight
  , to_remote_input_weight
  , penalty_tx_base_weight
  , max_standard_weight

    -- * Fee calculation
  , spending_fee
  ) where

import Bitcoin.Prim.Tx (Tx(..))
import Bitcoin.Prim.Tx.Sighash (SighashType(..))
import Data.List.NonEmpty (NonEmpty)
import Data.Word (Word64)
import GHC.Generics (Generic)
import Lightning.Protocol.BOLT3.Types
import Lightning.Protocol.BOLT3.Tx (
    CommitmentKeys(..)
  , OutputType(..)
  )

-- close identification -----------------------------------------------

-- | What kind of close was detected on chain.
data CloseType
  = MutualClose
    -- ^ Cooperative closure agreed by both parties.
  | LocalCommitClose
    -- ^ Our commitment transaction was broadcast.
  | RemoteCommitClose
    -- ^ The remote party's commitment transaction was broadcast.
  | RevokedCommitClose
    -- ^ A revoked (outdated) commitment transaction was broadcast.
  deriving (Eq, Show, Generic)

-- output classification ----------------------------------------------

-- | An unresolved commitment transaction output.
data UnresolvedOutput = UnresolvedOutput
  { uo_outpoint :: !OutPoint
  , uo_value    :: {-# UNPACK #-} !Satoshi
  , uo_type     :: !OutputResolution
  } deriving (Eq, Show, Generic)

-- | How to resolve an output, per BOLT #5 rules.
data OutputResolution
  = Resolved
    -- ^ Already resolved (e.g. to_remote on local commit).
  | SpendToLocal
      !ToSelfDelay !RevocationPubkey !LocalDelayedPubkey
    -- ^ Spend to_local after CSV delay.
  | SpendHTLCTimeout
      !HTLC !CommitmentKeys !ChannelFeatures
    -- ^ Spend via HTLC-timeout second-stage tx (local commit,
    --   local offer).
  | SpendHTLCSuccess
      !HTLC !CommitmentKeys !ChannelFeatures
    -- ^ Spend via HTLC-success second-stage tx (local commit,
    --   remote offer).
  | SpendHTLCTimeoutDirect !HTLC
    -- ^ Spend HTLC directly after timeout (remote commit,
    --   local offer).
  | SpendHTLCPreimageDirect !HTLC
    -- ^ Spend HTLC directly with preimage (remote commit,
    --   remote offer).
  | Revoke !RevocationPubkey
    -- ^ Spend revoked to_local with revocation key.
  | RevokeHTLC !RevocationPubkey !OutputType
    -- ^ Spend revoked HTLC output with revocation key.
  | AnchorSpend !FundingPubkey
    -- ^ Spend anchor output.
  deriving (Eq, Show, Generic)

-- spending transactions ----------------------------------------------

-- | Unsigned spending transaction, ready for caller to sign.
--
-- The caller uses bolt3 witness constructors to assemble the
-- final witness after signing.
data SpendingTx = SpendingTx
  { stx_tx           :: !Tx
    -- ^ The unsigned transaction.
  , stx_input_script :: !Script
    -- ^ Witness script for the input being spent.
  , stx_input_value  :: {-# UNPACK #-} !Satoshi
    -- ^ Value of the input being spent (for sighash).
  , stx_sighash_type :: !SighashType
    -- ^ Sighash type to use when signing.
  } deriving (Eq, Show, Generic)

-- penalty batching ---------------------------------------------------

-- | Context for constructing batched penalty transactions.
data PenaltyContext = PenaltyContext
  { pc_outputs        :: !(NonEmpty UnresolvedOutput)
    -- ^ Revoked outputs to sweep (must be non-empty).
  , pc_revocation_key :: !RevocationPubkey
    -- ^ Revocation pubkey for all outputs.
  , pc_destination    :: !Script
    -- ^ Destination scriptPubKey.
  , pc_feerate        :: !FeeratePerKw
    -- ^ Fee rate for the penalty transaction.
  } deriving (Eq, Show, Generic)

-- weight constants (BOLT #5 Appendix A) ------------------------------

-- | Expected weight of the to_local penalty transaction witness
--   (160 bytes).
to_local_penalty_witness_weight :: Word64
to_local_penalty_witness_weight = 160

-- | Expected weight of the offered_htlc penalty transaction
--   witness (243 bytes).
offered_htlc_penalty_witness_weight :: Word64
offered_htlc_penalty_witness_weight = 243

-- | Expected weight of the accepted_htlc penalty transaction
--   witness (249 bytes).
accepted_htlc_penalty_witness_weight :: Word64
accepted_htlc_penalty_witness_weight = 249

-- | Weight of a to_local penalty input (164 + 160 = 324 bytes).
to_local_penalty_input_weight :: Word64
to_local_penalty_input_weight = 324

-- | Weight of an offered_htlc penalty input
--   (164 + 243 = 407 bytes).
offered_htlc_penalty_input_weight :: Word64
offered_htlc_penalty_input_weight = 407

-- | Weight of an accepted_htlc penalty input
--   (164 + 249 = 413 bytes).
accepted_htlc_penalty_input_weight :: Word64
accepted_htlc_penalty_input_weight = 413

-- | Weight of a to_remote P2WPKH input
--   (108 + 164 = 272 bytes).
to_remote_input_weight :: Word64
to_remote_input_weight = 272

-- | Base weight of a penalty transaction (4*53 + 2 = 214 bytes).
--
-- Non-witness: version(4) + input_count(1) + output_count(1) +
-- value(8) + script_len(1) + p2wsh_script(34) + locktime(4) = 53
-- Witness header: 2 bytes.
penalty_tx_base_weight :: Word64
penalty_tx_base_weight = 214

-- | Maximum standard transaction weight (400,000 bytes).
max_standard_weight :: Word64
max_standard_weight = 400000

-- fee calculation ----------------------------------------------------

-- | Calculate the fee for a spending transaction given its weight.
--
-- @fee = feerate_per_kw * weight / 1000@
spending_fee :: FeeratePerKw -> Word64 -> Satoshi
spending_fee (FeeratePerKw !rate) !weight =
  Satoshi ((fromIntegral rate * weight) `div` 1000)
{-# INLINE spending_fee #-}