Skip to content

Commit aae084e

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 be15fd6 commit aae084e

File tree

8 files changed

+185
-238
lines changed

8 files changed

+185
-238
lines changed

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

Lines changed: 56 additions & 21 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._
@@ -227,7 +228,7 @@ case class LocalCommit(index: Long, spec: CommitmentSpec, commitTxAndRemoteSig:
227228
object LocalCommit {
228229
def fromCommitSig(keyManager: ChannelKeyManager, params: ChannelParams, fundingTxId: TxId,
229230
fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo,
230-
commit: CommitSig, localCommitIndex: Long, spec: CommitmentSpec, localPerCommitmentPoint: PublicKey, localNonce_opt: Option[(SecretNonce, IndividualNonce)]): Either[ChannelException, LocalCommit] = {
231+
commit: CommitSig, localCommitIndex: Long, spec: CommitmentSpec, localPerCommitmentPoint: PublicKey, localNonce_opt: Option[(SecretNonce, IndividualNonce)])(implicit log: LoggingAdapter): Either[ChannelException, LocalCommit] = {
231232
val (localCommitTx, htlcTxs) = Commitment.makeLocalTxs(keyManager, params.channelConfig, params.channelFeatures, localCommitIndex, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, localPerCommitmentPoint, spec)
232233
if (!localCommitTx.checkSig(commit, remoteFundingPubKey, TxOwner.Remote, params.commitmentFormat)) {
233234
return Left(InvalidCommitmentSignature(params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx))
@@ -241,6 +242,10 @@ object LocalCommit {
241242
val fundingPubkey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex).publicKey
242243
val Some(localNonce) = localNonce_opt
243244
if (!localCommitTx.checkPartialSignature(psig, fundingPubkey, localNonce._2, remoteFundingPubKey)) {
245+
log.debug(s"fromCommitSig: invalid partial signature $psig fundingPubkey = $fundingPubkey, fundingTxIndex = $fundingTxIndex localCommitIndex = $localCommitIndex localNonce = $localNonce remoteFundingPubKey = $remoteFundingPubKey")
246+
247+
val localNonce1 = keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingTxIndex, keyManager.keyPath(params.localParams, params.channelConfig), localCommitIndex)
248+
log.debug(s"with $localNonce1 ${localCommitTx.checkPartialSignature(psig, fundingPubkey, localNonce1._2, remoteFundingPubKey)}")
244249
return Left(InvalidCommitmentSignature(params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx))
245250
}
246251
}
@@ -265,9 +270,10 @@ case class RemoteCommit(index: Long, spec: CommitmentSpec, txid: TxId, remotePer
265270
def sign(keyManager: ChannelKeyManager, params: ChannelParams, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo, remoteNonce_opt: Option[IndividualNonce])(implicit log: LoggingAdapter): CommitSig = {
266271
val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(keyManager, params.channelConfig, params.channelFeatures, index, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, remotePerCommitmentPoint, spec)
267272
val (sig, tlvStream) = if (params.commitmentFormat.useTaproot) {
268-
val localNonce = keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingTxIndex, keyManager.keyPath(params.localParams, params.channelConfig), index)
273+
val localNonce = keyManager.signingNonce(params.localParams.fundingKeyPath, fundingTxIndex)
269274
val Some(remoteNonce) = remoteNonce_opt
270275
val Right(localPartialSigOfRemoteTx) = keyManager.partialSign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), remoteFundingPubKey, TxOwner.Remote, localNonce, remoteNonce)
276+
log.debug(s"RemoteCommit.sign localPartialSigOfRemoteTx = $localPartialSigOfRemoteTx fundingTxIndex = $fundingTxIndex remote commit index = $index remote nonce = $remoteNonce")
271277
val tlvStream: TlvStream[CommitSigTlv] = TlvStream(CommitSigTlv.PartialSignatureWithNonceTlv(PartialSignatureWithNonce(localPartialSigOfRemoteTx, localNonce._2)))
272278
(ByteVector64.Zeroes, tlvStream)
273279
} else {
@@ -659,7 +665,7 @@ case class Commitment(fundingTxIndex: Long,
659665
val localNonce = keyManager.signingNonce(params.localParams.fundingKeyPath, fundingTxIndex)
660666
val Some(remoteNonce) = nextRemoteNonce_opt
661667
val Right(psig) = keyManager.partialSign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), remoteFundingPubKey, TxOwner.Remote, localNonce, remoteNonce)
662-
log.debug(s"sendCommit: creating partial sig $psig for remote commit tx ${remoteCommitTx.tx.txid} with remote nonce $remoteNonce and remoteNextPerCommitmentPoint = $remoteNextPerCommitmentPoint")
668+
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")
663669
Set(CommitSigTlv.PartialSignatureWithNonceTlv(PartialSignatureWithNonce(psig, localNonce._2)))
664670
} else {
665671
Set.empty
@@ -1080,6 +1086,10 @@ case class Commitments(params: ChannelParams,
10801086
}
10811087
val channelKeyPath = keyManager.keyPath(params.localParams, params.channelConfig)
10821088
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, localCommitIndex + 1)
1089+
1090+
val fundingIndexes = active.map(_.fundingTxIndex).toSet
1091+
if (fundingIndexes.size > 1) log.warning(s"more than 1 funding tx index")
1092+
10831093
// Signatures are sent in order (most recent first), calling `zip` will drop trailing sigs that are for deactivated/pruned commitments.
10841094
val active1 = active.zip(commits).map { case (commitment, commit) =>
10851095
val localNonce_opt = if (params.commitmentFormat.useTaproot) {
@@ -1226,9 +1236,31 @@ case class Commitments(params: ChannelParams,
12261236
}
12271237

12281238
/** This function should be used to ignore a commit_sig that we've already received. */
1229-
def ignoreRetransmittedCommitSig(commitSig: CommitSig): Boolean = {
1230-
val latestRemoteSig = latest.localCommit.commitTxAndRemoteSig.remoteSig
1231-
params.channelFeatures.hasFeature(Features.DualFunding) && commitSig.batchSize == 1 && latestRemoteSig == commitSig.sigOrPartialSig
1239+
def ignoreRetransmittedCommitSig(commitSig: CommitSig, keyManager: ChannelKeyManager): Boolean = commitSig.sigOrPartialSig match {
1240+
case _ if !params.channelFeatures.hasFeature(Features.DualFunding) => false
1241+
case _ if commitSig.batchSize != 1 => false
1242+
case Left(_) =>
1243+
commitSig.sigOrPartialSig == latest.localCommit.commitTxAndRemoteSig.remoteSig
1244+
case Right(psig) if active.size > 1 =>
1245+
// we cannot compare partial signatures directly as they are not deterministic (a new signing nonce is used every time a signature is computed)
1246+
// => instead we simply check that the provided partial signature is valid for our latest commit tx
1247+
val localFundingKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, latest.fundingTxIndex).publicKey
1248+
val Some(localNonce) = generateLocalNonce(keyManager, latest.fundingTxIndex, latest.localCommit.index)
1249+
val Right(oldPsig) = latest.localCommit.commitTxAndRemoteSig.remoteSig
1250+
val currentcheck = latest.localCommit.commitTxAndRemoteSig.commitTx.checkPartialSignature(psig, localFundingKey, localNonce, latest.remoteFundingPubKey)
1251+
val oldcheck = latest.localCommit.commitTxAndRemoteSig.commitTx.checkPartialSignature(oldPsig, localFundingKey, localNonce, latest.remoteFundingPubKey)
1252+
require(oldcheck)
1253+
currentcheck
1254+
case Right(psig) =>
1255+
// we cannot compare partial signatures directly as they are not deterministic (a new signing nonce is used every time a signature is computed)
1256+
// => instead we simply check that the provided partial signature is valid for our latest commit tx
1257+
val localFundingKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, latest.fundingTxIndex).publicKey
1258+
val Some(localNonce) = generateLocalNonce(keyManager, latest.fundingTxIndex, latest.localCommit.index)
1259+
val Right(oldPsig) = latest.localCommit.commitTxAndRemoteSig.remoteSig
1260+
val currentcheck = latest.localCommit.commitTxAndRemoteSig.commitTx.checkPartialSignature(psig, localFundingKey, localNonce, latest.remoteFundingPubKey)
1261+
val oldcheck = latest.localCommit.commitTxAndRemoteSig.commitTx.checkPartialSignature(oldPsig, localFundingKey, localNonce, latest.remoteFundingPubKey)
1262+
require(oldcheck)
1263+
currentcheck
12321264
}
12331265

12341266
def localFundingSigs(fundingTxId: TxId): Option[TxSignatures] = {
@@ -1348,30 +1380,33 @@ case class Commitments(params: ChannelParams,
13481380
}
13491381

13501382
/**
1351-
* Create local verification nonces for the next funding tx
1352-
* @param keyManager key manager that will generate actual nonces
1353-
* @return a list of 2 verification nonces for the next funding tx: one for the current commitment index, one for the next commitment index
1383+
* Generate local verification nonces for a specific funding tx index and commit tx index
1384+
*
1385+
* @param keyManager key manager that will generate actual nonces
1386+
* @param fundingIndex funding tx index
1387+
* @param commitIndex commit tx index
1388+
* @return a public nonce for thr provided fundint tx index and commit tx index if taproot is used, None otherwise
13541389
*/
1355-
def generateLocalNonces(keyManager: ChannelKeyManager, fundingIndex: Long): List[IndividualNonce] = {
1390+
def generateLocalNonce(keyManager: ChannelKeyManager, fundingIndex: Long, commitIndex: Long): Option[IndividualNonce] = {
13561391
if (latest.params.commitmentFormat.useTaproot) {
1357-
1358-
def localNonce(commitIndex: Long) = {
1359-
val (_, nonce) = keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingIndex, keyManager.keyPath(params.localParams, params.channelConfig), commitIndex)
1360-
nonce
1361-
}
1362-
1363-
List(localNonce(localCommitIndex), localNonce(localCommitIndex + 1))
1392+
Some(keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingIndex, keyManager.keyPath(params.localParams, params.channelConfig), commitIndex)._2)
13641393
} else {
1365-
List.empty
1394+
None
13661395
}
13671396
}
13681397

13691398
/**
1370-
* Create local verification nonces for the next funding tx
1399+
* Create local verification nonces a specific funding tx index and a range of commit tx indexes
1400+
*
13711401
* @param keyManager key manager that will generate actual nonces
1372-
* @return a list of 2 verification nonces for the next funding tx: one for the current commitment index, one for the next commitment index
1402+
* @param fundingIndex funding tx index
1403+
* @param commitIndexes range of commit tx indexes
1404+
* @return a list of nonces if raproot is used, or an empty list
13731405
*/
1374-
def generateLocalNonces(keyManager: ChannelKeyManager): List[IndividualNonce] = generateLocalNonces(keyManager, latest.commitment.fundingTxIndex + 1)
1406+
def generateLocalNonces(keyManager: ChannelKeyManager, fundingIndex: Long, commitIndexes: Long*): List[IndividualNonce] = {
1407+
commitIndexes.toList.flatMap(commitIndex => generateLocalNonce(keyManager, fundingIndex, commitIndex))
1408+
}
1409+
13751410
}
13761411

13771412
object Commitments {

0 commit comments

Comments
 (0)