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 +}