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.Memo = []byte("memo")
i.Receipt = []byte("recipt") 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 return i, nil
} }
@ -50,6 +63,7 @@ func TestInvoiceWorkflow(t *testing.T) {
} }
fakeInvoice.Memo = []byte("memo") fakeInvoice.Memo = []byte("memo")
fakeInvoice.Receipt = []byte("recipt") fakeInvoice.Receipt = []byte("recipt")
fakeInvoice.PaymentRequest = []byte("")
copy(fakeInvoice.Terms.PaymentPreimage[:], rev[:]) copy(fakeInvoice.Terms.PaymentPreimage[:], rev[:])
fakeInvoice.Terms.Value = lnwire.NewMSatFromSatoshis(10000) fakeInvoice.Terms.Value = lnwire.NewMSatFromSatoshis(10000)

@ -43,6 +43,12 @@ const (
// MaxReceiptSize is the maximum size of the payment receipt stored // MaxReceiptSize is the maximum size of the payment receipt stored
// within the database along side incoming/outgoing invoices. // within the database along side incoming/outgoing invoices.
MaxReceiptSize = 1024 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 // ContractTerm is a companion struct to the Invoice struct. This struct houses
@ -86,6 +92,10 @@ type Invoice struct {
// TODO(roasbeef): document scheme. // TODO(roasbeef): document scheme.
Receipt []byte 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 is the exact time the invoice was created.
CreationDate time.Time CreationDate time.Time
@ -108,6 +118,11 @@ func validateInvoice(i *Invoice) error {
"of length %v was provided", MaxReceiptSize, "of length %v was provided", MaxReceiptSize,
len(i.Receipt)) 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 return nil
} }
@ -306,6 +321,9 @@ func serializeInvoice(w io.Writer, i *Invoice) error {
if err := wire.WriteVarBytes(w, 0, i.Receipt[:]); err != nil { if err := wire.WriteVarBytes(w, 0, i.Receipt[:]); err != nil {
return err return err
} }
if err := wire.WriteVarBytes(w, 0, i.PaymentRequest[:]); err != nil {
return err
}
birthBytes, err := i.CreationDate.MarshalBinary() birthBytes, err := i.CreationDate.MarshalBinary()
if err != nil { if err != nil {
@ -361,6 +379,11 @@ func deserializeInvoice(r io.Reader) (*Invoice, error) {
return nil, err 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") birthBytes, err := wire.ReadVarBytes(r, 0, 300, "birth")
if err != nil { if err != nil {
return nil, err return nil, err

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

@ -764,7 +764,7 @@ var sendPaymentCommand = cli.Command{
}, },
cli.StringFlag{ cli.StringFlag{
Name: "pay_req", Name: "pay_req",
Usage: "a zbase32-check encoded payment request to fulfill", Usage: "a bech32 encoded payment request to fulfill",
}, },
}, },
Action: sendPayment, Action: sendPayment,
@ -885,25 +885,49 @@ var addInvoiceCommand = cli.Command{
Name: "addinvoice", Name: "addinvoice",
Usage: "add a new invoice.", Usage: "add a new invoice.",
Description: "Add a new invoice, expressing intent for a future payment. " + 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", ArgsUsage: "value preimage",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "memo", Name: "memo",
Usage: "an optional memo to attach along with the invoice", Usage: "a description of the payment to attach along " +
"with the invoice (default=\"\")",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "receipt", Name: "receipt",
Usage: "an optional cryptographic receipt of payment", Usage: "an optional cryptographic receipt of payment",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "preimage", Name: "preimage",
Usage: "the hex-encoded preimage (32 byte) which will allow settling an incoming HTLC payable to this 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{ cli.Int64Flag{
Name: "value", Name: "value",
Usage: "the value of this invoice in satoshis", 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, Action: addInvoice,
} }
@ -911,6 +935,7 @@ var addInvoiceCommand = cli.Command{
func addInvoice(ctx *cli.Context) error { func addInvoice(ctx *cli.Context) error {
var ( var (
preimage []byte preimage []byte
descHash []byte
receipt []byte receipt []byte
value int64 value int64
err error err error
@ -945,16 +970,24 @@ func addInvoice(ctx *cli.Context) error {
return fmt.Errorf("unable to parse preimage: %v", err) 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")) receipt, err = hex.DecodeString(ctx.String("receipt"))
if err != nil { if err != nil {
return fmt.Errorf("unable to parse receipt: %v", err) return fmt.Errorf("unable to parse receipt: %v", err)
} }
invoice := &lnrpc.Invoice{ invoice := &lnrpc.Invoice{
Memo: ctx.String("memo"), Memo: ctx.String("memo"),
Receipt: receipt, Receipt: receipt,
RPreimage: preimage, RPreimage: preimage,
Value: value, Value: value,
DescriptionHash: descHash,
FallbackAddr: ctx.String("fallback_addr"),
Expiry: ctx.Int64("expiry"),
} }
resp, err := client.AddInvoice(context.Background(), invoice) resp, err := client.AddInvoice(context.Background(), invoice)
@ -1490,7 +1523,7 @@ var decodePayReqComamnd = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "pay_req", Name: "pay_req",
Usage: "the zpay32 encoded payment request", Usage: "the bech32 encoded payment request",
}, },
}, },
Action: decodePayReq, Action: decodePayReq,

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

@ -1245,7 +1245,7 @@
"properties": { "properties": {
"memo": { "memo": {
"type": "string", "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": { "receipt": {
"type": "string", "type": "string",
@ -1285,6 +1285,20 @@
"payment_request": { "payment_request": {
"type": "string", "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": "*\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": { "num_satoshis": {
"type": "string", "type": "string",
"format": "int64" "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 // resident node's private key. The returned signature is a pubkey-recoverable
// signature. // signature.
func (n *nodeSigner) SignCompact(msg []byte) ([]byte, error) { func (n *nodeSigner) SignCompact(msg []byte) ([]byte, error) {
// We'll sign the dsha256 of the target message.
// Otherwise, we'll sign the dsha256 of the target message.
digest := chainhash.DoubleHashB(msg) 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. // Should the signature reference a compressed public key or not.
isCompressedKey := true isCompressedKey := true
// btcec.SignCompact returns a pubkey-recoverable signature // 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) isCompressedKey)
if err != nil { 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 return sig, nil

@ -1372,6 +1372,18 @@ func (r *rpcServer) savePayment(route *routing.Route, amount lnwire.MilliSatoshi
return r.server.chanDB.AddPayment(payment) 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 // SendPayment dispatches a bi-directional streaming RPC for sending payments
// through the Lightning Network. A single RPC invocation creates a persistent // through the Lightning Network. A single RPC invocation creates a persistent
// bi-directional stream allowing clients to rapidly send payments through the // 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) errChan := make(chan error, 1)
payChan := make(chan *lnrpc.SendRequest)
// TODO(roasbeef): enforce fee limits, pass into router, ditch if exceed limit // TODO(roasbeef): enforce fee limits, pass into router, ditch if exceed limit
// * limit either a %, or absolute, or iff more than sending // * limit either a %, or absolute, or iff more than sending
@ -1443,31 +1462,67 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
return 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, // If the payment request field isn't blank,
// then the details of the invoice are encoded // 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 // attempt to decode it, populating the
// nextPayment accordingly. // payment accordingly.
if nextPayment.PaymentRequest != "" { if nextPayment.PaymentRequest != "" {
payReq, err := zpay32.Decode(nextPayment.PaymentRequest) payReq, err := zpay32.Decode(nextPayment.PaymentRequest)
if err != nil { if err != nil {
select { select {
case errChan <- err: case errChan <- err:
case <-reqQuit: case <-reqQuit:
return
} }
return return
} }
// TODO(roasbeef): eliminate necessary // TODO(roasbeef): eliminate necessary
// encode/decode // encode/decode
nextPayment.Dest = payReq.Destination.SerializeCompressed()
nextPayment.Amt = int64(payReq.Amount) // We first check that this payment
nextPayment.PaymentHash = payReq.PaymentHash[:] // 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 { select {
case payChan <- nextPayment: case payChan <- p:
case <-reqQuit: case <-reqQuit:
return return
} }
@ -1479,20 +1534,17 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
select { select {
case err := <-errChan: case err := <-errChan:
return err return err
case nextPayment := <-payChan: case p := <-payChan:
// Currently, within the bootstrap phase of the // Currently, within the bootstrap phase of the
// network, we limit the largest payment size allotted // network, we limit the largest payment size allotted
// to (2^32) - 1 mSAT or 4.29 million satoshis. // to (2^32) - 1 mSAT or 4.29 million satoshis.
amt := btcutil.Amount(nextPayment.Amt) if p.msat > maxPaymentMSat {
amtMSat := lnwire.NewMSatFromSatoshis(amt)
if amtMSat > maxPaymentMSat {
// In this case, we'll send an error to the // In this case, we'll send an error to the
// caller, but continue our loop for the next // caller, but continue our loop for the next
// payment. // payment.
pErr := fmt.Errorf("payment of %v is too "+ pErr := fmt.Errorf("payment of %v is too "+
"large, max payment allowed is %v", "large, max payment allowed is %v",
nextPayment.Amt, p.msat, maxPaymentMSat)
maxPaymentMSat.ToSatoshis())
if err := paymentStream.Send(&lnrpc.SendResponse{ if err := paymentStream.Send(&lnrpc.SendResponse{
PaymentError: pErr.Error(), PaymentError: pErr.Error(),
@ -1504,8 +1556,7 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
// Parse the details of the payment which include the // Parse the details of the payment which include the
// pubkey of the destination and the payment amount. // pubkey of the destination and the payment amount.
dest := nextPayment.Dest destNode, err := btcec.ParsePubKey(p.dest, btcec.S256())
destNode, err := btcec.ParsePubKey(dest, btcec.S256())
if err != nil { if err != nil {
return err return err
} }
@ -1514,10 +1565,10 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
// will pay to the same debug rHash. Otherwise, we pay // will pay to the same debug rHash. Otherwise, we pay
// to the rHash specified within the RPC request. // to the rHash specified within the RPC request.
var rHash [32]byte var rHash [32]byte
if cfg.DebugHTLC && len(nextPayment.PaymentHash) == 0 { if cfg.DebugHTLC && len(p.pHash) == 0 {
rHash = debugHash rHash = debugHash
} else { } else {
copy(rHash[:], nextPayment.PaymentHash) copy(rHash[:], p.pHash)
} }
// We launch a new goroutine to execute the current // We launch a new goroutine to execute the current
@ -1539,7 +1590,7 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer)
// error. // error.
payment := &routing.LightningPayment{ payment := &routing.LightningPayment{
Target: destNode, Target: destNode,
Amount: amtMSat, Amount: p.msat,
PaymentHash: rHash, PaymentHash: rHash,
} }
preImage, route, err := r.server.chanRouter.SendPayment(payment) 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 // Save the completed payment to the database
// for record keeping purposes. // 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 errChan <- err
return return
} }
@ -1604,7 +1655,7 @@ func (r *rpcServer) SendPaymentSync(ctx context.Context,
var ( var (
destPub *btcec.PublicKey destPub *btcec.PublicKey
amt btcutil.Amount amtMSat lnwire.MilliSatoshi
rHash [32]byte rHash [32]byte
) )
@ -1615,9 +1666,20 @@ func (r *rpcServer) SendPaymentSync(ctx context.Context,
if err != nil { if err != nil {
return nil, err 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 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 // Otherwise, the payment conditions have been manually
// specified in the proto. // specified in the proto.
@ -1645,13 +1707,14 @@ func (r *rpcServer) SendPaymentSync(ctx context.Context,
return nil, err 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 // Currently, within the bootstrap phase of the network, we limit the
// largest payment size allotted to (2^32) - 1 mSAT or 4.29 million // largest payment size allotted to (2^32) - 1 mSAT or 4.29 million
// satoshis. // satoshis.
amtMSat := lnwire.NewMSatFromSatoshis(amt)
if amtMSat > maxPaymentMSat { if amtMSat > maxPaymentMSat {
return nil, fmt.Errorf("payment of %v is too large, max payment "+ return nil, fmt.Errorf("payment of %v is too large, max payment "+
"allowed is %v", nextPayment.Amt, "allowed is %v", nextPayment.Amt,
@ -1718,8 +1781,8 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
copy(paymentPreimage[:], invoice.RPreimage[:]) copy(paymentPreimage[:], invoice.RPreimage[:])
} }
// The size of the memo and receipt attached must not exceed the // The size of the memo, receipt and description hash attached must not
// maximum values for either of the fields. // exceed the maximum values for either of the fields.
if len(invoice.Memo) > channeldb.MaxMemoSize { if len(invoice.Memo) > channeldb.MaxMemoSize {
return nil, fmt.Errorf("memo too large: %v bytes "+ return nil, fmt.Errorf("memo too large: %v bytes "+
"(maxsize=%v)", len(invoice.Memo), channeldb.MaxMemoSize) "(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 "+ return nil, fmt.Errorf("receipt too large: %v bytes "+
"(maxsize=%v)", len(invoice.Receipt), channeldb.MaxReceiptSize) "(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) amt := btcutil.Amount(invoice.Value)
amtMSat := lnwire.NewMSatFromSatoshis(amt) amtMSat := lnwire.NewMSatFromSatoshis(amt)
@ -1743,10 +1810,78 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
"payment allowed is %v", amt, maxPaymentMSat.ToSatoshis()) "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{ i := &channeldb.Invoice{
CreationDate: time.Now(), CreationDate: creationDate,
Memo: []byte(invoice.Memo), Memo: []byte(invoice.Memo),
Receipt: invoice.Receipt, Receipt: invoice.Receipt,
PaymentRequest: []byte(payReqString),
Terms: channeldb.ContractTerm{ Terms: channeldb.ContractTerm{
Value: amtMSat, Value: amtMSat,
}, },
@ -1763,24 +1898,53 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
return nil, err 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{ return &lnrpc.AddInvoiceResponse{
RHash: rHash[:], RHash: rHash[:],
PaymentRequest: payReqString, PaymentRequest: payReqString,
}, nil }, 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. // 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 // The passed payment hash *must* be exactly 32 bytes, if not an error is
// returned. // returned.
@ -1831,22 +1995,12 @@ func (r *rpcServer) LookupInvoice(ctx context.Context,
return spew.Sdump(invoice) return spew.Sdump(invoice)
})) }))
preimage := invoice.Terms.PaymentPreimage rpcInvoice, err := createRPCInvoice(invoice)
satAmt := invoice.Terms.Value.ToSatoshis() if err != nil {
return &lnrpc.Invoice{ return nil, err
Memo: string(invoice.Memo[:]), }
Receipt: invoice.Receipt[:],
RHash: rHash, return rpcInvoice, nil
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
} }
// ListInvoices returns a list of all the invoices currently stored within the // 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)) invoices := make([]*lnrpc.Invoice, len(dbInvoices))
for i, dbInvoice := range dbInvoices { for i, dbInvoice := range dbInvoices {
invoiceAmount := dbInvoice.Terms.Value.ToSatoshis()
paymentPreimge := dbInvoice.Terms.PaymentPreimage[:]
rHash := sha256.Sum256(paymentPreimge)
invoice := &lnrpc.Invoice{ rpcInvoice, err := createRPCInvoice(dbInvoice)
Memo: string(dbInvoice.Memo[:]), if err != nil {
Receipt: dbInvoice.Receipt[:], return nil, err
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,
}),
} }
invoices[i] = invoice invoices[i] = rpcInvoice
} }
return &lnrpc.ListInvoiceResponse{ return &lnrpc.ListInvoiceResponse{
@ -1916,17 +2057,13 @@ func (r *rpcServer) SubscribeInvoices(req *lnrpc.InvoiceSubscription,
select { select {
// TODO(roasbeef): include newly added invoices? // TODO(roasbeef): include newly added invoices?
case settledInvoice := <-invoiceClient.SettledInvoices: case settledInvoice := <-invoiceClient.SettledInvoices:
preImage := settledInvoice.Terms.PaymentPreimage[:]
rHash := sha256.Sum256(preImage) rpcInvoice, err := createRPCInvoice(settledInvoice)
invoice := &lnrpc.Invoice{ if err != nil {
Memo: string(settledInvoice.Memo[:]), return err
Receipt: settledInvoice.Receipt[:],
RHash: rHash[:],
RPreimage: preImage,
Value: int64(settledInvoice.Terms.Value.ToSatoshis()),
Settled: settledInvoice.Terms.Settled,
} }
if err := updateStream.Send(invoice); err != nil {
if err := updateStream.Send(rpcInvoice); err != nil {
return err return err
} }
case <-r.quit: case <-r.quit:
@ -2713,11 +2850,41 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
return nil, err 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() dest := payReq.Destination.SerializeCompressed()
return &lnrpc.PayReq{ return &lnrpc.PayReq{
Destination: hex.EncodeToString(dest), Destination: hex.EncodeToString(dest),
PaymentHash: hex.EncodeToString(payReq.PaymentHash[:]), PaymentHash: hex.EncodeToString(payReq.PaymentHash[:]),
NumSatoshis: int64(payReq.Amount), NumSatoshis: amt,
Timestamp: payReq.Timestamp.Unix(),
Description: desc,
DescriptionHash: hex.EncodeToString(descHash[:]),
FallbackAddr: fallbackAddr,
Expiry: expiry,
}, nil }, nil
} }

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

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

@ -1,4 +1,4 @@
package invoice package zpay32
import ( import (
"bytes" "bytes"
@ -109,9 +109,13 @@ type Invoice struct {
// Optional. Non-nil iff Description is nil. // Optional. Non-nil iff Description is nil.
DescriptionHash *[32]byte 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. // 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 // FallbackAddr is an on-chain address that can be used for payment in
// case the Lightning payment fails. // 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 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 // expiry of the created Invoice. If not set, a default expiry of 60 min will
// be implied. // be implied.
func Expiry(expiry time.Time) func(*Invoice) { func Expiry(expiry time.Duration) func(*Invoice) {
return func(i *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 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 // 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. // has all the necessary fields set for it to be considered valid by BOLT-0011.
func validateInvoice(invoice *Invoice) error { func validateInvoice(invoice *Invoice) error {
@ -624,7 +639,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
copy(dHash[:], hash[:]) copy(dHash[:], hash[:])
invoice.DescriptionHash = &dHash invoice.DescriptionHash = &dHash
case fieldTypeX: case fieldTypeX:
if invoice.Expiry != nil { if invoice.expiry != nil {
// We skip the field if we have already seen a // We skip the field if we have already seen a
// supported one. // supported one.
continue continue
@ -634,8 +649,8 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
if err != nil { if err != nil {
return err return err
} }
unix := time.Unix(int64(exp), 0) dur := time.Duration(exp) * time.Second
invoice.Expiry = &unix invoice.expiry = &dur
case fieldTypeF: case fieldTypeF:
if invoice.FallbackAddr != nil { if invoice.FallbackAddr != nil {
// We skip the field if we have already seen a // 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 { if invoice.expiry != nil {
unix := invoice.Expiry.Unix() seconds := invoice.expiry.Seconds()
expiry := uint64ToBase32(uint64(unix)) expiry := uint64ToBase32(uint64(seconds))
err := writeTaggedField(bufferBase32, fieldTypeX, expiry) err := writeTaggedField(bufferBase32, fieldTypeX, expiry)
if err != nil { if err != nil {
return err return err

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

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