Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions lnrpc/invoicesrpc/addinvoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,15 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
)
options = append(options, zpay32.Features(invoiceFeatures))

// Generate and set a random payment address for this invoice. If the
// sender understands payment addresses, this can be used to avoid
// intermediaries probing the receiver.
var paymentAddr [32]byte
if _, err := rand.Read(paymentAddr[:]); err != nil {
return nil, nil, err
}
options = append(options, zpay32.PaymentAddr(paymentAddr))

// Create and encode the payment request as a bech32 (zpay32) string.
creationDate := time.Now()
payReq, err := zpay32.NewInvoice(
Expand Down Expand Up @@ -397,6 +406,7 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
Expiry: payReq.Expiry(),
Value: amtMSat,
PaymentPreimage: paymentPreimage,
PaymentAddr: paymentAddr,
Comment thread
cfromknecht marked this conversation as resolved.
Outdated
Features: invoiceFeatures,
},
}
Expand Down
1,088 changes: 548 additions & 540 deletions lnrpc/rpc.pb.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lnrpc/rpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2615,6 +2615,7 @@ message PayReq {
string fallback_addr = 8 [json_name = "fallback_addr"];
int64 cltv_expiry = 9 [json_name = "cltv_expiry"];
repeated RouteHint route_hints = 10 [json_name = "route_hints"];
bytes payment_addr = 11 [json_name = "payment_addr"];
Comment thread
cfromknecht marked this conversation as resolved.
Outdated
}

message FeeReportRequest {}
Expand Down
4 changes: 4 additions & 0 deletions lnrpc/rpc.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -3248,6 +3248,10 @@
"items": {
"$ref": "#/definitions/lnrpcRouteHint"
}
},
"payment_addr": {
"type": "string",
"format": "byte"
}
}
},
Expand Down
7 changes: 7 additions & 0 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4593,6 +4593,12 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
amt = int64(payReq.MilliSat.ToSatoshis())
}

// Extract the payment address from the payment request, if present.
var paymentAddr []byte
if payReq.PaymentAddr != nil {
paymentAddr = payReq.PaymentAddr[:]
}

dest := payReq.Destination.SerializeCompressed()
return &lnrpc.PayReq{
Destination: hex.EncodeToString(dest),
Expand All @@ -4605,6 +4611,7 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
Expiry: expiry,
CltvExpiry: int64(payReq.MinFinalCLTVExpiry()),
RouteHints: routeHints,
PaymentAddr: paymentAddr,
}, nil
}

Expand Down
107 changes: 55 additions & 52 deletions zpay32/invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
//
Expand Down Expand Up @@ -642,7 +659,15 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
continue
}

invoice.PaymentHash, err = parsePaymentHash(base32Data)
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
Expand All @@ -666,7 +691,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
continue
}

