From 2b7d01df7e8fa20a7eabc267eb28bc0ffcb5e448 Mon Sep 17 00:00:00 2001 From: Erick Cestari Date: Tue, 17 Feb 2026 15:41:13 -0300 Subject: [PATCH 1/2] htlcswitch/hop: require total_amount_msat for blinded final hops Per BOLT 4, when encrypted_recipient_data is present on a final node, total_amount_msat MUST be present. This adds explicit validation at payload parsing time. Found by differential fuzzing using bitcoinfuzz. --- htlcswitch/hop/payload.go | 10 ++++++++++ htlcswitch/hop/payload_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/htlcswitch/hop/payload.go b/htlcswitch/hop/payload.go index fc456828a57..6969722802c 100644 --- a/htlcswitch/hop/payload.go +++ b/htlcswitch/hop/payload.go @@ -275,6 +275,7 @@ func ValidateParsedPayloadTypes(parsedTypes tlv.TypeMap, _, hasAMP := parsedTypes[record.AMPOnionType] _, hasEncryptedData := parsedTypes[record.EncryptedDataOnionType] _, hasBlinding := parsedTypes[record.BlindingPointOnionType] + _, hasTotalAmtMsatBlinded := parsedTypes[record.TotalAmtMsatBlindedType] // All cleartext hops (including final hop) and the final hop in a // blinded path require the forwading amount and expiry TLVs to be set. @@ -311,6 +312,15 @@ func ValidateParsedPayloadTypes(parsedTypes tlv.TypeMap, FinalHop: isFinalHop, } + // If encrypted data is present and is final hop, we require that + // total_amount_msat is set. + case isFinalHop && hasEncryptedData && !hasTotalAmtMsatBlinded: + return ErrInvalidPayload{ + Type: record.TotalAmtMsatBlindedType, + Violation: OmittedViolation, + FinalHop: isFinalHop, + } + // Hops that need forwarding info must include an amount to forward. case needFwdInfo && !hasAmt: return ErrInvalidPayload{ diff --git a/htlcswitch/hop/payload_test.go b/htlcswitch/hop/payload_test.go index bd0081cb937..e0d4f27bfad 100644 --- a/htlcswitch/hop/payload_test.go +++ b/htlcswitch/hop/payload_test.go @@ -377,6 +377,8 @@ var decodePayloadTests = []decodePayloadTest{ 0x04, 0x00, // encrypted data 0x0a, 0x03, 0x03, 0x02, 0x01, + // total_amount + 0x12, 0x00, }, shouldHaveEncData: true, }, @@ -389,6 +391,8 @@ var decodePayloadTests = []decodePayloadTest{ 0x04, 0x00, // encrypted data 0x0a, 0x03, 0x03, 0x02, 0x01, + // total_amount + 0x12, 0x00, }, shouldHaveEncData: true, expErr: hop.ErrInvalidPayload{ @@ -397,6 +401,25 @@ var decodePayloadTests = []decodePayloadTest{ FinalHop: true, }, }, + { + name: "final blinded missing total_amount", + isFinalHop: true, + updateAddBlinded: true, + payload: []byte{ + // amount + 0x02, 0x00, + // cltv + 0x04, 0x00, + // encrypted data + 0x0a, 0x03, 0x03, 0x02, 0x01, + }, + shouldHaveEncData: true, + expErr: hop.ErrInvalidPayload{ + Type: record.TotalAmtMsatBlindedType, + Violation: hop.OmittedViolation, + FinalHop: true, + }, + }, { name: "final blinded missing cltv", isFinalHop: true, @@ -406,6 +429,8 @@ var decodePayloadTests = []decodePayloadTest{ 0x02, 0x00, // encrypted data 0x0a, 0x03, 0x03, 0x02, 0x01, + // total_amount + 0x12, 0x00, }, shouldHaveEncData: true, expErr: hop.ErrInvalidPayload{ From 774960d16d1678cd21d1d08866661e0d8eaba9c7 Mon Sep 17 00:00:00 2001 From: Erick Cestari Date: Wed, 18 Feb 2026 14:45:57 -0300 Subject: [PATCH 2/2] docs: add release-notes --- docs/release-notes/release-notes-0.21.0.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/release-notes/release-notes-0.21.0.md b/docs/release-notes/release-notes-0.21.0.md index f81ba63dcce..f3b2fb6c89c 100644 --- a/docs/release-notes/release-notes-0.21.0.md +++ b/docs/release-notes/release-notes-0.21.0.md @@ -75,6 +75,12 @@ transitions during startup, avoiding lost unlocks during slow database initialization. +* [Required `total_amount_msat` for blinded final + hops](https://github.com/lightningnetwork/lnd/pull/10597). Per BOLT 4, when + `encrypted_recipient_data` is present on a final node, `total_amount_msat` + must be included. Validation is now performed explicitly at payload parsing + time. + # New Features - Basic Support for [onion messaging forwarding](https://github.com/lightningnetwork/lnd/pull/9868)