Reject messages that include the wrong type of signatures (#3368)
What changed, and why it matters
This commit fixes a bug in the Eclair Lightning node where a peer could send a message containing both a correct standard signature and an incorrect partial signature. The old code would verify the correct signature but then store the invalid partial signature for later use. The fix makes Eclair reject messages that include the wrong kind of signature for the channel type, so non-taproot channels ignore partial signatures and taproot channels require partial signatures.
Upgrade Eclair nodes to a version containing this commit. Nodes running older code should be considered at risk of accepting malformed signatures from peers during channel open, commitment signing, and close negotiation. No immediate workaround is described in the commit.
Security signals we found
Type-confusion between IndividualSignature and PartialSignatureWithNonce in channel messages
Invalid partial signature could be stored after valid individual signature was verified
New signatureFor helper enforces commitment-format-aware signature selection
Messages with mismatched signature type now rejected instead of accepted
Tests added for FundingCreated, FundingSigned, CommitSig, and ClosingSig wrong-signature-type cases
Evidence from the diff
The patch changes signature handling in FundingCreated, FundingSigned, CommitSig, and ClosingSig messages. Previously these messages exposed a sigOrPartialSig field that returned whichever signature was present, allowing a non-taproot channel to accept a valid individual signature while also carrying an invalid partial_signature_with_nonce TLV. The new signatureFor(commitmentFormat) method returns None when the included signature type does not match the channel’s CommitmentFormat, and callers now reject the message with InvalidCommitmentSignature or an Error. ClosingSig handling is also restricted to the signature type matching the commitment format. Tests are added for each affected message type.
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/ChannelOpenSingleFunded.scalaeclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scalaeclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scalaInspect captured patch +179 / −78
### eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala
@@ -174,17 +174,21 @@ object LocalCommit {
commit: CommitSig, localCommitIndex: Long, spec: CommitmentSpec, commitmentFormat: CommitmentFormat)(implicit log: LoggingAdapter): Either[ChannelException, LocalCommit] = {
val (localCommitTx, htlcTxs) = Commitment.makeLocalTxs(channelParams, commitParams, commitKeys, localCommitIndex, fundingKey, remoteFundingPubKey, commitInput, commitmentFormat, spec)
log.info(s"built local commit number=$localCommitIndex toLocalMsat=${spec.toLocal.toLong} toRemoteMsat=${spec.toRemote.toLong} htlc_in={} htlc_out={} feeratePerKw=${spec.commitTxFeerate} txid=${localCommitTx.tx.txid} fundingTxId=$fundingTxId", spec.htlcs.collect(DirectedHtlc.incoming).map(_.id).mkString(","), spec.htlcs.collect(DirectedHtlc.outgoing).map(_.id).mkString(","))
- val remoteCommitSigOk = commitmentFormat match {
- case _: SegwitV0CommitmentFormat => localCommitTx.checkRemoteSig(fundingKey.publicKey, remoteFundingPubKey, commit.signature)
- case _: SimpleTaprootChannelCommitmentFormat => commit.sigOrPartialSig match {
- case _: IndividualSignature => false
- case remoteSig: PartialSignatureWithNonce =>
- val localNonce = NonceGenerator.verificationNonce(fundingTxId, fundingKey, remoteFundingPubKey, localCommitIndex)
- localCommitTx.checkRemotePartialSignature(fundingKey.publicKey, remoteFundingPubKey, remoteSig, localNonce.publicNonce)
- }
- }
- if (!remoteCommitSigOk) {
- return Left(InvalidCommitmentSignature(channelParams.channelId, fundingTxId, localCommitIndex, localCommitTx.tx))
+ val validRemoteCommitSig: ChannelSpendSignature = commit.signatureFor(commitmentFormat) match {
+ case Some(individualSig: IndividualSignature) =>
+ if (!localCommitTx.checkRemoteSig(fundingKey.publicKey, remoteFundingPubKey, individualSig)) {
+ return Left(InvalidCommitmentSignature(channelParams.channelId, fundingTxId, localCommitIndex, localCommitTx.tx))
+ } else {
+ individualSig
+ }
+ case Some(partialSig: PartialSignatureWithNonce) =>
+ val localNonce = NonceGenerator.verificationNonce(fundingTxId, fundingKey, remoteFundingPubKey, localCommitIndex)
+ if (!localCommitTx.checkRemotePartialSignature(fundingKey.publicKey, remoteFundingPubKey, partialSig, localNonce.publicNonce)) {
+ return Left(InvalidCommitmentSignature(channelParams.channelId, fundingTxId, localCommitIndex, localCommitTx.tx))
+ } else {
+ partialSig
+ }
+ case _ => return Left(InvalidCommitmentSignature(channelParams.channelId, fundingTxId, localCommitIndex, localCommitTx.tx))
}
val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index)
if (commit.htlcSignatures.size != sortedHtlcTxs.size) {
@@ -197,7 +201,7 @@ object LocalCommit {
}
remoteSig
}
- Right(LocalCommit(localCommitIndex, spec, localCommitTx.tx.txid, commit.sigOrPartialSig, htlcRemoteSigs))
+ Right(LocalCommit(localCommitIndex, spec, localCommitTx.tx.txid, validRemoteCommitSig, htlcRemoteSigs))
}
}
### eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala
@@ -920,14 +920,19 @@ object Helpers {
* for their next closing_sig that will match our latest closing_complete.
*/
def receiveSimpleClosingSig(channelKeys: ChannelKeys, commitment: FullCommitment, closingTxs: ClosingTxs, closingSig: ClosingSig, localClosingComplete_opt: Option[ClosingComplete], remoteNonce_opt: Option[IndividualNonce]): Either[ChannelException, ClosingTx] = {
- val closingTxsWithSig = Seq(
- closingSig.closerAndCloseeOutputsSig_opt.flatMap(sig => closingTxs.localAndRemote_opt.map(tx => (tx, IndividualSignature(sig)))),
- closingSig.closerAndCloseeOutputsPartialSig_opt.flatMap(sig => remoteNonce_opt.flatMap(nonce => closingTxs.localAndRemote_opt.map(tx => (tx, PartialSignatureWithNonce(sig, nonce))))),
- closingSig.closerOutputOnlySig_opt.flatMap(sig => closingTxs.localOnly_opt.map(tx => (tx, IndividualSignature(sig)))),
- closingSig.closerOutputOnlyPartialSig_opt.flatMap(sig => remoteNonce_opt.flatMap(nonce => closingTxs.localOnly_opt.map(tx => (tx, PartialSignatureWithNonce(sig, nonce))))),
- closingSig.closeeOutputOnlySig_opt.flatMap(sig => closingTxs.remoteOnly_opt.map(tx => (tx, IndividualSignature(sig)))),
- closingSig.closeeOutputOnlyPartialSig_opt.flatMap(sig => remoteNonce_opt.flatMap(nonce => closingTxs.remoteOnly_opt.map(tx => (tx, PartialSignatureWithNonce(sig, nonce)))))
- ).flatten
+ // We only consider the type of signature that matches our commitment format
+ val closingTxsWithSig: Seq[(ClosingTx, ChannelSpendSignature)] = commitment.commitmentFormat match {
+ case _: SegwitV0CommitmentFormat => Seq(
+ closingSig.closerAndCloseeOutputsSig_opt.flatMap(sig => closingTxs.localAndRemote_opt.map(tx => (tx, IndividualSignature(sig)))),
+ closingSig.closerOutputOnlySig_opt.flatMap(sig => closingTxs.localOnly_opt.map(tx => (tx, IndividualSignature(sig)))),
+ closingSig.closeeOutputOnlySig_opt.flatMap(sig => closingTxs.remoteOnly_opt.map(tx => (tx, IndividualSignature(sig)))),
+ ).flatten
+ case _: SimpleTaprootChannelCommitmentFormat => Seq(
+ closingSig.closerAndCloseeOutputsPartialSig_opt.flatMap(sig => remoteNonce_opt.flatMap(nonce => closingTxs.localAndRemote_opt.map(tx => (tx, PartialSignatureWithNonce(sig, nonce))))),
+ closingSig.closerOutputOnlyPartialSig_opt.flatMap(sig => remoteNonce_opt.flatMap(nonce => closingTxs.localOnly_opt.map(tx => (tx, PartialSignatureWithNonce(sig, nonce))))),
+ closingSig.closeeOutputOnlyPartialSig_opt.flatMap(sig => remoteNonce_opt.flatMap(nonce => closingTxs.remoteOnly_opt.map(tx => (tx, PartialSignatureWithNonce(sig, nonce))))),
+ ).flatten
+ }
closingTxsWithSig.headOption match {
case Some((closingTx, remoteSig)) =>
val localFundingKey = channelKeys.fundingKey(commitment.fundingTxIndex)
### eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala
@@ -289,16 +289,17 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers {
case Left(ex) => handleLocalError(ex, d, Some(fc))
case Right((localSpec, localCommitTx, remoteSpec, remoteCommitTx)) =>
// check remote signature validity
- val isRemoteSigValid = fc.sigOrPartialSig match {
- case psig: PartialSignatureWithNonce =>
+ val validRemoteSig_opt: Option[ChannelSpendSignature] = fc.signatureFor(d.commitmentFormat) match {
+ case Some(individualSig: IndividualSignature) =>
+ if (!localCommitTx.checkRemoteSig(fundingKey.publicKey, d.remoteFundingPubKey, individualSig)) None else Some(individualSig)
+ case Some(partialSig: PartialSignatureWithNonce) =>
val localNonce = NonceGenerator.verificationNonce(NonceGenerator.dummyFundingTxId, fundingKey, NonceGenerator.dummyRemoteFundingPubKey, 0)
- localCommitTx.checkRemotePartialSignature(fundingKey.publicKey, d.remoteFundingPubKey, psig, localNonce.publicNonce)
- case sig: IndividualSignature =>
- localCommitTx.checkRemoteSig(fundingKey.publicKey, d.remoteFundingPubKey, sig)
+ if (!localCommitTx.checkRemotePartialSignature(fundingKey.publicKey, d.remoteFundingPubKey, partialSig, localNonce.publicNonce)) None else Some(partialSig)
+ case _ => None
}
- isRemoteSigValid match {
- case false => handleLocalError(InvalidCommitmentSignature(temporaryChannelId, fundingTxId, commitmentNumber = 0, localCommitTx.tx), d, Some(fc))
- case true =>
+ validRemoteSig_opt match {
+ case None => handleLocalError(InvalidCommitmentSignature(temporaryChannelId, fundingTxId, commitmentNumber = 0, localCommitTx.tx), d, Some(fc))
+ case Some(validRemoteSig) =>
val channelId = toLongId(fundingTxId, fundingTxOutputIndex)
val localSigOfRemoteTx = d.commitmentFormat match {
case _: SimpleTaprootChannelCommitmentFormat =>
@@ -327,7 +328,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers {
remoteFundingStatus = RemoteFundingStatus.NotLocked,
commitmentFormat = d.commitmentFormat,
localCommitParams = d.localCommitParams,
- localCommit = LocalCommit(0, localSpec, localCommitTx.tx.txid, fc.sigOrPartialSig, htlcRemoteSigs = Nil),
+ localCommit = LocalCommit(0, localSpec, localCommitTx.tx.txid, validRemoteSig, htlcRemoteSigs = Nil),
remoteCommitParams = d.remoteCommitParams,
remoteCommit = RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, d.remoteFirstPerCommitmentPoint),
nextRemoteCommit_opt = None)
@@ -377,20 +378,21 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers {
case Event(fundingSigned: FundingSigned, d: DATA_WAIT_FOR_FUNDING_SIGNED) =>
// we make sure that their sig checks out and that our first commit tx is spendable
val fundingKey = channelKeys.fundingKey(fundingTxIndex = 0)
- val isRemoteSigValid = fundingSigned.sigOrPartialSig match {
- case psig: PartialSignatureWithNonce =>
+ val validRemoteSig_opt: Option[ChannelSpendSignature] = fundingSigned.signatureFor(d.commitmentFormat) match {
+ case Some(individualSig: IndividualSignature) =>
+ if (!d.localCommitTx.checkRemoteSig(fundingKey.publicKey, d.remoteFundingPubKey, individualSig)) None else Some(individualSig)
+ case Some(partialSig: PartialSignatureWithNonce) =>
val localNonce = NonceGenerator.verificationNonce(NonceGenerator.dummyFundingTxId, fundingKey, NonceGenerator.dummyRemoteFundingPubKey, 0)
- d.localCommitTx.checkRemotePartialSignature(fundingKey.publicKey, d.remoteFundingPubKey, psig, localNonce.publicNonce)
- case sig: IndividualSignature =>
- d.localCommitTx.checkRemoteSig(fundingKey.publicKey, d.remoteFundingPubKey, sig)
+ if (!d.localCommitTx.checkRemotePartialSignature(fundingKey.publicKey, d.remoteFundingPubKey, partialSig, localNonce.publicNonce)) None else Some(partialSig)
+ case _ => None
}
- isRemoteSigValid match {
- case false =>
+ validRemoteSig_opt match {
+ case None =>
// we rollback the funding tx, it will never be published
wallet.rollback(d.fundingTx)
d.replyTo ! OpenChannelResponse.Rejected("invalid commit signatures")
handleLocalError(InvalidCommitmentSignature(d.channelId, d.fundingTx.txid, commitmentNumber = 0, d.localCommitTx.tx), d, Some(fundingSigned))
- case true =>
+ case Some(validRemoteSig) =>
val commitment = Commitment(
fundingTxIndex = 0,
firstRemoteCommitIndex = 0,
@@ -401,7 +403,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers {
remoteFundingStatus = RemoteFundingStatus.NotLocked,
commitmentFormat = d.commitmentFormat,
localCommitParams = d.localCommitParams,
- localCommit = LocalCommit(0, d.localSpec, d.localCommitTx.tx.txid, fundingSigned.sigOrPartialSig, htlcRemoteSigs = Nil),
+ localCommit = LocalCommit(0, d.localSpec, d.localCommitTx.tx.txid, validRemoteSig, htlcRemoteSigs = Nil),
remoteCommitParams = d.remoteCommitParams,
remoteCommit = d.remoteCommit,
nextRemoteCommit_opt = None
### eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala
@@ -151,12 +151,12 @@ object LightningMessageCodecs {
("temporaryChannelId" | bytes32) ::
("fundingTxHash" | txIdAsHash) ::
("fundingOutputIndex" | uint16) ::
- ("signature" | bytes64) ::
+ ("signature" | bytes64.as[ChannelSpendSignature.IndividualSignature]) ::
("tlvStream" | FundingCreatedTlv.fundingCreatedTlvCodec)).as[FundingCreated]
val fundingSignedCodec: Codec[FundingSigned] = (
("channelId" | bytes32) ::
- ("signature" | bytes64) ::
+ ("signature" | bytes64.as[ChannelSpendSignature.IndividualSignature]) ::
("tlvStream" | FundingSignedTlv.fundingSignedTlvCodec)).as[FundingSigned]
val channelReadyCodec: Codec[ChannelReady] = (
### eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala
@@ -25,7 +25,7 @@ import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.channel.ChannelSpendSignature.{IndividualSignature, PartialSignatureWithNonce}
import fr.acinq.eclair.channel.{ChannelFlags, ChannelSpendSignature, ChannelType}
import fr.acinq.eclair.payment.relay.Relayer
-import fr.acinq.eclair.transactions.Transactions.InputInfo
+import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, InputInfo, SegwitV0CommitmentFormat, TaprootCommitmentFormat}
import fr.acinq.eclair.wire.protocol.ChannelReadyTlv.ShortChannelIdTlv
import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, Feature, Features, InitFeature, MilliSatoshi, MilliSatoshiLong, RealShortChannelId, ShortChannelId, TimestampSecond, UInt64, isAsciiPrintable}
import scodec.bits.ByteVector
@@ -338,16 +338,19 @@ case class AcceptDualFundedChannel(temporaryChannelId: ByteVector32,
case class FundingCreated(temporaryChannelId: ByteVector32,
fundingTxId: TxId,
fundingOutputIndex: Int,
- signature: ByteVector64,
+ signature: IndividualSignature,
tlvStream: TlvStream[FundingCreatedTlv] = TlvStream.empty) extends ChannelMessage with HasTemporaryChannelId {
- val sigOrPartialSig: ChannelSpendSignature = tlvStream.get[ChannelTlv.PartialSignatureWithNonceTlv].map(_.partialSigWithNonce).getOrElse(IndividualSignature(signature))
-}
+ val partialSignature_opt: Option[ChannelSpendSignature.PartialSignatureWithNonce] = tlvStream.get[ChannelTlv.PartialSignatureWithNonceTlv].map(_.partialSigWithNonce)
+ def signatureFor(commitmentFormat: CommitmentFormat): Option[ChannelSpendSignature] = commitmentFormat match {
+ case _: SegwitV0CommitmentFormat => if (partialSignature_opt.isDefined) None else Some(signature)
+ case _: TaprootCommitmentFormat => partialSignature_opt
+ }}
object FundingCreated {
def apply(temporaryChannelId: ByteVector32, fundingTxId: TxId, fundingOutputIndex: Int, sig: ChannelSpendSignature): FundingCreated = {
val individualSig = sig match {
- case IndividualSignature(sig) => sig
- case _: PartialSignatureWithNonce => ByteVector64.Zeroes
+ case sig: IndividualSignature => sig
+ case _: PartialSignatureWithNonce => IndividualSignature(ByteVector64.Zeroes)
}
val tlvs = sig match {
case _: IndividualSignature => TlvStream.empty[FundingCreatedTlv]
@@ -358,16 +361,20 @@ object FundingCreated {
}
case class FundingSigned(channelId: ByteVector32,
- signature: ByteVector64,
+ signature: IndividualSignature,
tlvStream: TlvStream[FundingSignedTlv] = TlvStream.empty) extends ChannelMessage with HasChannelId {
- val sigOrPartialSig: ChannelSpendSignature = tlvStream.get[ChannelTlv.PartialSignatureWithNonceTlv].map(_.partialSigWithNonce).getOrElse(IndividualSignature(signature))
+ val partialSignature_opt: Option[PartialSignatureWithNonce] = tlvStream.get[ChannelTlv.PartialSignatureWithNonceTlv].map(_.partialSigWithNonce)
+ def signatureFor(commitmentFormat: CommitmentFormat): Option[ChannelSpendSignature] = commitmentFormat match {
+ case _: SegwitV0CommitmentFormat => if (partialSignature_opt.isDefined) None else Some(signature)
+ case _: TaprootCommitmentFormat => partialSignature_opt
+ }
}
object FundingSigned {
def apply(channelId: ByteVector32, sig: ChannelSpendSignature): FundingSigned = {
val individualSig = sig match {
- case IndividualSignature(sig) => sig
- case _: PartialSignatureWithNonce => ByteVector64.Zeroes
+ case sig: IndividualSignature => sig
+ case _: PartialSignatureWithNonce => IndividualSignature(ByteVector64.Zeroes)
}
val tlvs = sig match {
case _: IndividualSignature => TlvStream.empty[FundingSignedTlv]
@@ -612,7 +619,10 @@ case class CommitSig(channelId: ByteVector32,
tlvStream: TlvStream[CommitSigTlv] = TlvStream.empty) extends CommitSigs {
val fundingTxId_opt: Option[TxId] = tlvStream.get[CommitSigTlv.FundingTx].map(_.txId)
val partialSignature_opt: Option[PartialSignatureWithNonce] = tlvStream.get[CommitSigTlv.PartialSignatureWithNonceTlv].map(_.partialSigWithNonce)
- val sigOrPartialSig: ChannelSpendSignature = partialSignature_opt.getOrElse(signature)
+ def signatureFor(commitmentFormat: CommitmentFormat): Option[ChannelSpendSignature] = commitmentFormat match {
+ case _: SegwitV0CommitmentFormat => if (partialSignature_opt.isDefined) None else Some(signature)
+ case _: TaprootCommitmentFormat => partialSignature_opt
+ }
}
object CommitSig {
### eclair-core/src/test/resources/nonreg/codecs/050001-DATA_WAIT_FOR_FUNDING_CONFIRMED/fundee/data.json
@@ -135,7 +135,9 @@
"waitingSince" : 400000,
"lastSent" : {
"channelId" : "54e64a242e3ddebf0ae861177c9527cab2590753ff06e083447058c14e52df16",
- "signature" : "31b9111f33d0905f65b7b2f8fc879af706334c78c0e38b981717ef28674cade56b0cd9af2269e139a087cb1ffbca9db5366f69b9aff2b801df0b1ea1017d32b7",
+ "signature" : {
+ "sig" : "31b9111f33d0905f65b7b2f8fc879af706334c78c0e38b981717ef28674cade56b0cd9af2269e139a087cb1ffbca9db5366f69b9aff2b801df0b1ea1017d32b7"
+ },
"tlvStream" : { }
}
}
\ No newline at end of file
### eclair-core/src/test/resources/nonreg/codecs/050001-DATA_WAIT_FOR_FUNDING_CONFIRMED/funder/data.json
@@ -138,7 +138,9 @@
"temporaryChannelId" : "0000000000000000000000000000000000000000000000000000000000000000",
"fundingTxId" : "16df524ec158704483e006ff530759b2ca27957c1761e80abfde3d2e244ae654",
"fundingOutputIndex" : 0,
- "signature" : "2510bfd05abddacfe165981318528c0ef7a5ab69f2a9146068550ebbc629eb906ec81eb888aac11575f8a58f522d79f5b36c85ecbbe9ec6a9379c5f257370e76",
+ "signature" : {
+ "sig" : "2510bfd05abddacfe165981318528c0ef7a5ab69f2a9146068550ebbc629eb906ec81eb888aac11575f8a58f522d79f5b36c85ecbbe9ec6a9379c5f257370e76"
+ },
"tlvStream" : { }
}
}
\ No newline at end of file
### eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala
@@ -524,9 +524,9 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit
// Bob sends signatures first as he did not contribute at all.
val successA = alice2bob.expectMsgType[Succeeded]
- assert(successA.commitSig.sigOrPartialSig.isInstanceOf[PartialSignatureWithNonce])
+ assert(successA.commitSig.partialSignature_opt.isDefined)
val successB = bob2alice.expectMsgType[Succeeded]
- assert(successB.commitSig.sigOrPartialSig.isInstanceOf[PartialSignatureWithNonce])
+ assert(successB.commitSig.partialSignature_opt.isDefined)
val (txA, _, txB, _) = fixtureParams.exchangeSigsBobFirst(bobParams, successA, successB)
assert(successA.nextRemoteCommitNonce_opt.contains((txA.txId, txCompleteB3.commitNonces_opt.get.nextCommitNonce)))
assert(successB.nextRemoteCommitNonce_opt.contains((txB.txId, txCompleteA.commitNonces_opt.get.nextCommitNonce)))
@@ -1188,11 +1188,11 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit
val successA2 = alice2bob.expectMsgType[Succeeded]
assert(successA2.signingSession.fundingTx.localSigs.previousFundingTxSig_opt.nonEmpty)
assert(successA2.signingSession.fundingTx.localSigs.previousFundingTxPartialSig_opt.isEmpty)
- assert(successA2.commitSig.sigOrPartialSig.isInstanceOf[PartialSignatureWithNonce])
+ assert(successA2.commitSig.partialSignature_opt.isDefined)
val successB2 = bob2alice.expectMsgType[Succeeded]
assert(successB2.signingSession.fundingTx.localSigs.previousFundingTxSig_opt.nonEmpty)
assert(successB2.signingSession.fundingTx.localSigs.previousFundingTxPartialSig_opt.isEmpty)
- assert(successB2.commitSig.sigOrPartialSig.isInstanceOf[PartialSignatureWithNonce])
+ assert(successB2.commitSig.partialSignature_opt.isDefined)
val (spliceTxA, commitmentA2, spliceTxB, commitmentB2) = fixtureParams.exchangeSigsBobFirst(spliceFixtureParams.fundingParamsB, successA2, successB2)
assert(successA2.nextRemoteCommitNonce_opt.contains((spliceTxA.txId, txCompleteB.commitNonces_opt.get.nextCommitNonce)))
assert(successB2.nextRemoteCommitNonce_opt.contains((spliceTxB.txId, txCompleteA.commitNonces_opt.get.nextCommitNonce)))
### eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingSignedStateSpec.scala
@@ -173,10 +173,10 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
alice.underlyingActor.context.system.eventStream.subscribe(listener.ref, classOf[TransactionPublished])
val commitSigB = bob2alice.expectMsgType[CommitSig]
- assert(commitSigB.sigOrPartialSig.isInstanceOf[PartialSignatureWithNonce])
+ assert(commitSigB.partialSignature_opt.isDefined)
bob2alice.forward(alice, commitSigB)
val commitSigA = alice2bob.expectMsgType[CommitSig]
- assert(commitSigA.sigOrPartialSig.isInstanceOf[PartialSignatureWithNonce])
+ assert(commitSigA.partialSignature_opt.isDefined)
alice2bob.forward(bob, commitSigA)
val expectedBalanceAlice = TestConstants.fundingSatoshis.toMilliSatoshi + TestConstants.nonInitiatorPushAmount - TestConstants.initiatorPushAmount
### eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala
@@ -18,15 +18,17 @@ package fr.acinq.eclair.channel.states.b
import akka.actor.ActorRef
import akka.testkit.{TestFSMRef, TestProbe}
+import fr.acinq.bitcoin.scalacompat.Musig2.IndividualNonce
import fr.acinq.bitcoin.scalacompat.{ByteVector32, SatoshiLong}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
+import fr.acinq.eclair.channel.ChannelSpendSignature.PartialSignatureWithNonce
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.publish.TxPublisher
import fr.acinq.eclair.channel.states.ChannelStateTestsBase
import fr.acinq.eclair.wire.protocol._
-import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion, toLongId}
+import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion, randomBytes, randomBytes32, toLongId}
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import org.scalatest.{Outcome, Tag}
@@ -116,6 +118,17 @@ class WaitForFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFun
awaitCond(bob.stateName == CLOSED)
}
+ test("recv invalid FundingCreated (non-taproot channels, but includes a partial signature)") { f =>
+ import f._
+ val fundingCreated = alice2bob.expectMsgType[FundingCreated]
+ // Alice's signature is genuine, but she also attaches an unsolicited partial_signature_with_nonce TLV
+ // filled with arbitrary bytes, on a channel that does not use taproot.
+ val poisonSig = PartialSignatureWithNonce(randomBytes32(), IndividualNonce(randomBytes(66)))
+ val poisoned = fundingCreated.copy(tlvStream = TlvStream(fundingCreated.tlvStream.records + ChannelTlv.PartialSignatureWithNonceTlv(poisonSig)))
+ alice2bob.forward(bob, poisoned)
+ bob2alice.expectMsgType[Error]
+ }
+
test("recv Error") { f =>
import f._
bob ! Error(ByteVector32.Zeroes, "oops")
### eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala
@@ -17,21 +17,22 @@
package fr.acinq.eclair.channel.states.b
import akka.testkit.{TestFSMRef, TestProbe}
+import fr.acinq.bitcoin.scalacompat.Musig2.IndividualNonce
import fr.acinq.bitcoin.scalacompat.{Btc, ByteVector32, ByteVector64, SatoshiLong}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair.TestUtils.randomTxId
import fr.acinq.eclair.blockchain.SingleKeyOnChainWallet
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
-import fr.acinq.eclair.channel.ChannelSpendSignature.PartialSignatureWithNonce
+import fr.acinq.eclair.channel.ChannelSpendSignature.{IndividualSignature, PartialSignatureWithNonce}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel
import fr.acinq.eclair.channel.fsm.Channel.TickChannelOpenTimeout
import fr.acinq.eclair.channel.publish.TxPublisher
import fr.acinq.eclair.channel.states.{ChannelStateTestsBase, ChannelStateTestsTags}
import fr.acinq.eclair.crypto.NonceGenerator
import fr.acinq.eclair.io.Peer.OpenChannelResponse
-import fr.acinq.eclair.wire.protocol.{AcceptChannel, Error, FundingCreated, FundingSigned, OpenChannel}
-import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestConstants, TestKitBaseClass, randomBytes32, randomKey}
+import fr.acinq.eclair.wire.protocol.{AcceptChannel, ChannelTlv, Error, FundingCreated, FundingSigned, OpenChannel, TlvStream}
+import fr.acinq.eclair.{BlockHeight, MilliSatoshiLong, TestConstants, TestKitBaseClass, randomBytes, randomBytes32, randomKey}
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import org.scalatest.{Outcome, Tag}
@@ -112,7 +113,7 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS
val listener = TestProbe()
alice.underlying.system.eventStream.subscribe(listener.ref, classOf[TransactionPublished])
val fundingSigned = bob2alice.expectMsgType[FundingSigned]
- assert(fundingSigned.sigOrPartialSig.isInstanceOf[PartialSignatureWithNonce])
+ assert(fundingSigned.partialSignature_opt.isDefined)
bob2alice.forward(alice, fundingSigned)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)
val watchConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed]
@@ -137,7 +138,7 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS
test("recv FundingSigned with invalid signature") { f =>
import f._
// sending an invalid sig
- alice ! FundingSigned(ByteVector32.Zeroes, ByteVector64.Zeroes)
+ alice ! FundingSigned(ByteVector32.Zeroes, IndividualSignature(ByteVector64.Zeroes))
awaitCond(alice.stateName == CLOSED)
alice2bob.expectMsgType[Error]
aliceOpenReplyTo.expectMsgType[OpenChannelResponse.Rejected]
@@ -154,6 +155,18 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS
listener.expectMsgType[ChannelAborted]
}
+ test("recv invalid FundingSigned (non-taproot channels, but includes a partial signature)") { f =>
+ import f._
+ val fundingSigned = bob2alice.expectMsgType[FundingSigned]
+
+ // Alice's signature is genuine, but she also attaches an unsolicited partial_signature_with_nonce TLV
+ // filled with arbitrary bytes, on a channel that does not use taproot.
+ val poisonSig = PartialSignatureWithNonce(randomBytes32(), IndividualNonce(randomBytes(66)))
+ val poisoned = fundingSigned.copy(tlvStream = TlvStream(fundingSigned.tlvStream.records + ChannelTlv.PartialSignatureWithNonceTlv(poisonSig)))
+ bob2alice.forward(alice, poisoned)
+ alice2bob.expectMsgType[Error]
+ }
+
test("recv FundingSigned (channel already exists)") { f =>
import f._
val fundingSigned = bob2alice.expectMsgType[FundingSigned]
### eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala
@@ -21,13 +21,14 @@ import akka.testkit.TestProbe
import com.softwaremill.quicklens.ModifyPimp
import fr.acinq.bitcoin.ScriptFlags
import fr.acinq.bitcoin.scalacompat.Crypto.PrivateKey
+import fr.acinq.bitcoin.scalacompat.Musig2.IndividualNonce
import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, SatoshiLong, Script, Transaction, TxOut}
import fr.acinq.eclair.TestConstants.{Alice, Bob}
import fr.acinq.eclair._
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
import fr.acinq.eclair.blockchain.fee._
import fr.acinq.eclair.blockchain.{CurrentBlockHeight, CurrentFeerates}
-import fr.acinq.eclair.channel.ChannelSpendSignature.IndividualSignature
+import fr.acinq.eclair.channel.ChannelSpendSignature.{IndividualSignature, PartialSignatureWithNonce}
import fr.acinq.eclair.channel._
import fr.acinq.eclair.channel.fsm.Channel._
import fr.acinq.eclair.channel.publish.TxPublisher.{PublishFinalTx, PublishReplaceableTx}
@@ -43,6 +44,7 @@ import fr.acinq.eclair.testutils.PimpTestProbe.convert
import fr.acinq.eclair.transactions.DirectedHtlc.{incoming, outgoing}
import fr.acinq.eclair.transactions.Transactions
import fr.acinq.eclair.transactions.Transactions._
+import fr.acinq.eclair.wire.protocol.LightningMessageCodecs.lightningMessageCodec
import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ChannelReestablish, ChannelReestablishTlv, ChannelUpdate, ClosingSigned, CommitSig, CommitSigTlv, Error, FailureMessageCodecs, FailureReason, Init, PermanentChannelFailure, RevokeAndAck, RevokeAndAckTlv, Shutdown, TemporaryNodeFailure, TlvStream, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFee, UpdateFulfillHtlc, UpdateFulfillHtlcTlv, Warning}
import org.scalatest.Inside.inside
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
@@ -1060,6 +1062,22 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.toLocal == initialState.commitments.latest.localCommit.spec.toLocal)
}
+ test("recv invalid CommitSig (non-taproot channels, but includes a partial signature)") { f =>
+ import f._
+ addHtlc(50000000 msat, alice, bob, alice2bob, bob2alice)
+ alice ! CMD_SIGN()
+ val commitSig = alice2bob.expectMsgType[CommitSig]
+ // Alice's signature is genuine, but she also attaches an unsolicited partial_signature_with_nonce TLV
+ // filled with arbitrary bytes, on a channel that does not use taproot.
+ val poisonSig = PartialSignatureWithNonce(randomBytes32(), IndividualNonce(randomBytes(66)))
+ val poisoned = commitSig.copy(tlvStream = TlvStream(commitSig.tlvStream.records + CommitSigTlv.PartialSignatureWithNonceTlv(poisonSig)))
+ // The poisoned message is valid on the wire.
+ val bin = lightningMessageCodec.encode(poisoned).require
+ assert(lightningMessageCodec.decode(bin).require.value == poisoned)
+ alice2bob.forward(bob, poisoned)
+ bob2alice.expectMsgType[Error]
+ }
+
def testRecvCommitSigMultipleHtlcs(f: FixtureParam): Unit = {
import f._
### eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala
@@ -313,7 +313,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
bob2alice.expectMsg(revB)
bob2alice.forward(alice)
val bobCommitSig2 = bob2alice.expectMsgType[CommitSig]
- assert(bobCommitSig2.sigOrPartialSig != bobCommitSig1.sigOrPartialSig)
+ assert(bobCommitSig2.partialSignature_opt.get != bobCommitSig1.partialSignature_opt.get)
bob2alice.forward(alice)
alice2bob.expectMsgType[RevokeAndAck]
alice2bob.forward(bob)
### eclair-core/src/test/scala/fr/acinq/eclair/channel/states/g/NegotiatingStateSpec.scala
@@ -32,7 +32,7 @@ import fr.acinq.eclair.transactions.Transactions
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.wire.protocol.ClosingSignedTlv.FeeRange
import fr.acinq.eclair.wire.protocol.{AnnouncementSignatures, ChannelUpdate, ClosingComplete, ClosingCompleteTlv, ClosingSig, ClosingSigTlv, ClosingSigned, ClosingTlv, Error, Shutdown, TlvStream, Warning}
-import fr.acinq.eclair.{BlockHeight, CltvExpiry, Features, MilliSatoshiLong, TestConstants, TestKitBaseClass, randomBytes32, randomKey}
+import fr.acinq.eclair.{BlockHeight, CltvExpiry, Features, MilliSatoshiLong, TestConstants, TestKitBaseClass, randomBytes32, randomBytes64, randomKey}
import org.scalatest.Inside.inside
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import org.scalatest.{Outcome, Tag}
@@ -482,6 +482,37 @@ class NegotiatingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
bob2blockchain.expectWatchTxConfirmed(tx.txid)
}
+ def testReceiveClosingSigWrongSignatureType(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = {
+ import f._
+ aliceClose(f)
+ val aliceClosingComplete = alice2bob.expectMsgType[ClosingComplete]
+ bob2alice.expectMsgType[ClosingComplete]
+ alice2bob.forward(bob, aliceClosingComplete)
+ val bobClosingSig = bob2alice.expectMsgType[ClosingSig]
+ val bobTx = bob2blockchain.expectMsgType[PublishFinalTx]
+ bob2blockchain.expectWatchTxConfirmed(bobTx.tx.txid)
+ // Bob adds a signature that doesn't match our commitment format: we must ignore it and use the one that matches.
+ val invalidSig: ClosingSigTlv = commitmentFormat match {
+ case _: SegwitV0CommitmentFormat => ClosingSigTlv.CloserAndCloseeOutputsPartialSignature(randomBytes32())
+ case _: TaprootCommitmentFormat => ClosingTlv.CloserAndCloseeOutputs(randomBytes64())
+ }
+ bob2alice.forward(alice, bobClosingSig.copy(tlvStream = TlvStream(bobClosingSig.tlvStream.records + invalidSig, bobClosingSig.tlvStream.unknown)))
+ inside(alice2blockchain.expectMsgType[PublishFinalTx]) { p =>
+ assert(p.tx.txid == bobTx.tx.txid)
+ assert(p.fee > 0.sat)
+ }
+ alice2blockchain.expectWatchTxConfirmed(bobTx.tx.txid)
+ assert(alice.stateName == NEGOTIATING_SIMPLE)
+ }
+
+ test("recv ClosingSig (invalid signature type)", Tag(ChannelStateTestsTags.SimpleClose)) { f =>
+ testReceiveClosingSigWrongSignatureType(f, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat)
+ }
+
+ test("recv ClosingSig (invalid signature type, simple taproot channels)", Tag(ChannelStateTestsTags.SimpleClose), Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f =>
+ testReceiveClosingSigWrongSignatureType(f, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat)
+ }
+
def testReceiveClosingCompleteBothOutputs(f: FixtureParam, commitmentFormat: CommitmentFormat): Unit = {
import f._
### eclair-core/src/test/scala/fr/acinq/eclair/io/PendingChannelsRateLimiterSpec.scala
@@ -23,6 +23,7 @@ import com.typesafe.config.ConfigFactory
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.{Block, ByteVector32, SatoshiLong, Transaction, TxId, TxOut}
import fr.acinq.eclair.TestUtils.randomTxId
+import fr.acinq.eclair.channel.ChannelSpendSignature.IndividualSignature
import fr.acinq.eclair.channel._
import fr.acinq.eclair.io.PendingChannelsRateLimiter.filterPendingChannels
import fr.acinq.eclair.router.Router.{GetNode, PublicNode, UnknownNode}
@@ -76,19 +77,19 @@ class PendingChannelsRateLimiterSpec extends ScalaTestWithActorTestKit(ConfigFac
val tx = Transaction.read("010000000110f01d4a4228ef959681feb1465c2010d0135be88fd598135b2e09d5413bf6f1000000006a473044022074658623424cebdac8290488b76f893cfb17765b7a3805e773e6770b7b17200102202892cfa9dda662d5eac394ba36fcfd1ea6c0b8bb3230ab96220731967bbdb90101210372d437866d9e4ead3d362b01b615d24cc0d5152c740d51e3c55fb53f6d335d82ffffffff01408b0700000000001976a914678db9a7caa2aca887af1177eda6f3d0f702df0d88ac00000000")
val closingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(10_000 sat, Nil)), tx, None)
val channelsOnWhitelistAtLimit: Seq[PersistentChannelData] = Seq(
- DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(peerOnWhitelistAtLimit, randomBytes32()), BlockHeight(0), None, Left(FundingCreated(randomBytes32(), TxId(ByteVector32.Zeroes), 3, randomBytes64()))),
+ DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(peerOnWhitelistAtLimit, randomBytes32()), BlockHeight(0), None, Left(FundingCreated(randomBytes32(), TxId(ByteVector32.Zeroes), 3, IndividualSignature(randomBytes64())))),
DATA_WAIT_FOR_CHANNEL_READY(commitments(peerOnWhitelistAtLimit, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)),
)
val channelsAtLimit1 = Seq(
- DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(peerAtLimit1, channelIdAtLimit1), BlockHeight(0), None, Left(FundingCreated(channelIdAtLimit1, TxId(ByteVector32.Zeroes), 3, randomBytes64()))),
+ DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(peerAtLimit1, channelIdAtLimit1), BlockHeight(0), None, Left(FundingCreated(channelIdAtLimit1, TxId(ByteVector32.Zeroes), 3, IndividualSignature(randomBytes64())))),
DATA_WAIT_FOR_CHANNEL_READY(commitments(peerAtLimit1, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)),
)
val channelsAtLimit2 = Seq(
DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED(commitments(peerAtLimit2, channelIdAtLimit2), 0 msat, 0 msat, BlockHeight(0), BlockHeight(0), DualFundingStatus.WaitingForConfirmations, None),
DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments(peerAtLimit2, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)),
)
val channelsBelowLimit1 = Seq(
- DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(peerBelowLimit1, channelIdBelowLimit1), BlockHeight(0), None, Left(FundingCreated(channelIdBelowLimit1, TxId(ByteVector32.Zeroes), 3, randomBytes64()))),
+ DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(peerBelowLimit1, channelIdBelowLimit1), BlockHeight(0), None, Left(FundingCreated(channelIdBelowLimit1, TxId(ByteVector32.Zeroes), 3, IndividualSignature(randomBytes64())))),
)
val channelsBelowLimit2 = Seq(
DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments(peerBelowLimit2, channelIdBelowLimit2), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)),
@@ -101,7 +102,7 @@ class PendingChannelsRateLimiterSpec extends ScalaTestWithActorTestKit(ConfigFac
DATA_NORMAL(commitments(privatePeer2, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, null, SpliceStatus.NoSplice, None, None, None),
)
val initiatorChannels = Seq(
- DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(peerBelowLimit1, randomBytes32(), isOpener = true), BlockHeight(0), None, Left(FundingCreated(channelIdAtLimit1, TxId(ByteVector32.Zeroes), 3, randomBytes64()))),
+ DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(peerBelowLimit1, randomBytes32(), isOpener = true), BlockHeight(0), None, Left(FundingCreated(channelIdAtLimit1, TxId(ByteVector32.Zeroes), 3, IndividualSignature(randomBytes64())))),
DATA_WAIT_FOR_CHANNEL_READY(commitments(peerBelowLimit1, randomBytes32(), isOpener = true), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)),
DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED(commitments(peerAtLimit1, randomBytes32(), isOpener = true), 0 msat, 0 msat, BlockHeight(0), BlockHeight(0), DualFundingStatus.WaitingForConfirmations, None),
DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments(peerAtLimit1, randomBytes32(), isOpener = true), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)),
@@ -323,7 +324,7 @@ class PendingChannelsRateLimiterSpec extends ScalaTestWithActorTestKit(ConfigFac
DATA_WAIT_FOR_DUAL_FUNDING_READY(commitments(randomKey().publicKey, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)),
DATA_NORMAL(commitments(randomKey().publicKey, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None), None, null, SpliceStatus.NoSplice, None, None, None),
DATA_SHUTDOWN(commitments(randomKey().publicKey, randomBytes32()), Shutdown(randomBytes32(), ByteVector.empty), Shutdown(randomBytes32(), ByteVector.empty), CloseStatus.Initiator(None)),
- DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(randomKey().publicKey, randomBytes32()), BlockHeight(0), None, Left(FundingCreated(randomBytes32(), TxId(ByteVector32.Zeroes), 3, randomBytes64()))),
+ DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(randomKey().publicKey, randomBytes32()), BlockHeight(0), None, Left(FundingCreated(randomBytes32(), TxId(ByteVector32.Zeroes), 3, IndividualSignature(randomBytes64())))),
)
val limiter = testKit.spawn(PendingChannelsRateLimiter(nodeParams, router.ref, channels))
### eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala
@@ -154,9 +154,9 @@ class LightningMessageCodecsSpec extends AnyFunSuite {
val tlvTag = UInt64(hex"47010000")
val refs = Map(
- hex"0023" ++ channelId ++ signature -> FundingSigned(channelId, signature),
- hex"0023" ++ channelId ++ signature ++ hex"fe47010000 00" -> FundingSigned(channelId, signature, TlvStream[FundingSignedTlv](Set.empty[FundingSignedTlv], Set(GenericTlv(tlvTag, ByteVector.empty)))),
- hex"0023" ++ channelId ++ signature ++ hex"fe47010000 07 cccccccccccccc" -> FundingSigned(channelId, signature, TlvStream[FundingSignedTlv](Set.empty[FundingSignedTlv], Set(GenericTlv(tlvTag, hex"cccccccccccccc")))),
+ hex"0023" ++ channelId ++ signature -> FundingSigned(channelId, IndividualSignature(signature)),
+ hex"0023" ++ channelId ++ signature ++ hex"fe47010000 00" -> FundingSigned(channelId, IndividualSignature(signature), TlvStream[FundingSignedTlv](Set.empty[FundingSignedTlv], Set(GenericTlv(tlvTag, ByteVector.empty)))),
+ hex"0023" ++ channelId ++ signature ++ hex"fe47010000 07 cccccccccccccc" -> FundingSigned(channelId, IndividualSignature(signature), TlvStream[FundingSignedTlv](Set.empty[FundingSignedTlv], Set(GenericTlv(tlvTag, hex"cccccccccccccc")))),
hex"0088" ++ channelId ++ hex"0001020304050607 0809aabbccddeeff" ++ key.value ++ point.value -> ChannelReestablish(channelId, 0x01020304050607L, 0x0809aabbccddeeffL, key, point),
hex"0088" ++ channelId ++ hex"0001020304050607 0809aabbccddeeff" ++ key.value ++ point.value ++ hex"00 20" ++ txId.value.reverse -> ChannelReestablish(channelId, 0x01020304050607L, 0x0809aabbccddeeffL, key, point, TlvStream(ChannelReestablishTlv.ExperimentalNextFundingTlv(txId))),
@@ -620,8 +620,8 @@ class LightningMessageCodecsSpec extends AnyFunSuite {
val msgs = List(
OpenChannel(BlockHash(randomBytes32()), randomBytes32(), 3 sat, 4 msat, 5 sat, UInt64(6), 7 sat, 8 msat, FeeratePerKw(9 sat), CltvExpiryDelta(10), 11, publicKey(1), point(2), point(3), point(4), point(5), point(6), ChannelFlags(announceChannel = false)),
AcceptChannel(randomBytes32(), 3 sat, UInt64(4), 5 sat, 6 msat, 7, CltvExpiryDelta(8), 9, publicKey(1), point(2), point(3), point(4), point(5), point(6)),
- FundingCreated(randomBytes32(), TxId(ByteVector32.Zeroes), 3, randomBytes64()),
- FundingSigned(randomBytes32(), randomBytes64()),
+ FundingCreated(randomBytes32(), TxId(ByteVector32.Zeroes), 3, IndividualSignature(randomBytes64())),
+ FundingSigned(randomBytes32(), IndividualSignature(randomBytes64())),
ChannelReady(randomBytes32(), point(2)),
ChannelReady(randomBytes32(), point(2), Alias(123456)),
ChannelReady(randomBytes32(), point(2), Alias(123456), IndividualNonce(randomBytes(66))),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.