From 9793fbb94bc6cc05b6fbc4758c85e8486872868f Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Wed, 18 Oct 2023 20:28:51 -0700 Subject: [PATCH] lnwire: add musig2 taproot execution messages for dynamic commitments --- lnwire/dyn_ack.go | 87 ++++++++++++++++++++++++++++++++++++++++++- lnwire/fuzz_test.go | 11 ++++++ lnwire/kickoff_sig.go | 56 ++++++++++++++++++++++++++++ lnwire/lnwire_test.go | 21 +++++++++++ lnwire/message.go | 5 +++ 5 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 lnwire/kickoff_sig.go diff --git a/lnwire/dyn_ack.go b/lnwire/dyn_ack.go index d0b72e030..24f23a228 100644 --- a/lnwire/dyn_ack.go +++ b/lnwire/dyn_ack.go @@ -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 diff --git a/lnwire/fuzz_test.go b/lnwire/fuzz_test.go index b2ee8471f..f364867a7 100644 --- a/lnwire/fuzz_test.go +++ b/lnwire/fuzz_test.go @@ -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) { diff --git a/lnwire/kickoff_sig.go b/lnwire/kickoff_sig.go new file mode 100644 index 000000000..3e46db453 --- /dev/null +++ b/lnwire/kickoff_sig.go @@ -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 } diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 873b35709..9d92b970b 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -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 { diff --git a/lnwire/message.go b/lnwire/message.go index 2f6d64a72..bc79ed003 100644 --- a/lnwire/message.go +++ b/lnwire/message.go @@ -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: