Skip to content

Commit 2755e8d

Browse files
committed
Fixup: simplify nonce handling in InteractiveTxBuilder and improve error handling
In the interactive session there can can be only one optional shared input, and we know its type in advance and generate a local nonce for it. We add specific channel exceptions for invalid/missing nonce which makes error handling safer (they are matched in our error handler).
1 parent c35a1e8 commit 2755e8d

File tree

6 files changed

+141
-79
lines changed

6 files changed

+141
-79
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,5 +150,9 @@ case class CommandUnavailableInThisState (override val channelId: Byte
150150
case class ForbiddenDuringSplice (override val channelId: ByteVector32, command: String) extends ChannelException(channelId, s"cannot process $command while splicing")
151151
case class ForbiddenDuringQuiescence (override val channelId: ByteVector32, command: String) extends ChannelException(channelId, s"cannot process $command while quiescent")
152152
case class ConcurrentRemoteSplice (override val channelId: ByteVector32) extends ChannelException(channelId, "splice attempt canceled, remote initiated splice before us")
153-
case class MissingNextLocalNonce (override val channelId: ByteVector32) extends ChannelException(channelId, "next local nonce tlv is missing")
153+
case class MissingNonce (override val channelId: ByteVector32, fundingTxId: TxId) extends ChannelException(channelId, s"next nonce for funding tx $fundingTxId is missing")
154+
case class InvalidNonce (override val channelId: ByteVector32, fundingTxId: TxId) extends ChannelException(channelId, s"next nonce for funding tx $fundingTxId is not valid")
155+
case class MissingFundingNonce (override val channelId: ByteVector32) extends ChannelException(channelId, "missing funding nonce")
156+
case class InvalidFundingNonce (override val channelId: ByteVector32) extends ChannelException(channelId, "invalid funding nonce")
157+
case class MissingShutdownNonce (override val channelId: ByteVector32) extends ChannelException(channelId, "missing shutdown nonce")
154158
// @formatter:on

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

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ object LocalCommit {
199199

200200
/** The remote commitment maps to a commitment transaction that only our peer can sign and broadcast. */
201201
case class RemoteCommit(index: Long, spec: CommitmentSpec, txId: TxId, remotePerCommitmentPoint: PublicKey) {
202-
def sign(channelParams: ChannelParams, commitParams: CommitParams, channelKeys: ChannelKeys, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo, commitmentFormat: CommitmentFormat, remoteNonce_opt: Option[IndividualNonce]): CommitSig = {
202+
def sign(channelParams: ChannelParams, commitParams: CommitParams, channelKeys: ChannelKeys, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo, commitmentFormat: CommitmentFormat, remoteNonce_opt: Option[IndividualNonce]): Either[ChannelException, CommitSig] = {
203203
val fundingKey = channelKeys.fundingKey(fundingTxIndex)
204204
val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentPoint, commitmentFormat)
205205
val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(channelParams, commitParams, commitKeys, index, fundingKey, remoteFundingPubKey, commitInput, commitmentFormat, spec)
@@ -208,11 +208,15 @@ case class RemoteCommit(index: Long, spec: CommitmentSpec, txId: TxId, remotePer
208208
commitmentFormat match {
209209
case _: SegwitV0CommitmentFormat =>
210210
val sig = remoteCommitTx.sign(fundingKey, remoteFundingPubKey).sig
211-
CommitSig(channelParams.channelId, sig, htlcSigs.toList)
211+
Right(CommitSig(channelParams.channelId, sig, htlcSigs.toList))
212+
case _: SimpleTaprootChannelCommitmentFormat if remoteNonce_opt.isEmpty =>
213+
Left(MissingNonce(channelParams.channelId, commitInput.outPoint.txid))
212214
case _: SimpleTaprootChannelCommitmentFormat =>
213215
val localNonce = NonceGenerator.signingNonce(fundingKey.publicKey)
214-
val Right(psig) = remoteCommitTx.partialSign(fundingKey, remoteFundingPubKey, Map.empty, localNonce, Seq(localNonce.publicNonce, remoteNonce_opt.get))
215-
CommitSig(channelParams.channelId, ByteVector64.Zeroes, htlcSigs.toList, TlvStream[CommitSigTlv](CommitSigTlv.PartialSignatureWithNonceTlv(psig)))
216+
remoteCommitTx.partialSign(fundingKey, remoteFundingPubKey, Map.empty, localNonce, Seq(localNonce.publicNonce, remoteNonce_opt.get)) match {
217+
case Left(t) => Left(InvalidNonce(channelParams.channelId, commitInput.outPoint.txid))
218+
case Right(psig) => Right(CommitSig(channelParams.channelId, ByteVector64.Zeroes, htlcSigs.toList, TlvStream[CommitSigTlv](CommitSigTlv.PartialSignatureWithNonceTlv(psig))))
219+
}
216220
}
217221
}
218222
}
@@ -649,7 +653,7 @@ case class Commitment(fundingTxIndex: Long,
649653
Right(())
650654
}
651655

652-
def sendCommit(params: ChannelParams, channelKeys: ChannelKeys, commitKeys: RemoteCommitmentKeys, changes: CommitmentChanges, remoteNextPerCommitmentPoint: PublicKey, batchSize: Int, nextRemoteNonce_opt: Option[IndividualNonce])(implicit log: LoggingAdapter): (Commitment, CommitSig) = {
656+
def sendCommit(params: ChannelParams, channelKeys: ChannelKeys, commitKeys: RemoteCommitmentKeys, changes: CommitmentChanges, remoteNextPerCommitmentPoint: PublicKey, batchSize: Int, nextRemoteNonce_opt: Option[IndividualNonce])(implicit log: LoggingAdapter): Either[ChannelException, (Commitment, CommitSig)] = {
653657
// remote commitment will include all local proposed changes + remote acked changes
654658
val spec = CommitmentSpec.reduce(remoteCommit.spec, changes.remoteChanges.acked, changes.localChanges.proposed)
655659
val fundingKey = localFundingKey(channelKeys)
@@ -662,8 +666,12 @@ case class Commitment(fundingTxIndex: Long,
662666
val partialSig: Option[CommitSigTlv] = commitmentFormat match {
663667
case _: SimpleTaprootChannelCommitmentFormat =>
664668
val localNonce = NonceGenerator.signingNonce(fundingKey.publicKey)
669+
if (nextRemoteNonce_opt.isEmpty) return Left(MissingNonce(params.channelId, remoteCommitTx.input.outPoint.txid))
665670
val Some(remoteNonce) = nextRemoteNonce_opt
666-
val Right(psig) = remoteCommitTx.partialSign(fundingKey, remoteFundingPubKey, Map.empty, localNonce, Seq(localNonce.publicNonce, remoteNonce))
671+
val psig = remoteCommitTx.partialSign(fundingKey, remoteFundingPubKey, Map.empty, localNonce, Seq(localNonce.publicNonce, remoteNonce)) match {
672+
case Left(t) => return Left(InvalidNonce(params.channelId, remoteCommitTx.input.outPoint.txid))
673+
case Right(psig) => psig
674+
}
667675
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")
668676
Some(CommitSigTlv.PartialSignatureWithNonceTlv(psig))
669677
case _ =>
@@ -679,7 +687,7 @@ case class Commitment(fundingTxIndex: Long,
679687
).flatten[CommitSigTlv]
680688
val commitSig = CommitSig(params.channelId, sig, htlcSigs.toList, TlvStream(tlvs))
681689
val nextRemoteCommit = NextRemoteCommit(commitSig, RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint))
682-
(copy(nextRemoteCommit_opt = Some(nextRemoteCommit)), commitSig)
690+
Right((copy(nextRemoteCommit_opt = Some(nextRemoteCommit)), commitSig))
683691
}
684692

685693
def receiveCommit(params: ChannelParams, channelKeys: ChannelKeys, commitKeys: LocalCommitmentKeys, changes: CommitmentChanges, commit: CommitSig)(implicit log: LoggingAdapter): Either[ChannelException, Commitment] = {
@@ -1081,7 +1089,10 @@ case class Commitments(channelParams: ChannelParams,
10811089

10821090
val (active1, sigs) = active.map(c => {
10831091
val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remoteNextPerCommitmentPoint, c.commitmentFormat)
1084-
c.sendCommit(channelParams, channelKeys, commitKeys, changes, remoteNextPerCommitmentPoint, active.size, remoteNonce(c))
1092+
c.sendCommit(channelParams, channelKeys, commitKeys, changes, remoteNextPerCommitmentPoint, active.size, remoteNonce(c)) match {
1093+
case Left(e) => return Left(e)
1094+
case Right((c, cs)) => (c, cs)
1095+
}
10851096
}).unzip
10861097
val commitments1 = copy(
10871098
changes = changes.copy(

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ object Helpers {
133133
}
134134

135135
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel)
136-
if ((channelFeatures.hasFeature(Features.SimpleTaprootChannelsPhoenix) || channelFeatures.hasFeature(Features.SimpleTaprootChannelsStaging)) && open.nexLocalNonce_opt.isEmpty) return Left(MissingNextLocalNonce(open.temporaryChannelId))
136+
if ((channelFeatures.hasFeature(Features.SimpleTaprootChannelsPhoenix) || channelFeatures.hasFeature(Features.SimpleTaprootChannelsStaging)) && open.nexLocalNonce_opt.isEmpty) return Left(MissingNonce(open.temporaryChannelId, TxId(ByteVector32.Zeroes)))
137137

138138
// BOLT #2: The receiving node MUST fail the channel if: it considers feerate_per_kw too small for timely processing or unreasonably large.
139139
val localFeeratePerKw = nodeParams.onChainFeeConf.getCommitmentFeerate(nodeParams.currentBitcoinCoreFeerates, remoteNodeId, channelFeatures.commitmentFormat, open.fundingSatoshis)
@@ -145,7 +145,7 @@ object Helpers {
145145
val reserveToFundingRatio = open.channelReserveSatoshis.toLong.toDouble / Math.max(open.fundingSatoshis.toLong, 1)
146146
if (reserveToFundingRatio > nodeParams.channelConf.maxReserveToFundingRatio) return Left(ChannelReserveTooHigh(open.temporaryChannelId, open.channelReserveSatoshis, reserveToFundingRatio, nodeParams.channelConf.maxReserveToFundingRatio))
147147

148-
if (channelType.commitmentFormat.isInstanceOf[SimpleTaprootChannelCommitmentFormat] && open.nexLocalNonce_opt.isEmpty) return Left(MissingNextLocalNonce(open.temporaryChannelId))
148+
if (channelType.commitmentFormat.isInstanceOf[SimpleTaprootChannelCommitmentFormat] && open.nexLocalNonce_opt.isEmpty) return Left(MissingNonce(open.temporaryChannelId, TxId(ByteVector32.Zeroes)))
149149

150150
extractShutdownScript(open.temporaryChannelId, localFeatures, remoteFeatures, open.upfrontShutdownScript_opt).map(script_opt => (channelFeatures, script_opt))
151151
}
@@ -243,7 +243,7 @@ object Helpers {
243243
if (reserveToFundingRatio > nodeParams.channelConf.maxReserveToFundingRatio) return Left(ChannelReserveTooHigh(open.temporaryChannelId, accept.channelReserveSatoshis, reserveToFundingRatio, nodeParams.channelConf.maxReserveToFundingRatio))
244244

245245
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel)
246-
if (channelType.commitmentFormat.isInstanceOf[SimpleTaprootChannelCommitmentFormat] && accept.nexLocalNonce_opt.isEmpty) return Left(MissingNextLocalNonce(open.temporaryChannelId))
246+
if (channelType.commitmentFormat.isInstanceOf[SimpleTaprootChannelCommitmentFormat] && accept.nexLocalNonce_opt.isEmpty) return Left(MissingNonce(open.temporaryChannelId, TxId(ByteVector32.Zeroes)))
247247
extractShutdownScript(accept.temporaryChannelId, localFeatures, remoteFeatures, accept.upfrontShutdownScript_opt).map(script_opt => (channelFeatures, script_opt))
248248
}
249249

0 commit comments

Comments
 (0)