bolt4

Onion routing protocol, per BOLT #4 (docs.ppad.tech/bolt4).
git clone git://git.ppad.tech/bolt4.git
Log | Files | Refs | README | LICENSE

commit 62af5202761b82a58c373be9e3591e51e8fb95da
parent 7e9196bd1f726947eea0aebd2179454fffa62304
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 10 Oct 2026 13:44:51 +0400

lib: check the secret key length in nextEphemeral

nextEphemeral is exported and passed its secret key to mulSecKey,
which reads it with unsafe_roll32. A key shorter than 32 bytes read
past the end of the buffer. Return Nothing unless it is 32 bytes.

Diffstat:
Mlib/Lightning/Protocol/BOLT4/Blinding.hs | 22+++++++++++++---------
Mtest/Main.hs | 6++++++
2 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/lib/Lightning/Protocol/BOLT4/Blinding.hs b/lib/Lightning/Protocol/BOLT4/Blinding.hs @@ -134,20 +134,24 @@ deriveBlindedNodeId (SharedSecret !ss) !nodePub = do -- -- @e_{i+1} = SHA256(E_i || ss_i) * e_i@ -- @E_{i+1} = SHA256(E_i || ss_i) * E_i@ +-- +-- Returns 'Nothing' if the secret key is not 32 bytes. nextEphemeral :: BS.ByteString -- ^ e_i (32-byte secret key) -> Secp256k1.Projective -- ^ E_i -> SharedSecret -- ^ ss_i -> Maybe (BS.ByteString, Secp256k1.Projective) -- ^ (e_{i+1}, E_{i+1}) -nextEphemeral !secKey !pubKey (SharedSecret !ss) = do - let !pubBytes = Secp256k1.serialize_point pubKey - !blindingFactor = SHA256.hash (pubBytes <> ss) - bfInt <- Secp256k1.roll32 blindingFactor - -- Compute e_{i+1} = e_i * blindingFactor (mod q) - let !newSecKey = mulSecKey secKey blindingFactor - -- Compute E_{i+1} = E_i * blindingFactor - newPubKey <- Secp256k1.mul pubKey bfInt - pure (newSecKey, newPubKey) +nextEphemeral !secKey !pubKey (SharedSecret !ss) + | BS.length secKey /= 32 = Nothing + | otherwise = do + let !pubBytes = Secp256k1.serialize_point pubKey + !blindingFactor = SHA256.hash (pubBytes <> ss) + bfInt <- Secp256k1.roll32 blindingFactor + -- Compute e_{i+1} = e_i * blindingFactor (mod q) + let !newSecKey = mulSecKey secKey blindingFactor + -- Compute E_{i+1} = E_i * blindingFactor + newPubKey <- Secp256k1.mul pubKey bfInt + pure (newSecKey, newPubKey) {-# INLINE nextEphemeral #-} -- | Compute blinding factor for next path key (public key only). diff --git a/test/Main.hs b/test/Main.hs @@ -1255,6 +1255,12 @@ blindingEphemeralKeyTests = testSeed testSeedPubKey ss2 assertBool "results should differ" (result1 /= result2) + + , testCase "nextEphemeral rejects a secret not of 32 bytes" $ do + nextEphemeral (BS.singleton 0x07) testSeedPubKey testSharedSecretBS + @?= Nothing + nextEphemeral (BS.cons 0x00 testSeed) testSeedPubKey + testSharedSecretBS @?= Nothing ] -- 3. TLV Encoding/Decoding Tests -----------------------------------------