diff --git a/channeldb/migration_01_to_11/migration_11_invoices.go b/channeldb/migration_01_to_11/migration_11_invoices.go index 242631d86..7cb9ea886 100644 --- a/channeldb/migration_01_to_11/migration_11_invoices.go +++ b/channeldb/migration_01_to_11/migration_11_invoices.go @@ -9,8 +9,8 @@ import ( bitcoinCfg "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb/kvdb" + "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11/zpay32" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/zpay32" litecoinCfg "github.com/ltcsuite/ltcd/chaincfg" ) diff --git a/channeldb/migration_01_to_11/zpay32/amountunits.go b/channeldb/migration_01_to_11/zpay32/amountunits.go new file mode 100644 index 000000000..f53f3ff00 --- /dev/null +++ b/channeldb/migration_01_to_11/zpay32/amountunits.go @@ -0,0 +1,158 @@ +package zpay32 + +import ( + "fmt" + "strconv" + + "github.com/lightningnetwork/lnd/lnwire" +) + +var ( + // toMSat is a map from a unit to a function that converts an amount + // of that unit to millisatoshis. + toMSat = map[byte]func(uint64) (lnwire.MilliSatoshi, error){ + 'm': mBtcToMSat, + 'u': uBtcToMSat, + 'n': nBtcToMSat, + 'p': pBtcToMSat, + } + + // fromMSat is a map from a unit to a function that converts an amount + // in millisatoshis to an amount of that unit. + fromMSat = map[byte]func(lnwire.MilliSatoshi) (uint64, error){ + 'm': mSatToMBtc, + 'u': mSatToUBtc, + 'n': mSatToNBtc, + 'p': mSatToPBtc, + } +) + +// mBtcToMSat converts the given amount in milliBTC to millisatoshis. +func mBtcToMSat(m uint64) (lnwire.MilliSatoshi, error) { + return lnwire.MilliSatoshi(m) * 100000000, nil +} + +// uBtcToMSat converts the given amount in microBTC to millisatoshis. +func uBtcToMSat(u uint64) (lnwire.MilliSatoshi, error) { + return lnwire.MilliSatoshi(u * 100000), nil +} + +// nBtcToMSat converts the given amount in nanoBTC to millisatoshis. +func nBtcToMSat(n uint64) (lnwire.MilliSatoshi, error) { + return lnwire.MilliSatoshi(n * 100), nil +} + +// pBtcToMSat converts the given amount in picoBTC to millisatoshis. +func pBtcToMSat(p uint64) (lnwire.MilliSatoshi, error) { + if p < 10 { + return 0, fmt.Errorf("minimum amount is 10p") + } + if p%10 != 0 { + return 0, fmt.Errorf("amount %d pBTC not expressible in msat", + p) + } + return lnwire.MilliSatoshi(p / 10), nil +} + +// mSatToMBtc converts the given amount in millisatoshis to milliBTC. +func mSatToMBtc(msat lnwire.MilliSatoshi) (uint64, error) { + if msat%100000000 != 0 { + return 0, fmt.Errorf("%d msat not expressible "+ + "in mBTC", msat) + } + return uint64(msat / 100000000), nil +} + +// mSatToUBtc converts the given amount in millisatoshis to microBTC. +func mSatToUBtc(msat lnwire.MilliSatoshi) (uint64, error) { + if msat%100000 != 0 { + return 0, fmt.Errorf("%d msat not expressible "+ + "in uBTC", msat) + } + return uint64(msat / 100000), nil +} + +// mSatToNBtc converts the given amount in millisatoshis to nanoBTC. +func mSatToNBtc(msat lnwire.MilliSatoshi) (uint64, error) { + if msat%100 != 0 { + return 0, fmt.Errorf("%d msat not expressible in nBTC", msat) + } + return uint64(msat / 100), nil +} + +// mSatToPBtc converts the given amount in millisatoshis to picoBTC. +func mSatToPBtc(msat lnwire.MilliSatoshi) (uint64, error) { + return uint64(msat * 10), nil +} + +// decodeAmount returns the amount encoded by the provided string in +// millisatoshi. +func decodeAmount(amount string) (lnwire.MilliSatoshi, error) { + if len(amount) < 1 { + return 0, fmt.Errorf("amount must be non-empty") + } + + // If last character is a digit, then the amount can just be + // interpreted as BTC. + char := amount[len(amount)-1] + digit := char - '0' + if digit >= 0 && digit <= 9 { + btc, err := strconv.ParseUint(amount, 10, 64) + if err != nil { + return 0, err + } + return lnwire.MilliSatoshi(btc) * mSatPerBtc, nil + } + + // If not a digit, it must be part of the known units. + conv, ok := toMSat[char] + if !ok { + return 0, fmt.Errorf("unknown multiplier %c", char) + } + + // Known unit. + num := amount[:len(amount)-1] + if len(num) < 1 { + return 0, fmt.Errorf("number must be non-empty") + } + + am, err := strconv.ParseUint(num, 10, 64) + if err != nil { + return 0, err + } + + return conv(am) +} + +// encodeAmount encodes the provided millisatoshi amount using as few characters +// as possible. +func encodeAmount(msat lnwire.MilliSatoshi) (string, error) { + // If possible to express in BTC, that will always be the shortest + // representation. + if msat%mSatPerBtc == 0 { + return strconv.FormatInt(int64(msat/mSatPerBtc), 10), nil + } + + // Should always be expressible in pico BTC. + pico, err := fromMSat['p'](msat) + if err != nil { + return "", fmt.Errorf("unable to express %d msat as pBTC: %v", + msat, err) + } + shortened := strconv.FormatUint(pico, 10) + "p" + for unit, conv := range fromMSat { + am, err := conv(msat) + if err != nil { + // Not expressible using this unit. + continue + } + + // Save the shortest found representation. + str := strconv.FormatUint(am, 10) + string(unit) + if len(str) < len(shortened) { + shortened = str + } + } + + return shortened, nil +} diff --git a/channeldb/migration_01_to_11/zpay32/bech32.go b/channeldb/migration_01_to_11/zpay32/bech32.go new file mode 100644 index 000000000..cb1714e48 --- /dev/null +++ b/channeldb/migration_01_to_11/zpay32/bech32.go @@ -0,0 +1,168 @@ +package zpay32 + +import ( + "fmt" + "strings" +) + +const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + +var gen = []int{0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3} + +// NOTE: This method it a slight modification of the method bech32.Decode found +// btcutil, allowing strings to be more than 90 characters. + +// decodeBech32 decodes a bech32 encoded string, returning the human-readable +// part and the data part excluding the checksum. +// Note: the data will be base32 encoded, that is each element of the returned +// byte array will encode 5 bits of data. Use the ConvertBits method to convert +// this to 8-bit representation. +func decodeBech32(bech string) (string, []byte, error) { + // The maximum allowed length for a bech32 string is 90. It must also + // be at least 8 characters, since it needs a non-empty HRP, a + // separator, and a 6 character checksum. + // NB: The 90 character check specified in BIP173 is skipped here, to + // allow strings longer than 90 characters. + if len(bech) < 8 { + return "", nil, fmt.Errorf("invalid bech32 string length %d", + len(bech)) + } + // Only ASCII characters between 33 and 126 are allowed. + for i := 0; i < len(bech); i++ { + if bech[i] < 33 || bech[i] > 126 { + return "", nil, fmt.Errorf("invalid character in "+ + "string: '%c'", bech[i]) + } + } + + // The characters must be either all lowercase or all uppercase. + lower := strings.ToLower(bech) + upper := strings.ToUpper(bech) + if bech != lower && bech != upper { + return "", nil, fmt.Errorf("string not all lowercase or all " + + "uppercase") + } + + // We'll work with the lowercase string from now on. + bech = lower + + // The string is invalid if the last '1' is non-existent, it is the + // first character of the string (no human-readable part) or one of the + // last 6 characters of the string (since checksum cannot contain '1'), + // or if the string is more than 90 characters in total. + one := strings.LastIndexByte(bech, '1') + if one < 1 || one+7 > len(bech) { + return "", nil, fmt.Errorf("invalid index of 1") + } + + // The human-readable part is everything before the last '1'. + hrp := bech[:one] + data := bech[one+1:] + + // Each character corresponds to the byte with value of the index in + // 'charset'. + decoded, err := toBytes(data) + if err != nil { + return "", nil, fmt.Errorf("failed converting data to bytes: "+ + "%v", err) + } + + if !bech32VerifyChecksum(hrp, decoded) { + moreInfo := "" + checksum := bech[len(bech)-6:] + expected, err := toChars(bech32Checksum(hrp, + decoded[:len(decoded)-6])) + if err == nil { + moreInfo = fmt.Sprintf("Expected %v, got %v.", + expected, checksum) + } + return "", nil, fmt.Errorf("checksum failed. " + moreInfo) + } + + // We exclude the last 6 bytes, which is the checksum. + return hrp, decoded[:len(decoded)-6], nil +} + +// toBytes converts each character in the string 'chars' to the value of the +// index of the corresponding character in 'charset'. +func toBytes(chars string) ([]byte, error) { + decoded := make([]byte, 0, len(chars)) + for i := 0; i < len(chars); i++ { + index := strings.IndexByte(charset, chars[i]) + if index < 0 { + return nil, fmt.Errorf("invalid character not part of "+ + "charset: %v", chars[i]) + } + decoded = append(decoded, byte(index)) + } + return decoded, nil +} + +// toChars converts the byte slice 'data' to a string where each byte in 'data' +// encodes the index of a character in 'charset'. +func toChars(data []byte) (string, error) { + result := make([]byte, 0, len(data)) + for _, b := range data { + if int(b) >= len(charset) { + return "", fmt.Errorf("invalid data byte: %v", b) + } + result = append(result, charset[b]) + } + return string(result), nil +} + +// For more details on the checksum calculation, please refer to BIP 173. +func bech32Checksum(hrp string, data []byte) []byte { + // Convert the bytes to list of integers, as this is needed for the + // checksum calculation. + integers := make([]int, len(data)) + for i, b := range data { + integers[i] = int(b) + } + values := append(bech32HrpExpand(hrp), integers...) + values = append(values, []int{0, 0, 0, 0, 0, 0}...) + polymod := bech32Polymod(values) ^ 1 + var res []byte + for i := 0; i < 6; i++ { + res = append(res, byte((polymod>>uint(5*(5-i)))&31)) + } + return res +} + +// For more details on the polymod calculation, please refer to BIP 173. +func bech32Polymod(values []int) int { + chk := 1 + for _, v := range values { + b := chk >> 25 + chk = (chk&0x1ffffff)<<5 ^ v + for i := 0; i < 5; i++ { + if (b>>uint(i))&1 == 1 { + chk ^= gen[i] + } + } + } + return chk +} + +// For more details on HRP expansion, please refer to BIP 173. +func bech32HrpExpand(hrp string) []int { + v := make([]int, 0, len(hrp)*2+1) + for i := 0; i < len(hrp); i++ { + v = append(v, int(hrp[i]>>5)) + } + v = append(v, 0) + for i := 0; i < len(hrp); i++ { + v = append(v, int(hrp[i]&31)) + } + return v +} + +// For more details on the checksum verification, please refer to BIP 173. +func bech32VerifyChecksum(hrp string, data []byte) bool { + integers := make([]int, len(data)) + for i, b := range data { + integers[i] = int(b) + } + concat := append(bech32HrpExpand(hrp), integers...) + return bech32Polymod(concat) == 1 +} diff --git a/channeldb/migration_01_to_11/zpay32/decode.go b/channeldb/migration_01_to_11/zpay32/decode.go new file mode 100644 index 000000000..07929b45b --- /dev/null +++ b/channeldb/migration_01_to_11/zpay32/decode.go @@ -0,0 +1,497 @@ +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" +) + +// 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. + if len(data) < signatureBase32Len { + return nil, errors.New("short invoice") + } + 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 +} + +// 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 + } + + return lnwire.NewFeatureVector(rawFeatures, lnwire.Features), 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 +} diff --git a/channeldb/migration_01_to_11/zpay32/hophint.go b/channeldb/migration_01_to_11/zpay32/hophint.go new file mode 100644 index 000000000..250286281 --- /dev/null +++ b/channeldb/migration_01_to_11/zpay32/hophint.go @@ -0,0 +1,43 @@ +package zpay32 + +import "github.com/btcsuite/btcd/btcec" + +const ( + // DefaultFinalCLTVDelta is the default value to be used as the final + // CLTV delta for a route if one is unspecified. + DefaultFinalCLTVDelta = 9 +) + +// HopHint is a routing hint that contains the minimum information of a channel +// required for an intermediate hop in a route to forward the payment to the +// next. This should be ideally used for private channels, since they are not +// publicly advertised to the network for routing. +type HopHint struct { + // NodeID is the public key of the node at the start of the channel. + NodeID *btcec.PublicKey + + // ChannelID is the unique identifier of the channel. + ChannelID uint64 + + // FeeBaseMSat is the base fee of the channel in millisatoshis. + FeeBaseMSat uint32 + + // FeeProportionalMillionths is the fee rate, in millionths of a + // satoshi, for every satoshi sent through the channel. + FeeProportionalMillionths uint32 + + // CLTVExpiryDelta is the time-lock delta of the channel. + CLTVExpiryDelta uint16 +} + +// Copy returns a deep copy of the hop hint. +func (h HopHint) Copy() HopHint { + nodeID := *h.NodeID + return HopHint{ + NodeID: &nodeID, + ChannelID: h.ChannelID, + FeeBaseMSat: h.FeeBaseMSat, + FeeProportionalMillionths: h.FeeProportionalMillionths, + CLTVExpiryDelta: h.CLTVExpiryDelta, + } +} diff --git a/channeldb/migration_01_to_11/zpay32/invoice.go b/channeldb/migration_01_to_11/zpay32/invoice.go new file mode 100644 index 000000000..dbb991e6f --- /dev/null +++ b/channeldb/migration_01_to_11/zpay32/invoice.go @@ -0,0 +1,374 @@ +package zpay32 + +import ( + "errors" + "fmt" + "time" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcutil" + "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 CLTV expiry of the HTLC extended to it in + // the last hop. + // + // 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 +} + +// 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 DefaultInvoiceExpiry +} + +// 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") + } + + // 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 +} diff --git a/config.go b/config.go index 368661bad..3a52ca964 100644 --- a/config.go +++ b/config.go @@ -73,7 +73,7 @@ const ( // minTimeLockDelta is the minimum timelock we require for incoming // HTLCs on our channels. - minTimeLockDelta = 4 + minTimeLockDelta = routing.MinCLTVDelta // defaultAcceptorTimeout is the time after which an RPCAcceptor will time // out and return false if it hasn't yet received a response. diff --git a/lnrpc/invoicesrpc/addinvoice.go b/lnrpc/invoicesrpc/addinvoice.go index c1cde48a3..573d2f381 100644 --- a/lnrpc/invoicesrpc/addinvoice.go +++ b/lnrpc/invoicesrpc/addinvoice.go @@ -17,6 +17,7 @@ import ( "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/netann" + "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/zpay32" ) @@ -228,6 +229,14 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig, return nil, nil, fmt.Errorf("CLTV delta of %v is too large, max "+ "accepted is: %v", invoice.CltvExpiry, math.MaxUint16) case invoice.CltvExpiry != 0: + // Disallow user-chosen final CLTV deltas below the required + // minimum. + if invoice.CltvExpiry < routing.MinCLTVDelta { + return nil, nil, fmt.Errorf("CLTV delta of %v must be "+ + "greater than minimum of %v", + routing.MinCLTVDelta, invoice.CltvExpiry) + } + options = append(options, zpay32.CLTVExpiry(invoice.CltvExpiry)) default: diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index ba1f39ebd..92696ca38 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -26,7 +26,6 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" - "github.com/lightningnetwork/lnd/zpay32" ) const ( @@ -2076,7 +2075,7 @@ func TestPathFindSpecExample(t *testing.T) { const amt lnwire.MilliSatoshi = 4999999 route, err := ctx.router.FindRoute( bobNode.PubKeyBytes, carol, amt, noRestrictions, nil, nil, - zpay32.DefaultFinalCLTVDelta, + MinCLTVDelta, ) if err != nil { t.Fatalf("unable to find route: %v", err) @@ -2100,14 +2099,14 @@ func TestPathFindSpecExample(t *testing.T) { t.Fatalf("wrong hop fee: got %v, expected %v", fee, 0) } - // The CLTV expiry should be the current height plus 9 (the expiry for + // The CLTV expiry should be the current height plus 18 (the expiry for // the B -> C channel. if route.TotalTimeLock != - startingHeight+zpay32.DefaultFinalCLTVDelta { + startingHeight+MinCLTVDelta { t.Fatalf("wrong total time lock: got %v, expecting %v", route.TotalTimeLock, - startingHeight+zpay32.DefaultFinalCLTVDelta) + startingHeight+MinCLTVDelta) } // Next, we'll set A as the source node so we can assert that we create @@ -2132,7 +2131,7 @@ func TestPathFindSpecExample(t *testing.T) { // We'll now request a route from A -> B -> C. route, err = ctx.router.FindRoute( source.PubKeyBytes, carol, amt, noRestrictions, nil, nil, - zpay32.DefaultFinalCLTVDelta, + MinCLTVDelta, ) if err != nil { t.Fatalf("unable to find routes: %v", err) @@ -2145,15 +2144,16 @@ func TestPathFindSpecExample(t *testing.T) { } // The total amount should factor in a fee of 10199 and also use a CLTV - // delta total of 29 (20 + 9), + // delta total of 38 (20 + 18), expectedAmt := lnwire.MilliSatoshi(5010198) if route.TotalAmount != expectedAmt { t.Fatalf("wrong amount: got %v, expected %v", route.TotalAmount, expectedAmt) } - if route.TotalTimeLock != startingHeight+29 { + expectedDelta := uint32(20 + MinCLTVDelta) + if route.TotalTimeLock != startingHeight+expectedDelta { t.Fatalf("wrong total time lock: got %v, expecting %v", - route.TotalTimeLock, startingHeight+29) + route.TotalTimeLock, startingHeight+expectedDelta) } // Ensure that the hops of the route are properly crafted. @@ -2188,11 +2188,11 @@ func TestPathFindSpecExample(t *testing.T) { // The outgoing CLTV value itself should be the current height plus 30 // to meet Carol's requirements. if route.Hops[0].OutgoingTimeLock != - startingHeight+zpay32.DefaultFinalCLTVDelta { + startingHeight+MinCLTVDelta { t.Fatalf("wrong total time lock: got %v, expecting %v", route.Hops[0].OutgoingTimeLock, - startingHeight+zpay32.DefaultFinalCLTVDelta) + startingHeight+MinCLTVDelta) } // For B -> C, we assert that the final hop also has the proper @@ -2203,11 +2203,11 @@ func TestPathFindSpecExample(t *testing.T) { lastHop.AmtToForward, amt) } if lastHop.OutgoingTimeLock != - startingHeight+zpay32.DefaultFinalCLTVDelta { + startingHeight+MinCLTVDelta { t.Fatalf("wrong total time lock: got %v, expecting %v", lastHop.OutgoingTimeLock, - startingHeight+zpay32.DefaultFinalCLTVDelta) + startingHeight+MinCLTVDelta) } } diff --git a/routing/router.go b/routing/router.go index 9fa7bea5e..84fb9d0b1 100644 --- a/routing/router.go +++ b/routing/router.go @@ -46,6 +46,27 @@ const ( // stats related to processing new channels, updates, or node // announcements. defaultStatInterval = time.Minute + + // MinCLTVDelta is the minimum CLTV value accepted by LND for all + // timelock deltas. This includes both forwarding CLTV deltas set on + // channel updates, as well as final CLTV deltas used to create BOLT 11 + // payment requests. + // + // NOTE: For payment requests, BOLT 11 stipulates that a final CLTV + // delta of 9 should be used when no value is decoded. This however + // leads to inflexiblity in upgrading this default parameter, since it + // can create inconsistencies around the assumed value between sender + // and receiver. Specifically, if the receiver assumes a higher value + // than the sender, the receiver will always see the received HTLCs as + // invalid due to their timelock not meeting the required delta. + // + // We skirt this by always setting an explicit CLTV delta when creating + // invoices. This allows LND nodes to freely update the minimum without + // creating incompatibilities during the upgrade process. For some time + // LND has used an explicit default final CLTV delta of 40 blocks for + // bitcoin (160 for litecoin), though we now clamp the lower end of this + // range for user-chosen deltas to 18 blocks to be conservative. + MinCLTVDelta = 18 ) var ( diff --git a/routing/router_test.go b/routing/router_test.go index 3f31225b0..9844b5268 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -23,7 +23,6 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" - "github.com/lightningnetwork/lnd/zpay32" ) var uniquePaymentID uint64 = 1 // to be used atomically @@ -223,7 +222,7 @@ func TestFindRoutesWithFeeLimit(t *testing.T) { route, err := ctx.router.FindRoute( ctx.router.selfNode.PubKeyBytes, target, paymentAmt, restrictions, nil, nil, - zpay32.DefaultFinalCLTVDelta, + MinCLTVDelta, ) if err != nil { t.Fatalf("unable to find any routes: %v", err) @@ -1284,7 +1283,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { _, err = ctx.router.FindRoute( ctx.router.selfNode.PubKeyBytes, targetPubKeyBytes, paymentAmt, noRestrictions, nil, nil, - zpay32.DefaultFinalCLTVDelta, + MinCLTVDelta, ) if err != nil { t.Fatalf("unable to find any routes: %v", err) @@ -1327,7 +1326,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { _, err = ctx.router.FindRoute( ctx.router.selfNode.PubKeyBytes, targetPubKeyBytes, paymentAmt, noRestrictions, nil, nil, - zpay32.DefaultFinalCLTVDelta, + MinCLTVDelta, ) if err != nil { t.Fatalf("unable to find any routes: %v", err) diff --git a/zpay32/decode.go b/zpay32/decode.go new file mode 100644 index 000000000..07929b45b --- /dev/null +++ b/zpay32/decode.go @@ -0,0 +1,497 @@ +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" +) + +// 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. + if len(data) < signatureBase32Len { + return nil, errors.New("short invoice") + } + 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 +} + +// 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 + } + + return lnwire.NewFeatureVector(rawFeatures, lnwire.Features), 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 +} diff --git a/zpay32/encode.go b/zpay32/encode.go new file mode 100644 index 000000000..9084e2305 --- /dev/null +++ b/zpay32/encode.go @@ -0,0 +1,343 @@ +package zpay32 + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcutil/bech32" + "github.com/lightningnetwork/lnd/lnwire" +) + +// 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)) + _, 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 +} + +// 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(*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.PaymentAddr != nil { + err := writeBytes32( + bufferBase32, fieldTypeS, *invoice.PaymentAddr, + ) + 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 + } + } + + 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 +} + +// 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 >>= 5 + } + + // We only return non-zero leading groups. + return arr[i:] +} diff --git a/zpay32/hophint.go b/zpay32/hophint.go index 250286281..067c8437d 100644 --- a/zpay32/hophint.go +++ b/zpay32/hophint.go @@ -3,9 +3,10 @@ package zpay32 import "github.com/btcsuite/btcd/btcec" const ( - // DefaultFinalCLTVDelta is the default value to be used as the final - // CLTV delta for a route if one is unspecified. - DefaultFinalCLTVDelta = 9 + // DefaultAssumedFinalCLTVDelta is the default value to be used as the + // final CLTV delta for a route if one is unspecified in the payment + // request. + DefaultAssumedFinalCLTVDelta = 9 ) // HopHint is a routing hint that contains the minimum information of a channel diff --git a/zpay32/invoice.go b/zpay32/invoice.go index 1c183231f..8444e3eea 100644 --- a/zpay32/invoice.go +++ b/zpay32/invoice.go @@ -1,18 +1,13 @@ 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" ) @@ -310,237 +305,6 @@ func NewInvoice(net *chaincfg.Params, paymentHash [32]byte, 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 { @@ -560,7 +324,7 @@ func (invoice *Invoice) MinFinalCLTVExpiry() uint64 { return *invoice.minFinalCLTVExpiry } - return DefaultFinalCLTVDelta + return DefaultAssumedFinalCLTVDelta } // validateInvoice does a sanity check of the provided Invoice, making sure it @@ -608,581 +372,3 @@ func validateInvoice(invoice *Invoice) error { 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 - } - - return lnwire.NewFeatureVector(rawFeatures, lnwire.Features), 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.PaymentAddr != nil { - err := writeBytes32( - bufferBase32, fieldTypeS, *invoice.PaymentAddr, - ) - 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 - } - } - - 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:] -}