invoice.DescriptionHash, err = parseDescriptionHash(base32Data)
invoice.DescriptionHash, err = parse32Bytes(base32Data)
case fieldTypeX:
if invoice.expiry != nil {
// We skip the field if we have already seen a
Expand Down Expand Up @@ -733,12 +758,12 @@ func parseFieldDataLength(data []byte) (uint16, error) {
return uint16(data[0])<<5 | uint16(data[1]), nil
}

// parsePaymentHash converts a 256-bit payment hash (encoded in base32)
// to *[32]byte.
func parsePaymentHash(data []byte) (*[32]byte, error) {
// parse32Bytes converts a 256-bit value (encoded in base32) to *[32]byte. This
// can be used for payment hashes, description hashes, payment addresses, etc.
func parse32Bytes(data []byte) (*[32]byte, error) {
var paymentHash [32]byte

// As BOLT-11 states, a reader must skip over the payment hash field if
// As BOLT-11 states, a reader must skip over the 32-byte fields if
// it does not have a length of 52, so avoid returning an error.
if len(data) != hashBase32Len {
return nil, nil
Expand Down Expand Up @@ -784,27 +809,6 @@ func parseDestination(data []byte) (*btcec.PublicKey, error) {
return btcec.ParsePubKey(base256Data, btcec.S256())
}

// parseDescriptionHash converts a 256-bit description hash (encoded in base32)
// to *[32]byte.
func parseDescriptionHash(data []byte) (*[32]byte, error) {
var descriptionHash [32]byte

// As BOLT-11 states, a reader must skip over the description hash field
// if it does not have a length of 52, so avoid returning an error.
if len(data) != hashBase32Len {
return nil, nil
}

hash, err := bech32.ConvertBits(data, 5, 8, false)
if err != nil {
return nil, err
}

copy(descriptionHash[:], hash[:])

return &descriptionHash, nil
}

// parseExpiry converts the data (encoded in base32) into the expiry time.
func parseExpiry(data []byte) (*time.Duration, error) {
expiry, err := base32ToUint64(data)
Expand Down Expand Up @@ -944,18 +948,7 @@ func parseFeatures(data []byte) (*lnwire.FeatureVector, error) {
// base32 buffer.
func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
if invoice.PaymentHash != nil {
// Convert 32 byte hash to 52 5-bit groups.
base32, err := bech32.ConvertBits(invoice.PaymentHash[:], 8, 5,
true)
if err != nil {
return err
}
if len(base32) != hashBase32Len {
return fmt.Errorf("invalid payment hash length: %d",
len(invoice.PaymentHash))
}

err = writeTaggedField(bufferBase32, fieldTypeP, base32)
err := writeBytes32(bufferBase32, fieldTypeP, *invoice.PaymentHash)
if err != nil {
return err
}
Expand All @@ -974,19 +967,9 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
}

if invoice.DescriptionHash != nil {
// Convert 32 byte hash to 52 5-bit groups.
descBase32, err := bech32.ConvertBits(
invoice.DescriptionHash[:], 8, 5, true)
if err != nil {
return err
}

if len(descBase32) != hashBase32Len {
return fmt.Errorf("invalid description hash length: %d",
len(invoice.DescriptionHash))
}

err = writeTaggedField(bufferBase32, fieldTypeH, descBase32)
err := writeBytes32(
bufferBase32, fieldTypeH, *invoice.DescriptionHash,
)
if err != nil {
return err
}
Expand Down Expand Up @@ -1102,10 +1085,30 @@ 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
}

// writeBytes32 encodes a 32-byte array as base32 and writes it to bufferBase32
// under the passed fieldType.
func writeBytes32(bufferBase32 *bytes.Buffer, fieldType byte, b [32]byte) error {
// Convert 32 byte hash to 52 5-bit groups.
base32, err := bech32.ConvertBits(b[:], 8, 5, true)
if err != nil {
return err
}

return writeTaggedField(bufferBase32, fieldType, base32)
}

// writeTaggedField takes the type of a tagged data field, and the data of
// the tagged field (encoded in base32), and writes the type, length and data
// to the buffer.
Expand Down
56 changes: 3 additions & 53 deletions zpay32/invoice_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,10 @@ func TestParseFieldDataLength(t *testing.T) {
}
}

// TestParsePaymentHash checks that the payment hash is properly parsed.
// TestParse32Bytes checks that the payment hash is properly parsed.
// If the data does not have a length of 52 bytes, we skip over parsing the
// field and do not return an error.
func TestParsePaymentHash(t *testing.T) {
func TestParse32Bytes(t *testing.T) {
t.Parallel()

testPaymentHashData, _ := bech32.ConvertBits(testPaymentHash[:], 8, 5, true)
Expand Down Expand Up @@ -350,7 +350,7 @@ func TestParsePaymentHash(t *testing.T) {
}

for i, test := range tests {
paymentHash, err := parsePaymentHash(test.data)
paymentHash, err := parse32Bytes(test.data)
if (err == nil) != test.valid {
t.Errorf("payment hash decoding test %d failed: %v", i, err)
return
Expand Down Expand Up @@ -458,56 +458,6 @@ func TestParseDestination(t *testing.T) {
}
}

// TestParseDescriptionHash checks that the description hash is properly parsed.
// If the data does not have a length of 52 bytes, we skip over parsing the
// field and do not return an error.
func TestParseDescriptionHash(t *testing.T) {
t.Parallel()

testDescriptionHashData, _ := bech32.ConvertBits(testDescriptionHash[:], 8, 5, true)

tests := []struct {
data []byte
valid bool
result *[32]byte
}{
{
data: []byte{},
valid: true,
result: nil, // skip unknown length, not 52 bytes
},
{
data: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
valid: true,
result: nil, // skip unknown length, not 52 bytes
},
{
data: testDescriptionHashData,
valid: true,
result: &testDescriptionHash,
},
{
data: append(testDescriptionHashData, 0x0),
valid: true,
result: nil, // skip unknown length, not 52 bytes
},
}

for i, test := range tests {
descriptionHash, err := parseDescriptionHash(test.data)
if (err == nil) != test.valid {
t.Errorf("description hash decoding test %d failed: %v", i, err)
return
}
if test.valid && !compareHashes(descriptionHash, test.result) {
t.Fatalf("test %d failed decoding description hash: "+
"expected %x, got %x",
i, *test.result, *descriptionHash)
return
}
}
}

Comment thread
cfromknecht marked this conversation as resolved.
Outdated
// TestParseExpiry checks that the expiry is properly parsed.
func TestParseExpiry(t *testing.T) {
t.Parallel()
Expand Down
35 changes: 32 additions & 3 deletions zpay32/invoice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -94,15 +106,13 @@ var (
}

// Must be initialized in init().
testPaymentHash [32]byte
testDescriptionHash [32]byte

ltcTestNetParams chaincfg.Params
ltcMainNetParams chaincfg.Params
)

func init() {
copy(testPaymentHash[:], testPaymentHashSlice[:])
copy(testDescriptionHash[:], testDescriptionHashSlice[:])

// Initialize litecoin testnet and mainnet params by applying key fields
Expand Down Expand Up @@ -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",
Expand Down