commit 7e9196bd1f726947eea0aebd2179454fffa62304
parent dd4a5625a853a465d07feba5e6e5fee47c7b0776
Author: Jared Tobin <jared@jtobin.io>
Date: Sat, 10 Oct 2026 13:43:48 +0400
lib: reject hop payloads under 2 bytes in construct
An all-Nothing HopPayload encodes to zero bytes. construct accepted
it and produced an onion that every receiver must reject (the spec
reserves payload lengths 0 and 1). Fail with the new
InvalidHopPayload error, carrying the hop index, instead.
Construct tests that used empty payloads now use a minimal one.
Diffstat:
2 files changed, 24 insertions(+), 12 deletions(-)
diff --git a/lib/Lightning/Protocol/BOLT4/Construct.hs b/lib/Lightning/Protocol/BOLT4/Construct.hs
@@ -40,6 +40,7 @@ data Error
| TooManyHops
| PayloadTooLarge !Int
| InvalidHopPubKey !Int
+ | InvalidHopPayload !Int -- ^ hop index of a payload under 2 bytes
deriving (Eq, Show)
-- | Maximum number of hops in a route.
@@ -74,10 +75,13 @@ construct !sessionKey !hops !assocData
let hopPubKeys = map hopPubKey hops
(secrets, _) <- computeAllSecrets sessionKey ephPub hopPubKeys
- -- Validate payload sizes
+ -- Validate payload sizes (receivers reject lengths 0 and 1)
let payloadBytes = map (encodeHopPayload . hopPayload) hops
payloadSizes = map payloadShiftSize payloadBytes
totalSize = sum payloadSizes
+ case [ i | (i, p) <- zip [0 ..] payloadBytes, BS.length p < 2 ] of
+ i : _ -> Left (InvalidHopPayload i)
+ [] -> pure ()
if totalSize > hopPayloadsSize
then Left (PayloadTooLarge totalSize)
else do
diff --git a/test/Main.hs b/test/Main.hs
@@ -512,8 +512,20 @@ testConstructErrorCases = testGroup "error cases" [
case result of
Left TooManyHops -> return ()
_ -> assertFailure "Expected TooManyHops"
+ , testCase "rejects an empty hop payload" $ do
+ pub <- demand "parse_point" $
+ Secp256k1.parse_point (fromHex hop0PubKeyHex)
+ let emptyPayload = HopPayload Nothing Nothing Nothing
+ Nothing Nothing Nothing []
+ hops = [Hop pub minimalPayload, Hop pub emptyPayload]
+ construct sessionKey hops "" @?= Left (InvalidHopPayload 1)
]
+-- | A small, valid hop payload.
+minimalPayload :: HopPayload
+minimalPayload =
+ HopPayload (Just 1000) (Just 500000) Nothing Nothing Nothing Nothing []
+
testSharedSecretComputation :: TestTree
testSharedSecretComputation =
testCase "computes correct shared secrets (BOLT4 spec)" $ do
@@ -527,13 +539,11 @@ testSharedSecretComputation =
Secp256k1.parse_point (fromHex hop3PubKeyHex)
pub4 <- demand "parse_point" $
Secp256k1.parse_point (fromHex hop4PubKeyHex)
- let emptyPayload = HopPayload Nothing Nothing Nothing
- Nothing Nothing Nothing []
- hops = [ Hop pub0 emptyPayload
- , Hop pub1 emptyPayload
- , Hop pub2 emptyPayload
- , Hop pub3 emptyPayload
- , Hop pub4 emptyPayload
+ let hops = [ Hop pub0 minimalPayload
+ , Hop pub1 minimalPayload
+ , Hop pub2 minimalPayload
+ , Hop pub3 minimalPayload
+ , Hop pub4 minimalPayload
]
result = construct sessionKey hops ""
case result of
@@ -557,10 +567,8 @@ testPacketStructure =
Secp256k1.parse_point (fromHex hop0PubKeyHex)
pub1 <- demand "parse_point" $
Secp256k1.parse_point (fromHex hop1PubKeyHex)
- let emptyPayload = HopPayload Nothing Nothing Nothing
- Nothing Nothing Nothing []
- hops = [Hop pub0 emptyPayload,
- Hop pub1 emptyPayload]
+ let hops = [Hop pub0 minimalPayload,
+ Hop pub1 minimalPayload]
result = construct sessionKey hops ""
case result of
Left err ->