diff --git a/docs/release-notes/release-notes-0.22.0.md b/docs/release-notes/release-notes-0.22.0.md index 249ca6c825..521f11f983 100644 --- a/docs/release-notes/release-notes-0.22.0.md +++ b/docs/release-notes/release-notes-0.22.0.md @@ -119,6 +119,23 @@ later in the reservation flow as a funder-balance-dust error; they now surface a clearer, spec-aligned error string up front. +* [Require an explicit `channel_type` during channel + funding](https://github.com/lightningnetwork/lnd/pull/11064). It is now always + set in `open_channel` and echoed back in `accept_channel`, and an + `open_channel` that omits it is rejected. Implicit commitment type negotiation + is removed; if the RPC caller doesn't request a type, a default is derived + from both peers' features and signaled explicitly. + +* BOLT 11 invoice decoding [now + fails](https://github.com/lightningnetwork/lnd/pull/10326) if any field with + a fixed length (`p`, `h`, `s`, `n`) does not have the correct length (52, 52, + 52, 53), instead of skipping the field, and if the invoice has no payment + secret (`s`) and no blinded paths, as the BOLT 11 reader requirements + demand. The length rule was clarified in + [lightning/bolts#1243](https://github.com/lightning/bolts/pull/1243). + Stored invoices created by lnd versions before v0.9.0, which have no payment + secret, are still returned by the invoice RPCs. + ## BOLT 12 (Offers) * [Initial BOLT 12 Offer codec](https://github.com/lightningnetwork/lnd/pull/10789): @@ -201,3 +218,5 @@ * Jared Tobin * Kevin Cai * Vandit Singh +* Nishant Bansal +* Pins diff --git a/lnrpc/invoicesrpc/utils.go b/lnrpc/invoicesrpc/utils.go index f59c6dfbb6..f9dd1d641f 100644 --- a/lnrpc/invoicesrpc/utils.go +++ b/lnrpc/invoicesrpc/utils.go @@ -33,8 +33,13 @@ func decodePayReq(invoice *invoices.Invoice, }, nil } - var err error - decoded, err := zpay32.Decode(paymentRequest, activeNetParams) + // Invoices created by lnd versions before v0.9.0 have no payment + // secret. We skip that check so that they can still be returned by the + // RPCs, since the invoice is only converted for display here. + decoded, err := zpay32.Decode( + paymentRequest, activeNetParams, + zpay32.WithSkipPaymentSecretCheck(), + ) if err != nil { return nil, fmt.Errorf("unable to decode payment "+ "request: %v", err) diff --git a/lnrpc/invoicesrpc/utils_test.go b/lnrpc/invoicesrpc/utils_test.go new file mode 100644 index 0000000000..ca801570b7 --- /dev/null +++ b/lnrpc/invoicesrpc/utils_test.go @@ -0,0 +1,31 @@ +package invoicesrpc + +import ( + "testing" + + "github.com/btcsuite/btcd/chaincfg/v2" + "github.com/lightningnetwork/lnd/invoices" + "github.com/stretchr/testify/require" +) + +// TestCreateRPCInvoiceWithoutPaymentSecret checks that a stored invoice +// without a payment secret, as created by lnd versions before v0.9.0, can +// still be converted to its RPC representation. +func TestCreateRPCInvoiceWithoutPaymentSecret(t *testing.T) { + t.Parallel() + + payReq := "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5" + + "rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7en" + + "xv4jszjlrkes0mgx4ghum45ha5gkzlac8xmrr4skgwey" + + "p6xqxucu4wz4j8uvpg0jznsesezax6hdt0gtyn3tuqpf" + + "y2curryn83zygkydmpxcqdfu7k0" + + invoice := &invoices.Invoice{ + PaymentRequest: []byte(payReq), + State: invoices.ContractOpen, + } + + rpcInvoice, err := CreateRPCInvoice(invoice, &chaincfg.MainNetParams) + require.NoError(t, err) + require.Equal(t, payReq, rpcInvoice.PaymentRequest) +} diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index ce350ff5e8..e09da47bad 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -1038,7 +1038,9 @@ func (r *RouterBackend) extractIntentFromSendRequest( } // An invoice must include either a payment address or - // blinded paths. + // blinded paths. zpay32.Decode already enforces this, but we + // keep the check here as a defense in case the decoder is + // relaxed. if payReq.PaymentAddr.IsNone() && len(payReq.BlindedPaymentPaths) == 0 { diff --git a/lnrpc/routerrpc/router_backend_test.go b/lnrpc/routerrpc/router_backend_test.go index de3a662d8f..6d97bcdfee 100644 --- a/lnrpc/routerrpc/router_backend_test.go +++ b/lnrpc/routerrpc/router_backend_test.go @@ -831,9 +831,8 @@ func TestExtractIntentFromSendRequest(t *testing.T) { sendReq: &SendPaymentRequest{ PaymentRequest: paymentReqMissingAddr, }, - valid: false, - expectedErrorMsg: "payment request must contain " + - "either a payment address or blinded paths", + valid: false, + expectedErrorMsg: "payment secret not found", }, { name: "Invalid dest vertex length", diff --git a/lnrpc/routerrpc/router_server_test.go b/lnrpc/routerrpc/router_server_test.go index 12592a72e2..5353dc0768 100644 --- a/lnrpc/routerrpc/router_server_test.go +++ b/lnrpc/routerrpc/router_server_test.go @@ -838,6 +838,7 @@ func TestProbePaymentRequestUsesUniqueHashPerLSP(t *testing.T) { zpay32.RouteHint([]zpay32.HopHint{bobHint}), zpay32.RouteHint([]zpay32.HopHint{eveHint}), zpay32.RouteHint([]zpay32.HopHint{daveHint}), + zpay32.PaymentAddr([32]byte{4, 5}), ) require.NoError(t, err) diff --git a/zpay32/decode.go b/zpay32/decode.go index 8a2e0bcb8e..9dd88ac6b0 100644 --- a/zpay32/decode.go +++ b/zpay32/decode.go @@ -64,10 +64,23 @@ func WithErrorOnUnknownFeatureBit() DecodeOption { } } +// WithSkipPaymentSecretCheck is a functional option that allows the Decode +// function to accept an invoice that has neither a payment secret nor blinded +// paths. BOLT 11 requires a reader to fail the payment for such an invoice, so +// this option must only be used when the invoice is not going to be paid, for +// example to display invoices that were stored before payment secrets were +// mandatory. +func WithSkipPaymentSecretCheck() DecodeOption { + return func(options *decodeOptions) { + options.skipPaymentSecretCheck = true + } +} + // decodeOptions holds the set of Decode options. type decodeOptions struct { - knownFeatureBits map[lnwire.FeatureBit]string - errorOnUnknownFeature bool + knownFeatureBits map[lnwire.FeatureBit]string + errorOnUnknownFeature bool + skipPaymentSecretCheck bool } // newDecodeOptions constructs the default decodeOptions struct. @@ -212,6 +225,16 @@ func Decode(invoice string, net *chaincfg.Params, opts ...DecodeOption) ( return nil, err } + // The invoice must include a payment secret, unless it uses blinded + // paths, in which case the PathID in the final hop serves the same + // purpose. + if !options.skipPaymentSecretCheck && + len(decodedInvoice.BlindedPaymentPaths) == 0 && + decodedInvoice.PaymentAddr.IsNone() { + + return nil, ErrPaymentSecretNotFound + } + if options.errorOnUnknownFeature { // Make sure that we understand all the required feature bits // in the invoice. @@ -265,11 +288,7 @@ func parseTimestamp(data []byte) (uint64, error) { // parseTaggedFields takes the base32 encoded tagged fields of the invoice, and // fills the Invoice struct accordingly. func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) error { - var ( - index int - paymentHashSeen bool - ) - + index := 0 for len(fields)-index > 0 { // If there are less than 3 groups to read, there cannot be more // interesting information, as we need the type (1 group) and @@ -298,27 +317,30 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er switch typ { case fieldTypeP: - if paymentHashSeen { + paymentHash, err := parse32Bytes(base32Data) + if err != nil { + return fmt.Errorf("payment hash: %w", err) + } + + if invoice.PaymentHash != nil { return ErrDuplicatePaymentHash } - paymentHashSeen = true - invoice.PaymentHash, err = parse32Bytes(base32Data) + invoice.PaymentHash = paymentHash case fieldTypeS: + addr, err := parse32Bytes(base32Data) + if err != nil { + return fmt.Errorf("payment secret: %w", err) + } + if invoice.PaymentAddr.IsSome() { // We skip the field if we have already seen a // supported one. continue } - addr, err := parse32Bytes(base32Data) - if err != nil { - return err - } - if addr != nil { - invoice.PaymentAddr = fn.Some(*addr) - } + invoice.PaymentAddr = fn.Some(*addr) case fieldTypeD: if invoice.Description != nil { @@ -339,22 +361,32 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er invoice.Metadata, err = parseMetadata(base32Data) case fieldTypeN: + destination, err := parseDestination(base32Data) + if err != nil { + return fmt.Errorf("destination id: %w", err) + } + if invoice.Destination != nil { // We skip the field if we have already seen a // supported one. continue } - invoice.Destination, err = parseDestination(base32Data) + invoice.Destination = destination case fieldTypeH: + descriptionHash, err := parse32Bytes(base32Data) + if err != nil { + return fmt.Errorf("description hash: %w", err) + } + if invoice.DescriptionHash != nil { // We skip the field if we have already seen a // supported one. continue } - invoice.DescriptionHash, err = parse32Bytes(base32Data) + invoice.DescriptionHash = descriptionHash case fieldTypeX: if invoice.expiry != nil { @@ -449,16 +481,10 @@ func parseFieldDataLength(data []byte) (uint16, error) { func parse32Bytes(data []byte) (*[32]byte, error) { var paymentHash [32]byte - // A field with an unexpected length is reported as absent rather - // than as an error, leaving it to the caller to decide whether a - // missing field is fatal. Note that BOLT 11 is stricter, and - // requires a reader to fail on a fixed-length field (p, h, s, n) - // with the wrong length. For the payment hash the end result is the - // same: a lone wrong-length field leaves PaymentHash nil and - // validateInvoice rejects the invoice, and a wrong-length field - // paired with a valid one is rejected as a duplicate. + // As BOLT-11 states, a reader must fail the payment if a 32-byte + // field (p, h, s) does not have a length of 52. if len(data) != hashBase32Len { - return nil, nil + return nil, ErrInvalidFieldLength } hash, err := bech32.ConvertBits(data, 5, 8, false) @@ -497,10 +523,10 @@ func parseMetadata(data []byte) ([]byte, error) { // parseDestination converts the data (encoded in base32) into a 33-byte public // key of the payee node. func parseDestination(data []byte) (*btcec.PublicKey, error) { - // As BOLT-11 states, a reader must skip over the destination field - // if it does not have a length of 53, so avoid returning an error. + // As BOLT-11 states, a reader must fail the payment if the destination + // field does not have a length of 53. if len(data) != pubKeyBase32Len { - return nil, nil + return nil, ErrInvalidFieldLength } base256Data, err := bech32.ConvertBits(data, 5, 8, false) diff --git a/zpay32/invoice.go b/zpay32/invoice.go index bcdaa1ca9d..af7ed96e91 100644 --- a/zpay32/invoice.go +++ b/zpay32/invoice.go @@ -109,6 +109,10 @@ var ( ErrDuplicatePaymentHash = errors.New( "invoice contains multiple payment hashes", ) + + // ErrPaymentSecretNotFound is returned when a decoded invoice has + // neither a payment secret nor blinded paths. + ErrPaymentSecretNotFound = errors.New("payment secret not found") ) // MessageSigner is passed to the Encode method to provide a signature diff --git a/zpay32/invoice_internal_test.go b/zpay32/invoice_internal_test.go index 656f8ee590..9c91b3e345 100644 --- a/zpay32/invoice_internal_test.go +++ b/zpay32/invoice_internal_test.go @@ -3,6 +3,7 @@ package zpay32 import ( "bytes" "encoding/binary" + "errors" "math" "reflect" "testing" @@ -317,8 +318,7 @@ func TestParseFieldDataLength(t *testing.T) { } // TestParse32Bytes checks that the payment hash is properly parsed. -// If the data does not have a length of 52 bytes, we skip over parsing the -// field and do not return an error. +// If the data does not have a length of 52 bytes, we return an error. func TestParse32Bytes(t *testing.T) { t.Parallel() @@ -330,14 +330,12 @@ func TestParse32Bytes(t *testing.T) { result *[32]byte }{ { - data: []byte{}, - valid: true, - result: nil, // skip unknown length, not 52 bytes + data: []byte{}, + valid: false, }, { - data: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - valid: true, - result: nil, // skip unknown length, not 52 bytes + data: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + valid: false, }, { data: testPaymentHashData, @@ -345,9 +343,8 @@ func TestParse32Bytes(t *testing.T) { result: &testPaymentHash, }, { - data: append(testPaymentHashData, 0x0), - valid: true, - result: nil, // skip unknown length, not 52 bytes + data: append(testPaymentHashData, 0x0), + valid: false, }, } @@ -423,8 +420,7 @@ func TestParseDescription(t *testing.T) { } // TestParseDestination checks that the destination is properly parsed. -// If the data does not have a length of 53 bytes, we skip over parsing the -// field and do not return an error. +// If the data does not have a length of 53 bytes, we return an error. func TestParseDestination(t *testing.T) { t.Parallel() @@ -436,14 +432,12 @@ func TestParseDestination(t *testing.T) { result *btcec.PublicKey }{ { - data: []byte{}, - valid: true, - result: nil, // skip unknown length, not 53 bytes + data: []byte{}, + valid: false, }, { - data: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - valid: true, - result: nil, // skip unknown length, not 53 bytes + data: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + valid: false, }, { data: testPubKeyData, @@ -451,9 +445,8 @@ func TestParseDestination(t *testing.T) { result: testPubKey, }, { - data: append(testPubKeyData, 0x0), - valid: true, - result: nil, // skip unknown length, not 53 bytes + data: append(testPubKeyData, 0x0), + valid: false, }, } @@ -808,6 +801,24 @@ func TestParseTaggedFields(t *testing.T) { &distinctPaymentHashes, fieldTypeP, secondPaymentHash, )) + // validThenMalformed returns a valid field of the given type followed + // by a second field of the same type with an invalid length. + validThenMalformed := func(fieldType byte, valid []byte) []byte { + var b bytes.Buffer + require.NoError(t, writeTaggedField(&b, fieldType, valid)) + require.NoError(t, writeTaggedField(&b, fieldType, []byte{0})) + + return b.Bytes() + } + + hashData, err := bech32.ConvertBits(testPaymentHash[:], 8, 5, true) + require.NoError(t, err) + + pubKeyData, err := bech32.ConvertBits( + testPubKey.SerializeCompressed(), 8, 5, true, + ) + require.NoError(t, err) + tests := []struct { name string data []byte @@ -837,7 +848,27 @@ func TestParseTaggedFields(t *testing.T) { { name: "malformed then valid payment hash", data: malformedThenValid.Bytes(), - wantErr: ErrDuplicatePaymentHash, + wantErr: ErrInvalidFieldLength, + }, + { + name: "valid then malformed payment hash", + data: validThenMalformed(fieldTypeP, hashData), + wantErr: ErrInvalidFieldLength, + }, + { + name: "valid then malformed payment secret", + data: validThenMalformed(fieldTypeS, hashData), + wantErr: ErrInvalidFieldLength, + }, + { + name: "valid then malformed description hash", + data: validThenMalformed(fieldTypeH, hashData), + wantErr: ErrInvalidFieldLength, + }, + { + name: "valid then malformed destination", + data: validThenMalformed(fieldTypeN, pubKeyData), + wantErr: ErrInvalidFieldLength, }, { name: "identical payment hashes", @@ -879,7 +910,7 @@ func TestParseTaggedFields(t *testing.T) { t.Run(tc.name, func(t *testing.T) { var invoice Invoice gotErr := parseTaggedFields(&invoice, tc.data, netParams) - if tc.wantErr != gotErr { + if !errors.Is(gotErr, tc.wantErr) { t.Fatalf("Unexpected error. want=%v got=%v", tc.wantErr, gotErr) } diff --git a/zpay32/invoice_test.go b/zpay32/invoice_test.go index 3dca55be73..ec3768cf4c 100644 --- a/zpay32/invoice_test.go +++ b/zpay32/invoice_test.go @@ -253,56 +253,54 @@ func TestDecodeEncode(t *testing.T) { encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85frqg00000000j9n4evl6mr5aj9f58zp6fyjzup6ywn3x6sk8akg5v4tgn2q8g4fhx05wf6juaxu9760yp46454gpg5mtzgerlzezqcqvjnhjh8z3g2qqsj5cgu", // invalid routing info length: not a multiple of 51 valid: false, }, + { + // no payment secret set + encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jszjlrkes0mgx4ghum45ha5gkzlac8xmrr4skgweyp6xqxucu4wz4j8uvpg0jznsesezax6hdt0gtyn3tuqpfy2curryn83zygkydmpxcqdfu7k0", + valid: false, + wantErr: ErrPaymentSecretNotFound, + }, + { + // payment secret with invalid length. + encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsspszyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zeams9aqn0ar5rz2lu6g29vqlc5rxwg384dnh482wpjpslmx8dfy6l8l2u6d75xe8lj3te4rfg37u0dcatyzxews2skx8a6dkt4tt0qqv548nr", + valid: false, + wantErr: ErrInvalidFieldLength, + }, + { + // pubkey set in 'n' field with invalid length. + encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsnpjz44wxwc2yzxsw3qej933wl5sn6qpwmj4m9az7gs7mc8exnwe45sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsa45ms8u8avxsgp0mjrxnf7u888ezl95wse779emq4wawy56en7hpyarvzq8rshgx80luw9m3cxe2mj5kv95pfljpzc3sjw76azyqdvgqpu7a59", + valid: false, + wantErr: ErrInvalidFieldLength, + }, { // no payment hash set encodedInvoice: "lnbc20m1pvjluezhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsjv38luh6p6s2xrv3mzvlmzaya43376h0twal5ax0k6p47498hp3hnaymzhsn424rxqjs0q7apn26yrhaxltq3vzwpqj9nc2r3kzwccsplnq470", valid: false, - decodedInvoice: func() *Invoice { - return &Invoice{ - Net: &chaincfg.MainNetParams, - MilliSat: &testMillisat20mBTC, - Timestamp: time.Unix(1496314658, 0), - DescriptionHash: &testDescriptionHash, - Destination: testPubKey, - Features: emptyFeatures, - } - }, + }, + { + // payment hash set with invalid length. + encodedInvoice: "lnbc20m1pvjluezppsqgpsgpgxquyqjqqpqgpsgpgxquyqjqqpqgpsgpgxquyqjqgzdq5xysxxatsyp3k7enxv4jsnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs43fqhf8np9n9xs6zgepegrmqfkq6crexhvhqp5958mettsrm6mh9jsxkeuc8vx5n0fg53hyjf5shnxzz5p67xljjq9rwt54rwfj5pfgq9ythaz", + valid: false, + wantErr: ErrInvalidFieldLength, }, { // Both Description and DescriptionHash set. - encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqs03vghs8y0kuj4ulrzls8ln7fnm9dk7sjsnqmghql6hd6jut36clkqpyuq0s5m6fhureyz0szx2qjc8hkgf4xc2hpw8jpu26jfeyvf4cpga36gt", + encodedInvoice: "lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsu6zmmjhtn0uhrqje9x4y2c05mjvvg0ftwg32cnjkzs2vwmuf7ltysjlvkvh2pkgg20ssp4muprn93ezasdgn6aezu5ec5g54nju9kkgqtf6fht", valid: false, - decodedInvoice: func() *Invoice { - return &Invoice{ - Net: &chaincfg.MainNetParams, - MilliSat: &testMillisat20mBTC, - Timestamp: time.Unix(1496314658, 0), - PaymentHash: &testPaymentHash, - Description: &testPleaseConsider, - DescriptionHash: &testDescriptionHash, - Destination: testPubKey, - Features: emptyFeatures, - } - }, }, { // Neither Description nor DescriptionHash set. - encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqn2rne0kagfl4e0xag0w6hqeg2dwgc54hrm9m0auw52dhwhwcu559qav309h598pyzn69wh2nqauneyyesnpmaax0g6acr8lh9559jmcquyq5a9", + encodedInvoice: "lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqwul6apumndhf3t4v50sdx0vc9jma3cjfq49reu9a6rsadhs933nsau8vwumegq0scs492xx5s6zp6rmr50gd2pdkv285kzsr7zt5xjsp69w7kw", valid: false, - decodedInvoice: func() *Invoice { - return &Invoice{ - Net: &chaincfg.MainNetParams, - MilliSat: &testMillisat20mBTC, - Timestamp: time.Unix(1496314658, 0), - PaymentHash: &testPaymentHash, - Destination: testPubKey, - Features: emptyFeatures, - } - }, + }, + { + // DescriptionHash set with invalid length. + encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhpskmm8utp5qqmw6ysf8h2yuqmgmudkagnv20d7fqgltr74mwxpsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs2ul3cn2n4j3gcl5yxv0mwp7zkslunmv405hlz0uesckxzu5nk60pv6pfq3zkuewfugx28u3vp9y36eutpa0kdwahu2perdlr4cdjclqpl7zwtf", + valid: false, + wantErr: ErrInvalidFieldLength, }, { // Has a few unknown fields, should just be ignored. - encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqtq2v93xxer9vczq8v93xxeqv72xr42ca60022jqu6fu73n453tmnr0ukc0pl0t23w7eavtensjz0j2wcu7nkxhfdgp9y37welajh5kw34mq7m4xuay0a72cwec8qwgqt5vqht", + encodedInvoice: "lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqtq0w4hxkmn0wahrqvgzq0w4hxkmn0wahrqvselxnzkx7sdr4jjeqz8c022yhhkwjskhg32kcprhc08atq06megxyy8xclrsuc2tpfnayhskjj6dxmy34330rxd32cp3evjcdaqqkmdqprtqpuh", valid: true, decodedInvoice: func() *Invoice { return &Invoice{ @@ -313,20 +311,15 @@ func TestDecodeEncode(t *testing.T) { Description: &testPleaseConsider, Destination: testPubKey, Features: emptyFeatures, + PaymentAddr: fn.Some(specPaymentAddr), } }, skipEncoding: true, // Skip encoding since we don't have the unknown fields to encode. }, { // Ignore unknown witness version in fallback address. - encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqq" + - "qsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan" + - "79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrq" + - "sfp4z6yn92zrp97a6q5hhh8swys7uf4hm9tr8a0xylnk" + - "26fvkg3jx0sdsxvma0zvf2h0pycyyzdrmjncq6lzrfuw" + - "xfhv6gzz4q5303n3up6as4ghe5qthg7x20z7vae8w5rq" + - "u6de3g4jl7kvuap3qedprqsqqmgqqm6s8sl", - valid: true, + encodedInvoice: "lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfp4z6yn92zrp97a6q5hhh8swys7uf4hm9tr8a0xylnk26fvkg3jx0sdsvy8e63r5vpmxxzfh7jm87h0unhsssqfara08llrvz2zphyw2hreqsa89482kprux7pcgvsuxa8rd4vqj6duzvgat40jk8ke7uyed3asqqqa2fg", + valid: true, decodedInvoice: func() *Invoice { return &Invoice{ Net: &chaincfg.MainNetParams, @@ -336,39 +329,21 @@ func TestDecodeEncode(t *testing.T) { DescriptionHash: &testDescriptionHash, Destination: testPubKey, Features: emptyFeatures, + PaymentAddr: fn.Some(specPaymentAddr), } }, skipEncoding: true, // Skip encoding since we don't have the unknown fields to encode. }, { - // Ignore fields with unknown lengths. The wrong-length - // duplicates of the h and n fields are skipped, while - // the valid p, h, and n fields are used. - encodedInvoice: "lnbc241pveeq09pp5qqqsyqcyq5rqwzqf" + - "qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan" + - "79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqshp38yjmd" + - "an79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahnp4q0n326hr8v" + - "9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66np3q0n326hr8v" + - "9zprg8gsvezcch06gfaqqhde2aj730yg0durunfp3ngd7vju6eywrly" + - "v9vu7l797m4x5yxvvhqd4rm8guqw5389vna986py0hkxen8kmtmte4d" + - "gv439wksk2rh4smnm5w43a0e43lecjvqptnpz74", - valid: true, - decodedInvoice: func() *Invoice { - return &Invoice{ - Net: &chaincfg.MainNetParams, - MilliSat: &testMillisat24BTC, - Timestamp: time.Unix(1503429093, 0), - PaymentHash: &testPaymentHash, - Destination: testPubKey, - DescriptionHash: &testDescriptionHash, - Features: emptyFeatures, - } - }, - skipEncoding: true, // Skip encoding since we don't have the unknown fields to encode. + // Reject fields with unknown lengths. + encodedInvoice: "lnbc241pveeq09pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqppsqgpsgpgxquyqjqqpqgpsgpgxquyqjqqpqgpsgpgxquyqjqgznp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66npjz44wxwc2yzxsw3qej933wl5sn6qpwmj4m9az7gs7mc8exnwe45hp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqshpskmm8utp5qqmw6ysf8h2yuqmgmudkagnv20d7fqgltr74mwxpsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsspszyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3hgvfdj2zty30gcyl6flpqrdztw56n8klxwrg2yqw3tjwkqy7k3s9av2rf2s4u5wm4k4e44rd2cvjk9cutlaqwv7uklgnd3zvrt79zgqqutvndk", + valid: false, + wantErr: ErrInvalidFieldLength, }, { - // Reject a duplicate payment hash even if it has an - // unknown length. + // A second payment hash with an unknown length is + // rejected by the length check before the duplicate + // check is reached. encodedInvoice: "lnbc241pveeq09pp5qqqsyqcyq5rqwzqf" + "qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqpp3qqqsyqcyq5rq" + "wzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqhp58yjmdan79s6" + @@ -379,11 +354,11 @@ func TestDecodeEncode(t *testing.T) { "wkymx47cszugvzn5v64ahemzzlmm62rpn9l9rm05h35aceq00tkt2" + "96289wepws9jh4499wq2l0vk6xcxffd90dpuqchqqztyayq", valid: false, - wantErr: ErrDuplicatePaymentHash, + wantErr: ErrInvalidFieldLength, }, { // Invoice with no amount. - encodedInvoice: "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jshwlglv23cytkzvq8ld39drs8sq656yh2zn0aevrwu6uqctaklelhtpjnmgjdzmvwsh0kuxuwqf69fjeap9m5mev2qzpp27xfswhs5vgqmn9xzq", + encodedInvoice: "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jssp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsk7mxg4jd0r638awa4kd6n0pzn6wtg33jg6ptdx68vclhlwpwxz7h4kn2tlah4zn3zwsgvn4xrcszhz0qu9nhem7u8lt4tz5vmkd8a5gpe4w8ra", valid: true, decodedInvoice: func() *Invoice { return &Invoice{ @@ -393,6 +368,7 @@ func TestDecodeEncode(t *testing.T) { Description: &testCupOfCoffee, Destination: testPubKey, Features: emptyFeatures, + PaymentAddr: fn.Some(specPaymentAddr), } }, beforeEncoding: func(i *Invoice) { @@ -404,7 +380,7 @@ func TestDecodeEncode(t *testing.T) { }, { // Please make a donation of any amount using rhash 0001020304050607080900010203040506070809000102030405060708090102 to me @03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad - encodedInvoice: "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w", + encodedInvoice: "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs3u3lp8kstkqc5h5u2s960n5qjkzzsqasr9rle9z3rw08frm5j62x8qhj66t83qccmkx7zh29puy7zpq0nf7qws0dkq5q6mav7n87rhgp5skgmx", valid: true, decodedInvoice: func() *Invoice { return &Invoice{ @@ -414,6 +390,7 @@ func TestDecodeEncode(t *testing.T) { Description: &testPleaseConsider, Destination: testPubKey, Features: emptyFeatures, + PaymentAddr: fn.Some(specPaymentAddr), } }, beforeEncoding: func(i *Invoice) { @@ -425,7 +402,7 @@ func TestDecodeEncode(t *testing.T) { }, { // Same as above, pubkey set in 'n' field. - encodedInvoice: "lnbc241pveeq09pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66jd3m5klcwhq68vdsmx2rjgxeay5v0tkt2v5sjaky4eqahe4fx3k9sqavvce3capfuwv8rvjng57jrtfajn5dkpqv8yelsewtljwmmycq62k443", + encodedInvoice: "lnbc241pveeq09pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygse2tu9s2lqeved59qef46rl2rsd5tkzdc9pjr6z3299aaaskgnk85zg96ng3xlknry8d9f9ssrqan6uj9dtm9mc00wcsam7awtkdgh6sqpf7nq7", valid: true, decodedInvoice: func() *Invoice { return &Invoice{ @@ -436,12 +413,13 @@ func TestDecodeEncode(t *testing.T) { Destination: testPubKey, Description: &testEmptyString, Features: emptyFeatures, + PaymentAddr: fn.Some(specPaymentAddr), } }, }, { // Please send $3 for a cup of coffee to the same peer, within 1 minute - encodedInvoice: "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp", + encodedInvoice: "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsxfj8ru680ls6z3cqpanuvtyqux2xuek3kr9xnfek7p258x4vhhns0hl5uvzryshanhlxnhl0fglhw9klpxl0rzsn6rv63uxwup0sgtspjum4ek", valid: true, decodedInvoice: func() *Invoice { i, _ := NewInvoice( @@ -451,7 +429,8 @@ func TestDecodeEncode(t *testing.T) { Amount(testMillisat2500uBTC), Description(testCupOfCoffee), Destination(testPubKey), - Expiry(testExpiry60)) + Expiry(testExpiry60), + PaymentAddr(specPaymentAddr)) return i }, beforeEncoding: func(i *Invoice) { @@ -463,7 +442,7 @@ func TestDecodeEncode(t *testing.T) { }, { // Please send 0.0025 BTC for a cup of nonsense (ナンセンス 1杯) to the same peer, within 1 minute - encodedInvoice: "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpuyk0sg5g70me25alkluzd2x62aysf2pyy8edtjeevuv4p2d5p76r4zkmneet7uvyakky2zr4cusd45tftc9c5fh0nnqpnl2jfll544esqchsrny", + encodedInvoice: "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpquwpc4curk03c9wlrswe78q4eyqc7d8d0xqzpusp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygslten3f0ydjxklpy86g3sh6yz5ar7f0jxjas9dkdvggwwnvq2q93xle0gsfxmm3qmulh5y5q5gunhj3r2mqn8glden5g7d6n8mzxze7cq4mqn5r", valid: true, decodedInvoice: func() *Invoice { i, _ := NewInvoice( @@ -473,7 +452,8 @@ func TestDecodeEncode(t *testing.T) { Amount(testMillisat2500uBTC), Description(testCupOfNonsense), Destination(testPubKey), - Expiry(testExpiry60)) + Expiry(testExpiry60), + PaymentAddr(specPaymentAddr)) return i }, beforeEncoding: func(i *Invoice) { @@ -485,7 +465,7 @@ func TestDecodeEncode(t *testing.T) { }, { // Now send $24 for an entire list of things (hashed) - encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqscc6gd6ql3jrc5yzme8v4ntcewwz5cnw92tz0pc8qcuufvq7khhr8wpald05e92xw006sq94mg8v2ndf4sefvf9sygkshp5zfem29trqq2yxxz7", + encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqssp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsz7q6sc79jzdwtegpf58988jufqr7qy0n59me7pc7wk4pl8ym6k94kjzyxccs6cc9kk88djqeac3lprndgtn2whk25xzw4xenr95ldhsq5k7v5a", valid: true, decodedInvoice: func() *Invoice { return &Invoice{ @@ -496,6 +476,7 @@ func TestDecodeEncode(t *testing.T) { DescriptionHash: &testDescriptionHash, Destination: testPubKey, Features: emptyFeatures, + PaymentAddr: fn.Some(specPaymentAddr), } }, beforeEncoding: func(i *Invoice) { @@ -507,7 +488,7 @@ func TestDecodeEncode(t *testing.T) { }, { // The same, on testnet, with a fallback address mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP - encodedInvoice: "lntb20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3x9et2e20v6pu37c5d9vax37wxq72un98k6vcx9fz94w0qf237cm2rqv9pmn5lnexfvf5579slr4zq3u8kmczecytdx0xg9rwzngp7e6guwqpqlhssu04sucpnz4axcv2dstmknqq6jsk2l", + encodedInvoice: "lntb20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3x9et2e20v6pu37c5d9vax37wxq72un98sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs30kjz30tq32mcqcl0scp9dydfk647cfapf9xcnc2e8ea53j6ya5ne96gylvnrldlnq3x9x6vfqzqmpeuyepzsrgdwevtwd74xppnnxcphq9hqz", valid: true, decodedInvoice: func() *Invoice { return &Invoice{ @@ -519,6 +500,7 @@ func TestDecodeEncode(t *testing.T) { Destination: testPubKey, FallbackAddr: testAddrTestnet, Features: emptyFeatures, + PaymentAddr: fn.Some(specPaymentAddr), } }, beforeEncoding: func(i *Invoice) { @@ -530,7 +512,7 @@ func TestDecodeEncode(t *testing.T) { }, { // On mainnet, with fallback address 1RustyRX2oai4EYYDpQGWvEL62BBGqN9T with extra routing info to get to node 029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 - encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85frzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvncsk57n4v9ehw86wq8fzvjejhv9z3w3q5zh6qkql005x9xl240ch23jk79ujzvr4hsmmafyxghpqe79psktnjl668ntaf4ne7ucs5csqh5mnnk", + encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85frzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqqqqqqq9qqqvsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs6jh3xwjhyldezxr7wx6myldtttk34k2q3jaqc4lyxcsfevjeh09kkv8d23pkdpeuf83wmftrwklazsftuwx8zgghn4jxafd3gq0k6vsqvh5ejq", valid: true, decodedInvoice: func() *Invoice { return &Invoice{ @@ -543,6 +525,7 @@ func TestDecodeEncode(t *testing.T) { FallbackAddr: testRustyAddr, RouteHints: [][]HopHint{testSingleHop}, Features: emptyFeatures, + PaymentAddr: fn.Some(specPaymentAddr), } }, beforeEncoding: func(i *Invoice) { @@ -554,7 +537,7 @@ func TestDecodeEncode(t *testing.T) { }, { // On mainnet, with fallback address 1RustyRX2oai4EYYDpQGWvEL62BBGqN9T with extra routing info to go via nodes 029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 then 039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 - encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqj9n4evl6mr5aj9f58zp6fyjzup6ywn3x6sk8akg5v4tgn2q8g4fhx05wf6juaxu9760yp46454gpg5mtzgerlzezqcqvjnhjh8z3g2qqdhhwkj", + encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsa492vv7x3elyzpkck3gyex3al2yywz0sm4clf3t4ayxz49005axh7qx3hp72wflvde27h8mp5rfv4ctvcqc8fy48r4mm69gpeucpadcq9vwr6c", valid: true, decodedInvoice: func() *Invoice { return &Invoice{ @@ -567,6 +550,7 @@ func TestDecodeEncode(t *testing.T) { FallbackAddr: testRustyAddr, RouteHints: [][]HopHint{testDoubleHop}, Features: emptyFeatures, + PaymentAddr: fn.Some(specPaymentAddr), } }, beforeEncoding: func(i *Invoice) { @@ -578,7 +562,7 @@ func TestDecodeEncode(t *testing.T) { }, { // On mainnet, with fallback (p2sh) address 3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX - encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfppj3a24vwu6r8ejrss3axul8rxldph2q7z9kk822r8plup77n9yq5ep2dfpcydrjwzxs0la84v3tfw43t3vqhek7f05m6uf8lmfkjn7zv7enn76sq65d8u9lxav2pl6x3xnc2ww3lqpagnh0u", + encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfppj3a24vwu6r8ejrss3axul8rxldph2q7z9sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygslp9th3hnp54ppyeh3cef40gv34rm63xdc6cvgn2wjdmxj860453zjjmm0h55spgl78vwjfnd9x8cjfwd0kckgct5r00d7ufm8t3lc0sp5e70g5", valid: true, decodedInvoice: func() *Invoice { return &Invoice{ @@ -590,6 +574,7 @@ func TestDecodeEncode(t *testing.T) { Destination: testPubKey, FallbackAddr: testAddrMainnetP2SH, Features: emptyFeatures, + PaymentAddr: fn.Some(specPaymentAddr), } }, beforeEncoding: func(i *Invoice) { @@ -655,7 +640,7 @@ func TestDecodeEncode(t *testing.T) { }, { // On mainnet, with fallback (p2wpkh) address bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 - encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfppqw508d6qejxtdg4y5r3zarvary0c5xw7kknt6zz5vxa8yh8jrnlkl63dah48yh6eupakk87fjdcnwqfcyt7snnpuz7vp83txauq4c60sys3xyucesxjf46yqnpplj0saq36a554cp9wt865", + encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfppqw508d6qejxtdg4y5r3zarvary0c5xw7ksp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs6dc3xx6alc55ypd3fqh6m4u53ksg72dz0grj0l2rzkeap48xt3u9q2m9mwp5gfu60f84zqnnky0rp9etwqkd43ra83mjxxyqnl0r6kcpru52gx", valid: true, decodedInvoice: func() *Invoice { return &Invoice{ @@ -667,6 +652,7 @@ func TestDecodeEncode(t *testing.T) { Destination: testPubKey, FallbackAddr: testAddrMainnetP2WPKH, Features: emptyFeatures, + PaymentAddr: fn.Some(specPaymentAddr), } }, beforeEncoding: func(i *Invoice) { @@ -678,7 +664,7 @@ func TestDecodeEncode(t *testing.T) { }, { // On mainnet, with fallback (p2wsh) address bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3 - encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfp4qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qvnjha2auylmwrltv2pkp2t22uy8ura2xsdwhq5nm7s574xva47djmnj2xeycsu7u5v8929mvuux43j0cqhhf32wfyn2th0sv4t9x55sppz5we8", + encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfp4qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsl8ea2vka9sd0vue9qp5tzqzgl4sj982pcpzrrgzdh5hlhdf44dayxy9fudw25kz36t5hgm0q8w4xsavsz28mhtxk567qhmmy6x3eprsp0zwaz6", valid: true, decodedInvoice: func() *Invoice { return &Invoice{ @@ -690,6 +676,7 @@ func TestDecodeEncode(t *testing.T) { Destination: testPubKey, FallbackAddr: testAddrMainnetP2WSH, Features: emptyFeatures, + PaymentAddr: fn.Some(specPaymentAddr), } }, beforeEncoding: func(i *Invoice) { @@ -702,49 +689,10 @@ func TestDecodeEncode(t *testing.T) { { // On mainnet, with fallback (p2tr) address "bc1pptdvg0d // 2nj99568qn6ssdy4cygnwuxgw2ukmnwgwz7jpqjz2kszse2s3lm" - encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqq" + - "qsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan" + - "79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrq" + - "sfp4pptdvg0d2nj99568qn6ssdy4cygnwuxgw2ukmnwg" + - "wz7jpqjz2kszs9zs3tmcpgulwc0ruwc2cm97udy6sdfe" + - "nwvha8qlkfwx49sgk40kze4kwsh706rae3uc30ltpwpw" + - "mjyhc3uan4ljz56wksg5gsnhrrhcqsrq93d", - - valid: true, - decodedInvoice: func() *Invoice { - return &Invoice{ - Net: &chaincfg.MainNetParams, - MilliSat: &testMillisat20mBTC, - Timestamp: time.Unix(1496314658, 0), - PaymentHash: &testPaymentHash, - DescriptionHash: &testDescriptionHash, - Destination: testPubKey, - FallbackAddr: testAddrMainnetP2TR, - Features: emptyFeatures, - } - }, - beforeEncoding: func(i *Invoice) { - // Since this destination pubkey was recovered - // from the signature, we must set it nil before - // encoding to get back the same invoice string. - i.Destination = nil - }, - }, - { - // On mainnet, with fallback (p2tr) address "bc1pptdvg0d - // 2nj99568qn6ssdy4cygnwuxgw2ukmnwgwz7jpqjz2kszse2s3lm" - // using the test vector payment from BOLT 11 - encodedInvoice: "lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zy" + - "g3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqc" + - "yq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqyp" + - "qhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98kly" + - "sy043l2ahrqsfp4pptdvg0d2nj99568qn6ssdy4cygnw" + - "uxgw2ukmnwgwz7jpqjz2kszs9qrsgqy606dznq28exny" + - "dt2r4c29y56xjtn3sk4mhgjtl4pg2y4ar3249rq4ajlm" + - "j9jy8zvlzw7cr8mggqzm842xfr0v72rswzq9xvr4hknf" + - "sqwmn6xd", - - valid: true, + // and a payment secret, with the tagged fields in the + // order LND encodes them. + encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfp4pptdvg0d2nj99568qn6ssdy4cygnwuxgw2ukmnwgwz7jpqjz2kszssp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqcresgm59s2yvvuc67ep5wah53rv6a22uwx2u3hm70gx7r334g5qsxy30063452qv2nsfuj88khr8xxdhe79p0ukxqh8lhkagwalshwgqwalarg", + valid: true, decodedInvoice: func() *Invoice { return &Invoice{ Net: &chaincfg.MainNetParams, @@ -763,14 +711,17 @@ func TestDecodeEncode(t *testing.T) { PaymentAddr: fn.Some(specPaymentAddr), } }, - // Skip encoding since LND encode the tagged fields - // in a different order. - skipEncoding: true, + beforeEncoding: func(i *Invoice) { + // Since this destination pubkey was recovered + // from the signature, we must set it nil before + // encoding to get back the same invoice string. + i.Destination = nil + }, }, { // Send 2500uBTC for a cup of coffee with a custom CLTV // expiry value. - encodedInvoice: "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jscqzysnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66ysxkvnxhcvhz48sn72lp77h4fxcur27z0he48u5qvk3sxse9mr9jhkltt962s8arjnzk8rk59yj5nw4p495747gksj30gza0crhzwjcpgxzy00", + encodedInvoice: "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jscqzysnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs4uwcaqm89zz7y0t8xlhyt80r8mc5yy2q33j0wctghfnwjf3j8fgzpe0q0reenxg92eweyxg67c32x4kqa0wt0xaq0t6s307kltapmssqglqfg3", valid: true, decodedInvoice: func() *Invoice { i, _ := NewInvoice( @@ -781,6 +732,7 @@ func TestDecodeEncode(t *testing.T) { Description(testCupOfCoffee), Destination(testPubKey), CLTVExpiry(144), + PaymentAddr(specPaymentAddr), ) return i @@ -1027,8 +979,34 @@ func TestDecodeDuplicatePaymentHashes(t *testing.T) { } } +// TestDecodeSkipPaymentSecretCheck checks that an invoice without a payment +// secret is rejected by default, and accepted when the payment secret check is +// skipped. +func TestDecodeSkipPaymentSecretCheck(t *testing.T) { + t.Parallel() + + encodedInvoice := "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5" + + "rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7en" + + "xv4jszjlrkes0mgx4ghum45ha5gkzlac8xmrr4skgwey" + + "p6xqxucu4wz4j8uvpg0jznsesezax6hdt0gtyn3tuqpf" + + "y2curryn83zygkydmpxcqdfu7k0" + + _, err := Decode(encodedInvoice, &chaincfg.MainNetParams) + require.ErrorIs(t, err, ErrPaymentSecretNotFound) + + invoice, err := Decode( + encodedInvoice, &chaincfg.MainNetParams, + WithSkipPaymentSecretCheck(), + ) + require.NoError(t, err) + require.Equal(t, testPaymentHash, *invoice.PaymentHash) + require.True(t, invoice.PaymentAddr.IsNone()) +} + // TestNewInvoice tests that providing the optional arguments to the NewInvoice // method creates an Invoice that encodes to the expected string. +// +//nolint:ll func TestNewInvoice(t *testing.T) { t.Parallel() @@ -1043,7 +1021,9 @@ func TestNewInvoice(t *testing.T) { return NewInvoice(&chaincfg.MainNetParams, testPaymentHash, time.Unix(1496314658, 0), DescriptionHash(testDescriptionHash), - Description(testPleaseConsider)) + Description(testPleaseConsider), + PaymentAddr(specPaymentAddr), + ) }, valid: false, // Both Description and DescriptionHash set. }, @@ -1055,10 +1035,11 @@ func TestNewInvoice(t *testing.T) { testPaymentHash, time.Unix(1496314658, 0), Description(testCupOfCoffee), + PaymentAddr(specPaymentAddr), ) }, valid: true, - encodedInvoice: "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jshwlglv23cytkzvq8ld39drs8sq656yh2zn0aevrwu6uqctaklelhtpjnmgjdzmvwsh0kuxuwqf69fjeap9m5mev2qzpp27xfswhs5vgqmn9xzq", + encodedInvoice: "lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jssp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsk7mxg4jd0r638awa4kd6n0pzn6wtg33jg6ptdx68vclhlwpwxz7h4kn2tlah4zn3zwsgvn4xrcszhz0qu9nhem7u8lt4tz5vmkd8a5gpe4w8ra", }, { // 'n' field set. @@ -1067,10 +1048,12 @@ func TestNewInvoice(t *testing.T) { testPaymentHash, time.Unix(1503429093, 0), Amount(testMillisat24BTC), Description(testEmptyString), - Destination(testPubKey)) + Destination(testPubKey), + PaymentAddr(specPaymentAddr), + ) }, valid: true, - encodedInvoice: "lnbc241pveeq09pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66jd3m5klcwhq68vdsmx2rjgxeay5v0tkt2v5sjaky4eqahe4fx3k9sqavvce3capfuwv8rvjng57jrtfajn5dkpqv8yelsewtljwmmycq62k443", + encodedInvoice: "lnbc241pveeq09pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygse2tu9s2lqeved59qef46rl2rsd5tkzdc9pjr6z3299aaaskgnk85zg96ng3xlknry8d9f9ssrqan6uj9dtm9mc00wcsam7awtkdgh6sqpf7nq7", }, { // On mainnet, with fallback address 1RustyRX2oai4EYYDpQGWvEL62BBGqN9T with extra routing info to go via nodes 029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 then 039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255 @@ -1081,10 +1064,11 @@ func TestNewInvoice(t *testing.T) { DescriptionHash(testDescriptionHash), FallbackAddr(testRustyAddr), RouteHint(testDoubleHop), + PaymentAddr(specPaymentAddr), ) }, valid: true, - encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqj9n4evl6mr5aj9f58zp6fyjzup6ywn3x6sk8akg5v4tgn2q8g4fhx05wf6juaxu9760yp46454gpg5mtzgerlzezqcqvjnhjh8z3g2qqdhhwkj", + encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpp3qjmp7lwpagxun9pygexvgpjdc4jdj85fr9yq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpeuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4grqszsvpcgpy9qqqqqqgqqqqq7qqzqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsa492vv7x3elyzpkck3gyex3al2yywz0sm4clf3t4ayxz49005axh7qx3hp72wflvde27h8mp5rfv4ctvcqc8fy48r4mm69gpeucpadcq9vwr6c", }, { // On simnet @@ -1093,10 +1077,12 @@ func TestNewInvoice(t *testing.T) { testPaymentHash, time.Unix(1496314658, 0), Amount(testMillisat24BTC), Description(testEmptyString), - Destination(testPubKey)) + Destination(testPubKey), + PaymentAddr(specPaymentAddr), + ) }, valid: true, - encodedInvoice: "lnsb241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66jdgev3gnwg0aul7unhqlqvrkp23f0negjsw8ac9f6wa8w9nvppgp3updmr5znhze6l5zneztc0alknntn0wv8fkkgvjqwp0jss66cngqcj9tj6", + encodedInvoice: "lnsb241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs6ypz232f2e8t2u957z8p82h0gkjpzcv4e3jaggs7vje4mqhtyvspl75kv9mkt3ye35rhh904m4mtz665jwkrmu2cdkjdfcz05s6ut5qqke0kry", }, { // On regtest @@ -1105,10 +1091,12 @@ func TestNewInvoice(t *testing.T) { testPaymentHash, time.Unix(1496314658, 0), Amount(testMillisat24BTC), Description(testEmptyString), - Destination(testPubKey)) + Destination(testPubKey), + PaymentAddr(specPaymentAddr), + ) }, valid: true, - encodedInvoice: "lnbcrt241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66df5c8pqjjt4z4ymmuaxfx8eh5v7hmzs3wrfas8m2sz5qz56rw2lxy8mmgm4xln0ha26qkw6u3vhu22pss2udugr9g74c3x20slpcqjgq0el4h6", + encodedInvoice: "lnbcrt241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsxuyyugag372x5v5ntk8ep3ag2hnk99acwn58yjnqna7htk364n0khlrx0htkvq58d280y9yeqjxpsludequ3sl5xwf9mthkr6dr8f5qp07r5jw", }, { // Mainnet invoice with two blinded paths. @@ -1122,8 +1110,7 @@ func TestNewInvoice(t *testing.T) { WithBlindedPaymentPath(blindedPath2), ) }, - valid: true, - //nolint:ll + valid: true, encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4js5fdqqqqq2qqqqqpgqyzqqqqqqqqqqqqyqqqqqqqqqqqvsqqqqlnxy0ffrlt2y2jgtzw89kgr3zg4dlwtlfycn3yuek8x5eucnuchqps82xf0m2u6sx5wnjw7xxgnxz5kf09quqsv5zvkgj7d5kpzttp4qz7q5qsyqcyq5pzq2feycsemrh7wvendc4kw3tsmkt25a36ev6kfehrv7ecfkrp5zs9q5zqxqspqtr4avek5quzjn427asptzews5wrczfhychr2sq6ue9phmn35tjqcrspqgpsgpgxquyqjzstpsxsu59zqqqqqpqqqqqqyqq2qqqqqqqqqqqqqqqqqqqqqqqqpgqqqqk8t6endgpc99824amqzk9japgu8synwf3wx4qp4ej2r0h8rghypsqsygpf8ynzr8vwleenxdhzke69wrwed2nk8t9n2e8xudnm8pxcvxs2q5qsyqcyq5y4rdlhtf84f8rgdj34275juwls2ftxtcfh035863q3p9k6s94hpxhdmzfn5gxpsazdznxs56j4vt3fdhe00g9v2l3szher50hp4xlggqkxf77f", }, } @@ -1150,6 +1137,8 @@ func TestNewInvoice(t *testing.T) { // TestMaxInvoiceLength tests that attempting to decode an invoice greater than // maxInvoiceLength fails with ErrInvoiceTooLarge. +// +//nolint:ll func TestMaxInvoiceLength(t *testing.T) { t.Parallel() @@ -1159,7 +1148,7 @@ func TestMaxInvoiceLength(t *testing.T) { }{ { // Valid since it is less than maxInvoiceLength. - encodedInvoice: "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeesrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvrzjq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv660a66stp9cgqfnp8xklqlxm655zp9x435k6px4hnsajsp00dtcgrxygq67f32qrmxvfk9qcs8mwhzxu8gxr24k2s7364jc9rn4xtn3ncq4t8cej", + encodedInvoice: "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeesr75q20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpfuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4gpqgpsgpgxquyqqqqqqyqqqqq5qqps98sr4yqms4f5lu0f93puw3p37l88yprqvr70022uxls53auvwuj4qypqxpq9qcrssqqqqqqsqqqqzsqqxq57qw5srwz4xnl3ayky836yx8muuusyvps0eaaftsm7zj8h33mj25qsyqcyq5rqwzqqqqqqzqqqqq2qqqczncp6jqdc2560785jcs78gscl0nnjq3sxpl8h49wr0c2g77x8wf2szqsrqszsvpcgqqqqqqgqqqqpgqqrq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpfuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4gpqgpsgpgxquyqqqqqqyqqqqq5qqps98sr4yqms4f5lu0f93puw3p37l88yprqvr70022uxls53auvwuj4qypqxpq9qcrssqqqqqqsqqqqzsqqxq57qw5srwz4xnl3ayky836yx8muuusyvps0eaaftsm7zj8h33mj25qsyqcyq5rqwzqqqqqqzqqqqq2qqqczncp6jqdc2560785jcs78gscl0nnjq3sxpl8h49wr0c2g77x8wf2szqsrqszsvpcgqqqqqqgqqqqpgqqrq20q82gphp2nflc7jtzrcazrra7wwgzxqc8u7754cdlpfrmccae92qgzqvzq2ps8pqqqqqqpqqqqq9qqqvpfuqafqxu92d8lr6fvg0r5gv0heeeqgcrqlnm6jhphu9y00rrhy4gpqgpsgpgxquyqqqqqqyqqqqq5qqpssp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygshsu4qvmajk64zafgqprkqpk9tj5ljf9n8l4pkqpzunrs4sy7mpy8vsf2lk8532vdzz4sl0kdf07ccp7uz5mq7zxg5ylhvr45w25q9xsqea9kfg", }, { // Invalid since it is greater than maxInvoiceLength. @@ -1184,8 +1173,12 @@ func TestMaxInvoiceLength(t *testing.T) { // checksum in bech32 strings cannot cause a signature to become valid and // therefore cause a wrong destination to be decoded for invoices where the // destination is extracted from the signature. +// +//nolint:ll func TestInvoiceChecksumMalleability(t *testing.T) { - privKeyHex := "a50f3bdf9b6c4b1fdd7c51a8bbf4b5855cf381f413545ed155c0282f4412a1b1" + // The key is chosen so that the checksum of the encoded invoice + // ends in "p", which the test below relies on. + privKeyHex := "a50f3bdf9b6c4b1fdd7c51a8bbf4b5855cf381f413545ed155c0282f4412b1b1" privKeyBytes, _ := hex.DecodeString(privKeyHex) chain := &chaincfg.SimNetParams var payHash [32]byte @@ -1198,7 +1191,9 @@ func TestInvoiceChecksumMalleability(t *testing.T) { return ecdsa.SignCompact(privKey, hash, true), nil }, } - opts := []func(*Invoice){Description("test")} + opts := []func(*Invoice){ + Description("test"), PaymentAddr(specPaymentAddr), + } invoice, err := NewInvoice(chain, payHash, ts, opts...) if err != nil { t.Fatal(err)