mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-19 12:01:27 +02:00
multi: add blinded path TLVs to onion payload / hops
This commit adds the encrypted_data, blinding_point and total_amt_msat tlvs to the known set of even tlvs for the onion payload. These TLVs are added in two places (the onion payload and hop struct) because lnd uses the same set of TLV types for both structs (and they inherently represent the same thing). Note: in some places, unit tests intentionally mimic the style of older tests, so as to be more consistently readable.
This commit is contained in:
committed by
Olaoluwa Osuntokun
parent
539a275faa
commit
fee0e05708
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
@@ -94,9 +95,20 @@ type Payload struct {
|
||||
// were included in the payload.
|
||||
customRecords record.CustomSet
|
||||
|
||||
// encryptedData is a blob of data encrypted by the receiver for use
|
||||
// in blinded routes.
|
||||
encryptedData []byte
|
||||
|
||||
// blindingPoint is an ephemeral pubkey for use in blinded routes.
|
||||
blindingPoint *btcec.PublicKey
|
||||
|
||||
// metadata is additional data that is sent along with the payment to
|
||||
// the payee.
|
||||
metadata []byte
|
||||
|
||||
// totalAmtMsat holds the info provided in total_amount_msat when
|
||||
// parsed from a TLV onion payload.
|
||||
totalAmtMsat lnwire.MilliSatoshi
|
||||
}
|
||||
|
||||
// NewLegacyPayload builds a Payload from the amount, cltv, and next hop
|
||||
@@ -118,12 +130,15 @@ func NewLegacyPayload(f *sphinx.HopData) *Payload {
|
||||
// should correspond to the bytes encapsulated in a TLV onion payload.
|
||||
func NewPayloadFromReader(r io.Reader) (*Payload, error) {
|
||||
var (
|
||||
cid uint64
|
||||
amt uint64
|
||||
cltv uint32
|
||||
mpp = &record.MPP{}
|
||||
amp = &record.AMP{}
|
||||
metadata []byte
|
||||
cid uint64
|
||||
amt uint64
|
||||
totalAmtMsat uint64
|
||||
cltv uint32
|
||||
mpp = &record.MPP{}
|
||||
amp = &record.AMP{}
|
||||
encryptedData []byte
|
||||
blindingPoint *btcec.PublicKey
|
||||
metadata []byte
|
||||
)
|
||||
|
||||
tlvStream, err := tlv.NewStream(
|
||||
@@ -131,8 +146,11 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) {
|
||||
record.NewLockTimeRecord(&cltv),
|
||||
record.NewNextHopIDRecord(&cid),
|
||||
mpp.Record(),
|
||||
record.NewEncryptedDataRecord(&encryptedData),
|
||||
record.NewBlindingPointRecord(&blindingPoint),
|
||||
amp.Record(),
|
||||
record.NewMetadataRecord(&metadata),
|
||||
record.NewTotalAmtMsatBlinded(&totalAmtMsat),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -175,6 +193,12 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) {
|
||||
amp = nil
|
||||
}
|
||||
|
||||
// If no encrypted data was parsed, set the field on our resulting
|
||||
// payload to nil.
|
||||
if _, ok := parsedTypes[record.EncryptedDataOnionType]; !ok {
|
||||
encryptedData = nil
|
||||
}
|
||||
|
||||
// If no metadata field was parsed, set the metadata field on the
|
||||
// resulting payload to nil.
|
||||
if _, ok := parsedTypes[record.MetadataOnionType]; !ok {
|
||||
@@ -193,7 +217,10 @@ func NewPayloadFromReader(r io.Reader) (*Payload, error) {
|
||||
MPP: mpp,
|
||||
AMP: amp,
|
||||
metadata: metadata,
|
||||
encryptedData: encryptedData,
|
||||
blindingPoint: blindingPoint,
|
||||
customRecords: customRecords,
|
||||
totalAmtMsat: lnwire.MilliSatoshi(totalAmtMsat),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -297,12 +324,29 @@ func (h *Payload) CustomRecords() record.CustomSet {
|
||||
return h.customRecords
|
||||
}
|
||||
|
||||
// EncryptedData returns the route blinding encrypted data parsed from the
|
||||
// onion payload.
|
||||
func (h *Payload) EncryptedData() []byte {
|
||||
return h.encryptedData
|
||||
}
|
||||
|
||||
// BlindingPoint returns the route blinding point parsed from the onion payload.
|
||||
func (h *Payload) BlindingPoint() *btcec.PublicKey {
|
||||
return h.blindingPoint
|
||||
}
|
||||
|
||||
// Metadata returns the additional data that is sent along with the
|
||||
// payment to the payee.
|
||||
func (h *Payload) Metadata() []byte {
|
||||
return h.metadata
|
||||
}
|
||||
|
||||
// TotalAmtMsat returns the total amount sent to the final hop, as set by the
|
||||
// payee.
|
||||
func (h *Payload) TotalAmtMsat() lnwire.MilliSatoshi {
|
||||
return h.totalAmtMsat
|
||||
}
|
||||
|
||||
// getMinRequiredViolation checks for unrecognized required (even) fields in the
|
||||
// standard range and returns the lowest required type. Always returning the
|
||||
// lowest required type allows a failure message to be deterministic.
|
||||
|
@@ -2,15 +2,23 @@ package hop_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
//nolint:lll
|
||||
testPrivKeyBytes, _ = hex.DecodeString("e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734")
|
||||
_, testPubKey = btcec.PrivKeyFromBytes(testPrivKeyBytes)
|
||||
)
|
||||
|
||||
const testUnknownRequiredType = 0x80
|
||||
|
||||
type decodePayloadTest struct {
|
||||
@@ -20,7 +28,10 @@ type decodePayloadTest struct {
|
||||
expCustomRecords map[uint64][]byte
|
||||
shouldHaveMPP bool
|
||||
shouldHaveAMP bool
|
||||
shouldHaveEncData bool
|
||||
shouldHaveBlinding bool
|
||||
shouldHaveMetadata bool
|
||||
shouldHaveTotalAmt bool
|
||||
}
|
||||
|
||||
var decodePayloadTests = []decodePayloadTest{
|
||||
@@ -217,6 +228,33 @@ var decodePayloadTests = []decodePayloadTest{
|
||||
FinalHop: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "intermediate hop with encrypted data",
|
||||
payload: []byte{
|
||||
// amount
|
||||
0x02, 0x00,
|
||||
// cltv
|
||||
0x04, 0x00,
|
||||
// encrypted data
|
||||
0x0a, 0x03, 0x03, 0x02, 0x01,
|
||||
},
|
||||
shouldHaveEncData: true,
|
||||
},
|
||||
{
|
||||
name: "intermediate hop with blinding point",
|
||||
payload: append([]byte{
|
||||
// amount
|
||||
0x02, 0x00,
|
||||
// cltv
|
||||
0x04, 0x00,
|
||||
// blinding point (type / length)
|
||||
0x0c, 0x21,
|
||||
},
|
||||
// blinding point (value)
|
||||
testPubKey.SerializeCompressed()...,
|
||||
),
|
||||
shouldHaveBlinding: true,
|
||||
},
|
||||
{
|
||||
name: "final hop with mpp",
|
||||
payload: []byte{
|
||||
@@ -271,6 +309,18 @@ var decodePayloadTests = []decodePayloadTest{
|
||||
},
|
||||
shouldHaveMetadata: true,
|
||||
},
|
||||
{
|
||||
name: "final hop with total amount",
|
||||
payload: []byte{
|
||||
// amount
|
||||
0x02, 0x00,
|
||||
// cltv
|
||||
0x04, 0x00,
|
||||
// total amount
|
||||
0x12, 0x01, 0x01,
|
||||
},
|
||||
shouldHaveTotalAmt: true,
|
||||
},
|
||||
}
|
||||
|
||||
// TestDecodeHopPayloadRecordValidation asserts that parsing the payloads in the
|
||||
@@ -306,6 +356,7 @@ func testDecodeHopPayloadValidation(t *testing.T, test decodePayloadTest) {
|
||||
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
||||
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
|
||||
}
|
||||
testEncData = []byte{3, 2, 1}
|
||||
testMetadata = []byte{1, 2, 3}
|
||||
testChildIndex = uint32(9)
|
||||
)
|
||||
@@ -354,6 +405,29 @@ func testDecodeHopPayloadValidation(t *testing.T, test decodePayloadTest) {
|
||||
t.Fatalf("unexpected metadata")
|
||||
}
|
||||
|
||||
if test.shouldHaveEncData {
|
||||
require.NotNil(t, p.EncryptedData(),
|
||||
"payment should have encrypted data")
|
||||
|
||||
require.Equal(t, testEncData, p.EncryptedData())
|
||||
} else {
|
||||
require.Nil(t, p.EncryptedData())
|
||||
}
|
||||
|
||||
if test.shouldHaveBlinding {
|
||||
require.NotNil(t, p.BlindingPoint())
|
||||
|
||||
require.Equal(t, testPubKey, p.BlindingPoint())
|
||||
} else {
|
||||
require.Nil(t, p.BlindingPoint())
|
||||
}
|
||||
|
||||
if test.shouldHaveTotalAmt {
|
||||
require.NotZero(t, p.TotalAmtMsat())
|
||||
} else {
|
||||
require.Zero(t, p.TotalAmtMsat())
|
||||
}
|
||||
|
||||
// Convert expected nil map to empty map, because we always expect an
|
||||
// initiated map from the payload.
|
||||
expCustomRecords := make(record.CustomSet)
|
||||
|
Reference in New Issue
Block a user