Skip to content
Open
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
25 changes: 24 additions & 1 deletion lnrpc/offersrpc/offers_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ func (s *Server) PayOffer(ctx context.Context,
// The invoice paid is the payment's request: kept with the payment,
// it is, with the preimage, the proof of what was paid and to whom
// (the issuer signed it), as a BOLT11 request is for those payments.
intent.PaymentRequest = []byte(bolt12)
intent.PaymentRequest = []byte(storedInvoice(bolt12))

hash := intent.Identifier()
preimage, rt, err := s.cfg.Deps.PayInvoice(ctx, intent)
Expand Down Expand Up @@ -815,3 +815,26 @@ func invoiceToRPC(inv *bolt12.Invoice) *InvoiceInfo {
SignatureValid: bolt12.VerifyInvoice(inv) == nil,
}
}

// MaxStoredInvoice bounds the BOLT 12 invoice kept as a payment's request.
// A BOLT 11 request is at most about 7 KB; a BOLT 12 invoice its issuer pads
// (unknown odd TLVs) could run to megabytes, and every listpayments page
// holding it would carry them, past what a gRPC client accepts by default.
const MaxStoredInvoice = 32 * 1024

// storedInvoice is the invoice to keep with a payment: re-encoded from its
// bytes (no '+' continuations or whitespace, the same signed TLVs, so it is
// still the issuer's signature over what was paid), or nothing when that is
// above MaxStoredInvoice or it does not decode.
func storedInvoice(s string) string {
hrp, data, err := bolt12.Decode(strings.TrimSpace(s))
if err != nil {
return ""
}
enc, err := bolt12.Encode(hrp, data)
if err != nil || len(enc) > MaxStoredInvoice {
return ""
}

return enc
}
20 changes: 20 additions & 0 deletions lnrpc/offersrpc/offers_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package offersrpc

import (
"bytes"
"context"
"errors"
"strings"
Expand Down Expand Up @@ -804,3 +805,22 @@ func fetchPayAndList(t *testing.T, chain [32]byte) {
})
requireCode(t, err, codes.InvalidArgument)
}

// TestStoredInvoice checks the invoice kept with a payment: the same bytes,
// re-encoded without continuations, and nothing above MaxStoredInvoice.
func TestStoredInvoice(t *testing.T) {
data := bytes.Repeat([]byte{0x42}, 600)
plain, err := bolt12.Encode(offers.InvoiceHRP, data)
require.NoError(t, err)

padded := plain[:20] + "+\n " + plain[20:300] + "+ " + plain[300:]
require.Equal(t, plain, storedInvoice(" "+padded+"\n"))

big, err := bolt12.Encode(
offers.InvoiceHRP, make([]byte, MaxStoredInvoice),
)
require.NoError(t, err)
require.Empty(t, storedInvoice(big))

require.Empty(t, storedInvoice("not an invoice"))
}
Loading