Skip to content
Merged
4 changes: 4 additions & 0 deletions docs/release-notes/release-notes-0.20.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
- Fixed [shutdown deadlock](https://github.com/lightningnetwork/lnd/pull/10042)
when we fail starting up LND before we startup the chanbackup sub-server.

- [Fixed](https://github.com/lightningnetwork/lnd/pull/10027) an issue where
known TLV fields were incorrectly encoded into the `ExtraData` field of
messages in the dynamic commitment set.

# New Features

## Functional Enhancements
Expand Down
67 changes: 23 additions & 44 deletions lnwire/dyn_ack.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@ import (
"bytes"
"io"

"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/tlv"
)

const (
// DALocalMusig2Pubnonce is the TLV type number that identifies the
// musig2 public nonce that we need to verify the commitment transaction
// signature.
DALocalMusig2Pubnonce tlv.Type = 0
DALocalMusig2Pubnonce tlv.Type = 14
)

// DynAck is the message used to accept the parameters of a dynamic commitment
Expand All @@ -33,7 +31,7 @@ type DynAck struct {
// used to verify the first commitment transaction signature. This will
// only be populated if the DynPropose we are responding to specifies
// taproot channels in the ChannelType field.
LocalNonce fn.Option[Musig2Nonce]
LocalNonce tlv.OptionalRecordT[tlv.TlvType14, Musig2Nonce]
Comment thread
yyforyongyu marked this conversation as resolved.

// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
Expand Down Expand Up @@ -62,31 +60,27 @@ func (da *DynAck) Encode(w *bytes.Buffer, _ uint32) error {
return err
}

var tlvRecords []tlv.Record
da.LocalNonce.WhenSome(func(nonce Musig2Nonce) {
tlvRecords = append(
tlvRecords, tlv.MakeStaticRecord(
DALocalMusig2Pubnonce, &nonce,
musig2.PubNonceSize, nonceTypeEncoder,
nonceTypeDecoder,
),
)
})
tlv.SortRecords(tlvRecords)

tlvStream, err := tlv.NewStream(tlvRecords...)
// Create extra data records.
producers, err := da.ExtraData.RecordProducers()
if err != nil {
return err
}

var extraBytesWriter bytes.Buffer
if err := tlvStream.Encode(&extraBytesWriter); err != nil {
// Append the known records.
da.LocalNonce.WhenSome(
func(rec tlv.RecordT[tlv.TlvType14, Musig2Nonce]) {
producers = append(producers, &rec)
},
)

// Encode all records.
var tlvData ExtraOpaqueData
err = tlvData.PackRecords(producers...)
if err != nil {
return err
}

da.ExtraData = ExtraOpaqueData(extraBytesWriter.Bytes())

return WriteBytes(w, da.ExtraData)
return WriteBytes(w, tlvData)
}

// Decode deserializes the serialized DynAck stored in the passed io.Reader into
Expand All @@ -106,37 +100,22 @@ func (da *DynAck) Decode(r io.Reader, _ uint32) error {
return err
}

// Prepare receiving buffers to be filled by TLV extraction.
var localNonceScratch Musig2Nonce
localNonce := tlv.MakeStaticRecord(
DALocalMusig2Pubnonce, &localNonceScratch, musig2.PubNonceSize,
nonceTypeEncoder, nonceTypeDecoder,
// Parse all known records and extra data.
nonce := da.LocalNonce.Zero()
knownRecords, extraData, err := ParseAndExtractExtraData(
tlvRecords, &nonce,
)

// Create set of Records to read TLV bytestream into.
records := []tlv.Record{localNonce}
tlv.SortRecords(records)

// Read TLV stream into record set.
extraBytesReader := bytes.NewReader(tlvRecords)
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return err
}
typeMap, err := tlvStream.DecodeWithParsedTypesP2P(extraBytesReader)
if err != nil {
return err
}

// Check the results of the TLV Stream decoding and appropriately set
// message fields.
if val, ok := typeMap[DALocalMusig2Pubnonce]; ok && val == nil {
da.LocalNonce = fn.Some(localNonceScratch)
if _, ok := knownRecords[da.LocalNonce.TlvType()]; ok {
da.LocalNonce = tlv.SomeRecordT(nonce)
}

if len(tlvRecords) != 0 {
da.ExtraData = tlvRecords
}
da.ExtraData = extraData

return nil
}
Expand Down
84 changes: 84 additions & 0 deletions lnwire/dyn_ack_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package lnwire

import (
"bytes"
"testing"

"github.com/lightningnetwork/lnd/lnutils"
"github.com/stretchr/testify/require"
)

// TestDynAckEncodeDecode checks that the Encode and Decode methods for DynAck
// work as expected.
func TestDynAckEncodeDecode(t *testing.T) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lovely lovely

t.Parallel()

// Generate random channel ID.
chanIDBytes, err := generateRandomBytes(32)
require.NoError(t, err)

var chanID ChannelID
copy(chanID[:], chanIDBytes)

// Generate random sig.
sigBytes, err := generateRandomBytes(64)
require.NoError(t, err)

var sig Sig
copy(sig.bytes[:], sigBytes)

// Create test data for the TLVs. The actual value doesn't matter, as we
// only care about that the raw bytes can be decoded into a msg, and the
// msg can be encoded into the exact same raw bytes.
testTlvData := []byte{
// ExtraData - unknown tlv record.
//
// NOTE: This record is optional and occupies the type 1.
0x1, // type.
0x2, // length.
0x79, 0x79, // value.

// LocalNonce tlv.
0x14, // type.
0x42, // length.
0x2c, 0xd4, 0x53, 0x7d, 0xaa, 0x7b, // value.
0x7e, 0xae, 0x18, 0x32, 0xa6, 0xc4, 0x29, 0xe9, 0xe0, 0x91,
0x32, 0x7a, 0xaf, 0xd1, 0x1c, 0x2b, 0x04, 0xa0, 0x4d, 0xb5,
0x6a, 0x6f, 0x8b, 0x6c, 0xdc, 0xd1, 0x80, 0x2d, 0xff, 0x72,
0xd8, 0x3c, 0xfc, 0x01, 0x6e, 0x7c, 0x1a, 0xc8, 0x5e, 0x3a,
0x16, 0x98, 0xbc, 0x9b, 0x6e, 0x22, 0x58, 0x96, 0x96, 0xad,
0x88, 0xbf, 0xff, 0x59, 0x90, 0xbd, 0x36, 0x0b, 0x0b, 0x4d,

// ExtraData - unknown tlv.
0x6f, // type.
0x2, // length.
0x79, 0x79, // value.
}

msg := &DynAck{}

// Pre-allocate a new slice with enough capacity for all three parts for
// efficiency.
totalLen := len(chanIDBytes) + len(sigBytes) + len(testTlvData)
rawBytes := make([]byte, 0, totalLen)

// Append each slice to the new rawBytes slice.
rawBytes = append(rawBytes, chanIDBytes...)
rawBytes = append(rawBytes, sigBytes...)
rawBytes = append(rawBytes, testTlvData...)

// Decode the raw bytes.
r := bytes.NewBuffer(rawBytes)
err = msg.Decode(r, 0)
require.NoError(t, err)

t.Logf("Encoded msg is %v", lnutils.SpewLogClosure(msg))

// Encode the msg into raw bytes and assert the encoded bytes equal to
// the rawBytes.
w := new(bytes.Buffer)
err = msg.Encode(w, 0)
require.NoError(t, err)

require.Equal(t, rawBytes, w.Bytes())
}
75 changes: 42 additions & 33 deletions lnwire/dyn_commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,28 @@ func (dc *DynCommit) Encode(w *bytes.Buffer, _ uint32) error {
return err
}

var extra ExtraOpaqueData
err := extra.PackRecords(dynProposeRecords(&dc.DynPropose)...)
// Create extra data records.
producers, err := dc.ExtraData.RecordProducers()
if err != nil {
return err
}
dc.ExtraData = extra

return WriteBytes(w, dc.ExtraData)
// Append the known records.
producers = append(producers, dynProposeRecords(&dc.DynPropose)...)
dc.LocalNonce.WhenSome(
func(rec tlv.RecordT[tlv.TlvType14, Musig2Nonce]) {
producers = append(producers, &rec)
},
)

// Encode all known records.
var tlvData ExtraOpaqueData
err = tlvData.PackRecords(producers...)
if err != nil {
return err
}

return WriteBytes(w, tlvData)
}

// Decode deserializes the serialized DynCommit stored in the passed io.Reader
Expand All @@ -75,58 +89,53 @@ func (dc *DynCommit) Decode(r io.Reader, _ uint32) error {
}

// Prepare receiving buffers to be filled by TLV extraction.
var dustLimit tlv.RecordT[tlv.TlvType0, uint64]
var maxValue tlv.RecordT[tlv.TlvType2, uint64]
var htlcMin tlv.RecordT[tlv.TlvType4, uint64]
var reserve tlv.RecordT[tlv.TlvType6, uint64]
var dustLimit tlv.RecordT[tlv.TlvType0, tlv.BigSizeT[btcutil.Amount]]
var maxValue tlv.RecordT[tlv.TlvType2, MilliSatoshi]
var htlcMin tlv.RecordT[tlv.TlvType4, MilliSatoshi]
var reserve tlv.RecordT[tlv.TlvType6, tlv.BigSizeT[btcutil.Amount]]
csvDelay := dc.CsvDelay.Zero()
maxHtlcs := dc.MaxAcceptedHTLCs.Zero()
chanType := dc.ChannelType.Zero()
nonce := dc.LocalNonce.Zero()

typeMap, err := tlvRecords.ExtractRecords(
&dustLimit, &maxValue, &htlcMin, &reserve, &csvDelay, &maxHtlcs,
&chanType,
// Parse all known records and extra data.
knownRecords, extraData, err := ParseAndExtractExtraData(
tlvRecords, &dustLimit, &maxValue, &htlcMin, &reserve,
&csvDelay, &maxHtlcs, &chanType, &nonce,
)
if err != nil {
return err
}

// Check the results of the TLV Stream decoding and appropriately set
// message fields.
if val, ok := typeMap[dc.DustLimit.TlvType()]; ok && val == nil {
var rec tlv.RecordT[tlv.TlvType0, btcutil.Amount]
rec.Val = btcutil.Amount(dustLimit.Val)
dc.DustLimit = tlv.SomeRecordT(rec)
if _, ok := knownRecords[dc.DustLimit.TlvType()]; ok {
dc.DustLimit = tlv.SomeRecordT(dustLimit)
}
if val, ok := typeMap[dc.MaxValueInFlight.TlvType()]; ok && val == nil {
var rec tlv.RecordT[tlv.TlvType2, MilliSatoshi]
rec.Val = MilliSatoshi(maxValue.Val)
dc.MaxValueInFlight = tlv.SomeRecordT(rec)
if _, ok := knownRecords[dc.MaxValueInFlight.TlvType()]; ok {
dc.MaxValueInFlight = tlv.SomeRecordT(maxValue)
}
if val, ok := typeMap[dc.HtlcMinimum.TlvType()]; ok && val == nil {
var rec tlv.RecordT[tlv.TlvType4, MilliSatoshi]
rec.Val = MilliSatoshi(htlcMin.Val)
dc.HtlcMinimum = tlv.SomeRecordT(rec)
if _, ok := knownRecords[dc.HtlcMinimum.TlvType()]; ok {
dc.HtlcMinimum = tlv.SomeRecordT(htlcMin)
}
if val, ok := typeMap[dc.ChannelReserve.TlvType()]; ok && val == nil {
var rec tlv.RecordT[tlv.TlvType6, btcutil.Amount]
rec.Val = btcutil.Amount(reserve.Val)
dc.ChannelReserve = tlv.SomeRecordT(rec)
if _, ok := knownRecords[dc.ChannelReserve.TlvType()]; ok {
dc.ChannelReserve = tlv.SomeRecordT(reserve)
}
if val, ok := typeMap[dc.CsvDelay.TlvType()]; ok && val == nil {
if _, ok := knownRecords[dc.CsvDelay.TlvType()]; ok {
dc.CsvDelay = tlv.SomeRecordT(csvDelay)
}
if val, ok := typeMap[dc.MaxAcceptedHTLCs.TlvType()]; ok && val == nil {
if _, ok := knownRecords[dc.MaxAcceptedHTLCs.TlvType()]; ok {
dc.MaxAcceptedHTLCs = tlv.SomeRecordT(maxHtlcs)
}
if val, ok := typeMap[dc.ChannelType.TlvType()]; ok && val == nil {
if _, ok := knownRecords[dc.ChannelType.TlvType()]; ok {
dc.ChannelType = tlv.SomeRecordT(chanType)
}

if len(tlvRecords) != 0 {
dc.ExtraData = tlvRecords
if _, ok := knownRecords[dc.LocalNonce.TlvType()]; ok {
dc.LocalNonce = tlv.SomeRecordT(nonce)
}

dc.ExtraData = extraData

return nil
}

Expand Down
Loading
Loading