diff --git a/.gitignore b/.gitignore index 157010fd932..8303df3003d 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,5 @@ coverage.txt # Release build directory (to avoid build.vcs.modified Golang build tag to be # set to true by having untracked files in the working directory). /lnd-*/ + +test_lnd* diff --git a/channeldb/channel.go b/channeldb/channel.go index 8c04ccdc226..175ef92668f 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -278,6 +278,10 @@ const ( // ScidAliasFeatureBit indicates that the scid-alias feature bit was // negotiated during the lifetime of this channel. ScidAliasFeatureBit ChannelType = 1 << 9 + + // SimpleTaprootFeatureBit indicates that the simple-taproot-channels + // feature bit was negotiated during the lifetime of the channel. + SimpleTaprootFeatureBit ChannelType = 1 << 10 ) // IsSingleFunder returns true if the channel type if one of the known single @@ -343,6 +347,11 @@ func (c ChannelType) HasScidAliasFeature() bool { return c&ScidAliasFeatureBit == ScidAliasFeatureBit } +// IsTaproot returns true if the channel is using taproot features. +func (c ChannelType) IsTaproot() bool { + return c&SimpleTaprootFeatureBit == SimpleTaprootFeatureBit +} + // ChannelConstraints represents a set of constraints meant to allow a node to // limit their exposure, enact flow control and ensure that all HTLCs are // economically relevant. This struct will be mirrored for both sides of the @@ -1315,11 +1324,11 @@ func (c *OpenChannel) SecondCommitmentPoint() (*btcec.PublicKey, error) { // commitment chains in the case of a last or only partially processed message. // When the remote party receives this message one of three things may happen: // -// 1. We're fully synced and no messages need to be sent. -// 2. We didn't get the last CommitSig message they sent, so they'll re-send -// it. -// 3. We didn't get the last RevokeAndAck message they sent, so they'll -// re-send it. +// 1. We're fully synced and no messages need to be sent. +// 2. We didn't get the last CommitSig message they sent, so they'll re-send +// it. +// 3. We didn't get the last RevokeAndAck message they sent, so they'll +// re-send it. // // If this is a restored channel, having status ChanStatusRestored, then we'll // modify our typical chan sync message to ensure they force close even if diff --git a/channeldb/graph.go b/channeldb/graph.go index 957c0b828d4..5107cccd17b 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -2724,7 +2724,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/cmd/lncli/cmd_open_channel.go b/cmd/lncli/cmd_open_channel.go index a6cd8ef68cb..325156beb2a 100644 --- a/cmd/lncli/cmd_open_channel.go +++ b/cmd/lncli/cmd_open_channel.go @@ -59,8 +59,9 @@ Signed base64 encoded PSBT or hex encoded raw wire TX (or path to text file): ` // of memory issues or other weird errors. psbtMaxFileSize = 1024 * 1024 - channelTypeTweakless = "tweakless" - channelTypeAnchors = "anchors" + channelTypeTweakless = "tweakless" + channelTypeAnchors = "anchors" + channelTypeSimpleTaproot = "taproot" ) // TODO(roasbeef): change default number of confirmations. @@ -207,8 +208,9 @@ var openChannelCommand = cli.Command{ cli.StringFlag{ Name: "channel_type", Usage: fmt.Sprintf("(optional) the type of channel to "+ - "propose to the remote peer (%q, %q)", - channelTypeTweakless, channelTypeAnchors), + "propose to the remote peer (%q, %q, %q)", + channelTypeTweakless, channelTypeAnchors, + channelTypeSimpleTaproot), }, cli.BoolFlag{ Name: "zero_conf", @@ -339,6 +341,8 @@ func openChannel(ctx *cli.Context) error { req.CommitmentType = lnrpc.CommitmentType_STATIC_REMOTE_KEY case channelTypeAnchors: req.CommitmentType = lnrpc.CommitmentType_ANCHORS + case channelTypeSimpleTaproot: + req.CommitmentType = lnrpc.CommitmentType_SIMPLE_TAPROOT default: return fmt.Errorf("unsupported channel type %v", channelType) } @@ -388,15 +392,16 @@ func openChannel(ctx *cli.Context) error { // protocol involves several steps between the RPC server and the CLI client: // // RPC server CLI client -// | | -// | |<------open channel (stream)-----| -// | |-------ready for funding----->| | -// | |<------PSBT verify------------| | -// | |-------ready for signing----->| | -// | |<------PSBT finalize----------| | -// | |-------channel pending------->| | -// | |-------channel open------------->| -// | | +// +// | | +// | |<------open channel (stream)-----| +// | |-------ready for funding----->| | +// | |<------PSBT verify------------| | +// | |-------ready for signing----->| | +// | |<------PSBT finalize----------| | +// | |-------channel pending------->| | +// | |-------channel open------------->| +// | | func openChannelPsbt(rpcCtx context.Context, ctx *cli.Context, client lnrpc.LightningClient, req *lnrpc.OpenChannelRequest) error { diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index 3939fef0bb4..8af4db90ba2 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -287,17 +287,32 @@ func (c *chainWatcher) Start() error { } } - localKey := chanState.LocalChanCfg.MultiSigKey.PubKey.SerializeCompressed() - remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey.SerializeCompressed() - multiSigScript, err := input.GenMultiSigScript( - localKey, remoteKey, + localKey := chanState.LocalChanCfg.MultiSigKey.PubKey + remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey + + var ( + pkScript []byte + err error ) - if err != nil { - return err - } - pkScript, err := input.WitnessScriptHash(multiSigScript) - if err != nil { - return err + if chanState.ChanType.IsTaproot() { + pkScript, _, err = input.GenTaprootFundingScript( + localKey, remoteKey, 0, + ) + if err != nil { + return err + } + } else { + multiSigScript, err := input.GenMultiSigScript( + localKey.SerializeCompressed(), + remoteKey.SerializeCompressed(), + ) + if err != nil { + return err + } + pkScript, err = input.WitnessScriptHash(multiSigScript) + if err != nil { + return err + } } spendNtfn, err := c.cfg.notifier.RegisterSpendNtfn( @@ -404,7 +419,7 @@ func (c *chainWatcher) handleUnknownLocalState( } remoteScript, _, err := lnwallet.CommitScriptToRemote( c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator, - commitKeyRing.ToRemoteKey, leaseExpiry, + commitKeyRing.ToRemoteKey, leaseExpiry, nil, ) if err != nil { return false, err @@ -833,8 +848,11 @@ func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail, } // Create an AnchorResolution for the breached state. + // + // TODO(roasbeef): make keyring for taproot chans to pass in instead of + // nil anchorRes, err := lnwallet.NewAnchorResolution( - c.cfg.chanState, commitSpend.SpendingTx, + c.cfg.chanState, commitSpend.SpendingTx, nil, ) if err != nil { return false, fmt.Errorf("unable to create anchor "+ diff --git a/discovery/gossiper.go b/discovery/gossiper.go index bceb3755c1e..b1cf9c3fc40 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -2049,25 +2049,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/feature/default_sets.go b/feature/default_sets.go index 1b1fd1f104a..cefa1c0bc42 100644 --- a/feature/default_sets.go +++ b/feature/default_sets.go @@ -83,4 +83,8 @@ var defaultSetDesc = setDesc{ SetInit: {}, // I SetNodeAnn: {}, // N }, + lnwire.SimpleTaprootChannelsOptional: { + SetInit: {}, // I + SetNodeAnn: {}, // N + }, } diff --git a/feature/deps.go b/feature/deps.go index 8e1d8ac095c..5b8ad964707 100644 --- a/feature/deps.go +++ b/feature/deps.go @@ -75,6 +75,9 @@ var deps = depDesc{ lnwire.ZeroConfOptional: { lnwire.ScidAliasOptional: {}, }, + lnwire.SimpleTaprootChannelsOptional: { + lnwire.ExplicitChannelTypeOptional: {}, + }, } // ValidateDeps asserts that a feature vector sets all features and their diff --git a/funding/commitment_type_negotiation.go b/funding/commitment_type_negotiation.go index 80d5be904a6..66d1012c92a 100644 --- a/funding/commitment_type_negotiation.go +++ b/funding/commitment_type_negotiation.go @@ -220,6 +220,51 @@ func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local, } return lnwallet.CommitmentTypeTweakless, nil + // Simple taproot channels only. + case channelFeatures.OnlyContains(lnwire.SimpleTaprootChannelsRequired): + + if !hasFeatures( + local, remote, lnwire.SimpleTaprootChannelsOptional, + ) { + return 0, errUnsupportedChannelType + } + + return lnwallet.CommitmentTypeSimpleTaproot, nil + + // Simple taproot channels with scid only. + case channelFeatures.OnlyContains( + lnwire.SimpleTaprootChannelsRequired, + lnwire.ScidAliasRequired, + ): + + if !hasFeatures( + local, remote, + lnwire.SimpleTaprootChannelsOptional, + lnwire.ScidAliasOptional, + ) { + return 0, errUnsupportedChannelType + } + + return lnwallet.CommitmentTypeSimpleTaproot, nil + + // Simple taproot channels with zero conf only. + case channelFeatures.OnlyContains( + lnwire.SimpleTaprootChannelsRequired, + lnwire.ScidAliasRequired, + lnwire.ZeroConfRequired, + ): + + if !hasFeatures( + local, remote, + lnwire.SimpleTaprootChannelsOptional, + lnwire.ScidAliasOptional, + lnwire.ZeroConfOptional, + ) { + return 0, errUnsupportedChannelType + } + + return lnwallet.CommitmentTypeSimpleTaproot, nil + // No features, use legacy commitment type. case channelFeatures.IsEmpty(): return lnwallet.CommitmentTypeLegacy, nil diff --git a/funding/manager.go b/funding/manager.go index d10872bf62f..8e3f5140ffc 100644 --- a/funding/manager.go +++ b/funding/manager.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" @@ -502,6 +503,17 @@ type Manager struct { nonceMtx sync.RWMutex chanIDNonce uint64 + // pendingMusigNonces is used to store the musig2 nonce we generate to + // send funding locked until we receive a funding locked message from + // the remote party. We'll use this to keep track of the nonce we + // generated, so we send the local+remote nonces to the peer state + // machine. + // + // NOTE: This map is protected by the nonceMtx above. + // + // TODO(roasbeef): replace w/ generic concurrent map + pendingMusigNonces map[lnwire.ChannelID]*musig2.Nonces + // activeReservations is a map which houses the state of all pending // funding workflows. activeReservations map[serializedPubKey]pendingChannels @@ -591,6 +603,7 @@ func NewFundingManager(cfg Config) (*Manager, error) { fundingRequests: make(chan *InitFundingMsg, msgBufferSize), localDiscoverySignals: make(map[lnwire.ChannelID]chan struct{}), handleFundingLockedBarriers: make(map[lnwire.ChannelID]struct{}), + pendingMusigNonces: make(map[lnwire.ChannelID]*musig2.Nonces), quit: make(chan struct{}), }, nil } @@ -1418,15 +1431,24 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, } } + public := msg.ChannelFlags&lnwire.FFAnnounceChannel != 0 + switch { // Sending the option-scid-alias channel type for a public channel is // disallowed. - public := msg.ChannelFlags&lnwire.FFAnnounceChannel != 0 - if public && scid { + case public && scid: err = fmt.Errorf("option-scid-alias chantype for public " + "channel") log.Error(err) f.failFundingFlow(peer, msg.PendingChannelID, err) return + + // The current variant of taproot channels can only be should only be + // used with unadvertised channels for now. + case commitType == lnwallet.CommitmentTypeSimpleTaproot && public: + err = fmt.Errorf("taproot channel type for public channel") + log.Error(err) + f.failFundingFlow(peer, msg.PendingChannelID, err) + return } req := &lnwallet.InitFundingReserveMsg{ @@ -1646,6 +1668,9 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, }, }, UpfrontShutdown: msg.UpfrontShutdownScript, + LocalNonce: &musig2.Nonces{ + PubNonce: *msg.LocalNonce, + }, } err = reservation.ProcessSingleContribution(remoteContribution) if err != nil { @@ -1659,9 +1684,17 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, log.Debugf("Remote party accepted commitment constraints: %v", spew.Sdump(remoteContribution.ChannelConfig.ChannelConstraints)) + ourContribution := reservation.OurContribution() + + var localNonce *lnwire.Musig2Nonce + if commitType.IsTaproot() { + localNonce = (*lnwire.Musig2Nonce)( + &ourContribution.LocalNonce.PubNonce, + ) + } + // With the initiator's contribution recorded, respond with our // contribution in the next message of the workflow. - ourContribution := reservation.OurContribution() fundingAccept := lnwire.AcceptChannel{ PendingChannelID: msg.PendingChannelID, DustLimit: ourContribution.DustLimit, @@ -1680,6 +1713,7 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer, UpfrontShutdownScript: ourContribution.UpfrontShutdown, ChannelType: chanTypeFeatureBits, LeaseExpiry: msg.LeaseExpiry, + LocalNonce: localNonce, } if err := peer.SendMessage(true, &fundingAccept); err != nil { @@ -1867,6 +1901,9 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer, }, }, UpfrontShutdown: msg.UpfrontShutdownScript, + LocalNonce: &musig2.Nonces{ + PubNonce: *msg.LocalNonce, + }, } err = resCtx.reservation.ProcessContribution(remoteContribution) @@ -2047,12 +2084,27 @@ func (f *Manager) continueFundingAccept(resCtx *reservationWithCtx, PendingChannelID: pendingChanID, FundingPoint: *outPoint, } - fundingCreated.CommitSig, err = lnwire.NewSigFromSignature(sig) - if err != nil { - log.Errorf("Unable to parse signature: %v", err) - f.failFundingFlow(resCtx.peer, pendingChanID, err) - return + + // If this is a taproot channel, then we'll need to populate the musig2 + // partial sig field instead of the regaulr commit sig field. + if resCtx.reservation.IsTaproot() { + partialSig, ok := sig.(*lnwallet.MusigPartialSig) + if !ok { + log.Errorf("expected musig partial sig, got %T", sig) + f.failFundingFlow(resCtx.peer, pendingChanID, err) + return + } + + fundingCreated.PartialSig = partialSig.ToWireSig() + } else { + fundingCreated.CommitSig, err = lnwire.NewSigFromSignature(sig) + if err != nil { + log.Errorf("Unable to parse signature: %v", err) + f.failFundingFlow(resCtx.peer, pendingChanID, err) + return + } } + if err := resCtx.peer.SendMessage(true, fundingCreated); err != nil { log.Errorf("Unable to send funding complete message: %v", err) f.failFundingFlow(resCtx.peer, pendingChanID, err) @@ -2086,11 +2138,21 @@ func (f *Manager) handleFundingCreated(peer lnpeer.Peer, log.Infof("completing pending_id(%x) with ChannelPoint(%v)", pendingChanID[:], fundingOut) - commitSig, err := msg.CommitSig.ToSignature() - if err != nil { - log.Errorf("unable to parse signature: %v", err) - f.failFundingFlow(peer, pendingChanID, err) - return + // For taproot channels, the commit signature is actually the partial + // signature. Otherwise, we can convert the ECDSA commit signature into + // our internal input.Signature type. + var commitSig input.Signature + if resCtx.reservation.IsTaproot() { + commitSig = new(lnwallet.MusigPartialSig).FromWireSig( + msg.PartialSig, + ) + } else { + commitSig, err = msg.CommitSig.ToSignature() + if err != nil { + log.Errorf("unable to parse signature: %v", err) + f.failFundingFlow(peer, pendingChanID, err) + return + } } // With all the necessary data available, attempt to advance the @@ -2152,21 +2214,34 @@ func (f *Manager) handleFundingCreated(peer lnpeer.Peer, log.Infof("sending FundingSigned for pending_id(%x) over "+ "ChannelPoint(%v)", pendingChanID[:], fundingOut) - // With their signature for our version of the commitment transaction - // verified, we can now send over our signature to the remote peer. - _, sig := resCtx.reservation.OurSignatures() - ourCommitSig, err := lnwire.NewSigFromSignature(sig) - if err != nil { - log.Errorf("unable to parse signature: %v", err) - f.failFundingFlow(peer, pendingChanID, err) - deleteFromDatabase() - return + fundingSigned := &lnwire.FundingSigned{ + ChanID: channelID, } - fundingSigned := &lnwire.FundingSigned{ - ChanID: channelID, - CommitSig: ourCommitSig, + // For taproot channels, we'll need to send over a partial signature + // that includes the nonce along sidethe signature. + _, sig := resCtx.reservation.OurSignatures() + if resCtx.reservation.IsTaproot() { + partialSig, ok := sig.(*lnwallet.MusigPartialSig) + if !ok { + log.Errorf("expected musig partial sig, got %T", sig) + f.failFundingFlow(resCtx.peer, pendingChanID, err) + return + } + + fundingSigned.PartialSig = partialSig.ToWireSig() + } else { + fundingSigned.CommitSig, err = lnwire.NewSigFromSignature(sig) + if err != nil { + log.Errorf("unable to parse signature: %v", err) + f.failFundingFlow(peer, pendingChanID, err) + deleteFromDatabase() + return + } } + + // With their signature for our version of the commitment transaction + // verified, we can now send over our signature to the remote peer. if err := peer.SendMessage(true, fundingSigned); err != nil { log.Errorf("unable to send FundingSigned message: %v", err) f.failFundingFlow(peer, pendingChanID, err) @@ -2255,14 +2330,29 @@ func (f *Manager) handleFundingSigned(peer lnpeer.Peer, f.localDiscoverySignals[permChanID] = make(chan struct{}) f.localDiscoveryMtx.Unlock() - // The remote peer has responded with a signature for our commitment - // transaction. We'll verify the signature for validity, then commit - // the state to disk as we can now open the channel. - commitSig, err := msg.CommitSig.ToSignature() - if err != nil { - log.Errorf("Unable to parse signature: %v", err) - f.failFundingFlow(peer, pendingChanID, err) - return + // If this is a taproot channel, then we'll need to force the + // schnorr encoding. + // + // TODO(roasbeef): remove after nonce ting + if resCtx.reservation.IsTaproot() { + msg.CommitSig.ForceSchnorr() + } + + // For taproot channels, the commit signature is actually the partial + // signature. Otherwise, we can convert the ECDSA commit signature into + // our internal input.Signature type. + var commitSig input.Signature + if resCtx.reservation.IsTaproot() { + commitSig = new(lnwallet.MusigPartialSig).FromWireSig( + msg.PartialSig, + ) + } else { + commitSig, err = msg.CommitSig.ToSignature() + if err != nil { + log.Errorf("unable to parse signature: %v", err) + f.failFundingFlow(peer, pendingChanID, err) + return + } } completeChan, err := resCtx.reservation.CompleteReservation( @@ -2487,15 +2577,29 @@ func (f *Manager) waitForFundingWithTimeout( // makeFundingScript re-creates the funding script for the funding transaction // of the target channel. func makeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) { - localKey := channel.LocalChanCfg.MultiSigKey.PubKey.SerializeCompressed() - remoteKey := channel.RemoteChanCfg.MultiSigKey.PubKey.SerializeCompressed() + localKey := channel.LocalChanCfg.MultiSigKey.PubKey + remoteKey := channel.RemoteChanCfg.MultiSigKey.PubKey - multiSigScript, err := input.GenMultiSigScript(localKey, remoteKey) - if err != nil { - return nil, err - } + if channel.ChanType.IsTaproot() { + pkScript, _, err := input.GenTaprootFundingScript( + localKey, remoteKey, int64(channel.Capacity), + ) + if err != nil { + return nil, err + } + + return pkScript, nil + } else { + multiSigScript, err := input.GenMultiSigScript( + localKey.SerializeCompressed(), + remoteKey.SerializeCompressed(), + ) + if err != nil { + return nil, err + } - return input.WitnessScriptHash(multiSigScript) + return input.WitnessScriptHash(multiSigScript) + } } // waitForFundingConfirmation handles the final stages of the channel funding @@ -2788,6 +2892,38 @@ func (f *Manager) sendFundingLocked(completeChan *channeldb.OpenChannel, } fundingLockedMsg := lnwire.NewFundingLocked(chanID, nextRevocation) + // If this is a taproot channel, then we also need to send along our + // set of musig2 nonces as well. + if completeChan.ChanType.IsTaproot() { + log.Infof("ChanID(%v): generating musig2 nonces...", + chanID) + + f.nonceMtx.Lock() + localNonce, ok := f.pendingMusigNonces[chanID] + if !ok { + // If we don't have any nonces generated yet for this + // first state, then we'll generate them now and stow + // them away. When we receive the funding locked + // message, we'll then pass along this same set of + // nonces. + newNonce, err := channel.GenMusigNonces() + if err != nil { + f.nonceMtx.Unlock() + return err + } + + // Now that we've generated the nonce for this channel, + // we'll store it in the set of pending nonces. + localNonce = newNonce + f.pendingMusigNonces[chanID] = localNonce + } + f.nonceMtx.Unlock() + + fundingLockedMsg.LocalNonce = (*lnwire.Musig2Nonce)( + &localNonce.PubNonce, + ) + } + // If the channel negotiated the option-scid-alias feature bit, we'll // send a TLV segment that includes an alias the peer can use in their // invoice hop hints. We'll send the first alias we find for the @@ -2977,6 +3113,7 @@ func (f *Manager) addToRouterGraph(completeChan *channeldb.OpenChannel, &completeChan.LocalChanCfg.MultiSigKey, completeChan.RemoteChanCfg.MultiSigKey.PubKey, *shortChanID, chanID, fwdMinHTLC, fwdMaxHTLC, ourPolicy, + completeChan.ChanType, ) if err != nil { return fmt.Errorf("error generating channel "+ @@ -3177,7 +3314,7 @@ func (f *Manager) annAfterSixConfs(completeChan *channeldb.OpenChannel, f.cfg.IDKey, completeChan.IdentityPub, &completeChan.LocalChanCfg.MultiSigKey, completeChan.RemoteChanCfg.MultiSigKey.PubKey, - *shortChanID, chanID, + *shortChanID, chanID, completeChan.ChanType, ) if err != nil { return fmt.Errorf("channel announcement failed: %v", err) @@ -3447,6 +3584,45 @@ func (f *Manager) handleFundingLocked(peer lnpeer.Peer, return } + // If this is a taproot channel, then we'll need to map the received + // nonces to a nonce pair, and also fetch our pending nonces, which are + // required in order to make the channel whole. + var chanOpts []lnwallet.ChannelOpt + if channel.ChanType.IsTaproot() { + f.nonceMtx.Lock() + localNonce, ok := f.pendingMusigNonces[chanID] + if !ok { + // If there's no pending nonce for this channel ID, + // then we'll generate one now. + verNonce, err := lnwallet.NewMusigVerificationNonce( + channel.LocalChanCfg.MultiSigKey.PubKey, + channel.LocalCommitment.CommitHeight, + channel.RevocationProducer, false, + ) + if err != nil { + f.nonceMtx.Unlock() + log.Error("unable to generate musig channel "+ + "nonces: %v", err) + return + } + + localNonce = verNonce + f.pendingMusigNonces[chanID] = localNonce + } + f.nonceMtx.Unlock() + + log.Infof("ChanID(%v): applying local+remote musig2 nonces", + chanID) + + chanOpts = append( + chanOpts, + lnwallet.WithLocalMusigNonces(localNonce), + lnwallet.WithRemoteMusigNonces(&musig2.Nonces{ + PubNonce: *msg.LocalNonce, + }), + ) + } + // Launch a defer so we _ensure_ that the channel barrier is properly // closed even if the target peer is no longer online at this point. defer func() { @@ -3464,7 +3640,11 @@ func (f *Manager) handleFundingLocked(peer lnpeer.Peer, f.barrierMtx.Unlock() }() - if err := peer.AddNewChannel(channel, f.quit); err != nil { + err = peer.AddNewChannel(&lnpeer.NewChannel{ + OpenChannel: channel, + ChanOpts: chanOpts, + }, f.quit) + if err != nil { log.Errorf("Unable to add new channel %v with peer %x: %v", channel.FundingOutpoint, peer.IdentityKey().SerializeCompressed(), err, @@ -3491,9 +3671,9 @@ type chanAnnouncement struct { func (f *Manager) newChanAnnouncement(localPubKey, remotePubKey *btcec.PublicKey, localFundingKey *keychain.KeyDescriptor, remoteFundingKey *btcec.PublicKey, shortChanID lnwire.ShortChannelID, - chanID lnwire.ChannelID, fwdMinHTLC, - fwdMaxHTLC lnwire.MilliSatoshi, - ourPolicy *channeldb.ChannelEdgePolicy) (*chanAnnouncement, error) { + chanID lnwire.ChannelID, fwdMinHTLC, fwdMaxHTLC lnwire.MilliSatoshi, + ourPolicy *channeldb.ChannelEdgePolicy, + chanType channeldb.ChannelType) (*chanAnnouncement, error) { chainHash := *f.cfg.Wallet.Cfg.NetParams.GenesisHash @@ -3506,6 +3686,18 @@ func (f *Manager) newChanAnnouncement(localPubKey, ChainHash: chainHash, } + // If this is a taproot channel, then we'll set a special bit in the + // feature vector to indicate to the routing layer that this needs a + // slightly different type of validation. + // + // TODO(roasbeef): temp, remove after gossip 1.5 + if chanType.IsTaproot() { + log.Debugf("Applying taproot feature bit to "+ + "ChannelAnnouncement for %v", chanID) + + chanAnn.Features.Set(lnwire.SimpleTaprootChannelsRequired) + } + // The chanFlags field indicates which directed edge of the channel is // being updated within the ChannelUpdateAnnouncement announcement // below. A value of zero means it's the edge of the "first" node and 1 @@ -3652,7 +3844,7 @@ func (f *Manager) newChanAnnouncement(localPubKey, func (f *Manager) announceChannel(localIDKey, remoteIDKey *btcec.PublicKey, localFundingKey *keychain.KeyDescriptor, remoteFundingKey *btcec.PublicKey, shortChanID lnwire.ShortChannelID, - chanID lnwire.ChannelID) error { + chanID lnwire.ChannelID, chanType channeldb.ChannelType) error { // First, we'll create the batch of announcements to be sent upon // initial channel creation. This includes the channel announcement @@ -3663,7 +3855,7 @@ func (f *Manager) announceChannel(localIDKey, remoteIDKey *btcec.PublicKey, // only use the channel announcement message from the returned struct. ann, err := f.newChanAnnouncement(localIDKey, remoteIDKey, localFundingKey, remoteFundingKey, shortChanID, chanID, - 0, 0, nil, + 0, 0, nil, chanType, ) if err != nil { log.Errorf("can't generate channel announcement: %v", err) @@ -4037,6 +4229,13 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { log.Infof("Starting funding workflow with %v for pending_id(%x), "+ "committype=%v", msg.Peer.Address(), chanID, commitType) + var localNonce *lnwire.Musig2Nonce + if commitType.IsTaproot() { + localNonce = (*lnwire.Musig2Nonce)( + &ourContribution.LocalNonce.PubNonce, + ) + } + fundingOpen := lnwire.OpenChannel{ ChainHash: *f.cfg.Wallet.Cfg.NetParams.GenesisHash, PendingChannelID: chanID, @@ -4059,6 +4258,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) { UpfrontShutdownScript: shutdown, ChannelType: chanType, LeaseExpiry: leaseExpiry, + LocalNonce: localNonce, } if err := msg.Peer.SendMessage(true, &fundingOpen); err != nil { e := fmt.Errorf("unable to send funding request message: %v", diff --git a/go.mod b/go.mod index 79c7fdd46c7..6054c5f2bcc 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ require ( github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82 github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 github.com/btcsuite/btcd v0.23.1 - github.com/btcsuite/btcd/btcec/v2 v2.2.1 + github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/btcsuite/btcd/btcutil v1.1.2 github.com/btcsuite/btcd/btcutil/psbt v1.1.5 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 @@ -44,7 +44,7 @@ require ( github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 github.com/miekg/dns v1.1.43 github.com/prometheus/client_golang v1.11.0 - github.com/stretchr/testify v1.7.1 + github.com/stretchr/testify v1.8.0 github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 github.com/urfave/cli v1.22.9 go.etcd.io/etcd/client/pkg/v3 v3.5.0 @@ -119,7 +119,7 @@ require ( github.com/sirupsen/logrus v1.7.0 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.2.0 // indirect + github.com/stretchr/objx v0.4.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/ulikunitz/xz v0.5.10 // indirect @@ -153,7 +153,7 @@ require ( gopkg.in/errgo.v1 v1.0.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.2.0 // indirect ) diff --git a/go.sum b/go.sum index ab09cd58a9f..135a3ff6452 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/btcsuite/btcd v0.23.1/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZg github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcec/v2 v2.2.1 h1:xP60mv8fvp+0khmrN0zTdPC3cNm24rfeE6lh2R/Yv3E= -github.com/btcsuite/btcd/btcec/v2 v2.2.1/go.mod h1:9/CSmJxmuvqzX9Wh2fXMWToLOHhPd11lSPuIupwTkI8= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34= @@ -634,16 +634,18 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= @@ -1124,8 +1126,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/htlcswitch/link.go b/htlcswitch/link.go index f2525ba86e3..c6b9c8e3a8a 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -9,6 +9,7 @@ import ( "sync/atomic" "time" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog" @@ -693,6 +694,21 @@ func (l *channelLink) syncChanStates() error { "ChannelPoint(%v)", l.channel.ChannelPoint()) } + // If this is a tarpoot channel, then in addition to the normal reest + // message, we'll also send our local+remote nonces as well. + // + // TODO(roasbeef): move into ChanSyncMsg + if chanState.ChanType.IsTaproot() { + localNonce, err := l.channel.GenMusigNonces() + if err != nil { + return fmt.Errorf("unable to generate nonce "+ + "pair for chan: %w", err) + } + localChanSyncMsg.LocalNonce = (*lnwire.Musig2Nonce)( + &localNonce.PubNonce, + ) + } + if err := l.cfg.Peer.SendMessage(true, localChanSyncMsg); err != nil { return fmt.Errorf("unable to send chan sync message for "+ "ChannelPoint(%v): %v", l.channel.ChannelPoint(), err) @@ -760,6 +776,22 @@ func (l *channelLink) syncChanStates() error { } } + // Before we process the ChanSync message, if this is a taproot + // channel, then we'll init our musig2 nonces state. + if chanState.ChanType.IsTaproot() { + l.log.Infof("initializing musig2 nonces") + + syncMsg := remoteChanSyncMsg + remoteNonce := &musig2.Nonces{ + PubNonce: *syncMsg.LocalNonce, + } + err := l.channel.InitRemoteMusigNonces(remoteNonce) + if err != nil { + return fmt.Errorf("unable to init musig2 "+ + "nonces: %w", err) + } + } + // In any case, we'll then process their ChanSync message. l.log.Info("received re-establishment message from remote side") @@ -1891,7 +1923,11 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // We just received a new updates to our local commitment // chain, validate this new commitment, closing the link if // invalid. - err = l.channel.ReceiveNewCommitment(msg.CommitSig, msg.HtlcSigs) + err = l.channel.ReceiveNewCommitment(&lnwallet.CommitSigs{ + CommitSig: msg.CommitSig, + HtlcSigs: msg.HtlcSigs, + PartialSig: msg.PartialSig, + }) if err != nil { // If we were unable to reconstruct their proposed // commitment, then we'll examine the type of error. If @@ -2205,7 +2241,7 @@ func (l *channelLink) updateCommitTx() error { return nil } - theirCommitSig, htlcSigs, pendingHTLCs, err := l.channel.SignNextCommitment() + newCommit, err := l.channel.SignNextCommitment() if err == lnwallet.ErrNoWindow { l.cfg.PendingCommitTicker.Resume() l.log.Trace("PendingCommitTicker resumed") @@ -2237,7 +2273,7 @@ func (l *channelLink) updateCommitTx() error { // pending). newUpdate := &contractcourt.ContractUpdate{ HtlcKey: contractcourt.RemotePendingHtlcSet, - Htlcs: pendingHTLCs, + Htlcs: newCommit.PendingHTLCs, } err = l.cfg.NotifyContractUpdate(newUpdate) if err != nil { @@ -2252,9 +2288,10 @@ func (l *channelLink) updateCommitTx() error { } commitSig := &lnwire.CommitSig{ - ChanID: l.ChanID(), - CommitSig: theirCommitSig, - HtlcSigs: htlcSigs, + ChanID: l.ChanID(), + CommitSig: newCommit.CommitSig, + HtlcSigs: newCommit.HtlcSigs, + PartialSig: newCommit.PartialSig, } l.cfg.Peer.SendMessage(false, commitSig) diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index b209e2c514a..f48d8249ea9 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -639,7 +639,7 @@ func (s *mockServer) Address() net.Addr { return nil } -func (s *mockServer) AddNewChannel(channel *channeldb.OpenChannel, +func (s *mockServer) AddNewChannel(channel *lnpeer.NewChannel, cancel <-chan struct{}) error { return nil diff --git a/input/musig2.go b/input/musig2.go index 2fc689d7745..4bcc099de63 100644 --- a/input/musig2.go +++ b/input/musig2.go @@ -31,9 +31,16 @@ type MuSig2Signer interface { // public key of the local signing key. If nonces of other parties are // already known, they can be submitted as well to reduce the number of // method calls necessary later on. + // + // The set of sessionOpts are _optional_ and allow a caller to modify the + // generated sessions. As an example the local nonce might already be generated + // ahead of time. MuSig2CreateSession(keychain.KeyLocator, []*btcec.PublicKey, - *MuSig2Tweaks, [][musig2.PubNonceSize]byte) (*MuSig2SessionInfo, - error) + *MuSig2Tweaks, [][musig2.PubNonceSize]byte, + ...musig2.SessionOption) (*MuSig2SessionInfo, error) + + // TODO(roasbeef): need to make the sparse one here? + // * don't have any info but want a session // MuSig2RegisterNonces registers one or more public nonces of other // signing participants for a session identified by its ID. This method @@ -76,6 +83,8 @@ type MuSig2SessionInfo struct { // CombinedKey is the combined public key with all tweaks applied to it. CombinedKey *btcec.PublicKey + // TODO(roasbeef): also add combined nonce + // TaprootTweak indicates whether a taproot tweak (BIP-0086 or script // path) was used. The TaprootInternalKey will only be set if this is // set to true. diff --git a/input/script_utils.go b/input/script_utils.go index e12b57c43af..329d49dac66 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -6,6 +6,8 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -141,6 +143,40 @@ func GenFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, erro return witnessScript, wire.NewTxOut(amt, pkScript), nil } +// GenTaprootFundingScript constructs the taproot-native funding output that +// uses musig2 to create a single aggregated key to anchor the channel. +func GenTaprootFundingScript(aPub, bPub *btcec.PublicKey, + amt int64) ([]byte, *wire.TxOut, error) { + + // Similar to the existing p2wsh funding script, we'll always make sure + // we sort the keys before any major operations. In order to ensure + // that there's no other way this output can be spent, we'll use a BIP + // 86 tweak here during aggregation. + // + // TODO(roasbeef): revisit if BIP 86 is needed here? + combinedKey, _, _, err := musig2.AggregateKeys( + []*btcec.PublicKey{aPub, bPub}, true, + musig2.WithBIP86KeyTweak(), + ) + if err != nil { + return nil, nil, fmt.Errorf("unable to combine keys: %w", err) + } + + // Now that we have the combined key, we can create a taproot pkScript + // from this, and then make the txout given the amount. + pkScript, err := PayToTaprootScript(combinedKey.FinalKey) + if err != nil { + return nil, nil, fmt.Errorf("unable to make taproot "+ + "pkscript: %w", err) + } + + txOut := wire.NewTxOut(amt, pkScript) + + // For the "witness program" we just return the raw pkScript since the + // output we create can _only_ be spent with a musig2 signature. + return pkScript, txOut, nil +} + // SpendMultiSig generates the witness stack required to redeem the 2-of-2 p2wsh // multi-sig output. func SpendMultiSig(witnessScript, pubA []byte, sigA Signature, @@ -203,35 +239,40 @@ func Ripemd160H(d []byte) []byte { // output payment for the sender's version of the commitment transaction. The // possible script paths from this output include: // -// * The sender timing out the HTLC using the second level HTLC timeout -// transaction. -// * The receiver of the HTLC claiming the output on-chain with the payment -// preimage. -// * The receiver of the HTLC sweeping all the funds in the case that a -// revoked commitment transaction bearing this HTLC was broadcast. +// - The sender timing out the HTLC using the second level HTLC timeout +// transaction. +// - The receiver of the HTLC claiming the output on-chain with the payment +// preimage. +// - The receiver of the HTLC sweeping all the funds in the case that a +// revoked commitment transaction bearing this HTLC was broadcast. // // If confirmedSpend=true, a 1 OP_CSV check will be added to the non-revocation // cases, to allow sweeping only after confirmation. // // Possible Input Scripts: -// SENDR: <0> <0> (spend using HTLC timeout transaction) -// RECVR: -// REVOK: -// * receiver revoke +// +// SENDR: <0> <0> (spend using HTLC timeout transaction) +// RECVR: +// REVOK: +// * receiver revoke // // OP_DUP OP_HASH160 OP_EQUAL // OP_IF -// OP_CHECKSIG +// +// OP_CHECKSIG +// // OP_ELSE -// -// OP_SWAP OP_SIZE 32 OP_EQUAL -// OP_NOTIF -// OP_DROP 2 OP_SWAP 2 OP_CHECKMULTISIG -// OP_ELSE -// OP_HASH160 OP_EQUALVERIFY -// OP_CHECKSIG -// OP_ENDIF -// [1 OP_CHECKSEQUENCEVERIFY OP_DROP] <- if allowing confirmed spend only. +// +// +// OP_SWAP OP_SIZE 32 OP_EQUAL +// OP_NOTIF +// OP_DROP 2 OP_SWAP 2 OP_CHECKMULTISIG +// OP_ELSE +// OP_HASH160 OP_EQUALVERIFY +// OP_CHECKSIG +// OP_ENDIF +// [1 OP_CHECKSEQUENCEVERIFY OP_DROP] <- if allowing confirmed spend only. +// // OP_ENDIF func SenderHTLCScript(senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey, paymentHash []byte, @@ -439,39 +480,292 @@ func SenderHtlcSpendTimeout(receiverSig Signature, return witnessStack, nil } +// SenderHTLCTapLeafTimeout returns the full tapscript leaf for the timeout +// path of the sender HTLC. This is a small script that allows the sender to +// timeout the HTLC after a period of time: +// +// OP_CHECKSIGVERIFY +// OP_CHECKSIG +func SenderHTLCTapLeafTimeout(senderHtlcKey, + receiverHtlcKey *btcec.PublicKey) (txscript.TapLeaf, error) { + + builder := txscript.NewScriptBuilder() + + builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIG) + + timeoutLeafScript, err := builder.Script() + if err != nil { + return txscript.TapLeaf{}, err + } + + return txscript.NewBaseTapLeaf(timeoutLeafScript), nil +} + +// SenderHTLCTapLeafSuccess returns the full tapscript leaf for the success +// path of the sender HTLC. This is a small script that allows the sender to +// redeem the HTLC with a pre-image: +// +// OP_SIZE 32 OP_EQUALVERIFY OP_HASH160 +// OP_EQUALVERIFY +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY +func SenderHTLCTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, + paymentHash []byte) (txscript.TapLeaf, error) { + + builder := txscript.NewScriptBuilder() + + // Check that the pre-image is 32 bytes as required. + builder.AddOp(txscript.OP_SIZE) + builder.AddInt64(32) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // Check that the specified pre-images matches what we hard code into + // the script. + builder.AddOp(txscript.OP_HASH160) + builder.AddData(Ripemd160H(paymentHash)) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // Verify the remote party's signature, then make them wait 1 block + // after confirmation to properly sweep. + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIG) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + successLeafScript, err := builder.Script() + if err != nil { + return txscript.TapLeaf{}, err + } + + return txscript.NewBaseTapLeaf(successLeafScript), nil +} + +// HtlcScriptTree... +type HtlcScriptTree struct { + // TaprootKey... + TaprootKey *btcec.PublicKey + + // SuccessTapLeaf... + SuccessTapLeaf txscript.TapLeaf + + // TimeoutTapLeaf... + TimeoutTapLeaf txscript.TapLeaf + + // TapscriptTree... + TapscriptTree *txscript.IndexedTapScriptTree +} + +// senderHtlcTapScriptTree builds the tapscript tree which is used to anchor +// the HTLC key for HTLCs on the sender's commitment. +func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, + revokeKey *btcec.PublicKey, payHash []byte) (*HtlcScriptTree, error) { + + // First, we'll obtain the tap leaves for both the success and timeout + // path. + successTapLeaf, err := SenderHTLCTapLeafSuccess( + receiverHtlcKey, payHash, + ) + if err != nil { + return nil, err + } + timeoutTapLeaf, err := SenderHTLCTapLeafTimeout( + senderHtlcKey, receiverHtlcKey, + ) + if err != nil { + return nil, err + } + + // With the two leaves obtained, we'll now make the tapscript tree, + // then obtain the root from that + tapscriptTree := txscript.AssembleTaprootScriptTree( + successTapLeaf, timeoutTapLeaf, + ) + + tapScriptRoot := tapscriptTree.RootNode.TapHash() + + // With the tapscript root obtained, we'll tweak the revocation key + // with this value to obtain the key that HTLCs will be sent to. + htlcKey := txscript.ComputeTaprootOutputKey( + revokeKey, tapScriptRoot[:], + ) + + return &HtlcScriptTree{ + TaprootKey: htlcKey, + SuccessTapLeaf: successTapLeaf, + TimeoutTapLeaf: timeoutTapLeaf, + TapscriptTree: tapscriptTree, + }, nil +} + +// SenderHTLCScriptTaproot constructs the taproot witness program (schnorr key) +// for an outgoing HTLC on the sender's version of the commitment transaction. +// This method returns the top level tweaked public key that commits to both +// the script paths. +// +// The returned key commits to a tapscript tree with two possible paths: +// +// - Timeout path: +// OP_CHECKSIGVERIFY +// OP_CHECKSIG +// +// - Success path: +// OP_SIZE 32 OP_EQUALVERIFY +// OP_HASH160 OP_EQUALVERIFY +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY +// +// The timeout path can be spent with a witness of (sender timeout): +// +// +// +// The success path can be spent with a valid control block, and a witness of +// (receiver redeem): +// +// +// +// The top level keyspend key is the revocation key, which allows a defender to +// unilaterally spend the created output. +func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey, + revokeKey *btcec.PublicKey, payHash []byte) (*HtlcScriptTree, error) { + + // Given all the necessary parameters, we'll return the HTLC script + // tree that includes the top level output script, as well as the two + // tap leaf paths. + return senderHtlcTapScriptTree( + senderHtlcKey, receiverHtlcKey, revokeKey, payHash, + ) +} + +// SenderHTLCScriptTaprootRedeem creates a valid witness needed to redeem a +// sender taproot HTLC with the pre-image. The returned witness is valid and +// includes the control block required to spend the output. +func SenderHTLCScriptTaprootRedeem(signer Signer, signDesc *SignDescriptor, + sweepTx *wire.MsgTx, preimage []byte, revokeKey *btcec.PublicKey, + tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { + + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) + if err != nil { + return nil, err + } + + // In addition to the signature and the witness/leaf script, we also + // need to make a control block proof using the tapscript tree. + successTapLeafHash := txscript.NewBaseTapLeaf( + signDesc.WitnessScript, + ).TapHash() + successIdx := tapscriptTree.LeafProofIndex[successTapLeafHash] + successMerkleProof := tapscriptTree.LeafMerkleProofs[successIdx] + successControlBlock := successMerkleProof.ToControlBlock(revokeKey) + + // The final witness stack is: + // + witnessStack := make(wire.TxWitness, 4) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + witnessStack[1] = preimage + witnessStack[2] = signDesc.WitnessScript + witnessStack[4], err = successControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return witnessStack, nil +} + +// SenderHTLCScriptTaprootTimeout creates a valid witness needed to timeout an +// HTLC on the sender's commitment transaction. The returned witness is valid and +// includes the control block required to spend the output. +func SenderHTLCScriptTaprootTimeout(receiverSig Signature, + receiverSigHash txscript.SigHashType, signer Signer, + signDesc *SignDescriptor, htlcTimeoutTx *wire.MsgTx, + revokeKey *btcec.PublicKey, + tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { + + sweepSig, err := signer.SignOutputRaw(htlcTimeoutTx, signDesc) + if err != nil { + return nil, err + } + + // With the sweep signature obtained, we'll obtain the control block + // proof needed to perform a valid spend for the timeout path. + timeoutTapLeafHash := txscript.NewBaseTapLeaf( + signDesc.WitnessScript, + ).TapHash() + timeoutIdx := tapscriptTree.LeafProofIndex[timeoutTapLeafHash] + timeoutMerkleProof := tapscriptTree.LeafMerkleProofs[timeoutIdx] + timeoutControlBlock := timeoutMerkleProof.ToControlBlock(revokeKey) + + // The final witness stack is: + // + witnessStack := make(wire.TxWitness, 4) + witnessStack[0] = append(receiverSig.Serialize(), byte(receiverSigHash)) + witnessStack[1] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + witnessStack[2] = signDesc.WitnessScript + witnessStack[3], err = timeoutControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return witnessStack, nil +} + +// SenderHTLCScriptTaprootRevoke creates a valid witness needed to spend the +// revocation path of the HTLC. This uses a plain keyspend using the specified +// revocation key. +func SenderHTLCScriptTaprootRevoke(signer Signer, signDesc *SignDescriptor, + sweepTx *wire.MsgTx) (wire.TxWitness, error) { + + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) + if err != nil { + return nil, err + } + + // The witness stack in this case is pretty simple: we only need to + // specify the signature generated. + witnessStack := make(wire.TxWitness, 1) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + + return witnessStack, nil +} + // ReceiverHTLCScript constructs the public key script for an incoming HTLC // output payment for the receiver's version of the commitment transaction. The // possible execution paths from this script include: -// * The receiver of the HTLC uses its second level HTLC transaction to +// - The receiver of the HTLC uses its second level HTLC transaction to // advance the state of the HTLC into the delay+claim state. -// * The sender of the HTLC sweeps all the funds of the HTLC as a breached +// - The sender of the HTLC sweeps all the funds of the HTLC as a breached // commitment was broadcast. -// * The sender of the HTLC sweeps the HTLC on-chain after the timeout period +// - The sender of the HTLC sweeps the HTLC on-chain after the timeout period // of the HTLC has passed. // // If confirmedSpend=true, a 1 OP_CSV check will be added to the non-revocation // cases, to allow sweeping only after confirmation. // // Possible Input Scripts: -// RECVR: <0> (spend using HTLC success transaction) -// REVOK: -// SENDR: 0 // +// RECVR: <0> (spend using HTLC success transaction) +// REVOK: +// SENDR: 0 // // OP_DUP OP_HASH160 OP_EQUAL // OP_IF -// OP_CHECKSIG +// +// OP_CHECKSIG +// // OP_ELSE -// -// OP_SWAP OP_SIZE 32 OP_EQUAL -// OP_IF -// OP_HASH160 OP_EQUALVERIFY -// 2 OP_SWAP 2 OP_CHECKMULTISIG -// OP_ELSE -// OP_DROP OP_CHECKLOCKTIMEVERIFY OP_DROP -// OP_CHECKSIG -// OP_ENDIF -// [1 OP_CHECKSEQUENCEVERIFY OP_DROP] <- if allowing confirmed spend only. +// +// +// OP_SWAP OP_SIZE 32 OP_EQUAL +// OP_IF +// OP_HASH160 OP_EQUALVERIFY +// 2 OP_SWAP 2 OP_CHECKMULTISIG +// OP_ELSE +// OP_DROP OP_CHECKLOCKTIMEVERIFY OP_DROP +// OP_CHECKSIG +// OP_ENDIF +// [1 OP_CHECKSEQUENCEVERIFY OP_DROP] <- if allowing confirmed spend only. +// // OP_ENDIF func ReceiverHTLCScript(cltvExpiry uint32, senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey, @@ -708,32 +1002,285 @@ func ReceiverHtlcSpendTimeout(signer Signer, signDesc *SignDescriptor, return witnessStack, nil } +// ReceiverHtlcTapLeafTimeout returns the full tapscript leaf for the timeout +// path of the sender HTLC. This is a small script that allows the sender +// timeout the HTLC after expiry: +// +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY +// OP_CHECKLOCKTIMEVERIFY OP_DROP +func ReceiverHtlcTapLeafTimeout(receiverHtlcKey *btcec.PublicKey, + cltvExpiry uint32) (txscript.TapLeaf, error) { + + builder := txscript.NewScriptBuilder() + + // The first part of the script will verify a signature from the + // receiver authorizing the spend. + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIG) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + // The second portion will ensure that the CLTV expiry on the spending + // transaction is correct. + builder.AddInt64(int64(cltvExpiry)) + builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) + builder.AddOp(txscript.OP_DROP) + + timeoutLeafScript, err := builder.Script() + if err != nil { + return txscript.TapLeaf{}, nil + } + + return txscript.NewBaseTapLeaf(timeoutLeafScript), nil +} + +// ReceiverHtlcTapLeafSuccess returns the full tapscript leaf for the success +// path for an HTLC on the receiver's commitment transaction. This script +// allows the receiver to redeem an HTLC with knowledge of the preimage: +// +// OP_SIZE 32 OP_EQUALVERIFY OP_HASH160 +// OP_EQUALVERIFY +// OP_CHECKSIGVERIFY +// OP_CHECKSIG +func ReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, + senderHtlcKey *btcec.PublicKey, + paymentHash []byte) (txscript.TapLeaf, error) { + + builder := txscript.NewScriptBuilder() + + // Check that the pre-image is 32 bytes as required. + builder.AddOp(txscript.OP_SIZE) + builder.AddInt64(32) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // Check that the specified pre-images matches what we hard code into + // the script. + builder.AddOp(txscript.OP_HASH160) + builder.AddData(Ripemd160H(paymentHash)) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // Verify the "2-of-2" multi-sig that requires both parties to sign + // off. + builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIG) + + successLeafScript, err := builder.Script() + if err != nil { + return txscript.TapLeaf{}, err + } + + return txscript.NewBaseTapLeaf(successLeafScript), nil +} + +// receiverHtlcTapScriptTree builds the tapscript tree which is used to anchor +// the HTLC key for HTLCs on the receiver's commitment. +func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, + revokeKey *btcec.PublicKey, payHash []byte, + cltvExpiry uint32) (*HtlcScriptTree, error) { + + // First, we'll obtain the tap leaves for both the success and timeout + // path. + successTapLeaf, err := ReceiverHtlcTapLeafSuccess( + receiverHtlcKey, senderHtlcKey, payHash, + ) + if err != nil { + return nil, err + } + timeoutTapLeaf, err := ReceiverHtlcTapLeafTimeout( + receiverHtlcKey, cltvExpiry, + ) + if err != nil { + return nil, err + } + + // With the two leaves obtained, we'll now make the tapscript tree, + // then obtain the root from that + tapscriptTree := txscript.AssembleTaprootScriptTree( + successTapLeaf, timeoutTapLeaf, + ) + + tapScriptRoot := tapscriptTree.RootNode.TapHash() + + // With the tapscript root obtained, we'll tweak the revocation key + // with this value to obtain the key that HTLCs will be sent to. + htlcKey := txscript.ComputeTaprootOutputKey( + revokeKey, tapScriptRoot[:], + ) + + return &HtlcScriptTree{ + TaprootKey: htlcKey, + SuccessTapLeaf: successTapLeaf, + TimeoutTapLeaf: timeoutTapLeaf, + TapscriptTree: tapscriptTree, + }, nil +} + +// ReceiverHTLCScriptTaproot cosntructs the taproot witness program (schnor +// key) for an outgoing HTLC on the receiver's version of the commitment +// transaction. This method returns the top level tweaked public key that +// commits to both the script paths. +// +// The returned key commits to a tapscript tree with two possible paths: +// +// - The timeout path: +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY +// OP_CHECKLOCKTIMEVERIFY OP_DROP +// +// - Success path: +// OP_SIZE 32 OP_EQUALVERIFY +// OP_HASH160 OP_EQUALVERIFY +// OP_CHECKSIGVERIFY +// OP_CHECKSIG +// +// The timeout path can be be spent with a witness of: +// - +// +// The success path can be spent with a witness of: +// - +// +// The top level keyspend key is the revocation key, which allows a defender to +// unilaterally spend the created output. Both the final output key as well as +// the tap leaf are returned. +func ReceiverHTLCScriptTaproot(cltvExpiry uint32, + senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey, + payHash []byte) (*HtlcScriptTree, error) { + + // Given all the necessary parameters, we'll return the HTLC script + // tree that includes the top level output script, as well as the two + // tap leaf paths. + return receiverHtlcTapScriptTree( + senderHtlcKey, receiverHtlcKey, revocationKey, payHash, + cltvExpiry, + ) +} + +// ReceiverHTLCScriptTaprootRedeem creates a valid witness needed to redeem a +// receiver taproot HTLC with the pre-image. The returned witness is valid and +// includes the control block required to spend the output. +func ReceiverHTLCScriptTaprootRedeem(senderSig Signature, + senderSigHash txscript.SigHashType, paymentPreimage []byte, + signer Signer, signDesc *SignDescriptor, + htlcSuccessTx *wire.MsgTx, revokeKey *btcec.PublicKey, + tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { + + // First, we'll generate a signature for the HTLC success transaction. + // The signDesc should be signing with the public key used as the + // receiver's public key and also the correct single tweak. + sweepSig, err := signer.SignOutputRaw(htlcSuccessTx, signDesc) + if err != nil { + return nil, err + } + + // In addition to the signature and the witness/leaf script, we also + // need to make a control block proof using the tapscript tree. + timeoutTapLeafHash := txscript.NewBaseTapLeaf( + signDesc.WitnessScript, + ).TapHash() + timeoutIdx := tapscriptTree.LeafProofIndex[timeoutTapLeafHash] + timeoutMerkleProof := tapscriptTree.LeafMerkleProofs[timeoutIdx] + timeoutControlBlock := timeoutMerkleProof.ToControlBlock(revokeKey) + + // The final witness stack is: + // * + witnessStack := wire.TxWitness(make([][]byte, 5)) + witnessStack[0] = append(senderSig.Serialize(), byte(senderSigHash)) + witnessStack[1] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + witnessStack[2] = paymentPreimage + witnessStack[3] = signDesc.WitnessScript + witnessStack[4], err = timeoutControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return witnessStack, nil +} + +// ReceiverHtlcTapLeafTimeout creates a valid witness needed to timeout an HTLC +// on the receiver's commitment transaction after the timeout has elapsed +func ReceiverHTLCScriptTaprootTimeout(signer Signer, signDesc *SignDescriptor, + sweepTx *wire.MsgTx, cltvExpiry int32, revokeKey *btcec.PublicKey, + tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { + + // If the caller set a proper timeout value, then we'll apply it + // directly to the transaction. + // + // TODO(roasbeef): helper func + if cltvExpiry != -1 { + // The HTLC output has an absolute time period before we are + // permitted to recover the pending funds. Therefore we need to + // set the locktime on this sweeping transaction in order to + // pass Script verification. + sweepTx.LockTime = uint32(cltvExpiry) + } + + // With the lock time on the transaction set, we'll not generate a + // signature for the sweep transaction. The passed sign descriptor + // should be created using the raw public key of the sender (w/o the + // single tweak applied), and the single tweak set to the proper value + // taking into account the current state's point. + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) + if err != nil { + return nil, err + } + + // In addition to the signature and the witness/leaf script, we also + // need to make a control block proof using the tapscript tree. + successTapLeafHash := txscript.NewBaseTapLeaf( + signDesc.WitnessScript, + ).TapHash() + successIdx := tapscriptTree.LeafProofIndex[successTapLeafHash] + successMerkleProof := tapscriptTree.LeafMerkleProofs[successIdx] + successControlBlock := successMerkleProof.ToControlBlock(revokeKey) + + // The final witness is pretty simple, we just need to present a valid + // signature for the script, and then provide the control block. + witnessStack := make(wire.TxWitness, 3) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + witnessStack[1] = signDesc.WitnessScript + witnessStack[2], err = successControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return witnessStack, nil +} + // SecondLevelHtlcScript is the uniform script that's used as the output for // the second-level HTLC transactions. The second level transaction act as a // sort of covenant, ensuring that a 2-of-2 multi-sig output can only be // spent in a particular way, and to a particular output. // // Possible Input Scripts: -// * To revoke an HTLC output that has been transitioned to the claim+delay -// state: -// * 1 // -// * To claim and HTLC output, either with a pre-image or due to a timeout: -// * 0 +// - To revoke an HTLC output that has been transitioned to the claim+delay +// state: +// +// - 1 +// +// - To claim and HTLC output, either with a pre-image or due to a timeout: +// +// - 0 // // OP_IF -// +// +// +// // OP_ELSE -// -// OP_CHECKSEQUENCEVERIFY -// OP_DROP -// +// +// +// OP_CHECKSEQUENCEVERIFY +// OP_DROP +// +// // OP_ENDIF // OP_CHECKSIG // // TODO(roasbeef): possible renames for second-level -// * transition? -// * covenant output +// - transition? +// - covenant output func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, csvDelay uint32) ([]byte, error) { @@ -774,29 +1321,193 @@ func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, return builder.Script() } +// TODO(roasbeef): move all taproot stuff to new file? + +// TaprootSecondLevelTapLeaf constructs the tap leaf used as the sole script +// path for a second level HTLC spend. +// +// The final script used is: +// +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY OP_DROP +func TaprootSecondLevelTapLeaf(delayKey *btcec.PublicKey, + csvDelay uint32) (txscript.TapLeaf, error) { + + builder := txscript.NewScriptBuilder() + + // Ensure the proper party can sign for this output. + builder.AddData(schnorr.SerializePubKey(delayKey)) + builder.AddOp(txscript.OP_CHECKSIG) + + // Assuming the above passes, then we'll now ensure that the CSV delay + // has been upheld, dropping the int we pushed on. If the sig above is + // valid, then a 1 will be left on the stack. + builder.AddInt64(int64(csvDelay)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) + + secondLevelLeafScript, err := builder.Script() + if err != nil { + return txscript.TapLeaf{}, err + } + + return txscript.NewBaseTapLeaf(secondLevelLeafScript), nil +} + +// SecondLevelHtlcTapscriptTree construct the indexed tapscript tree needed to +// generate the taptweak to create the final output and also control block. +func SecondLevelHtlcTapscriptTree(delayKey *btcec.PublicKey, + csvDelay uint32) (*txscript.IndexedTapScriptTree, error) { + + // First grab the second level leaf script we need to create the top level + // output. + secondLevelTapLeaf, err := TaprootSecondLevelTapLeaf(delayKey, csvDelay) + if err != nil { + return nil, err + } + + // Now that we have the sole second level script, we can create the + // tapscript tree that commits to both the leaves. + return txscript.AssembleTaprootScriptTree(secondLevelTapLeaf), nil +} + +// TaprootSecondLevelHtlcScript is the uniform script that's used as the output +// for the second-level HTLC transaction. The second level transaction acts as +// an off-chain 2-of-2 covenant that can only be spent a particular way and to +// a particular output. +// +// Possible Input Scripts: +// - Claiming the HTLC output with a pre-image or a timeout: +// - +// +// The script main script lets the broadcaster spend after a delay the script +// path: +// +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY OP_DROP +// +// The keyspend path require knowledge of the top level revocation private key. +func TaprootSecondLevelHtlcScript(revokeKey, delayKey *btcec.PublicKey, + csvDelay uint32) (*btcec.PublicKey, error) { + + // First, we'll make the tapscript tree that commits to the redemption + // path. + tapScriptTree, err := SecondLevelHtlcTapscriptTree( + delayKey, csvDelay, + ) + if err != nil { + return nil, err + } + + tapScriptRoot := tapScriptTree.RootNode.TapHash() + + // With the tapscript root obtained, we'll tweak the revocation key + // with this value to obtain the key that the second level spend will + // create. + redemptionKey := txscript.ComputeTaprootOutputKey( + revokeKey, tapScriptRoot[:], + ) + + return redemptionKey, nil +} + +// TaprootHtlcSpendRevoke spends a second-level HTLC output via the revocation +// path. This uses the top level keyspend path to redeem the contested output. +// +// The passed SignDescriptor MUST have the proper witness script and also the +// proper top-level tweak derived from the tapscript tree for the second level +// output. +func TaprootHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor, + revokeTx *wire.MsgTx) (wire.TxWitness, error) { + + // We don't need any spacial modifications to the transaction as this + // is just sweeping a revoked HTLC output. So we'll generate a regular + // schnorr signature. + sweepSig, err := signer.SignOutputRaw(revokeTx, signDesc) + if err != nil { + return nil, err + } + + // The witness stack in this case is pretty simple: we only need to + // specify the signature generated. + witnessStack := make(wire.TxWitness, 1) + witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + + return witnessStack, nil +} + +// TaprootHtlcSpendSuccess spends a second-level HTLC output via the redemption +// path. This should be used to sweep funds after the pre-image is known or the +// timeout has elapsed on the commitment transaction of the broadcaster. +// +// NOTE: The caller MUST set the txn version, sequence number, and sign +// descriptor's sig hash cache before invocation. +func TaprootHtlcSpendSuccess(signer Signer, signDesc *SignDescriptor, + revokeKey *btcec.PublicKey, sweepTx *wire.MsgTx, + tapscriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { + + // First, we'll generate the sweep signature based on the populated + // sign desc. This should give us a valid schnorr signature for the + // sole script path leaf. + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) + if err != nil { + return nil, err + } + + // Now that we have the sweep signature, we'll construct the control + // block needed to spend the script path. + redeemTapLeafHash := txscript.NewBaseTapLeaf( + signDesc.WitnessScript, + ).TapHash() + redeemIdx := tapscriptTree.LeafProofIndex[redeemTapLeafHash] + redeemMerkleProof := tapscriptTree.LeafMerkleProofs[redeemIdx] + redeemControlBlock := redeemMerkleProof.ToControlBlock(revokeKey) + + // Now that we have the redeem control block, we can construct the + // final witness needed to spend the script: + // + // + witnessStack := make(wire.TxWitness, 3) + witnessStack[1] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + witnessStack[2] = signDesc.WitnessScript + witnessStack[3], err = redeemControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return witnessStack, nil +} + // LeaseSecondLevelHtlcScript is the uniform script that's used as the output for // the second-level HTLC transactions. The second level transaction acts as a // sort of covenant, ensuring that a 2-of-2 multi-sig output can only be // spent in a particular way, and to a particular output. // // Possible Input Scripts: -// * To revoke an HTLC output that has been transitioned to the claim+delay -// state: -// * 1 // -// * To claim an HTLC output, either with a pre-image or due to a timeout: -// * 0 +// - To revoke an HTLC output that has been transitioned to the claim+delay +// state: +// +// - 1 +// +// - To claim an HTLC output, either with a pre-image or due to a timeout: +// +// - 0 // // OP_IF -// +// +// +// // OP_ELSE -// -// OP_CHECKLOCKTIMEVERIFY -// OP_DROP -// -// OP_CHECKSEQUENCEVERIFY -// OP_DROP -// +// +// +// OP_CHECKLOCKTIMEVERIFY +// OP_DROP +// +// OP_CHECKSEQUENCEVERIFY +// OP_DROP +// +// // OP_ENDIF // OP_CHECKSIG. func LeaseSecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, @@ -944,7 +1655,7 @@ func HtlcSecondLevelSpend(signer Signer, signDesc *SignDescriptor, // LockTimeToSequence converts the passed relative locktime to a sequence // number in accordance to BIP-68. // See: https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki -// * (Compatibility) +// - (Compatibility) func LockTimeToSequence(isSeconds bool, locktime uint32) uint32 { if !isSeconds { // The locktime is to be expressed in confirmations. @@ -964,17 +1675,19 @@ func LockTimeToSequence(isSeconds bool, locktime uint32) uint32 { // can claim all the settled funds in the channel, plus the unsettled funds. // // Possible Input Scripts: -// REVOKE: 1 -// SENDRSWEEP: +// +// REVOKE: 1 +// SENDRSWEEP: // // Output Script: -// OP_IF -// -// OP_ELSE -// OP_CHECKSEQUENCEVERIFY OP_DROP -// -// OP_ENDIF -// OP_CHECKSIG +// +// OP_IF +// +// OP_ELSE +// OP_CHECKSEQUENCEVERIFY OP_DROP +// +// OP_ENDIF +// OP_CHECKSIG func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey) ([]byte, error) { // This script is spendable under two conditions: either the // 'csvTimeout' has passed and we can redeem our funds, or they can @@ -1009,24 +1722,82 @@ func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey) return builder.Script() } +// TaprootCommitScriptToSelf creates the taproot witness program that commits +// to the revocation (keyspend) and delay path (script path) in a single +// taproot output key. +// +// For the delay path we have the following tapscript leaf script: +// +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY OP_DROP +// +// This can then be spent with just: +// +// +// +// Where the to_delay_script is listed above, and the delay_control_block +// computed as: +// +// delay_control_block = (output_key_y_parity | 0xc0) || revocationpubkey +// +// The revocation key spend path will simply present a valid signature with the +// witness being just: +// +// +func TaprootCommitScriptToSelf(csvTimeout uint32, + selfKey, revokeKey *btcec.PublicKey) (*btcec.PublicKey, error) { + + // First, we'll need to construct the tapLeaf that'll be our delay CSV + // clause. + // + // TODO(roasbeef): extract into diff func + builder := txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(selfKey)) + builder.AddOp(txscript.OP_CHECKSIG) + builder.AddInt64(int64(csvTimeout)) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) + + delayScript, err := builder.Script() + if err != nil { + return nil, err + } + + // With the delay script computed, we'll now create a tapscript tree + // with a single leaf, and then obtain a root from that. + tapLeaf := txscript.NewBaseTapLeaf(delayScript) + tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) + tapScriptRoot := tapScriptTree.RootNode.TapHash() + + // Now that we have our root, we can arrive at the final output script + // by tweaking the internal key with this root. + toLocalOutputKey := txscript.ComputeTaprootOutputKey( + revokeKey, tapScriptRoot[:], + ) + + return toLocalOutputKey, nil +} + // LeaseCommitScriptToSelf constructs the public key script for the output on the // commitment transaction paying to the "owner" of said commitment transaction. // If the other party learns of the preimage to the revocation hash, then they // can claim all the settled funds in the channel, plus the unsettled funds. // // Possible Input Scripts: -// REVOKE: 1 -// SENDRSWEEP: +// +// REVOKE: 1 +// SENDRSWEEP: // // Output Script: -// OP_IF -// -// OP_ELSE -// OP_CHECKLOCKTIMEVERIFY OP_DROP -// OP_CHECKSEQUENCEVERIFY OP_DROP -// -// OP_ENDIF -// OP_CHECKSIG +// +// OP_IF +// +// OP_ELSE +// OP_CHECKLOCKTIMEVERIFY OP_DROP +// OP_CHECKSEQUENCEVERIFY OP_DROP +// +// OP_ENDIF +// OP_CHECKSIG func LeaseCommitScriptToSelf(selfKey, revokeKey *btcec.PublicKey, csvTimeout, leaseExpiry uint32) ([]byte, error) { @@ -1194,9 +1965,11 @@ func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) { // transaction. The money can only be spend after one confirmation. // // Possible Input Scripts: -// SWEEP: +// +// SWEEP: // // Output Script: +// // OP_CHECKSIGVERIFY // 1 OP_CHECKSEQUENCEVERIFY func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) { @@ -1213,17 +1986,67 @@ func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) { return builder.Script() } +// TaprootCommitScriptToRemote constructs a taproot witness program for the +// output on the commitment transaction for the remote party. For the top level +// key spend, we'll use the combined funding key (musig2.KeyAgg(k1, k2)), as a +// sort of practical NUMs point (the local party would never sign for this). We +// then commit to a single tapscript leaf that holds the normal CSV 1 delay +// script. +// +// Our single tapleaf will use the following script: +// +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY +// +// The CSV clause is a bit subtle, but OP_CHECKSIG will return true if it +// succeeds, which then enforces our 1 CSV. The true will remain on the stack, +// causing the script to pass. If the CHECKSIG fails, then a 0 will remain on +// the stack. +// +// TODO(roasbeef): double check here can't pass additional stack elements? +func TaprootCommitScriptToRemote(combinedFundingKey, + remoteKey *btcec.PublicKey) (*btcec.PublicKey, error) { + + // First, construct the remote party's tapscript they'll use to sweep their + // outputs. + builder := txscript.NewScriptBuilder() + builder.AddData(schnorr.SerializePubKey(remoteKey)) + builder.AddOp(txscript.OP_CHECKSIG) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + delayScript, err := builder.Script() + if err != nil { + return nil, err + } + + // With this script constructed, we'll map that into a tapLeaf, then + // make a new tapscript root from that. + tapLeaf := txscript.NewBaseTapLeaf(delayScript) + tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) + tapScriptRoot := tapScriptTree.RootNode.TapHash() + + // Now that we have our root, we can arrive at the final output script + // by tweaking the internal key with this root. + toRemoteOutputKey := txscript.ComputeTaprootOutputKey( + combinedFundingKey, tapScriptRoot[:], + ) + + return toRemoteOutputKey, nil +} + // LeaseCommitScriptToRemoteConfirmed constructs the script for the output on // the commitment transaction paying to the remote party of said commitment // transaction. The money can only be spend after one confirmation. // // Possible Input Scripts: -// SWEEP: +// +// SWEEP: // // Output Script: -// OP_CHECKSIGVERIFY -// OP_CHECKLOCKTIMEVERIFY OP_DROP -// 1 OP_CHECKSEQUENCEVERIFY +// +// OP_CHECKSIGVERIFY +// OP_CHECKLOCKTIMEVERIFY OP_DROP +// 1 OP_CHECKSEQUENCEVERIFY func LeaseCommitScriptToRemoteConfirmed(key *btcec.PublicKey, leaseExpiry uint32) ([]byte, error) { @@ -1278,10 +2101,12 @@ func CommitSpendToRemoteConfirmed(signer Signer, signDesc *SignDescriptor, // the given key immediately, or by anyone after 16 confirmations. // // Possible Input Scripts: -// By owner: -// By anyone (after 16 conf): +// +// By owner: +// By anyone (after 16 conf): // // Output Script: +// // OP_CHECKSIG OP_IFDUP // OP_NOTIF // OP_16 OP_CSV @@ -1305,6 +2130,41 @@ func CommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) { return builder.Script() } +// TaprootOutputKeyAnchor returns the segwit v1 (taproot) witness program that +// encodes the anchor output spending conditions: the passed key can be used +// for keyspend, with the OP_CSV 16 clause living within an internal tapscript +// leaf. +// +// Spend paths: +// - Key spend: +// - Script spend: OP_16 CSV +func TaprootOutputKeyAnchor(key *btcec.PublicKey) (*btcec.PublicKey, error) { + // The main script used is just a OP_16 CSV (anyone can sweep after 16 + // blocks). + builder := txscript.NewScriptBuilder() + builder.AddOp(txscript.OP_16) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + anchorScript, err := builder.Script() + if err != nil { + return nil, err + } + + // With the script, we can make our sole leaf, then derive the root + // from that. + tapLeaf := txscript.NewBaseTapLeaf(anchorScript) + tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf) + tapScriptRoot := tapScriptTree.RootNode.TapHash() + + // Now that we have our root, we can arrive at the final output script + // by tweaking the internal key with this root. + anchorKey := txscript.ComputeTaprootOutputKey( + key, tapScriptRoot[:], + ) + + return anchorKey, nil +} + // CommitSpendAnchor constructs a valid witness allowing a node to spend their // anchor output on the commitment transaction using their funding key. This is // used for the anchor channel type. @@ -1348,7 +2208,7 @@ func CommitSpendAnchorAnyone(script []byte) (wire.TxWitness, error) { // the pay/delay base point. The end end results is that the basePoint is // tweaked as follows: // -// * key = basePoint + sha256(commitPoint || basePoint)*G +// - key = basePoint + sha256(commitPoint || basePoint)*G func SingleTweakBytes(commitPoint, basePoint *btcec.PublicKey) []byte { h := sha256.New() h.Write(commitPoint.SerializeCompressed()) @@ -1363,15 +2223,15 @@ func SingleTweakBytes(commitPoint, basePoint *btcec.PublicKey) []byte { // The opposite applies for when tweaking remote keys. Precisely, the following // operation is used to "tweak" public keys: // -// tweakPub := basePoint + sha256(commitPoint || basePoint) * G -// := G*k + sha256(commitPoint || basePoint)*G -// := G*(k + sha256(commitPoint || basePoint)) +// tweakPub := basePoint + sha256(commitPoint || basePoint) * G +// := G*k + sha256(commitPoint || basePoint)*G +// := G*(k + sha256(commitPoint || basePoint)) // // Therefore, if a party possess the value k, the private key of the base // point, then they are able to derive the proper private key for the // revokeKey by computing: // -// revokePriv := k + sha256(commitPoint || basePoint) mod N +// revokePriv := k + sha256(commitPoint || basePoint) mod N // // Where N is the order of the sub-group. // @@ -1415,7 +2275,7 @@ func TweakPubKeyWithTweak(pubKey *btcec.PublicKey, // revoked state. Precisely, the following operation is used to derive a // tweaked private key: // -// * tweakPriv := basePriv + sha256(commitment || basePub) mod N +// - tweakPriv := basePriv + sha256(commitment || basePub) mod N // // Where N is the order of the sub-group. func TweakPrivKey(basePriv *btcec.PrivateKey, @@ -1436,24 +2296,24 @@ func TweakPrivKey(basePriv *btcec.PrivateKey, // revoked commitment transaction, then if the other party knows the revocation // preimage, then they'll be able to derive the corresponding private key to // this private key by exploiting the homomorphism in the elliptic curve group: -// * https://en.wikipedia.org/wiki/Group_homomorphism#Homomorphisms_of_abelian_groups +// - https://en.wikipedia.org/wiki/Group_homomorphism#Homomorphisms_of_abelian_groups // // The derivation is performed as follows: // -// revokeKey := revokeBase * sha256(revocationBase || commitPoint) + -// commitPoint * sha256(commitPoint || revocationBase) +// revokeKey := revokeBase * sha256(revocationBase || commitPoint) + +// commitPoint * sha256(commitPoint || revocationBase) // -// := G*(revokeBasePriv * sha256(revocationBase || commitPoint)) + -// G*(commitSecret * sha256(commitPoint || revocationBase)) +// := G*(revokeBasePriv * sha256(revocationBase || commitPoint)) + +// G*(commitSecret * sha256(commitPoint || revocationBase)) // -// := G*(revokeBasePriv * sha256(revocationBase || commitPoint) + -// commitSecret * sha256(commitPoint || revocationBase)) +// := G*(revokeBasePriv * sha256(revocationBase || commitPoint) + +// commitSecret * sha256(commitPoint || revocationBase)) // // Therefore, once we divulge the revocation secret, the remote peer is able to // compute the proper private key for the revokeKey by computing: // -// revokePriv := (revokeBasePriv * sha256(revocationBase || commitPoint)) + -// (commitSecret * sha256(commitPoint || revocationBase)) mod N +// revokePriv := (revokeBasePriv * sha256(revocationBase || commitPoint)) + +// (commitSecret * sha256(commitPoint || revocationBase)) mod N // // Where N is the order of the sub-group. func DeriveRevocationPubkey(revokeBase, @@ -1505,8 +2365,9 @@ func DeriveRevocationPubkey(revokeBase, // a previously revoked commitment transaction. // // The private key is derived as follows: -// revokePriv := (revokeBasePriv * sha256(revocationBase || commitPoint)) + -// (commitSecret * sha256(commitPoint || revocationBase)) mod N +// +// revokePriv := (revokeBasePriv * sha256(revocationBase || commitPoint)) + +// (commitSecret * sha256(commitPoint || revocationBase)) mod N // // Where N is the order of the sub-group. func DeriveRevocationPrivKey(revokeBasePriv *btcec.PrivateKey, diff --git a/input/taproot.go b/input/taproot.go index 0319228badb..8008179d310 100644 --- a/input/taproot.go +++ b/input/taproot.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/waddrmgr" @@ -105,3 +106,15 @@ func TapscriptPartialReveal(internalKey *btcec.PublicKey, RevealedScript: revealedLeaf.Script, } } + +// PayToTaprootScript creates a new script to pay to a version 1 +// (taproot) witness program. The passed public key will be serialized as an +// x-only key to create the witness program. +func PayToTaprootScript(taprootKey *btcec.PublicKey) ([]byte, error) { + builder := txscript.NewScriptBuilder() + + builder.AddOp(txscript.OP_1) + builder.AddData(schnorr.SerializePubKey(taprootKey)) + + return builder.Script() +} diff --git a/input/test_utils.go b/input/test_utils.go index 99b8e050fd8..de1ab6f1c81 100644 --- a/input/test_utils.go +++ b/input/test_utils.go @@ -1,245 +1 @@ package input - -import ( - "bytes" - "crypto/sha256" - "encoding/hex" - "fmt" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" - "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd/keychain" -) - -var ( - - // For simplicity a single priv key controls all of our test outputs. - testWalletPrivKey = []byte{ - 0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf, - 0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9, - 0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f, - 0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90, - } - - // We're alice :) - bobsPrivKey = []byte{ - 0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, - 0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, - 0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d, - 0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9, - } - - // Use a hard-coded HD seed. - testHdSeed = chainhash.Hash{ - 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, - 0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4, - 0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9, - 0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53, - } -) - -// MockSigner is a simple implementation of the Signer interface. Each one has -// a set of private keys in a slice and can sign messages using the appropriate -// one. -type MockSigner struct { - Privkeys []*btcec.PrivateKey - NetParams *chaincfg.Params -} - -// SignOutputRaw generates a signature for the passed transaction according to -// the data within the passed SignDescriptor. -func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx, - signDesc *SignDescriptor) (Signature, error) { - - pubkey := signDesc.KeyDesc.PubKey - switch { - case signDesc.SingleTweak != nil: - pubkey = TweakPubKeyWithTweak(pubkey, signDesc.SingleTweak) - case signDesc.DoubleTweak != nil: - pubkey = DeriveRevocationPubkey(pubkey, signDesc.DoubleTweak.PubKey()) - } - - hash160 := btcutil.Hash160(pubkey.SerializeCompressed()) - privKey := m.findKey(hash160, signDesc.SingleTweak, signDesc.DoubleTweak) - if privKey == nil { - return nil, fmt.Errorf("mock signer does not have key") - } - - sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes, - signDesc.InputIndex, signDesc.Output.Value, signDesc.WitnessScript, - signDesc.HashType, privKey) - if err != nil { - return nil, err - } - - return ecdsa.ParseDERSignature(sig[:len(sig)-1]) -} - -// ComputeInputScript generates a complete InputIndex for the passed transaction -// with the signature as defined within the passed SignDescriptor. This method -// should be capable of generating the proper input script for both regular -// p2wkh output and p2wkh outputs nested within a regular p2sh output. -func (m *MockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*Script, error) { - scriptType, addresses, _, err := txscript.ExtractPkScriptAddrs( - signDesc.Output.PkScript, m.NetParams) - if err != nil { - return nil, err - } - - switch scriptType { - case txscript.PubKeyHashTy: - privKey := m.findKey(addresses[0].ScriptAddress(), signDesc.SingleTweak, - signDesc.DoubleTweak) - if privKey == nil { - return nil, fmt.Errorf("mock signer does not have key for "+ - "address %v", addresses[0]) - } - - sigScript, err := txscript.SignatureScript( - tx, signDesc.InputIndex, signDesc.Output.PkScript, - txscript.SigHashAll, privKey, true, - ) - if err != nil { - return nil, err - } - - return &Script{SigScript: sigScript}, nil - - case txscript.WitnessV0PubKeyHashTy: - privKey := m.findKey(addresses[0].ScriptAddress(), signDesc.SingleTweak, - signDesc.DoubleTweak) - if privKey == nil { - return nil, fmt.Errorf("mock signer does not have key for "+ - "address %v", addresses[0]) - } - - witnessScript, err := txscript.WitnessSignature(tx, signDesc.SigHashes, - signDesc.InputIndex, signDesc.Output.Value, - signDesc.Output.PkScript, txscript.SigHashAll, privKey, true) - if err != nil { - return nil, err - } - - return &Script{Witness: witnessScript}, nil - - default: - return nil, fmt.Errorf("unexpected script type: %v", scriptType) - } -} - -// MuSig2CreateSession creates a new MuSig2 signing session using the local -// key identified by the key locator. The complete list of all public keys of -// all signing parties must be provided, including the public key of the local -// signing key. If nonces of other parties are already known, they can be -// submitted as well to reduce the number of method calls necessary later on. -func (m *MockSigner) MuSig2CreateSession(keychain.KeyLocator, - []*btcec.PublicKey, *MuSig2Tweaks, - [][musig2.PubNonceSize]byte) (*MuSig2SessionInfo, error) { - - return nil, nil -} - -// MuSig2RegisterNonces registers one or more public nonces of other signing -// participants for a session identified by its ID. This method returns true -// once we have all nonces for all other signing participants. -func (m *MockSigner) MuSig2RegisterNonces(MuSig2SessionID, - [][musig2.PubNonceSize]byte) (bool, error) { - - return false, nil -} - -// MuSig2Sign creates a partial signature using the local signing key -// that was specified when the session was created. This can only be -// called when all public nonces of all participants are known and have -// been registered with the session. If this node isn't responsible for -// combining all the partial signatures, then the cleanup parameter -// should be set, indicating that the session can be removed from memory -// once the signature was produced. -func (m *MockSigner) MuSig2Sign(MuSig2SessionID, - [sha256.Size]byte, bool) (*musig2.PartialSignature, error) { - - return nil, nil -} - -// MuSig2CombineSig combines the given partial signature(s) with the -// local one, if it already exists. Once a partial signature of all -// participants is registered, the final signature will be combined and -// returned. -func (m *MockSigner) MuSig2CombineSig(MuSig2SessionID, - []*musig2.PartialSignature) (*schnorr.Signature, bool, error) { - - return nil, false, nil -} - -// MuSig2Cleanup removes a session from memory to free up resources. -func (m *MockSigner) MuSig2Cleanup(MuSig2SessionID) error { - return nil -} - -// findKey searches through all stored private keys and returns one -// corresponding to the hashed pubkey if it can be found. The public key may -// either correspond directly to the private key or to the private key with a -// tweak applied. -func (m *MockSigner) findKey(needleHash160 []byte, singleTweak []byte, - doubleTweak *btcec.PrivateKey) *btcec.PrivateKey { - - for _, privkey := range m.Privkeys { - // First check whether public key is directly derived from private key. - hash160 := btcutil.Hash160(privkey.PubKey().SerializeCompressed()) - if bytes.Equal(hash160, needleHash160) { - return privkey - } - - // Otherwise check if public key is derived from tweaked private key. - switch { - case singleTweak != nil: - privkey = TweakPrivKey(privkey, singleTweak) - case doubleTweak != nil: - privkey = DeriveRevocationPrivKey(privkey, doubleTweak) - default: - continue - } - hash160 = btcutil.Hash160(privkey.PubKey().SerializeCompressed()) - if bytes.Equal(hash160, needleHash160) { - return privkey - } - } - return nil -} - -// pubkeyFromHex parses a Bitcoin public key from a hex encoded string. -func pubkeyFromHex(keyHex string) (*btcec.PublicKey, error) { - bytes, err := hex.DecodeString(keyHex) - if err != nil { - return nil, err - } - return btcec.ParsePubKey(bytes) -} - -// privkeyFromHex parses a Bitcoin private key from a hex encoded string. -func privkeyFromHex(keyHex string) (*btcec.PrivateKey, error) { - bytes, err := hex.DecodeString(keyHex) - if err != nil { - return nil, err - } - key, _ := btcec.PrivKeyFromBytes(bytes) - return key, nil - -} - -// pubkeyToHex serializes a Bitcoin public key to a hex encoded string. -func pubkeyToHex(key *btcec.PublicKey) string { - return hex.EncodeToString(key.SerializeCompressed()) -} - -// privkeyFromHex serializes a Bitcoin private key to a hex encoded string. -func privkeyToHex(key *btcec.PrivateKey) string { - return hex.EncodeToString(key.Serialize()) -} diff --git a/lnpeer/peer.go b/lnpeer/peer.go index 465a41cb903..3749b9bea19 100644 --- a/lnpeer/peer.go +++ b/lnpeer/peer.go @@ -6,9 +6,18 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" ) +// NewChannel... +type NewChannel struct { + *channeldb.OpenChannel + + // ChanOpts... + ChanOpts []lnwallet.ChannelOpt +} + // Peer is an interface which represents a remote lightning node. type Peer interface { // SendMessage sends a variadic number of high-priority message to @@ -25,7 +34,7 @@ type Peer interface { // AddNewChannel adds a new channel to the peer. The channel should fail // to be added if the cancel channel is closed. - AddNewChannel(channel *channeldb.OpenChannel, cancel <-chan struct{}) error + AddNewChannel(newChan *NewChannel, cancel <-chan struct{}) error // WipeChannel removes the channel uniquely identified by its channel // point from all indexes associated with the peer. diff --git a/lnrpc/lightning.pb.go b/lnrpc/lightning.pb.go index ff1073173e3..8af55bdc2b7 100644 --- a/lnrpc/lightning.pb.go +++ b/lnrpc/lightning.pb.go @@ -179,6 +179,8 @@ const ( //to guarantee that the channel initiator has no incentives to close a leased //channel before its maturity date. CommitmentType_SCRIPT_ENFORCED_LEASE CommitmentType = 4 + // TODO(roasbeef): need script enforce mirror type for the above as well? + CommitmentType_SIMPLE_TAPROOT CommitmentType = 5 ) // Enum value maps for CommitmentType. @@ -189,6 +191,7 @@ var ( 2: "STATIC_REMOTE_KEY", 3: "ANCHORS", 4: "SCRIPT_ENFORCED_LEASE", + 5: "SIMPLE_TAPROOT", } CommitmentType_value = map[string]int32{ "UNKNOWN_COMMITMENT_TYPE": 0, @@ -196,6 +199,7 @@ var ( "STATIC_REMOTE_KEY": 2, "ANCHORS": 3, "SCRIPT_ENFORCED_LEASE": 4, + "SIMPLE_TAPROOT": 5, } ) @@ -19483,412 +19487,413 @@ var file_lightning_proto_rawDesc = []byte{ 0x44, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, 0x55, 0x4e, 0x55, 0x53, 0x45, 0x44, 0x5f, 0x54, 0x41, - 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x05, 0x2a, 0x78, - 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, - 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, - 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, 0x41, - 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x10, 0x02, - 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x10, 0x03, 0x12, 0x19, 0x0a, - 0x15, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x45, 0x4e, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x44, - 0x5f, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x10, 0x04, 0x2a, 0x61, 0x0a, 0x09, 0x49, 0x6e, 0x69, 0x74, - 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, - 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, - 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, - 0x01, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x52, - 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x49, 0x54, 0x49, - 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x42, 0x4f, 0x54, 0x48, 0x10, 0x03, 0x2a, 0x60, 0x0a, 0x0e, 0x52, - 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, - 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, - 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x49, - 0x4e, 0x43, 0x4f, 0x4d, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, 0x02, 0x12, 0x11, - 0x0a, 0x0d, 0x4f, 0x55, 0x54, 0x47, 0x4f, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x10, - 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x04, 0x2a, 0x71, 0x0a, - 0x11, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x75, 0x74, 0x63, 0x6f, - 0x6d, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x43, 0x4f, 0x4d, 0x45, 0x5f, 0x55, 0x4e, - 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4c, 0x41, 0x49, 0x4d, - 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, - 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x42, 0x41, 0x4e, 0x44, 0x4f, 0x4e, 0x45, 0x44, - 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x49, 0x52, 0x53, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x47, - 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x05, - 0x2a, 0x39, 0x0a, 0x0e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, - 0x1a, 0x0a, 0x16, 0x42, 0x45, 0x54, 0x57, 0x45, 0x45, 0x4e, 0x4e, 0x45, 0x53, 0x53, 0x5f, 0x43, - 0x45, 0x4e, 0x54, 0x52, 0x41, 0x4c, 0x49, 0x54, 0x59, 0x10, 0x01, 0x2a, 0x3b, 0x0a, 0x10, 0x49, - 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, - 0x07, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x41, - 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x2a, 0xd9, 0x01, 0x0a, 0x14, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, - 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x46, 0x41, - 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x49, 0x4d, - 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, - 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x5f, 0x52, 0x4f, 0x55, 0x54, - 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, - 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x2c, 0x0a, - 0x28, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, - 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, - 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x04, 0x12, 0x27, 0x0a, 0x23, 0x46, - 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, - 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, - 0x43, 0x45, 0x10, 0x05, 0x2a, 0xcf, 0x04, 0x0a, 0x0a, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, - 0x42, 0x69, 0x74, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, - 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x00, 0x12, 0x18, 0x0a, - 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, - 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x49, 0x4e, 0x49, 0x54, 0x49, - 0x41, 0x4c, 0x5f, 0x52, 0x4f, 0x55, 0x49, 0x4e, 0x47, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x03, - 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, - 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x52, 0x45, 0x51, 0x10, - 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, 0x5f, 0x53, 0x48, 0x55, - 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x4f, 0x50, 0x54, - 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, - 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, - 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, - 0x10, 0x07, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, - 0x52, 0x45, 0x51, 0x10, 0x08, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, 0x4e, 0x49, - 0x4f, 0x4e, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x09, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, - 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, - 0x45, 0x51, 0x10, 0x0a, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, - 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0b, + 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x5f, 0x50, 0x55, 0x42, 0x4b, 0x45, 0x59, 0x10, 0x05, 0x2a, 0x8c, + 0x01, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, + 0x4d, 0x49, 0x54, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, + 0x0a, 0x06, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, + 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x10, + 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x10, 0x03, 0x12, 0x19, + 0x0a, 0x15, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, 0x5f, 0x45, 0x4e, 0x46, 0x4f, 0x52, 0x43, 0x45, + 0x44, 0x5f, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x10, 0x04, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x49, 0x4d, + 0x50, 0x4c, 0x45, 0x5f, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x05, 0x2a, 0x61, 0x0a, + 0x09, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x15, 0x0a, 0x11, 0x49, 0x4e, + 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x4c, + 0x4f, 0x43, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, + 0x54, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, + 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x54, 0x4f, 0x52, 0x5f, 0x42, 0x4f, 0x54, 0x48, 0x10, 0x03, + 0x2a, 0x60, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, + 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x10, 0x01, + 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x43, 0x4f, 0x4d, 0x49, 0x4e, 0x47, 0x5f, 0x48, 0x54, 0x4c, + 0x43, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x4f, 0x55, 0x54, 0x47, 0x4f, 0x49, 0x4e, 0x47, 0x5f, + 0x48, 0x54, 0x4c, 0x43, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, + 0x10, 0x04, 0x2a, 0x71, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, + 0x4f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x55, 0x54, 0x43, 0x4f, + 0x4d, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, + 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x43, + 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x41, 0x42, 0x41, 0x4e, + 0x44, 0x4f, 0x4e, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x46, 0x49, 0x52, 0x53, 0x54, + 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x10, 0x04, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x49, 0x4d, 0x45, + 0x4f, 0x55, 0x54, 0x10, 0x05, 0x2a, 0x39, 0x0a, 0x0e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, + 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x42, 0x45, 0x54, 0x57, 0x45, 0x45, 0x4e, 0x4e, + 0x45, 0x53, 0x53, 0x5f, 0x43, 0x45, 0x4e, 0x54, 0x52, 0x41, 0x4c, 0x49, 0x54, 0x59, 0x10, 0x01, + 0x2a, 0x3b, 0x0a, 0x10, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x48, 0x54, 0x4c, 0x43, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x43, 0x43, 0x45, 0x50, 0x54, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x54, 0x54, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, + 0x0c, 0x0a, 0x08, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x2a, 0xd9, 0x01, + 0x0a, 0x14, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, + 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x13, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, + 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, + 0x1a, 0x0a, 0x16, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x4f, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x46, + 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4e, 0x4f, + 0x5f, 0x52, 0x4f, 0x55, 0x54, 0x45, 0x10, 0x02, 0x12, 0x18, 0x0a, 0x14, 0x46, 0x41, 0x49, 0x4c, + 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x10, 0x03, 0x12, 0x2c, 0x0a, 0x28, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, + 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x43, 0x4f, 0x52, 0x52, 0x45, 0x43, 0x54, 0x5f, 0x50, + 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x53, 0x10, 0x04, + 0x12, 0x27, 0x0a, 0x23, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x53, + 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x53, 0x55, 0x46, 0x46, 0x49, 0x43, 0x49, 0x45, 0x4e, 0x54, 0x5f, + 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x05, 0x2a, 0xcf, 0x04, 0x0a, 0x0a, 0x46, 0x65, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x42, 0x69, 0x74, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, + 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x52, 0x45, 0x51, + 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x41, 0x54, 0x41, 0x4c, 0x4f, 0x53, 0x53, 0x5f, 0x50, + 0x52, 0x4f, 0x54, 0x45, 0x43, 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, + 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x52, 0x4f, 0x55, 0x49, 0x4e, 0x47, 0x5f, 0x53, + 0x59, 0x4e, 0x43, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, 0x54, + 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, 0x54, + 0x5f, 0x52, 0x45, 0x51, 0x10, 0x04, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x46, 0x52, 0x4f, 0x4e, + 0x54, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x44, 0x4f, 0x57, 0x4e, 0x5f, 0x53, 0x43, 0x52, 0x49, 0x50, + 0x54, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, + 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x06, 0x12, + 0x16, 0x0a, 0x12, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, + 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x07, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, 0x56, 0x5f, 0x4f, + 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x08, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x4c, + 0x56, 0x5f, 0x4f, 0x4e, 0x49, 0x4f, 0x4e, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x09, 0x12, 0x1a, 0x0a, + 0x16, 0x45, 0x58, 0x54, 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, + 0x49, 0x45, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0a, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x58, 0x54, + 0x5f, 0x47, 0x4f, 0x53, 0x53, 0x49, 0x50, 0x5f, 0x51, 0x55, 0x45, 0x52, 0x49, 0x45, 0x53, 0x5f, + 0x4f, 0x50, 0x54, 0x10, 0x0b, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, + 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, - 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0c, 0x12, 0x19, 0x0a, 0x15, 0x53, - 0x54, 0x41, 0x54, 0x49, 0x43, 0x5f, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x4b, 0x45, 0x59, - 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, - 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x0e, 0x12, 0x14, 0x0a, 0x10, - 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x4f, 0x50, 0x54, - 0x10, 0x0f, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x10, 0x12, - 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x11, 0x12, 0x16, 0x0a, 0x12, - 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x52, - 0x45, 0x51, 0x10, 0x12, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, - 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x13, 0x12, 0x0f, 0x0a, 0x0b, - 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x14, 0x12, 0x0f, 0x0a, - 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x15, 0x12, 0x1d, - 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, - 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x16, 0x12, 0x1d, 0x0a, - 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, - 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x17, 0x12, 0x0b, 0x0a, 0x07, - 0x41, 0x4d, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x1e, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, - 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x1f, 0x2a, 0xac, 0x01, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, 0x41, - 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, - 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, - 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, - 0x12, 0x1c, 0x0a, 0x18, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, - 0x52, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x1f, - 0x0a, 0x1b, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, - 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x10, 0x03, 0x12, - 0x24, 0x0a, 0x20, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, - 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x50, 0x41, 0x52, 0x41, 0x4d, 0x45, - 0x54, 0x45, 0x52, 0x10, 0x04, 0x32, 0x8f, 0x26, 0x0a, 0x09, 0x4c, 0x69, 0x67, 0x68, 0x74, 0x6e, - 0x69, 0x6e, 0x67, 0x12, 0x4a, 0x0a, 0x0d, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, - 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x4d, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, - 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, - 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x44, 0x0a, 0x0b, 0x45, - 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x45, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0d, 0x12, 0x14, 0x0a, 0x10, 0x50, + 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, 0x52, 0x5f, 0x52, 0x45, 0x51, 0x10, + 0x0e, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x41, 0x59, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x41, 0x44, 0x44, + 0x52, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x0f, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x52, + 0x45, 0x51, 0x10, 0x10, 0x12, 0x0b, 0x0a, 0x07, 0x4d, 0x50, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, + 0x11, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, + 0x45, 0x4c, 0x53, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x12, 0x12, 0x16, 0x0a, 0x12, 0x57, 0x55, 0x4d, + 0x42, 0x4f, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x4e, 0x45, 0x4c, 0x53, 0x5f, 0x4f, 0x50, 0x54, 0x10, + 0x13, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x52, 0x45, 0x51, + 0x10, 0x14, 0x12, 0x0f, 0x0a, 0x0b, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x4f, 0x50, + 0x54, 0x10, 0x15, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, + 0x45, 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x52, 0x45, 0x51, + 0x10, 0x16, 0x12, 0x1d, 0x0a, 0x19, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52, 0x53, 0x5f, 0x5a, 0x45, + 0x52, 0x4f, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x48, 0x54, 0x4c, 0x43, 0x5f, 0x4f, 0x50, 0x54, 0x10, + 0x17, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x52, 0x45, 0x51, 0x10, 0x1e, 0x12, 0x0b, + 0x0a, 0x07, 0x41, 0x4d, 0x50, 0x5f, 0x4f, 0x50, 0x54, 0x10, 0x1f, 0x2a, 0xac, 0x01, 0x0a, 0x0d, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12, 0x1a, 0x0a, + 0x16, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, + 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x55, 0x50, 0x44, + 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, + 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, + 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, + 0x44, 0x10, 0x02, 0x12, 0x1f, 0x0a, 0x1b, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x41, + 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x45, + 0x52, 0x52, 0x10, 0x03, 0x12, 0x24, 0x0a, 0x20, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, + 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x50, + 0x41, 0x52, 0x41, 0x4d, 0x45, 0x54, 0x45, 0x52, 0x10, 0x04, 0x32, 0x8f, 0x26, 0x0a, 0x09, 0x4c, + 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x4a, 0x0a, 0x0d, 0x57, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x57, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, + 0x12, 0x44, 0x0a, 0x0b, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x12, + 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, + 0x46, 0x65, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x73, - 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x17, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, + 0x69, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, + 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, + 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, + 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x15, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x08, 0x53, 0x65, + 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, + 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x53, 0x69, + 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, + 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4a, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x19, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x50, 0x65, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x17, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, - 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, - 0x70, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x62, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x08, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, - 0x79, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, - 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, - 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x56, - 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, - 0x0e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x12, - 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, - 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, - 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x13, - 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x1a, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, - 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x50, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, - 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, - 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, - 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x16, - 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, - 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, - 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, - 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0b, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, - 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x53, 0x0a, 0x10, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, - 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, - 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, - 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4c, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x53, 0x74, 0x65, 0x70, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, - 0x67, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, 0x65, 0x73, 0x70, 0x12, 0x50, - 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x6f, - 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, - 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, - 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, - 0x12, 0x46, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x41, 0x62, 0x61, 0x6e, - 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x3a, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x12, 0x2e, 0x6c, 0x6e, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x47, 0x0a, 0x13, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x50, 0x65, + 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x10, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, + 0x65, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x07, 0x47, 0x65, + 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x15, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, + 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, + 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x56, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, 0x0e, 0x43, 0x6c, 0x6f, + 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x6e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x43, 0x0a, 0x0b, 0x4f, + 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x70, + 0x65, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, + 0x12, 0x53, 0x0a, 0x10, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x74, + 0x63, 0x68, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, 0x10, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x67, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, + 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x74, 0x65, 0x70, 0x52, + 0x65, 0x73, 0x70, 0x12, 0x50, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x0c, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, + 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x4d, 0x0a, + 0x0e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, + 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x62, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0b, + 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, - 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0f, - 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x53, 0x79, 0x6e, 0x63, 0x12, - 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x37, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x0e, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x1a, 0x19, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x33, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, - 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x48, 0x61, 0x73, 0x68, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, - 0x6f, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x11, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, - 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, - 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x32, 0x0a, 0x0c, 0x44, 0x65, 0x63, 0x6f, 0x64, - 0x65, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x1a, 0x0d, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x47, 0x0a, 0x0c, 0x4c, - 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x56, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x0d, 0x44, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x62, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x47, 0x0a, 0x0e, 0x47, 0x65, - 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x19, 0x2e, 0x6c, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, 0x01, 0x12, 0x3a, 0x0a, + 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x79, 0x6e, 0x63, + 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, + 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x53, 0x65, 0x6e, + 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x88, 0x02, 0x01, 0x28, 0x01, 0x30, + 0x01, 0x12, 0x41, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, + 0x53, 0x79, 0x6e, 0x63, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, + 0x64, 0x54, 0x6f, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x12, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, + 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x19, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0d, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x49, 0x6e, + 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x11, 0x53, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1a, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x0e, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x49, 0x6e, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x30, 0x01, 0x12, 0x32, 0x0a, 0x0c, + 0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x12, 0x13, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x1a, 0x0d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x61, 0x79, 0x52, 0x65, 0x71, + 0x12, 0x47, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x0d, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, + 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x6c, 0x6c, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, + 0x0d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x1a, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, + 0x61, 0x70, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, + 0x47, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, + 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x4e, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, - 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x12, 0x36, - 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, - 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x44, 0x0a, 0x0b, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, - 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x0e, - 0x47, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x43, + 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x45, + 0x64, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x44, 0x0a, 0x0b, 0x51, + 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x35, 0x0a, - 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x15, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, - 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x20, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, - 0x6f, 0x67, 0x79, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, - 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, 0x54, 0x6f, 0x70, - 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x30, 0x01, 0x12, 0x41, 0x0a, - 0x0a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x18, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, - 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x3e, 0x0a, 0x09, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, - 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x4e, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x56, 0x0a, 0x11, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, - 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, - 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, - 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x45, 0x78, 0x70, 0x6f, - 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, - 0x21, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x54, 0x0a, 0x17, 0x45, 0x78, 0x70, 0x6f, - 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, - 0x75, 0x70, 0x73, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x4e, - 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, - 0x75, 0x70, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, - 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x1a, 0x1f, 0x2e, - 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, 0x6e, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, - 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, - 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, - 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, - 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x61, 0x6e, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x30, 0x01, - 0x12, 0x47, 0x0a, 0x0c, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, - 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, - 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, - 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x12, 0x1d, 0x2e, 0x6c, - 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, - 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, - 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x10, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x12, - 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, - 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, - 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x53, 0x0a, 0x18, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x61, 0x72, - 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, - 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, - 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x65, 0x72, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, - 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, - 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x1b, - 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, - 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, - 0x56, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, - 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, - 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x73, 0x12, 0x25, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, - 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, - 0x63, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x30, - 0x01, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, - 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, - 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, - 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x66, 0x6f, 0x12, 0x35, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, + 0x12, 0x12, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, + 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x15, 0x53, 0x75, 0x62, + 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, + 0x70, 0x68, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, 0x70, 0x68, + 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x72, 0x61, + 0x70, 0x68, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0a, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x12, 0x18, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x09, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x12, 0x17, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x65, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1a, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, + 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, + 0x72, 0x70, 0x63, 0x2e, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x69, + 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, + 0x13, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x12, 0x21, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x70, + 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x54, 0x0a, + 0x17, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x45, 0x78, 0x70, 0x6f, 0x72, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x12, 0x4e, 0x0a, 0x10, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x43, 0x68, 0x61, + 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, + 0x6f, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, + 0x79, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x1f, 0x2e, 0x6c, + 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x42, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x20, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x30, 0x01, 0x12, 0x47, 0x0a, 0x0c, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, 0x63, + 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, + 0x6b, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x42, 0x61, 0x6b, 0x65, 0x4d, 0x61, + 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, + 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, + 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, + 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x61, 0x63, + 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x53, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, + 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x44, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1d, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x18, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x4d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x4d, 0x61, 0x63, 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4d, 0x61, 0x63, + 0x50, 0x65, 0x72, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x15, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x50, 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, + 0x65, 0x77, 0x61, 0x72, 0x65, 0x12, 0x1c, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, + 0x43, 0x4d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x1a, 0x1b, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x50, 0x43, 0x4d, + 0x69, 0x64, 0x64, 0x6c, 0x65, 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x28, 0x01, 0x30, 0x01, 0x12, 0x56, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x6c, 0x6e, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6c, 0x6e, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, 0x0a, 0x17, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12, 0x25, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, + 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, + 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x30, 0x01, 0x12, 0x44, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, + 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1a, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x69, + 0x61, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, + 0x6e, 0x69, 0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, + 0x6c, 0x6e, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/lnrpc/lightning.proto b/lnrpc/lightning.proto index b1716e34004..c7451f31dd8 100644 --- a/lnrpc/lightning.proto +++ b/lnrpc/lightning.proto @@ -1320,6 +1320,13 @@ enum CommitmentType { channel before its maturity date. */ SCRIPT_ENFORCED_LEASE = 4; + + /* + A channel that uses musig2 for the funding output, and the new tapscript + features where relevant. + */ + // TODO(roasbeef): need script enforce mirror type for the above as well? + SIMPLE_TAPROOT = 5; } message ChannelConstraints { diff --git a/lnrpc/lightning.swagger.json b/lnrpc/lightning.swagger.json index 5007e143cfa..70ec21e41d0 100644 --- a/lnrpc/lightning.swagger.json +++ b/lnrpc/lightning.swagger.json @@ -3982,10 +3982,11 @@ "LEGACY", "STATIC_REMOTE_KEY", "ANCHORS", - "SCRIPT_ENFORCED_LEASE" + "SCRIPT_ENFORCED_LEASE", + "SIMPLE_TAPROOT" ], "default": "UNKNOWN_COMMITMENT_TYPE", - "description": " - UNKNOWN_COMMITMENT_TYPE: Returned when the commitment type isn't known or unavailable.\n - LEGACY: A channel using the legacy commitment format having tweaked to_remote\nkeys.\n - STATIC_REMOTE_KEY: A channel that uses the modern commitment format where the key in the\noutput of the remote party does not change each state. This makes back\nup and recovery easier as when the channel is closed, the funds go\ndirectly to that key.\n - ANCHORS: A channel that uses a commitment format that has anchor outputs on the\ncommitments, allowing fee bumping after a force close transaction has\nbeen broadcast.\n - SCRIPT_ENFORCED_LEASE: A channel that uses a commitment type that builds upon the anchors\ncommitment format, but in addition requires a CLTV clause to spend outputs\npaying to the channel initiator. This is intended for use on leased channels\nto guarantee that the channel initiator has no incentives to close a leased\nchannel before its maturity date." + "title": "- UNKNOWN_COMMITMENT_TYPE: Returned when the commitment type isn't known or unavailable.\n - LEGACY: A channel using the legacy commitment format having tweaked to_remote\nkeys.\n - STATIC_REMOTE_KEY: A channel that uses the modern commitment format where the key in the\noutput of the remote party does not change each state. This makes back\nup and recovery easier as when the channel is closed, the funds go\ndirectly to that key.\n - ANCHORS: A channel that uses a commitment format that has anchor outputs on the\ncommitments, allowing fee bumping after a force close transaction has\nbeen broadcast.\n - SCRIPT_ENFORCED_LEASE: A channel that uses a commitment type that builds upon the anchors\ncommitment format, but in addition requires a CLTV clause to spend outputs\npaying to the channel initiator. This is intended for use on leased channels\nto guarantee that the channel initiator has no incentives to close a leased\nchannel before its maturity date.\n - SIMPLE_TAPROOT: TODO(roasbeef): need script enforce mirror type for the above as well?" }, "lnrpcConnectPeerRequest": { "type": "object", diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go index a372af521cd..b9104be9488 100644 --- a/lnrpc/routerrpc/router_backend.go +++ b/lnrpc/routerrpc/router_backend.go @@ -1304,7 +1304,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 7c098f25704..7ddd39f147f 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/lntest/mock/signer.go b/lntest/mock/signer.go index 4dd465cd878..6b17cbf667d 100644 --- a/lntest/mock/signer.go +++ b/lntest/mock/signer.go @@ -196,7 +196,7 @@ func (s *SingleSigner) SignMessage(keyLoc keychain.KeyLocator, // submitted as well to reduce the number of method calls necessary later on. func (s *SingleSigner) MuSig2CreateSession(keychain.KeyLocator, []*btcec.PublicKey, *input.MuSig2Tweaks, - [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, error) { + [][musig2.PubNonceSize]byte, ...musig2.SessionOption) (*input.MuSig2SessionInfo, error) { return nil, nil } diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index eca08eb37e3..50c92b93434 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -25,11 +25,11 @@ import ( "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/lightningnetwork/lnd/blockcache" - "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/lnwallet/musession" ) const ( @@ -109,8 +109,7 @@ type BtcWallet struct { blockCache *blockcache.BlockCache - musig2Sessions map[input.MuSig2SessionID]*muSig2State - musig2SessionsMtx sync.Mutex + *musession.Manager } // A compile time check to ensure that BtcWallet implements the @@ -172,16 +171,21 @@ func New(cfg Config, blockCache *blockcache.BlockCache) (*BtcWallet, error) { } } - return &BtcWallet{ - cfg: &cfg, - wallet: wallet, - db: wallet.Database(), - chain: cfg.ChainSource, - netParams: cfg.NetParams, - chainKeyScope: chainKeyScope, - blockCache: blockCache, - musig2Sessions: make(map[input.MuSig2SessionID]*muSig2State), - }, nil + finalWallet := &BtcWallet{ + cfg: &cfg, + wallet: wallet, + db: wallet.Database(), + chain: cfg.ChainSource, + netParams: cfg.NetParams, + chainKeyScope: chainKeyScope, + blockCache: blockCache, + } + + finalWallet.Manager = musession.NewManager( + finalWallet.fetchPrivKey, + ) + + return finalWallet, nil } // loaderCfg holds optional wallet loader configuration. diff --git a/lnwallet/btcwallet/musig.go b/lnwallet/btcwallet/musig.go new file mode 100644 index 00000000000..951ade5b786 --- /dev/null +++ b/lnwallet/btcwallet/musig.go @@ -0,0 +1 @@ +package btcwallet diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go index 91ee53e80e7..1a750ea2fad 100644 --- a/lnwallet/btcwallet/signer.go +++ b/lnwallet/btcwallet/signer.go @@ -1,13 +1,11 @@ package btcwallet import ( - "crypto/sha256" "fmt" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -403,7 +401,13 @@ func (b *BtcWallet) SignOutputRaw(tx *wire.MsgTx, } } - sig, err := schnorr.ParseSignature(rawSig) + // The signature returned above might have a sighash flag + // attached if a non-default type was used. We'll slice this + // off if it exists to ensure we can properly parse the raw + // signature. + sig, err := schnorr.ParseSignature( + rawSig[:schnorr.SignatureSize], + ) if err != nil { return nil, err } @@ -457,275 +461,6 @@ func (b *BtcWallet) ComputeInputScript(tx *wire.MsgTx, }, nil } -// muSig2State is a struct that holds on to the internal signing session state -// of a MuSig2 session. -type muSig2State struct { - // MuSig2SessionInfo is the associated meta information of the signing - // session. - input.MuSig2SessionInfo - - // context is the signing context responsible for keeping track of the - // public keys involved in the signing process. - context *musig2.Context - - // session is the signing session responsible for keeping track of the - // nonces and partial signatures involved in the signing process. - session *musig2.Session -} - -// MuSig2CreateSession creates a new MuSig2 signing session using the local -// key identified by the key locator. The complete list of all public keys of -// all signing parties must be provided, including the public key of the local -// signing key. If nonces of other parties are already known, they can be -// submitted as well to reduce the number of method calls necessary later on. -func (b *BtcWallet) MuSig2CreateSession(keyLoc keychain.KeyLocator, - allSignerPubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks, - otherSignerNonces [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, - error) { - - // We need to derive the private key for signing. In the remote signing - // setup, this whole RPC call will be forwarded to the signing - // instance, which requires it to be stateful. - privKey, err := b.fetchPrivKey(&keychain.KeyDescriptor{ - KeyLocator: keyLoc, - }) - if err != nil { - return nil, fmt.Errorf("error deriving private key: %v", err) - } - - // The context keeps track of all signing keys and our local key. - allOpts := append( - []musig2.ContextOption{ - musig2.WithKnownSigners(allSignerPubKeys), - }, - tweaks.ToContextOptions()..., - ) - musigContext, err := musig2.NewContext(privKey, true, allOpts...) - if err != nil { - return nil, fmt.Errorf("error creating MuSig2 signing "+ - "context: %v", err) - } - - // The session keeps track of the own and other nonces. - musigSession, err := musigContext.NewSession() - if err != nil { - return nil, fmt.Errorf("error creating MuSig2 signing "+ - "session: %v", err) - } - - // Add all nonces we might've learned so far. - haveAllNonces := false - for _, otherSignerNonce := range otherSignerNonces { - haveAllNonces, err = musigSession.RegisterPubNonce( - otherSignerNonce, - ) - if err != nil { - return nil, fmt.Errorf("error registering other "+ - "signer public nonce: %v", err) - } - } - - // Register the new session. - combinedKey, err := musigContext.CombinedKey() - if err != nil { - return nil, fmt.Errorf("error getting combined key: %v", err) - } - session := &muSig2State{ - MuSig2SessionInfo: input.MuSig2SessionInfo{ - SessionID: input.NewMuSig2SessionID( - combinedKey, musigSession.PublicNonce(), - ), - PublicNonce: musigSession.PublicNonce(), - CombinedKey: combinedKey, - TaprootTweak: tweaks.HasTaprootTweak(), - HaveAllNonces: haveAllNonces, - }, - context: musigContext, - session: musigSession, - } - - // The internal key is only calculated if we are using a taproot tweak - // and need to know it for a potential script spend. - if tweaks.HasTaprootTweak() { - internalKey, err := musigContext.TaprootInternalKey() - if err != nil { - return nil, fmt.Errorf("error getting internal key: %v", - err) - } - session.TaprootInternalKey = internalKey - } - - // Since we generate new nonces for every session, there is no way that - // a session with the same ID already exists. So even if we call the API - // twice with the same signers, we still get a new ID. - b.musig2SessionsMtx.Lock() - b.musig2Sessions[session.SessionID] = session - b.musig2SessionsMtx.Unlock() - - return &session.MuSig2SessionInfo, nil -} - -// MuSig2RegisterNonces registers one or more public nonces of other signing -// participants for a session identified by its ID. This method returns true -// once we have all nonces for all other signing participants. -func (b *BtcWallet) MuSig2RegisterNonces(sessionID input.MuSig2SessionID, - otherSignerNonces [][musig2.PubNonceSize]byte) (bool, error) { - - // We hold the lock during the whole operation, we don't want any - // interference with calls that might come through in parallel for the - // same session. - b.musig2SessionsMtx.Lock() - defer b.musig2SessionsMtx.Unlock() - - session, ok := b.musig2Sessions[sessionID] - if !ok { - return false, fmt.Errorf("session with ID %x not found", - sessionID[:]) - } - - // Make sure we don't exceed the number of expected nonces as that would - // indicate something is wrong with the signing setup. - if session.HaveAllNonces { - return true, fmt.Errorf("already have all nonces") - } - - numSigners := len(session.context.SigningKeys()) - remainingNonces := numSigners - session.session.NumRegisteredNonces() - if len(otherSignerNonces) > remainingNonces { - return false, fmt.Errorf("only %d other nonces remaining but "+ - "trying to register %d more", remainingNonces, - len(otherSignerNonces)) - } - - // Add all nonces we've learned so far. - var err error - for _, otherSignerNonce := range otherSignerNonces { - session.HaveAllNonces, err = session.session.RegisterPubNonce( - otherSignerNonce, - ) - if err != nil { - return false, fmt.Errorf("error registering other "+ - "signer public nonce: %v", err) - } - } - - return session.HaveAllNonces, nil -} - -// MuSig2Sign creates a partial signature using the local signing key -// that was specified when the session was created. This can only be -// called when all public nonces of all participants are known and have -// been registered with the session. If this node isn't responsible for -// combining all the partial signatures, then the cleanup parameter -// should be set, indicating that the session can be removed from memory -// once the signature was produced. -func (b *BtcWallet) MuSig2Sign(sessionID input.MuSig2SessionID, - msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) { - - // We hold the lock during the whole operation, we don't want any - // interference with calls that might come through in parallel for the - // same session. - b.musig2SessionsMtx.Lock() - defer b.musig2SessionsMtx.Unlock() - - session, ok := b.musig2Sessions[sessionID] - if !ok { - return nil, fmt.Errorf("session with ID %x not found", - sessionID[:]) - } - - // We can only sign once we have all other signer's nonces. - if !session.HaveAllNonces { - return nil, fmt.Errorf("only have %d of %d required nonces", - session.session.NumRegisteredNonces(), - len(session.context.SigningKeys())) - } - - // Create our own partial signature with the local signing key. - partialSig, err := session.session.Sign(msg, musig2.WithSortedKeys()) - if err != nil { - return nil, fmt.Errorf("error signing with local key: %v", err) - } - - // Clean up our local state if requested. - if cleanUp { - delete(b.musig2Sessions, sessionID) - } - - return partialSig, nil -} - -// MuSig2CombineSig combines the given partial signature(s) with the -// local one, if it already exists. Once a partial signature of all -// participants is registered, the final signature will be combined and -// returned. -func (b *BtcWallet) MuSig2CombineSig(sessionID input.MuSig2SessionID, - partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool, - error) { - - // We hold the lock during the whole operation, we don't want any - // interference with calls that might come through in parallel for the - // same session. - b.musig2SessionsMtx.Lock() - defer b.musig2SessionsMtx.Unlock() - - session, ok := b.musig2Sessions[sessionID] - if !ok { - return nil, false, fmt.Errorf("session with ID %x not found", - sessionID[:]) - } - - // Make sure we don't exceed the number of expected partial signatures - // as that would indicate something is wrong with the signing setup. - if session.HaveAllSigs { - return nil, true, fmt.Errorf("already have all partial" + - "signatures") - } - - // Add all sigs we got so far. - var ( - finalSig *schnorr.Signature - err error - ) - for _, otherPartialSig := range partialSigs { - session.HaveAllSigs, err = session.session.CombineSig( - otherPartialSig, - ) - if err != nil { - return nil, false, fmt.Errorf("error combining "+ - "partial signature: %v", err) - } - } - - // If we have all partial signatures, we should be able to get the - // complete signature now. We also remove this session from memory since - // there is nothing more left to do. - if session.HaveAllSigs { - finalSig = session.session.FinalSig() - delete(b.musig2Sessions, sessionID) - } - - return finalSig, session.HaveAllSigs, nil -} - -// MuSig2Cleanup removes a session from memory to free up resources. -func (b *BtcWallet) MuSig2Cleanup(sessionID input.MuSig2SessionID) error { - // We hold the lock during the whole operation, we don't want any - // interference with calls that might come through in parallel for the - // same session. - b.musig2SessionsMtx.Lock() - defer b.musig2SessionsMtx.Unlock() - - _, ok := b.musig2Sessions[sessionID] - if !ok { - return fmt.Errorf("session with ID %x not found", sessionID[:]) - } - - delete(b.musig2Sessions, sessionID) - - return nil -} - // A compile time check to ensure that BtcWallet implements the Signer // interface. var _ input.Signer = (*BtcWallet)(nil) diff --git a/lnwallet/btcwallet/test_utils.go b/lnwallet/btcwallet/test_utils.go new file mode 100644 index 00000000000..951ade5b786 --- /dev/null +++ b/lnwallet/btcwallet/test_utils.go @@ -0,0 +1 @@ +package btcwallet diff --git a/lnwallet/chancloser/chancloser.go b/lnwallet/chancloser/chancloser.go index 1af162f7dfb..82eb3039355 100644 --- a/lnwallet/chancloser/chancloser.go +++ b/lnwallet/chancloser/chancloser.go @@ -4,14 +4,17 @@ import ( "bytes" "fmt" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/labels" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" @@ -113,6 +116,16 @@ type Channel interface { // ShortChanID returns the scid of the channel. ShortChanID() lnwire.ShortChannelID + // ChanType.... + ChanType() channeldb.ChannelType + + // FundingTxOut returns the funding output of the channel. + FundingTxOut() *wire.TxOut + + // MultiSigKeys returns the local and remote multi-sig keys for the + // channel. + MultiSigKeys() (keychain.KeyDescriptor, keychain.KeyDescriptor) + // AbsoluteThawHeight returns the absolute thaw height of the channel. // If the channel is pending, or an unconfirmed zero conf channel, then // an error should be returned. @@ -127,14 +140,16 @@ type Channel interface { // of a valid signature, the chainhash of the final txid, and our final // balance in the created state. CreateCloseProposal(proposedFee btcutil.Amount, localDeliveryScript []byte, - remoteDeliveryScript []byte) (input.Signature, *chainhash.Hash, + remoteDeliveryScript []byte, + closeOpt ...lnwallet.ChanCloseOpt) (input.Signature, *chainhash.Hash, btcutil.Amount, error) // CompleteCooperativeClose persistently "completes" the cooperative // close by producing a fully signed co-op close transaction. CompleteCooperativeClose(localSig, remoteSig input.Signature, localDeliveryScript, remoteDeliveryScript []byte, - proposedFee btcutil.Amount) (*wire.MsgTx, btcutil.Amount, error) + proposedFee btcutil.Amount, closeOpt ...lnwallet.ChanCloseOpt, + ) (*wire.MsgTx, btcutil.Amount, error) } // ChanCloseCfg holds all the items that a ChanCloser requires to carry out its @@ -143,6 +158,9 @@ type ChanCloseCfg struct { // Channel is the channel that should be closed. Channel Channel + // Signer... + Signer input.Signer + // BroadcastTx broadcasts the passed transaction to the network. BroadcastTx func(*wire.MsgTx, string) error @@ -229,6 +247,15 @@ type ChanCloser struct { // locallyInitiated is true if we initiated the channel close. locallyInitiated bool + + // musigSession is the MuSig session that we'll use to sign the closing + // transaction. + // + // NOTE: This is only populated if this is a taproot channel. + musigSession *lnwallet.MusigSession + + // musigNoncePair... + musigNoncePair *lnwallet.MusigNoncePair } // NewChanCloser creates a new instance of the channel closure given the passed @@ -277,19 +304,43 @@ func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) { // closing script. shutdown := lnwire.NewShutdown(c.cid, c.localDeliveryScript) - // Before closing, we'll attempt to send a disable update for the channel. - // We do so before closing the channel as otherwise the current edge policy - // won't be retrievable from the graph. + // If this is a taproot channel, then we'll need to also generate a + // nonce that'll be used sign the co-op close transaction offer. + if c.cfg.Channel.ChanType().IsTaproot() { + // TODO(roasbeef): temp, need a puibkey input, can expose main key + // otherwise -- gotta use the actual here here + localKey, _ := c.cfg.Channel.MultiSigKeys() + firstClosingNonce, err := musig2.GenNonces( + musig2.WithPublicKey(localKey.PubKey), + ) + if err != nil { + return nil, err + } + shutdown.ShutdownNonce = (*lnwire.ShutdownNonce)( + &firstClosingNonce.PubNonce, + ) + + chancloserLog.Infof("Initiating shutdown w/ nonce: %v", + spew.Sdump(firstClosingNonce.PubNonce)) + + c.musigNoncePair = &lnwallet.MusigNoncePair{ + VerificationNonce: *firstClosingNonce, + } + } + + // Before closing, we'll attempt to send a disable update for the + // channel. We do so before closing the channel as otherwise the + // current edge policy won't be retrievable from the graph. if err := c.cfg.DisableChannel(c.chanPoint); err != nil { chancloserLog.Warnf("Unable to disable channel %v on close: %v", c.chanPoint, err) } - // Before continuing, mark the channel as cooperatively closed with a nil - // txn. Even though we haven't negotiated the final txn, this guarantees - // that our listchannels rpc will be externally consistent, and reflect - // that the channel is being shutdown by the time the closing request - // returns. + // Before continuing, mark the channel as cooperatively closed with a + // nil txn. Even though we haven't negotiated the final txn, this + // guarantees that our listchannels rpc will be externally consistent, + // and reflect that the channel is being shutdown by the time the + // closing request returns. err := c.cfg.Channel.MarkCoopBroadcasted(nil, c.locallyInitiated) if err != nil { return nil, err @@ -358,6 +409,7 @@ func (c *ChanCloser) CloseRequest() *htlcswitch.ChanClose { // NOTE: This method will PANIC if the underlying channel implementation isn't // the desired type. func (c *ChanCloser) Channel() *lnwallet.LightningChannel { + // TODO(roasbeef): remove this return c.cfg.Channel.(*lnwallet.LightningChannel) } @@ -432,8 +484,9 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // as otherwise, this is an attempted invalid state transition. shutdownMsg, ok := msg.(*lnwire.Shutdown) if !ok { - return nil, false, fmt.Errorf("expected lnwire.Shutdown, instead "+ - "have %v", spew.Sdump(msg)) + return nil, false, fmt.Errorf("expected "+ + "lnwire.Shutdown, instead have %v", + spew.Sdump(msg)) } // As we're the responder to this shutdown (the other party @@ -477,6 +530,15 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, return nil, false, err } + // If this is a taproot channel, then we'll want to stash the + // remote nonces so we can properly create a new musig + // session for signing. + if c.cfg.Channel.ChanType().IsTaproot() { + c.musigNoncePair.SigningNonce = musig2.Nonces{ + PubNonce: *shutdownMsg.ShutdownNonce, + } + } + chancloserLog.Infof("ChannelPoint(%v): responding to shutdown", c.chanPoint) @@ -494,7 +556,8 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, if chanInitiator { closeSigned, err := c.proposeCloseSigned(c.idealFeeSat) if err != nil { - return nil, false, err + return nil, false, fmt.Errorf("unable to sign "+ + "new co op close offer: %w", err) } msgsToSend = append(msgsToSend, closeSigned) } @@ -534,6 +597,15 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // closing transaction should look like. c.state = closeFeeNegotiation + // If this is a taproot channel, then we'll want to stash the + // local+remote nonces so we can properly create a new musig + // session for signing. + if c.cfg.Channel.ChanType().IsTaproot() { + c.musigNoncePair.SigningNonce = musig2.Nonces{ + PubNonce: *shutdownMsg.ShutdownNonce, + } + } + chancloserLog.Infof("ChannelPoint(%v): shutdown response received, "+ "entering fee negotiation", c.chanPoint) @@ -543,7 +615,8 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, if c.cfg.Channel.IsInitiator() { closeSigned, err := c.proposeCloseSigned(c.idealFeeSat) if err != nil { - return nil, false, err + return nil, false, fmt.Errorf("unable to sign "+ + "new co op close offer: %w", err) } return []lnwire.Message{closeSigned}, false, nil @@ -563,14 +636,38 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, "instead have %v", spew.Sdump(msg)) } - // We'll compare the proposed total fee, to what we've proposed during - // the negotiations. If it doesn't match any of our prior offers, then - // we'll attempt to ratchet the fee closer to + // We'll compare the proposed total fee, to what we've proposed + // during the negotiations. If it doesn't match any of our + // prior offers, then we'll attempt to ratchet the fee closer + // to remoteProposedFee := closeSignedMsg.FeeSatoshis - if _, ok := c.priorFeeOffers[remoteProposedFee]; !ok { - // We'll now attempt to ratchet towards a fee deemed acceptable by - // both parties, factoring in our ideal fee rate, and the last - // proposed fee by both sides. + + // For taproot channels, since nonces are involved, we can't do + // the existing co-op close negotiation process without going + // to a fully round based model. Rather than do this, we'll + // just accept the very first offer by the initiator. + if c.cfg.Channel.ChanType().IsTaproot() && + !c.cfg.Channel.IsInitiator() { + chancloserLog.Infof("ChannelPoint(%v) accepting "+ + "initiator fee of %v", c.chanPoint, + remoteProposedFee) + + // To auto-accept the initiators proposal, we'll just + // send back a signature w/ the same offer. + closeSigned, err := c.proposeCloseSigned( + remoteProposedFee, + ) + if err != nil { + return nil, false, fmt.Errorf("unable to sign "+ + "new co op close offer: %w", err) + } + + return []lnwire.Message{closeSigned}, false, nil + + } else if _, ok := c.priorFeeOffers[remoteProposedFee]; !ok { + // We'll now attempt to ratchet towards a fee deemed + // acceptable by both parties, factoring in our ideal + // fee rate, and the last proposed fee by both sides. feeProposal := calcCompromiseFee(c.chanPoint, c.idealFeeSat, c.lastFeeProposal, remoteProposedFee, ) @@ -580,17 +677,21 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, c.maxFee) } - // With our new fee proposal calculated, we'll craft a new close - // signed signature to send to the other party so we can continue - // the fee negotiation process. + // With our new fee proposal calculated, we'll craft a + // new close signed signature to send to the other + // party so we can continue the fee negotiation + // process. closeSigned, err := c.proposeCloseSigned(feeProposal) if err != nil { - return nil, false, err + return nil, false, fmt.Errorf("unable to sign "+ + "new co op close offer: %w", err) } - // If the compromise fee doesn't match what the peer proposed, then - // we'll return this latest close signed message so we can continue - // negotiation. + // TODO(roasbeef): need to clean up old musig2 session + + // If the compromise fee doesn't match what the peer + // proposed, then we'll return this latest close signed + // message so we can continue negotiation. if feeProposal != remoteProposedFee { chancloserLog.Debugf("ChannelPoint(%v): close tx fee "+ "disagreement, continuing negotiation", c.chanPoint) @@ -601,32 +702,68 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, chancloserLog.Infof("ChannelPoint(%v) fee of %v accepted, ending "+ "negotiation", c.chanPoint, remoteProposedFee) - // Otherwise, we've agreed on a fee for the closing transaction! We'll - // craft the final closing transaction so we can broadcast it to the - // network. - matchingSig := c.priorFeeOffers[remoteProposedFee].Signature - localSig, err := matchingSig.ToSignature() - if err != nil { - return nil, false, err - } + // Otherwise, we've agreed on a fee for the closing + // transaction! We'll craft the final closing transaction so we + // can broadcast it to the network. + var ( + localSig, remoteSig input.Signature + closeOpts []lnwallet.ChanCloseOpt + err error + ) + matchingSig := c.priorFeeOffers[remoteProposedFee] + if c.cfg.Channel.ChanType().IsTaproot() { + // We'll convert the wire partial signatures into an + // input.Signature compliant struct so we can pass it + // into the final combination function. + localPartialSig := &lnwire.PartialSigWithNonce{ + PartialSig: *matchingSig.PartialSig, + Nonce: c.musigNoncePair.VerificationNonce.PubNonce, + } + remotePartialSig := &lnwire.PartialSigWithNonce{ + PartialSig: *closeSignedMsg.PartialSig, + Nonce: c.musigNoncePair.SigningNonce.PubNonce, + } - remoteSig, err := closeSignedMsg.Signature.ToSignature() - if err != nil { - return nil, false, err + localSig = new(lnwallet.MusigPartialSig).FromWireSig( + localPartialSig, + ) + remoteSig = new(lnwallet.MusigPartialSig).FromWireSig( + remotePartialSig, + ) + + // For taproot channels, we'll need to pass along the + // session so the final combined signature can be + // created. + closeOpts = append( + closeOpts, + lnwallet.WithCoopCloseMusigSession(c.musigSession), + ) + } else { + localSig, err = matchingSig.Signature.ToSignature() + if err != nil { + return nil, false, err + } + remoteSig, err = closeSignedMsg.Signature.ToSignature() + if err != nil { + return nil, false, err + } } closeTx, _, err := c.cfg.Channel.CompleteCooperativeClose( - localSig, remoteSig, c.localDeliveryScript, c.remoteDeliveryScript, - remoteProposedFee, + localSig, remoteSig, c.localDeliveryScript, + c.remoteDeliveryScript, remoteProposedFee, closeOpts..., ) if err != nil { return nil, false, err } c.closingTx = closeTx - // Before publishing the closing tx, we persist it to the database, - // such that it can be republished if something goes wrong. - err = c.cfg.Channel.MarkCoopBroadcasted(closeTx, c.locallyInitiated) + // Before publishing the closing tx, we persist it to the + // database, such that it can be republished if something goes + // wrong. + err = c.cfg.Channel.MarkCoopBroadcasted( + closeTx, c.locallyInitiated, + ) if err != nil { return nil, false, err } @@ -680,29 +817,79 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message, // transaction for a channel based on the prior fee negotiations and our current // compromise fee. func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSigned, error) { + var ( + closeOpts []lnwallet.ChanCloseOpt + err error + ) + + // If this is a taproot channel, then we'll include the musig session + // generated for the next co-op close negotiation round. + if c.cfg.Channel.ChanType().IsTaproot() { + localKey, remoteKey := c.cfg.Channel.MultiSigKeys() + c.musigSession = lnwallet.NewPartialMusigSession( + c.musigNoncePair.VerificationNonce, localKey, remoteKey, + c.cfg.Signer, c.cfg.Channel.FundingTxOut(), false, + ) + err := c.musigSession.FinalizeSession( + c.musigNoncePair.SigningNonce, + ) + if err != nil { + return nil, err + } + + closeOpts = append( + closeOpts, + lnwallet.WithCoopCloseMusigSession(c.musigSession), + ) + } + rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal( fee, c.localDeliveryScript, c.remoteDeliveryScript, + closeOpts..., ) if err != nil { 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. c.lastFeeProposal = fee - parsedSig, err := lnwire.NewSigFromSignature(rawSig) - if err != nil { - 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. + var ( + parsedSig lnwire.Sig + partialSig *lnwire.PartialSigWithNonce + ) + if c.cfg.Channel.ChanType().IsTaproot() { + musig, ok := rawSig.(*lnwallet.MusigPartialSig) + if !ok { + return nil, fmt.Errorf("expected MusigPartialSig, "+ + "got %T", rawSig) + } + + partialSig = musig.ToWireSig() + } else { + 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 - // closure process. + // 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 closure process. closeSignedMsg := lnwire.NewClosingSigned(c.cid, fee, parsedSig) + // For musig2 channels, the main sig is blank, and instead we'll send + // over a partial signature which'll be combine donce our offer is + // accepted. + if partialSig != nil { + closeSignedMsg.PartialSig = &partialSig.PartialSig + } + // We'll also save this close signed, in the case that the remote party // accepts our offer. This way, we don't have to re-sign. c.priorFeeOffers[fee] = closeSignedMsg diff --git a/lnwallet/chanfunding/assembler.go b/lnwallet/chanfunding/assembler.go index 4e6e62d259f..7611dba9868 100644 --- a/lnwallet/chanfunding/assembler.go +++ b/lnwallet/chanfunding/assembler.go @@ -78,6 +78,11 @@ type Request struct { // ChangeAddr is a closure that will provide the Assembler with a // change address for the funding transaction if needed. ChangeAddr func() (btcutil.Address, error) + + // Musig2 is true, then musig2 will be used to generate teh funding + // output. By definition, this'll also use segwit v1 (taproot) for the + // funding output. + Musig2 bool } // Intent is returned by an Assembler and represents the base functionality the diff --git a/lnwallet/chanfunding/canned_assembler.go b/lnwallet/chanfunding/canned_assembler.go index 603d90fe273..cb9472f5172 100644 --- a/lnwallet/chanfunding/canned_assembler.go +++ b/lnwallet/chanfunding/canned_assembler.go @@ -35,6 +35,11 @@ type ShimIntent struct { // a normal channel. Until this height, it's considered frozen, so it // can only be cooperatively closed by the responding party. thawHeight uint32 + + // musig2 determines if the funding output should use musig2 to + // generate an aggregate key to use as the taproot-native multi-sig + // output. + musig2 bool } // FundingOutput returns the witness script, and the output that creates the @@ -48,6 +53,19 @@ func (s *ShimIntent) FundingOutput() ([]byte, *wire.TxOut, error) { } totalAmt := s.localFundingAmt + s.remoteFundingAmt + + // If musig2 is active, then we'll return a single aggregated key + // rather than using the "existing" funding script. + if s.musig2 { + // Similar to the existing p2wsh script, we'll always ensure + // the keys are sorted before use. + return input.GenTaprootFundingScript( + s.localKey.PubKey, + s.remoteKey, + int64(totalAmt), + ) + } + return input.GenFundingPkScript( s.localKey.PubKey.SerializeCompressed(), s.remoteKey.SerializeCompressed(), @@ -171,13 +189,20 @@ type CannedAssembler struct { // a normal channel. Until this height, it's considered frozen, so it // can only be cooperatively closed by the responding party. thawHeight uint32 + + // musig2 determines if the funding output should use musig2 to + // generate an aggregate key to use as the taproot-native multi-sig + // output. + musig2 bool } // NewCannedAssembler creates a new CannedAssembler from the material required // to construct a funding output and channel point. +// +// TODO(roasbeef): pass in chan type instead? func NewCannedAssembler(thawHeight uint32, chanPoint wire.OutPoint, fundingAmt btcutil.Amount, localKey *keychain.KeyDescriptor, - remoteKey *btcec.PublicKey, initiator bool) *CannedAssembler { + remoteKey *btcec.PublicKey, initiator, musig2 bool) *CannedAssembler { return &CannedAssembler{ initiator: initiator, @@ -186,6 +211,7 @@ func NewCannedAssembler(thawHeight uint32, chanPoint wire.OutPoint, fundingAmt: fundingAmt, chanPoint: chanPoint, thawHeight: thawHeight, + musig2: musig2, } } @@ -207,6 +233,7 @@ func (c *CannedAssembler) ProvisionChannel(req *Request) (Intent, error) { remoteKey: c.remoteKey, chanPoint: &c.chanPoint, thawHeight: c.thawHeight, + musig2: c.musig2, } if c.initiator { diff --git a/lnwallet/chanfunding/psbt_assembler.go b/lnwallet/chanfunding/psbt_assembler.go index 8632b175d91..5f0bb88e7b8 100644 --- a/lnwallet/chanfunding/psbt_assembler.go +++ b/lnwallet/chanfunding/psbt_assembler.go @@ -523,6 +523,7 @@ func (p *PsbtAssembler) ProvisionChannel(req *Request) (Intent, error) { intent := &PsbtIntent{ ShimIntent: ShimIntent{ localFundingAmt: p.fundingAmt, + musig2: req.Musig2, }, State: PsbtShimRegistered, BasePsbt: p.basePsbt, diff --git a/lnwallet/chanfunding/wallet_assembler.go b/lnwallet/chanfunding/wallet_assembler.go index 6d9c1974e0c..b3946aecccd 100644 --- a/lnwallet/chanfunding/wallet_assembler.go +++ b/lnwallet/chanfunding/wallet_assembler.go @@ -354,6 +354,7 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) { ShimIntent: ShimIntent{ localFundingAmt: localContributionAmt, remoteFundingAmt: r.RemoteAmt, + musig2: r.Musig2, }, InputCoins: selectedCoins, coinLocker: w.cfg.CoinLocker, diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 16c412f3297..369bd1046e8 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -13,6 +13,7 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/txsort" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -24,8 +25,10 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/shachain" ) var ( @@ -229,7 +232,7 @@ func (u updateType) String() string { // the original added HTLC. // // TODO(roasbeef): LogEntry interface?? -// * need to separate attrs for cancel/add/settle/feeupdate +// - need to separate attrs for cancel/add/settle/feeupdate type PaymentDescriptor struct { // RHash is the payment hash for this HTLC. The HTLC can be settled iff // the preimage to this hash is presented. @@ -315,7 +318,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 @@ -1038,8 +1041,8 @@ func (s *commitmentChain) hasUnackedCommitment() bool { // // TODO(roasbeef): create lightning package, move commitment and update to // package? -// * also move state machine, separate from lnwallet package -// * possible embed updateLog within commitmentChain. +// - also move state machine, separate from lnwallet package +// - possible embed updateLog within commitmentChain. type updateLog struct { // logIndex is a monotonically increasing integer that tracks the total // number of update entries ever applied to the log. When sending new @@ -1235,18 +1238,18 @@ func compactLogs(ourLog, theirLog *updateLog, // preimages in order to populate their revocation window for the remote party. // // The state machine has for main methods: -// * .SignNextCommitment() -// * Called one one wishes to sign the next commitment, either initiating a -// new state update, or responding to a received commitment. -// * .ReceiveNewCommitment() -// * Called upon receipt of a new commitment from the remote party. If the -// new commitment is valid, then a revocation should immediately be -// generated and sent. -// * .RevokeCurrentCommitment() -// * Revokes the current commitment. Should be called directly after -// receiving a new commitment. -// * .ReceiveRevocation() -// * Processes a revocation from the remote party. If successful creates a +// - .SignNextCommitment() +// - Called one one wishes to sign the next commitment, either initiating a +// new state update, or responding to a received commitment. +// - .ReceiveNewCommitment() +// - Called upon receipt of a new commitment from the remote party. If the +// new commitment is valid, then a revocation should immediately be +// generated and sent. +// - .RevokeCurrentCommitment() +// - Revokes the current commitment. Should be called directly after +// receiving a new commitment. +// - .ReceiveRevocation() +// - Processes a revocation from the remote party. If successful creates a // new defacto broadcastable state. // // See the individual comments within the above methods for further details. @@ -1312,9 +1315,46 @@ type LightningChannel struct { // log is a channel-specific logging instance. log btclog.Logger + // musigSessions... + musigSessions *MusigPairSession + + // pendingVerificationNonce... + pendingVerificationNonce *musig2.Nonces + + // fundingOutput... + fundingOutput wire.TxOut + sync.RWMutex } +// ChannelOpt... +type ChannelOpt func(*channelOpts) + +// WithLocalMusigNonces... +func WithLocalMusigNonces(nonce *musig2.Nonces) ChannelOpt { + return func(o *channelOpts) { + o.localNonce = nonce + } +} + +// WithRemoteMusigNonces... +func WithRemoteMusigNonces(nonces *musig2.Nonces) ChannelOpt { + return func(o *channelOpts) { + o.remoteNonce = nonces + } +} + +// channelOpts... +type channelOpts struct { + localNonce *musig2.Nonces + remoteNonce *musig2.Nonces +} + +// defaultChannelOpts... +func defaultChannelOpts() *channelOpts { + return &channelOpts{} +} + // NewLightningChannel creates a new, active payment channel given an // implementation of the chain notifier, channel database, and the current // settled channel state. Throughout state transitions, then channel will @@ -1322,7 +1362,12 @@ type LightningChannel struct { // manner. func NewLightningChannel(signer input.Signer, state *channeldb.OpenChannel, - sigPool *SigPool) (*LightningChannel, error) { + sigPool *SigPool, chanOpts ...ChannelOpt) (*LightningChannel, error) { + + opts := defaultChannelOpts() + for _, optFunc := range chanOpts { + optFunc(opts) + } localCommit := state.LocalCommitment remoteCommit := state.RemoteCommitment @@ -1355,6 +1400,18 @@ func NewLightningChannel(signer input.Signer, log: build.NewPrefixLog(logPrefix, walletLog), } + // At this point, we mwy already have of nonces that were passed in, so + // we'll check that now as this lets us skip some steps later. + if opts.localNonce != nil { + lc.pendingVerificationNonce = opts.localNonce + } + if lc.pendingVerificationNonce != nil && opts.remoteNonce != nil { + err := lc.InitRemoteMusigNonces(opts.remoteNonce) + if err != nil { + return nil, err + } + } + // With the main channel struct reconstructed, we'll now restore the // commitment state in memory and also the update logs themselves. err := lc.restoreCommitState(&localCommit, &remoteCommit) @@ -1375,29 +1432,48 @@ func NewLightningChannel(signer input.Signer, // createSignDesc derives the SignDescriptor for commitment transactions from // other fields on the LightningChannel. func (lc *LightningChannel) createSignDesc() error { - localKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey. - SerializeCompressed() - remoteKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. - SerializeCompressed() - multiSigScript, err := input.GenMultiSigScript(localKey, remoteKey) - if err != nil { - return err + var ( + fundingPkScript, multiSigScript []byte + err error + ) + chanState := lc.channelState + if chanState.ChanType.IsTaproot() { + fundingPkScript, _, err = input.GenTaprootFundingScript( + chanState.LocalChanCfg.MultiSigKey.PubKey, + chanState.RemoteChanCfg.MultiSigKey.PubKey, + int64(lc.channelState.Capacity), + ) + if err != nil { + return err + } + } else { + localKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey. + SerializeCompressed() + remoteKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. + SerializeCompressed() + + multiSigScript, err := input.GenMultiSigScript(localKey, remoteKey) + if err != nil { + return err + } + + fundingPkScript, err = input.WitnessScriptHash(multiSigScript) + if err != nil { + return err + } } - fundingPkScript, err := input.WitnessScriptHash(multiSigScript) - if err != nil { - return err + lc.fundingOutput = wire.TxOut{ + PkScript: fundingPkScript, + Value: int64(lc.channelState.Capacity), } lc.signDesc = &input.SignDescriptor{ KeyDesc: lc.channelState.LocalChanCfg.MultiSigKey, WitnessScript: multiSigScript, - Output: &wire.TxOut{ - PkScript: fundingPkScript, - Value: int64(lc.channelState.Capacity), - }, - HashType: txscript.SigHashAll, - InputIndex: 0, + Output: &lc.fundingOutput, + HashType: txscript.SigHashAll, + InputIndex: 0, } return nil @@ -2337,7 +2413,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, isRemoteInitiator := !chanState.IsInitiator ourScript, ourDelay, err := CommitScriptToRemote( chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, - leaseExpiry, + leaseExpiry, keyRing.CombinedFundingKey, ) if err != nil { return nil, err @@ -3173,7 +3249,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, // If the HTLC isn't dust, then we'll create an empty sign job // to add to the batch momentarily. - sigJob := SignJob{} + var sigJob SignJob sigJob.Cancel = cancelChan sigJob.Resp = make(chan SignJobResp, 1) @@ -3200,21 +3276,37 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, return nil, nil, err } + // Construct a full hash cache as we may be signing a segwit v1 + // sighash. + txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex] + prevFetcher := txscript.NewCannedPrevOutputFetcher( + txOut.PkScript, int64(htlc.Amount.ToSatoshis()), + ) + hashCache := txscript.NewTxSigHashes(sigJob.Tx, prevFetcher) + // Finally, we'll generate a sign descriptor to generate a // signature to give to the remote party for this commitment // transaction. Note we use the raw HTLC amount. - txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex] sigJob.SignDesc = input.SignDescriptor{ - KeyDesc: localChanCfg.HtlcBasePoint, - SingleTweak: keyRing.LocalHtlcKeyTweak, - WitnessScript: htlc.theirWitnessScript, - Output: txOut, - HashType: sigHashType, - SigHashes: input.NewTxSigHashesV0Only(sigJob.Tx), - InputIndex: 0, + KeyDesc: localChanCfg.HtlcBasePoint, + SingleTweak: keyRing.LocalHtlcKeyTweak, + WitnessScript: htlc.theirWitnessScript, + Output: txOut, + PrevOutputFetcher: prevFetcher, + HashType: sigHashType, + SigHashes: hashCache, + InputIndex: 0, } sigJob.OutputIndex = htlc.remoteOutputIndex + // If this is a taproot channel, then we'll need to set the + // method type to ensure we generate a valid signature. + if chanType.IsTaproot() { + sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod + } + + walletLog.Infof("sign desc second level: %v", spew.Sdump(sigJob.SignDesc)) + sigBatch = append(sigBatch, sigJob) } for _, htlc := range remoteCommitView.outgoingHTLCs { @@ -3254,21 +3346,35 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing, return nil, nil, err } + // Construct a full hash cache as we may be signing a segwit v1 + // sighash. + txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex] + prevFetcher := txscript.NewCannedPrevOutputFetcher( + txOut.PkScript, int64(htlc.Amount.ToSatoshis()), + ) + hashCache := txscript.NewTxSigHashes(sigJob.Tx, prevFetcher) + // Finally, we'll generate a sign descriptor to generate a // signature to give to the remote party for this commitment // transaction. Note we use the raw HTLC amount. - txOut := remoteCommitView.txn.TxOut[htlc.remoteOutputIndex] sigJob.SignDesc = input.SignDescriptor{ - KeyDesc: localChanCfg.HtlcBasePoint, - SingleTweak: keyRing.LocalHtlcKeyTweak, - WitnessScript: htlc.theirWitnessScript, - Output: txOut, - HashType: sigHashType, - SigHashes: input.NewTxSigHashesV0Only(sigJob.Tx), - InputIndex: 0, + KeyDesc: localChanCfg.HtlcBasePoint, + SingleTweak: keyRing.LocalHtlcKeyTweak, + WitnessScript: htlc.theirWitnessScript, + Output: txOut, + PrevOutputFetcher: prevFetcher, + HashType: sigHashType, + SigHashes: hashCache, + InputIndex: 0, } sigJob.OutputIndex = htlc.remoteOutputIndex + // If this is a taproot channel, then we'll need to set the + // method type to ensure we generate a valid signature. + if chanType.IsTaproot() { + sigJob.SignDesc.SignMethod = input.TaprootScriptSpendSignMethod + } + sigBatch = append(sigBatch, sigJob) } @@ -3661,6 +3767,28 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, return nil } +// CommitSig... +type CommitSigs struct { + // CommitSig... + CommitSig lnwire.Sig + + // HtlcSigs... + HtlcSigs []lnwire.Sig + + // PartialSig... + PartialSig *lnwire.PartialSigWithNonce +} + +// NewCommitState wraps the various signatures needed to properly +// propose/accept a new commitment state. This includes the signer's nonce for +// musig2 channels. +type NewCommitState struct { + *CommitSigs + + // PendingHTLCs... + PendingHTLCs []channeldb.HTLC +} + // SignNextCommitment signs a new commitment which includes any previous // unsettled HTLCs, any new HTLCs, and any modifications to prior HTLCs // committed in previous commitment updates. Signing a new commitment @@ -3672,8 +3800,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, // any). The HTLC signatures are sorted according to the BIP 69 order of the // HTLC's on the commitment transaction. Finally, the new set of pending HTLCs // for the remote party's commitment are also returned. -func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, - []channeldb.HTLC, error) { +func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { lc.Lock() defer lc.Unlock() @@ -3687,8 +3814,9 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, } var ( - sig lnwire.Sig - htlcSigs []lnwire.Sig + sig lnwire.Sig + partialSig *lnwire.PartialSigWithNonce + htlcSigs []lnwire.Sig ) // If we're awaiting for an ACK to a commitment signature, or if we @@ -3700,7 +3828,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, if unacked || commitPoint == nil { lc.log.Tracef("waiting for remote ack=%v, nil "+ "RemoteNextRevocation: %v", unacked, commitPoint == nil) - return sig, htlcSigs, nil, ErrNoWindow + return nil, ErrNoWindow } // Determine the last update on the remote log that has been locked in. @@ -3715,7 +3843,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, remoteACKedIndex, lc.localUpdateLog.logIndex, true, nil, nil, ) if err != nil { - return sig, htlcSigs, nil, err + return nil, err } // Grab the next commitment point for the remote party. This will be @@ -3738,7 +3866,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, remoteACKedIndex, remoteHtlcIndex, keyRing, ) if err != nil { - return sig, htlcSigs, nil, err + return nil, err } lc.log.Tracef("extending remote chain to height %v, "+ @@ -3769,23 +3897,45 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, &lc.channelState.RemoteChanCfg, newCommitView, ) if err != nil { - return sig, htlcSigs, nil, err + return nil, err } lc.sigPool.SubmitSignBatch(sigBatch) // While the jobs are being carried out, we'll Sign their version of // the new commitment transaction while we're waiting for the rest of // the HTLC signatures to be processed. - lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(newCommitView.txn) - rawSig, err := lc.Signer.SignOutputRaw(newCommitView.txn, lc.signDesc) - if err != nil { - close(cancelChan) - return sig, htlcSigs, nil, err - } - sig, err = lnwire.NewSigFromSignature(rawSig) - if err != nil { - close(cancelChan) - return sig, htlcSigs, nil, err + // + // TODO(roasbeef): abstract into CommitSigner interface? + if lc.channelState.ChanType.IsTaproot() { + // In this case, we'll send out a partial signature as this is + // a musig2 channel. The encoded normal ECDSA signature will be + // just blank. + remoteSession := lc.musigSessions.RemoteSession + musig, err := remoteSession.SignCommit( + newCommitView.txn, + ) + if err != nil { + close(cancelChan) + return nil, err + } + + partialSig = musig.ToWireSig() + } else { + lc.signDesc.SigHashes = input.NewTxSigHashesV0Only( + newCommitView.txn, + ) + rawSig, err := lc.Signer.SignOutputRaw( + newCommitView.txn, lc.signDesc, + ) + if err != nil { + close(cancelChan) + return nil, err + } + sig, err = lnwire.NewSigFromSignature(rawSig) + if err != nil { + close(cancelChan) + return nil, err + } } // We'll need to send over the signatures to the remote party in the @@ -3805,7 +3955,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, // jobs. if jobResp.Err != nil { close(cancelChan) - return sig, htlcSigs, nil, jobResp.Err + return nil, jobResp.Err } htlcSigs = append(htlcSigs, jobResp.Sig) @@ -3816,11 +3966,11 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, // can retransmit it if necessary. commitDiff, err := lc.createCommitDiff(newCommitView, sig, htlcSigs) if err != nil { - return sig, htlcSigs, nil, err + return nil, err } err = lc.channelState.AppendRemoteCommitChain(commitDiff) if err != nil { - return sig, htlcSigs, nil, err + return nil, err } // TODO(roasbeef): check that one eclair bug @@ -3831,7 +3981,14 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, // latest commitment update. lc.remoteCommitChain.addCommitment(newCommitView) - return sig, htlcSigs, commitDiff.Commitment.Htlcs, nil + return &NewCommitState{ + CommitSigs: &CommitSigs{ + CommitSig: sig, + HtlcSigs: htlcSigs, + PartialSig: partialSig, + }, + PendingHTLCs: commitDiff.Commitment.Htlcs, + }, nil } // ProcessChanSyncMsg processes a ChannelReestablish message sent by the remote @@ -3844,10 +4001,10 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, // // One of two message sets will be returned: // -// * CommitSig+Updates: if we have a pending remote commit which they claim to -// have not received -// * RevokeAndAck: if we sent a revocation message that they claim to have -// not received +// - CommitSig+Updates: if we have a pending remote commit which they claim to +// have not received +// - RevokeAndAck: if we sent a revocation message that they claim to have +// not received // // If we detect a scenario where we need to send a CommitSig+Updates, this // method also returns two sets channeldb.CircuitKeys identifying the circuits @@ -3859,6 +4016,8 @@ func (lc *LightningChannel) ProcessChanSyncMsg( msg *lnwire.ChannelReestablish) ([]lnwire.Message, []channeldb.CircuitKey, []channeldb.CircuitKey, error) { + // TODO(roasbeef): need to replace w/ received nonces + // Now we'll examine the state we have, vs what was contained in the // chain sync message. If we're de-synchronized, then we'll send a // batch of messages which when applied will kick start the chain @@ -3989,19 +4148,22 @@ func (lc *LightningChannel) ProcessChanSyncMsg( // revocation, but also initiate a state transition to re-sync // them. if lc.OweCommitment() { - commitSig, htlcSigs, _, err := lc.SignNextCommitment() + newCommit, err := lc.SignNextCommitment() switch { // If we signed this state, then we'll accumulate // another update to send over. case err == nil: - updates = append(updates, &lnwire.CommitSig{ + commitSig := &lnwire.CommitSig{ ChanID: lnwire.NewChanIDFromOutPoint( &lc.channelState.FundingOutpoint, ), - CommitSig: commitSig, - HtlcSigs: htlcSigs, - }) + CommitSig: newCommit.CommitSig, + HtlcSigs: newCommit.HtlcSigs, + PartialSig: newCommit.PartialSig, + } + + updates = append(updates, commitSig) // If we get a failure due to not knowing their next // point, then this is fine as they'll either send @@ -4081,6 +4243,9 @@ func (lc *LightningChannel) ProcessChanSyncMsg( // With the batch of updates accumulated, we'll now re-send the // original CommitSig message required to re-sync their remote // commitment chain with our local version of their chain. + // + // TODO(roasbeef): need to re-sign commitment states w/ + // fresh nonce commitUpdates = append(commitUpdates, commitDiff.CommitSig) // NOTE: If a revocation is not owed, then updates is empty. @@ -4275,7 +4440,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, var ( htlcIndex uint64 sigHash func() ([]byte, error) - sig *ecdsa.Signature + sig input.Signature err error ) @@ -4309,11 +4474,30 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, return nil, err } + htlcAmt := int64(htlc.Amount.ToSatoshis()) + + if chanType.IsTaproot() { + // TODO(roasbeef): add abstraction in front + prevFetcher := txscript.NewCannedPrevOutputFetcher( + htlc.ourPkScript, htlcAmt, + ) + hashCache := txscript.NewTxSigHashes( + successTx, prevFetcher, + ) + tapLeaf := txscript.NewBaseTapLeaf( + htlc.ourWitnessScript, + ) + return txscript.CalcTapscriptSignaturehash( + hashCache, sigHashType, successTx, 0, + prevFetcher, tapLeaf, + ) + } + hashCache := input.NewTxSigHashesV0Only(successTx) sigHash, err := txscript.CalcWitnessSigHash( htlc.ourWitnessScript, hashCache, sigHashType, successTx, 0, - int64(htlc.Amount.ToSatoshis()), + htlcAmt, ) if err != nil { return nil, err @@ -4328,6 +4512,15 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, "signatures") } + if chanType.IsTaproot() { + // TODO(roasbeef): remove, temp hack + // * when sigs get encoded on the wire, they + // default back to sigTypeECDSA, so we need to + // force schnorr here to get the proper sig + // below for validation + htlcSigs[i].ForceSchnorr() + } + // With the sighash generated, we'll also store the // signature so it can be written to disk if this state // is valid. @@ -4335,6 +4528,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, if err != nil { return nil, err } + htlc.sig = sig // Otherwise, if this is an outgoing HTLC, then we'll need to @@ -4364,11 +4558,30 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, return nil, err } + htlcAmt := int64(htlc.Amount.ToSatoshis()) + + if chanType.IsTaproot() { + // TODO(roasbeef): add abstraction in front + prevFetcher := txscript.NewCannedPrevOutputFetcher( + htlc.ourPkScript, htlcAmt, + ) + hashCache := txscript.NewTxSigHashes( + timeoutTx, prevFetcher, + ) + tapLeaf := txscript.NewBaseTapLeaf( + htlc.ourWitnessScript, + ) + return txscript.CalcTapscriptSignaturehash( + hashCache, sigHashType, timeoutTx, 0, + prevFetcher, tapLeaf, + ) + } + hashCache := input.NewTxSigHashesV0Only(timeoutTx) sigHash, err := txscript.CalcWitnessSigHash( htlc.ourWitnessScript, hashCache, sigHashType, timeoutTx, 0, - int64(htlc.Amount.ToSatoshis()), + htlcAmt, ) if err != nil { return nil, err @@ -4383,6 +4596,11 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, "signatures") } + if chanType.IsTaproot() { + // TODO(roasbeef): remove, temp hack + htlcSigs[i].ForceSchnorr() + } + // With the sighash generated, we'll also store the // signature so it can be written to disk if this state // is valid. @@ -4390,6 +4608,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, if err != nil { return nil, err } + htlc.sig = sig default: @@ -4480,8 +4699,7 @@ var _ error = (*InvalidCommitSigError)(nil) // to our local commitment chain. Once we send a revocation for our prior // state, then this newly added commitment becomes our current accepted channel // state. -func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, - htlcSigs []lnwire.Sig) error { +func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { lc.Lock() defer lc.Unlock() @@ -4553,30 +4771,15 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, }), ) - // Construct the sighash of the commitment transaction corresponding to - // this newly proposed state update. - localCommitTx := localCommitmentView.txn - multiSigScript := lc.signDesc.WitnessScript - hashCache := input.NewTxSigHashesV0Only(localCommitTx) - sigHash, err := txscript.CalcWitnessSigHash( - multiSigScript, hashCache, txscript.SigHashAll, - localCommitTx, 0, int64(lc.channelState.Capacity), - ) - if err != nil { - // TODO(roasbeef): fetchview has already mutated the HTLCs... - // * need to either roll-back, or make pure - return err - } - // As an optimization, we'll generate a series of jobs for the worker - // pool to verify each of the HTLc signatures presented. Once + // pool to verify each of the HTLC signatures presented. Once // generated, we'll submit these jobs to the worker pool. var leaseExpiry uint32 if lc.channelState.ChanType.HasLeaseExpiration() { leaseExpiry = lc.channelState.ThawHeight } verifyJobs, err := genHtlcSigValidationJobs( - localCommitmentView, keyRing, htlcSigs, + localCommitmentView, keyRing, commitSigs.HtlcSigs, lc.channelState.ChanType, lc.channelState.IsInitiator, leaseExpiry, &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, @@ -4588,31 +4791,86 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, cancelChan := make(chan struct{}) verifyResps := lc.sigPool.SubmitVerifyBatch(verifyJobs, cancelChan) + localCommitTx := localCommitmentView.txn + multiSigScript := lc.signDesc.WitnessScript + prevFetcher := txscript.NewCannedPrevOutputFetcher( + multiSigScript, int64(lc.channelState.Capacity), + ) + hashCache := txscript.NewTxSigHashes(localCommitTx, prevFetcher) + // While the HTLC verification jobs are proceeding asynchronously, // we'll ensure that the newly constructed commitment state has a valid // signature. - verifyKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey + // + // To do that we'll, construct the sighash of the commitment + // transaction corresponding to this newly proposed state update. If + // this is a taproot channel, then in order to validate the sighash, + // we'll need to call into the relevant tapscript methods. + if lc.channelState.ChanType.IsTaproot() { + localSession := lc.musigSessions.LocalSession + + // As we want to ensure we never write nonces to disk, we'll + // use the shachain state to generate a nonce for our next + // local state. Similar to generateRevocation, we do height + 2 + // (next height + 1) here, as this is for the _next_ local + // state, and we're about to accept height + 1. + localCtrNonce := WithLocalCounterNonce( + nextHeight+1, lc.channelState.RevocationProducer, + ) + nextVerificationNonce, err := localSession.VerifyCommitSig( + localCommitTx, commitSigs.PartialSig, localCtrNonce, + ) + if err != nil { + // TODO(roasbeef): new InvalidPartialCommitSigError + return err + } - cSig, err := commitSig.ToSignature() - if err != nil { - return err - } - if !cSig.Verify(sigHash, verifyKey) { - close(cancelChan) + // Now that we have the next verification nonce for our local + // session, we'll refresh it to yield a new session we'll use + // for the next incoming signature. + newLocalSession, err := lc.musigSessions.LocalSession.Refresh( + nextVerificationNonce, + ) + if err != nil { + return err + } + lc.musigSessions.LocalSession = newLocalSession + + } else { + sigHash, err := txscript.CalcWitnessSigHash( + multiSigScript, hashCache, txscript.SigHashAll, + localCommitTx, 0, int64(lc.channelState.Capacity), + ) + + verifyKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey + + cSig, err := commitSigs.CommitSig.ToSignature() + if err != nil { + return err + } + if !cSig.Verify(sigHash, verifyKey) { + close(cancelChan) - // If we fail to validate their commitment signature, we'll - // generate a special error to send over the protocol. We'll - // include the exact signature and commitment we failed to - // verify against in order to aide debugging. - var txBytes bytes.Buffer - localCommitTx.Serialize(&txBytes) - return &InvalidCommitSigError{ - commitHeight: nextHeight, - commitSig: commitSig.ToSignatureBytes(), - sigHash: sigHash, - commitTx: txBytes.Bytes(), + // If we fail to validate their commitment signature, + // we'll generate a special error to send over the + // protocol. We'll include the exact signature and + // commitment we failed to verify against in order to + // aide debugging. + var txBytes bytes.Buffer + localCommitTx.Serialize(&txBytes) + return &InvalidCommitSigError{ + commitHeight: nextHeight, + commitSig: commitSigs.CommitSig.ToSignatureBytes(), + sigHash: sigHash, + commitTx: txBytes.Bytes(), + } } } + if err != nil { + // TODO(roasbeef): fetchview has already mutated the HTLCs... + // * need to either roll-back, or make pure + return err + } // With the primary commitment transaction validated, we'll check each // of the HTLC validation jobs. @@ -4647,8 +4905,21 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig, } // The signature checks out, so we can now add the new commitment to - // our local commitment chain. - localCommitmentView.sig = commitSig.ToSignatureBytes() + // our local commitment chain. For regular channels, we can just + // serialize the ECDSA sig. For taproot channels, we'll serialize the + // partial sig that includes the nonce that was used for signing. + if lc.channelState.ChanType.IsTaproot() { + var sigBytes [lnwire.PartialSigWithNonceLen]byte + b := bytes.NewBuffer(sigBytes[0:0]) + if err := commitSigs.PartialSig.Encode(b); err != nil { + return err + } + + localCommitmentView.sig = sigBytes[:] + } else { + localCommitmentView.sig = commitSigs.CommitSig.ToSignatureBytes() + } + lc.localCommitChain.addCommitment(localCommitmentView) return nil @@ -4825,6 +5096,17 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, []c &lc.channelState.FundingOutpoint, ) + // If this is a taproot channel, We've now accepted+revoked a new + // commitment, so we'll send the remote party another verification + // nonce they can use to generate new commitments. + if lc.channelState.ChanType.IsTaproot() { + localSession := lc.musigSessions.LocalSession + nextVerificationNonce := localSession.VerificationNonce() + revocationMsg.LocalNonce = (*lnwire.Musig2Nonce)( + &nextVerificationNonce.PubNonce, + ) + } + return revocationMsg, newCommitment.Htlcs, nil } @@ -4836,14 +5118,14 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, []c // commitment, and a log compaction is attempted. // // The returned values correspond to: -// 1. The forwarding package corresponding to the remote commitment height -// that was revoked. -// 2. The PaymentDescriptor of any Add HTLCs that were locked in by this -// revocation. -// 3. The PaymentDescriptor of any Settle/Fail HTLCs that were locked in by -// this revocation. -// 4. The set of HTLCs present on the current valid commitment transaction -// for the remote party. +// 1. The forwarding package corresponding to the remote commitment height +// that was revoked. +// 2. The PaymentDescriptor of any Add HTLCs that were locked in by this +// revocation. +// 3. The PaymentDescriptor of any Settle/Fail HTLCs that were locked in by +// this revocation. +// 4. The set of HTLCs present on the current valid commitment transaction +// for the remote party. func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( *channeldb.FwdPkg, []*PaymentDescriptor, []*PaymentDescriptor, []channeldb.HTLC, error) { @@ -5053,6 +5335,20 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) ( return nil, nil, nil, nil, err } + // Now that we have a new verification nonce from them, we can refresh + // our remote musig2 session which allows us to create another state. + if lc.channelState.ChanType.IsTaproot() { + newRemoteSession, err := lc.musigSessions.RemoteSession.Refresh( + &musig2.Nonces{ + PubNonce: *revMsg.LocalNonce, + }, + ) + if err != nil { + return nil, nil, nil, nil, err + } + lc.musigSessions.RemoteSession = newRemoteSession + } + // At this point, the revocation has been accepted, and we've rotated // the current revocation key+hash for the remote party. Therefore we // sync now to ensure the revocation producer state is consistent with @@ -5377,20 +5673,21 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64, err // is invalid, an error is returned. // // The additional arguments correspond to: -// * sourceRef: specifies the location of the Add HTLC within a forwarding -// package that this HTLC is settling. Every Settle fails exactly one Add, -// so this should never be empty in practice. // -// * destRef: specifies the location of the Settle HTLC within another -// channel's forwarding package. This value can be nil if the corresponding -// Add HTLC was never locked into an outgoing commitment txn, or this -// HTLC does not originate as a response from the peer on the outgoing -// link, e.g. on-chain resolutions. +// - sourceRef: specifies the location of the Add HTLC within a forwarding +// package that this HTLC is settling. Every Settle fails exactly one Add, +// so this should never be empty in practice. +// +// - destRef: specifies the location of the Settle HTLC within another +// channel's forwarding package. This value can be nil if the corresponding +// Add HTLC was never locked into an outgoing commitment txn, or this +// HTLC does not originate as a response from the peer on the outgoing +// link, e.g. on-chain resolutions. // -// * closeKey: identifies the circuit that should be deleted after this Settle -// HTLC is included in a commitment txn. This value should only be nil if -// the HTLC was settled locally before committing a circuit to the circuit -// map. +// - closeKey: identifies the circuit that should be deleted after this Settle +// HTLC is included in a commitment txn. This value should only be nil if +// the HTLC was settled locally before committing a circuit to the circuit +// map. // // NOTE: It is okay for sourceRef, destRef, and closeKey to be nil when unit // testing the wallet. @@ -5487,20 +5784,21 @@ func (lc *LightningChannel) ReceiveHTLCSettle(preimage [32]byte, htlcIndex uint6 // _incoming_ HTLC. // // The additional arguments correspond to: -// * sourceRef: specifies the location of the Add HTLC within a forwarding -// package that this HTLC is failing. Every Fail fails exactly one Add, so -// this should never be empty in practice. // -// * destRef: specifies the location of the Fail HTLC within another channel's -// forwarding package. This value can be nil if the corresponding Add HTLC -// was never locked into an outgoing commitment txn, or this HTLC does not -// originate as a response from the peer on the outgoing link, e.g. -// on-chain resolutions. +// - sourceRef: specifies the location of the Add HTLC within a forwarding +// package that this HTLC is failing. Every Fail fails exactly one Add, so +// this should never be empty in practice. // -// * closeKey: identifies the circuit that should be deleted after this Fail -// HTLC is included in a commitment txn. This value should only be nil if -// the HTLC was failed locally before committing a circuit to the circuit -// map. +// - destRef: specifies the location of the Fail HTLC within another channel's +// forwarding package. This value can be nil if the corresponding Add HTLC +// was never locked into an outgoing commitment txn, or this HTLC does not +// originate as a response from the peer on the outgoing link, e.g. +// on-chain resolutions. +// +// - closeKey: identifies the circuit that should be deleted after this Fail +// HTLC is included in a commitment txn. This value should only be nil if +// the HTLC was failed locally before committing a circuit to the circuit +// map. // // NOTE: It is okay for sourceRef, destRef, and closeKey to be nil when unit // testing the wallet. @@ -5662,7 +5960,7 @@ func (lc *LightningChannel) RemoteUpfrontShutdownScript() lnwire.DeliveryAddress // AbsoluteThawHeight determines a frozen channel's absolute thaw height. If // the channel is not frozen, then 0 is returned. // -// An error is returned if the channel is penidng, or is an unconfirmed zero +// An error is returned if the channel is pending, or is an unconfirmed zero // conf channel. func (lc *LightningChannel) AbsoluteThawHeight() (uint32, error) { return lc.channelState.AbsoluteThawHeight() @@ -5676,30 +5974,106 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { localCommit := lc.channelState.LocalCommitment commitTx := localCommit.CommitTx.Copy() - theirSig, err := ecdsa.ParseDERSignature(localCommit.CommitSig) - if err != nil { - return nil, err - } + ourKey := lc.channelState.LocalChanCfg.MultiSigKey + theirKey := lc.channelState.RemoteChanCfg.MultiSigKey - // With this, we then generate the full witness so the caller can - // broadcast a fully signed transaction. - lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(commitTx) - ourSig, err := lc.Signer.SignOutputRaw(commitTx, lc.signDesc) - if err != nil { - return nil, err - } + var witness wire.TxWitness + switch { + // If this is a taproot channel, then we'll need to re-derive the nonce + // we need to generate a new signature + case lc.channelState.ChanType.IsTaproot(): + // First, we'll need to re-derive the local nonce we sent to + // the remote party to create this musig session. For the + // target height we pass in 1 minus the current height, as + // NewMusigVerificationNonce is used to create the nonce for + // the _next_ height. + // + // TODO(roasbeef): make into func for unit tests + localNonce, err := NewMusigVerificationNonce( + ourKey.PubKey, lc.currentHeight, + lc.channelState.RevocationProducer, true, + ) + if err != nil { + return nil, err + } - // With the final signature generated, create the witness stack - // required to spend from the multi-sig output. - ourKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey. - SerializeCompressed() - theirKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. - SerializeCompressed() + // Now that we have the local nonce, we'll re-create the musig + // session we had for this height. + musigSession := NewPartialMusigSession( + *localNonce, ourKey, theirKey, lc.Signer, + &lc.fundingOutput, false, + ) - commitTx.TxIn[0].Witness = input.SpendMultiSig( - lc.signDesc.WitnessScript, ourKey, - ourSig, theirKey, theirSig, - ) + var remoteSig lnwire.PartialSigWithNonce + err = remoteSig.Decode( + bytes.NewReader(localCommit.CommitSig), + ) + if err != nil { + return nil, fmt.Errorf("unable to decode remote "+ + "partial sig: %w", err) + } + + // Next, we'll manually finalize the session with the signing + // nonce we got from the remote party which is embedded in the + // signature we have. + err = musigSession.FinalizeSession(musig2.Nonces{ + PubNonce: remoteSig.Nonce, + }) + if err != nil { + return nil, fmt.Errorf("unable to finalize musig "+ + "session: %w", err) + } + + // Now that the session has been finalized, we can generate our + // half of the signature for the state. We don't capture the + // sig as it's stored within the session. + if _, err := musigSession.SignCommit(commitTx); err != nil { + return nil, err + } + + // The final step is now to combine this signature we generated + // above, with the remote party's signature. We only need to + // pass the remote sig, as the local sig was already cached in + // the session. + var partialSig MusigPartialSig + partialSig.FromWireSig(&remoteSig) + finalSig, err := musigSession.CombineSigs(partialSig.sig) + if err != nil { + return nil, fmt.Errorf("unable to combine musig "+ + "partial sigs: %w", err) + } + + // The witness is the single keyspend schnorr sig. + witness = wire.TxWitness{ + finalSig.Serialize(), + } + + // Otherwise, the final witness we generate will be a normal p2wsh + // multi-sig spend. + default: + theirSig, err := ecdsa.ParseDERSignature(localCommit.CommitSig) + if err != nil { + return nil, err + } + + // With this, we then generate the full witness so the caller + // can broadcast a fully signed transaction. + lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(commitTx) + ourSig, err := lc.Signer.SignOutputRaw(commitTx, lc.signDesc) + if err != nil { + return nil, err + } + + // With the final signature generated, create the witness stack + // required to spend from the multi-sig output. + witness = input.SpendMultiSig( + lc.signDesc.WitnessScript, + ourKey.PubKey.SerializeCompressed(), ourSig, + theirKey.PubKey.SerializeCompressed(), theirSig, + ) + } + + commitTx.TxIn[0].Witness = witness return commitTx, nil } @@ -5812,7 +6186,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si // transaction. selfScript, maturityDelay, err := CommitScriptToRemote( chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, - leaseExpiry, + leaseExpiry, keyRing.CombinedFundingKey, ) if err != nil { return nil, fmt.Errorf("unable to create self commit "+ @@ -5883,7 +6257,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si } anchorResolution, err := NewAnchorResolution( - chanState, commitTxBroadcast, + chanState, commitTxBroadcast, keyRing, ) if err != nil { return nil, err @@ -6580,7 +6954,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, } anchorResolution, err := NewAnchorResolution( - chanState, commitTx, + chanState, commitTx, keyRing, ) if err != nil { return nil, err @@ -6596,6 +6970,26 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, }, nil } +// chanCloseOpt... +type chanCloseOpt struct { + musigSession *MusigSession +} + +// ChanCloseOpt.. +type ChanCloseOpt func(*chanCloseOpt) + +// defaultCloseOpts... +func defaultCloseOpts() *chanCloseOpt { + return &chanCloseOpt{} +} + +// WithCoopCloseMusigSession... +func WithCoopCloseMusigSession(session *MusigSession) ChanCloseOpt { + return func(opts *chanCloseOpt) { + opts.musigSession = session + } +} + // CreateCloseProposal is used by both parties in a cooperative channel close // workflow to generate proposed close transactions and signatures. This method // should only be executed once all pending HTLCs (if any) on the channel have @@ -6607,8 +7001,8 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, // TODO(roasbeef): caller should initiate signal to reject all incoming HTLCs, // settle any in flight. func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, - localDeliveryScript []byte, - remoteDeliveryScript []byte) (input.Signature, *chainhash.Hash, + localDeliveryScript []byte, remoteDeliveryScript []byte, + closeOpts ...ChanCloseOpt) (input.Signature, *chainhash.Hash, btcutil.Amount, error) { lc.Lock() @@ -6620,6 +7014,11 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, return nil, nil, 0, ErrChanClosing } + opts := defaultCloseOpts() + for _, optFunc := range closeOpts { + optFunc(opts) + } + // Get the final balances after subtracting the proposed fee, taking // care not to persist the adjusted balance, as the feeRate may change // during the channel closing process. @@ -6645,14 +7044,25 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, return nil, nil, 0, err } - // Finally, sign the completed cooperative closure transaction. As the - // initiator we'll simply send our signature over to the remote party, - // using the generated txid to be notified once the closure transaction - // has been confirmed. - lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(closeTx) - sig, err := lc.Signer.SignOutputRaw(closeTx, lc.signDesc) - if err != nil { - return nil, nil, 0, err + // If we have a co-op close musig session, then this is a taproot + // channel, so we'll generate a _partial_ signature. + var sig input.Signature + if opts.musigSession != nil { + sig, err = opts.musigSession.SignCommit(closeTx) + if err != nil { + return nil, nil, 0, err + } + } else { + // For regular channels we'll, sign the completed cooperative + // closure transaction. As the initiator we'll simply send our + // signature over to the remote party, using the generated txid + // to be notified once the closure transaction has been + // confirmed. + lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(closeTx) + sig, err = lc.Signer.SignOutputRaw(closeTx, lc.signDesc) + if err != nil { + return nil, nil, 0, err + } } // As everything checks out, indicate in the channel status that a @@ -6673,7 +7083,8 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, func (lc *LightningChannel) CompleteCooperativeClose( localSig, remoteSig input.Signature, localDeliveryScript, remoteDeliveryScript []byte, - proposedFee btcutil.Amount) (*wire.MsgTx, btcutil.Amount, error) { + proposedFee btcutil.Amount, + closeOpts ...ChanCloseOpt) (*wire.MsgTx, btcutil.Amount, error) { lc.Lock() defer lc.Unlock() @@ -6684,6 +7095,11 @@ func (lc *LightningChannel) CompleteCooperativeClose( return nil, 0, ErrChanClosing } + opts := defaultCloseOpts() + for _, optFunc := range closeOpts { + optFunc(opts) + } + // Get the final balances after subtracting the proposed fee. ourBalance, theirBalance, err := CoopCloseBalance( lc.channelState.ChanType, lc.channelState.IsInitiator, @@ -6706,31 +7122,62 @@ func (lc *LightningChannel) CompleteCooperativeClose( // consensus rules such as being too big, or having any value with a // negative output. tx := btcutil.NewTx(closeTx) + prevOut := lc.signDesc.Output if err := blockchain.CheckTransactionSanity(tx); err != nil { return nil, 0, err } - hashCache := input.NewTxSigHashesV0Only(closeTx) - - // Finally, construct the witness stack minding the order of the - // pubkeys+sigs on the stack. - ourKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey. - SerializeCompressed() - theirKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. - SerializeCompressed() - witness := input.SpendMultiSig( - lc.signDesc.WitnessScript, ourKey, localSig, theirKey, - remoteSig, + + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + prevOut.PkScript, prevOut.Value, ) - closeTx.TxIn[0].Witness = witness + hashCache := txscript.NewTxSigHashes(closeTx, prevOutputFetcher) + + // Next, we'll complete the co-op close transaction. Depending on the + // set of options, we'll either do a regular p2wsh spend, or construct + // the final schnorr signature from a set of partial sigs. + if opts.musigSession != nil { + // For taproot channels, we'll use the attached session to + // combine the two partial signatures into a proper schnorr + // signature. + remotePartialSig, ok := remoteSig.(*MusigPartialSig) + if !ok { + return nil, 0, fmt.Errorf("expected MusigPartialSig, "+ + "got %T", remoteSig) + } + + finalSchnorrSig, err := opts.musigSession.CombineSigs( + remotePartialSig.sig, + ) + if err != nil { + return nil, 0, fmt.Errorf("unable to combine "+ + "final co-op close sig: %w", err) + } + + // The witness for a keyspend is just the signature itself. + closeTx.TxIn[0].Witness = wire.TxWitness{ + finalSchnorrSig.Serialize(), + } + } else { + + // For regular channels, we'll need to , construct the witness + // stack minding the order of the pubkeys+sigs on the stack. + ourKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey. + SerializeCompressed() + theirKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. + SerializeCompressed() + witness := input.SpendMultiSig( + lc.signDesc.WitnessScript, ourKey, localSig, theirKey, + remoteSig, + ) + closeTx.TxIn[0].Witness = witness + + } // Validate the finalized transaction to ensure the output script is // properly met, and that the remote peer supplied a valid signature. - prevOut := lc.signDesc.Output vm, err := txscript.NewEngine( prevOut.PkScript, closeTx, 0, txscript.StandardVerifyFlags, nil, - hashCache, prevOut.Value, txscript.NewCannedPrevOutputFetcher( - prevOut.PkScript, prevOut.Value, - ), + hashCache, prevOut.Value, prevOutputFetcher, ) if err != nil { return nil, 0, err @@ -6764,7 +7211,7 @@ type AnchorResolutions struct { // NewAnchorResolutions returns a set of anchor resolutions wrapped in the // struct AnchorResolutions. Because we have no view on the mempool, we can -// only blindly anchor all of these txes down. Caller needs to check the +// only blindly anchor all of these txes down. The caller needs to check the // returned values against nil to decide whether there exists an anchor // resolution for local/remote/pending remote commitment txes. func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, @@ -6773,11 +7220,25 @@ func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, lc.Lock() defer lc.Unlock() - resolutions := &AnchorResolutions{} + // TODO(roasbeef): store revocation state along the commits? + + var resolutions AnchorResolutions // Add anchor for local commitment tx, if any. + revocation, err := lc.channelState.RevocationProducer.AtIndex( + lc.currentHeight, + ) + if err != nil { + return nil, err + } + localCommitPoint := input.ComputeCommitmentPoint(revocation[:]) + localKeyRing := DeriveCommitmentKeys( + localCommitPoint, true, lc.channelState.ChanType, + &lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg, + ) localRes, err := NewAnchorResolution( lc.channelState, lc.channelState.LocalCommitment.CommitTx, + localKeyRing, ) if err != nil { return nil, err @@ -6785,8 +7246,14 @@ func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, resolutions.Local = localRes // Add anchor for remote commitment tx, if any. + remoteKeyRing := DeriveCommitmentKeys( + lc.channelState.RemoteCurrentRevocation, false, + lc.channelState.ChanType, &lc.channelState.LocalChanCfg, + &lc.channelState.RemoteChanCfg, + ) remoteRes, err := NewAnchorResolution( lc.channelState, lc.channelState.RemoteCommitment.CommitTx, + remoteKeyRing, ) if err != nil { return nil, err @@ -6800,9 +7267,15 @@ func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, } if remotePendingCommit != nil { + pendingRemoteKeyRing := DeriveCommitmentKeys( + lc.channelState.RemoteNextRevocation, false, + lc.channelState.ChanType, &lc.channelState.LocalChanCfg, + &lc.channelState.RemoteChanCfg, + ) remotePendingRes, err := NewAnchorResolution( lc.channelState, remotePendingCommit.Commitment.CommitTx, + pendingRemoteKeyRing, ) if err != nil { return nil, err @@ -6810,13 +7283,14 @@ func (lc *LightningChannel) NewAnchorResolutions() (*AnchorResolutions, resolutions.RemotePending = remotePendingRes } - return resolutions, nil + return &resolutions, nil } // NewAnchorResolution returns the information that is required to sweep the // local anchor. func NewAnchorResolution(chanState *channeldb.OpenChannel, - commitTx *wire.MsgTx) (*AnchorResolution, error) { + commitTx *wire.MsgTx, + keyRing *CommitmentKeyRing) (*AnchorResolution, error) { // Return nil resolution if the channel has no anchors. if !chanState.ChanType.HasAnchors() { @@ -6825,7 +7299,8 @@ func NewAnchorResolution(chanState *channeldb.OpenChannel, // Derive our local anchor script. localAnchor, _, err := CommitScriptAnchors( - &chanState.LocalChanCfg, &chanState.RemoteChanCfg, + chanState.ChanType, &chanState.LocalChanCfg, + &chanState.RemoteChanCfg, keyRing, ) if err != nil { return nil, err @@ -7170,7 +7645,8 @@ func (lc *LightningChannel) generateRevocation(height uint64) (*lnwire.RevokeAnd revocationMsg.NextRevocationKey = input.ComputeCommitmentPoint(nextCommitSecret[:]) revocationMsg.ChanID = lnwire.NewChanIDFromOutPoint( - &lc.channelState.FundingOutpoint) + &lc.channelState.FundingOutpoint, + ) return revocationMsg, nil } @@ -7316,11 +7792,23 @@ func (lc *LightningChannel) IdealCommitFeeRate(netFeeRate, minRelayFeeRate, return absoluteMaxFee } -// RemoteNextRevocation returns the channelState's RemoteNextRevocation. +// RemoteNextRevocation returns the channelState's RemoteNextRevocation. For +// musig2 channels, until a nonce pair is processed by the remote party, a nil +// public key is returned. +// +// TODO(roasbeef): revisit, maybe just make a more general method instead? func (lc *LightningChannel) RemoteNextRevocation() *btcec.PublicKey { lc.RLock() defer lc.RUnlock() + if !lc.channelState.ChanType.IsTaproot() { + return lc.channelState.RemoteNextRevocation + } + + if lc.musigSessions == nil { + return nil + } + return lc.channelState.RemoteNextRevocation } @@ -7500,3 +7988,120 @@ func (lc *LightningChannel) unsignedLocalUpdates(remoteMessageIndex, return localPeerUpdates } + +// GenMusigNonces generates the verification nonce to start off a new musig2 +// channel session. +func (lc *LightningChannel) GenMusigNonces() (*musig2.Nonces, error) { + lc.RLock() + defer lc.RUnlock() + + var err error + lc.pendingVerificationNonce, err = NewMusigVerificationNonce( + lc.channelState.LocalChanCfg.MultiSigKey.PubKey, + lc.currentHeight, lc.channelState.RevocationProducer, + false, + ) + if err != nil { + return nil, err + } + + return lc.pendingVerificationNonce, nil +} + +// NewMusigVerificationNonce generates the local or verification nonce for +// another musig2 session. In order to permit our implementation to not have to +// write any secret nonce state to disk, we'll use the _next_ shachain +// pre-image as our primary randomness source. +func NewMusigVerificationNonce(pubKey *btcec.PublicKey, currentHeight uint64, + shaGen shachain.Producer, forBroadcast bool) (*musig2.Nonces, error) { + + // If we're broadcasting this commitment, then we need to get the nonce + // for the current height. Otherwise, we'll add one, as we're + // generating a local nonce for the _next_ height. + targetHeight := func() uint64 { + if forBroadcast { + return currentHeight + } + + return currentHeight + 1 + }() + + // Now that we know what height we need, we'll grab the shachain + // pre-image at the target destination. + nextPreimage, err := shaGen.AtIndex(targetHeight) + if err != nil { + return nil, err + } + + shaChainRand := musig2.WithCustomRand(bytes.NewBuffer(nextPreimage[:])) + pubKeyOpt := musig2.WithPublicKey(pubKey) + + return musig2.GenNonces(pubKeyOpt, shaChainRand) +} + +// HasRemoteNonces returns true if the channel has a remote nonce pair. +func (lc *LightningChannel) HasRemoteNonces() bool { + return lc.musigSessions != nil +} + +// InitRemoteMusigNonces processes the remote musig nonces sent by the remote +// party. This should be called upon connection re-establishment, after we've +// generated our own nonces. Once this method returns a nil error, then the +// channel can be used to sign commitment states. +func (lc *LightningChannel) InitRemoteMusigNonces(remoteNonce *musig2.Nonces, +) error { + + lc.RLock() + defer lc.RUnlock() + + // Now that we have the set of local and remote nonces, we can generate + // a new pair of musig sessions for our local commitment and the + // commitment of the remote party. + localNonce := lc.pendingVerificationNonce + + localChanCfg := lc.channelState.LocalChanCfg + remoteChanCfg := lc.channelState.RemoteChanCfg + + // TODO(roasbeef): propagate rename of signing and verification nonces + + sessionCfg := &MusigSessionCfg{ + LocalKey: localChanCfg.MultiSigKey, + RemoteKey: remoteChanCfg.MultiSigKey, + LocalNonce: *localNonce, + RemoteNonce: *remoteNonce, + Signer: lc.Signer, + InputTxOut: &lc.fundingOutput, + } + lc.musigSessions = NewMusigPairSession( + sessionCfg, + ) + + lc.pendingVerificationNonce = nil + + return nil +} + +// ChanType... +func (lc *LightningChannel) ChanType() channeldb.ChannelType { + lc.RLock() + defer lc.RUnlock() + + return lc.channelState.ChanType +} + +// FundingTxOut... +func (lc *LightningChannel) FundingTxOut() *wire.TxOut { + lc.RLock() + defer lc.RUnlock() + + return &lc.fundingOutput +} + +// MultiSigKeys... +func (lc *LightningChannel) MultiSigKeys() (keychain.KeyDescriptor, keychain.KeyDescriptor) { + lc.RLock() + defer lc.RUnlock() + + return lc.channelState.LocalChanCfg.MultiSigKey, + lc.channelState.RemoteChanCfg.MultiSigKey +} diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 25aa1aa0b92..a7ac2e2b2fa 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -59,7 +59,9 @@ func assertOutputExistsByValue(t *testing.T, commitTx *wire.MsgTx, // testAddSettleWorkflow tests a simple channel scenario where Alice and Bob // add, the settle an HTLC between themselves. -func testAddSettleWorkflow(t *testing.T, tweakless bool) { +func testAddSettleWorkflow(t *testing.T, tweakless bool, + chanTypeModifier channeldb.ChannelType) { + // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. @@ -68,6 +70,10 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { chanType = channeldb.SingleFunderBit } + if chanTypeModifier != 0 { + chanType |= chanTypeModifier + } + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(chanType) require.NoError(t, err, "unable to create test channels") defer cleanUp() @@ -94,13 +100,13 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { // we expect the messages to be ordered, Bob will receive the HTLC we // just sent before he receives this signature, so the signature will // cover the HTLC. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") // Bob receives this signature message, and checks that this covers the // state he has in his remote log. This includes the HTLC just sent // from Alice. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's new commitment") // Bob revokes his prior commitment given to him by Alice, since he now @@ -112,7 +118,7 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { // This signature will cover the HTLC, since Bob will first send the // revocation just created. The revocation also acks every received // HTLC up to the point where Alice sent here signature. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign alice's commitment") // Alice then processes this revocation, sending her own revocation for @@ -132,7 +138,7 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { // Alice then processes bob's signature, and since she just received // the revocation, she expect this signature to cover everything up to // the point where she sent her signature, including the HTLC. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to process bob's new commitment") // Alice then generates a revocation for bob. @@ -187,12 +193,18 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { // Both commitment transactions should have three outputs, and one of // them should be exactly the amount of the HTLC. - if len(aliceChannel.channelState.LocalCommitment.CommitTx.TxOut) != 3 { + numOutputs := 3 + if chanTypeModifier.HasAnchors() { + // In this case we expect two extra outputs as both sides need an + // anchor output. + numOutputs = 5 + } + if len(aliceChannel.channelState.LocalCommitment.CommitTx.TxOut) != numOutputs { t.Fatalf("alice should have three commitment outputs, instead "+ "have %v", len(aliceChannel.channelState.LocalCommitment.CommitTx.TxOut)) } - if len(bobChannel.channelState.LocalCommitment.CommitTx.TxOut) != 3 { + if len(bobChannel.channelState.LocalCommitment.CommitTx.TxOut) != numOutputs { t.Fatalf("bob should have three commitment outputs, instead "+ "have %v", len(bobChannel.channelState.LocalCommitment.CommitTx.TxOut)) @@ -216,14 +228,14 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { t.Fatalf("alice unable to accept settle of outbound htlc: %v", err) } - bobSig2, bobHtlcSigs2, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign settle commitment") - err = aliceChannel.ReceiveNewCommitment(bobSig2, bobHtlcSigs2) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to process bob's new commitment") aliceRevocation2, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to generate revocation") - aliceSig2, aliceHtlcSigs2, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign new commitment") fwdPkg, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation2) @@ -237,7 +249,7 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { "should forward none", len(fwdPkg.SettleFails)) } - err = bobChannel.ReceiveNewCommitment(aliceSig2, aliceHtlcSigs2) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's new commitment") bobRevocation2, _, err := bobChannel.RevokeCurrentCommitment() @@ -316,17 +328,25 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { // // TODO(roasbeef): write higher level framework to exercise various states of // the state machine -// * DSL language perhaps? -// * constructed via input/output files +// - DSL language perhaps? +// - constructed via input/output files func TestSimpleAddSettleWorkflow(t *testing.T) { t.Parallel() for _, tweakless := range []bool{true, false} { tweakless := tweakless t.Run(fmt.Sprintf("tweakless=%v", tweakless), func(t *testing.T) { - testAddSettleWorkflow(t, tweakless) + testAddSettleWorkflow(t, tweakless, 0) }) } + t.Run("anchors", func(t *testing.T) { + testAddSettleWorkflow( + t, true, channeldb.AnchorOutputsBit|channeldb.ZeroHtlcTxFeeBit, + ) + }) + t.Run("taproot", func(t *testing.T) { + testAddSettleWorkflow(t, true, channeldb.SimpleTaprootFeatureBit) + }) } // TestChannelZeroAddLocalHeight tests that we properly set the addCommitHeightLocal @@ -335,17 +355,18 @@ func TestSimpleAddSettleWorkflow(t *testing.T) { // The full state transition of this test is: // // Alice Bob -// -----add------> -// -----sig------> -// <----rev------- -// <----sig------- -// -----rev------> -// <---settle----- -// <----sig------- -// -----rev------> -// *alice dies* -// <----add------- -// x----sig------- +// +// -----add------> +// -----sig------> +// <----rev------- +// <----sig------- +// -----rev------> +// <---settle----- +// <----sig------- +// -----rev------> +// *alice dies* +// <----add------- +// x----sig------- // // The last sig will be rejected if addCommitHeightLocal is not set for the // initial add that Alice sent. This test checks that this behavior does @@ -386,10 +407,10 @@ func TestChannelZeroAddLocalHeight(t *testing.T) { // Bob should send a commitment signature to Alice. // <----sig------ - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) // Alice should reply with a revocation. @@ -420,12 +441,12 @@ func TestChannelZeroAddLocalHeight(t *testing.T) { // Bob should now send a commitment signature to Alice. // <----sig----- - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err) // Alice should accept the commitment. Previously she would // force close here. - err = newAliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = newAliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) } @@ -567,10 +588,10 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) { // tie-breaking for commitment sorting won't affect the commitment // signed by Alice because received HTLC scripts commit to the CLTV // directly, so the outputs will have different scriptPubkeys. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign alice's commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive alice's commitment") bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() @@ -582,19 +603,20 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) { // the offered HTLC scripts he adds for Alice will need to have the // tie-breaking applied because the CLTV is not committed, but instead // implicit via the construction of the second-level transactions. - bobSig, bobHtlcSigs, bobHtlcs, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign bob's commitment") - if len(bobHtlcs) != numHtlcs { - t.Fatalf("expected %d htlcs, got: %v", numHtlcs, len(bobHtlcs)) + if len(bobNewCommit.PendingHTLCs) != numHtlcs { + t.Fatalf("expected %d htlcs, got: %v", numHtlcs, + len(bobNewCommit.PendingHTLCs)) } // Ensure that our HTLCs appear in the reverse order from which they // were added by inspecting each's outpoint index. We expect the output // indexes to be in descending order, i.e. the first HTLC added had the // highest CLTV and should end up last. - lastIndex := bobHtlcs[0].OutputIndex - for i, htlc := range bobHtlcs[1:] { + lastIndex := bobNewCommit.PendingHTLCs[0].OutputIndex + for i, htlc := range bobNewCommit.PendingHTLCs[1:] { if htlc.OutputIndex >= lastIndex { t.Fatalf("htlc %d output index %d is not descending", i, htlc.OutputIndex) @@ -628,7 +650,7 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) { // Finally, have Alice validate the signatures to ensure that she is // expecting the signatures in the proper order. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive bob's commitment") } @@ -1411,17 +1433,19 @@ func TestHTLCSigNumber(t *testing.T) { aboveDust) defer cleanUp() - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "Error signing next commitment") - if len(aliceHtlcSigs) != 2 { + if len(aliceNewCommit.HtlcSigs) != 2 { t.Fatalf("expected 2 htlc sig, instead got %v", - len(aliceHtlcSigs)) + len(aliceNewCommit.HtlcSigs)) } // Now discard one signature from the htlcSig slice. Bob should reject // the commitment because of this. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs[1:]) + aliceNewCommitCopy := *aliceNewCommit + aliceNewCommitCopy.HtlcSigs = aliceNewCommitCopy.HtlcSigs[1:] + err = bobChannel.ReceiveNewCommitment(aliceNewCommitCopy.CommitSigs) if err == nil { t.Fatalf("Expected Bob to reject signatures") } @@ -1433,17 +1457,19 @@ func TestHTLCSigNumber(t *testing.T) { aliceChannel, bobChannel, cleanUp = createChanWithHTLC(aboveDust) defer cleanUp() - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "Error signing next commitment") - if len(aliceHtlcSigs) != 1 { + if len(aliceNewCommit.HtlcSigs) != 1 { t.Fatalf("expected 1 htlc sig, instead got %v", - len(aliceHtlcSigs)) + len(aliceNewCommit.HtlcSigs)) } // Now just give Bob an empty htlcSig slice. He should reject the // commitment because of this. - err = bobChannel.ReceiveNewCommitment(aliceSig, []lnwire.Sig{}) + aliceCommitCopy := *aliceNewCommit.CommitSigs + aliceCommitCopy.HtlcSigs = []lnwire.Sig{} + err = bobChannel.ReceiveNewCommitment(&aliceCommitCopy) if err == nil { t.Fatalf("Expected Bob to reject signatures") } @@ -1454,17 +1480,17 @@ func TestHTLCSigNumber(t *testing.T) { aliceChannel, bobChannel, cleanUp = createChanWithHTLC(belowDust) defer cleanUp() - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "Error signing next commitment") // Since the HTLC is below Bob's dust limit, Alice won't need to send // any signatures for this HTLC. - if len(aliceHtlcSigs) != 0 { + if len(aliceNewCommit.HtlcSigs) != 0 { t.Fatalf("expected no htlc sigs, instead got %v", - len(aliceHtlcSigs)) + len(aliceNewCommit.HtlcSigs)) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "Bob failed receiving commitment") // ================================================================ @@ -1473,17 +1499,17 @@ func TestHTLCSigNumber(t *testing.T) { aliceChannel, bobChannel, cleanUp = createChanWithHTLC(aboveDust) defer cleanUp() - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "Error signing next commitment") // Since the HTLC is above Bob's dust limit, Alice should send a // signature for this HTLC. - if len(aliceHtlcSigs) != 1 { + if len(aliceNewCommit.PendingHTLCs) != 1 { t.Fatalf("expected 1 htlc sig, instead got %v", - len(aliceHtlcSigs)) + len(aliceNewCommit.PendingHTLCs)) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "Bob failed receiving commitment") // ==================================================================== @@ -1496,20 +1522,22 @@ func TestHTLCSigNumber(t *testing.T) { // Alice should produce only one signature, since one HTLC is below // dust. - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "Error signing next commitment") - if len(aliceHtlcSigs) != 1 { + if len(aliceNewCommit.HtlcSigs) != 1 { t.Fatalf("expected 1 htlc sig, instead got %v", - len(aliceHtlcSigs)) + len(aliceNewCommit.HtlcSigs)) } // Add an extra signature. - aliceHtlcSigs = append(aliceHtlcSigs, aliceHtlcSigs[0]) + aliceNewCommit.HtlcSigs = append( + aliceNewCommit.HtlcSigs, aliceNewCommit.HtlcSigs[0], + ) // Bob should reject these signatures since they don't match the number // of HTLCs above dust. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err == nil { t.Fatalf("Expected Bob to reject signatures") } @@ -2257,13 +2285,13 @@ func TestUpdateFeeFail(t *testing.T) { // Alice sends signature for commitment that does not cover any fee // update. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") // Bob verifies this commit, meaning that he checks that it is // consistent everything he has received. This should fail, since he got // the fee update, but Alice never sent it. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err == nil { t.Fatalf("expected bob to fail receiving alice's signature") } @@ -2306,15 +2334,15 @@ func TestUpdateFeeConcurrentSig(t *testing.T) { } // Alice signs a commitment, and sends this to bob. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommits, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") // At the same time, Bob signs a commitment. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommits, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign alice's commitment") // ...that Alice receives. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommits.CommitSigs) require.NoError(t, err, "alice unable to process bob's new commitment") // Now let Bob receive the fee update + commitment that Alice sent. @@ -2325,7 +2353,7 @@ func TestUpdateFeeConcurrentSig(t *testing.T) { // Bob receives this signature message, and verifies that it is // consistent with the state he had for Alice, including the received // HTLC and fee update. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommits.CommitSigs) require.NoError(t, err, "bob unable to process alice's new commitment") if chainfee.SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) == fee { @@ -2383,13 +2411,13 @@ func TestUpdateFeeSenderCommits(t *testing.T) { // Alice signs a commitment, which will cover everything sent to Bob // (the HTLC and the fee update), and everything acked by Bob (nothing // so far). - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommits, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") // Bob receives this signature message, and verifies that it is // consistent with the state he had for Alice, including the received // HTLC and fee update. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommits.CommitSigs) require.NoError(t, err, "bob unable to process alice's new commitment") if chainfee.SatPerKWeight( @@ -2413,7 +2441,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { // Bob commits to all updates he has received from Alice. This includes // the HTLC he received, and the fee update. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign alice's commitment") // Alice receives the revocation of the old one, and can now assume @@ -2424,7 +2452,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { // Alice receives new signature from Bob, and assumes this covers the // changes. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to process bob's new commitment") if chainfee.SatPerKWeight( @@ -2493,12 +2521,12 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // Bob commits to every change he has sent since last time (none). He // does not commit to the received HTLC and fee update, since Alice // cannot know if he has received them. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") // Alice receives this signature message, and verifies that it is // consistent with the remote state, not including any of the updates. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's new commitment") // Alice can revoke the prior commitment she had, this will ack @@ -2514,12 +2542,12 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // Alice will sign next commitment. Since she sent the revocation, she // also ack'ed everything received, but in this case this is nothing. // Since she sent the two updates, this signature will cover those two. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign alice's commitment") // Bob gets the signature for the new commitment from Alice. He assumes // this covers everything received from alice, including the two updates. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "alice unable to process bob's new commitment") if chainfee.SatPerKWeight( @@ -2544,7 +2572,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // Bob will send a new signature, which will cover what he just acked: // the HTLC and fee update. - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") // Alice receives revocation from Bob, and can now be sure that Bob @@ -2554,7 +2582,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // Alice will receive the signature from Bob, which will cover what was // just acked by his revocation. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to process bob's new commitment") if chainfee.SatPerKWeight( @@ -2636,7 +2664,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { // Alice signs a commitment, which will cover everything sent to Bob // (the HTLC and the fee update), and everything acked by Bob (nothing // so far). - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") bobChannel.ReceiveUpdateFee(fee1) @@ -2646,7 +2674,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { // Bob receives this signature message, and verifies that it is // consistent with the state he had for Alice, including the received // HTLC and fee update. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's new commitment") if chainfee.SatPerKWeight( @@ -2682,7 +2710,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { // Bob commits to all updates he has received from Alice. This includes // the HTLC he received, and the fee update. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign alice's commitment") // Alice receives the revocation of the old one, and can now assume that @@ -2693,7 +2721,8 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { // Alice receives new signature from Bob, and assumes this covers the // changes. - if err := aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs); err != nil { + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) + if err != nil { t.Fatalf("alice unable to process bob's new commitment: %v", err) } @@ -2991,7 +3020,7 @@ func TestChanSyncOweCommitment(t *testing.T) { // Now we'll begin the core of the test itself. Alice will extend a new // commitment to Bob, but the connection drops before Bob can process // it. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // Bob doesn't get this message so upon reconnection, they need to @@ -3058,20 +3087,20 @@ func TestChanSyncOweCommitment(t *testing.T) { t.Fatalf("expected a CommitSig message, instead have %v", spew.Sdump(aliceMsgsToSend[4])) } - if commitSigMsg.CommitSig != aliceSig { + if commitSigMsg.CommitSig != aliceNewCommit.CommitSig { t.Fatalf("commit sig msgs don't match: expected %x got %x", - aliceSig, commitSigMsg.CommitSig) + aliceNewCommit.CommitSig, commitSigMsg.CommitSig) } - if len(commitSigMsg.HtlcSigs) != len(aliceHtlcSigs) { + if len(commitSigMsg.HtlcSigs) != len(aliceNewCommit.HtlcSigs) { t.Fatalf("wrong number of htlc sigs: expected %v, got %v", - len(aliceHtlcSigs), len(commitSigMsg.HtlcSigs)) + len(aliceNewCommit.HtlcSigs), + len(commitSigMsg.HtlcSigs)) } for i, htlcSig := range commitSigMsg.HtlcSigs { - if htlcSig != aliceHtlcSigs[i] { + if htlcSig != aliceNewCommit.HtlcSigs[i] { t.Fatalf("htlc sig msgs don't match: "+ "expected %x got %x", - aliceHtlcSigs[i], - htlcSig) + aliceNewCommit.HtlcSigs[i], htlcSig) } } } @@ -3101,15 +3130,15 @@ func TestChanSyncOweCommitment(t *testing.T) { // At this point, we should be able to resume the prior state update // without any issues, resulting in Alice settling the 3 htlc's, and // adding one of her own. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to rev bob's commitment") aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") @@ -3250,12 +3279,13 @@ func TestChanSyncOweCommitmentPendingRemote(t *testing.T) { t.Fatalf("unable to settle htlc: %v", err) } - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() if err != nil { t.Fatalf("unable to sign commitment: %v", err) } - - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment( + aliceNewCommit.CommitSigs, + ) if err != nil { t.Fatalf("unable to receive commitment: %v", err) } @@ -3281,16 +3311,17 @@ func TestChanSyncOweCommitmentPendingRemote(t *testing.T) { require.NoError(t, err, "unable to restart bob") // Bob signs the commitment he owes. - bobCommit, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // This commitment is expected to contain no htlcs anymore. - if len(bobHtlcSigs) != 0 { - t.Fatalf("no htlcs expected, but got %v", len(bobHtlcSigs)) + if len(bobNewCommit.HtlcSigs) != 0 { + t.Fatalf("no htlcs expected, but got %v", + len(bobNewCommit.HtlcSigs)) } // Get Alice to revoke and trigger Bob to compact his logs. - err = aliceChannel.ReceiveNewCommitment(bobCommit, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -3356,19 +3387,19 @@ func TestChanSyncOweRevocation(t *testing.T) { // // Alice signs the next state, then Bob receives and sends his // revocation message. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to rev bob's commitment") // At this point, we'll simulate the connection breaking down by Bob's @@ -3507,16 +3538,16 @@ func TestChanSyncOweRevocationAndCommit(t *testing.T) { // Progressing the exchange: Alice will send her signature, Bob will // receive, send a revocation and also a signature for Alice's state. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommits, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommits.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") // Bob generates the revoke and sig message, but the messages don't // reach Alice before the connection dies. bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") // If we now attempt to resync, then Alice should conclude that she @@ -3558,19 +3589,22 @@ func TestChanSyncOweRevocationAndCommit(t *testing.T) { t.Fatalf("expected bob to re-send commit sig, instead sending: %v", spew.Sdump(bobMsgsToSend[1])) } - if bobReCommitSigMsg.CommitSig != bobSig { + if bobReCommitSigMsg.CommitSig != bobNewCommit.CommitSig { t.Fatalf("commit sig msgs don't match: expected %x got %x", - bobSig, bobReCommitSigMsg.CommitSig) + bobNewCommit.CommitSigs.CommitSig, + bobReCommitSigMsg.CommitSig) } - if len(bobReCommitSigMsg.HtlcSigs) != len(bobHtlcSigs) { + if len(bobReCommitSigMsg.HtlcSigs) != len(bobNewCommit.HtlcSigs) { t.Fatalf("wrong number of htlc sigs: expected %v, got %v", - len(bobHtlcSigs), len(bobReCommitSigMsg.HtlcSigs)) + len(bobNewCommit.HtlcSigs), + len(bobReCommitSigMsg.HtlcSigs)) } for i, htlcSig := range bobReCommitSigMsg.HtlcSigs { - if htlcSig != aliceHtlcSigs[i] { + if htlcSig != bobNewCommit.HtlcSigs[i] { t.Fatalf("htlc sig msgs don't match: "+ "expected %x got %x", - bobHtlcSigs[i], htlcSig) + htlcSig, + bobNewCommit.HtlcSigs[i]) } } } @@ -3589,8 +3623,8 @@ func TestChanSyncOweRevocationAndCommit(t *testing.T) { // messages, and send her final revocation. _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) - require.NoError(t, err, "alice unable to rev bob's commitment") + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) + require.NoError(t, err, "alice unable to recv bob's commitment") aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) @@ -3653,10 +3687,10 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) { require.NoError(t, err, "unable to recv bob's htlc") // Bob signs the new state update, and sends the signature to Alice. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to rev bob's commitment") // Alice revokes her current state, but doesn't immediately send a @@ -3677,9 +3711,9 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) { // Progressing the exchange: Alice will send her signature, with Bob // processing the new state locally. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommits, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommits.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") // Bob then sends his revocation message, but before Alice can process @@ -3775,9 +3809,10 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) { // message to Bob. _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") - err = aliceChannel.ReceiveNewCommitment( - bobSigMsg.CommitSig, bobSigMsg.HtlcSigs, - ) + err = aliceChannel.ReceiveNewCommitment(&CommitSigs{ + CommitSig: bobSigMsg.CommitSig, + HtlcSigs: bobSigMsg.HtlcSigs, + }) require.NoError(t, err, "alice unable to rev bob's commitment") aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") @@ -3863,11 +3898,11 @@ func TestChanSyncFailure(t *testing.T) { t.Fatalf("unable to recv bob's htlc: %v", err) } - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() if err != nil { t.Fatalf("unable to sign next commit: %v", err) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err != nil { t.Fatalf("unable to receive commit sig: %v", err) } @@ -4085,7 +4120,7 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { // Now, Alice will send a new commitment to Bob, but we'll simulate a // connection failure, so Bob doesn't get her signature. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // Restart both channels to simulate a connection restart. @@ -4144,19 +4179,20 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { t.Fatalf("expected a CommitSig message, instead have %v", spew.Sdump(aliceMsgsToSend[1])) } - if commitSigMsg.CommitSig != aliceSig { + if commitSigMsg.CommitSig != aliceNewCommit.CommitSig { t.Fatalf("commit sig msgs don't match: expected %x got %x", - aliceSig, commitSigMsg.CommitSig) + aliceNewCommit.CommitSig, commitSigMsg.CommitSig) } - if len(commitSigMsg.HtlcSigs) != len(aliceHtlcSigs) { + if len(commitSigMsg.HtlcSigs) != len(aliceNewCommit.HtlcSigs) { t.Fatalf("wrong number of htlc sigs: expected %v, got %v", - len(aliceHtlcSigs), len(commitSigMsg.HtlcSigs)) + len(aliceNewCommit.HtlcSigs), + len(commitSigMsg.HtlcSigs)) } for i, htlcSig := range commitSigMsg.HtlcSigs { - if htlcSig != aliceHtlcSigs[i] { + if htlcSig != aliceNewCommit.HtlcSigs[i] { t.Fatalf("htlc sig msgs don't match: "+ "expected %x got %x", - aliceHtlcSigs[i], htlcSig) + aliceNewCommit.HtlcSigs[i], htlcSig) } } @@ -4166,15 +4202,15 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { t.Fatalf("unable to update fee for Bob's channel: %v", err) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "alice unable to rev bob's commitment") aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") @@ -4312,7 +4348,7 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { // Now, Alice will send a new commitment to Bob, but we'll simulate a // connection failure, so Bob doesn't get the signature. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommitSig, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // Before restarting Alice, to mimic the old format, we fetch the @@ -4365,15 +4401,15 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { // We send Alice's commitment signatures, and finish the state // transition. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommitSig.CommitSigs) require.NoError(t, err, "bob unable to process alice's commitment") bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommitSigs, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "alice unable to recv revocation") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommitSigs.CommitSigs) require.NoError(t, err, "alice unable to rev bob's commitment") aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") @@ -5010,7 +5046,7 @@ func TestSignCommitmentFailNotLockedIn(t *testing.T) { // If we now try to initiate a state update, then it should fail as // Alice is unable to actually create a new state. - _, _, _, err = aliceChannel.SignNextCommitment() + _, err = aliceChannel.SignNextCommitment() if err != ErrNoWindow { t.Fatalf("expected ErrNoWindow, instead have: %v", err) } @@ -5049,11 +5085,11 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // We'll now manually initiate a state transition between Alice and // bob. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() if err != nil { t.Fatal(err) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -5078,12 +5114,12 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Now, have Bob initiate a transition to lock in the Adds sent by // Alice. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() if err != nil { t.Fatal(err) } - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -5123,11 +5159,11 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // We'll now initiate another state transition, but this time Bob will // lead. - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() if err != nil { t.Fatal(err) } - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -5158,11 +5194,11 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Now, begin another state transition led by Alice, and fail the second // HTLC part-way through the dance. - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() if err != nil { t.Fatal(err) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -5215,11 +5251,11 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Have Alice initiate a state transition, which does not include the // HTLCs just re-added to the channel state. - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() if err != nil { t.Fatal(err) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -5244,12 +5280,12 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { } // Now initiate a final update from Bob to lock in the final Fail. - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() if err != nil { t.Fatal(err) } - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -5276,11 +5312,11 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { // Finally, have Bob initiate a state transition that locks in the Fail // added after the restart. - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() if err != nil { t.Fatal(err) } - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err != nil { t.Fatal(err) } @@ -5335,15 +5371,22 @@ func TestInvalidCommitSigError(t *testing.T) { } // Alice will now attempt to initiate a state transition. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign new commit") // Before the signature gets to Bob, we'll mutate it, such that the // signature is now actually invalid. - aliceSig[0] ^= 88 + sigCopy := aliceNewCommit.CommitSig.Copy() + copyBytes := sigCopy.RawBytes() + copyBytes[0] ^= 80 + + aliceNewCommit.CommitSig, err = lnwire.NewSigFromSchnorrRawSignature( + copyBytes, + ) + require.NoError(t, err) // Bob should reject this new state, and return the proper error. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err == nil { t.Fatalf("bob accepted invalid state but shouldn't have") } @@ -5537,7 +5580,7 @@ func TestChannelUnilateralClosePendingCommit(t *testing.T) { // With the HTLC added, we'll now manually initiate a state transition // from Alice to Bob. - _, _, _, err = aliceChannel.SignNextCommitment() + _, err = aliceChannel.SignNextCommitment() if err != nil { t.Fatal(err) } @@ -5781,9 +5824,9 @@ func TestMaxAcceptedHTLCs(t *testing.T) { } // Add a commitment to Bob's commitment chain. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign next commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to recv new commitment") // The next HTLC should fail with ErrMaxHTLCNumber. The index is incremented @@ -5804,7 +5847,9 @@ func TestMaxAcceptedHTLCs(t *testing.T) { // fail) an HTLC from Alice when exchanging asynchronous payments. We want to // mimic the following case where Bob's commitment transaction is full before // starting: -// Alice Bob +// +// Alice Bob +// // 1. <---settle/fail--- // 2. <-------sig------- // 3. --------sig------> (covers an add sent before step 1) @@ -5885,18 +5930,18 @@ func TestMaxAsynchronousHtlcs(t *testing.T) { t.Fatalf("unable to receive fail htlc: %v", err) } - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign next commitment") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive new commitment") // Cover the HTLC referenced with id equal to numHTLCs-1 with a new // signature (step 3). - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign next commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive new commitment") // Both sides exchange revocations as in step 4 & 5. @@ -5923,10 +5968,10 @@ func TestMaxAsynchronousHtlcs(t *testing.T) { // Receiving the commitment should succeed as in step 7 since space was // made. - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign next commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive new commitment") } @@ -6601,11 +6646,11 @@ func TestChannelRestoreUpdateLogs(t *testing.T) { } // Let Alice sign a new state, which will include the HTLC just sent. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // Bob receives this commitment signature, and revokes his old state. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") @@ -6631,7 +6676,7 @@ func TestChannelRestoreUpdateLogs(t *testing.T) { // and remote commit chains are updated in an async fashion. Since the // remote chain was updated with the latest state (since Bob sent the // revocation earlier) we can keep advancing the remote commit chain. - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // After Alice has signed this commitment, her local commitment will @@ -6776,9 +6821,9 @@ func TestChannelRestoreUpdateLogsFailedHTLC(t *testing.T) { restoreAndAssert(t, aliceChannel, 1, 0, 0, 0) // Bob sends a signature. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") // When Alice receives Bob's new commitment, the logs will stay the @@ -6803,9 +6848,9 @@ func TestChannelRestoreUpdateLogsFailedHTLC(t *testing.T) { // Now send a signature from Alice. This will give Bob a new commitment // where the HTLC is removed. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") // When sending a new commitment, Alice will add a pending commit to @@ -6871,7 +6916,7 @@ func TestDuplicateFailRejection(t *testing.T) { // We'll now have Bob sign a new commitment to lock in the HTLC fail // for Alice. - _, _, _, err = bobChannel.SignNextCommitment() + _, err = bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commit") // We'll now force a restart for Bob and Alice, so we can test the @@ -6939,7 +6984,7 @@ func TestDuplicateSettleRejection(t *testing.T) { // We'll now have Bob sign a new commitment to lock in the HTLC fail // for Alice. - _, _, _, err = bobChannel.SignNextCommitment() + _, err = bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commit") // We'll now force a restart for Bob and Alice, so we can test the @@ -7024,7 +7069,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { } // Let Alice sign a new state, which will include the HTLC just sent. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // The HTLC should only be on the pending remote commitment, so the @@ -7034,7 +7079,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { ) // Bob receives this commitment signature, and revokes his old state. - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") @@ -7056,7 +7101,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // Now let Bob send the commitment signature making the HTLC lock in on // Alice's commitment. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // At this stage Bob has a pending remote commitment. Make sure @@ -7064,7 +7109,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // heights. bobChannel = restoreAndAssertCommitHeights(t, bobChannel, true, 0, 1, 1) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") @@ -7094,7 +7139,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // Send a new signature from Alice to Bob, making Alice have a pending // remote commitment. - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // A restoration should keep the add heights iof the first HTLC, and @@ -7106,7 +7151,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { t, aliceChannel, false, 1, 0, 2, ) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") bobRevocation, _, err = bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") @@ -7133,7 +7178,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // Sign a new state for Alice, making Bob have a pending remote // commitment. - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // The signing of a new commitment for Alice should have given the new @@ -7142,7 +7187,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { bobChannel = restoreAndAssertCommitHeights(t, bobChannel, true, 1, 2, 2) // Alice should receive the commitment and send over a revocation. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") @@ -7170,7 +7215,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { require.NoError(t, err, "unable to recv htlc cancel") // Now Bob signs for the fail update. - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commitment") // Bob has a pending commitment for Alice, it shouldn't affect the add @@ -7179,7 +7224,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { _ = restoreAndAssertCommitHeights(t, bobChannel, true, 1, 2, 2) // Alice receives commitment, sends revocation. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") _, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") @@ -7233,15 +7278,15 @@ func TestForceCloseBorkedState(t *testing.T) { // Do the commitment dance until Bob sends a revocation so Alice is // able to receive the revocation, and then also make a new state // herself. - aliceSigs, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commit") - err = bobChannel.ReceiveNewCommitment(aliceSigs, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") revokeMsg, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") - bobSigs, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commit") - err = aliceChannel.ReceiveNewCommitment(bobSigs, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err, "unable to receive commitment") // Now that we have a new Alice channel, we'll force close once to @@ -7273,7 +7318,7 @@ func TestForceCloseBorkedState(t *testing.T) { // We manually advance the commitment tail here since the above // ReceiveRevocation call will fail before it's actually advanced. aliceChannel.remoteCommitChain.advanceTail() - _, _, _, err = aliceChannel.SignNextCommitment() + _, err = aliceChannel.SignNextCommitment() if err != channeldb.ErrChanBorked { t.Fatalf("sign commitment should have failed: %v", err) } @@ -7547,11 +7592,11 @@ func TestChannelFeeRateFloor(t *testing.T) { } // Check that alice can still sign commitments. - sig, htlcSigs, _, err := alice.SignNextCommitment() + aliceNewCommit, err := alice.SignNextCommitment() require.NoError(t, err, "alice unable to sign commitment") // Check that bob can still receive commitments. - err = bob.ReceiveNewCommitment(sig, htlcSigs) + err = bob.ReceiveNewCommitment(aliceNewCommit.CommitSigs) if err != nil { t.Fatalf("bob unable to process alice's new commitment: %v", err) @@ -8799,20 +8844,21 @@ func TestProcessAddRemoveEntry(t *testing.T) { // The full state transition of this test is: // // Alice Bob -// -----add-----> -// -----sig-----> -// <----rev------ -// <----sig------ -// -----rev-----> -// <----fail----- -// <----sig------ -// -----rev-----> -// -----sig-----X (does not reach Bob! Alice dies!) // -// -----sig-----> -// <----rev------ -// <----add------ -// <----sig------ +// -----add-----> +// -----sig-----> +// <----rev------ +// <----sig------ +// -----rev-----> +// <----fail----- +// <----sig------ +// -----rev-----> +// -----sig-----X (does not reach Bob! Alice dies!) +// +// -----sig-----> +// <----rev------ +// <----add------ +// <----sig------ // // The last sig was rejected with the old behavior of deleting unsigned // acked updates from the database after signing for them. The current @@ -8854,9 +8900,9 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // Bob should send a commitment signature to Alice. // <----sig------ - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) // Alice should reply with a revocation. @@ -8869,7 +8915,7 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // Alice should sign the next commitment and go down before // sending it. // -----sig-----X - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err) newAliceChannel, err := NewLightningChannel( @@ -8880,7 +8926,7 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // Bob receives Alice's signature. // -----sig-----> - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) // Bob revokes his current commitment and sends a revocation @@ -8903,9 +8949,9 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // Bob sends the final signature to Alice and Alice should not // reject it, given that we properly restore the unsigned acked // updates and therefore our update log is structured correctly. - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err) - err = newAliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = newAliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) } @@ -8915,16 +8961,17 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // The full state transition is: // // Alice Bob -// <----add----- -// <----sig----- -// -----rev----> -// -----sig----> -// <----rev----- -// ----fail----> -// -----sig----> -// <----rev----- -// *reconnect* -// <----sig----- +// +// <----add----- +// <----sig----- +// -----rev----> +// -----sig----> +// <----rev----- +// ----fail----> +// -----sig----> +// <----rev----- +// *reconnect* +// <----sig----- // // Alice should reject the last signature since the settle is not restored // into the local update log and thus calculates Bob's signature as invalid. @@ -8964,9 +9011,9 @@ func TestChannelLocalUnsignedUpdatesFailure(t *testing.T) { // Alice should send a commitment signature to Bob. // -----sig----> - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) // Bob should reply with a revocation and Alice should save the fail as @@ -8988,9 +9035,9 @@ func TestChannelLocalUnsignedUpdatesFailure(t *testing.T) { // Bob sends the final signature and Alice should not reject it. // <----sig----- - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err) - err = newAliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = newAliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) } @@ -9000,22 +9047,22 @@ func TestChannelLocalUnsignedUpdatesFailure(t *testing.T) { // The full state transition of this test is: // // Alice Bob -// <----add------- -// <----sig------- -// -----rev------> -// -----sig------> -// <----rev------- -// ----settle----> -// -----sig------> -// <----rev------- -// <----sig------- -// -----add------> -// -----sig------> -// <----rev------- -// *restarts* -// -----rev------> -// <----sig------- // +// <----add------- +// <----sig------- +// -----rev------> +// -----sig------> +// <----rev------- +// ----settle----> +// -----sig------> +// <----rev------- +// <----sig------- +// -----add------> +// -----sig------> +// <----rev------- +// *restarts* +// -----rev------> +// <----sig------- func TestChannelSignedAckRegression(t *testing.T) { t.Parallel() @@ -9051,9 +9098,9 @@ func TestChannelSignedAckRegression(t *testing.T) { require.NoError(t, err) // -----sig----> - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) // <----rev----- @@ -9063,9 +9110,9 @@ func TestChannelSignedAckRegression(t *testing.T) { require.NoError(t, err) // <----sig----- - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) // Create an HTLC that Alice will send to Bob. @@ -9078,9 +9125,9 @@ func TestChannelSignedAckRegression(t *testing.T) { require.NoError(t, err) // -----sig----> - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) // <----rev----- @@ -9108,11 +9155,11 @@ func TestChannelSignedAckRegression(t *testing.T) { // Bob should no longer fail to sign this commitment due to faulty // update logs. // <----sig----- - bobSig, bobHtlcSigs, _, err = newBobChannel.SignNextCommitment() + bobNewCommit, err = newBobChannel.SignNextCommitment() require.NoError(t, err) // Alice should receive the new commitment without hiccups. - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) } @@ -9184,9 +9231,9 @@ func TestIsChannelClean(t *testing.T) { // removed from both commitments. // ---sig---> - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) @@ -9198,9 +9245,9 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // <---sig--- - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) @@ -9219,9 +9266,9 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // <---sig--- - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) @@ -9233,9 +9280,9 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // ---sig---> - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) @@ -9257,9 +9304,9 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // ---sig---> - aliceSig, aliceHtlcSigs, _, err = aliceChannel.SignNextCommitment() + aliceNewCommit, err = aliceChannel.SignNextCommitment() require.NoError(t, err) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) @@ -9271,9 +9318,9 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // <---sig--- - bobSig, bobHtlcSigs, _, err = bobChannel.SignNextCommitment() + bobNewCommit, err = bobChannel.SignNextCommitment() require.NoError(t, err) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) assertCleanOrDirty(false, aliceChannel, bobChannel, t) @@ -9409,9 +9456,9 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) { checkDust(bobChannel, htlc2Amt, htlc2Amt) // Alice signs for this HTLC and neither perspective should change. - aliceSig, aliceHtlcSigs, _, err := aliceChannel.SignNextCommitment() + aliceNewCommit, err := aliceChannel.SignNextCommitment() require.NoError(t, err) - err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) + err = bobChannel.ReceiveNewCommitment(aliceNewCommit.CommitSigs) require.NoError(t, err) checkDust(aliceChannel, htlc2Amt, htlc1Amt+htlc2Amt) checkDust(bobChannel, htlc2Amt, htlc2Amt) @@ -9428,9 +9475,9 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) { // The rest of the dance is completed and neither perspective should // change. - bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() + bobNewCommit, err := bobChannel.SignNextCommitment() require.NoError(t, err) - err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) + err = aliceChannel.ReceiveNewCommitment(bobNewCommit.CommitSigs) require.NoError(t, err) aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) diff --git a/lnwallet/chanvalidate/validate.go b/lnwallet/chanvalidate/validate.go index c349ea974b5..5cf5bbf1001 100644 --- a/lnwallet/chanvalidate/validate.go +++ b/lnwallet/chanvalidate/validate.go @@ -186,10 +186,14 @@ func Validate(ctx *Context) (*wire.OutPoint, error) { // If we reach this point, then all other checks have succeeded, so // we'll now attempt a full Script VM execution to ensure that we're // able to close the channel using this initial state. + prevFetcher := txscript.NewCannedPrevOutputFetcher( + ctx.MultiSigPkScript, fundingValue, + ) + commitTx := ctx.CommitCtx.FullySignedCommitTx + hashCache := txscript.NewTxSigHashes(commitTx, prevFetcher) vm, err := txscript.NewEngine( - ctx.MultiSigPkScript, ctx.CommitCtx.FullySignedCommitTx, - 0, txscript.StandardVerifyFlags, nil, nil, fundingValue, - txscript.NewCannedPrevOutputFetcher(ctx.MultiSigPkScript, 0), + ctx.MultiSigPkScript, commitTx, 0, txscript.StandardVerifyFlags, + nil, hashCache, fundingValue, prevFetcher, ) if err != nil { return nil, err diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index c4f4d931a40..04a615610f8 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" @@ -95,6 +96,13 @@ type CommitmentKeyRing struct { // If this is our commitment, it means the remote node can sign for // this key in case of a breach. RevocationKey *btcec.PublicKey + + // CombinedFundingKey is the taproot+musig2 funding key. This is the + // key _before_ the BIP 86 tweak is applied. + // + // NOTE: This will only be set if this is the KeyRing for a taproot + // channel. + CombinedFundingKey *btcec.PublicKey } // DeriveCommitmentKeys generates a new commitment key set using the base points @@ -177,6 +185,21 @@ func DeriveCommitmentKeys(commitPoint *btcec.PublicKey, ) } + // If this is a taproot commitment, then we'll use the funding keys to + // generate a combined funding key. + if chanType.IsTaproot() { + musigKey, _, _, _ := musig2.AggregateKeys( + []*btcec.PublicKey{ + localChanCfg.MultiSigKey.PubKey, + remoteChanCfg.MultiSigKey.PubKey, + }, + true, + musig2.WithBIP86KeyTweak(), + ) + + keyRing.CombinedFundingKey = musigKey.PreTweakedKey + } + return keyRing } @@ -190,6 +213,9 @@ type ScriptInfo struct { // output is being signed. For p2wkh it should be set equal to the // PkScript. WitnessScript []byte + + // TODO(roasbeef): embed the waddr taproot info? + // * can list the leaves, etc, etc } // CommitScriptToSelf constructs the public key script for the output on the @@ -202,36 +228,75 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, selfKey, revokeKey *btcec.PublicKey, csvDelay, leaseExpiry uint32) ( *ScriptInfo, error) { - var ( - toLocalRedeemScript []byte - err error - ) switch { + // For taproot scripts, we'll need to make a slightly modified script + // where the top level key is the revocation case, with our CSV timeout + // path living in a tapscript leaf. + // + // Our "redeem" script here is just the taproot witness program. + // + // TODO(roasbeef): rework ScriptInfo struct to have taproot specific + // info? + case chanType.IsTaproot(): + toLocalOutputKey, err := input.TaprootCommitScriptToSelf( + csvDelay, selfKey, revokeKey, + ) + if err != nil { + return nil, fmt.Errorf("unable to generate taproot "+ + "key: %w", err) + } + + toLocalPkScript, err := input.PayToTaprootScript( + toLocalOutputKey, + ) + + return &ScriptInfo{ + PkScript: toLocalPkScript, + }, nil + // If we are the initiator of a leased channel, then we have an - // additional CLTV requirement in addition to the usual CSV requirement. + // additional CLTV requirement in addition to the usual CSV + // requirement. case initiator && chanType.HasLeaseExpiration(): - toLocalRedeemScript, err = input.LeaseCommitScriptToSelf( + toLocalRedeemScript, err := input.LeaseCommitScriptToSelf( selfKey, revokeKey, csvDelay, leaseExpiry, ) + if err != nil { + return nil, err + } + + toLocalScriptHash, err := input.WitnessScriptHash( + toLocalRedeemScript, + ) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: toLocalScriptHash, + WitnessScript: toLocalRedeemScript, + }, nil default: - toLocalRedeemScript, err = input.CommitScriptToSelf( + toLocalRedeemScript, err := input.CommitScriptToSelf( csvDelay, selfKey, revokeKey, ) - } - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - toLocalScriptHash, err := input.WitnessScriptHash(toLocalRedeemScript) - if err != nil { - return nil, err - } + toLocalScriptHash, err := input.WitnessScriptHash( + toLocalRedeemScript, + ) + if err != nil { + return nil, err + } - return &ScriptInfo{ - PkScript: toLocalScriptHash, - WitnessScript: toLocalRedeemScript, - }, nil + return &ScriptInfo{ + PkScript: toLocalScriptHash, + WitnessScript: toLocalRedeemScript, + }, nil + } } // CommitScriptToRemote derives the appropriate to_remote script based on the @@ -239,8 +304,11 @@ func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool, // owner of the commitment transaction which we are generating the to_remote // script for. The second return value is the CSV delay of the output script, // what must be satisfied in order to spend the output. +// +// NOTE: The combinedFundingKey MUST be set if chanType is a taproot variant. func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, - key *btcec.PublicKey, leaseExpiry uint32) (*ScriptInfo, uint32, error) { + remoteKey *btcec.PublicKey, leaseExpiry uint32, + combinedFundingKey *btcec.PublicKey) (*ScriptInfo, uint32, error) { switch { // If we are not the initiator of a leased channel, then the remote @@ -248,7 +316,7 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, // CSV requirement. case chanType.HasLeaseExpiration() && !initiator: script, err := input.LeaseCommitScriptToRemoteConfirmed( - key, leaseExpiry, + remoteKey, leaseExpiry, ) if err != nil { return nil, 0, err @@ -264,10 +332,30 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, WitnessScript: script, }, 1, nil + // For taproot channels, we'll use a slightly different format, where + // the top-level key is the combined funding key (w/o the bip 86 + // tweak), with the sole tap leaf enforcing the 1 CSV delay. + case chanType.IsTaproot(): + toRemoteKey, err := input.TaprootCommitScriptToRemote( + combinedFundingKey, remoteKey, + ) + if err != nil { + return nil, 0, err + } + + toRemotePkScript, err := input.PayToTaprootScript(toRemoteKey) + if err != nil { + return nil, 0, err + } + + return &ScriptInfo{ + PkScript: toRemotePkScript, + }, 1, nil + // If this channel type has anchors, we derive the delayed to_remote // script. case chanType.HasAnchors(): - script, err := input.CommitScriptToRemoteConfirmed(key) + script, err := input.CommitScriptToRemoteConfirmed(remoteKey) if err != nil { return nil, 0, err } @@ -284,7 +372,7 @@ func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool, default: // Otherwise the to_remote will be a simple p2wkh. - p2wkh, err := input.CommitScriptUnencumbered(key) + p2wkh, err := input.CommitScriptUnencumbered(remoteKey) if err != nil { return nil, 0, err } @@ -418,45 +506,98 @@ func HtlcSuccessFee(chanType channeldb.ChannelType, return 0 } + // TODO(roasbeef): fee is still off here? + if chanType.HasAnchors() { return feePerKw.FeeForWeight(input.HtlcSuccessWeightConfirmed) } + return feePerKw.FeeForWeight(input.HtlcSuccessWeight) } // CommitScriptAnchors return the scripts to use for the local and remote // anchor. -func CommitScriptAnchors(localChanCfg, - remoteChanCfg *channeldb.ChannelConfig) (*ScriptInfo, - *ScriptInfo, error) { +func CommitScriptAnchors(chanType channeldb.ChannelType, + localChanCfg, remoteChanCfg *channeldb.ChannelConfig, + keyRing *CommitmentKeyRing) (*ScriptInfo, *ScriptInfo, error) { - // Helper to create anchor ScriptInfo from key. - anchorScript := func(key *btcec.PublicKey) (*ScriptInfo, error) { - script, err := input.CommitScriptAnchor(key) - if err != nil { - return nil, err + var ( + anchorScript func(key *btcec.PublicKey) (*ScriptInfo, error) + keySelector func(*channeldb.ChannelConfig, bool) *btcec.PublicKey + ) + + switch { + // For taproot channels, the anchor is slightly different: the top + // level key is now the (relative) local delay and remote public key, + // since these are fully revealed once the commitment hits the chain. + // + // TODO(roasbeef): need to re-examine the assumption of what's + // revealed? otherwise then have two levels of tweaks... + case chanType.IsTaproot(): + anchorScript = func(key *btcec.PublicKey) (*ScriptInfo, error) { + anchorKey, err := input.TaprootOutputKeyAnchor(key) + if err != nil { + return nil, err + } + + anchorPkScript, err := input.PayToTaprootScript(anchorKey) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: anchorPkScript, + }, nil } - scriptHash, err := input.WitnessScriptHash(script) - if err != nil { - return nil, err + keySelector = func(cfg *channeldb.ChannelConfig, + local bool) *btcec.PublicKey { + + if local { + return keyRing.ToLocalKey + } + + return keyRing.ToRemoteKey } - return &ScriptInfo{ - PkScript: scriptHash, - WitnessScript: script, - }, nil + // For normal channels we'll use the multi-sig keys since those are + // revealed when the channel closes + default: + // For normal channels, we'll create a p2wsh script based on + // the target key. + anchorScript = func(key *btcec.PublicKey) (*ScriptInfo, error) { + script, err := input.CommitScriptAnchor(key) + if err != nil { + return nil, err + } + + scriptHash, err := input.WitnessScriptHash(script) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: scriptHash, + WitnessScript: script, + }, nil + } + + // For the existing channels, we'll always select the multi-sig + // key from the party's channel config. + keySelector = func(cfg *channeldb.ChannelConfig, _ bool) *btcec.PublicKey { + return cfg.MultiSigKey.PubKey + } } // Get the script used for the anchor output spendable by the local // node. - localAnchor, err := anchorScript(localChanCfg.MultiSigKey.PubKey) + localAnchor, err := anchorScript(keySelector(localChanCfg, true)) if err != nil { return nil, nil, err } // And the anchor spendable by the remote node. - remoteAnchor, err := anchorScript(remoteChanCfg.MultiSigKey.PubKey) + remoteAnchor, err := anchorScript(keySelector(remoteChanCfg, false)) if err != nil { return nil, nil, err } @@ -468,7 +609,7 @@ func CommitScriptAnchors(localChanCfg, // with, and abstracts the various ways of constructing commitment // transactions. type CommitmentBuilder struct { - // chanState is the underlying channels's state struct, used to + // chanState is the underlying channel's state struct, used to // determine the type of channel we are dealing with, and relevant // parameters. chanState *channeldb.OpenChannel @@ -753,6 +894,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, // Next, we create the script paying to the remote. toRemoteScript, _, err := CommitScriptToRemote( chanType, initiator, keyRing.ToRemoteKey, leaseExpiry, + keyRing.CombinedFundingKey, ) if err != nil { return nil, err @@ -784,7 +926,7 @@ func CreateCommitTx(chanType channeldb.ChannelType, // If this channel type has anchors, we'll also add those. if chanType.HasAnchors() { localAnchor, remoteAnchor, err := CommitScriptAnchors( - localChanCfg, remoteChanCfg, + chanType, localChanCfg, remoteChanCfg, keyRing, ) if err != nil { return nil, err @@ -853,12 +995,10 @@ func CoopCloseBalance(chanType channeldb.ChannelType, isInitiator bool, return ourBalance, theirBalance, nil } -// genHtlcScript generates the proper P2WSH public key scripts for the HTLC -// output modified by two-bits denoting if this is an incoming HTLC, and if the -// HTLC is being applied to their commitment transaction or ours. -func genHtlcScript(chanType channeldb.ChannelType, isIncoming, ourCommit bool, - timeout uint32, rHash [32]byte, - keyRing *CommitmentKeyRing) ([]byte, []byte, error) { +// genSegwitV0HtlcScript.... +func genSegwitV0HtlcScript(chanType channeldb.ChannelType, + isIncoming, ourCommit bool, timeout uint32, rHash [32]byte, + keyRing *CommitmentKeyRing) (*ScriptInfo, error) { var ( witnessScript []byte @@ -912,17 +1052,157 @@ func genHtlcScript(chanType channeldb.ChannelType, isIncoming, ourCommit bool, ) } if err != nil { - return nil, nil, err + return nil, err } // Now that we have the redeem scripts, create the P2WSH public key // script for the output itself. htlcP2WSH, err := input.WitnessScriptHash(witnessScript) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: htlcP2WSH, + WitnessScript: witnessScript, + }, nil +} + +// genTaprootHtlcScript.... +func genTaprootHtlcScript(chanType channeldb.ChannelType, isIncoming, + ourCommit bool, timeout uint32, rHash [32]byte, + keyRing *CommitmentKeyRing) (*ScriptInfo, error) { + + var ( + taprootKey *btcec.PublicKey + secondLevelScript []byte + ) + + // Generate the proper redeem scripts for the HTLC output modified by + // two-bits denoting if this is an incoming HTLC, and if the HTLC is + // being applied to their commitment transaction or ours. + switch { + // The HTLC is paying to us, and being applied to our commitment + // transaction. So we need to use the receiver's version of HTLC the + // script. + case isIncoming && ourCommit: + scriptTree, err := input.ReceiverHTLCScriptTaproot( + timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, + keyRing.RevocationKey, rHash[:], + ) + if err != nil { + return nil, err + } + + taprootKey = scriptTree.TaprootKey + + // As this is an HTLC on our commitment transaction, the second + // level path we care about here is the success path. + // Therefore, we'll grab the tapLeaf corresponding to the + // success path. + secondLevelScript = scriptTree.SuccessTapLeaf.Script + + // We're being paid via an HTLC by the remote party, and the HTLC is + // being added to their commitment transaction, so we use the sender's + // version of the HTLC script. + case isIncoming && !ourCommit: + scriptTree, err := input.SenderHTLCScriptTaproot( + keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, + keyRing.RevocationKey, rHash[:], + ) + if err != nil { + return nil, err + } + + taprootKey = scriptTree.TaprootKey + + // In this case, this is an incoming HTLC on the commitment + // transaction of the remote party, so we'll return the timeout + // tapleaf since that's the second level spend they need in the + // case of a broadcast. + secondLevelScript = scriptTree.TimeoutTapLeaf.Script + + // We're sending an HTLC which is being added to our commitment + // transaction. Therefore, we need to use the sender's version of the + // HTLC script. + case !isIncoming && ourCommit: + scriptTree, err := input.SenderHTLCScriptTaproot( + keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, + keyRing.RevocationKey, rHash[:], + ) + if err != nil { + return nil, err + } + + taprootKey = scriptTree.TaprootKey + + // This is an outgoing HTLC on our commitment transaction, so + // we need to be able to generate/verify signatures for the + // timeout path. + secondLevelScript = scriptTree.TimeoutTapLeaf.Script + + // Finally, we're paying the remote party via an HTLC, which is being + // added to their commitment transaction. Therefore, we use the + // receiver's version of the HTLC script. + case !isIncoming && !ourCommit: + scriptTree, err := input.ReceiverHTLCScriptTaproot( + timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, + keyRing.RevocationKey, rHash[:], + ) + if err != nil { + return nil, err + } + + taprootKey = scriptTree.TaprootKey + + // This is an outgoing HTLC on the remote party's commitment + // transaction. In this case if they go on chain, they'll need + // the second level success spend, so we grab that tapscript + // path. + secondLevelScript = scriptTree.SuccessTapLeaf.Script + } + + // Now that we have the redeem scripts, create the P2TR public key + // script for the output itself. + p2trOutput, err := input.PayToTaprootScript(taprootKey) + if err != nil { + return nil, err + } + + return &ScriptInfo{ + PkScript: p2trOutput, + WitnessScript: secondLevelScript, + }, nil +} + +// genHtlcScript generates the proper P2WSH public key scripts for the HTLC +// output modified by two-bits denoting if this is an incoming HTLC, and if the +// HTLC is being applied to their commitment transaction or ours. +func genHtlcScript(chanType channeldb.ChannelType, isIncoming, ourCommit bool, + timeout uint32, rHash [32]byte, + keyRing *CommitmentKeyRing) ([]byte, []byte, error) { + + var ( + scriptInfo *ScriptInfo + err error + ) + + if !chanType.IsTaproot() { + scriptInfo, err = genSegwitV0HtlcScript( + chanType, isIncoming, ourCommit, timeout, rHash, + keyRing, + ) + } else { + scriptInfo, err = genTaprootHtlcScript( + chanType, isIncoming, ourCommit, timeout, rHash, + keyRing, + ) + } if err != nil { return nil, nil, err } - return htlcP2WSH, witnessScript, nil + return scriptInfo.PkScript, scriptInfo.WitnessScript, nil } // addHTLC adds a new HTLC to the passed commitment transaction. One of four @@ -939,7 +1219,7 @@ func addHTLC(commitTx *wire.MsgTx, ourCommit bool, timeout := paymentDesc.Timeout rHash := paymentDesc.RHash - p2wsh, witnessScript, err := genHtlcScript( + witnessProgram, witnessScript, err := genHtlcScript( chanType, isIncoming, ourCommit, timeout, rHash, keyRing, ) if err != nil { @@ -948,15 +1228,15 @@ func addHTLC(commitTx *wire.MsgTx, ourCommit bool, // Add the new HTLC outputs to the respective commitment transactions. amountPending := int64(paymentDesc.Amount.ToSatoshis()) - commitTx.AddTxOut(wire.NewTxOut(amountPending, p2wsh)) + commitTx.AddTxOut(wire.NewTxOut(amountPending, witnessProgram)) // Store the pkScript of this particular PaymentDescriptor so we can // quickly locate it within the commitment transaction later. if ourCommit { - paymentDesc.ourPkScript = p2wsh + paymentDesc.ourPkScript = witnessProgram paymentDesc.ourWitnessScript = witnessScript } else { - paymentDesc.theirPkScript = p2wsh + paymentDesc.theirPkScript = witnessProgram paymentDesc.theirWitnessScript = witnessScript } @@ -1017,7 +1297,7 @@ func findOutputIndexesFromRemote(revocationPreimage *chainhash.Hash, // commitment, the to remote output belongs to us. ourScript, _, err := CommitScriptToRemote( chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey, - leaseExpiry, + leaseExpiry, keyRing.CombinedFundingKey, ) if err != nil { return ourIndex, theirIndex, err diff --git a/lnwallet/musession/manager.go b/lnwallet/musession/manager.go new file mode 100644 index 00000000000..086e3765d30 --- /dev/null +++ b/lnwallet/musession/manager.go @@ -0,0 +1,309 @@ +package musession + +import ( + "crypto/sha256" + "fmt" + "sync" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" +) + +// MuSig2State is a struct that holds on to the internal signing session state +// of a MuSig2 session. +type MuSig2State struct { + // MuSig2SessionInfo is the associated meta information of the signing + // session. + input.MuSig2SessionInfo + + // context is the signing context responsible for keeping track of the + // public keys involved in the signing process. + context *musig2.Context + + // session is the signing session responsible for keeping track of the + // nonces and partial signatures involved in the signing process. + session *musig2.Session +} + +// PrivKeyFetcher... +type PrivKeyFetcher func(*keychain.KeyDescriptor) (*btcec.PrivateKey, error) + +// Manager... +type Manager struct { + sync.Mutex + + keyFetcher PrivKeyFetcher + + musig2Sessions map[input.MuSig2SessionID]*MuSig2State +} + +// NewManager... +func NewManager(keyFetcher PrivKeyFetcher) *Manager { + return &Manager{ + keyFetcher: keyFetcher, + musig2Sessions: make(map[input.MuSig2SessionID]*MuSig2State), + } +} + +// MuSig2CreateSession creates a new MuSig2 signing session using the local +// key identified by the key locator. The complete list of all public keys of +// all signing parties must be provided, including the public key of the local +// signing key. If nonces of other parties are already known, they can be +// submitted as well to reduce the number of method calls necessary later on. +// +// The set of sessionOpts are _optional_ and allow a caller to modify the +// generated sessions. As an example the local nonce might already be generated +// ahead of time. +func (m *Manager) MuSig2CreateSession(keyLoc keychain.KeyLocator, + allSignerPubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks, + otherSignerNonces [][musig2.PubNonceSize]byte, + sessionOpts ...musig2.SessionOption) (*input.MuSig2SessionInfo, error) { + + // We need to derive the private key for signing. In the remote signing + // setup, this whole RPC call will be forwarded to the signing + // instance, which requires it to be stateful. + privKey, err := m.keyFetcher(&keychain.KeyDescriptor{ + KeyLocator: keyLoc, + }) + if err != nil { + return nil, fmt.Errorf("error deriving private key: %v", err) + } + + // The context keeps track of all signing keys and our local key. + allOpts := append( + []musig2.ContextOption{ + musig2.WithKnownSigners(allSignerPubKeys), + }, + tweaks.ToContextOptions()..., + ) + musigContext, err := musig2.NewContext(privKey, true, allOpts...) + if err != nil { + return nil, fmt.Errorf("error creating MuSig2 signing "+ + "context: %v", err) + } + + // The session keeps track of the own and other nonces. + musigSession, err := musigContext.NewSession(sessionOpts...) + if err != nil { + return nil, fmt.Errorf("error creating MuSig2 signing "+ + "session: %v", err) + } + + // TODO(roasbeef): actually want to expose the context for channels so + // don't always need to re-create? + + // Add all nonces we might've learned so far. + haveAllNonces := false + for _, otherSignerNonce := range otherSignerNonces { + haveAllNonces, err = musigSession.RegisterPubNonce( + otherSignerNonce, + ) + if err != nil { + return nil, fmt.Errorf("error registering other "+ + "signer public nonce: %v", err) + } + } + + // Register the new session. + combinedKey, err := musigContext.CombinedKey() + if err != nil { + return nil, fmt.Errorf("error getting combined key: %v", err) + } + session := &MuSig2State{ + MuSig2SessionInfo: input.MuSig2SessionInfo{ + SessionID: input.NewMuSig2SessionID( + combinedKey, musigSession.PublicNonce(), + ), + PublicNonce: musigSession.PublicNonce(), + CombinedKey: combinedKey, + TaprootTweak: tweaks.HasTaprootTweak(), + HaveAllNonces: haveAllNonces, + }, + context: musigContext, + session: musigSession, + } + + // The internal key is only calculated if we are using a taproot tweak + // and need to know it for a potential script spend. + if tweaks.HasTaprootTweak() { + internalKey, err := musigContext.TaprootInternalKey() + if err != nil { + return nil, fmt.Errorf("error getting internal key: %v", + err) + } + session.TaprootInternalKey = internalKey + } + + // Since we generate new nonces for every session, there is no way that + // a session with the same ID already exists. So even if we call the API + // twice with the same signers, we still get a new ID. + m.Lock() + m.musig2Sessions[session.SessionID] = session + m.Unlock() + + return &session.MuSig2SessionInfo, nil +} + +// MuSig2Sign creates a partial signature using the local signing key +// that was specified when the session was created. This can only be +// called when all public nonces of all participants are known and have +// been registered with the session. If this node isn't responsible for +// combining all the partial signatures, then the cleanup parameter +// should be set, indicating that the session can be removed from memory +// once the signature was produced. +func (m *Manager) MuSig2Sign(sessionID input.MuSig2SessionID, + msg [sha256.Size]byte, cleanUp bool) (*musig2.PartialSignature, error) { + + // We hold the lock during the whole operation, we don't want any + // interference with calls that might come through in parallel for the + // same session. + m.Lock() + defer m.Unlock() + + session, ok := m.musig2Sessions[sessionID] + if !ok { + return nil, fmt.Errorf("session with ID %x not found", + sessionID[:]) + } + + // We can only sign once we have all other signer's nonces. + if !session.HaveAllNonces { + return nil, fmt.Errorf("only have %d of %d required nonces", + session.session.NumRegisteredNonces(), + len(session.context.SigningKeys())) + } + + // Create our own partial signature with the local signing key. + partialSig, err := session.session.Sign(msg, musig2.WithSortedKeys()) + if err != nil { + return nil, fmt.Errorf("error signing with local key: %v", err) + } + + // Clean up our local state if requested. + if cleanUp { + delete(m.musig2Sessions, sessionID) + } + + return partialSig, nil +} + +// MuSig2CombineSig combines the given partial signature(s) with the +// local one, if it already exists. Once a partial signature of all +// participants is registered, the final signature will be combined and +// returned. +func (m *Manager) MuSig2CombineSig(sessionID input.MuSig2SessionID, + partialSigs []*musig2.PartialSignature) (*schnorr.Signature, bool, + error) { + + // We hold the lock during the whole operation, we don't want any + // interference with calls that might come through in parallel for the + // same session. + m.Lock() + defer m.Unlock() + + session, ok := m.musig2Sessions[sessionID] + if !ok { + return nil, false, fmt.Errorf("session with ID %x not found", + sessionID[:]) + } + + // Make sure we don't exceed the number of expected partial signatures + // as that would indicate something is wrong with the signing setup. + if session.HaveAllSigs { + return nil, true, fmt.Errorf("already have all partial" + + "signatures") + } + + // Add all sigs we got so far. + var ( + finalSig *schnorr.Signature + err error + ) + for _, otherPartialSig := range partialSigs { + session.HaveAllSigs, err = session.session.CombineSig( + otherPartialSig, + ) + if err != nil { + return nil, false, fmt.Errorf("error combining "+ + "partial signature: %v", err) + } + } + + // If we have all partial signatures, we should be able to get the + // complete signature now. We also remove this session from memory since + // there is nothing more left to do. + if session.HaveAllSigs { + finalSig = session.session.FinalSig() + delete(m.musig2Sessions, sessionID) + } + + return finalSig, session.HaveAllSigs, nil +} + +// MuSig2Cleanup removes a session from memory to free up resources. +func (m *Manager) MuSig2Cleanup(sessionID input.MuSig2SessionID) error { + // We hold the lock during the whole operation, we don't want any + // interference with calls that might come through in parallel for the + // same session. + m.Lock() + defer m.Unlock() + + _, ok := m.musig2Sessions[sessionID] + if !ok { + return fmt.Errorf("session with ID %x not found", sessionID[:]) + } + + delete(m.musig2Sessions, sessionID) + + return nil +} + +// MuSig2RegisterNonces registers one or more public nonces of other signing +// participants for a session identified by its ID. This method returns true +// once we have all nonces for all other signing participants. +func (m *Manager) MuSig2RegisterNonces(sessionID input.MuSig2SessionID, + otherSignerNonces [][musig2.PubNonceSize]byte) (bool, error) { + + // We hold the lock during the whole operation, we don't want any + // interference with calls that might come through in parallel for the + // same session. + m.Lock() + defer m.Unlock() + + session, ok := m.musig2Sessions[sessionID] + if !ok { + return false, fmt.Errorf("session with ID %x not found", + sessionID[:]) + } + + // Make sure we don't exceed the number of expected nonces as that would + // indicate something is wrong with the signing setup. + if session.HaveAllNonces { + return true, fmt.Errorf("already have all nonces") + } + + numSigners := len(session.context.SigningKeys()) + remainingNonces := numSigners - session.session.NumRegisteredNonces() + if len(otherSignerNonces) > remainingNonces { + return false, fmt.Errorf("only %d other nonces remaining but "+ + "trying to register %d more", remainingNonces, + len(otherSignerNonces)) + } + + // Add all nonces we've learned so far. + var err error + for _, otherSignerNonce := range otherSignerNonces { + session.HaveAllNonces, err = session.session.RegisterPubNonce( + otherSignerNonce, + ) + if err != nil { + return false, fmt.Errorf("error registering other "+ + "signer public nonce: %v", err) + } + } + + return session.HaveAllNonces, nil +} diff --git a/lnwallet/musig2_session.go b/lnwallet/musig2_session.go new file mode 100644 index 00000000000..74757b7fb42 --- /dev/null +++ b/lnwallet/musig2_session.go @@ -0,0 +1,502 @@ +package lnwallet + +import ( + "bytes" + "fmt" + "io" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/shachain" +) + +// MusigPartialSig... +// +// TODO(roasbeef): move to wire package? +type MusigPartialSig struct { + sig *musig2.PartialSignature + + signerNonce [musig2.PubNonceSize]byte + + combinedNonce [musig2.PubNonceSize]byte + + signerKeys []*btcec.PublicKey +} + +// NewMusigPartialSig... +// +// TODO(roasbeef): need version that lets bind the rest later? +func NewMusigPartialSig(sig *musig2.PartialSignature, + signerNonce, combinedNonce [musig2.PubNonceSize]byte, + signerKeys []*btcec.PublicKey) *MusigPartialSig { + + return &MusigPartialSig{ + sig: sig, + signerNonce: signerNonce, + combinedNonce: combinedNonce, + signerKeys: signerKeys, + } +} + +// FromWireSig... +func (p *MusigPartialSig) FromWireSig(sig *lnwire.PartialSigWithNonce, +) *MusigPartialSig { + + p.sig = &musig2.PartialSignature{ + S: &sig.Sig, + } + p.signerNonce = sig.Nonce + + return p +} + +// ToWireSig... +func (p *MusigPartialSig) ToWireSig() *lnwire.PartialSigWithNonce { + return &lnwire.PartialSigWithNonce{ + PartialSig: lnwire.NewPartialSig(*p.sig.S), + Nonce: p.signerNonce, + } +} + +// Serialize serializes the musig2 partial signature. The serializing includes +// the signer's public nonce _and_ the partial signature. The final signature +// is always 98 bytes in length. +func (p *MusigPartialSig) Serialize() []byte { + var b bytes.Buffer + + p.ToWireSig().Encode(&b) + + return b.Bytes() +} + +// ToSchnorrShell... +func (p *MusigPartialSig) ToSchnorrShell() *schnorr.Signature { + var zeroVal btcec.FieldVal + return schnorr.NewSignature(&zeroVal, p.sig.S) +} + +// FromSchnorrShell... +// +// TODO(roasbeef): remove this and the above, pkg w/ nonce instead +func (p *MusigPartialSig) FromSchnorrShell(sig *schnorr.Signature) { + var ( + partialS btcec.ModNScalar + partialSBytes [32]byte + ) + copy(partialSBytes[:], sig.Serialize()[32:]) + partialS.SetBytes(&partialSBytes) + + p.sig = &musig2.PartialSignature{ + S: &partialS, + } +} + +// TODO(roasbeef): parse method, can recompute the nonce like above? + +// Verify... +func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool { + var m [32]byte + copy(m[:], msg) + + return p.sig.Verify( + p.signerNonce, p.combinedNonce, p.signerKeys, pub, m, + musig2.WithSortedKeys(), musig2.WithBip86SignTweak(), + ) +} + +// MusigNoncePair... +type MusigNoncePair struct { + // SigningNonce... + SigningNonce musig2.Nonces + + // VerificationNonce... + VerificationNonce musig2.Nonces +} + +// String... +func (n *MusigNoncePair) String() string { + return fmt.Sprintf("NoncePair(verification_nonce=%x, "+ + "signing_nonce=%x)", n.VerificationNonce.PubNonce[:], + n.SigningNonce.PubNonce[:]) +} + +// MusigSession... +type MusigSession struct { + session *input.MuSig2SessionInfo + + combinedNonce [musig2.PubNonceSize]byte + + // nonces is the set of nonces that'll be used to generate/verify the + // next commitment. + nonces MusigNoncePair + + // inputTxOut... + inputTxOut *wire.TxOut + + // signerKeys... + signerKeys []*btcec.PublicKey + + // remoteKey... + remoteKey keychain.KeyDescriptor + + // localKey... + localKey keychain.KeyDescriptor + + // signer... + signer input.MuSig2Signer + + // remoteCommit tracks if this session is for the remote commitment. + remoteCommit bool +} + +// NewPartialMusigSession... +func NewPartialMusigSession(verificationNonce musig2.Nonces, + localKey, remoteKey keychain.KeyDescriptor, + signer input.MuSig2Signer, inputTxOut *wire.TxOut, + remoteCommit bool) *MusigSession { + + signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey} + + nonces := MusigNoncePair{ + VerificationNonce: verificationNonce, + } + + return &MusigSession{ + nonces: nonces, + remoteKey: remoteKey, + localKey: localKey, + inputTxOut: inputTxOut, + signerKeys: signerKeys, + signer: signer, + remoteCommit: remoteCommit, + } +} + +// finalizeSession... +// +// TODO(roasbeef): make private again, add NewMusigSessionthat calls above then +// calls thisd +func (m *MusigSession) FinalizeSession(signingNonce musig2.Nonces) error { + var ( + localNonce, remoteNonce musig2.Nonces + err error + ) + + // First, we'll stash the freshly generated signing nonce. Depending on + // who's commitment we're handling, this'll either be our generated + // nonce, or the one we just got from the remote party. + m.nonces.SigningNonce = signingNonce + + switch { + + // If we're making a session for the remote commitment, then the nonce + // we use to sign is actually will be the signing nonce for the + // session, and their nonce the verification nonce. + case m.remoteCommit: + localNonce = m.nonces.SigningNonce + remoteNonce = m.nonces.VerificationNonce + + // Otherwise, we're generating/receiving a signature for our local + // commitment (to broadcast), so now our verification nonce is the one + // we've already generated, and we want to bind their new signing + // nonce. + default: + localNonce = m.nonces.VerificationNonce + remoteNonce = m.nonces.SigningNonce + } + + tweakDesc := input.MuSig2Tweaks{ + TaprootBIP0086Tweak: true, + } + m.session, err = m.signer.MuSig2CreateSession( + m.localKey.KeyLocator, m.signerKeys, &tweakDesc, + [][musig2.PubNonceSize]byte{remoteNonce.PubNonce}, + musig2.WithPreGeneratedNonce(&localNonce), + ) + if err != nil { + return err + } + + // We'll need the raw combined nonces later to be able to verify + // partial signatures, and also combine partial signatures, so we'll + // generate it now ourselves. + m.combinedNonce, err = musig2.AggregateNonces([][musig2.PubNonceSize]byte{ + m.nonces.SigningNonce.PubNonce, + m.nonces.VerificationNonce.PubNonce, + }) + if err != nil { + return nil + } + + return nil +} + +// taprootKeyspendSighash... +func taprootKeyspendSighash(tx *wire.MsgTx, pkScript []byte, + value int64) ([]byte, error) { + + prevOutputFetcher := txscript.NewCannedPrevOutputFetcher( + pkScript, value, + ) + + sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher) + + return txscript.CalcTaprootSignatureHash( + sigHashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher, + ) +} + +// SignCommit signs the passed commitment w/ the current signing (relative +// remote) nonce. Given nonces should only ever be used once, once the method +// returns a new nonce is returned, w/ the existing nonce blanked out. +func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) { + // If we already have a session, then we don't need to finalize as this + // was done up front (symmetric nonce case, like for co-op close). + if m.session == nil { + // Before we can sign a new commitment, we'll need to generate + // a fresh nonce that'll be sent along side our signature. With + // the nonce in hand, we can finalize the session. + // + // TODO(roasbeef): can also pass in stuff like the sighash to + // further bind context, etc, etc. + signingNonce, err := musig2.GenNonces( + musig2.WithPublicKey(m.localKey.PubKey), + ) + if err != nil { + return nil, err + } + if err := m.FinalizeSession(*signingNonce); err != nil { + return nil, err + } + } + + // Once we sign with a nonce, we'll never use it again, so it's safe to + // go ahead and clean up the session right here. + // defer m.signer.MuSig2Cleanup(m.session.SessionID) + // + // TODO(roasbeef): can't clean up here as need to combine sig + + // Next we can sign, we'll need to generate the sighash for their + // commitment transaction. + sigHash, err := taprootKeyspendSighash( + tx, m.inputTxOut.PkScript, m.inputTxOut.Value, + ) + if err != nil { + return nil, err + } + + // Now that we have our session created, we'll use it to generate the + // initial partial signature over our sighash. + var sigHashMsg [32]byte + copy(sigHashMsg[:], sigHash) + + walletLog.Infof("Generating new musig2 sig for session=%x, nonces=%s", + m.session.SessionID[:], m.nonces.String()) + + sig, err := m.signer.MuSig2Sign( + m.session.SessionID, sigHashMsg, false, + ) + if err != nil { + return nil, err + } + + return NewMusigPartialSig( + sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys, + ), nil +} + +// Refresh is called once we receive a new verification nonce from the remote +// party after sending a signature. This nonce will be coupled within the +// revoke-and-ack message of the remote party. +func (m *MusigSession) Refresh(verificationNonce *musig2.Nonces, +) (*MusigSession, error) { + + return NewPartialMusigSession( + *verificationNonce, m.localKey, m.remoteKey, m.signer, + m.inputTxOut, m.remoteCommit, + ), nil +} + +// VerificationNonce returns the current verification nonce for the session. +func (m *MusigSession) VerificationNonce() *musig2.Nonces { + return &m.nonces.VerificationNonce +} + +// musigSessionOpts... +type musigSessionOpts struct { + customRand io.Reader +} + +// defaultMusigSessionOpts... +func defaultMusigSessionOpts() *musigSessionOpts { + return &musigSessionOpts{} +} + +// MusigSessionOpt... +type MusigSessionOpt func(*musigSessionOpts) + +// WithLocalCounterNonce... +func WithLocalCounterNonce(targetHeight uint64, + shaGen shachain.Producer) MusigSessionOpt { + + return func(opt *musigSessionOpts) { + nextPreimage, _ := shaGen.AtIndex(targetHeight) + + opt.customRand = bytes.NewBuffer(nextPreimage[:]) + } +} + +// VerifyCommitSig attempts to verify the passed partial signature against the +// passed commitment transaction. A keyspend sighash is assumed to generate the +// signed message. As we never re-use nonces, a new verification nonce (our +// relative local nonce) returned to transmit to the remote party, which allows +// them to generate another signature. +func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx, + sig *lnwire.PartialSigWithNonce, + musigOpts ...MusigSessionOpt) (*musig2.Nonces, error) { + + opts := defaultMusigSessionOpts() + for _, optFunc := range musigOpts { + optFunc(opts) + } + + // Before we can verify the signature, we'll need to finalize the + // session by binding the remote party's provided signing nonce. + if err := m.FinalizeSession(musig2.Nonces{ + PubNonce: sig.Nonce, + }); err != nil { + return nil, err + } + + // When we verify a commitment signature, we always assume that we're + // verifying a signature on our local commitment. Therefore, we'll use: + // their remote nonce, and also public key. + partialSig := NewMusigPartialSig( + &musig2.PartialSignature{S: &sig.Sig}, + m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys, + ) + + walletLog.Infof("verify partial sig: %v", spew.Sdump(partialSig)) + + // With the partial sig loaded with the proper context, we'll now + // generate the sighash that the remote party should have signed. + sigHash, err := taprootKeyspendSighash( + commitTx, m.inputTxOut.PkScript, m.inputTxOut.Value, + ) + if err != nil { + return nil, err + } + + walletLog.Infof("Verfiying new musig2 sig for session=%x, nonce=%s", + m.session.SessionID[:], m.nonces.String()) + + if !partialSig.Verify(sigHash, m.remoteKey.PubKey) { + return nil, fmt.Errorf("invalid partial commit sig") + } + + nonceOpts := []musig2.NonceGenOption{ + musig2.WithPublicKey(m.localKey.PubKey), + } + if opts.customRand != nil { + nonceOpts = append( + nonceOpts, musig2.WithCustomRand(opts.customRand), + ) + } + + // At this point, we know that their signature is valid, so we'll + // generate another verification nonce for them, so they can generate a + // new state transition. + nextVerificationNonce, err := musig2.GenNonces(nonceOpts...) + if err != nil { + return nil, fmt.Errorf("unable to gen new nonce: %w", err) + } + + return nextVerificationNonce, nil +} + +// CombineSigs... +func (m *MusigSession) CombineSigs(sigs ...*musig2.PartialSignature, +) (*schnorr.Signature, error) { + + sig, _, err := m.signer.MuSig2CombineSig( + m.session.SessionID, + sigs, + ) + if err != nil { + return nil, err + } + + return sig, nil +} + +// MusigSessionCfg... +type MusigSessionCfg struct { + // LocalKey... + LocalKey keychain.KeyDescriptor + + // RemoteKey... + RemoteKey keychain.KeyDescriptor + + // LocalNonce... + LocalNonce musig2.Nonces + + // RemoteNonce... + RemoteNonce musig2.Nonces + + // Signer... + Signer input.MuSig2Signer + + // InputTxOut... + InputTxOut *wire.TxOut +} + +// MusigPairSession... +// +// TODO(roasbeef): split this up into two sessions? then can just make one +// later to be able to sign the txns +// +// TODO(roasbeef): chan session? +type MusigPairSession struct { + // LocalSession... + LocalSession *MusigSession + + // RemoteSession... + RemoteSession *MusigSession + + // signer... + signer input.MuSig2Signer +} + +// TODO(roasbeef): move sig here? + +// NewMusigPairSession.... +func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession { + // Given the config passed in, we'll now create our two sessions: one + // for the local commit, and one for the remote commit. + // + // Both sessions will be created using only the verification nonce for + // the local+remote party. + localSession := NewPartialMusigSession( + cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, + cfg.Signer, cfg.InputTxOut, false, + ) + remoteSession := NewPartialMusigSession( + cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, + cfg.Signer, cfg.InputTxOut, true, + ) + + return &MusigPairSession{ + LocalSession: localSession, + RemoteSession: remoteSession, + signer: cfg.Signer, + } +} + +// TODO(roasbeef): chan reest has a late nonce binding diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index ba9fc76c6b3..5515a65a20f 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -6,6 +6,7 @@ import ( "sync" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" @@ -42,6 +43,11 @@ const ( // guarantee that the channel initiator has no incentives to close a // leased channel before its maturity date. CommitmentTypeScriptEnforcedLease + + // CommitmentTypeSimpleTaproot is the base commitment type for the + // channels that use a musig2 funding output and the tapscript tree + // where relevant for the commitment transaciton pk scripts. + CommitmentTypeSimpleTaproot ) // HasStaticRemoteKey returns whether the commitment type supports remote @@ -50,7 +56,8 @@ func (c CommitmentType) HasStaticRemoteKey() bool { switch c { case CommitmentTypeTweakless, CommitmentTypeAnchorsZeroFeeHtlcTx, - CommitmentTypeScriptEnforcedLease: + CommitmentTypeScriptEnforcedLease, + CommitmentTypeSimpleTaproot: return true default: return false @@ -61,13 +68,19 @@ func (c CommitmentType) HasStaticRemoteKey() bool { func (c CommitmentType) HasAnchors() bool { switch c { case CommitmentTypeAnchorsZeroFeeHtlcTx, - CommitmentTypeScriptEnforcedLease: + CommitmentTypeScriptEnforcedLease, + CommitmentTypeSimpleTaproot: return true default: return false } } +// IsTaproot... +func (c CommitmentType) IsTaproot() bool { + return c == CommitmentTypeSimpleTaproot +} + // String returns the name of the CommitmentType. func (c CommitmentType) String() string { switch c { @@ -79,6 +92,8 @@ func (c CommitmentType) String() string { return "anchors-zero-fee-second-level" case CommitmentTypeScriptEnforcedLease: return "script-enforced-lease" + case CommitmentTypeSimpleTaproot: + return "simple-taproot" default: return "invalid" } @@ -117,6 +132,11 @@ type ChannelContribution struct { // UpfrontShutdown is an optional address to which the channel should be // paid out to on cooperative close. UpfrontShutdown lnwire.DeliveryAddress + + // LocalNonce is populated if the channel type is a simple taproot + // channel. This stores the public (and secret) nonce that will be used + // to generate commitments for the local party. + LocalNonce *musig2.Nonces } // toChanConfig returns the raw channel configuration generated by a node's @@ -137,25 +157,25 @@ func (c *ChannelContribution) toChanConfig() channeldb.ChannelConfig { // The reservation workflow consists of the following three steps: // 1. lnwallet.InitChannelReservation // * One requests the wallet to allocate the necessary resources for a -// channel reservation. These resources are put in limbo for the lifetime -// of a reservation. +// channel reservation. These resources are put in limbo for the lifetime +// of a reservation. // * Once completed the reservation will have the wallet's contribution -// accessible via the .OurContribution() method. This contribution -// contains the necessary items to allow the remote party to build both -// the funding, and commitment transactions. +// accessible via the .OurContribution() method. This contribution +// contains the necessary items to allow the remote party to build both +// the funding, and commitment transactions. // 2. ChannelReservation.ProcessContribution/ChannelReservation.ProcessSingleContribution // * The counterparty presents their contribution to the payment channel. -// This allows us to build the funding, and commitment transactions -// ourselves. +// This allows us to build the funding, and commitment transactions +// ourselves. // * We're now able to sign our inputs to the funding transactions, and -// the counterparty's version of the commitment transaction. +// the counterparty's version of the commitment transaction. // * All signatures crafted by us, are now available via .OurSignatures(). // 3. ChannelReservation.CompleteReservation/ChannelReservation.CompleteReservationSingle // * The final step in the workflow. The counterparty presents the -// signatures for all their inputs to the funding transaction, as well -// as a signature to our version of the commitment transaction. +// signatures for all their inputs to the funding transaction, as well +// as a signature to our version of the commitment transaction. // * We then verify the validity of all signatures before considering the -// channel "open". +// channel "open". type ChannelReservation struct { // This mutex MUST be held when either reading or modifying any of the // fields below. @@ -200,6 +220,8 @@ type ChannelReservation struct { // nextRevocationKeyLoc stores the key locator information for this // channel. nextRevocationKeyLoc keychain.KeyLocator + + musigSessions *MusigPairSession } // NewChannelReservation creates a new channel reservation. This function is @@ -372,6 +394,10 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount, chanType |= channeldb.FrozenBit } + if req.CommitType == CommitmentTypeSimpleTaproot { + chanType |= channeldb.SimpleTaprootFeatureBit + } + if req.ZeroConf { chanType |= channeldb.ZeroConfBit } @@ -454,6 +480,14 @@ func (r *ChannelReservation) IsZeroConf() bool { return r.partialState.IsZeroConf() } +// IsTaproot... +func (r *ChannelReservation) IsTaproot() bool { + r.RLock() + defer r.RUnlock() + + return r.partialState.ChanType.IsTaproot() +} + // CommitConstraints takes the constraints that the remote party specifies for // the type of commitments that we can generate for them. These constraints // include several parameters that serve as flow control restricting the amount diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go index b2b8bbfc56f..b1555b38b5c 100644 --- a/lnwallet/rpcwallet/rpcwallet.go +++ b/lnwallet/rpcwallet/rpcwallet.go @@ -383,8 +383,8 @@ func (r *RPCKeyRing) DeriveKey( // sha256 of the resulting shared point serialized in compressed format. If k is // our private key, and P is the public key, we perform the following operation: // -// sx := k*P -// s := sha256(sx.SerializeCompressed()) +// sx := k*P +// s := sha256(sx.SerializeCompressed()) // // NOTE: This method is part of the keychain.ECDHRing interface. func (r *RPCKeyRing) ECDH(keyDesc keychain.KeyDescriptor, @@ -445,11 +445,16 @@ 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 wireSig.ToSignature() + ecdsaSig, err := wireSig.ToSignature() + if err != nil { + return nil, err + } + + return ecdsaSig.(*ecdsa.Signature), nil } // SignMessageCompact signs the given message, single or double SHA256 hashing @@ -638,8 +643,8 @@ func (r *RPCKeyRing) ComputeInputScript(tx *wire.MsgTx, // submitted as well to reduce the number of method calls necessary later on. func (r *RPCKeyRing) MuSig2CreateSession(keyLoc keychain.KeyLocator, pubKeys []*btcec.PublicKey, tweaks *input.MuSig2Tweaks, - otherNonces [][musig2.PubNonceSize]byte) (*input.MuSig2SessionInfo, - error) { + otherNonces [][musig2.PubNonceSize]byte, + sessionOpts ...musig2.SessionOption) (*input.MuSig2SessionInfo, error) { // We need to serialize all data for the RPC call. We can do that by // putting everything directly into the request struct. @@ -674,6 +679,9 @@ func (r *RPCKeyRing) MuSig2CreateSession(keyLoc keychain.KeyLocator, } } + // TODO(roasbeef): extend RPC call w/ extra options needed for taproot + // musig2 session creation + ctxt, cancel := context.WithTimeout(context.Background(), r.rpcTimeout) defer cancel() diff --git a/lnwallet/sigpool.go b/lnwallet/sigpool.go index 30dc37ae901..2424757f937 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. @@ -114,8 +113,6 @@ type SignJobResp struct { Err error } -// TODO(roasbeef); fix description - // SigPool is a struct that is meant to allow the current channel state // machine to parallelize all signature generation and verification. This // struct is needed as _each_ HTLC when creating a commitment transaction @@ -207,7 +204,11 @@ func (s *SigPool) poolWorker() { } } + // Use the sig mapper to go from the input.Signature + // into the serialized lnwire.Sig that we'll send + // across the wire. sig, err := lnwire.NewSigFromSignature(rawSig) + select { case sigMsg.Resp <- SignJobResp{ Sig: sig, diff --git a/lnwallet/test/test_interface.go b/lnwallet/test/test_interface.go index ebaf91e8d94..ea5cd20f564 100644 --- a/lnwallet/test/test_interface.go +++ b/lnwallet/test/test_interface.go @@ -927,7 +927,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness, // by having Alice immediately process his contribution. err = aliceChanReservation.ProcessContribution(bobContribution) if err != nil { - t.Fatalf("alice unable to process bob's contribution") + t.Fatalf("alice unable to process bob's contribution: %v", err) } assertContributionInitPopulated(t, bobChanReservation.TheirContribution()) @@ -2748,6 +2748,19 @@ var walletTests = []walletTestCase{ ) }, }, + { + name: "single funding workflow musig2", + test: func(miner *rpctest.Harness, alice, + bob *lnwallet.LightningWallet, t *testing.T) { + + testSingleFunderReservationWorkflow( + miner, alice, bob, t, + lnwallet.CommitmentTypeSimpleTaproot, nil, + nil, [32]byte{}, 0, + ) + }, + }, + // TODO(roasbeef): add musig2 external funding { name: "single funding workflow external funding tx", test: testSingleFunderExternalFundingTx, @@ -2962,11 +2975,11 @@ func testSingleFunderExternalFundingTx(miner *rpctest.Harness, thawHeight := uint32(200) aliceExternalFunder := chanfunding.NewCannedAssembler( thawHeight, *chanPoint, btcutil.Amount(chanAmt), &aliceFundingKey, - bobFundingKey.PubKey, true, + bobFundingKey.PubKey, true, false, ) bobShimIntent, err := chanfunding.NewCannedAssembler( thawHeight, *chanPoint, btcutil.Amount(chanAmt), &bobFundingKey, - aliceFundingKey.PubKey, false, + aliceFundingKey.PubKey, false, false, ).ProvisionChannel(&chanfunding.Request{ LocalAmt: btcutil.Amount(chanAmt), MinConfs: 1, diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go index f1209d70148..4ced34eb1a1 100644 --- a/lnwallet/test_utils.go +++ b/lnwallet/test_utils.go @@ -1,9 +1,11 @@ package lnwallet import ( + "bytes" "crypto/rand" "encoding/binary" "encoding/hex" + "fmt" "io" "io/ioutil" prand "math/rand" @@ -11,13 +13,18 @@ import ( "os" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/lnwallet/musession" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" ) @@ -348,8 +355,8 @@ func CreateTestChannels(chanType channeldb.ChannelType) ( Packager: channeldb.NewChannelPackager(shortChanID), } - aliceSigner := &input.MockSigner{Privkeys: aliceKeys} - bobSigner := &input.MockSigner{Privkeys: bobKeys} + aliceSigner := NewMockSigner(aliceKeys, nil) + bobSigner := NewMockSigner(bobKeys, nil) // TODO(roasbeef): make mock version of pre-image store @@ -425,6 +432,27 @@ func CreateTestChannels(chanType channeldb.ChannelType) ( // network by populating the initial revocation windows of the passed // commitment state machines. func initRevocationWindows(chanA, chanB *LightningChannel) error { + // If these are taproot chanenls, then we need to also simulate sending + // either FundingLocked or ChannelReestablish by calling + // InitRemoteMusigNonces for both sides. + if chanA.channelState.ChanType.IsTaproot() { + chanANonces, err := chanA.GenMusigNonces() + if err != nil { + return err + } + chanBNonces, err := chanB.GenMusigNonces() + if err != nil { + return err + } + + if err := chanA.InitRemoteMusigNonces(chanBNonces); err != nil { + return err + } + if err := chanB.InitRemoteMusigNonces(chanANonces); err != nil { + return err + } + } + aliceNextRevoke, err := chanA.NextRevocationKey() if err != nil { return err @@ -502,11 +530,12 @@ func calcStaticFee(chanType channeldb.ChannelType, numHTLCs int) btcutil.Amount // pending updates. This method is useful when testing interactions between two // live state machines. func ForceStateTransition(chanA, chanB *LightningChannel) error { - aliceSig, aliceHtlcSigs, _, err := chanA.SignNextCommitment() + aliceNewCommit, err := chanA.SignNextCommitment() if err != nil { return err } - if err = chanB.ReceiveNewCommitment(aliceSig, aliceHtlcSigs); err != nil { + err = chanB.ReceiveNewCommitment(aliceNewCommit.CommitSigs) + if err != nil { return err } @@ -514,7 +543,7 @@ func ForceStateTransition(chanA, chanB *LightningChannel) error { if err != nil { return err } - bobSig, bobHtlcSigs, _, err := chanB.SignNextCommitment() + bobNewCommit, err := chanB.SignNextCommitment() if err != nil { return err } @@ -522,7 +551,7 @@ func ForceStateTransition(chanA, chanB *LightningChannel) error { if _, _, _, _, err := chanA.ReceiveRevocation(bobRevocation); err != nil { return err } - if err := chanA.ReceiveNewCommitment(bobSig, bobHtlcSigs); err != nil { + if err := chanA.ReceiveNewCommitment(bobNewCommit.CommitSigs); err != nil { return err } @@ -536,3 +565,211 @@ func ForceStateTransition(chanA, chanB *LightningChannel) error { return nil } + +// MockSigner is a simple implementation of the Signer interface. Each one has +// a set of private keys in a slice and can sign messages using the appropriate +// one. +type MockSigner struct { + Privkeys []*btcec.PrivateKey + NetParams *chaincfg.Params + + *musession.Manager +} + +var _ input.Signer = (*MockSigner)(nil) + +func NewMockSigner(privKeys []*btcec.PrivateKey, netParams *chaincfg.Params) *MockSigner { + signer := &MockSigner{ + Privkeys: privKeys, + NetParams: netParams, + } + + keyFetcher := func(*keychain.KeyDescriptor) (*btcec.PrivateKey, error) { + return signer.Privkeys[0], nil + } + signer.Manager = musession.NewManager(keyFetcher) + + return signer +} + +// SignOutputRaw generates a signature for the passed transaction according to +// the data within the passed SignDescriptor. +func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx, + signDesc *input.SignDescriptor) (input.Signature, error) { + + pubkey := signDesc.KeyDesc.PubKey + switch { + case signDesc.SingleTweak != nil: + pubkey = input.TweakPubKeyWithTweak(pubkey, signDesc.SingleTweak) + case signDesc.DoubleTweak != nil: + pubkey = input.DeriveRevocationPubkey(pubkey, signDesc.DoubleTweak.PubKey()) + } + + hash160 := btcutil.Hash160(pubkey.SerializeCompressed()) + privKey := m.findKey(hash160, signDesc.SingleTweak, signDesc.DoubleTweak) + if privKey == nil { + return nil, fmt.Errorf("mock signer does not have key") + } + + // In case of a taproot output any signature is always a Schnorr + // signature, based on the new tapscript sighash algorithm. + if txscript.IsPayToTaproot(signDesc.Output.PkScript) { + sigHashes := txscript.NewTxSigHashes( + tx, signDesc.PrevOutputFetcher, + ) + + // Are we spending a script path or the key path? The API is + // slightly different, so we need to account for that to get + // the raw signature. + var ( + rawSig []byte + err error + ) + switch signDesc.SignMethod { + case input.TaprootKeySpendBIP0086SignMethod, + input.TaprootKeySpendSignMethod: + + // This function tweaks the private key using the tap + // root key supplied as the tweak. + rawSig, err = txscript.RawTxInTaprootSignature( + tx, sigHashes, signDesc.InputIndex, + signDesc.Output.Value, signDesc.Output.PkScript, + signDesc.TapTweak, signDesc.HashType, + privKey, + ) + if err != nil { + return nil, err + } + + case input.TaprootScriptSpendSignMethod: + leaf := txscript.TapLeaf{ + LeafVersion: txscript.BaseLeafVersion, + Script: signDesc.WitnessScript, + } + rawSig, err = txscript.RawTxInTapscriptSignature( + tx, sigHashes, signDesc.InputIndex, + signDesc.Output.Value, signDesc.Output.PkScript, + leaf, signDesc.HashType, privKey, + ) + if err != nil { + return nil, err + } + } + + // The signature returned above might have a sighash flag + // attached if a non-default type was used. We'll slice this + // off if it exists to ensure we can properly parse the raw + // signature. + sig, err := schnorr.ParseSignature( + rawSig[:schnorr.SignatureSize], + ) + if err != nil { + return nil, err + } + + return sig, nil + } + + sig, err := txscript.RawTxInWitnessSignature( + tx, signDesc.SigHashes, signDesc.InputIndex, signDesc.Output.Value, + signDesc.WitnessScript, signDesc.HashType, privKey, + ) + if err != nil { + return nil, err + } + + return ecdsa.ParseDERSignature(sig[:len(sig)-1]) +} + +// ComputeInputScript generates a complete InputIndex for the passed transaction +// with the signature as defined within the passed SignDescriptor. This method +// should be capable of generating the proper input script for both regular +// p2wkh output and p2wkh outputs nested within a regular p2sh output. +func (m *MockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *input.SignDescriptor) (*input.Script, error) { + scriptType, addresses, _, err := txscript.ExtractPkScriptAddrs( + signDesc.Output.PkScript, m.NetParams) + if err != nil { + return nil, err + } + + switch scriptType { + case txscript.PubKeyHashTy: + privKey := m.findKey(addresses[0].ScriptAddress(), signDesc.SingleTweak, + signDesc.DoubleTweak) + if privKey == nil { + return nil, fmt.Errorf("mock signer does not have key for "+ + "address %v", addresses[0]) + } + + sigScript, err := txscript.SignatureScript( + tx, signDesc.InputIndex, signDesc.Output.PkScript, + txscript.SigHashAll, privKey, true, + ) + if err != nil { + return nil, err + } + + return &input.Script{SigScript: sigScript}, nil + + case txscript.WitnessV0PubKeyHashTy: + privKey := m.findKey(addresses[0].ScriptAddress(), signDesc.SingleTweak, + signDesc.DoubleTweak) + if privKey == nil { + return nil, fmt.Errorf("mock signer does not have key for "+ + "address %v", addresses[0]) + } + + witnessScript, err := txscript.WitnessSignature(tx, signDesc.SigHashes, + signDesc.InputIndex, signDesc.Output.Value, + signDesc.Output.PkScript, txscript.SigHashAll, privKey, true) + if err != nil { + return nil, err + } + + return &input.Script{Witness: witnessScript}, nil + + default: + return nil, fmt.Errorf("unexpected script type: %v", scriptType) + } +} + +// findKey searches through all stored private keys and returns one +// corresponding to the hashed pubkey if it can be found. The public key may +// either correspond directly to the private key or to the private key with a +// tweak applied. +func (m *MockSigner) findKey(needleHash160 []byte, singleTweak []byte, + doubleTweak *btcec.PrivateKey) *btcec.PrivateKey { + + for _, privkey := range m.Privkeys { + // First check whether public key is directly derived from private key. + hash160 := btcutil.Hash160(privkey.PubKey().SerializeCompressed()) + if bytes.Equal(hash160, needleHash160) { + return privkey + } + + // Otherwise check if public key is derived from tweaked private key. + switch { + case singleTweak != nil: + privkey = input.TweakPrivKey(privkey, singleTweak) + case doubleTweak != nil: + privkey = input.DeriveRevocationPrivKey(privkey, doubleTweak) + default: + continue + } + hash160 = btcutil.Hash160(privkey.PubKey().SerializeCompressed()) + if bytes.Equal(hash160, needleHash160) { + return privkey + } + } + return nil +} + +// pubkeyToHex serializes a Bitcoin public key to a hex encoded string. +func pubkeyToHex(key *btcec.PublicKey) string { + return hex.EncodeToString(key.SerializeCompressed()) +} + +// privkeyFromHex serializes a Bitcoin private key to a hex encoded string. +func privkeyToHex(key *btcec.PrivateKey) string { + return hex.EncodeToString(key.Serialize()) +} diff --git a/lnwallet/transactions.go b/lnwallet/transactions.go index 7fccf97c0d0..200895252a3 100644 --- a/lnwallet/transactions.go +++ b/lnwallet/transactions.go @@ -8,6 +8,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/input" ) const ( @@ -41,9 +42,13 @@ var ( // state transition to create another output which actually allows redemption // or revocation of an HTLC. // -// In order to spend the HTLC output, the witness for the passed transaction -// should be: -// * <0> +// In order to spend the segwit v0 HTLC output, the witness for the passed +// transaction should be: +// - <0> +// +// In order to spend the segwit v1 (tapoot) HTLC output, the witness for the +// passed transaction should be: +// - func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool, htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay, leaseExpiry uint32, revocationKey, delayKey *btcec.PublicKey) ( @@ -62,22 +67,43 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool, } successTx.AddTxIn(txin) - // Next, we'll generate the script used as the output for all second - // level HTLC which forces a covenant w.r.t what can be done with all - // HTLC outputs. - script, err := SecondLevelHtlcScript( - chanType, initiator, revocationKey, delayKey, csvDelay, - leaseExpiry, - ) - if err != nil { - return nil, err + var pkScript []byte + + // Depending on if this is a taproot channel or not, we'll create a v0 + // vs v1 segwit script. + if chanType.IsTaproot() { + taprootOutputKey, err := input.TaprootSecondLevelHtlcScript( + revocationKey, delayKey, csvDelay, + ) + if err != nil { + return nil, err + } + + pkScript, err = input.PayToTaprootScript(taprootOutputKey) + if err != nil { + return nil, err + } + } else { + + // Next, we'll generate the script used as the output for all second + // level HTLC which forces a covenant w.r.t what can be done with all + // HTLC outputs. + scriptInfo, err := SecondLevelHtlcScript( + chanType, initiator, revocationKey, delayKey, csvDelay, + leaseExpiry, + ) + if err != nil { + return nil, err + } + + pkScript = scriptInfo.PkScript } // Finally, the output is simply the amount of the HTLC (minus the // required fees), paying to the timeout script. successTx.AddTxOut(&wire.TxOut{ Value: int64(htlcAmt), - PkScript: script.PkScript, + PkScript: pkScript, }) return successTx, nil @@ -92,9 +118,13 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool, // transaction is locked with an absolute lock-time so the sender can only // attempt to claim the output using it after the lock time has passed. // -// In order to spend the HTLC output, the witness for the passed transaction -// should be: -// * <0> <0> +// In order to spend the HTLC output for segwit v0, the witness for the passed +// transaction should be: +// - <0> <0> +// +// In order to spend the HTLC output for segwit v1, then witness for the passed +// transaction should be: +// - // // NOTE: The passed amount for the HTLC should take into account the required // fee rate at the time the HTLC was created. The fee should be able to @@ -121,22 +151,43 @@ func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool, } timeoutTx.AddTxIn(txin) - // Next, we'll generate the script used as the output for all second - // level HTLC which forces a covenant w.r.t what can be done with all - // HTLC outputs. - script, err := SecondLevelHtlcScript( - chanType, initiator, revocationKey, delayKey, csvDelay, - leaseExpiry, - ) - if err != nil { - return nil, err + var pkScript []byte + + // Depending on if this is a taproot channel or not, we'll create a v0 + // vs v1 segwit script. + if chanType.IsTaproot() { + taprootOutputKey, err := input.TaprootSecondLevelHtlcScript( + revocationKey, delayKey, csvDelay, + ) + if err != nil { + return nil, err + } + + pkScript, err = input.PayToTaprootScript(taprootOutputKey) + if err != nil { + return nil, err + } + + } else { + // Next, we'll generate the script used as the output for all second + // level HTLC which forces a covenant w.r.t what can be done with all + // HTLC outputs. + scriptInfo, err := SecondLevelHtlcScript( + chanType, initiator, revocationKey, delayKey, csvDelay, + leaseExpiry, + ) + if err != nil { + return nil, err + } + + pkScript = scriptInfo.PkScript } // Finally, the output is simply the amount of the HTLC (minus the // required fees), paying to the regular second level HTLC script. timeoutTx.AddTxOut(&wire.TxOut{ Value: int64(htlcAmt), - PkScript: script.PkScript, + PkScript: pkScript, }) return timeoutTx, nil diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index ab61cf34d11..092b6744ef8 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -287,10 +287,10 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) { // Execute commit dance to arrive at the point where the local node has // received the test commitment and the remote signature. - localSig, localHtlcSigs, _, err := localChannel.SignNextCommitment() + localNewCommit, err := localChannel.SignNextCommitment() require.NoError(t, err, "local unable to sign commitment") - err = remoteChannel.ReceiveNewCommitment(localSig, localHtlcSigs) + err = remoteChannel.ReceiveNewCommitment(localNewCommit.CommitSigs) require.NoError(t, err) revMsg, _, err := remoteChannel.RevokeCurrentCommitment() @@ -299,16 +299,16 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) { _, _, _, _, err = localChannel.ReceiveRevocation(revMsg) require.NoError(t, err) - remoteSig, remoteHtlcSigs, _, err := remoteChannel.SignNextCommitment() + remoteNewCommit, err := remoteChannel.SignNextCommitment() require.NoError(t, err) - require.Equal(t, test.RemoteSigHex, hex.EncodeToString(remoteSig.ToSignatureBytes())) + require.Equal(t, test.RemoteSigHex, hex.EncodeToString(remoteNewCommit.CommitSig.ToSignatureBytes())) - for i, sig := range remoteHtlcSigs { + for i, sig := range remoteNewCommit.HtlcSigs { require.Equal(t, test.HtlcDescs[i].RemoteSigHex, hex.EncodeToString(sig.ToSignatureBytes())) } - err = localChannel.ReceiveNewCommitment(remoteSig, remoteHtlcSigs) + err = localChannel.ReceiveNewCommitment(remoteNewCommit.CommitSigs) require.NoError(t, err) _, _, err = localChannel.RevokeCurrentCommitment() @@ -536,7 +536,7 @@ func testSpendValidation(t *testing.T, tweakless bool) { remoteCommitTweak := input.SingleTweakBytes(commitPoint, aliceKeyPub) localCommitTweak := input.SingleTweakBytes(commitPoint, bobKeyPub) - aliceSelfOutputSigner := &input.MockSigner{ + aliceSelfOutputSigner := &MockSigner{ Privkeys: []*btcec.PrivateKey{aliceKeyPriv}, } @@ -628,7 +628,7 @@ func testSpendValidation(t *testing.T, tweakless bool) { t.Fatalf("spend from delay output is invalid: %v", err) } - localSigner := &input.MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}} + localSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}} // Next, we'll test bob spending with the derived revocation key to // simulate the scenario when Alice broadcasts this commitment @@ -711,10 +711,10 @@ func testSpendValidation(t *testing.T, tweakless bool) { // the commitment transaction. // // The following spending cases are covered by this test: -// * Alice's spend from the delayed output on her commitment transaction. -// * Bob's spend from Alice's delayed output when she broadcasts a revoked +// - Alice's spend from the delayed output on her commitment transaction. +// - Bob's spend from Alice's delayed output when she broadcasts a revoked // commitment transaction. -// * Bob's spend from his unencumbered output within Alice's commitment +// - Bob's spend from his unencumbered output within Alice's commitment // transaction. func TestCommitmentSpendValidation(t *testing.T) { t.Parallel() @@ -949,12 +949,12 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp } // Create mock signers that can sign for the keys that are used. - localSigner := &input.MockSigner{Privkeys: []*btcec.PrivateKey{ + localSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{ tc.localPaymentBasepointSecret, tc.localDelayedPaymentBasepointSecret, tc.localFundingPrivkey, localDummy1, localDummy2, }} - remoteSigner := &input.MockSigner{Privkeys: []*btcec.PrivateKey{ + remoteSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{ tc.remoteFundingPrivkey, tc.remoteRevocationBasepointSecret, tc.remotePaymentBasepointSecret, remoteDummy1, remoteDummy2, }} diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 49c6410beb3..eb446bc29dc 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/btcutil/txsort" @@ -778,6 +779,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg TaprootPubkey, true, DefaultAccountName, ) }, + Musig2: req.CommitType == CommitmentTypeSimpleTaproot, } fundingIntent, err = req.ChanFunder.ProvisionChannel( fundingReq, @@ -1206,6 +1208,28 @@ func (l *LightningWallet) initOurContribution(reservation *ChannelReservation, reservation.partialState.RevocationProducer = producer reservation.ourContribution.ChannelConstraints = l.Cfg.DefaultConstraints + // If taproot channels are active, then we'll generate two sets of + // nonces: one for our local commitment, and one for their remote + // commitment. + if reservation.partialState.ChanType.IsTaproot() { + // As we'd like the local nonce we send over to be generated + // determinstically, we'll provide a custom reader that + // actually just uses our sha-chain pre-image as the primary + // randomness source. + shaChainRand := musig2.WithCustomRand( + bytes.NewBuffer(firstPreimage[:]), + ) + pubKeyOpt := musig2.WithPublicKey( + reservation.ourContribution.MultiSigKey.PubKey, + ) + reservation.ourContribution.LocalNonce, err = musig2.GenNonces( + pubKeyOpt, shaChainRand, + ) + if err != nil { + return err + } + } + return nil } @@ -1275,6 +1299,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount, remoteCommitPoint, false, chanType, ourChanCfg, theirChanCfg, ) + // TODO(roasbeef): pass in taproot or not here, use to generate outputs ourCommitTx, err := CreateCommitTx( chanType, fundingTxIn, localCommitmentKeys, ourChanCfg, theirChanCfg, localBalance, remoteBalance, 0, initiator, @@ -1408,6 +1433,8 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { theirContribution.MultiSigKey.PubKey, ) + // TODO(roasbeef): bind nonces as well? + // With our keys bound, we can now construct+sign the final // funding transaction and also obtain the chanPoint that // creates the channel. @@ -1462,8 +1489,92 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { }) } -// handleChanPointReady continues the funding process once the channel point -// is known and the funding transaction can be completed. +// genMusigSession... +func genMusigSession(ourContribution, theirContribution *ChannelContribution, + signer input.MuSig2Signer, + fundingOutput *wire.TxOut) *MusigPairSession { + + return NewMusigPairSession(&MusigSessionCfg{ + LocalKey: ourContribution.MultiSigKey, + RemoteKey: theirContribution.MultiSigKey, + LocalNonce: *ourContribution.LocalNonce, + RemoteNonce: *theirContribution.LocalNonce, + Signer: signer, + InputTxOut: fundingOutput, + }) +} + +// signCommitTx... +func (l *LightningWallet) signCommitTx(pendingReservation *ChannelReservation, + commitTx *wire.MsgTx, fundingOutput *wire.TxOut, + fundingWitnessScript []byte) (input.Signature, error) { + + ourContribution := pendingReservation.ourContribution + theirContribution := pendingReservation.theirContribution + + var ( + sigTheirCommit input.Signature + err error + ) + switch { + // If this is a taproot channel, then we'll need to create an initial + // musig2 session here as we'll be sending over a _partial_ signature. + case pendingReservation.partialState.ChanType.IsTaproot(): + // We're now ready to sign the first commitment. However, we'll + // only create the session if that hasn't been done already. + if pendingReservation.musigSessions == nil { + musigSessions := genMusigSession( + ourContribution, theirContribution, + l.Cfg.Signer, fundingOutput, + ) + pendingReservation.musigSessions = musigSessions + } + + // Now that we have the funding outpoint, we'll generate a + // musig2 signature for their version of the commitment + // transaction. We use the remote session as this is for the + // remote commitment transaction. + // + // TODO(roasbeef): keep the signing nonce here? or just always + // regen for funding_locked? + musigSessions := pendingReservation.musigSessions + partialSig, err := musigSessions.RemoteSession.SignCommit( + commitTx, + ) + if err != nil { + return nil, fmt.Errorf("unable to sign "+ + "commitment: %w", err) + } + + sigTheirCommit = partialSig + + // For regular channels, we can just send over a normal ECDSA signature + // w/o any extra steps. + default: + ourKey := ourContribution.MultiSigKey + signDesc := input.SignDescriptor{ + WitnessScript: fundingWitnessScript, + KeyDesc: ourKey, + Output: fundingOutput, + HashType: txscript.SigHashAll, + SigHashes: input.NewTxSigHashesV0Only( + commitTx, + ), + InputIndex: 0, + } + sigTheirCommit, err = l.Cfg.Signer.SignOutputRaw( + commitTx, &signDesc, + ) + if err != nil { + return nil, err + } + } + + return sigTheirCommit, nil +} + +// handleChanPointReady continues the funding process once the channel point is +// known and the funding transaction can be completed. func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) { l.limboMtx.Lock() pendingReservation, ok := l.fundingLimbo[req.pendingFundingID] @@ -1473,6 +1584,7 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) { "funding state") return } + ourContribution := pendingReservation.ourContribution theirContribution := pendingReservation.theirContribution chanPoint := pendingReservation.partialState.FundingOutpoint @@ -1613,26 +1725,21 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) { fundingIntent := pendingReservation.fundingIntent fundingWitnessScript, fundingOutput, err := fundingIntent.FundingOutput() if err != nil { - req.err <- fmt.Errorf("unable to obtain funding output") + req.err <- fmt.Errorf("unable to obtain funding output: %w", err) return } // Generate a signature for their version of the initial commitment // transaction. - ourKey := ourContribution.MultiSigKey - signDesc := input.SignDescriptor{ - WitnessScript: fundingWitnessScript, - KeyDesc: ourKey, - Output: fundingOutput, - HashType: txscript.SigHashAll, - SigHashes: input.NewTxSigHashesV0Only(theirCommitTx), - InputIndex: 0, - } - sigTheirCommit, err := l.Cfg.Signer.SignOutputRaw(theirCommitTx, &signDesc) + sigTheirCommit, err := l.signCommitTx( + pendingReservation, theirCommitTx, fundingOutput, + fundingWitnessScript, + ) if err != nil { req.err <- err return } + pendingReservation.ourCommitmentSig = sigTheirCommit req.err <- nil @@ -1757,6 +1864,84 @@ func (l *LightningWallet) verifyFundingInputs(fundingTx *wire.MsgTx, return nil } +// verifyCommitSig... +func (l *LightningWallet) verifyCommitSig(res *ChannelReservation, + commitSig input.Signature, commitTx *wire.MsgTx) error { + + localKey := res.ourContribution.MultiSigKey.PubKey + remoteKey := res.theirContribution.MultiSigKey.PubKey + channelValue := int64(res.partialState.Capacity) + + // If this isn't a taproot channel, then we'll construct a segwit v0 + // p2wsh sighash. + switch { + case !res.partialState.ChanType.IsTaproot(): + hashCache := input.NewTxSigHashesV0Only(commitTx) + witnessScript, _, err := input.GenFundingPkScript( + localKey.SerializeCompressed(), + remoteKey.SerializeCompressed(), channelValue, + ) + if err != nil { + return err + } + + sigHash, err := txscript.CalcWitnessSigHash( + witnessScript, hashCache, txscript.SigHashAll, + commitTx, 0, channelValue, + ) + if err != nil { + return err + } + + // Verify that we've received a valid signature from the remote + // party for our version of the commitment transaction. + if !commitSig.Verify(sigHash, remoteKey) { + return fmt.Errorf("counterparty's commitment " + + "signature is invalid") + } + + return nil + + // Otherwise for taproot channels, we'll compute the segwit v1 sighash, + // which is slightly different. + default: + // First, check to see if we've generated the musig session + // already. If we're the responder in the funding flow, we may + // not have generated it already. + if res.musigSessions == nil { + _, fundingOutput, err := input.GenTaprootFundingScript( + localKey, remoteKey, channelValue, + ) + if err != nil { + return err + } + + res.musigSessions = genMusigSession( + res.ourContribution, res.theirContribution, + l.Cfg.Signer, fundingOutput, + ) + } + + // For the musig2 based channels, we'll use the generated local + // musig2 session to verify the signature. + localSession := res.musigSessions.LocalSession + + // At this point, the commitment signature passed in should + // actually be a wrapped musig2 signature, so we'll do a type + // asset to the get the signature we actually need. + partialSig, ok := commitSig.(*MusigPartialSig) + if !ok { + return fmt.Errorf("expected *musig2.PartialSignature, "+ + "got: %T", commitSig) + } + + _, err := localSession.VerifyCommitSig( + commitTx, partialSig.ToWireSig(), + ) + return err + } +} + // handleFundingCounterPartySigs is the final step in the channel reservation // workflow. During this step, we validate *all* the received signatures for // inputs to the funding transaction. If any of these are invalid, we bail, @@ -1799,44 +1984,18 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs // commitment transaction. res.theirCommitmentSig = msg.theirCommitmentSig commitTx := res.partialState.LocalCommitment.CommitTx - ourKey := res.ourContribution.MultiSigKey - theirKey := res.theirContribution.MultiSigKey - - // Re-generate both the witnessScript and p2sh output. We sign the - // witnessScript script, but include the p2sh output as the subscript - // for verification. - witnessScript, _, err := input.GenFundingPkScript( - ourKey.PubKey.SerializeCompressed(), - theirKey.PubKey.SerializeCompressed(), - int64(res.partialState.Capacity), - ) - if err != nil { - msg.err <- err - msg.completeChan <- nil - return - } - // Next, create the spending scriptSig, and then verify that the script - // is complete, allowing us to spend from the funding transaction. - channelValue := int64(res.partialState.Capacity) - hashCache := input.NewTxSigHashesV0Only(commitTx) - sigHash, err := txscript.CalcWitnessSigHash( - witnessScript, hashCache, txscript.SigHashAll, commitTx, - 0, channelValue, - ) + err := l.verifyCommitSig(res, msg.theirCommitmentSig, commitTx) if err != nil { - msg.err <- err + msg.err <- fmt.Errorf("counterparty's commitment signature is "+ + "invalid: %w", err) msg.completeChan <- nil return } - // Verify that we've received a valid signature from the remote party - // for our version of the commitment transaction. - if !msg.theirCommitmentSig.Verify(sigHash, theirKey.PubKey) { - msg.err <- fmt.Errorf("counterparty's commitment signature is invalid") - msg.completeChan <- nil - return - } + // TODO(roasbeef): need to convert sig into something actual? + // * also actually verify against target session? + theirCommitSigBytes := msg.theirCommitmentSig.Serialize() res.partialState.LocalCommitment.CommitSig = theirCommitSigBytes @@ -1915,6 +2074,7 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { defer pendingReservation.Unlock() chanState := pendingReservation.partialState + chanType := pendingReservation.partialState.ChanType chanState.FundingOutpoint = *req.fundingOutpoint fundingTxIn := wire.NewTxIn(req.fundingOutpoint, nil, nil) @@ -1933,7 +2093,7 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { pendingReservation.theirContribution.ChannelConfig, pendingReservation.ourContribution.FirstCommitmentPoint, pendingReservation.theirContribution.FirstCommitmentPoint, - *fundingTxIn, pendingReservation.partialState.ChanType, + *fundingTxIn, chanType, pendingReservation.partialState.IsInitiator, leaseExpiry, ) if err != nil { @@ -1969,13 +2129,10 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { walletLog.Debugf("Remote commit tx for ChannelPoint(%v): %v", req.fundingOutpoint, spew.Sdump(theirCommitTx)) - channelValue := int64(pendingReservation.partialState.Capacity) - hashCache := input.NewTxSigHashesV0Only(ourCommitTx) - theirKey := pendingReservation.theirContribution.MultiSigKey - ourKey := pendingReservation.ourContribution.MultiSigKey - witnessScript, _, err := input.GenFundingPkScript( - ourKey.PubKey.SerializeCompressed(), - theirKey.PubKey.SerializeCompressed(), channelValue, + // With both commitment transactions created, we'll now verify their + // signature on our commitment. + err = l.verifyCommitSig( + pendingReservation, req.theirCommitmentSig, ourCommitTx, ) if err != nil { req.err <- err @@ -1983,53 +2140,46 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { return } - sigHash, err := txscript.CalcWitnessSigHash( - witnessScript, hashCache, txscript.SigHashAll, ourCommitTx, 0, - channelValue, + theirCommitSigBytes := req.theirCommitmentSig.Serialize() + chanState.LocalCommitment.CommitSig = theirCommitSigBytes + + channelValue := int64(pendingReservation.partialState.Capacity) + theirKey := pendingReservation.theirContribution.MultiSigKey + ourKey := pendingReservation.ourContribution.MultiSigKey + + var ( + fundingWitnessScript []byte + fundingTxOut *wire.TxOut ) + if chanType.IsTaproot() { + fundingWitnessScript, fundingTxOut, err = input.GenTaprootFundingScript( + ourKey.PubKey, theirKey.PubKey, channelValue, + ) + } else { + fundingWitnessScript, fundingTxOut, err = input.GenFundingPkScript( + ourKey.PubKey.SerializeCompressed(), + theirKey.PubKey.SerializeCompressed(), channelValue, + ) + } if err != nil { req.err <- err req.completeChan <- nil return } - // Verify that we've received a valid signature from the remote party - // for our version of the commitment transaction. - if !req.theirCommitmentSig.Verify(sigHash, theirKey.PubKey) { - req.err <- fmt.Errorf("counterparty's commitment signature " + - "is invalid") - req.completeChan <- nil - return - } - theirCommitSigBytes := req.theirCommitmentSig.Serialize() - chanState.LocalCommitment.CommitSig = theirCommitSigBytes - // With their signature for our version of the commitment transactions // verified, we can now generate a signature for their version, // allowing the funding transaction to be safely broadcast. - p2wsh, err := input.WitnessScriptHash(witnessScript) - if err != nil { - req.err <- err - req.completeChan <- nil - return - } - signDesc := input.SignDescriptor{ - WitnessScript: witnessScript, - KeyDesc: ourKey, - Output: &wire.TxOut{ - PkScript: p2wsh, - Value: channelValue, - }, - HashType: txscript.SigHashAll, - SigHashes: input.NewTxSigHashesV0Only(theirCommitTx), - InputIndex: 0, - } - sigTheirCommit, err := l.Cfg.Signer.SignOutputRaw(theirCommitTx, &signDesc) + sigTheirCommit, err := l.signCommitTx( + pendingReservation, theirCommitTx, fundingTxOut, + fundingWitnessScript, + ) if err != nil { req.err <- err req.completeChan <- nil return } + pendingReservation.ourCommitmentSig = sigTheirCommit _, bestHeight, err := l.Cfg.ChainIO.GetBestBlock() @@ -2131,27 +2281,50 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel, if err != nil { return err } - signedCommitTx, err := channel.getSignedCommitTx() - if err != nil { - return err - } + + localKey := channelState.LocalChanCfg.MultiSigKey.PubKey + remoteKey := channelState.RemoteChanCfg.MultiSigKey.PubKey // We'll also need the multi-sig witness script itself so the // chanvalidate package can check it for correctness against the // funding transaction, and also commitment validity. - localKey := channelState.LocalChanCfg.MultiSigKey.PubKey - remoteKey := channelState.RemoteChanCfg.MultiSigKey.PubKey - witnessScript, err := input.GenMultiSigScript( - localKey.SerializeCompressed(), - remoteKey.SerializeCompressed(), - ) - if err != nil { - return err + var fundingScript []byte + if channelState.ChanType.IsTaproot() { + walletLog.Debugf("validating taproot channel: ChannelPoint(%v)", + channelState.FundingOutpoint) + + fundingScript, _, err = input.GenTaprootFundingScript( + localKey, remoteKey, int64(channel.Capacity), + ) + if err != nil { + return err + } + + } else { + walletLog.Debugf("validating p2wsh channel: ChannelPoint(%v)", + channelState.FundingOutpoint) + + witnessScript, err := input.GenMultiSigScript( + localKey.SerializeCompressed(), + remoteKey.SerializeCompressed(), + ) + if err != nil { + return err + } + fundingScript, err = input.WitnessScriptHash(witnessScript) + if err != nil { + return err + } } - pkScript, err := input.WitnessScriptHash(witnessScript) + + signedCommitTx, err := channel.getSignedCommitTx() if err != nil { return err } + commitCtx := &chanvalidate.CommitmentContext{ + Value: channel.Capacity, + FullySignedCommitTx: signedCommitTx, + } // Finally, we'll pass in all the necessary context needed to fully // validate that this channel is indeed what we expect, and can be @@ -2160,12 +2333,9 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel, Locator: &chanvalidate.OutPointChanLocator{ ChanPoint: channelState.FundingOutpoint, }, - MultiSigPkScript: pkScript, + MultiSigPkScript: fundingScript, FundingTx: fundingTx, - CommitCtx: &chanvalidate.CommitmentContext{ - Value: channel.Capacity, - FullySignedCommitTx: signedCommitTx, - }, + CommitCtx: commitCtx, }) if err != nil { return err 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..77bf5e9ee4b 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 } + + var recordProducers []tlv.RecordProducer + 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..11378e8e9ef 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,33 @@ 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, ) + + 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 +98,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 88b914c384b..8a0a95cd189 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..193936ed177 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 an required bit that indicates the + // node is able to create unadvertised taproot-native channels. + SimpleTaprootChannelsRequired = 80 + + // SimpleTaprootChannelsOptional is an optional bit that indicates the + // node is able to create unadvertised 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..60143248396 100644 --- a/lnwire/funding_locked.go +++ b/lnwire/funding_locked.go @@ -27,6 +27,11 @@ type FundingLocked struct { // ShortChannelID for forwarding. AliasScid *ShortChannelID + // LocalNonce is an optional field that stores a local musig2 nonce. 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. @@ -39,7 +44,7 @@ func NewFundingLocked(cid ChannelID, npcp *btcec.PublicKey) *FundingLocked { return &FundingLocked{ ChanID: cid, NextPerCommitmentPoint: npcp, - ExtraData: make([]byte, 0), + ExtraData: nil, } } @@ -57,16 +62,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 +90,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.LocalNonce = &localNonce + } + + if len(tlvRecords) != 0 { + c.ExtraData = tlvRecords + } return nil } @@ -95,12 +116,16 @@ func (c *FundingLocked) Encode(w *bytes.Buffer, pver uint32) error { } // We'll only encode the AliasScid in a TLV segment if it exists. + var recordProducers []tlv.RecordProducer 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.LocalNonce != nil { + recordProducers = append(recordProducers, c.LocalNonce) + } + 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/lnwire.go b/lnwire/lnwire.go index 0361d76484f..df01c180f00 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -171,7 +171,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 } case PingPayload: @@ -575,7 +575,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 } case *OpaqueReason: diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go index 44d6cfb9b31..381bc203f64 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: %v", 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: %v", 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,15 @@ 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 +615,15 @@ 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 +642,42 @@ func TestLightningWireProtocol(t *testing.T) { req := NewFundingLocked(ChannelID(c), pubKey) + if r.Int31()%2 == 0 { + scid := NewShortChanIDFromInt(uint64(r.Int63())) + req.AliasScid = &scid + req.LocalNonce = 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 +695,14 @@ 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 +722,8 @@ 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(1020)) + numSigs := uint16(r.Int31n(1019)) if numSigs > 0 { req.HtlcSigs = make([]Sig, numSigs) } @@ -632,6 +735,15 @@ 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 +763,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 +988,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..34a9bc9ef72 --- /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 = 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 bb86cb2c1ac..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. @@ -160,7 +167,6 @@ var _ Message = (*OpenChannel)(nil) // Encode serializes the target OpenChannel into the passed io.Writer // implementation. Serialization will observe the rules defined by the passed // protocol version. -// func (o *OpenChannel) Encode(w *bytes.Buffer, pver uint32) error { recordProducers := []tlv.RecordProducer{&o.UpfrontShutdownScript} if o.ChannelType != nil { @@ -169,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 @@ -293,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 @@ -308,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..b4c9ccbec13 --- /dev/null +++ b/lnwire/partial_sig.go @@ -0,0 +1,184 @@ +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... + PartialSigLen = 32 + + // PartialSigRecordType... + PartialSigRecordType = 6 +) + +// PartialSig... +type PartialSig struct { + // Sig... + Sig btcec.ModNScalar +} + +// NewPartialSig... +func NewPartialSig(sig btcec.ModNScalar) PartialSig { + return PartialSig{ + Sig: sig, + } +} + +// Record... +func (p *PartialSig) Record() tlv.Record { + return tlv.MakeStaticRecord( + PartialSigRecordType, p, PartialSigLen, + partialSigTypeEncoder, partialSigTypeDecoder, + ) +} + +// partialSigTypeEncoder... +func partialSigTypeEncoder(w io.Writer, val interface{}, buf *[8]byte) error { + if v, ok := val.(*PartialSig); ok { + sigBytes := v.Sig.Bytes() + if _, err := w.Write(sigBytes[:]); err != nil { + return err + } + + return nil + } + + return tlv.NewTypeForEncodingErr(val, "lnwire.PartialSig") +} + +// Encode... +func (p *PartialSig) Encode(w io.Writer) error { + return partialSigTypeEncoder(w, p, nil) +} + +// partialSigWithNonceTypeDecoder decodes a 98-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 + if _, err := io.ReadFull(r, sBytes[:]); 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... +func (p *PartialSig) Decode(r io.Reader) error { + return partialSigTypeDecoder(r, p, nil, PartialSigLen) +} + +const ( + // PartialSigWithNonceLength 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... + PartialSigWithNonceRecordType = 2 +) + +// PartialSigWithNonce... +type PartialSigWithNonce struct { + PartialSig + + // Nonce.... + Nonce Musig2Nonce +} + +// NewPartialSigWithNonce... +func NewPartialSigWithNonce(nonce [musig2.PubNonceSize]byte, + sig btcec.ModNScalar) *PartialSigWithNonce { + + return &PartialSigWithNonce{ + Nonce: nonce, + PartialSig: NewPartialSig(sig), + } +} + +// Record... +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... +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 + if _, err := io.ReadFull(r, sBytes[:]); 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... +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..43f4f361b45 100644 --- a/lnwire/shutdown.go +++ b/lnwire/shutdown.go @@ -3,8 +3,54 @@ package lnwire import ( "bytes" "io" + + "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2" + "github.com/lightningnetwork/lnd/tlv" +) + +const ( + // ShutdownNonceRecordType... + ShutdownNonceRecordType = 8 ) +// ShutdownNonce... +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 +63,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 +90,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 +123,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..c5c18e455cb 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,71 @@ 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... +type sigType uint + +const ( + // sigTypeECDSA... + sigTypeECDSA sigType = iota + + // sigTypeSchnorr... + sigTypeSchnorr +) + +// TODO(roasbef): make into interface after all? + +// 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... +func (s *Sig) ForceSchnorr() { + s.sigType = sigTypeSchnorr +} + +// RawBytes... +func (s *Sig) RawBytes() []byte { + return s.bytes[:] +} + +// Copy... +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 +98,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 +108,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 +119,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 +130,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 +141,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 +149,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 +178,91 @@ 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: + var sigBytes [64]byte + copy(sigBytes[:], e.Serialize()) + + return Sig{ + bytes: sigBytes, + sigType: sigTypeSchnorr, + }, nil + + // 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 (b *Sig) ToSignature() (input.Signature, error) { + switch b.sigType { + case sigTypeSchnorr: + return schnorr.ParseSignature(b.bytes[:]) - return sig, nil + case sigTypeECDSA: + // Parse the signature with strict checks. + sigBytes := b.ToSignatureBytes() + sig, err := ecdsa.ParseDERSignature(sigBytes) + if err != nil { + return nil, err + } + + return sig, nil + + default: + return nil, fmt.Errorf("unknown sig type: %v", b.sigType) + } } -// ToSignatureBytes serializes the target fixed-sized signature into the raw -// bytes of a DER encoding. +// 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 (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 + switch b.sigType { + // For ECDSA signatures, we'll convert to DER encoding. + case sigTypeECDSA: + // Extract canonically-padded bigint representations from buffer + r := extractCanonicalPadding(b.bytes[0:32]) + s := extractCanonicalPadding(b.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[:], b.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 5b99ab368a6..f3397881d90 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_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/peer/brontide.go b/peer/brontide.go index b9741fae270..c869ea53ea1 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -86,7 +86,7 @@ type outgoingMsg struct { // the receiver of the request to report when the channel creation process has // completed. type newChannelMsg struct { - channel *channeldb.OpenChannel + channel *lnpeer.NewChannel err chan error } @@ -2294,9 +2294,10 @@ out: chanPoint := &newChan.FundingOutpoint chanID := lnwire.NewChanIDFromOutPoint(chanPoint) - // Only update RemoteNextRevocation if the channel is in the - // activeChannels map and if we added the link to the switch. - // Only active channels will be added to the switch. + // Only update RemoteNextRevocation if the channel is + // in the activeChannels map and if we added the link + // to the switch. Only active channels will be added + // to the switch. p.activeChanMtx.Lock() currentChan, ok := p.activeChannels[chanID] if ok && currentChan != nil { @@ -2326,6 +2327,8 @@ out: continue } + // TODO(roasbeef): don't also need to apply + // nonces here? get from chan reest continue } @@ -2333,7 +2336,8 @@ out: // set of active channels, so we can look it up later // easily according to its channel ID. lnChan, err := lnwallet.NewLightningChannel( - p.cfg.Signer, newChan, p.cfg.SigPool, + p.cfg.Signer, newChan.OpenChannel, + p.cfg.SigPool, newChan.ChanOpts..., ) if err != nil { p.activeChanMtx.Unlock() @@ -2720,6 +2724,7 @@ func (p *Brontide) createChanCloser(channel *lnwallet.LightningChannel, chanCloser := chancloser.NewChanCloser( chancloser.ChanCloseCfg{ Channel: channel, + Signer: p.cfg.Signer, BroadcastTx: p.cfg.Wallet.PublishTransaction, DisableChannel: func(op wire.OutPoint) error { return p.cfg.ChanStatusMgr.RequestDisable( @@ -3322,12 +3327,12 @@ func (p *Brontide) Address() net.Addr { // added if the cancel channel is closed. // // NOTE: Part of the lnpeer.Peer interface. -func (p *Brontide) AddNewChannel(channel *channeldb.OpenChannel, +func (p *Brontide) AddNewChannel(newChan *lnpeer.NewChannel, cancel <-chan struct{}) error { errChan := make(chan error, 1) newChanMsg := &newChannelMsg{ - channel: channel, + channel: newChan, err: errChan, } diff --git a/routing/router.go b/routing/router.go index 348914db733..3f1c0b78d2e 100644 --- a/routing/router.go +++ b/routing/router.go @@ -1414,6 +1414,58 @@ func (r *ChannelRouter) addZombieEdge(chanID uint64) error { return nil } +// makeFundingScript... +// +// TODO(roasbeef: export and use elsewhere? +func makeFundingScript(bitcoinKey1, bitcoinKey2 []byte, + chanFeatures []byte) ([]byte, error) { + + // In order to make the correct funding script, we'll need to parse the + // chanFeatures bytes into a feature vector we can interact with. + rawFeatures := lnwire.NewRawFeatureVector() + err := rawFeatures.Decode(bytes.NewReader(chanFeatures)) + if err != nil { + return nil, fmt.Errorf("unable to parse chan feature "+ + "bits: %w", err) + } + + chanFeatureBits := lnwire.NewFeatureVector( + rawFeatures, lnwire.Features, + ) + if chanFeatureBits.HasFeature(lnwire.SimpleTaprootChannelsOptional) { + pubKey1, err := btcec.ParsePubKey(bitcoinKey1) + if err != nil { + return nil, err + } + pubKey2, err := btcec.ParsePubKey(bitcoinKey2) + if err != nil { + return nil, err + } + + fundingScript, _, err := input.GenTaprootFundingScript( + pubKey1, pubKey2, 0, + ) + if err != nil { + return nil, err + } + + return fundingScript, nil + } else { + witnessScript, err := input.GenMultiSigScript( + bitcoinKey1[:], bitcoinKey2[:], + ) + if err != nil { + return nil, err + } + pkScript, err := input.WitnessScriptHash(witnessScript) + if err != nil { + return nil, err + } + + return pkScript, nil + } +} + // processUpdate processes a new relate authenticated channel/edge, node or // channel/edge update network update. If the update didn't affect the internal // state of the draft due to either being out of date, invalid, or redundant, @@ -1520,16 +1572,13 @@ func (r *ChannelRouter) processUpdate(msg interface{}, // Recreate witness output to be sure that declared in channel // edge bitcoin keys and channel value corresponds to the // reality. - witnessScript, err := input.GenMultiSigScript( + fundingPkScript, err := makeFundingScript( msg.BitcoinKey1Bytes[:], msg.BitcoinKey2Bytes[:], + msg.Features, ) if err != nil { return err } - pkScript, err := input.WitnessScriptHash(witnessScript) - if err != nil { - return err - } // Next we'll validate that this channel is actually well // formed. If this check fails, then this channel either @@ -1539,7 +1588,7 @@ func (r *ChannelRouter) processUpdate(msg interface{}, Locator: &chanvalidate.ShortChanIDChanLocator{ ID: channelID, }, - MultiSigPkScript: pkScript, + MultiSigPkScript: fundingPkScript, FundingTx: fundingTx, }) if err != nil { @@ -1556,10 +1605,6 @@ func (r *ChannelRouter) processUpdate(msg interface{}, // Now that we have the funding outpoint of the channel, ensure // that it hasn't yet been spent. If so, then this channel has // been closed so we'll ignore it. - fundingPkScript, err := input.WitnessScriptHash(witnessScript) - if err != nil { - return err - } chanUtxo, err := r.cfg.Chain.GetUtxo( fundingPoint, fundingPkScript, channelID.BlockHeight, r.quit, diff --git a/rpcserver.go b/rpcserver.go index 95d0b136d54..3180f5b0e16 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1816,10 +1816,12 @@ func newFundingShimAssembler(chanPointShim *lnrpc.ChanPointShim, initiator bool, // With all the parts assembled, we can now make the canned assembler // to pass into the wallet. + // + // TODO(roasbeef): update to support musig2 return chanfunding.NewCannedAssembler( chanPointShim.ThawHeight, *chanPoint, btcutil.Amount(chanPointShim.Amt), &localKeyDesc, - remoteKey, initiator, + remoteKey, initiator, false, ), nil } @@ -2066,11 +2068,39 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest, *channelType = lnwire.ChannelType(*fv) + case lnrpc.CommitmentType_SIMPLE_TAPROOT: + // If the taproot channel type is being set, then the channel + // MUST be private (unadvertised) for now. + if !in.Private { + return nil, fmt.Errorf("taproot channels must be " + + "private") + } + + channelType = new(lnwire.ChannelType) + fv := lnwire.NewRawFeatureVector( + lnwire.SimpleTaprootChannelsRequired, + ) + + // TODO(roasbeef): no need for the rest as they're now + // implicit? + + if in.ZeroConf { + fv.Set(lnwire.ZeroConfRequired) + } + + if in.ScidAlias { + fv.Set(lnwire.ScidAliasRequired) + } + + *channelType = lnwire.ChannelType(*fv) + default: return nil, fmt.Errorf("unhandled request channel type %v", in.CommitmentType) } + // TODO(roasbeef): make taproot the default chan type? + // Instruct the server to trigger the necessary events to attempt to // open a new channel. A stream is returned in place, this stream will // be used to consume updates of the state of the pending channel. @@ -3957,19 +3987,22 @@ func rpcCommitmentType(chanType channeldb.ChannelType) lnrpc.CommitmentType { // Extract the commitment type from the channel type flags. We must // first check whether it has anchors, since in that case it would also // be tweakless. - if chanType.HasLeaseExpiration() { + switch { + case chanType.IsTaproot(): + return lnrpc.CommitmentType_SIMPLE_TAPROOT + + case chanType.HasLeaseExpiration(): return lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE - } - if chanType.HasAnchors() { + case chanType.HasAnchors(): return lnrpc.CommitmentType_ANCHORS - } - if chanType.IsTweakless() { + case chanType.IsTweakless(): return lnrpc.CommitmentType_STATIC_REMOTE_KEY - } + default: - return lnrpc.CommitmentType_LEGACY + return lnrpc.CommitmentType_LEGACY + } } // createChannelConstraint creates a *lnrpc.ChannelConstraints using the @@ -5933,7 +5966,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context, // within the HTLC. // // TODO(roasbeef): should return a slice of routes in reality -// * create separate PR to send based on well formatted route +// - create separate PR to send based on well formatted route func (r *rpcServer) QueryRoutes(ctx context.Context, in *lnrpc.QueryRoutesRequest) (*lnrpc.QueryRoutesResponse, error) { diff --git a/server.go b/server.go index cf8e8a52ba2..b6d1950c642 100644 --- a/server.go +++ b/server.go @@ -827,7 +827,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 56b8ae4d546..7b39eeb4083 100644 --- a/watchtower/blob/justice_kit.go +++ b/watchtower/blob/justice_kit.go @@ -43,9 +43,10 @@ const ( ) // Size returns the size of the encoded-and-encrypted blob in bytes. -// nonce: 24 bytes -// enciphered plaintext: n bytes -// MAC: 16 bytes +// +// nonce: 24 bytes +// enciphered plaintext: n bytes +// MAC: 16 bytes func Size(blobType Type) int { return NonceSize + PlaintextSize(blobType) + CiphertextExpansion } @@ -173,7 +174,8 @@ func (b *JusticeKit) CommitToLocalWitnessScript() ([]byte, error) { // CommitToLocalRevokeWitnessStack constructs a witness stack spending the // revocation clause of the commitment to-local output. -// 1 +// +// 1 func (b *JusticeKit) CommitToLocalRevokeWitnessStack() ([][]byte, error) { toLocalSig, err := b.CommitToLocalSig.ToSignature() if err != nil { @@ -220,7 +222,8 @@ func (b *JusticeKit) CommitToRemoteWitnessScript() ([]byte, error) { // CommitToRemoteWitnessStack returns a witness stack spending the commitment // to-remote output, which consists of a single signature satisfying either the // legacy or anchor witness scripts. -// +// +// func (b *JusticeKit) CommitToRemoteWitnessStack() ([][]byte, error) { toRemoteSig, err := b.CommitToRemoteSig.ToSignature() if err != nil { @@ -345,14 +348,15 @@ func (b *JusticeKit) decode(r io.Reader, blobType Type) error { // constant-size plaintext size of 274 bytes. // // blob version 0 plaintext encoding: -// sweep address length: 1 byte -// padded sweep address: 42 bytes -// revocation pubkey: 33 bytes -// local delay pubkey: 33 bytes -// csv delay: 4 bytes -// commit to-local revocation sig: 64 bytes -// commit to-remote pubkey: 33 bytes, maybe blank -// commit to-remote sig: 64 bytes, maybe blank +// +// sweep address length: 1 byte +// padded sweep address: 42 bytes +// revocation pubkey: 33 bytes +// local delay pubkey: 33 bytes +// csv delay: 4 bytes +// commit to-local revocation sig: 64 bytes +// commit to-remote pubkey: 33 bytes, maybe blank +// commit to-remote sig: 64 bytes, maybe blank func (b *JusticeKit) encodeV0(w io.Writer) error { // Assert the sweep address length is sane. if len(b.SweepAddress) > MaxSweepAddrSize { @@ -394,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 } @@ -406,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 } @@ -416,14 +420,15 @@ func (b *JusticeKit) encodeV0(w io.Writer) error { // to-remote output. // // blob version 0 plaintext encoding: -// sweep address length: 1 byte -// padded sweep address: 42 bytes -// revocation pubkey: 33 bytes -// local delay pubkey: 33 bytes -// csv delay: 4 bytes -// commit to-local revocation sig: 64 bytes -// commit to-remote pubkey: 33 bytes, maybe blank -// commit to-remote sig: 64 bytes, maybe blank +// +// sweep address length: 1 byte +// padded sweep address: 42 bytes +// revocation pubkey: 33 bytes +// local delay pubkey: 33 bytes +// csv delay: 4 bytes +// commit to-local revocation sig: 64 bytes +// commit to-remote pubkey: 33 bytes, maybe blank +// commit to-remote sig: 64 bytes, maybe blank func (b *JusticeKit) decodeV0(r io.Reader) error { // Read the sweep address length as a single byte. var sweepAddrLen uint8 @@ -467,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. @@ -493,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/wtclient/backup_task.go b/watchtower/wtclient/backup_task.go index a7268930340..516e87e0d23 100644 --- a/watchtower/wtclient/backup_task.go +++ b/watchtower/wtclient/backup_task.go @@ -365,24 +365,24 @@ 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/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 }