044e1e692f
The Core devs decided to us the same bech32 HRP for Signet as is used for the current Testnet3. This might be okay for on-chain addresses since they are compatible in theory. But for invoices we want to use a distinct HRP to distinguish testnet from signet. Also see spec PR https://github.com/lightningnetwork/lightning-rfc/pull/844 for more information about the reasoning.
353 lines
9.6 KiB
Go
353 lines
9.6 KiB
Go
package zpay32
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"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"
|
|
)
|
|
|
|
// 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,
|
|
// except for signet where we add an additional "s" to differentiate it
|
|
// from the older testnet3 (Core devs decided to use the same hrp for
|
|
// signet as for testnet3 which is not optimal for LN). See
|
|
// https://github.com/lightningnetwork/lightning-rfc/pull/844 for more
|
|
// information.
|
|
hrp := "ln" + invoice.Net.Bech32HRPSegwit
|
|
if invoice.Net.Name == chaincfg.SigNetParams.Name {
|
|
hrp = "lntbs"
|
|
}
|
|
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...)
|
|
|
|
// 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(toSign)
|
|
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)
|
|
}
|
|
|
|
hash := chainhash.HashB(toSign)
|
|
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:]
|
|
}
|