diff --git a/zpay32/decode.go b/zpay32/decode.go new file mode 100644 index 000000000..93f24af7a --- /dev/null +++ b/zpay32/decode.go @@ -0,0 +1,497 @@ +package zpay32 + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "strings" + "time" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcutil/bech32" + "github.com/lightningnetwork/lnd/lnwire" +) + +// Decode parses the provided encoded invoice and returns a decoded Invoice if +// it is valid by BOLT-0011 and matches the provided active network. +func Decode(invoice string, net *chaincfg.Params) (*Invoice, error) { + decodedInvoice := Invoice{} + + // Before bech32 decoding the invoice, make sure that it is not too large. + // This is done as an anti-DoS measure since bech32 decoding is expensive. + if len(invoice) > maxInvoiceLength { + return nil, ErrInvoiceTooLarge + } + + // Decode the invoice using the modified bech32 decoder. + hrp, data, err := decodeBech32(invoice) + if err != nil { + return nil, err + } + + // We expect the human-readable part to at least have ln + one char + // encoding the network. + if len(hrp) < 3 { + return nil, fmt.Errorf("hrp too short") + } + + // First two characters of HRP should be "ln". + if hrp[:2] != "ln" { + return nil, fmt.Errorf("prefix should be \"ln\"") + } + + // The next characters should be a valid prefix for a segwit BIP173 + // address that match the active network. + if !strings.HasPrefix(hrp[2:], net.Bech32HRPSegwit) { + return nil, fmt.Errorf( + "invoice not for current active network '%s'", net.Name) + } + decodedInvoice.Net = net + + // Optionally, if there's anything left of the HRP after ln + the segwit + // prefix, we try to decode this as the payment amount. + var netPrefixLength = len(net.Bech32HRPSegwit) + 2 + if len(hrp) > netPrefixLength { + amount, err := decodeAmount(hrp[netPrefixLength:]) + if err != nil { + return nil, err + } + decodedInvoice.MilliSat = &amount + } + + // Everything except the last 520 bits of the data encodes the invoice's + // timestamp and tagged fields. + if len(data) < signatureBase32Len { + return nil, errors.New("short invoice") + } + invoiceData := data[:len(data)-signatureBase32Len] + + // Parse the timestamp and tagged fields, and fill the Invoice struct. + if err := parseData(&decodedInvoice, invoiceData, net); err != nil { + return nil, err + } + + // The last 520 bits (104 groups) make up the signature. + sigBase32 := data[len(data)-signatureBase32Len:] + sigBase256, err := bech32.ConvertBits(sigBase32, 5, 8, true) + if err != nil { + return nil, err + } + var sig lnwire.Sig + copy(sig[:], sigBase256[:64]) + recoveryID := sigBase256[64] + + // The signature is over the hrp + the data the invoice, encoded in + // base 256. + taggedDataBytes, err := bech32.ConvertBits(invoiceData, 5, 8, true) + if err != nil { + return nil, err + } + + toSign := append([]byte(hrp), taggedDataBytes...) + + // We expect the signature to be over the single SHA-256 hash of that + // data. + hash := chainhash.HashB(toSign) + + // If the destination pubkey was provided as a tagged field, use that + // to verify the signature, if not do public key recovery. + if decodedInvoice.Destination != nil { + signature, err := sig.ToSignature() + if err != nil { + return nil, fmt.Errorf("unable to deserialize "+ + "signature: %v", err) + } + if !signature.Verify(hash, decodedInvoice.Destination) { + return nil, fmt.Errorf("invalid invoice signature") + } + } else { + headerByte := recoveryID + 27 + 4 + compactSign := append([]byte{headerByte}, sig[:]...) + pubkey, _, err := btcec.RecoverCompact(btcec.S256(), + compactSign, hash) + if err != nil { + return nil, err + } + decodedInvoice.Destination = pubkey + } + + // If no feature vector was decoded, populate an empty one. + if decodedInvoice.Features == nil { + decodedInvoice.Features = lnwire.NewFeatureVector( + nil, lnwire.Features, + ) + } + + // Now that we have created the invoice, make sure it has the required + // fields set. + if err := validateInvoice(&decodedInvoice); err != nil { + return nil, err + } + + return &decodedInvoice, nil +} + +// parseData parses the data part of the invoice. It expects base32 data +// returned from the bech32.Decode method, except signature. +func parseData(invoice *Invoice, data []byte, net *chaincfg.Params) error { + // It must contain the timestamp, encoded using 35 bits (7 groups). + if len(data) < timestampBase32Len { + return fmt.Errorf("data too short: %d", len(data)) + } + + t, err := parseTimestamp(data[:timestampBase32Len]) + if err != nil { + return err + } + invoice.Timestamp = time.Unix(int64(t), 0) + + // The rest are tagged parts. + tagData := data[7:] + return parseTaggedFields(invoice, tagData, net) +} + +// parseTimestamp converts a 35-bit timestamp (encoded in base32) to uint64. +func parseTimestamp(data []byte) (uint64, error) { + if len(data) != timestampBase32Len { + return 0, fmt.Errorf("timestamp must be 35 bits, was %d", + len(data)*5) + } + + return base32ToUint64(data) +} + +// parseTaggedFields takes the base32 encoded tagged fields of the invoice, and +// fills the Invoice struct accordingly. +func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) error { + index := 0 + for len(fields)-index > 0 { + // If there are less than 3 groups to read, there cannot be more + // interesting information, as we need the type (1 group) and + // length (2 groups). + // + // This means the last tagged field is broken. + if len(fields)-index < 3 { + return ErrBrokenTaggedField + } + + typ := fields[index] + dataLength, err := parseFieldDataLength(fields[index+1 : index+3]) + if err != nil { + return err + } + + // If we don't have enough field data left to read this length, + // return error. + if len(fields) < index+3+int(dataLength) { + return ErrInvalidFieldLength + } + base32Data := fields[index+3 : index+3+int(dataLength)] + + // Advance the index in preparation for the next iteration. + index += 3 + int(dataLength) + + switch typ { + case fieldTypeP: + if invoice.PaymentHash != nil { + // We skip the field if we have already seen a + // supported one. + continue + } + + invoice.PaymentHash, err = parse32Bytes(base32Data) + case fieldTypeS: + if invoice.PaymentAddr != nil { + // We skip the field if we have already seen a + // supported one. + continue + } + + invoice.PaymentAddr, err = parse32Bytes(base32Data) + case fieldTypeD: + if invoice.Description != nil { + // We skip the field if we have already seen a + // supported one. + continue + } + + invoice.Description, err = parseDescription(base32Data) + case fieldTypeN: + if invoice.Destination != nil { + // We skip the field if we have already seen a + // supported one. + continue + } + + invoice.Destination, err = parseDestination(base32Data) + case fieldTypeH: + if invoice.DescriptionHash != nil { + // We skip the field if we have already seen a + // supported one. + continue + } + + invoice.DescriptionHash, err = parse32Bytes(base32Data) + case fieldTypeX: + if invoice.expiry != nil { + // We skip the field if we have already seen a + // supported one. + continue + } + + invoice.expiry, err = parseExpiry(base32Data) + case fieldTypeC: + if invoice.minFinalCLTVExpiry != nil { + // We skip the field if we have already seen a + // supported one. + continue + } + + invoice.minFinalCLTVExpiry, err = parseMinFinalCLTVExpiry(base32Data) + case fieldTypeF: + if invoice.FallbackAddr != nil { + // We skip the field if we have already seen a + // supported one. + continue + } + + invoice.FallbackAddr, err = parseFallbackAddr(base32Data, net) + case fieldTypeR: + // An `r` field can be included in an invoice multiple + // times, so we won't skip it if we have already seen + // one. + routeHint, err := parseRouteHint(base32Data) + if err != nil { + return err + } + + invoice.RouteHints = append(invoice.RouteHints, routeHint) + case fieldType9: + if invoice.Features != nil { + // We skip the field if we have already seen a + // supported one. + continue + } + + invoice.Features, err = parseFeatures(base32Data) + default: + // Ignore unknown type. + } + + // Check if there was an error from parsing any of the tagged + // fields and return it. + if err != nil { + return err + } + } + + return nil +} + +// parseFieldDataLength converts the two byte slice into a uint16. +func parseFieldDataLength(data []byte) (uint16, error) { + if len(data) != 2 { + return 0, fmt.Errorf("data length must be 2 bytes, was %d", + len(data)) + } + + return uint16(data[0])<<5 | uint16(data[1]), nil +} + +// parse32Bytes converts a 256-bit value (encoded in base32) to *[32]byte. This +// can be used for payment hashes, description hashes, payment addresses, etc. +func parse32Bytes(data []byte) (*[32]byte, error) { + var paymentHash [32]byte + + // As BOLT-11 states, a reader must skip over the 32-byte fields if + // it does not have a length of 52, so avoid returning an error. + if len(data) != hashBase32Len { + return nil, nil + } + + hash, err := bech32.ConvertBits(data, 5, 8, false) + if err != nil { + return nil, err + } + + copy(paymentHash[:], hash[:]) + + return &paymentHash, nil +} + +// parseDescription converts the data (encoded in base32) into a string to use +// as the description. +func parseDescription(data []byte) (*string, error) { + base256Data, err := bech32.ConvertBits(data, 5, 8, false) + if err != nil { + return nil, err + } + + description := string(base256Data) + + return &description, nil +} + +// parseDestination converts the data (encoded in base32) into a 33-byte public +// key of the payee node. +func parseDestination(data []byte) (*btcec.PublicKey, error) { + // As BOLT-11 states, a reader must skip over the destination field + // if it does not have a length of 53, so avoid returning an error. + if len(data) != pubKeyBase32Len { + return nil, nil + } + + base256Data, err := bech32.ConvertBits(data, 5, 8, false) + if err != nil { + return nil, err + } + + return btcec.ParsePubKey(base256Data, btcec.S256()) +} + +// parseExpiry converts the data (encoded in base32) into the expiry time. +func parseExpiry(data []byte) (*time.Duration, error) { + expiry, err := base32ToUint64(data) + if err != nil { + return nil, err + } + + duration := time.Duration(expiry) * time.Second + + return &duration, nil +} + +// parseMinFinalCLTVExpiry converts the data (encoded in base32) into a uint64 +// to use as the minFinalCLTVExpiry. +func parseMinFinalCLTVExpiry(data []byte) (*uint64, error) { + expiry, err := base32ToUint64(data) + if err != nil { + return nil, err + } + + return &expiry, nil +} + +// parseFallbackAddr converts the data (encoded in base32) into a fallback +// on-chain address. +func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, error) { + // Checks if the data is empty or contains a version without an address. + if len(data) < 2 { + return nil, fmt.Errorf("empty fallback address field") + } + + var addr btcutil.Address + + version := data[0] + switch version { + case 0: + witness, err := bech32.ConvertBits(data[1:], 5, 8, false) + if err != nil { + return nil, err + } + + switch len(witness) { + case 20: + addr, err = btcutil.NewAddressWitnessPubKeyHash(witness, net) + case 32: + addr, err = btcutil.NewAddressWitnessScriptHash(witness, net) + default: + return nil, fmt.Errorf("unknown witness program length %d", + len(witness)) + } + + if err != nil { + return nil, err + } + case 17: + pubKeyHash, err := bech32.ConvertBits(data[1:], 5, 8, false) + if err != nil { + return nil, err + } + + addr, err = btcutil.NewAddressPubKeyHash(pubKeyHash, net) + if err != nil { + return nil, err + } + case 18: + scriptHash, err := bech32.ConvertBits(data[1:], 5, 8, false) + if err != nil { + return nil, err + } + + addr, err = btcutil.NewAddressScriptHashFromHash(scriptHash, net) + if err != nil { + return nil, err + } + default: + // Ignore unknown version. + } + + return addr, nil +} + +// parseRouteHint converts the data (encoded in base32) into an array containing +// one or more routing hop hints that represent a single route hint. +func parseRouteHint(data []byte) ([]HopHint, error) { + base256Data, err := bech32.ConvertBits(data, 5, 8, false) + if err != nil { + return nil, err + } + + // Check that base256Data is a multiple of hopHintLen. + if len(base256Data)%hopHintLen != 0 { + return nil, fmt.Errorf("expected length multiple of %d bytes, "+ + "got %d", hopHintLen, len(base256Data)) + } + + var routeHint []HopHint + + for len(base256Data) > 0 { + hopHint := HopHint{} + hopHint.NodeID, err = btcec.ParsePubKey(base256Data[:33], btcec.S256()) + if err != nil { + return nil, err + } + hopHint.ChannelID = binary.BigEndian.Uint64(base256Data[33:41]) + hopHint.FeeBaseMSat = binary.BigEndian.Uint32(base256Data[41:45]) + hopHint.FeeProportionalMillionths = binary.BigEndian.Uint32(base256Data[45:49]) + hopHint.CLTVExpiryDelta = binary.BigEndian.Uint16(base256Data[49:51]) + + routeHint = append(routeHint, hopHint) + + base256Data = base256Data[51:] + } + + return routeHint, nil +} + +// parseFeatures decodes any feature bits directly from the base32 +// representation. +func parseFeatures(data []byte) (*lnwire.FeatureVector, error) { + rawFeatures := lnwire.NewRawFeatureVector() + err := rawFeatures.DecodeBase32(bytes.NewReader(data), len(data)) + if err != nil { + return nil, err + } + + return lnwire.NewFeatureVector(rawFeatures, lnwire.Features), nil +} + +// base32ToUint64 converts a base32 encoded number to uint64. +func base32ToUint64(data []byte) (uint64, error) { + // Maximum that fits in uint64 is ceil(64 / 5) = 12 groups. + if len(data) > 13 { + return 0, fmt.Errorf("cannot parse data of length %d as uint64", + len(data)) + } + + val := uint64(0) + for i := 0; i < len(data); i++ { + val = val<<5 | uint64(data[i]) + } + return val, nil +} diff --git a/zpay32/encode.go b/zpay32/encode.go new file mode 100644 index 000000000..8a9c3bf6d --- /dev/null +++ b/zpay32/encode.go @@ -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:] +} diff --git a/zpay32/invoice.go b/zpay32/invoice.go index 1c183231f..dbb991e6f 100644 --- a/zpay32/invoice.go +++ b/zpay32/invoice.go @@ -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:] -}