From df72097f2dc8879fd2bef8400b0c10f948a10b4a Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 5 Dec 2019 07:59:17 -0800 Subject: [PATCH] zpay32/invoice: parse payment address as type s --- zpay32/invoice.go | 33 +++++++++++++++++++++++++++++++++ zpay32/invoice_test.go | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/zpay32/invoice.go b/zpay32/invoice.go index 0df3b1d9..9cfc0a9f 100644 --- a/zpay32/invoice.go +++ b/zpay32/invoice.go @@ -73,6 +73,11 @@ const ( // supported or required by the receiver. fieldType9 = 5 + // fieldTypeS contains a 32-byte payment address, which is a nonce + // included in the final hop's payload to prevent intermediaries from + // probing the recipient. + fieldTypeS = 16 + // maxInvoiceLength is the maximum total length an invoice can have. // This is chosen to be the maximum number of bytes that can fit into a // single QR code: https://en.wikipedia.org/wiki/QR_code#Storage @@ -126,6 +131,10 @@ type Invoice struct { // invoice. PaymentHash *[32]byte + // PaymentAddr is the payment address to be used by payments to prevent + // probing of the destination. + PaymentAddr *[32]byte + // Destination is the public key of the target node. This will always // be set after decoding, and can optionally be set before encoding to // include the pubkey as an 'n' field. If this is not set before @@ -258,6 +267,14 @@ func Features(features *lnwire.FeatureVector) func(*Invoice) { } } +// PaymentAddr is a functional option that allows callers of NewInvoice to set +// the desired payment address tht is advertised on the invoice. +func PaymentAddr(addr [32]byte) func(*Invoice) { + return func(i *Invoice) { + i.PaymentAddr = &addr + } +} + // NewInvoice creates a new Invoice object. The last parameter is a set of // variadic arguments for setting optional fields of the invoice. // @@ -643,6 +660,14 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er } invoice.PaymentHash, err = parse32Bytes(base32Data) + case fieldTypeS: + if invoice.PaymentAddr != nil { + // We skip the field if we have already seen a + // supported one. + continue + } + + invoice.PaymentAddr, err = parse32Bytes(base32Data) case fieldTypeD: if invoice.Description != nil { // We skip the field if we have already seen a @@ -1060,6 +1085,14 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error { return err } } + if invoice.PaymentAddr != nil { + err := writeBytes32( + bufferBase32, fieldTypeS, *invoice.PaymentAddr, + ) + if err != nil { + return err + } + } return nil } diff --git a/zpay32/invoice_test.go b/zpay32/invoice_test.go index eec242f7..5feff241 100644 --- a/zpay32/invoice_test.go +++ b/zpay32/invoice_test.go @@ -28,7 +28,19 @@ var ( testMillisat25mBTC = lnwire.MilliSatoshi(2500000000) testMillisat20mBTC = lnwire.MilliSatoshi(2000000000) - testPaymentHashSlice, _ = hex.DecodeString("0001020304050607080900010203040506070809000102030405060708090102") + testPaymentHash = [32]byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x01, 0x02, + } + + testPaymentAddr = [32]byte{ + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x01, 0x02, + 0x06, 0x07, 0x08, 0x09, 0x00, 0x01, 0x02, 0x03, + 0x08, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + } testEmptyString = "" testCupOfCoffee = "1 cup coffee" @@ -94,7 +106,6 @@ var ( } // Must be initialized in init(). - testPaymentHash [32]byte testDescriptionHash [32]byte ltcTestNetParams chaincfg.Params @@ -102,7 +113,6 @@ var ( ) func init() { - copy(testPaymentHash[:], testPaymentHashSlice[:]) copy(testDescriptionHash[:], testDescriptionHashSlice[:]) // Initialize litecoin testnet and mainnet params by applying key fields @@ -587,6 +597,25 @@ func TestDecodeEncode(t *testing.T) { return i }, }, + { + // Send 2500uBTC for a cup of coffee with a payment + // address. + encodedInvoice: "lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66sp5qszsvpcgpyqsyps8pqysqqgzqvyqjqqpqgpsgpgqqypqxpq9qcrsusq8nx2hdt3st3ankwz23xy9w7udvqq3f0mdlpc6ga5ew3y67u4qkx8vu72ejg5x6tqhyclm28r7r0mg6lx9x3vls9g6glp2qy3y34cpry54xp", + valid: true, + decodedInvoice: func() *Invoice { + i, _ := NewInvoice( + &chaincfg.MainNetParams, + testPaymentHash, + time.Unix(1496314658, 0), + Amount(testMillisat2500uBTC), + Description(testCupOfCoffee), + Destination(testPubKey), + PaymentAddr(testPaymentAddr), + ) + + return i + }, + }, { // Decode a mainnet invoice while expecting active net to be testnet encodedInvoice: "lnbc241pveeq09pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66jd3m5klcwhq68vdsmx2rjgxeay5v0tkt2v5sjaky4eqahe4fx3k9sqavvce3capfuwv8rvjng57jrtfajn5dkpqv8yelsewtljwmmycq62k443",