What changed, and why it matters
This commit fixes a bug in Eclair's Lightning payment channels that use the newer taproot format. When two nodes reconnect after a temporary network interruption, the local node used to resend the exact same cryptographic signature it had sent before. For taproot channels, the remote peer may choose a fresh random 'nonce' after reconnecting, which makes the old signature invalid. The fix makes the local node re-sign the next commitment using the latest nonces received during reconnection, preventing the channel from getting stuck or failing to advance after reconnect.
Treat this as a security-relevant correctness fix. Nodes running taproot channels should upgrade to ensure reconnection signatures are valid and channels do not get stuck or force-closed unnecessarily. Review whether any other code paths still rely on a cached CommitSig after a peer nonce may have changed.
Security signals we found
Protocol-level signature invalidation on reconnection for taproot channels
Stored CommitSig no longer trusted after peer nonce change
Channel synchronization failure risk if old signature is accepted/rejected incorrectly
Taproot-specific nonce handling changed in reconnection path
Evidence from the diff
The patch changes the reconnection synchronization logic in Helpers.scala so that, instead of retransmitting the stored CommitSig for the next remote commitment, it re-signs the pending remote commitment using the remote nonce from the incoming channel_reestablish message. To support this, the NextRemoteCommit wrapper (which bundled a stored sig with the RemoteCommit) is removed; Commitment.nextRemoteCommit_opt now stores only a RemoteCommit. RemoteCommit.sign gains a batchSize parameter and is invoked during sync with remoteChannelReestablish.nextCommitNonces. Serialization codecs are updated to read the legacy format (with stored sig) and write the new format (without sig). Tests confirm that the resent CommitSig differs from the original one when taproot is enabled.
Changed components
eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scalaeclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scalaeclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scalaeclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scalaeclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scalaInspect captured patch +84 / −64
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala
index 7200127..5ff0323 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala
@@ -200,7 +200,7 @@ object LocalCommit {
/** The remote commitment maps to a commitment transaction that only our peer can sign and broadcast. */
case class RemoteCommit(index: Long, spec: CommitmentSpec, txId: TxId, remotePerCommitmentPoint: PublicKey) {
- def sign(channelParams: ChannelParams, commitParams: CommitParams, channelKeys: ChannelKeys, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo, commitmentFormat: CommitmentFormat, remoteNonce_opt: Option[IndividualNonce]): Either[ChannelException, CommitSig] = {
+ def sign(channelParams: ChannelParams, commitParams: CommitParams, channelKeys: ChannelKeys, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo, commitmentFormat: CommitmentFormat, remoteNonce_opt: Option[IndividualNonce], batchSize: Int = 1): Either[ChannelException, CommitSig] = {
val fundingKey = channelKeys.fundingKey(fundingTxIndex)
val commitKeys = RemoteCommitmentKeys(channelParams, channelKeys, remotePerCommitmentPoint, commitmentFormat)
val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(channelParams, commitParams, commitKeys, index, fundingKey, remoteFundingPubKey, commitInput, commitmentFormat, spec)
@@ -209,14 +209,14 @@ case class RemoteCommit(index: Long, spec: CommitmentSpec, txId: TxId, remotePer
commitmentFormat match {
case _: SegwitV0CommitmentFormat =>
val sig = remoteCommitTx.sign(fundingKey, remoteFundingPubKey)
- Right(CommitSig(channelParams.channelId, sig, htlcSigs.toList))
+ Right(CommitSig(channelParams.channelId, sig, htlcSigs.toList, batchSize))
case _: SimpleTaprootChannelCommitmentFormat =>
remoteNonce_opt match {
case Some(remoteNonce) =>
val localNonce = NonceGenerator.signingNonce(fundingKey.publicKey, remoteFundingPubKey, commitInput.outPoint.txid)
remoteCommitTx.partialSign(fundingKey, remoteFundingPubKey, localNonce, Seq(localNonce.publicNonce, remoteNonce)) match {
case Left(_) => Left(InvalidCommitNonce(channelParams.channelId, commitInput.outPoint.txid, index))
- case Right(psig) => Right(CommitSig(channelParams.channelId, psig, htlcSigs.toList, batchSize = 1))
+ case Right(psig) => Right(CommitSig(channelParams.channelId, psig, htlcSigs.toList, batchSize))
}
case None => Left(MissingCommitNonce(channelParams.channelId, commitInput.outPoint.txid, index))
}
@@ -224,9 +224,6 @@ case class RemoteCommit(index: Long, spec: CommitmentSpec, txId: TxId, remotePer
}
}
-/** We have the next remote commit when we've sent our commit_sig but haven't yet received their revoke_and_ack. */
-case class NextRemoteCommit(sig: CommitSig, commit: RemoteCommit)
-
/**
* If we ignore revoked commitments, there can be at most three concurrent commitment transactions during a force-close:
* - the local commitment
@@ -263,9 +260,9 @@ case class Commitment(fundingTxIndex: Long,
localCommit: LocalCommit,
remoteCommitParams: CommitParams,
remoteCommit: RemoteCommit,
- nextRemoteCommit_opt: Option[NextRemoteCommit]) {
+ nextRemoteCommit_opt: Option[RemoteCommit]) {
val fundingTxId: TxId = fundingInput.txid
- val commitTxIds: CommitTxIds = CommitTxIds(localCommit.txId, remoteCommit.txId, nextRemoteCommit_opt.map(_.commit.txId))
+ val commitTxIds: CommitTxIds = CommitTxIds(localCommit.txId, remoteCommit.txId, nextRemoteCommit_opt.map(_.txId))
val capacity: Satoshi = fundingAmount
// We can safely cast to millisatoshis since we verify that it's less than a valid millisatoshi amount.
val maxHtlcValueInFlight: MilliSatoshi = Seq(localCommitParams.maxHtlcValueInFlight, remoteCommitParams.maxHtlcValueInFlight, UInt64(MilliSatoshi.MaxMoney.toLong)).min.toBigInt.toLong.msat
@@ -328,7 +325,7 @@ case class Commitment(fundingTxIndex: Long,
def availableBalanceForSend(params: ChannelParams, changes: CommitmentChanges): MilliSatoshi = {
import params._
// we need to base the next current commitment on the last sig we sent, even if we didn't yet receive their revocation
- val remoteCommit1 = nextRemoteCommit_opt.map(_.commit).getOrElse(remoteCommit)
+ val remoteCommit1 = nextRemoteCommit_opt.getOrElse(remoteCommit)
val reduced = CommitmentSpec.reduce(remoteCommit1.spec, changes.remoteChanges.acked, changes.localChanges.proposed)
val balanceNoFees = (reduced.toRemote - localChannelReserve(params)).max(0 msat)
if (localParams.paysCommitTxFees) {
@@ -416,7 +413,7 @@ case class Commitment(fundingTxIndex: Long,
localCommit.spec.htlcs.collect(DirectedHtlc.outgoing).filter(expired) ++
remoteCommit.spec.htlcs.collect(DirectedHtlc.incoming).filter(expired) ++
- nextRemoteCommit_opt.toSeq.flatMap(_.commit.spec.htlcs.collect(DirectedHtlc.incoming).filter(expired).toSet)
+ nextRemoteCommit_opt.toSeq.flatMap(_.spec.htlcs.collect(DirectedHtlc.incoming).filter(expired).toSet)
}
/**
@@ -427,7 +424,7 @@ case class Commitment(fundingTxIndex: Long,
* NB: if we're in the middle of fulfilling or failing that HTLC, it will not be returned by this function.
*/
def getOutgoingHtlcCrossSigned(htlcId: Long): Option[UpdateAddHtlc] = for {
- localSigned <- nextRemoteCommit_opt.map(_.commit).getOrElse(remoteCommit).spec.findIncomingHtlcById(htlcId)
+ localSigned <- nextRemoteCommit_opt.getOrElse(remoteCommit).spec.findIncomingHtlcById(htlcId)
remoteSigned <- localCommit.spec.findOutgoingHtlcById(htlcId)
} yield {
require(localSigned.add == remoteSigned.add)
@@ -442,7 +439,7 @@ case class Commitment(fundingTxIndex: Long,
* NB: if we're in the middle of fulfilling or failing that HTLC, it will not be returned by this function.
*/
def getIncomingHtlcCrossSigned(htlcId: Long): Option[UpdateAddHtlc] = for {
- localSigned <- nextRemoteCommit_opt.map(_.commit).getOrElse(remoteCommit).spec.findOutgoingHtlcById(htlcId)
+ localSigned <- nextRemoteCommit_opt.getOrElse(remoteCommit).spec.findOutgoingHtlcById(htlcId)
remoteSigned <- localCommit.spec.findIncomingHtlcById(htlcId)
} yield {
require(localSigned.add == remoteSigned.add)
@@ -476,7 +473,7 @@ case class Commitment(fundingTxIndex: Long,
// let's compute the current commitments *as seen by them* with the additional htlc
// we need to base the next current commitment on the last sig we sent, even if we didn't yet receive their revocation
- val remoteCommit1 = nextRemoteCommit_opt.map(_.commit).getOrElse(remoteCommit)
+ val remoteCommit1 = nextRemoteCommit_opt.getOrElse(remoteCommit)
val reduced = CommitmentSpec.reduce(remoteCommit1.spec, changes.remoteChanges.acked, changes.localChanges.proposed)
// the HTLC we are about to create is outgoing, but from their point of view it is incoming
val outgoingHtlcs = reduced.htlcs.collect(DirectedHtlc.incoming)
@@ -680,7 +677,7 @@ case class Commitment(fundingTxIndex: Long,
}
}
val commitSig = CommitSig(params.channelId, sig, htlcSigs.toList, batchSize)
- val nextRemoteCommit = NextRemoteCommit(commitSig, RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint))
+ val nextRemoteCommit = RemoteCommit(remoteCommit.index + 1, spec, remoteCommitTx.tx.txid, remoteNextPerCommitmentPoint)
Right((copy(nextRemoteCommit_opt = Some(nextRemoteCommit)), commitSig))
}
@@ -796,7 +793,7 @@ case class FullCommitment(channelParams: ChannelParams, changes: CommitmentChang
val remoteChannelParams: RemoteChannelParams = channelParams.remoteParams
val remoteCommitParams: CommitParams = commitment.remoteCommitParams
val remoteCommit: RemoteCommit = commitment.remoteCommit
- val nextRemoteCommit_opt: Option[NextRemoteCommit] = commitment.nextRemoteCommit_opt
+ val nextRemoteCommit_opt: Option[RemoteCommit] = commitment.nextRemoteCommit_opt
val commitmentFormat: CommitmentFormat = commitment.commitmentFormat
val capacity: Satoshi = commitment.fundingAmount
@@ -829,10 +826,10 @@ case class FullCommitment(channelParams: ChannelParams, changes: CommitmentChang
| htlcs:
|${remoteCommit.spec.htlcs.map(h => s" ${h.direction} ${h.add.id} ${h.add.cltvExpiry}").mkString("\n")}
|next remotecommit:
- | toLocal: ${nextRemoteCommit_opt.map(_.commit.spec.toLocal).getOrElse("N/A")}
- | toRemote: ${nextRemoteCommit_opt.map(_.commit.spec.toRemote).getOrElse("N/A")}
+ | toLocal: ${nextRemoteCommit_opt.map(_.spec.toLocal).getOrElse("N/A")}
+ | toRemote: ${nextRemoteCommit_opt.map(_.spec.toRemote).getOrElse("N/A")}
| htlcs:
- |${nextRemoteCommit_opt.map(_.commit.spec.htlcs.map(h => s" ${h.direction} ${h.add.id} ${h.add.cltvExpiry}").mkString("\n")).getOrElse("N/A")}""".stripMargin
+ |${nextRemoteCommit_opt.map(_.spec.htlcs.map(h => s" ${h.direction} ${h.add.id} ${h.add.cltvExpiry}").mkString("\n")).getOrElse("N/A")}""".stripMargin
}
}
@@ -1191,7 +1188,7 @@ case class Commitments(channelParams: ChannelParams,
case _ => true
})
// NB: we are supposed to keep nextRemoteCommit_opt consistent with remoteNextCommitInfo: this should exist.
- val nextRemoteSpec = active.head.nextRemoteCommit_opt.get.commit.spec
+ val nextRemoteSpec = active.head.nextRemoteCommit_opt.get.spec
val remoteSpecWithoutNewHtlcs = nextRemoteSpec.copy(htlcs = nextRemoteSpec.htlcs.filter {
case OutgoingHtlc(add) if receivedHtlcs.contains(add) => false
case _ => true
@@ -1227,7 +1224,7 @@ case class Commitments(channelParams: ChannelParams,
// we remove the newly completed htlcs from the origin map
val originChannels1 = originChannels -- completedOutgoingHtlcs
val active1 = active.map(c => c.copy(
- remoteCommit = c.nextRemoteCommit_opt.get.commit,
+ remoteCommit = c.nextRemoteCommit_opt.get,
nextRemoteCommit_opt = None,
))
val commitments1 = copy(
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala
index 8b7fb20..98e7317 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala
@@ -496,19 +496,19 @@ object Helpers {
def checkRemoteCommit(remoteChannelReestablish: ChannelReestablish, retransmitRevocation_opt: Option[RevokeAndAck]): SyncResult = {
commitments.remoteNextCommitInfo match {
case Left(waitingForRevocation) if remoteChannelReestablish.nextLocalCommitmentNumber == commitments.nextRemoteCommitIndex =>
- // we just sent a new commit_sig but they didn't receive it
- // we resend the same updates and the same sig, and preserve the same ordering
+ // We just sent a new commit_sig but they didn't receive it: we resend the same updates and sign them again,
+ // and preserve the same ordering of messages.
val signedUpdates = commitments.changes.localChanges.signed
- val commitSigs = CommitSigs(commitments.active.flatMap(_.nextRemoteCommit_opt).map { nextRemoteCommit =>
- // If there was a race condition with the remote splice_locked, we may need to adjust the batch size
- // on reconnection: we may have less commit_sig messages to send than before the disconnection.
- val commitSig = nextRemoteCommit.sig
- if (commitments.active.size == 1) {
- commitSig.copy(tlvStream = TlvStream(commitSig.tlvStream.records.filterNot(_.isInstanceOf[CommitSigTlv.BatchTlv])))
- } else {
- commitSig.copy(tlvStream = TlvStream(commitSig.tlvStream.records.filterNot(_.isInstanceOf[CommitSigTlv.BatchTlv]) + CommitSigTlv.BatchTlv(commitments.active.size)))
- }
- })
+ val channelParams = commitments.channelParams
+ val batchSize = commitments.active.size
+ val commitSigs = CommitSigs(commitments.active.flatMap(c => {
+ val commitInput = c.commitInput(c.localFundingKey(channelKeys))
+ val remoteCommitNonce_opt = remoteChannelReestablish.nextCommitNonces.get(c.fundingTxId)
+ // Note that we ignore errors and simply skip failures to sign: we've already signed those updates before
+ // the disconnection, so we don't expect any error here unless our peer sends an invalid nonce. In that
+ // case, we simply won't send back our commit_sig until they fix their node.
+ c.nextRemoteCommit_opt.flatMap(_.sign(channelParams, c.remoteCommitParams, channelKeys, c.fundingTxIndex, c.remoteFundingPubKey, commitInput, c.commitmentFormat, remoteCommitNonce_opt, batchSize).toOption)
+ }))
retransmitRevocation_opt match {
case None =>
SyncResult.Success(retransmit = signedUpdates :+ commitSigs)
@@ -651,7 +651,7 @@ object Helpers {
case _ if closing.remoteCommitPublished.exists(_.isConfirmed) =>
Some(CurrentRemoteClose(closing.commitments.latest.remoteCommit, closing.remoteCommitPublished.get))
case _ if closing.nextRemoteCommitPublished.exists(_.isConfirmed) =>
- Some(NextRemoteClose(closing.commitments.latest.nextRemoteCommit_opt.get.commit, closing.nextRemoteCommitPublished.get))
+ Some(NextRemoteClose(closing.commitments.latest.nextRemoteCommit_opt.get, closing.nextRemoteCommitPublished.get))
case _ if closing.futureRemoteCommitPublished.exists(_.isConfirmed) =>
Some(RecoveryClose(closing.futureRemoteCommitPublished.get))
case _ if closing.revokedCommitPublished.exists(_.isConfirmed) =>
@@ -677,7 +677,7 @@ object Helpers {
case closing: DATA_CLOSING if closing.remoteCommitPublished.exists(_.isDone) =>
Some(CurrentRemoteClose(closing.commitments.latest.remoteCommit, closing.remoteCommitPublished.get))
case closing: DATA_CLOSING if closing.nextRemoteCommitPublished.exists(_.isDone) =>
- Some(NextRemoteClose(closing.commitments.latest.nextRemoteCommit_opt.get.commit, closing.nextRemoteCommitPublished.get))
+ Some(NextRemoteClose(closing.commitments.latest.nextRemoteCommit_opt.get, closing.nextRemoteCommitPublished.get))
case closing: DATA_CLOSING if closing.futureRemoteCommitPublished.exists(_.isDone) =>
Some(RecoveryClose(closing.futureRemoteCommitPublished.get))
case closing: DATA_CLOSING if closing.revokedCommitPublished.exists(_.isDone) =>
@@ -1511,7 +1511,7 @@ object Helpers {
val fromRemote = commitment.remoteCommit.spec.htlcs.collect {
case IncomingHtlc(add) if add.paymentHash == paymentHash => (add, paymentPreimage)
}
- val fromNextRemote = commitment.nextRemoteCommit_opt.map(_.commit.spec.htlcs).getOrElse(Set.empty).collect {
+ val fromNextRemote = commitment.nextRemoteCommit_opt.map(_.spec.htlcs).getOrElse(Set.empty).collect {
case IncomingHtlc(add) if add.paymentHash == paymentHash => (add, paymentPreimage)
}
fromLocal ++ fromRemote ++ fromNextRemote
@@ -1604,7 +1604,7 @@ object Helpers {
def overriddenOutgoingHtlcs(d: DATA_CLOSING, tx: Transaction): Set[UpdateAddHtlc] = {
val localCommit = d.commitments.latest.localCommit
val remoteCommit = d.commitments.latest.remoteCommit
- val nextRemoteCommit_opt = d.commitments.latest.nextRemoteCommit_opt.map(_.commit)
+ val nextRemoteCommit_opt = d.commitments.latest.nextRemoteCommit_opt
// NB: from the p.o.v of remote, their incoming htlcs are our outgoing htlcs.
val outgoingHtlcs = localCommit.spec.htlcs.collect(outgoing) ++ (remoteCommit.spec.htlcs ++ nextRemoteCommit_opt.map(_.spec.htlcs).getOrElse(Set.empty)).collect(incoming)
if (localCommit.txId == tx.txid) {
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala
index 86ddaa3..ccbbabc 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala
@@ -437,7 +437,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
doPublish(rcp, secondStageTransactions, commitment)
})
closing.nextRemoteCommitPublished.foreach(rcp => {
- val remoteCommit = commitment.nextRemoteCommit_opt.get.commit
+ val remoteCommit = commitment.nextRemoteCommit_opt.get
val (_, secondStageTransactions) = Closing.RemoteClose.claimCommitTxOutputs(channelKeys, commitment, remoteCommit, rcp.commitTx, closingFeerate, closing.finalScriptPubKey, nodeParams.onChainFeeConf.spendAnchorWithoutHtlcs)
doPublish(rcp, secondStageTransactions, commitment)
})
@@ -643,7 +643,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
d.commitments.sendCommit(channelKeys, remoteNextCommitNonces) match {
case Right((commitments1, commit)) =>
log.debug("sending a new sig, spec:\n{}", commitments1.latest.specs2String)
- val nextRemoteCommit = commitments1.latest.nextRemoteCommit_opt.get.commit
+ val nextRemoteCommit = commitments1.latest.nextRemoteCommit_opt.get
val nextCommitNumber = nextRemoteCommit.index
// We persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our
// counterparty, so only htlcs above remote's dust_limit matter.
@@ -1663,7 +1663,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
d.commitments.sendCommit(channelKeys, remoteNextCommitNonces) match {
case Right((commitments1, commit)) =>
log.debug("sending a new sig, spec:\n{}", commitments1.latest.specs2String)
- val nextRemoteCommit = commitments1.latest.nextRemoteCommit_opt.get.commit
+ val nextRemoteCommit = commitments1.latest.nextRemoteCommit_opt.get
val nextCommitNumber = nextRemoteCommit.index
// We persist htlc data in order to be able to claim htlc outputs in case a revoked tx is published by our
// counterparty, so only htlcs above remote's dust_limit matter.
@@ -2004,7 +2004,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
})
})
d.nextRemoteCommitPublished.foreach(nrcp => {
- val remoteCommit = commitment.nextRemoteCommit_opt.get.commit
+ val remoteCommit = commitment.nextRemoteCommit_opt.get
val commitKeys = commitment.remoteKeys(channelKeys, remoteCommit.remotePerCommitmentPoint)
Closing.RemoteClose.claimHtlcsWithPreimage(channelKeys, commitKeys, nrcp, commitment, remoteCommit, c.r, d.finalScriptPubKey).foreach(htlcTx => {
txPublisher ! TxPublisher.PublishReplaceableTx(htlcTx, nrcp.commitTx, commitment, Closing.confirmationTarget(htlcTx))
@@ -2015,7 +2015,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
log.info("htlc #{} was failed downstream, recalculating watched htlc outputs", c.id)
val lcp1 = d.localCommitPublished.map(lcp => Closing.LocalClose.ignoreFailedIncomingHtlc(c.id, lcp, commitment))
val rcp1 = d.remoteCommitPublished.map(rcp => Closing.RemoteClose.ignoreFailedIncomingHtlc(c.id, rcp, commitment, commitment.remoteCommit))
- val nrcp1 = d.nextRemoteCommitPublished.map(nrcp => Closing.RemoteClose.ignoreFailedIncomingHtlc(c.id, nrcp, commitment, commitment.nextRemoteCommit_opt.get.commit))
+ val nrcp1 = d.nextRemoteCommitPublished.map(nrcp => Closing.RemoteClose.ignoreFailedIncomingHtlc(c.id, nrcp, commitment, commitment.nextRemoteCommit_opt.get))
d.copy(commitments = commitments1, localCommitPublished = lcp1, remoteCommitPublished = rcp1, nextRemoteCommitPublished = nrcp1)
}
handleCommandSuccess(c, d1) storing()
@@ -2088,7 +2088,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
} else if (tx.txid == d.commitments.latest.remoteCommit.txId) {
// counterparty may attempt to spend its last commit tx at any time
handleRemoteSpentCurrent(tx, d)
- } else if (d.commitments.latest.nextRemoteCommit_opt.exists(_.commit.txId == tx.txid)) {
+ } else if (d.commitments.latest.nextRemoteCommit_opt.exists(_.txId == tx.txid)) {
// counterparty may attempt to spend its last commit tx at any time
handleRemoteSpentNext(tx, d)
} else if (tx.txIn.map(_.outPoint.txid).contains(d.commitments.latest.fundingTxId)) {
@@ -2140,7 +2140,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
spendLocalCurrent(d1, d.maxClosingFeerate_opt)
} else if (commitment.remoteCommit.txId == tx.txid && commitment.remoteCommit.index == d.commitments.remoteCommitIndex) {
handleRemoteSpentCurrent(tx, d1)
- } else if (commitment.nextRemoteCommit_opt.exists(_.commit.txId == tx.txid) && commitment.remoteCommit.index == d.commitments.remoteCommitIndex && d.commitments.remoteNextCommitInfo.isLeft) {
+ } else if (commitment.nextRemoteCommit_opt.exists(_.txId == tx.txid) && commitment.remoteCommit.index == d.commitments.remoteCommitIndex && d.commitments.remoteNextCommitInfo.isLeft) {
handleRemoteSpentNext(tx, d1)
} else {
// Our counterparty is trying to broadcast a revoked commit tx (cheating attempt).
@@ -2154,7 +2154,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
// so the fail command will be a no-op.
val outgoingHtlcs = d.commitments.latest.localCommit.spec.htlcs.collect(DirectedHtlc.outgoing) ++
d.commitments.latest.remoteCommit.spec.htlcs.collect(DirectedHtlc.incoming) ++
- d.commitments.latest.nextRemoteCommit_opt.map(_.commit.spec.htlcs.collect(DirectedHtlc.incoming)).getOrElse(Set.empty)
+ d.commitments.latest.nextRemoteCommit_opt.map(_.spec.htlcs.collect(DirectedHtlc.incoming)).getOrElse(Set.empty)
outgoingHtlcs.foreach { add =>
d.commitments.originChannels.get(add.id) match {
case Some(origin) =>
@@ -2280,7 +2280,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
}
// for our outgoing payments, let's send events if we know that they will settle on chain
Closing
- .onChainOutgoingHtlcs(d.commitments.latest.localCommit, d.commitments.latest.remoteCommit, d.commitments.latest.nextRemoteCommit_opt.map(_.commit), tx)
+ .onChainOutgoingHtlcs(d.commitments.latest.localCommit, d.commitments.latest.remoteCommit, d.commitments.latest.nextRemoteCommit_opt, tx)
.map(add => (add, d.commitments.originChannels.get(add.id).map(_.upstream).collect { case Upstream.Local(id) => id })) // we resolve the payment id if this was a local payment
.collect { case (add, Some(id)) => context.system.eventStream.publish(PaymentSettlingOnChain(id, amount = add.amountMsat, add.paymentHash)) }
// finally, if one of the unilateral closes is done, we move to CLOSED state, otherwise we stay()
@@ -2321,7 +2321,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
} yield PublishReplaceableTx(anchorTx, rcp.commitTx, commitment, c.confirmationTarget)
val nextRemoteAnchor_opt = for {
nrcp <- d.nextRemoteCommitPublished
- commitKeys = commitment.remoteKeys(channelKeys, commitment.nextRemoteCommit_opt.get.commit.remotePerCommitmentPoint)
+ commitKeys = commitment.remoteKeys(channelKeys, commitment.nextRemoteCommit_opt.get.remotePerCommitmentPoint)
anchorTx <- Closing.RemoteClose.claimAnchor(fundingKey, commitKeys, nrcp.commitTx, commitmentFormat)
} yield PublishReplaceableTx(anchorTx, nrcp.commitTx, commitment, c.confirmationTarget)
// We favor the remote commitment(s) because they're more interesting than the local commitment (no CSV delays).
@@ -3039,7 +3039,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
stay()
} else if (tx.txid == d.commitments.latest.remoteCommit.txId) {
handleRemoteSpentCurrent(tx, d)
- } else if (d.commitments.latest.nextRemoteCommit_opt.exists(_.commit.txId == tx.txid)) {
+ } else if (d.commitments.latest.nextRemoteCommit_opt.exists(_.txId == tx.txid)) {
handleRemoteSpentNext(tx, d)
} else if (tx.txid == d.commitments.latest.localCommit.txId) {
log.warning(s"processing local commit spent from the outside")
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala
index 3e8c077..2cd6262 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ErrorHandlers.scala
@@ -290,7 +290,7 @@ trait ErrorHandlers extends CommonHandlers {
val commitment = d.commitments.latest
log.warning(s"they published their next commit in txid=${commitTx.txid}")
require(commitment.nextRemoteCommit_opt.nonEmpty, "next remote commit must be defined")
- val remoteCommit = commitment.nextRemoteCommit_opt.get.commit
+ val remoteCommit = commitment.nextRemoteCommit_opt.get
require(commitTx.txid == remoteCommit.txId, "txid mismatch")
val finalScriptPubKey = getOrGenerateFinalScriptPubKey(d)
val closingFeerate = d match {
@@ -312,7 +312,7 @@ trait ErrorHandlers extends CommonHandlers {
/** Publish 2nd-stage transactions for the remote commitment (no need for 3rd-stage transactions in that case). */
def doPublish(rcp: RemoteCommitPublished, txs: Closing.RemoteClose.SecondStageTransactions, commitment: FullCommitment): Unit = {
val remoteCommit = commitment.nextRemoteCommit_opt match {
- case Some(c) if rcp.commitTx.txid == c.commit.txId => c.commit
+ case Some(commit) if rcp.commitTx.txid == commit.txId => commit
case _ => commitment.remoteCommit
}
val publishAnchorTx_opt = txs.anchorTx_opt match {
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala
index 5c067cd..d6d76d0 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala
@@ -608,7 +608,7 @@ object OriginSerializer extends MinimalSerializer({
// @formatter:off
case class CommitmentJson(fundingTxIndex: Long, fundingInput: OutPoint, fundingAmount: Satoshi, localFunding: LocalFundingStatus, remoteFunding: RemoteFundingStatus, commitmentFormat: String, localCommitParams: CommitParams, localCommit: LocalCommit, remoteCommitParams: CommitParams, remoteCommit: RemoteCommit, nextRemoteCommit: Option[RemoteCommit])
-object CommitmentSerializer extends ConvertClassSerializer[Commitment](c => CommitmentJson(c.fundingTxIndex, c.fundingInput, c.fundingAmount, c.localFundingStatus, c.remoteFundingStatus, c.commitmentFormat.toString, c.localCommitParams, c.localCommit, c.remoteCommitParams, c.remoteCommit, c.nextRemoteCommit_opt.map(_.commit)))
+object CommitmentSerializer extends ConvertClassSerializer[Commitment](c => CommitmentJson(c.fundingTxIndex, c.fundingInput, c.fundingAmount, c.localFundingStatus, c.remoteFundingStatus, c.commitmentFormat.toString, c.localCommitParams, c.localCommit, c.remoteCommitParams, c.remoteCommit, c.nextRemoteCommit_opt))
// @formatter:on
// @formatter:off
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala
index 12c28c2..657e02d 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5.scala
@@ -280,9 +280,18 @@ private[channel] object ChannelCodecs5 {
("txid" | txId) ::
("remotePerCommitmentPoint" | publicKey)).as[RemoteCommit]
- private def nextRemoteCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[NextRemoteCommit] = (
- ("sig" | lengthDelimited(commitSigCodec)) ::
- ("commit" | remoteCommitCodec(commitmentSpecCodec))).as[NextRemoteCommit]
+ // We previously stored our commit_sig for the next remote commit and retransmitted it on reconnection.
+ // For taproot channels, we need to re-sign because the remote nonce may have changed, so we stopped storing those.
+ private case class NextRemoteCommitWithSig(commitSig: ByteVector, commit: RemoteCommit)
+
+ private def nextRemoteCommitWithSigCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[NextRemoteCommitWithSig] = (
+ ("sig" | lengthDelimited(bytes)) ::
+ ("commit" | remoteCommitCodec(commitmentSpecCodec))).as[NextRemoteCommitWithSig]
+
+ private def nextRemoteCommitCodec(commitmentSpecCodec: Codec[CommitmentSpec]): Codec[RemoteCommit] = discriminatorWithDefault(
+ discriminator = discriminated[RemoteCommit].by(varintoverflow).typecase(0L, remoteCommitCodec(commitmentSpecCodec)),
+ fallback = nextRemoteCommitWithSigCodec(commitmentSpecCodec).xmap(c => c.commit, c => NextRemoteCommitWithSig(ByteVector.empty, c))
+ )
private def commitmentCodec(htlcs: Set[DirectedHtlc]): Codec[Commitment] = (
("fundingTxIndex" | uint32) ::
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelTypes5.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelTypes5.scala
index eb61d3b..d23645d 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelTypes5.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelTypes5.scala
@@ -66,7 +66,7 @@ private[channel] object ChannelTypes5 {
val commitmentsSet = commitments.active.head +: commitments.inactive
val htlcs = commitmentsSet.flatMap(_.localCommit.spec.htlcs).toSet ++
commitmentsSet.flatMap(_.remoteCommit.spec.htlcs.map(_.opposite)).toSet ++
- commitmentsSet.flatMap(_.nextRemoteCommit_opt.toList.flatMap(_.commit.spec.htlcs.map(_.opposite))).toSet
+ commitmentsSet.flatMap(_.nextRemoteCommit_opt.toList.flatMap(_.spec.htlcs.map(_.opposite))).toSet
EncodedCommitments(
channelParams = commitments.channelParams,
changes = commitments.changes,
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala
index 94667ba..4d802eb 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala
@@ -345,7 +345,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat
assert(Closing.isClosingTypeAlreadyKnown(
DATA_CLOSING(
commitments = commitments
- .modify(_.active.at(0).nextRemoteCommit_opt).setTo(Some(NextRemoteCommit(null, commitments.active.head.remoteCommit)))
+ .modify(_.active.at(0).nextRemoteCommit_opt).setTo(Some(commitments.active.head.remoteCommit))
.modify(_.remoteNextCommitInfo).setTo(Left(WaitForRev(7))),
waitingSince = BlockHeight(0),
finalScriptPubKey = Script.write(Script.pay2wpkh(randomKey().publicKey)),
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala
index 14c7c04..61accbd 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPublisherSpec.scala
@@ -339,7 +339,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
bob2alice.expectMsgType[RevokeAndAck]
bob2alice.expectMsgType[CommitSig]
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.nextRemoteCommit_opt.nonEmpty)
- val nextRemoteCommitTxId = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.nextRemoteCommit_opt.get.commit.txId
+ val nextRemoteCommitTxId = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.nextRemoteCommit_opt.get.txId
val nextRemoteCommit = bob.signCommitTx()
assert(nextRemoteCommit.txid == nextRemoteCommitTxId)
@@ -487,7 +487,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
if (nextCommit) {
assert(alice.stateData.asInstanceOf[DATA_CLOSING].commitments.latest.nextRemoteCommit_opt.nonEmpty)
- assert(alice.stateData.asInstanceOf[DATA_CLOSING].commitments.latest.nextRemoteCommit_opt.map(_.commit.txId).contains(commitTx.txid))
+ assert(alice.stateData.asInstanceOf[DATA_CLOSING].commitments.latest.nextRemoteCommit_opt.map(_.txId).contains(commitTx.txid))
}
val targetFeerate = FeeratePerKw(3000 sat)
@@ -1028,7 +1028,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
bob2alice.expectMsgType[RevokeAndAck]
bob2alice.expectMsgType[CommitSig]
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.nextRemoteCommit_opt.nonEmpty)
- val nextRemoteCommitTxId = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.nextRemoteCommit_opt.get.commit.txId
+ val nextRemoteCommitTxId = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.nextRemoteCommit_opt.get.txId
// Force-close channel.
probe.send(alice, CMD_FORCECLOSE(probe.ref))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala
index 0891f37..7f5dc70 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala
@@ -268,7 +268,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
awaitCond(bob.stateName == NORMAL)
}
- test("resume htlc settlement", Tag(IgnoreChannelUpdates)) { f =>
+ test("resume htlc settlement", Tag(IgnoreChannelUpdates), Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f =>
import f._
// Successfully send a first payment.
@@ -287,7 +287,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
alice2bob.expectMsgType[CommitSig]
alice2bob.forward(bob)
val revB = bob2alice.expectMsgType[RevokeAndAck]
- bob2alice.expectMsgType[CommitSig]
+ val bobCommitSig1 = bob2alice.expectMsgType[CommitSig]
disconnect(alice, bob)
reconnect(alice, bob, alice2bob, bob2alice)
@@ -299,14 +299,16 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(reestablishB.nextRemoteRevocationNumber == 3)
bob2alice.forward(alice, reestablishB)
- // alice does not re-send messages bob already received
+ // Alice does not re-send messages that Bob already received.
alice2bob.expectNoMessage(100 millis)
alice2bob.forward(bob, reestablishA)
- // bob re-sends its revocation and signature, alice then completes the update
+ // Bob re-sends its revocation and signature, Alice then completes the update.
+ // Note that Bob signs with a fresh nonce instead of retransmitting its previous signature, in case Alice changed her nonce.
bob2alice.expectMsg(revB)
bob2alice.forward(alice)
- bob2alice.expectMsgType[CommitSig]
+ val bobCommitSig2 = bob2alice.expectMsgType[CommitSig]
+ assert(bobCommitSig2.sigOrPartialSig != bobCommitSig1.sigOrPartialSig)
bob2alice.forward(alice)
alice2bob.expectMsgType[RevokeAndAck]
alice2bob.forward(bob)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5Spec.scala
index 759d52f..b149dc0 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5Spec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version5/ChannelCodecs5Spec.scala
@@ -12,10 +12,11 @@ import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningS
import fr.acinq.eclair.transactions.CommitmentSpec
import fr.acinq.eclair.transactions.Transactions.ZeroFeeHtlcTxAnchorOutputsCommitmentFormat
import fr.acinq.eclair.wire.internal.channel.version5.ChannelCodecs5.Codecs.{dualFundingStatusCodec, remoteChannelParamsCodec}
+import fr.acinq.eclair.wire.internal.channel.version5.ChannelCodecs5.channelDataCodec
import fr.acinq.eclair.wire.protocol.{LiquidityAds, TxSignatures}
import fr.acinq.eclair.{CltvExpiryDelta, Features, MilliSatoshiLong, UInt64, randomBytes32, randomKey}
import org.scalatest.funsuite.AnyFunSuite
-import scodec.bits.ByteVector
+import scodec.bits.{ByteVector, HexStringSyntax}
class ChannelCodecs5Spec extends AnyFunSuite {
@@ -68,4 +69,15 @@ class ChannelCodecs5Spec extends AnyFunSuite {
assert(remoteChannelParamsCodec.decodeValue(remoteChannelParamsCodec.encode(remoteParams1).require).require == remoteParams1)
}
+ test("decode next remote commit with local sig") {
+ // We previously included our commit_sig in the next remote commit, which was then removed.
+ val data = hex"000601f43830d0f526556303b25738bde42d88c28efdc91d3919f829beaff30b258c7901010002bbbb671d15145722fb8c28d732cddb249bcc6652ed2b297ff1f77a18371b1e630009e4339e114c4b79062494bccd4c68e42513ee8cc63b1d50101e87ece4caf7442f80000000ff0000000000004e20000000000000140800000000000000000000000000100802aa698202aaaa00ce2f18a967dc4f25f414e671ba6585f8ef0b8c5fb812c21064f55a2eaaff000000000000271003848a1c528b43bbf9d5b41726af470bec7a70e723e019cc66d60f5fd604da46ee038b6665469260317a7916d408aa8183fc7b3cbf3cb221296976e9ba251759f7f802662a75915a93fd734c3dd0fd45aeb0955086a76d2ca9fe663574677abfc9fe1a02d74e2de9a063e435ebfdca09bdb45e38bd3598d2c5f546064d164fcf2355acad0000186b0200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000180802aa6982000000000002fd05b30080f43830d0f526556303b25738bde42d88c28efdc91d3919f829beaff30b258c7900000000000000000000000000989680e39773295a0d4ca41f8d3296f747c62bcb4ab6fb450cc132ecc62bfa4b5e19f200061b1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fe0001a1470107fd05b30080f43830d0f526556303b25738bde42d88c28efdc91d3919f829beaff30b258c7900000000000000010000000000989680e39773295a0d4ca41f8d3296f747c62bcb4ab6fb450cc132ecc62bfa4b5e19f200061b1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fe0001a1470107000000000000000000000000000000020000000000000002000401fd05b1f43830d0f526556303b25738bde42d88c28efdc91d3919f829beaff30b258c7900000000000000000000000000989680e39773295a0d4ca41f8d3296f747c62bcb4ab6fb450cc132ecc62bfa4b5e19f200061b1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fe0001a147010701fd05b1f43830d0f526556303b25738bde42d88c28efdc91d3919f829beaff30b258c7900000000000000010000000000989680e39773295a0d4ca41f8d3296f747c62bcb4ab6fb450cc132ecc62bfa4b5e19f200061b1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fe0001a147010702fd05b1f43830d0f526556303b25738bde42d88c28efdc91d3919f829beaff30b258c7900000000000000000000000000989680e39773295a0d4ca41f8d3296f747c62bcb4ab6fb450cc132ecc62bfa4b5e19f200061b1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fe0001a147010702fd05b1f43830d0f526556303b25738bde42d88c28efdc91d3919f829beaff30b258c7900000000000000010000000000989680e39773295a0d4ca41f8d3296f747c62bcb4ab6fb450cc132ecc62bfa4b5e19f200061b1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fe0001a1470107000100000000000000000000000024f43830d0f526556303b25738bde42d88c28efdc91d3919f829beaff30b258c790000000000000000000f424003aa939f1ba99432524ba89981618690cb137b1fd20b7c00fe594bf8204404ad910400012401010101010101010101010101010101010101010101010101010101010101012a0000002b40420f0000000000220020b75be8b30696f7138c5744ea32dd1eaecad1ba9fb21fbd73ee05fa4ef7795af1061a8000002a00000000020200000000000003e800000000000003e8000000003b9aca00001e009000000000000000010002010000000000000000010000000000000001000009c4000000000bebc200000000002e7ddb00f7a749de90158ec0334c50145e83d443039b1f4cc61c65f5c089ffcae175b44601be0cadd4d166f142a11de8b4e035c2c5a7ceb4a3160196a6e7500551b96b58b22d07281e988fa4dc032197294185e512373e9f27870bd535d4fe2e1610cec80800026ecda215289729225586be698c3386d2abf72854e30c92f06ca4bb525851e9563f996e488934cf2895ac5873b2e295f28299e0795b8d158ca714c43b6488ec6ed68f2055cc2bf023c842ff052ce588023618afcecec7c53858a9e6bd5c9e9b2c4b5cd2559bd7c3df2a2178852c85c1d3cfddc42c1ef9b2ea9d36310d5dae7619000000000000044c0000000000000000000000001dcd6500006402d000000000000000010002020000000000000000020000000000000001000009c4000000002e7ddb00000000000bebc2009ea3b732ba139ec68ea953e89c7f8c27bfb21627fb59fed8729267d24aae6cac03b04d85469db4306e69392111b1f5c7a39aad2303626755c44677712994a4a45afffd0162f43830d0f526556303b25738bde42d88c28efdc91d3919f829beaff30b258c79b1f4f39e8e7f8135319254c2e8c730cdeb4c0319b96e201b2196bcb90a05ab941af4409cd0c5b5146c6407cae9a46729bb8316a6feeb676bbab67e06bd7c76530004b1dd884af8abb418a8dca68e57ef7335c456c778d19e74d99bda5f5c762bbb1a28430d6461a6e5a4bcdaee10f1c1c31ef9ab2b8c7ac56f0520fc527adf58893a58a3e6eda1952b22766bf95c87ba4da9035a7a9f6b3e9d3a2b8ac2500c8c4e1f3355aeea738a9e15b655e60cf8c3ce026e5b291ed9306734a5ffc157bd0221b26fff6c51847f1be721883e4cc06106c6c865b15f187858e9c436b1f8f73d30c4640649a82be3e331f576d821c8ce47c3a4db0ab4e5e686ce4e4696944b8dc560626d6190bc1be9edf4f15a45cde70d6ff2bb95c8cab38d621302e5083dd477b50dd7f19b11893d174219a959ada6dadb720877daf3e811d6b8b0aa9ab99ccc1800000000000000020004020000000000000000020000000000000001010000000000000000010000000000000001000009c4000000002e7ddb00000000000aba950096697cdae6d868d426cb707b6a5317f9d88dd0a36c52733812f30bf9f85ec42f02ce20153b619ef455bc2c17ef261592d07ac330736c411bdf9af30bbb636d7b4e0000000000000000000001000100400000ffffffffffff00207d73dcf750c035b8829d403a3c3920527cf6f3d1b9b14ba683ffe6967786c0ee80007fffffffffff80000200000000000000000001008f963fab2e443aacdd1a4e2759006800000000000000010001008f963fab2e443aacdd1a4e275900680000010067eaf14e46c2abff012e5f30f125b4680088850e1466aca19c286a653a27d75ced3648dc39a8f2b558f3a22f5224896b3c6c177c29f969d35fb33b8260d4b68e5c36990e8f2d00cc7438ccb7b6b0fd92992206226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f0067eaf14e46c2ab68bec30f030100900000000000000000000858b800000014000000001dcd650001000000"
+ val decoded = channelDataCodec.decode(data.bits).require.value.asInstanceOf[DATA_NORMAL]
+ assert(decoded.commitments.remoteNextCommitInfo == Left(WaitForRev(1)))
+ val reEncoded = channelDataCodec.encode(decoded).require.bytes
+ assert(reEncoded.size < data.size) // we don't encode the commit_sig anymore
+ val reDecoded = channelDataCodec.decode(reEncoded.bits).require.value.asInstanceOf[DATA_NORMAL]
+ assert(decoded == reDecoded)
+ }
+
}
Why this scored 60/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.