Merge pull request #317 from halseth/invoice-usage-after-cp

Use the BOLT-11 invoice format within lnd
This commit is contained in:
Olaoluwa Osuntokun 2017-09-27 19:32:03 -07:00 committed by GitHub
commit d8967e5b15
16 changed files with 1020 additions and 849 deletions

@ -29,6 +29,19 @@ func randInvoice(value lnwire.MilliSatoshi) (*Invoice, error) {
i.Memo = []byte("memo")
i.Receipt = []byte("recipt")
// Create a random byte slice of MaxPaymentRequestSize bytes to be used
// as a dummy paymentrequest, and determine if it should be set based
// on one of the random bytes.
var r [MaxPaymentRequestSize]byte
if _, err := rand.Read(r[:]); err != nil {
return nil, err
}
if r[0]&1 == 0 {
i.PaymentRequest = r[:]
} else {
i.PaymentRequest = []byte("")
}
return i, nil
}
@ -50,6 +63,7 @@ func TestInvoiceWorkflow(t *testing.T) {
}
fakeInvoice.Memo = []byte("memo")
fakeInvoice.Receipt = []byte("recipt")
fakeInvoice.PaymentRequest = []byte("")
copy(fakeInvoice.Terms.PaymentPreimage[:], rev[:])
fakeInvoice.Terms.Value = lnwire.NewMSatFromSatoshis(10000)

