Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2/4] Route Blinding Receives: Receive and send to a single blinded path in an invoice. #8735

Merged
merged 30 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4f5dd20
go.mod: update lightning-onion dep
ellemouton Jul 15, 2024
188dd44
zpay32: improve readability of blinded path encoding
ellemouton Jul 11, 2024
9192c16
feature: define new feature bit for bolt11 blinded paths
ellemouton Jul 4, 2024
62a97f8
record: add NextNodeID type to BlindedRouteData
ellemouton Jul 10, 2024
4457ca2
record: stricter type for PaymentRelayInfo.BaseFee
ellemouton Jul 1, 2024
f87cc62
lnrpc/invoicesrpc: add function for padding encrypted data
ellemouton May 4, 2024
3b2a604
lnrpc/invoicesrpc: blinded path total path policy calc
ellemouton May 4, 2024
0855e3e
lnrpc/invoicesrpc: add blinded path policy buffer
ellemouton May 4, 2024
4b5327f
lnrpc/invoicesrpc: build blinded path
ellemouton Jul 4, 2024
9787ae9
lnrpc/invoicesrpc: prep AddInvoice for blinded routes
ellemouton May 4, 2024
1039aed
routing: find blinded paths to a destination node
ellemouton May 4, 2024
e12a226
routing: use mission control to select blinded paths
ellemouton May 5, 2024
de97533
multi: add blinded paths to invoices
ellemouton May 5, 2024
18cb7f0
lncli: add --blind option to addinvoice
ellemouton Jul 2, 2024
a15e4bb
refactor+htlcswitch: method for TLV payload parsing logic
ellemouton Jul 5, 2024
55c25f4
htlcswitch+refactor: continue modularising extractTLVPayload
ellemouton Jul 10, 2024
c1c2e1c
htlcswitch+refactor: continue modularising extractTLVPayload
ellemouton Jul 10, 2024
3d9c77d
htlcswitch+refactor: add rHash and sphinx.Router to sphinxHopIterator
ellemouton Jul 10, 2024
b0d3e4d
multi: extract path ID and total amt from received payment
ellemouton May 5, 2024
65aef6a
htlcswitch: handle blinded path dummy hops
ellemouton Jul 10, 2024
34d8fff
itest: update route blind tests
ellemouton May 6, 2024
735d7d9
multi: send to a blinded path in an invoice
ellemouton May 6, 2024
64a99d4
itest: end to end route blinding invoices test
ellemouton May 7, 2024
f0558ba
multi: send MPP payment to blinded path
ellemouton May 7, 2024
66765de
itest: add route blinding dummy hops test
ellemouton Jul 8, 2024
74e45ec
docs: update release notes
ellemouton Jul 2, 2024
c62a9c2
itest: test blinded paths over private channels
ellemouton Jul 24, 2024
398623b
blindedpath: move blinded path logic to own pkg
ellemouton Jul 24, 2024
60a856a
record/routing: set minimum padding size
ellemouton Jul 26, 2024
c490279
blindedpath: smarter dummy hop policy selection
ellemouton Jul 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions channeldb/payment_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@ var (

// ErrValueMismatch is returned if we try to register a non-MPP attempt
// with an amount that doesn't match the payment amount.
ErrValueMismatch = errors.New("attempted value doesn't match payment" +
ErrValueMismatch = errors.New("attempted value doesn't match payment " +
"amount")

// ErrValueExceedsAmt is returned if we try to register an attempt that
// would take the total sent amount above the payment amount.
ErrValueExceedsAmt = errors.New("attempted value exceeds payment" +
ErrValueExceedsAmt = errors.New("attempted value exceeds payment " +
"amount")

// ErrNonMPPayment is returned if we try to register an MPP attempt for
Expand All @@ -83,6 +83,17 @@ var (
// a payment that already has an MPP attempt registered.
ErrMPPayment = errors.New("payment has MPP attempts")

// ErrMPPRecordInBlindedPayment is returned if we try to register an
// attempt with an MPP record for a payment to a blinded path.
ErrMPPRecordInBlindedPayment = errors.New("blinded payment cannot " +
"contain MPP records")

// ErrBlindedPaymentTotalAmountMismatch is returned if we try to
// register an HTLC shard to a blinded route where the total amount
// doesn't match existing shards.
ErrBlindedPaymentTotalAmountMismatch = errors.New("blinded path " +
"total amount mismatch")

// ErrMPPPaymentAddrMismatch is returned if we try to register an MPP
// shard where the payment address doesn't match existing shards.
ErrMPPPaymentAddrMismatch = errors.New("payment address mismatch")
Expand All @@ -96,7 +107,7 @@ var (
// attempt to a payment that has at least one of its HTLCs settled.
ErrPaymentPendingSettled = errors.New("payment has settled htlcs")

// ErrPaymentAlreadyFailed is returned when we try to add a new attempt
// ErrPaymentPendingFailed is returned when we try to add a new attempt
// to a payment that already has a failure reason.
ErrPaymentPendingFailed = errors.New("payment has failure reason")

Expand Down Expand Up @@ -334,12 +345,48 @@ func (p *PaymentControl) RegisterAttempt(paymentHash lntypes.Hash,
return err
}

// If the final hop has encrypted data, then we know this is a
// blinded payment. In blinded payments, MPP records are not set
// for split payments and the recipient is responsible for using
// a consistent PathID across the various encrypted data
// payloads that we received from them for this payment. All we
// need to check is that the total amount field for each HTLC
// in the split payment is correct.
isBlinded := len(attempt.Route.FinalHop().EncryptedData) != 0

// Make sure any existing shards match the new one with regards
// to MPP options.
mpp := attempt.Route.FinalHop().MPP

// MPP records should not be set for attempts to blinded paths.
if isBlinded && mpp != nil {
return ErrMPPRecordInBlindedPayment
}

for _, h := range payment.InFlightHTLCs() {
hMpp := h.Route.FinalHop().MPP

// If this is a blinded payment, then no existing HTLCs
// should have MPP records.
if isBlinded && hMpp != nil {
return ErrMPPRecordInBlindedPayment
}

// If this is a blinded payment, then we just need to
// check that the TotalAmtMsat field for this shard
// is equal to that of any other shard in the same
// payment.
if isBlinded {
if attempt.Route.FinalHop().TotalAmtMsat !=
h.Route.FinalHop().TotalAmtMsat {

//nolint:lll
return ErrBlindedPaymentTotalAmountMismatch
}

continue
}

switch {
// We tried to register a non-MPP attempt for a MPP
// payment.
Expand Down Expand Up @@ -367,9 +414,10 @@ func (p *PaymentControl) RegisterAttempt(paymentHash lntypes.Hash,
}

// If this is a non-MPP attempt, it must match the total amount
// exactly.
// exactly. Note that a blinded payment is considered an MPP
// attempt.
amt := attempt.Route.ReceiverAmt()
if mpp == nil && amt != payment.Info.Value {
if !isBlinded && mpp == nil && amt != payment.Info.Value {
return ErrValueMismatch
}

Expand Down
14 changes: 14 additions & 0 deletions cmd/lncli/cmd_invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ var addInvoiceCommand = cli.Command{
Usage: "creates an AMP invoice. If true, preimage " +
"should not be set.",
},
cli.BoolFlag{
Name: "blind",
Usage: "creates an invoice that contains blinded " +
"paths. Note that invoices with blinded " +
"paths will be signed using a random " +
"ephemeral key so as not to reveal the real " +
"node ID of this node.",
},
},
Action: actionDecorator(addInvoice),
}
Expand Down Expand Up @@ -127,6 +135,11 @@ func addInvoice(ctx *cli.Context) error {
return fmt.Errorf("unable to parse description_hash: %w", err)
}

if ctx.IsSet("private") && ctx.IsSet("blind") {
return fmt.Errorf("cannot include both route hints and " +
"blinded paths in the same invoice")
}

invoice := &lnrpc.Invoice{
Memo: ctx.String("memo"),
RPreimage: preimage,
Expand All @@ -138,6 +151,7 @@ func addInvoice(ctx *cli.Context) error {
CltvExpiry: ctx.Uint64("cltv_expiry_delta"),
Private: ctx.Bool("private"),
IsAmp: ctx.Bool("amp"),
Blind: ctx.Bool("blind"),
}

resp, err := client.AddInvoice(ctxc, invoice)
Expand Down
23 changes: 11 additions & 12 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,15 @@ func DefaultConfig() Config {
Invoices: &lncfg.Invoices{
HoldExpiryDelta: lncfg.DefaultHoldInvoiceExpiryDelta,
},
Routing: &lncfg.Routing{
BlindedPaths: lncfg.BlindedPaths{
MinNumRealHops: lncfg.DefaultMinNumRealBlindedPathHops,
NumHops: lncfg.DefaultNumBlindedPathHops,
MaxNumPaths: lncfg.DefaultMaxNumBlindedPaths,
PolicyIncreaseMultiplier: lncfg.DefaultBlindedPathPolicyIncreaseMultiplier,
PolicyDecreaseMultiplier: lncfg.DefaultBlindedPathPolicyDecreaseMultiplier,
},
},
MaxOutgoingCltvExpiry: htlcswitch.DefaultMaxOutgoingCltvExpiry,
MaxChannelFeeAllocation: htlcswitch.DefaultMaxLinkFeeAllocation,
MaxCommitFeeRateAnchors: lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte,
Expand Down Expand Up @@ -1656,18 +1665,6 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
return nil, mkErr("error parsing gossip syncer: %v", err)
}

// Log a warning if our expiry delta is not greater than our incoming
// broadcast delta. We do not fail here because this value may be set
// to zero to intentionally keep lnd's behavior unchanged from when we
// didn't auto-cancel these invoices.
if cfg.Invoices.HoldExpiryDelta <= lncfg.DefaultIncomingBroadcastDelta {
ltndLog.Warnf("Invoice hold expiry delta: %v <= incoming "+
"delta: %v, accepted hold invoices will force close "+
"channels if they are not canceled manually",
cfg.Invoices.HoldExpiryDelta,
lncfg.DefaultIncomingBroadcastDelta)
}

// If the experimental protocol options specify any protocol messages
// that we want to handle as custom messages, set them now.
customMsg := cfg.ProtocolOptions.CustomMessageOverrides()
Expand All @@ -1690,6 +1687,8 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
cfg.RemoteSigner,
cfg.Sweeper,
cfg.Htlcswitch,
cfg.Invoices,
cfg.Routing,
)
if err != nil {
return nil, err
Expand Down
5 changes: 5 additions & 0 deletions docs/release-notes/release-notes-0.18.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@
* [Groundwork](https://github.com/lightningnetwork/lnd/pull/8752) in preparation
for implementing route blinding receives.

* [Generate and send to](https://github.com/lightningnetwork/lnd/pull/8735) an
invoice with blinded paths. With this, the `--blind` flag can be used with
the `lncli addinvoice` command to instruct LND to include blinded paths in the
invoice.

## Testing
## Database

Expand Down
3 changes: 3 additions & 0 deletions feature/default_sets.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,7 @@ var defaultSetDesc = setDesc{
SetInit: {}, // I
SetNodeAnn: {}, // N
},
lnwire.Bolt11BlindedPathsOptional: {
SetInvoice: {}, // I
},
}
4 changes: 2 additions & 2 deletions feature/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ var deps = depDesc{
lnwire.RouteBlindingOptional: {
lnwire.TLVOnionPayloadOptional: {},
},
lnwire.RouteBlindingRequired: {
lnwire.TLVOnionPayloadRequired: {},
lnwire.Bolt11BlindedPathsOptional: {
lnwire.RouteBlindingOptional: {},
},
}

Expand Down
4 changes: 4 additions & 0 deletions feature/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) {
raw.Unset(lnwire.MPPRequired)
raw.Unset(lnwire.RouteBlindingOptional)
raw.Unset(lnwire.RouteBlindingRequired)
raw.Unset(lnwire.Bolt11BlindedPathsOptional)
raw.Unset(lnwire.Bolt11BlindedPathsRequired)
raw.Unset(lnwire.AMPOptional)
raw.Unset(lnwire.AMPRequired)
raw.Unset(lnwire.KeysendOptional)
Expand Down Expand Up @@ -187,6 +189,8 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) {
if cfg.NoRouteBlinding {
raw.Unset(lnwire.RouteBlindingOptional)
raw.Unset(lnwire.RouteBlindingRequired)
raw.Unset(lnwire.Bolt11BlindedPathsOptional)
raw.Unset(lnwire.Bolt11BlindedPathsRequired)
}
for _, custom := range cfg.CustomFeatures[set] {
if custom > set.Maximum() {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ require (
github.com/kkdai/bstream v1.0.0
github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd
github.com/lightninglabs/neutrino/cache v1.1.2
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f
github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb
github.com/lightningnetwork/lnd/cert v1.2.2
github.com/lightningnetwork/lnd/clock v1.1.1
github.com/lightningnetwork/lnd/fn v1.2.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,8 @@ github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3
github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo=
github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display h1:pRdza2wleRN1L2fJXd6ZoQ9ZegVFTAb2bOQfruJPKcY=
github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f h1:Pua7+5TcFEJXIIZ1I2YAUapmbcttmLj4TTi786bIi3s=
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI=
github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb h1:yfM05S8DXKhuCBp5qSMZdtSwvJ+GFzl94KbXMNB1JDY=
github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI=
github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI=
github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U=
github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0=
Expand Down
3 changes: 1 addition & 2 deletions htlcswitch/circuit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
bitcoinCfg "github.com/btcsuite/btcd/chaincfg"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch"
Expand Down Expand Up @@ -87,7 +86,7 @@ func initTestExtracter() {
func newOnionProcessor(t *testing.T) *hop.OnionProcessor {
sphinxRouter := sphinx.NewRouter(
&keychain.PrivKeyECDH{PrivKey: sphinxPrivKey},
&bitcoinCfg.SimNetParams, sphinx.NewMemoryReplayLog(),
sphinx.NewMemoryReplayLog(),
)

if err := sphinxRouter.Start(); err != nil {
Expand Down
6 changes: 6 additions & 0 deletions htlcswitch/hop/forwarding_info.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package hop

import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/lnwire"
)

Expand All @@ -27,4 +28,9 @@ type ForwardingInfo struct {
// node in UpdateAddHtlc. This field is set if the htlc is part of a
// blinded route.
NextBlinding lnwire.BlindingPointRecord

// PathID is a secret identifier that the creator of a blinded path
// sets for itself to ensure that the blinded path has been used in the
// correct context.
PathID *chainhash.Hash
}
Loading
Loading