Merge pull request #8752 from ellemouton/rb-receives-setup

[1/4] Route Blinding Receives: Groundwork
This commit is contained in:
Olaoluwa Osuntokun authored and GitHub committed 2024-07-10 18:27:11 -07:00
commit f464dac1cd
27 files changed
+1267 -459

No files matched your search

+14 -2
View File
@@ -1099,8 +1099,13 @@ var queryRoutesCommand = cli.Command{
}, },
cli.Int64Flag{ cli.Int64Flag{
Name: "final_cltv_delta", Name: "final_cltv_delta",
Usage: "(optional) number of blocks the last hop has to reveal " + Usage: "(optional) number of blocks the last hop has " +
"the preimage", "to reveal the preimage. Note that this " +
"should not be set in the case where the " +
"path includes a blinded path since in " +
"that case, the receiver will already have " +
"accounted for this value in the " +
"blinded_cltv value",
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "use_mc", Name: "use_mc",
@@ -1238,6 +1243,13 @@ func parseBlindedPaymentParameters(ctx *cli.Context) (
return nil, nil return nil, nil
} }
// If a blinded path has been provided, then the final_cltv_delta flag
// should not be provided since this value will be ignored.
if ctx.IsSet("final_cltv_delta") {
return nil, fmt.Errorf("`final_cltv_delta` should not be " +
"provided if a blinded path is provided")
}
// If any one of our blinding related flags is set, we expect the // If any one of our blinding related flags is set, we expect the
// full set to be set and we'll error out accordingly. // full set to be set and we'll error out accordingly.
introNode, err := route.NewVertexFromStr( introNode, err := route.NewVertexFromStr(
@@ -89,6 +89,9 @@
channel. We will still wait for the channel to have at least one confirmation channel. We will still wait for the channel to have at least one confirmation
and so the main change here is that we don't error out for such a case. and so the main change here is that we don't error out for such a case.
* [Groundwork](https://github.com/lightningnetwork/lnd/pull/8752) in preparation
for implementing route blinding receives.
## Testing ## Testing
## Database ## Database
+27 -4
View File
@@ -360,6 +360,7 @@ func (b *BlindingKit) DecryptAndValidateFwdInfo(payload *Payload,
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Validate the data in the blinded route against our incoming htlc's // Validate the data in the blinded route against our incoming htlc's
// information. // information.
if err := ValidateBlindedRouteData( if err := ValidateBlindedRouteData(
@@ -368,9 +369,31 @@ func (b *BlindingKit) DecryptAndValidateFwdInfo(payload *Payload,
return nil, err return nil, err
} }
// Exit early if this onion is for the exit hop of the route since
// route blinding receives are not yet supported.
if isFinalHop {
return nil, fmt.Errorf("being the final hop in a blinded " +
"path is not yet supported")
}
// At this point, we know we are a forwarding node for this onion
// and so we expect the relay info and next SCID fields to be set.
relayInfo, err := routeData.RelayInfo.UnwrapOrErr(
fmt.Errorf("relay info not set for non-final blinded hop"),
)
if err != nil {
return nil, err
}
nextSCID, err := routeData.ShortChannelID.UnwrapOrErr(
fmt.Errorf("next SCID not set for non-final blinded hop"),
)
if err != nil {
return nil, err
}
fwdAmt, err := calculateForwardingAmount( fwdAmt, err := calculateForwardingAmount(
b.IncomingAmount, routeData.RelayInfo.Val.BaseFee, b.IncomingAmount, relayInfo.Val.BaseFee, relayInfo.Val.FeeRate,
routeData.RelayInfo.Val.FeeRate,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -400,10 +423,10 @@ func (b *BlindingKit) DecryptAndValidateFwdInfo(payload *Payload,
} }
return &ForwardingInfo{ return &ForwardingInfo{
NextHop: routeData.ShortChannelID.Val, NextHop: nextSCID.Val,
AmountToForward: fwdAmt, AmountToForward: fwdAmt,
OutgoingCTLV: b.IncomingCltv - uint32( OutgoingCTLV: b.IncomingCltv - uint32(
routeData.RelayInfo.Val.CltvExpiryDelta, relayInfo.Val.CltvExpiryDelta,
), ),
// Remap from blinding override type to blinding point type. // Remap from blinding override type to blinding point type.
NextBlinding: tlv.SomeRecordT( NextBlinding: tlv.SomeRecordT(
+1 -1
View File
@@ -186,7 +186,7 @@ func TestDecryptAndValidateFwdInfo(t *testing.T) {
// Encode valid blinding data that we'll fake decrypting for our test. // Encode valid blinding data that we'll fake decrypting for our test.
maxCltv := 1000 maxCltv := 1000
blindedData := record.NewBlindedRouteData( blindedData := record.NewNonFinalBlindedRouteData(
lnwire.NewShortChanIDFromInt(1500), nil, lnwire.NewShortChanIDFromInt(1500), nil,
record.PaymentRelayInfo{ record.PaymentRelayInfo{
CltvExpiryDelta: 10, CltvExpiryDelta: 10,
+6 -6
View File
@@ -646,7 +646,7 @@ func TestValidateBlindedRouteData(t *testing.T) {
}{ }{
{ {
name: "max cltv expired", name: "max cltv expired",
data: record.NewBlindedRouteData( data: record.NewNonFinalBlindedRouteData(
scid, scid,
nil, nil,
record.PaymentRelayInfo{}, record.PaymentRelayInfo{},
@@ -663,7 +663,7 @@ func TestValidateBlindedRouteData(t *testing.T) {
}, },
{ {
name: "zero max cltv", name: "zero max cltv",
data: record.NewBlindedRouteData( data: record.NewNonFinalBlindedRouteData(
scid, scid,
nil, nil,
record.PaymentRelayInfo{}, record.PaymentRelayInfo{},
@@ -682,7 +682,7 @@ func TestValidateBlindedRouteData(t *testing.T) {
}, },
{ {
name: "amount below minimum", name: "amount below minimum",
data: record.NewBlindedRouteData( data: record.NewNonFinalBlindedRouteData(
scid, scid,
nil, nil,
record.PaymentRelayInfo{}, record.PaymentRelayInfo{},
@@ -699,7 +699,7 @@ func TestValidateBlindedRouteData(t *testing.T) {
}, },
{ {
name: "valid, no features", name: "valid, no features",
data: record.NewBlindedRouteData( data: record.NewNonFinalBlindedRouteData(
scid, scid,
nil, nil,
record.PaymentRelayInfo{}, record.PaymentRelayInfo{},
@@ -714,7 +714,7 @@ func TestValidateBlindedRouteData(t *testing.T) {
}, },
{ {
name: "unknown features", name: "unknown features",
data: record.NewBlindedRouteData( data: record.NewNonFinalBlindedRouteData(
scid, scid,
nil, nil,
record.PaymentRelayInfo{}, record.PaymentRelayInfo{},
@@ -738,7 +738,7 @@ func TestValidateBlindedRouteData(t *testing.T) {
}, },
{ {
name: "valid data", name: "valid data",
data: record.NewBlindedRouteData( data: record.NewNonFinalBlindedRouteData(
scid, scid,
nil, nil,
record.PaymentRelayInfo{ record.PaymentRelayInfo{
+2 -2
View File
@@ -676,7 +676,7 @@ func (b *blindedForwardTest) createBlindedRoute(hops []*forwardingEdge,
// Encode the route's blinded data and include it in the // Encode the route's blinded data and include it in the
// blinded hop. // blinded hop.
payload := record.NewBlindedRouteData( payload := record.NewNonFinalBlindedRouteData(
scid, nil, *relayInfo, constraints, nil, scid, nil, *relayInfo, constraints, nil,
) )
payloadBytes, err := record.EncodeBlindedRouteData(payload) payloadBytes, err := record.EncodeBlindedRouteData(payload)
@@ -739,7 +739,7 @@ func (b *blindedForwardTest) createBlindedRoute(hops []*forwardingEdge,
// node ID here so that it _looks like_ a valid // node ID here so that it _looks like_ a valid
// forwarding hop (though in reality it's the last // forwarding hop (though in reality it's the last
// hop). // hop).
record.NewBlindedRouteData( record.NewNonFinalBlindedRouteData(
lnwire.NewShortChanIDFromInt(100), nil, lnwire.NewShortChanIDFromInt(100), nil,
record.PaymentRelayInfo{}, nil, nil, record.PaymentRelayInfo{}, nil, nil,
), ),
+54
View File
@@ -266,6 +266,60 @@ func CreateRPCRouteHints(routeHints [][]zpay32.HopHint) []*lnrpc.RouteHint {
return res return res
} }
// CreateRPCBlindedPayments takes a set of zpay32.BlindedPaymentPath and
// converts them into a set of lnrpc.BlindedPaymentPaths.
func CreateRPCBlindedPayments(blindedPaths []*zpay32.BlindedPaymentPath) (
[]*lnrpc.BlindedPaymentPath, error) {
var res []*lnrpc.BlindedPaymentPath
for _, path := range blindedPaths {
features := path.Features.Features()
var featuresSlice []lnrpc.FeatureBit
for feature := range features {
featuresSlice = append(
featuresSlice, lnrpc.FeatureBit(feature),
)
}
if len(path.Hops) == 0 {
return nil, fmt.Errorf("each blinded path must " +
"contain at least one hop")
}
var hops []*lnrpc.BlindedHop
for _, hop := range path.Hops {
blindedNodeID := hop.BlindedNodePub.
SerializeCompressed()
hops = append(hops, &lnrpc.BlindedHop{
BlindedNode: blindedNodeID,
EncryptedData: hop.CipherText,
})
}
introNode := path.Hops[0].BlindedNodePub
firstBlindingPoint := path.FirstEphemeralBlindingPoint
blindedPath := &lnrpc.BlindedPath{
IntroductionNode: introNode.SerializeCompressed(),
BlindingPoint: firstBlindingPoint.
SerializeCompressed(),
BlindedHops: hops,
}
res = append(res, &lnrpc.BlindedPaymentPath{
BlindedPath: blindedPath,
BaseFeeMsat: uint64(path.FeeBaseMsat),
ProportionalFeeRate: path.FeeRate,
TotalCltvDelta: uint32(path.CltvExpiryDelta),
HtlcMinMsat: path.HTLCMinMsat,
HtlcMaxMsat: path.HTLCMaxMsat,
Features: featuresSlice,
})
}
return res, nil
}
// CreateZpay32HopHints takes in the lnrpc form of route hints and converts them // CreateZpay32HopHints takes in the lnrpc form of route hints and converts them
// into an invoice decoded form. // into an invoice decoded form.
func CreateZpay32HopHints(routeHints []*lnrpc.RouteHint) ([][]zpay32.HopHint, error) { func CreateZpay32HopHints(routeHints []*lnrpc.RouteHint) ([][]zpay32.HopHint, error) {
+214 -201
View File
@@ -14304,19 +14304,20 @@ type PayReq struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
Destination string `protobuf:"bytes,1,opt,name=destination,proto3" json:"destination,omitempty"` Destination string `protobuf:"bytes,1,opt,name=destination,proto3" json:"destination,omitempty"`
PaymentHash string `protobuf:"bytes,2,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` PaymentHash string `protobuf:"bytes,2,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"`
NumSatoshis int64 `protobuf:"varint,3,opt,name=num_satoshis,json=numSatoshis,proto3" json:"num_satoshis,omitempty"` NumSatoshis int64 `protobuf:"varint,3,opt,name=num_satoshis,json=numSatoshis,proto3" json:"num_satoshis,omitempty"`
Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Timestamp int64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
Expiry int64 `protobuf:"varint,5,opt,name=expiry,proto3" json:"expiry,omitempty"` Expiry int64 `protobuf:"varint,5,opt,name=expiry,proto3" json:"expiry,omitempty"`
Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"` Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"`
DescriptionHash string `protobuf:"bytes,7,opt,name=description_hash,json=descriptionHash,proto3" json:"description_hash,omitempty"` DescriptionHash string `protobuf:"bytes,7,opt,name=description_hash,json=descriptionHash,proto3" json:"description_hash,omitempty"`
FallbackAddr string `protobuf:"bytes,8,opt,name=fallback_addr,json=fallbackAddr,proto3" json:"fallback_addr,omitempty"` FallbackAddr string `protobuf:"bytes,8,opt,name=fallback_addr,json=fallbackAddr,proto3" json:"fallback_addr,omitempty"`
CltvExpiry int64 `protobuf:"varint,9,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"` CltvExpiry int64 `protobuf:"varint,9,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"`
RouteHints []*RouteHint `protobuf:"bytes,10,rep,name=route_hints,json=routeHints,proto3" json:"route_hints,omitempty"` RouteHints []*RouteHint `protobuf:"bytes,10,rep,name=route_hints,json=routeHints,proto3" json:"route_hints,omitempty"`
PaymentAddr []byte `protobuf:"bytes,11,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"` PaymentAddr []byte `protobuf:"bytes,11,opt,name=payment_addr,json=paymentAddr,proto3" json:"payment_addr,omitempty"`
NumMsat int64 `protobuf:"varint,12,opt,name=num_msat,json=numMsat,proto3" json:"num_msat,omitempty"` NumMsat int64 `protobuf:"varint,12,opt,name=num_msat,json=numMsat,proto3" json:"num_msat,omitempty"`
Features map[uint32]*Feature `protobuf:"bytes,13,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Features map[uint32]*Feature `protobuf:"bytes,13,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
BlindedPaths []*BlindedPaymentPath `protobuf:"bytes,14,rep,name=blinded_paths,json=blindedPaths,proto3" json:"blinded_paths,omitempty"`
} }
func (x *PayReq) Reset() { func (x *PayReq) Reset() {
@@ -14442,6 +14443,13 @@ func (x *PayReq) GetFeatures() map[uint32]*Feature {
return nil return nil
} }
func (x *PayReq) GetBlindedPaths() []*BlindedPaymentPath {
if x != nil {
return x.BlindedPaths
}
return nil
}
type Feature struct { type Feature struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@@ -20202,7 +20210,7 @@ var file_lightning_proto_rawDesc = []byte{
0x75, 0x62, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x27, 0x0a, 0x0c, 0x50, 0x61, 0x79, 0x75, 0x62, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x27, 0x0a, 0x0c, 0x50, 0x61, 0x79,
0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x61, 0x79,
0x5f, 0x72, 0x65, 0x71, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x79, 0x52, 0x5f, 0x72, 0x65, 0x71, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x79, 0x52,
0x65, 0x71, 0x22, 0xb0, 0x04, 0x0a, 0x06, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x20, 0x0a, 0x65, 0x71, 0x22, 0xf0, 0x04, 0x0a, 0x06, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x20, 0x0a,
0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12,
0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18,
@@ -20232,7 +20240,11 @@ var file_lightning_proto_rawDesc = []byte{
0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x37, 0x0a, 0x08, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71,
0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x2e, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08,
0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x3e, 0x0a, 0x0d, 0x62, 0x6c, 0x69, 0x6e,
0x64, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x64, 0x50,
0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x52, 0x0c, 0x62, 0x6c, 0x69, 0x6e,
0x64, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x73, 0x1a, 0x4b, 0x0a, 0x0d, 0x46, 0x65, 0x61, 0x74,
0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x75, 0x72, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x6c, 0x6e, 0x72,
@@ -21520,192 +21532,193 @@ var file_lightning_proto_depIdxs = []int32{
38, // 138: lnrpc.AbandonChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint 38, // 138: lnrpc.AbandonChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint
150, // 139: lnrpc.PayReq.route_hints:type_name -> lnrpc.RouteHint 150, // 139: lnrpc.PayReq.route_hints:type_name -> lnrpc.RouteHint
244, // 140: lnrpc.PayReq.features:type_name -> lnrpc.PayReq.FeaturesEntry 244, // 140: lnrpc.PayReq.features:type_name -> lnrpc.PayReq.FeaturesEntry
179, // 141: lnrpc.FeeReportResponse.channel_fees:type_name -> lnrpc.ChannelFeeReport 151, // 141: lnrpc.PayReq.blinded_paths:type_name -> lnrpc.BlindedPaymentPath
38, // 142: lnrpc.PolicyUpdateRequest.chan_point:type_name -> lnrpc.ChannelPoint 179, // 142: lnrpc.FeeReportResponse.channel_fees:type_name -> lnrpc.ChannelFeeReport
181, // 143: lnrpc.PolicyUpdateRequest.inbound_fee:type_name -> lnrpc.InboundFee 38, // 143: lnrpc.PolicyUpdateRequest.chan_point:type_name -> lnrpc.ChannelPoint
39, // 144: lnrpc.FailedUpdate.outpoint:type_name -> lnrpc.OutPoint 181, // 144: lnrpc.PolicyUpdateRequest.inbound_fee:type_name -> lnrpc.InboundFee
11, // 145: lnrpc.FailedUpdate.reason:type_name -> lnrpc.UpdateFailure 39, // 145: lnrpc.FailedUpdate.outpoint:type_name -> lnrpc.OutPoint
183, // 146: lnrpc.PolicyUpdateResponse.failed_updates:type_name -> lnrpc.FailedUpdate 11, // 146: lnrpc.FailedUpdate.reason:type_name -> lnrpc.UpdateFailure
186, // 147: lnrpc.ForwardingHistoryResponse.forwarding_events:type_name -> lnrpc.ForwardingEvent 183, // 147: lnrpc.PolicyUpdateResponse.failed_updates:type_name -> lnrpc.FailedUpdate
38, // 148: lnrpc.ExportChannelBackupRequest.chan_point:type_name -> lnrpc.ChannelPoint 186, // 148: lnrpc.ForwardingHistoryResponse.forwarding_events:type_name -> lnrpc.ForwardingEvent
38, // 149: lnrpc.ChannelBackup.chan_point:type_name -> lnrpc.ChannelPoint 38, // 149: lnrpc.ExportChannelBackupRequest.chan_point:type_name -> lnrpc.ChannelPoint
38, // 150: lnrpc.MultiChanBackup.chan_points:type_name -> lnrpc.ChannelPoint 38, // 150: lnrpc.ChannelBackup.chan_point:type_name -> lnrpc.ChannelPoint
193, // 151: lnrpc.ChanBackupSnapshot.single_chan_backups:type_name -> lnrpc.ChannelBackups 38, // 151: lnrpc.MultiChanBackup.chan_points:type_name -> lnrpc.ChannelPoint
190, // 152: lnrpc.ChanBackupSnapshot.multi_chan_backup:type_name -> lnrpc.MultiChanBackup 193, // 152: lnrpc.ChanBackupSnapshot.single_chan_backups:type_name -> lnrpc.ChannelBackups
189, // 153: lnrpc.ChannelBackups.chan_backups:type_name -> lnrpc.ChannelBackup 190, // 153: lnrpc.ChanBackupSnapshot.multi_chan_backup:type_name -> lnrpc.MultiChanBackup
193, // 154: lnrpc.RestoreChanBackupRequest.chan_backups:type_name -> lnrpc.ChannelBackups 189, // 154: lnrpc.ChannelBackups.chan_backups:type_name -> lnrpc.ChannelBackup
198, // 155: lnrpc.BakeMacaroonRequest.permissions:type_name -> lnrpc.MacaroonPermission 193, // 155: lnrpc.RestoreChanBackupRequest.chan_backups:type_name -> lnrpc.ChannelBackups
198, // 156: lnrpc.MacaroonPermissionList.permissions:type_name -> lnrpc.MacaroonPermission 198, // 156: lnrpc.BakeMacaroonRequest.permissions:type_name -> lnrpc.MacaroonPermission
245, // 157: lnrpc.ListPermissionsResponse.method_permissions:type_name -> lnrpc.ListPermissionsResponse.MethodPermissionsEntry 198, // 157: lnrpc.MacaroonPermissionList.permissions:type_name -> lnrpc.MacaroonPermission
20, // 158: lnrpc.Failure.code:type_name -> lnrpc.Failure.FailureCode 245, // 158: lnrpc.ListPermissionsResponse.method_permissions:type_name -> lnrpc.ListPermissionsResponse.MethodPermissionsEntry
209, // 159: lnrpc.Failure.channel_update:type_name -> lnrpc.ChannelUpdate 20, // 159: lnrpc.Failure.code:type_name -> lnrpc.Failure.FailureCode
211, // 160: lnrpc.MacaroonId.ops:type_name -> lnrpc.Op 209, // 160: lnrpc.Failure.channel_update:type_name -> lnrpc.ChannelUpdate
198, // 161: lnrpc.CheckMacPermRequest.permissions:type_name -> lnrpc.MacaroonPermission 211, // 161: lnrpc.MacaroonId.ops:type_name -> lnrpc.Op
215, // 162: lnrpc.RPCMiddlewareRequest.stream_auth:type_name -> lnrpc.StreamAuth 198, // 162: lnrpc.CheckMacPermRequest.permissions:type_name -> lnrpc.MacaroonPermission
216, // 163: lnrpc.RPCMiddlewareRequest.request:type_name -> lnrpc.RPCMessage 215, // 163: lnrpc.RPCMiddlewareRequest.stream_auth:type_name -> lnrpc.StreamAuth
216, // 164: lnrpc.RPCMiddlewareRequest.response:type_name -> lnrpc.RPCMessage 216, // 164: lnrpc.RPCMiddlewareRequest.request:type_name -> lnrpc.RPCMessage
218, // 165: lnrpc.RPCMiddlewareResponse.register:type_name -> lnrpc.MiddlewareRegistration 216, // 165: lnrpc.RPCMiddlewareRequest.response:type_name -> lnrpc.RPCMessage
219, // 166: lnrpc.RPCMiddlewareResponse.feedback:type_name -> lnrpc.InterceptFeedback 218, // 166: lnrpc.RPCMiddlewareResponse.register:type_name -> lnrpc.MiddlewareRegistration
177, // 167: lnrpc.Peer.FeaturesEntry.value:type_name -> lnrpc.Feature 219, // 167: lnrpc.RPCMiddlewareResponse.feedback:type_name -> lnrpc.InterceptFeedback
177, // 168: lnrpc.GetInfoResponse.FeaturesEntry.value:type_name -> lnrpc.Feature 177, // 168: lnrpc.Peer.FeaturesEntry.value:type_name -> lnrpc.Feature
4, // 169: lnrpc.PendingChannelsResponse.PendingChannel.initiator:type_name -> lnrpc.Initiator 177, // 169: lnrpc.GetInfoResponse.FeaturesEntry.value:type_name -> lnrpc.Feature
3, // 170: lnrpc.PendingChannelsResponse.PendingChannel.commitment_type:type_name -> lnrpc.CommitmentType 4, // 170: lnrpc.PendingChannelsResponse.PendingChannel.initiator:type_name -> lnrpc.Initiator
226, // 171: lnrpc.PendingChannelsResponse.PendingOpenChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel 3, // 171: lnrpc.PendingChannelsResponse.PendingChannel.commitment_type:type_name -> lnrpc.CommitmentType
226, // 172: lnrpc.PendingChannelsResponse.WaitingCloseChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel 226, // 172: lnrpc.PendingChannelsResponse.PendingOpenChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
229, // 173: lnrpc.PendingChannelsResponse.WaitingCloseChannel.commitments:type_name -> lnrpc.PendingChannelsResponse.Commitments 226, // 173: lnrpc.PendingChannelsResponse.WaitingCloseChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
226, // 174: lnrpc.PendingChannelsResponse.ClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel 229, // 174: lnrpc.PendingChannelsResponse.WaitingCloseChannel.commitments:type_name -> lnrpc.PendingChannelsResponse.Commitments
226, // 175: lnrpc.PendingChannelsResponse.ForceClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel 226, // 175: lnrpc.PendingChannelsResponse.ClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
108, // 176: lnrpc.PendingChannelsResponse.ForceClosedChannel.pending_htlcs:type_name -> lnrpc.PendingHTLC 226, // 176: lnrpc.PendingChannelsResponse.ForceClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
15, // 177: lnrpc.PendingChannelsResponse.ForceClosedChannel.anchor:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState 108, // 177: lnrpc.PendingChannelsResponse.ForceClosedChannel.pending_htlcs:type_name -> lnrpc.PendingHTLC
113, // 178: lnrpc.WalletBalanceResponse.AccountBalanceEntry.value:type_name -> lnrpc.WalletAccountBalance 15, // 178: lnrpc.PendingChannelsResponse.ForceClosedChannel.anchor:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState
177, // 179: lnrpc.LightningNode.FeaturesEntry.value:type_name -> lnrpc.Feature 113, // 179: lnrpc.WalletBalanceResponse.AccountBalanceEntry.value:type_name -> lnrpc.WalletAccountBalance
137, // 180: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry.value:type_name -> lnrpc.FloatMetric 177, // 180: lnrpc.LightningNode.FeaturesEntry.value:type_name -> lnrpc.Feature
177, // 181: lnrpc.NodeUpdate.FeaturesEntry.value:type_name -> lnrpc.Feature 137, // 181: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry.value:type_name -> lnrpc.FloatMetric
177, // 182: lnrpc.Invoice.FeaturesEntry.value:type_name -> lnrpc.Feature 177, // 182: lnrpc.NodeUpdate.FeaturesEntry.value:type_name -> lnrpc.Feature
154, // 183: lnrpc.Invoice.AmpInvoiceStateEntry.value:type_name -> lnrpc.AMPInvoiceState 177, // 183: lnrpc.Invoice.FeaturesEntry.value:type_name -> lnrpc.Feature
177, // 184: lnrpc.PayReq.FeaturesEntry.value:type_name -> lnrpc.Feature 154, // 184: lnrpc.Invoice.AmpInvoiceStateEntry.value:type_name -> lnrpc.AMPInvoiceState
205, // 185: lnrpc.ListPermissionsResponse.MethodPermissionsEntry.value:type_name -> lnrpc.MacaroonPermissionList 177, // 185: lnrpc.PayReq.FeaturesEntry.value:type_name -> lnrpc.Feature
114, // 186: lnrpc.Lightning.WalletBalance:input_type -> lnrpc.WalletBalanceRequest 205, // 186: lnrpc.ListPermissionsResponse.MethodPermissionsEntry.value:type_name -> lnrpc.MacaroonPermissionList
117, // 187: lnrpc.Lightning.ChannelBalance:input_type -> lnrpc.ChannelBalanceRequest 114, // 187: lnrpc.Lightning.WalletBalance:input_type -> lnrpc.WalletBalanceRequest
30, // 188: lnrpc.Lightning.GetTransactions:input_type -> lnrpc.GetTransactionsRequest 117, // 188: lnrpc.Lightning.ChannelBalance:input_type -> lnrpc.ChannelBalanceRequest
42, // 189: lnrpc.Lightning.EstimateFee:input_type -> lnrpc.EstimateFeeRequest 30, // 189: lnrpc.Lightning.GetTransactions:input_type -> lnrpc.GetTransactionsRequest
46, // 190: lnrpc.Lightning.SendCoins:input_type -> lnrpc.SendCoinsRequest 42, // 190: lnrpc.Lightning.EstimateFee:input_type -> lnrpc.EstimateFeeRequest
48, // 191: lnrpc.Lightning.ListUnspent:input_type -> lnrpc.ListUnspentRequest 46, // 191: lnrpc.Lightning.SendCoins:input_type -> lnrpc.SendCoinsRequest
30, // 192: lnrpc.Lightning.SubscribeTransactions:input_type -> lnrpc.GetTransactionsRequest 48, // 192: lnrpc.Lightning.ListUnspent:input_type -> lnrpc.ListUnspentRequest
44, // 193: lnrpc.Lightning.SendMany:input_type -> lnrpc.SendManyRequest 30, // 193: lnrpc.Lightning.SubscribeTransactions:input_type -> lnrpc.GetTransactionsRequest
50, // 194: lnrpc.Lightning.NewAddress:input_type -> lnrpc.NewAddressRequest 44, // 194: lnrpc.Lightning.SendMany:input_type -> lnrpc.SendManyRequest
52, // 195: lnrpc.Lightning.SignMessage:input_type -> lnrpc.SignMessageRequest 50, // 195: lnrpc.Lightning.NewAddress:input_type -> lnrpc.NewAddressRequest
54, // 196: lnrpc.Lightning.VerifyMessage:input_type -> lnrpc.VerifyMessageRequest 52, // 196: lnrpc.Lightning.SignMessage:input_type -> lnrpc.SignMessageRequest
56, // 197: lnrpc.Lightning.ConnectPeer:input_type -> lnrpc.ConnectPeerRequest 54, // 197: lnrpc.Lightning.VerifyMessage:input_type -> lnrpc.VerifyMessageRequest
58, // 198: lnrpc.Lightning.DisconnectPeer:input_type -> lnrpc.DisconnectPeerRequest 56, // 198: lnrpc.Lightning.ConnectPeer:input_type -> lnrpc.ConnectPeerRequest
74, // 199: lnrpc.Lightning.ListPeers:input_type -> lnrpc.ListPeersRequest 58, // 199: lnrpc.Lightning.DisconnectPeer:input_type -> lnrpc.DisconnectPeerRequest
76, // 200: lnrpc.Lightning.SubscribePeerEvents:input_type -> lnrpc.PeerEventSubscription 74, // 200: lnrpc.Lightning.ListPeers:input_type -> lnrpc.ListPeersRequest
78, // 201: lnrpc.Lightning.GetInfo:input_type -> lnrpc.GetInfoRequest 76, // 201: lnrpc.Lightning.SubscribePeerEvents:input_type -> lnrpc.PeerEventSubscription
80, // 202: lnrpc.Lightning.GetDebugInfo:input_type -> lnrpc.GetDebugInfoRequest 78, // 202: lnrpc.Lightning.GetInfo:input_type -> lnrpc.GetInfoRequest
82, // 203: lnrpc.Lightning.GetRecoveryInfo:input_type -> lnrpc.GetRecoveryInfoRequest 80, // 203: lnrpc.Lightning.GetDebugInfo:input_type -> lnrpc.GetDebugInfoRequest
109, // 204: lnrpc.Lightning.PendingChannels:input_type -> lnrpc.PendingChannelsRequest 82, // 204: lnrpc.Lightning.GetRecoveryInfo:input_type -> lnrpc.GetRecoveryInfoRequest
63, // 205: lnrpc.Lightning.ListChannels:input_type -> lnrpc.ListChannelsRequest 109, // 205: lnrpc.Lightning.PendingChannels:input_type -> lnrpc.PendingChannelsRequest
111, // 206: lnrpc.Lightning.SubscribeChannelEvents:input_type -> lnrpc.ChannelEventSubscription 63, // 206: lnrpc.Lightning.ListChannels:input_type -> lnrpc.ListChannelsRequest
70, // 207: lnrpc.Lightning.ClosedChannels:input_type -> lnrpc.ClosedChannelsRequest 111, // 207: lnrpc.Lightning.SubscribeChannelEvents:input_type -> lnrpc.ChannelEventSubscription
96, // 208: lnrpc.Lightning.OpenChannelSync:input_type -> lnrpc.OpenChannelRequest 70, // 208: lnrpc.Lightning.ClosedChannels:input_type -> lnrpc.ClosedChannelsRequest
96, // 209: lnrpc.Lightning.OpenChannel:input_type -> lnrpc.OpenChannelRequest 96, // 209: lnrpc.Lightning.OpenChannelSync:input_type -> lnrpc.OpenChannelRequest
93, // 210: lnrpc.Lightning.BatchOpenChannel:input_type -> lnrpc.BatchOpenChannelRequest 96, // 210: lnrpc.Lightning.OpenChannel:input_type -> lnrpc.OpenChannelRequest
106, // 211: lnrpc.Lightning.FundingStateStep:input_type -> lnrpc.FundingTransitionMsg 93, // 211: lnrpc.Lightning.BatchOpenChannel:input_type -> lnrpc.BatchOpenChannelRequest
37, // 212: lnrpc.Lightning.ChannelAcceptor:input_type -> lnrpc.ChannelAcceptResponse 106, // 212: lnrpc.Lightning.FundingStateStep:input_type -> lnrpc.FundingTransitionMsg
88, // 213: lnrpc.Lightning.CloseChannel:input_type -> lnrpc.CloseChannelRequest 37, // 213: lnrpc.Lightning.ChannelAcceptor:input_type -> lnrpc.ChannelAcceptResponse
171, // 214: lnrpc.Lightning.AbandonChannel:input_type -> lnrpc.AbandonChannelRequest 88, // 214: lnrpc.Lightning.CloseChannel:input_type -> lnrpc.CloseChannelRequest
33, // 215: lnrpc.Lightning.SendPayment:input_type -> lnrpc.SendRequest 171, // 215: lnrpc.Lightning.AbandonChannel:input_type -> lnrpc.AbandonChannelRequest
33, // 216: lnrpc.Lightning.SendPaymentSync:input_type -> lnrpc.SendRequest 33, // 216: lnrpc.Lightning.SendPayment:input_type -> lnrpc.SendRequest
35, // 217: lnrpc.Lightning.SendToRoute:input_type -> lnrpc.SendToRouteRequest 33, // 217: lnrpc.Lightning.SendPaymentSync:input_type -> lnrpc.SendRequest
35, // 218: lnrpc.Lightning.SendToRouteSync:input_type -> lnrpc.SendToRouteRequest 35, // 218: lnrpc.Lightning.SendToRoute:input_type -> lnrpc.SendToRouteRequest
155, // 219: lnrpc.Lightning.AddInvoice:input_type -> lnrpc.Invoice 35, // 219: lnrpc.Lightning.SendToRouteSync:input_type -> lnrpc.SendToRouteRequest
160, // 220: lnrpc.Lightning.ListInvoices:input_type -> lnrpc.ListInvoiceRequest 155, // 220: lnrpc.Lightning.AddInvoice:input_type -> lnrpc.Invoice
159, // 221: lnrpc.Lightning.LookupInvoice:input_type -> lnrpc.PaymentHash 160, // 221: lnrpc.Lightning.ListInvoices:input_type -> lnrpc.ListInvoiceRequest
162, // 222: lnrpc.Lightning.SubscribeInvoices:input_type -> lnrpc.InvoiceSubscription 159, // 222: lnrpc.Lightning.LookupInvoice:input_type -> lnrpc.PaymentHash
175, // 223: lnrpc.Lightning.DecodePayReq:input_type -> lnrpc.PayReqString 162, // 223: lnrpc.Lightning.SubscribeInvoices:input_type -> lnrpc.InvoiceSubscription
165, // 224: lnrpc.Lightning.ListPayments:input_type -> lnrpc.ListPaymentsRequest 175, // 224: lnrpc.Lightning.DecodePayReq:input_type -> lnrpc.PayReqString
167, // 225: lnrpc.Lightning.DeletePayment:input_type -> lnrpc.DeletePaymentRequest 165, // 225: lnrpc.Lightning.ListPayments:input_type -> lnrpc.ListPaymentsRequest
168, // 226: lnrpc.Lightning.DeleteAllPayments:input_type -> lnrpc.DeleteAllPaymentsRequest 167, // 226: lnrpc.Lightning.DeletePayment:input_type -> lnrpc.DeletePaymentRequest
133, // 227: lnrpc.Lightning.DescribeGraph:input_type -> lnrpc.ChannelGraphRequest 168, // 227: lnrpc.Lightning.DeleteAllPayments:input_type -> lnrpc.DeleteAllPaymentsRequest
135, // 228: lnrpc.Lightning.GetNodeMetrics:input_type -> lnrpc.NodeMetricsRequest 133, // 228: lnrpc.Lightning.DescribeGraph:input_type -> lnrpc.ChannelGraphRequest
138, // 229: lnrpc.Lightning.GetChanInfo:input_type -> lnrpc.ChanInfoRequest 135, // 229: lnrpc.Lightning.GetNodeMetrics:input_type -> lnrpc.NodeMetricsRequest
127, // 230: lnrpc.Lightning.GetNodeInfo:input_type -> lnrpc.NodeInfoRequest 138, // 230: lnrpc.Lightning.GetChanInfo:input_type -> lnrpc.ChanInfoRequest
119, // 231: lnrpc.Lightning.QueryRoutes:input_type -> lnrpc.QueryRoutesRequest 127, // 231: lnrpc.Lightning.GetNodeInfo:input_type -> lnrpc.NodeInfoRequest
139, // 232: lnrpc.Lightning.GetNetworkInfo:input_type -> lnrpc.NetworkInfoRequest 119, // 232: lnrpc.Lightning.QueryRoutes:input_type -> lnrpc.QueryRoutesRequest
141, // 233: lnrpc.Lightning.StopDaemon:input_type -> lnrpc.StopRequest 139, // 233: lnrpc.Lightning.GetNetworkInfo:input_type -> lnrpc.NetworkInfoRequest
143, // 234: lnrpc.Lightning.SubscribeChannelGraph:input_type -> lnrpc.GraphTopologySubscription 141, // 234: lnrpc.Lightning.StopDaemon:input_type -> lnrpc.StopRequest
173, // 235: lnrpc.Lightning.DebugLevel:input_type -> lnrpc.DebugLevelRequest 143, // 235: lnrpc.Lightning.SubscribeChannelGraph:input_type -> lnrpc.GraphTopologySubscription
178, // 236: lnrpc.Lightning.FeeReport:input_type -> lnrpc.FeeReportRequest 173, // 236: lnrpc.Lightning.DebugLevel:input_type -> lnrpc.DebugLevelRequest
182, // 237: lnrpc.Lightning.UpdateChannelPolicy:input_type -> lnrpc.PolicyUpdateRequest 178, // 237: lnrpc.Lightning.FeeReport:input_type -> lnrpc.FeeReportRequest
185, // 238: lnrpc.Lightning.ForwardingHistory:input_type -> lnrpc.ForwardingHistoryRequest 182, // 238: lnrpc.Lightning.UpdateChannelPolicy:input_type -> lnrpc.PolicyUpdateRequest
188, // 239: lnrpc.Lightning.ExportChannelBackup:input_type -> lnrpc.ExportChannelBackupRequest 185, // 239: lnrpc.Lightning.ForwardingHistory:input_type -> lnrpc.ForwardingHistoryRequest
191, // 240: lnrpc.Lightning.ExportAllChannelBackups:input_type -> lnrpc.ChanBackupExportRequest 188, // 240: lnrpc.Lightning.ExportChannelBackup:input_type -> lnrpc.ExportChannelBackupRequest
192, // 241: lnrpc.Lightning.VerifyChanBackup:input_type -> lnrpc.ChanBackupSnapshot 191, // 241: lnrpc.Lightning.ExportAllChannelBackups:input_type -> lnrpc.ChanBackupExportRequest
194, // 242: lnrpc.Lightning.RestoreChannelBackups:input_type -> lnrpc.RestoreChanBackupRequest 192, // 242: lnrpc.Lightning.VerifyChanBackup:input_type -> lnrpc.ChanBackupSnapshot
196, // 243: lnrpc.Lightning.SubscribeChannelBackups:input_type -> lnrpc.ChannelBackupSubscription 194, // 243: lnrpc.Lightning.RestoreChannelBackups:input_type -> lnrpc.RestoreChanBackupRequest
199, // 244: lnrpc.Lightning.BakeMacaroon:input_type -> lnrpc.BakeMacaroonRequest 196, // 244: lnrpc.Lightning.SubscribeChannelBackups:input_type -> lnrpc.ChannelBackupSubscription
201, // 245: lnrpc.Lightning.ListMacaroonIDs:input_type -> lnrpc.ListMacaroonIDsRequest 199, // 245: lnrpc.Lightning.BakeMacaroon:input_type -> lnrpc.BakeMacaroonRequest
203, // 246: lnrpc.Lightning.DeleteMacaroonID:input_type -> lnrpc.DeleteMacaroonIDRequest 201, // 246: lnrpc.Lightning.ListMacaroonIDs:input_type -> lnrpc.ListMacaroonIDsRequest
206, // 247: lnrpc.Lightning.ListPermissions:input_type -> lnrpc.ListPermissionsRequest 203, // 247: lnrpc.Lightning.DeleteMacaroonID:input_type -> lnrpc.DeleteMacaroonIDRequest
212, // 248: lnrpc.Lightning.CheckMacaroonPermissions:input_type -> lnrpc.CheckMacPermRequest 206, // 248: lnrpc.Lightning.ListPermissions:input_type -> lnrpc.ListPermissionsRequest
217, // 249: lnrpc.Lightning.RegisterRPCMiddleware:input_type -> lnrpc.RPCMiddlewareResponse 212, // 249: lnrpc.Lightning.CheckMacaroonPermissions:input_type -> lnrpc.CheckMacPermRequest
25, // 250: lnrpc.Lightning.SendCustomMessage:input_type -> lnrpc.SendCustomMessageRequest 217, // 250: lnrpc.Lightning.RegisterRPCMiddleware:input_type -> lnrpc.RPCMiddlewareResponse
23, // 251: lnrpc.Lightning.SubscribeCustomMessages:input_type -> lnrpc.SubscribeCustomMessagesRequest 25, // 251: lnrpc.Lightning.SendCustomMessage:input_type -> lnrpc.SendCustomMessageRequest
66, // 252: lnrpc.Lightning.ListAliases:input_type -> lnrpc.ListAliasesRequest 23, // 252: lnrpc.Lightning.SubscribeCustomMessages:input_type -> lnrpc.SubscribeCustomMessagesRequest
21, // 253: lnrpc.Lightning.LookupHtlcResolution:input_type -> lnrpc.LookupHtlcResolutionRequest 66, // 253: lnrpc.Lightning.ListAliases:input_type -> lnrpc.ListAliasesRequest
115, // 254: lnrpc.Lightning.WalletBalance:output_type -> lnrpc.WalletBalanceResponse 21, // 254: lnrpc.Lightning.LookupHtlcResolution:input_type -> lnrpc.LookupHtlcResolutionRequest
118, // 255: lnrpc.Lightning.ChannelBalance:output_type -> lnrpc.ChannelBalanceResponse 115, // 255: lnrpc.Lightning.WalletBalance:output_type -> lnrpc.WalletBalanceResponse
31, // 256: lnrpc.Lightning.GetTransactions:output_type -> lnrpc.TransactionDetails 118, // 256: lnrpc.Lightning.ChannelBalance:output_type -> lnrpc.ChannelBalanceResponse
43, // 257: lnrpc.Lightning.EstimateFee:output_type -> lnrpc.EstimateFeeResponse 31, // 257: lnrpc.Lightning.GetTransactions:output_type -> lnrpc.TransactionDetails
47, // 258: lnrpc.Lightning.SendCoins:output_type -> lnrpc.SendCoinsResponse 43, // 258: lnrpc.Lightning.EstimateFee:output_type -> lnrpc.EstimateFeeResponse
49, // 259: lnrpc.Lightning.ListUnspent:output_type -> lnrpc.ListUnspentResponse 47, // 259: lnrpc.Lightning.SendCoins:output_type -> lnrpc.SendCoinsResponse
29, // 260: lnrpc.Lightning.SubscribeTransactions:output_type -> lnrpc.Transaction 49, // 260: lnrpc.Lightning.ListUnspent:output_type -> lnrpc.ListUnspentResponse
45, // 261: lnrpc.Lightning.SendMany:output_type -> lnrpc.SendManyResponse 29, // 261: lnrpc.Lightning.SubscribeTransactions:output_type -> lnrpc.Transaction
51, // 262: lnrpc.Lightning.NewAddress:output_type -> lnrpc.NewAddressResponse 45, // 262: lnrpc.Lightning.SendMany:output_type -> lnrpc.SendManyResponse
53, // 263: lnrpc.Lightning.SignMessage:output_type -> lnrpc.SignMessageResponse 51, // 263: lnrpc.Lightning.NewAddress:output_type -> lnrpc.NewAddressResponse
55, // 264: lnrpc.Lightning.VerifyMessage:output_type -> lnrpc.VerifyMessageResponse 53, // 264: lnrpc.Lightning.SignMessage:output_type -> lnrpc.SignMessageResponse
57, // 265: lnrpc.Lightning.ConnectPeer:output_type -> lnrpc.ConnectPeerResponse 55, // 265: lnrpc.Lightning.VerifyMessage:output_type -> lnrpc.VerifyMessageResponse
59, // 266: lnrpc.Lightning.DisconnectPeer:output_type -> lnrpc.DisconnectPeerResponse 57, // 266: lnrpc.Lightning.ConnectPeer:output_type -> lnrpc.ConnectPeerResponse
75, // 267: lnrpc.Lightning.ListPeers:output_type -> lnrpc.ListPeersResponse 59, // 267: lnrpc.Lightning.DisconnectPeer:output_type -> lnrpc.DisconnectPeerResponse
77, // 268: lnrpc.Lightning.SubscribePeerEvents:output_type -> lnrpc.PeerEvent 75, // 268: lnrpc.Lightning.ListPeers:output_type -> lnrpc.ListPeersResponse
79, // 269: lnrpc.Lightning.GetInfo:output_type -> lnrpc.GetInfoResponse 77, // 269: lnrpc.Lightning.SubscribePeerEvents:output_type -> lnrpc.PeerEvent
81, // 270: lnrpc.Lightning.GetDebugInfo:output_type -> lnrpc.GetDebugInfoResponse 79, // 270: lnrpc.Lightning.GetInfo:output_type -> lnrpc.GetInfoResponse
83, // 271: lnrpc.Lightning.GetRecoveryInfo:output_type -> lnrpc.GetRecoveryInfoResponse 81, // 271: lnrpc.Lightning.GetDebugInfo:output_type -> lnrpc.GetDebugInfoResponse
110, // 272: lnrpc.Lightning.PendingChannels:output_type -> lnrpc.PendingChannelsResponse 83, // 272: lnrpc.Lightning.GetRecoveryInfo:output_type -> lnrpc.GetRecoveryInfoResponse
64, // 273: lnrpc.Lightning.ListChannels:output_type -> lnrpc.ListChannelsResponse 110, // 273: lnrpc.Lightning.PendingChannels:output_type -> lnrpc.PendingChannelsResponse
112, // 274: lnrpc.Lightning.SubscribeChannelEvents:output_type -> lnrpc.ChannelEventUpdate 64, // 274: lnrpc.Lightning.ListChannels:output_type -> lnrpc.ListChannelsResponse
71, // 275: lnrpc.Lightning.ClosedChannels:output_type -> lnrpc.ClosedChannelsResponse 112, // 275: lnrpc.Lightning.SubscribeChannelEvents:output_type -> lnrpc.ChannelEventUpdate
38, // 276: lnrpc.Lightning.OpenChannelSync:output_type -> lnrpc.ChannelPoint 71, // 276: lnrpc.Lightning.ClosedChannels:output_type -> lnrpc.ClosedChannelsResponse
97, // 277: lnrpc.Lightning.OpenChannel:output_type -> lnrpc.OpenStatusUpdate 38, // 277: lnrpc.Lightning.OpenChannelSync:output_type -> lnrpc.ChannelPoint
95, // 278: lnrpc.Lightning.BatchOpenChannel:output_type -> lnrpc.BatchOpenChannelResponse 97, // 278: lnrpc.Lightning.OpenChannel:output_type -> lnrpc.OpenStatusUpdate
107, // 279: lnrpc.Lightning.FundingStateStep:output_type -> lnrpc.FundingStateStepResp 95, // 279: lnrpc.Lightning.BatchOpenChannel:output_type -> lnrpc.BatchOpenChannelResponse
36, // 280: lnrpc.Lightning.ChannelAcceptor:output_type -> lnrpc.ChannelAcceptRequest 107, // 280: lnrpc.Lightning.FundingStateStep:output_type -> lnrpc.FundingStateStepResp
89, // 281: lnrpc.Lightning.CloseChannel:output_type -> lnrpc.CloseStatusUpdate 36, // 281: lnrpc.Lightning.ChannelAcceptor:output_type -> lnrpc.ChannelAcceptRequest
172, // 282: lnrpc.Lightning.AbandonChannel:output_type -> lnrpc.AbandonChannelResponse 89, // 282: lnrpc.Lightning.CloseChannel:output_type -> lnrpc.CloseStatusUpdate
34, // 283: lnrpc.Lightning.SendPayment:output_type -> lnrpc.SendResponse 172, // 283: lnrpc.Lightning.AbandonChannel:output_type -> lnrpc.AbandonChannelResponse
34, // 284: lnrpc.Lightning.SendPaymentSync:output_type -> lnrpc.SendResponse 34, // 284: lnrpc.Lightning.SendPayment:output_type -> lnrpc.SendResponse
34, // 285: lnrpc.Lightning.SendToRoute:output_type -> lnrpc.SendResponse 34, // 285: lnrpc.Lightning.SendPaymentSync:output_type -> lnrpc.SendResponse
34, // 286: lnrpc.Lightning.SendToRouteSync:output_type -> lnrpc.SendResponse 34, // 286: lnrpc.Lightning.SendToRoute:output_type -> lnrpc.SendResponse
158, // 287: lnrpc.Lightning.AddInvoice:output_type -> lnrpc.AddInvoiceResponse 34, // 287: lnrpc.Lightning.SendToRouteSync:output_type -> lnrpc.SendResponse
161, // 288: lnrpc.Lightning.ListInvoices:output_type -> lnrpc.ListInvoiceResponse 158, // 288: lnrpc.Lightning.AddInvoice:output_type -> lnrpc.AddInvoiceResponse
155, // 289: lnrpc.Lightning.LookupInvoice:output_type -> lnrpc.Invoice 161, // 289: lnrpc.Lightning.ListInvoices:output_type -> lnrpc.ListInvoiceResponse
155, // 290: lnrpc.Lightning.SubscribeInvoices:output_type -> lnrpc.Invoice 155, // 290: lnrpc.Lightning.LookupInvoice:output_type -> lnrpc.Invoice
176, // 291: lnrpc.Lightning.DecodePayReq:output_type -> lnrpc.PayReq 155, // 291: lnrpc.Lightning.SubscribeInvoices:output_type -> lnrpc.Invoice
166, // 292: lnrpc.Lightning.ListPayments:output_type -> lnrpc.ListPaymentsResponse 176, // 292: lnrpc.Lightning.DecodePayReq:output_type -> lnrpc.PayReq
169, // 293: lnrpc.Lightning.DeletePayment:output_type -> lnrpc.DeletePaymentResponse 166, // 293: lnrpc.Lightning.ListPayments:output_type -> lnrpc.ListPaymentsResponse
170, // 294: lnrpc.Lightning.DeleteAllPayments:output_type -> lnrpc.DeleteAllPaymentsResponse 169, // 294: lnrpc.Lightning.DeletePayment:output_type -> lnrpc.DeletePaymentResponse
134, // 295: lnrpc.Lightning.DescribeGraph:output_type -> lnrpc.ChannelGraph 170, // 295: lnrpc.Lightning.DeleteAllPayments:output_type -> lnrpc.DeleteAllPaymentsResponse
136, // 296: lnrpc.Lightning.GetNodeMetrics:output_type -> lnrpc.NodeMetricsResponse 134, // 296: lnrpc.Lightning.DescribeGraph:output_type -> lnrpc.ChannelGraph
132, // 297: lnrpc.Lightning.GetChanInfo:output_type -> lnrpc.ChannelEdge 136, // 297: lnrpc.Lightning.GetNodeMetrics:output_type -> lnrpc.NodeMetricsResponse
128, // 298: lnrpc.Lightning.GetNodeInfo:output_type -> lnrpc.NodeInfo 132, // 298: lnrpc.Lightning.GetChanInfo:output_type -> lnrpc.ChannelEdge
122, // 299: lnrpc.Lightning.QueryRoutes:output_type -> lnrpc.QueryRoutesResponse 128, // 299: lnrpc.Lightning.GetNodeInfo:output_type -> lnrpc.NodeInfo
140, // 300: lnrpc.Lightning.GetNetworkInfo:output_type -> lnrpc.NetworkInfo 122, // 300: lnrpc.Lightning.QueryRoutes:output_type -> lnrpc.QueryRoutesResponse
142, // 301: lnrpc.Lightning.StopDaemon:output_type -> lnrpc.StopResponse 140, // 301: lnrpc.Lightning.GetNetworkInfo:output_type -> lnrpc.NetworkInfo
144, // 302: lnrpc.Lightning.SubscribeChannelGraph:output_type -> lnrpc.GraphTopologyUpdate 142, // 302: lnrpc.Lightning.StopDaemon:output_type -> lnrpc.StopResponse
174, // 303: lnrpc.Lightning.DebugLevel:output_type -> lnrpc.DebugLevelResponse 144, // 303: lnrpc.Lightning.SubscribeChannelGraph:output_type -> lnrpc.GraphTopologyUpdate
180, // 304: lnrpc.Lightning.FeeReport:output_type -> lnrpc.FeeReportResponse 174, // 304: lnrpc.Lightning.DebugLevel:output_type -> lnrpc.DebugLevelResponse
184, // 305: lnrpc.Lightning.UpdateChannelPolicy:output_type -> lnrpc.PolicyUpdateResponse 180, // 305: lnrpc.Lightning.FeeReport:output_type -> lnrpc.FeeReportResponse
187, // 306: lnrpc.Lightning.ForwardingHistory:output_type -> lnrpc.ForwardingHistoryResponse 184, // 306: lnrpc.Lightning.UpdateChannelPolicy:output_type -> lnrpc.PolicyUpdateResponse
189, // 307: lnrpc.Lightning.ExportChannelBackup:output_type -> lnrpc.ChannelBackup 187, // 307: lnrpc.Lightning.ForwardingHistory:output_type -> lnrpc.ForwardingHistoryResponse
192, // 308: lnrpc.Lightning.ExportAllChannelBackups:output_type -> lnrpc.ChanBackupSnapshot 189, // 308: lnrpc.Lightning.ExportChannelBackup:output_type -> lnrpc.ChannelBackup
197, // 309: lnrpc.Lightning.VerifyChanBackup:output_type -> lnrpc.VerifyChanBackupResponse 192, // 309: lnrpc.Lightning.ExportAllChannelBackups:output_type -> lnrpc.ChanBackupSnapshot
195, // 310: lnrpc.Lightning.RestoreChannelBackups:output_type -> lnrpc.RestoreBackupResponse 197, // 310: lnrpc.Lightning.VerifyChanBackup:output_type -> lnrpc.VerifyChanBackupResponse
192, // 311: lnrpc.Lightning.SubscribeChannelBackups:output_type -> lnrpc.ChanBackupSnapshot 195, // 311: lnrpc.Lightning.RestoreChannelBackups:output_type -> lnrpc.RestoreBackupResponse
200, // 312: lnrpc.Lightning.BakeMacaroon:output_type -> lnrpc.BakeMacaroonResponse 192, // 312: lnrpc.Lightning.SubscribeChannelBackups:output_type -> lnrpc.ChanBackupSnapshot
202, // 313: lnrpc.Lightning.ListMacaroonIDs:output_type -> lnrpc.ListMacaroonIDsResponse 200, // 313: lnrpc.Lightning.BakeMacaroon:output_type -> lnrpc.BakeMacaroonResponse
204, // 314: lnrpc.Lightning.DeleteMacaroonID:output_type -> lnrpc.DeleteMacaroonIDResponse 202, // 314: lnrpc.Lightning.ListMacaroonIDs:output_type -> lnrpc.ListMacaroonIDsResponse
207, // 315: lnrpc.Lightning.ListPermissions:output_type -> lnrpc.ListPermissionsResponse 204, // 315: lnrpc.Lightning.DeleteMacaroonID:output_type -> lnrpc.DeleteMacaroonIDResponse
213, // 316: lnrpc.Lightning.CheckMacaroonPermissions:output_type -> lnrpc.CheckMacPermResponse 207, // 316: lnrpc.Lightning.ListPermissions:output_type -> lnrpc.ListPermissionsResponse
214, // 317: lnrpc.Lightning.RegisterRPCMiddleware:output_type -> lnrpc.RPCMiddlewareRequest 213, // 317: lnrpc.Lightning.CheckMacaroonPermissions:output_type -> lnrpc.CheckMacPermResponse
26, // 318: lnrpc.Lightning.SendCustomMessage:output_type -> lnrpc.SendCustomMessageResponse 214, // 318: lnrpc.Lightning.RegisterRPCMiddleware:output_type -> lnrpc.RPCMiddlewareRequest
24, // 319: lnrpc.Lightning.SubscribeCustomMessages:output_type -> lnrpc.CustomMessage 26, // 319: lnrpc.Lightning.SendCustomMessage:output_type -> lnrpc.SendCustomMessageResponse
67, // 320: lnrpc.Lightning.ListAliases:output_type -> lnrpc.ListAliasesResponse 24, // 320: lnrpc.Lightning.SubscribeCustomMessages:output_type -> lnrpc.CustomMessage
22, // 321: lnrpc.Lightning.LookupHtlcResolution:output_type -> lnrpc.LookupHtlcResolutionResponse 67, // 321: lnrpc.Lightning.ListAliases:output_type -> lnrpc.ListAliasesResponse
254, // [254:322] is the sub-list for method output_type 22, // 322: lnrpc.Lightning.LookupHtlcResolution:output_type -> lnrpc.LookupHtlcResolutionResponse
186, // [186:254] is the sub-list for method input_type 255, // [255:323] is the sub-list for method output_type
186, // [186:186] is the sub-list for extension type_name 187, // [187:255] is the sub-list for method input_type
186, // [186:186] is the sub-list for extension extendee 187, // [187:187] is the sub-list for extension type_name
0, // [0:186] is the sub-list for field type_name 187, // [187:187] is the sub-list for extension extendee
0, // [0:187] is the sub-list for field type_name
} }
func init() { file_lightning_proto_init() } func init() { file_lightning_proto_init() }
+1
View File
@@ -4289,6 +4289,7 @@ message PayReq {
bytes payment_addr = 11; bytes payment_addr = 11;
int64 num_msat = 12; int64 num_msat = 12;
map<uint32, Feature> features = 13; map<uint32, Feature> features = 13;
repeated BlindedPaymentPath blinded_paths = 14;
} }
enum FeatureBit { enum FeatureBit {
+6
View File
@@ -6299,6 +6299,12 @@
"additionalProperties": { "additionalProperties": {
"$ref": "#/definitions/lnrpcFeature" "$ref": "#/definitions/lnrpcFeature"
} }
},
"blinded_paths": {
"type": "array",
"items": {
"$ref": "#/definitions/lnrpcBlindedPaymentPath"
}
} }
} }
}, },
+98 -15
View File
@@ -14,8 +14,18 @@ import (
// route encrypted data blob that is created by the recipient to provide // route encrypted data blob that is created by the recipient to provide
// forwarding information. // forwarding information.
type BlindedRouteData struct { type BlindedRouteData struct {
// Padding is an optional set of bytes that a recipient can use to pad
// the data so that the encrypted recipient data blobs are all the same
// length.
Padding tlv.OptionalRecordT[tlv.TlvType1, []byte]
// ShortChannelID is the channel ID of the next hop. // ShortChannelID is the channel ID of the next hop.
ShortChannelID tlv.RecordT[tlv.TlvType2, lnwire.ShortChannelID] ShortChannelID tlv.OptionalRecordT[tlv.TlvType2, lnwire.ShortChannelID]
// PathID is a secret set of bytes that the blinded path creator will
// set so that they can check the value on decryption to ensure that the
// path they created was used for the intended purpose.
PathID tlv.OptionalRecordT[tlv.TlvType6, []byte]
// NextBlindingOverride is a blinding point that should be switched // NextBlindingOverride is a blinding point that should be switched
// in for the next hop. This is used to combine two blinded paths into // in for the next hop. This is used to combine two blinded paths into
@@ -24,7 +34,7 @@ type BlindedRouteData struct {
NextBlindingOverride tlv.OptionalRecordT[tlv.TlvType8, *btcec.PublicKey] NextBlindingOverride tlv.OptionalRecordT[tlv.TlvType8, *btcec.PublicKey]
// RelayInfo provides the relay parameters for the hop. // RelayInfo provides the relay parameters for the hop.
RelayInfo tlv.RecordT[tlv.TlvType10, PaymentRelayInfo] RelayInfo tlv.OptionalRecordT[tlv.TlvType10, PaymentRelayInfo]
// Constraints provides the payment relay constraints for the hop. // Constraints provides the payment relay constraints for the hop.
Constraints tlv.OptionalRecordT[tlv.TlvType12, PaymentConstraints] Constraints tlv.OptionalRecordT[tlv.TlvType12, PaymentConstraints]
@@ -33,16 +43,20 @@ type BlindedRouteData struct {
Features tlv.OptionalRecordT[tlv.TlvType14, lnwire.FeatureVector] Features tlv.OptionalRecordT[tlv.TlvType14, lnwire.FeatureVector]
} }
// NewBlindedRouteData creates the data that's provided for hops within a // NewNonFinalBlindedRouteData creates the data that's provided for hops within
// blinded route. // a blinded route.
func NewBlindedRouteData(chanID lnwire.ShortChannelID, func NewNonFinalBlindedRouteData(chanID lnwire.ShortChannelID,
blindingOverride *btcec.PublicKey, relayInfo PaymentRelayInfo, blindingOverride *btcec.PublicKey, relayInfo PaymentRelayInfo,
constraints *PaymentConstraints, constraints *PaymentConstraints,
features *lnwire.FeatureVector) *BlindedRouteData { features *lnwire.FeatureVector) *BlindedRouteData {
info := &BlindedRouteData{ info := &BlindedRouteData{
ShortChannelID: tlv.NewRecordT[tlv.TlvType2](chanID), ShortChannelID: tlv.SomeRecordT(
RelayInfo: tlv.NewRecordT[tlv.TlvType10](relayInfo), tlv.NewRecordT[tlv.TlvType2](chanID),
),
RelayInfo: tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType10](relayInfo),
),
} }
if blindingOverride != nil { if blindingOverride != nil {
@@ -64,12 +78,36 @@ func NewBlindedRouteData(chanID lnwire.ShortChannelID,
return info return info
} }
// NewFinalHopBlindedRouteData creates the data that's provided for the final
// hop in a blinded route.
func NewFinalHopBlindedRouteData(constraints *PaymentConstraints,
pathID []byte) *BlindedRouteData {
var data BlindedRouteData
if pathID != nil {
data.PathID = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType6](pathID),
)
}
if constraints != nil {
data.Constraints = tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType12](*constraints))
}
return &data
}
// DecodeBlindedRouteData decodes the data provided within a blinded route. // DecodeBlindedRouteData decodes the data provided within a blinded route.
func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) { func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) {
var ( var (
d BlindedRouteData d BlindedRouteData
padding = d.Padding.Zero()
scid = d.ShortChannelID.Zero()
pathID = d.PathID.Zero()
blindingOverride = d.NextBlindingOverride.Zero() blindingOverride = d.NextBlindingOverride.Zero()
relayInfo = d.RelayInfo.Zero()
constraints = d.Constraints.Zero() constraints = d.Constraints.Zero()
features = d.Features.Zero() features = d.Features.Zero()
) )
@@ -80,19 +118,35 @@ func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) {
} }
typeMap, err := tlvRecords.ExtractRecords( typeMap, err := tlvRecords.ExtractRecords(
&d.ShortChannelID, &padding, &scid, &pathID, &blindingOverride, &relayInfo,
&blindingOverride, &d.RelayInfo.Val, &constraints, &constraints, &features,
&features,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
val, ok := typeMap[d.NextBlindingOverride.TlvType()] val, ok := typeMap[d.Padding.TlvType()]
if ok && val == nil {
d.Padding = tlv.SomeRecordT(padding)
}
if val, ok := typeMap[d.ShortChannelID.TlvType()]; ok && val == nil {
d.ShortChannelID = tlv.SomeRecordT(scid)
}
if val, ok := typeMap[d.PathID.TlvType()]; ok && val == nil {
d.PathID = tlv.SomeRecordT(pathID)
}
val, ok = typeMap[d.NextBlindingOverride.TlvType()]
if ok && val == nil { if ok && val == nil {
d.NextBlindingOverride = tlv.SomeRecordT(blindingOverride) d.NextBlindingOverride = tlv.SomeRecordT(blindingOverride)
} }
if val, ok := typeMap[d.RelayInfo.TlvType()]; ok && val == nil {
d.RelayInfo = tlv.SomeRecordT(relayInfo)
}
if val, ok := typeMap[d.Constraints.TlvType()]; ok && val == nil { if val, ok := typeMap[d.Constraints.TlvType()]; ok && val == nil {
d.Constraints = tlv.SomeRecordT(constraints) d.Constraints = tlv.SomeRecordT(constraints)
} }
@@ -111,7 +165,19 @@ func EncodeBlindedRouteData(data *BlindedRouteData) ([]byte, error) {
recordProducers = make([]tlv.RecordProducer, 0, 5) recordProducers = make([]tlv.RecordProducer, 0, 5)
) )
recordProducers = append(recordProducers, &data.ShortChannelID) data.Padding.WhenSome(func(p tlv.RecordT[tlv.TlvType1, []byte]) {
recordProducers = append(recordProducers, &p)
})
data.ShortChannelID.WhenSome(func(scid tlv.RecordT[tlv.TlvType2,
lnwire.ShortChannelID]) {
recordProducers = append(recordProducers, &scid)
})
data.PathID.WhenSome(func(pathID tlv.RecordT[tlv.TlvType6, []byte]) {
recordProducers = append(recordProducers, &pathID)
})
data.NextBlindingOverride.WhenSome(func(pk tlv.RecordT[tlv.TlvType8, data.NextBlindingOverride.WhenSome(func(pk tlv.RecordT[tlv.TlvType8,
*btcec.PublicKey]) { *btcec.PublicKey]) {
@@ -119,7 +185,11 @@ func EncodeBlindedRouteData(data *BlindedRouteData) ([]byte, error) {
recordProducers = append(recordProducers, &pk) recordProducers = append(recordProducers, &pk)
}) })
recordProducers = append(recordProducers, &data.RelayInfo.Val) data.RelayInfo.WhenSome(func(r tlv.RecordT[tlv.TlvType10,
PaymentRelayInfo]) {
recordProducers = append(recordProducers, &r)
})
data.Constraints.WhenSome(func(cs tlv.RecordT[tlv.TlvType12, data.Constraints.WhenSome(func(cs tlv.RecordT[tlv.TlvType12,
PaymentConstraints]) { PaymentConstraints]) {
@@ -140,6 +210,19 @@ func EncodeBlindedRouteData(data *BlindedRouteData) ([]byte, error) {
return e[:], nil return e[:], nil
} }
// PadBy adds "n" padding bytes to the BlindedRouteData using the Padding field.
// Callers should be aware that the total payload size will change by more than
// "n" since the "n" bytes will be prefixed by BigSize type and length fields.
// Callers may need to call PadBy iteratively until each encrypted data packet
// is the same size and so each call will overwrite the Padding record.
// Note that calling PadBy with an n value of 0 will still result in a zero
// length TLV entry being added.
func (b *BlindedRouteData) PadBy(n int) {
b.Padding = tlv.SomeRecordT(
tlv.NewPrimitiveRecord[tlv.TlvType1](make([]byte, n)),
)
}
// PaymentRelayInfo describes the relay policy for a blinded path. // PaymentRelayInfo describes the relay policy for a blinded path.
type PaymentRelayInfo struct { type PaymentRelayInfo struct {
// CltvExpiryDelta is the expiry delta for the payment. // CltvExpiryDelta is the expiry delta for the payment.
@@ -153,8 +236,8 @@ type PaymentRelayInfo struct {
BaseFee uint32 BaseFee uint32
} }
// newPaymentRelayRecord creates a tlv.Record that encodes the payment relay // Record creates a tlv.Record that encodes the payment relay (type 10) type for
// (type 10) type for an encrypted blob payload. // an encrypted blob payload.
func (i *PaymentRelayInfo) Record() tlv.Record { func (i *PaymentRelayInfo) Record() tlv.Record {
return tlv.MakeDynamicRecord( return tlv.MakeDynamicRecord(
10, &i, func() uint64 { 10, &i, func() uint64 {
+134 -4
View File
@@ -101,7 +101,7 @@ func TestBlindedDataEncoding(t *testing.T) {
} }
} }
encodedData := NewBlindedRouteData( encodedData := NewNonFinalBlindedRouteData(
channelID, pubkey(t), info, constraints, channelID, pubkey(t), info, constraints,
testCase.features, testCase.features,
) )
@@ -118,6 +118,127 @@ func TestBlindedDataEncoding(t *testing.T) {
} }
} }
// 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 // TestBlindedRouteVectors tests encoding/decoding of the test vectors for
// blinded route data provided in the specification. // blinded route data provided in the specification.
// //
@@ -131,10 +252,11 @@ func TestBlindingSpecTestVectors(t *testing.T) {
tests := []struct { tests := []struct {
encoded string encoded string
expectedPaymentData *BlindedRouteData expectedPaymentData *BlindedRouteData
expectedPadding int
}{ }{
{ {
encoded: "011a0000000000000000000000000000000000000000000000000000020800000000000006c10a0800240000009627100c06000b69e505dc0e00fd023103123456", encoded: "011a0000000000000000000000000000000000000000000000000000020800000000000006c10a0800240000009627100c06000b69e505dc0e00fd023103123456",
expectedPaymentData: NewBlindedRouteData( expectedPaymentData: NewNonFinalBlindedRouteData(
lnwire.ShortChannelID{ lnwire.ShortChannelID{
BlockHeight: 0, BlockHeight: 0,
TxIndex: 0, TxIndex: 0,
@@ -155,10 +277,11 @@ func TestBlindingSpecTestVectors(t *testing.T) {
lnwire.Features, lnwire.Features,
), ),
), ),
expectedPadding: 26,
}, },
{ {
encoded: "020800000000000004510821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0a0800300000006401f40c06000b69c105dc0e00", encoded: "020800000000000004510821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0a0800300000006401f40c06000b69c105dc0e00",
expectedPaymentData: NewBlindedRouteData( expectedPaymentData: NewNonFinalBlindedRouteData(
lnwire.ShortChannelID{ lnwire.ShortChannelID{
TxPosition: 1105, TxPosition: 1105,
}, },
@@ -175,7 +298,8 @@ func TestBlindingSpecTestVectors(t *testing.T) {
lnwire.NewFeatureVector( lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(), lnwire.NewRawFeatureVector(),
lnwire.Features, lnwire.Features,
)), ),
),
}, },
} }
@@ -189,6 +313,12 @@ func TestBlindingSpecTestVectors(t *testing.T) {
decodedRoute, err := DecodeBlindedRouteData(buff) decodedRoute, err := DecodeBlindedRouteData(buff)
require.NoError(t, err) require.NoError(t, err)
if test.expectedPadding != 0 {
test.expectedPaymentData.PadBy(
test.expectedPadding,
)
}
require.Equal( require.Equal(
t, test.expectedPaymentData, decodedRoute, t, test.expectedPaymentData, decodedRoute,
) )
+57 -7
View File
@@ -2,8 +2,8 @@ package routing
import ( import (
"errors" "errors"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/channeldb/models" "github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
@@ -29,6 +29,11 @@ type AdditionalEdge interface {
// EdgePolicy returns the policy of the additional edge. // EdgePolicy returns the policy of the additional edge.
EdgePolicy() *models.CachedEdgePolicy EdgePolicy() *models.CachedEdgePolicy
// BlindedPayment returns the BlindedPayment that this additional edge
// info was derived from. It will return nil if this edge was not
// derived from a blinded route.
BlindedPayment() *BlindedPayment
} }
// PayloadSizeFunc defines the interface for the payload size function. // PayloadSizeFunc defines the interface for the payload size function.
@@ -60,12 +65,49 @@ func (p *PrivateEdge) IntermediatePayloadSize(amount lnwire.MilliSatoshi,
return hop.PayloadSize(channelID) return hop.PayloadSize(channelID)
} }
// BlindedPayment is a no-op for a PrivateEdge since it is not associated with
// a blinded payment. This will thus return nil.
func (p *PrivateEdge) BlindedPayment() *BlindedPayment {
return nil
}
// BlindedEdge implements the AdditionalEdge interface. Blinded hops are viewed // BlindedEdge implements the AdditionalEdge interface. Blinded hops are viewed
// as additional edges because they are appened at the end of a normal route. // as additional edges because they are appended at the end of a normal route.
type BlindedEdge struct { type BlindedEdge struct {
policy *models.CachedEdgePolicy policy *models.CachedEdgePolicy
cipherText []byte
blindingPoint *btcec.PublicKey // blindedPayment is the BlindedPayment that this blinded edge was
// derived from.
blindedPayment *BlindedPayment
// hopIndex is the index of the hop in the blinded payment path that
// this edge is associated with.
hopIndex int
}
// NewBlindedEdge constructs a new BlindedEdge which packages the policy info
// for a specific hop within the given blinded payment path. The hop index
// should correspond to the hop within the blinded payment that this edge is
// associated with.
func NewBlindedEdge(policy *models.CachedEdgePolicy, payment *BlindedPayment,
hopIndex int) (*BlindedEdge, error) {
if payment == nil {
return nil, fmt.Errorf("blinded payment cannot be nil for " +
"blinded edge")
}
if hopIndex < 0 || hopIndex >= len(payment.BlindedPath.BlindedHops) {
return nil, fmt.Errorf("the hop index %d is outside the "+
"valid range between 0 and %d", hopIndex,
len(payment.BlindedPath.BlindedHops)-1)
}
return &BlindedEdge{
policy: policy,
hopIndex: hopIndex,
blindedPayment: payment,
}, nil
} }
// EdgePolicy return the policy of the BlindedEdge. // EdgePolicy return the policy of the BlindedEdge.
@@ -78,15 +120,23 @@ func (b *BlindedEdge) EdgePolicy() *models.CachedEdgePolicy {
func (b *BlindedEdge) IntermediatePayloadSize(_ lnwire.MilliSatoshi, _ uint32, func (b *BlindedEdge) IntermediatePayloadSize(_ lnwire.MilliSatoshi, _ uint32,
_ uint64) uint64 { _ uint64) uint64 {
blindedPath := b.blindedPayment.BlindedPath
hop := route.Hop{ hop := route.Hop{
BlindingPoint: b.blindingPoint, BlindingPoint: blindedPath.BlindingPoint,
EncryptedData: b.cipherText, EncryptedData: blindedPath.BlindedHops[b.hopIndex].CipherText,
} }
// For blinded paths the next chanID is in the encrypted data tlv. // For blinded paths the next chanID is in the encrypted data tlv.
return hop.PayloadSize(0) return hop.PayloadSize(0)
} }
// BlindedPayment returns the blinded payment that this edge is associated
// with.
func (b *BlindedEdge) BlindedPayment() *BlindedPayment {
return b.blindedPayment
}
// Compile-time constraints to ensure the PrivateEdge and the BlindedEdge // Compile-time constraints to ensure the PrivateEdge and the BlindedEdge
// implement the AdditionalEdge interface. // implement the AdditionalEdge interface.
var _ AdditionalEdge = (*PrivateEdge)(nil) var _ AdditionalEdge = (*PrivateEdge)(nil)
+15 -7
View File
@@ -42,9 +42,13 @@ func TestIntermediatePayloadSize(t *testing.T) {
hop: route.Hop{ hop: route.Hop{
EncryptedData: []byte{12, 13}, EncryptedData: []byte{12, 13},
}, },
edge: &BlindedEdge{ edge: &BlindedEdge{blindedPayment: &BlindedPayment{
cipherText: []byte{12, 13}, BlindedPath: &sphinx.BlindedPath{
}, BlindedHops: []*sphinx.BlindedHopInfo{
{CipherText: []byte{12, 13}},
},
},
}},
}, },
{ {
name: "Blinded edge - introduction point", name: "Blinded edge - introduction point",
@@ -52,10 +56,14 @@ func TestIntermediatePayloadSize(t *testing.T) {
EncryptedData: []byte{12, 13}, EncryptedData: []byte{12, 13},
BlindingPoint: blindedPoint, BlindingPoint: blindedPoint,
}, },
edge: &BlindedEdge{ edge: &BlindedEdge{blindedPayment: &BlindedPayment{
cipherText: []byte{12, 13}, BlindedPath: &sphinx.BlindedPath{
blindingPoint: blindedPoint, BlindingPoint: blindedPoint,
}, BlindedHops: []*sphinx.BlindedHopInfo{
{CipherText: []byte{12, 13}},
},
},
}},
}, },
} }
+13 -15
View File
@@ -88,13 +88,13 @@ func (b *BlindedPayment) Validate() error {
// 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).
func (b *BlindedPayment) toRouteHints() RouteHints { func (b *BlindedPayment) toRouteHints() (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
// don't need to add any route hints. // don't need to add any route hints.
if len(b.BlindedPath.BlindedHops) == 1 { if len(b.BlindedPath.BlindedHops) == 1 {
return nil return nil, nil
} }
hintCount := len(b.BlindedPath.BlindedHops) - 1 hintCount := len(b.BlindedPath.BlindedHops) - 1
@@ -136,14 +136,13 @@ func (b *BlindedPayment) toRouteHints() RouteHints {
ToNodeFeatures: features, ToNodeFeatures: features,
} }
hints[fromNode] = []AdditionalEdge{ edge, err := NewBlindedEdge(edgePolicy, b, 0)
&BlindedEdge{ if err != nil {
policy: edgePolicy, return nil, err
cipherText: b.BlindedPath.BlindedHops[0].CipherText,
blindingPoint: b.BlindedPath.BlindingPoint,
},
} }
hints[fromNode] = []AdditionalEdge{edge}
// 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
// because we're dealing with hops as pairs. // because we're dealing with hops as pairs.
@@ -169,14 +168,13 @@ func (b *BlindedPayment) toRouteHints() RouteHints {
ToNodeFeatures: features, ToNodeFeatures: features,
} }
hints[fromNode] = []AdditionalEdge{ edge, err := NewBlindedEdge(edgePolicy, b, i)
&BlindedEdge{ if err != nil {
policy: edgePolicy, return nil, err
cipherText: b.BlindedPath.BlindedHops[i].
CipherText,
},
} }
hints[fromNode] = []AdditionalEdge{edge}
} }
return hints return hints, nil
} }
+33 -29
View File
@@ -128,7 +128,9 @@ func TestBlindedPaymentToHints(t *testing.T) {
HtlcMaximum: htlcMax, HtlcMaximum: htlcMax,
Features: features, Features: features,
} }
require.Nil(t, blindedPayment.toRouteHints()) hints, err := blindedPayment.toRouteHints()
require.NoError(t, err)
require.Nil(t, hints)
// Populate the blinded payment with hops. // Populate the blinded payment with hops.
blindedPayment.BlindedPath.BlindedHops = []*sphinx.BlindedHopInfo{ blindedPayment.BlindedPath.BlindedHops = []*sphinx.BlindedHopInfo{
@@ -146,41 +148,43 @@ func TestBlindedPaymentToHints(t *testing.T) {
}, },
} }
policy1 := &models.CachedEdgePolicy{
TimeLockDelta: cltvDelta,
MinHTLC: lnwire.MilliSatoshi(htlcMin),
MaxHTLC: lnwire.MilliSatoshi(htlcMax),
FeeBaseMSat: lnwire.MilliSatoshi(baseFee),
FeeProportionalMillionths: lnwire.MilliSatoshi(
ppmFee,
),
ToNodePubKey: func() route.Vertex {
return vb2
},
ToNodeFeatures: features,
}
policy2 := &models.CachedEdgePolicy{
ToNodePubKey: func() route.Vertex {
return vb3
},
ToNodeFeatures: features,
}
blindedEdge1, err := NewBlindedEdge(policy1, blindedPayment, 0)
require.NoError(t, err)
blindedEdge2, err := NewBlindedEdge(policy2, blindedPayment, 1)
require.NoError(t, err)
expected := RouteHints{ expected := RouteHints{
v1: { v1: {
//nolint:lll blindedEdge1,
&BlindedEdge{
policy: &models.CachedEdgePolicy{
TimeLockDelta: cltvDelta,
MinHTLC: lnwire.MilliSatoshi(htlcMin),
MaxHTLC: lnwire.MilliSatoshi(htlcMax),
FeeBaseMSat: lnwire.MilliSatoshi(baseFee),
FeeProportionalMillionths: lnwire.MilliSatoshi(
ppmFee,
),
ToNodePubKey: func() route.Vertex {
return vb2
},
ToNodeFeatures: features,
},
blindingPoint: blindedPoint,
cipherText: cipherText,
},
}, },
vb2: { vb2: {
&BlindedEdge{ blindedEdge2,
policy: &models.CachedEdgePolicy{
ToNodePubKey: func() route.Vertex {
return vb3
},
ToNodeFeatures: features,
},
cipherText: cipherText,
},
}, },
} }
actual := blindedPayment.toRouteHints() actual, err := blindedPayment.toRouteHints()
require.NoError(t, err)
require.Equal(t, len(expected), len(actual)) require.Equal(t, len(expected), len(actual))
for vertex, expectedHint := range expected { for vertex, expectedHint := range expected {
+27 -7
View File
@@ -96,9 +96,17 @@ type edgePolicyWithSource struct {
// such as amounts and cltvs, as well as more complex features like destination // such as amounts and cltvs, as well as more complex features like destination
// custom records and payment address. // custom records and payment address.
type finalHopParams struct { type finalHopParams struct {
amt lnwire.MilliSatoshi amt lnwire.MilliSatoshi
totalAmt lnwire.MilliSatoshi totalAmt lnwire.MilliSatoshi
cltvDelta uint16
// cltvDelta is the final hop's minimum CLTV expiry delta.
//
// NOTE that in the case of paying to a blinded path, this value will
// be set to a duplicate of the blinded path's accumulated CLTV value.
// We would then only need to use this value in the case where the
// introduction node of the path is also the destination node.
cltvDelta uint16
records record.CustomSet records record.CustomSet
paymentAddr *[32]byte paymentAddr *[32]byte
@@ -190,10 +198,21 @@ func newRoute(sourceVertex route.Vertex,
// reporting through RPC. Set to zero for the final hop. // reporting through RPC. Set to zero for the final hop.
fee = 0 fee = 0
// As this is the last hop, we'll use the specified // Only include the final hop CLTV delta in the total
// final CLTV delta value instead of the value from the // time lock value if this is not a route to a blinded
// last link in the route. // path. For blinded paths, the total time-lock from the
totalTimeLock += uint32(finalHop.cltvDelta) // whole path will be deduced from the introduction
// 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 {
// As this is the last hop, we'll use the
// specified final CLTV delta value instead of
// the value from the last link in the route.
totalTimeLock += uint32(finalHop.cltvDelta)
}
outgoingTimeLock = totalTimeLock outgoingTimeLock = totalTimeLock
// Attach any custom records to the final hop. // Attach any custom records to the final hop.
@@ -968,6 +987,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
inboundFee, inboundFee,
fakeHopHintCapacity, fakeHopHintCapacity,
reverseEdge.edge.IntermediatePayloadSize, reverseEdge.edge.IntermediatePayloadSize,
reverseEdge.edge.BlindedPayment(),
) )
} }
+11 -6
View File
@@ -3322,16 +3322,19 @@ func TestBlindedRouteConstruction(t *testing.T) {
ToNodeFeatures: tlvFeatures, ToNodeFeatures: tlvFeatures,
} }
// Create final hop parameters for payment amount = 110. Note // Create final hop parameters for payment amount = 110.
// that final cltv delta is not set because blinded paths
// include this final delta in their aggregate delta. A
// sender-set delta may be added to account for block arrival
// during payment, but we do not set it in this test.
totalAmt lnwire.MilliSatoshi = 110 totalAmt lnwire.MilliSatoshi = 110
finalHopParams = finalHopParams{ finalHopParams = finalHopParams{
amt: totalAmt, amt: totalAmt,
totalAmt: totalAmt, totalAmt: totalAmt,
metadata: metadata, metadata: metadata,
// We set a CLTV delta here just to test that this will
// be ignored by newRoute since this is a blinded path
// where the accumulated CLTV delta for the route
// communicated in the blinded path should be assumed to
// include the CLTV delta of the final hop.
cltvDelta: MaxCLTVDelta,
} }
) )
@@ -3341,7 +3344,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 := blindedPayment.toRouteHints() blindedEdges, err := blindedPayment.toRouteHints()
require.NoError(t, err)
carolDaveEdge := blindedEdges[carolVertex][0] carolDaveEdge := blindedEdges[carolVertex][0]
daveEveEdge := blindedEdges[daveBlindedVertex][0] daveEveEdge := blindedEdges[daveBlindedVertex][0]
+5 -1
View File
@@ -2001,6 +2001,7 @@ func NewRouteRequest(source route.Vertex, target *route.Vertex,
// Assume that we're starting off with a regular payment. // Assume that we're starting off with a regular payment.
requestHints = routeHints requestHints = routeHints
requestExpiry = finalExpiry requestExpiry = finalExpiry
err error
) )
if blindedPayment != nil { if blindedPayment != nil {
@@ -2038,7 +2039,10 @@ func NewRouteRequest(source route.Vertex, target *route.Vertex,
requestExpiry = blindedPayment.CltvExpiryDelta requestExpiry = blindedPayment.CltvExpiryDelta
} }
requestHints = blindedPayment.toRouteHints() requestHints, err = blindedPayment.toRouteHints()
if err != nil {
return nil, err
}
} }
requestTarget, err := getTargetNode(target, blindedPayment) requestTarget, err := getTargetNode(target, blindedPayment)
+38 -26
View File
@@ -51,7 +51,8 @@ func newNodeEdgeUnifier(sourceNode, toNode route.Vertex, useInboundFees bool,
// incorrectly specified. // incorrectly specified.
func (u *nodeEdgeUnifier) addPolicy(fromNode route.Vertex, func (u *nodeEdgeUnifier) addPolicy(fromNode route.Vertex,
edge *models.CachedEdgePolicy, inboundFee models.InboundFee, edge *models.CachedEdgePolicy, inboundFee models.InboundFee,
capacity btcutil.Amount, hopPayloadSizeFn PayloadSizeFunc) { capacity btcutil.Amount, hopPayloadSizeFn PayloadSizeFunc,
blindedPayment *BlindedPayment) {
localChan := fromNode == u.sourceNode localChan := fromNode == u.sourceNode
@@ -86,12 +87,9 @@ func (u *nodeEdgeUnifier) addPolicy(fromNode route.Vertex,
inboundFee = models.InboundFee{} inboundFee = models.InboundFee{}
} }
unifier.edges = append(unifier.edges, &unifiedEdge{ unifier.edges = append(unifier.edges, newUnifiedEdge(
policy: edge, edge, capacity, inboundFee, hopPayloadSizeFn, blindedPayment,
capacity: capacity, ))
hopPayloadSizeFn: hopPayloadSizeFn,
inboundFees: inboundFee,
})
} }
// addGraphPolicies adds all policies that are known for the toNode in the // addGraphPolicies adds all policies that are known for the toNode in the
@@ -115,7 +113,7 @@ func (u *nodeEdgeUnifier) addGraphPolicies(g routingGraph) error {
u.addPolicy( u.addPolicy(
channel.OtherNode, channel.InPolicy, inboundFee, channel.OtherNode, channel.InPolicy, inboundFee,
channel.Capacity, defaultHopPayloadSize, channel.Capacity, defaultHopPayloadSize, nil,
) )
return nil return nil
@@ -137,6 +135,24 @@ type unifiedEdge struct {
// is needed because hops of a blinded path differ in their payload // is needed because hops of a blinded path differ in their payload
// structure compared to cleartext hops. // structure compared to cleartext hops.
hopPayloadSizeFn PayloadSizeFunc hopPayloadSizeFn PayloadSizeFunc
// blindedPayment if set, is the BlindedPayment that this edge was
// derived from originally.
blindedPayment *BlindedPayment
}
// newUnifiedEdge constructs a new unifiedEdge.
func newUnifiedEdge(policy *models.CachedEdgePolicy, capacity btcutil.Amount,
inboundFees models.InboundFee, hopPayloadSizeFn PayloadSizeFunc,
blindedPayment *BlindedPayment) *unifiedEdge {
return &unifiedEdge{
policy: policy,
capacity: capacity,
inboundFees: inboundFees,
hopPayloadSizeFn: hopPayloadSizeFn,
blindedPayment: blindedPayment,
}
} }
// amtInRange checks whether an amount falls within the valid range for a // amtInRange checks whether an amount falls within the valid range for a
@@ -292,12 +308,10 @@ func (u *edgeUnifier) getEdgeLocal(netAmtReceived lnwire.MilliSatoshi,
maxBandwidth = bandwidth maxBandwidth = bandwidth
// Update best edge. // Update best edge.
bestEdge = &unifiedEdge{ bestEdge = newUnifiedEdge(
policy: edge.policy, edge.policy, edge.capacity, edge.inboundFees,
capacity: edge.capacity, edge.hopPayloadSizeFn, edge.blindedPayment,
hopPayloadSizeFn: edge.hopPayloadSizeFn, )
inboundFees: edge.inboundFees,
}
} }
return bestEdge return bestEdge
@@ -376,10 +390,10 @@ func (u *edgeUnifier) getEdgeNetwork(netAmtReceived lnwire.MilliSatoshi,
} }
maxFee = fee maxFee = fee
bestPolicy = &unifiedEdge{ bestPolicy = newUnifiedEdge(
policy: edge.policy, edge.policy, 0, edge.inboundFees, nil,
inboundFees: edge.inboundFees, edge.blindedPayment,
} )
// The payload size function for edges to a connected peer is // The payload size function for edges to a connected peer is
// always the same hence there is not need to find the maximum. // always the same hence there is not need to find the maximum.
@@ -404,15 +418,13 @@ func (u *edgeUnifier) getEdgeNetwork(netAmtReceived lnwire.MilliSatoshi,
// chance for this node pair. But this is all only needed for nodes that // chance for this node pair. But this is all only needed for nodes that
// have distinct policies for channels to the same peer. // have distinct policies for channels to the same peer.
policyCopy := *bestPolicy.policy policyCopy := *bestPolicy.policy
modifiedEdge := unifiedEdge{ policyCopy.TimeLockDelta = maxTimelock
policy: &policyCopy, modifiedEdge := newUnifiedEdge(
inboundFees: bestPolicy.inboundFees, &policyCopy, maxCapMsat.ToSatoshis(), bestPolicy.inboundFees,
} hopPayloadSizeFn, bestPolicy.blindedPayment,
modifiedEdge.policy.TimeLockDelta = maxTimelock )
modifiedEdge.capacity = maxCapMsat.ToSatoshis()
modifiedEdge.hopPayloadSizeFn = hopPayloadSizeFn
return &modifiedEdge return modifiedEdge
} }
// minAmt returns the minimum amount that can be forwarded on this connection. // minAmt returns the minimum amount that can be forwarded on this connection.
+10 -9
View File
@@ -59,37 +59,37 @@ func TestNodeEdgeUnifier(t *testing.T) {
unifierFilled := newNodeEdgeUnifier(source, toNode, false, nil) unifierFilled := newNodeEdgeUnifier(source, toNode, false, nil)
unifierFilled.addPolicy( unifierFilled.addPolicy(
fromNode, &p1, inboundFee1, c1, defaultHopPayloadSize, fromNode, &p1, inboundFee1, c1, defaultHopPayloadSize, nil,
) )
unifierFilled.addPolicy( unifierFilled.addPolicy(
fromNode, &p2, inboundFee2, c2, defaultHopPayloadSize, fromNode, &p2, inboundFee2, c2, defaultHopPayloadSize, nil,
) )
unifierNoCapacity := newNodeEdgeUnifier(source, toNode, false, nil) unifierNoCapacity := newNodeEdgeUnifier(source, toNode, false, nil)
unifierNoCapacity.addPolicy( unifierNoCapacity.addPolicy(
fromNode, &p1, inboundFee1, 0, defaultHopPayloadSize, fromNode, &p1, inboundFee1, 0, defaultHopPayloadSize, nil,
) )
unifierNoCapacity.addPolicy( unifierNoCapacity.addPolicy(
fromNode, &p2, inboundFee2, 0, defaultHopPayloadSize, fromNode, &p2, inboundFee2, 0, defaultHopPayloadSize, nil,
) )
unifierNoInfo := newNodeEdgeUnifier(source, toNode, false, nil) unifierNoInfo := newNodeEdgeUnifier(source, toNode, false, nil)
unifierNoInfo.addPolicy( unifierNoInfo.addPolicy(
fromNode, &models.CachedEdgePolicy{}, models.InboundFee{}, fromNode, &models.CachedEdgePolicy{}, models.InboundFee{},
0, defaultHopPayloadSize, 0, defaultHopPayloadSize, nil,
) )
unifierInboundFee := newNodeEdgeUnifier(source, toNode, true, nil) unifierInboundFee := newNodeEdgeUnifier(source, toNode, true, nil)
unifierInboundFee.addPolicy( unifierInboundFee.addPolicy(
fromNode, &p1, inboundFee1, c1, defaultHopPayloadSize, fromNode, &p1, inboundFee1, c1, defaultHopPayloadSize, nil,
) )
unifierInboundFee.addPolicy( unifierInboundFee.addPolicy(
fromNode, &p2, inboundFee2, c2, defaultHopPayloadSize, fromNode, &p2, inboundFee2, c2, defaultHopPayloadSize, nil,
) )
unifierLocal := newNodeEdgeUnifier(fromNode, toNode, true, nil) unifierLocal := newNodeEdgeUnifier(fromNode, toNode, true, nil)
unifierLocal.addPolicy( unifierLocal.addPolicy(
fromNode, &p1, inboundFee1, c1, defaultHopPayloadSize, fromNode, &p1, inboundFee1, c1, defaultHopPayloadSize, nil,
) )
inboundFeeZero := models.InboundFee{} inboundFeeZero := models.InboundFee{}
@@ -98,10 +98,11 @@ func TestNodeEdgeUnifier(t *testing.T) {
} }
unifierNegInboundFee := newNodeEdgeUnifier(source, toNode, true, nil) unifierNegInboundFee := newNodeEdgeUnifier(source, toNode, true, nil)
unifierNegInboundFee.addPolicy( unifierNegInboundFee.addPolicy(
fromNode, &p1, inboundFeeZero, c1, defaultHopPayloadSize, fromNode, &p1, inboundFeeZero, c1, defaultHopPayloadSize, nil,
) )
unifierNegInboundFee.addPolicy( unifierNegInboundFee.addPolicy(
fromNode, &p2, inboundFeeNegative, c2, defaultHopPayloadSize, fromNode, &p2, inboundFeeNegative, c2, defaultHopPayloadSize,
nil,
) )
tests := []struct { tests := []struct {
+8
View File
@@ -6971,6 +6971,13 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
// Convert between the `lnrpc` and `routing` types. // Convert between the `lnrpc` and `routing` types.
routeHints := invoicesrpc.CreateRPCRouteHints(payReq.RouteHints) routeHints := invoicesrpc.CreateRPCRouteHints(payReq.RouteHints)
blindedPaymentPaths, err := invoicesrpc.CreateRPCBlindedPayments(
payReq.BlindedPaymentPaths,
)
if err != nil {
return nil, err
}
var amtSat, amtMsat int64 var amtSat, amtMsat int64
if payReq.MilliSat != nil { if payReq.MilliSat != nil {
amtSat = int64(payReq.MilliSat.ToSatoshis()) amtSat = int64(payReq.MilliSat.ToSatoshis())
@@ -6996,6 +7003,7 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
Expiry: expiry, Expiry: expiry,
CltvExpiry: int64(payReq.MinFinalCLTVExpiry()), CltvExpiry: int64(payReq.MinFinalCLTVExpiry()),
RouteHints: routeHints, RouteHints: routeHints,
BlindedPaths: blindedPaymentPaths,
PaymentAddr: paymentAddr, PaymentAddr: paymentAddr,
Features: invoicesrpc.CreateRPCFeatures(payReq.Features), Features: invoicesrpc.CreateRPCFeatures(payReq.Features),
}, nil }, nil
+246
View File
@@ -0,0 +1,246 @@
package zpay32
import (
"encoding/binary"
"fmt"
"io"
"math"
"github.com/btcsuite/btcd/btcec/v2"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// relayInfoSize is the number of bytes that the relay info of a blinded
// payment will occupy.
// base fee: 4 bytes
// prop fee: 4 bytes
// cltv delta: 2 bytes
// min htlc: 8 bytes
// max htlc: 8 bytes
relayInfoSize = 26
// maxNumHopsPerPath is the maximum number of blinded path hops that can
// be included in a single encoded blinded path. This is calculated
// based on the `data_length` limit of 638 bytes for any tagged field in
// a BOLT 11 invoice along with the estimated number of bytes required
// for encoding the most minimal blinded path hop. See the [bLIP
// proposal](https://github.com/lightning/blips/pull/39) for a detailed
// calculation.
maxNumHopsPerPath = 7
)
// BlindedPaymentPath holds all the information a payer needs to know about a
// blinded path to a receiver of a payment.
type BlindedPaymentPath struct {
// FeeBaseMsat is the total base fee for the path in milli-satoshis.
FeeBaseMsat uint32
// FeeRate is the total fee rate for the path in parts per million.
FeeRate uint32
// CltvExpiryDelta is the total CLTV delta to apply to the path.
CltvExpiryDelta uint16
// HTLCMinMsat is the minimum number of milli-satoshis that any hop in
// the path will route.
HTLCMinMsat uint64
// HTLCMaxMsat is the maximum number of milli-satoshis that a hop in the
// path will route.
HTLCMaxMsat uint64
// Features is the feature bit vector for the path.
Features *lnwire.FeatureVector
// FirstEphemeralBlindingPoint is the blinding point to send to the
// introduction node. It will be used by the introduction node to derive
// a shared secret with the receiver which can then be used to decode
// the encrypted payload from the receiver.
FirstEphemeralBlindingPoint *btcec.PublicKey
// Hops is the blinded path. The first hop is the introduction node and
// so the BlindedNodeID of this hop will be the real node ID.
Hops []*sphinx.BlindedHopInfo
}
// DecodeBlindedPayment attempts to parse a BlindedPaymentPath from the passed
// reader.
func DecodeBlindedPayment(r io.Reader) (*BlindedPaymentPath, error) {
var relayInfo [relayInfoSize]byte
n, err := r.Read(relayInfo[:])
if err != nil {
return nil, err
}
if n != relayInfoSize {
return nil, fmt.Errorf("unable to read %d relay info bytes "+
"off of the given stream: %w", relayInfoSize, err)
}
var payment BlindedPaymentPath
// Parse the relay info fields.
payment.FeeBaseMsat = binary.BigEndian.Uint32(relayInfo[:4])
payment.FeeRate = binary.BigEndian.Uint32(relayInfo[4:8])
payment.CltvExpiryDelta = binary.BigEndian.Uint16(relayInfo[8:10])
payment.HTLCMinMsat = binary.BigEndian.Uint64(relayInfo[10:18])
payment.HTLCMaxMsat = binary.BigEndian.Uint64(relayInfo[18:])
// Parse the feature bit vector.
f := lnwire.EmptyFeatureVector()
err = f.Decode(r)
if err != nil {
return nil, err
}
payment.Features = f
// Parse the first ephemeral blinding point.
var blindingPointBytes [btcec.PubKeyBytesLenCompressed]byte
_, err = r.Read(blindingPointBytes[:])
if err != nil {
return nil, err
}
blinding, err := btcec.ParsePubKey(blindingPointBytes[:])
if err != nil {
return nil, err
}
payment.FirstEphemeralBlindingPoint = blinding
// Read the one byte hop number.
var numHops [1]byte
_, err = r.Read(numHops[:])
if err != nil {
return nil, err
}
payment.Hops = make([]*sphinx.BlindedHopInfo, int(numHops[0]))
// Parse each hop.
for i := 0; i < len(payment.Hops); i++ {
hop, err := DecodeBlindedHop(r)
if err != nil {
return nil, err
}
payment.Hops[i] = hop
}
return &payment, nil
}
// Encode serialises the BlindedPaymentPath and writes the bytes to the passed
// writer.
// 1) The first 26 bytes contain the relay info:
// - Base Fee in msat: uint32 (4 bytes).
// - Proportional Fee in PPM: uint32 (4 bytes).
// - CLTV expiry delta: uint16 (2 bytes).
// - HTLC min msat: uint64 (8 bytes).
// - HTLC max msat: uint64 (8 bytes).
//
// 2) Feature bit vector length (2 bytes).
// 3) Feature bit vector (can be zero length).
// 4) First blinding point: 33 bytes.
// 5) Number of hops: 1 byte.
// 6) Encoded BlindedHops.
func (p *BlindedPaymentPath) Encode(w io.Writer) error {
var relayInfo [26]byte
binary.BigEndian.PutUint32(relayInfo[:4], p.FeeBaseMsat)
binary.BigEndian.PutUint32(relayInfo[4:8], p.FeeRate)
binary.BigEndian.PutUint16(relayInfo[8:10], p.CltvExpiryDelta)
binary.BigEndian.PutUint64(relayInfo[10:18], p.HTLCMinMsat)
binary.BigEndian.PutUint64(relayInfo[18:], p.HTLCMaxMsat)
_, err := w.Write(relayInfo[:])
if err != nil {
return err
}
err = p.Features.Encode(w)
if err != nil {
return err
}
_, err = w.Write(p.FirstEphemeralBlindingPoint.SerializeCompressed())
if err != nil {
return err
}
numHops := len(p.Hops)
if numHops > maxNumHopsPerPath {
return fmt.Errorf("the number of hops, %d, exceeds the "+
"maximum of %d", numHops, maxNumHopsPerPath)
}
_, err = w.Write([]byte{byte(numHops)})
if err != nil {
return err
}
for _, hop := range p.Hops {
err = EncodeBlindedHop(w, hop)
if err != nil {
return err
}
}
return nil
}
// DecodeBlindedHop reads a sphinx.BlindedHopInfo from the passed reader.
func DecodeBlindedHop(r io.Reader) (*sphinx.BlindedHopInfo, error) {
var nodeIDBytes [btcec.PubKeyBytesLenCompressed]byte
_, err := r.Read(nodeIDBytes[:])
if err != nil {
return nil, err
}
nodeID, err := btcec.ParsePubKey(nodeIDBytes[:])
if err != nil {
return nil, err
}
dataLen, err := tlv.ReadVarInt(r, &[8]byte{})
if err != nil {
return nil, err
}
encryptedData := make([]byte, dataLen)
_, err = r.Read(encryptedData)
if err != nil {
return nil, err
}
return &sphinx.BlindedHopInfo{
BlindedNodePub: nodeID,
CipherText: encryptedData,
}, nil
}
// EncodeBlindedHop writes the passed BlindedHopInfo to the given writer.
//
// 1) Blinded node pub key: 33 bytes
// 2) Cipher text length: BigSize
// 3) Cipher text.
func EncodeBlindedHop(w io.Writer, hop *sphinx.BlindedHopInfo) error {
_, err := w.Write(hop.BlindedNodePub.SerializeCompressed())
if err != nil {
return err
}
if len(hop.CipherText) > math.MaxUint16 {
return fmt.Errorf("encrypted recipient data can not exceed a "+
"length of %d bytes", math.MaxUint16)
}
err = tlv.WriteVarInt(w, uint64(len(hop.CipherText)), &[8]byte{})
if err != nil {
return err
}
_, err = w.Write(hop.CipherText)
return err
}
+41 -3
View File
@@ -215,6 +215,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
} }
invoice.PaymentHash, err = parse32Bytes(base32Data) invoice.PaymentHash, err = parse32Bytes(base32Data)
case fieldTypeS: case fieldTypeS:
if invoice.PaymentAddr != nil { if invoice.PaymentAddr != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@@ -223,6 +224,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
} }
invoice.PaymentAddr, err = parse32Bytes(base32Data) invoice.PaymentAddr, err = parse32Bytes(base32Data)
case fieldTypeD: case fieldTypeD:
if invoice.Description != nil { if invoice.Description != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@@ -231,6 +233,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
} }
invoice.Description, err = parseDescription(base32Data) invoice.Description, err = parseDescription(base32Data)
case fieldTypeM: case fieldTypeM:
if invoice.Metadata != nil { if invoice.Metadata != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@@ -248,6 +251,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
} }
invoice.Destination, err = parseDestination(base32Data) invoice.Destination, err = parseDestination(base32Data)
case fieldTypeH: case fieldTypeH:
if invoice.DescriptionHash != nil { if invoice.DescriptionHash != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@@ -256,6 +260,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
} }
invoice.DescriptionHash, err = parse32Bytes(base32Data) invoice.DescriptionHash, err = parse32Bytes(base32Data)
case fieldTypeX: case fieldTypeX:
if invoice.expiry != nil { if invoice.expiry != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@@ -264,6 +269,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
} }
invoice.expiry, err = parseExpiry(base32Data) invoice.expiry, err = parseExpiry(base32Data)
case fieldTypeC: case fieldTypeC:
if invoice.minFinalCLTVExpiry != nil { if invoice.minFinalCLTVExpiry != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@@ -271,7 +277,9 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
continue continue
} }
invoice.minFinalCLTVExpiry, err = parseMinFinalCLTVExpiry(base32Data) invoice.minFinalCLTVExpiry, err =
parseMinFinalCLTVExpiry(base32Data)
case fieldTypeF: case fieldTypeF:
if invoice.FallbackAddr != nil { if invoice.FallbackAddr != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@@ -279,7 +287,10 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
continue continue
} }
invoice.FallbackAddr, err = parseFallbackAddr(base32Data, net) invoice.FallbackAddr, err = parseFallbackAddr(
base32Data, net,
)
case fieldTypeR: case fieldTypeR:
// An `r` field can be included in an invoice multiple // An `r` field can be included in an invoice multiple
// times, so we won't skip it if we have already seen // times, so we won't skip it if we have already seen
@@ -289,7 +300,10 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
return err return err
} }
invoice.RouteHints = append(invoice.RouteHints, routeHint) invoice.RouteHints = append(
invoice.RouteHints, routeHint,
)
case fieldType9: case fieldType9:
if invoice.Features != nil { if invoice.Features != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
@@ -298,6 +312,19 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
} }
invoice.Features, err = parseFeatures(base32Data) invoice.Features, err = parseFeatures(base32Data)
case fieldTypeB:
blindedPaymentPath, err := parseBlindedPaymentPath(
base32Data,
)
if err != nil {
return err
}
invoice.BlindedPaymentPaths = append(
invoice.BlindedPaymentPaths, blindedPaymentPath,
)
default: default:
// Ignore unknown type. // Ignore unknown type.
} }
@@ -495,6 +522,17 @@ func parseRouteHint(data []byte) ([]HopHint, error) {
return routeHint, nil return routeHint, nil
} }
// parseBlindedPaymentPath attempts to parse a BlindedPaymentPath from the given
// byte slice.
func parseBlindedPaymentPath(data []byte) (*BlindedPaymentPath, error) {
base256Data, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}
return DecodeBlindedPayment(bytes.NewReader(base256Data))
}
// parseFeatures decodes any feature bits directly from the base32 // parseFeatures decodes any feature bits directly from the base32
// representation. // representation.
func parseFeatures(data []byte) (*lnwire.FeatureVector, error) { func parseFeatures(data []byte) (*lnwire.FeatureVector, error) {
+23
View File
@@ -260,6 +260,29 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
} }
} }
for _, path := range invoice.BlindedPaymentPaths {
var buf bytes.Buffer
err := path.Encode(&buf)
if err != nil {
return err
}
blindedPathBase32, err := bech32.ConvertBits(
buf.Bytes(), 8, 5, true,
)
if err != nil {
return err
}
err = writeTaggedField(
bufferBase32, fieldTypeB, blindedPathBase32,
)
if err != nil {
return err
}
}
if invoice.Destination != nil { if invoice.Destination != nil {
// Convert 33 byte pubkey to 53 5-bit groups. // Convert 33 byte pubkey to 53 5-bit groups.
pubKeyBase32, err := bech32.ConvertBits( pubKeyBase32, err := bech32.ConvertBits(
+33 -1
View File
@@ -76,6 +76,10 @@ const (
// probing the recipient. // probing the recipient.
fieldTypeS = 16 fieldTypeS = 16
// fieldTypeB contains blinded payment path information. This field may
// be repeated to include multiple blinded payment paths in the invoice.
fieldTypeB = 20
// maxInvoiceLength is the maximum total length an invoice can have. // maxInvoiceLength is the maximum total length an invoice can have.
// This is chosen to be the maximum number of bytes that can fit into a // This is chosen to be the maximum number of bytes that can fit into a
// single QR code: https://en.wikipedia.org/wiki/QR_code#Storage // single QR code: https://en.wikipedia.org/wiki/QR_code#Storage
@@ -152,6 +156,10 @@ type Invoice struct {
// This field is un-exported and can only be read by the // This field is un-exported and can only be read by the
// MinFinalCLTVExpiry() method. By forcing callers to read via this // MinFinalCLTVExpiry() method. By forcing callers to read via this
// method, we can easily enforce the default if not specified. // method, we can easily enforce the default if not specified.
//
// NOTE: this field is ignored in the case that the invoice contains
// blinded paths since then the final minimum cltv expiry delta is
// expected to be included in the route's accumulated CLTV delta value.
minFinalCLTVExpiry *uint64 minFinalCLTVExpiry *uint64
// Description is a short description of the purpose of this invoice. // Description is a short description of the purpose of this invoice.
@@ -180,9 +188,17 @@ type Invoice struct {
// hint can be individually used to reach the destination. These usually // hint can be individually used to reach the destination. These usually
// represent private routes. // represent private routes.
// //
// NOTE: This is optional. // NOTE: This is optional and should not be set at the same time as
// BlindedPaymentPaths.
RouteHints [][]HopHint RouteHints [][]HopHint
// BlindedPaymentPaths is a set of blinded payment paths that can be
// used to find the payment receiver.
//
// NOTE: This is optional and should not be set at the same time as
// RouteHints.
BlindedPaymentPaths []*BlindedPaymentPath
// Features represents an optional field used to signal optional or // Features represents an optional field used to signal optional or
// required support for features by the receiver. // required support for features by the receiver.
Features *lnwire.FeatureVector Features *lnwire.FeatureVector
@@ -263,6 +279,15 @@ func RouteHint(routeHint []HopHint) func(*Invoice) {
} }
} }
// WithBlindedPaymentPath is a functional option that allows a caller of
// NewInvoice to attach a blinded payment path to the invoice. The option can
// be used multiple times to attach multiple paths.
func WithBlindedPaymentPath(p *BlindedPaymentPath) func(*Invoice) {
return func(i *Invoice) {
i.BlindedPaymentPaths = append(i.BlindedPaymentPaths, p)
}
}
// Features is a functional option that allows callers of NewInvoice to set the // Features is a functional option that allows callers of NewInvoice to set the
// desired feature bits that are advertised on the invoice. If this option is // desired feature bits that are advertised on the invoice. If this option is
// not used, an empty feature vector will automatically be populated. // not used, an empty feature vector will automatically be populated.
@@ -355,6 +380,13 @@ func validateInvoice(invoice *Invoice) error {
return fmt.Errorf("no payment hash found") return fmt.Errorf("no payment hash found")
} }
if len(invoice.RouteHints) != 0 &&
len(invoice.BlindedPaymentPaths) != 0 {
return fmt.Errorf("cannot have both route hints and blinded " +
"payment paths")
}
// Either Description or DescriptionHash must be set, not both. // Either Description or DescriptionHash must be set, not both.
if invoice.Description != nil && invoice.DescriptionHash != nil { if invoice.Description != nil && invoice.DescriptionHash != nil {
return fmt.Errorf("both description and description hash set") return fmt.Errorf("both description and description hash set")
+147 -113
View File
@@ -7,7 +7,6 @@ import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"reflect"
"strings" "strings"
"testing" "testing"
"time" "time"
@@ -17,7 +16,9 @@ import (
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
) )
var ( var (
@@ -116,6 +117,62 @@ var (
// Must be initialized in init(). // Must be initialized in init().
testDescriptionHash [32]byte testDescriptionHash [32]byte
testBlindedPK1Bytes, _ = hex.DecodeString("03f3311e948feb5115242c4e39" +
"6c81c448ab7ee5fd24c4e24e66c73533cc4f98b8")
testBlindedHopPK1, _ = btcec.ParsePubKey(testBlindedPK1Bytes)
testBlindedPK2Bytes, _ = hex.DecodeString("03a8c97ed5cd40d474e4ef18c8" +
"99854b25e5070106504cb225e6d2c112d61a805e")
testBlindedHopPK2, _ = btcec.ParsePubKey(testBlindedPK2Bytes)
testBlindedPK3Bytes, _ = hex.DecodeString("0220293926219d8efe733336e2" +
"b674570dd96aa763acb3564e6e367b384d861a0a")
testBlindedHopPK3, _ = btcec.ParsePubKey(testBlindedPK3Bytes)
testBlindedPK4Bytes, _ = hex.DecodeString("02c75eb336a038294eaaf76015" +
"8b2e851c3c0937262e35401ae64a1bee71a2e40c")
testBlindedHopPK4, _ = btcec.ParsePubKey(testBlindedPK4Bytes)
blindedPath1 = &BlindedPaymentPath{
FeeBaseMsat: 40,
FeeRate: 20,
CltvExpiryDelta: 130,
HTLCMinMsat: 2,
HTLCMaxMsat: 100,
Features: lnwire.EmptyFeatureVector(),
FirstEphemeralBlindingPoint: testBlindedHopPK1,
Hops: []*sphinx.BlindedHopInfo{
{
BlindedNodePub: testBlindedHopPK2,
CipherText: []byte{1, 2, 3, 4, 5},
},
{
BlindedNodePub: testBlindedHopPK3,
CipherText: []byte{5, 4, 3, 2, 1},
},
{
BlindedNodePub: testBlindedHopPK4,
CipherText: []byte{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14,
},
},
},
}
blindedPath2 = &BlindedPaymentPath{
FeeBaseMsat: 4,
FeeRate: 2,
CltvExpiryDelta: 10,
HTLCMinMsat: 0,
HTLCMaxMsat: 10,
Features: lnwire.EmptyFeatureVector(),
FirstEphemeralBlindingPoint: testBlindedHopPK4,
Hops: []*sphinx.BlindedHopInfo{
{
BlindedNodePub: testBlindedHopPK3,
CipherText: []byte{1, 2, 3, 4, 5},
},
},
}
) )
func init() { func init() {
@@ -125,6 +182,8 @@ func init() {
// TestDecodeEncode tests that an encoded invoice gets decoded into the expected // TestDecodeEncode tests that an encoded invoice gets decoded into the expected
// Invoice object, and that reencoding the decoded invoice gets us back to the // Invoice object, and that reencoding the decoded invoice gets us back to the
// original encoded string. // original encoded string.
//
//nolint:lll
func TestDecodeEncode(t *testing.T) { func TestDecodeEncode(t *testing.T) {
t.Parallel() t.Parallel()
@@ -673,52 +732,77 @@ func TestDecodeEncode(t *testing.T) {
i.Destination = nil i.Destination = nil
}, },
}, },
{
// Invoice with blinded payment paths.
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4js5fdqqqqq2qqqqqpgqyzqqqqqqqqqqqqyqqqqqqqqqqqvsqqqqlnxy0ffrlt2y2jgtzw89kgr3zg4dlwtlfycn3yuek8x5eucnuchqps82xf0m2u6sx5wnjw7xxgnxz5kf09quqsv5zvkgj7d5kpzttp4qz7q5qsyqcyq5pzq2feycsemrh7wvendc4kw3tsmkt25a36ev6kfehrv7ecfkrp5zs9q5zqxqspqtr4avek5quzjn427asptzews5wrczfhychr2sq6ue9phmn35tjqcrspqgpsgpgxquyqjzstpsxsu59zqqqqqpqqqqqqyqq2qqqqqqqqqqqqqqqqqqqqqqqqpgqqqqk8t6endgpc99824amqzk9japgu8synwf3wx4qp4ej2r0h8rghypsqsygpf8ynzr8vwleenxdhzke69wrwed2nk8t9n2e8xudnm8pxcvxs2q5qsyqcyq5y4rdlhtf84f8rgdj34275juwls2ftxtcfh035863q3p9k6s94hpxhdmzfn5gxpsazdznxs56j4vt3fdhe00g9v2l3szher50hp4xlggqkxf77f",
valid: true,
decodedInvoice: func() *Invoice {
return &Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
Description: &testCupOfCoffee,
Destination: testPubKey,
Features: emptyFeatures,
BlindedPaymentPaths: []*BlindedPaymentPath{
blindedPath1,
blindedPath2,
},
}
},
beforeEncoding: func(i *Invoice) {
// Since this destination pubkey was recovered
// from the signature, we must set it nil before
// encoding to get back the same invoice string.
i.Destination = nil
},
},
} }
for i, test := range tests { for i, test := range tests {
var decodedInvoice *Invoice test := test
net := &chaincfg.MainNetParams
if test.decodedInvoice != nil {
decodedInvoice = test.decodedInvoice()
net = decodedInvoice.Net
}
invoice, err := Decode(test.encodedInvoice, net) t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
if (err == nil) != test.valid { t.Parallel()
t.Errorf("Decoding test %d failed: %v", i, err)
return
}
if test.valid { var decodedInvoice *Invoice
if err := compareInvoices(decodedInvoice, invoice); err != nil { net := &chaincfg.MainNetParams
t.Errorf("Invoice decoding result %d not as expected: %v", i, err) if test.decodedInvoice != nil {
decodedInvoice = test.decodedInvoice()
net = decodedInvoice.Net
}
invoice, err := Decode(test.encodedInvoice, net)
if !test.valid {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, decodedInvoice, invoice)
}
if test.skipEncoding {
return return
} }
}
if test.skipEncoding { if test.beforeEncoding != nil {
continue test.beforeEncoding(decodedInvoice)
} }
if test.beforeEncoding != nil { if decodedInvoice == nil {
test.beforeEncoding(decodedInvoice) return
} }
if decodedInvoice != nil {
reencoded, err := decodedInvoice.Encode( reencoded, err := decodedInvoice.Encode(
testMessageSigner, testMessageSigner,
) )
if (err == nil) != test.valid { if !test.valid {
t.Errorf("Encoding test %d failed: %v", i, err) require.Error(t, err)
return return
} }
require.NoError(t, err)
if test.valid && test.encodedInvoice != reencoded { require.Equal(t, test.encodedInvoice, reencoded)
t.Errorf("Encoding %d failed, expected %v, got %v", })
i, test.encodedInvoice, reencoded)
return
}
}
} }
} }
@@ -805,25 +889,42 @@ func TestNewInvoice(t *testing.T) {
valid: true, valid: true,
encodedInvoice: "lnbcrt241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66df5c8pqjjt4z4ymmuaxfx8eh5v7hmzs3wrfas8m2sz5qz56rw2lxy8mmgm4xln0ha26qkw6u3vhu22pss2udugr9g74c3x20slpcqjgq0el4h6", encodedInvoice: "lnbcrt241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66df5c8pqjjt4z4ymmuaxfx8eh5v7hmzs3wrfas8m2sz5qz56rw2lxy8mmgm4xln0ha26qkw6u3vhu22pss2udugr9g74c3x20slpcqjgq0el4h6",
}, },
{
// Mainnet invoice with two blinded paths.
newInvoice: func() (*Invoice, error) {
return NewInvoice(&chaincfg.MainNetParams,
testPaymentHash,
time.Unix(1496314658, 0),
Amount(testMillisat20mBTC),
Description(testCupOfCoffee),
WithBlindedPaymentPath(blindedPath1),
WithBlindedPaymentPath(blindedPath2),
)
},
valid: true,
//nolint:lll
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4js5fdqqqqq2qqqqqpgqyzqqqqqqqqqqqqyqqqqqqqqqqqvsqqqqlnxy0ffrlt2y2jgtzw89kgr3zg4dlwtlfycn3yuek8x5eucnuchqps82xf0m2u6sx5wnjw7xxgnxz5kf09quqsv5zvkgj7d5kpzttp4qz7q5qsyqcyq5pzq2feycsemrh7wvendc4kw3tsmkt25a36ev6kfehrv7ecfkrp5zs9q5zqxqspqtr4avek5quzjn427asptzews5wrczfhychr2sq6ue9phmn35tjqcrspqgpsgpgxquyqjzstpsxsu59zqqqqqpqqqqqqyqq2qqqqqqqqqqqqqqqqqqqqqqqqpgqqqqk8t6endgpc99824amqzk9japgu8synwf3wx4qp4ej2r0h8rghypsqsygpf8ynzr8vwleenxdhzke69wrwed2nk8t9n2e8xudnm8pxcvxs2q5qsyqcyq5y4rdlhtf84f8rgdj34275juwls2ftxtcfh035863q3p9k6s94hpxhdmzfn5gxpsazdznxs56j4vt3fdhe00g9v2l3szher50hp4xlggqkxf77f",
},
} }
for i, test := range tests { for i, test := range tests {
test := test
invoice, err := test.newInvoice() t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
if err != nil && !test.valid { t.Parallel()
continue
}
encoded, err := invoice.Encode(testMessageSigner)
if (err == nil) != test.valid {
t.Errorf("NewInvoice test %d failed: %v", i, err)
return
}
if test.valid && test.encodedInvoice != encoded { invoice, err := test.newInvoice()
t.Errorf("Encoding %d failed, expected %v, got %v", if !test.valid {
i, test.encodedInvoice, encoded) require.Error(t, err)
return return
} }
require.NoError(t, err)
encoded, err := invoice.Encode(testMessageSigner)
require.NoError(t, err)
require.Equal(t, test.encodedInvoice, encoded)
})
} }
} }
@@ -909,73 +1010,6 @@ func TestInvoiceChecksumMalleability(t *testing.T) {
} }
} }
func compareInvoices(expected, actual *Invoice) error {
if !reflect.DeepEqual(expected.Net, actual.Net) {
return fmt.Errorf("expected net %v, got %v",
expected.Net, actual.Net)
}
if !reflect.DeepEqual(expected.MilliSat, actual.MilliSat) {
return fmt.Errorf("expected milli sat %d, got %d",
*expected.MilliSat, *actual.MilliSat)
}
if expected.Timestamp != actual.Timestamp {
return fmt.Errorf("expected timestamp %v, got %v",
expected.Timestamp, actual.Timestamp)
}
if !compareHashes(expected.PaymentHash, actual.PaymentHash) {
return fmt.Errorf("expected payment hash %x, got %x",
*expected.PaymentHash, *actual.PaymentHash)
}
if !reflect.DeepEqual(expected.Description, actual.Description) {
return fmt.Errorf("expected description \"%s\", got \"%s\"",
*expected.Description, *actual.Description)
}
if !comparePubkeys(expected.Destination, actual.Destination) {
return fmt.Errorf("expected destination pubkey %x, got %x",
expected.Destination.SerializeCompressed(),
actual.Destination.SerializeCompressed())
}
if !compareHashes(expected.DescriptionHash, actual.DescriptionHash) {
return fmt.Errorf("expected description hash %x, got %x",
*expected.DescriptionHash, *actual.DescriptionHash)
}
if expected.Expiry() != actual.Expiry() {
return fmt.Errorf("expected expiry %d, got %d",
expected.Expiry(), actual.Expiry())
}
if !reflect.DeepEqual(expected.FallbackAddr, actual.FallbackAddr) {
return fmt.Errorf("expected FallbackAddr %v, got %v",
expected.FallbackAddr, actual.FallbackAddr)
}
if len(expected.RouteHints) != len(actual.RouteHints) {
return fmt.Errorf("expected %d RouteHints, got %d",
len(expected.RouteHints), len(actual.RouteHints))
}
for i, routeHint := range expected.RouteHints {
err := compareRouteHints(routeHint, actual.RouteHints[i])
if err != nil {
return err
}
}
if !reflect.DeepEqual(expected.Features, actual.Features) {
return fmt.Errorf("expected features %v, got %v",
expected.Features, actual.Features)
}
return nil
}
func comparePubkeys(a, b *btcec.PublicKey) bool { func comparePubkeys(a, b *btcec.PublicKey) bool {
if a == b { if a == b {
return true return true