bolt3

Lightning transaction and script formats, per BOLT #3 (docs.ppad.tech/bolt3).
git clone git://git.ppad.tech/bolt3.git
Log | Files | Refs | README | LICENSE

Scripts.hs (16039B)


      1 {-# OPTIONS_HADDOCK hide #-}
      2 {-# LANGUAGE BangPatterns #-}
      3 
      4 -- |
      5 -- Module: Lightning.Protocol.BOLT3.Scripts
      6 -- Copyright: (c) 2025 Jared Tobin
      7 -- License: MIT
      8 -- Maintainer: Jared Tobin <jared@ppad.tech>
      9 --
     10 -- Scripts and witnesses for BOLT #3 transaction outputs.
     11 
     12 module Lightning.Protocol.BOLT3.Scripts (
     13     -- * Funding output
     14     funding_script
     15   , funding_witness
     16 
     17     -- * to_local output
     18   , to_local_script
     19   , to_local_witness_spend
     20   , to_local_witness_revoke
     21 
     22     -- * to_remote output
     23   , to_remote_witness_script
     24   , to_remote_script_pubkey
     25   , to_remote_witness
     26 
     27     -- * Anchor outputs
     28   , anchor_script
     29   , anchor_witness_owner
     30   , anchor_witness_anyone
     31 
     32     -- * Offered HTLC outputs
     33   , offered_htlc_script
     34   , offered_htlc_witness_preimage
     35   , offered_htlc_witness_revoke
     36 
     37     -- * Received HTLC outputs
     38   , received_htlc_script
     39   , received_htlc_witness_timeout
     40   , received_htlc_witness_revoke
     41 
     42     -- * HTLC transaction inputs
     43   , htlc_success_witness
     44   , htlc_timeout_witness
     45   , remote_htlc_sighash
     46 
     47     -- * P2WSH
     48   , to_p2wsh
     49   ) where
     50 
     51 import Bitcoin.Prim.Tx (Witness(..))
     52 import Bitcoin.Prim.Tx.Sighash (SighashType(..))
     53 import qualified Crypto.Hash.RIPEMD160 as RIPEMD160
     54 import qualified Crypto.Hash.SHA256 as SHA256
     55 import Data.Bits ((.&.), shiftR)
     56 import qualified Data.ByteString as BS
     57 import qualified Data.ByteString.Builder as BSB
     58 import qualified Data.ByteString.Lazy as BSL
     59 import Data.Word (Word8, Word16, Word32)
     60 import Lightning.Protocol.BOLT1 (Point, PaymentPreimage)
     61 import qualified Lightning.Protocol.BOLT1 as BOLT1
     62 import Lightning.Protocol.BOLT3.Types
     63 
     64 -- opcodes --------------------------------------------------------------------
     65 
     66 op_0, op_1, op_2, op_16 :: Word8
     67 op_0  = 0x00
     68 op_1  = 0x51
     69 op_2  = 0x52
     70 op_16 = 0x60
     71 
     72 op_if, op_notif, op_else, op_endif, op_drop, op_dup, op_ifdup :: Word8
     73 op_if    = 0x63
     74 op_notif = 0x64
     75 op_else  = 0x67
     76 op_endif = 0x68
     77 op_ifdup = 0x73
     78 op_drop  = 0x75
     79 op_dup   = 0x76
     80 
     81 op_swap, op_size, op_equal, op_equalverify, op_hash160 :: Word8
     82 op_swap        = 0x7c
     83 op_size        = 0x82
     84 op_equal       = 0x87
     85 op_equalverify = 0x88
     86 op_hash160     = 0xa9
     87 
     88 op_checksig, op_checksigverify, op_checkmultisig :: Word8
     89 op_checksig       = 0xac
     90 op_checksigverify = 0xad
     91 op_checkmultisig  = 0xae
     92 
     93 op_checklocktimeverify, op_checksequenceverify :: Word8
     94 op_checklocktimeverify = 0xb1
     95 op_checksequenceverify = 0xb2
     96 
     97 -- helpers --------------------------------------------------------------------
     98 
     99 op :: Word8 -> BSB.Builder
    100 op = BSB.word8
    101 {-# INLINE op #-}
    102 
    103 -- Push data of at most 75 bytes (all pushes here are keys, hashes or
    104 -- short numbers).
    105 push :: BS.ByteString -> BSB.Builder
    106 push bs = BSB.word8 (fromIntegral (BS.length bs)) <> BSB.byteString bs
    107 {-# INLINE push #-}
    108 
    109 push_point :: Point -> BSB.Builder
    110 push_point = push . BOLT1.un_point
    111 {-# INLINE push_point #-}
    112 
    113 -- Push a non-negative number, minimally encoded.
    114 push_num :: Word32 -> BSB.Builder
    115 push_num n
    116   | n == 0    = op op_0
    117   | n <= 16   = op (0x50 + fromIntegral n)
    118   | otherwise = push (BS.pack (signed (le n)))
    119   where
    120     -- little-endian magnitude
    121     le :: Word32 -> [Word8]
    122     le !x
    123       | x == 0    = []
    124       | otherwise = fromIntegral (x .&. 0xff) : le (x `shiftR` 8)
    125 
    126     -- append a sign byte if the top bit is set
    127     signed bs = case reverse bs of
    128       (h : _) | h .&. 0x80 /= 0 -> bs <> [0x00]
    129       _ -> bs
    130 
    131 push_delay :: Word16 -> BSB.Builder
    132 push_delay = push_num . fromIntegral
    133 {-# INLINE push_delay #-}
    134 
    135 script :: BSB.Builder -> Script
    136 script = Script . BSL.toStrict . BSB.toLazyByteString
    137 {-# INLINE script #-}
    138 
    139 hash160 :: BS.ByteString -> BS.ByteString
    140 hash160 = RIPEMD160.hash . SHA256.hash
    141 {-# INLINE hash160 #-}
    142 
    143 -- Append a witness script to a witness stack.
    144 with_script :: [BS.ByteString] -> Script -> Witness
    145 with_script items (Script s) = Witness (items <> [s])
    146 {-# INLINE with_script #-}
    147 
    148 -- P2WSH ----------------------------------------------------------------------
    149 
    150 -- | The P2WSH scriptPubKey for a witness script:
    151 --   @0 \<SHA256(script)\>@.
    152 to_p2wsh :: Script -> Script
    153 to_p2wsh (Script s) = script (op op_0 <> push (SHA256.hash s))
    154 
    155 -- funding output -------------------------------------------------------------
    156 
    157 -- | The funding output's witness script:
    158 --
    159 --   @2 \<pubkey1\> \<pubkey2\> 2 OP_CHECKMULTISIG@
    160 --
    161 --   where @pubkey1@ is the lexicographically lesser of the two funding
    162 --   pubkeys. The argument order doesn't matter.
    163 funding_script :: FundingPubkey -> FundingPubkey -> Script
    164 funding_script (FundingPubkey a) (FundingPubkey b) =
    165   let (lo, hi) = if a <= b then (a, b) else (b, a)
    166   in  script $
    167            op op_2 <> push_point lo <> push_point hi
    168         <> op op_2 <> op op_checkmultisig
    169 
    170 -- | The witness spending the funding output, given each party's
    171 --   funding pubkey and signature (DER-encoded, with sighash byte):
    172 --
    173 --   @0 \<signature_for_pubkey1\> \<signature_for_pubkey2\> \<script\>@
    174 --
    175 --   The signatures are ordered to match the pubkeys in
    176 --   'funding_script', so the argument order doesn't matter.
    177 funding_witness
    178   :: FundingPubkey -> BS.ByteString
    179   -> FundingPubkey -> BS.ByteString
    180   -> Witness
    181 funding_witness pa@(FundingPubkey a) sa pb@(FundingPubkey b) sb =
    182   let (s1, s2) = if a <= b then (sa, sb) else (sb, sa)
    183   in  with_script [BS.empty, s1, s2] (funding_script pa pb)
    184 
    185 -- to_local output ------------------------------------------------------------
    186 
    187 -- | The @to_local@ output's witness script:
    188 --
    189 --   @
    190 --   OP_IF
    191 --       \<revocationpubkey\>
    192 --   OP_ELSE
    193 --       \<to_self_delay\> OP_CHECKSEQUENCEVERIFY OP_DROP
    194 --       \<local_delayedpubkey\>
    195 --   OP_ENDIF
    196 --   OP_CHECKSIG
    197 --   @
    198 --
    199 --   HTLC-success and HTLC-timeout transaction outputs use the same
    200 --   script.
    201 to_local_script
    202   :: RevocationPubkey
    203   -> ToSelfDelay
    204   -> LocalDelayedPubkey
    205   -> Script
    206 to_local_script
    207   (RevocationPubkey rev)
    208   (ToSelfDelay delay)
    209   (LocalDelayedPubkey delayed) = script $
    210        op op_if
    211     <> push_point rev
    212     <> op op_else
    213     <> push_delay delay
    214     <> op op_checksequenceverify
    215     <> op op_drop
    216     <> push_point delayed
    217     <> op op_endif
    218     <> op op_checksig
    219 
    220 -- | The witness for the owner's delayed spend of a @to_local@ (or
    221 --   HTLC transaction) output, given its witness script:
    222 --
    223 --   @\<local_delayedsig\> \<\> \<script\>@
    224 --
    225 --   The spending input's @nSequence@ must be @to_self_delay@.
    226 to_local_witness_spend :: BS.ByteString -> Script -> Witness
    227 to_local_witness_spend sig = with_script [sig, BS.empty]
    228 
    229 -- | The witness for the revocation spend of a @to_local@ (or HTLC
    230 --   transaction) output, given its witness script:
    231 --
    232 --   @\<revocation_sig\> 1 \<script\>@
    233 to_local_witness_revoke :: BS.ByteString -> Script -> Witness
    234 to_local_witness_revoke sig = with_script [sig, BS.singleton 0x01]
    235 
    236 -- to_remote output -----------------------------------------------------------
    237 
    238 -- | The @to_remote@ output's witness script under 'Anchors':
    239 --
    240 --   @\<remotepubkey\> OP_CHECKSIGVERIFY 1 OP_CHECKSEQUENCEVERIFY@
    241 --
    242 --   (Under 'StaticRemotekey' the output is P2WPKH, with no witness
    243 --   script; see 'to_remote_script_pubkey'.)
    244 to_remote_witness_script :: RemotePubkey -> Script
    245 to_remote_witness_script (RemotePubkey pk) = script $
    246      push_point pk
    247   <> op op_checksigverify
    248   <> op op_1
    249   <> op op_checksequenceverify
    250 
    251 -- | The @to_remote@ output's scriptPubKey: P2WPKH to @remotepubkey@
    252 --   under 'StaticRemotekey', and P2WSH of 'to_remote_witness_script'
    253 --   under 'Anchors'.
    254 to_remote_script_pubkey :: RemotePubkey -> CommitmentFormat -> Script
    255 to_remote_script_pubkey pk@(RemotePubkey p) fmt = case fmt of
    256   StaticRemotekey -> script (op op_0 <> push (hash160 (BOLT1.un_point p)))
    257   Anchors         -> to_p2wsh (to_remote_witness_script pk)
    258 
    259 -- | The witness spending a @to_remote@ output: @\<remote_sig\>
    260 --   \<remotepubkey\>@ (P2WPKH) under 'StaticRemotekey', and
    261 --   @\<remote_sig\> \<script\>@ under 'Anchors', where the spending
    262 --   input's @nSequence@ must be 1.
    263 to_remote_witness
    264   :: BS.ByteString -> RemotePubkey -> CommitmentFormat -> Witness
    265 to_remote_witness sig pk@(RemotePubkey p) fmt = case fmt of
    266   StaticRemotekey -> Witness [sig, BOLT1.un_point p]
    267   Anchors         -> with_script [sig] (to_remote_witness_script pk)
    268 
    269 -- anchor outputs -------------------------------------------------------------
    270 
    271 -- | An anchor output's witness script, for the funding pubkey of the
    272 --   party it belongs to:
    273 --
    274 --   @
    275 --   \<funding_pubkey\> OP_CHECKSIG OP_IFDUP
    276 --   OP_NOTIF
    277 --       OP_16 OP_CHECKSEQUENCEVERIFY
    278 --   OP_ENDIF
    279 --   @
    280 anchor_script :: FundingPubkey -> Script
    281 anchor_script (FundingPubkey pk) = script $
    282      push_point pk
    283   <> op op_checksig
    284   <> op op_ifdup
    285   <> op op_notif
    286   <> op op_16
    287   <> op op_checksequenceverify
    288   <> op op_endif
    289 
    290 -- | The witness for an anchor's owner to spend it:
    291 --   @\<sig\> \<script\>@.
    292 anchor_witness_owner :: BS.ByteString -> FundingPubkey -> Witness
    293 anchor_witness_owner sig pk = with_script [sig] (anchor_script pk)
    294 
    295 -- | The witness for anyone to sweep an anchor, 16 blocks after the
    296 --   commitment transaction confirms: @\<\> \<script\>@.
    297 anchor_witness_anyone :: FundingPubkey -> Witness
    298 anchor_witness_anyone pk = with_script [BS.empty] (anchor_script pk)
    299 
    300 -- offered HTLC outputs -------------------------------------------------------
    301 
    302 -- csv suffix for anchors HTLC scripts
    303 htlc_csv :: CommitmentFormat -> BSB.Builder
    304 htlc_csv fmt = case fmt of
    305   StaticRemotekey -> mempty
    306   Anchors         -> op op_1 <> op op_checksequenceverify <> op op_drop
    307 {-# INLINE htlc_csv #-}
    308 
    309 -- | An offered HTLC output's witness script:
    310 --
    311 --   @
    312 --   OP_DUP OP_HASH160 \<RIPEMD160(SHA256(revocationpubkey))\> OP_EQUAL
    313 --   OP_IF
    314 --       OP_CHECKSIG
    315 --   OP_ELSE
    316 --       \<remote_htlcpubkey\> OP_SWAP OP_SIZE 32 OP_EQUAL
    317 --       OP_NOTIF
    318 --           OP_DROP 2 OP_SWAP \<local_htlcpubkey\> 2 OP_CHECKMULTISIG
    319 --       OP_ELSE
    320 --           OP_HASH160 \<RIPEMD160(payment_hash)\> OP_EQUALVERIFY
    321 --           OP_CHECKSIG
    322 --       OP_ENDIF
    323 --   OP_ENDIF
    324 --   @
    325 --
    326 --   Under 'Anchors', @1 OP_CHECKSEQUENCEVERIFY OP_DROP@ precedes the
    327 --   final @OP_ENDIF@.
    328 offered_htlc_script
    329   :: RevocationPubkey
    330   -> RemoteHtlcPubkey
    331   -> LocalHtlcPubkey
    332   -> BOLT1.PaymentHash
    333   -> CommitmentFormat
    334   -> Script
    335 offered_htlc_script
    336   (RevocationPubkey rev)
    337   (RemoteHtlcPubkey remote)
    338   (LocalHtlcPubkey local)
    339   ph
    340   fmt = script $
    341        op op_dup <> op op_hash160
    342     <> push (hash160 (BOLT1.un_point rev))
    343     <> op op_equal
    344     <> op op_if
    345     <>   op op_checksig
    346     <> op op_else
    347     <>   push_point remote <> op op_swap <> op op_size
    348     <>   push (BS.singleton 32) <> op op_equal
    349     <>   op op_notif
    350     <>     op op_drop <> op op_2 <> op op_swap
    351     <>     push_point local <> op op_2 <> op op_checkmultisig
    352     <>   op op_else
    353     <>     op op_hash160
    354     <>     push (RIPEMD160.hash (BOLT1.un_payment_hash ph))
    355     <>     op op_equalverify <> op op_checksig
    356     <>   op op_endif
    357     <>   htlc_csv fmt
    358     <> op op_endif
    359 
    360 -- | The witness for the remote party to claim an offered HTLC output
    361 --   with the payment preimage, given its witness script:
    362 --
    363 --   @\<remotehtlcsig\> \<payment_preimage\> \<script\>@
    364 --
    365 --   Under 'Anchors' the spending input's @nSequence@ must be 1.
    366 offered_htlc_witness_preimage
    367   :: BS.ByteString -> PaymentPreimage -> Script -> Witness
    368 offered_htlc_witness_preimage sig pre =
    369   with_script [sig, BOLT1.un_payment_preimage pre]
    370 
    371 -- | The witness for the revocation spend of an offered HTLC output,
    372 --   given its witness script:
    373 --
    374 --   @\<revocation_sig\> \<revocationpubkey\> \<script\>@
    375 offered_htlc_witness_revoke
    376   :: BS.ByteString -> RevocationPubkey -> Script -> Witness
    377 offered_htlc_witness_revoke sig (RevocationPubkey rev) =
    378   with_script [sig, BOLT1.un_point rev]
    379 
    380 -- received HTLC outputs ------------------------------------------------------
    381 
    382 -- | A received HTLC output's witness script:
    383 --
    384 --   @
    385 --   OP_DUP OP_HASH160 \<RIPEMD160(SHA256(revocationpubkey))\> OP_EQUAL
    386 --   OP_IF
    387 --       OP_CHECKSIG
    388 --   OP_ELSE
    389 --       \<remote_htlcpubkey\> OP_SWAP OP_SIZE 32 OP_EQUAL
    390 --       OP_IF
    391 --           OP_HASH160 \<RIPEMD160(payment_hash)\> OP_EQUALVERIFY
    392 --           2 OP_SWAP \<local_htlcpubkey\> 2 OP_CHECKMULTISIG
    393 --       OP_ELSE
    394 --           OP_DROP \<cltv_expiry\> OP_CHECKLOCKTIMEVERIFY OP_DROP
    395 --           OP_CHECKSIG
    396 --       OP_ENDIF
    397 --   OP_ENDIF
    398 --   @
    399 --
    400 --   Under 'Anchors', @1 OP_CHECKSEQUENCEVERIFY OP_DROP@ precedes the
    401 --   final @OP_ENDIF@.
    402 received_htlc_script
    403   :: RevocationPubkey
    404   -> RemoteHtlcPubkey
    405   -> LocalHtlcPubkey
    406   -> BOLT1.PaymentHash
    407   -> CltvExpiry
    408   -> CommitmentFormat
    409   -> Script
    410 received_htlc_script
    411   (RevocationPubkey rev)
    412   (RemoteHtlcPubkey remote)
    413   (LocalHtlcPubkey local)
    414   ph
    415   (CltvExpiry expiry)
    416   fmt = script $
    417        op op_dup <> op op_hash160
    418     <> push (hash160 (BOLT1.un_point rev))
    419     <> op op_equal
    420     <> op op_if
    421     <>   op op_checksig
    422     <> op op_else
    423     <>   push_point remote <> op op_swap <> op op_size
    424     <>   push (BS.singleton 32) <> op op_equal
    425     <>   op op_if
    426     <>     op op_hash160
    427     <>     push (RIPEMD160.hash (BOLT1.un_payment_hash ph))
    428     <>     op op_equalverify
    429     <>     op op_2 <> op op_swap <> push_point local
    430     <>     op op_2 <> op op_checkmultisig
    431     <>   op op_else
    432     <>     op op_drop <> push_num expiry
    433     <>     op op_checklocktimeverify <> op op_drop
    434     <>     op op_checksig
    435     <>   op op_endif
    436     <>   htlc_csv fmt
    437     <> op op_endif
    438 
    439 -- | The witness for the remote party to time out a received HTLC
    440 --   output, given its witness script:
    441 --
    442 --   @\<remotehtlcsig\> \<\> \<script\>@
    443 --
    444 --   Under 'Anchors' the spending input's @nSequence@ must be 1.
    445 received_htlc_witness_timeout :: BS.ByteString -> Script -> Witness
    446 received_htlc_witness_timeout sig = with_script [sig, BS.empty]
    447 
    448 -- | The witness for the revocation spend of a received HTLC output,
    449 --   given its witness script:
    450 --
    451 --   @\<revocation_sig\> \<revocationpubkey\> \<script\>@
    452 received_htlc_witness_revoke
    453   :: BS.ByteString -> RevocationPubkey -> Script -> Witness
    454 received_htlc_witness_revoke sig (RevocationPubkey rev) =
    455   with_script [sig, BOLT1.un_point rev]
    456 
    457 -- HTLC transaction inputs ----------------------------------------------------
    458 
    459 -- | The witness for an HTLC-success transaction's input, given both
    460 --   HTLC signatures, the payment preimage and the received HTLC
    461 --   output's witness script:
    462 --
    463 --   @0 \<remotehtlcsig\> \<localhtlcsig\> \<payment_preimage\> \<script\>@
    464 --
    465 --   See 'remote_htlc_sighash' for the signatures' sighash types.
    466 htlc_success_witness
    467   :: BS.ByteString    -- ^ remotehtlcsig
    468   -> BS.ByteString    -- ^ localhtlcsig
    469   -> PaymentPreimage
    470   -> Script           -- ^ the received HTLC output's witness script
    471   -> Witness
    472 htlc_success_witness remote local pre =
    473   with_script [BS.empty, remote, local, BOLT1.un_payment_preimage pre]
    474 
    475 -- | The witness for an HTLC-timeout transaction's input, given both
    476 --   HTLC signatures and the offered HTLC output's witness script:
    477 --
    478 --   @0 \<remotehtlcsig\> \<localhtlcsig\> \<\> \<script\>@
    479 --
    480 --   See 'remote_htlc_sighash' for the signatures' sighash types.
    481 htlc_timeout_witness
    482   :: BS.ByteString    -- ^ remotehtlcsig
    483   -> BS.ByteString    -- ^ localhtlcsig
    484   -> Script           -- ^ the offered HTLC output's witness script
    485   -> Witness
    486 htlc_timeout_witness remote local =
    487   with_script [BS.empty, remote, local, BS.empty]
    488 
    489 -- | The sighash type of @remotehtlcsig@: the signature on an
    490 --   HTLC-success or HTLC-timeout transaction that the commitment's
    491 --   non-owner sends in @commitment_signed@.
    492 --
    493 --   Under 'Anchors' it is @SIGHASH_SINGLE|SIGHASH_ANYONECANPAY@, so
    494 --   that the owner can add inputs and outputs to pay the (otherwise
    495 --   zero) fee; under 'StaticRemotekey' it is @SIGHASH_ALL@. The
    496 --   owner's own signature, @localhtlcsig@, always uses @SIGHASH_ALL@.
    497 --
    498 --   >>> remote_htlc_sighash Anchors
    499 --   SIGHASH_SINGLE_ANYONECANPAY
    500 remote_htlc_sighash :: CommitmentFormat -> SighashType
    501 remote_htlc_sighash fmt = case fmt of
    502   StaticRemotekey -> SIGHASH_ALL
    503   Anchors         -> SIGHASH_SINGLE_ANYONECANPAY