diff --git a/channeldb/channel.go b/channeldb/channel.go index e624876b7..d69765645 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -3863,8 +3863,6 @@ func putChanRevocationState(chanBucket kvdb.RwBucket, channel *OpenChannel) erro return err } - // TODO(roasbeef): don't keep producer on disk - // If the next revocation is present, which is only the case after the // ChannelReady message has been sent, then we'll write it to disk. if channel.RemoteNextRevocation != nil { diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 3f360e160..bcf78c6a4 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -1317,12 +1317,16 @@ type LightningChannel struct { // log is a channel-specific logging instance. log btclog.Logger + // taprootNonceProducer is used to generate a shachain tree for the + // purpose of generating verification nonces for taproot channels. + taprootNonceProducer shachain.Producer + // musigSessions holds the current musig2 pair session for the channel. musigSessions *MusigPairSession // pendingVerificationNonce is the initial verification nonce generated // for musig2 channels when the state machine is intiated. Once we know - // the verification nonce of the remote party, then we can star tto use + // the verification nonce of the remote party, then we can start to use // the channel as normal. pendingVerificationNonce *musig2.Nonces @@ -1391,24 +1395,40 @@ func NewLightningChannel(signer input.Signer, logPrefix := fmt.Sprintf("ChannelPoint(%v):", state.FundingOutpoint) - lc := &LightningChannel{ - Signer: signer, - sigPool: sigPool, - currentHeight: localCommit.CommitHeight, - remoteCommitChain: newCommitmentChain(), - localCommitChain: newCommitmentChain(), - channelState: state, - commitBuilder: NewCommitmentBuilder(state), - localUpdateLog: localUpdateLog, - remoteUpdateLog: remoteUpdateLog, - ChanPoint: &state.FundingOutpoint, - Capacity: state.Capacity, - LocalFundingKey: state.LocalChanCfg.MultiSigKey.PubKey, - RemoteFundingKey: state.RemoteChanCfg.MultiSigKey.PubKey, - log: build.NewPrefixLog(logPrefix, walletLog), + // In order to obtain the revocation root hash to create the taproot + // revocation, we'll encode the producer into a buffer, then use that + // to derive the shachain root needed. + var rootHashBuf bytes.Buffer + if err := state.RevocationProducer.Encode(&rootHashBuf); err != nil { + return nil, fmt.Errorf("unable to encode producer: %v", err) } - // At this point, we mwy already have of nonces that were passed in, so + revRootHash := chainhash.HashH(rootHashBuf.Bytes()) + + taprootNonceProducer, err := deriveMusig2Shachain(revRootHash) + if err != nil { + return nil, fmt.Errorf("unable to derive shachain: %v", err) + } + + lc := &LightningChannel{ + Signer: signer, + sigPool: sigPool, + currentHeight: localCommit.CommitHeight, + remoteCommitChain: newCommitmentChain(), + localCommitChain: newCommitmentChain(), + channelState: state, + commitBuilder: NewCommitmentBuilder(state), + localUpdateLog: localUpdateLog, + remoteUpdateLog: remoteUpdateLog, + ChanPoint: &state.FundingOutpoint, + Capacity: state.Capacity, + LocalFundingKey: state.LocalChanCfg.MultiSigKey.PubKey, + RemoteFundingKey: state.RemoteChanCfg.MultiSigKey.PubKey, + taprootNonceProducer: taprootNonceProducer, + log: build.NewPrefixLog(logPrefix, walletLog), + } + + // At this point, we may already have 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 @@ -1422,7 +1442,7 @@ func NewLightningChannel(signer input.Signer, // 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) + err = lc.restoreCommitState(&localCommit, &remoteCommit) if err != nil { return nil, err } @@ -3824,17 +3844,21 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, return nil } -// CommitSig holds the set of related signatures for a new commitment +// CommitSigs holds the set of related signatures for a new commitment // transaction state. type CommitSigs struct { // CommitSig is the normal commitment signature. This will only be a - // non-zero commitment signature for taproot channels. + // non-zero commitment signature for non-taproot channels. CommitSig lnwire.Sig // HtlcSigs is the set of signatures for all HTLCs in the commitment // transaction. Depending on the channel type, these will either be // ECDSA or Schnorr signatures. HtlcSigs []lnwire.Sig + + // PartialSig is the musig2 partial signature for taproot commitment + // transactions. + PartialSig *lnwire.PartialSigWithNonce } // NewCommitState wraps the various signatures needed to properly @@ -3873,8 +3897,9 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { } 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 @@ -3962,16 +3987,38 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { // 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 nil, err - } - sig, err = lnwire.NewSigFromSignature(rawSig) - if err != nil { - close(cancelChan) - return 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 @@ -4019,8 +4066,9 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) { return &NewCommitState{ CommitSigs: &CommitSigs{ - CommitSig: sig, - HtlcSigs: htlcSigs, + CommitSig: sig, + HtlcSigs: htlcSigs, + PartialSig: partialSig, }, PendingHTLCs: commitDiff.Commitment.Htlcs, }, nil @@ -4051,6 +4099,8 @@ func (lc *LightningChannel) ProcessChanSyncMsg( msg *lnwire.ChannelReestablish) ([]lnwire.Message, []models.CircuitKey, []models.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 @@ -4191,8 +4241,9 @@ func (lc *LightningChannel) ProcessChanSyncMsg( ChanID: lnwire.NewChanIDFromOutPoint( &lc.channelState.FundingOutpoint, ), - CommitSig: newCommit.CommitSig, - HtlcSigs: newCommit.HtlcSigs, + CommitSig: newCommit.CommitSig, + HtlcSigs: newCommit.HtlcSigs, + PartialSig: newCommit.PartialSig, } updates = append(updates, commitSig) @@ -4275,6 +4326,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. @@ -4503,11 +4557,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 @@ -4522,6 +4595,13 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, "signatures") } + // If this is a taproot channel, then we'll convert it + // to a schnorr signature, so we can get correct type + // from ToSignature below. + if chanType.IsTaproot() { + 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. @@ -4558,11 +4638,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 @@ -4577,6 +4676,13 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, "signatures") } + // If this is a taproot channel, then we'll convert it + // to a schnorr signature, so we can get correct type + // from ToSignature below. + if chanType.IsTaproot() { + 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. @@ -4584,6 +4690,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment, if err != nil { return nil, err } + htlc.sig = sig default: @@ -4637,6 +4744,22 @@ func (i *InvalidCommitSigError) Error() string { // error interface. var _ error = (*InvalidCommitSigError)(nil) +// InvalidPartialCommitSigError is used when we encounter an invalid musig2 +// partial signature. +type InvalidPartialCommitSigError struct { + InvalidCommitSigError + + *invalidPartialSigError +} + +// Error returns a detailed error string including the exact transaction that +// caused an invalid partial commit sig signature. +func (i *InvalidPartialCommitSigError) Error() string { + return fmt.Sprintf("rejected commitment: commit_height=%v, "+ + "commit_tx=%x -- %v", i.commitHeight, i.commitTx, + i.invalidPartialSigError) +} + // InvalidHtlcSigError is a struct that implements the error interface to // report a failure to validate an htlc signature from a remote peer. We'll use // the items in this struct to generate a rich error message for the remote @@ -4746,23 +4869,8 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { }), ) - // 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() { @@ -4781,29 +4889,104 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { cancelChan := make(chan struct{}) verifyResps := lc.sigPool.SubmitVerifyBatch(verifyJobs, cancelChan) + localCommitTx := localCommitmentView.txn + // 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 - cSig, err := commitSigs.CommitSig.ToSignature() - if err != nil { - return err - } - if !cSig.Verify(sigHash, verifyKey) { - close(cancelChan) + // 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.taprootNonceProducer, + ) + nextVerificationNonce, err := localSession.VerifyCommitSig( + localCommitTx, commitSigs.PartialSig, localCtrNonce, + ) + if err != nil { + 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: commitSigs.CommitSig.ToSignatureBytes(), - sigHash: sigHash, - commitTx: txBytes.Bytes(), + var sigErr invalidPartialSigError + if errors.As(err, &sigErr) { + // 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 &InvalidPartialCommitSigError{ + invalidPartialSigError: &sigErr, + InvalidCommitSigError: InvalidCommitSigError{ //nolint:lll + commitHeight: nextHeight, + commitTx: txBytes.Bytes(), + }, + } + } + + return err + } + + // 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 { + multiSigScript := lc.signDesc.WitnessScript + prevFetcher := txscript.NewCannedPrevOutputFetcher( + multiSigScript, int64(lc.channelState.Capacity), + ) + hashCache := txscript.NewTxSigHashes(localCommitTx, prevFetcher) + + 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 + } + + 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: commitSigs.CommitSig.ToSignatureBytes(), + sigHash: sigHash, + commitTx: txBytes.Bytes(), + } } } @@ -4840,8 +5023,21 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error { } // The signature checks out, so we can now add the new commitment to - // our local commitment chain. - localCommitmentView.sig = commitSigs.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 @@ -5020,6 +5216,17 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, &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, finalHtlcs, nil } @@ -5248,6 +5455,24 @@ 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() { + if revMsg.LocalNonce == nil { + return nil, nil, nil, nil, fmt.Errorf("next " + + "revocation nonce not set") + } + 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 @@ -5873,30 +6098,103 @@ 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 + + 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. We pass in + // the same height here as we're generating the nonce needed + // for the _current_ state. + localNonce, err := NewMusigVerificationNonce( + ourKey.PubKey, lc.currentHeight, + lc.taprootNonceProducer, + ) + if err != nil { + return nil, err + } + + // 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, LocalMusigCommit, + ) + + 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, + ) } - // 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. - ourKey := lc.channelState.LocalChanCfg.MultiSigKey.PubKey. - SerializeCompressed() - theirKey := lc.channelState.RemoteChanCfg.MultiSigKey.PubKey. - SerializeCompressed() - - commitTx.TxIn[0].Witness = input.SpendMultiSig( - lc.signDesc.WitnessScript, ourKey, - ourSig, theirKey, theirSig, - ) + commitTx.TxIn[0].Witness = witness return commitTx, nil } @@ -7775,14 +8073,17 @@ func (lc *LightningChannel) unsignedLocalUpdates(remoteMessageIndex, // 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() + lc.Lock() + defer lc.Unlock() var err error + + // We pass in the current height+1 as this'll be the set of + // verification nonces we'll send to the party to create our _next_ + // state. lc.pendingVerificationNonce, err = NewMusigVerificationNonce( lc.channelState.LocalChanCfg.MultiSigKey.PubKey, - lc.currentHeight, lc.channelState.RevocationProducer, - false, + lc.currentHeight+1, lc.taprootNonceProducer, ) if err != nil { return nil, err @@ -7794,20 +8095,10 @@ func (lc *LightningChannel) GenMusigNonces() (*musig2.Nonces, error) { // 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 - }() +// pre-image as our primary randomness source. When used to generate the nonce +// again to broadcast our commitment hte current height will be used. +func NewMusigVerificationNonce(pubKey *btcec.PublicKey, targetHeight uint64, + shaGen shachain.Producer) (*musig2.Nonces, error) { // Now that we know what height we need, we'll grab the shachain // pre-image at the target destination. @@ -7834,8 +8125,12 @@ func (lc *LightningChannel) HasRemoteNonces() bool { func (lc *LightningChannel) InitRemoteMusigNonces(remoteNonce *musig2.Nonces, ) error { - lc.RLock() - defer lc.RUnlock() + lc.Lock() + defer lc.Unlock() + + if lc.pendingVerificationNonce == nil { + return fmt.Errorf("pending verification nonce is not set") + } // 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 diff --git a/lnwallet/musig_session.go b/lnwallet/musig_session.go index afa62ee8f..8d5e11559 100644 --- a/lnwallet/musig_session.go +++ b/lnwallet/musig_session.go @@ -408,6 +408,23 @@ func WithLocalCounterNonce(targetHeight uint64, } } +// invalidPartialSigError is used to return additional debug information to a +// caller that encounters an invalid partial sig. +type invalidPartialSigError struct { + partialSig []byte + sigHash []byte + signingNonce [musig2.PubNonceSize]byte + verificationNonce [musig2.PubNonceSize]byte +} + +// Error returns the error string for the partial sig error. +func (i invalidPartialSigError) Error() string { + return fmt.Sprintf("invalid partial sig: partial_sig=%x, "+ + "sig_hash=%x, signing_nonce=%x, verification_nonce=%x", + i.partialSig, i.sigHash, i.signingNonce[:], + i.verificationNonce[:]) +} + // 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