zpay32: add BOLT 11 feature bits and test vectors
This commit is contained in:
parent
8c2176fbf8
commit
1311baf51f
@ -67,6 +67,16 @@ const (
|
|||||||
|
|
||||||
// fieldTypeC contains an optional requested final CLTV delta.
|
// fieldTypeC contains an optional requested final CLTV delta.
|
||||||
fieldTypeC = 24
|
fieldTypeC = 24
|
||||||
|
|
||||||
|
// fieldType9 contains one or more bytes for signaling features
|
||||||
|
// supported or required by the receiver.
|
||||||
|
fieldType9 = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// InvoiceFeatures holds the set of all known feature bits that are
|
||||||
|
// exposed as BOLT 11 features.
|
||||||
|
InvoiceFeatures = map[lnwire.FeatureBit]string{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// MessageSigner is passed to the Encode method to provide a signature
|
// MessageSigner is passed to the Encode method to provide a signature
|
||||||
@ -146,6 +156,10 @@ type Invoice struct {
|
|||||||
//
|
//
|
||||||
// NOTE: This is optional.
|
// NOTE: This is optional.
|
||||||
RouteHints [][]HopHint
|
RouteHints [][]HopHint
|
||||||
|
|
||||||
|
// Features represents an optional field used to signal optional or
|
||||||
|
// required support for features by the receiver.
|
||||||
|
Features *lnwire.FeatureVector
|
||||||
}
|
}
|
||||||
|
|
||||||
// Amount is a functional option that allows callers of NewInvoice to set the
|
// Amount is a functional option that allows callers of NewInvoice to set the
|
||||||
@ -663,6 +677,14 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
invoice.RouteHints = append(invoice.RouteHints, routeHint)
|
invoice.RouteHints = append(invoice.RouteHints, routeHint)
|
||||||
|
case fieldType9:
|
||||||
|
if invoice.Features != nil {
|
||||||
|
// We skip the field if we have already seen a
|
||||||
|
// supported one.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice.Features, err = parseFeatures(base32Data)
|
||||||
default:
|
default:
|
||||||
// Ignore unknown type.
|
// Ignore unknown type.
|
||||||
}
|
}
|
||||||
@ -874,6 +896,25 @@ func parseRouteHint(data []byte) ([]HopHint, error) {
|
|||||||
return routeHint, nil
|
return routeHint, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseFeatures decodes any feature bits directly from the base32
|
||||||
|
// representation.
|
||||||
|
func parseFeatures(data []byte) (*lnwire.FeatureVector, error) {
|
||||||
|
rawFeatures := lnwire.NewRawFeatureVector()
|
||||||
|
err := rawFeatures.DecodeBase32(bytes.NewReader(data), len(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fv := lnwire.NewFeatureVector(rawFeatures, InvoiceFeatures)
|
||||||
|
unknownFeatures := fv.UnknownRequiredFeatures()
|
||||||
|
if len(unknownFeatures) > 0 {
|
||||||
|
return nil, fmt.Errorf("invoice contains unknown required "+
|
||||||
|
"features: %v", unknownFeatures)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fv, nil
|
||||||
|
}
|
||||||
|
|
||||||
// writeTaggedFields writes the non-nil tagged fields of the Invoice to the
|
// writeTaggedFields writes the non-nil tagged fields of the Invoice to the
|
||||||
// base32 buffer.
|
// base32 buffer.
|
||||||
func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
|
func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
|
||||||
@ -1024,6 +1065,18 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if invoice.Features != nil && invoice.Features.SerializeSize32() > 0 {
|
||||||
|
var b bytes.Buffer
|
||||||
|
err := invoice.Features.RawFeatureVector.EncodeBase32(&b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writeTaggedField(bufferBase32, fieldType9, b.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -24,12 +24,14 @@ import (
|
|||||||
var (
|
var (
|
||||||
testMillisat24BTC = lnwire.MilliSatoshi(2400000000000)
|
testMillisat24BTC = lnwire.MilliSatoshi(2400000000000)
|
||||||
testMillisat2500uBTC = lnwire.MilliSatoshi(250000000)
|
testMillisat2500uBTC = lnwire.MilliSatoshi(250000000)
|
||||||
|
testMillisat25mBTC = lnwire.MilliSatoshi(2500000000)
|
||||||
testMillisat20mBTC = lnwire.MilliSatoshi(2000000000)
|
testMillisat20mBTC = lnwire.MilliSatoshi(2000000000)
|
||||||
|
|
||||||
testPaymentHashSlice, _ = hex.DecodeString("0001020304050607080900010203040506070809000102030405060708090102")
|
testPaymentHashSlice, _ = hex.DecodeString("0001020304050607080900010203040506070809000102030405060708090102")
|
||||||
|
|
||||||
testEmptyString = ""
|
testEmptyString = ""
|
||||||
testCupOfCoffee = "1 cup coffee"
|
testCupOfCoffee = "1 cup coffee"
|
||||||
|
testCoffeeBeans = "coffee beans"
|
||||||
testCupOfNonsense = "ナンセンス 1杯"
|
testCupOfNonsense = "ナンセンス 1杯"
|
||||||
testPleaseConsider = "Please consider supporting this project"
|
testPleaseConsider = "Please consider supporting this project"
|
||||||
|
|
||||||
@ -468,6 +470,59 @@ func TestDecodeEncode(t *testing.T) {
|
|||||||
i.Destination = nil
|
i.Destination = nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// On mainnet, please send $30 coffee beans supporting
|
||||||
|
// features 1 and 9.
|
||||||
|
encodedInvoice: "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdees9qzsze992adudgku8p05pstl6zh7av6rx2f297pv89gu5q93a0hf3g7lynl3xq56t23dpvah6u7y9qey9lccrdml3gaqwc6nxsl5ktzm464sq73t7cl",
|
||||||
|
valid: true,
|
||||||
|
decodedInvoice: func() *Invoice {
|
||||||
|
return &Invoice{
|
||||||
|
Net: &chaincfg.MainNetParams,
|
||||||
|
MilliSat: &testMillisat25mBTC,
|
||||||
|
Timestamp: time.Unix(1496314658, 0),
|
||||||
|
PaymentHash: &testPaymentHash,
|
||||||
|
Description: &testCoffeeBeans,
|
||||||
|
Destination: testPubKey,
|
||||||
|
Features: lnwire.NewFeatureVector(
|
||||||
|
lnwire.NewRawFeatureVector(1, 9),
|
||||||
|
InvoiceFeatures,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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, please send $30 coffee beans supporting
|
||||||
|
// features 1, 9, and 100.
|
||||||
|
encodedInvoice: "lnbc25m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5vdhkven9v5sxyetpdees9q4pqqqqqqqqqqqqqqqqqqszk3ed62snp73037h4py4gry05eltlp0uezm2w9ajnerhmxzhzhsu40g9mgyx5v3ad4aqwkmvyftzk4k9zenz90mhjcy9hcevc7r3lx2sphzfxz7",
|
||||||
|
valid: false,
|
||||||
|
skipEncoding: true,
|
||||||
|
decodedInvoice: func() *Invoice {
|
||||||
|
return &Invoice{
|
||||||
|
Net: &chaincfg.MainNetParams,
|
||||||
|
MilliSat: &testMillisat25mBTC,
|
||||||
|
Timestamp: time.Unix(1496314658, 0),
|
||||||
|
PaymentHash: &testPaymentHash,
|
||||||
|
Description: &testCoffeeBeans,
|
||||||
|
Destination: testPubKey,
|
||||||
|
Features: lnwire.NewFeatureVector(
|
||||||
|
lnwire.NewRawFeatureVector(1, 9, 100),
|
||||||
|
InvoiceFeatures,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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 (p2wpkh) address bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
|
// On mainnet, with fallback (p2wpkh) address bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
|
||||||
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfppqw508d6qejxtdg4y5r3zarvary0c5xw7kknt6zz5vxa8yh8jrnlkl63dah48yh6eupakk87fjdcnwqfcyt7snnpuz7vp83txauq4c60sys3xyucesxjf46yqnpplj0saq36a554cp9wt865",
|
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfppqw508d6qejxtdg4y5r3zarvary0c5xw7kknt6zz5vxa8yh8jrnlkl63dah48yh6eupakk87fjdcnwqfcyt7snnpuz7vp83txauq4c60sys3xyucesxjf46yqnpplj0saq36a554cp9wt865",
|
||||||
@ -814,6 +869,11 @@ func compareInvoices(expected, actual *Invoice) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expected.Features, actual.Features) {
|
||||||
|
return fmt.Errorf("expected features %v, got %v",
|
||||||
|
expected.Features.RawFeatureVector, actual.Features.RawFeatureVector)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user