44f13d1d60
This commit adds InvoiceExpryWatcher which is a separate class that receives new invoices (and existing ones upon restart) from InvoiceRegistry and actively watches their expiry. When an invoice is expired InvoiceExpiryWatcher will call into InvoiceRegistry to cancel the invoice and by that notify all subscribers about the state change.
1200 lines
34 KiB
Go
1200 lines
34 KiB
Go
package zpay32
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcec"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/btcsuite/btcutil/bech32"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
)
|
|
|
|
const (
|
|
// mSatPerBtc is the number of millisatoshis in 1 BTC.
|
|
mSatPerBtc = 100000000000
|
|
|
|
// signatureBase32Len is the number of 5-bit groups needed to encode
|
|
// the 512 bit signature + 8 bit recovery ID.
|
|
signatureBase32Len = 104
|
|
|
|
// timestampBase32Len is the number of 5-bit groups needed to encode
|
|
// the 35-bit timestamp.
|
|
timestampBase32Len = 7
|
|
|
|
// hashBase32Len is the number of 5-bit groups needed to encode a
|
|
// 256-bit hash. Note that the last group will be padded with zeroes.
|
|
hashBase32Len = 52
|
|
|
|
// pubKeyBase32Len is the number of 5-bit groups needed to encode a
|
|
// 33-byte compressed pubkey. Note that the last group will be padded
|
|
// with zeroes.
|
|
pubKeyBase32Len = 53
|
|
|
|
// hopHintLen is the number of bytes needed to encode the hop hint of a
|
|
// single private route.
|
|
hopHintLen = 51
|
|
|
|
// The following byte values correspond to the supported field types.
|
|
// The field name is the character representing that 5-bit value in the
|
|
// bech32 string.
|
|
|
|
// fieldTypeP is the field containing the payment hash.
|
|
fieldTypeP = 1
|
|
|
|
// fieldTypeD contains a short description of the payment.
|
|
fieldTypeD = 13
|
|
|
|
// fieldTypeN contains the pubkey of the target node.
|
|
fieldTypeN = 19
|
|
|
|
// fieldTypeH contains the hash of a description of the payment.
|
|
fieldTypeH = 23
|
|
|
|
// fieldTypeX contains the expiry in seconds of the invoice.
|
|
fieldTypeX = 6
|
|
|
|
// fieldTypeF contains a fallback on-chain address.
|
|
fieldTypeF = 9
|
|
|
|
// fieldTypeR contains extra routing information.
|
|
fieldTypeR = 3
|
|
|
|
// fieldTypeC contains an optional requested final CLTV delta.
|
|
fieldTypeC = 24
|
|
|
|
// fieldType9 contains one or more bytes for signaling features
|
|
// supported or required by the receiver.
|
|
fieldType9 = 5
|
|
|
|
// fieldTypeS contains a 32-byte payment address, which is a nonce
|
|
// included in the final hop's payload to prevent intermediaries from
|
|
// probing the recipient.
|
|
fieldTypeS = 16
|
|
|
|
// 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
|
|
maxInvoiceLength = 7089
|
|
|
|
// DefaultInvoiceExpiry is the default expiry duration from the creation
|
|
// timestamp if expiry is set to zero.
|
|
DefaultInvoiceExpiry = time.Hour
|
|
)
|
|
|
|
var (
|
|
// ErrInvoiceTooLarge is returned when an invoice exceeds
|
|
// maxInvoiceLength.
|
|
ErrInvoiceTooLarge = errors.New("invoice is too large")
|
|
|
|
// ErrInvalidFieldLength is returned when a tagged field was specified
|
|
// with a length larger than the left over bytes of the data field.
|
|
ErrInvalidFieldLength = errors.New("invalid field length")
|
|
|
|
// ErrBrokenTaggedField is returned when the last tagged field is
|
|
// incorrectly formatted and doesn't have enough bytes to be read.
|
|
ErrBrokenTaggedField = errors.New("last tagged field is broken")
|
|
)
|
|
|
|
// MessageSigner is passed to the Encode method to provide a signature
|
|
// corresponding to the node's pubkey.
|
|
type MessageSigner struct {
|
|
// SignCompact signs the passed hash with the node's privkey. The
|
|
// returned signature should be 65 bytes, where the last 64 are the
|
|
// compact signature, and the first one is a header byte. This is the
|
|
// format returned by btcec.SignCompact.
|
|
SignCompact func(hash []byte) ([]byte, error)
|
|
}
|
|
|
|
// Invoice represents a decoded invoice, or to-be-encoded invoice. Some of the
|
|
// fields are optional, and will only be non-nil if the invoice this was parsed
|
|
// from contains that field. When encoding, only the non-nil fields will be
|
|
// added to the encoded invoice.
|
|
type Invoice struct {
|
|
// Net specifies what network this Lightning invoice is meant for.
|
|
Net *chaincfg.Params
|
|
|
|
// MilliSat specifies the amount of this invoice in millisatoshi.
|
|
// Optional.
|
|
MilliSat *lnwire.MilliSatoshi
|
|
|
|
// Timestamp specifies the time this invoice was created.
|
|
// Mandatory
|
|
Timestamp time.Time
|
|
|
|
// PaymentHash is the payment hash to be used for a payment to this
|
|
// invoice.
|
|
PaymentHash *[32]byte
|
|
|
|
// PaymentAddr is the payment address to be used by payments to prevent
|
|
// probing of the destination.
|
|
PaymentAddr *[32]byte
|
|
|
|
// Destination is the public key of the target node. This will always
|
|
// be set after decoding, and can optionally be set before encoding to
|
|
// include the pubkey as an 'n' field. If this is not set before
|
|
// encoding then the destination pubkey won't be added as an 'n' field,
|
|
// and the pubkey will be extracted from the signature during decoding.
|
|
Destination *btcec.PublicKey
|
|
|
|
// minFinalCLTVExpiry is the value that the creator of the invoice
|
|
// expects to be used for the
|
|
//
|
|
// NOTE: This value is optional, and should be set to nil if the
|
|
// invoice creator doesn't have a strong requirement on the CLTV expiry
|
|
// of the final HTLC extended to it.
|
|
//
|
|
// 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.
|
|
minFinalCLTVExpiry *uint64
|
|
|
|
// Description is a short description of the purpose of this invoice.
|
|
// Optional. Non-nil iff DescriptionHash is nil.
|
|
Description *string
|
|
|
|
// DescriptionHash is the SHA256 hash of a description of the purpose of
|
|
// this invoice.
|
|
// Optional. Non-nil iff Description is nil.
|
|
DescriptionHash *[32]byte
|
|
|
|
// expiry specifies the timespan this invoice will be valid.
|
|
// Optional. If not set, a default expiry of 60 min will be implied.
|
|
//
|
|
// This field is unexported and can be read by the Expiry() method. This
|
|
// method makes sure the default expiry time is returned in case the
|
|
// field is not set.
|
|
expiry *time.Duration
|
|
|
|
// FallbackAddr is an on-chain address that can be used for payment in
|
|
// case the Lightning payment fails.
|
|
// Optional.
|
|
FallbackAddr btcutil.Address
|
|
|
|
// RouteHints represents one or more different route hints. Each route
|
|
// hint can be individually used to reach the destination. These usually
|
|
// represent private routes.
|
|
//
|
|
// NOTE: This is optional.
|
|
RouteHints [][]HopHint
|
|
|
|
// Features represents an optional field used to signal optional or
|
|
// required support for features by the receiver.
|
|
Features *lnwire.FeatureVector
|
|
}
|
|
|
|
// Amount is a functional option that allows callers of NewInvoice to set the
|
|
// amount in millisatoshis that the Invoice should encode.
|
|
func Amount(milliSat lnwire.MilliSatoshi) func(*Invoice) {
|
|
return func(i *Invoice) {
|
|
i.MilliSat = &milliSat
|
|
}
|
|
}
|
|
|
|
// Destination is a functional option that allows callers of NewInvoice to
|
|
// explicitly set the pubkey of the Invoice's destination node.
|
|
func Destination(destination *btcec.PublicKey) func(*Invoice) {
|
|
return func(i *Invoice) {
|
|
i.Destination = destination
|
|
}
|
|
}
|
|
|
|
// Description is a functional option that allows callers of NewInvoice to set
|
|
// the payment description of the created Invoice.
|
|
//
|
|
// NOTE: Must be used if and only if DescriptionHash is not used.
|
|
func Description(description string) func(*Invoice) {
|
|
return func(i *Invoice) {
|
|
i.Description = &description
|
|
}
|
|
}
|
|
|
|
// CLTVExpiry is an optional value which allows the receiver of the payment to
|
|
// specify the delta between the current height and the HTLC extended to the
|
|
// receiver.
|
|
func CLTVExpiry(delta uint64) func(*Invoice) {
|
|
return func(i *Invoice) {
|
|
i.minFinalCLTVExpiry = &delta
|
|
}
|
|
}
|
|
|
|
// DescriptionHash is a functional option that allows callers of NewInvoice to
|
|
// set the payment description hash of the created Invoice.
|
|
//
|
|
// NOTE: Must be used if and only if Description is not used.
|
|
func DescriptionHash(descriptionHash [32]byte) func(*Invoice) {
|
|
return func(i *Invoice) {
|
|
i.DescriptionHash = &descriptionHash
|
|
}
|
|
}
|
|
|
|
// Expiry is a functional option that allows callers of NewInvoice to set the
|
|
// expiry of the created Invoice. If not set, a default expiry of 60 min will
|
|
// be implied.
|
|
func Expiry(expiry time.Duration) func(*Invoice) {
|
|
return func(i *Invoice) {
|
|
i.expiry = &expiry
|
|
}
|
|
}
|
|
|
|
// FallbackAddr is a functional option that allows callers of NewInvoice to set
|
|
// the Invoice's fallback on-chain address that can be used for payment in case
|
|
// the Lightning payment fails
|
|
func FallbackAddr(fallbackAddr btcutil.Address) func(*Invoice) {
|
|
return func(i *Invoice) {
|
|
i.FallbackAddr = fallbackAddr
|
|
}
|
|
}
|
|
|
|
// RouteHint is a functional option that allows callers of NewInvoice to add
|
|
// one or more hop hints that represent a private route to the destination.
|
|
func RouteHint(routeHint []HopHint) func(*Invoice) {
|
|
return func(i *Invoice) {
|
|
i.RouteHints = append(i.RouteHints, routeHint)
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
func Features(features *lnwire.FeatureVector) func(*Invoice) {
|
|
return func(i *Invoice) {
|
|
i.Features = features
|
|
}
|
|
}
|
|
|
|
// PaymentAddr is a functional option that allows callers of NewInvoice to set
|
|
// the desired payment address tht is advertised on the invoice.
|
|
func PaymentAddr(addr [32]byte) func(*Invoice) {
|
|
return func(i *Invoice) {
|
|
i.PaymentAddr = &addr
|
|
}
|
|
}
|
|
|
|
// NewInvoice creates a new Invoice object. The last parameter is a set of
|
|
// variadic arguments for setting optional fields of the invoice.
|
|
//
|
|
// NOTE: Either Description or DescriptionHash must be provided for the Invoice
|
|
// to be considered valid.
|
|
func NewInvoice(net *chaincfg.Params, paymentHash [32]byte,
|
|
timestamp time.Time, options ...func(*Invoice)) (*Invoice, error) {
|
|
|
|
invoice := &Invoice{
|
|
Net: net,
|
|
PaymentHash: &paymentHash,
|
|
Timestamp: timestamp,
|
|
}
|
|
|
|
for _, option := range options {
|
|
option(invoice)
|
|
}
|
|
|
|
// If no features were set, we'll populate an empty feature vector.
|
|
if invoice.Features == nil {
|
|
invoice.Features = lnwire.NewFeatureVector(
|
|
nil, lnwire.Features,
|
|
)
|
|
}
|
|
|
|
if err := validateInvoice(invoice); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return invoice, nil
|
|
}
|
|
|
|
// Decode parses the provided encoded invoice and returns a decoded Invoice if
|
|
// it is valid by BOLT-0011 and matches the provided active network.
|
|
func Decode(invoice string, net *chaincfg.Params) (*Invoice, error) {
|
|
decodedInvoice := Invoice{}
|
|
|
|
// Before bech32 decoding the invoice, make sure that it is not too large.
|
|
// This is done as an anti-DoS measure since bech32 decoding is expensive.
|
|
if len(invoice) > maxInvoiceLength {
|
|
return nil, ErrInvoiceTooLarge
|
|
}
|
|
|
|
// Decode the invoice using the modified bech32 decoder.
|
|
hrp, data, err := decodeBech32(invoice)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// We expect the human-readable part to at least have ln + one char
|
|
// encoding the network.
|
|
if len(hrp) < 3 {
|
|
return nil, fmt.Errorf("hrp too short")
|
|
}
|
|
|
|
// First two characters of HRP should be "ln".
|
|
if hrp[:2] != "ln" {
|
|
return nil, fmt.Errorf("prefix should be \"ln\"")
|
|
}
|
|
|
|
// The next characters should be a valid prefix for a segwit BIP173
|
|
// address that match the active network.
|
|
if !strings.HasPrefix(hrp[2:], net.Bech32HRPSegwit) {
|
|
return nil, fmt.Errorf(
|
|
"invoice not for current active network '%s'", net.Name)
|
|
}
|
|
decodedInvoice.Net = net
|
|
|
|
// Optionally, if there's anything left of the HRP after ln + the segwit
|
|
// prefix, we try to decode this as the payment amount.
|
|
var netPrefixLength = len(net.Bech32HRPSegwit) + 2
|
|
if len(hrp) > netPrefixLength {
|
|
amount, err := decodeAmount(hrp[netPrefixLength:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
decodedInvoice.MilliSat = &amount
|
|
}
|
|
|
|
// Everything except the last 520 bits of the data encodes the invoice's
|
|
// timestamp and tagged fields.
|
|
invoiceData := data[:len(data)-signatureBase32Len]
|
|
|
|
// Parse the timestamp and tagged fields, and fill the Invoice struct.
|
|
if err := parseData(&decodedInvoice, invoiceData, net); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// The last 520 bits (104 groups) make up the signature.
|
|
sigBase32 := data[len(data)-signatureBase32Len:]
|
|
sigBase256, err := bech32.ConvertBits(sigBase32, 5, 8, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var sig lnwire.Sig
|
|
copy(sig[:], sigBase256[:64])
|
|
recoveryID := sigBase256[64]
|
|
|
|
// The signature is over the hrp + the data the invoice, encoded in
|
|
// base 256.
|
|
taggedDataBytes, err := bech32.ConvertBits(invoiceData, 5, 8, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
toSign := append([]byte(hrp), taggedDataBytes...)
|
|
|
|
// We expect the signature to be over the single SHA-256 hash of that
|
|
// data.
|
|
hash := chainhash.HashB(toSign)
|
|
|
|
// If the destination pubkey was provided as a tagged field, use that
|
|
// to verify the signature, if not do public key recovery.
|
|
if decodedInvoice.Destination != nil {
|
|
signature, err := sig.ToSignature()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to deserialize "+
|
|
"signature: %v", err)
|
|
}
|
|
if !signature.Verify(hash, decodedInvoice.Destination) {
|
|
return nil, fmt.Errorf("invalid invoice signature")
|
|
}
|
|
} else {
|
|
headerByte := recoveryID + 27 + 4
|
|
compactSign := append([]byte{headerByte}, sig[:]...)
|
|
pubkey, _, err := btcec.RecoverCompact(btcec.S256(),
|
|
compactSign, hash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
decodedInvoice.Destination = pubkey
|
|
}
|
|
|
|
// If no feature vector was decoded, populate an empty one.
|
|
if decodedInvoice.Features == nil {
|
|
decodedInvoice.Features = lnwire.NewFeatureVector(
|
|
nil, lnwire.Features,
|
|
)
|
|
}
|
|
|
|
// Now that we have created the invoice, make sure it has the required
|
|
// fields set.
|
|
if err := validateInvoice(&decodedInvoice); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &decodedInvoice, nil
|
|
}
|
|
|
|
// Encode takes the given MessageSigner and returns a string encoding this
|
|
// invoice signed by the node key of the signer.
|
|
func (invoice *Invoice) Encode(signer MessageSigner) (string, error) {
|
|
// First check that this invoice is valid before starting the encoding.
|
|
if err := validateInvoice(invoice); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// The buffer will encoded the invoice data using 5-bit groups (base32).
|
|
var bufferBase32 bytes.Buffer
|
|
|
|
// The timestamp will be encoded using 35 bits, in base32.
|
|
timestampBase32 := uint64ToBase32(uint64(invoice.Timestamp.Unix()))
|
|
|
|
// The timestamp must be exactly 35 bits, which means 7 groups. If it
|
|
// can fit into fewer groups we add leading zero groups, if it is too
|
|
// big we fail early, as there is not possible to encode it.
|
|
if len(timestampBase32) > timestampBase32Len {
|
|
return "", fmt.Errorf("timestamp too big: %d",
|
|
invoice.Timestamp.Unix())
|
|
}
|
|
|
|
// Add zero bytes to the first timestampBase32Len-len(timestampBase32)
|
|
// groups, then add the non-zero groups.
|
|
zeroes := make([]byte, timestampBase32Len-len(timestampBase32),
|
|
timestampBase32Len-len(timestampBase32))
|
|
_, err := bufferBase32.Write(zeroes)
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to write to buffer: %v", err)
|
|
}
|
|
_, err = bufferBase32.Write(timestampBase32)
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to write to buffer: %v", err)
|
|
}
|
|
|
|
// We now write the tagged fields to the buffer, which will fill the
|
|
// rest of the data part before the signature.
|
|
if err := writeTaggedFields(&bufferBase32, invoice); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// The human-readable part (hrp) is "ln" + net hrp + optional amount.
|
|
hrp := "ln" + invoice.Net.Bech32HRPSegwit
|
|
if invoice.MilliSat != nil {
|
|
// Encode the amount using the fewest possible characters.
|
|
am, err := encodeAmount(*invoice.MilliSat)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
hrp += am
|
|
}
|
|
|
|
// The signature is over the single SHA-256 hash of the hrp + the
|
|
// tagged fields encoded in base256.
|
|
taggedFieldsBytes, err := bech32.ConvertBits(bufferBase32.Bytes(), 5, 8, true)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
toSign := append([]byte(hrp), taggedFieldsBytes...)
|
|
hash := chainhash.HashB(toSign)
|
|
|
|
// We use compact signature format, and also encoded the recovery ID
|
|
// such that a reader of the invoice can recover our pubkey from the
|
|
// signature.
|
|
sign, err := signer.SignCompact(hash)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// From the header byte we can extract the recovery ID, and the last 64
|
|
// bytes encode the signature.
|
|
recoveryID := sign[0] - 27 - 4
|
|
var sig lnwire.Sig
|
|
copy(sig[:], sign[1:])
|
|
|
|
// If the pubkey field was explicitly set, it must be set to the pubkey
|
|
// used to create the signature.
|
|
if invoice.Destination != nil {
|
|
signature, err := sig.ToSignature()
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to deserialize "+
|
|
"signature: %v", err)
|
|
}
|
|
|
|
valid := signature.Verify(hash, invoice.Destination)
|
|
if !valid {
|
|
return "", fmt.Errorf("signature does not match " +
|
|
"provided pubkey")
|
|
}
|
|
}
|
|
|
|
// Convert the signature to base32 before writing it to the buffer.
|
|
signBase32, err := bech32.ConvertBits(append(sig[:], recoveryID), 8, 5, true)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
bufferBase32.Write(signBase32)
|
|
|
|
// Now we can create the bech32 encoded string from the base32 buffer.
|
|
b32, err := bech32.Encode(hrp, bufferBase32.Bytes())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Before returning, check that the bech32 encoded string is not greater
|
|
// than our largest supported invoice size.
|
|
if len(b32) > maxInvoiceLength {
|
|
return "", ErrInvoiceTooLarge
|
|
}
|
|
|
|
return b32, nil
|
|
}
|
|
|
|
// Expiry returns the expiry time for this invoice. If expiry time is not set
|
|
// explicitly, the default 3600 second expiry will be returned.
|
|
func (invoice *Invoice) Expiry() time.Duration {
|
|
if invoice.expiry != nil {
|
|
return *invoice.expiry
|
|
}
|
|
|
|
// If no expiry is set for this invoice, default is 3600 seconds.
|
|
return 3600 * time.Second
|
|
}
|
|
|
|
// MinFinalCLTVExpiry returns the minimum final CLTV expiry delta as specified
|
|
// by the creator of the invoice. This value specifies the delta between the
|
|
// current height and the expiry height of the HTLC extended in the last hop.
|
|
func (invoice *Invoice) MinFinalCLTVExpiry() uint64 {
|
|
if invoice.minFinalCLTVExpiry != nil {
|
|
return *invoice.minFinalCLTVExpiry
|
|
}
|
|
|
|
return DefaultFinalCLTVDelta
|
|
}
|
|
|
|
// validateInvoice does a sanity check of the provided Invoice, making sure it
|
|
// has all the necessary fields set for it to be considered valid by BOLT-0011.
|
|
func validateInvoice(invoice *Invoice) error {
|
|
// The net must be set.
|
|
if invoice.Net == nil {
|
|
return fmt.Errorf("net params not set")
|
|
}
|
|
|
|
// Ensure that if there is an amount set, it is not negative.
|
|
if invoice.MilliSat != nil && *invoice.MilliSat < 0 {
|
|
return fmt.Errorf("negative amount: %v", *invoice.MilliSat)
|
|
}
|
|
|
|
// The invoice must contain a payment hash.
|
|
if invoice.PaymentHash == nil {
|
|
return fmt.Errorf("no payment hash found")
|
|
}
|
|
|
|
// 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")
|
|
}
|
|
if invoice.Description == nil && invoice.DescriptionHash == nil {
|
|
return fmt.Errorf("neither description nor description hash set")
|
|
}
|
|
|
|
// Check that we support the field lengths.
|
|
if len(invoice.PaymentHash) != 32 {
|
|
return fmt.Errorf("unsupported payment hash length: %d",
|
|
len(invoice.PaymentHash))
|
|
}
|
|
|
|
if invoice.DescriptionHash != nil && len(invoice.DescriptionHash) != 32 {
|
|
return fmt.Errorf("unsupported description hash length: %d",
|
|
len(invoice.DescriptionHash))
|
|
}
|
|
|
|
if invoice.Destination != nil &&
|
|
len(invoice.Destination.SerializeCompressed()) != 33 {
|
|
return fmt.Errorf("unsupported pubkey length: %d",
|
|
len(invoice.Destination.SerializeCompressed()))
|
|
}
|
|
|
|
// Ensure that all invoices have feature vectors.
|
|
if invoice.Features == nil {
|
|
return fmt.Errorf("missing feature vector")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseData parses the data part of the invoice. It expects base32 data
|
|
// returned from the bech32.Decode method, except signature.
|
|
func parseData(invoice *Invoice, data []byte, net *chaincfg.Params) error {
|
|
// It must contain the timestamp, encoded using 35 bits (7 groups).
|
|
if len(data) < timestampBase32Len {
|
|
return fmt.Errorf("data too short: %d", len(data))
|
|
}
|
|
|
|
t, err := parseTimestamp(data[:timestampBase32Len])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
invoice.Timestamp = time.Unix(int64(t), 0)
|
|
|
|
// The rest are tagged parts.
|
|
tagData := data[7:]
|
|
return parseTaggedFields(invoice, tagData, net)
|
|
}
|
|
|
|
// parseTimestamp converts a 35-bit timestamp (encoded in base32) to uint64.
|
|
func parseTimestamp(data []byte) (uint64, error) {
|
|
if len(data) != timestampBase32Len {
|
|
return 0, fmt.Errorf("timestamp must be 35 bits, was %d",
|
|
len(data)*5)
|
|
}
|
|
|
|
return base32ToUint64(data)
|
|
}
|
|
|
|
// parseTaggedFields takes the base32 encoded tagged fields of the invoice, and
|
|
// fills the Invoice struct accordingly.
|
|
func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) error {
|
|
index := 0
|
|
for len(fields)-index > 0 {
|
|
// If there are less than 3 groups to read, there cannot be more
|
|
// interesting information, as we need the type (1 group) and
|
|
// length (2 groups).
|
|
//
|
|
// This means the last tagged field is broken.
|
|
if len(fields)-index < 3 {
|
|
return ErrBrokenTaggedField
|
|
}
|
|
|
|
typ := fields[index]
|
|
dataLength, err := parseFieldDataLength(fields[index+1 : index+3])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If we don't have enough field data left to read this length,
|
|
// return error.
|
|
if len(fields) < index+3+int(dataLength) {
|
|
return ErrInvalidFieldLength
|
|
}
|
|
base32Data := fields[index+3 : index+3+int(dataLength)]
|
|
|
|
// Advance the index in preparation for the next iteration.
|
|
index += 3 + int(dataLength)
|
|
|
|
switch typ {
|
|
case fieldTypeP:
|
|
if invoice.PaymentHash != nil {
|
|
// We skip the field if we have already seen a
|
|
// supported one.
|
|
continue
|
|
}
|
|
|
|
invoice.PaymentHash, err = parse32Bytes(base32Data)
|
|
case fieldTypeS:
|
|
if invoice.PaymentAddr != nil {
|
|
// We skip the field if we have already seen a
|
|
// supported one.
|
|
continue
|
|
}
|
|
|
|
invoice.PaymentAddr, err = parse32Bytes(base32Data)
|
|
case fieldTypeD:
|
|
if invoice.Description != nil {
|
|
// We skip the field if we have already seen a
|
|
// supported one.
|
|
continue
|
|
}
|
|
|
|
invoice.Description, err = parseDescription(base32Data)
|
|
case fieldTypeN:
|
|
if invoice.Destination != nil {
|
|
// We skip the field if we have already seen a
|
|
// supported one.
|
|
continue
|
|
}
|
|
|
|
invoice.Destination, err = parseDestination(base32Data)
|
|
case fieldTypeH:
|
|
if invoice.DescriptionHash != nil {
|
|
// We skip the field if we have already seen a
|
|
// supported one.
|
|
continue
|
|
}
|
|
|
|
invoice.DescriptionHash, err = parse32Bytes(base32Data)
|
|
case fieldTypeX:
|
|
if invoice.expiry != nil {
|
|
// We skip the field if we have already seen a
|
|
// supported one.
|
|
continue
|
|
}
|
|
|
|
invoice.expiry, err = parseExpiry(base32Data)
|
|
case fieldTypeC:
|
|
if invoice.minFinalCLTVExpiry != nil {
|
|
// We skip the field if we have already seen a
|
|
// supported one.
|
|
continue
|
|
}
|
|
|
|
invoice.minFinalCLTVExpiry, err = parseMinFinalCLTVExpiry(base32Data)
|
|
case fieldTypeF:
|
|
if invoice.FallbackAddr != nil {
|
|
// We skip the field if we have already seen a
|
|
// supported one.
|
|
continue
|
|
}
|
|
|
|
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
|
|
// one.
|
|
routeHint, err := parseRouteHint(base32Data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
invoice.RouteHints = append(invoice.RouteHints, routeHint)
|
|
case fieldType9:
|
|
if invoice.Features != nil {
|
|
// We skip the field if we have already seen a
|
|
// supported one.
|
|
continue
|
|
}
|
|
|
|
invoice.Features, err = parseFeatures(base32Data)
|
|
default:
|
|
// Ignore unknown type.
|
|
}
|
|
|
|
// Check if there was an error from parsing any of the tagged
|
|
// fields and return it.
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseFieldDataLength converts the two byte slice into a uint16.
|
|
func parseFieldDataLength(data []byte) (uint16, error) {
|
|
if len(data) != 2 {
|
|
return 0, fmt.Errorf("data length must be 2 bytes, was %d",
|
|
len(data))
|
|
}
|
|
|
|
return uint16(data[0])<<5 | uint16(data[1]), nil
|
|
}
|
|
|
|
// parse32Bytes converts a 256-bit value (encoded in base32) to *[32]byte. This
|
|
// can be used for payment hashes, description hashes, payment addresses, etc.
|
|
func parse32Bytes(data []byte) (*[32]byte, error) {
|
|
var paymentHash [32]byte
|
|
|
|
// As BOLT-11 states, a reader must skip over the 32-byte fields if
|
|
// it does not have a length of 52, so avoid returning an error.
|
|
if len(data) != hashBase32Len {
|
|
return nil, nil
|
|
}
|
|
|
|
hash, err := bech32.ConvertBits(data, 5, 8, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
copy(paymentHash[:], hash[:])
|
|
|
|
return &paymentHash, nil
|
|
}
|
|
|
|
// parseDescription converts the data (encoded in base32) into a string to use
|
|
// as the description.
|
|
func parseDescription(data []byte) (*string, error) {
|
|
base256Data, err := bech32.ConvertBits(data, 5, 8, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
description := string(base256Data)
|
|
|
|
return &description, nil
|
|
}
|
|
|
|
// parseDestination converts the data (encoded in base32) into a 33-byte public
|
|
// key of the payee node.
|
|
func parseDestination(data []byte) (*btcec.PublicKey, error) {
|
|
// As BOLT-11 states, a reader must skip over the destination field
|
|
// if it does not have a length of 53, so avoid returning an error.
|
|
if len(data) != pubKeyBase32Len {
|
|
return nil, nil
|
|
}
|
|
|
|
base256Data, err := bech32.ConvertBits(data, 5, 8, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return btcec.ParsePubKey(base256Data, btcec.S256())
|
|
}
|
|
|
|
// parseExpiry converts the data (encoded in base32) into the expiry time.
|
|
func parseExpiry(data []byte) (*time.Duration, error) {
|
|
expiry, err := base32ToUint64(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
duration := time.Duration(expiry) * time.Second
|
|
|
|
return &duration, nil
|
|
}
|
|
|
|
// parseMinFinalCLTVExpiry converts the data (encoded in base32) into a uint64
|
|
// to use as the minFinalCLTVExpiry.
|
|
func parseMinFinalCLTVExpiry(data []byte) (*uint64, error) {
|
|
expiry, err := base32ToUint64(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &expiry, nil
|
|
}
|
|
|
|
// parseFallbackAddr converts the data (encoded in base32) into a fallback
|
|
// on-chain address.
|
|
func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, error) {
|
|
// Checks if the data is empty or contains a version without an address.
|
|
if len(data) < 2 {
|
|
return nil, fmt.Errorf("empty fallback address field")
|
|
}
|
|
|
|
var addr btcutil.Address
|
|
|
|
version := data[0]
|
|
switch version {
|
|
case 0:
|
|
witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch len(witness) {
|
|
case 20:
|
|
addr, err = btcutil.NewAddressWitnessPubKeyHash(witness, net)
|
|
case 32:
|
|
addr, err = btcutil.NewAddressWitnessScriptHash(witness, net)
|
|
default:
|
|
return nil, fmt.Errorf("unknown witness program length %d",
|
|
len(witness))
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case 17:
|
|
pubKeyHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
addr, err = btcutil.NewAddressPubKeyHash(pubKeyHash, net)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case 18:
|
|
scriptHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
addr, err = btcutil.NewAddressScriptHashFromHash(scriptHash, net)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
// Ignore unknown version.
|
|
}
|
|
|
|
return addr, nil
|
|
}
|
|
|
|
// parseRouteHint converts the data (encoded in base32) into an array containing
|
|
// one or more routing hop hints that represent a single route hint.
|
|
func parseRouteHint(data []byte) ([]HopHint, error) {
|
|
base256Data, err := bech32.ConvertBits(data, 5, 8, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check that base256Data is a multiple of hopHintLen.
|
|
if len(base256Data)%hopHintLen != 0 {
|
|
return nil, fmt.Errorf("expected length multiple of %d bytes, "+
|
|
"got %d", hopHintLen, len(base256Data))
|
|
}
|
|
|
|
var routeHint []HopHint
|
|
|
|
for len(base256Data) > 0 {
|
|
hopHint := HopHint{}
|
|
hopHint.NodeID, err = btcec.ParsePubKey(base256Data[:33], btcec.S256())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hopHint.ChannelID = binary.BigEndian.Uint64(base256Data[33:41])
|
|
hopHint.FeeBaseMSat = binary.BigEndian.Uint32(base256Data[41:45])
|
|
hopHint.FeeProportionalMillionths = binary.BigEndian.Uint32(base256Data[45:49])
|
|
hopHint.CLTVExpiryDelta = binary.BigEndian.Uint16(base256Data[49:51])
|
|
|
|
routeHint = append(routeHint, hopHint)
|
|
|
|
base256Data = base256Data[51:]
|
|
}
|
|
|
|
return routeHint, nil
|
|
}
|
|
|
|
// parseFeatures decodes any feature bits directly from the base32
|
|
// representation.
|
|
func parseFeatures(data []byte) (*lnwire.FeatureVector, error) {
|
|
rawFeatures := lnwire.NewRawFeatureVector()
|
|
err := rawFeatures.DecodeBase32(bytes.NewReader(data), len(data))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fv := lnwire.NewFeatureVector(rawFeatures, lnwire.Features)
|
|
unknownFeatures := fv.UnknownRequiredFeatures()
|
|
if len(unknownFeatures) > 0 {
|
|
return nil, fmt.Errorf("invoice contains unknown required "+
|
|
"features: %v", unknownFeatures)
|
|
}
|
|
|
|
return fv, nil
|
|
}
|
|
|
|
// writeTaggedFields writes the non-nil tagged fields of the Invoice to the
|
|
// base32 buffer.
|
|
func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
|
|
if invoice.PaymentHash != nil {
|
|
err := writeBytes32(bufferBase32, fieldTypeP, *invoice.PaymentHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if invoice.Description != nil {
|
|
base32, err := bech32.ConvertBits([]byte(*invoice.Description),
|
|
8, 5, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = writeTaggedField(bufferBase32, fieldTypeD, base32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if invoice.DescriptionHash != nil {
|
|
err := writeBytes32(
|
|
bufferBase32, fieldTypeH, *invoice.DescriptionHash,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if invoice.minFinalCLTVExpiry != nil {
|
|
finalDelta := uint64ToBase32(uint64(*invoice.minFinalCLTVExpiry))
|
|
err := writeTaggedField(bufferBase32, fieldTypeC, finalDelta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if invoice.expiry != nil {
|
|
seconds := invoice.expiry.Seconds()
|
|
expiry := uint64ToBase32(uint64(seconds))
|
|
err := writeTaggedField(bufferBase32, fieldTypeX, expiry)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if invoice.FallbackAddr != nil {
|
|
var version byte
|
|
switch addr := invoice.FallbackAddr.(type) {
|
|
case *btcutil.AddressPubKeyHash:
|
|
version = 17
|
|
case *btcutil.AddressScriptHash:
|
|
version = 18
|
|
case *btcutil.AddressWitnessPubKeyHash:
|
|
version = addr.WitnessVersion()
|
|
case *btcutil.AddressWitnessScriptHash:
|
|
version = addr.WitnessVersion()
|
|
default:
|
|
return fmt.Errorf("unknown fallback address type")
|
|
}
|
|
base32Addr, err := bech32.ConvertBits(
|
|
invoice.FallbackAddr.ScriptAddress(), 8, 5, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = writeTaggedField(bufferBase32, fieldTypeF,
|
|
append([]byte{version}, base32Addr...))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, routeHint := range invoice.RouteHints {
|
|
// Each hop hint is encoded using 51 bytes, so we'll make to
|
|
// sure to allocate enough space for the whole route hint.
|
|
routeHintBase256 := make([]byte, 0, hopHintLen*len(routeHint))
|
|
|
|
for _, hopHint := range routeHint {
|
|
hopHintBase256 := make([]byte, hopHintLen)
|
|
copy(hopHintBase256[:33], hopHint.NodeID.SerializeCompressed())
|
|
binary.BigEndian.PutUint64(
|
|
hopHintBase256[33:41], hopHint.ChannelID,
|
|
)
|
|
binary.BigEndian.PutUint32(
|
|
hopHintBase256[41:45], hopHint.FeeBaseMSat,
|
|
)
|
|
binary.BigEndian.PutUint32(
|
|
hopHintBase256[45:49], hopHint.FeeProportionalMillionths,
|
|
)
|
|
binary.BigEndian.PutUint16(
|
|
hopHintBase256[49:51], hopHint.CLTVExpiryDelta,
|
|
)
|
|
routeHintBase256 = append(routeHintBase256, hopHintBase256...)
|
|
}
|
|
|
|
routeHintBase32, err := bech32.ConvertBits(
|
|
routeHintBase256, 8, 5, true,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = writeTaggedField(bufferBase32, fieldTypeR, routeHintBase32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if invoice.Destination != nil {
|
|
// Convert 33 byte pubkey to 53 5-bit groups.
|
|
pubKeyBase32, err := bech32.ConvertBits(
|
|
invoice.Destination.SerializeCompressed(), 8, 5, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(pubKeyBase32) != pubKeyBase32Len {
|
|
return fmt.Errorf("invalid pubkey length: %d",
|
|
len(invoice.Destination.SerializeCompressed()))
|
|
}
|
|
|
|
err = writeTaggedField(bufferBase32, fieldTypeN, pubKeyBase32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if invoice.Features.SerializeSize32() > 0 {
|
|
var b bytes.Buffer
|
|
err := invoice.Features.RawFeatureVector.EncodeBase32(&b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = writeTaggedField(bufferBase32, fieldType9, b.Bytes())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if invoice.PaymentAddr != nil {
|
|
err := writeBytes32(
|
|
bufferBase32, fieldTypeS, *invoice.PaymentAddr,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// writeBytes32 encodes a 32-byte array as base32 and writes it to bufferBase32
|
|
// under the passed fieldType.
|
|
func writeBytes32(bufferBase32 *bytes.Buffer, fieldType byte, b [32]byte) error {
|
|
// Convert 32 byte hash to 52 5-bit groups.
|
|
base32, err := bech32.ConvertBits(b[:], 8, 5, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return writeTaggedField(bufferBase32, fieldType, base32)
|
|
}
|
|
|
|
// writeTaggedField takes the type of a tagged data field, and the data of
|
|
// the tagged field (encoded in base32), and writes the type, length and data
|
|
// to the buffer.
|
|
func writeTaggedField(bufferBase32 *bytes.Buffer, dataType byte, data []byte) error {
|
|
// Length must be exactly 10 bits, so add leading zero groups if
|
|
// needed.
|
|
lenBase32 := uint64ToBase32(uint64(len(data)))
|
|
for len(lenBase32) < 2 {
|
|
lenBase32 = append([]byte{0}, lenBase32...)
|
|
}
|
|
|
|
if len(lenBase32) != 2 {
|
|
return fmt.Errorf("data length too big to fit within 10 bits: %d",
|
|
len(data))
|
|
}
|
|
|
|
err := bufferBase32.WriteByte(dataType)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to write to buffer: %v", err)
|
|
}
|
|
_, err = bufferBase32.Write(lenBase32)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to write to buffer: %v", err)
|
|
}
|
|
_, err = bufferBase32.Write(data)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to write to buffer: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// base32ToUint64 converts a base32 encoded number to uint64.
|
|
func base32ToUint64(data []byte) (uint64, error) {
|
|
// Maximum that fits in uint64 is ceil(64 / 5) = 12 groups.
|
|
if len(data) > 13 {
|
|
return 0, fmt.Errorf("cannot parse data of length %d as uint64",
|
|
len(data))
|
|
}
|
|
|
|
val := uint64(0)
|
|
for i := 0; i < len(data); i++ {
|
|
val = val<<5 | uint64(data[i])
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
// uint64ToBase32 converts a uint64 to a base32 encoded integer encoded using
|
|
// as few 5-bit groups as possible.
|
|
func uint64ToBase32(num uint64) []byte {
|
|
// Return at least one group.
|
|
if num == 0 {
|
|
return []byte{0}
|
|
}
|
|
|
|
// To fit an uint64, we need at most is ceil(64 / 5) = 13 groups.
|
|
arr := make([]byte, 13)
|
|
i := 13
|
|
for num > 0 {
|
|
i--
|
|
arr[i] = byte(num & uint64(31)) // 0b11111 in binary
|
|
num = num >> 5
|
|
}
|
|
|
|
// We only return non-zero leading groups.
|
|
return arr[i:]
|
|
}
|