From 71568cae58c0aebd176e83babdb4239aaed82058 Mon Sep 17 00:00:00 2001 From: Pierre-Marie Padiou Date: Thu, 13 Apr 2023 21:16:58 +0200 Subject: [PATCH] Dynamic funding pubkeys (#2634) Funding pubkeys are now dynamic and change for each splice. Main changes: - `remoteFundingPubkey` has been moved from `RemoteParams` to `Commitment` - `localFundingPubkey` is computed by appending the `fundingTxIndex` to a new dedicated `fundingKeyPath`. As a nice side-effect, the resulting funding pubkey is constant across rbf attempts. Also, there is no change in the data model, since the base `fundingKeyPath` is constant and still belongs to `LocalParams`. --------- Co-authored-by: Bastien Teinturier <31281497+t-bast@users.noreply.github.com> --- .../fr/acinq/eclair/channel/ChannelData.scala | 10 +- .../fr/acinq/eclair/channel/Commitments.scala | 46 ++-- .../fr/acinq/eclair/channel/Helpers.scala | 41 +-- .../fr/acinq/eclair/channel/fsm/Channel.scala | 41 +-- .../channel/fsm/ChannelOpenDualFunded.scala | 22 +- .../channel/fsm/ChannelOpenSingleFunded.scala | 38 +-- .../channel/fund/InteractiveTxBuilder.scala | 94 ++++--- .../channel/fund/InteractiveTxFunder.scala | 17 +- .../channel/publish/ReplaceableTxFunder.scala | 2 +- .../crypto/keymanager/ChannelKeyManager.scala | 4 +- .../keymanager/LocalChannelKeyManager.scala | 10 +- .../channel/version0/ChannelCodecs0.scala | 13 +- .../channel/version0/ChannelTypes0.scala | 38 ++- .../channel/version1/ChannelCodecs1.scala | 13 +- .../channel/version2/ChannelCodecs2.scala | 4 +- .../channel/version3/ChannelCodecs3.scala | 5 +- .../channel/version3/ChannelTypes3.scala | 7 +- .../channel/version4/ChannelCodecs4.scala | 6 +- .../protocol/LightningMessageCodecs.scala | 4 +- .../wire/protocol/LightningMessageTypes.scala | 12 +- .../fundee-announced/data.json | 1 - .../000003-DATA_NORMAL/funder/data.json | 1 - .../funder-announced/data.json | 1 - .../funder/data.json | 1 - .../funder/data.json | 1 - .../funder/data.json | 1 - .../eclair/channel/CommitmentsSpec.scala | 14 +- .../channel/InteractiveTxBuilderSpec.scala | 255 +++++++++--------- .../channel/states/e/NormalStateSpec.scala | 4 +- .../LocalChannelKeyManagerSpec.scala | 8 +- .../keymanager/LocalNodeKeyManagerSpec.scala | 3 +- .../integration/ChannelIntegrationSpec.scala | 5 +- .../eclair/payment/PaymentPacketSpec.scala | 4 +- .../internal/channel/ChannelCodecsSpec.scala | 9 +- .../channel/version1/ChannelCodecs1Spec.scala | 22 +- .../channel/version4/ChannelCodecs4Spec.scala | 4 +- 36 files changed, 398 insertions(+), 363 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala index dca03abee4..f9a0d1aef4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala @@ -22,15 +22,13 @@ import fr.acinq.bitcoin.scalacompat.{ByteVector32, DeterministicWallet, OutPoint import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.channel.LocalFundingStatus.DualFundedUnconfirmedFundingTx import fr.acinq.eclair.channel.fund.InteractiveTxBuilder._ -import fr.acinq.eclair.io.Peer import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} +import fr.acinq.eclair.io.Peer import fr.acinq.eclair.payment.OutgoingPaymentPacket.Upstream import fr.acinq.eclair.transactions.CommitmentSpec import fr.acinq.eclair.transactions.Transactions._ -import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelReady, ChannelReestablish, ChannelUpdate, ClosingSigned, FailureMessage, FundingCreated, FundingSigned, Init, OnionRoutingPacket, OpenChannel, OpenDualFundedChannel, Shutdown, SpliceInit, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFulfillHtlc} +import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelReady, ChannelReestablish, ChannelUpdate, ClosingSigned, CommitSig, FailureMessage, FundingCreated, FundingSigned, Init, OnionRoutingPacket, OpenChannel, OpenDualFundedChannel, Shutdown, SpliceInit, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFulfillHtlc} import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, Features, InitFeature, MilliSatoshi, MilliSatoshiLong, RealShortChannelId, UInt64} -import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, ChannelReady, ChannelReestablish, ChannelUpdate, ClosingSigned, CommitSig, FailureMessage, FundingCreated, FundingSigned, Init, OnionRoutingPacket, OpenChannel, OpenDualFundedChannel, Shutdown, UpdateAddHtlc, UpdateFailHtlc, UpdateFailMalformedHtlc, UpdateFulfillHtlc} -import fr.acinq.eclair.{Alias, BlockHeight, CltvExpiry, CltvExpiryDelta, Features, InitFeature, MilliSatoshi, RealShortChannelId, UInt64} import scodec.bits.ByteVector import java.util.UUID @@ -494,6 +492,7 @@ final case class DATA_WAIT_FOR_FUNDING_INTERNAL(params: ChannelParams, fundingAmount: Satoshi, pushAmount: MilliSatoshi, commitTxFeerate: FeeratePerKw, + remoteFundingPubKey: PublicKey, remoteFirstPerCommitmentPoint: PublicKey, replyTo: akka.actor.typed.ActorRef[Peer.OpenChannelResponse]) extends TransientChannelData { val channelId: ByteVector32 = params.channelId @@ -502,10 +501,12 @@ final case class DATA_WAIT_FOR_FUNDING_CREATED(params: ChannelParams, fundingAmount: Satoshi, pushAmount: MilliSatoshi, commitTxFeerate: FeeratePerKw, + remoteFundingPubKey: PublicKey, remoteFirstPerCommitmentPoint: PublicKey) extends TransientChannelData { val channelId: ByteVector32 = params.channelId } final case class DATA_WAIT_FOR_FUNDING_SIGNED(params: ChannelParams, + remoteFundingPubKey: PublicKey, fundingTx: Transaction, fundingTxFee: Satoshi, localSpec: CommitmentSpec, @@ -616,7 +617,6 @@ case class RemoteParams(nodeId: PublicKey, htlcMinimum: MilliSatoshi, toSelfDelay: CltvExpiryDelta, maxAcceptedHtlcs: Int, - fundingPubKey: PublicKey, revocationBasepoint: PublicKey, paymentBasepoint: PublicKey, delayedPaymentBasepoint: PublicKey, 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 68872313c8..d32f283636 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 @@ -202,9 +202,9 @@ case class LocalCommit(index: Long, spec: CommitmentSpec, commitTxAndRemoteSig: /** The remote commitment maps to a commitment transaction that only our peer can sign and broadcast. */ case class RemoteCommit(index: Long, spec: CommitmentSpec, txid: ByteVector32, remotePerCommitmentPoint: PublicKey) { - def sign(keyManager: ChannelKeyManager, params: ChannelParams, commitInput: InputInfo): CommitSig = { - val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(keyManager, params.channelConfig, params.channelFeatures, index, params.localParams, params.remoteParams, commitInput, remotePerCommitmentPoint, spec) - val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath), TxOwner.Remote, params.commitmentFormat) + def sign(keyManager: ChannelKeyManager, params: ChannelParams, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo): CommitSig = { + val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(keyManager, params.channelConfig, params.channelFeatures, index, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, remotePerCommitmentPoint, spec) + val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), TxOwner.Remote, params.commitmentFormat) val channelKeyPath = keyManager.keyPath(params.localParams, params.channelConfig) val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index) val htlcSigs = sortedHtlcTxs.map(keyManager.sign(_, keyManager.htlcPoint(channelKeyPath), remotePerCommitmentPoint, TxOwner.Remote, params.commitmentFormat)) @@ -224,6 +224,7 @@ case class NextRemoteCommit(sig: CommitSig, commit: RemoteCommit) * - commitments that share the same index are rbfed */ case class Commitment(fundingTxIndex: Long, + remoteFundingPubKey: PublicKey, localFundingStatus: LocalFundingStatus, remoteFundingStatus: RemoteFundingStatus, localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit_opt: Option[NextRemoteCommit]) { val commitInput: InputInfo = localCommit.commitTxAndRemoteSig.commitTx.input @@ -564,8 +565,8 @@ case class Commitment(fundingTxIndex: Long, def sendCommit(keyManager: ChannelKeyManager, params: ChannelParams, changes: CommitmentChanges, remoteNextPerCommitmentPoint: PublicKey)(implicit log: LoggingAdapter): (Commitment, CommitSig) = { // remote commitment will include all local proposed changes + remote acked changes val spec = CommitmentSpec.reduce(remoteCommit.spec, changes.remoteChanges.acked, changes.localChanges.proposed) - val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(keyManager, params.channelConfig, params.channelFeatures, remoteCommit.index + 1, params.localParams, params.remoteParams, commitInput, remoteNextPerCommitmentPoint, spec) - val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath), TxOwner.Remote, params.commitmentFormat) + val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(keyManager, params.channelConfig, params.channelFeatures, remoteCommit.index + 1, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, remoteNextPerCommitmentPoint, spec) + val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), TxOwner.Remote, params.commitmentFormat) val sortedHtlcTxs: Seq[TransactionWithInputInfo] = htlcTxs.sortBy(_.input.outPoint.index) val channelKeyPath = keyManager.keyPath(params.localParams, params.channelConfig) @@ -590,9 +591,9 @@ case class Commitment(fundingTxIndex: Long, // we will reply to this sig with our old revocation hash preimage (at index) and our next revocation hash (at index + 1) // and will increment our index val spec = CommitmentSpec.reduce(localCommit.spec, changes.localChanges.acked, changes.remoteChanges.proposed) - val (localCommitTx, htlcTxs) = Commitment.makeLocalTxs(keyManager, params.channelConfig, params.channelFeatures, localCommit.index + 1, params.localParams, params.remoteParams, commitInput, localPerCommitmentPoint, spec) + val (localCommitTx, htlcTxs) = Commitment.makeLocalTxs(keyManager, params.channelConfig, params.channelFeatures, localCommit.index + 1, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, localPerCommitmentPoint, spec) log.info(s"built local commit number=${localCommit.index + 1} 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(",")) - if (!checkSig(localCommitTx, commit.signature, params.remoteParams.fundingPubKey, TxOwner.Remote, params.commitmentFormat)) { + if (!checkSig(localCommitTx, commit.signature, remoteFundingPubKey, TxOwner.Remote, params.commitmentFormat)) { return Left(InvalidCommitmentSignature(params.channelId, localCommitTx.tx.txid)) } val sortedHtlcTxs: Seq[HtlcTx] = htlcTxs.sortBy(_.input.outPoint.index) @@ -615,9 +616,9 @@ case class Commitment(fundingTxIndex: Long, /** Return a fully signed commit tx, that can be published as-is. */ def fullySignedLocalCommitTx(params: ChannelParams, keyManager: ChannelKeyManager): CommitTx = { val unsignedCommitTx = localCommit.commitTxAndRemoteSig.commitTx - val localSig = keyManager.sign(unsignedCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath), TxOwner.Local, params.commitmentFormat) + val localSig = keyManager.sign(unsignedCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), TxOwner.Local, params.commitmentFormat) val remoteSig = localCommit.commitTxAndRemoteSig.remoteSig - val commitTx = addSigs(unsignedCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath).publicKey, params.remoteParams.fundingPubKey, localSig, remoteSig) + val commitTx = addSigs(unsignedCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex).publicKey, remoteFundingPubKey, localSig, remoteSig) // We verify the remote signature when receiving their commit_sig, so this check should always pass. require(checkSpendable(commitTx).isSuccess, "commit signatures are invalid") commitTx @@ -632,11 +633,13 @@ object Commitment { commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, + fundingTxIndex: Long, + remoteFundingPubKey: PublicKey, commitmentInput: InputInfo, localPerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTx]) = { val channelKeyPath = keyManager.keyPath(localParams, channelConfig) - val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey + val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath, fundingTxIndex).publicKey val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(channelKeyPath).publicKey, localPerCommitmentPoint) val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, localPerCommitmentPoint) val remotePaymentPubkey = if (channelFeatures.hasFeature(Features.StaticRemoteKey)) { @@ -647,7 +650,7 @@ object Commitment { val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, localPerCommitmentPoint) val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) val localPaymentBasepoint = localParams.walletStaticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey) - val outputs = makeCommitTxOutputs(localParams.isInitiator, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, localFundingPubkey, remoteParams.fundingPubKey, spec, channelFeatures.commitmentFormat) + val outputs = makeCommitTxOutputs(localParams.isInitiator, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, localFundingPubkey, remoteFundingPubKey, spec, channelFeatures.commitmentFormat) val commitTx = makeCommitTx(commitmentInput, commitTxNumber, localPaymentBasepoint, remoteParams.paymentBasepoint, localParams.isInitiator, outputs) val htlcTxs = makeHtlcTxs(commitTx.tx, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, spec.htlcTxFeerate(channelFeatures.commitmentFormat), outputs, channelFeatures.commitmentFormat) (commitTx, htlcTxs) @@ -659,11 +662,13 @@ object Commitment { commitTxNumber: Long, localParams: LocalParams, remoteParams: RemoteParams, + fundingTxIndex: Long, + remoteFundingPubKey: PublicKey, commitmentInput: InputInfo, remotePerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTx]) = { val channelKeyPath = keyManager.keyPath(localParams, channelConfig) - val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey + val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath, fundingTxIndex).publicKey val localPaymentBasepoint = localParams.walletStaticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey) val localPaymentPubkey = if (channelFeatures.hasFeature(Features.StaticRemoteKey)) { localPaymentBasepoint @@ -674,7 +679,7 @@ object Commitment { val remoteDelayedPaymentPubkey = Generators.derivePubKey(remoteParams.delayedPaymentBasepoint, remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remotePerCommitmentPoint) - val outputs = makeCommitTxOutputs(!localParams.isInitiator, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, remoteParams.fundingPubKey, localFundingPubkey, spec, channelFeatures.commitmentFormat) + val outputs = makeCommitTxOutputs(!localParams.isInitiator, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, remoteFundingPubKey, localFundingPubkey, spec, channelFeatures.commitmentFormat) val commitTx = makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, localPaymentBasepoint, !localParams.isInitiator, outputs) val htlcTxs = makeHtlcTxs(commitTx.tx, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, spec.htlcTxFeerate(channelFeatures.commitmentFormat), outputs, channelFeatures.commitmentFormat) (commitTx, htlcTxs) @@ -684,6 +689,7 @@ object Commitment { /** Subset of Commitments when we want to work with a single, specific commitment. */ case class FullCommitment(params: ChannelParams, changes: CommitmentChanges, fundingTxIndex: Long, + remoteFundingPubKey: PublicKey, localFundingStatus: LocalFundingStatus, remoteFundingStatus: RemoteFundingStatus, localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit_opt: Option[NextRemoteCommit]) { val channelId = params.channelId @@ -692,7 +698,7 @@ case class FullCommitment(params: ChannelParams, changes: CommitmentChanges, val commitInput = localCommit.commitTxAndRemoteSig.commitTx.input val fundingTxId = commitInput.outPoint.txid val capacity = commitInput.txOut.amount - val commitment = Commitment(fundingTxIndex, localFundingStatus, remoteFundingStatus, localCommit, remoteCommit, nextRemoteCommit_opt) + val commitment = Commitment(fundingTxIndex, remoteFundingPubKey, localFundingStatus, remoteFundingStatus, localCommit, remoteCommit, nextRemoteCommit_opt) def localChannelReserve: Satoshi = commitment.localChannelReserve(params) @@ -756,7 +762,7 @@ case class Commitments(params: ChannelParams, lazy val availableBalanceForReceive: MilliSatoshi = active.map(_.availableBalanceForReceive(params, changes)).min // We always use the last commitment that was created, to make sure we never go back in time. - val latest = FullCommitment(params, changes, active.head.fundingTxIndex, active.head.localFundingStatus, active.head.remoteFundingStatus, active.head.localCommit, active.head.remoteCommit, active.head.nextRemoteCommit_opt) + val latest = FullCommitment(params, changes, active.head.fundingTxIndex, active.head.remoteFundingPubKey, active.head.localFundingStatus, active.head.remoteFundingStatus, active.head.localCommit, active.head.remoteCommit, active.head.nextRemoteCommit_opt) val all: Seq[Commitment] = active ++ inactive @@ -1082,10 +1088,12 @@ case class Commitments(params: ChannelParams, } def validateSeed(keyManager: ChannelKeyManager): Boolean = { - val localFundingKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath).publicKey - val remoteFundingKey = params.remoteParams.fundingPubKey - val fundingScript = Script.write(Scripts.multiSig2of2(localFundingKey, remoteFundingKey)) - active.forall(_.commitInput.redeemScript == fundingScript) + active.forall { commitment => + val localFundingKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey + val remoteFundingKey = commitment.remoteFundingPubKey + val fundingScript = Script.write(Scripts.multiSig2of2(localFundingKey, remoteFundingKey)) + commitment.commitInput.redeemScript == fundingScript + } } /** 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 8ae8e86e8b..7b22c886e8 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 @@ -308,16 +308,16 @@ object Helpers { remoteFeeratePerKw < FeeratePerKw.MinimumFeeratePerKw } - def makeAnnouncementSignatures(nodeParams: NodeParams, channelParams: ChannelParams, shortChannelId: RealShortChannelId): AnnouncementSignatures = { + def makeAnnouncementSignatures(nodeParams: NodeParams, channelParams: ChannelParams, remoteFundingPubKey: PublicKey, shortChannelId: RealShortChannelId): AnnouncementSignatures = { val features = Features.empty[Feature] // empty features for now - val fundingPubKey = nodeParams.channelKeyManager.fundingPublicKey(channelParams.localParams.fundingKeyPath) + val fundingPubKey = nodeParams.channelKeyManager.fundingPublicKey(channelParams.localParams.fundingKeyPath, fundingTxIndex = 0) // TODO: public announcements are not yet supported with splices val witness = Announcements.generateChannelAnnouncementWitness( nodeParams.chainHash, shortChannelId, nodeParams.nodeKeyManager.nodeId, channelParams.remoteParams.nodeId, fundingPubKey.publicKey, - channelParams.remoteParams.fundingPubKey, + remoteFundingPubKey, features ) val localBitcoinSig = nodeParams.channelKeyManager.signChannelAnnouncement(witness, fundingPubKey.path) @@ -365,12 +365,15 @@ object Helpers { localPushAmount: MilliSatoshi, remotePushAmount: MilliSatoshi, commitTxFeerate: FeeratePerKw, fundingTxHash: ByteVector32, fundingTxOutputIndex: Int, + remoteFundingPubKey: PublicKey, remoteFirstPerCommitmentPoint: PublicKey): Either[ChannelException, (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx)] = makeCommitTxsWithoutHtlcs(keyManager, params, fundingAmount = localFundingAmount + remoteFundingAmount, toLocal = localFundingAmount.toMilliSatoshi - localPushAmount + remotePushAmount, toRemote = remoteFundingAmount.toMilliSatoshi + localPushAmount - remotePushAmount, - commitTxFeerate, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint, commitmentIndex = 0) + commitTxFeerate, + fundingTxIndex = 0, + fundingTxHash, fundingTxOutputIndex, remoteFundingPubKey = remoteFundingPubKey, remotePerCommitmentPoint = remoteFirstPerCommitmentPoint, commitmentIndex = 0) /** * This creates commitment transactions for both sides at an arbitrary `commitmentIndex`. There are no htlcs, only @@ -381,7 +384,9 @@ object Helpers { fundingAmount: Satoshi, toLocal: MilliSatoshi, toRemote: MilliSatoshi, commitTxFeerate: FeeratePerKw, + fundingTxIndex: Long, fundingTxHash: ByteVector32, fundingTxOutputIndex: Int, + remoteFundingPubKey: PublicKey, remotePerCommitmentPoint: PublicKey, commitmentIndex: Long): Either[ChannelException, (CommitmentSpec, CommitTx, CommitmentSpec, CommitTx)] = { import params._ @@ -400,12 +405,12 @@ object Helpers { } } - val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath) + val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath, fundingTxIndex) val channelKeyPath = keyManager.keyPath(localParams, channelConfig) - val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, fundingAmount, fundingPubKey.publicKey, remoteParams.fundingPubKey) + val commitmentInput = makeFundingInputInfo(fundingTxHash, fundingTxOutputIndex, fundingAmount, fundingPubKey.publicKey, remoteFundingPubKey) val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitmentIndex) - val (localCommitTx, _) = Commitment.makeLocalTxs(keyManager, channelConfig, channelFeatures, commitmentIndex, localParams, remoteParams, commitmentInput, localPerCommitmentPoint, localSpec) - val (remoteCommitTx, _) = Commitment.makeRemoteTxs(keyManager, channelConfig, channelFeatures, commitmentIndex, localParams, remoteParams, commitmentInput, remotePerCommitmentPoint, remoteSpec) + val (localCommitTx, _) = Commitment.makeLocalTxs(keyManager, channelConfig, channelFeatures, commitmentIndex, localParams, remoteParams, fundingTxIndex, remoteFundingPubKey, commitmentInput, localPerCommitmentPoint, localSpec) + val (remoteCommitTx, _) = Commitment.makeRemoteTxs(keyManager, channelConfig, channelFeatures, commitmentIndex, localParams, remoteParams, fundingTxIndex, remoteFundingPubKey, commitmentInput, remotePerCommitmentPoint, remoteSpec) Right(localSpec, localCommitTx, remoteSpec, remoteCommitTx) } @@ -617,7 +622,7 @@ object Helpers { def firstClosingFee(commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerates: ClosingFeerates)(implicit log: LoggingAdapter): ClosingFees = { // this is just to estimate the weight, it depends on size of the pubkey scripts val dummyClosingTx = Transactions.makeClosingTx(commitment.commitInput, localScriptPubkey, remoteScriptPubkey, commitment.localParams.isInitiator, Satoshi(0), Satoshi(0), commitment.localCommit.spec) - val closingWeight = Transaction.weight(Transactions.addSigs(dummyClosingTx, dummyPublicKey, commitment.remoteParams.fundingPubKey, Transactions.PlaceHolderSig, Transactions.PlaceHolderSig).tx) + val closingWeight = Transaction.weight(Transactions.addSigs(dummyClosingTx, dummyPublicKey, commitment.remoteFundingPubKey, Transactions.PlaceHolderSig, Transactions.PlaceHolderSig).tx) log.info(s"using feerates=$feerates for initial closing tx") feerates.computeFees(closingWeight) } @@ -653,7 +658,7 @@ object Helpers { log.debug("making closing tx with closing fee={} and commitments:\n{}", closingFees.preferred, commitment.specs2String) val dustLimit = commitment.localParams.dustLimit.max(commitment.remoteParams.dustLimit) val closingTx = Transactions.makeClosingTx(commitment.commitInput, localScriptPubkey, remoteScriptPubkey, commitment.localParams.isInitiator, dustLimit, closingFees.preferred, commitment.localCommit.spec) - val localClosingSig = keyManager.sign(closingTx, keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath), TxOwner.Local, commitment.params.commitmentFormat) + val localClosingSig = keyManager.sign(closingTx, keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex), TxOwner.Local, commitment.params.commitmentFormat) val closingSigned = ClosingSigned(commitment.channelId, closingFees.preferred, localClosingSig, TlvStream(ClosingSignedTlv.FeeRange(closingFees.min, closingFees.max))) log.debug(s"signed closing txid=${closingTx.tx.txid} with closing fee=${closingSigned.feeSatoshis}") log.debug(s"closingTxid=${closingTx.tx.txid} closingTx=${closingTx.tx}}") @@ -668,7 +673,7 @@ object Helpers { } else { val (closingTx, closingSigned) = makeClosingTx(keyManager, commitment, localScriptPubkey, remoteScriptPubkey, ClosingFees(remoteClosingFee, remoteClosingFee, remoteClosingFee)) if (checkClosingDustAmounts(closingTx)) { - val signedClosingTx = Transactions.addSigs(closingTx, keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath).publicKey, commitment.remoteParams.fundingPubKey, closingSigned.signature, remoteClosingSig) + val signedClosingTx = Transactions.addSigs(closingTx, keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey, commitment.remoteFundingPubKey, closingSigned.signature, remoteClosingSig) Transactions.checkSpendable(signedClosingTx) match { case Success(_) => Right(signedClosingTx, closingSigned) case _ => Left(InvalidCloseSignature(commitment.channelId, signedClosingTx.tx.txid)) @@ -736,7 +741,7 @@ object Helpers { val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitment.localCommit.index.toInt) val localRevocationPubkey = Generators.revocationPubKey(commitment.remoteParams.revocationBasepoint, localPerCommitmentPoint) val localDelayedPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(channelKeyPath).publicKey, localPerCommitmentPoint) - val localFundingPubKey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath).publicKey + val localFundingPubKey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey val feeratePerKwDelayed = onChainFeeConf.feeEstimator.getFeeratePerKw(onChainFeeConf.feeTargets.claimMainBlockTarget) // first we will claim our main output as soon as the delay is over @@ -758,7 +763,7 @@ object Helpers { Transactions.makeClaimLocalAnchorOutputTx(tx, localFundingPubKey, confirmCommitBefore) }, withTxGenerationLog("remote-anchor") { - Transactions.makeClaimRemoteAnchorOutputTx(tx, commitment.remoteParams.fundingPubKey) + Transactions.makeClaimRemoteAnchorOutputTx(tx, commitment.remoteFundingPubKey) } ).flatten } else { @@ -860,13 +865,13 @@ object Helpers { val claimAnchorTxs: List[ClaimAnchorOutputTx] = if (spendAnchors) { // If we don't have pending HTLCs, we don't have funds at risk, so we can aim for a slower confirmation. val confirmCommitBefore = htlcTxs.values.flatten.map(htlcTx => htlcTx.confirmBefore).minOption.getOrElse(currentBlockHeight + onChainFeeConf.feeTargets.commitmentWithoutHtlcsBlockTarget) - val localFundingPubkey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath).publicKey + val localFundingPubkey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey List( withTxGenerationLog("local-anchor") { Transactions.makeClaimLocalAnchorOutputTx(tx, localFundingPubkey, confirmCommitBefore) }, withTxGenerationLog("remote-anchor") { - Transactions.makeClaimRemoteAnchorOutputTx(tx, commitment.remoteParams.fundingPubKey) + Transactions.makeClaimRemoteAnchorOutputTx(tx, commitment.remoteFundingPubKey) } ).flatten } else { @@ -920,16 +925,16 @@ object Helpers { * Claim our htlc outputs only */ def claimHtlcOutputs(keyManager: ChannelKeyManager, commitment: FullCommitment, remoteCommit: RemoteCommit, feeEstimator: FeeEstimator, finalScriptPubKey: ByteVector)(implicit log: LoggingAdapter): Map[OutPoint, Option[ClaimHtlcTx]] = { - val (remoteCommitTx, _) = Commitment.makeRemoteTxs(keyManager, commitment.params.channelConfig, commitment.params.channelFeatures, remoteCommit.index, commitment.localParams, commitment.remoteParams, commitment.commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) + val (remoteCommitTx, _) = Commitment.makeRemoteTxs(keyManager, commitment.params.channelConfig, commitment.params.channelFeatures, remoteCommit.index, commitment.localParams, commitment.remoteParams, commitment.fundingTxIndex, commitment.remoteFundingPubKey, commitment.commitInput, remoteCommit.remotePerCommitmentPoint, remoteCommit.spec) require(remoteCommitTx.tx.txid == remoteCommit.txid, "txid mismatch, cannot recompute the current remote commit tx") val channelKeyPath = keyManager.keyPath(commitment.localParams, commitment.params.channelConfig) - val localFundingPubkey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath).publicKey + val localFundingPubkey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey val localHtlcPubkey = Generators.derivePubKey(keyManager.htlcPoint(channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) val remoteHtlcPubkey = Generators.derivePubKey(commitment.remoteParams.htlcBasepoint, remoteCommit.remotePerCommitmentPoint) val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) val remoteDelayedPaymentPubkey = Generators.derivePubKey(commitment.remoteParams.delayedPaymentBasepoint, remoteCommit.remotePerCommitmentPoint) val localPaymentPubkey = Generators.derivePubKey(keyManager.paymentPoint(channelKeyPath).publicKey, remoteCommit.remotePerCommitmentPoint) - val outputs = makeCommitTxOutputs(!commitment.localParams.isInitiator, commitment.remoteParams.dustLimit, remoteRevocationPubkey, commitment.localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, commitment.remoteParams.fundingPubKey, localFundingPubkey, remoteCommit.spec, commitment.params.commitmentFormat) + val outputs = makeCommitTxOutputs(!commitment.localParams.isInitiator, commitment.remoteParams.dustLimit, remoteRevocationPubkey, commitment.localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, commitment.remoteFundingPubKey, localFundingPubkey, remoteCommit.spec, commitment.params.commitmentFormat) // we need to use a rather high fee for htlc-claim because we compete with the counterparty val feeratePerKwHtlc = feeEstimator.getFeeratePerKw(target = 2) 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 43c07c5c4d..2c8756dddc 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 @@ -703,7 +703,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with } if (d.commitments.announceChannel) { // if channel is public we need to send our announcement_signatures in order to generate the channel_announcement - val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments.params, finalRealShortId.realScid) + val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments.params, d.commitments.latest.remoteFundingPubKey, finalRealShortId.realScid) // we use goto() instead of stay() because we want to fire transitions goto(NORMAL) using d.copy(shortIds = shortIds1, channelUpdate = channelUpdate1) storing() sending localAnnSigs } else { @@ -717,13 +717,13 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with case RealScidStatus.Final(realScid) => // we are aware that the channel has reached enough confirmations // we already had sent our announcement_signatures but we don't store them so we need to recompute it - val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments.params, realScid) + val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments.params, d.commitments.latest.remoteFundingPubKey, realScid) d.channelAnnouncement match { case None => require(localAnnSigs.shortChannelId == remoteAnnSigs.shortChannelId, s"shortChannelId mismatch: local=${localAnnSigs.shortChannelId} remote=${remoteAnnSigs.shortChannelId}") log.info(s"announcing channelId=${d.channelId} on the network with shortId=${localAnnSigs.shortChannelId}") - val fundingPubKey = keyManager.fundingPublicKey(d.commitments.params.localParams.fundingKeyPath) - val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, nodeParams.nodeId, d.commitments.params.remoteParams.nodeId, fundingPubKey.publicKey, d.commitments.params.remoteParams.fundingPubKey, localAnnSigs.nodeSignature, remoteAnnSigs.nodeSignature, localAnnSigs.bitcoinSignature, remoteAnnSigs.bitcoinSignature) + val fundingPubKey = keyManager.fundingPublicKey(d.commitments.params.localParams.fundingKeyPath, fundingTxIndex = 0) // TODO: public announcements are not yet supported with splices + val channelAnn = Announcements.makeChannelAnnouncement(nodeParams.chainHash, localAnnSigs.shortChannelId, nodeParams.nodeId, d.commitments.params.remoteParams.nodeId, fundingPubKey.publicKey, d.commitments.latest.remoteFundingPubKey, localAnnSigs.nodeSignature, remoteAnnSigs.nodeSignature, localAnnSigs.bitcoinSignature, remoteAnnSigs.bitcoinSignature) if (!Announcements.checkSigs(channelAnn)) { handleLocalError(InvalidAnnouncementSignatures(d.channelId, remoteAnnSigs), d, Some(remoteAnnSigs)) } else { @@ -776,14 +776,15 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with d.spliceStatus match { case SpliceStatus.NoSplice => if (d.commitments.isIdle && d.commitments.params.remoteParams.initFeatures.hasFeature(SplicePrototype)) { + val parentCommitment = d.commitments.latest.commitment val targetFeerate = nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKw(target = nodeParams.onChainFeeConf.feeTargets.fundingBlockTarget) val fundingContribution = InteractiveTxFunder.computeSpliceContribution( isInitiator = true, - sharedInput = Multisig2of2Input(keyManager, d.commitments.params, d.commitments.active.head), + sharedInput = Multisig2of2Input(parentCommitment), spliceInAmount = cmd.additionalLocalFunding, spliceOut = cmd.spliceOutputs, targetFeerate = targetFeerate) - if (d.commitments.latest.localCommit.spec.toLocal + fundingContribution < d.commitments.latest.localChannelReserve) { + if (parentCommitment.localCommit.spec.toLocal + fundingContribution < parentCommitment.localChannelReserve(d.commitments.params)) { log.warning("cannot do splice: insufficient funds") cmd.replyTo ! RES_FAILURE(cmd, InvalidSpliceRequest(d.channelId)) stay() @@ -797,6 +798,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with fundingContribution = fundingContribution, lockTime = nodeParams.currentBlockHeight.toLong, feerate = targetFeerate, + fundingPubKey = keyManager.fundingPublicKey(d.commitments.params.localParams.fundingKeyPath, parentCommitment.fundingTxIndex + 1).publicKey, pushAmount = cmd.pushAmount, requireConfirmedInputs = nodeParams.channelConf.requireConfirmedInputsForDualFunding ) @@ -819,20 +821,21 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with case SpliceStatus.NoSplice => if (d.commitments.isIdle && d.commitments.params.localParams.initFeatures.hasFeature(SplicePrototype)) { log.info(s"accepting splice with remote.in.amount=${msg.fundingContribution} remote.in.push=${msg.pushAmount}") + val parentCommitment = d.commitments.latest.commitment val spliceAck = SpliceAck(d.channelId, fundingContribution = 0.sat, // only remote contributes to the splice + fundingPubKey = keyManager.fundingPublicKey(d.commitments.params.localParams.fundingKeyPath, parentCommitment.fundingTxIndex + 1).publicKey, pushAmount = 0.msat, requireConfirmedInputs = nodeParams.channelConf.requireConfirmedInputsForDualFunding ) - val parentCommitment = d.commitments.latest.commitment val nextFundingAmount = parentCommitment.capacity + spliceAck.fundingContribution + msg.fundingContribution val fundingParams = InteractiveTxParams( channelId = d.channelId, isInitiator = false, localContribution = spliceAck.fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(keyManager, d.commitments.params, parentCommitment)), - fundingPubkeyScript = parentCommitment.commitInput.txOut.publicKeyScript, // same pubkey script as before + sharedInput_opt = Some(Multisig2of2Input(parentCommitment)), + remoteFundingPubKey = msg.fundingPubKey, localOutputs = Nil, lockTime = nodeParams.currentBlockHeight.toLong, dustLimit = d.commitments.params.localParams.dustLimit.max(d.commitments.params.remoteParams.dustLimit), @@ -872,8 +875,8 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with isInitiator = true, localContribution = spliceInit.fundingContribution, remoteContribution = msg.fundingContribution, - sharedInput_opt = Some(Multisig2of2Input(keyManager, d.commitments.params, parentCommitment)), - fundingPubkeyScript = parentCommitment.commitInput.txOut.publicKeyScript, // same pubkey script as before + sharedInput_opt = Some(Multisig2of2Input(parentCommitment)), + remoteFundingPubKey = msg.fundingPubKey, localOutputs = cmd.spliceOutputs, lockTime = spliceInit.lockTime, dustLimit = d.commitments.params.localParams.dustLimit.max(d.commitments.params.remoteParams.dustLimit), @@ -955,7 +958,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with d.commitments.latest.localFundingStatus match { case dfu@LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx: PartiallySignedSharedTransaction, _, _) if fundingTx.txId == msg.txId => // we already sent our tx_signatures - InteractiveTxSigningSession.addRemoteSigs(dfu.fundingParams, fundingTx, msg) match { + InteractiveTxSigningSession.addRemoteSigs(keyManager, d.commitments.params, dfu.fundingParams, fundingTx, msg) match { case Left(cause) => log.warning("received invalid tx_signatures for fundingTxId={}: {}", msg.txId, cause.getMessage) // The funding transaction may still confirm (since our peer should be able to generate valid signatures), @@ -975,7 +978,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with d.spliceStatus match { case SpliceStatus.SpliceWaitingForSigs(signingSession) => // we have not yet sent our tx_signatures - signingSession.receiveTxSigs(nodeParams, msg) match { + signingSession.receiveTxSigs(nodeParams, d.commitments.params, msg) match { case Left(f) => rollbackFundingAttempt(signingSession.fundingTx.tx, previousTxs = Seq.empty) // no splice rbf yet stay() using d.copy(spliceStatus = SpliceStatus.SpliceAborted) sending TxAbort(d.channelId, f.getMessage) @@ -1711,7 +1714,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with channelReestablish.nextFundingTxId_opt match { case Some(fundingTxId) if fundingTxId == d.signingSession.fundingTx.txId => // We retransmit our commit_sig, and will send our tx_signatures once we've received their commit_sig. - val commitSig = d.signingSession.remoteCommit.sign(keyManager, d.channelParams, d.signingSession.commitInput) + val commitSig = d.signingSession.remoteCommit.sign(keyManager, d.channelParams, d.signingSession.fundingTxIndex, d.signingSession.fundingParams.remoteFundingPubKey, d.signingSession.commitInput) goto(WAIT_FOR_DUAL_FUNDING_SIGNED) sending commitSig case _ => goto(WAIT_FOR_DUAL_FUNDING_SIGNED) } @@ -1722,13 +1725,13 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with d.rbfStatus match { case RbfStatus.RbfWaitingForSigs(signingSession) if signingSession.fundingTx.txId == fundingTxId => // We retransmit our commit_sig, and will send our tx_signatures once we've received their commit_sig. - val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.commitInput) + val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput) goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending commitSig case _ if d.latestFundingTx.sharedTx.txId == fundingTxId => val toSend = d.latestFundingTx.sharedTx match { case fundingTx: InteractiveTxBuilder.PartiallySignedSharedTransaction => // We have not received their tx_signatures: we retransmit our commit_sig because we don't know if they received it. - val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.commitInput) + val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput) Seq(commitSig, fundingTx.localSigs) case fundingTx: InteractiveTxBuilder.FullySignedSharedTransaction => // We've already received their tx_signatures, which means they've received and stored our commit_sig, we only need to retransmit our tx_signatures. @@ -1790,7 +1793,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with case SpliceStatus.SpliceWaitingForSigs(signingSession) if signingSession.fundingTx.txId == fundingTxId => // We retransmit our commit_sig, and will send our tx_signatures once we've received their commit_sig. log.info(s"re-sending commit_sig for splice attempt with fundingTxIndex=${signingSession.fundingTxIndex} fundingTxId=${signingSession.fundingTx.txId}") - val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.commitInput) + val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput) sendQueue = sendQueue :+ commitSig d.spliceStatus case _ if d.commitments.latest.fundingTxId == fundingTxId => @@ -1800,7 +1803,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with case fundingTx: InteractiveTxBuilder.PartiallySignedSharedTransaction => // If we have not received their tx_signatures, we can't tell whether they had received our commit_sig, so we need to retransmit it log.info(s"re-sending commit_sig and tx_signatures for fundingTxIndex=${d.commitments.latest.fundingTxIndex} fundingTxId=${d.commitments.latest.fundingTxId}") - val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.commitInput) + val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, d.commitments.latest.commitInput) sendQueue = sendQueue :+ commitSig :+ fundingTx.localSigs case fundingTx: InteractiveTxBuilder.FullySignedSharedTransaction => log.info(s"re-sending tx_signatures for fundingTxIndex=${d.commitments.latest.fundingTxIndex} fundingTxId=${d.commitments.latest.fundingTxId}") @@ -1853,7 +1856,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with // should we (re)send our announcement sigs? if (d.commitments.announceChannel && d.channelAnnouncement.isEmpty) { // BOLT 7: a node SHOULD retransmit the announcement_signatures message if it has not received an announcement_signatures message - val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments.params, realShortChannelId) + val localAnnSigs = Helpers.makeAnnouncementSignatures(nodeParams, d.commitments.params, d.commitments.latest.remoteFundingPubKey, realShortChannelId) sendQueue = sendQueue :+ localAnnSigs } case _ => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala index 7618261649..37a0149d71 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala @@ -18,7 +18,7 @@ package fr.acinq.eclair.channel.fsm import akka.actor.typed.scaladsl.adapter.{ClassicActorContextOps, actorRefAdapter} import com.softwaremill.quicklens.{ModifyPimp, QuicklensAt} -import fr.acinq.bitcoin.scalacompat.{SatoshiLong, Script} +import fr.acinq.bitcoin.scalacompat.SatoshiLong import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fsm.Channel._ @@ -27,7 +27,6 @@ import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningS import fr.acinq.eclair.channel.publish.TxPublisher.SetChannelId import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.io.Peer.OpenChannelResponse -import fr.acinq.eclair.transactions.Scripts import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{MilliSatoshiLong, RealShortChannelId, ToMilliSatoshiConversion, UInt64} @@ -105,7 +104,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { when(WAIT_FOR_INIT_DUAL_FUNDED_CHANNEL)(handleExceptions { case Event(input: INPUT_INIT_CHANNEL_INITIATOR, _) => - val fundingPubKey = keyManager.fundingPublicKey(input.localParams.fundingKeyPath).publicKey + val fundingPubKey = keyManager.fundingPublicKey(input.localParams.fundingKeyPath, fundingTxIndex = 0).publicKey val channelKeyPath = keyManager.keyPath(input.localParams, input.channelConfig) val upfrontShutdownScript_opt = input.localParams.upfrontShutdownScript_opt.map(scriptPubKey => ChannelTlv.UpfrontShutdownScriptTlv(scriptPubKey)) val tlvs: Set[OpenDualFundedChannelTlv] = Set( @@ -153,7 +152,6 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { htlcMinimum = open.htlcMinimum, toSelfDelay = open.toSelfDelay, maxAcceptedHtlcs = open.maxAcceptedHtlcs, - fundingPubKey = open.fundingPubkey, revocationBasepoint = open.revocationBasepoint, paymentBasepoint = open.paymentBasepoint, delayedPaymentBasepoint = open.delayedPaymentBasepoint, @@ -161,7 +159,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { initFeatures = remoteInit.features, upfrontShutdownScript_opt = remoteShutdownScript) log.debug("remote params: {}", remoteParams) - val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey + val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath, fundingTxIndex = 0).publicKey val channelKeyPath = keyManager.keyPath(localParams, d.init.channelConfig) val revocationBasePoint = keyManager.revocationPoint(channelKeyPath).publicKey // We've exchanged open_channel2 and accept_channel2, we now know the final channelId. @@ -198,14 +196,13 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { txPublisher ! SetChannelId(remoteNodeId, channelId) context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, accept.temporaryChannelId, channelId)) // We start the interactive-tx funding protocol. - val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey, remoteParams.fundingPubKey))) val fundingParams = InteractiveTxParams( channelId = channelId, isInitiator = localParams.isInitiator, localContribution = accept.fundingAmount, remoteContribution = open.fundingAmount, sharedInput_opt = None, - fundingPubkeyScript = fundingPubkeyScript, + remoteFundingPubKey = open.fundingPubkey, localOutputs = Nil, lockTime = open.lockTime, dustLimit = open.dustLimit.max(accept.dustLimit), @@ -252,7 +249,6 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { htlcMinimum = accept.htlcMinimum, toSelfDelay = accept.toSelfDelay, maxAcceptedHtlcs = accept.maxAcceptedHtlcs, - fundingPubKey = accept.fundingPubkey, revocationBasepoint = accept.revocationBasepoint, paymentBasepoint = accept.paymentBasepoint, delayedPaymentBasepoint = accept.delayedPaymentBasepoint, @@ -261,8 +257,6 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { upfrontShutdownScript_opt = remoteShutdownScript) log.debug("remote params: {}", remoteParams) // We start the interactive-tx funding protocol. - val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath) - val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey.publicKey, remoteParams.fundingPubKey))) val channelParams = ChannelParams(channelId, d.init.channelConfig, channelFeatures, localParams, remoteParams, d.lastSent.channelFlags) val localAmount = d.lastSent.fundingAmount val remoteAmount = accept.fundingAmount @@ -273,7 +267,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { localContribution = localAmount, remoteContribution = remoteAmount, sharedInput_opt = None, - fundingPubkeyScript = fundingPubkeyScript, + remoteFundingPubKey = accept.fundingPubkey, localOutputs = Nil, lockTime = d.lastSent.lockTime, dustLimit = d.lastSent.dustLimit.max(accept.dustLimit), @@ -400,7 +394,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { case Event(msg: InteractiveTxMessage, d: DATA_WAIT_FOR_DUAL_FUNDING_SIGNED) => msg match { case txSigs: TxSignatures => - d.signingSession.receiveTxSigs(nodeParams, txSigs) match { + d.signingSession.receiveTxSigs(nodeParams, d.channelParams, txSigs) match { case Left(f) => rollbackFundingAttempt(d.signingSession.fundingTx.tx, Nil) goto(CLOSED) sending Error(d.channelId, f.getMessage) @@ -448,7 +442,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { when(WAIT_FOR_DUAL_FUNDING_CONFIRMED)(handleExceptions { case Event(txSigs: TxSignatures, d: DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED) => d.latestFundingTx.sharedTx match { - case fundingTx: PartiallySignedSharedTransaction => InteractiveTxSigningSession.addRemoteSigs(d.latestFundingTx.fundingParams, fundingTx, txSigs) match { + case fundingTx: PartiallySignedSharedTransaction => InteractiveTxSigningSession.addRemoteSigs(keyManager, d.commitments.params, d.latestFundingTx.fundingParams, fundingTx, txSigs) match { case Left(cause) => val unsignedFundingTx = fundingTx.tx.buildUnsignedTx() log.warning("received invalid tx_signatures for txid={} (current funding txid={}): {}", txSigs.txId, unsignedFundingTx.txid, cause.getMessage) @@ -464,7 +458,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { case _: FullySignedSharedTransaction => d.rbfStatus match { case RbfStatus.RbfWaitingForSigs(signingSession) => - signingSession.receiveTxSigs(nodeParams, txSigs) match { + signingSession.receiveTxSigs(nodeParams, d.commitments.params, txSigs) match { case Left(f) => rollbackRbfAttempt(signingSession, d) stay() using d.copy(rbfStatus = RbfStatus.RbfAborted) sending TxAbort(d.channelId, f.getMessage) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala index 0318ff7ff3..3f25da6f1c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala @@ -73,7 +73,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { when(WAIT_FOR_INIT_SINGLE_FUNDED_CHANNEL)(handleExceptions { case Event(input: INPUT_INIT_CHANNEL_INITIATOR, _) => - val fundingPubKey = keyManager.fundingPublicKey(input.localParams.fundingKeyPath).publicKey + val fundingPubKey = keyManager.fundingPublicKey(input.localParams.fundingKeyPath, fundingTxIndex = 0).publicKey val channelKeyPath = keyManager.keyPath(input.localParams, input.channelConfig) // In order to allow TLV extensions and keep backwards-compatibility, we include an empty upfront_shutdown_script if this feature is not used // See https://github.com/lightningnetwork/lightning-rfc/pull/714. @@ -118,7 +118,6 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { htlcMinimum = open.htlcMinimumMsat, toSelfDelay = open.toSelfDelay, maxAcceptedHtlcs = open.maxAcceptedHtlcs, - fundingPubKey = open.fundingPubkey, revocationBasepoint = open.revocationBasepoint, paymentBasepoint = open.paymentBasepoint, delayedPaymentBasepoint = open.delayedPaymentBasepoint, @@ -126,7 +125,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { initFeatures = d.initFundee.remoteInit.features, upfrontShutdownScript_opt = remoteShutdownScript) log.debug("remote params: {}", remoteParams) - val fundingPubkey = keyManager.fundingPublicKey(d.initFundee.localParams.fundingKeyPath).publicKey + val fundingPubkey = keyManager.fundingPublicKey(d.initFundee.localParams.fundingKeyPath, fundingTxIndex = 0).publicKey val channelKeyPath = keyManager.keyPath(d.initFundee.localParams, d.initFundee.channelConfig) val params = ChannelParams(d.initFundee.temporaryChannelId, d.initFundee.channelConfig, channelFeatures, d.initFundee.localParams, remoteParams, open.channelFlags) val minimumDepth = params.minDepthFundee(nodeParams.channelConf.minDepthBlocks, open.fundingSatoshis) @@ -152,7 +151,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { ChannelTlv.UpfrontShutdownScriptTlv(localShutdownScript), ChannelTlv.ChannelTypeTlv(d.initFundee.channelType) )) - goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(params, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, open.firstPerCommitmentPoint) sending accept + goto(WAIT_FOR_FUNDING_CREATED) using DATA_WAIT_FOR_FUNDING_CREATED(params, open.fundingSatoshis, open.pushMsat, open.feeratePerKw, open.fundingPubkey, open.firstPerCommitmentPoint) sending accept } case Event(c: CloseCommand, d) => handleFastClose(c, d.channelId) @@ -177,7 +176,6 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { htlcMinimum = accept.htlcMinimumMsat, toSelfDelay = accept.toSelfDelay, maxAcceptedHtlcs = accept.maxAcceptedHtlcs, - fundingPubKey = accept.fundingPubkey, revocationBasepoint = accept.revocationBasepoint, paymentBasepoint = accept.paymentBasepoint, delayedPaymentBasepoint = accept.delayedPaymentBasepoint, @@ -186,11 +184,11 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { upfrontShutdownScript_opt = remoteShutdownScript) log.debug("remote params: {}", remoteParams) log.info("remote will use fundingMinDepth={}", accept.minimumDepth) - val localFundingPubkey = keyManager.fundingPublicKey(init.localParams.fundingKeyPath) - val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey.publicKey, remoteParams.fundingPubKey))) + val localFundingPubkey = keyManager.fundingPublicKey(init.localParams.fundingKeyPath, fundingTxIndex = 0) + val fundingPubkeyScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubkey.publicKey, accept.fundingPubkey))) wallet.makeFundingTx(fundingPubkeyScript, init.fundingAmount, init.fundingTxFeerate).pipeTo(self) val params = ChannelParams(init.temporaryChannelId, init.channelConfig, channelFeatures, init.localParams, remoteParams, open.channelFlags) - goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(params, init.fundingAmount, init.pushAmount_opt.getOrElse(0 msat), init.commitTxFeerate, accept.firstPerCommitmentPoint, d.initFunder.replyTo) + goto(WAIT_FOR_FUNDING_INTERNAL) using DATA_WAIT_FOR_FUNDING_INTERNAL(params, init.fundingAmount, init.pushAmount_opt.getOrElse(0 msat), init.commitTxFeerate, accept.fundingPubkey, accept.firstPerCommitmentPoint, d.initFunder.replyTo) } case Event(c: CloseCommand, d: DATA_WAIT_FOR_ACCEPT_CHANNEL) => @@ -211,14 +209,14 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { }) when(WAIT_FOR_FUNDING_INTERNAL)(handleExceptions { - case Event(MakeFundingTxResponse(fundingTx, fundingTxOutputIndex, fundingTxFee), d@DATA_WAIT_FOR_FUNDING_INTERNAL(params, fundingAmount, pushMsat, commitTxFeerate, remoteFirstPerCommitmentPoint, replyTo)) => + case Event(MakeFundingTxResponse(fundingTx, fundingTxOutputIndex, fundingTxFee), d@DATA_WAIT_FOR_FUNDING_INTERNAL(params, fundingAmount, pushMsat, commitTxFeerate, remoteFundingPubKey, remoteFirstPerCommitmentPoint, replyTo)) => val temporaryChannelId = params.channelId // let's create the first commitment tx that spends the yet uncommitted funding tx - Funding.makeFirstCommitTxs(keyManager, params, localFundingAmount = fundingAmount, remoteFundingAmount = 0 sat, localPushAmount = pushMsat, remotePushAmount = 0 msat, commitTxFeerate, fundingTx.hash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint) match { + Funding.makeFirstCommitTxs(keyManager, params, localFundingAmount = fundingAmount, remoteFundingAmount = 0 sat, localPushAmount = pushMsat, remotePushAmount = 0 msat, commitTxFeerate, fundingTx.hash, fundingTxOutputIndex, remoteFundingPubKey = remoteFundingPubKey, remoteFirstPerCommitmentPoint = remoteFirstPerCommitmentPoint) match { case Left(ex) => handleLocalError(ex, d, None) case Right((localSpec, localCommitTx, remoteSpec, remoteCommitTx)) => require(fundingTx.txOut(fundingTxOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, s"pubkey script mismatch!") - val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath), TxOwner.Remote, params.channelFeatures.commitmentFormat) + val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex = 0), TxOwner.Remote, params.channelFeatures.commitmentFormat) // signature of their initial commitment tx that pays remote pushMsat val fundingCreated = FundingCreated( temporaryChannelId = temporaryChannelId, @@ -232,7 +230,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { txPublisher ! SetChannelId(remoteNodeId, channelId) context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId)) // NB: we don't send a ChannelSignatureSent for the first commit - goto(WAIT_FOR_FUNDING_SIGNED) using DATA_WAIT_FOR_FUNDING_SIGNED(params1, fundingTx, fundingTxFee, localSpec, localCommitTx, RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), fundingCreated, replyTo) sending fundingCreated + goto(WAIT_FOR_FUNDING_SIGNED) using DATA_WAIT_FOR_FUNDING_SIGNED(params1, remoteFundingPubKey, fundingTx, fundingTxFee, localSpec, localCommitTx, RemoteCommit(0, remoteSpec, remoteCommitTx.tx.txid, remoteFirstPerCommitmentPoint), fundingCreated, replyTo) sending fundingCreated } case Event(Status.Failure(t), d: DATA_WAIT_FOR_FUNDING_INTERNAL) => @@ -258,16 +256,16 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { }) when(WAIT_FOR_FUNDING_CREATED)(handleExceptions { - case Event(FundingCreated(_, fundingTxHash, fundingTxOutputIndex, remoteSig, _), d@DATA_WAIT_FOR_FUNDING_CREATED(params, fundingAmount, pushMsat, commitTxFeerate, remoteFirstPerCommitmentPoint)) => + case Event(FundingCreated(_, fundingTxHash, fundingTxOutputIndex, remoteSig, _), d@DATA_WAIT_FOR_FUNDING_CREATED(params, fundingAmount, pushMsat, commitTxFeerate, remoteFundingPubKey, remoteFirstPerCommitmentPoint)) => val temporaryChannelId = params.channelId // they fund the channel with their funding tx, so the money is theirs (but we are paid pushMsat) - Funding.makeFirstCommitTxs(keyManager, params, localFundingAmount = 0 sat, remoteFundingAmount = fundingAmount, localPushAmount = 0 msat, remotePushAmount = pushMsat, commitTxFeerate, fundingTxHash, fundingTxOutputIndex, remoteFirstPerCommitmentPoint) match { + Funding.makeFirstCommitTxs(keyManager, params, localFundingAmount = 0 sat, remoteFundingAmount = fundingAmount, localPushAmount = 0 msat, remotePushAmount = pushMsat, commitTxFeerate, fundingTxHash, fundingTxOutputIndex, remoteFundingPubKey = remoteFundingPubKey, remoteFirstPerCommitmentPoint = remoteFirstPerCommitmentPoint) match { case Left(ex) => handleLocalError(ex, d, None) case Right((localSpec, localCommitTx, remoteSpec, remoteCommitTx)) => // check remote signature validity - val fundingPubKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath) + val fundingPubKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex = 0) val localSigOfLocalTx = keyManager.sign(localCommitTx, fundingPubKey, TxOwner.Local, params.channelFeatures.commitmentFormat) - val signedLocalCommitTx = Transactions.addSigs(localCommitTx, fundingPubKey.publicKey, params.remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) + val signedLocalCommitTx = Transactions.addSigs(localCommitTx, fundingPubKey.publicKey, remoteFundingPubKey, localSigOfLocalTx, remoteSig) Transactions.checkSpendable(signedLocalCommitTx) match { case Failure(_) => handleLocalError(InvalidCommitmentSignature(temporaryChannelId, signedLocalCommitTx.tx.txid), d, None) case Success(_) => @@ -279,6 +277,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { ) val commitment = Commitment( fundingTxIndex = 0, + remoteFundingPubKey = remoteFundingPubKey, localFundingStatus = SingleFundedUnconfirmedFundingTx(None), remoteFundingStatus = RemoteFundingStatus.NotLocked, localCommit = LocalCommit(0, localSpec, CommitTxAndRemoteSig(localCommitTx, remoteSig), htlcTxsAndRemoteSigs = Nil), @@ -310,11 +309,11 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { }) when(WAIT_FOR_FUNDING_SIGNED)(handleExceptions { - case Event(msg@FundingSigned(_, remoteSig, _), d@DATA_WAIT_FOR_FUNDING_SIGNED(params, fundingTx, fundingTxFee, localSpec, localCommitTx, remoteCommit, fundingCreated, _)) => + case Event(msg@FundingSigned(_, remoteSig, _), d@DATA_WAIT_FOR_FUNDING_SIGNED(params, remoteFundingPubKey, fundingTx, fundingTxFee, localSpec, localCommitTx, remoteCommit, fundingCreated, _)) => // we make sure that their sig checks out and that our first commit tx is spendable - val fundingPubKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath) + val fundingPubKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex = 0) val localSigOfLocalTx = keyManager.sign(localCommitTx, fundingPubKey, TxOwner.Local, params.channelFeatures.commitmentFormat) - val signedLocalCommitTx = Transactions.addSigs(localCommitTx, fundingPubKey.publicKey, params.remoteParams.fundingPubKey, localSigOfLocalTx, remoteSig) + val signedLocalCommitTx = Transactions.addSigs(localCommitTx, fundingPubKey.publicKey, remoteFundingPubKey, localSigOfLocalTx, remoteSig) Transactions.checkSpendable(signedLocalCommitTx) match { case Failure(cause) => // we rollback the funding tx, it will never be published @@ -324,6 +323,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers { case Success(_) => val commitment = Commitment( fundingTxIndex = 0, + remoteFundingPubKey = remoteFundingPubKey, localFundingStatus = SingleFundedUnconfirmedFundingTx(Some(fundingTx)), remoteFundingStatus = RemoteFundingStatus.NotLocked, localCommit = LocalCommit(0, localSpec, CommitTxAndRemoteSig(localCommitTx, remoteSig), htlcTxsAndRemoteSigs = Nil), diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala index 221521ce30..a41ecc3a05 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxBuilder.scala @@ -106,20 +106,20 @@ object InteractiveTxBuilder { // @formatter:on } - case class Multisig2of2Input(info: InputInfo, localFundingPubkey: PublicKey, remoteFundingPubkey: PublicKey) extends SharedFundingInput { + case class Multisig2of2Input(info: InputInfo, fundingTxIndex: Long, remoteFundingPubkey: PublicKey) extends SharedFundingInput { override val weight: Int = 388 override def sign(keyManager: ChannelKeyManager, params: ChannelParams, tx: Transaction): ByteVector64 = { - val fundingPubkey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath) - keyManager.sign(Transactions.SpliceTx(info, tx), fundingPubkey, TxOwner.Local, params.channelFeatures.commitmentFormat) + val localFundingPubkey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex) + keyManager.sign(Transactions.SpliceTx(info, tx), localFundingPubkey, TxOwner.Local, params.channelFeatures.commitmentFormat) } } object Multisig2of2Input { - def apply(keyManager: ChannelKeyManager, params: ChannelParams, commitment: Commitment): Multisig2of2Input = Multisig2of2Input( + def apply(commitment: Commitment): Multisig2of2Input = Multisig2of2Input( info = commitment.commitInput, - localFundingPubkey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath).publicKey, - remoteFundingPubkey = params.remoteParams.fundingPubKey + fundingTxIndex = commitment.fundingTxIndex, + remoteFundingPubkey = commitment.remoteFundingPubKey ) } @@ -129,7 +129,7 @@ object InteractiveTxBuilder { * @param localContribution amount contributed by us to the shared output (can be negative when removing funds from an existing channel). * @param remoteContribution amount contributed by our peer to the shared output (can be negative when removing funds from an existing channel). * @param sharedInput_opt previous input shared between the two participants (e.g. previous funding output when splicing). - * @param fundingPubkeyScript script of the shared output. + * @param remoteFundingPubKey public key provided by our peer, that will be combined with ours to create the script of the shared output. * @param localOutputs outputs to be added to the shared transaction (e.g. splice-out). * @param lockTime transaction lock time. * @param dustLimit outputs below this value are considered invalid. @@ -141,7 +141,7 @@ object InteractiveTxBuilder { localContribution: Satoshi, remoteContribution: Satoshi, sharedInput_opt: Option[SharedFundingInput], - fundingPubkeyScript: ByteVector, + remoteFundingPubKey: PublicKey, localOutputs: List[TxOut], lockTime: Long, dustLimit: Satoshi, @@ -165,6 +165,7 @@ object InteractiveTxBuilder { def remoteCommitIndex: Long def remotePerCommitmentPoint: PublicKey def commitTxFeerate: FeeratePerKw + def fundingTxIndex: Long } case class FundingTx(commitTxFeerate: FeeratePerKw, remotePerCommitmentPoint: PublicKey) extends Purpose { override val previousLocalBalance: MilliSatoshi = 0 msat @@ -172,29 +173,31 @@ object InteractiveTxBuilder { override val previousFundingAmount: Satoshi = 0 sat override val localCommitIndex: Long = 0 override val remoteCommitIndex: Long = 0 - } - case class SpliceTx(commitment: Commitment) extends Purpose { - // Note that previous balances are truncated, which can give away 1 sat as mining fees. - override val previousLocalBalance: MilliSatoshi = commitment.localCommit.spec.toLocal - override val previousRemoteBalance: MilliSatoshi = commitment.remoteCommit.spec.toLocal - override val previousFundingAmount: Satoshi = commitment.capacity - override val localCommitIndex: Long = commitment.localCommit.index - override val remoteCommitIndex: Long = commitment.remoteCommit.index - override val remotePerCommitmentPoint: PublicKey = commitment.remoteCommit.remotePerCommitmentPoint - override val commitTxFeerate: FeeratePerKw = commitment.localCommit.spec.commitTxFeerate + override val fundingTxIndex: Long = 0 + } + case class SpliceTx(parentCommitment: Commitment) extends Purpose { + override val previousLocalBalance: MilliSatoshi = parentCommitment.localCommit.spec.toLocal + override val previousRemoteBalance: MilliSatoshi = parentCommitment.remoteCommit.spec.toLocal + override val previousFundingAmount: Satoshi = parentCommitment.capacity + override val localCommitIndex: Long = parentCommitment.localCommit.index + override val remoteCommitIndex: Long = parentCommitment.remoteCommit.index + override val remotePerCommitmentPoint: PublicKey = parentCommitment.remoteCommit.remotePerCommitmentPoint + override val commitTxFeerate: FeeratePerKw = parentCommitment.localCommit.spec.commitTxFeerate + override val fundingTxIndex: Long = parentCommitment.fundingTxIndex + 1 } /** * @param previousTransactions interactive transactions are replaceable and can be RBF-ed, but we need to make sure that * only one of them ends up confirming. We guarantee this by having the latest transaction * always double-spend all its predecessors. */ - case class PreviousTxRbf(commitment: Commitment, previousLocalBalance: MilliSatoshi, previousRemoteBalance: MilliSatoshi, previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction]) extends Purpose { + case class PreviousTxRbf(replacedCommitment: Commitment, previousLocalBalance: MilliSatoshi, previousRemoteBalance: MilliSatoshi, previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction]) extends Purpose { // Note that the truncation is a no-op: the sum of balances in a channel must be a satoshi amount. override val previousFundingAmount: Satoshi = (previousLocalBalance + previousRemoteBalance).truncateToSatoshi - override val localCommitIndex: Long = commitment.localCommit.index - override val remoteCommitIndex: Long = commitment.remoteCommit.index - override val remotePerCommitmentPoint: PublicKey = commitment.remoteCommit.remotePerCommitmentPoint - override val commitTxFeerate: FeeratePerKw = commitment.localCommit.spec.commitTxFeerate + override val localCommitIndex: Long = replacedCommitment.localCommit.index + override val remoteCommitIndex: Long = replacedCommitment.remoteCommit.index + override val remotePerCommitmentPoint: PublicKey = replacedCommitment.remoteCommit.remotePerCommitmentPoint + override val commitTxFeerate: FeeratePerKw = replacedCommitment.localCommit.spec.commitTxFeerate + override val fundingTxIndex: Long = replacedCommitment.fundingTxIndex } // @formatter:on @@ -380,6 +383,8 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon private val log = context.log private val keyManager = nodeParams.channelKeyManager + private val localFundingPubKey: PublicKey = keyManager.fundingPublicKey(channelParams.localParams.fundingKeyPath, purpose.fundingTxIndex).publicKey + private val fundingPubkeyScript: ByteVector = Script.write(Script.pay2wsh(Scripts.multiSig2of2(localFundingPubKey, fundingParams.remoteFundingPubKey))) private val remoteNodeId = channelParams.remoteParams.nodeId private val previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction] = purpose match { case rbf: PreviousTxRbf => rbf.previousTransactions @@ -387,7 +392,7 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon } def start(): Behavior[Command] = { - val txFunder = context.spawnAnonymous(InteractiveTxFunder(remoteNodeId, fundingParams, purpose, wallet)) + val txFunder = context.spawnAnonymous(InteractiveTxFunder(remoteNodeId, fundingParams, fundingPubkeyScript, purpose, wallet)) txFunder ! InteractiveTxFunder.FundTransaction(context.messageAdapter[InteractiveTxFunder.Response](r => FundTransactionResult(r))) Behaviors.receiveMessagePartial { case FundTransactionResult(result) => result match { @@ -495,11 +500,11 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon Left(DuplicateSerialId(fundingParams.channelId, addOutput.serialId)) } else if (addOutput.amount < fundingParams.dustLimit) { Left(OutputBelowDust(fundingParams.channelId, addOutput.serialId, addOutput.amount, fundingParams.dustLimit)) - } else if (addOutput.pubkeyScript == fundingParams.fundingPubkeyScript && addOutput.amount != fundingParams.fundingAmount) { + } else if (addOutput.pubkeyScript == fundingPubkeyScript && addOutput.amount != fundingParams.fundingAmount) { Left(InvalidSharedOutputAmount(fundingParams.channelId, addOutput.serialId, addOutput.amount, fundingParams.fundingAmount)) } else if (!MutualClose.isValidFinalScriptPubkey(addOutput.pubkeyScript, allowAnySegwit = true)) { Left(InvalidSpliceOutputScript(fundingParams.channelId, addOutput.serialId, addOutput.pubkeyScript)) - } else if (addOutput.pubkeyScript == fundingParams.fundingPubkeyScript) { + } else if (addOutput.pubkeyScript == fundingPubkeyScript) { Right(Output.Shared(addOutput.serialId, addOutput.pubkeyScript, purpose.previousLocalBalance + fundingParams.localContribution, purpose.previousRemoteBalance + fundingParams.remoteContribution)) } else { Right(Output.Remote(addOutput.serialId, addOutput.amount, addOutput.pubkeyScript)) @@ -717,18 +722,22 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon private def signCommitTx(completeTx: SharedTransaction): Behavior[Command] = { val fundingTx = completeTx.buildUnsignedTx() - val fundingOutputIndex = fundingTx.txOut.indexWhere(_.publicKeyScript == fundingParams.fundingPubkeyScript) + val fundingOutputIndex = fundingTx.txOut.indexWhere(_.publicKeyScript == fundingPubkeyScript) Funding.makeCommitTxsWithoutHtlcs(keyManager, channelParams, fundingAmount = fundingParams.fundingAmount, toLocal = completeTx.sharedOutput.localAmount - localPushAmount + remotePushAmount, toRemote = completeTx.sharedOutput.remoteAmount - remotePushAmount + localPushAmount, - purpose.commitTxFeerate, fundingTx.hash, fundingOutputIndex, purpose.remotePerCommitmentPoint, commitmentIndex = purpose.localCommitIndex) match { + purpose.commitTxFeerate, + fundingTxIndex = purpose.fundingTxIndex, + fundingTx.hash, fundingOutputIndex, + remotePerCommitmentPoint = purpose.remotePerCommitmentPoint, remoteFundingPubKey = fundingParams.remoteFundingPubKey, + commitmentIndex = purpose.localCommitIndex) match { case Left(cause) => replyTo ! RemoteFailure(cause) unlockAndStop(completeTx) case Right((localSpec, localCommitTx, remoteSpec, remoteCommitTx)) => require(fundingTx.txOut(fundingOutputIndex).publicKeyScript == localCommitTx.input.txOut.publicKeyScript, "pubkey script mismatch!") - val fundingPubKey = keyManager.fundingPublicKey(channelParams.localParams.fundingKeyPath) + val fundingPubKey = keyManager.fundingPublicKey(channelParams.localParams.fundingKeyPath, purpose.fundingTxIndex) val localSigOfRemoteTx = keyManager.sign(remoteCommitTx, fundingPubKey, TxOwner.Remote, channelParams.channelFeatures.commitmentFormat) val localCommitSig = CommitSig(fundingParams.channelId, localSigOfRemoteTx, Nil) val localCommit = UnsignedLocalCommit(purpose.localCommitIndex, localSpec, localCommitTx, htlcTxs = Nil) @@ -741,13 +750,8 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon signTx(completeTx) Behaviors.receiveMessagePartial { case SignTransactionResult(signedTx) => - log.info("interactive-tx partially signed with {} local inputs, {} remote inputs, {} local outputs and {} remote outputs", signedTx.tx.localInputs.length, signedTx.tx.remoteInputs.length, signedTx.tx.localOutputs.length, signedTx.tx.remoteOutputs.length) - val fundingTxIndex = purpose match { - case _: FundingTx => 0 - case r: PreviousTxRbf => r.commitment.fundingTxIndex - case s: SpliceTx => s.commitment.fundingTxIndex + 1 - } - replyTo ! Succeeded(InteractiveTxSigningSession.WaitingForSigs(fundingParams, fundingTxIndex, signedTx, Left(localCommit), remoteCommit), commitSig) + log.info(s"interactive-tx txid=${signedTx.txId} partially signed with {} local inputs, {} remote inputs, {} local outputs and {} remote outputs", signedTx.tx.localInputs.length, signedTx.tx.remoteInputs.length, signedTx.tx.localOutputs.length, signedTx.tx.remoteOutputs.length) + replyTo ! Succeeded(InteractiveTxSigningSession.WaitingForSigs(fundingParams, purpose.fundingTxIndex, signedTx, Left(localCommit), remoteCommit), commitSig) Behaviors.stopped case WalletFailure(t) => log.error("could not sign funding transaction: ", t) @@ -844,7 +848,7 @@ object InteractiveTxSigningSession { } } - def addRemoteSigs(fundingParams: InteractiveTxParams, partiallySignedTx: PartiallySignedSharedTransaction, remoteSigs: TxSignatures)(implicit log: LoggingAdapter): Either[ChannelException, FullySignedSharedTransaction] = { + def addRemoteSigs(keyManager: ChannelKeyManager, params: ChannelParams, fundingParams: InteractiveTxParams, partiallySignedTx: PartiallySignedSharedTransaction, remoteSigs: TxSignatures)(implicit log: LoggingAdapter): Either[ChannelException, FullySignedSharedTransaction] = { if (partiallySignedTx.tx.localInputs.length != partiallySignedTx.localSigs.witnesses.length) { return Left(InvalidFundingSignature(fundingParams.channelId, Some(partiallySignedTx.txId))) } @@ -855,7 +859,9 @@ object InteractiveTxSigningSession { val sharedSigs_opt = fundingParams.sharedInput_opt match { case Some(sharedInput: Multisig2of2Input) => (partiallySignedTx.localSigs.previousFundingTxSig_opt, remoteSigs.previousFundingTxSig_opt) match { - case (Some(localSig), Some(remoteSig)) => Some(Scripts.witness2of2(localSig, remoteSig, sharedInput.localFundingPubkey, sharedInput.remoteFundingPubkey)) + case (Some(localSig), Some(remoteSig)) => + val localFundingPubkey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, sharedInput.fundingTxIndex).publicKey + Some(Scripts.witness2of2(localSig, remoteSig, localFundingPubkey, sharedInput.remoteFundingPubkey)) case _ => log.info("invalid tx_signatures: missing shared input signatures") return Left(InvalidFundingSignature(fundingParams.channelId, Some(partiallySignedTx.txId))) @@ -899,16 +905,16 @@ object InteractiveTxSigningSession { def receiveCommitSig(nodeParams: NodeParams, channelParams: ChannelParams, remoteCommitSig: CommitSig)(implicit log: LoggingAdapter): Either[ChannelException, InteractiveTxSigningSession] = { localCommit match { case Left(unsignedLocalCommit) => - val fundingPubKey = nodeParams.channelKeyManager.fundingPublicKey(channelParams.localParams.fundingKeyPath) + val fundingPubKey = nodeParams.channelKeyManager.fundingPublicKey(channelParams.localParams.fundingKeyPath, fundingTxIndex) val localSigOfLocalTx = nodeParams.channelKeyManager.sign(unsignedLocalCommit.commitTx, fundingPubKey, TxOwner.Local, channelParams.channelFeatures.commitmentFormat) - val signedLocalCommitTx = Transactions.addSigs(unsignedLocalCommit.commitTx, fundingPubKey.publicKey, channelParams.remoteParams.fundingPubKey, localSigOfLocalTx, remoteCommitSig.signature) + val signedLocalCommitTx = Transactions.addSigs(unsignedLocalCommit.commitTx, fundingPubKey.publicKey, fundingParams.remoteFundingPubKey, localSigOfLocalTx, remoteCommitSig.signature) Transactions.checkSpendable(signedLocalCommitTx) match { case Failure(_) => Left(InvalidCommitmentSignature(fundingParams.channelId, signedLocalCommitTx.tx.txid)) case Success(_) => val signedLocalCommit = LocalCommit(unsignedLocalCommit.index, unsignedLocalCommit.spec, CommitTxAndRemoteSig(unsignedLocalCommit.commitTx, remoteCommitSig.signature), htlcTxsAndRemoteSigs = Nil) if (shouldSignFirst(channelParams, fundingTx.tx)) { val fundingStatus = LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx, nodeParams.currentBlockHeight, fundingParams) - val commitment = Commitment(fundingTxIndex, fundingStatus, RemoteFundingStatus.NotLocked, signedLocalCommit, remoteCommit, None) + val commitment = Commitment(fundingTxIndex, fundingParams.remoteFundingPubKey, fundingStatus, RemoteFundingStatus.NotLocked, signedLocalCommit, remoteCommit, None) Right(SendingSigs(fundingStatus, commitment, fundingTx.localSigs)) } else { Right(this.copy(localCommit = Right(signedLocalCommit))) @@ -920,20 +926,20 @@ object InteractiveTxSigningSession { } } - def receiveTxSigs(nodeParams: NodeParams, remoteTxSigs: TxSignatures)(implicit log: LoggingAdapter): Either[ChannelException, SendingSigs] = { + def receiveTxSigs(nodeParams: NodeParams, channelParams: ChannelParams, remoteTxSigs: TxSignatures)(implicit log: LoggingAdapter): Either[ChannelException, SendingSigs] = { localCommit match { case Left(_) => log.info("received tx_signatures before commit_sig") Left(UnexpectedFundingSignatures(fundingParams.channelId)) case Right(signedLocalCommit) => - addRemoteSigs(fundingParams, fundingTx, remoteTxSigs) match { + addRemoteSigs(nodeParams.channelKeyManager, channelParams, fundingParams, fundingTx, remoteTxSigs) match { case Left(f) => log.info("received invalid tx_signatures") Left(f) case Right(fullySignedTx) => log.info("interactive-tx fully signed with {} local inputs, {} remote inputs, {} local outputs and {} remote outputs", fullySignedTx.tx.localInputs.length, fullySignedTx.tx.remoteInputs.length, fullySignedTx.tx.localOutputs.length, fullySignedTx.tx.remoteOutputs.length) val fundingStatus = LocalFundingStatus.DualFundedUnconfirmedFundingTx(fullySignedTx, nodeParams.currentBlockHeight, fundingParams) - val commitment = Commitment(fundingTxIndex, fundingStatus, RemoteFundingStatus.NotLocked, signedLocalCommit, remoteCommit, None) + val commitment = Commitment(fundingTxIndex, fundingParams.remoteFundingPubKey, fundingStatus, RemoteFundingStatus.NotLocked, signedLocalCommit, remoteCommit, None) Right(SendingSigs(fundingStatus, commitment, fullySignedTx.localSigs)) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxFunder.scala index 4ec969520a..3c02183db8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fund/InteractiveTxFunder.scala @@ -54,12 +54,12 @@ object InteractiveTxFunder { case object FundingFailed extends Response // @formatter:on - def apply(remoteNodeId: PublicKey, fundingParams: InteractiveTxParams, purpose: InteractiveTxBuilder.Purpose, wallet: OnChainChannelFunder)(implicit ec: ExecutionContext): Behavior[Command] = { + def apply(remoteNodeId: PublicKey, fundingParams: InteractiveTxParams, fundingPubkeyScript: ByteVector, purpose: InteractiveTxBuilder.Purpose, wallet: OnChainChannelFunder)(implicit ec: ExecutionContext): Behavior[Command] = { Behaviors.setup { context => Behaviors.withMdc(Logs.mdc(remoteNodeId_opt = Some(remoteNodeId), channelId_opt = Some(fundingParams.channelId))) { Behaviors.receiveMessagePartial { case FundTransaction(replyTo) => - val actor = new InteractiveTxFunder(replyTo, fundingParams, purpose, wallet, context) + val actor = new InteractiveTxFunder(replyTo, fundingParams, fundingPubkeyScript, purpose, wallet, context) actor.start() } } @@ -127,6 +127,7 @@ object InteractiveTxFunder { private class InteractiveTxFunder(replyTo: ActorRef[InteractiveTxFunder.Response], fundingParams: InteractiveTxParams, + fundingPubkeyScript: ByteVector, purpose: InteractiveTxBuilder.Purpose, wallet: OnChainChannelFunder, context: ActorContext[InteractiveTxFunder.Command])(implicit ec: ExecutionContext) { @@ -156,7 +157,7 @@ private class InteractiveTxFunder(replyTo: ActorRef[InteractiveTxFunder.Response // contribute to the RBF attempt. if (fundingParams.isInitiator) { val sharedInput = fundingParams.sharedInput_opt.toSeq.map(sharedInput => Input.Shared(UInt64(0), sharedInput.info.outPoint, 0xfffffffdL, purpose.previousLocalBalance, purpose.previousRemoteBalance)) - val sharedOutput = Output.Shared(UInt64(0), fundingParams.fundingPubkeyScript, purpose.previousLocalBalance + fundingParams.localContribution, purpose.previousRemoteBalance + fundingParams.remoteContribution) + val sharedOutput = Output.Shared(UInt64(0), fundingPubkeyScript, purpose.previousLocalBalance + fundingParams.localContribution, purpose.previousRemoteBalance + fundingParams.remoteContribution) val nonChangeOutputs = fundingParams.localOutputs.map(txOut => Output.Local.NonChange(UInt64(0), txOut.amount, txOut.publicKeyScript)) val fundingContributions = sortFundingContributions(fundingParams, sharedInput ++ previousWalletInputs, sharedOutput +: nonChangeOutputs) replyTo ! fundingContributions @@ -172,7 +173,7 @@ private class InteractiveTxFunder(replyTo: ActorRef[InteractiveTxFunder.Response // funding amount to our shared output to make sure bitcoind adds what is required for our local contribution. // We always include the shared input in our transaction and will let bitcoind make sure the target feerate is reached. // Note that if the shared output amount is smaller than the dust limit, bitcoind will reject the funding attempt. - val sharedTxOut = TxOut(purpose.previousFundingAmount + fundingParams.localContribution, fundingParams.fundingPubkeyScript) + val sharedTxOut = TxOut(purpose.previousFundingAmount + fundingParams.localContribution, fundingPubkeyScript) val sharedTxIn = fundingParams.sharedInput_opt.toSeq.map(sharedInput => TxIn(sharedInput.info.outPoint, ByteVector.empty, 0xfffffffdL)) val previousWalletTxIn = previousWalletInputs.map(i => TxIn(i.outPoint, ByteVector.empty, i.sequence)) val dummyTx = Transaction(2, sharedTxIn ++ previousWalletTxIn, sharedTxOut +: fundingParams.localOutputs, fundingParams.lockTime) @@ -219,7 +220,7 @@ private class InteractiveTxFunder(replyTo: ActorRef[InteractiveTxFunder.Response case inputDetails: InputDetails if inputDetails.unusableInputs.isEmpty => // This funding iteration did not add any unusable inputs, so we can directly return the results. // The transaction should still contain the funding output. - if (fundedTx.txOut.count(_.publicKeyScript == fundingParams.fundingPubkeyScript) != 1) { + if (fundedTx.txOut.count(_.publicKeyScript == fundingPubkeyScript) != 1) { log.error("funded transaction is missing the funding output: {}", fundedTx) sendResultAndStop(FundingFailed, fundedTx.txIn.map(_.outPoint).toSet ++ unusableInputs.map(_.outpoint)) } else if (fundingParams.localOutputs.exists(o => !fundedTx.txOut.contains(o))) { @@ -231,7 +232,7 @@ private class InteractiveTxFunder(replyTo: ActorRef[InteractiveTxFunder.Response val fundingContributions = if (fundingParams.isInitiator) { // The initiator is responsible for adding the shared output and the shared input. val inputs = inputDetails.usableInputs - val fundingOutput = Output.Shared(UInt64(0), fundingParams.fundingPubkeyScript, purpose.previousLocalBalance + fundingParams.localContribution, purpose.previousRemoteBalance + fundingParams.remoteContribution) + val fundingOutput = Output.Shared(UInt64(0), fundingPubkeyScript, purpose.previousLocalBalance + fundingParams.localContribution, purpose.previousRemoteBalance + fundingParams.remoteContribution) val outputs = Seq(fundingOutput) ++ nonChangeOutputs ++ changeOutput_opt.toSeq sortFundingContributions(fundingParams, inputs, outputs) } else { @@ -245,7 +246,7 @@ private class InteractiveTxFunder(replyTo: ActorRef[InteractiveTxFunder.Response // complexity of adding a change output, which would require a call to bitcoind to get a change address. val outputs = changeOutput_opt match { case Some(changeOutput) => - val txWeightWithoutInput = Transaction(2, Nil, Seq(TxOut(fundingParams.fundingAmount, fundingParams.fundingPubkeyScript)), 0).weight() + val txWeightWithoutInput = Transaction(2, Nil, Seq(TxOut(fundingParams.fundingAmount, fundingPubkeyScript)), 0).weight() val commonWeight = fundingParams.sharedInput_opt match { case Some(sharedInput) => sharedInput.weight + txWeightWithoutInput case None => txWeightWithoutInput @@ -267,7 +268,7 @@ private class InteractiveTxFunder(replyTo: ActorRef[InteractiveTxFunder.Response txIn = fundedTx.txIn.filter(txIn => !inputDetails.unusableInputs.map(_.outpoint).contains(txIn.outPoint)), // We remove the change output added by this funding iteration. txOut = fundedTx.txOut.filter { - case txOut if txOut.publicKeyScript == fundingParams.fundingPubkeyScript => true // shared output + case txOut if txOut.publicKeyScript == fundingPubkeyScript => true // shared output case txOut if fundingParams.localOutputs.contains(txOut) => true // non-change output case _ => false }, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala index 5c196829f0..78923e13a6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala @@ -283,7 +283,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams, val channelKeyPath = keyManager.keyPath(cmd.commitment.localParams, cmd.commitment.params.channelConfig) fundedTx match { case ClaimLocalAnchorWithWitnessData(anchorTx) => - val localSig = keyManager.sign(anchorTx, keyManager.fundingPublicKey(cmd.commitment.localParams.fundingKeyPath), TxOwner.Local, cmd.commitment.params.commitmentFormat) + val localSig = keyManager.sign(anchorTx, keyManager.fundingPublicKey(cmd.commitment.localParams.fundingKeyPath, cmd.commitment.fundingTxIndex), TxOwner.Local, cmd.commitment.params.commitmentFormat) val signedTx = ClaimLocalAnchorWithWitnessData(addSigs(anchorTx, localSig)) signWalletInputs(signedTx, txFeerate, amountIn) case htlcTx: HtlcWithWitnessData => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/ChannelKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/ChannelKeyManager.scala index e5e4bfdfb6..f37a27a2b7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/ChannelKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/ChannelKeyManager.scala @@ -27,7 +27,7 @@ import java.io.ByteArrayInputStream import java.nio.ByteOrder trait ChannelKeyManager { - def fundingPublicKey(keyPath: DeterministicWallet.KeyPath): ExtendedPublicKey + def fundingPublicKey(keyPath: DeterministicWallet.KeyPath, fundingTxIndex: Long): ExtendedPublicKey def revocationPoint(channelKeyPath: DeterministicWallet.KeyPath): ExtendedPublicKey @@ -44,7 +44,7 @@ trait ChannelKeyManager { def keyPath(localParams: LocalParams, channelConfig: ChannelConfig): DeterministicWallet.KeyPath = { if (channelConfig.hasOption(ChannelConfig.FundingPubKeyBasedChannelKeyPath)) { // deterministic mode: use the funding pubkey to compute the channel key path - ChannelKeyManager.keyPath(fundingPublicKey(localParams.fundingKeyPath)) + ChannelKeyManager.keyPath(fundingPublicKey(localParams.fundingKeyPath, fundingTxIndex = 0)) } else { // legacy mode: we reuse the funding key path as our channel key path localParams.fundingKeyPath diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManager.scala index 421b4051e8..931e72e545 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManager.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManager.scala @@ -80,7 +80,15 @@ class LocalChannelKeyManager(seed: ByteVector, chainHash: ByteVector32) extends DeterministicWallet.KeyPath(Seq(next(), next(), next(), next(), next(), next(), next(), next(), last)) } - override def fundingPublicKey(channelKeyPath: DeterministicWallet.KeyPath): ExtendedPublicKey = publicKeys.get(internalKeyPath(channelKeyPath, hardened(0))) + override def fundingPublicKey(channelKeyPath: DeterministicWallet.KeyPath, fundingTxIndex: Long): ExtendedPublicKey = { + val keyPath = if (fundingTxIndex == 0) { + // For backward-compat with pre-splice channels, we treat the initial funding pubkey differently + internalKeyPath(channelKeyPath, hardened(0)) + } else { + internalKeyPath(channelKeyPath, hardened(6)).derive(fundingTxIndex) + } + publicKeys.get(keyPath) + } override def revocationPoint(channelKeyPath: DeterministicWallet.KeyPath): ExtendedPublicKey = publicKeys.get(internalKeyPath(channelKeyPath, hardened(1))) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala index 5caf1c2f4c..b0282c4fe2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala @@ -47,15 +47,6 @@ private[channel] object ChannelCodecs0 { val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath].decodeOnly - val extendedPrivateKeyCodec: Codec[ExtendedPrivateKey] = ( - ("secretkeybytes" | bytes32) :: - ("chaincode" | bytes32) :: - ("depth" | uint16) :: - ("path" | keyPathCodec) :: - ("parent" | int64)) - .map { case a :: b :: c :: d :: e :: HNil => ExtendedPrivateKey(a, b, c, d, e) } - .decodeOnly - val channelVersionCodec: Codec[ChannelTypes0.ChannelVersion] = discriminatorWithDefault[ChannelTypes0.ChannelVersion]( discriminator = discriminated[ChannelTypes0.ChannelVersion].by(byte) .typecase(0x01, bits(ChannelTypes0.ChannelVersion.LENGTH_BITS).as[ChannelTypes0.ChannelVersion]) @@ -79,7 +70,7 @@ private[channel] object ChannelCodecs0 { ("walletStaticPaymentBasepoint" | optional(provide(channelVersion.paysDirectlyToWallet), publicKey)) :: ("features" | combinedFeaturesCodec)).as[LocalParams].decodeOnly - val remoteParamsCodec: Codec[RemoteParams] = ( + val remoteParamsCodec: Codec[ChannelTypes0.RemoteParams] = ( ("nodeId" | publicKey) :: ("dustLimit" | satoshi) :: ("maxHtlcValueInFlightMsat" | uint64) :: @@ -93,7 +84,7 @@ private[channel] object ChannelCodecs0 { ("delayedPaymentBasepoint" | publicKey) :: ("htlcBasepoint" | publicKey) :: ("features" | combinedFeaturesCodec) :: - ("shutdownScript" | provide[Option[ByteVector]](None))).as[RemoteParams].decodeOnly + ("shutdownScript" | provide[Option[ByteVector]](None))).as[ChannelTypes0.RemoteParams].decodeOnly val updateAddHtlcCodec: Codec[UpdateAddHtlc] = ( ("channelId" | bytes32) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala index 4f35e2f20a..119e25e469 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala @@ -24,8 +24,8 @@ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.CommitmentSpec import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.wire.protocol.CommitSig -import fr.acinq.eclair.{BlockHeight, Features, channel} -import scodec.bits.BitVector +import fr.acinq.eclair.{BlockHeight, CltvExpiryDelta, Features, InitFeature, MilliSatoshi, UInt64, channel} +import scodec.bits.{BitVector, ByteVector} private[channel] object ChannelTypes0 { @@ -180,6 +180,37 @@ private[channel] object ChannelTypes0 { val ANCHOR_OUTPUTS = STATIC_REMOTEKEY | fromBit(USE_ANCHOR_OUTPUTS_BIT) // PUBKEY_KEYPATH + STATIC_REMOTEKEY + ANCHOR_OUTPUTS } + case class RemoteParams(nodeId: PublicKey, + dustLimit: Satoshi, + maxHtlcValueInFlightMsat: UInt64, // this is not MilliSatoshi because it can exceed the total amount of MilliSatoshi + requestedChannelReserve_opt: Option[Satoshi], + htlcMinimum: MilliSatoshi, + toSelfDelay: CltvExpiryDelta, + maxAcceptedHtlcs: Int, + fundingPubKey: PublicKey, + revocationBasepoint: PublicKey, + paymentBasepoint: PublicKey, + delayedPaymentBasepoint: PublicKey, + htlcBasepoint: PublicKey, + initFeatures: Features[InitFeature], + upfrontShutdownScript_opt: Option[ByteVector]) { + def migrate(): channel.RemoteParams = channel.RemoteParams( + nodeId = nodeId, + dustLimit = dustLimit, + maxHtlcValueInFlightMsat = maxHtlcValueInFlightMsat, + requestedChannelReserve_opt = requestedChannelReserve_opt, + htlcMinimum = htlcMinimum, + toSelfDelay = toSelfDelay, + maxAcceptedHtlcs = maxAcceptedHtlcs, + revocationBasepoint = revocationBasepoint, + paymentBasepoint = paymentBasepoint, + delayedPaymentBasepoint = delayedPaymentBasepoint, + htlcBasepoint = htlcBasepoint, + initFeatures = initFeatures, + upfrontShutdownScript_opt = upfrontShutdownScript_opt + ) + } + case class WaitingForRevocation(nextRemoteCommit: RemoteCommit, sent: CommitSig, sentAfterLocalCommitIndex: Long) case class Commitments(channelVersion: ChannelVersion, @@ -207,6 +238,7 @@ private[channel] object ChannelTypes0 { } val commitment = Commitment( fundingTxIndex = 0, + remoteFundingPubKey = remoteParams.fundingPubKey, // We set an empty funding tx, even if it may be confirmed already (and the channel fully operational). We could // have set a specific Unknown status, but it would have forced us to keep it forever. We will retrieve the // funding tx when the channel is instantiated, and update the status (possibly immediately if it was confirmed). @@ -214,7 +246,7 @@ private[channel] object ChannelTypes0 { localCommit.migrate(remoteParams.fundingPubKey), remoteCommit, remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)) ) channel.Commitments( - ChannelParams(channelId, channelConfig, channelFeatures, localParams, remoteParams, channelFlags), + ChannelParams(channelId, channelConfig, channelFeatures, localParams, remoteParams.migrate(), channelFlags), CommitmentChanges(localChanges, remoteChanges, localNextHtlcId, remoteNextHtlcId), Seq(commitment), inactive = Nil, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala index f347fdc6f0..0cc0a62f21 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala @@ -41,15 +41,6 @@ private[channel] object ChannelCodecs1 { val keyPathCodec: Codec[KeyPath] = ("path" | listOfN(uint16, uint32)).xmap[KeyPath](l => KeyPath(l), keyPath => keyPath.path.toList).as[KeyPath] - val extendedPrivateKeyCodec: Codec[ExtendedPrivateKey] = ( - ("secretkeybytes" | bytes32) :: - ("chaincode" | bytes32) :: - ("depth" | uint16) :: - ("path" | keyPathCodec) :: - ("parent" | int64)) - .map { case a :: b :: c :: d :: e :: HNil => ExtendedPrivateKey(a, b, c, d, e) } - .decodeOnly - val channelVersionCodec: Codec[ChannelTypes0.ChannelVersion] = bits(ChannelTypes0.ChannelVersion.LENGTH_BITS).as[ChannelTypes0.ChannelVersion] def localParamsCodec(channelVersion: ChannelTypes0.ChannelVersion): Codec[LocalParams] = ( @@ -66,7 +57,7 @@ private[channel] object ChannelCodecs1 { ("walletStaticPaymentBasepoint" | optional(provide(channelVersion.paysDirectlyToWallet), publicKey)) :: ("features" | combinedFeaturesCodec)).as[LocalParams].decodeOnly - val remoteParamsCodec: Codec[RemoteParams] = ( + val remoteParamsCodec: Codec[ChannelTypes0.RemoteParams] = ( ("nodeId" | publicKey) :: ("dustLimit" | satoshi) :: ("maxHtlcValueInFlightMsat" | uint64) :: @@ -80,7 +71,7 @@ private[channel] object ChannelCodecs1 { ("delayedPaymentBasepoint" | publicKey) :: ("htlcBasepoint" | publicKey) :: ("features" | combinedFeaturesCodec) :: - ("shutdownScript" | provide[Option[ByteVector]](None))).as[RemoteParams] + ("shutdownScript" | provide[Option[ByteVector]](None))).as[ChannelTypes0.RemoteParams] def setCodec[T](codec: Codec[T]): Codec[Set[T]] = listOfN(uint16, codec).xmap(_.toSet, _.toList) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala index dddba75fa6..5a8191fc03 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala @@ -66,7 +66,7 @@ private[channel] object ChannelCodecs2 { ("walletStaticPaymentBasepoint" | optional(provide(channelVersion.paysDirectlyToWallet), publicKey)) :: ("features" | combinedFeaturesCodec)).as[LocalParams] - val remoteParamsCodec: Codec[RemoteParams] = ( + val remoteParamsCodec: Codec[ChannelTypes0.RemoteParams] = ( ("nodeId" | publicKey) :: ("dustLimit" | satoshi) :: ("maxHtlcValueInFlightMsat" | uint64) :: @@ -80,7 +80,7 @@ private[channel] object ChannelCodecs2 { ("delayedPaymentBasepoint" | publicKey) :: ("htlcBasepoint" | publicKey) :: ("features" | combinedFeaturesCodec) :: - ("shutdownScript" | provide[Option[ByteVector]](None))).as[RemoteParams] + ("shutdownScript" | provide[Option[ByteVector]](None))).as[ChannelTypes0.RemoteParams] def setCodec[T](codec: Codec[T]): Codec[Set[T]] = listOfN(uint16, codec).xmap(_.toSet, _.toList) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala index f535a8ab07..9cbed2909f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala @@ -25,6 +25,7 @@ import fr.acinq.eclair.channel.fund.InteractiveTxBuilder._ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions.{CommitmentSpec, DirectedHtlc, IncomingHtlc, OutgoingHtlc} +import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0 import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.LightningMessageCodecs._ import fr.acinq.eclair.wire.protocol.UpdateMessage @@ -77,7 +78,7 @@ private[channel] object ChannelCodecs3 { ("walletStaticPaymentBasepoint" | optional(provide(channelFeatures.paysDirectlyToWallet), publicKey)) :: ("features" | combinedFeaturesCodec)).as[LocalParams] - def remoteParamsCodec(channelFeatures: ChannelFeatures): Codec[RemoteParams] = ( + def remoteParamsCodec(channelFeatures: ChannelFeatures): Codec[ChannelTypes0.RemoteParams] = ( ("nodeId" | publicKey) :: ("dustLimit" | satoshi) :: ("maxHtlcValueInFlightMsat" | uint64) :: @@ -91,7 +92,7 @@ private[channel] object ChannelCodecs3 { ("delayedPaymentBasepoint" | publicKey) :: ("htlcBasepoint" | publicKey) :: ("features" | combinedFeaturesCodec) :: - ("shutdownScript" | optional(bool8, lengthDelimited(bytes)))).as[RemoteParams] + ("shutdownScript" | optional(bool8, lengthDelimited(bytes)))).as[ChannelTypes0.RemoteParams] def setCodec[T](codec: Codec[T]): Codec[Set[T]] = listOfN(uint16, codec).xmap(_.toSet, _.toList) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala index e9b7de2785..2720b5b440 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelTypes3.scala @@ -21,6 +21,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.eclair.channel import fr.acinq.eclair.channel._ import fr.acinq.eclair.crypto.ShaChain +import fr.acinq.eclair.wire.internal.channel.version0.ChannelTypes0 import fr.acinq.eclair.wire.protocol.CommitSig private[channel] object ChannelTypes3 { @@ -31,7 +32,7 @@ private[channel] object ChannelTypes3 { case class Commitments(channelId: ByteVector32, channelConfig: ChannelConfig, channelFeatures: ChannelFeatures, - localParams: LocalParams, remoteParams: RemoteParams, + localParams: LocalParams, remoteParams: ChannelTypes0.RemoteParams, channelFlags: ChannelFlags, localCommit: LocalCommit, remoteCommit: RemoteCommit, localChanges: LocalChanges, remoteChanges: RemoteChanges, @@ -42,9 +43,9 @@ private[channel] object ChannelTypes3 { remoteFundingStatus: RemoteFundingStatus, remotePerCommitmentSecrets: ShaChain) { def migrate(): channel.Commitments = channel.Commitments( - ChannelParams(channelId, channelConfig, channelFeatures, localParams, remoteParams, channelFlags), + ChannelParams(channelId, channelConfig, channelFeatures, localParams, remoteParams.migrate(), channelFlags), CommitmentChanges(localChanges, remoteChanges, localNextHtlcId, remoteNextHtlcId), - Seq(Commitment(fundingTxIndex = 0, localFundingStatus, remoteFundingStatus, localCommit, remoteCommit, remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)))), + Seq(Commitment(fundingTxIndex = 0, remoteFundingPubKey = remoteParams.fundingPubKey, localFundingStatus, remoteFundingStatus, localCommit, remoteCommit, remoteNextCommitInfo.left.toOption.map(w => NextRemoteCommit(w.sent, w.nextRemoteCommit)))), inactive = Nil, remoteNextCommitInfo.fold(w => Left(WaitForRev(w.sentAfterLocalCommitIndex)), remotePerCommitmentPoint => Right(remotePerCommitmentPoint)), remotePerCommitmentSecrets, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala index 3314fb1989..4f7b4f55b7 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala @@ -69,7 +69,6 @@ private[channel] object ChannelCodecs4 { ("htlcMinimum" | millisatoshi) :: ("toSelfDelay" | cltvExpiryDelta) :: ("maxAcceptedHtlcs" | uint16) :: - ("fundingPubKey" | publicKey) :: ("revocationBasepoint" | publicKey) :: ("paymentBasepoint" | publicKey) :: ("delayedPaymentBasepoint" | publicKey) :: @@ -213,7 +212,7 @@ private[channel] object ChannelCodecs4 { private val multisig2of2InputCodec: Codec[InteractiveTxBuilder.Multisig2of2Input] = ( ("info" | inputInfoCodec) :: - ("localFundingPubkey" | publicKey) :: + ("fundingTxIndex" | uint32) :: ("remoteFundingPubkey" | publicKey)).as[InteractiveTxBuilder.Multisig2of2Input] private val sharedFundingInputCodec: Codec[InteractiveTxBuilder.SharedFundingInput] = discriminated[InteractiveTxBuilder.SharedFundingInput].by(uint16) @@ -227,7 +226,7 @@ private[channel] object ChannelCodecs4 { ("localContribution" | satoshiSigned) :: ("remoteContribution" | satoshiSigned) :: ("sharedInput_opt" | optional(bool8, sharedFundingInputCodec)) :: - ("fundingPubkeyScript" | lengthDelimited(bytes)) :: + ("remoteFundingPubKey" | publicKey) :: ("localOutputs" | listOfN(uint16, txOutCodec)) :: ("lockTime" | uint32) :: ("dustLimit" | satoshi) :: @@ -354,6 +353,7 @@ private[channel] object ChannelCodecs4 { private def commitmentCodec(htlcs: Set[DirectedHtlc]): Codec[Commitment] = ( ("fundingTxIndex" | uint32) :: + ("fundingPubKey" | publicKey) :: ("fundingTxStatus" | fundingTxStatusCodec) :: ("remoteFundingStatus" | remoteFundingStatusCodec) :: ("localCommit" | localCommitCodec(minimalCommitmentSpecCodec(htlcs))) :: diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala index 702de9ca94..2fdaa0bc8b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala @@ -405,13 +405,15 @@ object LightningMessageCodecs { val spliceInitCodec: Codec[SpliceInit] = ( ("channelId" | bytes32) :: ("fundingContribution" | satoshiSigned) :: - ("lockTime" | uint32) :: ("feerate" | feeratePerKw) :: + ("lockTime" | uint32) :: + ("fundingPubkey" | publicKey) :: ("tlvStream" | SpliceInitTlv.spliceInitTlvCodec)).as[SpliceInit] val spliceAckCodec: Codec[SpliceAck] = ( ("channelId" | bytes32) :: ("fundingContribution" | satoshiSigned) :: + ("fundingPubkey" | publicKey) :: ("tlvStream" | SpliceAckTlv.spliceAckTlvCodec)).as[SpliceAck] val spliceLockedCodec: Codec[SpliceLocked] = ( diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index ad0dda7ff3..1086a3e59d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -280,37 +280,39 @@ case class ChannelReady(channelId: ByteVector32, case class SpliceInit(channelId: ByteVector32, fundingContribution: Satoshi, - lockTime: Long, feerate: FeeratePerKw, + lockTime: Long, + fundingPubKey: PublicKey, tlvStream: TlvStream[SpliceInitTlv] = TlvStream.empty) extends ChannelMessage with HasChannelId { val requireConfirmedInputs: Boolean = tlvStream.get[ChannelTlv.RequireConfirmedInputsTlv].nonEmpty val pushAmount: MilliSatoshi = tlvStream.get[ChannelTlv.PushAmountTlv].map(_.amount).getOrElse(0 msat) } object SpliceInit { - def apply(channelId: ByteVector32, fundingContribution: Satoshi, lockTime: Long, feerate: FeeratePerKw, pushAmount: MilliSatoshi, requireConfirmedInputs: Boolean): SpliceInit = { + def apply(channelId: ByteVector32, fundingContribution: Satoshi, lockTime: Long, feerate: FeeratePerKw, fundingPubKey: PublicKey, pushAmount: MilliSatoshi, requireConfirmedInputs: Boolean): SpliceInit = { val tlvs: Set[SpliceInitTlv] = Set( Some(ChannelTlv.PushAmountTlv(pushAmount)), if (requireConfirmedInputs) Some(ChannelTlv.RequireConfirmedInputsTlv()) else None, ).flatten - SpliceInit(channelId, fundingContribution, lockTime, feerate, TlvStream(tlvs)) + SpliceInit(channelId, fundingContribution, feerate, lockTime, fundingPubKey, TlvStream(tlvs)) } } case class SpliceAck(channelId: ByteVector32, fundingContribution: Satoshi, + fundingPubKey: PublicKey, tlvStream: TlvStream[SpliceAckTlv] = TlvStream.empty) extends ChannelMessage with HasChannelId { val requireConfirmedInputs: Boolean = tlvStream.get[ChannelTlv.RequireConfirmedInputsTlv].nonEmpty val pushAmount: MilliSatoshi = tlvStream.get[ChannelTlv.PushAmountTlv].map(_.amount).getOrElse(0 msat) } object SpliceAck { - def apply(channelId: ByteVector32, fundingContribution: Satoshi, pushAmount: MilliSatoshi, requireConfirmedInputs: Boolean): SpliceAck = { + def apply(channelId: ByteVector32, fundingContribution: Satoshi, fundingPubKey: PublicKey, pushAmount: MilliSatoshi, requireConfirmedInputs: Boolean): SpliceAck = { val tlvs: Set[SpliceAckTlv] = Set( Some(ChannelTlv.PushAmountTlv(pushAmount)), if (requireConfirmedInputs) Some(ChannelTlv.RequireConfirmedInputsTlv()) else None, ).flatten - SpliceAck(channelId, fundingContribution, TlvStream(tlvs)) + SpliceAck(channelId, fundingContribution, fundingPubKey, TlvStream(tlvs)) } } diff --git a/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/fundee-announced/data.json b/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/fundee-announced/data.json index 7fc72bfa22..d94d9d419f 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/fundee-announced/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/fundee-announced/data.json @@ -35,7 +35,6 @@ "htlcMinimum" : 1000, "toSelfDelay" : 2016, "maxAcceptedHtlcs" : 483, - "fundingPubKey" : "028cef3ef020cfda09692afc29e38ac4756ca60736563a93220481091c9cd05b64", "revocationBasepoint" : "02635ac9eedf5f219afbc4d125e37b5705f73c05deca71b05fe84096a691e055c1", "paymentBasepoint" : "034a711d28e8ed3ad389ec14ec75c199b6a45140c503bcc88110e3524e52ffbfb1", "delayedPaymentBasepoint" : "0316c70730b57a9e15845ce6f239e749ac78b25f44c90485a697066962a73d0467", diff --git a/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/funder/data.json index 644fc0b458..c55e33c4b0 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/000003-DATA_NORMAL/funder/data.json @@ -35,7 +35,6 @@ "htlcMinimum" : 1000, "toSelfDelay" : 1802, "maxAcceptedHtlcs" : 483, - "fundingPubKey" : "0215c35f143adeadf010abc4ce0be323760f9a9c486978b762d31cfcb101c44cc4", "revocationBasepoint" : "03d17fdddddae4aeeb7022dedf059f1d0f06b4b68b6309cade4e55ae1ac0f0230c", "paymentBasepoint" : "03c0c4257191e5c4b6e7dcf2e9fb9be00fc713686f77fc4719987e77ee2436d8bd", "delayedPaymentBasepoint" : "03550b13a43d2b09649423e75774bb5a91a243bac78af4d39aece23380bb42b397", diff --git a/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder-announced/data.json b/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder-announced/data.json index aa8bfe9d86..ff2c0bb718 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder-announced/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/020002-DATA_NORMAL/funder-announced/data.json @@ -42,7 +42,6 @@ "htlcMinimum" : 1, "toSelfDelay" : 1802, "maxAcceptedHtlcs" : 483, - "fundingPubKey" : "02eff5309b9368340edc6114d738b3590e6969bec4e95d8a080cf185e8b9ce5e4b", "revocationBasepoint" : "0343bf4bfbaea5c100f1f2bf1cdf82a0ef97c9a0069a2aec631e7c3084ba929b75", "paymentBasepoint" : "03c54e7d5ccfc13f1a6c7a441ffcfac86248574d1bc0fe9773836f4c724ea7b2bd", "delayedPaymentBasepoint" : "03765aaac2e8fa6dbce7de5143072e9d9d5e96a1fd451d02fe4ff803f413f303f8", diff --git a/eclair-core/src/test/resources/nonreg/codecs/030000-DATA_WAIT_FOR_FUNDING_CONFIRMED/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/030000-DATA_WAIT_FOR_FUNDING_CONFIRMED/funder/data.json index e2ba5a5c28..39107205e0 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/030000-DATA_WAIT_FOR_FUNDING_CONFIRMED/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/030000-DATA_WAIT_FOR_FUNDING_CONFIRMED/funder/data.json @@ -38,7 +38,6 @@ "htlcMinimum" : 1000, "toSelfDelay" : 144, "maxAcceptedHtlcs" : 30, - "fundingPubKey" : "02e3048a4918587b33fb380e3061b7ac38ef4038551c0f098850d43e45ab1cb283", "revocationBasepoint" : "02f7c3bf47cdc640304eda4c761a26dfebfee561de15ba106f3d9982d3ef3fbe10", "paymentBasepoint" : "02be361b7bf1bdfb283cff3f83bf16f6f8fb67d3f480b541e76518939f667ab834", "delayedPaymentBasepoint" : "03fcaec5443dd423f160c9b77a48b2585b186f2d850147f57210d3f8a8c8d754a7", diff --git a/eclair-core/src/test/resources/nonreg/codecs/03000a-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/03000a-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json index 14e5a799ab..f859bf865c 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/03000a-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/03000a-DATA_WAIT_FOR_CHANNEL_READY/funder/data.json @@ -39,7 +39,6 @@ "htlcMinimum" : 1000, "toSelfDelay" : 144, "maxAcceptedHtlcs" : 30, - "fundingPubKey" : "02b9da90e1ed391bd7b419b509f9499d09c52156c5deba6c927791c0074c1d18ff", "revocationBasepoint" : "033b164d14b54f08a951f9e40fb84e637021f376c9ae2907975f9ff0795431205d", "paymentBasepoint" : "03759da3f6bdcc2f595e1a98eb4803729743ab8608e28788cfe96726b5328c214c", "delayedPaymentBasepoint" : "03abc3ae99fac104e94f79cafcd70247cc336cdc21a53d4c3a4c321b54e20902e7", diff --git a/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json b/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json index b27afb8c9f..612f308af7 100644 --- a/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json +++ b/eclair-core/src/test/resources/nonreg/codecs/03000c-DATA_WAIT_FOR_DUAL_FUNDING_READY/funder/data.json @@ -41,7 +41,6 @@ "htlcMinimum" : 1000, "toSelfDelay" : 144, "maxAcceptedHtlcs" : 30, - "fundingPubKey" : "032e093165a2e7c96d5f362e08c51d46f931032513569fdfbb107781e09c6183ab", "revocationBasepoint" : "03626342f0af6e87ab41715bd6d1db7d6eee5e95a2a835680b42d9b357003c1c6a", "paymentBasepoint" : "02ed6013749b4e3a820e7c003d3d3c7c5f16dd0d25ce72d3de99e544d4011c0a67", "delayedPaymentBasepoint" : "038a15a3dcd087135509b8e91d95c4fb788be11735f5c1a9ac24283d3cecbc76d2", diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala index a76dd7e3c5..c78a39ca39 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/CommitmentsSpec.scala @@ -485,14 +485,15 @@ object CommitmentsSpec { def makeCommitments(toLocal: MilliSatoshi, toRemote: MilliSatoshi, feeRatePerKw: FeeratePerKw = FeeratePerKw(0 sat), dustLimit: Satoshi = 0 sat, isInitiator: Boolean = true, announceChannel: Boolean = true): Commitments = { val channelReserve = (toLocal + toRemote).truncateToSatoshi * 0.01 val localParams = LocalParams(randomKey().publicKey, DeterministicWallet.KeyPath(Seq(42L)), dustLimit, Long.MaxValue.msat, Some(channelReserve), 1 msat, CltvExpiryDelta(144), 50, isInitiator, None, None, Features.empty) - val remoteParams = RemoteParams(randomKey().publicKey, dustLimit, UInt64.MaxValue, Some(channelReserve), 1 msat, CltvExpiryDelta(144), 50, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) - val commitmentInput = Funding.makeFundingInputInfo(randomBytes32(), 0, (toLocal + toRemote).truncateToSatoshi, randomKey().publicKey, remoteParams.fundingPubKey) + val remoteParams = RemoteParams(randomKey().publicKey, dustLimit, UInt64.MaxValue, Some(channelReserve), 1 msat, CltvExpiryDelta(144), 50, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) + val remoteFundingPubKey = randomKey().publicKey + val commitmentInput = Funding.makeFundingInputInfo(randomBytes32(), 0, (toLocal + toRemote).truncateToSatoshi, randomKey().publicKey, remoteFundingPubKey) val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, feeRatePerKw, toLocal, toRemote), CommitTxAndRemoteSig(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(Set.empty, feeRatePerKw, toRemote, toLocal), randomBytes32(), randomKey().publicKey) Commitments( ChannelParams(randomBytes32(), ChannelConfig.standard, ChannelFeatures(), localParams, remoteParams, ChannelFlags(announceChannel = announceChannel)), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 1, remoteNextHtlcId = 1), - List(Commitment(0, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.Locked, localCommit, remoteCommit, None)), + List(Commitment(0, remoteFundingPubKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.Locked, localCommit, remoteCommit, None)), inactive = Nil, Right(randomKey().publicKey), ShaChain.init, @@ -503,14 +504,15 @@ object CommitmentsSpec { def makeCommitments(toLocal: MilliSatoshi, toRemote: MilliSatoshi, localNodeId: PublicKey, remoteNodeId: PublicKey, announceChannel: Boolean): Commitments = { val channelReserve = (toLocal + toRemote).truncateToSatoshi * 0.01 val localParams = LocalParams(localNodeId, DeterministicWallet.KeyPath(Seq(42L)), 0 sat, Long.MaxValue.msat, Some(channelReserve), 1 msat, CltvExpiryDelta(144), 50, isInitiator = true, None, None, Features.empty) - val remoteParams = RemoteParams(remoteNodeId, 0 sat, UInt64.MaxValue, Some(channelReserve), 1 msat, CltvExpiryDelta(144), 50, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) - val commitmentInput = Funding.makeFundingInputInfo(randomBytes32(), 0, (toLocal + toRemote).truncateToSatoshi, randomKey().publicKey, remoteParams.fundingPubKey) + val remoteParams = RemoteParams(remoteNodeId, 0 sat, UInt64.MaxValue, Some(channelReserve), 1 msat, CltvExpiryDelta(144), 50, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, Features.empty, None) + val remoteFundingPubKey = randomKey().publicKey + val commitmentInput = Funding.makeFundingInputInfo(randomBytes32(), 0, (toLocal + toRemote).truncateToSatoshi, randomKey().publicKey, remoteFundingPubKey) val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(0 sat), toLocal, toRemote), CommitTxAndRemoteSig(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(0 sat), toRemote, toLocal), randomBytes32(), randomKey().publicKey) Commitments( ChannelParams(randomBytes32(), ChannelConfig.standard, ChannelFeatures(), localParams, remoteParams, ChannelFlags(announceChannel = announceChannel)), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 1, remoteNextHtlcId = 1), - List(Commitment(0, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.Locked, localCommit, remoteCommit, None)), + List(Commitment(0, remoteFundingPubKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.Locked, localCommit, remoteCommit, None)), inactive = Nil, Right(randomKey().publicKey), ShaChain.init, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala index 916d65499c..0ed26f29cd 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala @@ -73,6 +73,12 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit case None => input.sharedInput_opt.get } + private def sharedInputs(commitmentA: Commitment, commitmentB: Commitment): (SharedFundingInput, SharedFundingInput) = { + val sharedInputA = Multisig2of2Input(commitmentA) + val sharedInputB = Multisig2of2Input(commitmentB) + (sharedInputA, sharedInputB) + } + case class FixtureParams(fundingParamsA: InteractiveTxParams, nodeParamsA: NodeParams, channelParamsA: ChannelParams, @@ -85,23 +91,28 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit private val firstPerCommitmentPointA = nodeParamsA.channelKeyManager.commitmentPoint(nodeParamsA.channelKeyManager.keyPath(channelParamsA.localParams, ChannelConfig.standard), 0) private val firstPerCommitmentPointB = nodeParamsB.channelKeyManager.commitmentPoint(nodeParamsB.channelKeyManager.keyPath(channelParamsB.localParams, ChannelConfig.standard), 0) - val fundingPurposeA = FundingTx(commitFeerate, firstPerCommitmentPointB) - val fundingPurposeB = FundingTx(commitFeerate, firstPerCommitmentPointA) + val fundingPubkeyScript: ByteVector = Script.write(Script.pay2wsh(Scripts.multiSig2of2(fundingParamsB.remoteFundingPubKey, fundingParamsA.remoteFundingPubKey))) def dummySharedInputB(amount: Satoshi): SharedFundingInput = { - val inputInfo = InputInfo(OutPoint(randomBytes32(), 3), TxOut(amount, fundingParamsB.fundingPubkeyScript), Nil) - Multisig2of2Input(inputInfo, channelParamsA.remoteParams.fundingPubKey, channelParamsB.remoteParams.fundingPubKey) + val inputInfo = InputInfo(OutPoint(randomBytes32(), 3), TxOut(amount, fundingPubkeyScript), Nil) + val fundingTxIndex = fundingParamsA.sharedInput_opt match { + case Some(input: Multisig2of2Input) => input.fundingTxIndex + 1 + case _ => 0 + } + Multisig2of2Input(inputInfo, fundingTxIndex, fundingParamsA.remoteFundingPubKey) } - def sharedInputs(commitmentA: Commitment, commitmentB: Commitment): (SharedFundingInput, SharedFundingInput) = { - val sharedInputA = Multisig2of2Input(nodeParamsA.channelKeyManager, channelParamsA, commitmentA) - val sharedInputB = Multisig2of2Input(nodeParamsB.channelKeyManager, channelParamsB, commitmentB) - (sharedInputA, sharedInputB) + def createSpliceFixtureParams(fundingTxIndex: Long, fundingAmountA: Satoshi, fundingAmountB: Satoshi, targetFeerate: FeeratePerKw, dustLimit: Satoshi, lockTime: Long, sharedInputA: SharedFundingInput, sharedInputB: SharedFundingInput, spliceOutputsA: List[TxOut] = Nil, spliceOutputsB: List[TxOut] = Nil, requireConfirmedInputs: RequireConfirmedInputs = RequireConfirmedInputs(forLocal = false, forRemote = false)): FixtureParams = { + val fundingPubKeyA = nodeParamsA.channelKeyManager.fundingPublicKey(channelParamsA.localParams.fundingKeyPath, fundingTxIndex).publicKey + val fundingPubKeyB = nodeParamsB.channelKeyManager.fundingPublicKey(channelParamsB.localParams.fundingKeyPath, fundingTxIndex).publicKey + val fundingParamsA = InteractiveTxParams(channelId, isInitiator = true, fundingAmountA, fundingAmountB, Some(sharedInputA), fundingPubKeyB, spliceOutputsA, lockTime, dustLimit, targetFeerate, Some(3), requireConfirmedInputs) + val fundingParamsB = InteractiveTxParams(channelId, isInitiator = false, fundingAmountB, fundingAmountA, Some(sharedInputB), fundingPubKeyA, spliceOutputsB, lockTime, dustLimit, targetFeerate, Some(3), requireConfirmedInputs) + copy(fundingParamsA = fundingParamsA, fundingParamsB = fundingParamsB) } def spawnTxBuilderAlice(wallet: OnChainWallet, fundingParams: InteractiveTxParams = fundingParamsA): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( nodeParamsA, fundingParams, channelParamsA, - fundingPurposeA, + FundingTx(commitFeerate, firstPerCommitmentPointB), 0 msat, 0 msat, wallet)) @@ -117,15 +128,15 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit 0 msat, 0 msat, wallet)) - def spawnTxBuilderSpliceRbfAlice(fundingParams: InteractiveTxParams, commitment: Commitment, previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction], wallet: OnChainWallet): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( + def spawnTxBuilderSpliceRbfAlice(fundingParams: InteractiveTxParams, parentCommitment: Commitment, replacedCommitment: Commitment, previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction], wallet: OnChainWallet): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( nodeParamsA, fundingParams, channelParamsA, - PreviousTxRbf(commitment, commitment.localCommit.spec.toLocal, commitment.remoteCommit.spec.toLocal, previousTransactions), + PreviousTxRbf(replacedCommitment, parentCommitment.localCommit.spec.toLocal, parentCommitment.remoteCommit.spec.toLocal, previousTransactions), 0 msat, 0 msat, wallet)) def spawnTxBuilderBob(wallet: OnChainWallet, fundingParams: InteractiveTxParams = fundingParamsB): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( nodeParamsB, fundingParams, channelParamsB, - fundingPurposeB, + FundingTx(commitFeerate, firstPerCommitmentPointA), 0 msat, 0 msat, wallet)) @@ -141,9 +152,9 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit 0 msat, 0 msat, wallet)) - def spawnTxBuilderSpliceRbfBob(fundingParams: InteractiveTxParams, commitment: Commitment, previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction], wallet: OnChainWallet): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( + def spawnTxBuilderSpliceRbfBob(fundingParams: InteractiveTxParams, parentCommitment: Commitment, replacedCommitment: Commitment, previousTransactions: Seq[InteractiveTxBuilder.SignedSharedTransaction], wallet: OnChainWallet): ActorRef[InteractiveTxBuilder.Command] = system.spawnAnonymous(InteractiveTxBuilder( nodeParamsB, fundingParams, channelParamsB, - PreviousTxRbf(commitment, commitment.localCommit.spec.toLocal, commitment.remoteCommit.spec.toLocal, previousTransactions), + PreviousTxRbf(replacedCommitment, parentCommitment.localCommit.spec.toLocal, parentCommitment.remoteCommit.spec.toLocal, previousTransactions), 0 msat, 0 msat, wallet)) @@ -155,11 +166,11 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val Right(sigsA: InteractiveTxSigningSession.SendingSigs) = successA.signingSession.receiveCommitSig(nodeParamsA, channelParamsA, successB.commitSig) assert(sigsA.fundingTx.sharedTx.isInstanceOf[PartiallySignedSharedTransaction]) // Alice --- tx_signatures --> Bob - val Right(sigsB) = signingSessionB2.receiveTxSigs(nodeParamsB, sigsA.localSigs) + val Right(sigsB) = signingSessionB2.receiveTxSigs(nodeParamsB, channelParamsB, sigsA.localSigs) assert(sigsB.fundingTx.sharedTx.isInstanceOf[FullySignedSharedTransaction]) val txB = sigsB.fundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction] // Alice <-- tx_signatures --- Bob - val Right(txA) = InteractiveTxSigningSession.addRemoteSigs(fundingParams, sigsA.fundingTx.sharedTx.asInstanceOf[PartiallySignedSharedTransaction], sigsB.localSigs) + val Right(txA) = InteractiveTxSigningSession.addRemoteSigs(nodeParamsA.channelKeyManager, channelParamsA, fundingParams, sigsA.fundingTx.sharedTx.asInstanceOf[PartiallySignedSharedTransaction], sigsB.localSigs) (txA, sigsA.commitment, txB, sigsB.commitment) } @@ -171,14 +182,13 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val Right(sigsB: InteractiveTxSigningSession.SendingSigs) = successB.signingSession.receiveCommitSig(nodeParamsB, channelParamsB, successA.commitSig) assert(sigsB.fundingTx.sharedTx.isInstanceOf[PartiallySignedSharedTransaction]) // Alice <-- tx_signatures --- Bob - val Right(sigsA) = signingSessionA2.receiveTxSigs(nodeParamsA, sigsB.localSigs) + val Right(sigsA) = signingSessionA2.receiveTxSigs(nodeParamsA, channelParamsA, sigsB.localSigs) assert(sigsA.fundingTx.sharedTx.isInstanceOf[FullySignedSharedTransaction]) val txA = sigsA.fundingTx.sharedTx.asInstanceOf[FullySignedSharedTransaction] // Alice --- tx_signatures --> Bob - val Right(txB) = InteractiveTxSigningSession.addRemoteSigs(fundingParams, sigsB.fundingTx.sharedTx.asInstanceOf[PartiallySignedSharedTransaction], sigsA.localSigs) + val Right(txB) = InteractiveTxSigningSession.addRemoteSigs(nodeParamsB.channelKeyManager, channelParamsB, fundingParams, sigsB.fundingTx.sharedTx.asInstanceOf[PartiallySignedSharedTransaction], sigsA.localSigs) (txA, sigsA.commitment, txB, sigsB.commitment) } - } private def createFixtureParams(fundingAmountA: Satoshi, fundingAmountB: Satoshi, targetFeerate: FeeratePerKw, dustLimit: Satoshi, lockTime: Long, requireConfirmedInputs: RequireConfirmedInputs = RequireConfirmedInputs(forLocal = false, forRemote = false)): FixtureParams = { @@ -193,7 +203,6 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit RemoteParams( nodeParams.nodeId, localParams.dustLimit, UInt64(localParams.maxHtlcValueInFlightMsat.toLong), None, localParams.htlcMinimum, localParams.toSelfDelay, localParams.maxAcceptedHtlcs, - nodeParams.channelKeyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey, nodeParams.channelKeyManager.revocationPoint(channelKeyPath).publicKey, nodeParams.channelKeyManager.paymentPoint(channelKeyPath).publicKey, nodeParams.channelKeyManager.delayedPaymentPoint(channelKeyPath).publicKey, @@ -203,9 +212,10 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit } val channelId = randomBytes32() - val fundingScript = Script.write(Script.pay2wsh(Scripts.multiSig2of2(remoteParamsA.fundingPubKey, remoteParamsB.fundingPubKey))) - val fundingParamsA = InteractiveTxParams(channelId, isInitiator = true, fundingAmountA, fundingAmountB, None, fundingScript, Nil, lockTime, dustLimit, targetFeerate, Some(3), requireConfirmedInputs) - val fundingParamsB = InteractiveTxParams(channelId, isInitiator = false, fundingAmountB, fundingAmountA, None, fundingScript, Nil, lockTime, dustLimit, targetFeerate, Some(3), requireConfirmedInputs) + val fundingPubKeyA = nodeParamsA.channelKeyManager.fundingPublicKey(localParamsA.fundingKeyPath, fundingTxIndex = 0).publicKey + val fundingPubKeyB = nodeParamsB.channelKeyManager.fundingPublicKey(localParamsB.fundingKeyPath, fundingTxIndex = 0).publicKey + val fundingParamsA = InteractiveTxParams(channelId, isInitiator = true, fundingAmountA, fundingAmountB, None, fundingPubKeyB, Nil, lockTime, dustLimit, targetFeerate, Some(3), requireConfirmedInputs) + val fundingParamsB = InteractiveTxParams(channelId, isInitiator = false, fundingAmountB, fundingAmountA, None, fundingPubKeyA, Nil, lockTime, dustLimit, targetFeerate, Some(3), requireConfirmedInputs) val channelParamsA = ChannelParams(channelId, ChannelConfig.standard, channelFeatures, localParamsA, remoteParamsB, ChannelFlags.Public) val channelParamsB = ChannelParams(channelId, ChannelConfig.standard, channelFeatures, localParamsB, remoteParamsA, ChannelFlags.Public) @@ -312,11 +322,10 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit assert(locksB == Set(toOutPoint(inputB1))) // Alice is responsible for adding the shared output. - assert(aliceParams.fundingPubkeyScript == bobParams.fundingPubkeyScript) assert(aliceParams.fundingAmount == fundingA + fundingB) - assert(Seq(outputA1, outputA2).count(_.pubkeyScript == aliceParams.fundingPubkeyScript) == 1) - assert(Seq(outputA1, outputA2).exists(o => o.pubkeyScript == aliceParams.fundingPubkeyScript && o.amount == fundingA + fundingB)) - assert(outputB1.pubkeyScript != aliceParams.fundingPubkeyScript) + assert(Seq(outputA1, outputA2).count(_.pubkeyScript == f.fixtureParams.fundingPubkeyScript) == 1) + assert(Seq(outputA1, outputA2).exists(o => o.pubkeyScript == f.fixtureParams.fundingPubkeyScript && o.amount == fundingA + fundingB)) + assert(outputB1.pubkeyScript != f.fixtureParams.fundingPubkeyScript) // Bob sends signatures first as he contributed less than Alice. val successA = alice2bob.expectMsgType[Succeeded] @@ -369,11 +378,10 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit fwd.forwardAlice2Bob[TxComplete] // Alice is responsible for adding the shared output. - assert(aliceParams.fundingPubkeyScript == bobParams.fundingPubkeyScript) assert(aliceParams.fundingAmount == fundingA + fundingB) - assert(Seq(outputA1, outputA2).count(_.pubkeyScript == aliceParams.fundingPubkeyScript) == 1) - assert(Seq(outputA1, outputA2).exists(o => o.pubkeyScript == aliceParams.fundingPubkeyScript && o.amount == fundingA + fundingB)) - assert(outputB.pubkeyScript != aliceParams.fundingPubkeyScript) + assert(Seq(outputA1, outputA2).count(_.pubkeyScript == f.fixtureParams.fundingPubkeyScript) == 1) + assert(Seq(outputA1, outputA2).exists(o => o.pubkeyScript == f.fixtureParams.fundingPubkeyScript && o.amount == fundingA + fundingB)) + assert(outputB.pubkeyScript != f.fixtureParams.fundingPubkeyScript) // Alice sends signatures first as she contributed less than Bob. val successA = alice2bob.expectMsgType[Succeeded] @@ -468,10 +476,9 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit fwd.forwardAlice2Bob[TxComplete] // Alice is responsible for adding the shared output. - assert(aliceParams.fundingPubkeyScript == bobParams.fundingPubkeyScript) assert(aliceParams.fundingAmount == fundingA) - assert(Seq(outputA1, outputA2).count(_.pubkeyScript == aliceParams.fundingPubkeyScript) == 1) - assert(Seq(outputA1, outputA2).exists(o => o.pubkeyScript == aliceParams.fundingPubkeyScript && o.amount == fundingA)) + assert(Seq(outputA1, outputA2).count(_.pubkeyScript == f.fixtureParams.fundingPubkeyScript) == 1) + assert(Seq(outputA1, outputA2).exists(o => o.pubkeyScript == f.fixtureParams.fundingPubkeyScript && o.amount == fundingA)) // Bob sends signatures first as he did not contribute at all. val successA = alice2bob.expectMsgType[Succeeded] @@ -566,11 +573,10 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice and Bob decide to splice additional funds in the channel. val additionalFundingA2 = 30_000.sat val additionalFundingB2 = 25_000.sat - val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) - val fundingParamsA1 = aliceParams.copy(localContribution = additionalFundingA2, remoteContribution = additionalFundingB2, sharedInput_opt = Some(sharedInputA)) - val fundingParamsB1 = bobParams.copy(localContribution = additionalFundingB2, remoteContribution = additionalFundingA2, sharedInput_opt = Some(sharedInputB)) - val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(fundingParamsA1, commitmentA1, walletA) - val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(fundingParamsB1, commitmentB1, walletB) + val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA2, fundingAmountB = additionalFundingB2, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) + val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) + val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(spliceFixtureParams.fundingParamsB, commitmentB1, walletB) val fwdSplice = TypeCheckedForwarder(aliceSplice, bobSplice, alice2bob, bob2alice) aliceSplice ! Start(alice2bob.ref) @@ -599,7 +605,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit assert(successA2.signingSession.fundingTx.localSigs.previousFundingTxSig_opt.nonEmpty) val successB2 = bob2alice.expectMsgType[Succeeded] assert(successB2.signingSession.fundingTx.localSigs.previousFundingTxSig_opt.nonEmpty) - val (spliceTxA, commitmentA2, spliceTxB, commitmentB2) = fixtureParams.exchangeSigsBobFirst(fundingParamsB1, successA2, successB2) + val (spliceTxA, commitmentA2, spliceTxB, commitmentB2) = fixtureParams.exchangeSigsBobFirst(spliceFixtureParams.fundingParamsB, successA2, successB2) assert(spliceTxA.signedTx.txIn.exists(_.outPoint == commitmentA1.commitInput.outPoint)) assert(0.msat < spliceTxA.tx.localFees) assert(0.msat < spliceTxA.tx.remoteFees) @@ -659,11 +665,11 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val spliceOutputsB = List(TxOut(30_000 sat, Script.pay2wpkh(randomKey().publicKey))) val subtractedFundingA = spliceOutputsA.map(_.amount).sum + 1_000.sat val subtractedFundingB = spliceOutputsB.map(_.amount).sum + 500.sat - val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) - val fundingParamsA1 = aliceParams.copy(localContribution = -subtractedFundingA, remoteContribution = -subtractedFundingB, sharedInput_opt = Some(sharedInputA), localOutputs = spliceOutputsA) - val fundingParamsB1 = bobParams.copy(localContribution = -subtractedFundingB, remoteContribution = -subtractedFundingA, sharedInput_opt = Some(sharedInputB), localOutputs = spliceOutputsB) - val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(fundingParamsA1, commitmentA1, walletA) - val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(fundingParamsB1, commitmentB1, walletB) + val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = -subtractedFundingA, fundingAmountB = -subtractedFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) + + val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) + val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(spliceFixtureParams.fundingParamsB, commitmentB1, walletB) val fwdSplice = TypeCheckedForwarder(aliceSplice, bobSplice, alice2bob, bob2alice) aliceSplice ! Start(alice2bob.ref) @@ -690,13 +696,13 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit assert(successA2.signingSession.fundingTx.localSigs.previousFundingTxSig_opt.nonEmpty) val successB2 = bob2alice.expectMsgType[Succeeded] assert(successB2.signingSession.fundingTx.localSigs.previousFundingTxSig_opt.nonEmpty) - val (spliceTxA, commitmentA2, spliceTxB, commitmentB2) = fixtureParams.exchangeSigsBobFirst(fundingParamsB1, successA2, successB2) + val (spliceTxA, commitmentA2, spliceTxB, commitmentB2) = fixtureParams.exchangeSigsBobFirst(spliceFixtureParams.fundingParamsB, successA2, successB2) assert(spliceTxA.tx.localFees == 1_000_000.msat) assert(spliceTxB.tx.localFees == 500_000.msat) assert(spliceTxB.tx.localFees == spliceTxA.tx.remoteFees) spliceOutputsA.foreach(txOut => assert(Set(outputA1, outputA2).map(o => TxOut(o.amount, o.pubkeyScript)).contains(txOut))) spliceOutputsB.foreach(txOut => assert(Set(outputB).map(o => TxOut(o.amount, o.pubkeyScript)).contains(txOut))) - assert(Set(outputA1, outputA2).exists(o => o.amount == fundingA1 + fundingB1 - subtractedFundingA - subtractedFundingB && o.pubkeyScript == fundingParamsA1.fundingPubkeyScript)) + assert(Set(outputA1, outputA2).exists(o => o.amount == fundingA1 + fundingB1 - subtractedFundingA - subtractedFundingB && o.pubkeyScript == spliceFixtureParams.fundingPubkeyScript)) assert(commitmentA2.localCommit.spec.toLocal == (fundingA1 - subtractedFundingA).toMilliSatoshi) assert(commitmentA2.localCommit.spec.toRemote == (fundingB1 - subtractedFundingB).toMilliSatoshi) @@ -747,11 +753,10 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val spliceOutputsB = List(25_000 sat, 15_000 sat).map(amount => TxOut(amount, Script.pay2wpkh(randomKey().publicKey))) val subtractedFundingA = spliceOutputsA.map(_.amount).sum + 1_000.sat val subtractedFundingB = spliceOutputsB.map(_.amount).sum + 500.sat - val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) - val fundingParamsA1 = aliceParams.copy(localContribution = -subtractedFundingA, remoteContribution = -subtractedFundingB, sharedInput_opt = Some(sharedInputA), localOutputs = spliceOutputsA) - val fundingParamsB1 = bobParams.copy(localContribution = -subtractedFundingB, remoteContribution = -subtractedFundingA, sharedInput_opt = Some(sharedInputB), localOutputs = spliceOutputsB) - val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(fundingParamsA1, commitmentA1, walletA) - val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(fundingParamsB1, commitmentB1, walletB) + val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = -subtractedFundingA, fundingAmountB = -subtractedFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) + val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) + val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(spliceFixtureParams.fundingParamsB, commitmentB1, walletB) val fwdSplice = TypeCheckedForwarder(aliceSplice, bobSplice, alice2bob, bob2alice) aliceSplice ! Start(alice2bob.ref) @@ -786,13 +791,13 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit assert(successA2.signingSession.fundingTx.localSigs.previousFundingTxSig_opt.nonEmpty) val successB2 = bob2alice.expectMsgType[Succeeded] assert(successB2.signingSession.fundingTx.localSigs.previousFundingTxSig_opt.nonEmpty) - val (spliceTxA, commitmentA2, spliceTxB, commitmentB2) = fixtureParams.exchangeSigsBobFirst(fundingParamsB1, successA2, successB2) + val (spliceTxA, commitmentA2, spliceTxB, commitmentB2) = fixtureParams.exchangeSigsBobFirst(spliceFixtureParams.fundingParamsB, successA2, successB2) assert(spliceTxA.tx.localFees == 1_000_000.msat) assert(spliceTxB.tx.localFees == 500_000.msat) assert(spliceTxB.tx.localFees == spliceTxA.tx.remoteFees) spliceOutputsA.foreach(txOut => assert(Set(outputA1, outputA2, outputA3, outputA4).map(o => TxOut(o.amount, o.pubkeyScript)).contains(txOut))) spliceOutputsB.foreach(txOut => assert(Set(outputB1, outputB2).map(o => TxOut(o.amount, o.pubkeyScript)).contains(txOut))) - assert(Set(outputA1, outputA2, outputA3, outputA4).exists(o => o.amount == fundingA1 + fundingB1 - subtractedFundingA - subtractedFundingB && o.pubkeyScript == fundingParamsA1.fundingPubkeyScript)) + assert(Set(outputA1, outputA2, outputA3, outputA4).exists(o => o.amount == fundingA1 + fundingB1 - subtractedFundingA - subtractedFundingB && o.pubkeyScript == spliceFixtureParams.fundingPubkeyScript)) assert(commitmentA2.localCommit.spec.toLocal == (fundingA1 - subtractedFundingA).toMilliSatoshi) assert(commitmentA2.localCommit.spec.toRemote == (fundingB1 - subtractedFundingB).toMilliSatoshi) @@ -845,11 +850,10 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val additionalFundingB = 15_000.sat val spliceOutputsA = List(TxOut(30_000 sat, Script.pay2wpkh(randomKey().publicKey))) val spliceOutputsB = List(TxOut(10_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) - val fundingParamsA1 = aliceParams.copy(localContribution = additionalFundingA, remoteContribution = additionalFundingB, sharedInput_opt = Some(sharedInputA), localOutputs = spliceOutputsA) - val fundingParamsB1 = bobParams.copy(localContribution = additionalFundingB, remoteContribution = additionalFundingA, sharedInput_opt = Some(sharedInputB), localOutputs = spliceOutputsB) - val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(fundingParamsA1, commitmentA1, walletA) - val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(fundingParamsB1, commitmentB1, walletB) + val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA, fundingAmountB = additionalFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) + val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) + val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(spliceFixtureParams.fundingParamsB, commitmentB1, walletB) val fwdSplice = TypeCheckedForwarder(aliceSplice, bobSplice, alice2bob, bob2alice) aliceSplice ! Start(alice2bob.ref) @@ -882,10 +886,10 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit assert(successA2.signingSession.fundingTx.localSigs.previousFundingTxSig_opt.nonEmpty) val successB2 = bob2alice.expectMsgType[Succeeded] assert(successB2.signingSession.fundingTx.localSigs.previousFundingTxSig_opt.nonEmpty) - val (spliceTxA, commitmentA2, _, commitmentB2) = fixtureParams.exchangeSigsBobFirst(fundingParamsB1, successA2, successB2) + val (spliceTxA, commitmentA2, _, commitmentB2) = fixtureParams.exchangeSigsBobFirst(spliceFixtureParams.fundingParamsB, successA2, successB2) spliceOutputsA.foreach(txOut => assert(Set(outputA1, outputA2, outputA3).map(o => TxOut(o.amount, o.pubkeyScript)).contains(txOut))) spliceOutputsB.foreach(txOut => assert(Set(outputB1, outputB2).map(o => TxOut(o.amount, o.pubkeyScript)).contains(txOut))) - assert(Set(outputA1, outputA2, outputA3).exists(o => o.amount == fundingA1 + fundingB1 + additionalFundingA + additionalFundingB && o.pubkeyScript == fundingParamsA1.fundingPubkeyScript)) + assert(Set(outputA1, outputA2, outputA3).exists(o => o.amount == fundingA1 + fundingB1 + additionalFundingA + additionalFundingB && o.pubkeyScript == spliceFixtureParams.fundingPubkeyScript)) assert(commitmentA2.localCommit.spec.toLocal == (fundingA1 + additionalFundingA).toMilliSatoshi) assert(commitmentA2.localCommit.spec.toRemote == (fundingB1 + additionalFundingB).toMilliSatoshi) @@ -1354,11 +1358,10 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val additionalFundingB = 5_000.sat val spliceOutputsA = List(TxOut(20_000 sat, Script.pay2wpkh(randomKey().publicKey))) val spliceOutputsB = List(TxOut(10_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) - val fundingParamsA1 = aliceParams.copy(localContribution = additionalFundingA, remoteContribution = additionalFundingB, sharedInput_opt = Some(sharedInputA), localOutputs = spliceOutputsA) - val fundingParamsB1 = bobParams.copy(localContribution = additionalFundingB, remoteContribution = additionalFundingA, sharedInput_opt = Some(sharedInputB), localOutputs = spliceOutputsB) - val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(fundingParamsA1, commitmentA1, walletA) - val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(fundingParamsB1, commitmentB1, walletB) + val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA, fundingAmountB = additionalFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) + val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(spliceFixtureParams.fundingParamsA, commitmentA1, walletA) + val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(spliceFixtureParams.fundingParamsB, commitmentB1, walletB) val fwdSplice = TypeCheckedForwarder(aliceSplice, bobSplice, alice2bob, bob2alice) aliceSplice ! Start(alice2bob.ref) @@ -1389,16 +1392,16 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val successA2 = alice2bob.expectMsgType[Succeeded] val successB2 = bob2alice.expectMsgType[Succeeded] - val (spliceTxA1, _, spliceTxB1, _) = fixtureParams.exchangeSigsBobFirst(fundingParamsB1, successA2, successB2) + val (spliceTxA1, commitmentA2, spliceTxB1, commitmentB2) = fixtureParams.exchangeSigsBobFirst(spliceFixtureParams.fundingParamsB, successA2, successB2) assert(targetFeerate * 0.9 <= spliceTxA1.feerate && spliceTxA1.feerate <= targetFeerate * 1.25) walletA.publishTransaction(spliceTxA1.signedTx).pipeTo(probe.ref) probe.expectMsg(spliceTxA1.txId) // Alice wants to increase the feerate of the splice transaction. - val fundingParamsA2 = fundingParamsA1.copy(targetFeerate = targetFeerate * 2) - val fundingParamsB2 = fundingParamsB1.copy(targetFeerate = targetFeerate * 2) - val aliceRbf = fixtureParams.spawnTxBuilderSpliceRbfAlice(fundingParamsA2, commitmentA1, Seq(spliceTxA1), walletA) - val bobRbf = fixtureParams.spawnTxBuilderSpliceRbfBob(fundingParamsB2, commitmentB1, Seq(spliceTxB1), walletB) + val fundingParamsA2 = spliceFixtureParams.fundingParamsA.copy(targetFeerate = targetFeerate * 2) + val fundingParamsB2 = spliceFixtureParams.fundingParamsB.copy(targetFeerate = targetFeerate * 2) + val aliceRbf = fixtureParams.spawnTxBuilderSpliceRbfAlice(fundingParamsA2, parentCommitment = commitmentA1, replacedCommitment = commitmentA2, Seq(spliceTxA1), walletA) + val bobRbf = fixtureParams.spawnTxBuilderSpliceRbfBob(fundingParamsB2, parentCommitment = commitmentB1, replacedCommitment = commitmentB2, Seq(spliceTxB1), walletB) val fwdRbf = TypeCheckedForwarder(aliceRbf, bobRbf, alice2bob, bob2alice) aliceRbf ! Start(alice2bob.ref) @@ -1480,9 +1483,10 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val additionalFundingB = 5_000.sat val spliceOutputsA = List(TxOut(20_000 sat, Script.pay2wpkh(randomKey().publicKey))) val spliceOutputsB = List(TxOut(10_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) - val fundingParamsA1 = aliceParams.copy(localContribution = additionalFundingA, remoteContribution = additionalFundingB, sharedInput_opt = Some(sharedInputA), localOutputs = spliceOutputsA) - val fundingParamsB1 = bobParams.copy(localContribution = additionalFundingB, remoteContribution = additionalFundingA, sharedInput_opt = Some(sharedInputB), localOutputs = spliceOutputsB) + val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA, fundingAmountB = additionalFundingB, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = spliceOutputsB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) + val fundingParamsA1 = spliceFixtureParams.fundingParamsA + val fundingParamsB1 = spliceFixtureParams.fundingParamsB val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(fundingParamsA1, commitmentA1, walletA) val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(fundingParamsB1, commitmentB1, walletB) val fwdSplice = TypeCheckedForwarder(aliceSplice, bobSplice, alice2bob, bob2alice) @@ -1515,7 +1519,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val successA2 = alice2bob.expectMsgType[Succeeded] val successB2 = bob2alice.expectMsgType[Succeeded] - val (spliceTxA1, _, spliceTxB1, _) = fixtureParams.exchangeSigsBobFirst(fundingParamsB1, successA2, successB2) + val (spliceTxA1, commitmentA2, spliceTxB1, commitmentB2) = fixtureParams.exchangeSigsBobFirst(fundingParamsB1, successA2, successB2) assert(targetFeerate * 0.9 <= spliceTxA1.feerate && spliceTxA1.feerate <= targetFeerate * 1.25) walletA.publishTransaction(spliceTxA1.signedTx).pipeTo(probe.ref) probe.expectMsg(spliceTxA1.txId) @@ -1523,8 +1527,8 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice wants to make a large increase to the feerate of the splice transaction, which requires additional inputs. val fundingParamsA2 = fundingParamsA1.copy(targetFeerate = FeeratePerKw(10_000 sat)) val fundingParamsB2 = fundingParamsB1.copy(targetFeerate = FeeratePerKw(10_000 sat)) - val aliceRbf = fixtureParams.spawnTxBuilderSpliceRbfAlice(fundingParamsA2, commitmentA1, Seq(spliceTxA1), walletA) - val bobRbf = fixtureParams.spawnTxBuilderSpliceRbfBob(fundingParamsB2, commitmentB1, Seq(spliceTxB1), walletB) + val aliceRbf = fixtureParams.spawnTxBuilderSpliceRbfAlice(fundingParamsA2, parentCommitment = commitmentA1, replacedCommitment = commitmentA2, Seq(spliceTxA1), walletA) + val bobRbf = fixtureParams.spawnTxBuilderSpliceRbfBob(fundingParamsB2, parentCommitment = commitmentB1, replacedCommitment = commitmentB2, Seq(spliceTxB1), walletB) val fwdRbf = TypeCheckedForwarder(aliceRbf, bobRbf, alice2bob, bob2alice) aliceRbf ! Start(alice2bob.ref) @@ -1609,9 +1613,10 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice splices some funds in, which requires using an additional input. val additionalFundingA1 = 25_000.sat - val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA1, commitmentB1) - val fundingParamsA1 = aliceParams.copy(localContribution = additionalFundingA1, remoteContribution = 0 sat, sharedInput_opt = Some(sharedInputA)) - val fundingParamsB1 = bobParams.copy(localContribution = 0 sat, remoteContribution = additionalFundingA1, sharedInput_opt = Some(sharedInputB)) + val (sharedInputA, sharedInputB) = sharedInputs(commitmentA1, commitmentB1) + val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = additionalFundingA1, fundingAmountB = 0 sat, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, requireConfirmedInputs = aliceParams.requireConfirmedInputs) + val fundingParamsA1 = spliceFixtureParams.fundingParamsA + val fundingParamsB1 = spliceFixtureParams.fundingParamsB val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(fundingParamsA1, commitmentA1, walletA) val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(fundingParamsB1, commitmentB1, walletB) val fwdSplice = TypeCheckedForwarder(aliceSplice, bobSplice, alice2bob, bob2alice) @@ -1640,7 +1645,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val successA2 = alice2bob.expectMsgType[Succeeded] val successB2 = bob2alice.expectMsgType[Succeeded] - val (spliceTxA1, _, spliceTxB1, _) = fixtureParams.exchangeSigsBobFirst(fundingParamsB1, successA2, successB2) + val (spliceTxA1, commitmentA2, spliceTxB1, commitmentB2) = fixtureParams.exchangeSigsBobFirst(fundingParamsB1, successA2, successB2) assert(targetFeerate * 0.9 <= spliceTxA1.feerate && spliceTxA1.feerate <= targetFeerate * 1.25) walletA.publishTransaction(spliceTxA1.signedTx).pipeTo(probe.ref) probe.expectMsg(spliceTxA1.txId) @@ -1648,25 +1653,33 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice wants to: // - increase the feerate of the splice transaction // - splice more additional funds - // Before that, she sent htlcs to Bob which decreased her balance. - val initialBalanceA = commitmentA1.localCommit.spec.toLocal - val initialBalanceB = commitmentA1.localCommit.spec.toRemote + // Before that, she sent htlcs to Bob which decreased her balance in all active commitments. val amountPaid = 25_000_400 msat - val commitmentA2 = commitmentA1 - .modify(_.localCommit.spec.toLocal).setTo(initialBalanceA - amountPaid) - .modify(_.localCommit.spec.toRemote).setTo(initialBalanceB + amountPaid) - .modify(_.remoteCommit.spec.toLocal).setTo(initialBalanceB + amountPaid) - .modify(_.remoteCommit.spec.toRemote).setTo(initialBalanceA - amountPaid) - val commitmentB2 = commitmentB1 - .modify(_.localCommit.spec.toLocal).setTo(initialBalanceB + amountPaid) - .modify(_.localCommit.spec.toRemote).setTo(initialBalanceA - amountPaid) - .modify(_.remoteCommit.spec.toLocal).setTo(initialBalanceA - amountPaid) - .modify(_.remoteCommit.spec.toRemote).setTo(initialBalanceB + amountPaid) + val commitmentA1bis = commitmentA1 + .modify(_.localCommit.spec.toLocal).using(balance => balance - amountPaid) + .modify(_.localCommit.spec.toRemote).using(balance => balance + amountPaid) + .modify(_.remoteCommit.spec.toLocal).using(balance => balance + amountPaid) + .modify(_.remoteCommit.spec.toRemote).using(balance => balance - amountPaid) + val commitmentA2bis = commitmentA2 + .modify(_.localCommit.spec.toLocal).using(balance => balance - amountPaid) + .modify(_.localCommit.spec.toRemote).using(balance => balance + amountPaid) + .modify(_.remoteCommit.spec.toLocal).using(balance => balance + amountPaid) + .modify(_.remoteCommit.spec.toRemote).using(balance => balance - amountPaid) + val commitmentB1bis = commitmentB1 + .modify(_.localCommit.spec.toLocal).using(balance => balance + amountPaid) + .modify(_.localCommit.spec.toRemote).using(balance => balance - amountPaid) + .modify(_.remoteCommit.spec.toLocal).using(balance => balance - amountPaid) + .modify(_.remoteCommit.spec.toRemote).using(balance => balance + amountPaid) + val commitmentB2bis = commitmentB2 + .modify(_.localCommit.spec.toLocal).using(balance => balance + amountPaid) + .modify(_.localCommit.spec.toRemote).using(balance => balance - amountPaid) + .modify(_.remoteCommit.spec.toLocal).using(balance => balance - amountPaid) + .modify(_.remoteCommit.spec.toRemote).using(balance => balance + amountPaid) val additionalFundingA2 = 50_000 sat - val fundingParamsA2 = fundingParamsA1.copy(targetFeerate = FeeratePerKw(5_000 sat), localContribution = additionalFundingA2) - val fundingParamsB2 = fundingParamsB1.copy(targetFeerate = FeeratePerKw(5_000 sat), remoteContribution = additionalFundingA2) - val aliceRbf = fixtureParams.spawnTxBuilderSpliceRbfAlice(fundingParamsA2, commitmentA2, Seq(spliceTxA1), walletA) - val bobRbf = fixtureParams.spawnTxBuilderSpliceRbfBob(fundingParamsB2, commitmentB2, Seq(spliceTxB1), walletB) + val fundingParamsA2 = fundingParamsA1.copy(targetFeerate = FeeratePerKw(5_000 sat), localContribution = additionalFundingA2, remoteContribution = 0 sat) + val fundingParamsB2 = fundingParamsB1.copy(targetFeerate = FeeratePerKw(5_000 sat), localContribution = 0 sat, remoteContribution = additionalFundingA2) + val aliceRbf = fixtureParams.spawnTxBuilderSpliceRbfAlice(fundingParamsA2, parentCommitment = commitmentA1bis, replacedCommitment = commitmentA2bis, Seq(spliceTxA1), walletA) + val bobRbf = fixtureParams.spawnTxBuilderSpliceRbfBob(fundingParamsB2, parentCommitment = commitmentB1bis, replacedCommitment = commitmentB2bis, Seq(spliceTxB1), walletB) val fwdRbf = TypeCheckedForwarder(aliceRbf, bobRbf, alice2bob, bob2alice) aliceRbf ! Start(alice2bob.ref) @@ -1698,10 +1711,10 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val successA3 = alice2bob.expectMsgType[Succeeded] val successB3 = bob2alice.expectMsgType[Succeeded] val (spliceTxA2, commitmentA3, _, commitmentB3) = fixtureParams.exchangeSigsBobFirst(fundingParamsB2, successA3, successB3) - assert(commitmentA3.localCommit.spec.toLocal == commitmentA2.localCommit.spec.toLocal + additionalFundingA2) - assert(commitmentA3.localCommit.spec.toRemote == commitmentA2.localCommit.spec.toRemote) - assert(commitmentB3.localCommit.spec.toLocal == commitmentB2.localCommit.spec.toLocal) - assert(commitmentB3.localCommit.spec.toRemote == commitmentB2.localCommit.spec.toRemote + additionalFundingA2) + assert(commitmentA3.localCommit.spec.toLocal == commitmentA1bis.localCommit.spec.toLocal + additionalFundingA2) + assert(commitmentA3.localCommit.spec.toRemote == commitmentA1bis.localCommit.spec.toRemote) + assert(commitmentB3.localCommit.spec.toLocal == commitmentB1bis.localCommit.spec.toLocal) + assert(commitmentB3.localCommit.spec.toRemote == commitmentB1bis.localCommit.spec.toRemote + additionalFundingA2) walletA.publishTransaction(spliceTxA2.signedTx).pipeTo(probe.ref) probe.expectMsg(spliceTxA2.txId) @@ -1851,7 +1864,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val subtractedFundingB = 398_000 sat val spliceOutputsA = List(TxOut(99_000 sat, Script.pay2wpkh(randomKey().publicKey))) val spliceOutputsB = List(TxOut(397_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA, commitmentB) + val (sharedInputA, sharedInputB) = sharedInputs(commitmentA, commitmentB) val fundingParamsA1 = aliceParams.copy(localContribution = -subtractedFundingA, remoteContribution = -subtractedFundingB, sharedInput_opt = Some(sharedInputA), localOutputs = spliceOutputsA) val fundingParamsB1 = bobParams.copy(localContribution = -subtractedFundingB, remoteContribution = -subtractedFundingA, sharedInput_opt = Some(sharedInputB), localOutputs = spliceOutputsB) val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(fundingParamsA1, commitmentA, walletA) @@ -1912,9 +1925,10 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice splices some funds out, which creates two outputs (a shared output and a splice output). val subtractedFundingA = 30_000 sat val spliceOutputsA = List(TxOut(25_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA, commitmentB) - val fundingParamsA1 = aliceParams.copy(localContribution = -subtractedFundingA, remoteContribution = 0 sat, sharedInput_opt = Some(sharedInputA), localOutputs = spliceOutputsA) - val fundingParamsB1 = bobParams.copy(localContribution = 0 sat, remoteContribution = -subtractedFundingA, sharedInput_opt = Some(sharedInputB), localOutputs = Nil) + val (sharedInputA, sharedInputB) = sharedInputs(commitmentA, commitmentB) + val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = -subtractedFundingA, fundingAmountB = 0 sat, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = Nil, requireConfirmedInputs = aliceParams.requireConfirmedInputs) + val fundingParamsA1 = spliceFixtureParams.fundingParamsA + val fundingParamsB1 = spliceFixtureParams.fundingParamsB val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(fundingParamsA1, commitmentA, walletA) val bobSplice = fixtureParams.spawnTxBuilderSpliceBob(fundingParamsB1, commitmentB, walletB) val fwdSplice = TypeCheckedForwarder(aliceSplice, bobSplice, alice2bob, bob2alice) @@ -1941,7 +1955,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice <-- commit_sig --- Bob val Right(signingA3: InteractiveTxSigningSession.WaitingForSigs) = successA2.signingSession.receiveCommitSig(fixtureParams.nodeParamsA, fixtureParams.channelParamsA, successB2.commitSig)(akka.event.NoLogging) // Alice <-- tx_signatures --- Bob - val Left(error) = signingA3.receiveTxSigs(fixtureParams.nodeParamsA, successB2.signingSession.fundingTx.localSigs.copy(tlvStream = TlvStream.empty))(akka.event.NoLogging) + val Left(error) = signingA3.receiveTxSigs(fixtureParams.nodeParamsA, fixtureParams.channelParamsA, successB2.signingSession.fundingTx.localSigs.copy(tlvStream = TlvStream.empty))(akka.event.NoLogging) assert(error == InvalidFundingSignature(bobParams.channelId, Some(successA2.signingSession.fundingTx.txId))) } } @@ -1978,9 +1992,10 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice splices some funds out, but she doesn't have the same commitment index than Bob. val subtractedFundingA = 30_000 sat val spliceOutputsA = List(TxOut(25_000 sat, Script.pay2wpkh(randomKey().publicKey))) - val (sharedInputA, sharedInputB) = fixtureParams.sharedInputs(commitmentA, commitmentB) - val fundingParamsA1 = aliceParams.copy(localContribution = -subtractedFundingA, remoteContribution = 0 sat, sharedInput_opt = Some(sharedInputA), localOutputs = spliceOutputsA) - val fundingParamsB1 = bobParams.copy(localContribution = 0 sat, remoteContribution = -subtractedFundingA, sharedInput_opt = Some(sharedInputB), localOutputs = Nil) + val (sharedInputA, sharedInputB) = sharedInputs(commitmentA, commitmentB) + val spliceFixtureParams = fixtureParams.createSpliceFixtureParams(fundingTxIndex = 1, fundingAmountA = -subtractedFundingA, fundingAmountB = 0 sat, aliceParams.targetFeerate, aliceParams.dustLimit, aliceParams.lockTime, sharedInputA = sharedInputA, sharedInputB = sharedInputB, spliceOutputsA = spliceOutputsA, spliceOutputsB = Nil, requireConfirmedInputs = aliceParams.requireConfirmedInputs) + val fundingParamsA1 = spliceFixtureParams.fundingParamsA + val fundingParamsB1 = spliceFixtureParams.fundingParamsB val aliceSplice = fixtureParams.spawnTxBuilderSpliceAlice(fundingParamsA1, commitmentA, walletA) val invalidCommitmentB = commitmentB .modify(_.localCommit.index).setTo(6) @@ -2226,11 +2241,11 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice <-- tx_complete --- Bob probe.expectMsgType[SendMessage] // Alice --- tx_add_output --> Bob - bob ! ReceiveMessage(TxAddOutput(params.channelId, UInt64(2), 100_000 sat, params.fundingParamsB.fundingPubkeyScript)) + bob ! ReceiveMessage(TxAddOutput(params.channelId, UInt64(2), 100_000 sat, params.fundingPubkeyScript)) // Alice <-- tx_complete --- Bob probe.expectMsgType[SendMessage] // Alice --- tx_add_output --> Bob - bob ! ReceiveMessage(TxAddOutput(params.channelId, UInt64(4), 100_000 sat, params.fundingParamsB.fundingPubkeyScript)) + bob ! ReceiveMessage(TxAddOutput(params.channelId, UInt64(4), 100_000 sat, params.fundingPubkeyScript)) // Alice <-- tx_complete --- Bob probe.expectMsgType[SendMessage] // Alice --- tx_complete --> Bob @@ -2251,7 +2266,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice <-- tx_complete --- Bob probe.expectMsgType[SendMessage] // Alice --- tx_add_output --> Bob - bob ! ReceiveMessage(TxAddOutput(params.channelId, UInt64(2), fundingParamsB.fundingAmount, fundingParamsB.fundingPubkeyScript)) + bob ! ReceiveMessage(TxAddOutput(params.channelId, UInt64(2), fundingParamsB.fundingAmount, params.fundingPubkeyScript)) // Alice <-- tx_complete --- Bob probe.expectMsgType[SendMessage] // Alice --- tx_complete --> Bob @@ -2270,7 +2285,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice <-- tx_complete --- Bob probe.expectMsgType[SendMessage] // Alice --- tx_add_output --> Bob - bob ! ReceiveMessage(TxAddOutput(params.channelId, UInt64(2), 100_001 sat, params.fundingParamsB.fundingPubkeyScript)) + bob ! ReceiveMessage(TxAddOutput(params.channelId, UInt64(2), 100_001 sat, params.fundingPubkeyScript)) assert(probe.expectMsgType[RemoteFailure].cause == InvalidSharedOutputAmount(params.channelId, UInt64(2), 100_001 sat, 100_000 sat)) } @@ -2279,7 +2294,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val wallet = new SingleKeyOnChainWallet() val params = createFixtureParams(100_000 sat, 0 sat, FeeratePerKw(5000 sat), 330 sat, 0) val previousCommitment = CommitmentsSpec.makeCommitments(25_000_000 msat, 50_000_000 msat).active.head - val fundingParams = params.fundingParamsB.copy(sharedInput_opt = Some(Multisig2of2Input(previousCommitment.commitInput, randomKey().publicKey, randomKey().publicKey))) + val fundingParams = params.fundingParamsB.copy(sharedInput_opt = Some(Multisig2of2Input(previousCommitment.commitInput, 0, randomKey().publicKey))) val bob = params.spawnTxBuilderSpliceBob(fundingParams, previousCommitment, wallet) bob ! Start(probe.ref) // Alice --- tx_add_input --> Bob @@ -2295,7 +2310,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val params = createFixtureParams(100_000 sat, 0 sat, FeeratePerKw(5000 sat), 330 sat, 0) val previousCommitment = CommitmentsSpec.makeCommitments(25_000_000 msat, 50_000_000 msat).active.head val fundingTx = Transaction(2, Nil, Seq(TxOut(50_000 sat, Script.pay2wpkh(randomKey().publicKey)), TxOut(20_000 sat, Script.pay2wpkh(randomKey().publicKey))), 0) - val sharedInput = Multisig2of2Input(InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head, Nil), randomKey().publicKey, randomKey().publicKey) + val sharedInput = Multisig2of2Input(InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head, Nil), 0, randomKey().publicKey) val bob = params.spawnTxBuilderSpliceBob(params.fundingParamsB.copy(sharedInput_opt = Some(sharedInput)), previousCommitment, wallet) bob ! Start(probe.ref) // Alice --- tx_add_input --> Bob @@ -2317,7 +2332,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice <-- tx_complete --- Bob probe.expectMsgType[SendMessage] // Alice --- tx_add_output --> Bob - bob ! ReceiveMessage(TxAddOutput(params.channelId, UInt64(2), 100_000 sat, params.fundingParamsB.fundingPubkeyScript)) + bob ! ReceiveMessage(TxAddOutput(params.channelId, UInt64(2), 100_000 sat, params.fundingPubkeyScript)) // Alice <-- tx_complete --- Bob probe.expectMsgType[SendMessage] // Alice --- tx_add_output --> Bob @@ -2341,7 +2356,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice <-- tx_complete --- Bob probe.expectMsgType[SendMessage] // Alice --- tx_add_output --> Bob - bob ! ReceiveMessage(TxAddOutput(params.channelId, UInt64(2), 100_000 sat, params.fundingParamsB.fundingPubkeyScript)) + bob ! ReceiveMessage(TxAddOutput(params.channelId, UInt64(2), 100_000 sat, params.fundingPubkeyScript)) // Alice <-- tx_complete --- Bob probe.expectMsgType[SendMessage] // Alice --- tx_add_output --> Bob @@ -2460,7 +2475,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit // Alice <-- tx_signatures --- Bob val signingA = alice2bob.expectMsgType[Succeeded].signingSession val signingB = bob2alice.expectMsgType[Succeeded].signingSession - val Left(error) = signingA.receiveTxSigs(params.nodeParamsA, signingB.fundingTx.localSigs)(akka.event.NoLogging) + val Left(error) = signingA.receiveTxSigs(params.nodeParamsA, params.channelParamsA, signingB.fundingTx.localSigs)(akka.event.NoLogging) assert(error == UnexpectedFundingSignatures(params.channelId)) } @@ -2488,7 +2503,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val successB1 = bob2alice.expectMsgType[Succeeded] val Right(signingA2: InteractiveTxSigningSession.WaitingForSigs) = successA1.signingSession.receiveCommitSig(params.nodeParamsA, params.channelParamsA, successB1.commitSig)(akka.event.NoLogging) // Alice <-- tx_signatures --- Bob - val Left(error) = signingA2.receiveTxSigs(params.nodeParamsA, successB1.signingSession.fundingTx.localSigs.copy(witnesses = Seq(Script.witnessPay2wpkh(randomKey().publicKey, ByteVector.fill(73)(0)))))(akka.event.NoLogging) + val Left(error) = signingA2.receiveTxSigs(params.nodeParamsA, params.channelParamsA, successB1.signingSession.fundingTx.localSigs.copy(witnesses = Seq(Script.witnessPay2wpkh(randomKey().publicKey, ByteVector.fill(73)(0)))))(akka.event.NoLogging) assert(error.isInstanceOf[InvalidFundingSignature]) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index aec0f4c837..1f47e3e4ad 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -3606,7 +3606,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] import initialState.commitments.latest.{localParams, remoteParams} - val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.channelKeyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) + val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.channelKeyManager.fundingPublicKey(localParams.fundingKeyPath, fundingTxIndex = 0).publicKey, initialState.commitments.latest.remoteFundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) // actual test starts here bob2alice.forward(alice, annSigsB) awaitAssert { @@ -3633,7 +3633,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with bob ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null) val annSigsB = bob2alice.expectMsgType[AnnouncementSignatures] import initialState.commitments.latest.{localParams, remoteParams} - val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.channelKeyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey, remoteParams.fundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) + val channelAnn = Announcements.makeChannelAnnouncement(Alice.nodeParams.chainHash, annSigsA.shortChannelId, Alice.nodeParams.nodeId, remoteParams.nodeId, Alice.channelKeyManager.fundingPublicKey(localParams.fundingKeyPath, fundingTxIndex = 0).publicKey, initialState.commitments.latest.remoteFundingPubKey, annSigsA.nodeSignature, annSigsB.nodeSignature, annSigsA.bitcoinSignature, annSigsB.bitcoinSignature) bob2alice.forward(alice) awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].channelAnnouncement.contains(channelAnn)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManagerSpec.scala index 57cc1b0df7..936596afe0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManagerSpec.scala @@ -65,7 +65,7 @@ class LocalChannelKeyManagerSpec extends AnyFunSuite { val seed = ByteVector.fromValidHex("17b086b228025fa8f4416324b6ba2ec36e68570ae2fc3d392520969f2a9d0c1501") val channelKeyManager = new LocalChannelKeyManager(seed, Block.TestnetGenesisBlock.hash) val fundingKeyPath = makefundingKeyPath(hex"be4fa97c62b9f88437a3be577b31eb48f2165c7bc252194a15ff92d995778cfb", isInitiator = true) - val fundingPub = channelKeyManager.fundingPublicKey(fundingKeyPath) + val fundingPub = channelKeyManager.fundingPublicKey(fundingKeyPath, fundingTxIndex = 0) val localParams = TestConstants.Alice.channelParams.copy(fundingKeyPath = fundingKeyPath) val channelKeyPath = channelKeyManager.keyPath(localParams, ChannelConfig.standard) @@ -82,7 +82,7 @@ class LocalChannelKeyManagerSpec extends AnyFunSuite { val seed = ByteVector.fromValidHex("aeb3e9b5642cd4523e9e09164047f60adb413633549c3c6189192921311894d501") val channelKeyManager = new LocalChannelKeyManager(seed, Block.TestnetGenesisBlock.hash) val fundingKeyPath = makefundingKeyPath(hex"06535806c1aa73971ec4877a5e2e684fa636136c073810f190b63eefc58ca488", isInitiator = false) - val fundingPub = channelKeyManager.fundingPublicKey(fundingKeyPath) + val fundingPub = channelKeyManager.fundingPublicKey(fundingKeyPath, fundingTxIndex = 0) val localParams = TestConstants.Alice.channelParams.copy(fundingKeyPath = fundingKeyPath) val channelKeyPath = channelKeyManager.keyPath(localParams, ChannelConfig.standard) @@ -99,7 +99,7 @@ class LocalChannelKeyManagerSpec extends AnyFunSuite { val seed = ByteVector.fromValidHex("d8d5431487c2b19ee6486aad6c3bdfb99d10b727bade7fa848e2ab7901c15bff01") val channelKeyManager = new LocalChannelKeyManager(seed, Block.LivenetGenesisBlock.hash) val fundingKeyPath = makefundingKeyPath(hex"ec1c41cd6be2b6e4ef46c1107f6c51fbb2066d7e1f7720bde4715af233ae1322", isInitiator = true) - val fundingPub = channelKeyManager.fundingPublicKey(fundingKeyPath) + val fundingPub = channelKeyManager.fundingPublicKey(fundingKeyPath, fundingTxIndex = 0) val localParams = TestConstants.Alice.channelParams.copy(fundingKeyPath = fundingKeyPath) val channelKeyPath = channelKeyManager.keyPath(localParams, ChannelConfig.standard) @@ -116,7 +116,7 @@ class LocalChannelKeyManagerSpec extends AnyFunSuite { val seed = ByteVector.fromValidHex("4b809dd593b36131c454d60c2f7bdfd49d12ec455e5b657c47a9ca0f5dfc5eef01") val channelKeyManager = new LocalChannelKeyManager(seed, Block.LivenetGenesisBlock.hash) val fundingKeyPath = makefundingKeyPath(hex"2b4f045be5303d53f9d3a84a1e70c12251168dc29f300cf9cece0ec85cd8182b", isInitiator = false) - val fundingPub = channelKeyManager.fundingPublicKey(fundingKeyPath) + val fundingPub = channelKeyManager.fundingPublicKey(fundingKeyPath, fundingTxIndex = 0) val localParams = TestConstants.Alice.channelParams.copy(fundingKeyPath = fundingKeyPath) val channelKeyPath = channelKeyManager.keyPath(localParams, ChannelConfig.standard) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalNodeKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalNodeKeyManagerSpec.scala index 3b33f54809..89755aa8b7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalNodeKeyManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalNodeKeyManagerSpec.scala @@ -45,7 +45,8 @@ class LocalNodeKeyManagerSpec extends AnyFunSuite { val channelKeyManager2 = new LocalChannelKeyManager(seed, Block.LivenetGenesisBlock.hash) assert(nodeKeyManager1.nodeId != nodeKeyManager2.nodeId) val keyPath = KeyPath(1L :: Nil) - assert(channelKeyManager1.fundingPublicKey(keyPath) != channelKeyManager2.fundingPublicKey(keyPath)) + assert(channelKeyManager1.fundingPublicKey(keyPath, fundingTxIndex = 0) != channelKeyManager2.fundingPublicKey(keyPath, fundingTxIndex = 0)) + assert(channelKeyManager1.fundingPublicKey(keyPath, fundingTxIndex = 42) != channelKeyManager2.fundingPublicKey(keyPath, fundingTxIndex = 42)) assert(channelKeyManager1.commitmentPoint(keyPath, 1) != channelKeyManager2.commitmentPoint(keyPath, 1)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala index 9650aa26bb..95261618a7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/ChannelIntegrationSpec.scala @@ -44,7 +44,6 @@ import java.util.UUID import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ import scala.jdk.CollectionConverters._ -import scala.util.{Success, Try} /** * Created by t-bast on 21/09/2020. @@ -434,8 +433,8 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec { val localPerCommitmentPointF = keyManagerF.commitmentPoint(channelKeyPathF, commitmentsF.localCommitIndex) val revokedCommitTx = { val commitTx = localCommitF.commitTxAndRemoteSig.commitTx - val localSig = keyManagerF.sign(commitTx, keyManagerF.fundingPublicKey(commitmentsF.params.localParams.fundingKeyPath), TxOwner.Local, commitmentFormat) - Transactions.addSigs(commitTx, keyManagerF.fundingPublicKey(commitmentsF.params.localParams.fundingKeyPath).publicKey, commitmentsF.params.remoteParams.fundingPubKey, localSig, localCommitF.commitTxAndRemoteSig.remoteSig).tx + val localSig = keyManagerF.sign(commitTx, keyManagerF.fundingPublicKey(commitmentsF.params.localParams.fundingKeyPath, commitmentsF.latest.fundingTxIndex), TxOwner.Local, commitmentFormat) + Transactions.addSigs(commitTx, keyManagerF.fundingPublicKey(commitmentsF.params.localParams.fundingKeyPath, commitmentsF.latest.fundingTxIndex).publicKey, commitmentsF.latest.remoteFundingPubKey, localSig, localCommitF.commitTxAndRemoteSig.remoteSig).tx } val htlcSuccess = htlcSuccessTxs.zip(Seq(preimage1, preimage2)).map { case (htlcTxAndSigs, preimage) => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala index 874eff679f..ede35793a2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala @@ -701,7 +701,7 @@ object PaymentPacketSpec { def makeCommitments(channelId: ByteVector32, testAvailableBalanceForSend: MilliSatoshi = 50000000 msat, testAvailableBalanceForReceive: MilliSatoshi = 50000000 msat, testCapacity: Satoshi = 100000 sat, channelFeatures: ChannelFeatures = ChannelFeatures()): Commitments = { val channelReserve = testCapacity * 0.01 val localParams = LocalParams(null, null, null, Long.MaxValue.msat, Some(channelReserve), null, null, 0, isInitiator = true, None, None, null) - val remoteParams = RemoteParams(randomKey().publicKey, null, UInt64.MaxValue, Some(channelReserve), null, null, maxAcceptedHtlcs = 0, null, null, null, null, null, null, None) + val remoteParams = RemoteParams(randomKey().publicKey, null, UInt64.MaxValue, Some(channelReserve), null, null, maxAcceptedHtlcs = 0, null, null, null, null, null, None) val commitInput = InputInfo(OutPoint(randomBytes32(), 1), TxOut(testCapacity, Nil), Nil) val localCommit = LocalCommit(0, null, CommitTxAndRemoteSig(Transactions.CommitTx(commitInput, null), null), Nil) val remoteCommit = RemoteCommit(0, null, null, randomKey().publicKey) @@ -711,7 +711,7 @@ object PaymentPacketSpec { new Commitments( ChannelParams(channelId, ChannelConfig.standard, channelFeatures, localParams, remoteParams, channelFlags), CommitmentChanges(localChanges, remoteChanges, 0, 0), - List(Commitment(0, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.Locked, localCommit, remoteCommit, None)), + List(Commitment(0, null, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.Locked, localCommit, remoteCommit, None)), inactive = Nil, Right(randomKey().publicKey), ShaChain.init, diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala index 88d82d0d61..b358bd5718 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/ChannelCodecsSpec.scala @@ -274,7 +274,6 @@ object ChannelCodecsSpec { htlcMinimum = 5000 msat, toSelfDelay = CltvExpiryDelta(144), maxAcceptedHtlcs = 50, - fundingPubKey = PrivateKey(ByteVector32(ByteVector.fill(32)(1)) :+ 1.toByte).publicKey, revocationBasepoint = PrivateKey(ByteVector.fill(32)(2)).publicKey, paymentBasepoint = PrivateKey(ByteVector.fill(32)(3)).publicKey, delayedPaymentBasepoint = PrivateKey(ByteVector.fill(32)(4)).publicKey, @@ -304,7 +303,9 @@ object ChannelCodecsSpec { val channelUpdate = Announcements.makeChannelUpdate(ByteVector32(ByteVector.fill(32)(1)), randomKey(), randomKey().publicKey, ShortChannelId(142553), CltvExpiryDelta(42), 15 msat, 575 msat, 53, Channel.MAX_FUNDING_WITHOUT_WUMBO.toMilliSatoshi) val fundingTx = Transaction.read("0200000001adbb20ea41a8423ea937e76e8151636bf6093b70eaff942930d20576600521fd000000006b48304502210090587b6201e166ad6af0227d3036a9454223d49a1f11839c1a362184340ef0240220577f7cd5cca78719405cbf1de7414ac027f0239ef6e214c90fcaab0454d84b3b012103535b32d5eb0a6ed0982a0479bbadc9868d9836f6ba94dd5a63be16d875069184ffffffff028096980000000000220020c015c4a6be010e21657068fc2e6a9d02b27ebe4d490a25846f7237f104d1a3cd20256d29010000001600143ca33c2e4446f4a305f23c80df8ad1afdcf652f900000000") val fundingAmount = fundingTx.txOut.head.amount - val commitmentInput = Funding.makeFundingInputInfo(fundingTx.hash, 0, fundingAmount, channelKeyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey, remoteParams.fundingPubKey) + val fundingTxIndex = 0 + val remoteFundingPubKey = PrivateKey(ByteVector32(ByteVector.fill(32)(1)) :+ 1.toByte).publicKey + val commitmentInput = Funding.makeFundingInputInfo(fundingTx.hash, 0, fundingAmount, channelKeyManager.fundingPublicKey(localParams.fundingKeyPath, fundingTxIndex).publicKey, remoteFundingPubKey) val remoteSig = ByteVector64(hex"2148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab7172bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d") val commitTx = Transaction( version = 2, @@ -312,7 +313,7 @@ object ChannelCodecsSpec { outPoint = commitmentInput.outPoint, signatureScript = ByteVector.empty, sequence = 0, - witness = Scripts.witness2of2(randomBytes64(), remoteSig, channelKeyManager.fundingPublicKey(localParams.fundingKeyPath).publicKey, remoteParams.fundingPubKey)) :: Nil, + witness = Scripts.witness2of2(randomBytes64(), remoteSig, channelKeyManager.fundingPublicKey(localParams.fundingKeyPath, fundingTxIndex).publicKey, remoteFundingPubKey)) :: Nil, txOut = Nil, lockTime = 0 ) @@ -323,7 +324,7 @@ object ChannelCodecsSpec { val commitments = Commitments( ChannelParams(channelId, ChannelConfig.standard, ChannelFeatures(), localParams, remoteParams, channelFlags), CommitmentChanges(LocalChanges(Nil, Nil, Nil), RemoteChanges(Nil, Nil, Nil), localNextHtlcId = 32, remoteNextHtlcId = 4), - Seq(Commitment(fundingTxIndex = 0, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.NotLocked, localCommit, remoteCommit, None)), + Seq(Commitment(fundingTxIndex, remoteFundingPubKey, LocalFundingStatus.SingleFundedUnconfirmedFundingTx(None), RemoteFundingStatus.NotLocked, localCommit, remoteCommit, None)), remoteNextCommitInfo = Right(randomKey().publicKey), remotePerCommitmentSecrets = ShaChain.init, originChannels = origins) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1Spec.scala index 4aa4c0f6e0..c7fecf052a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1Spec.scala @@ -60,27 +60,7 @@ class ChannelCodecs1Spec extends AnyFunSuite { require(localParamsCodec(ChannelVersion.STATIC_REMOTEKEY).decode(staticRemoteKey.toBitVector).require.value.upfrontShutdownScript_opt.isDefined) } - test("encode/decode remote params") { - val o = RemoteParams( - nodeId = randomKey().publicKey, - dustLimit = Satoshi(Random.nextInt(Int.MaxValue)), - maxHtlcValueInFlightMsat = UInt64(Random.nextInt(Int.MaxValue)), - requestedChannelReserve_opt = Some(Satoshi(Random.nextInt(Int.MaxValue))), - htlcMinimum = MilliSatoshi(Random.nextInt(Int.MaxValue)), - toSelfDelay = CltvExpiryDelta(Random.nextInt(Short.MaxValue)), - maxAcceptedHtlcs = Random.nextInt(Short.MaxValue), - fundingPubKey = randomKey().publicKey, - revocationBasepoint = randomKey().publicKey, - paymentBasepoint = randomKey().publicKey, - delayedPaymentBasepoint = randomKey().publicKey, - htlcBasepoint = randomKey().publicKey, - initFeatures = TestConstants.Alice.nodeParams.features.initFeatures(), - upfrontShutdownScript_opt = None) - val encoded = remoteParamsCodec.encode(o).require - val decoded = remoteParamsCodec.decodeValue(encoded).require - assert(o == decoded) - - // Backwards-compatibility: decode remote params with global features. + test("decode remote params with global features (backward compat)") { val withGlobalFeatures = hex"03c70c3b813815a8b79f41622b6f2c343fa24d94fb35fa7110bbb3d4d59cd9612e0000000059844cbc000000001b1524ea000000001503cbac000000006b75d3272e38777e029fa4e94066163024177311de7ba1befec2e48b473c387bbcee1484bf276a54460215e3dfb8e6f262222c5f343f5e38c5c9a43d2594c7f06dd7ac1a4326c665dd050347aba4d56d7007a7dcf03594423dccba9ed700d11e665d261594e1154203df31020d457ee336ba6eeb328d00f1b8bd8bfefb8a4dcd5af6db4c438b7ec5106c7edc0380df17e1beb0f238e51a39122ac4c6fb57f3c4f5b7bc9432f991b1ef4a8af3570002020000018a" val withGlobalFeaturesDecoded = remoteParamsCodec.decode(withGlobalFeatures.bits).require.value assert(withGlobalFeaturesDecoded.initFeatures.toByteVector == hex"028a") diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala index 2dfc9d01e1..dc41a85b76 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala @@ -70,7 +70,6 @@ class ChannelCodecs4Spec extends AnyFunSuite { randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, - randomKey().publicKey, Features(), None) @@ -106,7 +105,6 @@ class ChannelCodecs4Spec extends AnyFunSuite { randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, - randomKey().publicKey, Features(ChannelRangeQueries -> Optional, VariableLengthOnion -> Mandatory, PaymentSecret -> Mandatory), None) assert(codec.decodeValue(codec.encode(remoteParams).require).require == remoteParams) @@ -135,7 +133,7 @@ class ChannelCodecs4Spec extends AnyFunSuite { Transaction(2, Seq(TxIn(fundingInput.outPoint, Nil, 0)), Seq(TxOut(150_000 sat, Script.pay2wpkh(randomKey().publicKey))), 0), ) val waitingForSigs = InteractiveTxSigningSession.WaitingForSigs( - InteractiveTxParams(channelId, isInitiator = true, 100_000 sat, 75_000 sat, None, ByteVector.empty, Nil, 0, 330 sat, FeeratePerKw(500 sat), None, RequireConfirmedInputs(forLocal = false, forRemote = false)), + InteractiveTxParams(channelId, isInitiator = true, 100_000 sat, 75_000 sat, None, randomKey().publicKey, Nil, 0, 330 sat, FeeratePerKw(500 sat), None, RequireConfirmedInputs(forLocal = false, forRemote = false)), fundingTxIndex = 0, PartiallySignedSharedTransaction(fundingTx, TxSignatures(channelId, randomBytes32(), Nil)), Left(UnsignedLocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(1000 sat), 100_000_000 msat, 75_000_000 msat), commitTx, Nil)),