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{
Name: "final_cltv_delta",
Usage: "(optional) number of blocks the last hop has to reveal " +
"the preimage",
Usage: "(optional) number of blocks the last hop has " +
"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{
Name: "use_mc",
@@ -1238,6 +1243,13 @@ func parseBlindedPaymentParameters(ctx *cli.Context) (
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
// full set to be set and we'll error out accordingly.
introNode, err := route.NewVertexFromStr(
@@ -89,6 +89,9 @@
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.
* [Groundwork](https://github.com/lightningnetwork/lnd/pull/8752) in preparation
for implementing route blinding receives.
## Testing
## Database
+27 -4
View File
@@ -360,6 +360,7 @@ func (b *BlindingKit) DecryptAndValidateFwdInfo(payload *Payload,
if err != nil {
return nil, err
}
// Validate the data in the blinded route against our incoming htlc's
// information.
if err := ValidateBlindedRouteData(
@@ -368,9 +369,31 @@ func (b *BlindingKit) DecryptAndValidateFwdInfo(payload *Payload,
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(
b.IncomingAmount, routeData.RelayInfo.Val.BaseFee,
routeData.RelayInfo.Val.FeeRate,
b.IncomingAmount, relayInfo.Val.BaseFee, relayInfo.Val.FeeRate,
)
if err != nil {
return nil, err
@@ -400,10 +423,10 @@ func (b *BlindingKit) DecryptAndValidateFwdInfo(payload *Payload,
}
return &ForwardingInfo{
NextHop: routeData.ShortChannelID.Val,
NextHop: nextSCID.Val,
AmountToForward: fwdAmt,
OutgoingCTLV: b.IncomingCltv - uint32(
routeData.RelayInfo.Val.CltvExpiryDelta,
relayInfo.Val.CltvExpiryDelta,
),
// Remap from blinding override type to blinding point type.
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.
maxCltv := 1000
blindedData := record.NewBlindedRouteData(
blindedData := record.NewNonFinalBlindedRouteData(
lnwire.NewShortChanIDFromInt(1500), nil,
record.PaymentRelayInfo{
CltvExpiryDelta: 10,
+6 -6
View File
@@ -646,7 +646,7 @@ func TestValidateBlindedRouteData(t *testing.T) {
}{
{
name: "max cltv expired",
data: record.NewBlindedRouteData(
data: record.NewNonFinalBlindedRouteData(
scid,
nil,
record.PaymentRelayInfo{},
@@ -663,7 +663,7 @@ func TestValidateBlindedRouteData(t *testing.T) {
},
{
name: "zero max cltv",
data: record.NewBlindedRouteData(
data: record.NewNonFinalBlindedRouteData(
scid,
nil,
record.PaymentRelayInfo{},
@@ -682,7 +682,7 @@ func TestValidateBlindedRouteData(t *testing.T) {
},
{
name: "amount below minimum",
data: record.NewBlindedRouteData(
data: record.NewNonFinalBlindedRouteData(
scid,
nil,
record.PaymentRelayInfo{},
@@ -699,7 +699,7 @@ func TestValidateBlindedRouteData(t *testing.T) {
},
{
name: "valid, no features",
data: record.NewBlindedRouteData(
data: record.NewNonFinalBlindedRouteData(
scid,
nil,
record.PaymentRelayInfo{},
@@ -714,7 +714,7 @@ func TestValidateBlindedRouteData(t *testing.T) {
},
{
name: "unknown features",
data: record.NewBlindedRouteData(
data: record.NewNonFinalBlindedRouteData(
scid,
nil,
record.PaymentRelayInfo{},
@@ -738,7 +738,7 @@ func TestValidateBlindedRouteData(t *testing.T) {
},
{
name: "valid data",
data: record.NewBlindedRouteData(
data: record.NewNonFinalBlindedRouteData(
scid,
nil,
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
// blinded hop.
payload := record.NewBlindedRouteData(
payload := record.NewNonFinalBlindedRouteData(
scid, nil, *relayInfo, constraints, nil,
)
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
// forwarding hop (though in reality it's the last
// hop).
record.NewBlindedRouteData(
record.NewNonFinalBlindedRouteData(
lnwire.NewShortChanIDFromInt(100), nil,
record.PaymentRelayInfo{}, nil, nil,
),
+54
View File
@@ -266,6 +266,60 @@ func CreateRPCRouteHints(routeHints [][]zpay32.HopHint) []*lnrpc.RouteHint {
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
// into an invoice decoded form.
func CreateZpay32HopHints(routeHints []*lnrpc.RouteHint) ([][]zpay32.HopHint, error) {
+214 -201
View File
@@ -14304,19 +14304,20 @@ type PayReq struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
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"`
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"`
Expiry int64 `protobuf:"varint,5,opt,name=expiry,proto3" json:"expiry,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"`
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"`
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"`
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"`
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"`
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"`
Expiry int64 `protobuf:"varint,5,opt,name=expiry,proto3" json:"expiry,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"`
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"`
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"`
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"`
BlindedPaths []*BlindedPaymentPath `protobuf:"bytes,14,rep,name=blinded_paths,json=blindedPaths,proto3" json:"blinded_paths,omitempty"`
}
func (x *PayReq) Reset() {
@@ -14442,6 +14443,13 @@ func (x *PayReq) GetFeatures() map[uint32]*Feature {
return nil
}
func (x *PayReq) GetBlindedPaths() []*BlindedPaymentPath {
if x != nil {
return x.BlindedPaths
}
return nil
}
type Feature struct {
state protoimpl.MessageState
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,
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,
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,
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,
@@ -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,
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,
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,
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,
@@ -21520,192 +21532,193 @@ var file_lightning_proto_depIdxs = []int32{
38, // 138: lnrpc.AbandonChannelRequest.channel_point:type_name -> lnrpc.ChannelPoint
150, // 139: lnrpc.PayReq.route_hints:type_name -> lnrpc.RouteHint
244, // 140: lnrpc.PayReq.features:type_name -> lnrpc.PayReq.FeaturesEntry
179, // 141: lnrpc.FeeReportResponse.channel_fees:type_name -> lnrpc.ChannelFeeReport
38, // 142: lnrpc.PolicyUpdateRequest.chan_point:type_name -> lnrpc.ChannelPoint
181, // 143: lnrpc.PolicyUpdateRequest.inbound_fee:type_name -> lnrpc.InboundFee
39, // 144: lnrpc.FailedUpdate.outpoint:type_name -> lnrpc.OutPoint
11, // 145: lnrpc.FailedUpdate.reason:type_name -> lnrpc.UpdateFailure
183, // 146: lnrpc.PolicyUpdateResponse.failed_updates:type_name -> lnrpc.FailedUpdate
186, // 147: lnrpc.ForwardingHistoryResponse.forwarding_events:type_name -> lnrpc.ForwardingEvent
38, // 148: lnrpc.ExportChannelBackupRequest.chan_point:type_name -> lnrpc.ChannelPoint
38, // 149: lnrpc.ChannelBackup.chan_point:type_name -> lnrpc.ChannelPoint
38, // 150: lnrpc.MultiChanBackup.chan_points:type_name -> lnrpc.ChannelPoint
193, // 151: lnrpc.ChanBackupSnapshot.single_chan_backups:type_name -> lnrpc.ChannelBackups
190, // 152: lnrpc.ChanBackupSnapshot.multi_chan_backup:type_name -> lnrpc.MultiChanBackup
189, // 153: lnrpc.ChannelBackups.chan_backups:type_name -> lnrpc.ChannelBackup
193, // 154: lnrpc.RestoreChanBackupRequest.chan_backups:type_name -> lnrpc.ChannelBackups
198, // 155: lnrpc.BakeMacaroonRequest.permissions:type_name -> lnrpc.MacaroonPermission
198, // 156: lnrpc.MacaroonPermissionList.permissions:type_name -> lnrpc.MacaroonPermission
245, // 157: lnrpc.ListPermissionsResponse.method_permissions:type_name -> lnrpc.ListPermissionsResponse.MethodPermissionsEntry
20, // 158: lnrpc.Failure.code:type_name -> lnrpc.Failure.FailureCode
209, // 159: lnrpc.Failure.channel_update:type_name -> lnrpc.ChannelUpdate
211, // 160: lnrpc.MacaroonId.ops:type_name -> lnrpc.Op
198, // 161: lnrpc.CheckMacPermRequest.permissions:type_name -> lnrpc.MacaroonPermission
215, // 162: lnrpc.RPCMiddlewareRequest.stream_auth:type_name -> lnrpc.StreamAuth
216, // 163: lnrpc.RPCMiddlewareRequest.request:type_name -> lnrpc.RPCMessage
216, // 164: lnrpc.RPCMiddlewareRequest.response:type_name -> lnrpc.RPCMessage
218, // 165: lnrpc.RPCMiddlewareResponse.register:type_name -> lnrpc.MiddlewareRegistration
219, // 166: lnrpc.RPCMiddlewareResponse.feedback:type_name -> lnrpc.InterceptFeedback
177, // 167: lnrpc.Peer.FeaturesEntry.value:type_name -> lnrpc.Feature
177, // 168: lnrpc.GetInfoResponse.FeaturesEntry.value:type_name -> lnrpc.Feature
4, // 169: lnrpc.PendingChannelsResponse.PendingChannel.initiator:type_name -> lnrpc.Initiator
3, // 170: lnrpc.PendingChannelsResponse.PendingChannel.commitment_type:type_name -> lnrpc.CommitmentType
226, // 171: lnrpc.PendingChannelsResponse.PendingOpenChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
226, // 172: lnrpc.PendingChannelsResponse.WaitingCloseChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
229, // 173: lnrpc.PendingChannelsResponse.WaitingCloseChannel.commitments:type_name -> lnrpc.PendingChannelsResponse.Commitments
226, // 174: lnrpc.PendingChannelsResponse.ClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
226, // 175: lnrpc.PendingChannelsResponse.ForceClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
108, // 176: lnrpc.PendingChannelsResponse.ForceClosedChannel.pending_htlcs:type_name -> lnrpc.PendingHTLC
15, // 177: lnrpc.PendingChannelsResponse.ForceClosedChannel.anchor:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState
113, // 178: lnrpc.WalletBalanceResponse.AccountBalanceEntry.value:type_name -> lnrpc.WalletAccountBalance
177, // 179: lnrpc.LightningNode.FeaturesEntry.value:type_name -> lnrpc.Feature
137, // 180: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry.value:type_name -> lnrpc.FloatMetric
177, // 181: lnrpc.NodeUpdate.FeaturesEntry.value:type_name -> lnrpc.Feature
177, // 182: lnrpc.Invoice.FeaturesEntry.value:type_name -> lnrpc.Feature
154, // 183: lnrpc.Invoice.AmpInvoiceStateEntry.value:type_name -> lnrpc.AMPInvoiceState
177, // 184: lnrpc.PayReq.FeaturesEntry.value:type_name -> lnrpc.Feature
205, // 185: lnrpc.ListPermissionsResponse.MethodPermissionsEntry.value:type_name -> lnrpc.MacaroonPermissionList
114, // 186: lnrpc.Lightning.WalletBalance:input_type -> lnrpc.WalletBalanceRequest
117, // 187: lnrpc.Lightning.ChannelBalance:input_type -> lnrpc.ChannelBalanceRequest
30, // 188: lnrpc.Lightning.GetTransactions:input_type -> lnrpc.GetTransactionsRequest
42, // 189: lnrpc.Lightning.EstimateFee:input_type -> lnrpc.EstimateFeeRequest
46, // 190: lnrpc.Lightning.SendCoins:input_type -> lnrpc.SendCoinsRequest
48, // 191: lnrpc.Lightning.ListUnspent:input_type -> lnrpc.ListUnspentRequest
30, // 192: lnrpc.Lightning.SubscribeTransactions:input_type -> lnrpc.GetTransactionsRequest
44, // 193: lnrpc.Lightning.SendMany:input_type -> lnrpc.SendManyRequest
50, // 194: lnrpc.Lightning.NewAddress:input_type -> lnrpc.NewAddressRequest
52, // 195: lnrpc.Lightning.SignMessage:input_type -> lnrpc.SignMessageRequest
54, // 196: lnrpc.Lightning.VerifyMessage:input_type -> lnrpc.VerifyMessageRequest
56, // 197: lnrpc.Lightning.ConnectPeer:input_type -> lnrpc.ConnectPeerRequest
58, // 198: lnrpc.Lightning.DisconnectPeer:input_type -> lnrpc.DisconnectPeerRequest
74, // 199: lnrpc.Lightning.ListPeers:input_type -> lnrpc.ListPeersRequest
76, // 200: lnrpc.Lightning.SubscribePeerEvents:input_type -> lnrpc.PeerEventSubscription
78, // 201: lnrpc.Lightning.GetInfo:input_type -> lnrpc.GetInfoRequest
80, // 202: lnrpc.Lightning.GetDebugInfo:input_type -> lnrpc.GetDebugInfoRequest
82, // 203: lnrpc.Lightning.GetRecoveryInfo:input_type -> lnrpc.GetRecoveryInfoRequest
109, // 204: lnrpc.Lightning.PendingChannels:input_type -> lnrpc.PendingChannelsRequest
63, // 205: lnrpc.Lightning.ListChannels:input_type -> lnrpc.ListChannelsRequest
111, // 206: lnrpc.Lightning.SubscribeChannelEvents:input_type -> lnrpc.ChannelEventSubscription
70, // 207: lnrpc.Lightning.ClosedChannels:input_type -> lnrpc.ClosedChannelsRequest
96, // 208: lnrpc.Lightning.OpenChannelSync:input_type -> lnrpc.OpenChannelRequest
96, // 209: lnrpc.Lightning.OpenChannel:input_type -> lnrpc.OpenChannelRequest
93, // 210: lnrpc.Lightning.BatchOpenChannel:input_type -> lnrpc.BatchOpenChannelRequest
106, // 211: lnrpc.Lightning.FundingStateStep:input_type -> lnrpc.FundingTransitionMsg
37, // 212: lnrpc.Lightning.ChannelAcceptor:input_type -> lnrpc.ChannelAcceptResponse
88, // 213: lnrpc.Lightning.CloseChannel:input_type -> lnrpc.CloseChannelRequest
171, // 214: lnrpc.Lightning.AbandonChannel:input_type -> lnrpc.AbandonChannelRequest
33, // 215: lnrpc.Lightning.SendPayment:input_type -> lnrpc.SendRequest
33, // 216: lnrpc.Lightning.SendPaymentSync:input_type -> lnrpc.SendRequest
35, // 217: lnrpc.Lightning.SendToRoute:input_type -> lnrpc.SendToRouteRequest
35, // 218: lnrpc.Lightning.SendToRouteSync:input_type -> lnrpc.SendToRouteRequest
155, // 219: lnrpc.Lightning.AddInvoice:input_type -> lnrpc.Invoice
160, // 220: lnrpc.Lightning.ListInvoices:input_type -> lnrpc.ListInvoiceRequest
159, // 221: lnrpc.Lightning.LookupInvoice:input_type -> lnrpc.PaymentHash
162, // 222: lnrpc.Lightning.SubscribeInvoices:input_type -> lnrpc.InvoiceSubscription
175, // 223: lnrpc.Lightning.DecodePayReq:input_type -> lnrpc.PayReqString
165, // 224: lnrpc.Lightning.ListPayments:input_type -> lnrpc.ListPaymentsRequest
167, // 225: lnrpc.Lightning.DeletePayment:input_type -> lnrpc.DeletePaymentRequest
168, // 226: lnrpc.Lightning.DeleteAllPayments:input_type -> lnrpc.DeleteAllPaymentsRequest
133, // 227: lnrpc.Lightning.DescribeGraph:input_type -> lnrpc.ChannelGraphRequest
135, // 228: lnrpc.Lightning.GetNodeMetrics:input_type -> lnrpc.NodeMetricsRequest
138, // 229: lnrpc.Lightning.GetChanInfo:input_type -> lnrpc.ChanInfoRequest
127, // 230: lnrpc.Lightning.GetNodeInfo:input_type -> lnrpc.NodeInfoRequest
119, // 231: lnrpc.Lightning.QueryRoutes:input_type -> lnrpc.QueryRoutesRequest
139, // 232: lnrpc.Lightning.GetNetworkInfo:input_type -> lnrpc.NetworkInfoRequest
141, // 233: lnrpc.Lightning.StopDaemon:input_type -> lnrpc.StopRequest
143, // 234: lnrpc.Lightning.SubscribeChannelGraph:input_type -> lnrpc.GraphTopologySubscription
173, // 235: lnrpc.Lightning.DebugLevel:input_type -> lnrpc.DebugLevelRequest
178, // 236: lnrpc.Lightning.FeeReport:input_type -> lnrpc.FeeReportRequest
182, // 237: lnrpc.Lightning.UpdateChannelPolicy:input_type -> lnrpc.PolicyUpdateRequest
185, // 238: lnrpc.Lightning.ForwardingHistory:input_type -> lnrpc.ForwardingHistoryRequest
188, // 239: lnrpc.Lightning.ExportChannelBackup:input_type -> lnrpc.ExportChannelBackupRequest
191, // 240: lnrpc.Lightning.ExportAllChannelBackups:input_type -> lnrpc.ChanBackupExportRequest
192, // 241: lnrpc.Lightning.VerifyChanBackup:input_type -> lnrpc.ChanBackupSnapshot
194, // 242: lnrpc.Lightning.RestoreChannelBackups:input_type -> lnrpc.RestoreChanBackupRequest
196, // 243: lnrpc.Lightning.SubscribeChannelBackups:input_type -> lnrpc.ChannelBackupSubscription
199, // 244: lnrpc.Lightning.BakeMacaroon:input_type -> lnrpc.BakeMacaroonRequest
201, // 245: lnrpc.Lightning.ListMacaroonIDs:input_type -> lnrpc.ListMacaroonIDsRequest
203, // 246: lnrpc.Lightning.DeleteMacaroonID:input_type -> lnrpc.DeleteMacaroonIDRequest
206, // 247: lnrpc.Lightning.ListPermissions:input_type -> lnrpc.ListPermissionsRequest
212, // 248: lnrpc.Lightning.CheckMacaroonPermissions:input_type -> lnrpc.CheckMacPermRequest
217, // 249: lnrpc.Lightning.RegisterRPCMiddleware:input_type -> lnrpc.RPCMiddlewareResponse
25, // 250: lnrpc.Lightning.SendCustomMessage:input_type -> lnrpc.SendCustomMessageRequest
23, // 251: lnrpc.Lightning.SubscribeCustomMessages:input_type -> lnrpc.SubscribeCustomMessagesRequest
66, // 252: lnrpc.Lightning.ListAliases:input_type -> lnrpc.ListAliasesRequest
21, // 253: lnrpc.Lightning.LookupHtlcResolution:input_type -> lnrpc.LookupHtlcResolutionRequest
115, // 254: lnrpc.Lightning.WalletBalance:output_type -> lnrpc.WalletBalanceResponse
118, // 255: lnrpc.Lightning.ChannelBalance:output_type -> lnrpc.ChannelBalanceResponse
31, // 256: lnrpc.Lightning.GetTransactions:output_type -> lnrpc.TransactionDetails
43, // 257: lnrpc.Lightning.EstimateFee:output_type -> lnrpc.EstimateFeeResponse
47, // 258: lnrpc.Lightning.SendCoins:output_type -> lnrpc.SendCoinsResponse
49, // 259: lnrpc.Lightning.ListUnspent:output_type -> lnrpc.ListUnspentResponse
29, // 260: lnrpc.Lightning.SubscribeTransactions:output_type -> lnrpc.Transaction
45, // 261: lnrpc.Lightning.SendMany:output_type -> lnrpc.SendManyResponse
51, // 262: lnrpc.Lightning.NewAddress:output_type -> lnrpc.NewAddressResponse
53, // 263: lnrpc.Lightning.SignMessage:output_type -> lnrpc.SignMessageResponse
55, // 264: lnrpc.Lightning.VerifyMessage:output_type -> lnrpc.VerifyMessageResponse
57, // 265: lnrpc.Lightning.ConnectPeer:output_type -> lnrpc.ConnectPeerResponse
59, // 266: lnrpc.Lightning.DisconnectPeer:output_type -> lnrpc.DisconnectPeerResponse
75, // 267: lnrpc.Lightning.ListPeers:output_type -> lnrpc.ListPeersResponse
77, // 268: lnrpc.Lightning.SubscribePeerEvents:output_type -> lnrpc.PeerEvent
79, // 269: lnrpc.Lightning.GetInfo:output_type -> lnrpc.GetInfoResponse
81, // 270: lnrpc.Lightning.GetDebugInfo:output_type -> lnrpc.GetDebugInfoResponse
83, // 271: lnrpc.Lightning.GetRecoveryInfo:output_type -> lnrpc.GetRecoveryInfoResponse
110, // 272: lnrpc.Lightning.PendingChannels:output_type -> lnrpc.PendingChannelsResponse
64, // 273: lnrpc.Lightning.ListChannels:output_type -> lnrpc.ListChannelsResponse
112, // 274: lnrpc.Lightning.SubscribeChannelEvents:output_type -> lnrpc.ChannelEventUpdate
71, // 275: lnrpc.Lightning.ClosedChannels:output_type -> lnrpc.ClosedChannelsResponse
38, // 276: lnrpc.Lightning.OpenChannelSync:output_type -> lnrpc.ChannelPoint
97, // 277: lnrpc.Lightning.OpenChannel:output_type -> lnrpc.OpenStatusUpdate
95, // 278: lnrpc.Lightning.BatchOpenChannel:output_type -> lnrpc.BatchOpenChannelResponse
107, // 279: lnrpc.Lightning.FundingStateStep:output_type -> lnrpc.FundingStateStepResp
36, // 280: lnrpc.Lightning.ChannelAcceptor:output_type -> lnrpc.ChannelAcceptRequest
89, // 281: lnrpc.Lightning.CloseChannel:output_type -> lnrpc.CloseStatusUpdate
172, // 282: lnrpc.Lightning.AbandonChannel:output_type -> lnrpc.AbandonChannelResponse
34, // 283: lnrpc.Lightning.SendPayment:output_type -> lnrpc.SendResponse
34, // 284: lnrpc.Lightning.SendPaymentSync:output_type -> lnrpc.SendResponse
34, // 285: lnrpc.Lightning.SendToRoute:output_type -> lnrpc.SendResponse
34, // 286: lnrpc.Lightning.SendToRouteSync:output_type -> lnrpc.SendResponse
158, // 287: lnrpc.Lightning.AddInvoice:output_type -> lnrpc.AddInvoiceResponse
161, // 288: lnrpc.Lightning.ListInvoices:output_type -> lnrpc.ListInvoiceResponse
155, // 289: lnrpc.Lightning.LookupInvoice:output_type -> lnrpc.Invoice
155, // 290: lnrpc.Lightning.SubscribeInvoices:output_type -> lnrpc.Invoice
176, // 291: lnrpc.Lightning.DecodePayReq:output_type -> lnrpc.PayReq
166, // 292: lnrpc.Lightning.ListPayments:output_type -> lnrpc.ListPaymentsResponse
169, // 293: lnrpc.Lightning.DeletePayment:output_type -> lnrpc.DeletePaymentResponse
170, // 294: lnrpc.Lightning.DeleteAllPayments:output_type -> lnrpc.DeleteAllPaymentsResponse
134, // 295: lnrpc.Lightning.DescribeGraph:output_type -> lnrpc.ChannelGraph
136, // 296: lnrpc.Lightning.GetNodeMetrics:output_type -> lnrpc.NodeMetricsResponse
132, // 297: lnrpc.Lightning.GetChanInfo:output_type -> lnrpc.ChannelEdge
128, // 298: lnrpc.Lightning.GetNodeInfo:output_type -> lnrpc.NodeInfo
122, // 299: lnrpc.Lightning.QueryRoutes:output_type -> lnrpc.QueryRoutesResponse
140, // 300: lnrpc.Lightning.GetNetworkInfo:output_type -> lnrpc.NetworkInfo
142, // 301: lnrpc.Lightning.StopDaemon:output_type -> lnrpc.StopResponse
144, // 302: lnrpc.Lightning.SubscribeChannelGraph:output_type -> lnrpc.GraphTopologyUpdate
174, // 303: lnrpc.Lightning.DebugLevel:output_type -> lnrpc.DebugLevelResponse
180, // 304: lnrpc.Lightning.FeeReport:output_type -> lnrpc.FeeReportResponse
184, // 305: lnrpc.Lightning.UpdateChannelPolicy:output_type -> lnrpc.PolicyUpdateResponse
187, // 306: lnrpc.Lightning.ForwardingHistory:output_type -> lnrpc.ForwardingHistoryResponse
189, // 307: lnrpc.Lightning.ExportChannelBackup:output_type -> lnrpc.ChannelBackup
192, // 308: lnrpc.Lightning.ExportAllChannelBackups:output_type -> lnrpc.ChanBackupSnapshot
197, // 309: lnrpc.Lightning.VerifyChanBackup:output_type -> lnrpc.VerifyChanBackupResponse
195, // 310: lnrpc.Lightning.RestoreChannelBackups:output_type -> lnrpc.RestoreBackupResponse
192, // 311: lnrpc.Lightning.SubscribeChannelBackups:output_type -> lnrpc.ChanBackupSnapshot
200, // 312: lnrpc.Lightning.BakeMacaroon:output_type -> lnrpc.BakeMacaroonResponse
202, // 313: lnrpc.Lightning.ListMacaroonIDs:output_type -> lnrpc.ListMacaroonIDsResponse
204, // 314: lnrpc.Lightning.DeleteMacaroonID:output_type -> lnrpc.DeleteMacaroonIDResponse
207, // 315: lnrpc.Lightning.ListPermissions:output_type -> lnrpc.ListPermissionsResponse
213, // 316: lnrpc.Lightning.CheckMacaroonPermissions:output_type -> lnrpc.CheckMacPermResponse
214, // 317: lnrpc.Lightning.RegisterRPCMiddleware:output_type -> lnrpc.RPCMiddlewareRequest
26, // 318: lnrpc.Lightning.SendCustomMessage:output_type -> lnrpc.SendCustomMessageResponse
24, // 319: lnrpc.Lightning.SubscribeCustomMessages:output_type -> lnrpc.CustomMessage
67, // 320: lnrpc.Lightning.ListAliases:output_type -> lnrpc.ListAliasesResponse
22, // 321: lnrpc.Lightning.LookupHtlcResolution:output_type -> lnrpc.LookupHtlcResolutionResponse
254, // [254:322] is the sub-list for method output_type
186, // [186:254] is the sub-list for method input_type
186, // [186:186] is the sub-list for extension type_name
186, // [186:186] is the sub-list for extension extendee
0, // [0:186] is the sub-list for field type_name
151, // 141: lnrpc.PayReq.blinded_paths:type_name -> lnrpc.BlindedPaymentPath
179, // 142: lnrpc.FeeReportResponse.channel_fees:type_name -> lnrpc.ChannelFeeReport
38, // 143: lnrpc.PolicyUpdateRequest.chan_point:type_name -> lnrpc.ChannelPoint
181, // 144: lnrpc.PolicyUpdateRequest.inbound_fee:type_name -> lnrpc.InboundFee
39, // 145: lnrpc.FailedUpdate.outpoint:type_name -> lnrpc.OutPoint
11, // 146: lnrpc.FailedUpdate.reason:type_name -> lnrpc.UpdateFailure
183, // 147: lnrpc.PolicyUpdateResponse.failed_updates:type_name -> lnrpc.FailedUpdate
186, // 148: lnrpc.ForwardingHistoryResponse.forwarding_events:type_name -> lnrpc.ForwardingEvent
38, // 149: lnrpc.ExportChannelBackupRequest.chan_point:type_name -> lnrpc.ChannelPoint
38, // 150: lnrpc.ChannelBackup.chan_point:type_name -> lnrpc.ChannelPoint
38, // 151: lnrpc.MultiChanBackup.chan_points:type_name -> lnrpc.ChannelPoint
193, // 152: lnrpc.ChanBackupSnapshot.single_chan_backups:type_name -> lnrpc.ChannelBackups
190, // 153: lnrpc.ChanBackupSnapshot.multi_chan_backup:type_name -> lnrpc.MultiChanBackup
189, // 154: lnrpc.ChannelBackups.chan_backups:type_name -> lnrpc.ChannelBackup
193, // 155: lnrpc.RestoreChanBackupRequest.chan_backups:type_name -> lnrpc.ChannelBackups
198, // 156: lnrpc.BakeMacaroonRequest.permissions:type_name -> lnrpc.MacaroonPermission
198, // 157: lnrpc.MacaroonPermissionList.permissions:type_name -> lnrpc.MacaroonPermission
245, // 158: lnrpc.ListPermissionsResponse.method_permissions:type_name -> lnrpc.ListPermissionsResponse.MethodPermissionsEntry
20, // 159: lnrpc.Failure.code:type_name -> lnrpc.Failure.FailureCode
209, // 160: lnrpc.Failure.channel_update:type_name -> lnrpc.ChannelUpdate
211, // 161: lnrpc.MacaroonId.ops:type_name -> lnrpc.Op
198, // 162: lnrpc.CheckMacPermRequest.permissions:type_name -> lnrpc.MacaroonPermission
215, // 163: lnrpc.RPCMiddlewareRequest.stream_auth:type_name -> lnrpc.StreamAuth
216, // 164: lnrpc.RPCMiddlewareRequest.request:type_name -> lnrpc.RPCMessage
216, // 165: lnrpc.RPCMiddlewareRequest.response:type_name -> lnrpc.RPCMessage
218, // 166: lnrpc.RPCMiddlewareResponse.register:type_name -> lnrpc.MiddlewareRegistration
219, // 167: lnrpc.RPCMiddlewareResponse.feedback:type_name -> lnrpc.InterceptFeedback
177, // 168: lnrpc.Peer.FeaturesEntry.value:type_name -> lnrpc.Feature
177, // 169: lnrpc.GetInfoResponse.FeaturesEntry.value:type_name -> lnrpc.Feature
4, // 170: lnrpc.PendingChannelsResponse.PendingChannel.initiator:type_name -> lnrpc.Initiator
3, // 171: lnrpc.PendingChannelsResponse.PendingChannel.commitment_type:type_name -> lnrpc.CommitmentType
226, // 172: lnrpc.PendingChannelsResponse.PendingOpenChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
226, // 173: lnrpc.PendingChannelsResponse.WaitingCloseChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
229, // 174: lnrpc.PendingChannelsResponse.WaitingCloseChannel.commitments:type_name -> lnrpc.PendingChannelsResponse.Commitments
226, // 175: lnrpc.PendingChannelsResponse.ClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
226, // 176: lnrpc.PendingChannelsResponse.ForceClosedChannel.channel:type_name -> lnrpc.PendingChannelsResponse.PendingChannel
108, // 177: lnrpc.PendingChannelsResponse.ForceClosedChannel.pending_htlcs:type_name -> lnrpc.PendingHTLC
15, // 178: lnrpc.PendingChannelsResponse.ForceClosedChannel.anchor:type_name -> lnrpc.PendingChannelsResponse.ForceClosedChannel.AnchorState
113, // 179: lnrpc.WalletBalanceResponse.AccountBalanceEntry.value:type_name -> lnrpc.WalletAccountBalance
177, // 180: lnrpc.LightningNode.FeaturesEntry.value:type_name -> lnrpc.Feature
137, // 181: lnrpc.NodeMetricsResponse.BetweennessCentralityEntry.value:type_name -> lnrpc.FloatMetric
177, // 182: lnrpc.NodeUpdate.FeaturesEntry.value:type_name -> lnrpc.Feature
177, // 183: lnrpc.Invoice.FeaturesEntry.value:type_name -> lnrpc.Feature
154, // 184: lnrpc.Invoice.AmpInvoiceStateEntry.value:type_name -> lnrpc.AMPInvoiceState
177, // 185: lnrpc.PayReq.FeaturesEntry.value:type_name -> lnrpc.Feature
205, // 186: lnrpc.ListPermissionsResponse.MethodPermissionsEntry.value:type_name -> lnrpc.MacaroonPermissionList
114, // 187: lnrpc.Lightning.WalletBalance:input_type -> lnrpc.WalletBalanceRequest
117, // 188: lnrpc.Lightning.ChannelBalance:input_type -> lnrpc.ChannelBalanceRequest
30, // 189: lnrpc.Lightning.GetTransactions:input_type -> lnrpc.GetTransactionsRequest
42, // 190: lnrpc.Lightning.EstimateFee:input_type -> lnrpc.EstimateFeeRequest
46, // 191: lnrpc.Lightning.SendCoins:input_type -> lnrpc.SendCoinsRequest
48, // 192: lnrpc.Lightning.ListUnspent:input_type -> lnrpc.ListUnspentRequest
30, // 193: lnrpc.Lightning.SubscribeTransactions:input_type -> lnrpc.GetTransactionsRequest
44, // 194: lnrpc.Lightning.SendMany:input_type -> lnrpc.SendManyRequest
50, // 195: lnrpc.Lightning.NewAddress:input_type -> lnrpc.NewAddressRequest
52, // 196: lnrpc.Lightning.SignMessage:input_type -> lnrpc.SignMessageRequest
54, // 197: lnrpc.Lightning.VerifyMessage:input_type -> lnrpc.VerifyMessageRequest
56, // 198: lnrpc.Lightning.ConnectPeer:input_type -> lnrpc.ConnectPeerRequest
58, // 199: lnrpc.Lightning.DisconnectPeer:input_type -> lnrpc.DisconnectPeerRequest
74, // 200: lnrpc.Lightning.ListPeers:input_type -> lnrpc.ListPeersRequest
76, // 201: lnrpc.Lightning.SubscribePeerEvents:input_type -> lnrpc.PeerEventSubscription
78, // 202: lnrpc.Lightning.GetInfo:input_type -> lnrpc.GetInfoRequest
80, // 203: lnrpc.Lightning.GetDebugInfo:input_type -> lnrpc.GetDebugInfoRequest
82, // 204: lnrpc.Lightning.GetRecoveryInfo:input_type -> lnrpc.GetRecoveryInfoRequest
109, // 205: lnrpc.Lightning.PendingChannels:input_type -> lnrpc.PendingChannelsRequest
63, // 206: lnrpc.Lightning.ListChannels:input_type -> lnrpc.ListChannelsRequest
111, // 207: lnrpc.Lightning.SubscribeChannelEvents:input_type -> lnrpc.ChannelEventSubscription
70, // 208: lnrpc.Lightning.ClosedChannels:input_type -> lnrpc.ClosedChannelsRequest
96, // 209: lnrpc.Lightning.OpenChannelSync:input_type -> lnrpc.OpenChannelRequest
96, // 210: lnrpc.Lightning.OpenChannel:input_type -> lnrpc.OpenChannelRequest
93, // 211: lnrpc.Lightning.BatchOpenChannel:input_type -> lnrpc.BatchOpenChannelRequest
106, // 212: lnrpc.Lightning.FundingStateStep:input_type -> lnrpc.FundingTransitionMsg
37, // 213: lnrpc.Lightning.ChannelAcceptor:input_type -> lnrpc.ChannelAcceptResponse
88, // 214: lnrpc.Lightning.CloseChannel:input_type -> lnrpc.CloseChannelRequest
171, // 215: lnrpc.Lightning.AbandonChannel:input_type -> lnrpc.AbandonChannelRequest
33, // 216: lnrpc.Lightning.SendPayment:input_type -> lnrpc.SendRequest
33, // 217: lnrpc.Lightning.SendPaymentSync:input_type -> lnrpc.SendRequest
35, // 218: lnrpc.Lightning.SendToRoute:input_type -> lnrpc.SendToRouteRequest
35, // 219: lnrpc.Lightning.SendToRouteSync:input_type -> lnrpc.SendToRouteRequest
155, // 220: lnrpc.Lightning.AddInvoice:input_type -> lnrpc.Invoice
160, // 221: lnrpc.Lightning.ListInvoices:input_type -> lnrpc.ListInvoiceRequest
159, // 222: lnrpc.Lightning.LookupInvoice:input_type -> lnrpc.PaymentHash
162, // 223: lnrpc.Lightning.SubscribeInvoices:input_type -> lnrpc.InvoiceSubscription
175, // 224: lnrpc.Lightning.DecodePayReq:input_type -> lnrpc.PayReqString
165, // 225: lnrpc.Lightning.ListPayments:input_type -> lnrpc.ListPaymentsRequest
167, // 226: lnrpc.Lightning.DeletePayment:input_type -> lnrpc.DeletePaymentRequest
168, // 227: lnrpc.Lightning.DeleteAllPayments:input_type -> lnrpc.DeleteAllPaymentsRequest
133, // 228: lnrpc.Lightning.DescribeGraph:input_type -> lnrpc.ChannelGraphRequest
135, // 229: lnrpc.Lightning.GetNodeMetrics:input_type -> lnrpc.NodeMetricsRequest
138, // 230: lnrpc.Lightning.GetChanInfo:input_type -> lnrpc.ChanInfoRequest
127, // 231: lnrpc.Lightning.GetNodeInfo:input_type -> lnrpc.NodeInfoRequest
119, // 232: lnrpc.Lightning.QueryRoutes:input_type -> lnrpc.QueryRoutesRequest
139, // 233: lnrpc.Lightning.GetNetworkInfo:input_type -> lnrpc.NetworkInfoRequest
141, // 234: lnrpc.Lightning.StopDaemon:input_type -> lnrpc.StopRequest
143, // 235: lnrpc.Lightning.SubscribeChannelGraph:input_type -> lnrpc.GraphTopologySubscription
173, // 236: lnrpc.Lightning.DebugLevel:input_type -> lnrpc.DebugLevelRequest
178, // 237: lnrpc.Lightning.FeeReport:input_type -> lnrpc.FeeReportRequest
182, // 238: lnrpc.Lightning.UpdateChannelPolicy:input_type -> lnrpc.PolicyUpdateRequest
185, // 239: lnrpc.Lightning.ForwardingHistory:input_type -> lnrpc.ForwardingHistoryRequest
188, // 240: lnrpc.Lightning.ExportChannelBackup:input_type -> lnrpc.ExportChannelBackupRequest
191, // 241: lnrpc.Lightning.ExportAllChannelBackups:input_type -> lnrpc.ChanBackupExportRequest
192, // 242: lnrpc.Lightning.VerifyChanBackup:input_type -> lnrpc.ChanBackupSnapshot
194, // 243: lnrpc.Lightning.RestoreChannelBackups:input_type -> lnrpc.RestoreChanBackupRequest
196, // 244: lnrpc.Lightning.SubscribeChannelBackups:input_type -> lnrpc.ChannelBackupSubscription
199, // 245: lnrpc.Lightning.BakeMacaroon:input_type -> lnrpc.BakeMacaroonRequest
201, // 246: lnrpc.Lightning.ListMacaroonIDs:input_type -> lnrpc.ListMacaroonIDsRequest
203, // 247: lnrpc.Lightning.DeleteMacaroonID:input_type -> lnrpc.DeleteMacaroonIDRequest
206, // 248: lnrpc.Lightning.ListPermissions:input_type -> lnrpc.ListPermissionsRequest
212, // 249: lnrpc.Lightning.CheckMacaroonPermissions:input_type -> lnrpc.CheckMacPermRequest
217, // 250: lnrpc.Lightning.RegisterRPCMiddleware:input_type -> lnrpc.RPCMiddlewareResponse
25, // 251: lnrpc.Lightning.SendCustomMessage:input_type -> lnrpc.SendCustomMessageRequest
23, // 252: lnrpc.Lightning.SubscribeCustomMessages:input_type -> lnrpc.SubscribeCustomMessagesRequest
66, // 253: lnrpc.Lightning.ListAliases:input_type -> lnrpc.ListAliasesRequest
21, // 254: lnrpc.Lightning.LookupHtlcResolution:input_type -> lnrpc.LookupHtlcResolutionRequest
115, // 255: lnrpc.Lightning.WalletBalance:output_type -> lnrpc.WalletBalanceResponse
118, // 256: lnrpc.Lightning.ChannelBalance:output_type -> lnrpc.ChannelBalanceResponse
31, // 257: lnrpc.Lightning.GetTransactions:output_type -> lnrpc.TransactionDetails
43, // 258: lnrpc.Lightning.EstimateFee:output_type -> lnrpc.EstimateFeeResponse
47, // 259: lnrpc.Lightning.SendCoins:output_type -> lnrpc.SendCoinsResponse
49, // 260: lnrpc.Lightning.ListUnspent:output_type -> lnrpc.ListUnspentResponse
29, // 261: lnrpc.Lightning.SubscribeTransactions:output_type -> lnrpc.Transaction
45, // 262: lnrpc.Lightning.SendMany:output_type -> lnrpc.SendManyResponse
51, // 263: lnrpc.Lightning.NewAddress:output_type -> lnrpc.NewAddressResponse
53, // 264: lnrpc.Lightning.SignMessage:output_type -> lnrpc.SignMessageResponse
55, // 265: lnrpc.Lightning.VerifyMessage:output_type -> lnrpc.VerifyMessageResponse
57, // 266: lnrpc.Lightning.ConnectPeer:output_type -> lnrpc.ConnectPeerResponse
59, // 267: lnrpc.Lightning.DisconnectPeer:output_type -> lnrpc.DisconnectPeerResponse
75, // 268: lnrpc.Lightning.ListPeers:output_type -> lnrpc.ListPeersResponse
77, // 269: lnrpc.Lightning.SubscribePeerEvents:output_type -> lnrpc.PeerEvent
79, // 270: lnrpc.Lightning.GetInfo:output_type -> lnrpc.GetInfoResponse
81, // 271: lnrpc.Lightning.GetDebugInfo:output_type -> lnrpc.GetDebugInfoResponse
83, // 272: lnrpc.Lightning.GetRecoveryInfo:output_type -> lnrpc.GetRecoveryInfoResponse
110, // 273: lnrpc.Lightning.PendingChannels:output_type -> lnrpc.PendingChannelsResponse
64, // 274: lnrpc.Lightning.ListChannels:output_type -> lnrpc.ListChannelsResponse
112, // 275: lnrpc.Lightning.SubscribeChannelEvents:output_type -> lnrpc.ChannelEventUpdate
71, // 276: lnrpc.Lightning.ClosedChannels:output_type -> lnrpc.ClosedChannelsResponse
38, // 277: lnrpc.Lightning.OpenChannelSync:output_type -> lnrpc.ChannelPoint
97, // 278: lnrpc.Lightning.OpenChannel:output_type -> lnrpc.OpenStatusUpdate
95, // 279: lnrpc.Lightning.BatchOpenChannel:output_type -> lnrpc.BatchOpenChannelResponse
107, // 280: lnrpc.Lightning.FundingStateStep:output_type -> lnrpc.FundingStateStepResp
36, // 281: lnrpc.Lightning.ChannelAcceptor:output_type -> lnrpc.ChannelAcceptRequest
89, // 282: lnrpc.Lightning.CloseChannel:output_type -> lnrpc.CloseStatusUpdate
172, // 283: lnrpc.Lightning.AbandonChannel:output_type -> lnrpc.AbandonChannelResponse
34, // 284: lnrpc.Lightning.SendPayment:output_type -> lnrpc.SendResponse
34, // 285: lnrpc.Lightning.SendPaymentSync:output_type -> lnrpc.SendResponse
34, // 286: lnrpc.Lightning.SendToRoute:output_type -> lnrpc.SendResponse
34, // 287: lnrpc.Lightning.SendToRouteSync:output_type -> lnrpc.SendResponse
158, // 288: lnrpc.Lightning.AddInvoice:output_type -> lnrpc.AddInvoiceResponse
161, // 289: lnrpc.Lightning.ListInvoices:output_type -> lnrpc.ListInvoiceResponse
155, // 290: lnrpc.Lightning.LookupInvoice:output_type -> lnrpc.Invoice
155, // 291: lnrpc.Lightning.SubscribeInvoices:output_type -> lnrpc.Invoice
176, // 292: lnrpc.Lightning.DecodePayReq:output_type -> lnrpc.PayReq
166, // 293: lnrpc.Lightning.ListPayments:output_type -> lnrpc.ListPaymentsResponse
169, // 294: lnrpc.Lightning.DeletePayment:output_type -> lnrpc.DeletePaymentResponse
170, // 295: lnrpc.Lightning.DeleteAllPayments:output_type -> lnrpc.DeleteAllPaymentsResponse
134, // 296: lnrpc.Lightning.DescribeGraph:output_type -> lnrpc.ChannelGraph
136, // 297: lnrpc.Lightning.GetNodeMetrics:output_type -> lnrpc.NodeMetricsResponse
132, // 298: lnrpc.Lightning.GetChanInfo:output_type -> lnrpc.ChannelEdge
128, // 299: lnrpc.Lightning.GetNodeInfo:output_type -> lnrpc.NodeInfo
122, // 300: lnrpc.Lightning.QueryRoutes:output_type -> lnrpc.QueryRoutesResponse
140, // 301: lnrpc.Lightning.GetNetworkInfo:output_type -> lnrpc.NetworkInfo
142, // 302: lnrpc.Lightning.StopDaemon:output_type -> lnrpc.StopResponse
144, // 303: lnrpc.Lightning.SubscribeChannelGraph:output_type -> lnrpc.GraphTopologyUpdate
174, // 304: lnrpc.Lightning.DebugLevel:output_type -> lnrpc.DebugLevelResponse
180, // 305: lnrpc.Lightning.FeeReport:output_type -> lnrpc.FeeReportResponse
184, // 306: lnrpc.Lightning.UpdateChannelPolicy:output_type -> lnrpc.PolicyUpdateResponse
187, // 307: lnrpc.Lightning.ForwardingHistory:output_type -> lnrpc.ForwardingHistoryResponse
189, // 308: lnrpc.Lightning.ExportChannelBackup:output_type -> lnrpc.ChannelBackup
192, // 309: lnrpc.Lightning.ExportAllChannelBackups:output_type -> lnrpc.ChanBackupSnapshot
197, // 310: lnrpc.Lightning.VerifyChanBackup:output_type -> lnrpc.VerifyChanBackupResponse
195, // 311: lnrpc.Lightning.RestoreChannelBackups:output_type -> lnrpc.RestoreBackupResponse
192, // 312: lnrpc.Lightning.SubscribeChannelBackups:output_type -> lnrpc.ChanBackupSnapshot
200, // 313: lnrpc.Lightning.BakeMacaroon:output_type -> lnrpc.BakeMacaroonResponse
202, // 314: lnrpc.Lightning.ListMacaroonIDs:output_type -> lnrpc.ListMacaroonIDsResponse
204, // 315: lnrpc.Lightning.DeleteMacaroonID:output_type -> lnrpc.DeleteMacaroonIDResponse
207, // 316: lnrpc.Lightning.ListPermissions:output_type -> lnrpc.ListPermissionsResponse
213, // 317: lnrpc.Lightning.CheckMacaroonPermissions:output_type -> lnrpc.CheckMacPermResponse
214, // 318: lnrpc.Lightning.RegisterRPCMiddleware:output_type -> lnrpc.RPCMiddlewareRequest
26, // 319: lnrpc.Lightning.SendCustomMessage:output_type -> lnrpc.SendCustomMessageResponse
24, // 320: lnrpc.Lightning.SubscribeCustomMessages:output_type -> lnrpc.CustomMessage
67, // 321: lnrpc.Lightning.ListAliases:output_type -> lnrpc.ListAliasesResponse
22, // 322: lnrpc.Lightning.LookupHtlcResolution:output_type -> lnrpc.LookupHtlcResolutionResponse
255, // [255:323] is the sub-list for method output_type
187, // [187:255] is the sub-list for method input_type
187, // [187:187] is the sub-list for extension 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() }
+1
View File
@@ -4289,6 +4289,7 @@ message PayReq {
bytes payment_addr = 11;
int64 num_msat = 12;
map<uint32, Feature> features = 13;
repeated BlindedPaymentPath blinded_paths = 14;
}
enum FeatureBit {
+6
View File
@@ -6299,6 +6299,12 @@
"additionalProperties": {
"$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
// forwarding information.
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 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
// 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]
// 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 tlv.OptionalRecordT[tlv.TlvType12, PaymentConstraints]
@@ -33,16 +43,20 @@ type BlindedRouteData struct {
Features tlv.OptionalRecordT[tlv.TlvType14, lnwire.FeatureVector]
}
// NewBlindedRouteData creates the data that's provided for hops within a
// blinded route.
func NewBlindedRouteData(chanID lnwire.ShortChannelID,
// NewNonFinalBlindedRouteData creates the data that's provided for hops within
// a blinded route.
func NewNonFinalBlindedRouteData(chanID lnwire.ShortChannelID,
blindingOverride *btcec.PublicKey, relayInfo PaymentRelayInfo,
constraints *PaymentConstraints,
features *lnwire.FeatureVector) *BlindedRouteData {
info := &BlindedRouteData{
ShortChannelID: tlv.NewRecordT[tlv.TlvType2](chanID),
RelayInfo: tlv.NewRecordT[tlv.TlvType10](relayInfo),
ShortChannelID: tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType2](chanID),
),
RelayInfo: tlv.SomeRecordT(
tlv.NewRecordT[tlv.TlvType10](relayInfo),
),
}
if blindingOverride != nil {
@@ -64,12 +78,36 @@ func NewBlindedRouteData(chanID lnwire.ShortChannelID,
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.
func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) {
var (
d BlindedRouteData
padding = d.Padding.Zero()
scid = d.ShortChannelID.Zero()
pathID = d.PathID.Zero()
blindingOverride = d.NextBlindingOverride.Zero()
relayInfo = d.RelayInfo.Zero()
constraints = d.Constraints.Zero()
features = d.Features.Zero()
)
@@ -80,19 +118,35 @@ func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) {
}
typeMap, err := tlvRecords.ExtractRecords(
&d.ShortChannelID,
&blindingOverride, &d.RelayInfo.Val, &constraints,
&features,
&padding, &scid, &pathID, &blindingOverride, &relayInfo,
&constraints, &features,
)
if err != nil {
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 {
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 {
d.Constraints = tlv.SomeRecordT(constraints)
}
@@ -111,7 +165,19 @@ func EncodeBlindedRouteData(data *BlindedRouteData) ([]byte, error) {
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,
*btcec.PublicKey]) {
@@ -119,7 +185,11 @@ func EncodeBlindedRouteData(data *BlindedRouteData) ([]byte, error) {
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,
PaymentConstraints]) {
@@ -140,6 +210,19 @@ func EncodeBlindedRouteData(data *BlindedRouteData) ([]byte, error) {
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.
type PaymentRelayInfo struct {
// CltvExpiryDelta is the expiry delta for the payment.
@@ -153,8 +236,8 @@ type PaymentRelayInfo struct {
BaseFee uint32
}
// newPaymentRelayRecord creates a tlv.Record that encodes the payment relay
// (type 10) type for an encrypted blob payload.
// Record creates a tlv.Record that encodes the payment relay (type 10) type for
// an encrypted blob payload.
func (i *PaymentRelayInfo) Record() tlv.Record {
return tlv.MakeDynamicRecord(
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,
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
// blinded route data provided in the specification.
//
@@ -131,10 +252,11 @@ func TestBlindingSpecTestVectors(t *testing.T) {
tests := []struct {
encoded string
expectedPaymentData *BlindedRouteData
expectedPadding int
}{
{
encoded: "011a0000000000000000000000000000000000000000000000000000020800000000000006c10a0800240000009627100c06000b69e505dc0e00fd023103123456",
expectedPaymentData: NewBlindedRouteData(
expectedPaymentData: NewNonFinalBlindedRouteData(
lnwire.ShortChannelID{
BlockHeight: 0,
TxIndex: 0,
@@ -155,10 +277,11 @@ func TestBlindingSpecTestVectors(t *testing.T) {
lnwire.Features,
),
),
expectedPadding: 26,
},
{
encoded: "020800000000000004510821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0a0800300000006401f40c06000b69c105dc0e00",
expectedPaymentData: NewBlindedRouteData(
expectedPaymentData: NewNonFinalBlindedRouteData(
lnwire.ShortChannelID{
TxPosition: 1105,
},
@@ -175,7 +298,8 @@ func TestBlindingSpecTestVectors(t *testing.T) {
lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(),
lnwire.Features,
)),
),
),
},
}
@@ -189,6 +313,12 @@ func TestBlindingSpecTestVectors(t *testing.T) {
decodedRoute, err := DecodeBlindedRouteData(buff)
require.NoError(t, err)
if test.expectedPadding != 0 {
test.expectedPaymentData.PadBy(
test.expectedPadding,
)
}
require.Equal(
t, test.expectedPaymentData, decodedRoute,
)
+57 -7
View File
@@ -2,8 +2,8 @@ package routing
import (
"errors"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
@@ -29,6 +29,11 @@ type AdditionalEdge interface {
// EdgePolicy returns the policy of the additional edge.
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.
@@ -60,12 +65,49 @@ func (p *PrivateEdge) IntermediatePayloadSize(amount lnwire.MilliSatoshi,
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
// 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 {
policy *models.CachedEdgePolicy
cipherText []byte
blindingPoint *btcec.PublicKey
policy *models.CachedEdgePolicy
// 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.
@@ -78,15 +120,23 @@ func (b *BlindedEdge) EdgePolicy() *models.CachedEdgePolicy {
func (b *BlindedEdge) IntermediatePayloadSize(_ lnwire.MilliSatoshi, _ uint32,
_ uint64) uint64 {
blindedPath := b.blindedPayment.BlindedPath
hop := route.Hop{
BlindingPoint: b.blindingPoint,
EncryptedData: b.cipherText,
BlindingPoint: blindedPath.BlindingPoint,
EncryptedData: blindedPath.BlindedHops[b.hopIndex].CipherText,
}
// For blinded paths the next chanID is in the encrypted data tlv.
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
// implement the AdditionalEdge interface.
var _ AdditionalEdge = (*PrivateEdge)(nil)
+15 -7
View File
@@ -42,9 +42,13 @@ func TestIntermediatePayloadSize(t *testing.T) {
hop: route.Hop{
EncryptedData: []byte{12, 13},
},
edge: &BlindedEdge{
cipherText: []byte{12, 13},
},
edge: &BlindedEdge{blindedPayment: &BlindedPayment{
BlindedPath: &sphinx.BlindedPath{
BlindedHops: []*sphinx.BlindedHopInfo{
{CipherText: []byte{12, 13}},
},
},
}},
},
{
name: "Blinded edge - introduction point",
@@ -52,10 +56,14 @@ func TestIntermediatePayloadSize(t *testing.T) {
EncryptedData: []byte{12, 13},
BlindingPoint: blindedPoint,
},
edge: &BlindedEdge{
cipherText: []byte{12, 13},
blindingPoint: blindedPoint,
},
edge: &BlindedEdge{blindedPayment: &BlindedPayment{
BlindedPath: &sphinx.BlindedPath{
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
// hints (both for intermediate hops and the final_cltv_delta for the receiving
// 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
// 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
// don't need to add any route hints.
if len(b.BlindedPath.BlindedHops) == 1 {
return nil
return nil, nil
}
hintCount := len(b.BlindedPath.BlindedHops) - 1
@@ -136,14 +136,13 @@ func (b *BlindedPayment) toRouteHints() RouteHints {
ToNodeFeatures: features,
}
hints[fromNode] = []AdditionalEdge{
&BlindedEdge{
policy: edgePolicy,
cipherText: b.BlindedPath.BlindedHops[0].CipherText,
blindingPoint: b.BlindedPath.BlindingPoint,
},
edge, err := NewBlindedEdge(edgePolicy, b, 0)
if err != nil {
return nil, err
}
hints[fromNode] = []AdditionalEdge{edge}
// 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
// because we're dealing with hops as pairs.
@@ -169,14 +168,13 @@ func (b *BlindedPayment) toRouteHints() RouteHints {
ToNodeFeatures: features,
}
hints[fromNode] = []AdditionalEdge{
&BlindedEdge{
policy: edgePolicy,
cipherText: b.BlindedPath.BlindedHops[i].
CipherText,
},
edge, err := NewBlindedEdge(edgePolicy, b, i)
if err != nil {
return nil, err
}
hints[fromNode] = []AdditionalEdge{edge}
}
return hints
return hints, nil
}
+33 -29
View File
@@ -128,7 +128,9 @@ func TestBlindedPaymentToHints(t *testing.T) {
HtlcMaximum: htlcMax,
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.
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{
v1: {
//nolint:lll
&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,
},
blindedEdge1,
},
vb2: {
&BlindedEdge{
policy: &models.CachedEdgePolicy{
ToNodePubKey: func() route.Vertex {
return vb3
},
ToNodeFeatures: features,
},
cipherText: cipherText,
},
blindedEdge2,
},
}
actual := blindedPayment.toRouteHints()
actual, err := blindedPayment.toRouteHints()
require.NoError(t, err)
require.Equal(t, len(expected), len(actual))
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
// custom records and payment address.
type finalHopParams struct {
amt lnwire.MilliSatoshi
totalAmt lnwire.MilliSatoshi
cltvDelta uint16
amt lnwire.MilliSatoshi
totalAmt lnwire.MilliSatoshi
// 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
paymentAddr *[32]byte
@@ -190,10 +198,21 @@ func newRoute(sourceVertex route.Vertex,
// reporting through RPC. Set to zero for the final hop.
fee = 0
// 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)
// Only include the final hop CLTV delta in the total
// time lock value if this is not a route to a blinded
// path. For blinded paths, the total time-lock from the
// 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
// Attach any custom records to the final hop.
@@ -968,6 +987,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
inboundFee,
fakeHopHintCapacity,
reverseEdge.edge.IntermediatePayloadSize,
reverseEdge.edge.BlindedPayment(),
)
}
+11 -6
View File
@@ -3322,16 +3322,19 @@ func TestBlindedRouteConstruction(t *testing.T) {
ToNodeFeatures: tlvFeatures,
}
// Create final hop parameters for payment amount = 110. Note
// 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.
// Create final hop parameters for payment amount = 110.
totalAmt lnwire.MilliSatoshi = 110
finalHopParams = finalHopParams{
amt: totalAmt,
totalAmt: totalAmt,
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
// map is keyed by source node, so we can retrieve our blinded edges
// accordingly.
blindedEdges := blindedPayment.toRouteHints()
blindedEdges, err := blindedPayment.toRouteHints()
require.NoError(t, err)
carolDaveEdge := blindedEdges[carolVertex][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.
requestHints = routeHints
requestExpiry = finalExpiry
err error
)
if blindedPayment != nil {
@@ -2038,7 +2039,10 @@ func NewRouteRequest(source route.Vertex, target *route.Vertex,
requestExpiry = blindedPayment.CltvExpiryDelta
}
requestHints = blindedPayment.toRouteHints()
requestHints, err = blindedPayment.toRouteHints()
if err != nil {
return nil, err
}
}
requestTarget, err := getTargetNode(target, blindedPayment)
+38 -26
View File
@@ -51,7 +51,8 @@ func newNodeEdgeUnifier(sourceNode, toNode route.Vertex, useInboundFees bool,
// incorrectly specified.
func (u *nodeEdgeUnifier) addPolicy(fromNode route.Vertex,
edge *models.CachedEdgePolicy, inboundFee models.InboundFee,
capacity btcutil.Amount, hopPayloadSizeFn PayloadSizeFunc) {
capacity btcutil.Amount, hopPayloadSizeFn PayloadSizeFunc,
blindedPayment *BlindedPayment) {
localChan := fromNode == u.sourceNode
@@ -86,12 +87,9 @@ func (u *nodeEdgeUnifier) addPolicy(fromNode route.Vertex,
inboundFee = models.InboundFee{}
}
unifier.edges = append(unifier.edges, &unifiedEdge{
policy: edge,
capacity: capacity,
hopPayloadSizeFn: hopPayloadSizeFn,
inboundFees: inboundFee,
})
unifier.edges = append(unifier.edges, newUnifiedEdge(
edge, capacity, inboundFee, hopPayloadSizeFn, blindedPayment,
))
}
// 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(
channel.OtherNode, channel.InPolicy, inboundFee,
channel.Capacity, defaultHopPayloadSize,
channel.Capacity, defaultHopPayloadSize, nil,
)
return nil
@@ -137,6 +135,24 @@ type unifiedEdge struct {
// is needed because hops of a blinded path differ in their payload
// structure compared to cleartext hops.
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
@@ -292,12 +308,10 @@ func (u *edgeUnifier) getEdgeLocal(netAmtReceived lnwire.MilliSatoshi,
maxBandwidth = bandwidth
// Update best edge.
bestEdge = &unifiedEdge{
policy: edge.policy,
capacity: edge.capacity,
hopPayloadSizeFn: edge.hopPayloadSizeFn,
inboundFees: edge.inboundFees,
}
bestEdge = newUnifiedEdge(
edge.policy, edge.capacity, edge.inboundFees,
edge.hopPayloadSizeFn, edge.blindedPayment,
)
}
return bestEdge
@@ -376,10 +390,10 @@ func (u *edgeUnifier) getEdgeNetwork(netAmtReceived lnwire.MilliSatoshi,
}
maxFee = fee
bestPolicy = &unifiedEdge{
policy: edge.policy,
inboundFees: edge.inboundFees,
}
bestPolicy = newUnifiedEdge(
edge.policy, 0, edge.inboundFees, nil,
edge.blindedPayment,
)
// The payload size function for edges to a connected peer is
// 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
// have distinct policies for channels to the same peer.
policyCopy := *bestPolicy.policy
modifiedEdge := unifiedEdge{
policy: &policyCopy,
inboundFees: bestPolicy.inboundFees,
}
modifiedEdge.policy.TimeLockDelta = maxTimelock
modifiedEdge.capacity = maxCapMsat.ToSatoshis()
modifiedEdge.hopPayloadSizeFn = hopPayloadSizeFn
policyCopy.TimeLockDelta = maxTimelock
modifiedEdge := newUnifiedEdge(
&policyCopy, maxCapMsat.ToSatoshis(), bestPolicy.inboundFees,
hopPayloadSizeFn, bestPolicy.blindedPayment,
)
return &modifiedEdge
return modifiedEdge
}
// 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.addPolicy(
fromNode, &p1, inboundFee1, c1, defaultHopPayloadSize,
fromNode, &p1, inboundFee1, c1, defaultHopPayloadSize, nil,
)
unifierFilled.addPolicy(
fromNode, &p2, inboundFee2, c2, defaultHopPayloadSize,
fromNode, &p2, inboundFee2, c2, defaultHopPayloadSize, nil,
)
unifierNoCapacity := newNodeEdgeUnifier(source, toNode, false, nil)
unifierNoCapacity.addPolicy(
fromNode, &p1, inboundFee1, 0, defaultHopPayloadSize,
fromNode, &p1, inboundFee1, 0, defaultHopPayloadSize, nil,
)
unifierNoCapacity.addPolicy(
fromNode, &p2, inboundFee2, 0, defaultHopPayloadSize,
fromNode, &p2, inboundFee2, 0, defaultHopPayloadSize, nil,
)
unifierNoInfo := newNodeEdgeUnifier(source, toNode, false, nil)
unifierNoInfo.addPolicy(
fromNode, &models.CachedEdgePolicy{}, models.InboundFee{},
0, defaultHopPayloadSize,
0, defaultHopPayloadSize, nil,
)
unifierInboundFee := newNodeEdgeUnifier(source, toNode, true, nil)
unifierInboundFee.addPolicy(
fromNode, &p1, inboundFee1, c1, defaultHopPayloadSize,
fromNode, &p1, inboundFee1, c1, defaultHopPayloadSize, nil,
)
unifierInboundFee.addPolicy(
fromNode, &p2, inboundFee2, c2, defaultHopPayloadSize,
fromNode, &p2, inboundFee2, c2, defaultHopPayloadSize, nil,
)
unifierLocal := newNodeEdgeUnifier(fromNode, toNode, true, nil)
unifierLocal.addPolicy(
fromNode, &p1, inboundFee1, c1, defaultHopPayloadSize,
fromNode, &p1, inboundFee1, c1, defaultHopPayloadSize, nil,
)
inboundFeeZero := models.InboundFee{}
@@ -98,10 +98,11 @@ func TestNodeEdgeUnifier(t *testing.T) {
}
unifierNegInboundFee := newNodeEdgeUnifier(source, toNode, true, nil)
unifierNegInboundFee.addPolicy(
fromNode, &p1, inboundFeeZero, c1, defaultHopPayloadSize,
fromNode, &p1, inboundFeeZero, c1, defaultHopPayloadSize, nil,
)
unifierNegInboundFee.addPolicy(
fromNode, &p2, inboundFeeNegative, c2, defaultHopPayloadSize,
nil,
)
tests := []struct {
+8
View File
@@ -6971,6 +6971,13 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
// Convert between the `lnrpc` and `routing` types.
routeHints := invoicesrpc.CreateRPCRouteHints(payReq.RouteHints)
blindedPaymentPaths, err := invoicesrpc.CreateRPCBlindedPayments(
payReq.BlindedPaymentPaths,
)
if err != nil {
return nil, err
}
var amtSat, amtMsat int64
if payReq.MilliSat != nil {
amtSat = int64(payReq.MilliSat.ToSatoshis())
@@ -6996,6 +7003,7 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
Expiry: expiry,
CltvExpiry: int64(payReq.MinFinalCLTVExpiry()),
RouteHints: routeHints,
BlindedPaths: blindedPaymentPaths,
PaymentAddr: paymentAddr,
Features: invoicesrpc.CreateRPCFeatures(payReq.Features),
}, 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)
case fieldTypeS:
if invoice.PaymentAddr != nil {
// 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)
case fieldTypeD:
if invoice.Description != nil {
// 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)
case fieldTypeM:
if invoice.Metadata != nil {
// 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)
case fieldTypeH:
if invoice.DescriptionHash != nil {
// 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)
case fieldTypeX:
if invoice.expiry != nil {
// 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)
case fieldTypeC:
if invoice.minFinalCLTVExpiry != nil {
// 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
}
invoice.minFinalCLTVExpiry, err = parseMinFinalCLTVExpiry(base32Data)
invoice.minFinalCLTVExpiry, err =
parseMinFinalCLTVExpiry(base32Data)
case fieldTypeF:
if invoice.FallbackAddr != nil {
// 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
}
invoice.FallbackAddr, err = parseFallbackAddr(base32Data, net)
invoice.FallbackAddr, err = parseFallbackAddr(
base32Data, net,
)
case fieldTypeR:
// An `r` field can be included in an invoice multiple
// 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
}
invoice.RouteHints = append(invoice.RouteHints, routeHint)
invoice.RouteHints = append(
invoice.RouteHints, routeHint,
)
case fieldType9:
if invoice.Features != nil {
// 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)
case fieldTypeB:
blindedPaymentPath, err := parseBlindedPaymentPath(
base32Data,
)
if err != nil {
return err
}
invoice.BlindedPaymentPaths = append(
invoice.BlindedPaymentPaths, blindedPaymentPath,
)
default:
// Ignore unknown type.
}
@@ -495,6 +522,17 @@ func parseRouteHint(data []byte) ([]HopHint, error) {
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
// representation.
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 {
// Convert 33 byte pubkey to 53 5-bit groups.
pubKeyBase32, err := bech32.ConvertBits(
+33 -1
View File
@@ -76,6 +76,10 @@ const (
// probing the recipient.
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.
// 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
@@ -152,6 +156,10 @@ type Invoice struct {
// This field is un-exported and can only be read by the
// MinFinalCLTVExpiry() method. By forcing callers to read via this
// 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
// 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
// represent private routes.
//
// NOTE: This is optional.
// NOTE: This is optional and should not be set at the same time as
// BlindedPaymentPaths.
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
// required support for features by the receiver.
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
// desired feature bits that are advertised on the invoice. If this option is
// 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")
}
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.
if invoice.Description != nil && invoice.DescriptionHash != nil {
return fmt.Errorf("both description and description hash set")
+147 -113
View File
@@ -7,7 +7,6 @@ import (
"bytes"
"encoding/hex"
"fmt"
"reflect"
"strings"
"testing"
"time"
@@ -17,7 +16,9 @@ import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
var (
@@ -116,6 +117,62 @@ var (
// Must be initialized in init().
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() {
@@ -125,6 +182,8 @@ func init() {
// TestDecodeEncode tests that an encoded invoice gets decoded into the expected
// Invoice object, and that reencoding the decoded invoice gets us back to the
// original encoded string.
//
//nolint:lll
func TestDecodeEncode(t *testing.T) {
t.Parallel()
@@ -673,52 +732,77 @@ func TestDecodeEncode(t *testing.T) {
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 {
var decodedInvoice *Invoice
net := &chaincfg.MainNetParams
if test.decodedInvoice != nil {
decodedInvoice = test.decodedInvoice()
net = decodedInvoice.Net
}
test := test
invoice, err := Decode(test.encodedInvoice, net)
if (err == nil) != test.valid {
t.Errorf("Decoding test %d failed: %v", i, err)
return
}
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
t.Parallel()
if test.valid {
if err := compareInvoices(decodedInvoice, invoice); err != nil {
t.Errorf("Invoice decoding result %d not as expected: %v", i, err)
var decodedInvoice *Invoice
net := &chaincfg.MainNetParams
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
}
}
if test.skipEncoding {
continue
}
if test.beforeEncoding != nil {
test.beforeEncoding(decodedInvoice)
}
if test.beforeEncoding != nil {
test.beforeEncoding(decodedInvoice)
}
if decodedInvoice == nil {
return
}
if decodedInvoice != nil {
reencoded, err := decodedInvoice.Encode(
testMessageSigner,
)
if (err == nil) != test.valid {
t.Errorf("Encoding test %d failed: %v", i, err)
if !test.valid {
require.Error(t, err)
return
}
if test.valid && test.encodedInvoice != reencoded {
t.Errorf("Encoding %d failed, expected %v, got %v",
i, test.encodedInvoice, reencoded)
return
}
}
require.NoError(t, err)
require.Equal(t, test.encodedInvoice, reencoded)
})
}
}
@@ -805,25 +889,42 @@ func TestNewInvoice(t *testing.T) {
valid: true,
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 {
test := test
invoice, err := test.newInvoice()
if err != nil && !test.valid {
continue
}
encoded, err := invoice.Encode(testMessageSigner)
if (err == nil) != test.valid {
t.Errorf("NewInvoice test %d failed: %v", i, err)
return
}
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
t.Parallel()
if test.valid && test.encodedInvoice != encoded {
t.Errorf("Encoding %d failed, expected %v, got %v",
i, test.encodedInvoice, encoded)
return
}
invoice, err := test.newInvoice()
if !test.valid {
require.Error(t, err)
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 {
if a == b {
return true