package invoicesrpc import ( "bytes" "context" "crypto/rand" "errors" "fmt" "math" "time" "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. type AddInvoiceConfig struct { // AddInvoice is called to add the invoice to the registry. AddInvoice func(invoice *channeldb.Invoice, paymentHash lntypes.Hash) ( uint64, error) // IsChannelActive is used to generate valid hop hints. IsChannelActive func(chanID lnwire.ChannelID) bool // ChainParams are required to properly decode invoice payment requests // that are marshalled over rpc. ChainParams *chaincfg.Params // NodeSigner is an implementation of the MessageSigner implementation // that's backed by the identity private key of the running lnd node. NodeSigner *netann.NodeSigner // MaxPaymentMSat is the maximum allowed payment. MaxPaymentMSat lnwire.MilliSatoshi // DefaultCLTVExpiry is the default invoice expiry if no values is // specified. DefaultCLTVExpiry uint32 // ChanDB is a global boltdb instance which is needed to access the // channel graph. ChanDB *channeldb.DB } // AddInvoiceData contains the required data to create a new invoice. type AddInvoiceData struct { // An optional memo to attach along with the invoice. Used for record // keeping purposes for the invoice's creator, and will also be set in // the description field of the encoded payment request if the // description_hash field is not being used. Memo string // Deprecated. An optional cryptographic receipt of payment which is not // implemented. Receipt []byte // The preimage which will allow settling an incoming HTLC payable to // 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 // Hash (SHA-256) of a description of the payment. Used if the // description of payment (memo) is too long to naturally fit within the // description field of an encoded payment request. DescriptionHash []byte // Payment request expiry time in seconds. Default is 3600 (1 hour). Expiry int64 // Fallback on-chain address. FallbackAddr string // Delta to use for the time-lock of the CLTV extended to the final hop. CltvExpiry uint64 // Whether this invoice should include routing hints for private // channels. Private bool } // 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. func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, invoice *AddInvoiceData) (*lntypes.Hash, *channeldb.Invoice, error) { 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 } 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 // exceed the maximum values for either of the fields. if len(invoice.Memo) > channeldb.MaxMemoSize { return nil, nil, fmt.Errorf("memo too large: %v bytes "+ "(maxsize=%v)", len(invoice.Memo), channeldb.MaxMemoSize) } if len(invoice.Receipt) > channeldb.MaxReceiptSize { return nil, nil, fmt.Errorf("receipt too large: %v bytes "+ "(maxsize=%v)", len(invoice.Receipt), channeldb.MaxReceiptSize) } if len(invoice.DescriptionHash) > 0 && len(invoice.DescriptionHash) != 32 { return nil, nil, fmt.Errorf("description hash is %v bytes, must be %v", len(invoice.DescriptionHash), channeldb.MaxPaymentRequestSize) } // The value of the invoice must not be negative. if invoice.Value < 0 { return nil, nil, fmt.Errorf("payments of negative value "+ "are not allowed, value is %v", invoice.Value) } amtMSat := lnwire.NewMSatFromSatoshis(invoice.Value) // The value of the invoice must also not exceed the current soft-limit // on the largest payment within the network. if amtMSat > cfg.MaxPaymentMSat { return nil, nil, fmt.Errorf("payment of %v is too large, max "+ "payment allowed is %v", invoice.Value, cfg.MaxPaymentMSat.ToSatoshis(), ) } // 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 // we only support the required fields description/description_hash, // expiry, fallback address, and the amount field. var options []func(*zpay32.Invoice) // We only include the amount in the invoice if it is greater than 0. // By not including the amount, we enable the creation of invoices that // allow the payee to specify the amount of satoshis they wish to send. if amtMSat > 0 { options = append(options, zpay32.Amount(amtMSat)) } // If specified, add a fallback address to the payment request. if len(invoice.FallbackAddr) > 0 { addr, err := btcutil.DecodeAddress(invoice.FallbackAddr, cfg.ChainParams) if err != nil { return nil, nil, fmt.Errorf("invalid fallback address: %v", err) } options = append(options, zpay32.FallbackAddr(addr)) } // If expiry is set, specify it. If it is not provided, no expiry time // will be explicitly added to this payment request, which will imply // the default 3600 seconds. if invoice.Expiry > 0 { // We'll ensure that the specified expiry is restricted to sane // number of seconds. As a result, we'll reject an invoice with // an expiry greater than 1 year. maxExpiry := time.Hour * 24 * 365 expSeconds := invoice.Expiry if float64(expSeconds) > maxExpiry.Seconds() { return nil, nil, fmt.Errorf("expiry of %v seconds "+ "greater than max expiry of %v seconds", float64(expSeconds), maxExpiry.Seconds()) } expiry := time.Duration(invoice.Expiry) * time.Second options = append(options, zpay32.Expiry(expiry)) } // If the description hash is set, then we add it do the list of options. // If not, use the memo field as the payment request description. if len(invoice.DescriptionHash) > 0 { var descHash [32]byte copy(descHash[:], invoice.DescriptionHash[:]) options = append(options, zpay32.DescriptionHash(descHash)) } else { // Use the memo field as the description. If this is not set // this will just be an empty string. options = append(options, zpay32.Description(invoice.Memo)) } // We'll use our current default CLTV value unless one was specified as // an option on the command line when creating an invoice. switch { case invoice.CltvExpiry > math.MaxUint16: return nil, nil, fmt.Errorf("CLTV delta of %v is too large, max "+ "accepted is: %v", invoice.CltvExpiry, math.MaxUint16) case invoice.CltvExpiry != 0: options = append(options, zpay32.CLTVExpiry(invoice.CltvExpiry)) default: // TODO(roasbeef): assumes set delta between versions defaultDelta := cfg.DefaultCLTVExpiry options = append(options, zpay32.CLTVExpiry(uint64(defaultDelta))) } // If we were requested to include routing hints in the invoice, then // we'll fetch all of our available private channels and create routing // hints for them. if invoice.Private { openChannels, err := cfg.ChanDB.FetchAllChannels() if err != nil { return nil, nil, fmt.Errorf("could not fetch all channels") } graph := cfg.ChanDB.ChannelGraph() numHints := 0 for _, channel := range openChannels { // We'll restrict the number of individual route hints // to 20 to avoid creating overly large invoices. if numHints >= 20 { break } // Since we're only interested in our private channels, // we'll skip public ones. isPublic := channel.ChannelFlags&lnwire.FFAnnounceChannel != 0 if isPublic { continue } // Make sure the counterparty has enough balance in the // channel for our amount. We do this in order to reduce // payment errors when attempting to use this channel // as a hint. chanPoint := lnwire.NewChanIDFromOutPoint( &channel.FundingOutpoint, ) if amtMSat >= channel.LocalCommitment.RemoteBalance { log.Debugf("Skipping channel %v due to "+ "not having enough remote balance", chanPoint) continue } // Make sure the channel is active. if !cfg.IsChannelActive(chanPoint) { log.Debugf("Skipping channel %v due to not "+ "being eligible to forward payments", chanPoint) continue } // To ensure we don't leak unadvertised nodes, we'll // make sure our counterparty is publicly advertised // within the network. Otherwise, we'll end up leaking // information about nodes that intend to stay // unadvertised, like in the case of a node only having // private channels. var remotePub [33]byte copy(remotePub[:], channel.IdentityPub.SerializeCompressed()) isRemoteNodePublic, err := graph.IsPublicNode(remotePub) if err != nil { log.Errorf("Unable to determine if node %x "+ "is advertised: %v", remotePub, err) continue } if !isRemoteNodePublic { log.Debugf("Skipping channel %v due to "+ "counterparty %x being unadvertised", chanPoint, remotePub) continue } // Fetch the policies for each end of the channel. chanID := channel.ShortChanID().ToUint64() info, p1, p2, err := graph.FetchChannelEdgesByID(chanID) if err != nil { log.Errorf("Unable to fetch the routing "+ "policies for the edges of the channel "+ "%v: %v", chanPoint, err) continue } // Now, we'll need to determine which is the correct // policy for HTLCs being sent from the remote node. var remotePolicy *channeldb.ChannelEdgePolicy if bytes.Equal(remotePub[:], info.NodeKey1Bytes[:]) { remotePolicy = p1 } else { remotePolicy = p2 } // If for some reason we don't yet have the edge for // the remote party, then we'll just skip adding this // channel as a routing hint. if remotePolicy == nil { continue } // Finally, create the routing hint for this channel and // add it to our list of route hints. hint := zpay32.HopHint{ NodeID: channel.IdentityPub, ChannelID: chanID, FeeBaseMSat: uint32(remotePolicy.FeeBaseMSat), FeeProportionalMillionths: uint32( remotePolicy.FeeProportionalMillionths, ), CLTVExpiryDelta: remotePolicy.TimeLockDelta, } // Include the route hint in our set of options that // will be used when creating the invoice. routeHint := []zpay32.HopHint{hint} options = append(options, zpay32.RouteHint(routeHint)) numHints++ } } // Create and encode the payment request as a bech32 (zpay32) string. creationDate := time.Now() payReq, err := zpay32.NewInvoice( cfg.ChainParams, paymentHash, creationDate, options..., ) if err != nil { return nil, nil, err } payReqString, err := payReq.Encode( zpay32.MessageSigner{ SignCompact: cfg.NodeSigner.SignDigestCompact, }, ) if err != nil { return nil, nil, err } newInvoice := &channeldb.Invoice{ CreationDate: creationDate, Memo: []byte(invoice.Memo), Receipt: invoice.Receipt, PaymentRequest: []byte(payReqString), FinalCltvDelta: int32(payReq.MinFinalCLTVExpiry()), Expiry: payReq.Expiry(), Terms: channeldb.ContractTerm{ Value: amtMSat, PaymentPreimage: paymentPreimage, }, } log.Tracef("[addinvoice] adding new invoice %v", newLogClosure(func() string { return spew.Sdump(newInvoice) }), ) // With all sanity checks passed, write the invoice to the database. _, err = cfg.AddInvoice(newInvoice, paymentHash) if err != nil { return nil, nil, err } return &paymentHash, newInvoice, nil }