Skip to content

Commit 827bcd9

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 c34ef62 commit 827bcd9

File tree

8 files changed

+224
-39
lines changed

8 files changed

+224
-39
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._
@@ -229,7 +230,7 @@ case class LocalCommit(index: Long, spec: CommitmentSpec, commitTxAndRemoteSig:
229230
object LocalCommit {
230231
def fromCommitSig(keyManager: ChannelKeyManager, params: ChannelParams, fundingTxId: TxId,
231232
fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo,
232-
commit: CommitSig, localCommitIndex: Long, spec: CommitmentSpec, localPerCommitmentPoint: PublicKey, localNonce_opt: Option[(SecretNonce, IndividualNonce)]): Either[ChannelException, LocalCommit] = {
233+
commit: CommitSig, localCommitIndex: Long, spec: CommitmentSpec, localPerCommitmentPoint: PublicKey, localNonce_opt: Option[(SecretNonce, IndividualNonce)])(implicit log: LoggingAdapter): Either[ChannelException, LocalCommit] = {
233234
val (localCommitTx, htlcTxs) = Commitment.makeLocalTxs(keyManager, params.channelConfig, params.channelFeatures, localCommitIndex, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, localPerCommitmentPoint, spec)
234235
if (!localCommitTx.checkSig(commit, remoteFundingPubKey, TxOwner.Remote, params.commitmentFormat)) {
235236
return Left(InvalidCommitmentSignature(params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx))
@@ -243,6 +244,10 @@ object LocalCommit {
243244
val fundingPubkey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex).publicKey
244245
val Some(localNonce) = localNonce_opt
245246
if (!localCommitTx.checkPartialSignature(psig, fundingPubkey, localNonce._2, remoteFundingPubKey)) {
247+
log.debug(s"fromCommitSig: invalid partial signature $psig fundingPubkey = $fundingPubkey, fundingTxIndex = $fundingTxIndex localCommitIndex = $localCommitIndex localNonce = $localNonce remoteFundingPubKey = $remoteFundingPubKey")
248+
249+
val localNonce1 = keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingTxIndex, keyManager.keyPath(params.localParams, params.channelConfig), localCommitIndex)
250+
log.debug(s"with $localNonce1 ${localCommitTx.checkPartialSignature(psig, fundingPubkey, localNonce1._2, remoteFundingPubKey)}")
246251
return Left(InvalidCommitmentSignature(params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx))
247252
}
248253
}
@@ -267,9 +272,10 @@ case class RemoteCommit(index: Long, spec: CommitmentSpec, txid: TxId, remotePer
267272
def sign(keyManager: ChannelKeyManager, params: ChannelParams, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo, remoteNonce_opt: Option[IndividualNonce])(implicit log: LoggingAdapter): CommitSig = {
268273
val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(keyManager, params.channelConfig, params.channelFeatures, index, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, remotePerCommitmentPoint, spec)
269274
val (sig, tlvStream) = if (params.commitmentFormat.useTaproot) {
270-
val localNonce = keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingTxIndex, keyManager.keyPath(params.localParams, params.channelConfig), index)
275+
val localNonce = keyManager.signingNonce(params.localParams.fundingKeyPath, fundingTxIndex)
271276
val Some(remoteNonce) = remoteNonce_opt
272277
val Right(localPartialSigOfRemoteTx) = keyManager.partialSign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), remoteFundingPubKey, TxOwner.Remote, localNonce, remoteNonce)
278+
log.debug(s"RemoteCommit.sign localPartialSigOfRemoteTx = $localPartialSigOfRemoteTx fundingTxIndex = $fundingTxIndex remote commit index = $index remote nonce = $remoteNonce")
273279
val tlvStream: TlvStream[CommitSigTlv] = TlvStream(CommitSigTlv.PartialSignatureWithNonceTlv(PartialSignatureWithNonce(localPartialSigOfRemoteTx, localNonce._2)))
274280
(ByteVector64.Zeroes, tlvStream)
275281
} else {
@@ -661,7 +667,7 @@ case class Commitment(fundingTxIndex: Long,
661667
val localNonce = keyManager.signingNonce(params.localParams.fundingKeyPath, fundingTxIndex)
662668
val Some(remoteNonce) = nextRemoteNonce_opt
663669
val Right(psig) = keyManager.partialSign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), remoteFundingPubKey, TxOwner.Remote, localNonce, remoteNonce)
664-
log.debug(s"sendCommit: creating partial sig $psig for remote commit tx ${remoteCommitTx.tx.txid} with remote nonce $remoteNonce and remoteNextPerCommitmentPoint = $remoteNextPerCommitmentPoint")
670+
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")
665671
Set(CommitSigTlv.PartialSignatureWithNonceTlv(PartialSignatureWithNonce(psig, localNonce._2)))
666672
} else {
667673
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) {
@@ -1228,9 +1238,31 @@ case class Commitments(params: ChannelParams,
12281238
}
12291239

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

12361268
def localFundingSigs(fundingTxId: TxId): Option[TxSignatures] = {
@@ -1350,30 +1382,33 @@ case class Commitments(params: ChannelParams,
13501382
}
13511383

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

13711400
/**
1372-
* Create local verification nonces for the next funding tx
1401+
* Create local verification nonces a specific funding tx index and a range of commit tx indexes
1402+
*
13731403
* @param keyManager key manager that will generate actual nonces
1374-
* @return a list of 2 verification nonces for the next funding tx: one for the current commitment index, one for the next commitment index
1404+
* @param fundingIndex funding tx index
1405+
* @param commitIndexes range of commit tx indexes
1406+
* @return a list of nonces if raproot is used, or an empty list
13751407
*/
1376-
def generateLocalNonces(keyManager: ChannelKeyManager): List[IndividualNonce] = generateLocalNonces(keyManager, latest.commitment.fundingTxIndex + 1)
1408+
def generateLocalNonces(keyManager: ChannelKeyManager, fundingIndex: Long, commitIndexes: Long*): List[IndividualNonce] = {
1409+
commitIndexes.toList.flatMap(commitIndex => generateLocalNonce(keyManager, fundingIndex, commitIndex))
1410+
}
1411+
13771412
}
13781413

13791414
object Commitments {

0 commit comments

Comments
 (0)