Skip to content

Commit 735d7d9

Browse files
committed
multi: send to a blinded path in an invoice
Update the SendPayment flow so that it is able to send to an invoice containing a blinded path.
1 parent 34d8fff commit 735d7d9

File tree

4 files changed

+104
-2
lines changed

4 files changed

+104
-2
lines changed

lnrpc/routerrpc/router_backend.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,32 @@ func (r *RouterBackend) extractIntentFromSendRequest(
999999
payIntent.PaymentAddr = payAddr
10001000
payIntent.PaymentRequest = []byte(rpcPayReq.PaymentRequest)
10011001
payIntent.Metadata = payReq.Metadata
1002+
1003+
if len(payReq.BlindedPaymentPaths) > 0 {
1004+
// NOTE: Currently we only choose a single payment path.
1005+
// This will be updated in a future PR to handle
1006+
// multiple blinded payment paths.
1007+
path := payReq.BlindedPaymentPaths[0]
1008+
if len(path.Hops) == 0 {
1009+
return nil, fmt.Errorf("a blinded payment " +
1010+
"must have at least 1 hop")
1011+
}
1012+
1013+
finalHop := path.Hops[len(path.Hops)-1]
1014+
1015+
payIntent.BlindedPayment = MarshalBlindedPayment(path)
1016+
1017+
// Replace the target node with the blinded public key
1018+
// of the blinded path's final node.
1019+
copy(
1020+
payIntent.Target[:],
1021+
finalHop.BlindedNodePub.SerializeCompressed(),
1022+
)
1023+
1024+
if !path.Features.IsEmpty() {
1025+
payIntent.DestFeatures = path.Features.Clone()
1026+
}
1027+
}
10021028
} else {
10031029
// Otherwise, If the payment request field was not specified
10041030
// (and a custom route wasn't specified), construct the payment
@@ -1137,6 +1163,26 @@ func (r *RouterBackend) extractIntentFromSendRequest(
11371163
return payIntent, nil
11381164
}
11391165

1166+
// MarshalBlindedPayment marshals a zpay32.BLindedPaymentPath into a
1167+
// routing.BlindedPayment.
1168+
func MarshalBlindedPayment(
1169+
path *zpay32.BlindedPaymentPath) *routing.BlindedPayment {
1170+
1171+
return &routing.BlindedPayment{
1172+
BlindedPath: &sphinx.BlindedPath{
1173+
IntroductionPoint: path.Hops[0].BlindedNodePub,
1174+
BlindingPoint: path.FirstEphemeralBlindingPoint,
1175+
BlindedHops: path.Hops,
1176+
},
1177+
BaseFee: path.FeeBaseMsat,
1178+
ProportionalFeeRate: path.FeeRate,
1179+
CltvExpiryDelta: path.CltvExpiryDelta,
1180+
HtlcMinimum: path.HTLCMinMsat,
1181+
HtlcMaximum: path.HTLCMaxMsat,
1182+
Features: path.Features,
1183+
}
1184+
}
1185+
11401186
// unmarshallRouteHints unmarshalls a list of route hints.
11411187
func unmarshallRouteHints(rpcRouteHints []*lnrpc.RouteHint) (
11421188
[][]zpay32.HopHint, error) {

routing/payment_session.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/btcsuite/btcd/btcec/v2"
77
"github.com/btcsuite/btclog"
8+
sphinx "github.com/lightningnetwork/lightning-onion"
89
"github.com/lightningnetwork/lnd/build"
910
"github.com/lightningnetwork/lnd/channeldb"
1011
"github.com/lightningnetwork/lnd/channeldb/models"
@@ -205,6 +206,18 @@ func newPaymentSession(p *LightningPayment, selfNode route.Vertex,
205206
return nil, err
206207
}
207208

209+
if p.BlindedPayment != nil {
210+
if len(edges) != 0 {
211+
return nil, fmt.Errorf("cannot have both route hints " +
212+
"and blinded path")
213+
}
214+
215+
edges, err = p.BlindedPayment.toRouteHints()
216+
if err != nil {
217+
return nil, err
218+
}
219+
}
220+
208221
logPrefix := fmt.Sprintf("PaymentSession(%x):", p.Identifier())
209222

210223
return &paymentSession{
@@ -389,6 +402,11 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
389402
return nil, err
390403
}
391404

405+
var blindedPath *sphinx.BlindedPath
406+
if p.payment.BlindedPayment != nil {
407+
blindedPath = p.payment.BlindedPayment.BlindedPath
408+
}
409+
392410
// With the next candidate path found, we'll attempt to turn
393411
// this into a route by applying the time-lock and fee
394412
// requirements.
@@ -401,7 +419,7 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
401419
records: p.payment.DestCustomRecords,
402420
paymentAddr: p.payment.PaymentAddr,
403421
metadata: p.payment.Metadata,
404-
}, nil,
422+
}, blindedPath,
405423
)
406424
if err != nil {
407425
return nil, err

routing/router.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -922,9 +922,19 @@ type LightningPayment struct {
922922
// NOTE: This is optional unless required by the payment. When providing
923923
// multiple routes, ensure the hop hints within each route are chained
924924
// together and sorted in forward order in order to reach the
925-
// destination successfully.
925+
// destination successfully. This is mutually exclusive to the
926+
// BlindedPayment field.
926927
RouteHints [][]zpay32.HopHint
927928

929+
// BlindedPayment holds the information about a blinded path to the
930+
// payment recipient. This is mutually exclusive to the RouteHints
931+
// field.
932+
//
933+
// NOTE: a recipient may provide multiple blinded payment paths in the
934+
// same invoice. Currently, LND will only attempt to use the first one.
935+
// A future PR will handle multiple blinded payment paths.
936+
BlindedPayment *BlindedPayment
937+
928938
// OutgoingChannelIDs is the list of channels that are allowed for the
929939
// first hop. If nil, any channel may be used.
930940
OutgoingChannelIDs []uint64

rpcserver.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5110,6 +5110,7 @@ type rpcPaymentIntent struct {
51105110
paymentAddr *[32]byte
51115111
payReq []byte
51125112
metadata []byte
5113+
blindedPayment *routing.BlindedPayment
51135114

51145115
destCustomRecords record.CustomSet
51155116

@@ -5245,6 +5246,32 @@ func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPayme
52455246
payIntent.paymentAddr = payReq.PaymentAddr
52465247
payIntent.metadata = payReq.Metadata
52475248

5249+
if len(payReq.BlindedPaymentPaths) > 0 {
5250+
// NOTE: Currently we only choose a single payment path.
5251+
// This will be updated in a future PR to handle
5252+
// multiple blinded payment paths.
5253+
path := payReq.BlindedPaymentPaths[0]
5254+
if len(path.Hops) == 0 {
5255+
return payIntent, fmt.Errorf("a blinded " +
5256+
"payment must have at least 1 hop")
5257+
}
5258+
5259+
finalHop := path.Hops[len(path.Hops)-1]
5260+
payIntent.blindedPayment =
5261+
routerrpc.MarshalBlindedPayment(path)
5262+
5263+
// Replace the target node with the blinded public key
5264+
// of the blinded path's final node.
5265+
copy(
5266+
payIntent.dest[:],
5267+
finalHop.BlindedNodePub.SerializeCompressed(),
5268+
)
5269+
5270+
if !payReq.BlindedPaymentPaths[0].Features.IsEmpty() {
5271+
payIntent.destFeatures = path.Features.Clone()
5272+
}
5273+
}
5274+
52485275
if err := validateDest(payIntent.dest); err != nil {
52495276
return payIntent, err
52505277
}
@@ -5399,6 +5426,7 @@ func (r *rpcServer) dispatchPaymentIntent(
53995426
DestFeatures: payIntent.destFeatures,
54005427
PaymentAddr: payIntent.paymentAddr,
54015428
Metadata: payIntent.metadata,
5429+
BlindedPayment: payIntent.blindedPayment,
54025430

54035431
// Don't enable multi-part payments on the main rpc.
54045432
// Users need to use routerrpc for that.

0 commit comments

Comments
 (0)