lnd/record/blinded_data_test.go
Elle Mouton 9ada4a9068
record: add Padding field to BlindedRouteData
When we start creating blinded paths to ourselves, we will want to be
able to pad the data for each hop so that the `encrypted_recipient_data`
for each hop is the same. We add a `PadBy` method that allows a caller
to add a certain number of bytes to the padding field. Note that adding
n bytes won't always mean that the encoded payload will increase by size
n since there will be overhead for the type and lenght fields for the new
TLV field. This will also be the case when the number of bytes added
results in a BigSize bucket jump for TLV length field. The
responsibility of ensuring that the final payloads are the same size is
left to the caller who may need to call PadBy iteratively to achieve the
goal. I decided to leave this to the caller since doing this at the
actual TLV level will be quite intrusive & I think it is uneccessary to
touch that code for this unique use case.
2024-07-10 09:12:40 +02:00

328 lines
7.6 KiB
Go

package record
import (
"bytes"
"encoding/hex"
"fmt"
"math"
"testing"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
//nolint:lll
const pubkeyStr = "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"
func pubkey(t *testing.T) *btcec.PublicKey {
t.Helper()
nodeBytes, err := hex.DecodeString(pubkeyStr)
require.NoError(t, err)
nodePk, err := btcec.ParsePubKey(nodeBytes)
require.NoError(t, err)
return nodePk
}
// TestBlindedDataEncoding tests encoding and decoding of blinded data blobs.
// These tests specifically cover cases where the variable length encoded
// integers values have different numbers of leading zeros trimmed because
// these TLVs are the first composite records with variable length tlvs
// (previously, a variable length integer would take up the whole record).
func TestBlindedDataEncoding(t *testing.T) {
t.Parallel()
tests := []struct {
name string
baseFee uint32
htlcMin lnwire.MilliSatoshi
features *lnwire.FeatureVector
constraints bool
}{
{
name: "zero variable values",
baseFee: 0,
htlcMin: 0,
},
{
name: "zeros trimmed",
baseFee: math.MaxUint32 / 2,
htlcMin: math.MaxUint64 / 2,
},
{
name: "no zeros trimmed",
baseFee: math.MaxUint32,
htlcMin: math.MaxUint64,
},
{
name: "nil feature vector",
features: nil,
},
{
name: "non-nil, but empty feature vector",
features: lnwire.EmptyFeatureVector(),
},
{
name: "populated feature vector",
features: lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(lnwire.AMPOptional),
lnwire.Features,
),
},
{
name: "no payment constraints",
constraints: true,
},
}
for _, testCase := range tests {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
// Create a standard set of blinded route data, using
// the values from our test case for the variable
// length encoded values.
channelID := lnwire.NewShortChanIDFromInt(1)
info := PaymentRelayInfo{
FeeRate: 2,
CltvExpiryDelta: 3,
BaseFee: testCase.baseFee,
}
var constraints *PaymentConstraints
if testCase.constraints {
constraints = &PaymentConstraints{
MaxCltvExpiry: 4,
HtlcMinimumMsat: testCase.htlcMin,
}
}
encodedData := NewNonFinalBlindedRouteData(
channelID, pubkey(t), info, constraints,
testCase.features,
)
encoded, err := EncodeBlindedRouteData(encodedData)
require.NoError(t, err)
b := bytes.NewBuffer(encoded)
decodedData, err := DecodeBlindedRouteData(b)
require.NoError(t, err)
require.Equal(t, encodedData, decodedData)
})
}
}
// TestBlindedDataFinalHopEncoding tests the encoding and decoding of a blinded
// data blob intended for the final hop of a blinded path where only the pathID
// will potentially be set.
func TestBlindedDataFinalHopEncoding(t *testing.T) {
tests := []struct {
name string
pathID []byte
constraints bool
}{
{
name: "with path ID",
pathID: []byte{1, 2, 3, 4, 5, 6},
},
{
name: "with no path ID",
pathID: nil,
},
{
name: "with path ID and constraints",
pathID: []byte{1, 2, 3, 4, 5, 6},
constraints: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
var constraints *PaymentConstraints
if test.constraints {
constraints = &PaymentConstraints{
MaxCltvExpiry: 4,
HtlcMinimumMsat: 5,
}
}
encodedData := NewFinalHopBlindedRouteData(
constraints, test.pathID,
)
encoded, err := EncodeBlindedRouteData(encodedData)
require.NoError(t, err)
b := bytes.NewBuffer(encoded)
decodedData, err := DecodeBlindedRouteData(b)
require.NoError(t, err)
require.Equal(t, encodedData, decodedData)
})
}
}
// TestBlindedRouteDataPadding tests the PadBy method of BlindedRouteData.
func TestBlindedRouteDataPadding(t *testing.T) {
newBlindedRouteData := func() *BlindedRouteData {
channelID := lnwire.NewShortChanIDFromInt(1)
info := PaymentRelayInfo{
FeeRate: 2,
CltvExpiryDelta: 3,
BaseFee: 30,
}
constraints := &PaymentConstraints{
MaxCltvExpiry: 4,
HtlcMinimumMsat: 100,
}
return NewNonFinalBlindedRouteData(
channelID, pubkey(t), info, constraints, nil,
)
}
tests := []struct {
name string
paddingSize int
expectedSizeIncrease uint64
}{
{
// Calling PadBy with an n value of 0 in the case where
// there is not yet a padding field will result in a
// zero length TLV entry being added. This will add 2
// bytes for the type and length fields.
name: "no extra padding",
expectedSizeIncrease: 2,
},
{
name: "small padding (length " +
"field of 1 byte)",
paddingSize: 200,
expectedSizeIncrease: 202,
},
{
name: "medium padding (length field " +
"of 3 bytes)",
paddingSize: 256,
expectedSizeIncrease: 260,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
data := newBlindedRouteData()
prePaddingEncoding, err := EncodeBlindedRouteData(data)
require.NoError(t, err)
data.PadBy(test.paddingSize)
postPaddingEncoding, err := EncodeBlindedRouteData(data)
require.NoError(t, err)
require.EqualValues(
t, test.expectedSizeIncrease,
len(postPaddingEncoding)-
len(prePaddingEncoding),
)
})
}
}
// TestBlindedRouteVectors tests encoding/decoding of the test vectors for
// blinded route data provided in the specification.
//
//nolint:lll
func TestBlindingSpecTestVectors(t *testing.T) {
nextBlindingOverrideStr, err := hex.DecodeString("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")
require.NoError(t, err)
nextBlindingOverride, err := btcec.ParsePubKey(nextBlindingOverrideStr)
require.NoError(t, err)
tests := []struct {
encoded string
expectedPaymentData *BlindedRouteData
expectedPadding int
}{
{
encoded: "011a0000000000000000000000000000000000000000000000000000020800000000000006c10a0800240000009627100c06000b69e505dc0e00fd023103123456",
expectedPaymentData: NewNonFinalBlindedRouteData(
lnwire.ShortChannelID{
BlockHeight: 0,
TxIndex: 0,
TxPosition: 1729,
},
nil,
PaymentRelayInfo{
CltvExpiryDelta: 36,
FeeRate: 150,
BaseFee: 10000,
},
&PaymentConstraints{
MaxCltvExpiry: 748005,
HtlcMinimumMsat: 1500,
},
lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(),
lnwire.Features,
),
),
expectedPadding: 26,
},
{
encoded: "020800000000000004510821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0a0800300000006401f40c06000b69c105dc0e00",
expectedPaymentData: NewNonFinalBlindedRouteData(
lnwire.ShortChannelID{
TxPosition: 1105,
},
nextBlindingOverride,
PaymentRelayInfo{
CltvExpiryDelta: 48,
FeeRate: 100,
BaseFee: 500,
},
&PaymentConstraints{
MaxCltvExpiry: 747969,
HtlcMinimumMsat: 1500,
},
lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(),
lnwire.Features,
),
),
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
route, err := hex.DecodeString(test.encoded)
require.NoError(t, err)
buff := bytes.NewBuffer(route)
decodedRoute, err := DecodeBlindedRouteData(buff)
require.NoError(t, err)
if test.expectedPadding != 0 {
test.expectedPaymentData.PadBy(
test.expectedPadding,
)
}
require.Equal(
t, test.expectedPaymentData, decodedRoute,
)
})
}
}