2017-01-03 00:16:57 +01:00
|
|
|
package zpay32
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/binary"
|
|
|
|
"errors"
|
2017-01-29 23:57:57 +01:00
|
|
|
"fmt"
|
2017-01-03 00:16:57 +01:00
|
|
|
"hash/crc32"
|
|
|
|
"io"
|
|
|
|
|
|
|
|
"github.com/roasbeef/btcd/btcec"
|
|
|
|
"github.com/roasbeef/btcutil"
|
|
|
|
"github.com/tv42/zbase32"
|
|
|
|
)
|
|
|
|
|
|
|
|
// invoiceSize is the size of an encoded invoice without the added check-sum.
|
|
|
|
// The size of broken down as follows: 33-bytes (destination pub key), 32-bytes
|
|
|
|
// (payment hash), 8-bytes for the payment amount in satoshis.
|
|
|
|
const invoiceSize = 33 + 32 + 8
|
|
|
|
|
|
|
|
// ErrCheckSumMismatch is returned byt he Decode function fi when
|
|
|
|
// decoding an encoded invoice, the checksum doesn't match indicating
|
|
|
|
// an error somewhere in the bitstream.
|
|
|
|
var ErrCheckSumMismatch = errors.New("the checksum is incorrect")
|
|
|
|
|
2017-02-11 01:22:09 +01:00
|
|
|
// ErrDataTooShort is returned by the Decode function if when
|
|
|
|
// decoding an encoded payment request, the number of bytes decoded
|
|
|
|
// is too few for a valid invoice indicating invalid input.
|
|
|
|
var ErrDataTooShort = errors.New("the decoded data is too short")
|
|
|
|
|
2017-01-03 00:16:57 +01:00
|
|
|
// PaymentRequest is a bare-bones invoice for a payment within the Lightning
|
|
|
|
// Network. With the details of the invoice, the sender has all the data
|
|
|
|
// necessary to send a payment to the recipient.
|
|
|
|
type PaymentRequest struct {
|
|
|
|
// Destination is the public key of the node to be paid.
|
|
|
|
Destination *btcec.PublicKey
|
|
|
|
|
|
|
|
// PaymentHash is the has to use within the HTLC extended throughout
|
|
|
|
// the payment path to the destination.
|
|
|
|
PaymentHash [32]byte
|
|
|
|
|
|
|
|
// Amount is the amount to be sent to the destination expressed in
|
|
|
|
// satoshis.
|
|
|
|
Amount btcutil.Amount
|
|
|
|
}
|
|
|
|
|
|
|
|
// castagnoli is an initialized crc32 checksum generated which Castagnoli's
|
|
|
|
// polynomial.
|
|
|
|
var castagnoli = crc32.MakeTable(crc32.Castagnoli)
|
|
|
|
|
|
|
|
// checkSum calculates a 4-byte crc32 checksum of the passed data. The returned
|
|
|
|
// uint32 is serialized as a big-endian integer.
|
|
|
|
func checkSum(data []byte) []byte {
|
|
|
|
crc := crc32.New(castagnoli)
|
|
|
|
crc.Write(data)
|
|
|
|
return crc.Sum(nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Encode encodes the passed payment request using zbase32 with an added 4-byte
|
|
|
|
// crc32 checksum. The resulting encoding is 77-bytes long and consists of 124
|
|
|
|
// ASCII characters.
|
|
|
|
// TODO(roasbeef): add version byte?
|
|
|
|
func Encode(payReq *PaymentRequest) string {
|
|
|
|
var (
|
|
|
|
invoiceBytes [invoiceSize]byte
|
|
|
|
n int
|
|
|
|
)
|
|
|
|
|
|
|
|
// First copy each of the elements of the payment request into the
|
|
|
|
// buffer. Creating a stream that resembles: dest || r_hash || amt
|
|
|
|
n += copy(invoiceBytes[:], payReq.Destination.SerializeCompressed())
|
|
|
|
n += copy(invoiceBytes[n:], payReq.PaymentHash[:])
|
|
|
|
binary.BigEndian.PutUint64(invoiceBytes[n:], uint64(payReq.Amount))
|
|
|
|
|
|
|
|
// Next, we append the checksum to the end of the buffer which covers
|
|
|
|
// the serialized payment request.
|
|
|
|
b := append(invoiceBytes[:], checkSum(invoiceBytes[:])...)
|
|
|
|
|
|
|
|
// Finally encode the raw bytes as a zbase32 encoded string.
|
|
|
|
return zbase32.EncodeToString(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decode attempts to decode the zbase32 encoded payment request. If the
|
|
|
|
// trailing checksum doesn't match, then an error is returned.
|
|
|
|
func Decode(payData string) (*PaymentRequest, error) {
|
2017-01-29 23:57:57 +01:00
|
|
|
if payData == "" {
|
|
|
|
return nil, fmt.Errorf("encoded payment request must be a " +
|
|
|
|
"non-empty string")
|
|
|
|
}
|
|
|
|
|
2017-01-03 00:16:57 +01:00
|
|
|
// First we decode the zbase32 encoded string into a series of raw
|
|
|
|
// bytes.
|
|
|
|
payReqBytes, err := zbase32.DecodeString(payData)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-02-11 01:22:09 +01:00
|
|
|
// Check if there are at least enough bytes to represent the invoice
|
|
|
|
// and the checksum.
|
|
|
|
if len(payReqBytes) < invoiceSize+crc32.Size {
|
|
|
|
return nil, ErrDataTooShort
|
|
|
|
}
|
|
|
|
|
|
|
|
// With the bytes decoded, we verify the checksum to ensure the
|
2017-01-03 00:16:57 +01:00
|
|
|
// payment request wasn't altered in its decoded form.
|
|
|
|
invoiceBytes := payReqBytes[:invoiceSize]
|
|
|
|
generatedSum := checkSum(invoiceBytes)
|
|
|
|
|
|
|
|
// If the checksums don't match, then we return an error to the
|
|
|
|
// possibly detected error.
|
|
|
|
encodedSum := payReqBytes[invoiceSize:]
|
|
|
|
if !bytes.Equal(encodedSum, generatedSum) {
|
|
|
|
return nil, ErrCheckSumMismatch
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, we've verified the integrity of the encoded payment
|
|
|
|
// request and can safely decode the payReq, passing it back up to the
|
|
|
|
// caller.
|
|
|
|
invoiceReader := bytes.NewReader(invoiceBytes)
|
|
|
|
return decodePaymentRequest(invoiceReader)
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodePaymentRequest(r io.Reader) (*PaymentRequest, error) {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
i := &PaymentRequest{}
|
|
|
|
|
|
|
|
var pubKey [33]byte
|
|
|
|
if _, err := io.ReadFull(r, pubKey[:]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
i.Destination, err = btcec.ParsePubKey(pubKey[:], btcec.S256())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := io.ReadFull(r, i.PaymentHash[:]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var amt [8]byte
|
|
|
|
if _, err := io.ReadFull(r, amt[:]); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
i.Amount = btcutil.Amount(binary.BigEndian.Uint64(amt[:]))
|
|
|
|
|
|
|
|
return i, nil
|
|
|
|
}
|