lnwallet: update genHtlcSigValidationJobs to be taproot aware
In this commit, we update the genHtlcSigValidationJobs function to be taproot aware. As we actually need a schnorr signature for the taproot validation, we need to coerce the entire wire type into a schnorr sig with the ForceSchnorr() method.
This commit is contained in:
3 files changed
+428
-118
No files matched your search
@@ -3863,8 +3863,6 @@ func putChanRevocationState(chanBucket kvdb.RwBucket, channel *OpenChannel) erro
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(roasbeef): don't keep producer on disk
|
|
||||||
|
|
||||||
// If the next revocation is present, which is only the case after the
|
// 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.
|
// ChannelReady message has been sent, then we'll write it to disk.
|
||||||
if channel.RemoteNextRevocation != nil {
|
if channel.RemoteNextRevocation != nil {
|
||||||
|
|||||||
+411
-116
@@ -1317,12 +1317,16 @@ type LightningChannel struct {
|
|||||||
// log is a channel-specific logging instance.
|
// log is a channel-specific logging instance.
|
||||||
log btclog.Logger
|
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 holds the current musig2 pair session for the channel.
|
||||||
musigSessions *MusigPairSession
|
musigSessions *MusigPairSession
|
||||||
|
|
||||||
// pendingVerificationNonce is the initial verification nonce generated
|
// pendingVerificationNonce is the initial verification nonce generated
|
||||||
// for musig2 channels when the state machine is intiated. Once we know
|
// 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.
|
// the channel as normal.
|
||||||
pendingVerificationNonce *musig2.Nonces
|
pendingVerificationNonce *musig2.Nonces
|
||||||
|
|
||||||
@@ -1391,24 +1395,40 @@ func NewLightningChannel(signer input.Signer,
|
|||||||
|
|
||||||
logPrefix := fmt.Sprintf("ChannelPoint(%v):", state.FundingOutpoint)
|
logPrefix := fmt.Sprintf("ChannelPoint(%v):", state.FundingOutpoint)
|
||||||
|
|
||||||
lc := &LightningChannel{
|
// In order to obtain the revocation root hash to create the taproot
|
||||||
Signer: signer,
|
// revocation, we'll encode the producer into a buffer, then use that
|
||||||
sigPool: sigPool,
|
// to derive the shachain root needed.
|
||||||
currentHeight: localCommit.CommitHeight,
|
var rootHashBuf bytes.Buffer
|
||||||
remoteCommitChain: newCommitmentChain(),
|
if err := state.RevocationProducer.Encode(&rootHashBuf); err != nil {
|
||||||
localCommitChain: newCommitmentChain(),
|
return nil, fmt.Errorf("unable to encode producer: %v", err)
|
||||||
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),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
// we'll check that now as this lets us skip some steps later.
|
||||||
if opts.localNonce != nil {
|
if opts.localNonce != nil {
|
||||||
lc.pendingVerificationNonce = opts.localNonce
|
lc.pendingVerificationNonce = opts.localNonce
|
||||||
@@ -1422,7 +1442,7 @@ func NewLightningChannel(signer input.Signer,
|
|||||||
|
|
||||||
// With the main channel struct reconstructed, we'll now restore the
|
// With the main channel struct reconstructed, we'll now restore the
|
||||||
// commitment state in memory and also the update logs themselves.
|
// commitment state in memory and also the update logs themselves.
|
||||||
err := lc.restoreCommitState(&localCommit, &remoteCommit)
|
err = lc.restoreCommitState(&localCommit, &remoteCommit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -3824,17 +3844,21 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
|
|||||||
return nil
|
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.
|
// transaction state.
|
||||||
type CommitSigs struct {
|
type CommitSigs struct {
|
||||||
// CommitSig is the normal commitment signature. This will only be a
|
// 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
|
CommitSig lnwire.Sig
|
||||||
|
|
||||||
// HtlcSigs is the set of signatures for all HTLCs in the commitment
|
// HtlcSigs is the set of signatures for all HTLCs in the commitment
|
||||||
// transaction. Depending on the channel type, these will either be
|
// transaction. Depending on the channel type, these will either be
|
||||||
// ECDSA or Schnorr signatures.
|
// ECDSA or Schnorr signatures.
|
||||||
HtlcSigs []lnwire.Sig
|
HtlcSigs []lnwire.Sig
|
||||||
|
|
||||||
|
// PartialSig is the musig2 partial signature for taproot commitment
|
||||||
|
// transactions.
|
||||||
|
PartialSig *lnwire.PartialSigWithNonce
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCommitState wraps the various signatures needed to properly
|
// NewCommitState wraps the various signatures needed to properly
|
||||||
@@ -3873,8 +3897,9 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
sig lnwire.Sig
|
sig lnwire.Sig
|
||||||
htlcSigs []lnwire.Sig
|
partialSig *lnwire.PartialSigWithNonce
|
||||||
|
htlcSigs []lnwire.Sig
|
||||||
)
|
)
|
||||||
|
|
||||||
// If we're awaiting for an ACK to a commitment signature, or if we
|
// 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
|
// 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 new commitment transaction while we're waiting for the rest of
|
||||||
// the HTLC signatures to be processed.
|
// the HTLC signatures to be processed.
|
||||||
lc.signDesc.SigHashes = input.NewTxSigHashesV0Only(newCommitView.txn)
|
//
|
||||||
rawSig, err := lc.Signer.SignOutputRaw(newCommitView.txn, lc.signDesc)
|
// TODO(roasbeef): abstract into CommitSigner interface?
|
||||||
if err != nil {
|
if lc.channelState.ChanType.IsTaproot() {
|
||||||
close(cancelChan)
|
// In this case, we'll send out a partial signature as this is
|
||||||
return nil, err
|
// a musig2 channel. The encoded normal ECDSA signature will be
|
||||||
}
|
// just blank.
|
||||||
sig, err = lnwire.NewSigFromSignature(rawSig)
|
remoteSession := lc.musigSessions.RemoteSession
|
||||||
if err != nil {
|
musig, err := remoteSession.SignCommit(
|
||||||
close(cancelChan)
|
newCommitView.txn,
|
||||||
return nil, err
|
)
|
||||||
|
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
|
// 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{
|
return &NewCommitState{
|
||||||
CommitSigs: &CommitSigs{
|
CommitSigs: &CommitSigs{
|
||||||
CommitSig: sig,
|
CommitSig: sig,
|
||||||
HtlcSigs: htlcSigs,
|
HtlcSigs: htlcSigs,
|
||||||
|
PartialSig: partialSig,
|
||||||
},
|
},
|
||||||
PendingHTLCs: commitDiff.Commitment.Htlcs,
|
PendingHTLCs: commitDiff.Commitment.Htlcs,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -4051,6 +4099,8 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
|
|||||||
msg *lnwire.ChannelReestablish) ([]lnwire.Message, []models.CircuitKey,
|
msg *lnwire.ChannelReestablish) ([]lnwire.Message, []models.CircuitKey,
|
||||||
[]models.CircuitKey, error) {
|
[]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
|
// 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
|
// chain sync message. If we're de-synchronized, then we'll send a
|
||||||
// batch of messages which when applied will kick start the chain
|
// batch of messages which when applied will kick start the chain
|
||||||
@@ -4191,8 +4241,9 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
|
|||||||
ChanID: lnwire.NewChanIDFromOutPoint(
|
ChanID: lnwire.NewChanIDFromOutPoint(
|
||||||
&lc.channelState.FundingOutpoint,
|
&lc.channelState.FundingOutpoint,
|
||||||
),
|
),
|
||||||
CommitSig: newCommit.CommitSig,
|
CommitSig: newCommit.CommitSig,
|
||||||
HtlcSigs: newCommit.HtlcSigs,
|
HtlcSigs: newCommit.HtlcSigs,
|
||||||
|
PartialSig: newCommit.PartialSig,
|
||||||
}
|
}
|
||||||
|
|
||||||
updates = append(updates, commitSig)
|
updates = append(updates, commitSig)
|
||||||
@@ -4275,6 +4326,9 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
|
|||||||
// With the batch of updates accumulated, we'll now re-send the
|
// With the batch of updates accumulated, we'll now re-send the
|
||||||
// original CommitSig message required to re-sync their remote
|
// original CommitSig message required to re-sync their remote
|
||||||
// commitment chain with our local version of their chain.
|
// 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)
|
commitUpdates = append(commitUpdates, commitDiff.CommitSig)
|
||||||
|
|
||||||
// NOTE: If a revocation is not owed, then updates is empty.
|
// NOTE: If a revocation is not owed, then updates is empty.
|
||||||
@@ -4503,11 +4557,30 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
|
|||||||
return nil, err
|
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)
|
hashCache := input.NewTxSigHashesV0Only(successTx)
|
||||||
sigHash, err := txscript.CalcWitnessSigHash(
|
sigHash, err := txscript.CalcWitnessSigHash(
|
||||||
htlc.ourWitnessScript, hashCache,
|
htlc.ourWitnessScript, hashCache,
|
||||||
sigHashType, successTx, 0,
|
sigHashType, successTx, 0,
|
||||||
int64(htlc.Amount.ToSatoshis()),
|
htlcAmt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -4522,6 +4595,13 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
|
|||||||
"signatures")
|
"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
|
// With the sighash generated, we'll also store the
|
||||||
// signature so it can be written to disk if this state
|
// signature so it can be written to disk if this state
|
||||||
// is valid.
|
// is valid.
|
||||||
@@ -4558,11 +4638,30 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
|
|||||||
return nil, err
|
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)
|
hashCache := input.NewTxSigHashesV0Only(timeoutTx)
|
||||||
sigHash, err := txscript.CalcWitnessSigHash(
|
sigHash, err := txscript.CalcWitnessSigHash(
|
||||||
htlc.ourWitnessScript, hashCache,
|
htlc.ourWitnessScript, hashCache,
|
||||||
sigHashType, timeoutTx, 0,
|
sigHashType, timeoutTx, 0,
|
||||||
int64(htlc.Amount.ToSatoshis()),
|
htlcAmt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -4577,6 +4676,13 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
|
|||||||
"signatures")
|
"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
|
// With the sighash generated, we'll also store the
|
||||||
// signature so it can be written to disk if this state
|
// signature so it can be written to disk if this state
|
||||||
// is valid.
|
// is valid.
|
||||||
@@ -4584,6 +4690,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
htlc.sig = sig
|
htlc.sig = sig
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -4637,6 +4744,22 @@ func (i *InvalidCommitSigError) Error() string {
|
|||||||
// error interface.
|
// error interface.
|
||||||
var _ error = (*InvalidCommitSigError)(nil)
|
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
|
// 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
|
// 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
|
// 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
|
// 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.
|
// generated, we'll submit these jobs to the worker pool.
|
||||||
var leaseExpiry uint32
|
var leaseExpiry uint32
|
||||||
if lc.channelState.ChanType.HasLeaseExpiration() {
|
if lc.channelState.ChanType.HasLeaseExpiration() {
|
||||||
@@ -4781,29 +4889,104 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error {
|
|||||||
cancelChan := make(chan struct{})
|
cancelChan := make(chan struct{})
|
||||||
verifyResps := lc.sigPool.SubmitVerifyBatch(verifyJobs, cancelChan)
|
verifyResps := lc.sigPool.SubmitVerifyBatch(verifyJobs, cancelChan)
|
||||||
|
|
||||||
|
localCommitTx := localCommitmentView.txn
|
||||||
|
|
||||||
// While the HTLC verification jobs are proceeding asynchronously,
|
// While the HTLC verification jobs are proceeding asynchronously,
|
||||||
// we'll ensure that the newly constructed commitment state has a valid
|
// we'll ensure that the newly constructed commitment state has a valid
|
||||||
// signature.
|
// 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()
|
// As we want to ensure we never write nonces to disk, we'll
|
||||||
if err != nil {
|
// use the shachain state to generate a nonce for our next
|
||||||
return err
|
// local state. Similar to generateRevocation, we do height + 2
|
||||||
}
|
// (next height + 1) here, as this is for the _next_ local
|
||||||
if !cSig.Verify(sigHash, verifyKey) {
|
// state, and we're about to accept height + 1.
|
||||||
close(cancelChan)
|
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
|
var sigErr invalidPartialSigError
|
||||||
// generate a special error to send over the protocol. We'll
|
if errors.As(err, &sigErr) {
|
||||||
// include the exact signature and commitment we failed to
|
// If we fail to validate their commitment
|
||||||
// verify against in order to aide debugging.
|
// signature, we'll generate a special error to
|
||||||
var txBytes bytes.Buffer
|
// send over the protocol. We'll include the
|
||||||
localCommitTx.Serialize(&txBytes)
|
// exact signature and commitment we failed to
|
||||||
return &InvalidCommitSigError{
|
// verify against in order to aide debugging.
|
||||||
commitHeight: nextHeight,
|
var txBytes bytes.Buffer
|
||||||
commitSig: commitSigs.CommitSig.ToSignatureBytes(),
|
localCommitTx.Serialize(&txBytes)
|
||||||
sigHash: sigHash,
|
return &InvalidPartialCommitSigError{
|
||||||
commitTx: txBytes.Bytes(),
|
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
|
// The signature checks out, so we can now add the new commitment to
|
||||||
// our local commitment chain.
|
// our local commitment chain. For regular channels, we can just
|
||||||
localCommitmentView.sig = commitSigs.CommitSig.ToSignatureBytes()
|
// 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)
|
lc.localCommitChain.addCommitment(localCommitmentView)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -5020,6 +5216,17 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck,
|
|||||||
&lc.channelState.FundingOutpoint,
|
&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
|
return revocationMsg, newCommitment.Htlcs, finalHtlcs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5248,6 +5455,24 @@ func (lc *LightningChannel) ReceiveRevocation(revMsg *lnwire.RevokeAndAck) (
|
|||||||
return nil, nil, nil, nil, err
|
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
|
// At this point, the revocation has been accepted, and we've rotated
|
||||||
// the current revocation key+hash for the remote party. Therefore we
|
// the current revocation key+hash for the remote party. Therefore we
|
||||||
// sync now to ensure the revocation producer state is consistent with
|
// 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
|
localCommit := lc.channelState.LocalCommitment
|
||||||
commitTx := localCommit.CommitTx.Copy()
|
commitTx := localCommit.CommitTx.Copy()
|
||||||
|
|
||||||
theirSig, err := ecdsa.ParseDERSignature(localCommit.CommitSig)
|
ourKey := lc.channelState.LocalChanCfg.MultiSigKey
|
||||||
if err != nil {
|
theirKey := lc.channelState.RemoteChanCfg.MultiSigKey
|
||||||
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. 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
|
commitTx.TxIn[0].Witness = witness
|
||||||
// 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,
|
|
||||||
)
|
|
||||||
|
|
||||||
return commitTx, nil
|
return commitTx, nil
|
||||||
}
|
}
|
||||||
@@ -7775,14 +8073,17 @@ func (lc *LightningChannel) unsignedLocalUpdates(remoteMessageIndex,
|
|||||||
// GenMusigNonces generates the verification nonce to start off a new musig2
|
// GenMusigNonces generates the verification nonce to start off a new musig2
|
||||||
// channel session.
|
// channel session.
|
||||||
func (lc *LightningChannel) GenMusigNonces() (*musig2.Nonces, error) {
|
func (lc *LightningChannel) GenMusigNonces() (*musig2.Nonces, error) {
|
||||||
lc.RLock()
|
lc.Lock()
|
||||||
defer lc.RUnlock()
|
defer lc.Unlock()
|
||||||
|
|
||||||
var err error
|
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.pendingVerificationNonce, err = NewMusigVerificationNonce(
|
||||||
lc.channelState.LocalChanCfg.MultiSigKey.PubKey,
|
lc.channelState.LocalChanCfg.MultiSigKey.PubKey,
|
||||||
lc.currentHeight, lc.channelState.RevocationProducer,
|
lc.currentHeight+1, lc.taprootNonceProducer,
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -7794,20 +8095,10 @@ func (lc *LightningChannel) GenMusigNonces() (*musig2.Nonces, error) {
|
|||||||
// NewMusigVerificationNonce generates the local or verification nonce for
|
// NewMusigVerificationNonce generates the local or verification nonce for
|
||||||
// another musig2 session. In order to permit our implementation to not have to
|
// 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
|
// write any secret nonce state to disk, we'll use the _next_ shachain
|
||||||
// pre-image as our primary randomness source.
|
// pre-image as our primary randomness source. When used to generate the nonce
|
||||||
func NewMusigVerificationNonce(pubKey *btcec.PublicKey, currentHeight uint64,
|
// again to broadcast our commitment hte current height will be used.
|
||||||
shaGen shachain.Producer, forBroadcast bool) (*musig2.Nonces, error) {
|
func NewMusigVerificationNonce(pubKey *btcec.PublicKey, targetHeight uint64,
|
||||||
|
shaGen shachain.Producer) (*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
|
// Now that we know what height we need, we'll grab the shachain
|
||||||
// pre-image at the target destination.
|
// pre-image at the target destination.
|
||||||
@@ -7834,8 +8125,12 @@ func (lc *LightningChannel) HasRemoteNonces() bool {
|
|||||||
func (lc *LightningChannel) InitRemoteMusigNonces(remoteNonce *musig2.Nonces,
|
func (lc *LightningChannel) InitRemoteMusigNonces(remoteNonce *musig2.Nonces,
|
||||||
) error {
|
) error {
|
||||||
|
|
||||||
lc.RLock()
|
lc.Lock()
|
||||||
defer lc.RUnlock()
|
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
|
// 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
|
// a new pair of musig sessions for our local commitment and the
|
||||||
|
|||||||
@@ -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
|
// VerifyCommitSig attempts to verify the passed partial signature against the
|
||||||
// passed commitment transaction. A keyspend sighash is assumed to generate 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
|
// signed message. As we never re-use nonces, a new verification nonce (our
|
||||||
|
|||||||
Reference in new issue
Block a user