You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
352 lines
9.6 KiB
352 lines
9.6 KiB
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:] |
|
}
|
|
|