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
1 change: 1 addition & 0 deletions channeldb/migration_01_to_11/migration_11_invoices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ func getPayReq(net *bitcoinCfg.Params) (string, error) {
options := []func(*zpay32.Invoice){
zpay32.CLTVExpiry(uint64(testCltvDelta)),
zpay32.Description("test"),
zpay32.PaymentAddr([32]byte{}),
}

payReq, err := zpay32.NewInvoice(
Expand Down
6 changes: 6 additions & 0 deletions docs/release-notes/release-notes-0.21.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
# Technical and Architectural Updates
## BOLT Spec Updates

LND now [fail BOLT-11 payments](https://github.com/lightning/bolts/pull/1243)
if any mandatory field (`p`, `h`, `s`, `n`) does not have the correct length
(52, 52, 52, 53) in the BOLT 11 invoice.

## Testing

## Database
Expand All @@ -53,3 +57,5 @@
## Tooling and Documentation

# Contributors (Alphabetical Order)

Pins
5 changes: 2 additions & 3 deletions lnrpc/routerrpc/router_backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -782,9 +782,8 @@ func TestExtractIntentFromSendRequest(t *testing.T) {
sendReq: &SendPaymentRequest{
PaymentRequest: paymentReqMissingAddr,
},
valid: false,
expectedErrorMsg: "payment request must contain " +
"either a payment address or blinded paths",
valid: false,
expectedErrorMsg: "payment secret not found",
},
{
name: "Invalid dest vertex length",
Expand Down
23 changes: 16 additions & 7 deletions zpay32/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,9 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
}

invoice.PaymentHash, err = parse32Bytes(base32Data)
if err != nil {
return fmt.Errorf("payment hash: %w", err)
}

case fieldTypeS:
if invoice.PaymentAddr.IsSome() {
Expand All @@ -311,7 +314,7 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er

addr, err := parse32Bytes(base32Data)
if err != nil {
return err
return fmt.Errorf("payment secret: %w", err)
}
if addr != nil {
invoice.PaymentAddr = fn.Some(*addr)
Expand Down Expand Up @@ -343,6 +346,9 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
}

invoice.Destination, err = parseDestination(base32Data)
if err != nil {
return fmt.Errorf("destination id: %w", err)
}

case fieldTypeH:
if invoice.DescriptionHash != nil {
Expand All @@ -352,6 +358,9 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
}

invoice.DescriptionHash, err = parse32Bytes(base32Data)
if err != nil {
return fmt.Errorf("description hash: %w", err)
}

case fieldTypeX:
if invoice.expiry != nil {
Expand Down Expand Up @@ -446,10 +455,10 @@ func parseFieldDataLength(data []byte) (uint16, error) {
func parse32Bytes(data []byte) (*[32]byte, error) {
var paymentHash [32]byte

// 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.
// As BOLT-11 states, a reader must fail over the 32-byte fields if
// it does not have a length of 52.
if len(data) != hashBase32Len {
return nil, nil
return nil, ErrInvalidFieldLength
}

hash, err := bech32.ConvertBits(data, 5, 8, false)
Expand Down Expand Up @@ -488,10 +497,10 @@ func parseMetadata(data []byte) ([]byte, error) {
// parseDestination converts the data (encoded in base32) into a 33-byte public
// key of the payee node.
func parseDestination(data []byte) (*btcec.PublicKey, error) {
// As BOLT-11 states, a reader must skip over the destination field
// if it does not have a length of 53, so avoid returning an error.
// As BOLT-11 states, a reader must fail over the destination field
// if it does not have a length of 53.
if len(data) != pubKeyBase32Len {
return nil, nil
return nil, ErrInvalidFieldLength
}

base256Data, err := bech32.ConvertBits(data, 5, 8, false)
Expand Down
18 changes: 18 additions & 0 deletions zpay32/invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,24 @@ func validateInvoice(invoice *Invoice) error {
return fmt.Errorf("no payment hash found")
}

// The invoice must include a payment secret.
// If blinded paths are used, a payment secret is not required because
// the PathID in the final hop serves the same purpose.
if len(invoice.BlindedPaymentPaths) == 0 {
payAddr, err := invoice.PaymentAddr.UnwrapOrErr(
fmt.Errorf("payment secret not found"),
)
if err != nil {
return err
}
if len(payAddr) != 32 {
return fmt.Errorf(
"unsupported payment secret length: %d",
len(payAddr),
)
}
}

if len(invoice.RouteHints) != 0 &&
len(invoice.BlindedPaymentPaths) != 0 {

Expand Down
36 changes: 14 additions & 22 deletions zpay32/invoice_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,7 @@ func TestParseFieldDataLength(t *testing.T) {
}

// 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.
// If the data does not have a length of 52 bytes, we return an error.
func TestParse32Bytes(t *testing.T) {
t.Parallel()

Expand All @@ -329,24 +328,21 @@ func TestParse32Bytes(t *testing.T) {
result *[32]byte
}{
{
data: []byte{},
valid: true,
result: nil, // skip unknown length, not 52 bytes
data: []byte{},
valid: false,
},
{
data: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
valid: true,
result: nil, // skip unknown length, not 52 bytes
data: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
valid: false,
},
{
data: testPaymentHashData,
valid: true,
result: &testPaymentHash,
},
{
data: append(testPaymentHashData, 0x0),
valid: true,
result: nil, // skip unknown length, not 52 bytes
data: append(testPaymentHashData, 0x0),
valid: false,
},
}

Expand Down Expand Up @@ -422,8 +418,7 @@ func TestParseDescription(t *testing.T) {
}

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

Expand All @@ -435,24 +430,21 @@ func TestParseDestination(t *testing.T) {
result *btcec.PublicKey
}{
{
data: []byte{},
valid: true,
result: nil, // skip unknown length, not 53 bytes
data: []byte{},
valid: false,
},
{
data: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
valid: true,
result: nil, // skip unknown length, not 53 bytes
data: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
valid: false,
},
{
data: testPubKeyData,
valid: true,
result: testPubKey,
},
{
data: append(testPubKeyData, 0x0),
valid: true,
result: nil, // skip unknown length, not 53 bytes
data: append(testPubKeyData, 0x0),
valid: false,
},
}

Expand Down
Loading
Loading