Skip to content

Commit 48a9a8d

Browse files
committed
zpay32: add functional opt to error out on unknown feature bit
This commit adds two functional options to the zpay32.Decode function. `WithKnownFeatureBits` allows the caller to overwrite the default set of known feature bits used by the function. `WithErrorOnUnknownFeatureBit` allows the caller to instruct the function to error out if the invoice that is decoded contaijns unknown feature bits. We then use this new error-out option from the `rpcServer`'s `extractPaymentIntent` method.
1 parent e4619af commit 48a9a8d

File tree

3 files changed

+133
-4
lines changed

3 files changed

+133
-4
lines changed

rpcserver.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5210,6 +5210,7 @@ func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPayme
52105210
if rpcPayReq.PaymentRequest != "" {
52115211
payReq, err := zpay32.Decode(
52125212
rpcPayReq.PaymentRequest, r.cfg.ActiveNetParams.Params,
5213+
zpay32.WithErrorOnUnknownFeatureBit(),
52135214
)
52145215
if err != nil {
52155216
return payIntent, err

zpay32/decode.go

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,53 @@ import (
1717
"github.com/lightningnetwork/lnd/lnwire"
1818
)
1919

20+
// DecodeOption is a type that can be used to supply functional options to the
21+
// Decode function.
22+
type DecodeOption func(*decodeOptions)
23+
24+
// WithKnownFeatureBits is a functional option that overwrites the set of
25+
// known feature bits. If not set, then LND's lnwire.Features variable will be
26+
// used by default.
27+
func WithKnownFeatureBits(features map[lnwire.FeatureBit]string) DecodeOption {
28+
return func(options *decodeOptions) {
29+
options.knownFeatureBits = features
30+
}
31+
}
32+
33+
// WithErrorOnUnknownFeatureBit is a functional option that will cause the
34+
// Decode function to return an error if the decoded invoice contains an unknown
35+
// feature bit.
36+
func WithErrorOnUnknownFeatureBit() DecodeOption {
37+
return func(options *decodeOptions) {
38+
options.errorOnUnknownFeature = true
39+
}
40+
}
41+
42+
// decodeOptions holds the set of Decode options.
43+
type decodeOptions struct {
44+
knownFeatureBits map[lnwire.FeatureBit]string
45+
errorOnUnknownFeature bool
46+
}
47+
48+
// newDecodeOptions constructs the default decodeOptions struct.
49+
func newDecodeOptions() *decodeOptions {
50+
return &decodeOptions{
51+
knownFeatureBits: lnwire.Features,
52+
errorOnUnknownFeature: false,
53+
}
54+
}
55+
2056
// Decode parses the provided encoded invoice and returns a decoded Invoice if
2157
// it is valid by BOLT-0011 and matches the provided active network.
22-
func Decode(invoice string, net *chaincfg.Params) (*Invoice, error) {
23-
decodedInvoice := Invoice{}
58+
func Decode(invoice string, net *chaincfg.Params, opts ...DecodeOption) (
59+
*Invoice, error) {
60+
61+
options := newDecodeOptions()
62+
for _, o := range opts {
63+
o(options)
64+
}
65+
66+
var decodedInvoice Invoice
2467

2568
// Before bech32 decoding the invoice, make sure that it is not too large.
2669
// This is done as an anti-DoS measure since bech32 decoding is expensive.
@@ -134,7 +177,7 @@ func Decode(invoice string, net *chaincfg.Params) (*Invoice, error) {
134177
// If no feature vector was decoded, populate an empty one.
135178
if decodedInvoice.Features == nil {
136179
decodedInvoice.Features = lnwire.NewFeatureVector(
137-
nil, lnwire.Features,
180+
nil, options.knownFeatureBits,
138181
)
139182
}
140183

@@ -144,6 +187,24 @@ func Decode(invoice string, net *chaincfg.Params) (*Invoice, error) {
144187
return nil, err
145188
}
146189

190+
if options.errorOnUnknownFeature {
191+
// Make sure that we understand all the required feature bits
192+
// in the invoice.
193+
unknownFeatureBits := decodedInvoice.Features.
194+
UnknownRequiredFeatures()
195+
196+
if len(unknownFeatureBits) > 0 {
197+
errStr := fmt.Sprintf("invoice contains " +
198+
"unknown feature bits:")
199+
200+
for _, bit := range unknownFeatureBits {
201+
errStr += fmt.Sprintf(" %d,", bit)
202+
}
203+
204+
return nil, fmt.Errorf(strings.TrimRight(errStr, ","))
205+
}
206+
}
207+
147208
return &decodedInvoice, nil
148209
}
149210

zpay32/invoice_test.go

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ func TestDecodeEncode(t *testing.T) {
191191
encodedInvoice string
192192
valid bool
193193
decodedInvoice func() *Invoice
194+
decodeOpts []DecodeOption
194195
skipEncoding bool
195196
beforeEncoding func(*Invoice)
196197
}{
@@ -758,6 +759,70 @@ func TestDecodeEncode(t *testing.T) {
758759
i.Destination = nil
759760
},
760761
},
762+
{
763+
// Invoice with unknown feature bits but since the
764+
// WithErrorOnUnknownFeatureBit option is not provided,
765+
// it is not expected to error out.
766+
encodedInvoice: "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqpqsqq40wa3khl49yue3zsgm26jrepqr2eghqlx86rttutve3ugd05em86nsefzh4pfurpd9ek9w2vp95zxqnfe2u7ckudyahsa52q66tgzcp6t2dyk",
767+
valid: true,
768+
skipEncoding: true,
769+
decodedInvoice: func() *Invoice {
770+
return &Invoice{
771+
Net: &chaincfg.MainNetParams,
772+
MilliSat: &testMillisat25mBTC,
773+
Timestamp: time.Unix(1496314658, 0),
774+
PaymentHash: &testPaymentHash,
775+
PaymentAddr: &specPaymentAddr,
776+
Description: &testCoffeeBeans,
777+
Destination: testPubKey,
778+
Features: lnwire.NewFeatureVector(
779+
lnwire.NewRawFeatureVector(
780+
9, 15, 99, 100,
781+
),
782+
lnwire.Features,
783+
),
784+
}
785+
},
786+
decodeOpts: []DecodeOption{
787+
WithKnownFeatureBits(map[lnwire.FeatureBit]string{
788+
9: "9",
789+
15: "15",
790+
99: "99",
791+
}),
792+
},
793+
},
794+
{
795+
// Invoice with unknown feature bits with option set to
796+
// error out on unknown feature bit.
797+
encodedInvoice: "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdeessp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q4psqqqqqqqqqqqqqqqpqsqq40wa3khl49yue3zsgm26jrepqr2eghqlx86rttutve3ugd05em86nsefzh4pfurpd9ek9w2vp95zxqnfe2u7ckudyahsa52q66tgzcp6t2dyk",
798+
valid: false,
799+
skipEncoding: true,
800+
decodedInvoice: func() *Invoice {
801+
return &Invoice{
802+
Net: &chaincfg.MainNetParams,
803+
MilliSat: &testMillisat25mBTC,
804+
Timestamp: time.Unix(1496314658, 0),
805+
PaymentHash: &testPaymentHash,
806+
PaymentAddr: &specPaymentAddr,
807+
Description: &testCoffeeBeans,
808+
Destination: testPubKey,
809+
Features: lnwire.NewFeatureVector(
810+
lnwire.NewRawFeatureVector(
811+
9, 15, 99, 100,
812+
),
813+
lnwire.Features,
814+
),
815+
}
816+
},
817+
decodeOpts: []DecodeOption{
818+
WithKnownFeatureBits(map[lnwire.FeatureBit]string{
819+
9: "9",
820+
15: "15",
821+
99: "99",
822+
}),
823+
WithErrorOnUnknownFeatureBit(),
824+
},
825+
},
761826
}
762827

763828
for i, test := range tests {
@@ -773,7 +838,9 @@ func TestDecodeEncode(t *testing.T) {
773838
net = decodedInvoice.Net
774839
}
775840

776-
invoice, err := Decode(test.encodedInvoice, net)
841+
invoice, err := Decode(
842+
test.encodedInvoice, net, test.decodeOpts...,
843+
)
777844
if !test.valid {
778845
require.Error(t, err)
779846
} else {

0 commit comments

Comments
 (0)