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:
Carla Kirk-Cohen
2022-10-26 10:57:37 -04:00
committed by Olaoluwa Osuntokun
parent 539a275faa
commit fee0e05708
7 changed files with 392 additions and 23 deletions

View File

@@ -131,6 +131,20 @@ type Hop struct {
// Metadata is additional data that is sent along with the payment to
// the payee.
Metadata []byte
// EncryptedData is an encrypted data blob includes for hops that are
// part of a blinded route.
EncryptedData []byte
// BlindingPoint is an ephemeral public key used by introduction nodes
// in blinded routes to unblind their portion of the route and pass on
// the next ephemeral key to the next blinded node to do the same.
BlindingPoint *btcec.PublicKey
// TotalAmtMsat is the total amount for a blinded payment, potentially
// spread over more than one HTLC. This field should only be set for
// the final hop in a blinded path.
TotalAmtMsat lnwire.MilliSatoshi
}
// Copy returns a deep copy of the Hop.
@@ -147,6 +161,11 @@ func (h *Hop) Copy() *Hop {
c.AMP = &a
}
if h.BlindingPoint != nil {
b := *h.BlindingPoint
c.BlindingPoint = &b
}
return &c
}
@@ -197,6 +216,19 @@ func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64) error {
}
}
// Add encrypted data and blinding point if present.
if h.EncryptedData != nil {
records = append(records, record.NewEncryptedDataRecord(
&h.EncryptedData,
))
}
if h.BlindingPoint != nil {
records = append(records, record.NewBlindingPointRecord(
&h.BlindingPoint,
))
}
// If an AMP record is destined for this hop, ensure that we only ever
// attach it if we also have an MPP record. We can infer that this is
// already a final hop if MPP is non-nil otherwise we would have exited
@@ -216,6 +248,13 @@ func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64) error {
)
}
if h.TotalAmtMsat != 0 {
totalAmtInt := uint64(h.TotalAmtMsat)
records = append(records,
record.NewTotalAmtMsatBlinded(&totalAmtInt),
)
}
// Append any custom types destined for this hop.
tlvRecords := tlv.MapToRecords(h.CustomRecords)
records = append(records, tlvRecords...)
@@ -270,11 +309,33 @@ func (h *Hop) PayloadSize(nextChanID uint64) uint64 {
addRecord(record.AMPOnionType, h.AMP.PayloadSize())
}
// Add encrypted data and blinding point if present.
if h.EncryptedData != nil {
addRecord(
record.EncryptedDataOnionType,
uint64(len(h.EncryptedData)),
)
}
if h.BlindingPoint != nil {
addRecord(
record.BlindingPointOnionType,
btcec.PubKeyBytesLenCompressed,
)
}
// Add metadata if present.
if h.Metadata != nil {
addRecord(record.MetadataOnionType, uint64(len(h.Metadata)))
}
if h.TotalAmtMsat != 0 {
addRecord(
record.TotalAmtMsatBlindedType,
tlv.SizeTUint64(uint64(h.AmtToForward)),
)
}
// Add custom records.
for k, v := range h.CustomRecords {
addRecord(tlv.Type(k), uint64(len(v)))

View File

@@ -162,6 +162,8 @@ func TestAMPHop(t *testing.T) {
// TestPayloadSize tests the payload size calculation that is provided by Hop
// structs.
func TestPayloadSize(t *testing.T) {
t.Parallel()
hops := []*Hop{
{
PubKeyBytes: testPubKeyBytes,
@@ -181,7 +183,9 @@ func TestPayloadSize(t *testing.T) {
AmtToForward: 1200,
OutgoingTimeLock: 700000,
MPP: record.NewMPP(500, [32]byte{}),
AMP: record.NewAMP([32]byte{}, [32]byte{}, 8),
AMP: record.NewAMP(
[32]byte{}, [32]byte{}, 8,
),
CustomRecords: map[uint64][]byte{
100000: {1, 2, 3},
1000000: {4, 5},
@@ -190,6 +194,69 @@ func TestPayloadSize(t *testing.T) {
},
}
blindedHops := []*Hop{
{
// Unblinded hop to introduction node.
PubKeyBytes: testPubKeyBytes,
AmtToForward: 1000,
OutgoingTimeLock: 600000,
ChannelID: 3432483437438,
LegacyPayload: true,
},
{
// Payload for an introduction node in a blinded route
// that has the blinding point provided in the onion
// payload, and encrypted data pointing it to the next
// node.
PubKeyBytes: testPubKeyBytes,
EncryptedData: []byte{12, 13},
BlindingPoint: testPubKey,
},
{
// Payload for a forwarding node in a blinded route
// that has encrypted data provided in the onion
// payload, but no blinding point (it's provided in
// update_add_htlc).
PubKeyBytes: testPubKeyBytes,
EncryptedData: []byte{12, 13},
},
{
// Final hop has encrypted data and other final hop
// fields like metadata.
PubKeyBytes: testPubKeyBytes,
AmtToForward: 900,
OutgoingTimeLock: 50000,
Metadata: []byte{10, 11},
EncryptedData: []byte{12, 13},
TotalAmtMsat: lnwire.MilliSatoshi(900),
},
}
testCases := []struct {
name string
hops []*Hop
}{
{
name: "clear route",
hops: hops,
},
{
name: "blinded route",
hops: blindedHops,
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
testPayloadSize(t, testCase.hops)
})
}
}
func testPayloadSize(t *testing.T, hops []*Hop) {
rt := Route{
Hops: hops,
}