-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
lnwire: add wire types for dynamic commitments #8026
Merged
Roasbeef
merged 6 commits into
lightningnetwork:0-18-staging
from
ProofOfKeags:feature/dyncomms
Nov 10, 2023
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
3d8c57d
fn: introduce option type
ProofOfKeags 26b995c
lnwire: introduce message types for dynamic commitment negotiation
ProofOfKeags c28934a
lnwire: add TLV extension to channel_reestablish for dynamic commitments
ProofOfKeags fc9a965
lnwire: add fuzz tests for new dynamic commitment message types
ProofOfKeags f394401
docs: update release notes
ProofOfKeags 134e9a7
lnwire: add musig2 taproot execution messages for dynamic commitments
ProofOfKeags File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package fn | ||
|
||
// Option[A] represents a value which may or may not be there. This is very | ||
// often preferable to nil-able pointers. | ||
type Option[A any] struct { | ||
isSome bool | ||
some A | ||
} | ||
|
||
// Some trivially injects a value into an optional context. | ||
// | ||
// Some : A -> Option[A]. | ||
func Some[A any](a A) Option[A] { | ||
return Option[A]{ | ||
isSome: true, | ||
some: a, | ||
} | ||
} | ||
|
||
// None trivially constructs an empty option | ||
// | ||
// None : Option[A]. | ||
func None[A any]() Option[A] { | ||
return Option[A]{} | ||
} | ||
|
||
// ElimOption is the universal Option eliminator. It can be used to safely | ||
// handle all possible values inside the Option by supplying two continuations. | ||
// | ||
// ElimOption : (Option[A], () -> B, A -> B) -> B. | ||
func ElimOption[A, B any](o Option[A], b func() B, f func(A) B) B { | ||
if o.isSome { | ||
return f(o.some) | ||
} | ||
|
||
return b() | ||
} | ||
|
||
// UnwrapOr is used to extract a value from an option, and we supply the default | ||
// value in the case when the Option is empty. | ||
// | ||
// UnwrapOr : (Option[A], A) -> A. | ||
func (o Option[A]) UnwrapOr(a A) A { | ||
if o.isSome { | ||
return o.some | ||
} | ||
|
||
return a | ||
} | ||
|
||
// WhenSome is used to conditionally perform a side-effecting function that | ||
// accepts a value of the type that parameterizes the option. If this function | ||
// performs no side effects, WhenSome is useless. | ||
// | ||
// WhenSome : (Option[A], A -> ()) -> (). | ||
func (o Option[A]) WhenSome(f func(A)) { | ||
if o.isSome { | ||
f(o.some) | ||
} | ||
} | ||
|
||
// IsSome returns true if the Option contains a value | ||
// | ||
// IsSome : Option[A] -> bool. | ||
func (o Option[A]) IsSome() bool { | ||
return o.isSome | ||
} | ||
|
||
// IsNone returns true if the Option is empty | ||
// | ||
// IsNone : Option[A] -> bool. | ||
func (o Option[A]) IsNone() bool { | ||
return !o.isSome | ||
} | ||
|
||
// FlattenOption joins multiple layers of Options together such that if any of | ||
// the layers is None, then the joined value is None. Otherwise the innermost | ||
// Some value is returned. | ||
// | ||
// FlattenOption : Option[Option[A]] -> Option[A]. | ||
func FlattenOption[A any](oo Option[Option[A]]) Option[A] { | ||
if oo.IsNone() { | ||
return None[A]() | ||
} | ||
if oo.some.IsNone() { | ||
return None[A]() | ||
} | ||
|
||
return oo.some | ||
} | ||
|
||
// ChainOption transforms a function A -> Option[B] into one that accepts an | ||
// Option[A] as an argument. | ||
// | ||
// ChainOption : (A -> Option[B]) -> Option[A] -> Option[B]. | ||
func ChainOption[A, B any](f func(A) Option[B]) func(Option[A]) Option[B] { | ||
return func(o Option[A]) Option[B] { | ||
if o.isSome { | ||
return f(o.some) | ||
} | ||
|
||
return None[B]() | ||
} | ||
} | ||
|
||
// MapOption transforms a pure function A -> B into one that will operate | ||
// inside the Option context. | ||
// | ||
// MapOption : (A -> B) -> Option[A] -> Option[B]. | ||
func MapOption[A, B any](f func(A) B) func(Option[A]) Option[B] { | ||
return func(o Option[A]) Option[B] { | ||
if o.isSome { | ||
return Some(f(o.some)) | ||
} | ||
|
||
return None[B]() | ||
} | ||
} | ||
|
||
// LiftA2Option transforms a pure function (A, B) -> C into one that will | ||
// operate in an Option context. For the returned function, if either of its | ||
// arguments are None, then the result will be None. | ||
// | ||
// LiftA2Option : ((A, B) -> C) -> (Option[A], Option[B]) -> Option[C]. | ||
func LiftA2Option[A, B, C any]( | ||
f func(A, B) C, | ||
) func(Option[A], Option[B]) Option[C] { | ||
|
||
return func(o1 Option[A], o2 Option[B]) Option[C] { | ||
if o1.isSome && o2.isSome { | ||
return Some(f(o1.some, o2.some)) | ||
} | ||
|
||
return None[C]() | ||
} | ||
} | ||
|
||
// Alt chooses the left Option if it is full, otherwise it chooses the right | ||
// option. This can be useful in a long chain if you want to choose between | ||
// many different ways of producing the needed value. | ||
// | ||
// Alt : Option[A] -> Option[A] -> Option[A]. | ||
func (o Option[A]) Alt(o2 Option[A]) Option[A] { | ||
if o.isSome { | ||
return o | ||
} | ||
|
||
return o2 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
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 | ||
// negotiation. Additional optional parameters will need to be present depending | ||
// on the details of the dynamic commitment upgrade. | ||
type DynAck struct { | ||
ProofOfKeags marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// ChanID is the ChannelID of the channel that is currently undergoing | ||
// 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. | ||
ExtraData ExtraOpaqueData | ||
} | ||
|
||
// A compile time check to ensure DynAck implements the lnwire.Message | ||
// interface. | ||
var _ Message = (*DynAck)(nil) | ||
|
||
// Encode serializes the target DynAck into the passed io.Writer. Serialization | ||
// will observe the rules defined by the passed protocol version. | ||
// | ||
// This is a part of the lnwire.Message interface. | ||
func (da *DynAck) Encode(w *bytes.Buffer, _ uint32) error { | ||
if err := WriteChannelID(w, da.ChanID); err != nil { | ||
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) | ||
} | ||
|
||
// Decode deserializes the serialized DynAck stored in the passed io.Reader into | ||
// the target DynAck using the deserialization rules defined by the passed | ||
// protocol version. | ||
// | ||
// This is a part of the lnwire.Message interface. | ||
func (da *DynAck) Decode(r io.Reader, _ uint32) error { | ||
// 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 | ||
// as a DynAck on the wire. | ||
// | ||
// This is part of the lnwire.Message interface. | ||
func (da *DynAck) MsgType() MessageType { | ||
return MsgDynAck | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤩