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

Keys.hs (19660B)


      1 {-# OPTIONS_HADDOCK hide #-}
      2 {-# LANGUAGE BangPatterns #-}
      3 {-# LANGUAGE DeriveGeneric #-}
      4 
      5 -- |
      6 -- Module: Lightning.Protocol.BOLT3.Keys
      7 -- Copyright: (c) 2025 Jared Tobin
      8 -- License: MIT
      9 -- Maintainer: Jared Tobin <jared@ppad.tech>
     10 --
     11 -- Key derivation, per-commitment secrets and commitment number
     12 -- obscuring, per BOLT #3.
     13 
     14 module Lightning.Protocol.BOLT3.Keys (
     15     -- * Per-commitment points
     16     derive_per_commitment_point
     17   , derive_per_commitment_point'
     18 
     19     -- * Public keys
     20   , derive_pubkey
     21   , derive_revocationpubkey
     22   , CommitmentKeys(..)
     23   , derive_commitment_keys
     24   , derive_commitment_keys'
     25 
     26     -- * Private keys
     27   , derive_privkey
     28   , derive_revocationprivkey
     29 
     30     -- * Per-commitment secrets
     31   , generate_from_seed
     32   , SecretStore
     33   , empty_secret_store
     34   , insert_secret
     35   , derive_old_secret
     36   , secret_store
     37   , un_secret_store
     38 
     39     -- * Commitment number obscuring
     40   , obscured_commitment_number
     41   ) where
     42 
     43 import Control.DeepSeq (NFData(..))
     44 import qualified Crypto.Curve.Secp256k1 as S
     45 import qualified Crypto.Hash.SHA256 as SHA256
     46 import qualified Data.Choice as C
     47 import Data.Bits ((.&.), (.|.), complement, complementBit, shiftL, shiftR,
     48                   testBit, xor)
     49 import qualified Data.ByteString as BS
     50 import Data.Word (Word64)
     51 import GHC.Generics (Generic)
     52 import Lightning.Protocol.BOLT1 (Point, PerCommitmentSecret)
     53 import qualified Lightning.Protocol.BOLT1 as BOLT1
     54 import Lightning.Protocol.BOLT3.Types
     55 import qualified Numeric.Montgomery.Secp256k1.Scalar as SC
     56 
     57 -- points ---------------------------------------------------------------------
     58 
     59 -- Serialize a secp256k1 point, failing on the point at infinity.
     60 to_point :: S.Projective -> Maybe Point
     61 to_point p
     62   | p == S._CURVE_ZERO = Nothing
     63   | otherwise          = case BOLT1.point (S.serialize_point p) of
     64       Just pt -> Just pt
     65       Nothing -> internal_error
     66 {-# INLINE to_point #-}
     67 
     68 -- per-commitment points ------------------------------------------------------
     69 
     70 -- | Derive the per-commitment point from a per-commitment secret:
     71 --
     72 --   @per_commitment_point = per_commitment_secret * G@
     73 --
     74 --   Fails if the secret is not a valid secp256k1 secret key.
     75 --
     76 --   >>> let Just s = BOLT1.per_commitment_secret (BS.pack [31, 30 .. 0])
     77 --   >>> let Just (PerCommitmentPoint p) = derive_per_commitment_point s
     78 --   >>> B16.encode (BOLT1.un_point p)
     79 --   "025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486"
     80 derive_per_commitment_point
     81   :: PerCommitmentSecret
     82   -> Maybe PerCommitmentPoint
     83 derive_per_commitment_point s = do
     84   sk <- S.parse_int256 (BOLT1.un_per_commitment_secret s)
     85   pk <- S.derive_pub sk
     86   PerCommitmentPoint <$> to_point pk
     87 
     88 -- | As 'derive_per_commitment_point', but uses a precomputed
     89 --   'S.Context' to speed up the scalar multiplication.
     90 derive_per_commitment_point'
     91   :: S.Context
     92   -> PerCommitmentSecret
     93   -> Maybe PerCommitmentPoint
     94 derive_per_commitment_point' tex s = do
     95   sk <- S.parse_int256 (BOLT1.un_per_commitment_secret s)
     96   pk <- S.derive_pub' tex sk
     97   PerCommitmentPoint <$> to_point pk
     98 
     99 -- public keys ----------------------------------------------------------------
    100 
    101 -- | Derive a per-commitment public key from a basepoint:
    102 --
    103 --   @pubkey = basepoint + SHA256(per_commitment_point || basepoint) * G@
    104 --
    105 --   This gives @local_htlcpubkey@, @remote_htlcpubkey@,
    106 --   @local_delayedpubkey@ and the like, depending on the basepoint;
    107 --   'derive_commitment_keys' derives all of a commitment's keys at
    108 --   once.
    109 --
    110 --   Fails if the basepoint is not on the curve.
    111 --
    112 --   >>> let Just s = BOLT1.per_commitment_secret (BS.pack [31, 30 .. 0])
    113 --   >>> let Just pcp = derive_per_commitment_point s
    114 --   >>> let Just b = BOLT1.per_commitment_secret (BS.pack [0 .. 31])
    115 --   >>> let Just (PerCommitmentPoint bp) = derive_per_commitment_point b
    116 --   >>> fmap (B16.encode . BOLT1.un_point) (derive_pubkey bp pcp)
    117 --   Just "0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5"
    118 derive_pubkey :: Point -> PerCommitmentPoint -> Maybe Point
    119 derive_pubkey = derive_pubkey_with mul_g
    120 {-# INLINE derive_pubkey #-}
    121 
    122 -- Multiply the generator by a 32-byte scalar.
    123 mul_g :: BS.ByteString -> Maybe S.Projective
    124 mul_g bs = S.derive_pub =<< S.parse_int256 bs
    125 {-# INLINE mul_g #-}
    126 
    127 -- As 'mul_g', using a precomputed context.
    128 mul_g' :: S.Context -> BS.ByteString -> Maybe S.Projective
    129 mul_g' tex bs = S.derive_pub' tex =<< S.parse_int256 bs
    130 {-# INLINE mul_g' #-}
    131 
    132 derive_pubkey_with
    133   :: (BS.ByteString -> Maybe S.Projective)
    134   -> Point
    135   -> PerCommitmentPoint
    136   -> Maybe Point
    137 derive_pubkey_with mul bp (PerCommitmentPoint pcp) = do
    138   let !bp_bs = BOLT1.un_point bp
    139   base <- S.parse_point bp_bs
    140   t    <- mul (SHA256.hash (BOLT1.un_point pcp <> bp_bs))
    141   to_point (S.add base t)
    142 {-# INLINE derive_pubkey_with #-}
    143 
    144 -- | Derive a commitment's @revocationpubkey@ from the revocation
    145 --   basepoint of the party that will hold the revocation key, and the
    146 --   commitment owner's per-commitment point:
    147 --
    148 --   @
    149 --   revocationpubkey =
    150 --       revocation_basepoint
    151 --         * SHA256(revocation_basepoint || per_commitment_point)
    152 --     + per_commitment_point
    153 --         * SHA256(per_commitment_point || revocation_basepoint)
    154 --   @
    155 --
    156 --   Fails if either point is not on the curve.
    157 --
    158 --   >>> let Just s = BOLT1.per_commitment_secret (BS.pack [31, 30 .. 0])
    159 --   >>> let Just pcp = derive_per_commitment_point s
    160 --   >>> let Just b = BOLT1.per_commitment_secret (BS.pack [0 .. 31])
    161 --   >>> let Just (PerCommitmentPoint p) = derive_per_commitment_point b
    162 --   >>> let Just r = derive_revocationpubkey (RevocationBasepoint p) pcp
    163 --   >>> (\(RevocationPubkey k) -> B16.encode (BOLT1.un_point k)) r
    164 --   "02916e326636d19c33f13e8c0c3a03dd157f332f3e99c317c141dd865eb01f8ff0"
    165 derive_revocationpubkey
    166   :: RevocationBasepoint
    167   -> PerCommitmentPoint
    168   -> Maybe RevocationPubkey
    169 derive_revocationpubkey
    170   (RevocationBasepoint rbp)
    171   (PerCommitmentPoint pcp) = do
    172     let !rbp_bs = BOLT1.un_point rbp
    173         !pcp_bs = BOLT1.un_point pcp
    174     r  <- S.parse_point rbp_bs
    175     p  <- S.parse_point pcp_bs
    176     s1 <- S.parse_int256 (SHA256.hash (rbp_bs <> pcp_bs))
    177     s2 <- S.parse_int256 (SHA256.hash (pcp_bs <> rbp_bs))
    178     p1 <- S.mul r s1
    179     p2 <- S.mul p s2
    180     RevocationPubkey <$> to_point (S.add p1 p2)
    181 
    182 -- | The keys a commitment transaction's scripts use.
    183 data CommitmentKeys = CommitmentKeys
    184   { ck_revocation_pubkey :: !RevocationPubkey
    185   , ck_local_delayed     :: !LocalDelayedPubkey
    186   , ck_local_htlc        :: !LocalHtlcPubkey
    187   , ck_remote_htlc       :: !RemoteHtlcPubkey
    188   , ck_remote_payment    :: !RemotePubkey
    189   , ck_local_funding     :: !FundingPubkey
    190   , ck_remote_funding    :: !FundingPubkey
    191   } deriving (Eq, Show, Generic)
    192 
    193 instance NFData CommitmentKeys
    194 
    195 -- | Derive the keys for a commitment transaction from both parties'
    196 --   basepoints and funding pubkeys, and the commitment owner's
    197 --   per-commitment point.
    198 --
    199 --   The owner is the "local" party: its delayed and HTLC basepoints
    200 --   give @local_delayedpubkey@ and @local_htlcpubkey@, while the other
    201 --   party's revocation and HTLC basepoints give @revocationpubkey@ and
    202 --   @remote_htlcpubkey@. As every supported 'CommitmentFormat' uses
    203 --   @option_static_remotekey@, @remotepubkey@ is the other party's
    204 --   @payment_basepoint@.
    205 --
    206 --   Fails if a basepoint it derives a key from, or the per-commitment
    207 --   point, is not on the curve.
    208 derive_commitment_keys
    209   :: Basepoints          -- ^ the owner's basepoints
    210   -> FundingPubkey       -- ^ the owner's funding pubkey
    211   -> Basepoints          -- ^ the other party's basepoints
    212   -> FundingPubkey       -- ^ the other party's funding pubkey
    213   -> PerCommitmentPoint  -- ^ the owner's per-commitment point
    214   -> Maybe CommitmentKeys
    215 derive_commitment_keys = derive_commitment_keys_with mul_g
    216 
    217 -- | As 'derive_commitment_keys', but uses a precomputed 'S.Context'
    218 --   to speed up the scalar multiplications.
    219 derive_commitment_keys'
    220   :: S.Context
    221   -> Basepoints
    222   -> FundingPubkey
    223   -> Basepoints
    224   -> FundingPubkey
    225   -> PerCommitmentPoint
    226   -> Maybe CommitmentKeys
    227 derive_commitment_keys' tex = derive_commitment_keys_with (mul_g' tex)
    228 
    229 derive_commitment_keys_with
    230   :: (BS.ByteString -> Maybe S.Projective)
    231   -> Basepoints
    232   -> FundingPubkey
    233   -> Basepoints
    234   -> FundingPubkey
    235   -> PerCommitmentPoint
    236   -> Maybe CommitmentKeys
    237 derive_commitment_keys_with mul local local_fund remote remote_fund pcp = do
    238   let DelayedPaymentBasepoint local_delayed_bp = bp_delayed_payment local
    239       HtlcBasepoint local_htlc_bp = bp_htlc local
    240       HtlcBasepoint remote_htlc_bp = bp_htlc remote
    241       PaymentBasepoint remote_payment_bp = bp_payment remote
    242   revocation   <- derive_revocationpubkey (bp_revocation remote) pcp
    243   delayed      <- derive_pubkey_with mul local_delayed_bp pcp
    244   local_htlc   <- derive_pubkey_with mul local_htlc_bp pcp
    245   remote_htlc  <- derive_pubkey_with mul remote_htlc_bp pcp
    246   pure CommitmentKeys
    247     { ck_revocation_pubkey = revocation
    248     , ck_local_delayed     = LocalDelayedPubkey delayed
    249     , ck_local_htlc        = LocalHtlcPubkey local_htlc
    250     , ck_remote_htlc       = RemoteHtlcPubkey remote_htlc
    251     , ck_remote_payment    = RemotePubkey remote_payment_bp
    252     , ck_local_funding     = local_fund
    253     , ck_remote_funding    = remote_fund
    254     }
    255 
    256 -- private keys ---------------------------------------------------------------
    257 
    258 -- | Derive the private key for a per-commitment public key from its
    259 --   basepoint secret:
    260 --
    261 --   @privkey = basepoint_secret + SHA256(per_commitment_point || basepoint)@
    262 --
    263 --   where @basepoint = basepoint_secret * G@. The result is the secret
    264 --   key for @'derive_pubkey' basepoint per_commitment_point@.
    265 --
    266 --   The arithmetic on the secret is constant-time. Fails (with
    267 --   negligible probability) if the result is zero.
    268 --
    269 --   >>> let Just s = BOLT1.per_commitment_secret (BS.pack [31, 30 .. 0])
    270 --   >>> let Just pcp = derive_per_commitment_point s
    271 --   >>> let Just k = seckey (BS.pack [0 .. 31])
    272 --   >>> fmap (B16.encode . un_seckey) (derive_privkey k pcp)
    273 --   Just "cbced912d3b21bf196a766651e436aff192362621ce317704ea2f75d87e7be0f"
    274 derive_privkey :: Seckey -> PerCommitmentPoint -> Maybe Seckey
    275 derive_privkey (Seckey sk_bs) (PerCommitmentPoint pcp) = do
    276   sk <- S.parse_int256 sk_bs
    277   bp <- to_point =<< S.derive_pub sk
    278   let !h = SHA256.hash (BOLT1.un_point pcp <> BOLT1.un_point bp)
    279   to_seckey (SC.to sk + SC.to (S.unsafe_roll32 h))
    280 
    281 -- | Derive a commitment's @revocationprivkey@ from the revocation
    282 --   basepoint secret and the commitment's (revealed) per-commitment
    283 --   secret:
    284 --
    285 --   @
    286 --   revocationprivkey =
    287 --       revocation_basepoint_secret
    288 --         * SHA256(revocation_basepoint || per_commitment_point)
    289 --     + per_commitment_secret
    290 --         * SHA256(per_commitment_point || revocation_basepoint)
    291 --   @
    292 --
    293 --   The result is the secret key for the 'derive_revocationpubkey'
    294 --   of the corresponding points.
    295 --
    296 --   The arithmetic on the secrets is constant-time. Fails if the
    297 --   per-commitment secret is not a valid secret key, or (with
    298 --   negligible probability) if the result is zero.
    299 --
    300 --   >>> let Just s = BOLT1.per_commitment_secret (BS.pack [31, 30 .. 0])
    301 --   >>> let Just k = seckey (BS.pack [0 .. 31])
    302 --   >>> fmap (B16.encode . un_seckey) (derive_revocationprivkey k s)
    303 --   Just "d09ffff62ddb2297ab000cc85bcb4283fdeb6aa052affbc9dddcf33b61078110"
    304 derive_revocationprivkey
    305   :: Seckey               -- ^ revocation basepoint secret
    306   -> PerCommitmentSecret  -- ^ per-commitment secret
    307   -> Maybe Seckey
    308 derive_revocationprivkey (Seckey rbs_bs) pcs = do
    309   let !pcs_bs = BOLT1.un_per_commitment_secret pcs
    310   rbs <- S.parse_int256 rbs_bs
    311   ps  <- S.parse_int256 pcs_bs
    312   rbp <- to_point =<< S.derive_pub rbs
    313   pcp <- to_point =<< S.derive_pub ps
    314   let !rbp_bs = BOLT1.un_point rbp
    315       !pcp_bs = BOLT1.un_point pcp
    316       !h1 = SC.to (S.unsafe_roll32 (SHA256.hash (rbp_bs <> pcp_bs)))
    317       !h2 = SC.to (S.unsafe_roll32 (SHA256.hash (pcp_bs <> rbp_bs)))
    318   to_seckey (SC.to rbs * h1 + SC.to ps * h2)
    319 
    320 -- The secret key for a nonzero scalar. Reduction, arithmetic and the
    321 -- zero check are constant-time (ppad-fixed).
    322 to_seckey :: SC.Montgomery -> Maybe Seckey
    323 to_seckey r
    324   | C.decide (SC.eq r 0) = Nothing
    325   | otherwise            = Just (Seckey (S.unroll32 (SC.retr r)))
    326 
    327 -- per-commitment secrets -----------------------------------------------------
    328 
    329 -- Wrap a 32-byte value as a per-commitment secret.
    330 to_secret :: BS.ByteString -> PerCommitmentSecret
    331 to_secret bs = case BOLT1.per_commitment_secret bs of
    332   Just s  -> s
    333   Nothing -> internal_error
    334 {-# INLINE to_secret #-}
    335 
    336 -- Flip bit b (< 256) of a 32-byte value: the (b mod 8) bit of the
    337 -- (b div 8) byte.
    338 flip_bit :: Int -> BS.ByteString -> BS.ByteString
    339 flip_bit b bs =
    340   let !(pre, post) = BS.splitAt (b `shiftR` 3) bs
    341   in  case BS.uncons post of
    342         Just (h, t) -> pre <> BS.cons (complementBit h (b .&. 7)) t
    343         Nothing     -> bs
    344 {-# INLINE flip_bit #-}
    345 
    346 -- The spec's derive_secret: the secret for index i, from a base
    347 -- secret whose index agrees with i in bits 47 down to 'bits'.
    348 derive_secret :: BS.ByteString -> Int -> Word64 -> BS.ByteString
    349 derive_secret base bits i = go (bits - 1) base where
    350   go !b !p
    351     | b < 0       = p
    352     | testBit i b = go (b - 1) (SHA256.hash (flip_bit b p))
    353     | otherwise   = go (b - 1) p
    354 
    355 -- | Generate the per-commitment secret with a given index from a seed,
    356 --   per BOLT #3's @generate_from_seed@.
    357 --
    358 --   >>> let Just s = seed (BS.replicate 32 0xff)
    359 --   >>> let Just i = secret_index 281474976710655
    360 --   >>> B16.encode (BOLT1.un_per_commitment_secret (generate_from_seed s i))
    361 --   "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc"
    362 generate_from_seed :: Seed -> SecretIndex -> PerCommitmentSecret
    363 generate_from_seed (Seed s) (SecretIndex i) = to_secret (derive_secret s 48 i)
    364 
    365 -- | Compact storage for the per-commitment secrets received from a
    366 --   peer, per BOLT #3's "Efficient Per-commitment Secret Storage".
    367 --
    368 --   Holds at most 49 secrets, from which every secret received so far
    369 --   can be derived. This is secret material: its 'Show' instance is
    370 --   redacted, and it has no 'Eq' instance.
    371 newtype SecretStore = SecretStore [Entry]
    372 
    373 -- A stored secret: its bucket (the number of trailing zeros of its
    374 -- index), index, and value. Kept in ascending bucket order.
    375 data Entry = Entry
    376   {-# UNPACK #-} !Int
    377   {-# UNPACK #-} !Word64
    378   !BS.ByteString
    379 
    380 instance Show SecretStore where
    381   showsPrec d _ = showParen (d > 10) $
    382     showString "SecretStore <redacted>"
    383 
    384 instance NFData SecretStore where
    385   rnf (SecretStore es) = rnf_entries es where
    386     rnf_entries [] = ()
    387     rnf_entries (Entry _ _ s : rest) = rnf s `seq` rnf_entries rest
    388 
    389 -- | The empty 'SecretStore'.
    390 empty_secret_store :: SecretStore
    391 empty_secret_store = SecretStore []
    392 
    393 -- The bucket for an index: its number of trailing zeros (48 for 0).
    394 bucket_of :: Word64 -> Int
    395 bucket_of i = go 0 where
    396   go !b
    397     | b > 47      = 48
    398     | testBit i b = b
    399     | otherwise   = go (b + 1)
    400 
    401 -- Whether a secret at index j in bucket b can derive index i.
    402 can_derive :: Int -> Word64 -> Word64 -> Bool
    403 can_derive b j i = i .&. complement ((1 `shiftL` b) - 1) == j
    404 {-# INLINE can_derive #-}
    405 
    406 -- | Add a newly received per-commitment secret to the store, per
    407 --   BOLT #3's @insert_secret@.
    408 --
    409 --   Fails if a stored secret cannot be derived from the new one, i.e.
    410 --   if the secrets were not generated from the same seed (or were not
    411 --   received in order).
    412 --
    413 --   >>> let Just s = seed (BS.replicate 32 0xff)
    414 --   >>> let Just i = secret_index 281474976710655
    415 --   >>> let secret = generate_from_seed s i
    416 --   >>> let Just st = insert_secret secret i empty_secret_store
    417 --   >>> fmap (== secret) (derive_old_secret i st)
    418 --   Just True
    419 insert_secret
    420   :: PerCommitmentSecret
    421   -> SecretIndex
    422   -> SecretStore
    423   -> Maybe SecretStore
    424 insert_secret pcs (SecretIndex i) (SecretStore known)
    425   | all derives known = Just $! SecretStore (put known)
    426   | otherwise         = Nothing
    427   where
    428     !s = BOLT1.un_per_commitment_secret pcs
    429     !b = bucket_of i
    430     !e = Entry b i s
    431 
    432     -- every secret in a lower bucket must derive from the new one
    433     derives (Entry kb ki ks)
    434       | kb < b    = to_secret (derive_secret s b ki) == to_secret ks
    435       | otherwise = True
    436 
    437     -- replace this bucket's entry, keeping ascending bucket order
    438     put [] = [e]
    439     put (k@(Entry kb _ _) : ks) = case compare kb b of
    440       LT -> k : put ks
    441       EQ -> e : ks
    442       GT -> e : k : ks
    443 
    444 -- | Derive a previously received per-commitment secret from the
    445 --   store. Fails if the index has not been received.
    446 derive_old_secret
    447   :: SecretIndex
    448   -> SecretStore
    449   -> Maybe PerCommitmentSecret
    450 derive_old_secret (SecretIndex i) (SecretStore known) = go known where
    451   go [] = Nothing
    452   go (Entry b j s : rest)
    453     | can_derive b j i = Just $! to_secret (derive_secret s b i)
    454     | otherwise        = go rest
    455 
    456 -- | Rebuild a 'SecretStore' from its entries, e.g. as produced by
    457 --   'un_secret_store'.
    458 --
    459 --   Fails if two entries share a bucket (i.e. their indices have the
    460 --   same number of trailing zeros), or if an entry contradicts another
    461 --   from which it can be derived.
    462 secret_store
    463   :: [(SecretIndex, PerCommitmentSecret)]
    464   -> Maybe SecretStore
    465 secret_store = go [] where
    466   go acc [] = Just (SecretStore acc)
    467   go acc ((SecretIndex i, pcs) : rest) = do
    468     let !e = Entry (bucket_of i) i (BOLT1.un_per_commitment_secret pcs)
    469     acc' <- place e acc
    470     go acc' rest
    471 
    472   -- insert in ascending bucket order, checking consistency with the
    473   -- entries already placed
    474   place e@(Entry b i s) es
    475     | all (consistent e) es = put es
    476     | otherwise             = Nothing
    477     where
    478       put [] = Just [e]
    479       put (k@(Entry kb _ _) : ks) = case compare kb b of
    480         LT -> (k :) <$> put ks
    481         EQ -> Nothing
    482         GT -> Just (e : k : ks)
    483       consistent _ (Entry kb ki ks)
    484         | kb > b && can_derive kb ki i =
    485             to_secret (derive_secret ks kb i) == to_secret s
    486         | kb < b && can_derive b i ki =
    487             to_secret (derive_secret s b ki) == to_secret ks
    488         | otherwise = True
    489 
    490 -- | The entries of a 'SecretStore', for persistence; see
    491 --   'secret_store'.
    492 un_secret_store :: SecretStore -> [(SecretIndex, PerCommitmentSecret)]
    493 un_secret_store (SecretStore es) =
    494   [ (SecretIndex i, to_secret s) | Entry _ i s <- es ]
    495 
    496 -- commitment number obscuring ------------------------------------------------
    497 
    498 -- | Obscure a commitment number, by XOR with the lower 48 bits of
    499 --
    500 --   @SHA256(payment_basepoint from open_channel
    501 --           || payment_basepoint from accept_channel)@
    502 --
    503 --   As XOR is an involution, applying this to an obscured number
    504 --   recovers the commitment number. ('build_commitment_tx' obscures
    505 --   the commitment number itself.)
    506 --
    507 --   >>> let pay h = fmap PaymentBasepoint (BOLT1.point =<< B16.decode h)
    508 --   >>> let o1 = "034f355bdcb7cc0af728ef3cceb9615d9068"
    509 --   >>> let Just o = pay (o1 <> "4bb5b2ca5f859ab0f0b704075871aa")
    510 --   >>> let a1 = "032c0b7cf95324a07d05398b240174dc0c2b"
    511 --   >>> let Just a = pay (a1 <> "e444d96b159aa6c7f7b1e668680991")
    512 --   >>> fmap (obscured_commitment_number o a) (commitment_number 42)
    513 --   Just 48035859142974
    514 obscured_commitment_number
    515   :: PaymentBasepoint   -- ^ opener's payment_basepoint
    516   -> PaymentBasepoint   -- ^ accepter's payment_basepoint
    517   -> CommitmentNumber
    518   -> Word64
    519 obscured_commitment_number
    520   (PaymentBasepoint opener)
    521   (PaymentBasepoint accepter)
    522   cn =
    523     let !h = SHA256.hash (BOLT1.un_point opener <> BOLT1.un_point accepter)
    524         !mask = BS.foldl' (\acc x -> (acc `shiftL` 8) .|. fromIntegral x) 0
    525                   (BS.drop 26 h)
    526     in  un_commitment_number cn `xor` mask