Skip to content

Commit 6afa679

Browse files
committed
Fix splicing issues
Upon re-connection, when a splice has not been fully completed, nodes will re-send signatures for the previous remote commit tx. This signature will be ignored by the receiving nodes if it has already received it before it was disconnected, simply by comparing them (signatures are deterministic). With taproot channels, we also need to attach musig2 nonces for splices in progress to channel_reestablish, which are needed to re-generate the signature for the old commit tx.
1 parent c2cabf3 commit 6afa679

File tree

8 files changed

+188
-241
lines changed

8 files changed

+188
-241
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.SharedTransaction
1313
import fr.acinq.eclair.crypto.keymanager.ChannelKeyManager
1414
import fr.acinq.eclair.crypto.{Generators, ShaChain}
1515
import fr.acinq.eclair.payment.OutgoingPaymentPacket
16+
import fr.acinq.eclair.transactions.Transactions.TxOwner.{Local, Remote}
1617
import fr.acinq.eclair.transactions.Transactions._
1718
import fr.acinq.eclair.transactions._
1819
import fr.acinq.eclair.wire.protocol._
@@ -244,7 +245,7 @@ case class LocalCommit(index: Long, spec: CommitmentSpec, commitTxAndRemoteSig:
244245
object LocalCommit {
245246
def fromCommitSig(keyManager: ChannelKeyManager, params: ChannelParams, fundingTxId: TxId,
246247
fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo,
247-
commit: CommitSig, localCommitIndex: Long, spec: CommitmentSpec, localPerCommitmentPoint: PublicKey, localNonce_opt: Option[(SecretNonce, IndividualNonce)]): Either[ChannelException, LocalCommit] = {
248+
commit: CommitSig, localCommitIndex: Long, spec: CommitmentSpec, localPerCommitmentPoint: PublicKey, localNonce_opt: Option[(SecretNonce, IndividualNonce)])(implicit log: LoggingAdapter): Either[ChannelException, LocalCommit] = {
248249
val (localCommitTx, htlcTxs) = Commitment.makeLocalTxs(keyManager, params.channelConfig, params.channelFeatures, localCommitIndex, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, localPerCommitmentPoint, spec)
249250
if (!localCommitTx.checkSig(commit, remoteFundingPubKey, TxOwner.Remote, params.commitmentFormat)) {
250251
return Left(InvalidCommitmentSignature(params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx))
@@ -258,6 +259,10 @@ object LocalCommit {
258259
val fundingPubkey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex).publicKey
259260
val Some(localNonce) = localNonce_opt
260261
if (!localCommitTx.checkPartialSignature(psig, fundingPubkey, localNonce._2, remoteFundingPubKey)) {
262+
log.debug(s"fromCommitSig: invalid partial signature $psig fundingPubkey = $fundingPubkey, fundingTxIndex = $fundingTxIndex localCommitIndex = $localCommitIndex localNonce = $localNonce remoteFundingPubKey = $remoteFundingPubKey")
263+
264+
val localNonce1 = keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingTxIndex, keyManager.keyPath(params.localParams, params.channelConfig), localCommitIndex)
265+
log.debug(s"with $localNonce1 ${localCommitTx.checkPartialSignature(psig, fundingPubkey, localNonce1._2, remoteFundingPubKey)}")
261266
return Left(InvalidCommitmentSignature(params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx))
262267
}
263268
}
@@ -286,9 +291,10 @@ case class RemoteCommit(index: Long, spec: CommitmentSpec, txid: TxId, remotePer
286291
def sign(keyManager: ChannelKeyManager, params: ChannelParams, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo, remoteNonce_opt: Option[IndividualNonce])(implicit log: LoggingAdapter): CommitSig = {
287292
val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(keyManager, params.channelConfig, params.channelFeatures, index, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, remotePerCommitmentPoint, spec)
288293
val (sig, tlvStream) = if (params.commitmentFormat.useTaproot) {
289-
val localNonce = keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingTxIndex, keyManager.keyPath(params.localParams, params.channelConfig), index)
294+
val localNonce = keyManager.signingNonce(params.localParams.fundingKeyPath, fundingTxIndex)
290295
val Some(remoteNonce) = remoteNonce_opt
291296
val Right(localPartialSigOfRemoteTx) = keyManager.partialSign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), remoteFundingPubKey, TxOwner.Remote, localNonce, remoteNonce)
297+
log.debug(s"RemoteCommit.sign localPartialSigOfRemoteTx = $localPartialSigOfRemoteTx fundingTxIndex = $fundingTxIndex remote commit index = $index remote nonce = $remoteNonce")
292298
val tlvStream: TlvStream[CommitSigTlv] = TlvStream(CommitSigTlv.PartialSignatureWithNonceTlv(PartialSignatureWithNonce(localPartialSigOfRemoteTx, localNonce._2)))
293299
(ByteVector64.Zeroes, tlvStream)
294300
} else {
@@ -680,7 +686,7 @@ case class Commitment(fundingTxIndex: Long,
680686
val localNonce = keyManager.signingNonce(params.localParams.fundingKeyPath, fundingTxIndex)
681687
val Some(remoteNonce) = nextRemoteNonce_opt
682688
val Right(psig) = keyManager.partialSign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), remoteFundingPubKey, TxOwner.Remote, localNonce, remoteNonce)
683-
log.debug(s"sendCommit: creating partial sig $psig for remote commit tx ${remoteCommitTx.tx.txid} with remote nonce $remoteNonce and remoteNextPerCommitmentPoint = $remoteNextPerCommitmentPoint")
689+
log.debug(s"sendCommit: creating partial sig $psig for remote commit tx ${remoteCommitTx.tx.txid} with fundingTxIndex = $fundingTxIndex remoteCommit.index (should add +1) = ${remoteCommit.index} remote nonce $remoteNonce and remoteNextPerCommitmentPoint = $remoteNextPerCommitmentPoint")
684690
Set(CommitSigTlv.PartialSignatureWithNonceTlv(PartialSignatureWithNonce(psig, localNonce._2)))
685691
} else {
686692
Set.empty
@@ -1101,6 +1107,10 @@ case class Commitments(params: ChannelParams,
11011107
}
11021108
val channelKeyPath = keyManager.keyPath(params.localParams, params.channelConfig)
11031109
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, localCommitIndex + 1)
1110+
1111+
val fundingIndexes = active.map(_.fundingTxIndex).toSet
1112+
if (fundingIndexes.size > 1) log.warning(s"more than 1 funding tx index")
1113+
11041114
// Signatures are sent in order (most recent first), calling `zip` will drop trailing sigs that are for deactivated/pruned commitments.
11051115
val active1 = active.zip(commits).map { case (commitment, commit) =>
11061116
val localNonce_opt = if (params.commitmentFormat.useTaproot) {
@@ -1247,12 +1257,34 @@ case class Commitments(params: ChannelParams,
12471257
}
12481258

12491259
/** This function should be used to ignore a commit_sig that we've already received. */
1250-
def ignoreRetransmittedCommitSig(commitSig: CommitSig): Boolean = {
1251-
val latestRemoteSigOrPartialSig = latest.localCommit.commitTxAndRemoteSig.remoteSig match {
1252-
case RemoteSignature.FullSignature(sig) => Left(sig)
1253-
case RemoteSignature.PartialSignature(psig) => Right(psig)
1254-
}
1255-
params.channelFeatures.hasFeature(Features.DualFunding) && commitSig.batchSize == 1 && latestRemoteSigOrPartialSig == commitSig.sigOrPartialSig
1260+
def ignoreRetransmittedCommitSig(commitSig: CommitSig, keyManager: ChannelKeyManager): Boolean = commitSig.sigOrPartialSig match {
1261+
case _ if !params.channelFeatures.hasFeature(Features.DualFunding) => false
1262+
case _ if commitSig.batchSize != 1 => false
1263+
case Left(sig) =>
1264+
latest.localCommit.commitTxAndRemoteSig.remoteSig match {
1265+
case f: RemoteSignature.FullSignature => f.sig == sig
1266+
case _: RemoteSignature.PartialSignature => false
1267+
}
1268+
case Right(psig) if active.size > 1 =>
1269+
// we cannot compare partial signatures directly as they are not deterministic (a new signing nonce is used every time a signature is computed)
1270+
// => instead we simply check that the provided partial signature is valid for our latest commit tx
1271+
val localFundingKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, latest.fundingTxIndex).publicKey
1272+
val Some(localNonce) = generateLocalNonce(keyManager, latest.fundingTxIndex, latest.localCommit.index)
1273+
val RemoteSignature.PartialSignature(oldPsig) = latest.localCommit.commitTxAndRemoteSig.remoteSig
1274+
val currentcheck = latest.localCommit.commitTxAndRemoteSig.commitTx.checkPartialSignature(psig, localFundingKey, localNonce, latest.remoteFundingPubKey)
1275+
val oldcheck = latest.localCommit.commitTxAndRemoteSig.commitTx.checkPartialSignature(oldPsig, localFundingKey, localNonce, latest.remoteFundingPubKey)
1276+
require(oldcheck)
1277+
currentcheck
1278+
case Right(psig) =>
1279+
// we cannot compare partial signatures directly as they are not deterministic (a new signing nonce is used every time a signature is computed)
1280+
// => instead we simply check that the provided partial signature is valid for our latest commit tx
1281+
val localFundingKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, latest.fundingTxIndex).publicKey
1282+
val Some(localNonce) = generateLocalNonce(keyManager, latest.fundingTxIndex, latest.localCommit.index)
1283+
val RemoteSignature.PartialSignature(oldPsig) = latest.localCommit.commitTxAndRemoteSig.remoteSig
1284+
val currentcheck = latest.localCommit.commitTxAndRemoteSig.commitTx.checkPartialSignature(psig, localFundingKey, localNonce, latest.remoteFundingPubKey)
1285+
val oldcheck = latest.localCommit.commitTxAndRemoteSig.commitTx.checkPartialSignature(oldPsig, localFundingKey, localNonce, latest.remoteFundingPubKey)
1286+
require(oldcheck)
1287+
currentcheck
12561288
}
12571289

12581290
def localFundingSigs(fundingTxId: TxId): Option[TxSignatures] = {
@@ -1372,30 +1404,33 @@ case class Commitments(params: ChannelParams,
13721404
}
13731405

13741406
/**
1375-
* Create local verification nonces for the next funding tx
1376-
* @param keyManager key manager that will generate actual nonces
1377-
* @return a list of 2 verification nonces for the next funding tx: one for the current commitment index, one for the next commitment index
1407+
* Generate local verification nonces for a specific funding tx index and commit tx index
1408+
*
1409+
* @param keyManager key manager that will generate actual nonces
1410+
* @param fundingIndex funding tx index
1411+
* @param commitIndex commit tx index
1412+
* @return a public nonce for thr provided fundint tx index and commit tx index if taproot is used, None otherwise
13781413
*/
1379-
def generateLocalNonces(keyManager: ChannelKeyManager, fundingIndex: Long): List[IndividualNonce] = {
1414+
def generateLocalNonce(keyManager: ChannelKeyManager, fundingIndex: Long, commitIndex: Long): Option[IndividualNonce] = {
13801415
if (latest.params.commitmentFormat.useTaproot) {
1381-
1382-
def localNonce(commitIndex: Long) = {
1383-
val (_, nonce) = keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingIndex, keyManager.keyPath(params.localParams, params.channelConfig), commitIndex)
1384-
nonce
1385-
}
1386-
1387-
List(localNonce(localCommitIndex), localNonce(localCommitIndex + 1))
1416+
Some(keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingIndex, keyManager.keyPath(params.localParams, params.channelConfig), commitIndex)._2)
13881417
} else {
1389-
List.empty
1418+
None
13901419
}
13911420
}
13921421

13931422
/**
1394-
* Create local verification nonces for the next funding tx
1423+
* Create local verification nonces a specific funding tx index and a range of commit tx indexes
1424+
*
13951425
* @param keyManager key manager that will generate actual nonces
1396-
* @return a list of 2 verification nonces for the next funding tx: one for the current commitment index, one for the next commitment index
1426+
* @param fundingIndex funding tx index
1427+
* @param commitIndexes range of commit tx indexes
1428+
* @return a list of nonces if raproot is used, or an empty list
13971429
*/
1398-
def generateLocalNonces(keyManager: ChannelKeyManager): List[IndividualNonce] = generateLocalNonces(keyManager, latest.commitment.fundingTxIndex + 1)
1430+
def generateLocalNonces(keyManager: ChannelKeyManager, fundingIndex: Long, commitIndexes: Long*): List[IndividualNonce] = {
1431+
commitIndexes.toList.flatMap(commitIndex => generateLocalNonce(keyManager, fundingIndex, commitIndex))
1432+
}
1433+
13991434
}
14001435

14011436
object Commitments {

0 commit comments

Comments
 (0)