Detect.hs (9179B)
1 {-# OPTIONS_HADDOCK hide #-} 2 {-# LANGUAGE BangPatterns #-} 3 4 -- | 5 -- Module: Lightning.Protocol.BOLT5.Detect 6 -- Copyright: (c) 2025 Jared Tobin 7 -- License: MIT 8 -- Maintainer: Jared Tobin <jared@ppad.tech> 9 -- 10 -- Close identification, output classification, and preimage 11 -- extraction for BOLT #5 on-chain transaction handling. 12 13 module Lightning.Protocol.BOLT5.Detect ( 14 -- * Close identification 15 identify_close 16 17 -- * Output classification 18 , classify_local_commit_outputs 19 , classify_remote_commit_outputs 20 , classify_revoked_commit_outputs 21 22 -- * Preimage extraction 23 , extract_preimage_offered 24 , extract_preimage_htlc_success 25 26 -- * Timeout check 27 , htlc_timed_out 28 ) where 29 30 import Bitcoin.Prim.Tx (OutPoint(..), TxId, Witness(..)) 31 import qualified Bitcoin.Prim.Tx as BT 32 import qualified Crypto.Hash.SHA256 as SHA256 33 import qualified Data.ByteString as BS 34 import qualified Data.List.NonEmpty as NE 35 import Data.Word (Word32) 36 import Lightning.Protocol.BOLT1 (PaymentHash, PaymentPreimage) 37 import qualified Lightning.Protocol.BOLT1 as BOLT1 38 import Lightning.Protocol.BOLT3 39 import Lightning.Protocol.BOLT5.Types 40 41 -- close identification ----------------------------------------------- 42 43 -- | Identify the type of channel close from a transaction that 44 -- spends the funding output. 45 -- 46 -- Parses the on-chain transaction and compares its txid with the 47 -- txids of the known local and remote commitment transactions. 48 -- Comparing txids rather than bytes means the signed (segwit) 49 -- serialization found on chain matches the unsigned commitment. 50 -- 51 -- Returns 'Nothing' if the bytes don't parse as a transaction or 52 -- the transaction matches neither commitment. Mutual close and 53 -- revoked commitment detection are left to the caller (e.g. 54 -- comparing with the closing transaction, or checking a secret 55 -- store for older commitment numbers). 56 identify_close 57 :: CommitmentTx 58 -- ^ Our local commitment tx. 59 -> CommitmentTx 60 -- ^ The remote commitment tx (current). 61 -> BS.ByteString 62 -- ^ Raw serialized transaction found on chain. 63 -> Maybe CloseType 64 identify_close !local !remote !bytes = do 65 !tx <- BT.from_bytes bytes 66 let !txid = BT.txid tx 67 if commitment_txid local == txid 68 then Just LocalCommitClose 69 else if commitment_txid remote == txid 70 then Just RemoteCommitClose 71 else Nothing 72 73 -- output classification ---------------------------------------------- 74 75 -- | Classify the outputs of our local commitment transaction. 76 -- 77 -- Per BOLT #5: when our local commitment is on chain, we must 78 -- resolve each output. to_local requires a CSV-delayed spend, 79 -- to_remote is resolved by the commitment itself, HTLC outputs 80 -- need second-stage transactions, and our anchor can be spent 81 -- immediately. 82 -- 83 -- Each HTLC output's HTLC is read from its bolt3 'OutputHTLC', so 84 -- the commitment must be the one bolt3 built for this state. 85 classify_local_commit_outputs 86 :: CommitmentTx 87 -- ^ Our local commitment transaction. 88 -> CommitmentKeys 89 -- ^ Derived keys for this commitment. 90 -> ToSelfDelay 91 -- ^ Remote's to_self_delay (CSV delay for our outputs). 92 -> CommitmentFormat 93 -> [UnresolvedOutput] 94 classify_local_commit_outputs !tx !keys !delay !fmt = 95 classify_outputs resolve tx 96 where 97 resolve t = case t of 98 OutputToLocal -> 99 SpendToLocal delay (ck_revocation_pubkey keys) 100 (ck_local_delayed keys) 101 OutputToRemote -> Resolved 102 OutputLocalAnchor -> AnchorSpend (ck_local_funding keys) 103 OutputRemoteAnchor -> Resolved 104 OutputHTLC h -> case htlc_direction h of 105 HTLCOffered -> SpendHTLCTimeout h keys fmt 106 HTLCReceived -> SpendHTLCSuccess h keys fmt 107 108 -- | Classify the outputs of the remote commitment transaction. 109 -- 110 -- Per BOLT #5: when the remote commitment is on chain, there are 111 -- no CSV delays on our outputs. We can spend HTLCs we offered 112 -- directly after timeout, and HTLCs we received directly with the 113 -- preimage. 114 -- 115 -- The commitment, its keys and its HTLCs are from the remote's 116 -- point of view (as bolt3 builds the remote's commitment): an 117 -- HTLC the remote offered is one we received. 118 classify_remote_commit_outputs 119 :: CommitmentTx 120 -- ^ The remote commitment transaction. 121 -> CommitmentKeys 122 -- ^ Derived keys for this commitment (from the remote's point 123 -- of view, so local and remote are swapped). 124 -> [UnresolvedOutput] 125 classify_remote_commit_outputs !tx !keys = 126 classify_outputs resolve tx 127 where 128 resolve t = case t of 129 OutputToLocal -> Resolved -- the remote's to_local 130 OutputToRemote -> Resolved -- ours; resolved by the commitment 131 OutputLocalAnchor -> Resolved -- the remote's anchor 132 OutputRemoteAnchor -> AnchorSpend (ck_remote_funding keys) 133 OutputHTLC h -> case htlc_direction h of 134 HTLCOffered -> SpendHTLCPreimageDirect h -- we received it 135 HTLCReceived -> SpendHTLCTimeoutDirect h -- we offered it 136 137 -- | Classify the outputs of a revoked commitment transaction. 138 -- 139 -- Per BOLT #5: when a revoked commitment is on chain, we can 140 -- claim to_local and every HTLC output with the revocation key. 141 -- to_remote (ours) and the anchors are marked 'Resolved'. 142 classify_revoked_commit_outputs 143 :: CommitmentTx 144 -- ^ The revoked commitment transaction. 145 -> RevocationPubkey 146 -- ^ Revocation pubkey (derived from the revealed secret). 147 -> [UnresolvedOutput] 148 classify_revoked_commit_outputs !tx !revpk = 149 classify_outputs resolve tx 150 where 151 resolve t = case t of 152 OutputToLocal -> Revoke revpk 153 OutputToRemote -> Resolved 154 OutputLocalAnchor -> Resolved 155 OutputRemoteAnchor -> Resolved 156 OutputHTLC h -> RevokeHTLC revpk $ case htlc_direction h of 157 HTLCOffered -> HTLCOfferedOutput (htlc_cltv_expiry h) 158 HTLCReceived -> HTLCReceivedOutput (htlc_cltv_expiry h) 159 160 -- | Pair each commitment output with its outpoint, value and 161 -- resolution. 162 classify_outputs 163 :: (OutputType -> OutputResolution) 164 -> CommitmentTx 165 -> [UnresolvedOutput] 166 classify_outputs resolve tx = 167 zipWith classify [0..] (NE.toList (ctx_outputs tx)) 168 where 169 !txid = commitment_txid tx 170 classify :: Word32 -> CommitmentOutput -> UnresolvedOutput 171 classify !idx !out = UnresolvedOutput 172 { uo_outpoint = OutPoint txid idx 173 , uo_value = co_value out 174 , uo_type = resolve (co_type out) 175 } 176 177 -- preimage extraction ------------------------------------------------ 178 179 -- | Extract a payment preimage from the witness of an input 180 -- spending an offered HTLC output. 181 -- 182 -- When the remote party claims an offered HTLC on our local 183 -- commitment, the witness (see bolt3's 184 -- 'offered_htlc_witness_preimage') is: 185 -- 186 -- @\<remotehtlcsig\> \<payment_preimage\> \<witness_script\>@ 187 -- 188 -- Returns the preimage only if it hashes to the given payment 189 -- hash. Any other spend of the output, e.g. by revocation, yields 190 -- 'Nothing'. 191 extract_preimage_offered 192 :: PaymentHash 193 -- ^ Payment hash of the HTLC. 194 -> Witness 195 -- ^ Witness of the input spending the HTLC output. 196 -> Maybe PaymentPreimage 197 extract_preimage_offered !ph (Witness items) = case items of 198 [_sig, preimage, _script] -> check_preimage ph preimage 199 _ -> Nothing 200 201 -- | Extract a payment preimage from the witness of an HTLC-success 202 -- transaction's input. 203 -- 204 -- When the remote party claims an HTLC it received on its 205 -- commitment with an HTLC-success transaction, the witness (see 206 -- bolt3's 'htlc_success_witness') is: 207 -- 208 -- @0 \<remotehtlcsig\> \<localhtlcsig\> \<payment_preimage\> 209 -- \<witness_script\>@ 210 -- 211 -- Returns the preimage only if it hashes to the given payment 212 -- hash. 213 extract_preimage_htlc_success 214 :: PaymentHash 215 -- ^ Payment hash of the HTLC. 216 -> Witness 217 -- ^ Witness of the HTLC-success transaction input. 218 -> Maybe PaymentPreimage 219 extract_preimage_htlc_success !ph (Witness items) = case items of 220 [_zero, _remote_sig, _local_sig, preimage, _script] -> 221 check_preimage ph preimage 222 _ -> Nothing 223 224 -- | Accept a candidate preimage only if it hashes to the payment 225 -- hash. 226 check_preimage :: PaymentHash -> BS.ByteString -> Maybe PaymentPreimage 227 check_preimage !ph !bs 228 | SHA256.hash bs == BOLT1.un_payment_hash ph = BOLT1.payment_preimage bs 229 | otherwise = Nothing 230 231 -- timeout check ------------------------------------------------------ 232 233 -- | Whether an HTLC has timed out at the given block height: whether 234 -- the height is at least the HTLC's CLTV expiry. 235 -- 236 -- >>> let Just amt = BOLT1.milli_satoshi 1000000 237 -- >>> let Just ph = BOLT1.payment_hash (BS.replicate 32 0xaa) 238 -- >>> let htlc = HTLC HTLCOffered amt ph (CltvExpiry 800000) 239 -- >>> htlc_timed_out 799999 htlc 240 -- False 241 -- >>> htlc_timed_out 800000 htlc 242 -- True 243 htlc_timed_out :: Word32 -> HTLC -> Bool 244 htlc_timed_out !height !htlc = 245 let CltvExpiry expiry = htlc_cltv_expiry htlc 246 in height >= expiry 247 {-# INLINE htlc_timed_out #-} 248 249 -- internal helpers --------------------------------------------------- 250 251 -- | The txid of a commitment transaction. 252 commitment_txid :: CommitmentTx -> TxId 253 commitment_txid = BT.txid . commitment_to_tx 254 {-# INLINE commitment_txid #-}