From df453acf2ec866e4136054473dd2771a17963e21 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:45:11 -0800 Subject: [PATCH 01/15] lnwire: add feature bits for simple taproot chans --- lnwire/features.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lnwire/features.go b/lnwire/features.go index c386fd37be6..df0362c42a7 100644 --- a/lnwire/features.go +++ b/lnwire/features.go @@ -221,6 +221,14 @@ const ( // TODO: Decide on actual feature bit value. ScriptEnforcedLeaseOptional FeatureBit = 2023 + // SimpleTaprootChannelsRequred is a required bit that indicates the + // node is able to create taproot-native channels. + SimpleTaprootChannelsRequired = 80 + + // SimpleTaprootChannelsOptional is an optional bit that indicates the + // node is able to create taproot-native channels. + SimpleTaprootChannelsOptional = 81 + // maxAllowedSize is a maximum allowed size of feature vector. // // NOTE: Within the protocol, the maximum allowed message size is 65535 @@ -280,6 +288,8 @@ var Features = map[FeatureBit]string{ ZeroConfOptional: "zero-conf", ShutdownAnySegwitRequired: "shutdown-any-segwit", ShutdownAnySegwitOptional: "shutdown-any-segwit", + SimpleTaprootChannelsRequired: "simple-taproot-chans", + SimpleTaprootChannelsOptional: "simple-taproot-chans", } // RawFeatureVector represents a set of feature bits as defined in BOLT-09. A From 5e7a97cf406376e11f981a9cba5d1bc107b03a9c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:33:21 -0800 Subject: [PATCH 02/15] lnwire: update Sig to support both ECDSA and schnorr sigs In this commit, we update the Sig type to support ECDSA and schnorr signatures. We need to do this as the HTLC signatures will become schnorr sigs for taproot channels. The current spec draft opts to overload this field since both the sigs are actually 64 bytes in length. The only consideration with this move is that callers need to "coerce" a sig to the proper type if they need schnorr signatures. --- channeldb/graph.go | 2 +- discovery/gossiper.go | 8 +- htlcswitch/link_test.go | 10 +- lnrpc/routerrpc/router_backend.go | 2 +- lnrpc/signrpc/signer_server.go | 2 +- lnwallet/chancloser/chancloser.go | 10 +- lnwallet/channel.go | 4 +- lnwallet/channel_test.go | 6 +- lnwallet/rpcwallet/rpcwallet.go | 15 +- lnwallet/sigpool.go | 3 +- lnwire/fuzz_test.go | 5 +- lnwire/lnwire.go | 4 +- lnwire/signature.go | 212 +++++++++++++----- lnwire/signature_test.go | 28 ++- lnwire/writer.go | 2 +- lnwire/writer_test.go | 12 +- netann/channel_announcement.go | 8 +- netann/channel_announcement_test.go | 2 +- netann/channel_update.go | 4 +- routing/router_test.go | 2 +- server.go | 2 +- watchtower/blob/justice_kit.go | 21 +- watchtower/blob/justice_kit_test.go | 6 +- watchtower/lookout/justice_descriptor_test.go | 4 +- watchtower/lookout/lookout_test.go | 11 +- watchtower/wtclient/backup_task.go | 10 +- .../wtclient/backup_task_internal_test.go | 9 +- zpay32/decode.go | 8 +- zpay32/encode.go | 11 +- 29 files changed, 297 insertions(+), 126 deletions(-) diff --git a/channeldb/graph.go b/channeldb/graph.go index 1e6dbf8d62e..e39ef6372f1 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -2726,7 +2726,7 @@ func (l *LightningNode) NodeAnnouncement(signed bool) (*lnwire.NodeAnnouncement, return nodeAnn, nil } - sig, err := lnwire.NewSigFromRawSignature(l.AuthSigBytes) + sig, err := lnwire.NewSigFromECDSARawSignature(l.AuthSigBytes) if err != nil { return nil, err } diff --git a/discovery/gossiper.go b/discovery/gossiper.go index c02cd0b1047..71fc2ee8a92 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -2201,25 +2201,25 @@ func (d *AuthenticatedGossiper) updateChannel(info *channeldb.ChannelEdgeInfo, BitcoinKey2: info.BitcoinKey2Bytes, ExtraOpaqueData: edge.ExtraOpaqueData, } - chanAnn.NodeSig1, err = lnwire.NewSigFromRawSignature( + chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( info.AuthProof.NodeSig1Bytes, ) if err != nil { return nil, nil, err } - chanAnn.NodeSig2, err = lnwire.NewSigFromRawSignature( + chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( info.AuthProof.NodeSig2Bytes, ) if err != nil { return nil, nil, err } - chanAnn.BitcoinSig1, err = lnwire.NewSigFromRawSignature( + chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( info.AuthProof.BitcoinSig1Bytes, ) if err != nil { return nil, nil, err } - chanAnn.BitcoinSig2, err = lnwire.NewSigFromRawSignature( + chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( info.AuthProof.BitcoinSig2Bytes, ) if err != nil { diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index 55d77d13d0e..79eafb80e06 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -5374,9 +5374,15 @@ func TestChannelLinkFail(t *testing.T) { // Flip a bit on the signature, rendering it // invalid. - sig[19] ^= 1 + sigCopy := sig.Copy() + copyBytes := sigCopy.RawBytes() + copyBytes[19] ^= 1 + modifiedSig, err := lnwire.NewSigFromWireECDSA( + copyBytes, + ) + require.NoError(t, err) commitSig := &lnwire.CommitSig{ - CommitSig: sig, + CommitSig: modifiedSig, HtlcSigs: htlcSigs, } diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index 58b7a829491..9342ca6d173 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -1283,7 +1283,7 @@ func marshallChannelUpdate(update *lnwire.ChannelUpdate) *lnrpc.ChannelUpdate { } return &lnrpc.ChannelUpdate{ - Signature: update.Signature[:], + Signature: update.Signature.RawBytes(), ChainHash: update.ChainHash[:], ChanId: update.ShortChannelID.ToUint64(), Timestamp: update.Timestamp, diff --git a/lnrpc/signrpc/signer_server.go b/lnrpc/signrpc/signer_server.go index 01fc7696d6a..abbd6592c47 100644 --- a/lnrpc/signrpc/signer_server.go +++ b/lnrpc/signrpc/signer_server.go @@ -713,7 +713,7 @@ func (s *Server) VerifyMessage(_ context.Context, } // The signature must be fixed-size LN wire format encoded. - wireSig, err := lnwire.NewSigFromRawSignature(in.Signature) + wireSig, err := lnwire.NewSigFromECDSARawSignature(in.Signature) if err != nil { return nil, fmt.Errorf("failed to decode signature: %v", err) } diff --git a/lnwallet/chancloser/chancloser.go b/lnwallet/chancloser/chancloser.go index af71e7b9296..448e196c8aa 100644 --- a/lnwallet/chancloser/chancloser.go +++ b/lnwallet/chancloser/chancloser.go @@ -785,16 +785,18 @@ func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSign return nil, err } - // We'll note our last signature and proposed fee so when the remote party - // responds we'll be able to decide if we've agreed on fees or not. + // We'll note our last signature and proposed fee so when the remote + // party responds we'll be able to decide if we've agreed on fees or + // not. c.lastFeeProposal = fee + parsedSig, err := lnwire.NewSigFromSignature(rawSig) if err != nil { return nil, err } - chancloserLog.Infof("ChannelPoint(%v): proposing fee of %v sat to close "+ - "chan", c.chanPoint, int64(fee)) + chancloserLog.Infof("ChannelPoint(%v): proposing fee of %v sat to "+ + "close chan", c.chanPoint, int64(fee)) // We'll assemble a ClosingSigned message using this information and return // it to the caller so we can kick off the final stage of the channel diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 97b7ac44d36..6efd0b62e27 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -320,7 +320,7 @@ type PaymentDescriptor struct { // local node. This signature is generated by the remote node and // stored by the local node in the case that local node needs to // broadcast their commitment transaction. - sig *ecdsa.Signature + sig input.Signature // addCommitHeight[Remote|Local] encodes the height of the commitment // which included this HTLC on either the remote or local commitment @@ -4333,7 +4333,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, var ( htlcIndex uint64 sigHash func() ([]byte, error) - sig *ecdsa.Signature + sig input.Signature err error ) diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index fc8a7bef754..93bf4c747a4 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -5331,7 +5331,11 @@ func TestInvalidCommitSigError(t *testing.T) { // Before the signature gets to Bob, we'll mutate it, such that the // signature is now actually invalid. - aliceSig[0] ^= 88 + aliceSigCopy := aliceSig.Copy() + aliceSigCopyBytes := aliceSigCopy.RawBytes() + aliceSigCopyBytes[0] ^= 88 + aliceSig, err = lnwire.NewSigFromWireECDSA(aliceSigCopyBytes) + require.NoError(t, err) // Bob should reject this new state, and return the proper error. err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go index 38f345c14ad..5ccffbef960 100644 --- a/lnwallet/rpcwallet/rpcwallet.go +++ b/lnwallet/rpcwallet/rpcwallet.go @@ -453,11 +453,20 @@ func (r *RPCKeyRing) SignMessage(keyLoc keychain.KeyLocator, "signer instance: %v", err) } - wireSig, err := lnwire.NewSigFromRawSignature(resp.Signature) + wireSig, err := lnwire.NewSigFromECDSARawSignature(resp.Signature) if err != nil { - return nil, fmt.Errorf("error parsing raw signature: %v", err) + return nil, fmt.Errorf("unable to create sig: %w", err) } - return wireSig.ToSignature() + sig, err := wireSig.ToSignature() + if err != nil { + return nil, fmt.Errorf("unable to parse sig: %w", err) + } + ecdsaSig, ok := sig.(*ecdsa.Signature) + if !ok { + return nil, fmt.Errorf("unexpected signature type: %T", sig) + } + + return ecdsaSig, nil } // SignMessageCompact signs the given message, single or double SHA256 hashing diff --git a/lnwallet/sigpool.go b/lnwallet/sigpool.go index 30dc37ae901..0ebc3a931b6 100644 --- a/lnwallet/sigpool.go +++ b/lnwallet/sigpool.go @@ -5,7 +5,6 @@ import ( "sync" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" @@ -36,7 +35,7 @@ type VerifyJob struct { // Sig is the raw signature generated using the above public key. This // is the signature to be verified. - Sig *ecdsa.Signature + Sig input.Signature // SigHash is a function closure generates the sighashes that the // passed signature is known to have signed. diff --git a/lnwire/fuzz_test.go b/lnwire/fuzz_test.go index 34d144c8497..ac7aa150caa 100644 --- a/lnwire/fuzz_test.go +++ b/lnwire/fuzz_test.go @@ -383,7 +383,10 @@ func FuzzNodeAnnouncement(f *testing.F) { t.Fatal("new message was not NodeAnnouncement") } - if !bytes.Equal(first.Signature[:], second.Signature[:]) { + if !bytes.Equal( + first.Signature.RawBytes(), + second.Signature.RawBytes(), + ) { shouldPanic = true } diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index 5f042b7461d..46257cce511 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -183,7 +183,7 @@ func WriteElement(w *bytes.Buffer, element interface{}) error { case Sig: // Write buffer - if _, err := w.Write(e[:]); err != nil { + if _, err := w.Write(e.bytes[:]); err != nil { return err } @@ -618,7 +618,7 @@ func ReadElement(r io.Reader, element interface{}) error { *e = sigs case *Sig: - if _, err := io.ReadFull(r, e[:]); err != nil { + if _, err := io.ReadFull(r, e.bytes[:]); err != nil { return err } diff --git a/lnwire/signature.go b/lnwire/signature.go index f0bed72cb3f..57c27e4d19e 100644 --- a/lnwire/signature.go +++ b/lnwire/signature.go @@ -5,15 +5,10 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/lightningnetwork/lnd/input" ) -// Sig is a fixed-sized ECDSA signature. Unlike Bitcoin, we use fixed sized -// signatures on the wire, instead of DER encoded signatures. This type -// provides several methods to convert to/from a regular Bitcoin DER encoded -// signature (raw bytes and *ecdsa.Signature). -type Sig [64]byte - var ( errSigTooShort = errors.New("malformed signature: too short") errBadLength = errors.New("malformed signature: bad length") @@ -23,14 +18,74 @@ var ( errSTooLong = errors.New("S is over 32 bytes long without padding") ) -// NewSigFromRawSignature returns a Sig from a Bitcoin raw signature encoded in -// the canonical DER encoding. -func NewSigFromRawSignature(sig []byte) (Sig, error) { - var b Sig +// sigType represents the type of signature that is carried within the Sig. +// Today this can either be an ECDSA sig or a schnorr sig. Both of these can +// fit cleanly into 64 bytes. +type sigType uint + +const ( + // sigTypeECDSA represents an ECDSA signature. + sigTypeECDSA sigType = iota + + // sigTypeSchnorr represents a schnorr signature. + sigTypeSchnorr +) + +// Sig is a fixed-sized ECDSA signature or 64-byte schnorr signature. For the +// ECDSA sig, unlike Bitcoin, we use fixed sized signatures on the wire, +// instead of DER encoded signatures. This type provides several methods to +// convert to/from a regular Bitcoin DER encoded signature (raw bytes and +// *ecdsa.Signature). +type Sig struct { + bytes [64]byte + + sigType sigType +} + +// ForceSchnorr forces the signature to be interpreted as a schnorr signature. +// This is useful when reading an HTLC sig off the wire for a taproot channel. +// In this case, in order to obtain an input.Signature, we need to know that +// the sig is a schnorr sig. +func (s *Sig) ForceSchnorr() { + s.sigType = sigTypeSchnorr +} + +// RawBytes returns the raw bytes of signature. +func (s *Sig) RawBytes() []byte { + return s.bytes[:] +} + +// Copy copies the signature into a new Sig instance. +func (s *Sig) Copy() Sig { + var sCopy Sig + copy(sCopy.bytes[:], s.bytes[:]) + sCopy.sigType = s.sigType + + return sCopy +} + +// NewSigFromWireECDSA returns a Sig instance based on an ECDSA signature +// that's already in the 64-byte format we expect. +func NewSigFromWireECDSA(sig []byte) (Sig, error) { + if len(sig) != 64 { + return Sig{}, fmt.Errorf("%w: %v bytes", errSigTooShort, + len(sig)) + } + + var s Sig + copy(s.bytes[:], sig) + + return s, nil +} + +// NewSigFromECDSARawSignature returns a Sig from a Bitcoin raw signature +// encoded in the canonical DER encoding. +func NewSigFromECDSARawSignature(sig []byte) (Sig, error) { + var b [64]byte // Check the total length is above the minimal. if len(sig) < ecdsa.MinSigLen { - return b, errSigTooShort + return Sig{}, errSigTooShort } // The DER representation is laid out as: @@ -46,7 +101,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { // siglen should be less than the entire message and greater than // the minimal message size. if sigLen+2 > len(sig) || sigLen+2 < ecdsa.MinSigLen { - return b, errBadLength + return Sig{}, errBadLength } // Reading , remaining: [r 0x02 s] @@ -56,7 +111,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { // Assuming s is one byte, then we have 0x30, , 0x20, // , 0x20, , s, a total of 7 bytes. if rLen <= 0 || rLen+7 > len(sig) { - return b, errBadRLength + return Sig{}, errBadRLength } // Reading , remaining: [s] @@ -67,7 +122,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { // We know r is rLen bytes, and we have 0x30, , 0x20, // , 0x20, , a total of rLen+6 bytes. if sLen <= 0 || sLen+rLen+6 > len(sig) { - return b, errBadSLength + return Sig{}, errBadSLength } // Check to make sure R and S can both fit into their intended buffers. @@ -78,7 +133,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { // check S first. if sLen > 32 { if (sLen > 33) || (sig[6+rLen] != 0x00) { - return b, errSTooLong + return Sig{}, errSTooLong } sLen-- copy(b[64-sLen:], sig[7+rLen:]) @@ -89,7 +144,7 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { // Do the same for R as we did for S if rLen > 32 { if (rLen > 33) || (sig[4] != 0x00) { - return b, errRTooLong + return Sig{}, errRTooLong } rLen-- copy(b[32-rLen:], sig[5:5+rLen]) @@ -97,11 +152,24 @@ func NewSigFromRawSignature(sig []byte) (Sig, error) { copy(b[32-rLen:], sig[4:4+rLen]) } - return b, nil + return Sig{ + bytes: b, + sigType: sigTypeECDSA, + }, nil +} + +// NewSigFromSchnorrRawSignature converts a raw schnorr signature into an +// lnwire.Sig. +func NewSigFromSchnorrRawSignature(sig []byte) (Sig, error) { + var s Sig + copy(s.bytes[:], sig) + s.sigType = sigTypeSchnorr + + return s, nil } // NewSigFromSignature creates a new signature as used on the wire, from an -// existing ecdsa.Signature. +// existing ecdsa.Signature or schnorr.Signature. func NewSigFromSignature(e input.Signature) (Sig, error) { if e == nil { return Sig{}, fmt.Errorf("cannot decode empty signature") @@ -113,45 +181,85 @@ func NewSigFromSignature(e input.Signature) (Sig, error) { return Sig{}, fmt.Errorf("cannot decode empty signature") } - // Serialize the signature with all the checks that entails. - return NewSigFromRawSignature(e.Serialize()) -} + switch ecSig := e.(type) { + // If this is a schnorr signature, then we can just pack it as normal, + // since the default encoding is already 64 bytes. + case *schnorr.Signature: + return NewSigFromSchnorrRawSignature(e.Serialize()) + + // For ECDSA signatures, we'll need to do a bit more work to map the + // signature into a compact 64 byte form. + case *ecdsa.Signature: + // Serialize the signature with all the checks that entails. + return NewSigFromECDSARawSignature(e.Serialize()) -// ToSignature converts the fixed-sized signature to a ecdsa.Signature objects -// which can be used for signature validation checks. -func (b *Sig) ToSignature() (*ecdsa.Signature, error) { - // Parse the signature with strict checks. - sigBytes := b.ToSignatureBytes() - sig, err := ecdsa.ParseDERSignature(sigBytes) - if err != nil { - return nil, err + default: + return Sig{}, fmt.Errorf("unknown wire sig type: %T", ecSig) } +} + +// ToSignature converts the fixed-sized signature to a input.Signature which +// can be used for signature validation checks. +func (s *Sig) ToSignature() (input.Signature, error) { + switch s.sigType { + case sigTypeSchnorr: + return schnorr.ParseSignature(s.bytes[:]) - return sig, nil + case sigTypeECDSA: + // Parse the signature with strict checks. + sigBytes := s.ToSignatureBytes() + sig, err := ecdsa.ParseDERSignature(sigBytes) + if err != nil { + return nil, err + } + + return sig, nil + + default: + return nil, fmt.Errorf("unknown sig type: %v", s.sigType) + } } -// ToSignatureBytes serializes the target fixed-sized signature into the raw -// bytes of a DER encoding. -func (b *Sig) ToSignatureBytes() []byte { - // Extract canonically-padded bigint representations from buffer - r := extractCanonicalPadding(b[0:32]) - s := extractCanonicalPadding(b[32:64]) - rLen := uint8(len(r)) - sLen := uint8(len(s)) - - // Create a canonical serialized signature. DER format is: - // 0x30 0x02 r 0x02 s - sigBytes := make([]byte, 6+rLen+sLen) - sigBytes[0] = 0x30 // DER signature magic value - sigBytes[1] = 4 + rLen + sLen // Length of rest of signature - sigBytes[2] = 0x02 // Big integer magic value - sigBytes[3] = rLen // Length of R - sigBytes[rLen+4] = 0x02 // Big integer magic value - sigBytes[rLen+5] = sLen // Length of S - copy(sigBytes[4:], r) // Copy R - copy(sigBytes[rLen+6:], s) // Copy S - - return sigBytes +// ToSignatureBytes serializes the target fixed-sized signature into the +// encoding of the primary domain for the signature. For ECDSA signatures, this +// is the raw bytes of a DER encoding. +func (s *Sig) ToSignatureBytes() []byte { + switch s.sigType { + // For ECDSA signatures, we'll convert to DER encoding. + case sigTypeECDSA: + // Extract canonically-padded bigint representations from buffer + r := extractCanonicalPadding(s.bytes[0:32]) + s := extractCanonicalPadding(s.bytes[32:64]) + rLen := uint8(len(r)) + sLen := uint8(len(s)) + + // Create a canonical serialized signature. DER format is: + // 0x30 0x02 r 0x02 s + sigBytes := make([]byte, 6+rLen+sLen) + sigBytes[0] = 0x30 // DER signature magic value + sigBytes[1] = 4 + rLen + sLen // Length of rest of signature + sigBytes[2] = 0x02 // Big integer magic value + sigBytes[3] = rLen // Length of R + sigBytes[rLen+4] = 0x02 // Big integer magic value + sigBytes[rLen+5] = sLen // Length of S + copy(sigBytes[4:], r) // Copy R + copy(sigBytes[rLen+6:], s) // Copy S + + return sigBytes + + // For schnorr signatures, we can use the same internal 64 bytes. + case sigTypeSchnorr: + // We'll make a copy of the signature so we don't return a + // refrence into the raw slice. + var sig [64]byte + copy(sig[:], s.bytes[:]) + return sig[:] + + default: + // TODO(roasbeef): can only be called via public methods so + // never reachable? + panic("sig type not set") + } } // extractCanonicalPadding is a utility function to extract the canonical diff --git a/lnwire/signature_test.go b/lnwire/signature_test.go index 48ce212a05d..eee5eb4a077 100644 --- a/lnwire/signature_test.go +++ b/lnwire/signature_test.go @@ -21,11 +21,13 @@ func TestSignatureSerializeDeserialize(t *testing.T) { return err } - e2, err := sig.ToSignature() + e2Input, err := sig.ToSignature() if err != nil { return err } + e2 := e2Input.(*ecdsa.Signature) + if !e.IsEqual(e2) { return fmt.Errorf("pre/post-serialize sigs don't " + "match") @@ -188,16 +190,18 @@ func TestNewSigFromRawSignature(t *testing.T) { rawSig: normalSig, expectedErr: nil, expectedSig: Sig{ - // r value - 0x4e, 0x45, 0xe1, 0x69, 0x32, 0xb8, 0xaf, 0x51, - 0x49, 0x61, 0xa1, 0xd3, 0xa1, 0xa2, 0x5f, 0xdf, - 0x3f, 0x4f, 0x77, 0x32, 0xe9, 0xd6, 0x24, 0xc6, - 0xc6, 0x15, 0x48, 0xab, 0x5f, 0xb8, 0xcd, 0x41, - // s value - 0x18, 0x15, 0x22, 0xec, 0x8e, 0xca, 0x07, 0xde, - 0x48, 0x60, 0xa4, 0xac, 0xdd, 0x12, 0x90, 0x9d, - 0x83, 0x1c, 0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, - 0x08, 0x22, 0x21, 0xa8, 0x76, 0x8d, 0x1d, 0x09, + bytes: [64]byte{ + // r value + 0x4e, 0x45, 0xe1, 0x69, 0x32, 0xb8, 0xaf, 0x51, + 0x49, 0x61, 0xa1, 0xd3, 0xa1, 0xa2, 0x5f, 0xdf, + 0x3f, 0x4f, 0x77, 0x32, 0xe9, 0xd6, 0x24, 0xc6, + 0xc6, 0x15, 0x48, 0xab, 0x5f, 0xb8, 0xcd, 0x41, + // s value + 0x18, 0x15, 0x22, 0xec, 0x8e, 0xca, 0x07, 0xde, + 0x48, 0x60, 0xa4, 0xac, 0xdd, 0x12, 0x90, 0x9d, + 0x83, 0x1c, 0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, + 0x08, 0x22, 0x21, 0xa8, 0x76, 0x8d, 0x1d, 0x09, + }, }, }, { @@ -266,7 +270,7 @@ func TestNewSigFromRawSignature(t *testing.T) { for _, tc := range testCases { tc := tc t.Run(tc.name, func(t *testing.T) { - result, err := NewSigFromRawSignature(tc.rawSig) + result, err := NewSigFromECDSARawSignature(tc.rawSig) require.Equal(t, tc.expectedErr, err) require.Equal(t, tc.expectedSig, result) }) diff --git a/lnwire/writer.go b/lnwire/writer.go index 9c884105560..671ebfdc000 100644 --- a/lnwire/writer.go +++ b/lnwire/writer.go @@ -154,7 +154,7 @@ func WriteShortChannelID(buf *bytes.Buffer, shortChanID ShortChannelID) error { // WriteSig appends the signature to the provided buffer. func WriteSig(buf *bytes.Buffer, sig Sig) error { - return WriteBytes(buf, sig[:]) + return WriteBytes(buf, sig.bytes[:]) } // WriteSigs appends the slice of signatures to the provided buffer with its diff --git a/lnwire/writer_test.go b/lnwire/writer_test.go index 68594e59a45..185685c4132 100644 --- a/lnwire/writer_test.go +++ b/lnwire/writer_test.go @@ -147,7 +147,9 @@ func TestWriteShortChannelID(t *testing.T) { func TestWriteSig(t *testing.T) { buf := new(bytes.Buffer) - data := Sig{1, 2, 3} + data := Sig{ + bytes: [64]byte{1, 2, 3}, + } expectedBytes := [64]byte{1, 2, 3} err := WriteSig(buf, data) @@ -158,14 +160,14 @@ func TestWriteSig(t *testing.T) { func TestWriteSigs(t *testing.T) { buf := new(bytes.Buffer) - sig1, sig2, sig3 := Sig{1}, Sig{2}, Sig{3} + sig1, sig2, sig3 := Sig{bytes: [64]byte{1}}, Sig{bytes: [64]byte{2}}, Sig{bytes: [64]byte{3}} data := []Sig{sig1, sig2, sig3} // First two bytes encode the length of the slice. expectedBytes := []byte{0, 3} - expectedBytes = append(expectedBytes, sig1[:]...) - expectedBytes = append(expectedBytes, sig2[:]...) - expectedBytes = append(expectedBytes, sig3[:]...) + expectedBytes = append(expectedBytes, sig1.bytes[:]...) + expectedBytes = append(expectedBytes, sig2.bytes[:]...) + expectedBytes = append(expectedBytes, sig3.bytes[:]...) err := WriteSigs(buf, data) diff --git a/netann/channel_announcement.go b/netann/channel_announcement.go index 480b8cf3e87..0ae8d606d4b 100644 --- a/netann/channel_announcement.go +++ b/netann/channel_announcement.go @@ -36,25 +36,25 @@ func CreateChanAnnouncement(chanProof *channeldb.ChannelAuthProof, if err != nil { return nil, nil, nil, err } - chanAnn.BitcoinSig1, err = lnwire.NewSigFromRawSignature( + chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( chanProof.BitcoinSig1Bytes, ) if err != nil { return nil, nil, nil, err } - chanAnn.BitcoinSig2, err = lnwire.NewSigFromRawSignature( + chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( chanProof.BitcoinSig2Bytes, ) if err != nil { return nil, nil, nil, err } - chanAnn.NodeSig1, err = lnwire.NewSigFromRawSignature( + chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( chanProof.NodeSig1Bytes, ) if err != nil { return nil, nil, nil, err } - chanAnn.NodeSig2, err = lnwire.NewSigFromRawSignature( + chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( chanProof.NodeSig2Bytes, ) if err != nil { diff --git a/netann/channel_announcement_test.go b/netann/channel_announcement_test.go index 01aa767f8c2..bc2460b9af1 100644 --- a/netann/channel_announcement_test.go +++ b/netann/channel_announcement_test.go @@ -17,7 +17,7 @@ func TestCreateChanAnnouncement(t *testing.T) { t.Parallel() key := [33]byte{0x1} - sig := lnwire.Sig{0x1} + var sig lnwire.Sig features := lnwire.NewRawFeatureVector(lnwire.AnchorsRequired) var featuresBuf bytes.Buffer if err := features.Encode(&featuresBuf); err != nil { diff --git a/netann/channel_update.go b/netann/channel_update.go index ca26acac678..b6555f37b1b 100644 --- a/netann/channel_update.go +++ b/netann/channel_update.go @@ -143,7 +143,9 @@ func ChannelUpdateFromEdge(info *channeldb.ChannelEdgeInfo, update := UnsignedChannelUpdateFromEdge(info, policy) var err error - update.Signature, err = lnwire.NewSigFromRawSignature(policy.SigBytes) + update.Signature, err = lnwire.NewSigFromECDSARawSignature( + policy.SigBytes, + ) if err != nil { return nil, err } diff --git a/routing/router_test.go b/routing/router_test.go index 767cf734719..3f1d218e35b 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -506,7 +506,7 @@ func TestChannelUpdateValidation(t *testing.T) { // Set up a channel update message with an invalid signature to be // returned to the sender. - var invalidSignature [64]byte + var invalidSignature lnwire.Sig errChanUpdate := lnwire.ChannelUpdate{ Signature: invalidSignature, FeeRate: 500, diff --git a/server.go b/server.go index 7758ebe8c26..24738839259 100644 --- a/server.go +++ b/server.go @@ -841,7 +841,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, "self node announcement: %v", err) } selfNode.AuthSigBytes = authSig.Serialize() - nodeAnn.Signature, err = lnwire.NewSigFromRawSignature( + nodeAnn.Signature, err = lnwire.NewSigFromECDSARawSignature( selfNode.AuthSigBytes, ) if err != nil { diff --git a/watchtower/blob/justice_kit.go b/watchtower/blob/justice_kit.go index c18433c37ae..7b39eeb4083 100644 --- a/watchtower/blob/justice_kit.go +++ b/watchtower/blob/justice_kit.go @@ -398,7 +398,7 @@ func (b *JusticeKit) encodeV0(w io.Writer) error { } // Write 64-byte revocation signature for commit to-local output. - _, err = w.Write(b.CommitToLocalSig[:]) + _, err = w.Write(b.CommitToLocalSig.RawBytes()) if err != nil { return err } @@ -410,7 +410,7 @@ func (b *JusticeKit) encodeV0(w io.Writer) error { } // Write 64-byte commit to-remote signature, which may be blank. - _, err = w.Write(b.CommitToRemoteSig[:]) + _, err = w.Write(b.CommitToRemoteSig.RawBytes()) return err } @@ -472,14 +472,20 @@ func (b *JusticeKit) decodeV0(r io.Reader) error { } // Read 64-byte revocation signature for commit to-local output. - _, err = io.ReadFull(r, b.CommitToLocalSig[:]) + var localSig [64]byte + _, err = io.ReadFull(r, localSig[:]) + if err != nil { + return err + } + + b.CommitToLocalSig, err = lnwire.NewSigFromWireECDSA(localSig[:]) if err != nil { return err } var ( commitToRemotePubkey PubKey - commitToRemoteSig lnwire.Sig + commitToRemoteSig [64]byte ) // Read 33-byte commit to-remote public key, which may be discarded. @@ -498,7 +504,12 @@ func (b *JusticeKit) decodeV0(r io.Reader) error { // valid compressed public key was read from the reader. if btcec.IsCompressedPubKey(commitToRemotePubkey[:]) { b.CommitToRemotePubKey = commitToRemotePubkey - b.CommitToRemoteSig = commitToRemoteSig + b.CommitToRemoteSig, err = lnwire.NewSigFromWireECDSA( + commitToRemoteSig[:], + ) + if err != nil { + return err + } } return nil diff --git a/watchtower/blob/justice_kit_test.go b/watchtower/blob/justice_kit_test.go index b6b7b59adab..7db8d9a363b 100644 --- a/watchtower/blob/justice_kit_test.go +++ b/watchtower/blob/justice_kit_test.go @@ -28,8 +28,10 @@ func makePubKey(i uint64) blob.PubKey { } func makeSig(i int) lnwire.Sig { - var sig lnwire.Sig - binary.BigEndian.PutUint64(sig[:8], uint64(i)) + var sigBytes [64]byte + binary.BigEndian.PutUint64(sigBytes[:8], uint64(i)) + + sig, _ := lnwire.NewSigFromWireECDSA(sigBytes[:]) return sig } diff --git a/watchtower/lookout/justice_descriptor_test.go b/watchtower/lookout/justice_descriptor_test.go index aa1b10f7bf0..c37ed26930c 100644 --- a/watchtower/lookout/justice_descriptor_test.go +++ b/watchtower/lookout/justice_descriptor_test.go @@ -320,8 +320,8 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { require.Nil(t, err) // Complete our justice kit by copying the signatures into the payload. - copy(justiceKit.CommitToLocalSig[:], toLocalSig[:]) - copy(justiceKit.CommitToRemoteSig[:], toRemoteSig[:]) + justiceKit.CommitToLocalSig = toLocalSig + justiceKit.CommitToRemoteSig = toRemoteSig justiceDesc := &lookout.JusticeDescriptor{ BreachedCommitTx: breachTxn, diff --git a/watchtower/lookout/lookout_test.go b/watchtower/lookout/lookout_test.go index 34e5590e79c..c7305d9e082 100644 --- a/watchtower/lookout/lookout_test.go +++ b/watchtower/lookout/lookout_test.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/watchtower/blob" "github.com/lightningnetwork/lnd/watchtower/lookout" "github.com/lightningnetwork/lnd/watchtower/wtdb" @@ -57,6 +58,12 @@ func makeArray64(i uint64) [64]byte { return arr } +func makeTestSig(i uint64) lnwire.Sig { + sigBytes := makeArray64(i) + sig, _ := lnwire.NewSigFromWireECDSA(sigBytes[:]) + return sig +} + func makeAddrSlice(size int) []byte { addr := make([]byte, size) if _, err := io.ReadFull(rand.Reader, addr); err != nil { @@ -141,7 +148,7 @@ func TestLookoutBreachMatching(t *testing.T) { RevocationPubKey: makePubKey(1), LocalDelayPubKey: makePubKey(1), CSVDelay: 144, - CommitToLocalSig: makeArray64(1), + CommitToLocalSig: makeTestSig(1), } blob2 := &blob.JusticeKit{ BlobType: blobType, @@ -149,7 +156,7 @@ func TestLookoutBreachMatching(t *testing.T) { RevocationPubKey: makePubKey(2), LocalDelayPubKey: makePubKey(2), CSVDelay: 144, - CommitToLocalSig: makeArray64(2), + CommitToLocalSig: makeTestSig(2), } key1 := blob.NewBreachKeyFromHash(&hash1) diff --git a/watchtower/wtclient/backup_task.go b/watchtower/wtclient/backup_task.go index a7268930340..a815ab88a7c 100644 --- a/watchtower/wtclient/backup_task.go +++ b/watchtower/wtclient/backup_task.go @@ -365,24 +365,26 @@ func (t *backupTask) craftSessionPayload( // Re-encode the DER signature into a fixed-size 64 byte // signature. - signature, err := lnwire.NewSigFromRawSignature(rawSignature) + signature, err := lnwire.NewSigFromECDSARawSignature( + rawSignature, + ) if err != nil { return hint, nil, err } // Finally, copy the serialized signature into the justice kit, // using the input's witness type to select the appropriate - // field. + // field switch inp.WitnessType() { case input.CommitmentRevoke: - copy(justiceKit.CommitToLocalSig[:], signature[:]) + justiceKit.CommitToLocalSig = signature case input.CommitSpendNoDelayTweakless: fallthrough case input.CommitmentNoDelay: fallthrough case input.CommitmentToRemoteConfirmed: - copy(justiceKit.CommitToRemoteSig[:], signature[:]) + justiceKit.CommitToRemoteSig = signature default: return hint, nil, fmt.Errorf("invalid witness type: %v", inp.WitnessType()) diff --git a/watchtower/wtclient/backup_task_internal_test.go b/watchtower/wtclient/backup_task_internal_test.go index c536c433b9b..e49246b52d6 100644 --- a/watchtower/wtclient/backup_task_internal_test.go +++ b/watchtower/wtclient/backup_task_internal_test.go @@ -609,8 +609,9 @@ func testBackupTask(t *testing.T, test backupTaskTest) { // moment, it is tested indirectly by other packages and integration // tests. // TODO(conner): include signature validation checks - - emptyToLocalSig := bytes.Equal(jKit.CommitToLocalSig[:], zeroSig[:]) + emptyToLocalSig := bytes.Equal( + jKit.CommitToLocalSig.RawBytes(), zeroSig[:], + ) if hasToLocal { require.False(t, emptyToLocalSig, "to-local signature should "+ "not be empty") @@ -619,7 +620,9 @@ func testBackupTask(t *testing.T, test backupTaskTest) { "be empty") } - emptyToRemoteSig := bytes.Equal(jKit.CommitToRemoteSig[:], zeroSig[:]) + emptyToRemoteSig := bytes.Equal( + jKit.CommitToRemoteSig.RawBytes(), zeroSig[:], + ) if hasToRemote { require.False(t, emptyToRemoteSig, "to-remote signature "+ "should not be empty") diff --git a/zpay32/decode.go b/zpay32/decode.go index b881d58694e..d37a34cf9db 100644 --- a/zpay32/decode.go +++ b/zpay32/decode.go @@ -91,8 +91,10 @@ func Decode(invoice string, net *chaincfg.Params) (*Invoice, error) { if err != nil { return nil, err } - var sig lnwire.Sig - copy(sig[:], sigBase256[:64]) + sig, err := lnwire.NewSigFromWireECDSA(sigBase256[:64]) + if err != nil { + return nil, err + } recoveryID := sigBase256[64] // The signature is over the hrp + the data the invoice, encoded in @@ -121,7 +123,7 @@ func Decode(invoice string, net *chaincfg.Params) (*Invoice, error) { } } else { headerByte := recoveryID + 27 + 4 - compactSign := append([]byte{headerByte}, sig[:]...) + compactSign := append([]byte{headerByte}, sig.RawBytes()...) pubkey, _, err := ecdsa.RecoverCompact(compactSign, hash) if err != nil { return nil, err diff --git a/zpay32/encode.go b/zpay32/encode.go index a30d0c3911d..f8e1795e154 100644 --- a/zpay32/encode.go +++ b/zpay32/encode.go @@ -91,8 +91,10 @@ func (invoice *Invoice) Encode(signer MessageSigner) (string, error) { // From the header byte we can extract the recovery ID, and the last 64 // bytes encode the signature. recoveryID := sign[0] - 27 - 4 - var sig lnwire.Sig - copy(sig[:], sign[1:]) + sig, err := lnwire.NewSigFromWireECDSA(sign[1:]) + if err != nil { + return "", err + } // If the pubkey field was explicitly set, it must be set to the pubkey // used to create the signature. @@ -112,7 +114,10 @@ func (invoice *Invoice) Encode(signer MessageSigner) (string, error) { } // Convert the signature to base32 before writing it to the buffer. - signBase32, err := bech32.ConvertBits(append(sig[:], recoveryID), 8, 5, true) + signBase32, err := bech32.ConvertBits( + append(sig.RawBytes(), recoveryID), + 8, 5, true, + ) if err != nil { return "", err } From ef87a3af3121a96564b11958171469cb207f7e71 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:39:05 -0800 Subject: [PATCH 03/15] lnwire: add new Musig2Nonce TLV record --- lnwire/musig2.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 lnwire/musig2.go diff --git a/lnwire/musig2.go b/lnwire/musig2.go new file mode 100644 index 00000000000..8062a1a7d71 --- /dev/null +++ b/lnwire/musig2.go @@ -0,0 +1,50 @@ +package lnwire + +import ( + "io" + + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/lightningnetwork/lnd/tlv" +) + +const ( + // NonceRecordType is the TLV type used to encode a local musig2 nonce. + NonceRecordType tlv.Type = 4 +) + +// Musig2Nonce represents a musig2 public nonce, which is the concatenation of +// two EC points serialized in compressed format. +type Musig2Nonce [musig2.PubNonceSize]byte + +// Record returns a TLV record that can be used to encode/decode the musig2 +// nonce from a given TLV stream. +func (m *Musig2Nonce) Record() tlv.Record { + return tlv.MakeStaticRecord( + NonceRecordType, m, musig2.PubNonceSize, nonceTypeEncoder, + nonceTypeDecoder, + ) +} + +// nonceTypeEncoder is a custom TLV encoder for the Musig2Nonce type. +func nonceTypeEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + if v, ok := val.(*Musig2Nonce); ok { + _, err := w.Write(v[:]) + return err + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.Musig2Nonce") +} + +// nonceTypeDecoder is a custom TLV decoder for the Musig2Nonce record. +func nonceTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, + l uint64) error { + + if v, ok := val.(*Musig2Nonce); ok { + _, err := io.ReadFull(r, v[:]) + return err + } + + return tlv.NewTypeForDecodingErr( + val, "lnwire.Musig2Nonce", l, musig2.PubNonceSize, + ) +} From f043f1778aa36110e22b2e600644902b794cbd31 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:38:34 -0800 Subject: [PATCH 04/15] lnwire: add new musig2 partial signature type In this commit, we add the new types that'll house musig signatures with and without their nonces. We send the nonce along with the sig everywhere but the co-op close flow. --- lnwire/partial_sig.go | 201 ++++++++++++++++++++++++++++++++++++++++++ lnwire/writer_test.go | 4 +- 2 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 lnwire/partial_sig.go diff --git a/lnwire/partial_sig.go b/lnwire/partial_sig.go new file mode 100644 index 00000000000..fa05950ab2d --- /dev/null +++ b/lnwire/partial_sig.go @@ -0,0 +1,201 @@ +package lnwire + +import ( + "io" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/lightningnetwork/lnd/tlv" +) + +const ( + // PartialSigLen is the length of a musig2 partial signature. + PartialSigLen = 32 + + // PartialSigRecordType is the type of the tlv record for a musig2 + // partial signature. This is an _even_ type, which means it's required + // if included. + PartialSigRecordType tlv.Type = 6 +) + +// PartialSig is the base partial sig type. This only encodes the 32-byte +// partial signature. This is used for the co-op close flow, as both sides have +// already exchanged nonces, so they can send just the partial signature. +type PartialSig struct { + // Sig is the 32-byte musig2 partial signature. + Sig btcec.ModNScalar +} + +// NewPartialSig creates a new partial sig. +func NewPartialSig(sig btcec.ModNScalar) PartialSig { + return PartialSig{ + Sig: sig, + } +} + +// Record returns the tlv record for the partial sig. +func (p *PartialSig) Record() tlv.Record { + return tlv.MakeStaticRecord( + PartialSigRecordType, p, PartialSigLen, + partialSigTypeEncoder, partialSigTypeDecoder, + ) +} + +// partialSigTypeEncoder encodes a 32-byte musig2 partial signature as a TLV +// value. +func partialSigTypeEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + if v, ok := val.(*PartialSig); ok { + sigBytes := v.Sig.Bytes() + + return tlv.EBytes32(w, &sigBytes, buf) + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.PartialSig") +} + +// Encode writes the encoded version of this message to the passed io.Writer. +func (p *PartialSig) Encode(w io.Writer) error { + return partialSigTypeEncoder(w, p, nil) +} + +// partialSigTypeDecoder decodes a 32-byte musig2 extended partial signature. +func partialSigTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, + l uint64) error { + + if v, ok := val.(*PartialSig); ok && l == PartialSigLen { + var sBytes [32]byte + err := tlv.DBytes32(r, &sBytes, buf, PartialSigLen) + if err != nil { + return err + } + + var s btcec.ModNScalar + s.SetBytes(&sBytes) + + *v = PartialSig{ + Sig: s, + } + + return nil + } + + return tlv.NewTypeForDecodingErr(val, "lnwire.PartialSig", l, + PartialSigLen) +} + +// Decode reads the encoded version of this message from the passed io.Reader. +func (p *PartialSig) Decode(r io.Reader) error { + return partialSigTypeDecoder(r, p, nil, PartialSigLen) +} + +const ( + // PartialSigWithNonceLen is the length of a serialized + // PartialSigWithNonce. The sig is encoded as the 32 byte S value + // followed by the 66 nonce value. + PartialSigWithNonceLen = 98 + + // PartialSigWithNonceRecordType is the type of the tlv record for a + // musig2 partial signature with nonce. This is an _even_ type, which + // means it's required if included. + PartialSigWithNonceRecordType tlv.Type = 2 +) + +// PartialSigWithNonce is a partial signature with the nonce that was used to +// generate the signature. This is used for funding as well as the commitment +// transaction update dance. By sending the nonce only with the signature, we +// enable the sender to generate their nonce just before they create their +// signature. Signers can use this trait to mix in additional contextual data +// such as the commitment txn itself into their nonce generation function. +// +// The final signature is 98 bytes: 32 bytes for the S value, and 66 bytes for +// the public nonce (two compressed points). +type PartialSigWithNonce struct { + PartialSig + + // Nonce is the 66-byte musig2 nonce. + Nonce Musig2Nonce +} + +// NewPartialSigWithNonce creates a new partial sig with nonce. +func NewPartialSigWithNonce(nonce [musig2.PubNonceSize]byte, + sig btcec.ModNScalar) *PartialSigWithNonce { + + return &PartialSigWithNonce{ + Nonce: nonce, + PartialSig: NewPartialSig(sig), + } +} + +// Record returns the tlv record for the partial sig with nonce. +func (p *PartialSigWithNonce) Record() tlv.Record { + return tlv.MakeStaticRecord( + PartialSigWithNonceRecordType, p, PartialSigWithNonceLen, + partialSigWithNonceTypeEncoder, partialSigWithNonceTypeDecoder, + ) +} + +// partialSigWithNonceTypeEncoder encodes 98-byte musig2 extended partial +// signature as: s {32} || nonce {66}. +func partialSigWithNonceTypeEncoder(w io.Writer, val interface{}, + buf *[8]byte) error { + + if v, ok := val.(*PartialSigWithNonce); ok { + sigBytes := v.Sig.Bytes() + if _, err := w.Write(sigBytes[:]); err != nil { + return err + } + if _, err := w.Write(v.Nonce[:]); err != nil { + return err + } + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.PartialSigWithNonce") +} + +// Encode writes the encoded version of this message to the passed io.Writer. +func (p *PartialSigWithNonce) Encode(w io.Writer) error { + return partialSigWithNonceTypeEncoder(w, p, nil) +} + +// partialSigWithNonceTypeDecoder decodes a 98-byte musig2 extended partial +// signature. +func partialSigWithNonceTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, + l uint64) error { + + if v, ok := val.(*PartialSigWithNonce); ok && + l == PartialSigWithNonceLen { + + var sBytes [32]byte + err := tlv.DBytes32(r, &sBytes, buf, PartialSigLen) + if err != nil { + return err + } + + var s btcec.ModNScalar + s.SetBytes(&sBytes) + + var nonce [66]byte + if _, err := io.ReadFull(r, nonce[:]); err != nil { + return err + } + + *v = PartialSigWithNonce{ + PartialSig: NewPartialSig(s), + Nonce: nonce, + } + + return nil + } + + return tlv.NewTypeForDecodingErr(val, "lnwire.PartialSigWithNonce", l, + PartialSigWithNonceLen) +} + +// Decode reads the encoded version of this message from the passed io.Reader. +func (p *PartialSigWithNonce) Decode(r io.Reader) error { + return partialSigWithNonceTypeDecoder( + r, p, nil, PartialSigWithNonceLen, + ) +} diff --git a/lnwire/writer_test.go b/lnwire/writer_test.go index 185685c4132..ccdeabcf624 100644 --- a/lnwire/writer_test.go +++ b/lnwire/writer_test.go @@ -160,7 +160,9 @@ func TestWriteSig(t *testing.T) { func TestWriteSigs(t *testing.T) { buf := new(bytes.Buffer) - sig1, sig2, sig3 := Sig{bytes: [64]byte{1}}, Sig{bytes: [64]byte{2}}, Sig{bytes: [64]byte{3}} + sig1 := Sig{bytes: [64]byte{1}} + sig2 := Sig{bytes: [64]byte{2}} + sig3 := Sig{bytes: [64]byte{3}} data := []Sig{sig1, sig2, sig3} // First two bytes encode the length of the slice. From 8a5b7cf0a6997276587dd68a57e5864ed63c587e Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:39:24 -0800 Subject: [PATCH 05/15] lnwire: add LocalNonce to OpenChannel --- lnwire/open_channel.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lnwire/open_channel.go b/lnwire/open_channel.go index 4887ced3424..9cb4bc41ad3 100644 --- a/lnwire/open_channel.go +++ b/lnwire/open_channel.go @@ -141,6 +141,13 @@ type OpenChannel struct { // type. LeaseExpiry *LeaseExpiry + // LocalNonce is an optional field that transmits the + // local/verification nonce for a party. This nonce will be used to + // verify the very first commitment transaction signature. This will + // only be populated if the simple taproot channels type was + // negotiated. + LocalNonce *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. @@ -168,6 +175,9 @@ func (o *OpenChannel) Encode(w *bytes.Buffer, pver uint32) error { if o.LeaseExpiry != nil { recordProducers = append(recordProducers, o.LeaseExpiry) } + if o.LocalNonce != nil { + recordProducers = append(recordProducers, o.LocalNonce) + } err := EncodeMessageExtraData(&o.ExtraData, recordProducers...) if err != nil { return err @@ -292,9 +302,11 @@ func (o *OpenChannel) Decode(r io.Reader, pver uint32) error { var ( chanType ChannelType leaseExpiry LeaseExpiry + localNonce Musig2Nonce ) typeMap, err := tlvRecords.ExtractRecords( &o.UpfrontShutdownScript, &chanType, &leaseExpiry, + &localNonce, ) if err != nil { return err @@ -307,6 +319,9 @@ func (o *OpenChannel) Decode(r io.Reader, pver uint32) error { if val, ok := typeMap[LeaseExpiryRecordType]; ok && val == nil { o.LeaseExpiry = &leaseExpiry } + if val, ok := typeMap[NonceRecordType]; ok && val == nil { + o.LocalNonce = &localNonce + } o.ExtraData = tlvRecords From 86dd6a22c57e52a9da95099eee86f49492b0ce65 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:39:35 -0800 Subject: [PATCH 06/15] lnwire: add LocalNonce to AcceptChannel --- lnwire/accept_channel.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lnwire/accept_channel.go b/lnwire/accept_channel.go index cce1ba42ba7..66dda815c61 100644 --- a/lnwire/accept_channel.go +++ b/lnwire/accept_channel.go @@ -105,6 +105,13 @@ type AcceptChannel struct { // type. LeaseExpiry *LeaseExpiry + // LocalNonce is an optional field that transmits the + // local/verification nonce for a party. This nonce will be used to + // verify the very first commitment transaction signature. + // This will only be populated if the simple taproot channels type was + // negotiated. + LocalNonce *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. @@ -134,6 +141,9 @@ func (a *AcceptChannel) Encode(w *bytes.Buffer, pver uint32) error { if a.LeaseExpiry != nil { recordProducers = append(recordProducers, a.LeaseExpiry) } + if a.LocalNonce != nil { + recordProducers = append(recordProducers, a.LocalNonce) + } err := EncodeMessageExtraData(&a.ExtraData, recordProducers...) if err != nil { return err @@ -238,9 +248,11 @@ func (a *AcceptChannel) Decode(r io.Reader, pver uint32) error { var ( chanType ChannelType leaseExpiry LeaseExpiry + localNonce Musig2Nonce ) typeMap, err := tlvRecords.ExtractRecords( &a.UpfrontShutdownScript, &chanType, &leaseExpiry, + &localNonce, ) if err != nil { return err @@ -253,6 +265,9 @@ func (a *AcceptChannel) Decode(r io.Reader, pver uint32) error { if val, ok := typeMap[LeaseExpiryRecordType]; ok && val == nil { a.LeaseExpiry = &leaseExpiry } + if val, ok := typeMap[NonceRecordType]; ok && val == nil { + a.LocalNonce = &localNonce + } a.ExtraData = tlvRecords From 5c494ed9ee55aeb86a5b227822facb8edcf48d37 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:39:50 -0800 Subject: [PATCH 07/15] lnwire: add PartialSig to FundingCreated --- lnwire/funding_created.go | 47 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/lnwire/funding_created.go b/lnwire/funding_created.go index 02b5134716e..f8128ff761c 100644 --- a/lnwire/funding_created.go +++ b/lnwire/funding_created.go @@ -5,6 +5,7 @@ import ( "io" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/tlv" ) // FundingCreated is sent from Alice (the initiator) to Bob (the responder), @@ -26,6 +27,13 @@ type FundingCreated struct { // transaction. CommitSig Sig + // PartialSig is used to transmit a musig2 extended partial signature + // that also carries along the public nonce of the signer. + // + // NOTE: This field is only populated if a musig2 taproot channel is + // being signed for. In this case, the above Sig type MUST be blank. + PartialSig *PartialSigWithNonce + // 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. @@ -42,6 +50,15 @@ var _ Message = (*FundingCreated)(nil) // // This is part of the lnwire.Message interface. func (f *FundingCreated) Encode(w *bytes.Buffer, pver uint32) error { + recordProducers := make([]tlv.RecordProducer, 0, 1) + if f.PartialSig != nil { + recordProducers = append(recordProducers, f.PartialSig) + } + err := EncodeMessageExtraData(&f.ExtraData, recordProducers...) + if err != nil { + return err + } + if err := WriteBytes(w, f.PendingChannelID[:]); err != nil { return err } @@ -63,10 +80,36 @@ func (f *FundingCreated) Encode(w *bytes.Buffer, pver uint32) error { // // This is part of the lnwire.Message interface. func (f *FundingCreated) Decode(r io.Reader, pver uint32) error { - return ReadElements( + err := ReadElements( r, f.PendingChannelID[:], &f.FundingPoint, &f.CommitSig, - &f.ExtraData, ) + if err != nil { + return err + } + + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var ( + partialSig PartialSigWithNonce + ) + typeMap, err := tlvRecords.ExtractRecords(&partialSig) + if err != nil { + return err + } + + // Set the corresponding TLV types if they were included in the stream. + if val, ok := typeMap[PartialSigWithNonceRecordType]; ok && val == nil { + f.PartialSig = &partialSig + } + + if len(tlvRecords) != 0 { + f.ExtraData = tlvRecords + } + + return nil } // MsgType returns the uint32 code which uniquely identifies this message as a From 5d2f4d68a44d28423fad77793e320c5a339dd6c5 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:40:03 -0800 Subject: [PATCH 08/15] lnwire: add PartialSig to FundingSigned --- lnwire/funding_signed.go | 47 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/lnwire/funding_signed.go b/lnwire/funding_signed.go index d7386f2ed0f..c7fb03d155b 100644 --- a/lnwire/funding_signed.go +++ b/lnwire/funding_signed.go @@ -3,6 +3,8 @@ package lnwire import ( "bytes" "io" + + "github.com/lightningnetwork/lnd/tlv" ) // FundingSigned is sent from Bob (the responder) to Alice (the initiator) @@ -17,6 +19,13 @@ type FundingSigned struct { // transaction. CommitSig Sig + // PartialSig is used to transmit a musig2 extended partial signature + // that also carries along the public nonce of the signer. + // + // NOTE: This field is only populated if a musig2 taproot channel is + // being signed for. In this case, the above Sig type MUST be blank. + PartialSig *PartialSigWithNonce + // 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. @@ -33,6 +42,15 @@ var _ Message = (*FundingSigned)(nil) // // This is part of the lnwire.Message interface. func (f *FundingSigned) Encode(w *bytes.Buffer, pver uint32) error { + recordProducers := make([]tlv.RecordProducer, 0, 1) + if f.PartialSig != nil { + recordProducers = append(recordProducers, f.PartialSig) + } + err := EncodeMessageExtraData(&f.ExtraData, recordProducers...) + if err != nil { + return err + } + if err := WriteChannelID(w, f.ChanID); err != nil { return err } @@ -50,7 +68,34 @@ func (f *FundingSigned) Encode(w *bytes.Buffer, pver uint32) error { // // This is part of the lnwire.Message interface. func (f *FundingSigned) Decode(r io.Reader, pver uint32) error { - return ReadElements(r, &f.ChanID, &f.CommitSig, &f.ExtraData) + err := ReadElements(r, &f.ChanID, &f.CommitSig) + if err != nil { + return err + } + + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var ( + partialSig PartialSigWithNonce + ) + typeMap, err := tlvRecords.ExtractRecords(&partialSig) + if err != nil { + return err + } + + // Set the corresponding TLV types if they were included in the stream. + if val, ok := typeMap[PartialSigWithNonceRecordType]; ok && val == nil { + f.PartialSig = &partialSig + } + + if len(tlvRecords) != 0 { + f.ExtraData = tlvRecords + } + + return nil } // MsgType returns the uint32 code which uniquely identifies this message as a From e086d428d00f7fd40e29de2e6dceeb26a9d5e7c3 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:40:14 -0800 Subject: [PATCH 09/15] lnwire: add LocalNonce to FundingLocked --- lnwire/funding_locked.go | 44 ++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/lnwire/funding_locked.go b/lnwire/funding_locked.go index fb47356bad3..a56f867b0a7 100644 --- a/lnwire/funding_locked.go +++ b/lnwire/funding_locked.go @@ -27,6 +27,12 @@ type FundingLocked struct { // ShortChannelID for forwarding. AliasScid *ShortChannelID + // NextLocalNonce is an optional field that stores a local musig2 nonce. + // This will only be populated if the simple taproot channels type was + // negotiated. This is the local nonce that will be used by the sender + // to accept a new commitment state transition. + NextLocalNonce *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. @@ -39,7 +45,7 @@ func NewFundingLocked(cid ChannelID, npcp *btcec.PublicKey) *FundingLocked { return &FundingLocked{ ChanID: cid, NextPerCommitmentPoint: npcp, - ExtraData: make([]byte, 0), + ExtraData: nil, } } @@ -57,16 +63,25 @@ func (c *FundingLocked) Decode(r io.Reader, pver uint32) error { err := ReadElements(r, &c.ChanID, &c.NextPerCommitmentPoint, - &c.ExtraData, ) if err != nil { return err } + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + // Next we'll parse out the set of known records. For now, this is just // the AliasScidRecordType. - var aliasScid ShortChannelID - typeMap, err := c.ExtraData.ExtractRecords(&aliasScid) + var ( + aliasScid ShortChannelID + localNonce Musig2Nonce + ) + typeMap, err := tlvRecords.ExtractRecords( + &aliasScid, &localNonce, + ) if err != nil { return err } @@ -76,6 +91,13 @@ func (c *FundingLocked) Decode(r io.Reader, pver uint32) error { if val, ok := typeMap[AliasScidRecordType]; ok && val == nil { c.AliasScid = &aliasScid } + if val, ok := typeMap[NonceRecordType]; ok && val == nil { + c.NextLocalNonce = &localNonce + } + + if len(tlvRecords) != 0 { + c.ExtraData = tlvRecords + } return nil } @@ -95,12 +117,16 @@ func (c *FundingLocked) Encode(w *bytes.Buffer, pver uint32) error { } // We'll only encode the AliasScid in a TLV segment if it exists. + recordProducers := make([]tlv.RecordProducer, 0, 2) if c.AliasScid != nil { - recordProducers := []tlv.RecordProducer{c.AliasScid} - err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) - if err != nil { - return err - } + recordProducers = append(recordProducers, c.AliasScid) + } + if c.NextLocalNonce != nil { + recordProducers = append(recordProducers, c.NextLocalNonce) + } + err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) + if err != nil { + return err } return WriteBytes(w, c.ExtraData) From 5b57964abef9e5551951996c86d43e4b4eb6f51e Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:40:30 -0800 Subject: [PATCH 10/15] lnwire: add LocalNonce to ChannelReestablish --- lnwire/channel_reestablish.go | 41 ++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/lnwire/channel_reestablish.go b/lnwire/channel_reestablish.go index 387cc5b580f..1b6cfdffc36 100644 --- a/lnwire/channel_reestablish.go +++ b/lnwire/channel_reestablish.go @@ -5,6 +5,7 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/tlv" ) // ChannelReestablish is a message sent between peers that have an existing @@ -62,6 +63,13 @@ type ChannelReestablish struct { // current un-revoked commitment transaction of the sending party. LocalUnrevokedCommitPoint *btcec.PublicKey + // LocalNonce is an optional field that stores a local musig2 nonce. + // This will only be populated if the simple taproot channels type was + // negotiated. + // + // TODO(roasbeef): rename to verification nonce + LocalNonce *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. @@ -108,6 +116,16 @@ func (a *ChannelReestablish) Encode(w *bytes.Buffer, pver uint32) error { if err := WritePublicKey(w, a.LocalUnrevokedCommitPoint); err != nil { return err } + + recordProducers := make([]tlv.RecordProducer, 0, 1) + if a.LocalNonce != nil { + recordProducers = append(recordProducers, a.LocalNonce) + } + err := EncodeMessageExtraData(&a.ExtraData, recordProducers...) + if err != nil { + return err + } + return WriteBytes(w, a.ExtraData) } @@ -156,7 +174,28 @@ func (a *ChannelReestablish) Decode(r io.Reader, pver uint32) error { return err } - return a.ExtraData.Decode(r) + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var localNonce Musig2Nonce + typeMap, err := tlvRecords.ExtractRecords( + &localNonce, + ) + if err != nil { + return err + } + + if val, ok := typeMap[NonceRecordType]; ok && val == nil { + a.LocalNonce = &localNonce + } + + if len(tlvRecords) != 0 { + a.ExtraData = tlvRecords + } + + return nil } // MsgType returns the integer uniquely identifying this message type on the From 70bbd6f130b20a0a4d7c85acad5d24e8b1e87e4a Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:40:49 -0800 Subject: [PATCH 11/15] lnwire: add PartialSig to CommitSig --- lnwire/commit_sig.go | 48 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/lnwire/commit_sig.go b/lnwire/commit_sig.go index ca105f71a66..d25d36a8add 100644 --- a/lnwire/commit_sig.go +++ b/lnwire/commit_sig.go @@ -3,6 +3,8 @@ package lnwire import ( "bytes" "io" + + "github.com/lightningnetwork/lnd/tlv" ) // CommitSig is sent by either side to stage any pending HTLC's in the @@ -36,6 +38,13 @@ type CommitSig struct { // transaction should be signed. HtlcSigs []Sig + // PartialSig is used to transmit a musig2 extended partial signature + // that also carries along the public nonce of the signer. + // + // NOTE: This field is only populated if a musig2 taproot channel is + // being signed for. In this case, the above Sig type MUST be blank. + PartialSig *PartialSigWithNonce + // 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. @@ -58,12 +67,38 @@ var _ Message = (*CommitSig)(nil) // // This is part of the lnwire.Message interface. func (c *CommitSig) Decode(r io.Reader, pver uint32) error { - return ReadElements(r, + err := ReadElements(r, &c.ChanID, &c.CommitSig, &c.HtlcSigs, - &c.ExtraData, ) + if err != nil { + return err + } + + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var ( + partialSig PartialSigWithNonce + ) + typeMap, err := tlvRecords.ExtractRecords(&partialSig) + if err != nil { + return err + } + + // Set the corresponding TLV types if they were included in the stream. + if val, ok := typeMap[PartialSigWithNonceRecordType]; ok && val == nil { + c.PartialSig = &partialSig + } + + if len(tlvRecords) != 0 { + c.ExtraData = tlvRecords + } + + return nil } // Encode serializes the target CommitSig into the passed io.Writer @@ -71,6 +106,15 @@ func (c *CommitSig) Decode(r io.Reader, pver uint32) error { // // This is part of the lnwire.Message interface. func (c *CommitSig) Encode(w *bytes.Buffer, pver uint32) error { + recordProducers := make([]tlv.RecordProducer, 0, 1) + if c.PartialSig != nil { + recordProducers = append(recordProducers, c.PartialSig) + } + err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) + if err != nil { + return err + } + if err := WriteChannelID(w, c.ChanID); err != nil { return err } From 21d0bba73dc94429d119767cea18ec094ca45c9f Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:41:11 -0800 Subject: [PATCH 12/15] lnwire: add LocalNonce to RevokeAndAck --- lnwire/revoke_and_ack.go | 43 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/lnwire/revoke_and_ack.go b/lnwire/revoke_and_ack.go index bdc06d2fe43..6b6b801671c 100644 --- a/lnwire/revoke_and_ack.go +++ b/lnwire/revoke_and_ack.go @@ -5,6 +5,7 @@ import ( "io" "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightningnetwork/lnd/tlv" ) // RevokeAndAck is sent by either side once a CommitSig message has been @@ -32,6 +33,11 @@ type RevokeAndAck struct { // transaction. NextRevocationKey *btcec.PublicKey + // LocalNonce is the next _local_ nonce for the sending party. This + // allows the receiving party to propose a new commitment using their + // remote nonce and the sender's local nonce. + LocalNonce *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. @@ -54,12 +60,36 @@ var _ Message = (*RevokeAndAck)(nil) // // This is part of the lnwire.Message interface. func (c *RevokeAndAck) Decode(r io.Reader, pver uint32) error { - return ReadElements(r, + err := ReadElements(r, &c.ChanID, c.Revocation[:], &c.NextRevocationKey, - &c.ExtraData, ) + if err != nil { + return err + } + + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var musigNonce Musig2Nonce + typeMap, err := tlvRecords.ExtractRecords(&musigNonce) + if err != nil { + return err + } + + // Set the corresponding TLV types if they were included in the stream. + if val, ok := typeMap[NonceRecordType]; ok && val == nil { + c.LocalNonce = &musigNonce + } + + if len(tlvRecords) != 0 { + c.ExtraData = tlvRecords + } + + return nil } // Encode serializes the target RevokeAndAck into the passed io.Writer @@ -67,6 +97,15 @@ func (c *RevokeAndAck) Decode(r io.Reader, pver uint32) error { // // This is part of the lnwire.Message interface. func (c *RevokeAndAck) Encode(w *bytes.Buffer, pver uint32) error { + recordProducers := make([]tlv.RecordProducer, 0, 1) + if c.LocalNonce != nil { + recordProducers = append(recordProducers, c.LocalNonce) + } + err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) + if err != nil { + return err + } + if err := WriteChannelID(w, c.ChanID); err != nil { return err } From 4e8952dbed1555e7ee0bdc43f613a7746dbf4dcf Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:43:25 -0800 Subject: [PATCH 13/15] lnwire: add ShutdownNonce to Shutdown --- lnwire/closing_signed.go | 49 +++++++++++++++++++++- lnwire/shutdown.go | 88 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 134 insertions(+), 3 deletions(-) diff --git a/lnwire/closing_signed.go b/lnwire/closing_signed.go index 8e11c869934..3e3651964da 100644 --- a/lnwire/closing_signed.go +++ b/lnwire/closing_signed.go @@ -5,6 +5,7 @@ import ( "io" "github.com/btcsuite/btcd/btcutil" + "github.com/lightningnetwork/lnd/tlv" ) // ClosingSigned is sent by both parties to a channel once the channel is clear @@ -29,6 +30,14 @@ type ClosingSigned struct { // Signature is for the proposed channel close transaction. Signature Sig + // PartialSig is used to transmit a musig2 extended partial signature + // that signs the latest fee offer. The nonce isn't sent along side, as + // that has already been sent in the initial shutdown message. + // + // NOTE: This field is only populated if a musig2 taproot channel is + // being signed for. In this case, the above Sig type MUST be blank. + PartialSig *PartialSig + // 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. @@ -55,9 +64,36 @@ var _ Message = (*ClosingSigned)(nil) // // This is part of the lnwire.Message interface. func (c *ClosingSigned) Decode(r io.Reader, pver uint32) error { - return ReadElements( - r, &c.ChannelID, &c.FeeSatoshis, &c.Signature, &c.ExtraData, + err := ReadElements( + r, &c.ChannelID, &c.FeeSatoshis, &c.Signature, + ) + if err != nil { + return err + } + + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var ( + partialSig PartialSig ) + typeMap, err := tlvRecords.ExtractRecords(&partialSig) + if err != nil { + return err + } + + // Set the corresponding TLV types if they were included in the stream. + if val, ok := typeMap[PartialSigRecordType]; ok && val == nil { + c.PartialSig = &partialSig + } + + if len(tlvRecords) != 0 { + c.ExtraData = tlvRecords + } + + return nil } // Encode serializes the target ClosingSigned into the passed io.Writer @@ -65,6 +101,15 @@ func (c *ClosingSigned) Decode(r io.Reader, pver uint32) error { // // This is part of the lnwire.Message interface. func (c *ClosingSigned) Encode(w *bytes.Buffer, pver uint32) error { + recordProducers := make([]tlv.RecordProducer, 0, 1) + if c.PartialSig != nil { + recordProducers = append(recordProducers, c.PartialSig) + } + err := EncodeMessageExtraData(&c.ExtraData, recordProducers...) + if err != nil { + return err + } + if err := WriteChannelID(w, c.ChannelID); err != nil { return err } diff --git a/lnwire/shutdown.go b/lnwire/shutdown.go index 2adb6a082df..93cc50e3929 100644 --- a/lnwire/shutdown.go +++ b/lnwire/shutdown.go @@ -3,8 +3,56 @@ package lnwire import ( "bytes" "io" + + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/lightningnetwork/lnd/tlv" +) + +const ( + // ShutdownNonceRecordType is the type of the shutdown nonce TLV record. + ShutdownNonceRecordType = 8 ) +// ShutdownNonce is the type of the nonce we send during the shutdown flow. +// Unlike the other nonces, this nonce is symmetric w.r.t the message being +// signed (there's only one message for shutdown: the co-op close txn). +type ShutdownNonce Musig2Nonce + +// Record returns a TLV record that can be used to encode/decode the musig2 +// nonce from a given TLV stream. +func (s *ShutdownNonce) Record() tlv.Record { + return tlv.MakeStaticRecord( + ShutdownNonceRecordType, s, musig2.PubNonceSize, + shutdownNonceTypeEncoder, shutdownNonceTypeDecoder, + ) +} + +// shutdownNonceTypeEncoder is a custom TLV encoder for the Musig2Nonce type. +func shutdownNonceTypeEncoder(w io.Writer, val interface{}, + buf *[8]byte) error { + + if v, ok := val.(*ShutdownNonce); ok { + _, err := w.Write(v[:]) + return err + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.Musig2Nonce") +} + +// shutdownNonceTypeDecoder is a custom TLV decoder for the Musig2Nonce record. +func shutdownNonceTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, + l uint64) error { + + if v, ok := val.(*ShutdownNonce); ok { + _, err := io.ReadFull(r, v[:]) + return err + } + + return tlv.NewTypeForDecodingErr( + val, "lnwire.ShutdownNonce", l, musig2.PubNonceSize, + ) +} + // Shutdown is sent by either side in order to initiate the cooperative closure // of a channel. This message is sparse as both sides implicitly have the // information necessary to construct a transaction that will send the settled @@ -17,6 +65,10 @@ type Shutdown struct { // Address is the script to which the channel funds will be paid. Address DeliveryAddress + // ShutdownNonce is the nonce the sender will use to sign the first + // co-op sign offer. + ShutdownNonce *ShutdownNonce + // 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. @@ -40,7 +92,32 @@ var _ Message = (*Shutdown)(nil) // // This is part of the lnwire.Message interface. func (s *Shutdown) Decode(r io.Reader, pver uint32) error { - return ReadElements(r, &s.ChannelID, &s.Address, &s.ExtraData) + err := ReadElements(r, &s.ChannelID, &s.Address) + if err != nil { + return err + } + + var tlvRecords ExtraOpaqueData + if err := ReadElements(r, &tlvRecords); err != nil { + return err + } + + var musigNonce ShutdownNonce + typeMap, err := tlvRecords.ExtractRecords(&musigNonce) + if err != nil { + return err + } + + // Set the corresponding TLV types if they were included in the stream. + if val, ok := typeMap[ShutdownNonceRecordType]; ok && val == nil { + s.ShutdownNonce = &musigNonce + } + + if len(tlvRecords) != 0 { + s.ExtraData = tlvRecords + } + + return nil } // Encode serializes the target Shutdown into the passed io.Writer observing @@ -48,6 +125,15 @@ func (s *Shutdown) Decode(r io.Reader, pver uint32) error { // // This is part of the lnwire.Message interface. func (s *Shutdown) Encode(w *bytes.Buffer, pver uint32) error { + recordProducers := make([]tlv.RecordProducer, 0, 1) + if s.ShutdownNonce != nil { + recordProducers = append(recordProducers, s.ShutdownNonce) + } + err := EncodeMessageExtraData(&s.ExtraData, recordProducers...) + if err != nil { + return err + } + if err := WriteChannelID(w, s.ChannelID); err != nil { return err } From 5210891873b51d5a0509a72503fc6b60cf835481 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:43:59 -0800 Subject: [PATCH 14/15] lnwire: sort records in ExtractRecords This ensures that the caller doesn't need to worry about the TLV type ordering of the records the pass into the function. --- lnwire/extra_bytes.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lnwire/extra_bytes.go b/lnwire/extra_bytes.go index 17fd6df670a..200f313ca8a 100644 --- a/lnwire/extra_bytes.go +++ b/lnwire/extra_bytes.go @@ -90,6 +90,10 @@ func (e *ExtraOpaqueData) ExtractRecords(recordProducers ...tlv.RecordProducer) records = append(records, producer.Record()) } + // Ensure that the set of records are sorted before we attempt to + // decode from the stream, to ensure they're canonical. + tlv.SortRecords(records) + extraBytesReader := bytes.NewReader(*e) tlvStream, err := tlv.NewStream(records...) From c9b962e79fbacc1bd0d284ebc3565c7de07c4526 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 16 Jan 2023 19:44:17 -0800 Subject: [PATCH 15/15] lnwire: TestLightningWireProtocol quick check tests for taproot fields --- docs/release-notes/release-notes-0.17.0.md | 10 ++ lnwire/fuzz_test.go | 1 + lnwire/lnwire_test.go | 125 ++++++++++++++++++++- lnwire/signature.go | 2 +- lnwire/signature_test.go | 23 ++-- 5 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 docs/release-notes/release-notes-0.17.0.md diff --git a/docs/release-notes/release-notes-0.17.0.md b/docs/release-notes/release-notes-0.17.0.md new file mode 100644 index 00000000000..c77d38e1d61 --- /dev/null +++ b/docs/release-notes/release-notes-0.17.0.md @@ -0,0 +1,10 @@ +# Release Notes + +## BOLT Specs + +* The `lnwire` library is now able to [parse messages for the new experimental + taproot channels spec proposal](https://github.com/lightningnetwork/lnd/pull/7331). + +# Contributors (Alphabetical Order) + +* Olaoluwa Osuntokun diff --git a/lnwire/fuzz_test.go b/lnwire/fuzz_test.go index ac7aa150caa..9dfe85338f8 100644 --- a/lnwire/fuzz_test.go +++ b/lnwire/fuzz_test.go @@ -387,6 +387,7 @@ func FuzzNodeAnnouncement(f *testing.F) { first.Signature.RawBytes(), second.Signature.RawBytes(), ) { + shouldPanic = true } diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 96d0c8ae5ad..96951a4bc51 100644 --- a/lnwire/lnwire_test.go +++ b/lnwire/lnwire_test.go @@ -4,7 +4,9 @@ import ( "bytes" "encoding/binary" "encoding/hex" + "fmt" "image/color" + "io" "math" "math/rand" "net" @@ -39,6 +41,42 @@ var ( const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +func randLocalNonce(r *rand.Rand) *Musig2Nonce { + var nonce Musig2Nonce + _, _ = io.ReadFull(r, nonce[:]) + + return &nonce +} + +func randPartialSig(r *rand.Rand) (*PartialSig, error) { + var sigBytes [32]byte + if _, err := r.Read(sigBytes[:]); err != nil { + return nil, fmt.Errorf("unable to generate sig: %w", err) + } + + var s btcec.ModNScalar + s.SetByteSlice(sigBytes[:]) + + return &PartialSig{ + Sig: s, + }, nil +} + +func randPartialSigWithNonce(r *rand.Rand) (*PartialSigWithNonce, error) { + var sigBytes [32]byte + if _, err := r.Read(sigBytes[:]); err != nil { + return nil, fmt.Errorf("unable to generate sig: %w", err) + } + + var s btcec.ModNScalar + s.SetByteSlice(sigBytes[:]) + + return &PartialSigWithNonce{ + PartialSig: NewPartialSig(s), + Nonce: *randLocalNonce(r), + }, nil +} + func randAlias(r *rand.Rand) NodeAlias { var a NodeAlias for i := range a { @@ -438,6 +476,8 @@ func TestLightningWireProtocol(t *testing.T) { req.LeaseExpiry = new(LeaseExpiry) *req.LeaseExpiry = LeaseExpiry(1337) + + req.LocalNonce = randLocalNonce(r) } else { req.UpfrontShutdownScript = []byte{} } @@ -510,6 +550,8 @@ func TestLightningWireProtocol(t *testing.T) { req.LeaseExpiry = new(LeaseExpiry) *req.LeaseExpiry = LeaseExpiry(1337) + + req.LocalNonce = randLocalNonce(r) } else { req.UpfrontShutdownScript = []byte{} } @@ -544,6 +586,16 @@ func TestLightningWireProtocol(t *testing.T) { return } + // 1/2 chance to attach a partial sig. + if r.Intn(2) == 0 { + req.PartialSig, err = randPartialSigWithNonce(r) + if err != nil { + t.Fatalf("unable to generate sig: %v", + err) + return + } + } + v[0] = reflect.ValueOf(req) }, MsgFundingSigned: func(v []reflect.Value, r *rand.Rand) { @@ -564,6 +616,16 @@ func TestLightningWireProtocol(t *testing.T) { return } + // 1/2 chance to attach a partial sig. + if r.Intn(2) == 0 { + req.PartialSig, err = randPartialSigWithNonce(r) + if err != nil { + t.Fatalf("unable to generate sig: %v", + err) + return + } + } + v[0] = reflect.ValueOf(req) }, MsgFundingLocked: func(v []reflect.Value, r *rand.Rand) { @@ -582,8 +644,43 @@ func TestLightningWireProtocol(t *testing.T) { req := NewFundingLocked(ChannelID(c), pubKey) + if r.Int31()%2 == 0 { + scid := NewShortChanIDFromInt(uint64(r.Int63())) + req.AliasScid = &scid + req.NextLocalNonce = randLocalNonce(r) + } + v[0] = reflect.ValueOf(*req) }, + MsgShutdown: func(v []reflect.Value, r *rand.Rand) { + var c [32]byte + _, err := r.Read(c[:]) + if err != nil { + t.Fatalf("unable to generate chan id: %v", err) + return + } + + shutdownAddr, err := randDeliveryAddress(r) + if err != nil { + t.Fatalf("unable to generate delivery "+ + "address: %v", err) + return + } + + req := Shutdown{ + ChannelID: ChannelID(c), + Address: shutdownAddr, + ExtraData: make([]byte, 0), + } + + if r.Int31()%2 == 0 { + req.ShutdownNonce = (*ShutdownNonce)( + randLocalNonce(r), + ) + } + + v[0] = reflect.ValueOf(req) + }, MsgClosingSigned: func(v []reflect.Value, r *rand.Rand) { req := ClosingSigned{ FeeSatoshis: btcutil.Amount(r.Int63()), @@ -601,6 +698,15 @@ func TestLightningWireProtocol(t *testing.T) { return } + if r.Int31()%2 == 0 { + req.PartialSig, err = randPartialSig(r) + if err != nil { + t.Fatalf("unable to generate sig: %v", + err) + return + } + } + v[0] = reflect.ValueOf(req) }, MsgCommitSig: func(v []reflect.Value, r *rand.Rand) { @@ -620,7 +726,7 @@ func TestLightningWireProtocol(t *testing.T) { // Only create the slice if there will be any signatures // in it to prevent false positive test failures due to // an empty slice versus a nil slice. - numSigs := uint16(r.Int31n(1020)) + numSigs := uint16(r.Int31n(1019)) if numSigs > 0 { req.HtlcSigs = make([]Sig, numSigs) } @@ -632,6 +738,16 @@ func TestLightningWireProtocol(t *testing.T) { } } + // 50/50 chance to attach a partial sig. + if r.Int31()%2 == 0 { + req.PartialSig, err = randPartialSigWithNonce(r) + if err != nil { + t.Fatalf("unable to generate sig: %v", + err) + return + } + } + v[0] = reflect.ValueOf(*req) }, MsgRevokeAndAck: func(v []reflect.Value, r *rand.Rand) { @@ -651,6 +767,11 @@ func TestLightningWireProtocol(t *testing.T) { return } + // 50/50 chance to attach a local nonce. + if r.Int31()%2 == 0 { + req.LocalNonce = randLocalNonce(r) + } + v[0] = reflect.ValueOf(*req) }, MsgChannelAnnouncement: func(v []reflect.Value, r *rand.Rand) { @@ -871,6 +992,8 @@ func TestLightningWireProtocol(t *testing.T) { t.Fatalf("unable to generate key: %v", err) return } + + req.LocalNonce = randLocalNonce(r) } v[0] = reflect.ValueOf(req) diff --git a/lnwire/signature.go b/lnwire/signature.go index 57c27e4d19e..db6d59052f5 100644 --- a/lnwire/signature.go +++ b/lnwire/signature.go @@ -250,7 +250,7 @@ func (s *Sig) ToSignatureBytes() []byte { // For schnorr signatures, we can use the same internal 64 bytes. case sigTypeSchnorr: // We'll make a copy of the signature so we don't return a - // refrence into the raw slice. + // reference into the raw slice. var sig [64]byte copy(sig[:], s.bytes[:]) return sig[:] diff --git a/lnwire/signature_test.go b/lnwire/signature_test.go index eee5eb4a077..73263f1a667 100644 --- a/lnwire/signature_test.go +++ b/lnwire/signature_test.go @@ -26,7 +26,8 @@ func TestSignatureSerializeDeserialize(t *testing.T) { return err } - e2 := e2Input.(*ecdsa.Signature) + e2, ok := e2Input.(*ecdsa.Signature) + require.True(t, ok) if !e.IsEqual(e2) { return fmt.Errorf("pre/post-serialize sigs don't " + @@ -192,15 +193,19 @@ func TestNewSigFromRawSignature(t *testing.T) { expectedSig: Sig{ bytes: [64]byte{ // r value - 0x4e, 0x45, 0xe1, 0x69, 0x32, 0xb8, 0xaf, 0x51, - 0x49, 0x61, 0xa1, 0xd3, 0xa1, 0xa2, 0x5f, 0xdf, - 0x3f, 0x4f, 0x77, 0x32, 0xe9, 0xd6, 0x24, 0xc6, - 0xc6, 0x15, 0x48, 0xab, 0x5f, 0xb8, 0xcd, 0x41, + 0x4e, 0x45, 0xe1, 0x69, 0x32, 0xb8, + 0xaf, 0x51, 0x49, 0x61, 0xa1, 0xd3, + 0xa1, 0xa2, 0x5f, 0xdf, 0x3f, 0x4f, + 0x77, 0x32, 0xe9, 0xd6, 0x24, 0xc6, + 0xc6, 0x15, 0x48, 0xab, 0x5f, 0xb8, + 0xcd, 0x41, // s value - 0x18, 0x15, 0x22, 0xec, 0x8e, 0xca, 0x07, 0xde, - 0x48, 0x60, 0xa4, 0xac, 0xdd, 0x12, 0x90, 0x9d, - 0x83, 0x1c, 0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, - 0x08, 0x22, 0x21, 0xa8, 0x76, 0x8d, 0x1d, 0x09, + 0x18, 0x15, 0x22, 0xec, 0x8e, 0xca, + 0x07, 0xde, 0x48, 0x60, 0xa4, 0xac, + 0xdd, 0x12, 0x90, 0x9d, 0x83, 0x1c, + 0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, + 0x08, 0x22, 0x21, 0xa8, 0x76, 0x8d, + 0x1d, 0x09, }, }, },