diff --git a/channeldb/invoice_test.go b/channeldb/invoice_test.go index 3f35586b0..2a5c20e43 100644 --- a/channeldb/invoice_test.go +++ b/channeldb/invoice_test.go @@ -2,7 +2,6 @@ package channeldb import ( "crypto/rand" - "crypto/sha256" "reflect" "testing" "time" @@ -67,17 +66,18 @@ func TestInvoiceWorkflow(t *testing.T) { copy(fakeInvoice.Terms.PaymentPreimage[:], rev[:]) fakeInvoice.Terms.Value = lnwire.NewMSatFromSatoshis(10000) + paymentHash := fakeInvoice.Terms.PaymentPreimage.Hash() + // Add the invoice to the database, this should succeed as there aren't // any existing invoices within the database with the same payment // hash. - if _, err := db.AddInvoice(fakeInvoice); err != nil { + if _, err := db.AddInvoice(fakeInvoice, paymentHash); err != nil { t.Fatalf("unable to find invoice: %v", err) } // Attempt to retrieve the invoice which was just added to the // database. It should be found, and the invoice returned should be // identical to the one created above. - paymentHash := sha256.Sum256(fakeInvoice.Terms.PaymentPreimage[:]) dbInvoice, err := db.LookupInvoice(paymentHash) if err != nil { t.Fatalf("unable to find invoice: %v", err) @@ -126,7 +126,7 @@ func TestInvoiceWorkflow(t *testing.T) { // Attempt to insert generated above again, this should fail as // duplicates are rejected by the processing logic. - if _, err := db.AddInvoice(fakeInvoice); err != ErrDuplicateInvoice { + if _, err := db.AddInvoice(fakeInvoice, paymentHash); err != ErrDuplicateInvoice { t.Fatalf("invoice insertion should fail due to duplication, "+ "instead %v", err) } @@ -149,7 +149,8 @@ func TestInvoiceWorkflow(t *testing.T) { t.Fatalf("unable to create invoice: %v", err) } - if _, err := db.AddInvoice(invoice); err != nil { + hash := invoice.Terms.PaymentPreimage.Hash() + if _, err := db.AddInvoice(invoice, hash); err != nil { t.Fatalf("unable to add invoice %v", err) } @@ -198,7 +199,9 @@ func TestInvoiceAddTimeSeries(t *testing.T) { t.Fatalf("unable to create invoice: %v", err) } - if _, err := db.AddInvoice(invoice); err != nil { + paymentHash := invoice.Terms.PaymentPreimage.Hash() + + if _, err := db.AddInvoice(invoice, paymentHash); err != nil { t.Fatalf("unable to add invoice %v", err) } @@ -256,9 +259,7 @@ func TestInvoiceAddTimeSeries(t *testing.T) { for i := 10; i < len(invoices); i++ { invoice := &invoices[i] - paymentHash := sha256.Sum256( - invoice.Terms.PaymentPreimage[:], - ) + paymentHash := invoice.Terms.PaymentPreimage.Hash() _, err := db.SettleInvoice(paymentHash, 0) if err != nil { @@ -334,12 +335,13 @@ func TestDuplicateSettleInvoice(t *testing.T) { t.Fatalf("unable to create invoice: %v", err) } - if _, err := db.AddInvoice(invoice); err != nil { + payHash := invoice.Terms.PaymentPreimage.Hash() + + if _, err := db.AddInvoice(invoice, payHash); err != nil { t.Fatalf("unable to add invoice %v", err) } // With the invoice in the DB, we'll now attempt to settle the invoice. - payHash := sha256.Sum256(invoice.Terms.PaymentPreimage[:]) dbInvoice, err := db.SettleInvoice(payHash, amt) if err != nil { t.Fatalf("unable to settle invoice: %v", err) @@ -397,13 +399,14 @@ func TestQueryInvoices(t *testing.T) { t.Fatalf("unable to create invoice: %v", err) } - if _, err := db.AddInvoice(invoice); err != nil { + paymentHash := invoice.Terms.PaymentPreimage.Hash() + + if _, err := db.AddInvoice(invoice, paymentHash); err != nil { t.Fatalf("unable to add invoice: %v", err) } // We'll only settle half of all invoices created. if i%2 == 0 { - paymentHash := sha256.Sum256(invoice.Terms.PaymentPreimage[:]) if _, err := db.SettleInvoice(paymentHash, i); err != nil { t.Fatalf("unable to settle invoice: %v", err) } diff --git a/channeldb/invoices.go b/channeldb/invoices.go index 8584252fd..5764319c4 100644 --- a/channeldb/invoices.go +++ b/channeldb/invoices.go @@ -2,7 +2,6 @@ package channeldb import ( "bytes" - "crypto/sha256" "encoding/binary" "errors" "fmt" @@ -16,6 +15,10 @@ import ( ) var ( + // UnknownPreimage is an all-zeroes preimage that indicates that the + // preimage for this invoice is not yet known. + UnknownPreimage lntypes.Preimage + // invoiceBucket is the name of the bucket within the database that // stores all data related to invoices no matter their final state. // Within the invoice bucket, each invoice is keyed by its invoice ID @@ -218,7 +221,9 @@ func validateInvoice(i *Invoice) error { // insertion will be aborted and rejected due to the strict policy banning any // duplicate payment hashes. A side effect of this function is that it sets // AddIndex on newInvoice. -func (d *DB) AddInvoice(newInvoice *Invoice) (uint64, error) { +func (d *DB) AddInvoice(newInvoice *Invoice, paymentHash lntypes.Hash) ( + uint64, error) { + if err := validateInvoice(newInvoice); err != nil { return 0, err } @@ -245,9 +250,6 @@ func (d *DB) AddInvoice(newInvoice *Invoice) (uint64, error) { // Ensure that an invoice an identical payment hash doesn't // already exist within the index. - paymentHash := sha256.Sum256( - newInvoice.Terms.PaymentPreimage[:], - ) if invoiceIndex.Get(paymentHash[:]) != nil { return ErrDuplicateInvoice } @@ -269,6 +271,7 @@ func (d *DB) AddInvoice(newInvoice *Invoice) (uint64, error) { newIndex, err := putInvoice( invoices, invoiceIndex, addIndex, newInvoice, invoiceNum, + paymentHash, ) if err != nil { return err @@ -744,7 +747,8 @@ func (d *DB) InvoicesSettledSince(sinceSettleIndex uint64) ([]Invoice, error) { } func putInvoice(invoices, invoiceIndex, addIndex *bbolt.Bucket, - i *Invoice, invoiceNum uint32) (uint64, error) { + i *Invoice, invoiceNum uint32, paymentHash lntypes.Hash) ( + uint64, error) { // Create the invoice key which is just the big-endian representation // of the invoice number. @@ -763,7 +767,6 @@ func putInvoice(invoices, invoiceIndex, addIndex *bbolt.Bucket, // Add the payment hash to the invoice index. This will let us quickly // identify if we can settle an incoming payment, and also to possibly // allow a single invoice to have multiple payment installations. - paymentHash := sha256.Sum256(i.Terms.PaymentPreimage[:]) err := invoiceIndex.Put(paymentHash[:], invoiceKey[:]) if err != nil { return 0, err diff --git a/htlcswitch/link.go b/htlcswitch/link.go index b432eec07..98aa8d582 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -2569,6 +2569,19 @@ func (l *channelLink) processExitHop(pd *lnwallet.PaymentDescriptor, return true, nil } + // Reject invoices with unknown preimages. + if invoice.Terms.PaymentPreimage == channeldb.UnknownPreimage { + log.Errorf("rejecting htlc because preimage is unknown") + + failure := lnwire.NewFailUnknownPaymentHash(pd.Amount) + l.sendHTLCError( + pd.HtlcIndex, failure, obfuscator, + pd.SourceRef, + ) + + return true, nil + } + // If the invoice is already settled, we choose to accept the payment to // simplify failure recovery. // diff --git a/invoices/invoiceregistry.go b/invoices/invoiceregistry.go index 881118206..5914b7aec 100644 --- a/invoices/invoiceregistry.go +++ b/invoices/invoiceregistry.go @@ -392,7 +392,7 @@ func (i *InvoiceRegistry) AddInvoice(invoice *channeldb.Invoice, return spew.Sdump(invoice) })) - addIndex, err := i.cdb.AddInvoice(invoice) + addIndex, err := i.cdb.AddInvoice(invoice, paymentHash) if err != nil { return 0, err } diff --git a/lnrpc/invoicesrpc/addinvoice.go b/lnrpc/invoicesrpc/addinvoice.go index 42b79cbd0..bedeefc8d 100644 --- a/lnrpc/invoicesrpc/addinvoice.go +++ b/lnrpc/invoicesrpc/addinvoice.go @@ -4,19 +4,20 @@ import ( "bytes" "context" "crypto/rand" + "errors" "fmt" "math" "time" - "github.com/lightningnetwork/lnd/lntypes" - "github.com/lightningnetwork/lnd/netann" - "github.com/lightningnetwork/lnd/zpay32" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/netann" + "github.com/lightningnetwork/lnd/zpay32" ) // AddInvoiceConfig contains dependencies for invoice creation. @@ -61,9 +62,15 @@ type AddInvoiceData struct { Receipt []byte // The preimage which will allow settling an incoming HTLC payable to - // this preimage. + // this preimage. If Preimage is set, Hash should be nil. If both + // Preimage and Hash are nil, a random preimage is generated. Preimage *lntypes.Preimage + // The hash of the preimage. If Hash is set, Preimage should be nil. + // This condition indicates that we have a 'hold invoice' for which the + // htlc will be accepted and held until the preimage becomes known. + Hash *lntypes.Hash + // The value of this invoice in satoshis. Value btcutil.Amount @@ -87,19 +94,58 @@ type AddInvoiceData struct { } // AddInvoice attempts to add a new invoice to the invoice database. Any -// duplicated invoices are rejected, therefore all invoices *must* have a unique -// payment preimage. AddInvoice returns the payment hash and the invoice -// structure as stored in the database. +// duplicated invoices are rejected, therefore all invoices *must* have a +// unique payment preimage. func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, invoice *AddInvoiceData) (*lntypes.Hash, *channeldb.Invoice, error) { - var paymentPreimage lntypes.Preimage - if invoice.Preimage == nil { + var ( + paymentPreimage lntypes.Preimage + paymentHash lntypes.Hash + ) + + switch { + + // Only either preimage or hash can be set. + case invoice.Preimage != nil && invoice.Hash != nil: + return nil, nil, + errors.New("preimage and hash both set") + + // Prevent the unknown preimage magic value from being used for a + // regular invoice. This would cause the invoice the be handled as if it + // was a hold invoice. + case invoice.Preimage != nil && + *invoice.Preimage == channeldb.UnknownPreimage: + + return nil, nil, + fmt.Errorf("cannot use all zeroes as a preimage") + + // Prevent the hash of the unknown preimage magic value to be used for a + // hold invoice. This would make it impossible to settle the invoice, + // because it would still be interpreted as not having a preimage. + case invoice.Hash != nil && + *invoice.Hash == channeldb.UnknownPreimage.Hash(): + + return nil, nil, + fmt.Errorf("cannot use hash of all zeroes preimage") + + // If no hash or preimage is given, generate a random preimage. + case invoice.Preimage == nil && invoice.Hash == nil: if _, err := rand.Read(paymentPreimage[:]); err != nil { return nil, nil, err } - } else { + paymentHash = paymentPreimage.Hash() + + // If just a hash is given, we create a hold invoice by setting the + // preimage to unknown. + case invoice.Preimage == nil && invoice.Hash != nil: + paymentPreimage = channeldb.UnknownPreimage + paymentHash = *invoice.Hash + + // A specific preimage was supplied. Use that for the invoice. + case invoice.Preimage != nil && invoice.Hash == nil: paymentPreimage = *invoice.Preimage + paymentHash = invoice.Preimage.Hash() } // The size of the memo, receipt and description hash attached must not @@ -134,10 +180,6 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, ) } - // Next, generate the payment hash itself from the preimage. This will - // be used by clients to query for the state of a particular invoice. - rHash := paymentPreimage.Hash() - // We also create an encoded payment request which allows the // caller to compactly send the invoice to the payer. We'll create a // list of options to be added to the encoded payment request. For now @@ -332,7 +374,7 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, // Create and encode the payment request as a bech32 (zpay32) string. creationDate := time.Now() payReq, err := zpay32.NewInvoice( - cfg.ChainParams, rHash, creationDate, options..., + cfg.ChainParams, paymentHash, creationDate, options..., ) if err != nil { return nil, nil, err @@ -365,10 +407,10 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, ) // With all sanity checks passed, write the invoice to the database. - _, err = cfg.AddInvoice(newInvoice, rHash) + _, err = cfg.AddInvoice(newInvoice, paymentHash) if err != nil { return nil, nil, err } - return &rHash, newInvoice, nil + return &paymentHash, newInvoice, nil } diff --git a/lnrpc/invoicesrpc/utils.go b/lnrpc/invoicesrpc/utils.go index bbba240a3..b911602c7 100644 --- a/lnrpc/invoicesrpc/utils.go +++ b/lnrpc/invoicesrpc/utils.go @@ -65,11 +65,10 @@ func CreateRPCInvoice(invoice *channeldb.Invoice, invoice.Terms.State) } - return &lnrpc.Invoice{ + rpcInvoice := &lnrpc.Invoice{ Memo: string(invoice.Memo[:]), Receipt: invoice.Receipt[:], RHash: decoded.PaymentHash[:], - RPreimage: preimage[:], Value: int64(satAmt), CreationDate: invoice.CreationDate.Unix(), SettleDate: settleDate, @@ -87,7 +86,13 @@ func CreateRPCInvoice(invoice *channeldb.Invoice, AmtPaidMsat: int64(invoice.AmtPaid), AmtPaid: int64(invoice.AmtPaid), State: state, - }, nil + } + + if preimage != channeldb.UnknownPreimage { + rpcInvoice.RPreimage = preimage[:] + } + + return rpcInvoice, nil } // CreateRPCRouteHints takes in the decoded form of an invoice's route hints diff --git a/witness_beacon.go b/witness_beacon.go index 998269845..1d368c581 100644 --- a/witness_beacon.go +++ b/witness_beacon.go @@ -85,7 +85,9 @@ func (p *preimageBeacon) LookupPreimage( // If we've found the invoice, then we can return the preimage // directly. - if err != channeldb.ErrInvoiceNotFound { + if err != channeldb.ErrInvoiceNotFound && + invoice.Terms.PaymentPreimage != channeldb.UnknownPreimage { + return invoice.Terms.PaymentPreimage, true }