From 60a856ab658c9045322a1eea8674f027bcd5d78a Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Fri, 26 Jul 2024 10:32:47 +0200 Subject: [PATCH] record/routing: set minimum padding size --- record/blinded_data.go | 7 +++++++ record/blinded_data_test.go | 4 ++++ routing/blindedpath/blinded_path.go | 13 ++++++++----- routing/blindedpath/blinded_path_test.go | 17 +++++++++++++++-- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/record/blinded_data.go b/record/blinded_data.go index a62db00a7..3d9b17c27 100644 --- a/record/blinded_data.go +++ b/record/blinded_data.go @@ -10,6 +10,13 @@ import ( "github.com/lightningnetwork/lnd/tlv" ) +// AverageDummyHopPayloadSize is the size of a standard blinded path dummy hop +// payload. In most cases, this is larger than the other payload types and so +// to make sure that a sender cannot use this fact to know if a dummy hop is +// present or not, we'll make sure to always pad all payloads to at least this +// size. +const AverageDummyHopPayloadSize = 51 + // BlindedRouteData contains the information that is included in a blinded // route encrypted data blob that is created by the recipient to provide // forwarding information. diff --git a/record/blinded_data_test.go b/record/blinded_data_test.go index 604d5a7fb..7de8fa295 100644 --- a/record/blinded_data_test.go +++ b/record/blinded_data_test.go @@ -196,6 +196,10 @@ func TestDummyHopBlindedDataEncoding(t *testing.T) { encoded, err := EncodeBlindedRouteData(routeData) require.NoError(t, err) + // Assert the size of an average dummy hop payload in case we need to + // update this constant in future. + require.Len(t, encoded, AverageDummyHopPayloadSize) + b := bytes.NewBuffer(encoded) decodedData, err := DecodeBlindedRouteData(b) require.NoError(t, err) diff --git a/routing/blindedpath/blinded_path.go b/routing/blindedpath/blinded_path.go index a6e1c0762..03c24747b 100644 --- a/routing/blindedpath/blinded_path.go +++ b/routing/blindedpath/blinded_path.go @@ -257,7 +257,9 @@ func buildBlindedPaymentPath(cfg *BuildBlindedPathCfg, path *candidatePath) ( // Add padding to each route data instance until the encrypted data // blobs are all the same size. - paymentPath, _, err := padHopInfo(hopDataSet, true) + paymentPath, _, err := padHopInfo( + hopDataSet, true, record.AverageDummyHopPayloadSize, + ) if err != nil { return nil, err } @@ -731,9 +733,10 @@ type padStats struct { // edges. The number of iterations that this function takes is also returned for // testing purposes. If prePad is true, then zero byte padding is added to each // payload that does not yet have padding. This will save some iterations for -// the majority of cases. -func padHopInfo(hopInfo []*hopData, prePad bool) ([]*sphinx.HopInfo, *padStats, - error) { +// the majority of cases. minSize can be used to specify a minimum size that all +// payloads should be. +func padHopInfo(hopInfo []*hopData, prePad bool, minSize int) ( + []*sphinx.HopInfo, *padStats, error) { var ( paymentPath = make([]*sphinx.HopInfo, len(hopInfo)) @@ -759,7 +762,7 @@ func padHopInfo(hopInfo []*hopData, prePad bool) ([]*sphinx.HopInfo, *padStats, // current largest encoded data blob size. This will be the // size we aim to get the others to match. var ( - maxLen int + maxLen = minSize minLen = math.MaxInt8 ) for i, hop := range hopInfo { diff --git a/routing/blindedpath/blinded_path_test.go b/routing/blindedpath/blinded_path_test.go index 68a3c90a7..1f5d685d1 100644 --- a/routing/blindedpath/blinded_path_test.go +++ b/routing/blindedpath/blinded_path_test.go @@ -237,6 +237,10 @@ func TestPadBlindedHopInfo(t *testing.T) { // prePad is true if all the hop payloads should be pre-padded // with a zero length TLV Padding field. prePad bool + + // minPayloadSize can be used to set the minimum number of bytes + // that the resulting records should be. + minPayloadSize int }{ { // If there is only one entry, then no padding is @@ -250,6 +254,15 @@ func TestPadBlindedHopInfo(t *testing.T) { // bytes. expectedFinalSize: 12, }, + { + // Same as the above example but with a minimum final + // size specified. + name: "single entry with min size", + expectedIterations: 2, + pathIDs: []int{10}, + minPayloadSize: 500, + expectedFinalSize: 504, + }, { // All the payloads are the same size from the get go // meaning that no padding is expected. @@ -376,7 +389,7 @@ func TestPadBlindedHopInfo(t *testing.T) { } hopInfo, stats, err := padHopInfo( - hopDataSet, test.prePad, + hopDataSet, test.prePad, test.minPayloadSize, ) require.NoError(t, err) require.Equal(t, test.expectedIterations, @@ -400,7 +413,7 @@ func TestPadBlindedHopInfo(t *testing.T) { // asserts that the resulting padded set always has the same encoded length. func TestPadBlindedHopInfoBlackBox(t *testing.T) { fn := func(data hopDataList) bool { - resultList, _, err := padHopInfo(data, true) + resultList, _, err := padHopInfo(data, true, 0) require.NoError(t, err) // There should be a resulting sphinx.HopInfo struct for each