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"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"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
|
||||
// series, all paths will be kept.
|
||||
path := paths[0]
|
||||
// Derive an ephemeral target priv key that will be injected into each
|
||||
// blinded path final hop.
|
||||
targetPriv, err := btcec.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targetPub := targetPriv.PubKey()
|
||||
|
||||
finalHop := path.BlindedPath.
|
||||
BlindedHops[len(path.BlindedPath.BlindedHops)-1]
|
||||
// If any provided blinded path only has a single hop (ie, the
|
||||
// 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{
|
||||
paths: paths,
|
||||
targetPubKey: finalHop.BlindedNodePub,
|
||||
paths: pathSet,
|
||||
targetPubKey: targetPub,
|
||||
features: features,
|
||||
}, nil
|
||||
}
|
||||
@ -144,7 +164,7 @@ func (s *BlindedPaymentPathSet) ToRouteHints() (RouteHints, error) {
|
||||
hints := make(RouteHints)
|
||||
|
||||
for _, path := range s.paths {
|
||||
pathHints, err := path.toRouteHints()
|
||||
pathHints, err := path.toRouteHints(fn.Some(s.targetPubKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -223,8 +243,11 @@ func (b *BlindedPayment) Validate() error {
|
||||
// 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
|
||||
// hints (both for intermediate hops and the final_cltv_delta for the receiving
|
||||
// node).
|
||||
func (b *BlindedPayment) toRouteHints() (RouteHints, error) {
|
||||
// node). The pseudoTarget, if provided, will be used to override the pub key
|
||||
// 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
|
||||
// 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
|
||||
@ -272,12 +295,12 @@ func (b *BlindedPayment) toRouteHints() (RouteHints, error) {
|
||||
ToNodeFeatures: features,
|
||||
}
|
||||
|
||||
edge, err := NewBlindedEdge(edgePolicy, b, 0)
|
||||
lastEdge, err := NewBlindedEdge(edgePolicy, b, 0)
|
||||
if err != nil {
|
||||
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
|
||||
// is the introduction node and terminate at the second-last node
|
||||
@ -304,13 +327,24 @@ func (b *BlindedPayment) toRouteHints() (RouteHints, error) {
|
||||
ToNodeFeatures: features,
|
||||
}
|
||||
|
||||
edge, err := NewBlindedEdge(edgePolicy, b, i)
|
||||
lastEdge, err = NewBlindedEdge(edgePolicy, b, i)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -128,7 +129,7 @@ func TestBlindedPaymentToHints(t *testing.T) {
|
||||
HtlcMaximum: htlcMax,
|
||||
Features: features,
|
||||
}
|
||||
hints, err := blindedPayment.toRouteHints()
|
||||
hints, err := blindedPayment.toRouteHints(fn.None[*btcec.PublicKey]())
|
||||
require.NoError(t, err)
|
||||
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.Equal(t, len(expected), len(actual))
|
||||
|
@ -153,19 +153,24 @@ func newRoute(sourceVertex route.Vertex,
|
||||
// sender of the payment.
|
||||
nextIncomingAmount lnwire.MilliSatoshi
|
||||
|
||||
blindedPath *sphinx.BlindedPath
|
||||
blindedPayment *BlindedPayment
|
||||
)
|
||||
|
||||
if blindedPathSet != nil {
|
||||
blindedPath = blindedPathSet.GetPath().BlindedPath
|
||||
}
|
||||
|
||||
pathLength := len(pathEdges)
|
||||
for i := pathLength - 1; i >= 0; i-- {
|
||||
// Now we'll start to calculate the items within the per-hop
|
||||
// payload for the hop this edge is leading to.
|
||||
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
|
||||
// in the route. The base case is the final hop which includes
|
||||
// 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
|
||||
// where the final hop is the blinded path introduction
|
||||
// node.
|
||||
if blindedPath == nil ||
|
||||
len(blindedPath.BlindedHops) == 1 {
|
||||
if blindedPathSet == nil ||
|
||||
len(blindedPathSet.GetPath().BlindedPath.
|
||||
BlindedHops) == 1 {
|
||||
|
||||
// As this is the last hop, we'll use the
|
||||
// specified final CLTV delta value instead of
|
||||
@ -245,7 +251,7 @@ func newRoute(sourceVertex route.Vertex,
|
||||
|
||||
metadata = finalHop.metadata
|
||||
|
||||
if blindedPath != nil {
|
||||
if blindedPathSet != nil {
|
||||
totalAmtMsatBlinded = finalHop.totalAmt
|
||||
}
|
||||
} 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
|
||||
// additional data to the route that is required for blinded forwarding.
|
||||
// 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 (
|
||||
inBlindedRoute bool
|
||||
dataIndex = 0
|
||||
|
||||
blindedPath = blindedPayment.BlindedPath
|
||||
numHops = len(blindedPath.BlindedHops)
|
||||
realFinal = blindedPath.BlindedHops[numHops-1].
|
||||
BlindedNodePub
|
||||
|
||||
introVertex = route.NewVertex(
|
||||
blindedPath.IntroductionPoint,
|
||||
)
|
||||
@ -337,6 +357,11 @@ func newRoute(sourceVertex route.Vertex,
|
||||
if i != len(hops)-1 {
|
||||
hop.AmtToForward = 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++
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
switchhop "github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"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
|
||||
// map is keyed by source node, so we can retrieve our blinded edges
|
||||
// accordingly.
|
||||
blindedEdges, err := blindedPayment.toRouteHints()
|
||||
blindedEdges, err := blindedPayment.toRouteHints(
|
||||
fn.None[*btcec.PublicKey](),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
carolDaveEdge := blindedEdges[carolVertex][0]
|
||||
|
@ -573,20 +573,7 @@ func getTargetNode(target *route.Vertex,
|
||||
return route.Vertex{}, ErrTargetAndBlinded
|
||||
|
||||
case blinded:
|
||||
blindedPayment := blindedPathSet.GetPath()
|
||||
|
||||
// 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
|
||||
return route.NewVertex(blindedPathSet.TargetPubKey()), nil
|
||||
|
||||
case targetSet:
|
||||
return *target, nil
|
||||
|
@ -2231,7 +2231,10 @@ func TestNewRouteRequest(t *testing.T) {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var blindedPathInfo *BlindedPaymentPathSet
|
||||
var (
|
||||
blindedPathInfo *BlindedPaymentPathSet
|
||||
expectedTarget = testCase.expectedTarget
|
||||
)
|
||||
if testCase.blindedPayment != nil {
|
||||
blindedPathInfo, err = NewBlindedPaymentPathSet(
|
||||
[]*BlindedPayment{
|
||||
@ -2239,6 +2242,10 @@ func TestNewRouteRequest(t *testing.T) {
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedTarget = route.NewVertex(
|
||||
blindedPathInfo.TargetPubKey(),
|
||||
)
|
||||
}
|
||||
|
||||
req, err := NewRouteRequest(
|
||||
@ -2253,7 +2260,7 @@ func TestNewRouteRequest(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(t, req.Target, testCase.expectedTarget)
|
||||
require.Equal(t, req.Target, expectedTarget)
|
||||
require.Equal(
|
||||
t, req.FinalExpiry, testCase.expectedCltv,
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user