mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
routing: swap out final hop blinded route pub keys
If multiple blinded paths are provided, they will each have a different pub key for the destination node. This makes using our existing pathfinding logic tricky since it depends on having a single destination node (characterised by a single pub key). We want to re-use this logic. So what we do is swap out the pub keys of the destinaion hop with a pseudo target pub key. This will then be used during pathfinding. Later on once a path is found, we will swap the real destination keys back in so that onion creation can be done.
This commit is contained in:
parent
4a22ec8413
commit
8df03de3e9
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
)
|
)
|
||||||
@ -86,16 +87,35 @@ func NewBlindedPaymentPathSet(paths []*BlindedPayment) (*BlindedPaymentPathSet,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: for now, we just take a single path. By the end of this PR
|
// Derive an ephemeral target priv key that will be injected into each
|
||||||
// series, all paths will be kept.
|
// blinded path final hop.
|
||||||
path := paths[0]
|
targetPriv, err := btcec.NewPrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
targetPub := targetPriv.PubKey()
|
||||||
|
|
||||||
finalHop := path.BlindedPath.
|
// If any provided blinded path only has a single hop (ie, the
|
||||||
BlindedHops[len(path.BlindedPath.BlindedHops)-1]
|
// destination node is also the introduction node), then we discard all
|
||||||
|
// other paths since we know the real pub key of the destination node.
|
||||||
|
// For a single hop path, there is also no need for the pseudo target
|
||||||
|
// pub key replacement, so our target pub key in this case just remains
|
||||||
|
// the real introduction node ID.
|
||||||
|
var pathSet = paths
|
||||||
|
for _, path := range paths {
|
||||||
|
if len(path.BlindedPath.BlindedHops) != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pathSet = []*BlindedPayment{path}
|
||||||
|
targetPub = path.BlindedPath.IntroductionPoint
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
return &BlindedPaymentPathSet{
|
return &BlindedPaymentPathSet{
|
||||||
paths: paths,
|
paths: pathSet,
|
||||||
targetPubKey: finalHop.BlindedNodePub,
|
targetPubKey: targetPub,
|
||||||
features: features,
|
features: features,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -144,7 +164,7 @@ func (s *BlindedPaymentPathSet) ToRouteHints() (RouteHints, error) {
|
|||||||
hints := make(RouteHints)
|
hints := make(RouteHints)
|
||||||
|
|
||||||
for _, path := range s.paths {
|
for _, path := range s.paths {
|
||||||
pathHints, err := path.toRouteHints()
|
pathHints, err := path.toRouteHints(fn.Some(s.targetPubKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -223,8 +243,11 @@ func (b *BlindedPayment) Validate() error {
|
|||||||
// effectively the final_cltv_delta for the receiving introduction node). In
|
// effectively the final_cltv_delta for the receiving introduction node). In
|
||||||
// the case of multiple blinded hops, CLTV delta is fully accounted for in the
|
// the case of multiple blinded hops, CLTV delta is fully accounted for in the
|
||||||
// hints (both for intermediate hops and the final_cltv_delta for the receiving
|
// hints (both for intermediate hops and the final_cltv_delta for the receiving
|
||||||
// node).
|
// node). The pseudoTarget, if provided, will be used to override the pub key
|
||||||
func (b *BlindedPayment) toRouteHints() (RouteHints, error) {
|
// of the destination node in the path.
|
||||||
|
func (b *BlindedPayment) toRouteHints(
|
||||||
|
pseudoTarget fn.Option[*btcec.PublicKey]) (RouteHints, error) {
|
||||||
|
|
||||||
// If we just have a single hop in our blinded route, it just contains
|
// If we just have a single hop in our blinded route, it just contains
|
||||||
// an introduction node (this is a valid path according to the spec).
|
// an introduction node (this is a valid path according to the spec).
|
||||||
// Since we have the un-blinded node ID for the introduction node, we
|
// Since we have the un-blinded node ID for the introduction node, we
|
||||||
@ -272,12 +295,12 @@ func (b *BlindedPayment) toRouteHints() (RouteHints, error) {
|
|||||||
ToNodeFeatures: features,
|
ToNodeFeatures: features,
|
||||||
}
|
}
|
||||||
|
|
||||||
edge, err := NewBlindedEdge(edgePolicy, b, 0)
|
lastEdge, err := NewBlindedEdge(edgePolicy, b, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hints[fromNode] = []AdditionalEdge{edge}
|
hints[fromNode] = []AdditionalEdge{lastEdge}
|
||||||
|
|
||||||
// Start at an offset of 1 because the first node in our blinded hops
|
// Start at an offset of 1 because the first node in our blinded hops
|
||||||
// is the introduction node and terminate at the second-last node
|
// is the introduction node and terminate at the second-last node
|
||||||
@ -304,13 +327,24 @@ func (b *BlindedPayment) toRouteHints() (RouteHints, error) {
|
|||||||
ToNodeFeatures: features,
|
ToNodeFeatures: features,
|
||||||
}
|
}
|
||||||
|
|
||||||
edge, err := NewBlindedEdge(edgePolicy, b, i)
|
lastEdge, err = NewBlindedEdge(edgePolicy, b, i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hints[fromNode] = []AdditionalEdge{edge}
|
hints[fromNode] = []AdditionalEdge{lastEdge}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pseudoTarget.WhenSome(func(key *btcec.PublicKey) {
|
||||||
|
// For the very last hop on the path, switch out the ToNodePub
|
||||||
|
// for the pseudo target pub key.
|
||||||
|
lastEdge.policy.ToNodePubKey = func() route.Vertex {
|
||||||
|
return route.NewVertex(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then override the final hint with this updated edge.
|
||||||
|
hints[fromNode] = []AdditionalEdge{lastEdge}
|
||||||
|
})
|
||||||
|
|
||||||
return hints, nil
|
return hints, nil
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/routing/route"
|
"github.com/lightningnetwork/lnd/routing/route"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -128,7 +129,7 @@ func TestBlindedPaymentToHints(t *testing.T) {
|
|||||||
HtlcMaximum: htlcMax,
|
HtlcMaximum: htlcMax,
|
||||||
Features: features,
|
Features: features,
|
||||||
}
|
}
|
||||||
hints, err := blindedPayment.toRouteHints()
|
hints, err := blindedPayment.toRouteHints(fn.None[*btcec.PublicKey]())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Nil(t, hints)
|
require.Nil(t, hints)
|
||||||
|
|
||||||
@ -183,7 +184,7 @@ func TestBlindedPaymentToHints(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
actual, err := blindedPayment.toRouteHints()
|
actual, err := blindedPayment.toRouteHints(fn.None[*btcec.PublicKey]())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, len(expected), len(actual))
|
require.Equal(t, len(expected), len(actual))
|
||||||
|
@ -153,19 +153,24 @@ func newRoute(sourceVertex route.Vertex,
|
|||||||
// sender of the payment.
|
// sender of the payment.
|
||||||
nextIncomingAmount lnwire.MilliSatoshi
|
nextIncomingAmount lnwire.MilliSatoshi
|
||||||
|
|
||||||
blindedPath *sphinx.BlindedPath
|
blindedPayment *BlindedPayment
|
||||||
)
|
)
|
||||||
|
|
||||||
if blindedPathSet != nil {
|
|
||||||
blindedPath = blindedPathSet.GetPath().BlindedPath
|
|
||||||
}
|
|
||||||
|
|
||||||
pathLength := len(pathEdges)
|
pathLength := len(pathEdges)
|
||||||
for i := pathLength - 1; i >= 0; i-- {
|
for i := pathLength - 1; i >= 0; i-- {
|
||||||
// Now we'll start to calculate the items within the per-hop
|
// Now we'll start to calculate the items within the per-hop
|
||||||
// payload for the hop this edge is leading to.
|
// payload for the hop this edge is leading to.
|
||||||
edge := pathEdges[i].policy
|
edge := pathEdges[i].policy
|
||||||
|
|
||||||
|
// If this is an edge from a blinded path and the
|
||||||
|
// blindedPayment variable has not been set yet, then set it now
|
||||||
|
// by extracting the corresponding blinded payment from the
|
||||||
|
// edge.
|
||||||
|
isBlindedEdge := pathEdges[i].blindedPayment != nil
|
||||||
|
if isBlindedEdge && blindedPayment == nil {
|
||||||
|
blindedPayment = pathEdges[i].blindedPayment
|
||||||
|
}
|
||||||
|
|
||||||
// We'll calculate the amounts, timelocks, and fees for each hop
|
// We'll calculate the amounts, timelocks, and fees for each hop
|
||||||
// in the route. The base case is the final hop which includes
|
// in the route. The base case is the final hop which includes
|
||||||
// their amount and timelocks. These values will accumulate
|
// their amount and timelocks. These values will accumulate
|
||||||
@ -212,8 +217,9 @@ func newRoute(sourceVertex route.Vertex,
|
|||||||
// node's CLTV delta. The exception is for the case
|
// node's CLTV delta. The exception is for the case
|
||||||
// where the final hop is the blinded path introduction
|
// where the final hop is the blinded path introduction
|
||||||
// node.
|
// node.
|
||||||
if blindedPath == nil ||
|
if blindedPathSet == nil ||
|
||||||
len(blindedPath.BlindedHops) == 1 {
|
len(blindedPathSet.GetPath().BlindedPath.
|
||||||
|
BlindedHops) == 1 {
|
||||||
|
|
||||||
// As this is the last hop, we'll use the
|
// As this is the last hop, we'll use the
|
||||||
// specified final CLTV delta value instead of
|
// specified final CLTV delta value instead of
|
||||||
@ -245,7 +251,7 @@ func newRoute(sourceVertex route.Vertex,
|
|||||||
|
|
||||||
metadata = finalHop.metadata
|
metadata = finalHop.metadata
|
||||||
|
|
||||||
if blindedPath != nil {
|
if blindedPathSet != nil {
|
||||||
totalAmtMsatBlinded = finalHop.totalAmt
|
totalAmtMsatBlinded = finalHop.totalAmt
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -305,11 +311,25 @@ func newRoute(sourceVertex route.Vertex,
|
|||||||
// If we are creating a route to a blinded path, we need to add some
|
// If we are creating a route to a blinded path, we need to add some
|
||||||
// additional data to the route that is required for blinded forwarding.
|
// additional data to the route that is required for blinded forwarding.
|
||||||
// We do another pass on our edges to append this data.
|
// We do another pass on our edges to append this data.
|
||||||
if blindedPath != nil {
|
if blindedPathSet != nil {
|
||||||
|
// If the passed in BlindedPaymentPathSet is non-nil but no
|
||||||
|
// edge had a BlindedPayment attached, it means that the path
|
||||||
|
// chosen was an introduction-node-only path. So in this case,
|
||||||
|
// we can assume the relevant payment is the only one in the
|
||||||
|
// payment set.
|
||||||
|
if blindedPayment == nil {
|
||||||
|
blindedPayment = blindedPathSet.GetPath()
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
inBlindedRoute bool
|
inBlindedRoute bool
|
||||||
dataIndex = 0
|
dataIndex = 0
|
||||||
|
|
||||||
|
blindedPath = blindedPayment.BlindedPath
|
||||||
|
numHops = len(blindedPath.BlindedHops)
|
||||||
|
realFinal = blindedPath.BlindedHops[numHops-1].
|
||||||
|
BlindedNodePub
|
||||||
|
|
||||||
introVertex = route.NewVertex(
|
introVertex = route.NewVertex(
|
||||||
blindedPath.IntroductionPoint,
|
blindedPath.IntroductionPoint,
|
||||||
)
|
)
|
||||||
@ -337,6 +357,11 @@ func newRoute(sourceVertex route.Vertex,
|
|||||||
if i != len(hops)-1 {
|
if i != len(hops)-1 {
|
||||||
hop.AmtToForward = 0
|
hop.AmtToForward = 0
|
||||||
hop.OutgoingTimeLock = 0
|
hop.OutgoingTimeLock = 0
|
||||||
|
} else {
|
||||||
|
// For the final hop, we swap out the pub key
|
||||||
|
// bytes to the original destination node pub
|
||||||
|
// key for that payment path.
|
||||||
|
hop.PubKeyBytes = route.NewVertex(realFinal)
|
||||||
}
|
}
|
||||||
|
|
||||||
dataIndex++
|
dataIndex++
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||||
switchhop "github.com/lightningnetwork/lnd/htlcswitch/hop"
|
switchhop "github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
@ -3286,7 +3287,9 @@ func TestBlindedRouteConstruction(t *testing.T) {
|
|||||||
// that make up the graph we'll give to route construction. The hints
|
// that make up the graph we'll give to route construction. The hints
|
||||||
// map is keyed by source node, so we can retrieve our blinded edges
|
// map is keyed by source node, so we can retrieve our blinded edges
|
||||||
// accordingly.
|
// accordingly.
|
||||||
blindedEdges, err := blindedPayment.toRouteHints()
|
blindedEdges, err := blindedPayment.toRouteHints(
|
||||||
|
fn.None[*btcec.PublicKey](),
|
||||||
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
carolDaveEdge := blindedEdges[carolVertex][0]
|
carolDaveEdge := blindedEdges[carolVertex][0]
|
||||||
|
@ -573,20 +573,7 @@ func getTargetNode(target *route.Vertex,
|
|||||||
return route.Vertex{}, ErrTargetAndBlinded
|
return route.Vertex{}, ErrTargetAndBlinded
|
||||||
|
|
||||||
case blinded:
|
case blinded:
|
||||||
blindedPayment := blindedPathSet.GetPath()
|
return route.NewVertex(blindedPathSet.TargetPubKey()), nil
|
||||||
|
|
||||||
// If we're dealing with an edge-case blinded path that just
|
|
||||||
// has an introduction node (first hop expected to be the intro
|
|
||||||
// hop), then we return the unblinded introduction node as our
|
|
||||||
// target.
|
|
||||||
hops := blindedPayment.BlindedPath.BlindedHops
|
|
||||||
if len(hops) == 1 {
|
|
||||||
return route.NewVertex(
|
|
||||||
blindedPayment.BlindedPath.IntroductionPoint,
|
|
||||||
), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return route.NewVertex(hops[len(hops)-1].BlindedNodePub), nil
|
|
||||||
|
|
||||||
case targetSet:
|
case targetSet:
|
||||||
return *target, nil
|
return *target, nil
|
||||||
|
@ -2231,7 +2231,10 @@ func TestNewRouteRequest(t *testing.T) {
|
|||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
var blindedPathInfo *BlindedPaymentPathSet
|
var (
|
||||||
|
blindedPathInfo *BlindedPaymentPathSet
|
||||||
|
expectedTarget = testCase.expectedTarget
|
||||||
|
)
|
||||||
if testCase.blindedPayment != nil {
|
if testCase.blindedPayment != nil {
|
||||||
blindedPathInfo, err = NewBlindedPaymentPathSet(
|
blindedPathInfo, err = NewBlindedPaymentPathSet(
|
||||||
[]*BlindedPayment{
|
[]*BlindedPayment{
|
||||||
@ -2239,6 +2242,10 @@ func TestNewRouteRequest(t *testing.T) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expectedTarget = route.NewVertex(
|
||||||
|
blindedPathInfo.TargetPubKey(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := NewRouteRequest(
|
req, err := NewRouteRequest(
|
||||||
@ -2253,7 +2260,7 @@ func TestNewRouteRequest(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
require.Equal(t, req.Target, testCase.expectedTarget)
|
require.Equal(t, req.Target, expectedTarget)
|
||||||
require.Equal(
|
require.Equal(
|
||||||
t, req.FinalExpiry, testCase.expectedCltv,
|
t, req.FinalExpiry, testCase.expectedCltv,
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user