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/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/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/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 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 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/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 } 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...) 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 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 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) 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 diff --git a/lnwire/fuzz_test.go b/lnwire/fuzz_test.go index 34d144c8497..9dfe85338f8 100644 --- a/lnwire/fuzz_test.go +++ b/lnwire/fuzz_test.go @@ -383,7 +383,11 @@ 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/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/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, + ) +} 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 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/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 } 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 } diff --git a/lnwire/signature.go b/lnwire/signature.go index f0bed72cb3f..db6d59052f5 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 + // reference 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..73263f1a667 100644 --- a/lnwire/signature_test.go +++ b/lnwire/signature_test.go @@ -21,11 +21,14 @@ func TestSignatureSerializeDeserialize(t *testing.T) { return err } - e2, err := sig.ToSignature() + e2Input, err := sig.ToSignature() if err != nil { return err } + e2, ok := e2Input.(*ecdsa.Signature) + require.True(t, ok) + if !e.IsEqual(e2) { return fmt.Errorf("pre/post-serialize sigs don't " + "match") @@ -188,16 +191,22 @@ 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 +275,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..ccdeabcf624 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,16 @@ func TestWriteSig(t *testing.T) { func TestWriteSigs(t *testing.T) { buf := new(bytes.Buffer) - sig1, sig2, sig3 := Sig{1}, Sig{2}, Sig{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. 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 }