Merge pull request #4488 from cfromknecht/enforce-min-cltv-18
multi: enforce conservative minimum for user-chosen CLTV deltas
This commit is contained in:
commit
da16a196a3
@ -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"
|
||||
)
|
||||
|
||||
|
158
channeldb/migration_01_to_11/zpay32/amountunits.go
Normal file
158
channeldb/migration_01_to_11/zpay32/amountunits.go
Normal file
@ -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
|
||||
}
|
168
channeldb/migration_01_to_11/zpay32/bech32.go
Normal file
168
channeldb/migration_01_to_11/zpay32/bech32.go
Normal file
@ -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
|
||||
}
|
497
channeldb/migration_01_to_11/zpay32/decode.go
Normal file
497
channeldb/migration_01_to_11/zpay32/decode.go
Normal file
@ -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
|
||||
}
|
43
channeldb/migration_01_to_11/zpay32/hophint.go
Normal file
43
channeldb/migration_01_to_11/zpay32/hophint.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
374
channeldb/migration_01_to_11/zpay32/invoice.go
Normal file
374
channeldb/migration_01_to_11/zpay32/invoice.go
Normal file
@ -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
|
||||
}
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -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)
|
||||
|
497
zpay32/decode.go
Normal file
497
zpay32/decode.go
Normal file
@ -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
|
||||
}
|
343
zpay32/encode.go
Normal file
343
zpay32/encode.go
Normal file
@ -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:]
|
||||
}
|
@ -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
|
||||
|
@ -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:]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user