Merge pull request #3432 from cfromknecht/bolt11-features
zpay32: BOLT 11 Feature Bits
This commit is contained in:
commit
2026d3b45e
@ -127,6 +127,20 @@ func (fv *RawFeatureVector) Unset(feature FeatureBit) {
|
||||
// SerializeSize returns the number of bytes needed to represent feature vector
|
||||
// in byte format.
|
||||
func (fv *RawFeatureVector) SerializeSize() int {
|
||||
// We calculate byte-length via the largest bit index.
|
||||
return fv.serializeSize(8)
|
||||
}
|
||||
|
||||
// SerializeSize32 returns the number of bytes needed to represent feature
|
||||
// vector in base32 format.
|
||||
func (fv *RawFeatureVector) SerializeSize32() int {
|
||||
// We calculate base32-length via the largest bit index.
|
||||
return fv.serializeSize(5)
|
||||
}
|
||||
|
||||
// serializeSize returns the number of bytes required to encode the feature
|
||||
// vector using at most width bits per encoded byte.
|
||||
func (fv *RawFeatureVector) serializeSize(width int) int {
|
||||
// Find the largest feature bit index
|
||||
max := -1
|
||||
for feature := range fv.features {
|
||||
@ -139,8 +153,7 @@ func (fv *RawFeatureVector) SerializeSize() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// We calculate byte-length via the largest bit index
|
||||
return max/8 + 1
|
||||
return max/width + 1
|
||||
}
|
||||
|
||||
// Encode writes the feature vector in byte representation. Every feature
|
||||
@ -156,12 +169,25 @@ func (fv *RawFeatureVector) Encode(w io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return fv.encode(w, length, 8)
|
||||
}
|
||||
|
||||
// EncodeBase32 writes the feature vector in base32 representation. Every feature
|
||||
// encoded as a bit, and the bit vector is serialized using the least number of
|
||||
// bytes.
|
||||
func (fv *RawFeatureVector) EncodeBase32(w io.Writer) error {
|
||||
length := fv.SerializeSize32()
|
||||
return fv.encode(w, length, 5)
|
||||
}
|
||||
|
||||
// encode writes the feature vector
|
||||
func (fv *RawFeatureVector) encode(w io.Writer, length, width int) error {
|
||||
// Generate the data and write it.
|
||||
data := make([]byte, length)
|
||||
for feature := range fv.features {
|
||||
byteIndex := int(feature / 8)
|
||||
bitIndex := feature % 8
|
||||
data[length-byteIndex-1] |= 1 << bitIndex
|
||||
byteIndex := int(feature) / width
|
||||
bitIndex := int(feature) % width
|
||||
data[length-byteIndex-1] |= 1 << uint(bitIndex)
|
||||
}
|
||||
|
||||
_, err := w.Write(data)
|
||||
@ -180,6 +206,19 @@ func (fv *RawFeatureVector) Decode(r io.Reader) error {
|
||||
}
|
||||
length := binary.BigEndian.Uint16(l[:])
|
||||
|
||||
return fv.decode(r, int(length), 8)
|
||||
}
|
||||
|
||||
// DecodeBase32 reads the feature vector from its base32 representation. Every
|
||||
// feature encoded as a bit, and the bit vector is serialized using the least
|
||||
// number of bytes.
|
||||
func (fv *RawFeatureVector) DecodeBase32(r io.Reader, length int) error {
|
||||
return fv.decode(r, length, 5)
|
||||
}
|
||||
|
||||
// decode reads a feature vector from the next length bytes of the io.Reader,
|
||||
// assuming each byte has width feature bits encoded per byte.
|
||||
func (fv *RawFeatureVector) decode(r io.Reader, length, width int) error {
|
||||
// Read the feature vector data.
|
||||
data := make([]byte, length)
|
||||
if _, err := io.ReadFull(r, data); err != nil {
|
||||
@ -187,10 +226,10 @@ func (fv *RawFeatureVector) Decode(r io.Reader) error {
|
||||
}
|
||||
|
||||
// Set feature bits from parsed data.
|
||||
bitsNumber := len(data) * 8
|
||||
bitsNumber := len(data) * width
|
||||
for i := 0; i < bitsNumber; i++ {
|
||||
byteIndex := uint16(i / 8)
|
||||
bitIndex := uint(i % 8)
|
||||
byteIndex := int(i / width)
|
||||
bitIndex := uint(i % width)
|
||||
if (data[length-byteIndex-1]>>bitIndex)&1 == 1 {
|
||||
fv.Set(FeatureBit(i))
|
||||
}
|
||||
|
@ -67,6 +67,16 @@ const (
|
||||
|
||||
// fieldTypeC contains an optional requested final CLTV delta.
|
||||
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
|
||||
@ -146,6 +156,10 @@ type Invoice struct {
|
||||
//
|
||||
// NOTE: This is optional.
|
||||
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
|
||||
@ -663,6 +677,14 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
|
||||
}
|
||||
|
||||
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:
|
||||
// Ignore unknown type.
|
||||
}
|
||||
@ -874,6 +896,25 @@ func parseRouteHint(data []byte) ([]HopHint, error) {
|
||||
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
|
||||
// base32 buffer.
|
||||
func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
|
||||
@ -1024,6 +1065,18 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
|
||||
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
|
||||
}
|
||||
|
@ -24,12 +24,14 @@ import (
|
||||
var (
|
||||
testMillisat24BTC = lnwire.MilliSatoshi(2400000000000)
|
||||
testMillisat2500uBTC = lnwire.MilliSatoshi(250000000)
|
||||
testMillisat25mBTC = lnwire.MilliSatoshi(2500000000)
|
||||
testMillisat20mBTC = lnwire.MilliSatoshi(2000000000)
|
||||
|
||||
testPaymentHashSlice, _ = hex.DecodeString("0001020304050607080900010203040506070809000102030405060708090102")
|
||||
|
||||
testEmptyString = ""
|
||||
testCupOfCoffee = "1 cup coffee"
|
||||
testCoffeeBeans = "coffee beans"
|
||||
testCupOfNonsense = "ナンセンス 1杯"
|
||||
testPleaseConsider = "Please consider supporting this project"
|
||||
|
||||
@ -468,6 +470,59 @@ func TestDecodeEncode(t *testing.T) {
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user