@ -43,6 +43,12 @@ const (
// MaxReceiptSize is the maximum size of the payment receipt stored
// within the database along side incoming/outgoing invoices.
MaxReceiptSize = 1024
// MaxPaymentRequestSize is the max size of a a payment request for
// this invoice.
// TODO(halseth): determine the max length payment request when field
// lengths are final.
MaxPaymentRequestSize = 4096
)
// ContractTerm is a companion struct to the Invoice struct. This struct houses
@ -86,6 +92,10 @@ type Invoice struct {
// TODO(roasbeef): document scheme.
Receipt []byte
// PaymentRequest is an optional field where a payment request created
// for this invoice can be stored.
PaymentRequest []byte
// CreationDate is the exact time the invoice was created.
CreationDate time.Time
@ -108,6 +118,11 @@ func validateInvoice(i *Invoice) error {
"of length %v was provided", MaxReceiptSize,
len(i.Receipt))
}
if len(i.PaymentRequest) > MaxPaymentRequestSize {
return fmt.Errorf("max length of payment request is %v, length "+
"provided was %v", MaxPaymentRequestSize,
len(i.PaymentRequest))
}
return nil
}
@ -306,6 +321,9 @@ func serializeInvoice(w io.Writer, i *Invoice) error {
if err := wire.WriteVarBytes(w, 0, i.Receipt[:]); err != nil {
return err
}
if err := wire.WriteVarBytes(w, 0, i.PaymentRequest[:]); err != nil {
return err
}
birthBytes, err := i.CreationDate.MarshalBinary()
if err != nil {
@ -361,6 +379,11 @@ func deserializeInvoice(r io.Reader) (*Invoice, error) {
return nil, err
}
invoice.PaymentRequest, err = wire.ReadVarBytes(r, 0, MaxPaymentRequestSize, "")
if err != nil {
return nil, err
}
birthBytes, err := wire.ReadVarBytes(r, 0, 300, "birth")
if err != nil {
return nil, err

@ -17,9 +17,10 @@ func makeFakePayment() *OutgoingPayment {
fakeInvoice := &Invoice{
// Use single second precision to avoid false positive test
// failures due to the monotonic time component.
CreationDate: time.Unix(time.Now().Unix(), 0),
Memo: []byte("fake memo"),
Receipt: []byte("fake receipt"),
CreationDate: time.Unix(time.Now().Unix(), 0),
Memo: []byte("fake memo"),
Receipt: []byte("fake receipt"),
PaymentRequest: []byte(""),
}
copy(fakeInvoice.Terms.PaymentPreimage[:], rev[:])
@ -69,6 +70,8 @@ func makeRandomFakePayment() (*OutgoingPayment, error) {
return nil, err
}
fakeInvoice.PaymentRequest = []byte("")
preImg, err := randomBytes(32, 33)
if err != nil {
return nil, err

@ -764,7 +764,7 @@ var sendPaymentCommand = cli.Command{
},
cli.StringFlag{
Name: "pay_req",
Usage: "a zbase32-check encoded payment request to fulfill",
Usage: "a bech32 encoded payment request to fulfill",
},
},
Action: sendPayment,
@ -885,25 +885,49 @@ var addInvoiceCommand = cli.Command{
Name: "addinvoice",
Usage: "add a new invoice.",
Description: "Add a new invoice, expressing intent for a future payment. " +
"The value of the invoice in satoshis and a 32 byte hash preimage are neccesary for the creation",
"The value of the invoice in satoshis is neccesary for the " +
"creation, the remaining parameters are optional.",
ArgsUsage: "value preimage",
Flags: []cli.Flag{
cli.StringFlag{
Name: "memo",
Usage: "an optional memo to attach along with the invoice",
Name: "memo",
Usage: "a description of the payment to attach along " +
"with the invoice (default=\"\")",
},
cli.StringFlag{
Name: "receipt",
Usage: "an optional cryptographic receipt of payment",
},
cli.StringFlag{
Name: "preimage",
Usage: "the hex-encoded preimage (32 byte) which will allow settling an incoming HTLC payable to this preimage",
Name: "preimage",
Usage: "the hex-encoded preimage (32 byte) which will " +
"allow settling an incoming HTLC payable to this " +
"preimage. If not set, a random preimage will be " +
"created.",
},
cli.Int64Flag{
Name: "value",
Usage: "the value of this invoice in satoshis",
},
cli.StringFlag{
Name: "description_hash",
Usage: "SHA-256 hash of the description of the payment. " +
"Used if the purpose of payment cannot naturally " +
"fit within the memo. If provided this will be " +
"used instead of the description(memo) field in " +
"the encoded invoice.",
},
cli.StringFlag{
Name: "fallback_addr",
Usage: "fallback on-chain address that can be used in " +
"case the lightning payment fails",
},
cli.Int64Flag{
Name: "expiry",
Usage: "the invoice's expiry time in seconds. If not " +
"specified an expiry of 3600 seconds (1 hour) " +
"is implied.",
},
},
Action: addInvoice,
}
@ -911,6 +935,7 @@ var addInvoiceCommand = cli.Command{
func addInvoice(ctx *cli.Context) error {
var (
preimage []byte
descHash []byte
receipt []byte
value int64
err error
@ -945,16 +970,24 @@ func addInvoice(ctx *cli.Context) error {
return fmt.Errorf("unable to parse preimage: %v", err)
}
descHash, err = hex.DecodeString(ctx.String("description_hash"))
if err != nil {
return fmt.Errorf("unable to parse description_hash: %v", err)
}
receipt, err = hex.DecodeString(ctx.String("receipt"))
if err != nil {
return fmt.Errorf("unable to parse receipt: %v", err)
}
invoice := &lnrpc.Invoice{
Memo: ctx.String("memo"),
Receipt: receipt,
RPreimage: preimage,
Value: value,
Memo: ctx.String("memo"),
Receipt: receipt,
RPreimage: preimage,
Value: value,
DescriptionHash: descHash,
FallbackAddr: ctx.String("fallback_addr"),
Expiry: ctx.Int64("expiry"),
}
resp, err := client.AddInvoice(context.Background(), invoice)
@ -1490,7 +1523,7 @@ var decodePayReqComamnd = cli.Command{
Flags: []cli.Flag{
cli.StringFlag{
Name: "pay_req",
Usage: "the zpay32 encoded payment request",
Usage: "the bech32 encoded payment request",
},
},
Action: decodePayReq,

@ -1,5 +1,6 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// Code generated by protoc-gen-go.
// source: rpc.proto
// DO NOT EDIT!
/*
Package lnrpc is a generated protocol buffer package.
@ -2698,7 +2699,11 @@ func (*SetAliasResponse) ProtoMessage() {}
func (*SetAliasResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{68} }
type Invoice struct {
// / An optional memo to attach along with the invoice
// *
// An optional memo to attach along with the invoice. Used for record keeping
// purposes for the invoice's creator, and will also be set in the description
// field of the encoded payment request if the description_hash field is not
// being used.
Memo string `protobuf:"bytes,1,opt,name=memo" json:"memo,omitempty"`
// / An optional cryptographic receipt of payment
Receipt []byte `protobuf:"bytes,2,opt,name=receipt,proto3" json:"receipt,omitempty"`
@ -2721,6 +2726,15 @@ type Invoice struct {
// details of the invoice, the sender has all the data necessary to send a
// payment to the recipient.
PaymentRequest string `protobuf:"bytes,9,opt,name=payment_request" json:"payment_request,omitempty"`
// *
// Hash (SHA-256) of a description of the payment. Used if the description of
// payment (memo) is too long to naturally fit within the description field
// of an encoded payment request.
DescriptionHash []byte `protobuf:"bytes,10,opt,name=description_hash,proto3" json:"description_hash,omitempty"`
// / Payment request expiry time in seconds. Default is 3600 (1 hour).
Expiry int64 `protobuf:"varint,11,opt,name=expiry" json:"expiry,omitempty"`
// / Fallback on-chain address.
FallbackAddr string `protobuf:"bytes,12,opt,name=fallback_addr" json:"fallback_addr,omitempty"`
}
func (m *Invoice) Reset() { *m = Invoice{} }
@ -2791,6 +2805,27 @@ func (m *Invoice) GetPaymentRequest() string {
return ""
}
func (m *Invoice) GetDescriptionHash() []byte {
if m != nil {
return m.DescriptionHash
}
return nil
}
func (m *Invoice) GetExpiry() int64 {
if m != nil {
return m.Expiry
}
return 0
}
func (m *Invoice) GetFallbackAddr() string {
if m != nil {
return m.FallbackAddr
}
return ""
}
type AddInvoiceResponse struct {
RHash []byte `protobuf:"bytes,1,opt,name=r_hash,proto3" json:"r_hash,omitempty"`
// *
@ -3040,9 +3075,14 @@ func (m *PayReqString) GetPayReq() string {
}
type PayReq struct {
Destination string `protobuf:"bytes,1,opt,name=destination" json:"destination,omitempty"`
PaymentHash string `protobuf:"bytes,2,opt,name=payment_hash" json:"payment_hash,omitempty"`
NumSatoshis int64 `protobuf:"varint,3,opt,name=num_satoshis" json:"num_satoshis,omitempty"`
Destination string `protobuf:"bytes,1,opt,name=destination" json:"destination,omitempty"`
PaymentHash string `protobuf:"bytes,2,opt,name=payment_hash" json:"payment_hash,omitempty"`
NumSatoshis int64 `protobuf:"varint,3,opt,name=num_satoshis" json:"num_satoshis,omitempty"`
Timestamp int64 `protobuf:"varint,4,opt,name=timestamp" json:"timestamp,omitempty"`
Expiry int64 `protobuf:"varint,5,opt,name=expiry" json:"expiry,omitempty"`
Description string `protobuf:"bytes,6,opt,name=description" json:"description,omitempty"`
DescriptionHash string `protobuf:"bytes,7,opt,name=description_hash" json:"description_hash,omitempty"`
FallbackAddr string `protobuf:"bytes,8,opt,name=fallback_addr" json:"fallback_addr,omitempty"`
}
func (m *PayReq) Reset() { *m = PayReq{} }
@ -3071,6 +3111,41 @@ func (m *PayReq) GetNumSatoshis() int64 {
return 0
}
func (m *PayReq) GetTimestamp() int64 {
if m != nil {
return m.Timestamp
}
return 0
}
func (m *PayReq) GetExpiry() int64 {
if m != nil {
return m.Expiry
}
return 0
}
func (m *PayReq) GetDescription() string {
if m != nil {
return m.Description
}
return ""
}
func (m *PayReq) GetDescriptionHash() string {
if m != nil {
return m.DescriptionHash
}
return ""
}
func (m *PayReq) GetFallbackAddr() string {
if m != nil {
return m.FallbackAddr
}
return ""
}
type FeeReportRequest struct {
}
@ -5174,287 +5249,292 @@ var _Lightning_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("rpc.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 4501 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x7b, 0x5d, 0x6f, 0x1c, 0x4b,
0x5a, 0x7f, 0x7a, 0x3c, 0x63, 0xcf, 0x3c, 0x33, 0xe3, 0x97, 0xf2, 0xdb, 0x64, 0x92, 0x93, 0x4d,
0x6a, 0xa3, 0x13, 0xff, 0xbd, 0x2b, 0x3b, 0xf1, 0xfe, 0xf7, 0x90, 0x4d, 0x80, 0x23, 0xe7, 0xd5,
0x87, 0xf5, 0xc9, 0xf1, 0xb6, 0x73, 0x4e, 0x60, 0x57, 0xa8, 0x69, 0x4f, 0x97, 0xc7, 0xbd, 0xe9,
0xe9, 0xee, 0xd3, 0x5d, 0x13, 0x67, 0x36, 0x8a, 0x84, 0x0e, 0x08, 0x6e, 0x40, 0x2b, 0xb4, 0x08,
0xc4, 0x0d, 0x5a, 0x09, 0x71, 0x09, 0x5f, 0x80, 0x6f, 0x80, 0x40, 0x42, 0xda, 0x2b, 0x6e, 0xb8,
0xe2, 0x0b, 0x70, 0xc1, 0x3d, 0xaa, 0xd7, 0xae, 0xea, 0x6e, 0x27, 0x41, 0x20, 0xae, 0x3c, 0xf5,
0xab, 0xa7, 0x9f, 0xaa, 0x7a, 0xea, 0xa9, 0xe7, 0xad, 0xca, 0xd0, 0xc9, 0xd2, 0xd1, 0x4e, 0x9a,
0x25, 0x34, 0x41, 0xad, 0x28, 0xce, 0xd2, 0xd1, 0xf0, 0xea, 0x38, 0x49, 0xc6, 0x11, 0xd9, 0xf5,
0xd3, 0x70, 0xd7, 0x8f, 0xe3, 0x84, 0xfa, 0x34, 0x4c, 0xe2, 0x5c, 0x10, 0xe1, 0xff, 0x70, 0xa0,
0xfb, 0x3c, 0xf3, 0xe3, 0xdc, 0x1f, 0x31, 0x18, 0x0d, 0x60, 0x81, 0xbe, 0xf6, 0xce, 0xfc, 0xfc,
0x6c, 0xe0, 0x5c, 0x77, 0xb6, 0x3a, 0xae, 0x6a, 0xa2, 0x0d, 0x98, 0xf7, 0x27, 0xc9, 0x34, 0xa6,
0x83, 0xc6, 0x75, 0x67, 0x6b, 0xce, 0x95, 0x2d, 0xf4, 0x5d, 0x58, 0x89, 0xa7, 0x13, 0x6f, 0x94,
0xc4, 0xa7, 0x61, 0x36, 0x11, 0xcc, 0x07, 0x73, 0xd7, 0x9d, 0xad, 0x96, 0x5b, 0xed, 0x40, 0xd7,
0x00, 0x4e, 0xa2, 0x64, 0xf4, 0x52, 0x0c, 0xd1, 0xe4, 0x43, 0x18, 0x08, 0xc2, 0xd0, 0x93, 0x2d,
0x12, 0x8e, 0xcf, 0xe8, 0xa0, 0xc5, 0x19, 0x59, 0x18, 0xe3, 0x41, 0xc3, 0x09, 0xf1, 0x72, 0xea,
0x4f, 0xd2, 0xc1, 0x3c, 0x9f, 0x8d, 0x81, 0xf0, 0xfe, 0x84, 0xfa, 0x91, 0x77, 0x4a, 0x48, 0x3e,
0x58, 0x90, 0xfd, 0x1a, 0xc1, 0x03, 0xd8, 0x78, 0x4a, 0xa8, 0xb1, 0xea, 0xdc, 0x25, 0x5f, 0x4f,
0x49, 0x4e, 0xf1, 0x21, 0x20, 0x03, 0x7e, 0x44, 0xa8, 0x1f, 0x46, 0x39, 0xfa, 0x04, 0x7a, 0xd4,
0x20, 0x1e, 0x38, 0xd7, 0xe7, 0xb6, 0xba, 0x7b, 0x68, 0x87, 0xcb, 0x77, 0xc7, 0xf8, 0xc0, 0xb5,
0xe8, 0xf0, 0xbf, 0x38, 0xd0, 0x3d, 0x26, 0x71, 0x20, 0xb9, 0x23, 0x04, 0xcd, 0x80, 0xe4, 0x94,
0x0b, 0xb6, 0xe7, 0xf2, 0xdf, 0xe8, 0x5b, 0xd0, 0x65, 0x7f, 0xbd, 0x9c, 0x66, 0x61, 0x3c, 0xe6,
0xa2, 0xed, 0xb8, 0xc0, 0xa0, 0x63, 0x8e, 0xa0, 0x65, 0x98, 0xf3, 0x27, 0x94, 0x0b, 0x74, 0xce,
0x65, 0x3f, 0xd1, 0x0d, 0xe8, 0xa5, 0xfe, 0x6c, 0x42, 0x62, 0x5a, 0x08, 0xb1, 0xe7, 0x76, 0x25,
0x76, 0xc0, 0xa4, 0xb8, 0x03, 0xab, 0x26, 0x89, 0xe2, 0xde, 0xe2, 0xdc, 0x57, 0x0c, 0x4a, 0x39,
0xc8, 0x2d, 0x58, 0x52, 0xf4, 0x99, 0x98, 0x2c, 0x17, 0x6b, 0xc7, 0x5d, 0x94, 0xb0, 0x12, 0xd0,
0x9f, 0x3b, 0xd0, 0x13, 0x4b, 0xca, 0xd3, 0x24, 0xce, 0x09, 0xba, 0x09, 0x7d, 0xf5, 0x25, 0xc9,
0xb2, 0x24, 0x93, 0x5a, 0x63, 0x83, 0x68, 0x1b, 0x96, 0x15, 0x90, 0x66, 0x24, 0x9c, 0xf8, 0x63,
0xc2, 0x97, 0xda, 0x73, 0x2b, 0x38, 0xda, 0x2b, 0x38, 0x66, 0xc9, 0x94, 0x12, 0xbe, 0xf4, 0xee,
0x5e, 0x4f, 0x8a, 0xdb, 0x65, 0x98, 0x6b, 0x93, 0xe0, 0x6f, 0x1c, 0xe8, 0x3d, 0x3c, 0xf3, 0xe3,
0x98, 0x44, 0x47, 0x49, 0x18, 0x53, 0xa6, 0x46, 0xa7, 0xd3, 0x38, 0x08, 0xe3, 0xb1, 0x47, 0x5f,
0x87, 0x81, 0x14, 0xb9, 0x85, 0xb1, 0x49, 0x99, 0x6d, 0x26, 0x24, 0x29, 0xff, 0x0a, 0xce, 0xf8,
0x25, 0x53, 0x9a, 0x4e, 0xa9, 0x17, 0xc6, 0x01, 0x79, 0xcd, 0xe7, 0xd4, 0x77, 0x2d, 0x0c, 0xff,
0x26, 0x2c, 0x1f, 0x32, 0xfd, 0x8c, 0xc3, 0x78, 0xbc, 0x1f, 0x04, 0x19, 0xc9, 0x73, 0x76, 0x68,
0xd2, 0xe9, 0xc9, 0x4b, 0x32, 0x93, 0x72, 0x91, 0x2d, 0xa6, 0x0a, 0x67, 0x49, 0x4e, 0xe5, 0x78,
0xfc, 0x37, 0xfe, 0xa5, 0x03, 0x4b, 0x4c, 0xb6, 0x9f, 0xfb, 0xf1, 0x4c, 0xa9, 0xcc, 0x21, 0xf4,
0x18, 0xab, 0xe7, 0xc9, 0xbe, 0x38, 0x7a, 0x42, 0xf5, 0xb6, 0xa4, 0x2c, 0x4a, 0xd4, 0x3b, 0x26,
0xe9, 0xe3, 0x98, 0x66, 0x33, 0xd7, 0xfa, 0x7a, 0xf8, 0x29, 0xac, 0x54, 0x48, 0x98, 0x82, 0x15,
0xf3, 0x63, 0x3f, 0xd1, 0x1a, 0xb4, 0x5e, 0xf9, 0xd1, 0x94, 0xc8, 0x83, 0x2e, 0x1a, 0xf7, 0x1a,
0x77, 0x1d, 0xfc, 0x31, 0x2c, 0x17, 0x63, 0x4a, 0x0d, 0x40, 0xd0, 0xd4, 0x22, 0xee, 0xb8, 0xfc,
0x37, 0x13, 0x05, 0xa3, 0x7b, 0x98, 0x84, 0xfa, 0x6c, 0x31, 0x3a, 0x3f, 0x08, 0x94, 0x82, 0xf0,
0xdf, 0x17, 0xd9, 0x14, 0x7c, 0x0b, 0x56, 0x8c, 0xef, 0xdf, 0x31, 0xd0, 0x5f, 0x3b, 0xb0, 0xf2,
0x8c, 0x9c, 0x4b, 0x71, 0xab, 0xa1, 0xee, 0x42, 0x93, 0xce, 0x52, 0xc2, 0x29, 0x17, 0xf7, 0x6e,
0x4a, 0x69, 0x55, 0xe8, 0x76, 0x64, 0xf3, 0xf9, 0x2c, 0x25, 0x2e, 0xff, 0x02, 0x7f, 0x01, 0x5d,
0x03, 0x44, 0x9b, 0xb0, 0xfa, 0xe2, 0xb3, 0xe7, 0xcf, 0x1e, 0x1f, 0x1f, 0x7b, 0x47, 0x5f, 0x3e,
0xf8, 0xe1, 0xe3, 0xdf, 0xf1, 0x0e, 0xf6, 0x8f, 0x0f, 0x96, 0x2f, 0xa1, 0x0d, 0x40, 0xcf, 0x1e,
0x1f, 0x3f, 0x7f, 0xfc, 0xc8, 0xc2, 0x1d, 0xb4, 0x04, 0x5d, 0x13, 0x68, 0xe0, 0x21, 0x0c, 0x9e,
0x91, 0xf3, 0x17, 0x21, 0x8d, 0x49, 0x9e, 0xdb, 0xc3, 0xe3, 0x1d, 0x40, 0xe6, 0x9c, 0xe4, 0x32,
0x07, 0xb0, 0xe0, 0x0b, 0x48, 0x59, 0x60, 0xd9, 0xc4, 0x1f, 0x03, 0x3a, 0x0e, 0xc7, 0xf1, 0xe7,
0x24, 0xcf, 0xfd, 0x31, 0x51, 0x8b, 0x5d, 0x86, 0xb9, 0x49, 0x3e, 0x96, 0x1a, 0xce, 0x7e, 0xe2,
0xef, 0xc1, 0xaa, 0x45, 0x27, 0x19, 0x5f, 0x85, 0x4e, 0x1e, 0x8e, 0x63, 0x9f, 0x4e, 0x33, 0x22,
0x59, 0x17, 0x00, 0x7e, 0x02, 0x6b, 0x5f, 0x91, 0x2c, 0x3c, 0x9d, 0xbd, 0x8f, 0xbd, 0xcd, 0xa7,
0x51, 0xe6, 0xf3, 0x18, 0xd6, 0x4b, 0x7c, 0xe4, 0xf0, 0x42, 0xab, 0xe4, 0xfe, 0xb5, 0x5d, 0xd1,
0x30, 0x0e, 0x48, 0xc3, 0x3c, 0x20, 0xf8, 0x4b, 0x40, 0x0f, 0x93, 0x38, 0x26, 0x23, 0x7a, 0x44,
0x48, 0xa6, 0x26, 0xf3, 0x1d, 0x43, 0x87, 0xba, 0x7b, 0x9b, 0x72, 0x63, 0xcb, 0xa7, 0x4e, 0x2a,
0x17, 0x82, 0x66, 0x4a, 0xb2, 0x09, 0x67, 0xdc, 0x76, 0xf9, 0x6f, 0xbc, 0x0b, 0xab, 0x16, 0xdb,
0x42, 0xe6, 0x29, 0x21, 0x99, 0x27, 0x67, 0xd7, 0x72, 0x55, 0x13, 0xdf, 0x81, 0xf5, 0x47, 0x61,
0x3e, 0xaa, 0x4e, 0x85, 0x7d, 0x32, 0x3d, 0xf1, 0x8a, 0xa3, 0xa3, 0x9a, 0xcc, 0xbd, 0x94, 0x3f,
0x11, 0xc3, 0xe0, 0x3f, 0x72, 0xa0, 0x79, 0xf0, 0xfc, 0xf0, 0x21, 0x1a, 0x42, 0x3b, 0x8c, 0x47,
0xc9, 0x84, 0x19, 0x65, 0x21, 0x0e, 0xdd, 0xbe, 0xd0, 0xcf, 0x5e, 0x85, 0x0e, 0xb7, 0xe5, 0xcc,
0x13, 0x72, 0xfb, 0xd3, 0x73, 0x0b, 0x80, 0x79, 0x61, 0xf2, 0x3a, 0x0d, 0x33, 0xee, 0x66, 0x95,
0xf3, 0x6c, 0x72, 0x2b, 0x55, 0xed, 0xc0, 0xff, 0xd4, 0x84, 0xfe, 0xfe, 0x88, 0x86, 0xaf, 0x88,
0xb4, 0x9a, 0x7c, 0x54, 0x0e, 0xc8, 0xf9, 0xc8, 0x16, 0xb3, 0xef, 0x19, 0x99, 0x24, 0x94, 0x78,
0xd6, 0x36, 0xd9, 0x20, 0xa3, 0x1a, 0x09, 0x46, 0x5e, 0xca, 0xec, 0x2f, 0x9f, 0x5f, 0xc7, 0xb5,
0x41, 0x26, 0x32, 0x06, 0x30, 0x29, 0xb3, 0x99, 0x35, 0x5d, 0xd5, 0x64, 0xf2, 0x18, 0xf9, 0xa9,
0x3f, 0x0a, 0xe9, 0x8c, 0x3b, 0xa9, 0x39, 0x57, 0xb7, 0x19, 0xef, 0x28, 0x19, 0xf9, 0x91, 0x77,
0xe2, 0x47, 0x7e, 0x3c, 0x22, 0xd2, 0xe1, 0xdb, 0x20, 0xfa, 0x18, 0x16, 0xe5, 0x94, 0x14, 0x99,
0xf0, 0xfb, 0x25, 0x94, 0xc5, 0x06, 0xa3, 0x64, 0x32, 0x09, 0x29, 0x0b, 0x05, 0x06, 0x6d, 0x11,
0x1b, 0x14, 0x08, 0x5f, 0x89, 0x68, 0x9d, 0x0b, 0x19, 0x76, 0xc4, 0x68, 0x16, 0xc8, 0xb8, 0x9c,
0x12, 0xe2, 0xa5, 0x24, 0xf3, 0x5e, 0x9e, 0x0f, 0x40, 0x70, 0x29, 0x10, 0xb6, 0x1b, 0xd3, 0x38,
0x27, 0x94, 0x46, 0x24, 0xd0, 0x13, 0xea, 0x72, 0xb2, 0x6a, 0x07, 0xba, 0x0d, 0xab, 0x22, 0x3a,
0xc9, 0x7d, 0x9a, 0xe4, 0x67, 0x61, 0xee, 0xe5, 0x24, 0xa6, 0x83, 0x1e, 0xa7, 0xaf, 0xeb, 0x42,
0x77, 0x61, 0xb3, 0x04, 0x67, 0x64, 0x44, 0xc2, 0x57, 0x24, 0x18, 0xf4, 0xf9, 0x57, 0x17, 0x75,
0xa3, 0xeb, 0xd0, 0x65, 0x41, 0xd9, 0x34, 0x0d, 0x7c, 0x4a, 0xf2, 0xc1, 0x22, 0xdf, 0x07, 0x13,
0x42, 0x77, 0xa0, 0x9f, 0x12, 0xe1, 0xfe, 0xce, 0x68, 0x34, 0xca, 0x07, 0x4b, 0xdc, 0xe7, 0x74,
0xe5, 0x61, 0x63, 0xfa, 0xeb, 0xda, 0x14, 0x78, 0x1d, 0x56, 0x0f, 0xc3, 0x9c, 0x4a, 0x5d, 0xd2,
0xf6, 0xed, 0x00, 0xd6, 0x6c, 0x58, 0x9e, 0xb6, 0xdb, 0xd0, 0x96, 0x8a, 0x91, 0x0f, 0xba, 0x9c,
0xf9, 0x9a, 0x64, 0x6e, 0xe9, 0xa4, 0xab, 0xa9, 0xf0, 0x1f, 0x36, 0xa0, 0xc9, 0x4e, 0xd2, 0xc5,
0xa7, 0xce, 0x3c, 0xc2, 0x0d, 0xeb, 0x08, 0x9b, 0x06, 0x75, 0xce, 0x32, 0xa8, 0x3c, 0x18, 0x9d,
0x51, 0x22, 0xe5, 0x2d, 0x74, 0xd2, 0x40, 0x8a, 0xfe, 0x8c, 0x8c, 0x5e, 0x71, 0xc5, 0xd4, 0xfd,
0x0c, 0x61, 0x6a, 0x9b, 0xfb, 0x54, 0x7c, 0x2d, 0xb4, 0x52, 0xb7, 0x55, 0x1f, 0xff, 0x72, 0xa1,
0xe8, 0xe3, 0xdf, 0x0d, 0x60, 0x21, 0x8c, 0x4f, 0x92, 0x69, 0x1c, 0x70, 0x0d, 0x6c, 0xbb, 0xaa,
0xc9, 0x0e, 0x79, 0xca, 0x03, 0x8f, 0x70, 0x42, 0xa4, 0xea, 0x15, 0x00, 0x46, 0x2c, 0xc2, 0xc8,
0xb9, 0x4d, 0xd1, 0x42, 0xfe, 0x04, 0x56, 0x0c, 0x4c, 0x4a, 0xf8, 0x06, 0xb4, 0xd8, 0xea, 0x55,
0xa8, 0xaa, 0xf6, 0x8e, 0x1b, 0x23, 0xd1, 0x83, 0x97, 0x61, 0xf1, 0x29, 0xa1, 0x9f, 0xc5, 0xa7,
0x89, 0xe2, 0xf4, 0x9f, 0x0d, 0x58, 0xd2, 0x90, 0x64, 0xb4, 0x05, 0x4b, 0x61, 0x40, 0x62, 0x1a,
0xd2, 0x99, 0x67, 0x05, 0x32, 0x65, 0x98, 0x99, 0x77, 0x3f, 0x0a, 0xfd, 0x5c, 0x1a, 0x08, 0xd1,
0x40, 0x7b, 0xb0, 0xc6, 0x74, 0x4b, 0xa9, 0x8b, 0xde, 0x76, 0x11, 0x3f, 0xd5, 0xf6, 0xb1, 0xe3,
0xc0, 0x70, 0x61, 0x80, 0x8a, 0x4f, 0x84, 0x31, 0xab, 0xeb, 0x62, 0x52, 0x13, 0x9c, 0xd8, 0x92,
0x5b, 0x9c, 0xae, 0x00, 0x2a, 0x29, 0xc5, 0xbc, 0x88, 0xdd, 0xca, 0x29, 0x85, 0x91, 0x96, 0xb4,
0x2b, 0x69, 0xc9, 0x16, 0x2c, 0xe5, 0xb3, 0x78, 0x44, 0x02, 0x8f, 0x26, 0x6c, 0xdc, 0x30, 0xe6,
0xbb, 0xd3, 0x76, 0xcb, 0x30, 0x4f, 0xa0, 0x48, 0x4e, 0x63, 0x42, 0xb9, 0x5d, 0x68, 0xbb, 0xaa,
0xc9, 0x4c, 0x2c, 0x27, 0x11, 0x4a, 0xdf, 0x71, 0x65, 0x0b, 0xff, 0x8c, 0xbb, 0x3a, 0x9d, 0x23,
0x7d, 0xc9, 0xcf, 0x21, 0xba, 0x02, 0x1d, 0x31, 0x7e, 0x7e, 0xe6, 0x4b, 0xef, 0xdb, 0xe6, 0xc0,
0xf1, 0x99, 0xcf, 0x52, 0x00, 0x6b, 0x49, 0x42, 0xe3, 0xbb, 0x1c, 0x3b, 0x10, 0x2b, 0xba, 0x09,
0x8b, 0x2a, 0xfb, 0xca, 0xbd, 0x88, 0x9c, 0x52, 0x15, 0xb3, 0xc6, 0xd3, 0x09, 0x1b, 0x2e, 0x3f,
0x24, 0xa7, 0x14, 0x3f, 0x83, 0x15, 0x79, 0xda, 0xbe, 0x48, 0x89, 0x1a, 0xfa, 0x07, 0x65, 0x6b,
0x2e, 0xdc, 0xed, 0xaa, 0xd4, 0x22, 0x33, 0xd0, 0x2e, 0x99, 0x78, 0xec, 0x02, 0x92, 0xdd, 0x0f,
0xa3, 0x24, 0x27, 0x92, 0x21, 0x86, 0xde, 0x28, 0x4a, 0xf2, 0x72, 0x34, 0x6e, 0x62, 0x4c, 0x6e,
0xf9, 0x74, 0x34, 0x62, 0xa7, 0x54, 0x38, 0x6c, 0xd5, 0xc4, 0x04, 0x56, 0x39, 0x33, 0x65, 0x16,
0x74, 0x90, 0xf7, 0xe1, 0xb3, 0xec, 0x8d, 0xcc, 0xe4, 0x60, 0x0d, 0x5a, 0xa7, 0x49, 0x36, 0x22,
0x72, 0x20, 0xd1, 0xc0, 0xff, 0xea, 0xc0, 0x0a, 0x1f, 0xe7, 0x98, 0xfa, 0x74, 0x9a, 0xcb, 0xa9,
0xff, 0x3a, 0xf4, 0xd9, 0x34, 0x89, 0x52, 0x53, 0x39, 0xca, 0x9a, 0x3e, 0x51, 0x1c, 0x15, 0xc4,
0x07, 0x97, 0x5c, 0x9b, 0x18, 0x7d, 0x0a, 0x3d, 0x33, 0xfd, 0xe5, 0x03, 0x76, 0xf7, 0x2e, 0xab,
0x29, 0x56, 0x76, 0xfd, 0xe0, 0x92, 0x6b, 0x7d, 0x80, 0xee, 0x03, 0x70, 0x1f, 0xc9, 0xd9, 0xca,
0x4c, 0xe8, 0xb2, 0xbd, 0x42, 0x43, 0xd0, 0x07, 0x97, 0x5c, 0x83, 0xfc, 0x41, 0x1b, 0xe6, 0x85,
0x51, 0xc7, 0x4f, 0xa1, 0x6f, 0xcd, 0xd4, 0x8a, 0xa5, 0x7b, 0x22, 0x96, 0xae, 0xe4, 0x38, 0x8d,
0x9a, 0x1c, 0xe7, 0xdf, 0x1c, 0x40, 0x4c, 0x53, 0x4a, 0x7b, 0xf1, 0x31, 0x2c, 0x52, 0x3f, 0x1b,
0x13, 0xea, 0xd9, 0x61, 0x54, 0x09, 0xe5, 0xde, 0x27, 0x09, 0xac, 0x58, 0xa2, 0xe7, 0x9a, 0x10,
0xda, 0x01, 0x64, 0x34, 0x55, 0xe2, 0x2a, 0xec, 0x76, 0x4d, 0x0f, 0x33, 0x30, 0x22, 0x10, 0x50,
0x29, 0x9b, 0x8c, 0x9d, 0x9a, 0xdc, 0x76, 0xd6, 0xf6, 0x31, 0xd3, 0x9c, 0x4e, 0x59, 0x56, 0xec,
0x53, 0x15, 0x6d, 0xa8, 0x36, 0xfe, 0x95, 0x03, 0xcb, 0x6c, 0x81, 0x96, 0x12, 0xdc, 0x03, 0xae,
0x40, 0x1f, 0xa8, 0x03, 0x16, 0xed, 0xff, 0x5c, 0x05, 0xee, 0x42, 0x87, 0x33, 0x4c, 0x52, 0x12,
0x4b, 0x0d, 0x18, 0xd8, 0x1a, 0x50, 0x1c, 0xdd, 0x83, 0x4b, 0x6e, 0x41, 0x6c, 0xec, 0xff, 0x26,
0xac, 0xcb, 0x59, 0xda, 0x1b, 0x87, 0xff, 0x18, 0x60, 0xa3, 0xdc, 0xa3, 0xbd, 0xb4, 0x0c, 0x3d,
0xa2, 0x70, 0x72, 0x92, 0xe8, 0x28, 0xc6, 0x31, 0xa3, 0x12, 0xab, 0x0b, 0x9d, 0xc2, 0xba, 0x32,
0xe6, 0x6c, 0xfc, 0xc2, 0x74, 0x37, 0xb8, 0x17, 0xba, 0x6d, 0xcb, 0xab, 0x34, 0x9e, 0x82, 0x4d,
0xed, 0xaa, 0x67, 0x87, 0xc6, 0x30, 0xd0, 0x4e, 0x43, 0x9a, 0x10, 0xc3, 0xb1, 0xb0, 0xa1, 0xbe,
0xf3, 0xee, 0xa1, 0xf8, 0x91, 0x09, 0x14, 0x7a, 0x21, 0x33, 0xf4, 0x1a, 0xae, 0xa9, 0x3e, 0x6e,
0x23, 0xaa, 0xc3, 0x35, 0x3f, 0x64, 0x65, 0x4f, 0xd8, 0xb7, 0xf6, 0x98, 0xef, 0xe1, 0x3b, 0xfc,
0x47, 0x07, 0x16, 0x6d, 0x6e, 0xcc, 0x05, 0xc9, 0x58, 0x56, 0x1d, 0x03, 0xe5, 0x8a, 0x4b, 0x70,
0x35, 0x1a, 0x6f, 0xd4, 0x45, 0xe3, 0x66, 0xcc, 0x3d, 0xf7, 0xbe, 0x98, 0xbb, 0xf9, 0x61, 0x31,
0x77, 0xab, 0x2e, 0xe6, 0x1e, 0xfe, 0xb2, 0x01, 0xa8, 0xba, 0xbb, 0xe8, 0x89, 0x48, 0x07, 0x62,
0x12, 0xc9, 0x03, 0xf5, 0xdd, 0x0f, 0x52, 0x10, 0x05, 0xab, 0x8f, 0x99, 0xa2, 0x9a, 0x07, 0xc6,
0xf4, 0x89, 0x7d, 0xb7, 0xae, 0x0b, 0x6d, 0xc3, 0x32, 0x77, 0x95, 0xb9, 0x47, 0xc3, 0x28, 0x2a,
0x4e, 0x56, 0xdf, 0xad, 0xe0, 0xa5, 0x84, 0xa1, 0xf9, 0xfe, 0x84, 0xa1, 0xf5, 0xfe, 0x84, 0x61,
0xbe, 0x9c, 0x30, 0x0c, 0xdf, 0x40, 0xdf, 0x52, 0x90, 0xff, 0x35, 0xe1, 0x94, 0x5d, 0xaf, 0x50,
0x05, 0x0b, 0x1b, 0x7e, 0xd3, 0x00, 0x54, 0xd5, 0xd1, 0xff, 0xcb, 0x29, 0x70, 0x85, 0xb3, 0xcc,
0xcc, 0x9c, 0x54, 0x38, 0xcb, 0xc0, 0x6c, 0xc1, 0xd2, 0xc4, 0xa7, 0xd3, 0x8c, 0x85, 0x9d, 0x56,
0x8a, 0x5b, 0x86, 0x99, 0x4e, 0x14, 0x3b, 0xe9, 0xa9, 0x5e, 0x19, 0x1b, 0xd6, 0x75, 0xe1, 0x1f,
0xc0, 0xda, 0x0b, 0x3f, 0x8a, 0x08, 0x7d, 0x20, 0x06, 0x53, 0xae, 0xed, 0x06, 0xf4, 0xce, 0x45,
0xf5, 0xc6, 0x4b, 0xe2, 0x68, 0x26, 0xd3, 0xe3, 0xae, 0xc4, 0xbe, 0x88, 0xa3, 0x19, 0xbe, 0x03,
0xeb, 0xa5, 0x4f, 0x8b, 0xb2, 0x82, 0x6d, 0x36, 0x55, 0x93, 0x19, 0x64, 0x29, 0x27, 0x7b, 0x38,
0xbc, 0x07, 0x1b, 0xe5, 0x8e, 0xf7, 0x32, 0xfb, 0x14, 0xd0, 0x8f, 0xa6, 0x24, 0x9b, 0xf1, 0xd2,
0xa8, 0x2e, 0x82, 0x6d, 0x96, 0x53, 0xa5, 0xf9, 0x74, 0x7a, 0xf2, 0x43, 0x32, 0x53, 0x15, 0xe5,
0x86, 0xae, 0x28, 0xe3, 0xfb, 0xb0, 0x6a, 0x31, 0xd0, 0xb5, 0xdd, 0x79, 0x5e, 0x5e, 0x55, 0x69,
0x84, 0x5d, 0x82, 0x95, 0x7d, 0xf8, 0x2f, 0x1d, 0x98, 0x3b, 0x48, 0x52, 0x33, 0xbb, 0x77, 0xec,
0xec, 0x5e, 0xda, 0x23, 0x4f, 0x9b, 0x9b, 0x86, 0x3c, 0x22, 0x26, 0xc8, 0xac, 0x89, 0x3f, 0xa1,
0x2c, 0x90, 0x3e, 0x4d, 0xb2, 0x73, 0x3f, 0x0b, 0xa4, 0x0e, 0x94, 0x50, 0x36, 0xfd, 0xe2, 0x24,
0xb2, 0x9f, 0x2c, 0xb0, 0xe6, 0x25, 0x0e, 0xb5, 0xbf, 0xb2, 0x85, 0x7f, 0xee, 0x40, 0x8b, 0xcf,
0x95, 0x29, 0x8e, 0x70, 0x58, 0xfc, 0x96, 0x80, 0x57, 0x50, 0x1c, 0xa1, 0x38, 0x25, 0xb8, 0x74,
0x77, 0xd0, 0x28, 0xdf, 0x1d, 0xb0, 0x54, 0x43, 0xb4, 0x8a, 0xa2, 0x7c, 0x01, 0xa0, 0x6b, 0xd0,
0x3c, 0x4b, 0x52, 0xe5, 0x16, 0x40, 0xa5, 0xcc, 0x49, 0xea, 0x72, 0x1c, 0x6f, 0xc3, 0xd2, 0xb3,
0x24, 0x20, 0x46, 0xd6, 0x75, 0xe1, 0x36, 0xe1, 0xdf, 0x77, 0xa0, 0xad, 0x88, 0xd1, 0x16, 0x34,
0x99, 0x79, 0x2f, 0x45, 0x1e, 0xba, 0xf0, 0xc5, 0xe8, 0x5c, 0x4e, 0xc1, 0x4e, 0x1b, 0x8f, 0xfb,
0x0b, 0xdf, 0xab, 0xa2, 0xfe, 0xc2, 0xaf, 0xb1, 0x70, 0x8d, 0xcf, 0xb9, 0xe4, 0x00, 0x4a, 0x28,
0xfe, 0x85, 0x03, 0x7d, 0x6b, 0x0c, 0x16, 0xc0, 0x45, 0x7e, 0x4e, 0x65, 0xb1, 0x40, 0x0a, 0xd1,
0x84, 0xcc, 0x0c, 0xbd, 0x61, 0x67, 0xe8, 0x3a, 0x43, 0x9c, 0x33, 0x33, 0xc4, 0xdb, 0xd0, 0x91,
0xe9, 0x38, 0x51, 0x72, 0x53, 0x37, 0x2b, 0x6c, 0x44, 0x55, 0xd2, 0x2b, 0x88, 0xf0, 0x7d, 0xe8,
0x1a, 0x3d, 0x6c, 0xc0, 0x98, 0xd0, 0xf3, 0x24, 0x7b, 0xa9, 0x4a, 0x02, 0xb2, 0xa9, 0x2b, 0xce,
0x8d, 0xa2, 0xe2, 0x8c, 0xff, 0xce, 0x81, 0x3e, 0xd3, 0x89, 0x30, 0x1e, 0x1f, 0x25, 0x51, 0x38,
0x9a, 0x71, 0xdd, 0x50, 0xdb, 0xef, 0x05, 0x24, 0xa2, 0xbe, 0xd6, 0x0d, 0x1b, 0x66, 0x1e, 0x73,
0x12, 0xc6, 0xbc, 0xe6, 0x21, 0x35, 0x43, 0xb7, 0x99, 0x8e, 0x33, 0x73, 0x7e, 0xe2, 0xe7, 0xc4,
0x9b, 0xb0, 0xc0, 0x52, 0x1a, 0x30, 0x0b, 0x64, 0x66, 0x89, 0x01, 0x99, 0x4f, 0x89, 0x37, 0x09,
0xa3, 0x28, 0x14, 0xb4, 0x42, 0x97, 0xeb, 0xba, 0xf0, 0x3f, 0x34, 0xa0, 0x2b, 0x0d, 0xc2, 0xe3,
0x60, 0x2c, 0xea, 0x57, 0xd2, 0x8d, 0xeb, 0x83, 0x66, 0x20, 0xaa, 0xdf, 0x72, 0xfc, 0x06, 0x52,
0xde, 0xc0, 0xb9, 0xea, 0x06, 0xb2, 0x64, 0x3a, 0x09, 0xc8, 0x1d, 0x1e, 0x61, 0x88, 0x0b, 0xba,
0x02, 0x50, 0xbd, 0x7b, 0xbc, 0xb7, 0x55, 0xf4, 0x72, 0xc0, 0x8a, 0x29, 0xe6, 0x4b, 0x31, 0xc5,
0x5d, 0xe8, 0x49, 0x36, 0x5c, 0xee, 0xbc, 0x28, 0x52, 0xa8, 0xb2, 0xb5, 0x27, 0xae, 0x45, 0xa9,
0xbe, 0xdc, 0x53, 0x5f, 0xb6, 0xdf, 0xf7, 0xa5, 0xa2, 0xc4, 0xeb, 0xb0, 0x2a, 0x85, 0xf7, 0x34,
0xf3, 0xd3, 0x33, 0x65, 0x64, 0x03, 0x7d, 0x5b, 0xc4, 0x61, 0xb4, 0x0d, 0x2d, 0xf6, 0x99, 0xb2,
0x73, 0xf5, 0xc7, 0x4b, 0x90, 0xa0, 0x2d, 0x68, 0x91, 0x60, 0x4c, 0x54, 0x50, 0x8b, 0xec, 0x50,
0x9c, 0xed, 0x91, 0x2b, 0x08, 0xd8, 0x61, 0x67, 0x68, 0xe9, 0xb0, 0xdb, 0x36, 0x72, 0x9e, 0x35,
0x3f, 0x0b, 0xf0, 0x1a, 0xa0, 0x67, 0x42, 0x6b, 0xcd, 0x8a, 0xcc, 0x1f, 0xcc, 0x41, 0xd7, 0x80,
0xd9, 0xb9, 0x1d, 0xb3, 0x09, 0x7b, 0x41, 0xe8, 0x4f, 0x08, 0x25, 0x99, 0xd4, 0xd4, 0x12, 0xca,
0x4d, 0xe9, 0xab, 0xb1, 0x97, 0x4c, 0xa9, 0x17, 0x90, 0x71, 0x46, 0x44, 0xa6, 0xeb, 0xb8, 0x25,
0x94, 0xd1, 0x4d, 0xfc, 0xd7, 0x26, 0x9d, 0xd0, 0x87, 0x12, 0xaa, 0xea, 0x2b, 0x42, 0x46, 0xcd,
0xa2, 0xbe, 0x22, 0x24, 0x52, 0xb6, 0x38, 0xad, 0x1a, 0x8b, 0xf3, 0x09, 0x6c, 0x08, 0xdb, 0x22,
0xcf, 0xa6, 0x57, 0x52, 0x93, 0x0b, 0x7a, 0x59, 0xa4, 0xc6, 0xe6, 0xac, 0x14, 0x3c, 0x0f, 0x7f,
0x26, 0x0a, 0xbb, 0x8e, 0x5b, 0xc1, 0x19, 0x2d, 0x3b, 0x8e, 0x16, 0xad, 0x28, 0xf0, 0x56, 0x70,
0x4e, 0xeb, 0xbf, 0xb6, 0x69, 0x3b, 0x92, 0xb6, 0x84, 0xe3, 0x3e, 0x74, 0x8f, 0x69, 0x92, 0xaa,
0x4d, 0x59, 0x84, 0x9e, 0x68, 0xca, 0xa2, 0xfe, 0x15, 0xb8, 0xcc, 0xb5, 0xe8, 0x79, 0x92, 0x26,
0x51, 0x32, 0x9e, 0x1d, 0x4f, 0x4f, 0xf2, 0x51, 0x16, 0xa6, 0x2c, 0xe0, 0xc4, 0xff, 0xec, 0xc0,
0xaa, 0xd5, 0x2b, 0x33, 0xca, 0xff, 0x2f, 0x54, 0x5a, 0xd7, 0x61, 0x85, 0xe2, 0xad, 0x18, 0x86,
0x4f, 0x10, 0x8a, 0xe4, 0xf8, 0x4b, 0x59, 0x9a, 0xdd, 0x87, 0x25, 0x35, 0x33, 0xf5, 0xa1, 0xd0,
0xc2, 0x41, 0x55, 0x0b, 0xe5, 0xf7, 0x8b, 0xf2, 0x03, 0xc5, 0xe2, 0x37, 0x44, 0x30, 0x46, 0x02,
0xbe, 0x46, 0x95, 0x2f, 0x0d, 0xd5, 0xf7, 0x66, 0x00, 0xa8, 0x66, 0x30, 0xd2, 0x60, 0x8e, 0xff,
0xc4, 0x01, 0x28, 0x66, 0xc7, 0x14, 0xa3, 0x30, 0xde, 0x0e, 0xaf, 0x6a, 0x15, 0x00, 0x0b, 0x9d,
0x74, 0x95, 0xb0, 0xf0, 0x07, 0x5d, 0x85, 0xb1, 0x58, 0xe4, 0x16, 0x2c, 0x8d, 0xa3, 0xe4, 0x84,
0x7b, 0x57, 0x7e, 0x7f, 0x94, 0xcb, 0xab, 0x8d, 0x45, 0x01, 0x3f, 0x91, 0x68, 0xe1, 0x3c, 0x9a,
0x86, 0xf3, 0xc0, 0x7f, 0xda, 0xd0, 0xf5, 0xab, 0x62, 0xcd, 0x17, 0x9e, 0x32, 0xb4, 0x57, 0x31,
0x8e, 0x17, 0xd4, 0x8b, 0x78, 0x12, 0x7d, 0xf4, 0xde, 0x34, 0xe9, 0x3e, 0x2c, 0x66, 0xc2, 0xfa,
0x28, 0xd3, 0xd4, 0x7c, 0x87, 0x69, 0xea, 0x67, 0x96, 0xdf, 0xf9, 0x7f, 0xb0, 0xec, 0x07, 0xaf,
0x48, 0x46, 0x43, 0x1e, 0x06, 0x73, 0xf7, 0x2e, 0x0c, 0xea, 0x92, 0x81, 0x73, 0xaf, 0x7b, 0x0b,
0x96, 0xe4, 0x75, 0x92, 0xa6, 0x94, 0xd7, 0xf3, 0x05, 0xcc, 0x08, 0xf1, 0xdf, 0x38, 0xb2, 0x56,
0x66, 0xef, 0xe1, 0xc5, 0x12, 0x31, 0x57, 0xd7, 0x28, 0xad, 0xee, 0xdb, 0xb2, 0xf4, 0x15, 0xa8,
0x58, 0x5b, 0x16, 0x10, 0x05, 0x28, 0xcb, 0x8c, 0xb6, 0x48, 0x9b, 0x1f, 0x22, 0x52, 0xbc, 0x03,
0x4b, 0xc7, 0x84, 0xee, 0xb3, 0x1d, 0x54, 0x86, 0xf1, 0x0a, 0x74, 0x62, 0x72, 0xee, 0x89, 0x2d,
0x16, 0x6e, 0xbc, 0x1d, 0x93, 0x73, 0x4e, 0x83, 0x11, 0x2c, 0x17, 0xf4, 0xf2, 0xd4, 0xfd, 0x59,
0x03, 0x16, 0x3e, 0x8b, 0x5f, 0x25, 0xe1, 0x88, 0x17, 0xb3, 0x26, 0x64, 0x92, 0xa8, 0x8b, 0x61,
0xf6, 0x9b, 0x45, 0x05, 0xfc, 0xce, 0x23, 0xa5, 0xb2, 0xca, 0xa4, 0x9a, 0xcc, 0x43, 0x66, 0xc5,
0x2b, 0x04, 0xa1, 0x6d, 0x06, 0xc2, 0xa2, 0xc9, 0xcc, 0x7c, 0x58, 0x21, 0x5b, 0xc5, 0xad, 0x78,
0xcb, 0xb8, 0x15, 0xe7, 0x65, 0x4b, 0x71, 0x9d, 0xc3, 0xb7, 0xa4, 0xed, 0xaa, 0x26, 0x8f, 0x7a,
0x33, 0x22, 0xf2, 0x4e, 0xee, 0x6b, 0x17, 0x64, 0xd4, 0x6b, 0x82, 0xcc, 0x1f, 0x8b, 0x0f, 0x04,
0x8d, 0xb0, 0x57, 0x26, 0xc4, 0xe2, 0x93, 0xf2, 0xdb, 0x8c, 0x8e, 0x50, 0x93, 0x12, 0x8c, 0xbf,
0x02, 0xb4, 0x1f, 0x04, 0x52, 0x2a, 0x3a, 0x8a, 0x2f, 0xd6, 0xe3, 0x58, 0xeb, 0xa9, 0xe1, 0xdb,
0xa8, 0xe7, 0xfb, 0x18, 0xba, 0x47, 0xc6, 0xe3, 0x12, 0x2e, 0x40, 0xf5, 0xac, 0x44, 0x0a, 0xdd,
0x40, 0x8c, 0x01, 0x1b, 0xe6, 0x80, 0xf8, 0xd7, 0x00, 0x1d, 0x86, 0x39, 0xd5, 0xf3, 0xd3, 0xf9,
0x95, 0xae, 0xf2, 0x18, 0xf9, 0x95, 0xc4, 0x78, 0x7e, 0xb5, 0x2f, 0xae, 0x97, 0xca, 0x0b, 0xdb,
0x86, 0x76, 0x28, 0x20, 0x65, 0x3f, 0x17, 0xa5, 0xe2, 0x29, 0x4a, 0xdd, 0xcf, 0x02, 0x01, 0x09,
0x5a, 0xe6, 0xf9, 0xe7, 0x0e, 0x2c, 0xc8, 0xa5, 0x31, 0x37, 0x66, 0x3d, 0xab, 0x11, 0x0b, 0xb3,
0xb0, 0xfa, 0x97, 0x11, 0xd5, 0x9d, 0x9e, 0xab, 0xdb, 0x69, 0x04, 0xcd, 0xd4, 0xa7, 0x67, 0x3c,
0xc6, 0xed, 0xb8, 0xfc, 0xb7, 0xca, 0x65, 0x5a, 0x3a, 0x97, 0x51, 0x57, 0x69, 0x72, 0x52, 0xfa,
0x96, 0xe7, 0x81, 0xb8, 0x4a, 0x2b, 0xe0, 0x42, 0x06, 0x72, 0x82, 0x65, 0x19, 0x48, 0x52, 0x57,
0xf7, 0xe3, 0x21, 0x0c, 0x1e, 0x91, 0x88, 0x50, 0xb2, 0x1f, 0x45, 0x65, 0xfe, 0x57, 0xe0, 0x72,
0x4d, 0x9f, 0x3c, 0x6b, 0x4f, 0x60, 0xe5, 0x11, 0x39, 0x99, 0x8e, 0x0f, 0xc9, 0xab, 0xa2, 0xe4,
0x8b, 0xa0, 0x99, 0x9f, 0x25, 0xe7, 0x72, 0xbf, 0xf8, 0x6f, 0xf4, 0x11, 0x40, 0xc4, 0x68, 0xbc,
0x3c, 0x25, 0x23, 0xf5, 0x34, 0x80, 0x23, 0xc7, 0x29, 0x19, 0xe1, 0x4f, 0x00, 0x99, 0x7c, 0xe4,
0x12, 0xd8, 0x09, 0x98, 0x9e, 0x78, 0xf9, 0x2c, 0xa7, 0x64, 0xa2, 0x0e, 0xbf, 0x09, 0xe1, 0x5b,
0xd0, 0x3b, 0xf2, 0x67, 0x2e, 0xf9, 0x5a, 0xbe, 0x56, 0x62, 0x29, 0x93, 0x3f, 0x63, 0xea, 0xa9,
0x53, 0x26, 0xde, 0x8d, 0x33, 0x98, 0x17, 0x84, 0x8c, 0x69, 0x40, 0x72, 0x1a, 0xc6, 0xa2, 0xe8,
0x2a, 0x99, 0x1a, 0x50, 0x65, 0xbb, 0x1b, 0x35, 0xdb, 0x2d, 0x23, 0x1b, 0x75, 0x8b, 0x2a, 0xf7,
0xd5, 0xc2, 0x98, 0x71, 0x7a, 0x42, 0x88, 0x4b, 0xd2, 0x24, 0xd3, 0xaf, 0xa4, 0xfe, 0xca, 0x81,
0x65, 0x69, 0xfc, 0x74, 0x1f, 0xba, 0x61, 0x59, 0x4a, 0xa7, 0xae, 0x24, 0x77, 0x13, 0xfa, 0x3c,
0x57, 0x60, 0x89, 0x00, 0x4f, 0x0c, 0x64, 0xa2, 0x6c, 0x81, 0x6c, 0x6d, 0xaa, 0x72, 0x34, 0x09,
0x23, 0x39, 0x29, 0x13, 0x62, 0x56, 0x5d, 0xe5, 0x12, 0xdc, 0x88, 0x39, 0xae, 0x6e, 0xe3, 0x23,
0x58, 0x31, 0xe6, 0x2b, 0xf7, 0xe0, 0x3e, 0xa8, 0x1b, 0x12, 0x91, 0xf7, 0x0a, 0x55, 0xda, 0xb4,
0xed, 0x78, 0xf1, 0x99, 0x45, 0x8c, 0xff, 0xde, 0xe1, 0x22, 0x90, 0xe1, 0x82, 0x7e, 0x1e, 0x31,
0x2f, 0x3c, 0xb8, 0x50, 0x90, 0x83, 0x4b, 0xae, 0x6c, 0xa3, 0xef, 0x7f, 0xa0, 0x13, 0xd6, 0x97,
0x19, 0x17, 0xc8, 0x66, 0xae, 0x4e, 0x36, 0xef, 0x58, 0xf9, 0x83, 0x05, 0x68, 0xe5, 0xa3, 0x24,
0x25, 0x78, 0x95, 0x8b, 0x40, 0xcd, 0x57, 0x88, 0x60, 0xef, 0x6f, 0xaf, 0x40, 0x47, 0x07, 0xfc,
0xe8, 0xa7, 0xd0, 0xb7, 0x4a, 0x3a, 0xe8, 0x8a, 0x9c, 0x61, 0x5d, 0x8d, 0x68, 0x78, 0xb5, 0xbe,
0x53, 0x1e, 0x9f, 0x6b, 0xdf, 0xfc, 0xea, 0xdf, 0x7f, 0xd1, 0x18, 0xa0, 0x8d, 0xdd, 0x57, 0x77,
0x76, 0x65, 0xcd, 0x66, 0x97, 0x97, 0xa0, 0xc4, 0x8d, 0xe1, 0x4b, 0x58, 0xb4, 0x4b, 0x3e, 0xe8,
0xaa, 0x2d, 0x8e, 0xd2, 0x68, 0x1f, 0x5d, 0xd0, 0x2b, 0x87, 0xbb, 0xca, 0x87, 0xdb, 0x40, 0x6b,
0xe6, 0x70, 0x3a, 0x10, 0x27, 0xfc, 0x8e, 0xd7, 0x7c, 0xfb, 0x88, 0x14, 0xbf, 0xfa, 0x37, 0x91,
0xc3, 0xcb, 0xd5, 0x77, 0x8e, 0xf2, 0x61, 0x24, 0x1e, 0xf0, 0xa1, 0x10, 0x5a, 0x66, 0x43, 0x99,
0x4f, 0x1f, 0xd1, 0x4f, 0xa0, 0xa3, 0x1f, 0x70, 0xa1, 0x4d, 0xe3, 0xb9, 0x9a, 0xf9, 0x24, 0x6c,
0x38, 0xa8, 0x76, 0xa8, 0xa0, 0x9a, 0x73, 0x5e, 0xc7, 0x15, 0xce, 0xf7, 0x9c, 0x6d, 0x74, 0x08,
0xeb, 0xd2, 0x8a, 0x9f, 0x90, 0xff, 0xce, 0x4a, 0x6a, 0x5e, 0x6c, 0xde, 0x76, 0xd0, 0x7d, 0x68,
0xab, 0x37, 0x6d, 0x68, 0xa3, 0xfe, 0x61, 0xdd, 0x70, 0xb3, 0x82, 0xcb, 0x83, 0xb3, 0x0f, 0x50,
0x3c, 0xe1, 0x42, 0x83, 0x8b, 0x5e, 0x9a, 0x69, 0x21, 0xd6, 0xbc, 0xf7, 0x1a, 0xf3, 0x17, 0x6c,
0xf6, 0x0b, 0x31, 0xf4, 0xad, 0x82, 0xbe, 0xf6, 0xed, 0xd8, 0x3b, 0x18, 0xe2, 0x0d, 0x2e, 0xbb,
0x65, 0xb4, 0xc8, 0x64, 0x17, 0x93, 0x73, 0xf5, 0xda, 0xe1, 0x11, 0x74, 0x8d, 0x67, 0x61, 0x48,
0x71, 0xa8, 0x3e, 0x29, 0x1b, 0x0e, 0xeb, 0xba, 0xe4, 0x74, 0x7f, 0x0b, 0xfa, 0xd6, 0xfb, 0x2e,
0x7d, 0x32, 0xea, 0x5e, 0x8f, 0xe9, 0x93, 0x51, 0xff, 0x24, 0xec, 0xc7, 0xd0, 0x35, 0x5e, 0x63,
0x21, 0xe3, 0x52, 0xac, 0xf4, 0xda, 0x4a, 0xcf, 0xa8, 0xe6, 0xf1, 0x16, 0x5e, 0xe3, 0xeb, 0x5d,
0xc4, 0x1d, 0xb6, 0x5e, 0x7e, 0xe5, 0xcf, 0x94, 0xe4, 0xa7, 0xb0, 0x68, 0xbf, 0xc2, 0xd2, 0xa7,
0xaa, 0xf6, 0x3d, 0x97, 0x3e, 0x55, 0x17, 0x3c, 0xdd, 0x92, 0x0a, 0xb9, 0xbd, 0xaa, 0x07, 0xd9,
0x7d, 0x23, 0x0b, 0x5b, 0x6f, 0xd1, 0x8f, 0x98, 0xe9, 0x90, 0x6f, 0x30, 0x50, 0xf1, 0x2a, 0xcd,
0x7e, 0xa9, 0xa1, 0xb5, 0xbd, 0xf2, 0x5c, 0x03, 0xaf, 0x70, 0xe6, 0x5d, 0x54, 0xac, 0x00, 0x7d,
0x0e, 0x0b, 0xf2, 0x2d, 0x06, 0x5a, 0x2f, 0xb4, 0xda, 0x28, 0x0e, 0x0c, 0x37, 0xca, 0xb0, 0x64,
0xb6, 0xca, 0x99, 0xf5, 0x51, 0x97, 0x31, 0x1b, 0x13, 0x1a, 0x32, 0x1e, 0x11, 0x2c, 0xd9, 0xe5,
0xf9, 0x5c, 0x8b, 0xa3, 0xf6, 0x62, 0x50, 0x8b, 0xa3, 0xbe, 0xd6, 0x6f, 0x1b, 0x19, 0x65, 0x5c,
0x76, 0xd5, 0x9d, 0xe7, 0xef, 0x42, 0xcf, 0x7c, 0xf8, 0x83, 0x86, 0xc6, 0xca, 0x4b, 0x8f, 0x84,
0x86, 0x57, 0x6a, 0xfb, 0xec, 0xad, 0x45, 0x3d, 0x73, 0x18, 0xf4, 0x63, 0x58, 0x32, 0xee, 0x91,
0x8e, 0x67, 0xf1, 0x48, 0xab, 0x4e, 0xf5, 0x6e, 0x7a, 0x58, 0xe7, 0x5b, 0xf0, 0x26, 0x67, 0xbc,
0x82, 0x2d, 0xc6, 0x4c, 0x6d, 0x1e, 0x42, 0xd7, 0xbc, 0xa3, 0x7a, 0x07, 0xdf, 0x4d, 0xa3, 0xcb,
0xbc, 0x2d, 0xbe, 0xed, 0xa0, 0xbf, 0x70, 0xa0, 0x67, 0x3e, 0x59, 0x40, 0x56, 0x7e, 0x5d, 0xe2,
0x33, 0x30, 0xfb, 0x4c, 0x46, 0xf8, 0x19, 0x9f, 0xe4, 0xc1, 0xf6, 0x13, 0x4b, 0xc8, 0x6f, 0xac,
0x98, 0x61, 0xc7, 0x7c, 0xaa, 0xfc, 0xb6, 0xdc, 0x69, 0xde, 0xdd, 0xbf, 0xbd, 0xed, 0xa0, 0x7b,
0xe2, 0x41, 0xba, 0x0a, 0x79, 0x91, 0x61, 0xd6, 0xca, 0xe2, 0x32, 0x5f, 0x79, 0x6f, 0x39, 0xb7,
0x1d, 0xf4, 0x7b, 0xe2, 0x75, 0xb2, 0xfc, 0x96, 0x4b, 0xfd, 0x43, 0xbf, 0xc7, 0x37, 0xf9, 0x4a,
0xae, 0xe1, 0xcb, 0xd6, 0x4a, 0xca, 0x76, 0xfd, 0x08, 0xa0, 0xc8, 0x5f, 0x50, 0x29, 0x98, 0xd7,
0x16, 0xaf, 0x9a, 0xe2, 0xd8, 0xbb, 0xa9, 0x62, 0x7e, 0x61, 0x04, 0x7a, 0x46, 0xe6, 0x90, 0xeb,
0xed, 0xac, 0xe6, 0x21, 0xc3, 0x61, 0x5d, 0x97, 0xe4, 0xff, 0x6d, 0xce, 0xff, 0x23, 0x74, 0xc5,
0xe4, 0xbf, 0xfb, 0xc6, 0xcc, 0x5b, 0xde, 0xa2, 0xaf, 0xa0, 0x7f, 0x98, 0x24, 0x2f, 0xa7, 0xa9,
0x4e, 0x4b, 0xed, 0x48, 0x9c, 0xe5, 0x4e, 0xc3, 0xd2, 0xa2, 0xf0, 0x0d, 0xce, 0xf9, 0x0a, 0xba,
0x6c, 0x73, 0x2e, 0xb2, 0xa9, 0xb7, 0xc8, 0x87, 0x15, 0xed, 0xed, 0xf4, 0x42, 0x86, 0x36, 0x1f,
0x33, 0xa9, 0xa9, 0x8c, 0x61, 0xc5, 0x1f, 0x7a, 0x8c, 0x5c, 0xf1, 0xbc, 0xed, 0xa0, 0x23, 0xe8,
0x3d, 0x22, 0xa3, 0x24, 0x20, 0x32, 0x7a, 0x5e, 0x2d, 0x66, 0xae, 0xa3, 0xee, 0x61, 0xdf, 0x02,
0x6d, 0x0b, 0x90, 0xfa, 0xb3, 0x8c, 0x7c, 0xbd, 0xfb, 0x46, 0x86, 0xe5, 0x6f, 0x95, 0x05, 0x50,
0xa9, 0x84, 0x65, 0x01, 0x4a, 0xb9, 0x87, 0x65, 0x01, 0x2a, 0xb9, 0x87, 0x65, 0x01, 0x54, 0x2a,
0x83, 0x22, 0x96, 0x91, 0x94, 0xd2, 0x15, 0xed, 0x33, 0x2f, 0x4a, 0x72, 0x86, 0xd7, 0x2f, 0x26,
0xb0, 0x47, 0xdb, 0xb6, 0x47, 0x3b, 0x86, 0xfe, 0x23, 0x22, 0x84, 0x25, 0xea, 0xc5, 0x43, 0xdb,
0xa4, 0x98, 0xb5, 0xe5, 0xb2, 0xb9, 0xe1, 0x7d, 0xb6, 0x81, 0xe7, 0xc5, 0x5a, 0xf4, 0x13, 0xe8,
0x3e, 0x25, 0x54, 0x15, 0x88, 0x75, 0xe4, 0x51, 0xaa, 0x18, 0x0f, 0x6b, 0xea, 0xcb, 0xf8, 0x3a,
0xe7, 0x36, 0x44, 0x03, 0xcd, 0x6d, 0x97, 0x04, 0x63, 0x22, 0x0e, 0xbf, 0x17, 0x06, 0x6f, 0xd1,
0x6f, 0x73, 0xe6, 0xfa, 0xf6, 0x68, 0xc3, 0xa8, 0x2b, 0x9a, 0xcc, 0x97, 0x4a, 0x78, 0x1d, 0xe7,
0x38, 0x09, 0x88, 0xe1, 0xea, 0x62, 0xe8, 0x1a, 0x57, 0x85, 0xfa, 0x40, 0x55, 0xef, 0x1f, 0xf5,
0x81, 0xaa, 0xb9, 0x59, 0xc4, 0x5b, 0x7c, 0x1c, 0x8c, 0xae, 0x17, 0xe3, 0x88, 0xdb, 0xc4, 0x62,
0xa4, 0xdd, 0x37, 0xfe, 0x84, 0xbe, 0x45, 0x2f, 0xf8, 0x33, 0x45, 0xb3, 0x08, 0x5e, 0x44, 0x3e,
0xe5, 0x7a, 0xb9, 0x16, 0x96, 0xd1, 0x65, 0x47, 0x43, 0x62, 0x28, 0xee, 0x11, 0xbf, 0x0f, 0x70,
0x4c, 0x93, 0xf4, 0x91, 0x4f, 0x26, 0x49, 0x5c, 0x58, 0xb2, 0xa2, 0xd0, 0x5b, 0x58, 0x32, 0xa3,
0xda, 0x8b, 0x5e, 0x18, 0xb1, 0xa7, 0x75, 0x87, 0xa0, 0x94, 0xeb, 0xc2, 0x5a, 0xb0, 0x16, 0x48,
0x4d, 0x3d, 0x58, 0x85, 0xa1, 0xa2, 0xc8, 0x65, 0x84, 0xa1, 0x56, 0x95, 0xcc, 0x08, 0x43, 0xed,
0x6a, 0x18, 0x0b, 0x43, 0x8b, 0xcc, 0x5a, 0x87, 0xa1, 0x95, 0xa4, 0x5d, 0xdb, 0xd0, 0x9a, 0x34,
0xfc, 0x08, 0x3a, 0x45, 0xae, 0xaa, 0x06, 0x2a, 0x67, 0xb6, 0xda, 0x59, 0x55, 0x52, 0x48, 0xbc,
0xcc, 0xe5, 0x0c, 0xa8, 0xcd, 0xe4, 0xcc, 0xaf, 0x4a, 0x9f, 0x03, 0x88, 0xd5, 0x3d, 0x61, 0x2d,
0x83, 0xa5, 0x95, 0x29, 0x9a, 0x2c, 0xed, 0x94, 0x4c, 0x45, 0x32, 0x58, 0xb3, 0xbc, 0xe7, 0x6c,
0x9f, 0xcc, 0xf3, 0xff, 0x5b, 0xfb, 0xde, 0x7f, 0x05, 0x00, 0x00, 0xff, 0xff, 0x99, 0xff, 0x53,
0x74, 0xe9, 0x36, 0x00, 0x00,
// 4580 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x5b, 0xdd, 0x6f, 0x1c, 0x4b,
0x56, 0x4f, 0x8f, 0x67, 0xec, 0x99, 0x33, 0x33, 0xfe, 0x28, 0x7f, 0x4d, 0x26, 0xd9, 0x6c, 0x52,
0x1b, 0xdd, 0x18, 0xef, 0xca, 0x4e, 0xbc, 0xec, 0x25, 0x9b, 0x00, 0x57, 0xce, 0xa7, 0x2f, 0xeb,
0x9b, 0xeb, 0x6d, 0xe7, 0xde, 0xc0, 0xae, 0x50, 0xd3, 0x9e, 0x2e, 0x8f, 0x7b, 0xd3, 0xd3, 0xdd,
0xb7, 0xbb, 0x26, 0xce, 0x6c, 0x14, 0x09, 0x5d, 0x10, 0xbc, 0x80, 0xf6, 0x61, 0x11, 0x08, 0x09,
0xa1, 0x95, 0x10, 0x8f, 0xf0, 0x0f, 0xf0, 0x1f, 0x20, 0x90, 0x90, 0xf6, 0x89, 0x17, 0x9e, 0xf8,
0x07, 0x78, 0xe0, 0x89, 0x17, 0x54, 0x9f, 0x5d, 0xd5, 0xdd, 0x4e, 0x82, 0x40, 0x3c, 0x79, 0xea,
0x57, 0xd5, 0xa7, 0xaa, 0x4e, 0x9d, 0x3a, 0x5f, 0x75, 0x0c, 0x9d, 0x2c, 0x1d, 0xed, 0xa4, 0x59,
0x42, 0x13, 0xd4, 0x8a, 0xe2, 0x2c, 0x1d, 0x0d, 0xaf, 0x8e, 0x93, 0x64, 0x1c, 0x91, 0x5d, 0x3f,
0x0d, 0x77, 0xfd, 0x38, 0x4e, 0xa8, 0x4f, 0xc3, 0x24, 0xce, 0xc5, 0x20, 0xfc, 0x1f, 0x0e, 0x74,
0x9f, 0x67, 0x7e, 0x9c, 0xfb, 0x23, 0x06, 0xa3, 0x01, 0x2c, 0xd0, 0xd7, 0xde, 0x99, 0x9f, 0x9f,
0x0d, 0x9c, 0xeb, 0xce, 0x56, 0xc7, 0x55, 0x4d, 0xb4, 0x01, 0xf3, 0xfe, 0x24, 0x99, 0xc6, 0x74,
0xd0, 0xb8, 0xee, 0x6c, 0xcd, 0xb9, 0xb2, 0x85, 0xbe, 0x03, 0x2b, 0xf1, 0x74, 0xe2, 0x8d, 0x92,
0xf8, 0x34, 0xcc, 0x26, 0x82, 0xf8, 0x60, 0xee, 0xba, 0xb3, 0xd5, 0x72, 0xab, 0x1d, 0xe8, 0x1a,
0xc0, 0x49, 0x94, 0x8c, 0x5e, 0x8a, 0x29, 0x9a, 0x7c, 0x0a, 0x03, 0x41, 0x18, 0x7a, 0xb2, 0x45,
0xc2, 0xf1, 0x19, 0x1d, 0xb4, 0x38, 0x21, 0x0b, 0x63, 0x34, 0x68, 0x38, 0x21, 0x5e, 0x4e, 0xfd,
0x49, 0x3a, 0x98, 0xe7, 0xab, 0x31, 0x10, 0xde, 0x9f, 0x50, 0x3f, 0xf2, 0x4e, 0x09, 0xc9, 0x07,
0x0b, 0xb2, 0x5f, 0x23, 0x78, 0x00, 0x1b, 0x4f, 0x09, 0x35, 0x76, 0x9d, 0xbb, 0xe4, 0xab, 0x29,
0xc9, 0x29, 0x3e, 0x04, 0x64, 0xc0, 0x8f, 0x08, 0xf5, 0xc3, 0x28, 0x47, 0x1f, 0x43, 0x8f, 0x1a,
0x83, 0x07, 0xce, 0xf5, 0xb9, 0xad, 0xee, 0x1e, 0xda, 0xe1, 0xfc, 0xdd, 0x31, 0x3e, 0x70, 0xad,
0x71, 0xf8, 0x5f, 0x1c, 0xe8, 0x1e, 0x93, 0x38, 0x90, 0xd4, 0x11, 0x82, 0x66, 0x40, 0x72, 0xca,
0x19, 0xdb, 0x73, 0xf9, 0x6f, 0xf4, 0x4d, 0xe8, 0xb2, 0xbf, 0x5e, 0x4e, 0xb3, 0x30, 0x1e, 0x73,
0xd6, 0x76, 0x5c, 0x60, 0xd0, 0x31, 0x47, 0xd0, 0x32, 0xcc, 0xf9, 0x13, 0xca, 0x19, 0x3a, 0xe7,
0xb2, 0x9f, 0xe8, 0x06, 0xf4, 0x52, 0x7f, 0x36, 0x21, 0x31, 0x2d, 0x98, 0xd8, 0x73, 0xbb, 0x12,
0x3b, 0x60, 0x5c, 0xdc, 0x81, 0x55, 0x73, 0x88, 0xa2, 0xde, 0xe2, 0xd4, 0x57, 0x8c, 0x91, 0x72,
0x92, 0x5b, 0xb0, 0xa4, 0xc6, 0x67, 0x62, 0xb1, 0x9c, 0xad, 0x1d, 0x77, 0x51, 0xc2, 0x8a, 0x41,
0x7f, 0xe6, 0x40, 0x4f, 0x6c, 0x29, 0x4f, 0x93, 0x38, 0x27, 0xe8, 0x26, 0xf4, 0xd5, 0x97, 0x24,
0xcb, 0x92, 0x4c, 0x4a, 0x8d, 0x0d, 0xa2, 0x6d, 0x58, 0x56, 0x40, 0x9a, 0x91, 0x70, 0xe2, 0x8f,
0x09, 0xdf, 0x6a, 0xcf, 0xad, 0xe0, 0x68, 0xaf, 0xa0, 0x98, 0x25, 0x53, 0x4a, 0xf8, 0xd6, 0xbb,
0x7b, 0x3d, 0xc9, 0x6e, 0x97, 0x61, 0xae, 0x3d, 0x04, 0x7f, 0xed, 0x40, 0xef, 0xe1, 0x99, 0x1f,
0xc7, 0x24, 0x3a, 0x4a, 0xc2, 0x98, 0x32, 0x31, 0x3a, 0x9d, 0xc6, 0x41, 0x18, 0x8f, 0x3d, 0xfa,
0x3a, 0x0c, 0x24, 0xcb, 0x2d, 0x8c, 0x2d, 0xca, 0x6c, 0x33, 0x26, 0x49, 0xfe, 0x57, 0x70, 0x46,
0x2f, 0x99, 0xd2, 0x74, 0x4a, 0xbd, 0x30, 0x0e, 0xc8, 0x6b, 0xbe, 0xa6, 0xbe, 0x6b, 0x61, 0xf8,
0x37, 0x61, 0xf9, 0x90, 0xc9, 0x67, 0x1c, 0xc6, 0xe3, 0xfd, 0x20, 0xc8, 0x48, 0x9e, 0xb3, 0x4b,
0x93, 0x4e, 0x4f, 0x5e, 0x92, 0x99, 0xe4, 0x8b, 0x6c, 0x31, 0x51, 0x38, 0x4b, 0x72, 0x2a, 0xe7,
0xe3, 0xbf, 0xf1, 0x2f, 0x1c, 0x58, 0x62, 0xbc, 0xfd, 0xcc, 0x8f, 0x67, 0x4a, 0x64, 0x0e, 0xa1,
0xc7, 0x48, 0x3d, 0x4f, 0xf6, 0xc5, 0xd5, 0x13, 0xa2, 0xb7, 0x25, 0x79, 0x51, 0x1a, 0xbd, 0x63,
0x0e, 0x7d, 0x1c, 0xd3, 0x6c, 0xe6, 0x5a, 0x5f, 0x0f, 0x3f, 0x81, 0x95, 0xca, 0x10, 0x26, 0x60,
0xc5, 0xfa, 0xd8, 0x4f, 0xb4, 0x06, 0xad, 0x57, 0x7e, 0x34, 0x25, 0xf2, 0xa2, 0x8b, 0xc6, 0xbd,
0xc6, 0x5d, 0x07, 0x7f, 0x04, 0xcb, 0xc5, 0x9c, 0x52, 0x02, 0x10, 0x34, 0x35, 0x8b, 0x3b, 0x2e,
0xff, 0xcd, 0x58, 0xc1, 0xc6, 0x3d, 0x4c, 0x42, 0x7d, 0xb7, 0xd8, 0x38, 0x3f, 0x08, 0x94, 0x80,
0xf0, 0xdf, 0x17, 0xe9, 0x14, 0x7c, 0x0b, 0x56, 0x8c, 0xef, 0xdf, 0x31, 0xd1, 0x5f, 0x3b, 0xb0,
0xf2, 0x8c, 0x9c, 0x4b, 0x76, 0xab, 0xa9, 0xee, 0x42, 0x93, 0xce, 0x52, 0xc2, 0x47, 0x2e, 0xee,
0xdd, 0x94, 0xdc, 0xaa, 0x8c, 0xdb, 0x91, 0xcd, 0xe7, 0xb3, 0x94, 0xb8, 0xfc, 0x0b, 0xfc, 0x39,
0x74, 0x0d, 0x10, 0x6d, 0xc2, 0xea, 0x8b, 0x4f, 0x9f, 0x3f, 0x7b, 0x7c, 0x7c, 0xec, 0x1d, 0x7d,
0xf1, 0xe0, 0x07, 0x8f, 0x7f, 0xc7, 0x3b, 0xd8, 0x3f, 0x3e, 0x58, 0xbe, 0x84, 0x36, 0x00, 0x3d,
0x7b, 0x7c, 0xfc, 0xfc, 0xf1, 0x23, 0x0b, 0x77, 0xd0, 0x12, 0x74, 0x4d, 0xa0, 0x81, 0x87, 0x30,
0x78, 0x46, 0xce, 0x5f, 0x84, 0x34, 0x26, 0x79, 0x6e, 0x4f, 0x8f, 0x77, 0x00, 0x99, 0x6b, 0x92,
0xdb, 0x1c, 0xc0, 0x82, 0x2f, 0x20, 0xa5, 0x81, 0x65, 0x13, 0x7f, 0x04, 0xe8, 0x38, 0x1c, 0xc7,
0x9f, 0x91, 0x3c, 0xf7, 0xc7, 0x44, 0x6d, 0x76, 0x19, 0xe6, 0x26, 0xf9, 0x58, 0x4a, 0x38, 0xfb,
0x89, 0xbf, 0x0b, 0xab, 0xd6, 0x38, 0x49, 0xf8, 0x2a, 0x74, 0xf2, 0x70, 0x1c, 0xfb, 0x74, 0x9a,
0x11, 0x49, 0xba, 0x00, 0xf0, 0x13, 0x58, 0xfb, 0x92, 0x64, 0xe1, 0xe9, 0xec, 0x7d, 0xe4, 0x6d,
0x3a, 0x8d, 0x32, 0x9d, 0xc7, 0xb0, 0x5e, 0xa2, 0x23, 0xa7, 0x17, 0x52, 0x25, 0xcf, 0xaf, 0xed,
0x8a, 0x86, 0x71, 0x41, 0x1a, 0xe6, 0x05, 0xc1, 0x5f, 0x00, 0x7a, 0x98, 0xc4, 0x31, 0x19, 0xd1,
0x23, 0x42, 0x32, 0xb5, 0x98, 0x6f, 0x1b, 0x32, 0xd4, 0xdd, 0xdb, 0x94, 0x07, 0x5b, 0xbe, 0x75,
0x52, 0xb8, 0x10, 0x34, 0x53, 0x92, 0x4d, 0x38, 0xe1, 0xb6, 0xcb, 0x7f, 0xe3, 0x5d, 0x58, 0xb5,
0xc8, 0x16, 0x3c, 0x4f, 0x09, 0xc9, 0x3c, 0xb9, 0xba, 0x96, 0xab, 0x9a, 0xf8, 0x0e, 0xac, 0x3f,
0x0a, 0xf3, 0x51, 0x75, 0x29, 0xec, 0x93, 0xe9, 0x89, 0x57, 0x5c, 0x1d, 0xd5, 0x64, 0xe6, 0xa5,
0xfc, 0x89, 0x98, 0x06, 0xff, 0x91, 0x03, 0xcd, 0x83, 0xe7, 0x87, 0x0f, 0xd1, 0x10, 0xda, 0x61,
0x3c, 0x4a, 0x26, 0x4c, 0x29, 0x0b, 0x76, 0xe8, 0xf6, 0x85, 0x76, 0xf6, 0x2a, 0x74, 0xb8, 0x2e,
0x67, 0x96, 0x90, 0xeb, 0x9f, 0x9e, 0x5b, 0x00, 0xcc, 0x0a, 0x93, 0xd7, 0x69, 0x98, 0x71, 0x33,
0xab, 0x8c, 0x67, 0x93, 0x6b, 0xa9, 0x6a, 0x07, 0xfe, 0xa7, 0x26, 0xf4, 0xf7, 0x47, 0x34, 0x7c,
0x45, 0xa4, 0xd6, 0xe4, 0xb3, 0x72, 0x40, 0xae, 0x47, 0xb6, 0x98, 0x7e, 0xcf, 0xc8, 0x24, 0xa1,
0xc4, 0xb3, 0x8e, 0xc9, 0x06, 0xd9, 0xa8, 0x91, 0x20, 0xe4, 0xa5, 0x4c, 0xff, 0xf2, 0xf5, 0x75,
0x5c, 0x1b, 0x64, 0x2c, 0x63, 0x00, 0xe3, 0x32, 0x5b, 0x59, 0xd3, 0x55, 0x4d, 0xc6, 0x8f, 0x91,
0x9f, 0xfa, 0xa3, 0x90, 0xce, 0xb8, 0x91, 0x9a, 0x73, 0x75, 0x9b, 0xd1, 0x8e, 0x92, 0x91, 0x1f,
0x79, 0x27, 0x7e, 0xe4, 0xc7, 0x23, 0x22, 0x0d, 0xbe, 0x0d, 0xa2, 0x8f, 0x60, 0x51, 0x2e, 0x49,
0x0d, 0x13, 0x76, 0xbf, 0x84, 0x32, 0xdf, 0x60, 0x94, 0x4c, 0x26, 0x21, 0x65, 0xae, 0xc0, 0xa0,
0x2d, 0x7c, 0x83, 0x02, 0xe1, 0x3b, 0x11, 0xad, 0x73, 0xc1, 0xc3, 0x8e, 0x98, 0xcd, 0x02, 0x19,
0x95, 0x53, 0x42, 0xbc, 0x94, 0x64, 0xde, 0xcb, 0xf3, 0x01, 0x08, 0x2a, 0x05, 0xc2, 0x4e, 0x63,
0x1a, 0xe7, 0x84, 0xd2, 0x88, 0x04, 0x7a, 0x41, 0x5d, 0x3e, 0xac, 0xda, 0x81, 0x6e, 0xc3, 0xaa,
0xf0, 0x4e, 0x72, 0x9f, 0x26, 0xf9, 0x59, 0x98, 0x7b, 0x39, 0x89, 0xe9, 0xa0, 0xc7, 0xc7, 0xd7,
0x75, 0xa1, 0xbb, 0xb0, 0x59, 0x82, 0x33, 0x32, 0x22, 0xe1, 0x2b, 0x12, 0x0c, 0xfa, 0xfc, 0xab,
0x8b, 0xba, 0xd1, 0x75, 0xe8, 0x32, 0xa7, 0x6c, 0x9a, 0x06, 0x3e, 0x25, 0xf9, 0x60, 0x91, 0x9f,
0x83, 0x09, 0xa1, 0x3b, 0xd0, 0x4f, 0x89, 0x30, 0x7f, 0x67, 0x34, 0x1a, 0xe5, 0x83, 0x25, 0x6e,
0x73, 0xba, 0xf2, 0xb2, 0x31, 0xf9, 0x75, 0xed, 0x11, 0x78, 0x1d, 0x56, 0x0f, 0xc3, 0x9c, 0x4a,
0x59, 0xd2, 0xfa, 0xed, 0x00, 0xd6, 0x6c, 0x58, 0xde, 0xb6, 0xdb, 0xd0, 0x96, 0x82, 0x91, 0x0f,
0xba, 0x9c, 0xf8, 0x9a, 0x24, 0x6e, 0xc9, 0xa4, 0xab, 0x47, 0xe1, 0x3f, 0x6c, 0x40, 0x93, 0xdd,
0xa4, 0x8b, 0x6f, 0x9d, 0x79, 0x85, 0x1b, 0xd6, 0x15, 0x36, 0x15, 0xea, 0x9c, 0xa5, 0x50, 0xb9,
0x33, 0x3a, 0xa3, 0x44, 0xf2, 0x5b, 0xc8, 0xa4, 0x81, 0x14, 0xfd, 0x19, 0x19, 0xbd, 0xe2, 0x82,
0xa9, 0xfb, 0x19, 0xc2, 0xc4, 0x36, 0xf7, 0xa9, 0xf8, 0x5a, 0x48, 0xa5, 0x6e, 0xab, 0x3e, 0xfe,
0xe5, 0x42, 0xd1, 0xc7, 0xbf, 0x1b, 0xc0, 0x42, 0x18, 0x9f, 0x24, 0xd3, 0x38, 0xe0, 0x12, 0xd8,
0x76, 0x55, 0x93, 0x5d, 0xf2, 0x94, 0x3b, 0x1e, 0xe1, 0x84, 0x48, 0xd1, 0x2b, 0x00, 0x8c, 0x98,
0x87, 0x91, 0x73, 0x9d, 0xa2, 0x99, 0xfc, 0x31, 0xac, 0x18, 0x98, 0xe4, 0xf0, 0x0d, 0x68, 0xb1,
0xdd, 0x2b, 0x57, 0x55, 0x9d, 0x1d, 0x57, 0x46, 0xa2, 0x07, 0x2f, 0xc3, 0xe2, 0x53, 0x42, 0x3f,
0x8d, 0x4f, 0x13, 0x45, 0xe9, 0x3f, 0x1b, 0xb0, 0xa4, 0x21, 0x49, 0x68, 0x0b, 0x96, 0xc2, 0x80,
0xc4, 0x34, 0xa4, 0x33, 0xcf, 0x72, 0x64, 0xca, 0x30, 0x53, 0xef, 0x7e, 0x14, 0xfa, 0xb9, 0x54,
0x10, 0xa2, 0x81, 0xf6, 0x60, 0x8d, 0xc9, 0x96, 0x12, 0x17, 0x7d, 0xec, 0xc2, 0x7f, 0xaa, 0xed,
0x63, 0xd7, 0x81, 0xe1, 0x42, 0x01, 0x15, 0x9f, 0x08, 0x65, 0x56, 0xd7, 0xc5, 0xb8, 0x26, 0x28,
0xb1, 0x2d, 0xb7, 0xf8, 0xb8, 0x02, 0xa8, 0x84, 0x14, 0xf3, 0xc2, 0x77, 0x2b, 0x87, 0x14, 0x46,
0x58, 0xd2, 0xae, 0x84, 0x25, 0x5b, 0xb0, 0x94, 0xcf, 0xe2, 0x11, 0x09, 0x3c, 0x9a, 0xb0, 0x79,
0xc3, 0x98, 0x9f, 0x4e, 0xdb, 0x2d, 0xc3, 0x3c, 0x80, 0x22, 0x39, 0x8d, 0x09, 0xe5, 0x7a, 0xa1,
0xed, 0xaa, 0x26, 0x53, 0xb1, 0x7c, 0x88, 0x10, 0xfa, 0x8e, 0x2b, 0x5b, 0xf8, 0xa7, 0xdc, 0xd4,
0xe9, 0x18, 0xe9, 0x0b, 0x7e, 0x0f, 0xd1, 0x15, 0xe8, 0x88, 0xf9, 0xf3, 0x33, 0x5f, 0x5a, 0xdf,
0x36, 0x07, 0x8e, 0xcf, 0x7c, 0x16, 0x02, 0x58, 0x5b, 0x12, 0x12, 0xdf, 0xe5, 0xd8, 0x81, 0xd8,
0xd1, 0x4d, 0x58, 0x54, 0xd1, 0x57, 0xee, 0x45, 0xe4, 0x94, 0x2a, 0x9f, 0x35, 0x9e, 0x4e, 0xd8,
0x74, 0xf9, 0x21, 0x39, 0xa5, 0xf8, 0x19, 0xac, 0xc8, 0xdb, 0xf6, 0x79, 0x4a, 0xd4, 0xd4, 0xdf,
0x2f, 0x6b, 0x73, 0x61, 0x6e, 0x57, 0xa5, 0x14, 0x99, 0x8e, 0x76, 0x49, 0xc5, 0x63, 0x17, 0x90,
0xec, 0x7e, 0x18, 0x25, 0x39, 0x91, 0x04, 0x31, 0xf4, 0x46, 0x51, 0x92, 0x97, 0xbd, 0x71, 0x13,
0x63, 0x7c, 0xcb, 0xa7, 0xa3, 0x11, 0xbb, 0xa5, 0xc2, 0x60, 0xab, 0x26, 0x26, 0xb0, 0xca, 0x89,
0x29, 0xb5, 0xa0, 0x9d, 0xbc, 0x0f, 0x5f, 0x65, 0x6f, 0x64, 0x06, 0x07, 0x6b, 0xd0, 0x3a, 0x4d,
0xb2, 0x11, 0x91, 0x13, 0x89, 0x06, 0xfe, 0x57, 0x07, 0x56, 0xf8, 0x3c, 0xc7, 0xd4, 0xa7, 0xd3,
0x5c, 0x2e, 0xfd, 0xd7, 0xa1, 0xcf, 0x96, 0x49, 0x94, 0x98, 0xca, 0x59, 0xd6, 0xf4, 0x8d, 0xe2,
0xa8, 0x18, 0x7c, 0x70, 0xc9, 0xb5, 0x07, 0xa3, 0x4f, 0xa0, 0x67, 0x86, 0xbf, 0x7c, 0xc2, 0xee,
0xde, 0x65, 0xb5, 0xc4, 0xca, 0xa9, 0x1f, 0x5c, 0x72, 0xad, 0x0f, 0xd0, 0x7d, 0x00, 0x6e, 0x23,
0x39, 0x59, 0x19, 0x09, 0x5d, 0xb6, 0x77, 0x68, 0x30, 0xfa, 0xe0, 0x92, 0x6b, 0x0c, 0x7f, 0xd0,
0x86, 0x79, 0xa1, 0xd4, 0xf1, 0x53, 0xe8, 0x5b, 0x2b, 0xb5, 0x7c, 0xe9, 0x9e, 0xf0, 0xa5, 0x2b,
0x31, 0x4e, 0xa3, 0x26, 0xc6, 0xf9, 0x37, 0x07, 0x10, 0x93, 0x94, 0xd2, 0x59, 0x7c, 0x04, 0x8b,
0xd4, 0xcf, 0xc6, 0x84, 0x7a, 0xb6, 0x1b, 0x55, 0x42, 0xb9, 0xf5, 0x49, 0x02, 0xcb, 0x97, 0xe8,
0xb9, 0x26, 0x84, 0x76, 0x00, 0x19, 0x4d, 0x15, 0xb8, 0x0a, 0xbd, 0x5d, 0xd3, 0xc3, 0x14, 0x8c,
0x70, 0x04, 0x54, 0xc8, 0x26, 0x7d, 0xa7, 0x26, 0xd7, 0x9d, 0xb5, 0x7d, 0x4c, 0x35, 0xa7, 0x53,
0x16, 0x15, 0xfb, 0x54, 0x79, 0x1b, 0xaa, 0x8d, 0x7f, 0xe9, 0xc0, 0x32, 0xdb, 0xa0, 0x25, 0x04,
0xf7, 0x80, 0x0b, 0xd0, 0x07, 0xca, 0x80, 0x35, 0xf6, 0x7f, 0x2f, 0x02, 0x77, 0xa1, 0xc3, 0x09,
0x26, 0x29, 0x89, 0xa5, 0x04, 0x0c, 0x6c, 0x09, 0x28, 0xae, 0xee, 0xc1, 0x25, 0xb7, 0x18, 0x6c,
0x9c, 0xff, 0x26, 0xac, 0xcb, 0x55, 0xda, 0x07, 0x87, 0xff, 0x18, 0x60, 0xa3, 0xdc, 0xa3, 0xad,
0xb4, 0x74, 0x3d, 0xa2, 0x70, 0x72, 0x92, 0x68, 0x2f, 0xc6, 0x31, 0xbd, 0x12, 0xab, 0x0b, 0x9d,
0xc2, 0xba, 0x52, 0xe6, 0x6c, 0xfe, 0x42, 0x75, 0x37, 0xb8, 0x15, 0xba, 0x6d, 0xf3, 0xab, 0x34,
0x9f, 0x82, 0x4d, 0xe9, 0xaa, 0x27, 0x87, 0xc6, 0x30, 0xd0, 0x46, 0x43, 0xaa, 0x10, 0xc3, 0xb0,
0xb0, 0xa9, 0xbe, 0xfd, 0xee, 0xa9, 0xf8, 0x95, 0x09, 0x14, 0x7a, 0x21, 0x31, 0xf4, 0x1a, 0xae,
0xa9, 0x3e, 0xae, 0x23, 0xaa, 0xd3, 0x35, 0x3f, 0x64, 0x67, 0x4f, 0xd8, 0xb7, 0xf6, 0x9c, 0xef,
0xa1, 0x3b, 0xfc, 0x47, 0x07, 0x16, 0x6d, 0x6a, 0xcc, 0x04, 0x49, 0x5f, 0x56, 0x5d, 0x03, 0x65,
0x8a, 0x4b, 0x70, 0xd5, 0x1b, 0x6f, 0xd4, 0x79, 0xe3, 0xa6, 0xcf, 0x3d, 0xf7, 0x3e, 0x9f, 0xbb,
0xf9, 0x61, 0x3e, 0x77, 0xab, 0xce, 0xe7, 0x1e, 0xfe, 0xa2, 0x01, 0xa8, 0x7a, 0xba, 0xe8, 0x89,
0x08, 0x07, 0x62, 0x12, 0xc9, 0x0b, 0xf5, 0x9d, 0x0f, 0x12, 0x10, 0x05, 0xab, 0x8f, 0x99, 0xa0,
0x9a, 0x17, 0xc6, 0xb4, 0x89, 0x7d, 0xb7, 0xae, 0x0b, 0x6d, 0xc3, 0x32, 0x37, 0x95, 0xb9, 0x47,
0xc3, 0x28, 0x2a, 0x6e, 0x56, 0xdf, 0xad, 0xe0, 0xa5, 0x80, 0xa1, 0xf9, 0xfe, 0x80, 0xa1, 0xf5,
0xfe, 0x80, 0x61, 0xbe, 0x1c, 0x30, 0x0c, 0xdf, 0x40, 0xdf, 0x12, 0x90, 0xff, 0x33, 0xe6, 0x94,
0x4d, 0xaf, 0x10, 0x05, 0x0b, 0x1b, 0x7e, 0xdd, 0x00, 0x54, 0x95, 0xd1, 0xff, 0xcf, 0x25, 0x70,
0x81, 0xb3, 0xd4, 0xcc, 0x9c, 0x14, 0x38, 0x4b, 0xc1, 0x6c, 0xc1, 0xd2, 0xc4, 0xa7, 0xd3, 0x8c,
0xb9, 0x9d, 0x56, 0x88, 0x5b, 0x86, 0x99, 0x4c, 0x14, 0x27, 0xe9, 0xa9, 0x5e, 0xe9, 0x1b, 0xd6,
0x75, 0xe1, 0xef, 0xc3, 0xda, 0x0b, 0x3f, 0x8a, 0x08, 0x7d, 0x20, 0x26, 0x53, 0xa6, 0xed, 0x06,
0xf4, 0xce, 0x45, 0xf6, 0xc6, 0x4b, 0xe2, 0x68, 0x26, 0xc3, 0xe3, 0xae, 0xc4, 0x3e, 0x8f, 0xa3,
0x19, 0xbe, 0x03, 0xeb, 0xa5, 0x4f, 0x8b, 0xb4, 0x82, 0xad, 0x36, 0x55, 0x93, 0x29, 0x64, 0xc9,
0x27, 0x7b, 0x3a, 0xbc, 0x07, 0x1b, 0xe5, 0x8e, 0xf7, 0x12, 0xfb, 0x04, 0xd0, 0x0f, 0xa7, 0x24,
0x9b, 0xf1, 0xd4, 0xa8, 0x4e, 0x82, 0x6d, 0x96, 0x43, 0xa5, 0xf9, 0x74, 0x7a, 0xf2, 0x03, 0x32,
0x53, 0x19, 0xe5, 0x86, 0xce, 0x28, 0xe3, 0xfb, 0xb0, 0x6a, 0x11, 0xd0, 0xb9, 0xdd, 0x79, 0x9e,
0x5e, 0x55, 0x61, 0x84, 0x9d, 0x82, 0x95, 0x7d, 0xf8, 0x2f, 0x1c, 0x98, 0x3b, 0x48, 0x52, 0x33,
0xba, 0x77, 0xec, 0xe8, 0x5e, 0xea, 0x23, 0x4f, 0xab, 0x9b, 0x86, 0xbc, 0x22, 0x26, 0xc8, 0xb4,
0x89, 0x3f, 0xa1, 0xcc, 0x91, 0x3e, 0x4d, 0xb2, 0x73, 0x3f, 0x0b, 0xa4, 0x0c, 0x94, 0x50, 0xb6,
0xfc, 0xe2, 0x26, 0xb2, 0x9f, 0xcc, 0xb1, 0xe6, 0x29, 0x0e, 0x75, 0xbe, 0xb2, 0x85, 0x7f, 0xe6,
0x40, 0x8b, 0xaf, 0x95, 0x09, 0x8e, 0x30, 0x58, 0xfc, 0x95, 0x80, 0x67, 0x50, 0x1c, 0x21, 0x38,
0x25, 0xb8, 0xf4, 0x76, 0xd0, 0x28, 0xbf, 0x1d, 0xb0, 0x50, 0x43, 0xb4, 0x8a, 0xa4, 0x7c, 0x01,
0xa0, 0x6b, 0xd0, 0x3c, 0x4b, 0x52, 0x65, 0x16, 0x40, 0x85, 0xcc, 0x49, 0xea, 0x72, 0x1c, 0x6f,
0xc3, 0xd2, 0xb3, 0x24, 0x20, 0x46, 0xd4, 0x75, 0xe1, 0x31, 0xe1, 0xdf, 0x77, 0xa0, 0xad, 0x06,
0xa3, 0x2d, 0x68, 0x32, 0xf5, 0x5e, 0xf2, 0x3c, 0x74, 0xe2, 0x8b, 0x8d, 0x73, 0xf9, 0x08, 0x76,
0xdb, 0xb8, 0xdf, 0x5f, 0xd8, 0x5e, 0xe5, 0xf5, 0x17, 0x76, 0x8d, 0xb9, 0x6b, 0x7c, 0xcd, 0x25,
0x03, 0x50, 0x42, 0xf1, 0xcf, 0x1d, 0xe8, 0x5b, 0x73, 0x30, 0x07, 0x2e, 0xf2, 0x73, 0x2a, 0x93,
0x05, 0x92, 0x89, 0x26, 0x64, 0x46, 0xe8, 0x0d, 0x3b, 0x42, 0xd7, 0x11, 0xe2, 0x9c, 0x19, 0x21,
0xde, 0x86, 0x8e, 0x0c, 0xc7, 0x89, 0xe2, 0x9b, 0x7a, 0x59, 0x61, 0x33, 0xaa, 0x94, 0x5e, 0x31,
0x08, 0xdf, 0x87, 0xae, 0xd1, 0xc3, 0x26, 0x8c, 0x09, 0x3d, 0x4f, 0xb2, 0x97, 0x2a, 0x25, 0x20,
0x9b, 0x3a, 0xe3, 0xdc, 0x28, 0x32, 0xce, 0xf8, 0xef, 0x1c, 0xe8, 0x33, 0x99, 0x08, 0xe3, 0xf1,
0x51, 0x12, 0x85, 0xa3, 0x19, 0x97, 0x0d, 0x75, 0xfc, 0x5e, 0x40, 0x22, 0xea, 0x6b, 0xd9, 0xb0,
0x61, 0x66, 0x31, 0x27, 0x61, 0xcc, 0x73, 0x1e, 0x52, 0x32, 0x74, 0x9b, 0xc9, 0x38, 0x53, 0xe7,
0x27, 0x7e, 0x4e, 0xbc, 0x09, 0x73, 0x2c, 0xa5, 0x02, 0xb3, 0x40, 0xa6, 0x96, 0x18, 0x90, 0xf9,
0x94, 0x78, 0x93, 0x30, 0x8a, 0x42, 0x31, 0x56, 0xc8, 0x72, 0x5d, 0x17, 0xfe, 0x87, 0x06, 0x74,
0xa5, 0x42, 0x78, 0x1c, 0x8c, 0x45, 0xfe, 0x4a, 0x9a, 0x71, 0x7d, 0xd1, 0x0c, 0x44, 0xf5, 0x5b,
0x86, 0xdf, 0x40, 0xca, 0x07, 0x38, 0x57, 0x3d, 0x40, 0x16, 0x4c, 0x27, 0x01, 0xb9, 0xc3, 0x3d,
0x0c, 0xf1, 0x40, 0x57, 0x00, 0xaa, 0x77, 0x8f, 0xf7, 0xb6, 0x8a, 0x5e, 0x0e, 0x58, 0x3e, 0xc5,
0x7c, 0xc9, 0xa7, 0xb8, 0x0b, 0x3d, 0x49, 0x86, 0xf3, 0x9d, 0x27, 0x45, 0x0a, 0x51, 0xb6, 0xce,
0xc4, 0xb5, 0x46, 0xaa, 0x2f, 0xf7, 0xd4, 0x97, 0xed, 0xf7, 0x7d, 0xa9, 0x46, 0xe2, 0x75, 0x58,
0x95, 0xcc, 0x7b, 0x9a, 0xf9, 0xe9, 0x99, 0x52, 0xb2, 0x81, 0x7e, 0x2d, 0xe2, 0x30, 0xda, 0x86,
0x16, 0xfb, 0x4c, 0xe9, 0xb9, 0xfa, 0xeb, 0x25, 0x86, 0xa0, 0x2d, 0x68, 0x91, 0x60, 0x4c, 0x94,
0x53, 0x8b, 0x6c, 0x57, 0x9c, 0x9d, 0x91, 0x2b, 0x06, 0xb0, 0xcb, 0xce, 0xd0, 0xd2, 0x65, 0xb7,
0x75, 0xe4, 0x3c, 0x6b, 0x7e, 0x1a, 0xe0, 0x35, 0x40, 0xcf, 0x84, 0xd4, 0x9a, 0x19, 0x99, 0x3f,
0x98, 0x83, 0xae, 0x01, 0xb3, 0x7b, 0x3b, 0x66, 0x0b, 0xf6, 0x82, 0xd0, 0x9f, 0x10, 0x4a, 0x32,
0x29, 0xa9, 0x25, 0x94, 0xab, 0xd2, 0x57, 0x63, 0x2f, 0x99, 0x52, 0x2f, 0x20, 0xe3, 0x8c, 0x88,
0x48, 0xd7, 0x71, 0x4b, 0x28, 0x1b, 0x37, 0xf1, 0x5f, 0x9b, 0xe3, 0x84, 0x3c, 0x94, 0x50, 0x95,
0x5f, 0x11, 0x3c, 0x6a, 0x16, 0xf9, 0x15, 0xc1, 0x91, 0xb2, 0xc6, 0x69, 0xd5, 0x68, 0x9c, 0x8f,
0x61, 0x43, 0xe8, 0x16, 0x79, 0x37, 0xbd, 0x92, 0x98, 0x5c, 0xd0, 0xcb, 0x3c, 0x35, 0xb6, 0x66,
0x25, 0xe0, 0x79, 0xf8, 0x53, 0x91, 0xd8, 0x75, 0xdc, 0x0a, 0xce, 0xc6, 0xb2, 0xeb, 0x68, 0x8d,
0x15, 0x09, 0xde, 0x0a, 0xce, 0xc7, 0xfa, 0xaf, 0xed, 0xb1, 0x1d, 0x39, 0xb6, 0x84, 0xe3, 0x3e,
0x74, 0x8f, 0x69, 0x92, 0xaa, 0x43, 0x59, 0x84, 0x9e, 0x68, 0xca, 0xa4, 0xfe, 0x15, 0xb8, 0xcc,
0xa5, 0xe8, 0x79, 0x92, 0x26, 0x51, 0x32, 0x9e, 0x1d, 0x4f, 0x4f, 0xf2, 0x51, 0x16, 0xa6, 0xcc,
0xe1, 0xc4, 0xff, 0xec, 0xc0, 0xaa, 0xd5, 0x2b, 0x23, 0xca, 0x5f, 0x15, 0x22, 0xad, 0xf3, 0xb0,
0x42, 0xf0, 0x56, 0x0c, 0xc5, 0x27, 0x06, 0x8a, 0xe0, 0xf8, 0x0b, 0x99, 0x9a, 0xdd, 0x87, 0x25,
0xb5, 0x32, 0xf5, 0xa1, 0x90, 0xc2, 0x41, 0x55, 0x0a, 0xe5, 0xf7, 0x8b, 0xf2, 0x03, 0x45, 0xe2,
0x37, 0x84, 0x33, 0x46, 0x02, 0xbe, 0x47, 0x15, 0x2f, 0x0d, 0xd5, 0xf7, 0xa6, 0x03, 0xa8, 0x56,
0x30, 0xd2, 0x60, 0x8e, 0xff, 0xc4, 0x01, 0x28, 0x56, 0xc7, 0x04, 0xa3, 0x50, 0xde, 0x0e, 0xcf,
0x6a, 0x15, 0x00, 0x73, 0x9d, 0x74, 0x96, 0xb0, 0xb0, 0x07, 0x5d, 0x85, 0x31, 0x5f, 0xe4, 0x16,
0x2c, 0x8d, 0xa3, 0xe4, 0x84, 0x5b, 0x57, 0xfe, 0x7e, 0x94, 0xcb, 0xa7, 0x8d, 0x45, 0x01, 0x3f,
0x91, 0x68, 0x61, 0x3c, 0x9a, 0x86, 0xf1, 0xc0, 0x7f, 0xda, 0xd0, 0xf9, 0xab, 0x62, 0xcf, 0x17,
0xde, 0x32, 0xb4, 0x57, 0x51, 0x8e, 0x17, 0xe4, 0x8b, 0x78, 0x10, 0x7d, 0xf4, 0xde, 0x30, 0xe9,
0x3e, 0x2c, 0x66, 0x42, 0xfb, 0x28, 0xd5, 0xd4, 0x7c, 0x87, 0x6a, 0xea, 0x67, 0x96, 0xdd, 0xf9,
0x15, 0x58, 0xf6, 0x83, 0x57, 0x24, 0xa3, 0x21, 0x77, 0x83, 0xb9, 0x79, 0x17, 0x0a, 0x75, 0xc9,
0xc0, 0xb9, 0xd5, 0xbd, 0x05, 0x4b, 0xf2, 0x39, 0x49, 0x8f, 0x94, 0xcf, 0xf3, 0x05, 0xcc, 0x06,
0xe2, 0xbf, 0x71, 0x64, 0xae, 0xcc, 0x3e, 0xc3, 0x8b, 0x39, 0x62, 0xee, 0xae, 0x51, 0xda, 0xdd,
0xb7, 0x64, 0xea, 0x2b, 0x50, 0xbe, 0xb6, 0x4c, 0x20, 0x0a, 0x50, 0xa6, 0x19, 0x6d, 0x96, 0x36,
0x3f, 0x84, 0xa5, 0x78, 0x07, 0x96, 0x8e, 0x09, 0xdd, 0x67, 0x27, 0xa8, 0x14, 0xe3, 0x15, 0xe8,
0xc4, 0xe4, 0xdc, 0x13, 0x47, 0x2c, 0xcc, 0x78, 0x3b, 0x26, 0xe7, 0x7c, 0x0c, 0x46, 0xb0, 0x5c,
0x8c, 0x97, 0xb7, 0xee, 0xbf, 0x1a, 0xb0, 0xf0, 0x69, 0xfc, 0x2a, 0x09, 0x47, 0x3c, 0x99, 0x35,
0x21, 0x93, 0x44, 0x3d, 0x0c, 0xb3, 0xdf, 0xcc, 0x2b, 0xe0, 0x6f, 0x1e, 0x29, 0x95, 0x59, 0x26,
0xd5, 0x64, 0x16, 0x32, 0x2b, 0xaa, 0x10, 0x84, 0xb4, 0x19, 0x08, 0xf3, 0x26, 0x33, 0xb3, 0xb0,
0x42, 0xb6, 0x8a, 0x57, 0xf1, 0x96, 0xf1, 0x2a, 0xce, 0xd3, 0x96, 0xe2, 0x39, 0x87, 0x1f, 0x49,
0xdb, 0x55, 0x4d, 0xee, 0xf5, 0x66, 0x44, 0xc4, 0x9d, 0xdc, 0xd6, 0x2e, 0x48, 0xaf, 0xd7, 0x04,
0x99, 0x3d, 0x16, 0x1f, 0x88, 0x31, 0x42, 0x5f, 0x99, 0x10, 0xf3, 0x4f, 0xca, 0xb5, 0x19, 0x1d,
0x21, 0x26, 0x25, 0x98, 0x29, 0xb5, 0x80, 0x68, 0xdd, 0x23, 0xf6, 0x00, 0xa2, 0xca, 0xa2, 0x8c,
0x1b, 0x3e, 0xb3, 0x78, 0x96, 0x92, 0x2d, 0xee, 0xc7, 0xf8, 0x51, 0x74, 0xe2, 0x8f, 0x5e, 0x7a,
0xdc, 0x79, 0xea, 0x89, 0xdc, 0x81, 0x05, 0xe2, 0x2f, 0x01, 0xed, 0x07, 0x81, 0xe4, 0xbf, 0x8e,
0x17, 0x0a, 0xce, 0x39, 0x16, 0xe7, 0x6a, 0x76, 0xd0, 0xa8, 0xdd, 0x01, 0x7e, 0x0c, 0xdd, 0x23,
0xa3, 0x8c, 0x85, 0x1f, 0x95, 0x2a, 0x60, 0x91, 0xc7, 0x6b, 0x20, 0xc6, 0x84, 0x0d, 0x73, 0x42,
0xfc, 0x6b, 0x80, 0x0e, 0xc3, 0x9c, 0xea, 0xf5, 0xe9, 0x48, 0x4e, 0xe7, 0x93, 0x8c, 0x48, 0x4e,
0x62, 0x3c, 0x92, 0xdb, 0x17, 0x0f, 0x59, 0xe5, 0x8d, 0x6d, 0x43, 0x3b, 0x14, 0x90, 0xd2, 0xd4,
0x8b, 0x52, 0xc4, 0xd5, 0x48, 0xdd, 0xcf, 0x5c, 0x0e, 0x09, 0x5a, 0x86, 0xe0, 0x67, 0x0e, 0x2c,
0xc8, 0xad, 0x31, 0x83, 0x69, 0x15, 0xf0, 0x88, 0x8d, 0x59, 0x58, 0x7d, 0x0d, 0x46, 0x55, 0xa6,
0xe6, 0xea, 0x64, 0x0a, 0x41, 0x33, 0xf5, 0xe9, 0x19, 0xf7, 0xa6, 0x3b, 0x2e, 0xff, 0xad, 0xa2,
0xa6, 0x96, 0x8e, 0x9a, 0xd4, 0xa3, 0x9d, 0x5c, 0x94, 0x7e, 0x4f, 0x7a, 0x20, 0x1e, 0xed, 0x0a,
0xb8, 0xe0, 0x81, 0x5c, 0x60, 0x99, 0x07, 0x72, 0xa8, 0xab, 0xfb, 0xf1, 0x10, 0x06, 0x8f, 0x48,
0x44, 0x28, 0xd9, 0x8f, 0xa2, 0x32, 0xfd, 0x2b, 0x70, 0xb9, 0xa6, 0x4f, 0xde, 0xea, 0x27, 0xb0,
0xf2, 0x88, 0x9c, 0x4c, 0xc7, 0x87, 0xe4, 0x55, 0x91, 0x5c, 0x46, 0xd0, 0xcc, 0xcf, 0x92, 0x73,
0x79, 0x5e, 0xfc, 0x37, 0xfa, 0x06, 0x40, 0xc4, 0xc6, 0x78, 0x79, 0x4a, 0x46, 0xaa, 0x08, 0x81,
0x23, 0xc7, 0x29, 0x19, 0xe1, 0x8f, 0x01, 0x99, 0x74, 0xe4, 0x16, 0xd8, 0x5d, 0x9b, 0x9e, 0x78,
0xf9, 0x2c, 0xa7, 0x64, 0xa2, 0xd4, 0x8c, 0x09, 0xe1, 0x5b, 0xd0, 0x3b, 0xf2, 0x67, 0x2e, 0xf9,
0x4a, 0xd6, 0x45, 0xb1, 0xe0, 0xcc, 0x9f, 0x31, 0xf1, 0xd4, 0xc1, 0x19, 0xef, 0xc6, 0x7f, 0xd5,
0x80, 0x79, 0x31, 0x92, 0x51, 0x0d, 0x48, 0x4e, 0xc3, 0x58, 0xe4, 0x77, 0x25, 0x55, 0x03, 0xaa,
0x9c, 0x77, 0xa3, 0xe6, 0xbc, 0xa5, 0x13, 0xa5, 0x1e, 0x6c, 0xe5, 0xc1, 0x5a, 0x18, 0x8f, 0x3d,
0xc3, 0x09, 0x11, 0x65, 0x6f, 0x4d, 0x19, 0x7b, 0x2a, 0xa0, 0x14, 0x05, 0x17, 0x37, 0x5a, 0xac,
0x4f, 0x09, 0xa2, 0x34, 0x1c, 0x26, 0x54, 0xab, 0x37, 0x16, 0x44, 0x21, 0x54, 0x45, 0x6f, 0x54,
0xf4, 0x43, 0xbb, 0x4e, 0x3f, 0x20, 0x58, 0x7e, 0x42, 0x88, 0x4b, 0xd2, 0x24, 0xd3, 0xa5, 0x63,
0x7f, 0xe9, 0xc0, 0xb2, 0xb4, 0x08, 0xba, 0x0f, 0xdd, 0xb0, 0xcc, 0x87, 0x53, 0x97, 0xa7, 0xbc,
0x09, 0x7d, 0x1e, 0x40, 0xb1, 0xe8, 0x88, 0x47, 0x4b, 0x32, 0x7b, 0x60, 0x81, 0x6c, 0x97, 0x2a,
0x9d, 0x36, 0x09, 0x23, 0xc9, 0x3e, 0x13, 0x62, 0xa6, 0x4e, 0x05, 0x58, 0x9c, 0x79, 0x8e, 0xab,
0xdb, 0xf8, 0x08, 0x56, 0x8c, 0xf5, 0x4a, 0x71, 0xb9, 0x0f, 0xea, 0xd9, 0x48, 0x24, 0x03, 0x84,
0xd4, 0x6f, 0xda, 0xc6, 0xad, 0xf8, 0xcc, 0x1a, 0x8c, 0xff, 0xde, 0xe1, 0x2c, 0x90, 0x3e, 0x94,
0xae, 0x19, 0x99, 0x17, 0x6e, 0x8d, 0x90, 0xe5, 0x83, 0x4b, 0xae, 0x6c, 0xa3, 0xef, 0x7d, 0xa0,
0x67, 0xa2, 0x5f, 0x78, 0x2e, 0xe0, 0xcd, 0x5c, 0x1d, 0x6f, 0xde, 0xb1, 0xf3, 0x07, 0x0b, 0xd0,
0xca, 0x47, 0x49, 0x4a, 0xf0, 0x2a, 0x67, 0x81, 0x5a, 0xaf, 0x60, 0xc1, 0xde, 0xdf, 0x5e, 0x81,
0x8e, 0x8e, 0x82, 0xd0, 0x4f, 0xa0, 0x6f, 0xe5, 0xb9, 0xd0, 0x15, 0xb9, 0xc2, 0xba, 0xc4, 0xd9,
0xf0, 0x6a, 0x7d, 0xa7, 0xbc, 0xe9, 0xd7, 0xbe, 0xfe, 0xe5, 0xbf, 0xff, 0xbc, 0x31, 0x40, 0x1b,
0xbb, 0xaf, 0xee, 0xec, 0xca, 0x44, 0xd6, 0x2e, 0xcf, 0xcb, 0x89, 0x67, 0xd4, 0x97, 0xb0, 0x68,
0xe7, 0xc1, 0xd0, 0x55, 0x9b, 0x1d, 0xa5, 0xd9, 0xbe, 0x71, 0x41, 0xaf, 0x9c, 0xee, 0x2a, 0x9f,
0x6e, 0x03, 0xad, 0x99, 0xd3, 0xe9, 0xe8, 0x84, 0xf0, 0x87, 0x6f, 0xb3, 0x20, 0x14, 0x29, 0x7a,
0xf5, 0x85, 0xa2, 0xc3, 0xcb, 0xd5, 0xe2, 0x4f, 0x59, 0x2d, 0x8a, 0x07, 0x7c, 0x2a, 0x84, 0x96,
0xd9, 0x54, 0x66, 0x3d, 0x28, 0xfa, 0x31, 0x74, 0x74, 0x55, 0x1b, 0xda, 0x34, 0x6a, 0xf8, 0xcc,
0x3a, 0xb9, 0xe1, 0xa0, 0xda, 0xa1, 0x22, 0x0d, 0x4e, 0x79, 0x1d, 0x57, 0x28, 0xdf, 0x73, 0xb6,
0xd1, 0x21, 0xac, 0x4b, 0x83, 0x73, 0x42, 0xfe, 0x27, 0x3b, 0xa9, 0x29, 0x63, 0xbd, 0xed, 0xa0,
0xfb, 0xd0, 0x56, 0x85, 0x7e, 0x68, 0xa3, 0xbe, 0xda, 0x70, 0xb8, 0x59, 0xc1, 0xe5, 0xc5, 0xd9,
0x07, 0x28, 0xea, 0xda, 0xd0, 0xe0, 0xa2, 0xf2, 0x3b, 0xcd, 0xc4, 0x9a, 0x22, 0xb8, 0x31, 0x2f,
0xeb, 0xb3, 0xcb, 0xe6, 0xd0, 0x37, 0x8b, 0xf1, 0xb5, 0x05, 0x75, 0xef, 0x20, 0x88, 0x37, 0x38,
0xef, 0x96, 0xd1, 0x22, 0xe3, 0x5d, 0x4c, 0xce, 0x55, 0x09, 0xc8, 0x23, 0xe8, 0x1a, 0xb5, 0x72,
0x48, 0x51, 0xa8, 0xd6, 0xd9, 0x0d, 0x87, 0x75, 0x5d, 0x72, 0xb9, 0xbf, 0x05, 0x7d, 0xab, 0xe8,
0x4d, 0xdf, 0x8c, 0xba, 0x92, 0x3a, 0x7d, 0x33, 0xea, 0xeb, 0xe4, 0x7e, 0x04, 0x5d, 0xa3, 0x44,
0x0d, 0x19, 0x2f, 0x85, 0xa5, 0x12, 0x34, 0xbd, 0xa2, 0x9a, 0x8a, 0x36, 0xbc, 0xc6, 0xf7, 0xbb,
0x88, 0x3b, 0x6c, 0xbf, 0xbc, 0x0e, 0x82, 0x09, 0xc9, 0x4f, 0x60, 0xd1, 0x2e, 0x4d, 0xd3, 0xb7,
0xaa, 0xb6, 0xc8, 0x4d, 0xdf, 0xaa, 0x0b, 0xea, 0xd9, 0xa4, 0x40, 0x6e, 0xaf, 0xea, 0x49, 0x76,
0xdf, 0xc8, 0x6c, 0xdf, 0x5b, 0xf4, 0x43, 0xa6, 0x3a, 0x64, 0x61, 0x0a, 0x2a, 0x4a, 0xf5, 0xec,
0xf2, 0x15, 0x2d, 0xed, 0x95, 0x1a, 0x16, 0xbc, 0xc2, 0x89, 0x77, 0x51, 0xb1, 0x03, 0xf4, 0x19,
0x2c, 0xc8, 0x02, 0x15, 0xb4, 0x5e, 0x48, 0xb5, 0x91, 0x31, 0x19, 0x6e, 0x94, 0x61, 0x49, 0x6c,
0x95, 0x13, 0xeb, 0xa3, 0x2e, 0x23, 0x36, 0x26, 0x34, 0x64, 0x34, 0x22, 0x58, 0xb2, 0xdf, 0x2c,
0x72, 0xcd, 0x8e, 0xda, 0xd7, 0x52, 0xcd, 0x8e, 0xfa, 0x07, 0x10, 0x5b, 0xc9, 0x28, 0xe5, 0xb2,
0xab, 0x1e, 0x82, 0x7f, 0x17, 0x7a, 0x66, 0x35, 0x14, 0x1a, 0x1a, 0x3b, 0x2f, 0x55, 0x4e, 0x0d,
0xaf, 0xd4, 0xf6, 0xd9, 0x47, 0x8b, 0x7a, 0xe6, 0x34, 0xe8, 0x47, 0xb0, 0x64, 0x3c, 0xae, 0x1d,
0xcf, 0xe2, 0x91, 0x16, 0x9d, 0xea, 0x83, 0xfd, 0xb0, 0xce, 0xb6, 0xe0, 0x4d, 0x4e, 0x78, 0x05,
0x5b, 0x84, 0x99, 0xd8, 0x3c, 0x84, 0xae, 0xf9, 0x70, 0xf7, 0x0e, 0xba, 0x9b, 0x46, 0x97, 0xf9,
0x84, 0x7e, 0xdb, 0x41, 0x7f, 0xee, 0x40, 0xcf, 0xac, 0xe3, 0x40, 0x56, 0xd2, 0xa1, 0x44, 0x67,
0x60, 0xf6, 0x99, 0x84, 0xf0, 0x33, 0xbe, 0xc8, 0x83, 0xed, 0x27, 0x16, 0x93, 0xdf, 0x58, 0x3e,
0xc3, 0x8e, 0x59, 0xbf, 0xfd, 0xb6, 0xdc, 0x69, 0x16, 0x34, 0xbc, 0xbd, 0xed, 0xa0, 0x7b, 0xa2,
0x4a, 0x5f, 0x79, 0xe7, 0xc8, 0x50, 0x6b, 0x65, 0x76, 0x99, 0xa5, 0xef, 0x5b, 0xce, 0x6d, 0x07,
0xfd, 0x9e, 0x28, 0xd9, 0x96, 0xdf, 0x72, 0xae, 0x7f, 0xe8, 0xf7, 0xf8, 0x26, 0xdf, 0xc9, 0x35,
0x7c, 0xd9, 0xda, 0x49, 0x59, 0xaf, 0x1f, 0x01, 0x14, 0xa1, 0x16, 0x2a, 0xc5, 0x1d, 0x5a, 0xe3,
0x55, 0xa3, 0x31, 0xfb, 0x34, 0x55, 0x78, 0x22, 0x94, 0x40, 0xcf, 0x08, 0x72, 0x72, 0x7d, 0x9c,
0xd5, 0x90, 0x69, 0x38, 0xac, 0xeb, 0x92, 0xf4, 0xbf, 0xc5, 0xe9, 0x7f, 0x03, 0x5d, 0x31, 0xe9,
0xef, 0xbe, 0x31, 0x43, 0xac, 0xb7, 0xe8, 0x4b, 0xe8, 0x1f, 0x26, 0xc9, 0xcb, 0x69, 0xaa, 0x63,
0x75, 0x3b, 0x68, 0x60, 0x61, 0xde, 0xb0, 0xb4, 0x29, 0x7c, 0x83, 0x53, 0xbe, 0x82, 0x2e, 0xdb,
0x94, 0x8b, 0xc0, 0xef, 0x2d, 0xf2, 0x61, 0x45, 0x5b, 0x3b, 0xbd, 0x91, 0xa1, 0x4d, 0xc7, 0x8c,
0xbf, 0x2a, 0x73, 0x58, 0xfe, 0x87, 0x9e, 0x23, 0x57, 0x34, 0x6f, 0x3b, 0xe8, 0x08, 0x7a, 0x8f,
0xc8, 0x28, 0x09, 0x88, 0xf4, 0xf3, 0x57, 0x8b, 0x95, 0xeb, 0x00, 0x61, 0xd8, 0xb7, 0x40, 0x5b,
0x03, 0xa4, 0xfe, 0x2c, 0x23, 0x5f, 0xed, 0xbe, 0x91, 0x11, 0xc4, 0x5b, 0xa5, 0x01, 0x54, 0xd4,
0x63, 0x69, 0x80, 0x52, 0x98, 0x64, 0x69, 0x80, 0x4a, 0x98, 0x64, 0x69, 0x00, 0x15, 0x75, 0xa1,
0x88, 0x05, 0x4f, 0xa5, 0xc8, 0x4a, 0xdb, 0xcc, 0x8b, 0xe2, 0xb1, 0xe1, 0xf5, 0x8b, 0x07, 0xd8,
0xb3, 0x6d, 0xdb, 0xb3, 0x1d, 0x43, 0xff, 0x11, 0x11, 0xcc, 0x12, 0x49, 0xf4, 0xa1, 0xad, 0x52,
0xcc, 0x84, 0x7b, 0x59, 0xdd, 0xf0, 0x3e, 0x5b, 0xc1, 0xf3, 0x0c, 0x36, 0xfa, 0x31, 0x74, 0x9f,
0x12, 0xaa, 0xb2, 0xe6, 0xda, 0xf3, 0x28, 0xa5, 0xd1, 0x87, 0x35, 0x49, 0x77, 0x7c, 0x9d, 0x53,
0x1b, 0xa2, 0x81, 0xa6, 0xb6, 0x4b, 0x82, 0x31, 0x11, 0x97, 0xdf, 0x0b, 0x83, 0xb7, 0xe8, 0xb7,
0x39, 0x71, 0xfd, 0xa4, 0xb6, 0x61, 0x24, 0x5b, 0x4d, 0xe2, 0x4b, 0x25, 0xbc, 0x8e, 0x72, 0x9c,
0x04, 0xc4, 0x30, 0x75, 0x31, 0x74, 0x8d, 0xf7, 0x53, 0x7d, 0xa1, 0xaa, 0x8f, 0xb2, 0xfa, 0x42,
0xd5, 0x3c, 0xb7, 0xe2, 0x2d, 0x3e, 0x0f, 0x46, 0xd7, 0x8b, 0x79, 0xc4, 0x13, 0x6b, 0x31, 0xd3,
0xee, 0x1b, 0x7f, 0x42, 0xdf, 0xa2, 0x17, 0xbc, 0x76, 0xd3, 0x7c, 0x19, 0x28, 0x3c, 0x9f, 0xf2,
0x23, 0x82, 0x66, 0x96, 0xd1, 0x65, 0x7b, 0x43, 0x62, 0x2a, 0x6e, 0x11, 0xbf, 0x07, 0x70, 0x4c,
0x93, 0xf4, 0x91, 0x4f, 0x26, 0x49, 0x5c, 0x68, 0xb2, 0x22, 0xfb, 0x5d, 0x68, 0x32, 0x23, 0x05,
0x8e, 0x5e, 0x18, 0xbe, 0xa7, 0xf5, 0xb0, 0xa2, 0x84, 0xeb, 0xc2, 0x04, 0xb9, 0x66, 0x48, 0x4d,
0x92, 0x5c, 0xb9, 0xa1, 0x22, 0xf3, 0x67, 0xb8, 0xa1, 0x56, 0xea, 0xd0, 0x70, 0x43, 0xed, 0x14,
0x21, 0x73, 0x43, 0x8b, 0x24, 0x80, 0x76, 0x43, 0x2b, 0xf9, 0x05, 0xad, 0x43, 0x6b, 0x32, 0x06,
0x47, 0xd0, 0x29, 0x62, 0x55, 0x35, 0x51, 0x39, 0xb2, 0xd5, 0xc6, 0xaa, 0x12, 0x42, 0xe2, 0x65,
0xce, 0x67, 0x40, 0x6d, 0xc6, 0x67, 0xfe, 0x7e, 0xfc, 0x1c, 0x40, 0xec, 0xee, 0x09, 0x6b, 0x19,
0x24, 0xad, 0x48, 0xd1, 0x24, 0x69, 0x87, 0x64, 0xca, 0x93, 0xc1, 0x9a, 0xe4, 0x3d, 0x67, 0xfb,
0x64, 0x9e, 0xff, 0x33, 0xdf, 0x77, 0xff, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x45, 0xd4, 0x32, 0xab,
0xfe, 0x37, 0x00, 0x00,
}

@ -1128,7 +1128,12 @@ message SetAliasResponse {
}
message Invoice {
/// An optional memo to attach along with the invoice
/**
An optional memo to attach along with the invoice. Used for record keeping
purposes for the invoice's creator, and will also be set in the description
field of the encoded payment request if the description_hash field is not
being used.
*/
string memo = 1 [json_name = "memo"];
/// An optional cryptographic receipt of payment
@ -1161,6 +1166,19 @@ message Invoice {
payment to the recipient.
*/
string payment_request = 9 [json_name = "payment_request"];
/**
Hash (SHA-256) of a description of the payment. Used if the description of
payment (memo) is too long to naturally fit within the description field
of an encoded payment request.
*/
bytes description_hash = 10 [json_name = "description_hash"];
/// Payment request expiry time in seconds. Default is 3600 (1 hour).
int64 expiry = 11 [json_name = "expiry"];
/// Fallback on-chain address.
string fallback_addr = 12 [json_name = "fallback_addr"];
}
message AddInvoiceResponse {
bytes r_hash = 1 [json_name = "r_hash"];
@ -1240,6 +1258,11 @@ message PayReq {
string destination = 1 [json_name = "destination"];
string payment_hash = 2 [json_name = "payment_hash"];
int64 num_satoshis = 3 [json_name = "num_satoshis"];
int64 timestamp = 4 [json_name = "timestamp"];
int64 expiry = 5 [json_name = "expiry"];
string description = 6 [json_name = "description"];
string description_hash = 7 [json_name = "description_hash"];
string fallback_addr = 8 [json_name = "fallback_addr"];
}
message FeeReportRequest {}

@ -1245,7 +1245,7 @@
"properties": {
"memo": {
"type": "string",
"title": "/ An optional memo to attach along with the invoice"
"description": "*\nAn optional memo to attach along with the invoice. Used for record keeping\npurposes for the invoice's creator, and will also be set in the description\nfield of the encoded payment request if the description_hash field is not\nbeing used."
},
"receipt": {
"type": "string",
@ -1285,6 +1285,20 @@
"payment_request": {
"type": "string",
"description": "*\nA bare-bones invoice for a payment within the Lightning Network. With the\ndetails of the invoice, the sender has all the data necessary to send a\npayment to the recipient."
},
"description_hash": {
"type": "string",
"format": "byte",
"description": "*\nHash (SHA-256) of a description of the payment. Used if the description of\npayment (memo) is too long to naturally fit within the description field\nof an encoded payment request."
},
"expiry": {
"type": "string",
"format": "int64",
"description": "/ Payment request expiry time in seconds. Default is 3600 (1 hour)."
},
"fallback_addr": {
"type": "string",
"description": "/ Fallback on-chain address."
}
}
},
@ -1524,6 +1538,23 @@
"num_satoshis": {
"type": "string",
"format": "int64"
},
"timestamp": {
"type": "string",
"format": "int64"
},
"expiry": {
"type": "string",
"format": "int64"
},
"description": {
"type": "string"
},
"description_hash": {
"type": "string"
},
"fallback_addr": {
"type": "string"
}
}
},

@ -53,18 +53,24 @@ func (n *nodeSigner) SignMessage(pubKey *btcec.PublicKey,
// resident node's private key. The returned signature is a pubkey-recoverable
// signature.
func (n *nodeSigner) SignCompact(msg []byte) ([]byte, error) {
// Otherwise, we'll sign the dsha256 of the target message.
// We'll sign the dsha256 of the target message.
digest := chainhash.DoubleHashB(msg)
return n.SignDigestCompact(digest)
}
// SignDigestCompact signs the provided message digest under the resident
// node's private key. The returned signature is a pubkey-recoverable signature.
func (n *nodeSigner) SignDigestCompact(hash []byte) ([]byte, error) {
// Should the signature reference a compressed public key or not.
isCompressedKey := true
// btcec.SignCompact returns a pubkey-recoverable signature
sig, err := btcec.SignCompact(btcec.S256(), n.privKey, digest,
sig, err := btcec.SignCompact(btcec.S256(), n.privKey, hash,
isCompressedKey)
if err != nil {
return nil, fmt.Errorf("can't sign the message: %v", err)
return nil, fmt.Errorf("can't sign the hash: %v", err)
}
return sig, nil

@ -1372,6 +1372,18 @@ func (r *rpcServer) savePayment(route *routing.Route, amount lnwire.MilliSatoshi
return r.server.chanDB.AddPayment(payment)
}
// validatePayReqExpiry checks if the passed payment request has expired. In
// the case it has expired, an error will be returned.
func validatePayReqExpiry(payReq *zpay32.Invoice) error {
expiry := payReq.Expiry()
validUntil := payReq.Timestamp.Add(expiry)
if time.Now().After(validUntil) {
return fmt.Errorf("invoice expired. Valid until %v", validUntil)
}
return nil
}
// SendPayment dispatches a bi-directional streaming RPC for sending payments
// through the Lightning Network. A single RPC invocation creates a persistent
// bi-directional stream allowing clients to rapidly send payments through the
@ -1385,8 +1397,15 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
}
}
// For each payment we need to know the msat amount, the destination
// public key, and the payment hash.
type payment struct {
msat lnwire.MilliSatoshi
dest []byte
pHash []byte
}
payChan := make(chan *payment)
errChan := make(chan error, 1)
payChan := make(chan *lnrpc.SendRequest)
// TODO(roasbeef): enforce fee limits, pass into router, ditch if exceed limit
// * limit either a %, or absolute, or iff more than sending
@ -1443,31 +1462,67 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
return
}
// Populate the next payment, either from the
// payment request, or from the explicitly set
// fields.
p := &payment{}
// If the payment request field isn't blank,
// then the details of the invoice are encoded
// entirely within the encode payReq. So we'll
// entirely within the encoded payReq. So we'll
// attempt to decode it, populating the
// nextPayment accordingly.
// payment accordingly.
if nextPayment.PaymentRequest != "" {
payReq, err := zpay32.Decode(nextPayment.PaymentRequest)
if err != nil {
select {
case errChan <- err:
case <-reqQuit:
return
}
return
}
// TODO(roasbeef): eliminate necessary
// encode/decode
nextPayment.Dest = payReq.Destination.SerializeCompressed()
nextPayment.Amt = int64(payReq.Amount)
nextPayment.PaymentHash = payReq.PaymentHash[:]
// We first check that this payment
// request has not expired.
err = validatePayReqExpiry(payReq)
if err != nil {
select {
case errChan <- err:
case <-reqQuit:
}
return
}
p.dest = payReq.Destination.SerializeCompressed()
if payReq.MilliSat == nil {
err := fmt.Errorf("only payment" +
" requests specifying" +
" the amount are" +
" currently supported")
select {
case errChan <- err:
case <-reqQuit:
}
return
}
p.msat = *payReq.MilliSat
p.pHash = payReq.PaymentHash[:]
} else {
// If the payment request field was not
// specified, construct the payment from
// the other fields.
p.msat = lnwire.NewMSatFromSatoshis(
btcutil.Amount(nextPayment.Amt),
)
p.dest = nextPayment.Dest
p.pHash = nextPayment.PaymentHash
}
select {
case payChan <- nextPayment:
case payChan <- p:
case <-reqQuit:
return
}
@ -1479,20 +1534,17 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
select {
case err := <-errChan:
return err
case nextPayment := <-payChan:
case p := <-payChan:
// Currently, within the bootstrap phase of the
// network, we limit the largest payment size allotted
// to (2^32) - 1 mSAT or 4.29 million satoshis.
amt := btcutil.Amount(nextPayment.Amt)
amtMSat := lnwire.NewMSatFromSatoshis(amt)
if amtMSat > maxPaymentMSat {
if p.msat > maxPaymentMSat {
// In this case, we'll send an error to the
// caller, but continue our loop for the next
// payment.
pErr := fmt.Errorf("payment of %v is too "+
"large, max payment allowed is %v",
nextPayment.Amt,
maxPaymentMSat.ToSatoshis())
p.msat, maxPaymentMSat)
if err := paymentStream.Send(&lnrpc.SendResponse{
PaymentError: pErr.Error(),
@ -1504,8 +1556,7 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
// Parse the details of the payment which include the
// pubkey of the destination and the payment amount.
dest := nextPayment.Dest
destNode, err := btcec.ParsePubKey(dest, btcec.S256())
destNode, err := btcec.ParsePubKey(p.dest, btcec.S256())
if err != nil {
return err
}
@ -1514,10 +1565,10 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
// will pay to the same debug rHash. Otherwise, we pay
// to the rHash specified within the RPC request.
var rHash [32]byte
if cfg.DebugHTLC && len(nextPayment.PaymentHash) == 0 {
if cfg.DebugHTLC && len(p.pHash) == 0 {
rHash = debugHash
} else {
copy(rHash[:], nextPayment.PaymentHash)
copy(rHash[:], p.pHash)
}
// We launch a new goroutine to execute the current
@ -1539,7 +1590,7 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
// error.
payment := &routing.LightningPayment{
Target: destNode,
Amount: amtMSat,
Amount: p.msat,
PaymentHash: rHash,
}
preImage, route, err := r.server.chanRouter.SendPayment(payment)
@ -1558,7 +1609,7 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
// Save the completed payment to the database
// for record keeping purposes.
if err := r.savePayment(route, amtMSat, rHash[:]); err != nil {
if err := r.savePayment(route, p.msat, rHash[:]); err != nil {
errChan <- err
return
}
@ -1604,7 +1655,7 @@ func (r *rpcServer) SendPaymentSync(ctx context.Context,
var (
destPub *btcec.PublicKey
amt btcutil.Amount
amtMSat lnwire.MilliSatoshi
rHash [32]byte
)
@ -1615,9 +1666,20 @@ func (r *rpcServer) SendPaymentSync(ctx context.Context,
if err != nil {
return nil, err
}
// We first check that this payment request has not expired.
if err := validatePayReqExpiry(payReq); err != nil {
return nil, err
}
destPub = payReq.Destination
amt = payReq.Amount
rHash = payReq.PaymentHash
if payReq.MilliSat == nil {
return nil, fmt.Errorf("payment requests with no " +
"amount specified not currently supported")
}
amtMSat = *payReq.MilliSat
rHash = *payReq.PaymentHash
// Otherwise, the payment conditions have been manually
// specified in the proto.
@ -1645,13 +1707,14 @@ func (r *rpcServer) SendPaymentSync(ctx context.Context,
return nil, err
}
amt = btcutil.Amount(nextPayment.Amt)
amtMSat = lnwire.NewMSatFromSatoshis(
btcutil.Amount(nextPayment.Amt),
)
}
// Currently, within the bootstrap phase of the network, we limit the
// largest payment size allotted to (2^32) - 1 mSAT or 4.29 million
// satoshis.
amtMSat := lnwire.NewMSatFromSatoshis(amt)
if amtMSat > maxPaymentMSat {
return nil, fmt.Errorf("payment of %v is too large, max payment "+
"allowed is %v", nextPayment.Amt,
@ -1718,8 +1781,8 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
copy(paymentPreimage[:], invoice.RPreimage[:])
}
// The size of the memo and receipt attached must not exceed the
// maximum values for either of the fields.
// The size of the memo, receipt and description hash attached must not
// exceed the maximum values for either of the fields.
if len(invoice.Memo) > channeldb.MaxMemoSize {
return nil, fmt.Errorf("memo too large: %v bytes "+
"(maxsize=%v)", len(invoice.Memo), channeldb.MaxMemoSize)
@ -1728,6 +1791,10 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
return nil, fmt.Errorf("receipt too large: %v bytes "+
"(maxsize=%v)", len(invoice.Receipt), channeldb.MaxReceiptSize)
}
if len(invoice.DescriptionHash) > 0 && len(invoice.DescriptionHash) != 32 {
return nil, fmt.Errorf("description hash is %v bytes, must be %v",
len(invoice.DescriptionHash), channeldb.MaxPaymentRequestSize)
}
amt := btcutil.Amount(invoice.Value)
amtMSat := lnwire.NewMSatFromSatoshis(amt)
@ -1743,10 +1810,78 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
"payment allowed is %v", amt, maxPaymentMSat.ToSatoshis())
}
// Next, generate the payment hash itself from the preimage. This will
// be used by clients to query for the state of a particular invoice.
rHash := sha256.Sum256(paymentPreimage[:])
// We also create an encoded payment request which allows the
// caller to compactly send the invoice to the payer. We'll create a
// list of options to be added to the encoded payment request. For now
// we only support the required fields description/description_hash,
// expiry, fallback address, and the amount field.
var options []func(*zpay32.Invoice)
// Add the amount. This field is optional by the BOLT-11 format, but
// we require it for now.
options = append(options, zpay32.Amount(amtMSat))
// If specified, add a fallback address to the payment request.
if len(invoice.FallbackAddr) > 0 {
addr, err := btcutil.DecodeAddress(invoice.FallbackAddr,
activeNetParams.Params)
if err != nil {
return nil, fmt.Errorf("invalid fallback address: %v",
err)
}
options = append(options, zpay32.FallbackAddr(addr))
}
// If expiry is set, specify it. If it is not provided, no expiry time
// will be explicitly added to this payment request, which will imply
// the default 3600 seconds.
if invoice.Expiry > 0 {
exp := time.Duration(invoice.Expiry) * time.Second
options = append(options, zpay32.Expiry(exp))
}
// If the description hash is set, then we add it do the list of options.
// If not, use the memo field as the payment request description.
if len(invoice.DescriptionHash) > 0 {
var descHash [32]byte
copy(descHash[:], invoice.DescriptionHash[:])
options = append(options, zpay32.DescriptionHash(descHash))
} else {
// Use the memo field as the description. If this is not set
// this will just be an empty string.
options = append(options, zpay32.Description(invoice.Memo))
}
// Create and encode the payment request as a bech32 (zpay32) string.
creationDate := time.Now()
payReq, err := zpay32.NewInvoice(
activeNetParams.Params,
rHash,
creationDate,
options...,
)
if err != nil {
return nil, err
}
payReqString, err := payReq.Encode(
zpay32.MessageSigner{
SignCompact: r.server.nodeSigner.SignDigestCompact,
},
)
if err != nil {
return nil, err
}
i := &channeldb.Invoice{
CreationDate: time.Now(),
Memo: []byte(invoice.Memo),
Receipt: invoice.Receipt,
CreationDate: creationDate,
Memo: []byte(invoice.Memo),
Receipt: invoice.Receipt,
PaymentRequest: []byte(payReqString),
Terms: channeldb.ContractTerm{
Value: amtMSat,
},
@ -1763,24 +1898,53 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
return nil, err
}
// Next, generate the payment hash itself from the preimage. This will
// be used by clients to query for the state of a particular invoice.
rHash := sha256.Sum256(paymentPreimage[:])
// Finally we also create an encoded payment request which allows the
// caller to compactly send the invoice to the payer.
payReqString := zpay32.Encode(&zpay32.PaymentRequest{
Destination: r.server.identityPriv.PubKey(),
PaymentHash: rHash,
Amount: amt,
})
return &lnrpc.AddInvoiceResponse{
RHash: rHash[:],
PaymentRequest: payReqString,
}, nil
}
// createRPCInvoice creates an *lnrpc.Invoice from the *channeldb.Invoice.
func createRPCInvoice(invoice *channeldb.Invoice) (*lnrpc.Invoice, error) {
paymentRequest := string(invoice.PaymentRequest)
decoded, err := zpay32.Decode(paymentRequest)
if err != nil {
return nil, fmt.Errorf("unable to decode payment request: %v",
err)
}
descHash := []byte("")
if decoded.DescriptionHash != nil {
descHash = decoded.DescriptionHash[:]
}
fallbackAddr := ""
if decoded.FallbackAddr != nil {
fallbackAddr = decoded.FallbackAddr.String()
}
// Expiry time will default to 3600 seconds if not specified
// explicitly.
expiry := int64(decoded.Expiry().Seconds())
preimage := invoice.Terms.PaymentPreimage
satAmt := invoice.Terms.Value.ToSatoshis()
return &lnrpc.Invoice{
Memo: string(invoice.Memo[:]),
Receipt: invoice.Receipt[:],
RHash: decoded.PaymentHash[:],
RPreimage: preimage[:],
Value: int64(satAmt),
CreationDate: invoice.CreationDate.Unix(),
Settled: invoice.Terms.Settled,
PaymentRequest: paymentRequest,
DescriptionHash: descHash,
Expiry: expiry,
FallbackAddr: fallbackAddr,
}, nil
}
// LookupInvoice attemps to look up an invoice according to its payment hash.
// The passed payment hash *must* be exactly 32 bytes, if not an error is
// returned.
@ -1831,22 +1995,12 @@ func (r *rpcServer) LookupInvoice(ctx context.Context,
return spew.Sdump(invoice)
}))
preimage := invoice.Terms.PaymentPreimage
satAmt := invoice.Terms.Value.ToSatoshis()
return &lnrpc.Invoice{
Memo: string(invoice.Memo[:]),
Receipt: invoice.Receipt[:],
RHash: rHash,
RPreimage: preimage[:],
Value: int64(satAmt),
CreationDate: invoice.CreationDate.Unix(),
Settled: invoice.Terms.Settled,
PaymentRequest: zpay32.Encode(&zpay32.PaymentRequest{
Destination: r.server.identityPriv.PubKey(),
PaymentHash: sha256.Sum256(preimage[:]),
Amount: satAmt,
}),
}, nil
rpcInvoice, err := createRPCInvoice(invoice)
if err != nil {
return nil, err
}
return rpcInvoice, nil
}
// ListInvoices returns a list of all the invoices currently stored within the
@ -1869,26 +2023,13 @@ func (r *rpcServer) ListInvoices(ctx context.Context,
invoices := make([]*lnrpc.Invoice, len(dbInvoices))
for i, dbInvoice := range dbInvoices {
invoiceAmount := dbInvoice.Terms.Value.ToSatoshis()
paymentPreimge := dbInvoice.Terms.PaymentPreimage[:]
rHash := sha256.Sum256(paymentPreimge)
invoice := &lnrpc.Invoice{
Memo: string(dbInvoice.Memo[:]),
Receipt: dbInvoice.Receipt[:],
RHash: rHash[:],
RPreimage: paymentPreimge,
Value: int64(invoiceAmount),
Settled: dbInvoice.Terms.Settled,
CreationDate: dbInvoice.CreationDate.Unix(),
PaymentRequest: zpay32.Encode(&zpay32.PaymentRequest{
Destination: r.server.identityPriv.PubKey(),
PaymentHash: sha256.Sum256(paymentPreimge),
Amount: invoiceAmount,
}),
rpcInvoice, err := createRPCInvoice(dbInvoice)
if err != nil {
return nil, err
}
invoices[i] = invoice
invoices[i] = rpcInvoice
}
return &lnrpc.ListInvoiceResponse{
@ -1916,17 +2057,13 @@ func (r *rpcServer) SubscribeInvoices(req *lnrpc.InvoiceSubscription,
select {
// TODO(roasbeef): include newly added invoices?
case settledInvoice := <-invoiceClient.SettledInvoices:
preImage := settledInvoice.Terms.PaymentPreimage[:]
rHash := sha256.Sum256(preImage)
invoice := &lnrpc.Invoice{
Memo: string(settledInvoice.Memo[:]),
Receipt: settledInvoice.Receipt[:],
RHash: rHash[:],
RPreimage: preImage,
Value: int64(settledInvoice.Terms.Value.ToSatoshis()),
Settled: settledInvoice.Terms.Settled,
rpcInvoice, err := createRPCInvoice(settledInvoice)
if err != nil {
return err
}
if err := updateStream.Send(invoice); err != nil {
if err := updateStream.Send(rpcInvoice); err != nil {
return err
}
case <-r.quit:
@ -2713,11 +2850,41 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
return nil, err
}
// Let the fields default to empty strings.
desc := ""
if payReq.Description != nil {
desc = *payReq.Description
}
descHash := []byte("")
if payReq.DescriptionHash != nil {
descHash = payReq.DescriptionHash[:]
}
fallbackAddr := ""
if payReq.FallbackAddr != nil {
fallbackAddr = payReq.FallbackAddr.String()
}
// Expiry time will default to 3600 seconds if not specified
// explicitly.
expiry := int64(payReq.Expiry().Seconds())
amt := int64(0)
if payReq.MilliSat != nil {
amt = int64(payReq.MilliSat.ToSatoshis())
}
dest := payReq.Destination.SerializeCompressed()
return &lnrpc.PayReq{
Destination: hex.EncodeToString(dest),
PaymentHash: hex.EncodeToString(payReq.PaymentHash[:]),
NumSatoshis: int64(payReq.Amount),
Destination: hex.EncodeToString(dest),
PaymentHash: hex.EncodeToString(payReq.PaymentHash[:]),
NumSatoshis: amt,
Timestamp: payReq.Timestamp.Unix(),
Description: desc,
DescriptionHash: hex.EncodeToString(descHash[:]),
FallbackAddr: fallbackAddr,
Expiry: expiry,
}, nil
}

@ -1,4 +1,4 @@
package invoice
package zpay32
import (
"fmt"

@ -1,4 +1,4 @@
package invoice
package zpay32
import (
"fmt"

@ -1,4 +1,4 @@
package invoice
package zpay32
import (
"bytes"
@ -109,9 +109,13 @@ type Invoice struct {
// Optional. Non-nil iff Description is nil.
DescriptionHash *[32]byte
// Expiry specifies the timespan this invoice will be valid.
// expiry specifies the timespan this invoice will be valid.
// Optional. If not set, a default expiry of 60 min will be implied.
Expiry *time.Time
//
// This field is unexported and can be read by the Expiry() method. This
// method makes sure the default expiry time is returned in case the
// field is not set.
expiry *time.Duration
// FallbackAddr is an on-chain address that can be used for payment in
// case the Lightning payment fails.
@ -177,9 +181,9 @@ func DescriptionHash(descriptionHash [32]byte) func(*Invoice) {
// Expiry is a functional option that allows callers of NewInvoice to set the
// expiry of the created Invoice. If not set, a default expiry of 60 min will
// be implied.
func Expiry(expiry time.Time) func(*Invoice) {
func Expiry(expiry time.Duration) func(*Invoice) {
return func(i *Invoice) {
i.Expiry = &expiry
i.expiry = &expiry
}
}
@ -444,6 +448,17 @@ func (invoice *Invoice) Encode(signer MessageSigner) (string, error) {
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 {
if invoice.expiry != nil {
return *invoice.expiry
}
// If no expiry is set for this invoice, default is 3600 seconds.
return 3600 * time.Second
}
// validateInvoice does a sanity check of the provided Invoice, making sure it
// has all the necessary fields set for it to be considered valid by BOLT-0011.
func validateInvoice(invoice *Invoice) error {
@ -624,7 +639,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
copy(dHash[:], hash[:])
invoice.DescriptionHash = &dHash
case fieldTypeX:
if invoice.Expiry != nil {
if invoice.expiry != nil {
// We skip the field if we have already seen a
// supported one.
continue
@ -634,8 +649,8 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
if err != nil {
return err
}
unix := time.Unix(int64(exp), 0)
invoice.Expiry = &unix
dur := time.Duration(exp) * time.Second
invoice.expiry = &dur
case fieldTypeF:
if invoice.FallbackAddr != nil {
// We skip the field if we have already seen a
@ -783,9 +798,9 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
}
}
if invoice.Expiry != nil {
unix := invoice.Expiry.Unix()
expiry := uint64ToBase32(uint64(unix))
if invoice.expiry != nil {
seconds := invoice.expiry.Seconds()
expiry := uint64ToBase32(uint64(seconds))
err := writeTaggedField(bufferBase32, fieldTypeX, expiry)
if err != nil {
return err

@ -1,4 +1,4 @@
package invoice
package zpay32
import (
"testing"

@ -1,4 +1,4 @@
package invoice_test
package zpay32_test
import (
"bytes"
@ -8,8 +8,8 @@ import (
"testing"
"time"
"github.com/lightningnetwork/lnd/invoice"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/zpay32"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg"
"github.com/roasbeef/btcd/chaincfg/chainhash"
@ -29,7 +29,7 @@ var (
testMillisat2500uBTC = lnwire.MilliSatoshi(250000000)
testMillisat20mBTC = lnwire.MilliSatoshi(2000000000)
testExpiry60 = time.Unix(60, 0)
testExpiry60 = 60 * time.Second
testEmptyString = ""
testCupOfCoffee = "1 cup coffee"
testPleaseConsider = "Please consider supporting this project"
@ -45,7 +45,7 @@ var (
testPaymentHash [32]byte
testDescriptionHash [32]byte
testMessageSigner = invoice.MessageSigner{
testMessageSigner = zpay32.MessageSigner{
SignCompact: func(hash []byte) ([]byte, error) {
sig, err := btcec.SignCompact(btcec.S256(),
testPrivKey, hash, true)
@ -72,9 +72,9 @@ func TestDecodeEncode(t *testing.T) {
tests := []struct {
encodedInvoice string
valid bool
decodedInvoice *invoice.Invoice
decodedInvoice func() *zpay32.Invoice
skipEncoding bool
beforeEncoding func(*invoice.Invoice)
beforeEncoding func(*zpay32.Invoice)
}{
{
encodedInvoice: "asdsaddnasdnas", // no hrp
@ -112,51 +112,59 @@ func TestDecodeEncode(t *testing.T) {
// no payment hash set
encodedInvoice: "lnbc20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsjv38luh6p6s2xrv3mzvlmzaya43376h0twal5ax0k6p47498hp3hnaymzhsn424rxqjs0q7apn26yrhaxltq3vzwpqj9nc2r3kzwccsplnq470",
valid: false,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
}
},
},
{
// Both Description and DescriptionHash set.
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs03vghs8y0kuj4ulrzls8ln7fnm9dk7sjsnqmghql6hd6jut36clkqpyuq0s5m6fhureyz0szx2qjc8hkgf4xc2hpw8jpu26jfeyvf4cpga36gt",
valid: false,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
Description: &testPleaseConsider,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
Description: &testPleaseConsider,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
}
},
},
{
// Neither Description nor DescriptionHash set.
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqn2rne0kagfl4e0xag0w6hqeg2dwgc54hrm9m0auw52dhwhwcu559qav309h598pyzn69wh2nqauneyyesnpmaax0g6acr8lh9559jmcquyq5a9",
valid: false,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
Destination: testPubKey,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
Destination: testPubKey,
}
},
},
{
// Has a few unknown fields, should just be ignored.
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqtq2v93xxer9vczq8v93xxeqv72xr42ca60022jqu6fu73n453tmnr0ukc0pl0t23w7eavtensjz0j2wcu7nkxhfdgp9y37welajh5kw34mq7m4xuay0a72cwec8qwgqt5vqht",
valid: true,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
Description: &testPleaseConsider,
Destination: testPubKey,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
Description: &testPleaseConsider,
Destination: testPubKey,
}
},
skipEncoding: true, // Skip encoding since we don't have the unknown fields to encode.
},
@ -164,13 +172,15 @@ func TestDecodeEncode(t *testing.T) {
// Ignore unknown witness version in fallback address.
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpppw508d6qejxtdg4y5r3zarvary0c5xw7k8txqv6x0a75xuzp0zsdzk5hq6tmfgweltvs6jk5nhtyd9uqksvr48zga9mw08667w8264gkspluu66jhtcmct36nx363km6cquhhv2cpc6q43r",
valid: true,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
}
},
skipEncoding: true, // Skip encoding since we don't have the unknown fields to encode.
},
@ -178,13 +188,15 @@ func TestDecodeEncode(t *testing.T) {
// Ignore fields with unknown lengths.
encodedInvoice: "lnbc241pveeq09pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqpp3qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqshp38yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66np3q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfy8huflvs2zwkymx47cszugvzn5v64ahemzzlmm62rpn9l9rm05h35aceq00tkt296289wepws9jh4499wq2l0vk6xcxffd90dpuqchqqztyayq",
valid: true,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat24BTC,
Timestamp: time.Unix(1503429093, 0),
PaymentHash: &testPaymentHash,
Destination: testPubKey,
DescriptionHash: &testDescriptionHash,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat24BTC,
Timestamp: time.Unix(1503429093, 0),
PaymentHash: &testPaymentHash,
Destination: testPubKey,
DescriptionHash: &testDescriptionHash,
}
},
skipEncoding: true, // Skip encoding since we don't have the unknown fields to encode.
},
@ -192,14 +204,16 @@ func TestDecodeEncode(t *testing.T) {
// Please make a donation of any amount using rhash 0001020304050607080900010203040506070809000102030405060708090102 to me @03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad
encodedInvoice: "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w",
valid: true,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
Description: &testPleaseConsider,
Destination: testPubKey,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.MainNetParams,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
Description: &testPleaseConsider,
Destination: testPubKey,
}
},
beforeEncoding: func(i *invoice.Invoice) {
beforeEncoding: func(i *zpay32.Invoice) {
// Since this destination pubkey was recovered
// from the signature, we must set it nil before
// encoding to get back the same invoice string.
@ -210,29 +224,33 @@ func TestDecodeEncode(t *testing.T) {
// Same as above, pubkey set in 'n' field.
encodedInvoice: "lnbc241pveeq09pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66jd3m5klcwhq68vdsmx2rjgxeay5v0tkt2v5sjaky4eqahe4fx3k9sqavvce3capfuwv8rvjng57jrtfajn5dkpqv8yelsewtljwmmycq62k443",
valid: true,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat24BTC,
Timestamp: time.Unix(1503429093, 0),
PaymentHash: &testPaymentHash,
Destination: testPubKey,
Description: &testEmptyString,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat24BTC,
Timestamp: time.Unix(1503429093, 0),
PaymentHash: &testPaymentHash,
Destination: testPubKey,
Description: &testEmptyString,
}
},
},
{
// Please send $3 for a cup of coffee to the same peer, within 1 minute
encodedInvoice: "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp",
valid: true,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat2500uBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
Description: &testCupOfCoffee,
Destination: testPubKey,
Expiry: &testExpiry60,
decodedInvoice: func() *zpay32.Invoice {
i, _ := zpay32.NewInvoice(
&chaincfg.MainNetParams,
testPaymentHash,
time.Unix(1496314658, 0),
zpay32.Amount(testMillisat2500uBTC),
zpay32.Description(testCupOfCoffee),
zpay32.Destination(testPubKey),
zpay32.Expiry(testExpiry60))
return i
},
beforeEncoding: func(i *invoice.Invoice) {
beforeEncoding: func(i *zpay32.Invoice) {
// Since this destination pubkey was recovered
// from the signature, we must set it nil before
// encoding to get back the same invoice string.
@ -243,15 +261,17 @@ func TestDecodeEncode(t *testing.T) {
// Now send $24 for an entire list of things (hashed)
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqscc6gd6ql3jrc5yzme8v4ntcewwz5cnw92tz0pc8qcuufvq7khhr8wpald05e92xw006sq94mg8v2ndf4sefvf9sygkshp5zfem29trqq2yxxz7",
valid: true,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
}
},
beforeEncoding: func(i *invoice.Invoice) {
beforeEncoding: func(i *zpay32.Invoice) {
// Since this destination pubkey was recovered
// from the signature, we must set it nil before
// encoding to get back the same invoice string.
@ -262,16 +282,18 @@ func TestDecodeEncode(t *testing.T) {
// The same, on testnet, with a fallback address mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP
encodedInvoice: "lntb20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3x9et2e20v6pu37c5d9vax37wxq72un98k6vcx9fz94w0qf237cm2rqv9pmn5lnexfvf5579slr4zq3u8kmczecytdx0xg9rwzngp7e6guwqpqlhssu04sucpnz4axcv2dstmknqq6jsk2l",
valid: true,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.TestNet3Params,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
FallbackAddr: testAddrTestnet,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.TestNet3Params,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
FallbackAddr: testAddrTestnet,
}
},
beforeEncoding: func(i *invoice.Invoice) {
beforeEncoding: func(i *zpay32.Invoice) {
// Since this destination pubkey was recovered
// from the signature, we must set it nil before
// encoding to get back the same invoice string.
@ -282,24 +304,26 @@ func TestDecodeEncode(t *testing.T) {
// On mainnet, with fallback address 1RustyRX2oai4EYYDpQGWvEL62BBGqN9T with extra routing info to get to node 029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85frzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvncsk57n4v9ehw86wq8fzvjejhv9z3w3q5zh6qkql005x9xl240ch23jk79ujzvr4hsmmafyxghpqe79psktnjl668ntaf4ne7ucs5csqh5mnnk",
valid: true,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
FallbackAddr: testRustyAddr,
RoutingInfo: []invoice.ExtraRoutingInfo{
{
PubKey: testRoutingInfoPubkey,
ShortChanID: 0x0102030405060708,
Fee: 20,
CltvExpDelta: 3,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
FallbackAddr: testRustyAddr,
RoutingInfo: []zpay32.ExtraRoutingInfo{
{
PubKey: testRoutingInfoPubkey,
ShortChanID: 0x0102030405060708,
Fee: 20,
CltvExpDelta: 3,
},
},
},
}
},
beforeEncoding: func(i *invoice.Invoice) {
beforeEncoding: func(i *zpay32.Invoice) {
// Since this destination pubkey was recovered
// from the signature, we must set it nil before
// encoding to get back the same invoice string.
@ -310,30 +334,32 @@ func TestDecodeEncode(t *testing.T) {
// On mainnet, with fallback address 1RustyRX2oai4EYYDpQGWvEL62BBGqN9T with extra routing info to go via nodes 029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 then 039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqqqqqqq7qqzqfnlkwydm8rg30gjku7wmxmk06sevjp53fmvrcfegvwy7d5443jvyhxsel0hulkstws7vqv400q4j3wgpk4crg49682hr4scqvmad43cqd5m7tf",
valid: true,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
FallbackAddr: testRustyAddr,
RoutingInfo: []invoice.ExtraRoutingInfo{
{
PubKey: testRoutingInfoPubkey,
ShortChanID: 0x0102030405060708,
Fee: 20,
CltvExpDelta: 3,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
FallbackAddr: testRustyAddr,
RoutingInfo: []zpay32.ExtraRoutingInfo{
{
PubKey: testRoutingInfoPubkey,
ShortChanID: 0x0102030405060708,
Fee: 20,
CltvExpDelta: 3,
},
{
PubKey: testRoutingInfoPubkey2,
ShortChanID: 0x030405060708090a,
Fee: 30,
CltvExpDelta: 4,
},
},
{
PubKey: testRoutingInfoPubkey2,
ShortChanID: 0x030405060708090a,
Fee: 30,
CltvExpDelta: 4,
},
},
}
},
beforeEncoding: func(i *invoice.Invoice) {
beforeEncoding: func(i *zpay32.Invoice) {
// Since this destination pubkey was recovered
// from the signature, we must set it nil before
// encoding to get back the same invoice string.
@ -344,16 +370,18 @@ func TestDecodeEncode(t *testing.T) {
// On mainnet, with fallback (p2sh) address 3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfppj3a24vwu6r8ejrss3axul8rxldph2q7z9kk822r8plup77n9yq5ep2dfpcydrjwzxs0la84v3tfw43t3vqhek7f05m6uf8lmfkjn7zv7enn76sq65d8u9lxav2pl6x3xnc2ww3lqpagnh0u",
valid: true,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
FallbackAddr: testAddrMainnetP2SH,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
FallbackAddr: testAddrMainnetP2SH,
}
},
beforeEncoding: func(i *invoice.Invoice) {
beforeEncoding: func(i *zpay32.Invoice) {
// Since this destination pubkey was recovered
// from the signature, we must set it nil before
// encoding to get back the same invoice string.
@ -364,16 +392,18 @@ func TestDecodeEncode(t *testing.T) {
// On mainnet, with fallback (p2wpkh) address bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfppqw508d6qejxtdg4y5r3zarvary0c5xw7kknt6zz5vxa8yh8jrnlkl63dah48yh6eupakk87fjdcnwqfcyt7snnpuz7vp83txauq4c60sys3xyucesxjf46yqnpplj0saq36a554cp9wt865",
valid: true,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
FallbackAddr: testAddrMainnetP2WPKH,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
FallbackAddr: testAddrMainnetP2WPKH,
}
},
beforeEncoding: func(i *invoice.Invoice) {
beforeEncoding: func(i *zpay32.Invoice) {
// Since this destination pubkey was recovered
// from the signature, we must set it nil before
// encoding to get back the same invoice string.
@ -384,16 +414,18 @@ func TestDecodeEncode(t *testing.T) {
// On mainnet, with fallback (p2wsh) address bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfp4qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qvnjha2auylmwrltv2pkp2t22uy8ura2xsdwhq5nm7s574xva47djmnj2xeycsu7u5v8929mvuux43j0cqhhf32wfyn2th0sv4t9x55sppz5we8",
valid: true,
decodedInvoice: &invoice.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
FallbackAddr: testAddrMainnetP2WSH,
decodedInvoice: func() *zpay32.Invoice {
return &zpay32.Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
FallbackAddr: testAddrMainnetP2WSH,
}
},
beforeEncoding: func(i *invoice.Invoice) {
beforeEncoding: func(i *zpay32.Invoice) {
// Since this destination pubkey was recovered
// from the signature, we must set it nil before
// encoding to get back the same invoice string.
@ -403,14 +435,14 @@ func TestDecodeEncode(t *testing.T) {
}
for i, test := range tests {
invoice, err := invoice.Decode(test.encodedInvoice)
invoice, err := zpay32.Decode(test.encodedInvoice)
if (err == nil) != test.valid {
t.Errorf("Decoding test %d failed: %v", i, err)
return
}
if test.valid {
if err := compareInvoices(test.decodedInvoice, invoice); err != nil {
if err := compareInvoices(test.decodedInvoice(), invoice); err != nil {
t.Errorf("Invoice decoding result %d not as expected: %v", i, err)
return
}
@ -420,12 +452,17 @@ func TestDecodeEncode(t *testing.T) {
continue
}
if test.beforeEncoding != nil {
test.beforeEncoding(test.decodedInvoice)
var decodedInvoice *zpay32.Invoice
if test.decodedInvoice != nil {
decodedInvoice = test.decodedInvoice()
}
if test.decodedInvoice != nil {
reencoded, err := test.decodedInvoice.Encode(
if test.beforeEncoding != nil {
test.beforeEncoding(decodedInvoice)
}
if decodedInvoice != nil {
reencoded, err := decodedInvoice.Encode(
testMessageSigner,
)
if (err == nil) != test.valid {
@ -448,42 +485,42 @@ func TestNewInvoice(t *testing.T) {
t.Parallel()
tests := []struct {
newInvoice func() (*invoice.Invoice, error)
newInvoice func() (*zpay32.Invoice, error)
encodedInvoice string
valid bool
}{
{
// Both Description and DescriptionHash set.
newInvoice: func() (*invoice.Invoice, error) {
return invoice.NewInvoice(&chaincfg.MainNetParams,
newInvoice: func() (*zpay32.Invoice, error) {
return zpay32.NewInvoice(&chaincfg.MainNetParams,
testPaymentHash, time.Unix(1496314658, 0),
invoice.DescriptionHash(testDescriptionHash),
invoice.Description(testPleaseConsider))
zpay32.DescriptionHash(testDescriptionHash),
zpay32.Description(testPleaseConsider))
},
valid: false, // Both Description and DescriptionHash set.
},
{
// 'n' field set.
newInvoice: func() (*invoice.Invoice, error) {
return invoice.NewInvoice(&chaincfg.MainNetParams,
newInvoice: func() (*zpay32.Invoice, error) {
return zpay32.NewInvoice(&chaincfg.MainNetParams,
testPaymentHash, time.Unix(1503429093, 0),
invoice.Amount(testMillisat24BTC),
invoice.Description(testEmptyString),
invoice.Destination(testPubKey))
zpay32.Amount(testMillisat24BTC),
zpay32.Description(testEmptyString),
zpay32.Destination(testPubKey))
},
valid: true,
encodedInvoice: "lnbc241pveeq09pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66jd3m5klcwhq68vdsmx2rjgxeay5v0tkt2v5sjaky4eqahe4fx3k9sqavvce3capfuwv8rvjng57jrtfajn5dkpqv8yelsewtljwmmycq62k443",
},
{
// On mainnet, with fallback address 1RustyRX2oai4EYYDpQGWvEL62BBGqN9T with extra routing info to go via nodes 029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 then 039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255
newInvoice: func() (*invoice.Invoice, error) {
return invoice.NewInvoice(&chaincfg.MainNetParams,
newInvoice: func() (*zpay32.Invoice, error) {
return zpay32.NewInvoice(&chaincfg.MainNetParams,
testPaymentHash, time.Unix(1496314658, 0),
invoice.Amount(testMillisat20mBTC),
invoice.DescriptionHash(testDescriptionHash),
invoice.FallbackAddr(testRustyAddr),
invoice.RoutingInfo(
[]invoice.ExtraRoutingInfo{
zpay32.Amount(testMillisat20mBTC),
zpay32.DescriptionHash(testDescriptionHash),
zpay32.FallbackAddr(testRustyAddr),
zpay32.RoutingInfo(
[]zpay32.ExtraRoutingInfo{
{
PubKey: testRoutingInfoPubkey,
ShortChanID: 0x0102030405060708,
@ -525,7 +562,7 @@ func TestNewInvoice(t *testing.T) {
}
}
func compareInvoices(expected, actual *invoice.Invoice) error {
func compareInvoices(expected, actual *zpay32.Invoice) error {
if !reflect.DeepEqual(expected.Net, actual.Net) {
return fmt.Errorf("expected net %v, got %v",
expected.Net, actual.Net)
@ -560,9 +597,9 @@ func compareInvoices(expected, actual *invoice.Invoice) error {
*expected.DescriptionHash, *actual.DescriptionHash)
}
if !reflect.DeepEqual(expected.Expiry, actual.Expiry) {
if expected.Expiry() != actual.Expiry() {
return fmt.Errorf("expected expiry %d, got %d",
expected.Expiry, actual.Expiry)
expected.Expiry(), actual.Expiry())
}
if !reflect.DeepEqual(expected.FallbackAddr, actual.FallbackAddr) {

@ -1,150 +0,0 @@
package zpay32
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"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")
// 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")
// 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) {
if payData == "" {
return nil, fmt.Errorf("encoded payment request must be a " +
"non-empty string")
}
// First we decode the zbase32 encoded string into a series of raw
// bytes.
payReqBytes, err := zbase32.DecodeString(payData)
if err != nil {
return nil, err
}
// 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
// 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
}

@ -1,111 +0,0 @@
package zpay32
import (
"bytes"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcutil"
)
var (
testPrivKey = []byte{
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
}
_, testPubKey = btcec.PrivKeyFromBytes(btcec.S256(), testPrivKey)
testPayHash = [32]byte{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
)
func TestEncodeDecode(t *testing.T) {
t.Parallel()
testPubKey.Curve = nil
tests := []struct {
version int
payReq PaymentRequest
encoding string
}{
{
payReq: PaymentRequest{
Destination: testPubKey,
PaymentHash: testPayHash,
Amount: btcutil.Amount(50000),
},
encoding: "yj8p9uh793syszrd4giu66gtsqprp47cc6cqbdo3" +
"qaxi7sr63y6bbphw8bx148zzipg3rh6t1btadpnxf7z1mnfd76" +
"hsw1eaoca3ot4uyyyyyyyyydbib998je6o",
version: 1,
},
}
for i, test := range tests {
// First ensure encoding the test payment request string in the
// specified encoding.
encodedReq := Encode(&test.payReq)
if encodedReq != test.encoding {
t.Fatalf("encoding mismatch for %v: expected %v got %v",
spew.Sdump(test.payReq), test.encoding, encodedReq)
}
// Next ensure the correctness of the transformation in the
// other direction.
decodedReq, err := Decode(test.encoding)
if err != nil {
t.Fatalf("unable to decode invoice #%v: %v", i, err)
}
if !test.payReq.Destination.IsEqual(decodedReq.Destination) {
t.Fatalf("test #%v:, destination mismatch for decoded request: "+
"expected %v got %v", i, test.payReq.Destination,
decodedReq.Destination)
}
if !bytes.Equal(test.payReq.PaymentHash[:], decodedReq.PaymentHash[:]) {
t.Fatalf("test #%v: payment hash mismatch for decoded request: "+
"expected %x got %x", i, test.payReq.PaymentHash,
decodedReq.PaymentHash)
}
if test.payReq.Amount != decodedReq.Amount {
t.Fatalf("test #%v: amount mismatch for decoded request: "+
"expected %x got %x", i, test.payReq.Amount,
decodedReq.Amount)
}
}
}
func TestChecksumMismatch(t *testing.T) {
t.Parallel()
// We start with a pre-encoded invoice, which has a valid checksum.
payReqString := []byte("ycyr8brdjic6oak3bemztc5nupo56y3itq4z5q4qxwb35orf7fmj5phw8bx148zzipg3rh6t1btadpnxf7z1mnfd76hsw1eaoca3ot4uyyyyyyyyydbibt79jo1o")
// To modify the resulting checksum, we shift a few of the bytes within the
// string itself.
payReqString[1] = 98
payReqString[5] = 102
if _, err := Decode(string(payReqString)); err != ErrCheckSumMismatch {
t.Fatalf("decode should fail with checksum mismatch, instead: %v", err)
}
}
func TestDecodeTooShort(t *testing.T) {
t.Parallel()
// We start with a pre-encoded too-short string.
payReqString := "ycyr8brdji"
if _, err := Decode(payReqString); err != ErrDataTooShort {
t.Fatalf("decode should fail with data too short, instead: %v", err)
}
}