mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-28 05:47:04 +02:00
routing: add additional validation to hop payload creation
This commit is contained in:
@@ -38,6 +38,13 @@ var (
|
|||||||
// ErrAMPMissingMPP is returned when the caller tries to attach an AMP
|
// ErrAMPMissingMPP is returned when the caller tries to attach an AMP
|
||||||
// record but no MPP record is presented for the final hop.
|
// record but no MPP record is presented for the final hop.
|
||||||
ErrAMPMissingMPP = errors.New("cannot send AMP without MPP record")
|
ErrAMPMissingMPP = errors.New("cannot send AMP without MPP record")
|
||||||
|
|
||||||
|
// ErrMissingField is returned if a required TLV is missing.
|
||||||
|
ErrMissingField = errors.New("required tlv missing")
|
||||||
|
|
||||||
|
// ErrIncorrectField is returned if a tlv field is included when it
|
||||||
|
// should not be.
|
||||||
|
ErrIncorrectField = errors.New("incorrect tlv included")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Vertex is a simple alias for the serialization of a compressed Bitcoin
|
// Vertex is a simple alias for the serialization of a compressed Bitcoin
|
||||||
@@ -193,8 +200,28 @@ func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64,
|
|||||||
var records []tlv.Record
|
var records []tlv.Record
|
||||||
|
|
||||||
// Hops that are not part of a blinded path will have an amount and
|
// Hops that are not part of a blinded path will have an amount and
|
||||||
// a CLTV expiry field. Zero values indicate that the hop is inside of
|
// a CLTV expiry field. In a blinded route (where encrypted data is
|
||||||
// a blinded route, so the TLV should not be included.
|
// non-nil), these values may be omitted for intermediate nodes.
|
||||||
|
// Validate these fields against the structure of the payload so that
|
||||||
|
// we know they're included (or excluded) correctly.
|
||||||
|
isBlinded := h.EncryptedData != nil
|
||||||
|
|
||||||
|
if err := optionalBlindedField(
|
||||||
|
h.AmtToForward == 0, isBlinded, finalHop,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("%w: amount to forward: %v", err,
|
||||||
|
h.AmtToForward)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := optionalBlindedField(
|
||||||
|
h.OutgoingTimeLock == 0, isBlinded, finalHop,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("%w: outgoing timelock: %v", err,
|
||||||
|
h.OutgoingTimeLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once we've validated that these TLVs are set as we expect, we can
|
||||||
|
// go ahead and include them if non-zero.
|
||||||
amt := uint64(h.AmtToForward)
|
amt := uint64(h.AmtToForward)
|
||||||
if amt != 0 {
|
if amt != 0 {
|
||||||
records = append(
|
records = append(
|
||||||
@@ -208,10 +235,13 @@ func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BOLT 04 says the next_hop_id should be omitted for the final hop,
|
// Validate channel TLV is present as expected based on location in
|
||||||
// but present for all others.
|
// route and whether this hop is blinded.
|
||||||
//
|
err := validateNextChanID(nextChanID != 0, isBlinded, finalHop)
|
||||||
// TODO(conner): test using hop.Exit once available
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: channel id: %v", err, nextChanID)
|
||||||
|
}
|
||||||
|
|
||||||
if nextChanID != 0 {
|
if nextChanID != 0 {
|
||||||
records = append(records,
|
records = append(records,
|
||||||
record.NewNextHopIDRecord(&nextChanID),
|
record.NewNextHopIDRecord(&nextChanID),
|
||||||
@@ -284,6 +314,62 @@ func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64,
|
|||||||
return tlvStream.Encode(w)
|
return tlvStream.Encode(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// optionalBlindedField validates fields that we expect to be non-zero for all
|
||||||
|
// hops in a regular route, but may be zero for intermediate nodes in a blinded
|
||||||
|
// route. It will validate the following cases:
|
||||||
|
// - Not blinded: require non-zero values.
|
||||||
|
// - Intermediate blinded node: require zero values.
|
||||||
|
// - Final blinded node: require non-zero values.
|
||||||
|
func optionalBlindedField(isZero, blindedHop, finalHop bool) error {
|
||||||
|
switch {
|
||||||
|
// We are not in a blinded route and the TLV is not set when it should
|
||||||
|
// be.
|
||||||
|
case !blindedHop && isZero:
|
||||||
|
return ErrMissingField
|
||||||
|
|
||||||
|
// We are not in a blinded route and the TLV is set as expected.
|
||||||
|
case !blindedHop:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// In a blinded route the final hop is expected to have TLV values set.
|
||||||
|
case finalHop && isZero:
|
||||||
|
return ErrMissingField
|
||||||
|
|
||||||
|
// In an intermediate hop in a blinded route and the field is not zero.
|
||||||
|
case !finalHop && !isZero:
|
||||||
|
return ErrIncorrectField
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateNextChanID validates the presence of the nextChanID TLV field in
|
||||||
|
// a payload. For regular payments, it is expected to be present for all hops
|
||||||
|
// except the final hop. For blinded paths, it is not expected to be included
|
||||||
|
// at all (as this value is provided in encrypted data).
|
||||||
|
func validateNextChanID(nextChanIDIsSet, isBlinded, finalHop bool) error {
|
||||||
|
switch {
|
||||||
|
// Hops in a blinded route should not have a next channel ID set.
|
||||||
|
case isBlinded && nextChanIDIsSet:
|
||||||
|
return ErrIncorrectField
|
||||||
|
|
||||||
|
// Otherwise, blinded hops are allowed to have a zero value.
|
||||||
|
case isBlinded:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// The final hop in a regular route is expected to have a zero value.
|
||||||
|
case finalHop && nextChanIDIsSet:
|
||||||
|
return ErrIncorrectField
|
||||||
|
|
||||||
|
// Intermediate hops in regular routes require non-zero value.
|
||||||
|
case !finalHop && !nextChanIDIsSet:
|
||||||
|
return ErrMissingField
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Size returns the total size this hop's payload would take up in the onion
|
// Size returns the total size this hop's payload would take up in the onion
|
||||||
// packet.
|
// packet.
|
||||||
func (h *Hop) PayloadSize(nextChanID uint64) uint64 {
|
func (h *Hop) PayloadSize(nextChanID uint64) uint64 {
|
||||||
|
@@ -170,7 +170,7 @@ func TestNoForwardingParams(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
err := hop.PackHopPayload(&b, 2, false)
|
err := hop.PackHopPayload(&b, 0, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user