diff --git a/zpay32/decode.go b/zpay32/decode.go index 04d6c84426..b881d58694 100644 --- a/zpay32/decode.go +++ b/zpay32/decode.go @@ -229,6 +229,15 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er } invoice.Description, err = parseDescription(base32Data) + case fieldTypeM: + if invoice.Metadata != nil { + // We skip the field if we have already seen a + // supported one. + continue + } + + invoice.Metadata, err = parseMetadata(base32Data) + case fieldTypeN: if invoice.Destination != nil { // We skip the field if we have already seen a @@ -345,6 +354,12 @@ func parseDescription(data []byte) (*string, error) { return &description, nil } +// parseMetadata converts the data (encoded in base32) into a byte slice to use +// as the metadata. +func parseMetadata(data []byte) ([]byte, error) { + return bech32.ConvertBits(data, 5, 8, false) +} + // parseDestination converts the data (encoded in base32) into a 33-byte public // key of the payee node. func parseDestination(data []byte) (*btcec.PublicKey, error) { diff --git a/zpay32/encode.go b/zpay32/encode.go index 30ddbb0e6e..a30d0c3911 100644 --- a/zpay32/encode.go +++ b/zpay32/encode.go @@ -164,6 +164,17 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error { } } + if invoice.Metadata != nil { + base32, err := bech32.ConvertBits(invoice.Metadata, 8, 5, true) + if err != nil { + return err + } + err = writeTaggedField(bufferBase32, fieldTypeM, base32) + if err != nil { + return err + } + } + if invoice.minFinalCLTVExpiry != nil { finalDelta := uint64ToBase32(*invoice.minFinalCLTVExpiry) err := writeTaggedField(bufferBase32, fieldTypeC, finalDelta) diff --git a/zpay32/invoice.go b/zpay32/invoice.go index 5b3c235dd9..e0c05efc15 100644 --- a/zpay32/invoice.go +++ b/zpay32/invoice.go @@ -46,6 +46,9 @@ const ( // fieldTypeD contains a short description of the payment. fieldTypeD = 13 + // fieldTypeM contains the payment metadata. + fieldTypeM = 27 + // fieldTypeN contains the pubkey of the target node. fieldTypeN = 19 @@ -183,6 +186,10 @@ type Invoice struct { // Features represents an optional field used to signal optional or // required support for features by the receiver. Features *lnwire.FeatureVector + + // Metadata is additional data that is sent along with the payment to + // the payee. + Metadata []byte } // Amount is a functional option that allows callers of NewInvoice to set the @@ -273,6 +280,14 @@ func PaymentAddr(addr [32]byte) func(*Invoice) { } } +// Metadata is a functional option that allows callers of NewInvoice to set +// the desired payment Metadata tht is advertised on the invoice. +func Metadata(metadata []byte) func(*Invoice) { + return func(i *Invoice) { + i.Metadata = metadata + } +} + // NewInvoice creates a new Invoice object. The last parameter is a set of // variadic arguments for setting optional fields of the invoice. // diff --git a/zpay32/invoice_test.go b/zpay32/invoice_test.go index 3a21150675..fa77d14c06 100644 --- a/zpay32/invoice_test.go +++ b/zpay32/invoice_test.go @@ -27,6 +27,7 @@ var ( testMillisat2500uBTC = lnwire.MilliSatoshi(250000000) testMillisat25mBTC = lnwire.MilliSatoshi(2500000000) testMillisat20mBTC = lnwire.MilliSatoshi(2000000000) + testMillisat10mBTC = lnwire.MilliSatoshi(1000000000) testPaymentHash = [32]byte{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, @@ -49,11 +50,12 @@ var ( 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, } - testEmptyString = "" - testCupOfCoffee = "1 cup coffee" - testCoffeeBeans = "coffee beans" - testCupOfNonsense = "ナンセンス 1杯" - testPleaseConsider = "Please consider supporting this project" + testEmptyString = "" + testCupOfCoffee = "1 cup coffee" + testCoffeeBeans = "coffee beans" + testCupOfNonsense = "ナンセンス 1杯" + testPleaseConsider = "Please consider supporting this project" + testPaymentMetadata = "payment metadata inside" testPrivKeyBytes, _ = hex.DecodeString("e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734") testPrivKey, testPubKey = btcec.PrivKeyFromBytes(testPrivKeyBytes) @@ -692,6 +694,33 @@ func TestDecodeEncode(t *testing.T) { } }, }, + { + // Please send 0.01 BTC with payment metadata 0x01fafaf0. + encodedInvoice: "lnbc10m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdp9wpshjmt9de6zqmt9w3skgct5vysxjmnnd9jx2mq8q8a04uqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9q2gqqqqqqsgq7hf8he7ecf7n4ffphs6awl9t6676rrclv9ckg3d3ncn7fct63p6s365duk5wrk202cfy3aj5xnnp5gs3vrdvruverwwq7yzhkf5a3xqpd05wjc", + valid: true, + decodedInvoice: func() *Invoice { + return &Invoice{ + Net: &chaincfg.MainNetParams, + MilliSat: &testMillisat10mBTC, + Timestamp: time.Unix(1496314658, 0), + PaymentHash: &testPaymentHash, + Description: &testPaymentMetadata, + Destination: testPubKey, + PaymentAddr: &specPaymentAddr, + Features: lnwire.NewFeatureVector( + lnwire.NewRawFeatureVector(8, 14, 48), + lnwire.Features, + ), + Metadata: []byte{0x01, 0xfa, 0xfa, 0xf0}, + } + }, + 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 + }, + }, } for i, test := range tests {