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