zpay32: split off encoding/decoding into own files
As a preliminary step to isolating zpay32 in migrations 01-11, we'll split out the encoding and decoding logic into separate files. Migration 11 only requires invoice decoding, so this prevents us from needing to copy in the encoding logic that would otherwise be unused.
This commit is contained in:
parent
e64e71d86d
commit
49c601e62a
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
|
||||
}
|
344
zpay32/encode.go
Normal file
344
zpay32/encode.go
Normal file
@ -0,0 +1,344 @@
|
||||
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),
|
||||
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(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
|
||||
}
|
||||
|
||||
// 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:]
|
||||
}
|
@ -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 {
|
||||
@ -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