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 501b5455752bd45997c9fa5d747ff67c52ebb10e
parent ddc0adda850b8f4451dbcdcdc3318b1bb32dcab7
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 10 Oct 2026 13:32:36 +0400

test: add payload_length bound tests for process

Diffstat:
Mtest/Main.hs | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 62 insertions(+), 0 deletions(-)

diff --git a/test/Main.hs b/test/Main.hs @@ -582,6 +582,7 @@ processTests = testGroup "packet processing" [ , testEphemeralKeyValidation , testHmacValidation , testProcessBasic + , testPayloadLength ] testVersionValidation :: TestTree @@ -738,6 +739,67 @@ testProcessBasic = testGroup "basic processing" [ assertFailure "expected Receive, got Forward" ] +-- | Encrypt 1300 bytes of plaintext hop_payloads into a packet for +-- the node with secret key 'sessionKey' (using its own public key +-- as the ephemeral key), with empty associated data. +mkPacket :: BS.ByteString -> IO OnionPacket +mkPacket plain = do + pub <- demand "derive_pub" + (Secp256k1.roll32 sessionKey >>= Secp256k1.derive_pub) + ss <- demand "computeSharedSecret" (computeSharedSecret sessionKey pub) + let stream = generateStream (deriveRho ss) hopPayloadsSize + enc = BS.pack (BS.zipWith xor plain stream) + mac = computeHmac (deriveMu ss) enc BS.empty + hp <- demand "hopPayloads" (hopPayloads enc) + hm <- demand "hmac32" (hmac32 mac) + pure (OnionPacket versionByte (Secp256k1.serialize_point pub) hp hm) + +-- | Pad (or truncate) plaintext hop_payloads to 1300 bytes. +pad1300 :: BS.ByteString -> BS.ByteString +pad1300 bs = BS.take hopPayloadsSize (bs <> BS.replicate hopPayloadsSize 0) + +assertInvalidPayload :: Either RejectReason ProcessResult -> Assertion +assertInvalidPayload r = case r of + Left (InvalidPayload _) -> pure () + Left e -> assertFailure ("expected InvalidPayload, got " ++ show e) + Right _ -> assertFailure "expected rejection, got success" + +testPayloadLength :: TestTree +testPayloadLength = testGroup "payload_length bounds" [ + testCase "rejects payload_length below 2" $ do + pkt <- mkPacket (pad1300 (BS.pack [0x01, 0x02])) + assertInvalidPayload (process sessionKey pkt BS.empty) + + , testCase "rejects payload_length past hop_payloads" $ do + -- payload_length 2000, covered by an odd TLV (type 257, length + -- 1994) that runs into the zero-extension stream + let prefix = BS.pack [0xfd, 0x07, 0xd0, 0xfd, 0x01, 0x01, + 0xfd, 0x07, 0xca] + pkt <- mkPacket (pad1300 prefix) + assertInvalidPayload (process sessionKey pkt BS.empty) + + , testCase "rejects payload_length of 2^64 - 1" $ do + pkt <- mkPacket (pad1300 (BS.pack (0xff : replicate 8 0xff))) + assertInvalidPayload (process sessionKey pkt BS.empty) + + , testCase "rejects payload one byte too long" $ do + -- 3-byte prefix + 1266-byte payload + 32-byte HMAC = 1301 + let tlv = BS.pack [0x01, 0xfd, 0x04, 0xee] <> BS.replicate 1262 0 + pkt <- mkPacket (pad1300 (encodeBigSize 1266 <> tlv)) + assertInvalidPayload (process sessionKey pkt BS.empty) + + , testCase "accepts the largest payload that fits" $ do + -- 3-byte prefix + 1265-byte payload + 32-byte HMAC = 1300 + let val = BS.replicate 1261 0 + tlv = BS.pack [0x01, 0xfd, 0x04, 0xed] <> val + pkt <- mkPacket (pad1300 (encodeBigSize 1265 <> tlv)) + case process sessionKey pkt BS.empty of + Right (Receive ri) -> + hpUnknownTlvs (riPayload ri) @?= [TlvRecord 1 val] + Right (Forward _) -> assertFailure "expected Receive" + Left e -> assertFailure ("expected success, got " ++ show e) + ] + -- Error tests ------------------------------------------------------------- errorTests :: TestTree