lnwire: add musig2 taproot execution messages for dynamic commitments

This commit is contained in:
Keagan McClelland 2023-10-18 20:28:51 -07:00 committed by Olaoluwa Osuntokun
parent 9b1c04c210
commit 9793fbb94b
5 changed files with 179 additions and 1 deletions

View File

@ -3,6 +3,17 @@ package lnwire
import (
"bytes"
"io"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/lightningnetwork/lnd/fn"
"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
)
// DynAck is the message used to accept the parameters of a dynamic commitment
@ -13,6 +24,13 @@ type DynAck struct {
// a dynamic commitment negotiation
ChanID ChannelID
// LocalNonce is an optional field that is transmitted when accepting
// a dynamic commitment upgrade to Taproot Channels. This nonce will be
// 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]
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
@ -32,6 +50,30 @@ 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...)
if err != nil {
return err
}
var extraBytesWriter bytes.Buffer
if err := tlvStream.Encode(&extraBytesWriter); err != nil {
return err
}
da.ExtraData = ExtraOpaqueData(extraBytesWriter.Bytes())
return WriteBytes(w, da.ExtraData)
}
@ -41,7 +83,50 @@ func (da *DynAck) Encode(w *bytes.Buffer, _ uint32) error {
//
// This is a part of the lnwire.Message interface.
func (da *DynAck) Decode(r io.Reader, _ uint32) error {
return ReadElements(r, &da.ChanID, &da.ExtraData)
// Parse out main message.
if err := ReadElements(r, &da.ChanID); err != nil {
return err
}
// Parse out TLV records.
var tlvRecords ExtraOpaqueData
if err := ReadElement(r, &tlvRecords); err != nil {
return err
}
// Prepare receiving buffers to be filled by TLV extraction.
var localNonceScratch Musig2Nonce
localNonce := tlv.MakeStaticRecord(
DALocalMusig2Pubnonce, &localNonceScratch, musig2.PubNonceSize,
nonceTypeEncoder, nonceTypeDecoder,
)
// 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 len(tlvRecords) != 0 {
da.ExtraData = tlvRecords
}
return nil
}
// MsgType returns the MessageType code which uniquely identifies this message

View File

@ -610,6 +610,17 @@ func FuzzDynAck(f *testing.F) {
})
}
func FuzzKickoffSig(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
// Prefix with KickoffSig
data = prefixWithMsgType(data, MsgKickoffSig)
// Pass the message into our general fuzz harness for wire
// messages!
harness(t, data)
})
}
func FuzzCustomMessage(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte, customMessageType uint16) {
if customMessageType < uint16(CustomTypeStart) {

56
lnwire/kickoff_sig.go Normal file
View File

@ -0,0 +1,56 @@
package lnwire
import (
"bytes"
"io"
)
// KickoffSig is the message used to transmit the signature for a kickoff
// transaction during the execution phase of a dynamic commitment negotiation
// that requires a reanchoring step.
type KickoffSig struct {
// ChanID identifies the channel id for which this signature is
// intended.
ChanID ChannelID
// Signature contains the ECDSA signature that signs the kickoff
// transaction.
Signature Sig
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
ExtraData ExtraOpaqueData
}
// A compile time check to ensure that KickoffSig implements the lnwire.Message
// interface.
var _ Message = (*KickoffSig)(nil)
// Encode serializes the target KickoffSig into the passed bytes.Buffer
// observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (ks *KickoffSig) Encode(w *bytes.Buffer, _ uint32) error {
if err := WriteChannelID(w, ks.ChanID); err != nil {
return err
}
if err := WriteSig(w, ks.Signature); err != nil {
return err
}
return WriteBytes(w, ks.ExtraData)
}
// Decode deserializes a serialized KickoffSig message stored in the passed
// io.Reader observing the specified protocol version.
//
// This is part of the lnwire.Message interface.
func (ks *KickoffSig) Decode(r io.Reader, _ uint32) error {
return ReadElements(r, &ks.ChanID, &ks.Signature, &ks.ExtraData)
}
// MsgType returns the integer uniquely identifying KickoffSig on the wire.
//
// This is part of the lnwire.Message interface.
func (ks *KickoffSig) MsgType() MessageType { return MsgKickoffSig }

View File

@ -804,9 +804,24 @@ func TestLightningWireProtocol(t *testing.T) {
var da DynAck
rand.Read(da.ChanID[:])
if rand.Uint32()%2 == 0 {
var nonce Musig2Nonce
rand.Read(nonce[:])
da.LocalNonce = fn.Some(nonce)
}
v[0] = reflect.ValueOf(da)
},
MsgKickoffSig: func(v []reflect.Value, r *rand.Rand) {
ks := KickoffSig{
ExtraData: make([]byte, 0),
}
rand.Read(ks.ChanID[:])
rand.Read(ks.Signature.bytes[:])
v[0] = reflect.ValueOf(ks)
},
MsgCommitSig: func(v []reflect.Value, r *rand.Rand) {
req := NewCommitSig()
if _, err := r.Read(req.ChanID[:]); err != nil {
@ -1270,6 +1285,12 @@ func TestLightningWireProtocol(t *testing.T) {
return mainScenario(&m)
},
},
{
msgType: MsgKickoffSig,
scenario: func(m KickoffSig) bool {
return mainScenario(&m)
},
},
{
msgType: MsgUpdateAddHTLC,
scenario: func(m UpdateAddHTLC) bool {

View File

@ -54,6 +54,7 @@ const (
MsgQueryChannelRange = 263
MsgReplyChannelRange = 264
MsgGossipTimestampRange = 265
MsgKickoffSig = 777
)
// ErrorEncodeMessage is used when failed to encode the message payload.
@ -103,6 +104,8 @@ func (t MessageType) String() string {
return "DynAck"
case MsgDynReject:
return "DynReject"
case MsgKickoffSig:
return "KickoffSig"
case MsgUpdateAddHTLC:
return "UpdateAddHTLC"
case MsgUpdateFailHTLC:
@ -211,6 +214,8 @@ func makeEmptyMessage(msgType MessageType) (Message, error) {
msg = &DynAck{}
case MsgDynReject:
msg = &DynReject{}
case MsgKickoffSig:
msg = &KickoffSig{}
case MsgUpdateAddHTLC:
msg = &UpdateAddHTLC{}
case MsgUpdateFailHTLC: