Skip to content

Commit

Permalink
fixup! multi: add validation of blinded route encrypted data
Browse files Browse the repository at this point in the history
  • Loading branch information
carlaKC committed Mar 9, 2024
1 parent 7d458af commit b65fab8
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 127 deletions.
100 changes: 40 additions & 60 deletions htlcswitch/hop/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,72 +429,52 @@ func ValidateBlindedRouteData(blindedData *record.BlindedRouteData,

// Bolt 04 notes that we should enforce payment constraints _if_ they
// are present, so we do not fail if not provided.
if blindedData.Constraints != nil {
// MUST fail if the expiry is greater than max_cltv_expiry.
maxCLTV := blindedData.Constraints.MaxCltvExpiry
if incomingTimelock > maxCLTV {
return ErrInvalidPayload{
Type: record.LockTimeOnionType,
Violation: InsufficientViolation,
var err error
blindedData.Constraints.WhenSome(
func(c tlv.RecordT[tlv.TlvType12, record.PaymentConstraints]) {
// MUST fail if the expiry is greater than
// max_cltv_expiry.
if incomingTimelock > c.Val.MaxCltvExpiry {
err = ErrInvalidPayload{
Type: record.LockTimeOnionType,
Violation: InsufficientViolation,
}
}
}

// MUST fail if the amount is below htlc_minimum_msat.
if incomingAmount < blindedData.Constraints.HtlcMinimumMsat {
return ErrInvalidPayload{
Type: record.AmtOnionType,
Violation: InsufficientViolation,
// MUST fail if the amount is below htlc_minimum_msat.
if incomingAmount < c.Val.HtlcMinimumMsat {
err = ErrInvalidPayload{
Type: record.AmtOnionType,
Violation: InsufficientViolation,
}
}
}
}

// Encrypted data MUST include payment relay information.
if blindedData.RelayInfo == nil {
return ErrInvalidPayload{
Type: record.PaymentRelayType,
Violation: OmittedViolation,
}
}

// Specification validation instructions for receivers are:
// * encrypted data must either have short_channel id _or_ next_node_id.
//
// However, instructions for creators of blinded route encrypted data
// for payments state that a short_channel_id MUST be included.
//
// This difference in validation and creation instructions is present
// because blinded routes are used for blinded payments and onion
// messages (the latter relying on node IDs rather than short channel
// ids).
//
// While we may transition relaying to node_id based payments, this is
// not currently a valid option for blinded payments (because the
// receiver must give us a short channel ID), so we simply validate
// that we have a short channel ID.
//
// TODO: relax validation if node_id based blinded payments are
// introduced to the specification.
if blindedData.ShortChannelID == nil {
return ErrInvalidPayload{
Type: record.ShortChannelIDType,
Violation: OmittedViolation,
}
}

// No need to check anything else if features are not provided (bolt 4
// indicates that omitted features should be treated like an empty
// vector).
if blindedData.Features == nil {
return nil
},
)
if err != nil {
return err
}

// Fail if we don't understand any features (even or odd), because we
// expect the features to have been set from our announcement.
if blindedData.Features.UnknownFeatures() {
return ErrInvalidPayload{
Type: record.FeatureVectorType,
Violation: IncludedViolation,
}
// expect the features to have been set from our announcement. If the
// feature vector TLV is not included, it's interpreted as an empty
// vector (no validation required).
blindedData.Features.WhenSome(
func(f tlv.RecordT[tlv.TlvType14, record.BlindedFeatures]) {
rawFeatures := lnwire.RawFeatureVector(f.Val)
featureVector := lnwire.NewFeatureVector(
&rawFeatures, lnwire.Features,
)

if featureVector.UnknownFeatures() {
err = ErrInvalidPayload{
Type: 14,
Violation: IncludedViolation,
}
}
},
)
if err != nil {
return err
}

return nil
Expand Down
113 changes: 46 additions & 67 deletions htlcswitch/hop/payload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,13 +572,15 @@ func TestValidateBlindedRouteData(t *testing.T) {
}{
{
name: "max cltv expired",
data: &record.BlindedRouteData{
Constraints: &record.PaymentConstraints{
data: record.NewBlindedRouteData(
scid,
nil,
record.PaymentRelayInfo{},
&record.PaymentConstraints{
MaxCltvExpiry: 100,
},
RelayInfo: &record.PaymentRelayInfo{},
ShortChannelID: &scid,
},
nil,
),
incomingTimelock: 200,
err: hop.ErrInvalidPayload{
Type: record.LockTimeOnionType,
Expand All @@ -587,14 +589,16 @@ func TestValidateBlindedRouteData(t *testing.T) {
},
{
name: "zero max cltv",
data: &record.BlindedRouteData{
Constraints: &record.PaymentConstraints{
data: record.NewBlindedRouteData(
scid,
nil,
record.PaymentRelayInfo{},
&record.PaymentConstraints{
MaxCltvExpiry: 0,
HtlcMinimumMsat: 10,
},
RelayInfo: &record.PaymentRelayInfo{},
ShortChannelID: &scid,
},
nil,
),
incomingAmount: 100,
incomingTimelock: 10,
err: hop.ErrInvalidPayload{
Expand All @@ -604,13 +608,15 @@ func TestValidateBlindedRouteData(t *testing.T) {
},
{
name: "amount below minimum",
data: &record.BlindedRouteData{
Constraints: &record.PaymentConstraints{
data: record.NewBlindedRouteData(
scid,
nil,
record.PaymentRelayInfo{},
&record.PaymentConstraints{
HtlcMinimumMsat: 15,
},
RelayInfo: &record.PaymentRelayInfo{},
ShortChannelID: &scid,
},
nil,
),
incomingAmount: 10,
err: hop.ErrInvalidPayload{
Type: record.AmtOnionType,
Expand All @@ -619,86 +625,59 @@ func TestValidateBlindedRouteData(t *testing.T) {
},
{
name: "valid, no features",
data: &record.BlindedRouteData{
Constraints: &record.PaymentConstraints{
data: record.NewBlindedRouteData(
scid,
nil,
record.PaymentRelayInfo{},
&record.PaymentConstraints{
MaxCltvExpiry: 100,
HtlcMinimumMsat: 20,
},
RelayInfo: &record.PaymentRelayInfo{},
ShortChannelID: &scid,
},
nil,
),
incomingAmount: 40,
incomingTimelock: 80,
},
{
name: "unknown features",
data: &record.BlindedRouteData{
Constraints: &record.PaymentConstraints{
data: record.NewBlindedRouteData(
scid,
nil,
record.PaymentRelayInfo{},
&record.PaymentConstraints{
MaxCltvExpiry: 100,
HtlcMinimumMsat: 20,
},
RelayInfo: &record.PaymentRelayInfo{},
ShortChannelID: &scid,
Features: lnwire.NewFeatureVector(
lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(
lnwire.FeatureBit(9999),
),
lnwire.Features,
),
},
),
incomingAmount: 40,
incomingTimelock: 80,
err: hop.ErrInvalidPayload{
Type: record.FeatureVectorType,
Type: 14,
Violation: hop.IncludedViolation,
},
},
{
name: "no node id or channel id",
data: &record.BlindedRouteData{
Constraints: &record.PaymentConstraints{
MaxCltvExpiry: 100,
HtlcMinimumMsat: 20,
},
RelayInfo: &record.PaymentRelayInfo{},
},
incomingAmount: 40,
incomingTimelock: 80,
err: hop.ErrInvalidPayload{
Type: record.ShortChannelIDType,
Violation: hop.OmittedViolation,
},
},
{
name: "no payment relay",
data: &record.BlindedRouteData{
Constraints: &record.PaymentConstraints{
MaxCltvExpiry: 100,
HtlcMinimumMsat: 20,
},
ShortChannelID: &scid,
},
incomingAmount: 40,
incomingTimelock: 80,
err: hop.ErrInvalidPayload{
Type: record.PaymentRelayType,
Violation: hop.OmittedViolation,
},
},
{
name: "valid data",
data: &record.BlindedRouteData{
Constraints: &record.PaymentConstraints{
MaxCltvExpiry: 100,
HtlcMinimumMsat: 20,
},
RelayInfo: &record.PaymentRelayInfo{
data: record.NewBlindedRouteData(
scid,
nil,
record.PaymentRelayInfo{
CltvExpiryDelta: 10,
FeeRate: 10,
BaseFee: 100,
},
ShortChannelID: &scid,
},
&record.PaymentConstraints{
MaxCltvExpiry: 100,
HtlcMinimumMsat: 20,
},
nil,
),
incomingAmount: 40,
incomingTimelock: 80,
},
Expand Down

0 comments on commit b65fab8

Please sign in to comment.