From fe0252cb6be4e1c22a1607be8142519b62d0c4e9 Mon Sep 17 00:00:00 2001 From: t-bast Date: Fri, 4 Apr 2025 18:48:09 +0200 Subject: [PATCH] Refactor output redeem type from `TransactionWithInputInfo` We refactor `TransactionWithInputInfo` to extract the redeem information from the `InputInfo` class. This is necessary for taproot channels (and other future segwit versions) to easily integrate into our transaction class hierarchy: this way we can explicitly specify which kind of redeem paths are possible for each transaction type. For example, we can now express that a `CommitTx` uses either a segwit v0 input or a taproot input that we can spend via the key path. Note that when we'll later introduce v3 commitments, we may have txs that can be spent: - via segwit v0 for non-taproot channels - via a taproot script path for taproot channels - AND via a taproot key path for v3 taproot channels This is why we cannot simply pick a single one of the taproot redeem paths, we may need to support multiple of them in the future. The core of this PR is just the following small type changes, that should be reviewed first: - we introduce the `RedeemInfo` trait - we remove the redeem information from `InputInfo`, which now becomes a trivial container for `OutPoint` + `TxOut` - we update `CommitmentOutputLink` to use an abstract `RedeemInfo` in the `CommitmentOutput`, which will make it easy to add taproot outputs without modifying the `CommitmentOutputLink` type - we take the `commitmentFormat` parameter when creating transactions, which makes it easy to pattern match on it to produce the right redeem information Note that this highlighted that we unnecessarily recomputed HTLC scripts when creating claim-htlc transactions: we can directly leverage the existing `CommitmentOutput`s. I wondered if we could do this for every transaction creation, but we can't because we need to handle spending revoked commitments, for which we don't have a `CommitmentSpec` object and thus cannot create the set of `CommitmentOutput`s. --- .../eclair/SpendFromChannelAddress.scala | 8 +- .../fr/acinq/eclair/channel/ChannelData.scala | 2 +- .../fr/acinq/eclair/channel/Commitments.scala | 33 +- .../fr/acinq/eclair/channel/Helpers.scala | 108 +-- .../fr/acinq/eclair/channel/fsm/Channel.scala | 16 +- .../channel/fsm/ChannelOpenDualFunded.scala | 2 +- .../channel/fund/InteractiveTxBuilder.scala | 11 +- .../channel/publish/ReplaceableTxFunder.scala | 6 +- .../publish/ReplaceableTxPrePublisher.scala | 4 +- .../eclair/transactions/CommitmentSpec.scala | 18 +- .../eclair/transactions/Transactions.scala | 638 ++++++++++-------- .../channel/version0/ChannelCodecs0.scala | 30 +- .../channel/version0/ChannelTypes0.scala | 29 +- .../channel/version1/ChannelCodecs1.scala | 30 +- .../channel/version2/ChannelCodecs2.scala | 40 +- .../channel/version3/ChannelCodecs3.scala | 50 +- .../channel/version4/ChannelCodecs4.scala | 51 +- .../eclair/channel/ChannelDataSpec.scala | 4 +- .../eclair/channel/CommitmentsSpec.scala | 18 +- .../fr/acinq/eclair/channel/HelpersSpec.scala | 23 +- .../channel/InteractiveTxBuilderSpec.scala | 10 +- .../publish/ReplaceableTxFunderSpec.scala | 21 +- .../channel/publish/TxPublisherSpec.scala | 14 +- .../channel/states/e/NormalStateSpec.scala | 6 +- .../channel/states/e/OfflineStateSpec.scala | 2 +- .../io/OpenChannelInterceptorSpec.scala | 4 +- .../io/PendingChannelsRateLimiterSpec.scala | 4 +- .../eclair/json/JsonSerializersSpec.scala | 16 +- .../eclair/payment/PaymentPacketSpec.scala | 6 +- .../payment/PostRestartHtlcCleanerSpec.scala | 4 +- .../eclair/transactions/TestVectorsSpec.scala | 11 +- .../transactions/TransactionsSpec.scala | 183 +++-- .../internal/channel/ChannelCodecsSpec.scala | 8 +- .../channel/version4/ChannelCodecs4Spec.scala | 9 +- 34 files changed, 769 insertions(+), 650 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala b/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala index 8f799def24..b7e14535e1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/SpendFromChannelAddress.scala @@ -4,8 +4,8 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.{ByteVector64, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Script, ScriptWitness, Transaction, TxIn, TxOut, addressToPublicKeyScript} import fr.acinq.eclair.blockchain.fee.FeeratePerKw import fr.acinq.eclair.transactions.Scripts.multiSig2of2 +import fr.acinq.eclair.transactions.Transactions._ import fr.acinq.eclair.transactions.{Scripts, Transactions} -import fr.acinq.eclair.transactions.Transactions.{DefaultCommitmentFormat, InputInfo, PlaceHolderPubKey, PlaceHolderSig, TxOwner} import scodec.bits.ByteVector import scala.concurrent.Future @@ -42,10 +42,10 @@ trait SpendFromChannelAddress { outPoint = unsignedTx.txIn.head.outPoint inputTx <- appKit.wallet.getTransaction(outPoint.txid) localFundingPubkey = appKit.nodeParams.channelKeyManager.fundingPublicKey(fundingKeyPath, fundingTxIndex) - fundingRedeemScript = multiSig2of2(localFundingPubkey.publicKey, remoteFundingPubkey) - inputInfo = InputInfo(outPoint, inputTx.txOut(outPoint.index.toInt), fundingRedeemScript) + redeemInfo = RedeemInfo.SegwitV0(multiSig2of2(localFundingPubkey.publicKey, remoteFundingPubkey)) + inputInfo = InputInfo(outPoint, inputTx.txOut(outPoint.index.toInt)) localSig = appKit.nodeParams.channelKeyManager.sign( - Transactions.SpliceTx(inputInfo, unsignedTx), // classify as splice, doesn't really matter + Transactions.SpliceTx(inputInfo, redeemInfo, unsignedTx), // classify as splice, doesn't really matter localFundingPubkey, TxOwner.Local, // unused DefaultCommitmentFormat, // unused 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 2e1a9c887f..02cf188080 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 @@ -277,7 +277,7 @@ final case class RES_ADD_FAILED[+T <: ChannelException](c: CMD_ADD_HTLC, t: T, c sealed trait HtlcResult object HtlcResult { sealed trait Fulfill extends HtlcResult { def paymentPreimage: ByteVector32 } - case class RemoteFulfill(fulfill: UpdateFulfillHtlc) extends Fulfill { override val paymentPreimage = fulfill.paymentPreimage } + case class RemoteFulfill(fulfill: UpdateFulfillHtlc) extends Fulfill { override val paymentPreimage: ByteVector32 = fulfill.paymentPreimage } case class OnChainFulfill(paymentPreimage: ByteVector32) extends Fulfill sealed trait Fail extends HtlcResult case class RemoteFail(fail: UpdateFailHtlc) extends Fail 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 2bf8cc5e34..73ba0c29c4 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 @@ -3,7 +3,7 @@ package fr.acinq.eclair.channel import akka.event.LoggingAdapter import fr.acinq.bitcoin.crypto.musig2.IndividualNonce import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} -import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, Satoshi, SatoshiLong, Script, Transaction, TxId} +import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, Satoshi, SatoshiLong, Transaction, TxId} import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw, FeeratesPerKw, OnChainFeeConf} import fr.acinq.eclair.channel.Helpers.Closing import fr.acinq.eclair.channel.Monitoring.{Metrics, Tags} @@ -192,10 +192,10 @@ case class LocalCommit(index: Long, spec: CommitmentSpec, commitTxAndRemoteSig: object LocalCommit { def fromCommitSig(keyManager: ChannelKeyManager, params: ChannelParams, fundingTxId: TxId, - fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo, + fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo, redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, commit: CommitSig, localCommitIndex: Long, spec: CommitmentSpec, localPerCommitmentPoint: PublicKey): Either[ChannelException, LocalCommit] = { - val (localCommitTx, htlcTxs) = Commitment.makeLocalTxs(keyManager, params.channelConfig, params.channelFeatures, localCommitIndex, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, localPerCommitmentPoint, spec) - if (!localCommitTx.checkSig(commit.signature, remoteFundingPubKey, TxOwner.Remote, params.commitmentFormat)) { + val (localCommitTx, htlcTxs) = Commitment.makeLocalTxs(keyManager, params.channelConfig, params.channelFeatures, localCommitIndex, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, redeemInfo, localPerCommitmentPoint, spec) + if (!localCommitTx.checkSig(commit.signature, remoteFundingPubKey, TxOwner.Remote, params.commitmentFormat, extraUtxos = Map.empty)) { return Left(InvalidCommitmentSignature(params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx)) } val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index) @@ -205,7 +205,7 @@ object LocalCommit { val remoteHtlcPubkey = Generators.derivePubKey(params.remoteParams.htlcBasepoint, localPerCommitmentPoint) val htlcTxsAndRemoteSigs = sortedHtlcTxs.zip(commit.htlcSignatures).toList.map { case (htlcTx: HtlcTx, remoteSig) => - if (!htlcTx.checkSig(remoteSig, remoteHtlcPubkey, TxOwner.Remote, params.commitmentFormat)) { + if (!htlcTx.checkSig(remoteSig, remoteHtlcPubkey, TxOwner.Remote, params.commitmentFormat, extraUtxos = Map.empty)) { return Left(InvalidHtlcSignature(params.channelId, htlcTx.tx.txid)) } HtlcTxAndRemoteSig(htlcTx, remoteSig) @@ -216,8 +216,8 @@ object LocalCommit { /** The remote commitment maps to a commitment transaction that only our peer can sign and broadcast. */ case class RemoteCommit(index: Long, spec: CommitmentSpec, txid: TxId, remotePerCommitmentPoint: PublicKey) { - def sign(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) + def sign(keyManager: ChannelKeyManager, params: ChannelParams, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo, redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0): CommitSig = { + val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(keyManager, params.channelConfig, params.channelFeatures, index, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, redeemInfo, remotePerCommitmentPoint, spec) val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), TxOwner.Remote, params.commitmentFormat, Map.empty) val channelKeyPath = keyManager.keyPath(params.localParams, params.channelConfig) val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index) @@ -246,6 +246,7 @@ case class Commitment(fundingTxIndex: Long, localFundingStatus: LocalFundingStatus, remoteFundingStatus: RemoteFundingStatus, localCommit: LocalCommit, remoteCommit: RemoteCommit, nextRemoteCommit_opt: Option[NextRemoteCommit]) { val commitInput: InputInfo = localCommit.commitTxAndRemoteSig.commitTx.input + val redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0 = localCommit.commitTxAndRemoteSig.commitTx.redeemInfo val fundingTxId: TxId = commitInput.outPoint.txid val capacity: Satoshi = commitInput.txOut.amount /** Once the funding transaction is confirmed, short_channel_id matching this transaction. */ @@ -621,7 +622,7 @@ case class Commitment(fundingTxIndex: Long, def sendCommit(keyManager: ChannelKeyManager, params: ChannelParams, changes: CommitmentChanges, remoteNextPerCommitmentPoint: PublicKey, batchSize: Int)(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, fundingTxIndex, remoteFundingPubKey, commitInput, remoteNextPerCommitmentPoint, spec) + val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(keyManager, params.channelConfig, params.channelFeatures, remoteCommit.index + 1, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, redeemInfo, remoteNextPerCommitmentPoint, spec) val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), TxOwner.Remote, params.commitmentFormat, Map.empty) val sortedHtlcTxs: Seq[TransactionWithInputInfo] = htlcTxs.sortBy(_.input.outPoint.index) @@ -650,7 +651,7 @@ case class Commitment(fundingTxIndex: Long, // and will increment our index val localCommitIndex = localCommit.index + 1 val spec = CommitmentSpec.reduce(localCommit.spec, changes.localChanges.acked, changes.remoteChanges.proposed) - LocalCommit.fromCommitSig(keyManager, params, fundingTxId, fundingTxIndex, remoteFundingPubKey, commitInput, commit, localCommitIndex, spec, localPerCommitmentPoint).map { localCommit1 => + LocalCommit.fromCommitSig(keyManager, params, fundingTxId, fundingTxIndex, remoteFundingPubKey, commitInput, redeemInfo, commit, localCommitIndex, spec, localPerCommitmentPoint).map { localCommit1 => log.info(s"built local commit number=$localCommitIndex toLocalMsat=${spec.toLocal.toLong} toRemoteMsat=${spec.toRemote.toLong} htlc_in={} htlc_out={} feeratePerKw=${spec.commitTxFeerate} txid=${localCommit1.commitTxAndRemoteSig.commitTx.tx.txid} fundingTxId=$fundingTxId", spec.htlcs.collect(DirectedHtlc.incoming).map(_.id).mkString(","), spec.htlcs.collect(DirectedHtlc.outgoing).map(_.id).mkString(",")) copy(localCommit = localCommit1) } @@ -679,6 +680,7 @@ object Commitment { fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitmentInput: InputInfo, + redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, localPerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTx]) = { val channelKeyPath = keyManager.keyPath(localParams, channelConfig) @@ -694,7 +696,7 @@ object Commitment { val localRevocationPubkey = Generators.revocationPubKey(remoteParams.revocationBasepoint, localPerCommitmentPoint) val localPaymentBasepoint = localParams.walletStaticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey) val outputs = makeCommitTxOutputs(localParams.paysCommitTxFees, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, remotePaymentPubkey, localHtlcPubkey, remoteHtlcPubkey, localFundingPubkey, remoteFundingPubKey, spec, channelFeatures.commitmentFormat) - val commitTx = makeCommitTx(commitmentInput, commitTxNumber, localPaymentBasepoint, remoteParams.paymentBasepoint, localParams.isChannelOpener, outputs) + val commitTx = makeCommitTx(commitmentInput, redeemInfo, commitTxNumber, localPaymentBasepoint, remoteParams.paymentBasepoint, localParams.isChannelOpener, outputs) val htlcTxs = makeHtlcTxs(commitTx.tx, localParams.dustLimit, localRevocationPubkey, remoteParams.toSelfDelay, localDelayedPaymentPubkey, spec.htlcTxFeerate(channelFeatures.commitmentFormat), outputs, channelFeatures.commitmentFormat) (commitTx, htlcTxs) } @@ -708,6 +710,7 @@ object Commitment { fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitmentInput: InputInfo, + redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, remotePerCommitmentPoint: PublicKey, spec: CommitmentSpec): (CommitTx, Seq[HtlcTx]) = { val channelKeyPath = keyManager.keyPath(localParams, channelConfig) @@ -723,7 +726,7 @@ object Commitment { val remoteHtlcPubkey = Generators.derivePubKey(remoteParams.htlcBasepoint, remotePerCommitmentPoint) val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remotePerCommitmentPoint) val outputs = makeCommitTxOutputs(!localParams.paysCommitTxFees, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, localPaymentPubkey, remoteHtlcPubkey, localHtlcPubkey, remoteFundingPubKey, localFundingPubkey, spec, channelFeatures.commitmentFormat) - val commitTx = makeCommitTx(commitmentInput, commitTxNumber, remoteParams.paymentBasepoint, localPaymentBasepoint, !localParams.isChannelOpener, outputs) + val commitTx = makeCommitTx(commitmentInput, redeemInfo, commitTxNumber, remoteParams.paymentBasepoint, localPaymentBasepoint, !localParams.isChannelOpener, outputs) val htlcTxs = makeHtlcTxs(commitTx.tx, remoteParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, spec.htlcTxFeerate(channelFeatures.commitmentFormat), outputs, channelFeatures.commitmentFormat) (commitTx, htlcTxs) } @@ -751,6 +754,7 @@ case class FullCommitment(params: ChannelParams, changes: CommitmentChanges, val localParams = params.localParams val remoteParams = params.remoteParams val commitInput = localCommit.commitTxAndRemoteSig.commitTx.input + val redeemInfo = localCommit.commitTxAndRemoteSig.commitTx.redeemInfo val fundingTxId = commitInput.outPoint.txid val capacity = commitInput.txOut.amount val commitment = Commitment(fundingTxIndex, firstRemoteCommitIndex, remoteFundingPubKey, localFundingStatus, remoteFundingStatus, localCommit, remoteCommit, nextRemoteCommit_opt) @@ -1154,11 +1158,8 @@ case class Commitments(params: ChannelParams, 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 match { - case InputInfo.SegwitInput(_, _, redeemScript) => redeemScript == fundingScript - case _: InputInfo.TaprootInput => false - } + val fundingScript = Helpers.Funding.makeFundingPubKeyScript(localFundingKey, remoteFundingKey, params.commitmentFormat) + commitment.commitInput.txOut.publicKeyScript == 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 24a1448459..804ef6ac7d 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 @@ -275,7 +275,7 @@ object Helpers { for { script_opt <- extractShutdownScript(accept.temporaryChannelId, localFeatures, remoteFeatures, accept.upfrontShutdownScript_opt) - fundingScript = Funding.makeFundingPubKeyScript(open.fundingPubkey, accept.fundingPubkey) + fundingScript = Funding.makeFundingPubKeyScript(open.fundingPubkey, accept.fundingPubkey, channelType.commitmentFormat) liquidityPurchase_opt <- LiquidityAds.validateRemoteFunding(open.requestFunding_opt, remoteNodeId, accept.temporaryChannelId, fundingScript, accept.fundingAmount, open.fundingFeerate, isChannelCreation = true, accept.willFund_opt) } yield { val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel) @@ -373,12 +373,19 @@ object Helpers { object Funding { - def makeFundingPubKeyScript(localFundingKey: PublicKey, remoteFundingKey: PublicKey): ByteVector = write(pay2wsh(multiSig2of2(localFundingKey, remoteFundingKey))) + def makeFundingPubKeyScript(localFundingKey: PublicKey, remoteFundingKey: PublicKey, commitmentFormat: CommitmentFormat): ByteVector = { + commitmentFormat match { + case Transactions.DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => write(pay2wsh(multiSig2of2(localFundingKey, remoteFundingKey))) + } + } - def makeFundingInputInfo(fundingTxId: TxId, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey): InputInfo.SegwitInput = { - val fundingScript = multiSig2of2(fundingPubkey1, fundingPubkey2) - val fundingTxOut = TxOut(fundingSatoshis, pay2wsh(fundingScript)) - InputInfo.SegwitInput(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, write(fundingScript)) + def makeFundingInputInfo(fundingTxId: TxId, fundingTxOutputIndex: Int, fundingAmount: Satoshi, localFundingKey: PublicKey, remoteFundingKey: PublicKey, commitmentFormat: CommitmentFormat): (InputInfo, RedeemInfo.TaprootKeyPathOrSegwitV0) = { + val (redeemInfo, pubkeyScript) = commitmentFormat match { + case Transactions.DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + val fundingScript = multiSig2of2(localFundingKey, remoteFundingKey) + (RedeemInfo.SegwitV0(fundingScript), pay2wsh(fundingScript)) + } + (InputInfo(OutPoint(fundingTxId, fundingTxOutputIndex), TxOut(fundingAmount, pubkeyScript)), redeemInfo) } /** @@ -441,10 +448,10 @@ object Helpers { val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath, fundingTxIndex) val channelKeyPath = keyManager.keyPath(localParams, channelConfig) - val commitmentInput = makeFundingInputInfo(fundingTxId, fundingTxOutputIndex, fundingAmount, fundingPubKey.publicKey, remoteFundingPubKey) + val (commitmentInput, redeemInfo) = makeFundingInputInfo(fundingTxId, fundingTxOutputIndex, fundingAmount, fundingPubKey.publicKey, remoteFundingPubKey, commitmentFormat) val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, localCommitmentIndex) - val (localCommitTx, _) = Commitment.makeLocalTxs(keyManager, channelConfig, channelFeatures, localCommitmentIndex, localParams, remoteParams, fundingTxIndex, remoteFundingPubKey, commitmentInput, localPerCommitmentPoint, localSpec) - val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(keyManager, channelConfig, channelFeatures, remoteCommitmentIndex, localParams, remoteParams, fundingTxIndex, remoteFundingPubKey, commitmentInput, remotePerCommitmentPoint, remoteSpec) + val (localCommitTx, _) = Commitment.makeLocalTxs(keyManager, channelConfig, channelFeatures, localCommitmentIndex, localParams, remoteParams, fundingTxIndex, remoteFundingPubKey, commitmentInput, redeemInfo, localPerCommitmentPoint, localSpec) + val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(keyManager, channelConfig, channelFeatures, remoteCommitmentIndex, localParams, remoteParams, fundingTxIndex, remoteFundingPubKey, commitmentInput, redeemInfo, remotePerCommitmentPoint, remoteSpec) val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index) Right(localSpec, localCommitTx, remoteSpec, remoteCommitTx, sortedHtlcTxs) } @@ -667,7 +674,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.paysClosingFees, Satoshi(0), Satoshi(0), commitment.localCommit.spec) + val dummyClosingTx = Transactions.makeClosingTx(commitment.commitInput, commitment.redeemInfo, localScriptPubkey, remoteScriptPubkey, commitment.localParams.paysClosingFees, Satoshi(0), Satoshi(0), commitment.localCommit.spec) val closingWeight = Transaction.weight(Transactions.addSigs(dummyClosingTx, Transactions.PlaceHolderPubKey, commitment.remoteFundingPubKey, Transactions.PlaceHolderSig, Transactions.PlaceHolderSig).tx) log.info(s"using feerates=$feerates for initial closing tx") feerates.computeFees(closingWeight) @@ -700,7 +707,7 @@ object Helpers { def makeClosingTx(keyManager: ChannelKeyManager, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, closingFees: ClosingFees)(implicit log: LoggingAdapter): (ClosingTx, ClosingSigned) = { 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.paysClosingFees, dustLimit, closingFees.preferred, commitment.localCommit.spec) + val closingTx = Transactions.makeClosingTx(commitment.commitInput, commitment.redeemInfo, localScriptPubkey, remoteScriptPubkey, commitment.localParams.paysClosingFees, dustLimit, closingFees.preferred, commitment.localCommit.spec) val localClosingSig = keyManager.sign(closingTx, keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex), TxOwner.Local, commitment.params.commitmentFormat, Map.empty) 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}") @@ -725,7 +732,7 @@ object Helpers { def makeSimpleClosingTx(currentBlockHeight: BlockHeight, keyManager: ChannelKeyManager, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, feerate: FeeratePerKw): Either[ChannelException, (ClosingTxs, ClosingComplete)] = { // We must convert the feerate to a fee: we must build dummy transactions to compute their weight. val closingFee = { - val dummyClosingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput, commitment.localCommit.spec, SimpleClosingTxFee.PaidByUs(0 sat), currentBlockHeight.toLong, localScriptPubkey, remoteScriptPubkey) + val dummyClosingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput, commitment.redeemInfo, commitment.localCommit.spec, SimpleClosingTxFee.PaidByUs(0 sat), currentBlockHeight.toLong, localScriptPubkey, remoteScriptPubkey) dummyClosingTxs.preferred_opt match { case Some(dummyTx) => val dummySignedTx = Transactions.addSigs(dummyTx, Transactions.PlaceHolderPubKey, Transactions.PlaceHolderPubKey, Transactions.PlaceHolderSig, Transactions.PlaceHolderSig) @@ -734,7 +741,7 @@ object Helpers { } } // Now that we know the fee we're ready to pay, we can create our closing transactions. - val closingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput, commitment.localCommit.spec, closingFee, currentBlockHeight.toLong, localScriptPubkey, remoteScriptPubkey) + val closingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput, commitment.redeemInfo, commitment.localCommit.spec, closingFee, currentBlockHeight.toLong, localScriptPubkey, remoteScriptPubkey) closingTxs.preferred_opt match { case Some(closingTx) if closingTx.fee > 0.sat => () case _ => return Left(CannotGenerateClosingTx(commitment.channelId)) @@ -756,7 +763,7 @@ object Helpers { */ def signSimpleClosingTx(keyManager: ChannelKeyManager, commitment: FullCommitment, localScriptPubkey: ByteVector, remoteScriptPubkey: ByteVector, closingComplete: ClosingComplete): Either[ChannelException, (ClosingTx, ClosingSig)] = { val closingFee = SimpleClosingTxFee.PaidByThem(closingComplete.fees) - val closingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput, commitment.localCommit.spec, closingFee, closingComplete.lockTime, localScriptPubkey, remoteScriptPubkey) + val closingTxs = Transactions.makeSimpleClosingTxs(commitment.commitInput, commitment.redeemInfo, commitment.localCommit.spec, closingFee, closingComplete.lockTime, localScriptPubkey, remoteScriptPubkey) // If our output isn't dust, they must provide a signature for a transaction that includes it. // Note that we're the closee, so we look for signatures including the closee output. (closingTxs.localAndRemote_opt, closingTxs.localOnly_opt) match { @@ -881,7 +888,7 @@ object Helpers { // first we will claim our main output as soon as the delay is over val mainDelayedTx = withTxGenerationLog("local-main-delayed") { - Transactions.makeClaimLocalDelayedOutputTx(tx, commitment.localParams.dustLimit, localRevocationPubkey, commitment.remoteParams.toSelfDelay, localDelayedPubkey, finalScriptPubKey, feeratePerKwDelayed).map(claimDelayed => { + Transactions.makeClaimLocalDelayedOutputTx(tx, commitment.localParams.dustLimit, localRevocationPubkey, commitment.remoteParams.toSelfDelay, localDelayedPubkey, finalScriptPubKey, feeratePerKwDelayed, commitment.params.commitmentFormat).map(claimDelayed => { val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(channelKeyPath), localPerCommitmentPoint, TxOwner.Local, commitment.params.commitmentFormat, Map.empty) Transactions.addSigs(claimDelayed, sig) }) @@ -912,10 +919,10 @@ object Helpers { val localFundingPubKey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey val claimAnchorTxs = List( withTxGenerationLog("local-anchor") { - Transactions.makeClaimLocalAnchorOutputTx(lcp.commitTx, localFundingPubKey, confirmationTarget) + Transactions.makeClaimLocalAnchorOutputTx(lcp.commitTx, localFundingPubKey, confirmationTarget, commitment.params.commitmentFormat) }, withTxGenerationLog("remote-anchor") { - Transactions.makeClaimRemoteAnchorOutputTx(lcp.commitTx, commitment.remoteFundingPubKey) + Transactions.makeClaimRemoteAnchorOutputTx(lcp.commitTx, commitment.remoteFundingPubKey, commitment.params.commitmentFormat) } ).flatten lcp.copy(claimAnchorTxs = claimAnchorTxs) @@ -943,12 +950,12 @@ object Helpers { val nonRelayedIncomingHtlcs: Set[Long] = commitment.changes.remoteChanges.all.collect { case add: UpdateAddHtlc => add.id }.toSet commitment.localCommit.htlcTxsAndRemoteSigs.collect { - case HtlcTxAndRemoteSig(txInfo@HtlcSuccessTx(_, _, paymentHash, _, _), remoteSig) => - if (hash2Preimage.contains(paymentHash)) { + case HtlcTxAndRemoteSig(txInfo: HtlcSuccessTx, remoteSig) => + if (hash2Preimage.contains(txInfo.paymentHash)) { // We immediately spend incoming htlcs for which we have the preimage. Some(txInfo.input.outPoint -> withTxGenerationLog("htlc-success") { val localSig = keyManager.sign(txInfo, keyManager.htlcPoint(channelKeyPath), localPerCommitmentPoint, TxOwner.Local, commitment.params.commitmentFormat, Map.empty) - Right(Transactions.addSigs(txInfo, localSig, remoteSig, hash2Preimage(paymentHash), commitment.params.commitmentFormat)) + Right(Transactions.addSigs(txInfo, localSig, remoteSig, hash2Preimage(txInfo.paymentHash), commitment.params.commitmentFormat)) }) } else if (failedIncomingHtlcs.contains(txInfo.htlcId)) { // We can ignore incoming htlcs that we started failing: our peer will claim them after the timeout. @@ -990,7 +997,7 @@ object Helpers { val htlcDelayedTx_opt = withTxGenerationLog("htlc-delayed") { // Note that this will return None if the transaction wasn't one of our HTLC transactions, which may happen // if our peer was able to claim the HTLC output before us (race condition between success and timeout). - Transactions.makeHtlcDelayedTx(tx, commitment.localParams.dustLimit, localRevocationPubkey, commitment.remoteParams.toSelfDelay, localDelayedPubkey, finalScriptPubKey, feeratePerKwDelayed).map(claimDelayed => { + Transactions.makeHtlcDelayedTx(tx, commitment.localParams.dustLimit, localRevocationPubkey, commitment.remoteParams.toSelfDelay, localDelayedPubkey, finalScriptPubKey, feeratePerKwDelayed, commitment.params.commitmentFormat).map(claimDelayed => { val sig = keyManager.sign(claimDelayed, keyManager.delayedPaymentPoint(channelKeyPath), localPerCommitmentPoint, TxOwner.Local, commitment.params.commitmentFormat, Map.empty) Transactions.addSigs(claimDelayed, sig) }) @@ -1041,10 +1048,10 @@ object Helpers { val localFundingPubkey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey val claimAnchorTxs = List( withTxGenerationLog("local-anchor") { - Transactions.makeClaimLocalAnchorOutputTx(rcp.commitTx, localFundingPubkey, confirmationTarget) + Transactions.makeClaimLocalAnchorOutputTx(rcp.commitTx, localFundingPubkey, confirmationTarget, commitment.params.commitmentFormat) }, withTxGenerationLog("remote-anchor") { - Transactions.makeClaimRemoteAnchorOutputTx(rcp.commitTx, commitment.remoteFundingPubKey) + Transactions.makeClaimRemoteAnchorOutputTx(rcp.commitTx, commitment.remoteFundingPubKey, commitment.params.commitmentFormat) } ).flatten rcp.copy(claimAnchorTxs = claimAnchorTxs) @@ -1078,7 +1085,7 @@ object Helpers { }) } case _: AnchorOutputsCommitmentFormat => withTxGenerationLog("remote-main-delayed") { - Transactions.makeClaimRemoteDelayedOutputTx(tx, params.localParams.dustLimit, localPaymentPoint, finalScriptPubKey, feeratePerKwMain).map(claimMain => { + Transactions.makeClaimRemoteDelayedOutputTx(tx, params.localParams.dustLimit, localPaymentPoint, finalScriptPubKey, feeratePerKwMain, params.commitmentFormat).map(claimMain => { val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), TxOwner.Local, params.commitmentFormat, Map.empty) Transactions.addSigs(claimMain, sig) }) @@ -1091,7 +1098,7 @@ object Helpers { * Claim our htlc outputs only from the remote commitment. */ def claimHtlcOutputs(keyManager: ChannelKeyManager, commitment: FullCommitment, remoteCommit: RemoteCommit, feerates: FeeratesPerKw, 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.fundingTxIndex, commitment.remoteFundingPubKey, 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, commitment.redeemInfo, 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, commitment.fundingTxIndex).publicKey @@ -1121,7 +1128,7 @@ object Helpers { case OutgoingHtlc(add: UpdateAddHtlc) => // NB: we first generate the tx skeleton and finalize it below if we have the preimage, so we set logSuccess to false to avoid logging twice. withTxGenerationLog("claim-htlc-success", logSuccess = false) { - Transactions.makeClaimHtlcSuccessTx(remoteCommitTx.tx, outputs, commitment.localParams.dustLimit, localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, finalScriptPubKey, add, feeratePerKwHtlc, commitment.params.commitmentFormat) + Transactions.makeClaimHtlcSuccessTx(remoteCommitTx.tx, outputs, commitment.localParams.dustLimit, finalScriptPubKey, add, feeratePerKwHtlc, commitment.params.commitmentFormat) }.map(claimHtlcTx => { if (hash2Preimage.contains(add.paymentHash)) { // We immediately spend incoming htlcs for which we have the preimage. @@ -1149,7 +1156,7 @@ object Helpers { // back after the timeout. // NB: we first generate the tx skeleton and finalize it below, so we set logSuccess to false to avoid logging twice. withTxGenerationLog("claim-htlc-timeout", logSuccess = false) { - Transactions.makeClaimHtlcTimeoutTx(remoteCommitTx.tx, outputs, commitment.localParams.dustLimit, localHtlcPubkey, remoteHtlcPubkey, remoteRevocationPubkey, finalScriptPubKey, add, feeratePerKwHtlc, commitment.params.commitmentFormat) + Transactions.makeClaimHtlcTimeoutTx(remoteCommitTx.tx, outputs, commitment.localParams.dustLimit, finalScriptPubKey, add, feeratePerKwHtlc, commitment.params.commitmentFormat) }.map(claimHtlcTx => { Some(claimHtlcTx.input.outPoint -> withTxGenerationLog("claim-htlc-timeout") { val sig = keyManager.sign(claimHtlcTx, keyManager.htlcPoint(channelKeyPath), remoteCommit.remotePerCommitmentPoint, TxOwner.Local, commitment.params.commitmentFormat, Map.empty) @@ -1225,7 +1232,7 @@ object Helpers { }) } case _: AnchorOutputsCommitmentFormat => withTxGenerationLog("remote-main-delayed") { - Transactions.makeClaimRemoteDelayedOutputTx(commitTx, localParams.dustLimit, localPaymentPoint, finalScriptPubKey, feerateMain).map(claimMain => { + Transactions.makeClaimRemoteDelayedOutputTx(commitTx, localParams.dustLimit, localPaymentPoint, finalScriptPubKey, feerateMain, commitmentFormat).map(claimMain => { val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), TxOwner.Local, commitmentFormat, Map.empty) Transactions.addSigs(claimMain, sig) }) @@ -1235,7 +1242,7 @@ object Helpers { // then we punish them by stealing their main output val mainPenaltyTx = withTxGenerationLog("main-penalty") { - Transactions.makeMainPenaltyTx(commitTx, localParams.dustLimit, remoteRevocationPubkey, finalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePenalty).map(txinfo => { + Transactions.makeMainPenaltyTx(commitTx, localParams.dustLimit, remoteRevocationPubkey, finalScriptPubKey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, feeratePenalty, commitmentFormat).map(txinfo => { val sig = keyManager.sign(txinfo, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret, TxOwner.Local, commitmentFormat, Map.empty) Transactions.addSigs(txinfo, sig) }) @@ -1244,18 +1251,25 @@ object Helpers { // we retrieve the information needed to rebuild htlc scripts val htlcInfos = db.listHtlcInfos(channelId, commitmentNumber) log.info("got {} htlcs for commitmentNumber={}", htlcInfos.size, commitmentNumber) - val htlcsRedeemScripts = ( - htlcInfos.map { case (paymentHash, cltvExpiry) => Scripts.htlcReceived(remoteHtlcPubkey, localHtlcPubkey, remoteRevocationPubkey, Crypto.ripemd160(paymentHash), cltvExpiry, commitmentFormat) } ++ - htlcInfos.map { case (paymentHash, _) => Scripts.htlcOffered(remoteHtlcPubkey, localHtlcPubkey, remoteRevocationPubkey, Crypto.ripemd160(paymentHash), commitmentFormat) } - ) - .map(redeemScript => Script.write(pay2wsh(redeemScript)) -> Script.write(redeemScript)) - .toMap + val htlcScriptToRedeemInfo: Map[ByteVector, RedeemInfo.TaprootKeyPathOrSegwitV0] = htlcInfos.flatMap { + case (paymentHash, cltvExpiry) => + // We don't know if this is an incoming or outgoing HTLC, so we try both options. + commitmentFormat match { + case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + val offered = Scripts.htlcOffered(remoteHtlcPubkey, localHtlcPubkey, remoteRevocationPubkey, Crypto.ripemd160(paymentHash), commitmentFormat) + val received = Scripts.htlcReceived(remoteHtlcPubkey, localHtlcPubkey, remoteRevocationPubkey, Crypto.ripemd160(paymentHash), cltvExpiry, commitmentFormat) + Map( + Script.write(pay2wsh(offered)) -> RedeemInfo.SegwitV0(offered), + Script.write(pay2wsh(received)) -> RedeemInfo.SegwitV0(received) + ) + } + }.toMap // and finally we steal the htlc outputs - val htlcPenaltyTxs = commitTx.txOut.zipWithIndex.collect { case (txOut, outputIndex) if htlcsRedeemScripts.contains(txOut.publicKeyScript) => - val htlcRedeemScript = htlcsRedeemScripts(txOut.publicKeyScript) + val htlcPenaltyTxs = commitTx.txOut.zipWithIndex.collect { case (txOut, outputIndex) if htlcScriptToRedeemInfo.contains(txOut.publicKeyScript) => + val htlcRedeemInfo = htlcScriptToRedeemInfo(txOut.publicKeyScript) withTxGenerationLog("htlc-penalty") { - Transactions.makeHtlcPenaltyTx(commitTx, outputIndex, htlcRedeemScript, localParams.dustLimit, finalScriptPubKey, feeratePenalty).map(htlcPenalty => { + Transactions.makeHtlcPenaltyTx(commitTx, outputIndex, htlcRedeemInfo, localParams.dustLimit, finalScriptPubKey, feeratePenalty).map(htlcPenalty => { val sig = keyManager.sign(htlcPenalty, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret, TxOwner.Local, commitmentFormat, Map.empty) Transactions.addSigs(htlcPenalty, sig, remoteRevocationPubkey) }) @@ -1309,7 +1323,7 @@ object Helpers { val remoteRevocationPubkey = Generators.revocationPubKey(keyManager.revocationPoint(channelKeyPath).publicKey, remotePerCommitmentPoint) // We need to use a high fee when spending HTLC txs because after a delay they can also be spent by the counterparty. val feeratePerKwPenalty = feerates.fastest - val penaltyTxs = Transactions.makeClaimHtlcDelayedOutputPenaltyTxs(htlcTx, localParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, finalScriptPubKey, feeratePerKwPenalty).flatMap(claimHtlcDelayedOutputPenaltyTx => { + val penaltyTxs = Transactions.makeClaimHtlcDelayedOutputPenaltyTxs(htlcTx, localParams.dustLimit, remoteRevocationPubkey, localParams.toSelfDelay, remoteDelayedPaymentPubkey, finalScriptPubKey, feeratePerKwPenalty, params.commitmentFormat).flatMap(claimHtlcDelayedOutputPenaltyTx => { withTxGenerationLog("htlc-delayed-penalty") { claimHtlcDelayedOutputPenaltyTx.map(htlcDelayedPenalty => { val sig = keyManager.sign(htlcDelayedPenalty, keyManager.revocationPoint(channelKeyPath), remotePerCommitmentSecret, TxOwner.Local, commitmentFormat, Map.empty) @@ -1383,13 +1397,13 @@ object Helpers { tx.txIn.flatMap(txIn => localCommitPublished.htlcTxs.get(txIn.outPoint) match { // This may also be our peer claiming the HTLC by revealing the preimage: in that case we have already // extracted the preimage with [[extractPreimages]] and relayed it upstream. - case Some(Some(HtlcTimeoutTx(_, _, htlcId, _))) if Scripts.extractPreimagesFromClaimHtlcSuccess(tx).isEmpty => - untrimmedHtlcs.find(_.id == htlcId) match { + case Some(Some(htlcTx: HtlcTimeoutTx)) if Scripts.extractPreimagesFromClaimHtlcSuccess(tx).isEmpty => + untrimmedHtlcs.find(_.id == htlcTx.htlcId) match { case Some(htlc) => - log.info("htlc-timeout tx for htlc #{} paymentHash={} expiry={} has been confirmed (tx={})", htlcId, htlc.paymentHash, tx.lockTime, tx) + log.info("htlc-timeout tx for htlc #{} paymentHash={} expiry={} has been confirmed (tx={})", htlcTx.htlcId, htlc.paymentHash, tx.lockTime, tx) Some(htlc) case None => - log.error("could not find htlc #{} for htlc-timeout tx={}", htlcId, tx) + log.error("could not find htlc #{} for htlc-timeout tx={}", htlcTx.htlcId, tx) None } case _ => None @@ -1415,13 +1429,13 @@ object Helpers { tx.txIn.flatMap(txIn => remoteCommitPublished.claimHtlcTxs.get(txIn.outPoint) match { // This may also be our peer claiming the HTLC by revealing the preimage: in that case we have already // extracted the preimage with [[extractPreimages]] and relayed it upstream. - case Some(Some(ClaimHtlcTimeoutTx(_, _, htlcId, _))) if Scripts.extractPreimagesFromHtlcSuccess(tx).isEmpty => - untrimmedHtlcs.find(_.id == htlcId) match { + case Some(Some(claimHtlcTx: ClaimHtlcTimeoutTx)) if Scripts.extractPreimagesFromHtlcSuccess(tx).isEmpty => + untrimmedHtlcs.find(_.id == claimHtlcTx.htlcId) match { case Some(htlc) => - log.info("claim-htlc-timeout tx for htlc #{} paymentHash={} expiry={} has been confirmed (tx={})", htlcId, htlc.paymentHash, tx.lockTime, tx) + log.info("claim-htlc-timeout tx for htlc #{} paymentHash={} expiry={} has been confirmed (tx={})", claimHtlcTx.htlcId, htlc.paymentHash, tx.lockTime, tx) Some(htlc) case None => - log.error("could not find htlc #{} for claim-htlc-timeout tx={}", htlcId, tx) + log.error("could not find htlc #{} for claim-htlc-timeout tx={}", claimHtlcTx.htlcId, tx) None } case _ => None 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 3e2e1bd78e..366669edae 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 @@ -1027,7 +1027,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with } else { val parentCommitment = d.commitments.latest.commitment val localFundingPubKey = nodeParams.channelKeyManager.fundingPublicKey(d.commitments.params.localParams.fundingKeyPath, parentCommitment.fundingTxIndex + 1).publicKey - val fundingScript = Funding.makeFundingPubKeyScript(localFundingPubKey, msg.fundingPubKey) + val fundingScript = Funding.makeFundingPubKeyScript(localFundingPubKey, msg.fundingPubKey, d.commitments.params.commitmentFormat) LiquidityAds.validateRequest(nodeParams.privateKey, d.channelId, fundingScript, msg.feerate, isChannelCreation = false, msg.requestFunding_opt, nodeParams.liquidityAdsConfig.rates_opt, msg.useFeeCredit_opt) match { case Left(t) => log.warning("rejecting splice request with invalid liquidity ads: {}", t.getMessage) @@ -1098,7 +1098,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with targetFeerate = spliceInit.feerate, requireConfirmedInputs = RequireConfirmedInputs(forLocal = msg.requireConfirmedInputs, forRemote = spliceInit.requireConfirmedInputs) ) - val fundingScript = Funding.makeFundingPubKeyScript(spliceInit.fundingPubKey, msg.fundingPubKey) + val fundingScript = Funding.makeFundingPubKeyScript(spliceInit.fundingPubKey, msg.fundingPubKey, d.commitments.params.commitmentFormat) LiquidityAds.validateRemoteFunding(spliceInit.requestFunding_opt, remoteNodeId, d.channelId, fundingScript, msg.fundingContribution, spliceInit.feerate, isChannelCreation = false, msg.willFund_opt) match { case Left(t) => log.info("rejecting splice attempt: invalid liquidity ads response ({})", t.getMessage) @@ -2357,7 +2357,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with case Some(fundingTxId) if fundingTxId == d.signingSession.fundingTx.txId && channelReestablish.nextLocalCommitmentNumber == 0 => // They haven't received our commit_sig: we retransmit it, and will send our tx_signatures once we've received // their commit_sig or their tx_signatures (depending on who must send tx_signatures first). - val commitSig = d.signingSession.remoteCommit.sign(keyManager, d.channelParams, d.signingSession.fundingTxIndex, d.signingSession.fundingParams.remoteFundingPubKey, d.signingSession.commitInput) + val commitSig = d.signingSession.remoteCommit.sign(keyManager, d.channelParams, d.signingSession.fundingTxIndex, d.signingSession.fundingParams.remoteFundingPubKey, d.signingSession.commitInput, d.signingSession.redeemInfo) goto(WAIT_FOR_DUAL_FUNDING_SIGNED) sending commitSig case _ => goto(WAIT_FOR_DUAL_FUNDING_SIGNED) } @@ -2370,7 +2370,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with if (channelReestablish.nextLocalCommitmentNumber == 0) { // They haven't received our commit_sig: we retransmit it. // We're also waiting for signatures from them, and will send our tx_signatures once we receive them. - val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput) + val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput, signingSession.redeemInfo) goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending commitSig } else { // They have already received our commit_sig, but we were waiting for them to send either commit_sig or @@ -2381,7 +2381,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with // We've already received their commit_sig and sent our tx_signatures. We retransmit our tx_signatures // and our commit_sig if they haven't received it already. if (channelReestablish.nextLocalCommitmentNumber == 0) { - val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, 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, d.commitments.latest.redeemInfo) goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending Seq(commitSig, d.latestFundingTx.sharedTx.localSigs) } else { goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) sending d.latestFundingTx.sharedTx.localSigs @@ -2409,7 +2409,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with d.commitments.latest.localFundingStatus.localSigs_opt match { case Some(txSigs) if channelReestablish.nextLocalCommitmentNumber == 0 => log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId) - val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, 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, d.commitments.latest.redeemInfo) goto(WAIT_FOR_DUAL_FUNDING_READY) sending Seq(commitSig, txSigs, channelReady) case Some(txSigs) => log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId) @@ -2471,7 +2471,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with // They haven't received our commit_sig: we retransmit it. // We're also waiting for signatures from them, and will send our tx_signatures once we receive them. log.info("re-sending commit_sig for splice attempt with fundingTxIndex={} fundingTxId={}", signingSession.fundingTxIndex, signingSession.fundingTx.txId) - val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput) + val commitSig = signingSession.remoteCommit.sign(keyManager, d.commitments.params, signingSession.fundingTxIndex, signingSession.fundingParams.remoteFundingPubKey, signingSession.commitInput, signingSession.redeemInfo) sendQueue = sendQueue :+ commitSig } d.spliceStatus @@ -2482,7 +2482,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder with // tx_signatures and our commit_sig if they haven't received it already. if (channelReestablish.nextLocalCommitmentNumber == d.commitments.remoteCommitIndex) { log.info("re-sending commit_sig and tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId) - val commitSig = d.commitments.latest.remoteCommit.sign(keyManager, d.commitments.params, d.commitments.latest.fundingTxIndex, d.commitments.latest.remoteFundingPubKey, 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, d.commitments.latest.redeemInfo) sendQueue = sendQueue :+ commitSig :+ dfu.sharedTx.localSigs } else { log.info("re-sending tx_signatures for fundingTxIndex={} fundingTxId={}", d.commitments.latest.fundingTxIndex, d.commitments.latest.fundingTxId) 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 6eb40193eb..9b202e40fa 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 @@ -142,7 +142,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers { case Event(open: OpenDualFundedChannel, d: DATA_WAIT_FOR_OPEN_DUAL_FUNDED_CHANNEL) => import d.init.{localParams, remoteInit} val localFundingPubkey = keyManager.fundingPublicKey(localParams.fundingKeyPath, fundingTxIndex = 0).publicKey - val fundingScript = Funding.makeFundingPubKeyScript(localFundingPubkey, open.fundingPubkey) + val fundingScript = Funding.makeFundingPubKeyScript(localFundingPubkey, open.fundingPubkey, d.init.channelType.commitmentFormat) Helpers.validateParamsDualFundedNonInitiator(nodeParams, d.init.channelType, open, fundingScript, remoteNodeId, localParams.initFeatures, remoteInit.features, d.init.fundingContribution_opt) match { case Left(t) => handleLocalError(t, d, Some(open)) case Right((channelFeatures, remoteShutdownScript, willFund_opt)) => 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 94dec79916..704f9a76d0 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 @@ -34,7 +34,7 @@ import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.Output.Local import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.Purpose import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession.UnsignedLocalCommit import fr.acinq.eclair.crypto.keymanager.ChannelKeyManager -import fr.acinq.eclair.transactions.Transactions.{CommitTx, HtlcTx, InputInfo, TxOwner} +import fr.acinq.eclair.transactions.Transactions.{CommitTx, HtlcTx, InputInfo, RedeemInfo, TxOwner} import fr.acinq.eclair.transactions.{CommitmentSpec, DirectedHtlc, Scripts, Transactions} import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{Logs, MilliSatoshi, MilliSatoshiLong, NodeParams, ToMilliSatoshiConversion, UInt64} @@ -104,22 +104,24 @@ object InteractiveTxBuilder { sealed trait SharedFundingInput { // @formatter:off def info: InputInfo + def redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0 def weight: Int // @formatter:on } - case class Multisig2of2Input(info: InputInfo, fundingTxIndex: Long, remoteFundingPubkey: PublicKey) extends SharedFundingInput { + case class Multisig2of2Input(info: InputInfo, redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, fundingTxIndex: Long, remoteFundingPubkey: PublicKey) extends SharedFundingInput { override val weight: Int = 388 def sign(keyManager: ChannelKeyManager, params: ChannelParams, tx: Transaction, spentUtxos: Map[OutPoint, TxOut]): ByteVector64 = { val localFundingPubkey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex) - keyManager.sign(Transactions.SpliceTx(info, tx), localFundingPubkey, TxOwner.Local, params.commitmentFormat, spentUtxos) + keyManager.sign(Transactions.SpliceTx(info, redeemInfo, tx), localFundingPubkey, TxOwner.Local, params.commitmentFormat, spentUtxos) } } object Multisig2of2Input { def apply(commitment: Commitment): Multisig2of2Input = Multisig2of2Input( info = commitment.commitInput, + redeemInfo = commitment.redeemInfo, fundingTxIndex = commitment.fundingTxIndex, remoteFundingPubkey = commitment.remoteFundingPubKey ) @@ -1089,6 +1091,7 @@ object InteractiveTxSigningSession { remoteCommit: RemoteCommit, liquidityPurchase_opt: Option[LiquidityAds.PurchaseBasicInfo]) extends InteractiveTxSigningSession { val commitInput: InputInfo = localCommit.fold(_.commitTx.input, _.commitTxAndRemoteSig.commitTx.input) + val redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0 = localCommit.fold(_.commitTx.redeemInfo, _.commitTxAndRemoteSig.commitTx.redeemInfo) val localCommitIndex: Long = localCommit.fold(_.index, _.index) // This value tells our peer whether we need them to retransmit their commit_sig on reconnection or not. val nextLocalCommitmentNumber: Long = localCommit match { @@ -1101,7 +1104,7 @@ object InteractiveTxSigningSession { case Left(unsignedLocalCommit) => val channelKeyPath = nodeParams.channelKeyManager.keyPath(channelParams.localParams, channelParams.channelConfig) val localPerCommitmentPoint = nodeParams.channelKeyManager.commitmentPoint(channelKeyPath, localCommitIndex) - LocalCommit.fromCommitSig(nodeParams.channelKeyManager, channelParams, fundingTx.txId, fundingTxIndex, fundingParams.remoteFundingPubKey, commitInput, remoteCommitSig, localCommitIndex, unsignedLocalCommit.spec, localPerCommitmentPoint).map { signedLocalCommit => + LocalCommit.fromCommitSig(nodeParams.channelKeyManager, channelParams, fundingTx.txId, fundingTxIndex, fundingParams.remoteFundingPubKey, commitInput, redeemInfo, remoteCommitSig, localCommitIndex, unsignedLocalCommit.spec, localPerCommitmentPoint).map { signedLocalCommit => if (shouldSignFirst(fundingParams.isInitiator, channelParams, fundingTx.tx)) { val fundingStatus = LocalFundingStatus.DualFundedUnconfirmedFundingTx(fundingTx, nodeParams.currentBlockHeight, fundingParams, liquidityPurchase_opt) val commitment = Commitment(fundingTxIndex, remoteCommit.index, fundingParams.remoteFundingPubKey, fundingStatus, RemoteFundingStatus.NotLocked, signedLocalCommit, remoteCommit, None) 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 10301aaa8a..c1caf0b206 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 @@ -351,9 +351,9 @@ private class ReplaceableTxFunder(nodeParams: NodeParams, import fr.acinq.bitcoin.scalacompat.KotlinUtils._ // We create a PSBT with the non-wallet input already signed: - val witnessScript = locallySignedTx.txInfo.input match { - case InputInfo.SegwitInput(_, _, redeemScript) => fr.acinq.bitcoin.Script.parse(redeemScript) - case _: InputInfo.TaprootInput => null + val witnessScript = locallySignedTx.txInfo.redeemInfo match { + case RedeemInfo.SegwitV0(redeemScript) => fr.acinq.bitcoin.Script.parse(redeemScript) + case _: RedeemInfo.Taproot => null // TODO: PSBT must be updated differently in that case } val sigHash = locallySignedTx.txInfo.sighash(TxOwner.Local, cmd.commitment.params.commitmentFormat) val psbt = new Psbt(locallySignedTx.txInfo.tx) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPrePublisher.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPrePublisher.scala index e15119860e..26c32ec745 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPrePublisher.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxPrePublisher.scala @@ -252,7 +252,7 @@ private class ReplaceableTxPrePublisher(nodeParams: NodeParams, htlcTx match { case tx: HtlcSuccessTx => commitment.localCommit.htlcTxsAndRemoteSigs.collectFirst { - case HtlcTxAndRemoteSig(HtlcSuccessTx(input, _, _, _, _), remoteSig) if input.outPoint == tx.input.outPoint => remoteSig + case HtlcTxAndRemoteSig(tx1: HtlcSuccessTx, remoteSig) if tx1.input.outPoint == tx.input.outPoint => remoteSig } match { case Some(remoteSig) => commitment.changes.localChanges.all.collectFirst { @@ -269,7 +269,7 @@ private class ReplaceableTxPrePublisher(nodeParams: NodeParams, } case tx: HtlcTimeoutTx => commitment.localCommit.htlcTxsAndRemoteSigs.collectFirst { - case HtlcTxAndRemoteSig(HtlcTimeoutTx(input, _, _, _), remoteSig) if input.outPoint == tx.input.outPoint => remoteSig + case HtlcTxAndRemoteSig(tx1: HtlcTimeoutTx, remoteSig) if tx1.input.outPoint == tx.input.outPoint => remoteSig } match { case Some(remoteSig) => Some(HtlcTimeoutWithWitnessData(tx, remoteSig)) case None => diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala index 88f43c6149..1f2d28d0cb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/CommitmentSpec.scala @@ -19,23 +19,25 @@ package fr.acinq.eclair.transactions import fr.acinq.bitcoin.scalacompat.SatoshiLong import fr.acinq.eclair.MilliSatoshi import fr.acinq.eclair.blockchain.fee.FeeratePerKw -import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat} +import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, RedeemInfo, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat} import fr.acinq.eclair.wire.protocol._ /** * Created by PM on 07/12/2016. */ -sealed trait CommitmentOutput +sealed trait CommitmentOutput { + def redeemInfo: RedeemInfo +} object CommitmentOutput { // @formatter:off - case object ToLocal extends CommitmentOutput - case object ToRemote extends CommitmentOutput - case object ToLocalAnchor extends CommitmentOutput - case object ToRemoteAnchor extends CommitmentOutput - case class InHtlc(incomingHtlc: IncomingHtlc) extends CommitmentOutput - case class OutHtlc(outgoingHtlc: OutgoingHtlc) extends CommitmentOutput + case class ToLocal(redeemInfo: RedeemInfo.TaprootScriptPathOrSegwitV0) extends CommitmentOutput + case class ToRemote(redeemInfo: RedeemInfo.TaprootScriptPathOrSegwitV0) extends CommitmentOutput + case class ToLocalAnchor(redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0) extends CommitmentOutput + case class ToRemoteAnchor(redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0) extends CommitmentOutput + case class InHtlc(redeemInfo: RedeemInfo.TaprootScriptPathOrSegwitV0, incomingHtlc: IncomingHtlc) extends CommitmentOutput + case class OutHtlc(redeemInfo: RedeemInfo.TaprootScriptPathOrSegwitV0, outgoingHtlc: OutgoingHtlc) extends CommitmentOutput // @formatter:on } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala index 9cb1ea9194..19e0fd7025 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala @@ -76,7 +76,7 @@ object Transactions { } object AnchorOutputsCommitmentFormat { - val anchorAmount = Satoshi(330) + val anchorAmount: Satoshi = Satoshi(330) } /** @@ -93,24 +93,57 @@ object Transactions { case object ZeroFeeHtlcTxAnchorOutputsCommitmentFormat extends AnchorOutputsCommitmentFormat // @formatter:off - case class OutputInfo(index: Long, amount: Satoshi, publicKeyScript: ByteVector) + /** + * This trait contains redeem information necessary to spend different types of segwit inputs. + * We also define subtraits to express all the combinations we support: this isn't very elegant, but what we really + * need is union types that only exist in Scala 3 (or in libraries like Cats), so we don't really have a choice if + * we want to benefit from exhaustive pattern matching compiler checks. + */ + sealed trait RedeemInfo + object RedeemInfo { + /** Inputs that use either segwit v0 or a taproot key path. */ + sealed trait TaprootKeyPathOrSegwitV0 extends RedeemInfo + /** Inputs that use either segwit v0 or a taproot script path. */ + sealed trait TaprootScriptPathOrSegwitV0 extends RedeemInfo + + /** @param redeemScript the actual script must be known to redeem segwit v0 inputs (p2wpkh or p2wsh). */ + case class SegwitV0(redeemScript: ByteVector) extends TaprootKeyPathOrSegwitV0 with TaprootScriptPathOrSegwitV0 + object SegwitV0 { + def apply(redeemScript: Seq[ScriptElt]): SegwitV0 = SegwitV0(Script.write(redeemScript)) + } - sealed trait InputInfo { - val outPoint: OutPoint - val txOut: TxOut - } + /** This trait can be used to match all taproot spending paths. */ + sealed trait Taproot extends RedeemInfo - object InputInfo { - case class SegwitInput(outPoint: OutPoint, txOut: TxOut, redeemScript: ByteVector) extends InputInfo - case class TaprootInput(outPoint: OutPoint, txOut: TxOut, internalKey: XonlyPublicKey, scriptTree_opt: Option[ScriptTree]) extends InputInfo { - val publicKeyScript: ByteVector = Script.write(Script.pay2tr(internalKey, scriptTree_opt)) - } + /** + * @param internalKey the private key associated with this public key will be used to sign. + * @param scriptTree_opt the script tree must be known if there is one, even when spending via the key path. + */ + case class TaprootKeyPath(internalKey: XonlyPublicKey, scriptTree_opt: Option[ScriptTree]) extends Taproot with TaprootKeyPathOrSegwitV0 - def apply(outPoint: OutPoint, txOut: TxOut, redeemScript: ByteVector): SegwitInput = SegwitInput(outPoint, txOut, redeemScript) - def apply(outPoint: OutPoint, txOut: TxOut, redeemScript: Seq[ScriptElt]): SegwitInput = SegwitInput(outPoint, txOut, Script.write(redeemScript)) - def apply(outPoint: OutPoint, txOut: TxOut, internalKey: XonlyPublicKey, scriptTree_opt: Option[ScriptTree]): TaprootInput = TaprootInput(outPoint, txOut, internalKey, scriptTree_opt) + /** + * @param internalKey we need the internal key, even if we don't have the private key, to spend via a script path. + * @param scriptTree we need the complete script tree to spend taproot inputs. + * @param leafHash hash of the leaf script we're spending (must belong to the tree). + */ + case class TaprootScriptPath(internalKey: XonlyPublicKey, scriptTree: ScriptTree, leafHash: ByteVector32) extends Taproot with TaprootScriptPathOrSegwitV0 { + require(findScript(scriptTree, leafHash).nonEmpty, "script tree must contain the provided leaf") + + /** + * TODO: this won't be needed once findScript is added to bitcoin-kmp, remove when updating + * + * @return the leaf that matches `leafHash` + */ + private def findScript(scriptTree: ScriptTree, leafHash: ByteVector32): Option[ScriptTree.Leaf] = scriptTree match { + case l: ScriptTree.Leaf => if (l.hash() == KotlinUtils.scala2kmp(leafHash)) Some(l) else None + case b: ScriptTree.Branch => findScript(b.getLeft, leafHash) orElse findScript(b.getRight, leafHash) + } + } } + case class InputInfo(outPoint: OutPoint, txOut: TxOut) + case class OutputInfo(index: Long, amount: Satoshi, publicKeyScript: ByteVector) + /** Owner of a given transaction (local/remote). */ sealed trait TxOwner object TxOwner { @@ -120,6 +153,8 @@ object Transactions { sealed trait TransactionWithInputInfo { def input: InputInfo + /** Redeem information for the [[input]] this transaction spends. */ + def redeemInfo: RedeemInfo def desc: String def tx: Transaction def amountIn: Satoshi = input.txOut.amount @@ -129,7 +164,10 @@ object Transactions { Satoshi(FeeratePerKw.MinimumRelayFeeRate * vsize / 1000) } /** Sighash flags to use when signing the transaction. */ - def sighash(txOwner: TxOwner, commitmentFormat: CommitmentFormat): Int = SIGHASH_ALL + def sighash(txOwner: TxOwner, commitmentFormat: CommitmentFormat): Int = redeemInfo match { + case _: RedeemInfo.SegwitV0 => SIGHASH_ALL + case _: RedeemInfo.Taproot => SIGHASH_DEFAULT + } /** * @param extraUtxos extra outputs spent by this transaction (in addition to the main [[input]]). @@ -139,36 +177,44 @@ object Transactions { } def sign(key: PrivateKey, sighashType: Int, extraUtxos: Map[OutPoint, TxOut]): ByteVector64 = { - val inputsMap = extraUtxos + (input.outPoint -> input.txOut) - tx.txIn.foreach(txIn => { - // Note that using a require here is dangerous, because callers don't except this function to throw. - // But we want to ensure that we're correctly providing input details, otherwise our signature will silently be - // invalid when using taproot. We verify this in all cases, even when using segwit v0, to ensure that we have as - // many tests as possible that exercise this codepath. - require(inputsMap.contains(txIn.outPoint), s"cannot sign $desc with txId=${tx.txid}: missing input details for ${txIn.outPoint}") - }) - input match { - case InputInfo.SegwitInput(outPoint, txOut, redeemScript) => - // NB: the tx may have multiple inputs, we will only sign the one provided in txinfo.input. Bear in mind that the - // signature will be invalidated if other inputs are added *afterwards* and sighashType was SIGHASH_ALL. - val inputIndex = tx.txIn.indexWhere(_.outPoint == outPoint) - val sigDER = Transaction.signInput(tx, inputIndex, redeemScript, sighashType, txOut.amount, SIGVERSION_WITNESS_V0, key) + verifyInputDetails(extraUtxos) + // NB: the tx may have multiple inputs, we will only sign the one matching our outPoint. Bear in mind that the + // signature will be invalidated if other inputs are added *afterwards* depending on the sighashType used. + val inputIndex = tx.txIn.indexWhere(_.outPoint == input.outPoint) + redeemInfo match { + case RedeemInfo.SegwitV0(redeemScript) => + val sigDER = Transaction.signInput(tx, inputIndex, redeemScript, sighashType, input.txOut.amount, SIGVERSION_WITNESS_V0, key) Crypto.der2compact(sigDER) - case _: InputInfo.TaprootInput => ??? + case _: RedeemInfo.Taproot => ??? } } - def checkSig(sig: ByteVector64, pubKey: PublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): Boolean = input match { - case _: InputInfo.TaprootInput => false - case InputInfo.SegwitInput(outPoint, txOut, redeemScript) => - val sighash = this.sighash(txOwner, commitmentFormat) - val inputIndex = tx.txIn.indexWhere(_.outPoint == outPoint) - if (inputIndex >= 0) { - val data = Transaction.hashForSigning(tx, inputIndex, redeemScript, sighash, txOut.amount, SIGVERSION_WITNESS_V0) - Crypto.verifySignature(data, sig, pubKey) - } else { - false + def checkSig(sig: ByteVector64, pubKey: PublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat, extraUtxos: Map[OutPoint, TxOut]): Boolean = { + verifyInputDetails(extraUtxos) + val inputIndex = tx.txIn.indexWhere(_.outPoint == input.outPoint) + if (inputIndex >= 0) { + redeemInfo match { + case RedeemInfo.SegwitV0(redeemScript) => + val data = Transaction.hashForSigning(tx, inputIndex, redeemScript, sighash(txOwner, commitmentFormat), input.txOut.amount, SIGVERSION_WITNESS_V0) + Crypto.verifySignature(data, sig, pubKey) + case _: RedeemInfo.Taproot => ??? } + } else { + false + } + } + + /** Verify that we have the [[TxOut]] for every input in the transaction, and returns them in the transaction order. */ + private def verifyInputDetails(extraUtxos: Map[OutPoint, TxOut]): Seq[TxOut] = { + val inputsMap = extraUtxos + (input.outPoint -> input.txOut) + tx.txIn.map(txIn => { + // Note that using a require here is dangerous, because callers don't expect this function to throw. + // But we want to ensure that we're correctly providing input details, otherwise our signature will silently be + // invalid when using taproot. We verify this in all cases, even when using segwit v0, to ensure that we have as + // many tests as possible that exercise this codepath. + require(inputsMap.contains(txIn.outPoint), s"cannot sign or verify $desc with txId=${tx.txid}: missing input details for ${txIn.outPoint}") + inputsMap(txIn.outPoint) + }) } } @@ -177,9 +223,7 @@ object Transactions { def confirmationTarget: ConfirmationTarget } - case class SpliceTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "splice-tx" } - - case class CommitTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "commit-tx" } + case class CommitTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "commit-tx" } /** * It's important to note that htlc transactions with the default commitment format are not actually replaceable: only * anchor outputs htlc transactions are replaceable. We should have used different types for these different kinds of @@ -203,27 +247,28 @@ object Transactions { } override def confirmationTarget: ConfirmationTarget.Absolute } - case class HtlcSuccessTx(input: InputInfo, tx: Transaction, paymentHash: ByteVector32, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends HtlcTx { override def desc: String = "htlc-success" } - case class HtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends HtlcTx { override def desc: String = "htlc-timeout" } - case class HtlcDelayedTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "htlc-delayed" } + case class HtlcSuccessTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootScriptPathOrSegwitV0, tx: Transaction, paymentHash: ByteVector32, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends HtlcTx { override def desc: String = "htlc-success" } + case class HtlcTimeoutTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootScriptPathOrSegwitV0, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends HtlcTx { override def desc: String = "htlc-timeout" } + case class HtlcDelayedTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootScriptPathOrSegwitV0, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "htlc-delayed" } sealed trait ClaimHtlcTx extends ReplaceableTransactionWithInputInfo { def htlcId: Long override def confirmationTarget: ConfirmationTarget.Absolute } - case class LegacyClaimHtlcSuccessTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" } - case class ClaimHtlcSuccessTx(input: InputInfo, tx: Transaction, paymentHash: ByteVector32, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" } - case class ClaimHtlcTimeoutTx(input: InputInfo, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends ClaimHtlcTx { override def desc: String = "claim-htlc-timeout" } + case class LegacyClaimHtlcSuccessTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootScriptPathOrSegwitV0, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" } + case class ClaimHtlcSuccessTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootScriptPathOrSegwitV0, tx: Transaction, paymentHash: ByteVector32, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends ClaimHtlcTx { override def desc: String = "claim-htlc-success" } + case class ClaimHtlcTimeoutTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootScriptPathOrSegwitV0, tx: Transaction, htlcId: Long, confirmationTarget: ConfirmationTarget.Absolute) extends ClaimHtlcTx { override def desc: String = "claim-htlc-timeout" } sealed trait ClaimAnchorOutputTx extends TransactionWithInputInfo - case class ClaimLocalAnchorOutputTx(input: InputInfo, tx: Transaction, confirmationTarget: ConfirmationTarget) extends ClaimAnchorOutputTx with ReplaceableTransactionWithInputInfo { override def desc: String = "local-anchor" } - case class ClaimRemoteAnchorOutputTx(input: InputInfo, tx: Transaction) extends ClaimAnchorOutputTx { override def desc: String = "remote-anchor" } + case class ClaimLocalAnchorOutputTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, tx: Transaction, confirmationTarget: ConfirmationTarget) extends ClaimAnchorOutputTx with ReplaceableTransactionWithInputInfo { override def desc: String = "local-anchor" } + case class ClaimRemoteAnchorOutputTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, tx: Transaction) extends ClaimAnchorOutputTx { override def desc: String = "remote-anchor" } sealed trait ClaimRemoteCommitMainOutputTx extends TransactionWithInputInfo - case class ClaimP2WPKHOutputTx(input: InputInfo, tx: Transaction) extends ClaimRemoteCommitMainOutputTx { override def desc: String = "remote-main" } - case class ClaimRemoteDelayedOutputTx(input: InputInfo, tx: Transaction) extends ClaimRemoteCommitMainOutputTx { override def desc: String = "remote-main-delayed" } - case class ClaimLocalDelayedOutputTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "local-main-delayed" } - case class MainPenaltyTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "main-penalty" } - case class HtlcPenaltyTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "htlc-penalty" } - case class ClaimHtlcDelayedOutputPenaltyTx(input: InputInfo, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "htlc-delayed-penalty" } - case class ClosingTx(input: InputInfo, tx: Transaction, toLocalOutput: Option[OutputInfo]) extends TransactionWithInputInfo { override def desc: String = "closing" } + case class ClaimP2WPKHOutputTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, tx: Transaction) extends ClaimRemoteCommitMainOutputTx { override def desc: String = "remote-main" } + case class ClaimRemoteDelayedOutputTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootScriptPathOrSegwitV0, tx: Transaction) extends ClaimRemoteCommitMainOutputTx { override def desc: String = "remote-main-delayed" } + case class ClaimLocalDelayedOutputTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootScriptPathOrSegwitV0, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "local-main-delayed" } + case class MainPenaltyTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "main-penalty" } + case class HtlcPenaltyTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "htlc-penalty" } + case class ClaimHtlcDelayedOutputPenaltyTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "htlc-delayed-penalty" } + case class ClosingTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, tx: Transaction, toLocalOutput: Option[OutputInfo]) extends TransactionWithInputInfo { override def desc: String = "closing" } + case class SpliceTx(input: InputInfo, redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, tx: Transaction) extends TransactionWithInputInfo { override def desc: String = "splice-tx" } sealed trait TxGenerationSkipped case object OutputNotFound extends TxGenerationSkipped { override def toString = "output not found (probably trimmed)" } @@ -273,7 +318,7 @@ object Transactions { val mainPenaltyWeight = 484 val htlcPenaltyWeight = 578 // based on spending an HTLC-Success output (would be 571 with HTLC-Timeout) - def weight2feeMsat(feeratePerKw: FeeratePerKw, weight: Int): MilliSatoshi = MilliSatoshi(feeratePerKw.toLong * weight) + private def weight2feeMsat(feeratePerKw: FeeratePerKw, weight: Int): MilliSatoshi = MilliSatoshi(feeratePerKw.toLong * weight) def weight2fee(feeratePerKw: FeeratePerKw, weight: Int): Satoshi = weight2feeMsat(feeratePerKw, weight).truncateToSatoshi @@ -298,7 +343,7 @@ object Transactions { } /** When an output is using OP_RETURN, we usually want to make sure its amount is 0, otherwise bitcoind won't accept it. */ - def isOpReturn(scriptPubKey: ByteVector): Boolean = { + private def isOpReturn(scriptPubKey: ByteVector): Boolean = { Try(Script.parse(scriptPubKey)) match { case Success(OP_RETURN :: _) => true case _ => false @@ -417,7 +462,7 @@ object Transactions { def decodeTxNumber(sequence: Long, locktime: Long): Long = ((sequence & 0xffffffL) << 24) + (locktime & 0xffffffL) - def getHtlcTxInputSequence(commitmentFormat: CommitmentFormat): Long = commitmentFormat match { + private def getHtlcTxInputSequence(commitmentFormat: CommitmentFormat): Long = commitmentFormat match { case DefaultCommitmentFormat => 0 // htlc txs immediately spend the commit tx case _: AnchorOutputsCommitmentFormat => 1 // htlc txs have a 1-block delay to allow CPFP carve-out on anchors } @@ -426,13 +471,12 @@ object Transactions { * Represent a link between a commitment spec item (to-local, to-remote, anchors, htlc) and the actual output in the commit tx * * @param output transaction output - * @param redeemScript redeem script that matches this output (most of them are p2wsh) * @param commitmentOutput commitment spec item this output is built from */ - case class CommitmentOutputLink[T <: CommitmentOutput](output: TxOut, redeemScript: Seq[ScriptElt], commitmentOutput: T) + case class CommitmentOutputLink[T <: CommitmentOutput](output: TxOut, commitmentOutput: T) /** Type alias for a collection of commitment output links */ - type CommitmentOutputs = Seq[CommitmentOutputLink[CommitmentOutput]] + private type CommitmentOutputs = Seq[CommitmentOutputLink[CommitmentOutput]] object CommitmentOutputLink { /** @@ -441,7 +485,7 @@ object Transactions { * See https://github.com/lightningnetwork/lightning-rfc/issues/448#issuecomment-432074187. */ def sort(a: CommitmentOutputLink[CommitmentOutput], b: CommitmentOutputLink[CommitmentOutput]): Boolean = (a.commitmentOutput, b.commitmentOutput) match { - case (OutHtlc(OutgoingHtlc(htlcA)), OutHtlc(OutgoingHtlc(htlcB))) if htlcA.paymentHash == htlcB.paymentHash && htlcA.amountMsat.truncateToSatoshi == htlcB.amountMsat.truncateToSatoshi => + case (OutHtlc(_, OutgoingHtlc(htlcA)), OutHtlc(_, OutgoingHtlc(htlcB))) if htlcA.paymentHash == htlcB.paymentHash && htlcA.amountMsat.truncateToSatoshi == htlcB.amountMsat.truncateToSatoshi => htlcA.cltvExpiry <= htlcB.cltvExpiry case _ => LexicographicalOrdering.isLessThan(a.output, b.output) } @@ -462,13 +506,21 @@ object Transactions { val outputs = collection.mutable.ArrayBuffer.empty[CommitmentOutputLink[CommitmentOutput]] trimOfferedHtlcs(localDustLimit, spec, commitmentFormat).foreach { htlc => - val redeemScript = htlcOffered(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash.bytes), commitmentFormat) - outputs.append(CommitmentOutputLink(TxOut(htlc.add.amountMsat.truncateToSatoshi, pay2wsh(redeemScript)), redeemScript, OutHtlc(htlc))) + val (publicKeyScript, redeemInfo) = commitmentFormat match { + case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + val redeemScript = htlcOffered(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash.bytes), commitmentFormat) + (pay2wsh(redeemScript), RedeemInfo.SegwitV0(redeemScript)) + } + outputs.append(CommitmentOutputLink(TxOut(htlc.add.amountMsat.truncateToSatoshi, publicKeyScript), OutHtlc(redeemInfo, htlc))) } trimReceivedHtlcs(localDustLimit, spec, commitmentFormat).foreach { htlc => - val redeemScript = htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash.bytes), htlc.add.cltvExpiry, commitmentFormat) - outputs.append(CommitmentOutputLink(TxOut(htlc.add.amountMsat.truncateToSatoshi, pay2wsh(redeemScript)), redeemScript, InHtlc(htlc))) + val (publicKeyScript, redeemInfo) = commitmentFormat match { + case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + val redeemScript = htlcReceived(localHtlcPubkey, remoteHtlcPubkey, localRevocationPubkey, ripemd160(htlc.add.paymentHash.bytes), htlc.add.cltvExpiry, commitmentFormat) + (pay2wsh(redeemScript), RedeemInfo.SegwitV0(redeemScript)) + } + outputs.append(CommitmentOutputLink(TxOut(htlc.add.amountMsat.truncateToSatoshi, publicKeyScript), InHtlc(redeemInfo, htlc))) } val hasHtlcs = outputs.nonEmpty @@ -480,40 +532,45 @@ object Transactions { } // NB: we don't care if values are < 0, they will be trimmed if they are < dust limit anyway if (toLocalAmount >= localDustLimit) { - outputs.append(CommitmentOutputLink( - TxOut(toLocalAmount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey))), - toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey), - ToLocal)) + val (publicKeyScript, redeemInfo) = commitmentFormat match { + case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) + (pay2wsh(redeemScript), RedeemInfo.SegwitV0(redeemScript)) + } + outputs.append(CommitmentOutputLink(TxOut(toLocalAmount, publicKeyScript), ToLocal(redeemInfo))) } if (toRemoteAmount >= localDustLimit) { - commitmentFormat match { - case DefaultCommitmentFormat => outputs.append(CommitmentOutputLink( - TxOut(toRemoteAmount, pay2wpkh(remotePaymentPubkey)), - pay2pkh(remotePaymentPubkey), - ToRemote)) - case _: AnchorOutputsCommitmentFormat => outputs.append(CommitmentOutputLink( - TxOut(toRemoteAmount, pay2wsh(toRemoteDelayed(remotePaymentPubkey))), - toRemoteDelayed(remotePaymentPubkey), - ToRemote)) + val (publicKeyScript, redeemInfo) = commitmentFormat match { + case DefaultCommitmentFormat => + // Note: when using p2wpkh, the redeem script is actually p2pkh. + val redeemScript = pay2pkh(remotePaymentPubkey) + (pay2wpkh(remotePaymentPubkey), RedeemInfo.SegwitV0(redeemScript)) + case _: AnchorOutputsCommitmentFormat => + val redeemScript = toRemoteDelayed(remotePaymentPubkey) + (pay2wsh(redeemScript), RedeemInfo.SegwitV0(redeemScript)) } + outputs.append(CommitmentOutputLink(TxOut(toRemoteAmount, publicKeyScript), ToRemote(redeemInfo))) } commitmentFormat match { + case DefaultCommitmentFormat => () case _: AnchorOutputsCommitmentFormat => if (toLocalAmount >= localDustLimit || hasHtlcs) { - outputs.append(CommitmentOutputLink(TxOut(AnchorOutputsCommitmentFormat.anchorAmount, pay2wsh(anchor(localFundingPubkey))), anchor(localFundingPubkey), ToLocalAnchor)) + val redeemScript = anchor(localFundingPubkey) + outputs.append(CommitmentOutputLink(TxOut(AnchorOutputsCommitmentFormat.anchorAmount, pay2wsh(redeemScript)), ToLocalAnchor(RedeemInfo.SegwitV0(redeemScript)))) } if (toRemoteAmount >= localDustLimit || hasHtlcs) { - outputs.append(CommitmentOutputLink(TxOut(AnchorOutputsCommitmentFormat.anchorAmount, pay2wsh(anchor(remoteFundingPubkey))), anchor(remoteFundingPubkey), ToRemoteAnchor)) + val redeemScript = anchor(remoteFundingPubkey) + outputs.append(CommitmentOutputLink(TxOut(AnchorOutputsCommitmentFormat.anchorAmount, pay2wsh(redeemScript)), ToRemoteAnchor(RedeemInfo.SegwitV0(redeemScript)))) } - case _ => } outputs.sortWith(CommitmentOutputLink.sort).toSeq } def makeCommitTx(commitTxInput: InputInfo, + redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, commitTxNumber: Long, localPaymentBasePoint: PublicKey, remotePaymentBasePoint: PublicKey, @@ -521,67 +578,70 @@ object Transactions { outputs: CommitmentOutputs): CommitTx = { val txNumber = obscuredCommitTxNumber(commitTxNumber, localIsChannelOpener, localPaymentBasePoint, remotePaymentBasePoint) val (sequence, lockTime) = encodeTxNumber(txNumber) - val tx = Transaction( version = 2, txIn = TxIn(commitTxInput.outPoint, ByteVector.empty, sequence = sequence) :: Nil, txOut = outputs.map(_.output), - lockTime = lockTime) - - CommitTx(commitTxInput, tx) + lockTime = lockTime + ) + CommitTx(commitTxInput, redeemInfo, tx) } - def makeHtlcTimeoutTx(commitTx: Transaction, - output: CommitmentOutputLink[OutHtlc], - outputIndex: Int, - localDustLimit: Satoshi, - localRevocationPubkey: PublicKey, - toLocalDelay: CltvExpiryDelta, - localDelayedPaymentPubkey: PublicKey, - feeratePerKw: FeeratePerKw, - commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, HtlcTimeoutTx] = { + private def makeHtlcTimeoutTx(commitTx: Transaction, + output: CommitmentOutputLink[OutHtlc], + outputIndex: Int, + localDustLimit: Satoshi, + localRevocationPubkey: PublicKey, + toLocalDelay: CltvExpiryDelta, + localDelayedPaymentPubkey: PublicKey, + feeratePerKw: FeeratePerKw, + commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, HtlcTimeoutTx] = { val fee = weight2fee(feeratePerKw, commitmentFormat.htlcTimeoutWeight) - val redeemScript = output.redeemScript val htlc = output.commitmentOutput.outgoingHtlc.add val amount = htlc.amountMsat.truncateToSatoshi - fee if (amount < localDustLimit) { Left(AmountBelowDustLimit) } else { - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) + val publicKeyScript = commitmentFormat match { + case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey)) + } val tx = Transaction( version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, getHtlcTxInputSequence(commitmentFormat)) :: Nil, - txOut = TxOut(amount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey))) :: Nil, + txOut = TxOut(amount, publicKeyScript) :: Nil, lockTime = htlc.cltvExpiry.toLong ) - Right(HtlcTimeoutTx(input, tx, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))) + Right(HtlcTimeoutTx(input, output.commitmentOutput.redeemInfo, tx, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))) } } - def makeHtlcSuccessTx(commitTx: Transaction, - output: CommitmentOutputLink[InHtlc], - outputIndex: Int, - localDustLimit: Satoshi, - localRevocationPubkey: PublicKey, - toLocalDelay: CltvExpiryDelta, - localDelayedPaymentPubkey: PublicKey, - feeratePerKw: FeeratePerKw, - commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, HtlcSuccessTx] = { + private def makeHtlcSuccessTx(commitTx: Transaction, + output: CommitmentOutputLink[InHtlc], + outputIndex: Int, + localDustLimit: Satoshi, + localRevocationPubkey: PublicKey, + toLocalDelay: CltvExpiryDelta, + localDelayedPaymentPubkey: PublicKey, + feeratePerKw: FeeratePerKw, + commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, HtlcSuccessTx] = { val fee = weight2fee(feeratePerKw, commitmentFormat.htlcSuccessWeight) - val redeemScript = output.redeemScript val htlc = output.commitmentOutput.incomingHtlc.add val amount = htlc.amountMsat.truncateToSatoshi - fee if (amount < localDustLimit) { Left(AmountBelowDustLimit) } else { - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) + val publicKeyScript = commitmentFormat match { + case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey)) + } val tx = Transaction( version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, getHtlcTxInputSequence(commitmentFormat)) :: Nil, - txOut = TxOut(amount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey))) :: Nil, + txOut = TxOut(amount, publicKeyScript) :: Nil, lockTime = 0 ) - Right(HtlcSuccessTx(input, tx, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))) + Right(HtlcSuccessTx(input, output.commitmentOutput.redeemInfo, tx, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))) } } @@ -594,13 +654,13 @@ object Transactions { outputs: CommitmentOutputs, commitmentFormat: CommitmentFormat): Seq[HtlcTx] = { val htlcTimeoutTxs = outputs.zipWithIndex.collect { - case (CommitmentOutputLink(o, s, OutHtlc(ou)), outputIndex) => - val co = CommitmentOutputLink(o, s, OutHtlc(ou)) + case (CommitmentOutputLink(txOut, OutHtlc(redeemInfo, htlcOut)), outputIndex) => + val co = CommitmentOutputLink(txOut, OutHtlc(redeemInfo, htlcOut)) makeHtlcTimeoutTx(commitTx, co, outputIndex, localDustLimit, localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey, feeratePerKw, commitmentFormat) }.collect { case Right(htlcTimeoutTx) => htlcTimeoutTx } val htlcSuccessTxs = outputs.zipWithIndex.collect { - case (CommitmentOutputLink(o, s, InHtlc(in)), outputIndex) => - val co = CommitmentOutputLink(o, s, InHtlc(in)) + case (CommitmentOutputLink(txOut, InHtlc(redeemInfo, htlcIn)), outputIndex) => + val co = CommitmentOutputLink(txOut, InHtlc(redeemInfo, htlcIn)) makeHtlcSuccessTx(commitTx, co, outputIndex, localDustLimit, localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey, feeratePerKw, commitmentFormat) }.collect { case Right(htlcSuccessTx) => htlcSuccessTx } htlcTimeoutTxs ++ htlcSuccessTxs @@ -609,33 +669,29 @@ object Transactions { def makeClaimHtlcSuccessTx(commitTx: Transaction, outputs: CommitmentOutputs, localDustLimit: Satoshi, - localHtlcPubkey: PublicKey, - remoteHtlcPubkey: PublicKey, - remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: ByteVector, htlc: UpdateAddHtlc, feeratePerKw: FeeratePerKw, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimHtlcSuccessTx] = { - val redeemScript = htlcOffered(remoteHtlcPubkey, localHtlcPubkey, remoteRevocationPubkey, ripemd160(htlc.paymentHash.bytes), commitmentFormat) outputs.zipWithIndex.collectFirst { - case (CommitmentOutputLink(_, _, OutHtlc(OutgoingHtlc(outgoingHtlc))), outIndex) if outgoingHtlc.id == htlc.id => outIndex + case (CommitmentOutputLink(_, OutHtlc(redeemInfo, OutgoingHtlc(outgoingHtlc))), outIndex) if outgoingHtlc.id == htlc.id => (redeemInfo, outIndex) } match { - case Some(outputIndex) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) - // unsigned tx - val tx = Transaction( + case Some((redeemInfo, outputIndex)) => + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) + val unsignedTx = Transaction( version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, getHtlcTxInputSequence(commitmentFormat)) :: Nil, - txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, - lockTime = 0) - val weight = addSigs(ClaimHtlcSuccessTx(input, tx, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))), PlaceHolderSig, ByteVector32.Zeroes).tx.weight() + txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil, + lockTime = 0 + ) + val weight = addSigs(ClaimHtlcSuccessTx(input, redeemInfo, unsignedTx, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))), PlaceHolderSig, ByteVector32.Zeroes).tx.weight() val fee = weight2fee(feeratePerKw, weight) val amount = input.txOut.amount - fee if (amount < localDustLimit) { Left(AmountBelowDustLimit) } else { - val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil) - Right(ClaimHtlcSuccessTx(input, tx1, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))) + val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil) + Right(ClaimHtlcSuccessTx(input, redeemInfo, tx, htlc.paymentHash, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))) } case None => Left(OutputNotFound) } @@ -644,230 +700,249 @@ object Transactions { def makeClaimHtlcTimeoutTx(commitTx: Transaction, outputs: CommitmentOutputs, localDustLimit: Satoshi, - localHtlcPubkey: PublicKey, - remoteHtlcPubkey: PublicKey, - remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: ByteVector, htlc: UpdateAddHtlc, feeratePerKw: FeeratePerKw, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimHtlcTimeoutTx] = { - val redeemScript = htlcReceived(remoteHtlcPubkey, localHtlcPubkey, remoteRevocationPubkey, ripemd160(htlc.paymentHash.bytes), htlc.cltvExpiry, commitmentFormat) outputs.zipWithIndex.collectFirst { - case (CommitmentOutputLink(_, _, InHtlc(IncomingHtlc(incomingHtlc))), outIndex) if incomingHtlc.id == htlc.id => outIndex + case (CommitmentOutputLink(_, InHtlc(redeemInfo, IncomingHtlc(incomingHtlc))), outIndex) if incomingHtlc.id == htlc.id => (redeemInfo, outIndex) } match { - case Some(outputIndex) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) - // unsigned tx - val tx = Transaction( + case Some((redeemInfo, outputIndex)) => + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) + val unsignedTx = Transaction( version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, getHtlcTxInputSequence(commitmentFormat)) :: Nil, - txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, - lockTime = htlc.cltvExpiry.toLong) - val weight = addSigs(ClaimHtlcTimeoutTx(input, tx, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))), PlaceHolderSig).tx.weight() + txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil, + lockTime = htlc.cltvExpiry.toLong + ) + val weight = addSigs(ClaimHtlcTimeoutTx(input, redeemInfo, unsignedTx, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong))), PlaceHolderSig).tx.weight() val fee = weight2fee(feeratePerKw, weight) val amount = input.txOut.amount - fee if (amount < localDustLimit) { Left(AmountBelowDustLimit) } else { - val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil) - Right(ClaimHtlcTimeoutTx(input, tx1, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))) + val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil) + Right(ClaimHtlcTimeoutTx(input, redeemInfo, tx, htlc.id, ConfirmationTarget.Absolute(BlockHeight(htlc.cltvExpiry.toLong)))) } case None => Left(OutputNotFound) } } def makeClaimP2WPKHOutputTx(commitTx: Transaction, localDustLimit: Satoshi, localPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: FeeratePerKw): Either[TxGenerationSkipped, ClaimP2WPKHOutputTx] = { - val redeemScript = Script.pay2pkh(localPaymentPubkey) + val redeemInfo = RedeemInfo.SegwitV0(Script.pay2pkh(localPaymentPubkey)) val pubkeyScript = write(pay2wpkh(localPaymentPubkey)) findPubKeyScriptIndex(commitTx, pubkeyScript) match { case Left(skip) => Left(skip) case Right(outputIndex) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) - // unsigned tx - val tx = Transaction( + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) + val unsignedTx = Transaction( version = 2, - txIn = TxIn(input.outPoint, ByteVector.empty, 0x00000000L) :: Nil, - txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, - lockTime = 0) + txIn = TxIn(input.outPoint, ByteVector.empty, 0) :: Nil, + txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil, + lockTime = 0 + ) // compute weight with a dummy 73 bytes signature (the largest you can get) and a dummy 33 bytes pubkey - val weight = addSigs(ClaimP2WPKHOutputTx(input, tx), PlaceHolderPubKey, PlaceHolderSig).tx.weight() + val weight = addSigs(ClaimP2WPKHOutputTx(input, redeemInfo, unsignedTx), PlaceHolderPubKey, PlaceHolderSig).tx.weight() val fee = weight2fee(feeratePerKw, weight) val amount = input.txOut.amount - fee if (amount < localDustLimit) { Left(AmountBelowDustLimit) } else { - val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil) - Right(ClaimP2WPKHOutputTx(input, tx1)) + val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil) + Right(ClaimP2WPKHOutputTx(input, redeemInfo, tx)) } } } - def makeClaimRemoteDelayedOutputTx(commitTx: Transaction, localDustLimit: Satoshi, localPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: FeeratePerKw): Either[TxGenerationSkipped, ClaimRemoteDelayedOutputTx] = { - val redeemScript = toRemoteDelayed(localPaymentPubkey) - val pubkeyScript = write(pay2wsh(redeemScript)) + def makeClaimRemoteDelayedOutputTx(commitTx: Transaction, localDustLimit: Satoshi, localPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: FeeratePerKw, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimRemoteDelayedOutputTx] = { + val (redeemInfo, pubkeyScript) = commitmentFormat match { + case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + val redeemScript = toRemoteDelayed(localPaymentPubkey) + (RedeemInfo.SegwitV0(redeemScript), pay2wsh(redeemScript)) + } findPubKeyScriptIndex(commitTx, pubkeyScript) match { case Left(skip) => Left(skip) case Right(outputIndex) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) - // unsigned transaction - val tx = Transaction( + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) + val unsignedTx = Transaction( version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, 1) :: Nil, - txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, - lockTime = 0) + txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil, + lockTime = 0 + ) // compute weight with a dummy 73 bytes signature (the largest you can get) - val weight = addSigs(ClaimRemoteDelayedOutputTx(input, tx), PlaceHolderSig).tx.weight() + val weight = addSigs(ClaimRemoteDelayedOutputTx(input, redeemInfo, unsignedTx), PlaceHolderSig).tx.weight() val fee = weight2fee(feeratePerKw, weight) val amount = input.txOut.amount - fee if (amount < localDustLimit) { Left(AmountBelowDustLimit) } else { - val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil) - Right(ClaimRemoteDelayedOutputTx(input, tx1)) + val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil) + Right(ClaimRemoteDelayedOutputTx(input, redeemInfo, tx)) } } } - def makeHtlcDelayedTx(htlcTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: FeeratePerKw): Either[TxGenerationSkipped, HtlcDelayedTx] = { - makeLocalDelayedOutputTx(htlcTx, localDustLimit, localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey, localFinalScriptPubKey, feeratePerKw).map { - case (input, tx) => HtlcDelayedTx(input, tx) + def makeHtlcDelayedTx(htlcTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: FeeratePerKw, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, HtlcDelayedTx] = { + makeLocalDelayedOutputTx(htlcTx, localDustLimit, localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey, localFinalScriptPubKey, feeratePerKw, commitmentFormat).map { + case (input, redeemInfo, tx) => HtlcDelayedTx(input, redeemInfo, tx) } } - def makeClaimLocalDelayedOutputTx(commitTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: FeeratePerKw): Either[TxGenerationSkipped, ClaimLocalDelayedOutputTx] = { - makeLocalDelayedOutputTx(commitTx, localDustLimit, localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey, localFinalScriptPubKey, feeratePerKw).map { - case (input, tx) => ClaimLocalDelayedOutputTx(input, tx) + def makeClaimLocalDelayedOutputTx(commitTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: FeeratePerKw, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimLocalDelayedOutputTx] = { + makeLocalDelayedOutputTx(commitTx, localDustLimit, localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey, localFinalScriptPubKey, feeratePerKw, commitmentFormat).map { + case (input, redeemInfo, tx) => ClaimLocalDelayedOutputTx(input, redeemInfo, tx) } } - private def makeLocalDelayedOutputTx(parentTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: FeeratePerKw): Either[TxGenerationSkipped, (InputInfo, Transaction)] = { - val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) - val pubkeyScript = write(pay2wsh(redeemScript)) + private def makeLocalDelayedOutputTx(parentTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: FeeratePerKw, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, (InputInfo, RedeemInfo.TaprootScriptPathOrSegwitV0, Transaction)] = { + val (redeemInfo, pubkeyScript) = commitmentFormat match { + case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) + (RedeemInfo.SegwitV0(redeemScript), write(pay2wsh(redeemScript))) + } findPubKeyScriptIndex(parentTx, pubkeyScript) match { case Left(skip) => Left(skip) case Right(outputIndex) => - val input = InputInfo(OutPoint(parentTx, outputIndex), parentTx.txOut(outputIndex), write(redeemScript)) - // unsigned transaction - val tx = Transaction( + val input = InputInfo(OutPoint(parentTx, outputIndex), parentTx.txOut(outputIndex)) + val unsignedTx = Transaction( version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, toLocalDelay.toInt) :: Nil, - txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, - lockTime = 0) + txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil, + lockTime = 0 + ) // compute weight with a dummy 73 bytes signature (the largest you can get) - val weight = addSigs(ClaimLocalDelayedOutputTx(input, tx), PlaceHolderSig).tx.weight() + val weight = addSigs(ClaimLocalDelayedOutputTx(input, redeemInfo, unsignedTx), PlaceHolderSig).tx.weight() val fee = weight2fee(feeratePerKw, weight) val amount = input.txOut.amount - fee if (amount < localDustLimit) { Left(AmountBelowDustLimit) } else { - val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil) - Right(input, tx1) + val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil) + Right((input, redeemInfo, tx)) } } } - private def makeClaimAnchorOutputTx(commitTx: Transaction, fundingPubkey: PublicKey): Either[TxGenerationSkipped, (InputInfo, Transaction)] = { - val redeemScript = anchor(fundingPubkey) - val pubkeyScript = write(pay2wsh(redeemScript)) + private def makeClaimAnchorOutputTx(commitTx: Transaction, anchorKey: PublicKey, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, (InputInfo, RedeemInfo.TaprootKeyPathOrSegwitV0, Transaction)] = { + val (redeemInfo, pubkeyScript) = commitmentFormat match { + case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + val redeemScript = anchor(anchorKey) + (RedeemInfo.SegwitV0(redeemScript), write(pay2wsh(redeemScript))) + } findPubKeyScriptIndex(commitTx, pubkeyScript) match { case Left(skip) => Left(skip) case Right(outputIndex) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) - // unsigned transaction - val tx = Transaction( + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) + val unsignedTx = Transaction( version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, 0) :: Nil, txOut = Nil, // anchor is only used to bump fees, the output will be added later depending on available inputs - lockTime = 0) - Right((input, tx)) + lockTime = 0 + ) + Right((input, redeemInfo, unsignedTx)) } } - def makeClaimLocalAnchorOutputTx(commitTx: Transaction, localFundingPubkey: PublicKey, confirmationTarget: ConfirmationTarget): Either[TxGenerationSkipped, ClaimLocalAnchorOutputTx] = { - makeClaimAnchorOutputTx(commitTx, localFundingPubkey).map { case (input, tx) => ClaimLocalAnchorOutputTx(input, tx, confirmationTarget) } + def makeClaimLocalAnchorOutputTx(commitTx: Transaction, localFundingPubkey: PublicKey, confirmationTarget: ConfirmationTarget, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimLocalAnchorOutputTx] = { + makeClaimAnchorOutputTx(commitTx, localFundingPubkey, commitmentFormat).map { + case (input, redeemInfo, tx) => ClaimLocalAnchorOutputTx(input, redeemInfo, tx, confirmationTarget) + } } - def makeClaimRemoteAnchorOutputTx(commitTx: Transaction, remoteFundingPubkey: PublicKey): Either[TxGenerationSkipped, ClaimRemoteAnchorOutputTx] = { - makeClaimAnchorOutputTx(commitTx, remoteFundingPubkey).map { case (input, tx) => ClaimRemoteAnchorOutputTx(input, tx) } + def makeClaimRemoteAnchorOutputTx(commitTx: Transaction, remoteFundingPubkey: PublicKey, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, ClaimRemoteAnchorOutputTx] = { + makeClaimAnchorOutputTx(commitTx, remoteFundingPubkey, commitmentFormat).map { + case (input, redeemInfo, tx) => ClaimRemoteAnchorOutputTx(input, redeemInfo, tx) + } } - def makeClaimHtlcDelayedOutputPenaltyTxs(htlcTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: FeeratePerKw): Seq[Either[TxGenerationSkipped, ClaimHtlcDelayedOutputPenaltyTx]] = { - val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) - val pubkeyScript = write(pay2wsh(redeemScript)) + def makeClaimHtlcDelayedOutputPenaltyTxs(htlcTx: Transaction, localDustLimit: Satoshi, localRevocationPubkey: PublicKey, toLocalDelay: CltvExpiryDelta, localDelayedPaymentPubkey: PublicKey, localFinalScriptPubKey: ByteVector, feeratePerKw: FeeratePerKw, commitmentFormat: CommitmentFormat): Seq[Either[TxGenerationSkipped, ClaimHtlcDelayedOutputPenaltyTx]] = { + val (redeemInfo, pubkeyScript) = commitmentFormat match { + case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + val redeemScript = toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) + (RedeemInfo.SegwitV0(redeemScript), write(pay2wsh(redeemScript))) + } findPubKeyScriptIndexes(htlcTx, pubkeyScript) match { case Left(skip) => Seq(Left(skip)) case Right(outputIndexes) => outputIndexes.map(outputIndex => { - val input = InputInfo(OutPoint(htlcTx, outputIndex), htlcTx.txOut(outputIndex), write(redeemScript)) - // unsigned transaction - val tx = Transaction( + val input = InputInfo(OutPoint(htlcTx, outputIndex), htlcTx.txOut(outputIndex)) + val unsignedTx = Transaction( version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, 0xffffffffL) :: Nil, - txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, - lockTime = 0) + txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil, + lockTime = 0 + ) // compute weight with a dummy 73 bytes signature (the largest you can get) - val weight = addSigs(ClaimHtlcDelayedOutputPenaltyTx(input, tx), PlaceHolderSig).tx.weight() + val weight = addSigs(ClaimHtlcDelayedOutputPenaltyTx(input, redeemInfo, unsignedTx), PlaceHolderSig).tx.weight() val fee = weight2fee(feeratePerKw, weight) val amount = input.txOut.amount - fee if (amount < localDustLimit) { Left(AmountBelowDustLimit) } else { - val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil) - Right(ClaimHtlcDelayedOutputPenaltyTx(input, tx1)) + val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil) + Right(ClaimHtlcDelayedOutputPenaltyTx(input, redeemInfo, tx)) } }) } } - def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: ByteVector, toRemoteDelay: CltvExpiryDelta, remoteDelayedPaymentPubkey: PublicKey, feeratePerKw: FeeratePerKw): Either[TxGenerationSkipped, MainPenaltyTx] = { - val redeemScript = toLocalDelayed(remoteRevocationPubkey, toRemoteDelay, remoteDelayedPaymentPubkey) - val pubkeyScript = write(pay2wsh(redeemScript)) + def makeMainPenaltyTx(commitTx: Transaction, localDustLimit: Satoshi, remoteRevocationPubkey: PublicKey, localFinalScriptPubKey: ByteVector, toRemoteDelay: CltvExpiryDelta, remoteDelayedPaymentPubkey: PublicKey, feeratePerKw: FeeratePerKw, commitmentFormat: CommitmentFormat): Either[TxGenerationSkipped, MainPenaltyTx] = { + val (redeemInfo, pubkeyScript) = commitmentFormat match { + case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => + val redeemScript = toLocalDelayed(remoteRevocationPubkey, toRemoteDelay, remoteDelayedPaymentPubkey) + (RedeemInfo.SegwitV0(redeemScript), write(pay2wsh(redeemScript))) + } findPubKeyScriptIndex(commitTx, pubkeyScript) match { case Left(skip) => Left(skip) case Right(outputIndex) => - val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex), write(redeemScript)) - // unsigned transaction - val tx = Transaction( + val input = InputInfo(OutPoint(commitTx, outputIndex), commitTx.txOut(outputIndex)) + val unsignedTx = Transaction( version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, 0xffffffffL) :: Nil, txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, - lockTime = 0) + lockTime = 0 + ) // compute weight with a dummy 73 bytes signature (the largest you can get) - val weight = addSigs(MainPenaltyTx(input, tx), PlaceHolderSig).tx.weight() + val weight = addSigs(MainPenaltyTx(input, redeemInfo, unsignedTx), PlaceHolderSig).tx.weight() val fee = weight2fee(feeratePerKw, weight) val amount = input.txOut.amount - fee if (amount < localDustLimit) { Left(AmountBelowDustLimit) } else { - val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil) - Right(MainPenaltyTx(input, tx1)) + val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil) + Right(MainPenaltyTx(input, redeemInfo, tx)) } } } - /** - * We already have the redeemScript, no need to build it - */ - def makeHtlcPenaltyTx(commitTx: Transaction, htlcOutputIndex: Int, redeemScript: ByteVector, localDustLimit: Satoshi, localFinalScriptPubKey: ByteVector, feeratePerKw: FeeratePerKw): Either[TxGenerationSkipped, HtlcPenaltyTx] = { - val input = InputInfo(OutPoint(commitTx, htlcOutputIndex), commitTx.txOut(htlcOutputIndex), redeemScript) - // unsigned transaction - val tx = Transaction( + def makeHtlcPenaltyTx(commitTx: Transaction, htlcOutputIndex: Int, redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, localDustLimit: Satoshi, localFinalScriptPubKey: ByteVector, feeratePerKw: FeeratePerKw): Either[TxGenerationSkipped, HtlcPenaltyTx] = { + val input = InputInfo(OutPoint(commitTx, htlcOutputIndex), commitTx.txOut(htlcOutputIndex)) + val unsignedTx = Transaction( version = 2, txIn = TxIn(input.outPoint, ByteVector.empty, 0xffffffffL) :: Nil, - txOut = TxOut(Satoshi(0), localFinalScriptPubKey) :: Nil, - lockTime = 0) + txOut = TxOut(0 sat, localFinalScriptPubKey) :: Nil, + lockTime = 0 + ) // compute weight with a dummy 73 bytes signature (the largest you can get) - val weight = addSigs(MainPenaltyTx(input, tx), PlaceHolderSig).tx.weight() + val weight = addSigs(MainPenaltyTx(input, redeemInfo, unsignedTx), PlaceHolderSig).tx.weight() val fee = weight2fee(feeratePerKw, weight) val amount = input.txOut.amount - fee if (amount < localDustLimit) { Left(AmountBelowDustLimit) } else { - val tx1 = tx.copy(txOut = tx.txOut.head.copy(amount = amount) :: Nil) - Right(HtlcPenaltyTx(input, tx1)) + val tx = unsignedTx.copy(txOut = TxOut(amount, localFinalScriptPubKey) :: Nil) + Right(HtlcPenaltyTx(input, redeemInfo, tx)) } } - def makeClosingTx(commitTxInput: InputInfo, localScriptPubKey: ByteVector, remoteScriptPubKey: ByteVector, localPaysClosingFees: Boolean, dustLimit: Satoshi, closingFee: Satoshi, spec: CommitmentSpec): ClosingTx = { + def makeClosingTx(commitTxInput: InputInfo, + redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, + localScriptPubKey: ByteVector, + remoteScriptPubKey: ByteVector, + localPaysClosingFees: Boolean, + dustLimit: Satoshi, + closingFee: Satoshi, + spec: CommitmentSpec): ClosingTx = { require(spec.htlcs.isEmpty, "there shouldn't be any pending htlcs") val (toLocalAmount: Satoshi, toRemoteAmount: Satoshi) = if (localPaysClosingFees) { @@ -883,9 +958,10 @@ object Transactions { version = 2, txIn = TxIn(commitTxInput.outPoint, ByteVector.empty, sequence = 0xffffffffL) :: Nil, txOut = toLocalOutput_opt.toSeq ++ toRemoteOutput_opt.toSeq ++ Nil, - lockTime = 0)) + lockTime = 0 + )) val toLocalOutput = findPubKeyScriptIndex(tx, localScriptPubKey).map(index => OutputInfo(index, toLocalAmount, localScriptPubKey)).toOption - ClosingTx(commitTxInput, tx, toLocalOutput) + ClosingTx(commitTxInput, redeemInfo, tx, toLocalOutput) } // @formatter:off @@ -906,7 +982,13 @@ object Transactions { override def toString: String = s"localAndRemote=${localAndRemote_opt.map(_.tx.toString()).getOrElse("n/a")}, localOnly=${localOnly_opt.map(_.tx.toString()).getOrElse("n/a")}, remoteOnly=${remoteOnly_opt.map(_.tx.toString()).getOrElse("n/a")}" } - def makeSimpleClosingTxs(input: InputInfo, spec: CommitmentSpec, fee: SimpleClosingTxFee, lockTime: Long, localScriptPubKey: ByteVector, remoteScriptPubKey: ByteVector): ClosingTxs = { + def makeSimpleClosingTxs(input: InputInfo, + redeemInfo: RedeemInfo.TaprootKeyPathOrSegwitV0, + spec: CommitmentSpec, + fee: SimpleClosingTxFee, + lockTime: Long, + localScriptPubKey: ByteVector, + remoteScriptPubKey: ByteVector): ClosingTxs = { require(spec.htlcs.isEmpty, "there shouldn't be any pending htlcs") val txNoOutput = Transaction(2, Seq(TxIn(input.outPoint, ByteVector.empty, sequence = 0xFFFFFFFDL)), Nil, lockTime) @@ -938,27 +1020,29 @@ object Transactions { val txLocalAndRemote = LexicographicalOrdering.sort(txNoOutput.copy(txOut = Seq(toLocalOutput, toRemoteOutput))) val Right(toLocalOutputInfo) = findPubKeyScriptIndex(txLocalAndRemote, localScriptPubKey).map(index => OutputInfo(index, toLocalOutput.amount, localScriptPubKey)) ClosingTxs( - localAndRemote_opt = Some(ClosingTx(input, txLocalAndRemote, Some(toLocalOutputInfo))), + localAndRemote_opt = Some(ClosingTx(input, redeemInfo, txLocalAndRemote, Some(toLocalOutputInfo))), // We also provide a version of the transaction without the remote output, which they may want to omit if not economical to spend. - localOnly_opt = Some(ClosingTx(input, txNoOutput.copy(txOut = Seq(toLocalOutput)), Some(OutputInfo(0, toLocalOutput.amount, localScriptPubKey)))), + localOnly_opt = Some(ClosingTx(input, redeemInfo, txNoOutput.copy(txOut = Seq(toLocalOutput)), Some(OutputInfo(0, toLocalOutput.amount, localScriptPubKey)))), remoteOnly_opt = None ) case (Some(toLocalOutput), None) => ClosingTxs( localAndRemote_opt = None, - localOnly_opt = Some(ClosingTx(input, txNoOutput.copy(txOut = Seq(toLocalOutput)), Some(OutputInfo(0, toLocalOutput.amount, localScriptPubKey)))), + localOnly_opt = Some(ClosingTx(input, redeemInfo, txNoOutput.copy(txOut = Seq(toLocalOutput)), Some(OutputInfo(0, toLocalOutput.amount, localScriptPubKey)))), remoteOnly_opt = None ) case (None, Some(toRemoteOutput)) => ClosingTxs( localAndRemote_opt = None, localOnly_opt = None, - remoteOnly_opt = Some(ClosingTx(input, txNoOutput.copy(txOut = Seq(toRemoteOutput)), None)) + remoteOnly_opt = Some(ClosingTx(input, redeemInfo, txNoOutput.copy(txOut = Seq(toRemoteOutput)), None)) ) case (None, None) => ClosingTxs(None, None, None) } } + def findPubKeyScriptIndex(tx: Transaction, pubkeyScript: Seq[ScriptElt]): Either[TxGenerationSkipped, Int] = findPubKeyScriptIndex(tx, write(pubkeyScript)) + def findPubKeyScriptIndex(tx: Transaction, pubkeyScript: ByteVector): Either[TxGenerationSkipped, Int] = { val outputIndex = tx.txOut.indexWhere(_.publicKeyScript == pubkeyScript) if (outputIndex >= 0) { @@ -968,7 +1052,7 @@ object Transactions { } } - def findPubKeyScriptIndexes(tx: Transaction, pubkeyScript: ByteVector): Either[TxGenerationSkipped, Seq[Int]] = { + private def findPubKeyScriptIndexes(tx: Transaction, pubkeyScript: ByteVector): Either[TxGenerationSkipped, Seq[Int]] = { val outputIndexes = tx.txOut.zipWithIndex.collect { case (txOut, index) if txOut.publicKeyScript == pubkeyScript => index } @@ -982,13 +1066,13 @@ object Transactions { /** * Default public key used for fee estimation */ - val PlaceHolderPubKey = PrivateKey(ByteVector32.One).publicKey + val PlaceHolderPubKey: PublicKey = PrivateKey(ByteVector32.One).publicKey /** * This default sig takes 72B when encoded in DER (incl. 1B for the trailing sig hash), it is used for fee estimation * It is 72 bytes because our signatures are normalized (low-s) and will take up 72 bytes at most in DER format */ - val PlaceHolderSig = ByteVector64(ByteVector.fill(64)(0xaa)) + val PlaceHolderSig: ByteVector64 = ByteVector64(ByteVector.fill(64)(0xaa)) assert(der(PlaceHolderSig).size == 72) def addSigs(commitTx: CommitTx, localFundingPubkey: PublicKey, remoteFundingPubkey: PublicKey, localSig: ByteVector64, remoteSig: ByteVector64): CommitTx = { @@ -996,46 +1080,46 @@ object Transactions { commitTx.copy(tx = commitTx.tx.updateWitness(0, witness)) } - def addSigs(mainPenaltyTx: MainPenaltyTx, revocationSig: ByteVector64): MainPenaltyTx = mainPenaltyTx.input match { - case InputInfo.SegwitInput(_, _, redeemScript) => + def addSigs(mainPenaltyTx: MainPenaltyTx, revocationSig: ByteVector64): MainPenaltyTx = mainPenaltyTx.redeemInfo match { + case RedeemInfo.SegwitV0(redeemScript) => val witness = Scripts.witnessToLocalDelayedWithRevocationSig(revocationSig, redeemScript) mainPenaltyTx.copy(tx = mainPenaltyTx.tx.updateWitness(0, witness)) - case _: InputInfo.TaprootInput => mainPenaltyTx + case _: RedeemInfo.TaprootKeyPath => mainPenaltyTx } - def addSigs(htlcPenaltyTx: HtlcPenaltyTx, revocationSig: ByteVector64, revocationPubkey: PublicKey): HtlcPenaltyTx = htlcPenaltyTx.input match { - case InputInfo.SegwitInput(_, _, redeemScript) => + def addSigs(htlcPenaltyTx: HtlcPenaltyTx, revocationSig: ByteVector64, revocationPubkey: PublicKey): HtlcPenaltyTx = htlcPenaltyTx.redeemInfo match { + case RedeemInfo.SegwitV0(redeemScript) => val witness = Scripts.witnessHtlcWithRevocationSig(revocationSig, revocationPubkey, redeemScript) htlcPenaltyTx.copy(tx = htlcPenaltyTx.tx.updateWitness(0, witness)) - case _: InputInfo.TaprootInput => htlcPenaltyTx + case _: RedeemInfo.TaprootKeyPath => htlcPenaltyTx } - def addSigs(htlcSuccessTx: HtlcSuccessTx, localSig: ByteVector64, remoteSig: ByteVector64, paymentPreimage: ByteVector32, commitmentFormat: CommitmentFormat): HtlcSuccessTx = htlcSuccessTx.input match { - case InputInfo.SegwitInput(_, _, redeemScript) => + def addSigs(htlcSuccessTx: HtlcSuccessTx, localSig: ByteVector64, remoteSig: ByteVector64, paymentPreimage: ByteVector32, commitmentFormat: CommitmentFormat): HtlcSuccessTx = htlcSuccessTx.redeemInfo match { + case RedeemInfo.SegwitV0(redeemScript) => val witness = witnessHtlcSuccess(localSig, remoteSig, paymentPreimage, redeemScript, commitmentFormat) htlcSuccessTx.copy(tx = htlcSuccessTx.tx.updateWitness(0, witness)) - case _: InputInfo.TaprootInput => htlcSuccessTx + case _: RedeemInfo.TaprootScriptPath => htlcSuccessTx } - def addSigs(htlcTimeoutTx: HtlcTimeoutTx, localSig: ByteVector64, remoteSig: ByteVector64, commitmentFormat: CommitmentFormat): HtlcTimeoutTx = htlcTimeoutTx.input match { - case InputInfo.SegwitInput(_, _, redeemScript) => + def addSigs(htlcTimeoutTx: HtlcTimeoutTx, localSig: ByteVector64, remoteSig: ByteVector64, commitmentFormat: CommitmentFormat): HtlcTimeoutTx = htlcTimeoutTx.redeemInfo match { + case RedeemInfo.SegwitV0(redeemScript) => val witness = witnessHtlcTimeout(localSig, remoteSig, redeemScript, commitmentFormat) htlcTimeoutTx.copy(tx = htlcTimeoutTx.tx.updateWitness(0, witness)) - case _: InputInfo.TaprootInput => htlcTimeoutTx + case _: RedeemInfo.TaprootScriptPath => htlcTimeoutTx } - def addSigs(claimHtlcSuccessTx: ClaimHtlcSuccessTx, localSig: ByteVector64, paymentPreimage: ByteVector32): ClaimHtlcSuccessTx = claimHtlcSuccessTx.input match { - case InputInfo.SegwitInput(_, _, redeemScript) => + def addSigs(claimHtlcSuccessTx: ClaimHtlcSuccessTx, localSig: ByteVector64, paymentPreimage: ByteVector32): ClaimHtlcSuccessTx = claimHtlcSuccessTx.redeemInfo match { + case RedeemInfo.SegwitV0(redeemScript) => val witness = witnessClaimHtlcSuccessFromCommitTx(localSig, paymentPreimage, redeemScript) claimHtlcSuccessTx.copy(tx = claimHtlcSuccessTx.tx.updateWitness(0, witness)) - case _: InputInfo.TaprootInput => claimHtlcSuccessTx + case _: RedeemInfo.TaprootScriptPath => claimHtlcSuccessTx } - def addSigs(claimHtlcTimeoutTx: ClaimHtlcTimeoutTx, localSig: ByteVector64): ClaimHtlcTimeoutTx = claimHtlcTimeoutTx.input match { - case InputInfo.SegwitInput(_, _, redeemScript) => + def addSigs(claimHtlcTimeoutTx: ClaimHtlcTimeoutTx, localSig: ByteVector64): ClaimHtlcTimeoutTx = claimHtlcTimeoutTx.redeemInfo match { + case RedeemInfo.SegwitV0(redeemScript) => val witness = witnessClaimHtlcTimeoutFromCommitTx(localSig, redeemScript) claimHtlcTimeoutTx.copy(tx = claimHtlcTimeoutTx.tx.updateWitness(0, witness)) - case _: InputInfo.TaprootInput => claimHtlcTimeoutTx + case _: RedeemInfo.TaprootScriptPath => claimHtlcTimeoutTx } def addSigs(claimP2WPKHOutputTx: ClaimP2WPKHOutputTx, localPaymentPubkey: PublicKey, localSig: ByteVector64): ClaimP2WPKHOutputTx = { @@ -1043,39 +1127,39 @@ object Transactions { claimP2WPKHOutputTx.copy(tx = claimP2WPKHOutputTx.tx.updateWitness(0, witness)) } - def addSigs(claimRemoteDelayedOutputTx: ClaimRemoteDelayedOutputTx, localSig: ByteVector64): ClaimRemoteDelayedOutputTx = claimRemoteDelayedOutputTx.input match { - case InputInfo.SegwitInput(_, _, redeemScript) => + def addSigs(claimRemoteDelayedOutputTx: ClaimRemoteDelayedOutputTx, localSig: ByteVector64): ClaimRemoteDelayedOutputTx = claimRemoteDelayedOutputTx.redeemInfo match { + case RedeemInfo.SegwitV0(redeemScript) => val witness = witnessClaimToRemoteDelayedFromCommitTx(localSig, redeemScript) claimRemoteDelayedOutputTx.copy(tx = claimRemoteDelayedOutputTx.tx.updateWitness(0, witness)) - case _: InputInfo.TaprootInput => claimRemoteDelayedOutputTx + case _: RedeemInfo.TaprootScriptPath => claimRemoteDelayedOutputTx } - def addSigs(claimDelayedOutputTx: ClaimLocalDelayedOutputTx, localSig: ByteVector64): ClaimLocalDelayedOutputTx = claimDelayedOutputTx.input match { - case InputInfo.SegwitInput(_, _, redeemScript) => + def addSigs(claimDelayedOutputTx: ClaimLocalDelayedOutputTx, localSig: ByteVector64): ClaimLocalDelayedOutputTx = claimDelayedOutputTx.redeemInfo match { + case RedeemInfo.SegwitV0(redeemScript) => val witness = witnessToLocalDelayedAfterDelay(localSig, redeemScript) claimDelayedOutputTx.copy(tx = claimDelayedOutputTx.tx.updateWitness(0, witness)) - case _: InputInfo.TaprootInput => claimDelayedOutputTx + case _: RedeemInfo.TaprootScriptPath => claimDelayedOutputTx } - def addSigs(htlcDelayedTx: HtlcDelayedTx, localSig: ByteVector64): HtlcDelayedTx = htlcDelayedTx.input match { - case InputInfo.SegwitInput(_, _, redeemScript) => + def addSigs(htlcDelayedTx: HtlcDelayedTx, localSig: ByteVector64): HtlcDelayedTx = htlcDelayedTx.redeemInfo match { + case RedeemInfo.SegwitV0(redeemScript) => val witness = witnessToLocalDelayedAfterDelay(localSig, redeemScript) htlcDelayedTx.copy(tx = htlcDelayedTx.tx.updateWitness(0, witness)) - case _: InputInfo.TaprootInput => htlcDelayedTx + case _: RedeemInfo.TaprootScriptPath => htlcDelayedTx } - def addSigs(claimAnchorOutputTx: ClaimLocalAnchorOutputTx, localSig: ByteVector64): ClaimLocalAnchorOutputTx = claimAnchorOutputTx.input match { - case InputInfo.SegwitInput(_, _, redeemScript) => + def addSigs(claimAnchorOutputTx: ClaimLocalAnchorOutputTx, localSig: ByteVector64): ClaimLocalAnchorOutputTx = claimAnchorOutputTx.redeemInfo match { + case RedeemInfo.SegwitV0(redeemScript) => val witness = witnessAnchor(localSig, redeemScript) claimAnchorOutputTx.copy(tx = claimAnchorOutputTx.tx.updateWitness(0, witness)) - case _: InputInfo.TaprootInput => claimAnchorOutputTx + case _: RedeemInfo.TaprootKeyPath => claimAnchorOutputTx } - def addSigs(claimHtlcDelayedPenalty: ClaimHtlcDelayedOutputPenaltyTx, revocationSig: ByteVector64): ClaimHtlcDelayedOutputPenaltyTx = claimHtlcDelayedPenalty.input match { - case InputInfo.SegwitInput(_, _, redeemScript) => + def addSigs(claimHtlcDelayedPenalty: ClaimHtlcDelayedOutputPenaltyTx, revocationSig: ByteVector64): ClaimHtlcDelayedOutputPenaltyTx = claimHtlcDelayedPenalty.redeemInfo match { + case RedeemInfo.SegwitV0(redeemScript) => val witness = Scripts.witnessToLocalDelayedWithRevocationSig(revocationSig, redeemScript) claimHtlcDelayedPenalty.copy(tx = claimHtlcDelayedPenalty.tx.updateWitness(0, witness)) - case _: InputInfo.TaprootInput => claimHtlcDelayedPenalty + case _: RedeemInfo.TaprootKeyPath => claimHtlcDelayedPenalty } def addSigs(closingTx: ClosingTx, localFundingPubkey: PublicKey, remoteFundingPubkey: PublicKey, localSig: ByteVector64, remoteSig: ByteVector64): ClosingTx = { @@ -1083,8 +1167,8 @@ object Transactions { closingTx.copy(tx = closingTx.tx.updateWitness(0, witness)) } - def checkSpendable(txinfo: TransactionWithInputInfo): Try[Unit] = { - // NB: we don't verify the other inputs as they should only be wallet inputs used to RBF the transaction - Try(Transaction.correctlySpends(txinfo.tx, Map(txinfo.input.outPoint -> txinfo.input.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) + def checkSpendable(txInfo: TransactionWithInputInfo): Try[Unit] = { + // NB: we don't verify the other inputs as they should only be wallet inputs used to RBF the transaction. + Try(Transaction.correctlySpends(txInfo.tx, Map(txInfo.input.outPoint -> txInfo.input.txOut), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)) } } 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 281e3d7d8b..cc8b44f154 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 @@ -124,10 +124,9 @@ private[channel] object ChannelCodecs0 { closingTx => closingTx.tx ) - val inputInfoCodec: Codec[InputInfo] = ( - ("outPoint" | outPointCodec) :: - ("txOut" | txOutCodec) :: - ("redeemScript" | varsizebinarydata)).as[InputInfo.SegwitInput].upcast[InputInfo].decodeOnly + val inputInfoCodec: Codec[InputInfo] = (("outPoint" | outPointCodec) :: ("txOut" | txOutCodec)).as[InputInfo].decodeOnly + + val redeemInfoCodec: Codec[RedeemInfo.SegwitV0] = ("redeemScript" | varsizebinarydata).as[RedeemInfo.SegwitV0].decodeOnly private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0))) @@ -137,16 +136,16 @@ private[channel] object ChannelCodecs0 { // as quickly as possible. It's very unlikely that nodes will run into this, so it's a good trade-off between code // complexity and real world impact. val txWithInputInfoCodec: Codec[TransactionWithInputInfo] = discriminated[TransactionWithInputInfo].by(uint16) - .typecase(0x01, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) - .typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L)) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx]) - .typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[HtlcTimeoutTx]) - .typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx]) - .typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx]) - .typecase(0x06, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx]) - .typecase(0x07, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx]) - .typecase(0x08, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx]) - .typecase(0x09, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcPenaltyTx]) - .typecase(0x10, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | provide(Option.empty[OutputInfo]))).as[ClosingTx]) + .typecase(0x01, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[CommitTx]) + .typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L)) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx]) + .typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[HtlcTimeoutTx]) + .typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx]) + .typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx]) + .typecase(0x06, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx]) + .typecase(0x07, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx]) + .typecase(0x08, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[MainPenaltyTx]) + .typecase(0x09, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[HtlcPenaltyTx]) + .typecase(0x10, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec) :: ("outputIndex" | provide(Option.empty[OutputInfo]))).as[ClosingTx]) // this is a backward compatible codec (we used to store the sig as DER encoded), now we store it as 64-bytes val sig64OrDERCodec: Codec[ByteVector64] = Codec[ByteVector64]( @@ -163,7 +162,7 @@ private[channel] object ChannelCodecs0 { ("remoteSig" | variableSizeBytes(uint16, sig64OrDERCodec))).as[HtlcTxAndSigs].decodeOnly val publishableTxsCodec: Codec[PublishableTxs] = ( - ("commitTx" | (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) :: + ("commitTx" | (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[CommitTx]) :: ("htlcTxsAndSigs" | listOfN(uint16, htlcTxAndSigsCodec))).as[PublishableTxs].decodeOnly val localCommitCodec: Codec[ChannelTypes0.LocalCommit] = ( @@ -290,6 +289,7 @@ private[channel] object ChannelCodecs0 { ("originChannels" | originsMapCodec) :: ("remoteNextCommitInfo" | either(bool, waitingForRevocationCodec, publicKey)) :: ("commitInput" | inputInfoCodec) :: + ("redeemInfo" | redeemInfoCodec) :: ("remotePerCommitmentSecrets" | ShaChain.shaChainCodec) :: ("channelId" | bytes32) }).as[ChannelTypes0.Commitments].decodeOnly.map[Commitments](_.migrate()).decodeOnly 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 f11978187c..671ee9f8cf 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 @@ -47,9 +47,11 @@ private[channel] object ChannelTypes0 { // modified: we don't use the InputInfo in closing business logic, so we don't need to fill everything (this part // assumes that we only have standard channels, no anchor output channels - which was the case before version2). val input = childTx.txIn.head.outPoint - InputInfo(input, parentTx.txOut(input.index.toInt), Nil) + InputInfo(input, parentTx.txOut(input.index.toInt)) } + private def getDummyRedeemInfo(): RedeemInfo.SegwitV0 = RedeemInfo.SegwitV0(Nil) + case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx: Option[Transaction], htlcSuccessTxs: List[Transaction], htlcTimeoutTxs: List[Transaction], claimHtlcDelayedTxs: List[Transaction], irrevocablySpent: Map[OutPoint, TxId]) { def migrate(): channel.LocalCommitPublished = { val htlcTxs = htlcSuccessTxs ++ htlcTimeoutTxs @@ -57,14 +59,14 @@ private[channel] object ChannelTypes0 { // NB: irrevocablySpent may contain transactions that belong to our peer: we will drop them in this migration but // the channel will put a watch at start-up which will make us fetch the spending transaction. val irrevocablySpentNew = irrevocablySpent.collect { case (outpoint, txid) if knownTxs.contains(txid) => (outpoint, knownTxs(txid)) } - val claimMainDelayedOutputTxNew = claimMainDelayedOutputTx.map(tx => ClaimLocalDelayedOutputTx(getPartialInputInfo(commitTx, tx), tx)) - val htlcSuccessTxsNew = htlcSuccessTxs.map(tx => HtlcSuccessTx(getPartialInputInfo(commitTx, tx), tx, ByteVector32.Zeroes, 0, ConfirmationTarget.Absolute(BlockHeight(0)))) - val htlcTimeoutTxsNew = htlcTimeoutTxs.map(tx => HtlcTimeoutTx(getPartialInputInfo(commitTx, tx), tx, 0, ConfirmationTarget.Absolute(BlockHeight(0)))) + val claimMainDelayedOutputTxNew = claimMainDelayedOutputTx.map(tx => ClaimLocalDelayedOutputTx(getPartialInputInfo(commitTx, tx), getDummyRedeemInfo(), tx)) + val htlcSuccessTxsNew = htlcSuccessTxs.map(tx => HtlcSuccessTx(getPartialInputInfo(commitTx, tx), getDummyRedeemInfo(), tx, ByteVector32.Zeroes, 0, ConfirmationTarget.Absolute(BlockHeight(0)))) + val htlcTimeoutTxsNew = htlcTimeoutTxs.map(tx => HtlcTimeoutTx(getPartialInputInfo(commitTx, tx), getDummyRedeemInfo(), tx, 0, ConfirmationTarget.Absolute(BlockHeight(0)))) val htlcTxsNew = (htlcSuccessTxsNew ++ htlcTimeoutTxsNew).map(tx => tx.input.outPoint -> Some(tx)).toMap val claimHtlcDelayedTxsNew = claimHtlcDelayedTxs.map(tx => { val htlcTx = htlcTxs.find(_.txid == tx.txIn.head.outPoint.txid) require(htlcTx.nonEmpty, s"3rd-stage htlc tx doesn't spend one of our htlc txs: claim-htlc-tx=$tx, htlc-txs=${htlcTxs.mkString(",")}") - HtlcDelayedTx(getPartialInputInfo(htlcTx.get, tx), tx) + HtlcDelayedTx(getPartialInputInfo(htlcTx.get, tx), getDummyRedeemInfo(), tx) }) channel.LocalCommitPublished(commitTx, claimMainDelayedOutputTxNew, htlcTxsNew, claimHtlcDelayedTxsNew, Nil, irrevocablySpentNew) } @@ -77,9 +79,9 @@ private[channel] object ChannelTypes0 { // NB: irrevocablySpent may contain transactions that belong to our peer: we will drop them in this migration but // the channel will put a watch at start-up which will make us fetch the spending transaction. val irrevocablySpentNew = irrevocablySpent.collect { case (outpoint, txid) if knownTxs.contains(txid) => (outpoint, knownTxs(txid)) } - val claimMainOutputTxNew = claimMainOutputTx.map(tx => ClaimP2WPKHOutputTx(getPartialInputInfo(commitTx, tx), tx)) - val claimHtlcSuccessTxsNew = claimHtlcSuccessTxs.map(tx => LegacyClaimHtlcSuccessTx(getPartialInputInfo(commitTx, tx), tx, 0, ConfirmationTarget.Absolute(BlockHeight(0)))) - val claimHtlcTimeoutTxsNew = claimHtlcTimeoutTxs.map(tx => ClaimHtlcTimeoutTx(getPartialInputInfo(commitTx, tx), tx, 0, ConfirmationTarget.Absolute(BlockHeight(0)))) + val claimMainOutputTxNew = claimMainOutputTx.map(tx => ClaimP2WPKHOutputTx(getPartialInputInfo(commitTx, tx), getDummyRedeemInfo(), tx)) + val claimHtlcSuccessTxsNew = claimHtlcSuccessTxs.map(tx => LegacyClaimHtlcSuccessTx(getPartialInputInfo(commitTx, tx), getDummyRedeemInfo(), tx, 0, ConfirmationTarget.Absolute(BlockHeight(0)))) + val claimHtlcTimeoutTxsNew = claimHtlcTimeoutTxs.map(tx => ClaimHtlcTimeoutTx(getPartialInputInfo(commitTx, tx), getDummyRedeemInfo(), tx, 0, ConfirmationTarget.Absolute(BlockHeight(0)))) val claimHtlcTxsNew = (claimHtlcSuccessTxsNew ++ claimHtlcTimeoutTxsNew).map(tx => tx.input.outPoint -> Some(tx)).toMap channel.RemoteCommitPublished(commitTx, claimMainOutputTxNew, claimHtlcTxsNew, Nil, irrevocablySpentNew) } @@ -91,12 +93,12 @@ private[channel] object ChannelTypes0 { // NB: irrevocablySpent may contain transactions that belong to our peer: we will drop them in this migration but // the channel will put a watch at start-up which will make us fetch the spending transaction. val irrevocablySpentNew = irrevocablySpent.collect { case (outpoint, txid) if knownTxs.contains(txid) => (outpoint, knownTxs(txid)) } - val claimMainOutputTxNew = claimMainOutputTx.map(tx => ClaimP2WPKHOutputTx(getPartialInputInfo(commitTx, tx), tx)) - val mainPenaltyTxNew = mainPenaltyTx.map(tx => MainPenaltyTx(getPartialInputInfo(commitTx, tx), tx)) - val htlcPenaltyTxsNew = htlcPenaltyTxs.map(tx => HtlcPenaltyTx(getPartialInputInfo(commitTx, tx), tx)) + val claimMainOutputTxNew = claimMainOutputTx.map(tx => ClaimP2WPKHOutputTx(getPartialInputInfo(commitTx, tx), getDummyRedeemInfo(), tx)) + val mainPenaltyTxNew = mainPenaltyTx.map(tx => MainPenaltyTx(getPartialInputInfo(commitTx, tx), getDummyRedeemInfo(), tx)) + val htlcPenaltyTxsNew = htlcPenaltyTxs.map(tx => HtlcPenaltyTx(getPartialInputInfo(commitTx, tx), getDummyRedeemInfo(), tx)) val claimHtlcDelayedPenaltyTxsNew = claimHtlcDelayedPenaltyTxs.map(tx => { // We don't have all the `InputInfo` data, but it's ok: we only use the tx that is fully signed. - ClaimHtlcDelayedOutputPenaltyTx(InputInfo(tx.txIn.head.outPoint, TxOut(Satoshi(0), Nil), Nil), tx) + ClaimHtlcDelayedOutputPenaltyTx(InputInfo(tx.txIn.head.outPoint, TxOut(Satoshi(0), Nil)), getDummyRedeemInfo(), tx) }) channel.RevokedCommitPublished(commitTx, claimMainOutputTxNew, mainPenaltyTxNew, htlcPenaltyTxsNew, claimHtlcDelayedPenaltyTxsNew, irrevocablySpentNew) } @@ -113,7 +115,7 @@ private[channel] object ChannelTypes0 { * the raw transaction. It provides more information for auditing but is not used for business logic, so we can safely * put dummy values in the migration. */ - def migrateClosingTx(tx: Transaction): ClosingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(Satoshi(0), Nil), Nil), tx, None) + def migrateClosingTx(tx: Transaction): ClosingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(Satoshi(0), Nil)), getDummyRedeemInfo(), tx, None) case class HtlcTxAndSigs(txinfo: HtlcTx, localSig: ByteVector64, remoteSig: ByteVector64) @@ -230,6 +232,7 @@ private[channel] object ChannelTypes0 { originChannels: Map[Long, Origin], remoteNextCommitInfo: Either[WaitingForRevocation, PublicKey], commitInput: InputInfo, + redeemInfo: RedeemInfo.SegwitV0, remotePerCommitmentSecrets: ShaChain, channelId: ByteVector32) { def migrate(): channel.Commitments = { val channelConfig = if (channelVersion.hasPubkeyKeyPath) { 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 323774a41f..0395023111 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 @@ -96,26 +96,25 @@ private[channel] object ChannelCodecs1 { closingTx => closingTx.tx ) - val inputInfoCodec: Codec[InputInfo] = ( - ("outPoint" | outPointCodec) :: - ("txOut" | txOutCodec) :: - ("redeemScript" | lengthDelimited(bytes))).as[InputInfo.SegwitInput].upcast[InputInfo].decodeOnly + val inputInfoCodec: Codec[InputInfo] = (("outPoint" | outPointCodec) :: ("txOut" | txOutCodec)).as[InputInfo].decodeOnly + + val redeemInfoCodec: Codec[RedeemInfo.SegwitV0] = ("redeemScript" | lengthDelimited(bytes)).as[RedeemInfo.SegwitV0].decodeOnly private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0))) // NB: we can safely set htlcId = 0 for htlc txs. This information is only used to find upstream htlcs to fail when a // downstream htlc times out, and `Helpers.Closing.timedOutHtlcs` explicitly handles the case where htlcId is missing. val txWithInputInfoCodec: Codec[TransactionWithInputInfo] = discriminated[TransactionWithInputInfo].by(uint16) - .typecase(0x01, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) - .typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L)) :: ("confirmationTargetBefore" | defaultConfirmationTarget)).as[HtlcSuccessTx]) - .typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[HtlcTimeoutTx]) - .typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx]) - .typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx]) - .typecase(0x06, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx]) - .typecase(0x07, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx]) - .typecase(0x08, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx]) - .typecase(0x09, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcPenaltyTx]) - .typecase(0x10, (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | provide(Option.empty[OutputInfo]))).as[ClosingTx]) + .typecase(0x01, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[CommitTx]) + .typecase(0x02, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | provide(0L)) :: ("confirmationTargetBefore" | defaultConfirmationTarget)).as[HtlcSuccessTx]) + .typecase(0x03, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[HtlcTimeoutTx]) + .typecase(0x04, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx]) + .typecase(0x05, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | provide(0L)) :: ("confirmBefore" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx]) + .typecase(0x06, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx]) + .typecase(0x07, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx]) + .typecase(0x08, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[MainPenaltyTx]) + .typecase(0x09, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[HtlcPenaltyTx]) + .typecase(0x10, (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec) :: ("outputIndex" | provide(Option.empty[OutputInfo]))).as[ClosingTx]) val htlcTxAndSigsCodec: Codec[HtlcTxAndSigs] = ( ("txinfo" | txWithInputInfoCodec.downcast[HtlcTx]) :: @@ -123,7 +122,7 @@ private[channel] object ChannelCodecs1 { ("remoteSig" | lengthDelimited(bytes64))).as[HtlcTxAndSigs] val publishableTxsCodec: Codec[PublishableTxs] = ( - ("commitTx" | (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) :: + ("commitTx" | (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[CommitTx]) :: ("htlcTxsAndSigs" | listOfN(uint16, htlcTxAndSigsCodec))).as[PublishableTxs] val localCommitCodec: Codec[ChannelTypes0.LocalCommit] = ( @@ -203,6 +202,7 @@ private[channel] object ChannelCodecs1 { ("originChannels" | originsMapCodec) :: ("remoteNextCommitInfo" | either(bool8, waitingForRevocationCodec, publicKey)) :: ("commitInput" | inputInfoCodec) :: + ("redeemInfo" | redeemInfoCodec) :: ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: ("channelId" | bytes32) }).as[ChannelTypes0.Commitments].decodeOnly.map[Commitments](_.migrate()).decodeOnly 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 93d405f7cf..7ea64b1941 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 @@ -100,10 +100,9 @@ private[channel] object ChannelCodecs2 { val txCodec: Codec[Transaction] = lengthDelimited(bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d))) - val inputInfoCodec: Codec[InputInfo] = ( - ("outPoint" | outPointCodec) :: - ("txOut" | txOutCodec) :: - ("redeemScript" | lengthDelimited(bytes))).as[InputInfo.SegwitInput].upcast[InputInfo].decodeOnly + val inputInfoCodec: Codec[InputInfo] = (("outPoint" | outPointCodec) :: ("txOut" | txOutCodec)).as[InputInfo].decodeOnly + + val redeemInfoCodec: Codec[RedeemInfo.SegwitV0] = ("redeemScript" | lengthDelimited(bytes)).as[RedeemInfo.SegwitV0].decodeOnly val outputInfoCodec: Codec[OutputInfo] = ( ("index" | uint32) :: @@ -112,21 +111,21 @@ private[channel] object ChannelCodecs2 { private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0))) - val commitTxCodec: Codec[CommitTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx] - val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx] - val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcTimeoutTx] - val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcDelayedTx] - val claimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx] - val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx] - val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx] - val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx] - val claimRemoteDelayedOutputTxCodec: Codec[ClaimRemoteDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteDelayedOutputTx] - val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx] - val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcPenaltyTx] - val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimHtlcDelayedOutputPenaltyTx] - val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | defaultConfirmationTarget.upcast[ConfirmationTarget])).as[ClaimLocalAnchorOutputTx] - val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx] - val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx] + val commitTxCodec: Codec[CommitTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[CommitTx] + val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx] + val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcTimeoutTx] + val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec)).as[HtlcDelayedTx] + val claimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx] + val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx] + val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx] + val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx] + val claimRemoteDelayedOutputTxCodec: Codec[ClaimRemoteDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimRemoteDelayedOutputTx] + val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[MainPenaltyTx] + val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[HtlcPenaltyTx] + val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimHtlcDelayedOutputPenaltyTx] + val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec) :: ("confirmationTarget" | defaultConfirmationTarget.upcast[ConfirmationTarget])).as[ClaimLocalAnchorOutputTx] + val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx] + val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx] val claimRemoteCommitMainOutputTxCodec: Codec[ClaimRemoteCommitMainOutputTx] = discriminated[ClaimRemoteCommitMainOutputTx].by(uint8) .typecase(0x01, claimP2WPKHOutputTxCodec) @@ -150,7 +149,7 @@ private[channel] object ChannelCodecs2 { ("remoteSig" | lengthDelimited(bytes64))).as[HtlcTxAndSigs] val publishableTxsCodec: Codec[PublishableTxs] = ( - ("commitTx" | (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx]) :: + ("commitTx" | (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[CommitTx]) :: ("htlcTxsAndSigs" | listOfN(uint16, htlcTxAndSigsCodec))).as[PublishableTxs] val localCommitCodec: Codec[ChannelTypes0.LocalCommit] = ( @@ -230,6 +229,7 @@ private[channel] object ChannelCodecs2 { ("originChannels" | originsMapCodec) :: ("remoteNextCommitInfo" | either(bool8, waitingForRevocationCodec, publicKey)) :: ("commitInput" | inputInfoCodec) :: + ("redeemInfo" | redeemInfoCodec) :: ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) :: ("channelId" | bytes32) }).as[ChannelTypes0.Commitments].decodeOnly.map[Commitments](_.migrate()).decodeOnly 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 e9aa69650e..f25a8b8d90 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 @@ -113,10 +113,9 @@ private[channel] object ChannelCodecs3 { val txCodec: Codec[Transaction] = lengthDelimited(bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d))) - val inputInfoCodec: Codec[InputInfo] = ( - ("outPoint" | outPointCodec) :: - ("txOut" | txOutCodec) :: - ("redeemScript" | lengthDelimited(bytes))).as[InputInfo.SegwitInput].upcast[InputInfo].decodeOnly + val inputInfoCodec: Codec[InputInfo] = (("outPoint" | outPointCodec) :: ("txOut" | txOutCodec)).as[InputInfo].decodeOnly + + val redeemInfoCodec: Codec[RedeemInfo.SegwitV0] = ("redeemScript" | lengthDelimited(bytes)).as[RedeemInfo.SegwitV0].decodeOnly val outputInfoCodec: Codec[OutputInfo] = ( ("index" | uint32) :: @@ -126,27 +125,27 @@ private[channel] object ChannelCodecs3 { private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0))) private val blockHeightConfirmationTarget: Codec[ConfirmationTarget.Absolute] = blockHeight.map(ConfirmationTarget.Absolute).decodeOnly - val commitTxCodec: Codec[CommitTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx] - val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcSuccessTx] - val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcTimeoutTx] - private val htlcSuccessTxNoConfirmCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx] - private val htlcTimeoutTxNoConfirmCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcTimeoutTx] - val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcDelayedTx] - private val legacyClaimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx] - val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcSuccessTx] - val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcTimeoutTx] - private val claimHtlcSuccessTxNoConfirmCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcSuccessTx] - private val claimHtlcTimeoutTxNoConfirmCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx] - val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx] - val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx] - val claimRemoteDelayedOutputTxCodec: Codec[ClaimRemoteDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteDelayedOutputTx] - val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx] - val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcPenaltyTx] - val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimHtlcDelayedOutputPenaltyTx] - val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | blockHeightConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx] - private val claimLocalAnchorOutputTxNoConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | defaultConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx] - val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx] - val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx] + val commitTxCodec: Codec[CommitTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[CommitTx] + val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcSuccessTx] + val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcTimeoutTx] + private val htlcSuccessTxNoConfirmCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx] + private val htlcTimeoutTxNoConfirmCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcTimeoutTx] + val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec)).as[HtlcDelayedTx] + private val legacyClaimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx] + val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcSuccessTx] + val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcTimeoutTx] + private val claimHtlcSuccessTxNoConfirmCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcSuccessTx] + private val claimHtlcTimeoutTxNoConfirmCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx] + val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx] + val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx] + val claimRemoteDelayedOutputTxCodec: Codec[ClaimRemoteDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimRemoteDelayedOutputTx] + val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[MainPenaltyTx] + val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[HtlcPenaltyTx] + val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimHtlcDelayedOutputPenaltyTx] + val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec) :: ("confirmationTarget" | blockHeightConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx] + private val claimLocalAnchorOutputTxNoConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec) :: ("confirmationTarget" | defaultConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx] + val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx] + val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx] val txWithInputInfoCodec: Codec[TransactionWithInputInfo] = discriminated[TransactionWithInputInfo].by(uint16) // Important: order matters! @@ -285,6 +284,7 @@ private[channel] object ChannelCodecs3 { ("originChannels" | originsMapCodec) :: ("remoteNextCommitInfo" | either(bool8, waitingForRevocationCodec, publicKey)) :: ("commitInput" | inputInfoCodec.map(_ => ()).decodeOnly) :: + ("redeemInfo" | redeemInfoCodec.map(_ => ()).decodeOnly) :: ("fundingTxStatus" | provide(SingleFundedUnconfirmedFundingTx(None)).upcast[LocalFundingStatus]) :: ("remoteFundingTxStatus" | provide(RemoteFundingStatus.Locked).upcast[RemoteFundingStatus]) :: ("remotePerCommitmentSecrets" | byteAligned(ShaChain.shaChainCodec)) 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 2169e640b8..c5571e92a7 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 @@ -110,10 +110,8 @@ private[channel] object ChannelCodecs4 { val txCodec: Codec[Transaction] = lengthDelimited(bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d))) // all v4-encoded channels use segwit inputs, support for taproot inputs will be added later in v5 codecs - val inputInfoCodec: Codec[InputInfo] = ( - ("outPoint" | outPointCodec) :: - ("txOut" | txOutCodec) :: - ("redeemScript" | lengthDelimited(bytes))).as[InputInfo.SegwitInput].upcast[InputInfo] + val inputInfoCodec: Codec[InputInfo] = (("outPoint" | outPointCodec) :: ("txOut" | txOutCodec)).as[InputInfo] + val redeemInfoCodec: Codec[RedeemInfo.SegwitV0] = ("redeemScript" | lengthDelimited(bytes)).as[RedeemInfo.SegwitV0] val outputInfoCodec: Codec[OutputInfo] = ( ("index" | uint32) :: @@ -131,28 +129,28 @@ private[channel] object ChannelCodecs4 { .typecase(0x00, blockHeightConfirmationTarget) .typecase(0x01, priorityConfirmationTarget) - val commitTxCodec: Codec[CommitTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[CommitTx] - val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcSuccessTx] - val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcTimeoutTx] - private val htlcSuccessTxNoConfirmCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx] - private val htlcTimeoutTxNoConfirmCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcTimeoutTx] - val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcDelayedTx] - private val legacyClaimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx] - val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcSuccessTx] - val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcTimeoutTx] - private val claimHtlcSuccessTxNoConfirmCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcSuccessTx] - private val claimHtlcTimeoutTxNoConfirmCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx] - val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx] - val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx] - val claimRemoteDelayedOutputTxCodec: Codec[ClaimRemoteDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteDelayedOutputTx] - val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx] - val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcPenaltyTx] - val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimHtlcDelayedOutputPenaltyTx] - val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | confirmationTarget)).as[ClaimLocalAnchorOutputTx] - private val claimLocalAnchorOutputTxBlockHeightConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | blockHeightConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx] - private val claimLocalAnchorOutputTxNoConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | defaultConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx] - private val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx] - val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx] + val commitTxCodec: Codec[CommitTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[CommitTx] + val htlcSuccessTxCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcSuccessTx] + val htlcTimeoutTxCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[HtlcTimeoutTx] + private val htlcSuccessTxNoConfirmCodec: Codec[HtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcSuccessTx] + private val htlcTimeoutTxNoConfirmCodec: Codec[HtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[HtlcTimeoutTx] + val htlcDelayedTxCodec: Codec[HtlcDelayedTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec)).as[HtlcDelayedTx] + private val legacyClaimHtlcSuccessTxCodec: Codec[LegacyClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[LegacyClaimHtlcSuccessTx] + val claimHtlcSuccessTxCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcSuccessTx] + val claimHtlcTimeoutTxCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | blockHeightConfirmationTarget)).as[ClaimHtlcTimeoutTx] + private val claimHtlcSuccessTxNoConfirmCodec: Codec[ClaimHtlcSuccessTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("paymentHash" | bytes32) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcSuccessTx] + private val claimHtlcTimeoutTxNoConfirmCodec: Codec[ClaimHtlcTimeoutTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec) :: ("htlcId" | uint64overflow) :: ("confirmationTarget" | defaultConfirmationTarget)).as[ClaimHtlcTimeoutTx] + val claimLocalDelayedOutputTxCodec: Codec[ClaimLocalDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimLocalDelayedOutputTx] + val claimP2WPKHOutputTxCodec: Codec[ClaimP2WPKHOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimP2WPKHOutputTx] + val claimRemoteDelayedOutputTxCodec: Codec[ClaimRemoteDelayedOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootScriptPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimRemoteDelayedOutputTx] + val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[MainPenaltyTx] + val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[HtlcPenaltyTx] + val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimHtlcDelayedOutputPenaltyTx] + val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec) :: ("confirmationTarget" | confirmationTarget)).as[ClaimLocalAnchorOutputTx] + private val claimLocalAnchorOutputTxBlockHeightConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec) :: ("confirmationTarget" | blockHeightConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx] + private val claimLocalAnchorOutputTxNoConfirmCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec) :: ("confirmationTarget" | defaultConfirmationTarget).upcast[ConfirmationTarget]).as[ClaimLocalAnchorOutputTx] + private val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx] + val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx] val claimRemoteCommitMainOutputTxCodec: Codec[ClaimRemoteCommitMainOutputTx] = discriminated[ClaimRemoteCommitMainOutputTx].by(uint8) .typecase(0x01, claimP2WPKHOutputTxCodec) @@ -246,6 +244,7 @@ private[channel] object ChannelCodecs4 { private val multisig2of2InputCodec: Codec[InteractiveTxBuilder.Multisig2of2Input] = ( ("info" | inputInfoCodec) :: + ("redeemInfo" | redeemInfoCodec).upcast[RedeemInfo.TaprootKeyPathOrSegwitV0] :: ("fundingTxIndex" | uint32) :: ("remoteFundingPubkey" | publicKey)).as[InteractiveTxBuilder.Multisig2of2Input] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelDataSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelDataSpec.scala index 5fd196fe17..3b77d61afe 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelDataSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/ChannelDataSpec.scala @@ -600,8 +600,8 @@ class ChannelDataSpec extends TestKitBaseClass with AnyFunSuiteLike with Channel case (current, tx) => Closing.updateRevokedCommitPublished(current, tx) }.copy( claimHtlcDelayedPenaltyTxs = List( - ClaimHtlcDelayedOutputPenaltyTx(InputInfo(OutPoint(htlcSuccess, 0), TxOut(2_500 sat, Nil), Nil), Transaction(2, Seq(TxIn(OutPoint(htlcSuccess, 0), ByteVector.empty, 0)), Seq(TxOut(5_000 sat, ByteVector.empty)), 0)), - ClaimHtlcDelayedOutputPenaltyTx(InputInfo(OutPoint(htlcTimeout, 0), TxOut(3_000 sat, Nil), Nil), Transaction(2, Seq(TxIn(OutPoint(htlcTimeout, 0), ByteVector.empty, 0)), Seq(TxOut(6_000 sat, ByteVector.empty)), 0)) + ClaimHtlcDelayedOutputPenaltyTx(InputInfo(OutPoint(htlcSuccess, 0), TxOut(2_500 sat, Nil)), RedeemInfo.SegwitV0(Nil), Transaction(2, Seq(TxIn(OutPoint(htlcSuccess, 0), ByteVector.empty, 0)), Seq(TxOut(5_000 sat, ByteVector.empty)), 0)), + ClaimHtlcDelayedOutputPenaltyTx(InputInfo(OutPoint(htlcTimeout, 0), TxOut(3_000 sat, Nil)), RedeemInfo.SegwitV0(Nil), Transaction(2, Seq(TxIn(OutPoint(htlcTimeout, 0), ByteVector.empty, 0)), Seq(TxOut(6_000 sat, ByteVector.empty)), 0)) ) ) assert(!rvk4b.isDone) 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 4448fb9993..d796fcf472 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 @@ -25,7 +25,7 @@ import fr.acinq.eclair.channel.Helpers.Funding import fr.acinq.eclair.channel.states.ChannelStateTestsBase import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.crypto.keymanager.LocalChannelKeyManager -import fr.acinq.eclair.transactions.Transactions.CommitTx +import fr.acinq.eclair.transactions.Transactions.{CommitTx, DefaultCommitmentFormat, RedeemInfo} import fr.acinq.eclair.transactions.{CommitmentSpec, Scripts, Transactions} import fr.acinq.eclair.wire.protocol._ import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -486,14 +486,16 @@ class CommitmentsSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with object CommitmentsSpec { def makeCommitments(toLocal: MilliSatoshi, toRemote: MilliSatoshi, feeRatePerKw: FeeratePerKw = FeeratePerKw(0 sat), dustLimit: Satoshi = 0 sat, isOpener: Boolean = true, announcement_opt: Option[ChannelAnnouncement] = None): Commitments = { + val commitmentFormat = DefaultCommitmentFormat 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, isOpener, isOpener, 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, Features.empty, None) val localFundingPubKey = randomKey().publicKey val remoteFundingPubKey = randomKey().publicKey - val fundingTx = Transaction(2, Nil, Seq(TxOut((toLocal + toRemote).truncateToSatoshi, Funding.makeFundingPubKeyScript(localFundingPubKey, remoteFundingPubKey))), 0) - val commitmentInput = Transactions.InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head, Scripts.multiSig2of2(localFundingPubKey, remoteFundingPubKey)) - val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, feeRatePerKw, toLocal, toRemote), CommitTxAndRemoteSig(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), ByteVector64.Zeroes), Nil) + val fundingTx = Transaction(2, Nil, Seq(TxOut((toLocal + toRemote).truncateToSatoshi, Funding.makeFundingPubKeyScript(localFundingPubKey, remoteFundingPubKey, commitmentFormat))), 0) + val commitmentInput = Transactions.InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head) + val redeemInfo = RedeemInfo.SegwitV0(Scripts.multiSig2of2(localFundingPubKey, remoteFundingPubKey)) + val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, feeRatePerKw, toLocal, toRemote), CommitTxAndRemoteSig(CommitTx(commitmentInput, redeemInfo, Transaction(2, Nil, Nil, 0)), ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(Set.empty, feeRatePerKw, toRemote, toLocal), randomTxId(), randomKey().publicKey) val localFundingStatus = announcement_opt match { case Some(ann) => LocalFundingStatus.ConfirmedFundingTx(fundingTx, ann.shortChannelId, None, None) @@ -511,14 +513,16 @@ object CommitmentsSpec { } def makeCommitments(toLocal: MilliSatoshi, toRemote: MilliSatoshi, localNodeId: PublicKey, remoteNodeId: PublicKey, announcement_opt: Option[ChannelAnnouncement]): Commitments = { + val commitmentFormat = DefaultCommitmentFormat 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, isChannelOpener = true, paysCommitTxFees = 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, Features.empty, None) val localFundingPubKey = randomKey().publicKey val remoteFundingPubKey = randomKey().publicKey - val fundingTx = Transaction(2, Nil, Seq(TxOut((toLocal + toRemote).truncateToSatoshi, Funding.makeFundingPubKeyScript(localFundingPubKey, remoteFundingPubKey))), 0) - val commitmentInput = Transactions.InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head, Scripts.multiSig2of2(localFundingPubKey, 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 fundingTx = Transaction(2, Nil, Seq(TxOut((toLocal + toRemote).truncateToSatoshi, Funding.makeFundingPubKeyScript(localFundingPubKey, remoteFundingPubKey, commitmentFormat))), 0) + val commitmentInput = Transactions.InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head) + val redeemInfo = RedeemInfo.SegwitV0(Scripts.multiSig2of2(localFundingPubKey, remoteFundingPubKey)) + val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(0 sat), toLocal, toRemote), CommitTxAndRemoteSig(CommitTx(commitmentInput, redeemInfo, Transaction(2, Nil, Nil, 0)), ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(0 sat), toRemote, toLocal), randomTxId(), randomKey().publicKey) val localFundingStatus = announcement_opt match { case Some(ann) => LocalFundingStatus.ConfirmedFundingTx(fundingTx, ann.shortChannelId, None, None) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala index e511ba7030..7d46dd884b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/HelpersSpec.scala @@ -178,7 +178,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat ) def toClosingTx(txOut: Seq[TxOut]): ClosingTx = { - ClosingTx(InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(1000 sat, Nil), Nil), Transaction(2, Nil, txOut, 0), None) + ClosingTx(InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(1000 sat, Nil)), RedeemInfo.SegwitV0(Nil), Transaction(2, Nil, txOut, 0), None) } assert(Closing.MutualClose.checkClosingDustAmounts(toClosingTx(allOutputsAboveDust))) @@ -191,6 +191,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat test("tell closing type") { val commitments = CommitmentsSpec.makeCommitments(10000 msat, 15000 msat) + val dummyRedeemInfo = RedeemInfo.SegwitV0(Nil) val tx1 :: tx2 :: tx3 :: tx4 :: tx5 :: tx6 :: Nil = List( Transaction.read("010000000110f01d4a4228ef959681feb1465c2010d0135be88fd598135b2e09d5413bf6f1000000006a473044022074658623424cebdac8290488b76f893cfb17765b7a3805e773e6770b7b17200102202892cfa9dda662d5eac394ba36fcfd1ea6c0b8bb3230ab96220731967bbdb90101210372d437866d9e4ead3d362b01b615d24cc0d5152c740d51e3c55fb53f6d335d82ffffffff01408b0700000000001976a914678db9a7caa2aca887af1177eda6f3d0f702df0d88ac00000000"), Transaction.read("0100000001be43e9788523ed4de0b24a007a90009bc25e667ddac0e9ee83049be03e220138000000006b483045022100f74dd6ad3e6a00201d266a0ed860a6379c6e68b473970423f3fc8a15caa1ea0f022065b4852c9da230d9e036df743cb743601ca5229e1cb610efdd99769513f2a2260121020636de7755830fb4a3f136e97ecc6c58941611957ba0364f01beae164b945b2fffffffff0150f80c000000000017a9146809053148799a10480eada3d56d15edf4a648c88700000000"), @@ -198,7 +199,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat Transaction.read("0200000001c8a8934fb38a44b969528252bc37be66ee166c7897c57384d1e561449e110c93010000006b483045022100dc6c50f445ed53d2fb41067fdcb25686fe79492d90e6e5db43235726ace247210220773d35228af0800c257970bee9cf75175d75217de09a8ecd83521befd040c4ca012102082b751372fe7e3b012534afe0bb8d1f2f09c724b1a10a813ce704e5b9c217ccfdffffff0247ba2300000000001976a914f97a7641228e6b17d4b0b08252ae75bd62a95fe788ace3de24000000000017a914a9fefd4b9a9282a1d7a17d2f14ac7d1eb88141d287f7d50800"), Transaction.read("010000000235a2f5c4fd48672534cce1ac063047edc38683f43c5a883f815d6026cb5f8321020000006a47304402206be5fd61b1702599acf51941560f0a1e1965aa086634b004967747f79788bd6e022002f7f719a45b8b5e89129c40a9d15e4a8ee1e33be3a891cf32e859823ecb7a510121024756c5adfbc0827478b0db042ce09d9b98e21ad80d036e73bd8e7f0ecbc254a2ffffffffb2387d3125bb8c84a2da83f4192385ce329283661dfc70191f4112c67ce7b4d0000000006b483045022100a2c737eab1c039f79238767ccb9bb3e81160e965ef0fc2ea79e8360c61b7c9f702202348b0f2c0ea2a757e25d375d9be183200ce0a79ec81d6a4ebb2ae4dc31bc3c9012102db16a822e2ec3706c58fc880c08a3617c61d8ef706cc8830cfe4561d9a5d52f0ffffffff01808d5b00000000001976a9141210c32def6b64d0d77ba8d99adeb7e9f91158b988ac00000000"), Transaction.read("0100000001b14ba6952c83f6f8c382befbf4e44270f13e479d5a5ff3862ac3a112f103ff2a010000006b4830450221008b097fd69bfa3715fc5e119a891933c091c55eabd3d1ddae63a1c2cc36dc9a3e02205666d5299fa403a393bcbbf4b05f9c0984480384796cdebcf69171674d00809c01210335b592484a59a44f40998d65a94f9e2eecca47e8d1799342112a59fc96252830ffffffff024bf308000000000017a914440668d018e5e0ba550d6e042abcf726694f515c8798dd1801000000001976a91453a503fe151dd32e0503bd9a2fbdbf4f9a3af1da88ac00000000") - ).map(tx => ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(10_000 sat, Nil), Nil), tx, None)) + ).map(tx => ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(10_000 sat, Nil)), dummyRedeemInfo, tx, None)) // only mutual close assert(Closing.isClosingTypeAlreadyKnown( @@ -225,7 +226,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat mutualClosePublished = tx1 :: Nil, localCommitPublished = Some(LocalCommitPublished( commitTx = tx2.tx, - claimMainDelayedOutputTx = Some(ClaimLocalDelayedOutputTx(tx3.input, tx3.tx)), + claimMainDelayedOutputTx = Some(ClaimLocalDelayedOutputTx(tx3.input, dummyRedeemInfo, tx3.tx)), htlcTxs = Map.empty, claimHtlcDelayedTxs = Nil, claimAnchorTxs = Nil, @@ -247,7 +248,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat mutualClosePublished = tx1 :: Nil, localCommitPublished = Some(LocalCommitPublished( commitTx = tx2.tx, - claimMainDelayedOutputTx = Some(ClaimLocalDelayedOutputTx(tx3.input, tx3.tx)), + claimMainDelayedOutputTx = Some(ClaimLocalDelayedOutputTx(tx3.input, dummyRedeemInfo, tx3.tx)), htlcTxs = Map.empty, claimHtlcDelayedTxs = Nil, claimAnchorTxs = Nil, @@ -342,7 +343,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat )), nextRemoteCommitPublished = Some(RemoteCommitPublished( commitTx = tx4.tx, - claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx5.input, tx5.tx)), + claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx5.input, dummyRedeemInfo, tx5.tx)), claimHtlcTxs = Map.empty, claimAnchorTxs = Nil, irrevocablySpent = Map(tx4.input.outPoint -> tx4.tx) @@ -364,7 +365,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat nextRemoteCommitPublished = None, futureRemoteCommitPublished = Some(RemoteCommitPublished( commitTx = tx4.tx, - claimMainOutputTx = Some(ClaimRemoteDelayedOutputTx(tx5.input, tx5.tx)), + claimMainOutputTx = Some(ClaimRemoteDelayedOutputTx(tx5.input, dummyRedeemInfo, tx5.tx)), claimHtlcTxs = Map.empty, claimAnchorTxs = Nil, irrevocablySpent = Map.empty @@ -385,7 +386,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat nextRemoteCommitPublished = None, futureRemoteCommitPublished = Some(RemoteCommitPublished( commitTx = tx4.tx, - claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx5.input, tx5.tx)), + claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx5.input, dummyRedeemInfo, tx5.tx)), claimHtlcTxs = Map.empty, claimAnchorTxs = Nil, irrevocablySpent = Map(tx4.input.outPoint -> tx4.tx) @@ -415,7 +416,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat revokedCommitPublished = RevokedCommitPublished( commitTx = tx2.tx, - claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx3.input, tx3.tx)), + claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx3.input, dummyRedeemInfo, tx3.tx)), mainPenaltyTx = None, htlcPenaltyTxs = Nil, claimHtlcDelayedPenaltyTxs = Nil, @@ -423,7 +424,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat ) :: RevokedCommitPublished( commitTx = tx4.tx, - claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx5.input, tx5.tx)), + claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx5.input, dummyRedeemInfo, tx5.tx)), mainPenaltyTx = None, htlcPenaltyTxs = Nil, claimHtlcDelayedPenaltyTxs = Nil, @@ -462,7 +463,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat revokedCommitPublished = RevokedCommitPublished( commitTx = tx2.tx, - claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx3.input, tx3.tx)), + claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx3.input, dummyRedeemInfo, tx3.tx)), mainPenaltyTx = None, htlcPenaltyTxs = Nil, claimHtlcDelayedPenaltyTxs = Nil, @@ -470,7 +471,7 @@ class HelpersSpec extends TestKitBaseClass with AnyFunSuiteLike with ChannelStat ) :: RevokedCommitPublished( commitTx = tx4.tx, - claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx5.input, tx5.tx)), + claimMainOutputTx = Some(ClaimP2WPKHOutputTx(tx5.input, dummyRedeemInfo, tx5.tx)), mainPenaltyTx = None, htlcPenaltyTxs = Nil, claimHtlcDelayedPenaltyTxs = Nil, 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 446d5a0a51..7442e29ad3 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 @@ -34,7 +34,7 @@ import fr.acinq.eclair.blockchain.{OnChainWallet, SingleKeyOnChainWallet} import fr.acinq.eclair.channel.fund.InteractiveTxBuilder._ import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} import fr.acinq.eclair.io.OpenChannelInterceptor.makeChannelParams -import fr.acinq.eclair.transactions.Transactions.InputInfo +import fr.acinq.eclair.transactions.Transactions.{InputInfo, RedeemInfo} import fr.acinq.eclair.transactions.{Scripts, Transactions} import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{Feature, FeatureSupport, Features, InitFeature, MilliSatoshiLong, NodeParams, TestConstants, TestKitBaseClass, ToMilliSatoshiConversion, UInt64, randomBytes32, randomKey} @@ -106,12 +106,12 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit val fundingPubkeyScript: ByteVector = Script.write(Script.pay2wsh(Scripts.multiSig2of2(fundingParamsB.remoteFundingPubKey, fundingParamsA.remoteFundingPubKey))) def dummySharedInputB(amount: Satoshi): SharedFundingInput = { - val inputInfo = InputInfo(OutPoint(randomTxId(), 3), TxOut(amount, fundingPubkeyScript), Nil) + val inputInfo = InputInfo(OutPoint(randomTxId(), 3), TxOut(amount, fundingPubkeyScript)) val fundingTxIndex = fundingParamsA.sharedInput_opt match { case Some(input: Multisig2of2Input) => input.fundingTxIndex + 1 case _ => 0 } - Multisig2of2Input(inputInfo, fundingTxIndex, fundingParamsA.remoteFundingPubKey) + Multisig2of2Input(inputInfo, RedeemInfo.SegwitV0(Nil), fundingTxIndex, fundingParamsA.remoteFundingPubKey) } 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 = { @@ -2598,7 +2598,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, 0, randomKey().publicKey))) + val fundingParams = params.fundingParamsB.copy(sharedInput_opt = Some(Multisig2of2Input(previousCommitment.commitInput, previousCommitment.redeemInfo, 0, randomKey().publicKey))) val bob = params.spawnTxBuilderSpliceBob(fundingParams, previousCommitment, wallet) bob ! Start(probe.ref) // Alice --- tx_add_input --> Bob @@ -2614,7 +2614,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), 0, randomKey().publicKey) + val sharedInput = Multisig2of2Input(InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head), RedeemInfo.SegwitV0(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 diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunderSpec.scala index 40d369925e..4d34895dac 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunderSpec.scala @@ -39,19 +39,20 @@ class ReplaceableTxFunderSpec extends TestKitBaseClass with AnyFunSuiteLike { private def createAnchorTx(): (CommitTx, ClaimLocalAnchorOutputTx) = { val anchorScript = Scripts.anchor(PlaceHolderPubKey) - val commitInput = Funding.makeFundingInputInfo(randomTxId(), 1, 500 sat, PlaceHolderPubKey, PlaceHolderPubKey) + val (commitInput, redeemInfo) = Funding.makeFundingInputInfo(randomTxId(), 1, 500 sat, PlaceHolderPubKey, PlaceHolderPubKey, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val commitTx = Transaction( 2, - Seq(TxIn(commitInput.outPoint, commitInput.redeemScript, 0, Scripts.witness2of2(PlaceHolderSig, PlaceHolderSig, PlaceHolderPubKey, PlaceHolderPubKey))), + Seq(TxIn(commitInput.outPoint, redeemInfo.asInstanceOf[RedeemInfo.SegwitV0].redeemScript, 0, Scripts.witness2of2(PlaceHolderSig, PlaceHolderSig, PlaceHolderPubKey, PlaceHolderPubKey))), Seq(TxOut(330 sat, Script.pay2wsh(anchorScript))), 0 ) val anchorTx = ClaimLocalAnchorOutputTx( - InputInfo(OutPoint(commitTx, 0), commitTx.txOut.head, anchorScript), + InputInfo(OutPoint(commitTx, 0), commitTx.txOut.head), + RedeemInfo.SegwitV0(anchorScript), Transaction(2, Seq(TxIn(OutPoint(commitTx, 0), ByteVector.empty, 0)), Nil, 0), ConfirmationTarget.Absolute(BlockHeight(0)) ) - (CommitTx(commitInput, commitTx), anchorTx) + (CommitTx(commitInput, redeemInfo, commitTx), anchorTx) } private def createHtlcTxs(): (Transaction, HtlcSuccessWithWitnessData, HtlcTimeoutWithWitnessData) = { @@ -66,14 +67,16 @@ class ReplaceableTxFunderSpec extends TestKitBaseClass with AnyFunSuiteLike { 0 ) val htlcSuccess = HtlcSuccessWithWitnessData(HtlcSuccessTx( - InputInfo(OutPoint(commitTx, 0), commitTx.txOut.head, htlcSuccessScript), + InputInfo(OutPoint(commitTx, 0), commitTx.txOut.head), + RedeemInfo.SegwitV0(htlcSuccessScript), Transaction(2, Seq(TxIn(OutPoint(commitTx, 0), ByteVector.empty, 0)), Seq(TxOut(5000 sat, Script.pay2wpkh(PlaceHolderPubKey))), 0), paymentHash, 17, ConfirmationTarget.Absolute(BlockHeight(0)) ), PlaceHolderSig, preimage) val htlcTimeout = HtlcTimeoutWithWitnessData(HtlcTimeoutTx( - InputInfo(OutPoint(commitTx, 1), commitTx.txOut.last, htlcTimeoutScript), + InputInfo(OutPoint(commitTx, 1), commitTx.txOut.last), + RedeemInfo.SegwitV0(htlcTimeoutScript), Transaction(2, Seq(TxIn(OutPoint(commitTx, 1), ByteVector.empty, 0)), Seq(TxOut(4000 sat, Script.pay2wpkh(PlaceHolderPubKey))), 0), 12, ConfirmationTarget.Absolute(BlockHeight(0)) @@ -93,14 +96,16 @@ class ReplaceableTxFunderSpec extends TestKitBaseClass with AnyFunSuiteLike { 0 ) val claimHtlcSuccess = ClaimHtlcSuccessWithWitnessData(ClaimHtlcSuccessTx( - InputInfo(OutPoint(commitTx, 0), commitTx.txOut.head, htlcSuccessScript), + InputInfo(OutPoint(commitTx, 0), commitTx.txOut.head), + RedeemInfo.SegwitV0(htlcSuccessScript), Transaction(2, Seq(TxIn(OutPoint(commitTx, 0), ByteVector.empty, 0)), Seq(TxOut(5000 sat, Script.pay2wpkh(PlaceHolderPubKey))), 0), paymentHash, 5, ConfirmationTarget.Absolute(BlockHeight(0)) ), preimage) val claimHtlcTimeout = ClaimHtlcTimeoutWithWitnessData(ClaimHtlcTimeoutTx( - InputInfo(OutPoint(commitTx, 1), commitTx.txOut.last, htlcTimeoutScript), + InputInfo(OutPoint(commitTx, 1), commitTx.txOut.last), + RedeemInfo.SegwitV0(htlcTimeoutScript), Transaction(2, Seq(TxIn(OutPoint(commitTx, 1), ByteVector.empty, 0)), Seq(TxOut(5000 sat, Script.pay2wpkh(PlaceHolderPubKey))), 0), 7, ConfirmationTarget.Absolute(BlockHeight(0)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala index 1f0647aa4d..69d65c8f0a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/TxPublisherSpec.scala @@ -27,7 +27,7 @@ import fr.acinq.eclair.blockchain.fee.{ConfirmationPriority, ConfirmationTarget} import fr.acinq.eclair.channel.publish import fr.acinq.eclair.channel.publish.TxPublisher.TxRejectedReason._ import fr.acinq.eclair.channel.publish.TxPublisher._ -import fr.acinq.eclair.transactions.Transactions.{ClaimLocalAnchorOutputTx, HtlcSuccessTx, InputInfo} +import fr.acinq.eclair.transactions.Transactions.{ClaimLocalAnchorOutputTx, HtlcSuccessTx, InputInfo, RedeemInfo} import fr.acinq.eclair.{BlockHeight, NodeParams, TestConstants, TestKitBaseClass, randomBytes32, randomKey} import org.scalatest.Outcome import org.scalatest.funsuite.FixtureAnyFunSuiteLike @@ -105,7 +105,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val confirmBefore = ConfirmationTarget.Absolute(nodeParams.currentBlockHeight + 12) val input = OutPoint(randomTxId(), 3) - val cmd = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), confirmBefore), null, null) + val cmd = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil)), RedeemInfo.SegwitV0(Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), confirmBefore), null, null) txPublisher ! cmd val child = factory.expectMsgType[ReplaceableTxPublisherSpawned].actor val p = child.expectMsgType[ReplaceableTxPublisher.Publish] @@ -117,7 +117,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val confirmBefore = nodeParams.currentBlockHeight + 12 val input = OutPoint(randomTxId(), 3) - val anchorTx = ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), ConfirmationTarget.Priority(ConfirmationPriority.Medium)) + val anchorTx = ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil)), RedeemInfo.SegwitV0(Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), ConfirmationTarget.Priority(ConfirmationPriority.Medium)) val cmd = PublishReplaceableTx(anchorTx, null, null) txPublisher ! cmd val child = factory.expectMsgType[ReplaceableTxPublisherSpawned].actor @@ -175,7 +175,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val attempt2 = factory.expectMsgType[FinalTxPublisherSpawned].actor attempt2.expectMsgType[FinalTxPublisher.Publish] - val cmd3 = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)), null, null) + val cmd3 = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil)), RedeemInfo.SegwitV0(Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)), null, null) txPublisher ! cmd3 val attempt3 = factory.expectMsgType[ReplaceableTxPublisherSpawned].actor attempt3.expectMsgType[ReplaceableTxPublisher.Publish] @@ -197,7 +197,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val attempt1 = factory.expectMsgType[FinalTxPublisherSpawned] attempt1.actor.expectMsgType[FinalTxPublisher.Publish] - val cmd2 = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)), null, null) + val cmd2 = PublishReplaceableTx(ClaimLocalAnchorOutputTx(InputInfo(input, TxOut(25_000 sat, Nil)), RedeemInfo.SegwitV0(Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, TxOut(20_000 sat, Nil) :: Nil, 0), ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)), null, null) txPublisher ! cmd2 val attempt2 = factory.expectMsgType[ReplaceableTxPublisherSpawned] attempt2.actor.expectMsgType[ReplaceableTxPublisher.Publish] @@ -237,7 +237,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val target = nodeParams.currentBlockHeight + 12 val input = OutPoint(randomTxId(), 7) val paymentHash = randomBytes32() - val cmd = PublishReplaceableTx(HtlcSuccessTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, ConfirmationTarget.Absolute(target)), null, null) + val cmd = PublishReplaceableTx(HtlcSuccessTx(InputInfo(input, TxOut(25_000 sat, Nil)), RedeemInfo.SegwitV0(Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, ConfirmationTarget.Absolute(target)), null, null) txPublisher ! cmd val attempt1 = factory.expectMsgType[ReplaceableTxPublisherSpawned] attempt1.actor.expectMsgType[ReplaceableTxPublisher.Publish] @@ -301,7 +301,7 @@ class TxPublisherSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { val input = OutPoint(randomTxId(), 7) val paymentHash = randomBytes32() - val cmd = PublishReplaceableTx(HtlcSuccessTx(InputInfo(input, TxOut(25_000 sat, Nil), Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)), null, null) + val cmd = PublishReplaceableTx(HtlcSuccessTx(InputInfo(input, TxOut(25_000 sat, Nil)), RedeemInfo.SegwitV0(Nil), Transaction(2, TxIn(input, Nil, 0) :: Nil, Nil, 0), paymentHash, 3, ConfirmationTarget.Absolute(nodeParams.currentBlockHeight)), null, null) txPublisher ! cmd val attempt1 = factory.expectMsgType[ReplaceableTxPublisherSpawned] attempt1.actor.expectMsgType[ReplaceableTxPublisher.Publish] 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 4533175d59..556b16d1ec 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 @@ -2883,7 +2883,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // condition between his HTLC-success and Alice's HTLC-timeout val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] val initialCommitTx = initialState.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - val HtlcSuccessTx(_, htlcSuccessTx, _, _, _) = initialState.commitments.latest.localCommit.htlcTxsAndRemoteSigs.head.htlcTx + val HtlcSuccessTx(_, _, htlcSuccessTx, _, _, _) = initialState.commitments.latest.localCommit.htlcTxsAndRemoteSigs.head.htlcTx bob ! CMD_FULFILL_HTLC(htlc.id, r, commit = true) bob2alice.expectMsgType[UpdateFulfillHtlc] @@ -2917,7 +2917,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // condition between his HTLC-success and Alice's HTLC-timeout val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] val initialCommitTx = initialState.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - val HtlcSuccessTx(_, htlcSuccessTx, _, _, _) = initialState.commitments.latest.localCommit.htlcTxsAndRemoteSigs.head.htlcTx + val HtlcSuccessTx(_, _, htlcSuccessTx, _, _, _) = initialState.commitments.latest.localCommit.htlcTxsAndRemoteSigs.head.htlcTx bob ! CMD_FULFILL_HTLC(htlc.id, r, commit = false) bob2alice.expectMsgType[UpdateFulfillHtlc] @@ -2951,7 +2951,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with // condition between his HTLC-success and Alice's HTLC-timeout val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] val initialCommitTx = initialState.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - val HtlcSuccessTx(_, htlcSuccessTx, _, _, _) = initialState.commitments.latest.localCommit.htlcTxsAndRemoteSigs.head.htlcTx + val HtlcSuccessTx(_, _, htlcSuccessTx, _, _, _) = initialState.commitments.latest.localCommit.htlcTxsAndRemoteSigs.head.htlcTx bob ! CMD_FULFILL_HTLC(htlc.id, r, commit = true) bob2alice.expectMsgType[UpdateFulfillHtlc] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala index 7766558f80..6c49b51c60 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/OfflineStateSpec.scala @@ -612,7 +612,7 @@ class OfflineStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val initialState = bob.stateData.asInstanceOf[DATA_NORMAL] val initialCommitTx = initialState.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx - val HtlcSuccessTx(_, htlcSuccessTx, _, _, _) = initialState.commitments.latest.localCommit.htlcTxsAndRemoteSigs.head.htlcTx + val HtlcSuccessTx(_, _, htlcSuccessTx, _, _, _) = initialState.commitments.latest.localCommit.htlcTxsAndRemoteSigs.head.htlcTx disconnect(alice, bob) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala index a8abd39b02..7ed994eed5 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala @@ -34,7 +34,7 @@ import fr.acinq.eclair.io.OpenChannelInterceptor.{DefaultParams, OpenChannelInit import fr.acinq.eclair.io.Peer.{OpenChannelResponse, OutgoingMessage, SpawnChannelInitiator, SpawnChannelNonInitiator} import fr.acinq.eclair.io.PeerSpec.{createOpenChannelMessage, createOpenDualFundedChannelMessage} import fr.acinq.eclair.io.PendingChannelsRateLimiter.AddOrRejectChannel -import fr.acinq.eclair.transactions.Transactions.{ClosingTx, InputInfo} +import fr.acinq.eclair.transactions.Transactions.{ClosingTx, InputInfo, RedeemInfo} import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec import fr.acinq.eclair.wire.protocol.{ChannelReestablish, ChannelTlv, Error, IPAddress, LiquidityAds, NodeAddress, OpenChannel, OpenChannelTlv, Shutdown, TlvStream} import fr.acinq.eclair.{AcceptOpenChannel, BlockHeight, CltvExpiryDelta, FeatureSupport, Features, InitFeature, InterceptOpenChannelCommand, InterceptOpenChannelPlugin, InterceptOpenChannelReceived, MilliSatoshiLong, RejectOpenChannel, TestConstants, UnknownFeature, randomBytes32, randomKey} @@ -139,7 +139,7 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory val currentChannels = Seq( Peer.ChannelInfo(TestProbe().ref, SHUTDOWN, DATA_SHUTDOWN(commitments(isOpener = true), Shutdown(randomBytes32(), ByteVector.empty), Shutdown(randomBytes32(), ByteVector.empty), CloseStatus.Initiator(None))), Peer.ChannelInfo(TestProbe().ref, NEGOTIATING, DATA_NEGOTIATING(commitments(), Shutdown(randomBytes32(), ByteVector.empty), Shutdown(randomBytes32(), ByteVector.empty), List(Nil), None)), - Peer.ChannelInfo(TestProbe().ref, CLOSING, DATA_CLOSING(commitments(), BlockHeight(0), ByteVector.empty, Nil, ClosingTx(InputInfo(OutPoint(TxId(randomBytes32()), 5), TxOut(100_000 sat, Nil), Nil), Transaction(2, Nil, Nil, 0), None) :: Nil)), + Peer.ChannelInfo(TestProbe().ref, CLOSING, DATA_CLOSING(commitments(), BlockHeight(0), ByteVector.empty, Nil, ClosingTx(InputInfo(OutPoint(TxId(randomBytes32()), 5), TxOut(100_000 sat, Nil)), RedeemInfo.SegwitV0(Nil), Transaction(2, Nil, Nil, 0), None) :: Nil)), Peer.ChannelInfo(TestProbe().ref, WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT, DATA_WAIT_FOR_REMOTE_PUBLISH_FUTURE_COMMITMENT(commitments(), ChannelReestablish(randomBytes32(), 0, 0, randomKey(), randomKey().publicKey))), ) peer.expectMessageType[Peer.GetPeerChannels].replyTo ! Peer.PeerChannels(remoteNodeId, currentChannels) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/PendingChannelsRateLimiterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/PendingChannelsRateLimiterSpec.scala index 6dd156453f..9e387fe17d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/io/PendingChannelsRateLimiterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/PendingChannelsRateLimiterSpec.scala @@ -25,7 +25,7 @@ import fr.acinq.eclair.channel._ import fr.acinq.eclair.io.PendingChannelsRateLimiter.filterPendingChannels import fr.acinq.eclair.router.Router.{GetNode, PublicNode, UnknownNode} import fr.acinq.eclair.router.{Announcements, Router} -import fr.acinq.eclair.transactions.Transactions.{ClosingTx, InputInfo} +import fr.acinq.eclair.transactions.Transactions.{ClosingTx, InputInfo, RedeemInfo} import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.{BlockHeight, Features, MilliSatoshiLong, NodeParams, RealShortChannelId, ShortChannelId, TestConstants, TimestampSecondLong, randomBytes32, randomBytes64, randomKey} import org.scalatest.Outcome @@ -72,7 +72,7 @@ class PendingChannelsRateLimiterSpec extends ScalaTestWithActorTestKit(ConfigFac val probe = TestProbe[PendingChannelsRateLimiter.Response]() val nodeParams = TestConstants.Alice.nodeParams.copy(channelConf = TestConstants.Alice.nodeParams.channelConf.copy(maxPendingChannelsPerPeer = maxPendingChannelsPerPeer, maxTotalPendingChannelsPrivateNodes = maxTotalPendingChannelsPrivateNodes, channelOpenerWhitelist = Set(peerOnWhitelistAtLimit))) val tx = Transaction.read("010000000110f01d4a4228ef959681feb1465c2010d0135be88fd598135b2e09d5413bf6f1000000006a473044022074658623424cebdac8290488b76f893cfb17765b7a3805e773e6770b7b17200102202892cfa9dda662d5eac394ba36fcfd1ea6c0b8bb3230ab96220731967bbdb90101210372d437866d9e4ead3d362b01b615d24cc0d5152c740d51e3c55fb53f6d335d82ffffffff01408b0700000000001976a914678db9a7caa2aca887af1177eda6f3d0f702df0d88ac00000000") - val closingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(10_000 sat, Nil), Nil), tx, None) + val closingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(10_000 sat, Nil)), RedeemInfo.SegwitV0(Nil), tx, None) val channelsOnWhitelistAtLimit: Seq[PersistentChannelData] = Seq( DATA_WAIT_FOR_FUNDING_CONFIRMED(commitments(peerOnWhitelistAtLimit, randomBytes32()), BlockHeight(0), None, Left(FundingCreated(randomBytes32(), TxId(ByteVector32.Zeroes), 3, randomBytes64()))), DATA_WAIT_FOR_CHANNEL_READY(commitments(peerOnWhitelistAtLimit, randomBytes32()), ShortIdAliases(ShortChannelId.generateLocalAlias(), None)), diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala index ac539bc313..14a7315917 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala @@ -123,8 +123,8 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat val dummyBytes32 = ByteVector32(hex"0202020202020202020202020202020202020202020202020202020202020202") val localParams = LocalParams(dummyPublicKey, DeterministicWallet.KeyPath(Seq(42L)), 546 sat, Long.MaxValue.msat, Some(1000 sat), 1 msat, CltvExpiryDelta(144), 50, isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) val remoteParams = RemoteParams(dummyPublicKey, 546 sat, UInt64.MaxValue, Some(1000 sat), 1 msat, CltvExpiryDelta(144), 50, dummyPublicKey, dummyPublicKey, dummyPublicKey, dummyPublicKey, Features.empty, None) - val commitmentInput = Funding.makeFundingInputInfo(TxId(dummyBytes32), 0, 150_000 sat, dummyPublicKey, dummyPublicKey) - val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(2500 sat), 100_000_000 msat, 50_000_000 msat), CommitTxAndRemoteSig(CommitTx(commitmentInput, Transaction(2, Nil, Nil, 0)), ByteVector64.Zeroes), Nil) + val (commitmentInput, redeemInfo) = Funding.makeFundingInputInfo(TxId(dummyBytes32), 0, 150_000 sat, dummyPublicKey, dummyPublicKey, DefaultCommitmentFormat) + val localCommit = LocalCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(2500 sat), 100_000_000 msat, 50_000_000 msat), CommitTxAndRemoteSig(CommitTx(commitmentInput, redeemInfo, Transaction(2, Nil, Nil, 0)), ByteVector64.Zeroes), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(Set.empty, FeeratePerKw(2500 sat), 50_000_000 msat, 100_000_000 msat), TxId(dummyBytes32), dummyPublicKey) val channelInfo = RES_GET_CHANNEL_INFO( PublicKey(hex"0270685ca81a8e4d4d01beec5781f4cc924684072ae52c507f8ebe9daf0caaab7b"), @@ -288,7 +288,6 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat val inputInfo = InputInfo( outPoint = OutPoint(TxHash.fromValidHex("345b2b05ec046ffe0c14d3b61838c79980713ad1cf8ae7a45c172ce90c9c0b9f"), 42), txOut = TxOut(456651 sat, hex"3c7a66997c681a3de1bae56438abeee4fc50a16554725a430ade1dc8db6bdd76704d45c6151c4051d710cf487e63"), - redeemScript = hex"00dc6c50f445ed53d2fb41067fdcb25686fe79492d90e6e5db43235726ace247210220773" ) JsonSerializers.serialization.write(inputInfo)(JsonSerializers.formats) shouldBe """{"outPoint":"9f0b9c0ce92c175ca4e78acfd13a718099c73818b6d3140cfe6f04ec052b5b34:42","amountSatoshis":456651}""" } @@ -383,10 +382,11 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat test("TransactionWithInputInfo serializer") { // the input info is ignored when serializing to JSON - val dummyInputInfo = InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(Satoshi(0), Nil), Nil) + val dummyInputInfo = InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(Satoshi(0), Nil)) + val dummyRedeemInfo = RedeemInfo.SegwitV0(Nil) val htlcSuccessTx = Transaction.read("0200000001c8a8934fb38a44b969528252bc37be66ee166c7897c57384d1e561449e110c93010000006b483045022100dc6c50f445ed53d2fb41067fdcb25686fe79492d90e6e5db43235726ace247210220773d35228af0800c257970bee9cf75175d75217de09a8ecd83521befd040c4ca012102082b751372fe7e3b012534afe0bb8d1f2f09c724b1a10a813ce704e5b9c217ccfdffffff0247ba2300000000001976a914f97a7641228e6b17d4b0b08252ae75bd62a95fe788ace3de24000000000017a914a9fefd4b9a9282a1d7a17d2f14ac7d1eb88141d287f7d50800") - val htlcSuccessTxInfo = HtlcSuccessTx(dummyInputInfo, htlcSuccessTx, ByteVector32.One, 3, ConfirmationTarget.Absolute(BlockHeight(1105))) + val htlcSuccessTxInfo = HtlcSuccessTx(dummyInputInfo, dummyRedeemInfo, htlcSuccessTx, ByteVector32.One, 3, ConfirmationTarget.Absolute(BlockHeight(1105))) val htlcSuccessJson = s"""{ | "txid": "${htlcSuccessTx.txid.value.toHex}", @@ -399,7 +399,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat assertJsonEquals(JsonSerializers.serialization.write(htlcSuccessTxInfo)(JsonSerializers.formats), htlcSuccessJson) val claimHtlcTimeoutTx = Transaction.read("010000000110f01d4a4228ef959681feb1465c2010d0135be88fd598135b2e09d5413bf6f1000000006a473044022074658623424cebdac8290488b76f893cfb17765b7a3805e773e6770b7b17200102202892cfa9dda662d5eac394ba36fcfd1ea6c0b8bb3230ab96220731967bbdb90101210372d437866d9e4ead3d362b01b615d24cc0d5152c740d51e3c55fb53f6d335d82ffffffff01408b0700000000001976a914678db9a7caa2aca887af1177eda6f3d0f702df0d88ac00000000") - val claimHtlcTimeoutTxInfo = ClaimHtlcTimeoutTx(dummyInputInfo, claimHtlcTimeoutTx, 2, ConfirmationTarget.Absolute(BlockHeight(144))) + val claimHtlcTimeoutTxInfo = ClaimHtlcTimeoutTx(dummyInputInfo, dummyRedeemInfo, claimHtlcTimeoutTx, 2, ConfirmationTarget.Absolute(BlockHeight(144))) val claimHtlcTimeoutJson = s"""{ | "txid": "${claimHtlcTimeoutTx.txid.value.toHex}", @@ -411,7 +411,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat assertJsonEquals(JsonSerializers.serialization.write(claimHtlcTimeoutTxInfo)(JsonSerializers.formats), claimHtlcTimeoutJson) val closingTx = Transaction.read("0100000001be43e9788523ed4de0b24a007a90009bc25e667ddac0e9ee83049be03e220138000000006b483045022100f74dd6ad3e6a00201d266a0ed860a6379c6e68b473970423f3fc8a15caa1ea0f022065b4852c9da230d9e036df743cb743601ca5229e1cb610efdd99769513f2a2260121020636de7755830fb4a3f136e97ecc6c58941611957ba0364f01beae164b945b2fffffffff0150f80c000000000017a9146809053148799a10480eada3d56d15edf4a648c88700000000") - val closingTxWithLocalOutput = ClosingTx(dummyInputInfo, closingTx, Some(OutputInfo(1, Satoshi(15000), hex"deadbeef"))) + val closingTxWithLocalOutput = ClosingTx(dummyInputInfo, dummyRedeemInfo, closingTx, Some(OutputInfo(1, Satoshi(15000), hex"deadbeef"))) val closingTxWithLocalOutputJson = s"""{ | "txid": "${closingTx.txid.value.toHex}", @@ -425,7 +425,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat """.stripMargin assertJsonEquals(JsonSerializers.serialization.write(closingTxWithLocalOutput)(JsonSerializers.formats), closingTxWithLocalOutputJson) - val closingTxWithoutLocalOutput = ClosingTx(dummyInputInfo, closingTx, None) + val closingTxWithoutLocalOutput = ClosingTx(dummyInputInfo, dummyRedeemInfo, closingTx, None) val closingTxWithoutLocalOutputJson = s"""{ | "txid": "${closingTx.txid.value.toHex}", 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 dc3cb76fee..15b03ae24d 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 @@ -32,7 +32,7 @@ import fr.acinq.eclair.router.BaseRouterSpec.{blindedRouteFromHops, channelHopFr import fr.acinq.eclair.router.BlindedRouteCreation import fr.acinq.eclair.router.Router.{NodeHop, Route} import fr.acinq.eclair.transactions.Transactions -import fr.acinq.eclair.transactions.Transactions.InputInfo +import fr.acinq.eclair.transactions.Transactions.{InputInfo, RedeemInfo} import fr.acinq.eclair.wire.protocol.OfferTypes.{InvoiceRequest, Offer, PaymentInfo} import fr.acinq.eclair.wire.protocol.PaymentOnion.{FinalPayload, IntermediatePayload, OutgoingBlindedPerHopPayload} import fr.acinq.eclair.wire.protocol._ @@ -714,8 +714,8 @@ object PaymentPacketSpec { val localParams = LocalParams(null, null, null, Long.MaxValue.msat, Some(channelReserve), null, null, 0, isChannelOpener = true, paysCommitTxFees = true, None, None, Features.empty) val remoteParams = RemoteParams(randomKey().publicKey, null, UInt64.MaxValue, Some(channelReserve), null, null, maxAcceptedHtlcs = 0, null, null, null, null, null, None) val fundingTx = Transaction(2, Nil, Seq(TxOut(testCapacity, Nil)), 0) - val commitInput = InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head, Nil) - val localCommit = LocalCommit(0, null, CommitTxAndRemoteSig(Transactions.CommitTx(commitInput, null), RemoteSignature.FullSignature(null)), Nil) + val commitInput = InputInfo(OutPoint(fundingTx, 0), fundingTx.txOut.head) + val localCommit = LocalCommit(0, null, CommitTxAndRemoteSig(Transactions.CommitTx(commitInput, RedeemInfo.SegwitV0(Nil), null), RemoteSignature.FullSignature(null)), Nil) val remoteCommit = RemoteCommit(0, null, null, randomKey().publicKey) val localChanges = LocalChanges(Nil, Nil, Nil) val remoteChanges = RemoteChanges(Nil, Nil, Nil) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala index 8133fafc34..235be3b2fd 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala @@ -34,7 +34,7 @@ import fr.acinq.eclair.payment.relay.{OnTheFlyFunding, PostRestartHtlcCleaner, R import fr.acinq.eclair.payment.send.SpontaneousRecipient import fr.acinq.eclair.router.BaseRouterSpec.channelHopFromUpdate import fr.acinq.eclair.router.Router.Route -import fr.acinq.eclair.transactions.Transactions.{ClaimRemoteDelayedOutputTx, InputInfo} +import fr.acinq.eclair.transactions.Transactions.{ClaimRemoteDelayedOutputTx, InputInfo, RedeemInfo} import fr.acinq.eclair.transactions.{DirectedHtlc, IncomingHtlc, OutgoingHtlc} import fr.acinq.eclair.wire.internal.channel.{ChannelCodecs, ChannelCodecsSpec} import fr.acinq.eclair.wire.protocol._ @@ -513,7 +513,7 @@ class PostRestartHtlcCleanerSpec extends TestKitBaseClass with FixtureAnyFunSuit // commit we accept it as such, so it simplifies the test. val revokedCommitTx = normal.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.copy(txOut = Seq(TxOut(4500 sat, Script.pay2wpkh(randomKey().publicKey)))) val dummyClaimMainTx = Transaction(2, Seq(TxIn(OutPoint(revokedCommitTx, 0), Nil, 0)), Seq(revokedCommitTx.txOut.head.copy(amount = 4000 sat)), 0) - val dummyClaimMain = ClaimRemoteDelayedOutputTx(InputInfo(OutPoint(revokedCommitTx, 0), revokedCommitTx.txOut.head, Nil), dummyClaimMainTx) + val dummyClaimMain = ClaimRemoteDelayedOutputTx(InputInfo(OutPoint(revokedCommitTx, 0), revokedCommitTx.txOut.head), RedeemInfo.SegwitV0(Nil), dummyClaimMainTx) val rcp = RevokedCommitPublished(revokedCommitTx, Some(dummyClaimMain), None, Nil, Nil, Map(revokedCommitTx.txIn.head.outPoint -> revokedCommitTx)) DATA_CLOSING(normal.commitments, BlockHeight(0), Script.write(Script.pay2wpkh(randomKey().publicKey)), mutualCloseProposed = Nil, revokedCommitPublished = List(rcp)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala index 81a7111cf4..02c7c5195f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala @@ -124,7 +124,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { val fundingAmount = fundingTx.txOut(0).amount logger.info(s"# funding-tx: $fundingTx}") - val commitmentInput = Funding.makeFundingInputInfo(fundingTx.txid, 0, fundingAmount, Local.funding_pubkey, Remote.funding_pubkey) + val (commitmentInput, redeemInfo) = Funding.makeFundingInputInfo(fundingTx.txid, 0, fundingAmount, Local.funding_pubkey, Remote.funding_pubkey, commitmentFormat) val obscured_tx_number = Transactions.obscuredCommitTxNumber(42, localIsChannelOpener = true, Local.payment_basepoint, Remote.payment_basepoint) assert(obscured_tx_number == (0x2bb038521914L ^ 42L)) @@ -140,8 +140,8 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { logger.info(s"remotekey: ${Remote.payment_privkey.publicKey}") logger.info(s"local_delayedkey: ${Local.delayed_payment_privkey.publicKey}") logger.info(s"local_revocation_key: ${Local.revocation_pubkey}") - logger.info(s"# funding wscript = ${commitmentInput.redeemScript}") - assert(commitmentInput.redeemScript == hex"5221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae") + logger.info(s"# funding wscript = $redeemInfo") + assert(redeemInfo == RedeemInfo.SegwitV0(hex"5221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae")) val paymentPreimages = Seq( ByteVector32(hex"0000000000000000000000000000000000000000000000000000000000000000"), @@ -204,6 +204,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { val commitTx = { val tx = Transactions.makeCommitTx( commitTxInput = commitmentInput, + redeemInfo = redeemInfo, commitTxNumber = Local.commitTxNumber, localPaymentBasePoint = Local.payment_basepoint, remotePaymentBasePoint = Remote.payment_basepoint, @@ -250,7 +251,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { case tx: HtlcSuccessTx => val localSig = tx.sign(Local.htlc_privkey, TxOwner.Local, commitmentFormat, Map.empty) val remoteSig = tx.sign(Remote.htlc_privkey, TxOwner.Remote, commitmentFormat, Map.empty) - val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.asInstanceOf[InputInfo.SegwitInput].redeemScript)) + val htlcIndex = htlcScripts.indexOf(Script.parse(tx.redeemInfo.asInstanceOf[RedeemInfo.SegwitV0].redeemScript)) val preimage = paymentPreimages.find(p => Crypto.sha256(p) == tx.paymentHash).get val tx1 = Transactions.addSigs(tx, localSig, remoteSig, preimage, commitmentFormat) Transaction.correctlySpends(tx1.tx, Seq(commitTx.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) @@ -262,7 +263,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging { case tx: HtlcTimeoutTx => val localSig = tx.sign(Local.htlc_privkey, TxOwner.Local, commitmentFormat, Map.empty) val remoteSig = tx.sign(Remote.htlc_privkey, TxOwner.Remote, commitmentFormat, Map.empty) - val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.asInstanceOf[InputInfo.SegwitInput].redeemScript)) + val htlcIndex = htlcScripts.indexOf(Script.parse(tx.redeemInfo.asInstanceOf[RedeemInfo.SegwitV0].redeemScript)) val tx1 = Transactions.addSigs(tx, localSig, remoteSig, commitmentFormat) Transaction.correctlySpends(tx1.tx, Seq(commitTx.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) logger.info(s"# signature for output #${tx.input.outPoint.index} (htlc-timeout for htlc #$htlcIndex)") diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala index b6ce30a205..066d1e9b83 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TransactionsSpec.scala @@ -52,7 +52,6 @@ class TransactionsSpec extends AnyFunSuite with Logging { val localHtlcPriv = PrivateKey(randomBytes32()) val remoteHtlcPriv = PrivateKey(randomBytes32()) val finalPubKeyScript = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32()).publicKey)) - val commitInput = Funding.makeFundingInputInfo(randomTxId(), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey) val toLocalDelay = CltvExpiryDelta(144) val localDustLimit = Satoshi(546) val feeratePerKw = FeeratePerKw(22000 sat) @@ -134,7 +133,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { // first we create a fake htlcSuccessOrTimeoutTx tx, containing only the output that will be spent by the 3rd-stage tx val pubKeyScript = write(pay2wsh(toLocalDelayed(localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey))) val htlcSuccessOrTimeoutTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(20000 sat, pubKeyScript) :: Nil, lockTime = 0) - val Right(htlcDelayedTx) = makeHtlcDelayedTx(htlcSuccessOrTimeoutTx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val Right(htlcDelayedTx) = makeHtlcDelayedTx(htlcSuccessOrTimeoutTx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, DefaultCommitmentFormat) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(htlcDelayedTx, PlaceHolderSig).tx) assert(htlcDelayedWeight == weight) @@ -145,7 +144,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { // first we create a fake commitTx tx, containing only the output that will be spent by the MainPenaltyTx val pubKeyScript = write(pay2wsh(toLocalDelayed(localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey))) val commitTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(20000 sat, pubKeyScript) :: Nil, lockTime = 0) - val Right(mainPenaltyTx) = makeMainPenaltyTx(commitTx, localDustLimit, localRevocationPriv.publicKey, finalPubKeyScript, toLocalDelay, localPaymentPriv.publicKey, feeratePerKw) + val Right(mainPenaltyTx) = makeMainPenaltyTx(commitTx, localDustLimit, localRevocationPriv.publicKey, finalPubKeyScript, toLocalDelay, localPaymentPriv.publicKey, feeratePerKw, DefaultCommitmentFormat) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(mainPenaltyTx, PlaceHolderSig).tx) assert(mainPenaltyWeight == weight) @@ -159,7 +158,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { val redeemScript = htlcReceived(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, ripemd160(htlc.paymentHash), htlc.cltvExpiry, DefaultCommitmentFormat) val pubKeyScript = write(pay2wsh(redeemScript)) val commitTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(htlc.amountMsat.truncateToSatoshi, pubKeyScript) :: Nil, lockTime = 0) - val Right(htlcPenaltyTx) = makeHtlcPenaltyTx(commitTx, 0, Script.write(redeemScript), localDustLimit, finalPubKeyScript, feeratePerKw) + val Right(htlcPenaltyTx) = makeHtlcPenaltyTx(commitTx, 0, RedeemInfo.SegwitV0(redeemScript), localDustLimit, finalPubKeyScript, feeratePerKw) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(htlcPenaltyTx, PlaceHolderSig, localRevocationPriv.publicKey).tx) assert(htlcPenaltyWeight == weight) @@ -174,7 +173,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { val outputs = makeCommitTxOutputs(localPaysCommitTxFees = true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, spec, DefaultCommitmentFormat) val pubKeyScript = write(pay2wsh(htlcOffered(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, ripemd160(htlc.paymentHash), DefaultCommitmentFormat))) val commitTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(htlc.amountMsat.truncateToSatoshi, pubKeyScript) :: Nil, lockTime = 0) - val Right(claimHtlcSuccessTx) = makeClaimHtlcSuccessTx(commitTx, outputs, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc, feeratePerKw, DefaultCommitmentFormat) + val Right(claimHtlcSuccessTx) = makeClaimHtlcSuccessTx(commitTx, outputs, localDustLimit, finalPubKeyScript, htlc, feeratePerKw, DefaultCommitmentFormat) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(claimHtlcSuccessTx, PlaceHolderSig, paymentPreimage).tx) assert(claimHtlcSuccessWeight == weight) @@ -189,7 +188,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { val outputs = makeCommitTxOutputs(localPaysCommitTxFees = true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, spec, DefaultCommitmentFormat) val pubKeyScript = write(pay2wsh(htlcReceived(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, ripemd160(htlc.paymentHash), htlc.cltvExpiry, DefaultCommitmentFormat))) val commitTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(htlc.amountMsat.truncateToSatoshi, pubKeyScript) :: Nil, lockTime = 0) - val Right(claimClaimHtlcTimeoutTx) = makeClaimHtlcTimeoutTx(commitTx, outputs, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc, feeratePerKw, DefaultCommitmentFormat) + val Right(claimClaimHtlcTimeoutTx) = makeClaimHtlcTimeoutTx(commitTx, outputs, localDustLimit, finalPubKeyScript, htlc, feeratePerKw, DefaultCommitmentFormat) // we use dummy signatures to compute the weight val weight = Transaction.weight(addSigs(claimClaimHtlcTimeoutTx, PlaceHolderSig).tx) assert(claimHtlcTimeoutWeight == weight) @@ -200,7 +199,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { // first we create a fake commitTx tx, containing only the output that will be spent by the ClaimAnchorOutputTx val pubKeyScript = write(pay2wsh(anchor(localFundingPriv.publicKey))) val commitTx = Transaction(version = 2, txIn = Nil, txOut = TxOut(anchorAmount, pubKeyScript) :: Nil, lockTime = 0) - val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx, localFundingPriv.publicKey, ConfirmationTarget.Absolute(BlockHeight(1105))) + val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx, localFundingPriv.publicKey, ConfirmationTarget.Absolute(BlockHeight(1105)), ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) assert(claimAnchorOutputTx.tx.txOut.isEmpty) assert(claimAnchorOutputTx.confirmationTarget == ConfirmationTarget.Absolute(BlockHeight(1105))) // we will always add at least one input and one output to be able to set our desired feerate @@ -228,25 +227,25 @@ class TransactionsSpec extends AnyFunSuite with Logging { { val toRemoteFundeeBelowDust = spec.copy(toRemote = belowDust) val outputs = makeCommitTxOutputs(localPaysCommitTxFees = true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, toRemoteFundeeBelowDust, DefaultCommitmentFormat) - assert(outputs.map(_.commitmentOutput) == Seq(CommitmentOutput.ToLocal)) + assert(outputs.forall(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToLocal])) assert(outputs.head.output.amount.toMilliSatoshi == toRemoteFundeeBelowDust.toLocal - commitFee) } { val toLocalFunderBelowDust = spec.copy(toLocal = belowDustWithFee) val outputs = makeCommitTxOutputs(localPaysCommitTxFees = true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, toLocalFunderBelowDust, DefaultCommitmentFormat) - assert(outputs.map(_.commitmentOutput) == Seq(CommitmentOutput.ToRemote)) + assert(outputs.forall(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToRemote])) assert(outputs.head.output.amount.toMilliSatoshi == toLocalFunderBelowDust.toRemote) } { val toRemoteFunderBelowDust = spec.copy(toRemote = belowDustWithFee) val outputs = makeCommitTxOutputs(localPaysCommitTxFees = false, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, toRemoteFunderBelowDust, DefaultCommitmentFormat) - assert(outputs.map(_.commitmentOutput) == Seq(CommitmentOutput.ToLocal)) + assert(outputs.forall(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToLocal])) assert(outputs.head.output.amount.toMilliSatoshi == toRemoteFunderBelowDust.toLocal) } { val toLocalFundeeBelowDust = spec.copy(toLocal = belowDust) val outputs = makeCommitTxOutputs(localPaysCommitTxFees = false, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, toLocalFundeeBelowDust, DefaultCommitmentFormat) - assert(outputs.map(_.commitmentOutput) == Seq(CommitmentOutput.ToRemote)) + assert(outputs.forall(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToRemote])) assert(outputs.head.output.amount.toMilliSatoshi == toLocalFundeeBelowDust.toRemote - commitFee) } { @@ -258,7 +257,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { test("generate valid commitment and htlc transactions (default commitment format)") { val finalPubKeyScript = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32()).publicKey)) - val commitInput = Funding.makeFundingInputInfo(randomTxId(), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey) + val (commitInput, redeemInfo) = Funding.makeFundingInputInfo(randomTxId(), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, DefaultCommitmentFormat) // htlc1 and htlc2 are regular IN/OUT htlcs val paymentPreimage1 = randomBytes32() @@ -290,7 +289,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { val commitTxNumber = 0x404142434445L val commitTx = { - val txInfo = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.publicKey, remotePaymentPriv.publicKey, localIsChannelOpener = true, outputs) + val txInfo = makeCommitTx(commitInput, redeemInfo, commitTxNumber, localPaymentPriv.publicKey, remotePaymentPriv.publicKey, localIsChannelOpener = true, outputs) val localSig = txInfo.sign(localPaymentPriv, TxOwner.Local, DefaultCommitmentFormat, Map.empty) val remoteSig = txInfo.sign(remotePaymentPriv, TxOwner.Remote, DefaultCommitmentFormat, Map.empty) Transactions.addSigs(txInfo, localFundingPriv.publicKey, remoteFundingPriv.publicKey, localSig, remoteSig) @@ -326,18 +325,18 @@ class TransactionsSpec extends AnyFunSuite with Logging { } { // local spends delayed output of htlc1 timeout tx - val Right(htlcDelayed) = makeHtlcDelayedTx(htlcTimeoutTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val Right(htlcDelayed) = makeHtlcDelayedTx(htlcTimeoutTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, DefaultCommitmentFormat) val localSig = htlcDelayed.sign(localDelayedPaymentPriv, TxOwner.Local, DefaultCommitmentFormat, Map.empty) val signedTx = addSigs(htlcDelayed, localSig) assert(checkSpendable(signedTx).isSuccess) // local can't claim delayed output of htlc3 timeout tx because it is below the dust limit - val htlcDelayed1 = makeHtlcDelayedTx(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val htlcDelayed1 = makeHtlcDelayedTx(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, DefaultCommitmentFormat) assert(htlcDelayed1 == Left(OutputNotFound)) } { // remote spends local->remote htlc1/htlc3 output directly in case of success for ((htlc, paymentPreimage) <- (htlc1, paymentPreimage1) :: (htlc3, paymentPreimage3) :: Nil) { - val Right(claimHtlcSuccessTx) = makeClaimHtlcSuccessTx(commitTx.tx, outputs, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc, feeratePerKw, DefaultCommitmentFormat) + val Right(claimHtlcSuccessTx) = makeClaimHtlcSuccessTx(commitTx.tx, outputs, localDustLimit, finalPubKeyScript, htlc, feeratePerKw, DefaultCommitmentFormat) val localSig = claimHtlcSuccessTx.sign(remoteHtlcPriv, TxOwner.Local, DefaultCommitmentFormat, Map.empty) val signed = addSigs(claimHtlcSuccessTx, localSig, paymentPreimage) assert(checkSpendable(signed).isSuccess) @@ -351,22 +350,22 @@ class TransactionsSpec extends AnyFunSuite with Logging { val signedTx = addSigs(htlcSuccessTx, localSig, remoteSig, paymentPreimage, DefaultCommitmentFormat) assert(checkSpendable(signedTx).isSuccess) // check remote sig - assert(htlcSuccessTx.checkSig(remoteSig, remoteHtlcPriv.publicKey, TxOwner.Remote, DefaultCommitmentFormat)) + assert(htlcSuccessTx.checkSig(remoteSig, remoteHtlcPriv.publicKey, TxOwner.Remote, DefaultCommitmentFormat, Map.empty)) } } { // local spends delayed output of htlc2 success tx - val Right(htlcDelayed) = makeHtlcDelayedTx(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val Right(htlcDelayed) = makeHtlcDelayedTx(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, DefaultCommitmentFormat) val localSig = htlcDelayed.sign(localDelayedPaymentPriv, TxOwner.Local, DefaultCommitmentFormat, Map.empty) val signedTx = addSigs(htlcDelayed, localSig) assert(checkSpendable(signedTx).isSuccess) // local can't claim delayed output of htlc4 success tx because it is below the dust limit - val htlcDelayed1 = makeHtlcDelayedTx(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val htlcDelayed1 = makeHtlcDelayedTx(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, DefaultCommitmentFormat) assert(htlcDelayed1 == Left(AmountBelowDustLimit)) } { // local spends main delayed output - val Right(claimMainOutputTx) = makeClaimLocalDelayedOutputTx(commitTx.tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val Right(claimMainOutputTx) = makeClaimLocalDelayedOutputTx(commitTx.tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, DefaultCommitmentFormat) val localSig = claimMainOutputTx.sign(localDelayedPaymentPriv, TxOwner.Local, DefaultCommitmentFormat, Map.empty) val signedTx = addSigs(claimMainOutputTx, localSig) assert(checkSpendable(signedTx).isSuccess) @@ -380,58 +379,58 @@ class TransactionsSpec extends AnyFunSuite with Logging { } { // remote spends remote->local htlc output directly in case of timeout - val Right(claimHtlcTimeoutTx) = makeClaimHtlcTimeoutTx(commitTx.tx, outputs, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc2, feeratePerKw, DefaultCommitmentFormat) + val Right(claimHtlcTimeoutTx) = makeClaimHtlcTimeoutTx(commitTx.tx, outputs, localDustLimit, finalPubKeyScript, htlc2, feeratePerKw, DefaultCommitmentFormat) val localSig = claimHtlcTimeoutTx.sign(remoteHtlcPriv, TxOwner.Local, DefaultCommitmentFormat, Map.empty) val signed = addSigs(claimHtlcTimeoutTx, localSig) assert(checkSpendable(signed).isSuccess) } { // remote spends local main delayed output with revocation key - val Right(mainPenaltyTx) = makeMainPenaltyTx(commitTx.tx, localDustLimit, localRevocationPriv.publicKey, finalPubKeyScript, toLocalDelay, localDelayedPaymentPriv.publicKey, feeratePerKw) + val Right(mainPenaltyTx) = makeMainPenaltyTx(commitTx.tx, localDustLimit, localRevocationPriv.publicKey, finalPubKeyScript, toLocalDelay, localDelayedPaymentPriv.publicKey, feeratePerKw, DefaultCommitmentFormat) val sig = mainPenaltyTx.sign(localRevocationPriv, TxOwner.Local, DefaultCommitmentFormat, Map.empty) val signed = addSigs(mainPenaltyTx, sig) assert(checkSpendable(signed).isSuccess) } { // remote spends htlc1's htlc-timeout tx with revocation key - val Seq(Right(claimHtlcDelayedPenaltyTx)) = makeClaimHtlcDelayedOutputPenaltyTxs(htlcTimeoutTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val Seq(Right(claimHtlcDelayedPenaltyTx)) = makeClaimHtlcDelayedOutputPenaltyTxs(htlcTimeoutTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, DefaultCommitmentFormat) val sig = claimHtlcDelayedPenaltyTx.sign(localRevocationPriv, TxOwner.Local, DefaultCommitmentFormat, Map.empty) val signed = addSigs(claimHtlcDelayedPenaltyTx, sig) assert(checkSpendable(signed).isSuccess) // remote can't claim revoked output of htlc3's htlc-timeout tx because it is below the dust limit - val claimHtlcDelayedPenaltyTx1 = makeClaimHtlcDelayedOutputPenaltyTxs(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val claimHtlcDelayedPenaltyTx1 = makeClaimHtlcDelayedOutputPenaltyTxs(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, DefaultCommitmentFormat) assert(claimHtlcDelayedPenaltyTx1 == Seq(Left(AmountBelowDustLimit))) } { // remote spends offered HTLC output with revocation key val script = Script.write(Scripts.htlcOffered(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, Crypto.ripemd160(htlc1.paymentHash), DefaultCommitmentFormat)) val Some(htlcOutputIndex) = outputs.zipWithIndex.find { - case (CommitmentOutputLink(_, _, OutHtlc(OutgoingHtlc(someHtlc))), _) => someHtlc.id == htlc1.id + case (CommitmentOutputLink(_, OutHtlc(_, OutgoingHtlc(someHtlc))), _) => someHtlc.id == htlc1.id case _ => false }.map(_._2) - val Right(htlcPenaltyTx) = makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, script, localDustLimit, finalPubKeyScript, feeratePerKw) + val Right(htlcPenaltyTx) = makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, RedeemInfo.SegwitV0(script), localDustLimit, finalPubKeyScript, feeratePerKw) val sig = htlcPenaltyTx.sign(localRevocationPriv, TxOwner.Local, DefaultCommitmentFormat, Map.empty) val signed = addSigs(htlcPenaltyTx, sig, localRevocationPriv.publicKey) assert(checkSpendable(signed).isSuccess) } { // remote spends htlc2's htlc-success tx with revocation key - val Seq(Right(claimHtlcDelayedPenaltyTx)) = makeClaimHtlcDelayedOutputPenaltyTxs(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val Seq(Right(claimHtlcDelayedPenaltyTx)) = makeClaimHtlcDelayedOutputPenaltyTxs(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, DefaultCommitmentFormat) val sig = claimHtlcDelayedPenaltyTx.sign(localRevocationPriv, TxOwner.Local, DefaultCommitmentFormat, Map.empty) val signed = addSigs(claimHtlcDelayedPenaltyTx, sig) assert(checkSpendable(signed).isSuccess) // remote can't claim revoked output of htlc4's htlc-success tx because it is below the dust limit - val claimHtlcDelayedPenaltyTx1 = makeClaimHtlcDelayedOutputPenaltyTxs(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val claimHtlcDelayedPenaltyTx1 = makeClaimHtlcDelayedOutputPenaltyTxs(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, DefaultCommitmentFormat) assert(claimHtlcDelayedPenaltyTx1 == Seq(Left(AmountBelowDustLimit))) } { // remote spends received HTLC output with revocation key val script = Script.write(Scripts.htlcReceived(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, Crypto.ripemd160(htlc2.paymentHash), htlc2.cltvExpiry, DefaultCommitmentFormat)) val Some(htlcOutputIndex) = outputs.zipWithIndex.find { - case (CommitmentOutputLink(_, _, InHtlc(IncomingHtlc(someHtlc))), _) => someHtlc.id == htlc2.id + case (CommitmentOutputLink(_, InHtlc(_, IncomingHtlc(someHtlc))), _) => someHtlc.id == htlc2.id case _ => false }.map(_._2) - val Right(htlcPenaltyTx) = makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, script, localDustLimit, finalPubKeyScript, feeratePerKw) + val Right(htlcPenaltyTx) = makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, RedeemInfo.SegwitV0(script), localDustLimit, finalPubKeyScript, feeratePerKw) val sig = htlcPenaltyTx.sign(localRevocationPriv, TxOwner.Local, DefaultCommitmentFormat, Map.empty) val signed = addSigs(htlcPenaltyTx, sig, localRevocationPriv.publicKey) assert(checkSpendable(signed).isSuccess) @@ -446,39 +445,39 @@ class TransactionsSpec extends AnyFunSuite with Logging { { val outputs = makeCommitTxOutputs(localPaysCommitTxFees = true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, spec, UnsafeLegacyAnchorOutputsCommitmentFormat) - assert(outputs.map(_.commitmentOutput).toSet == Set(CommitmentOutput.ToLocal, CommitmentOutput.ToRemote, CommitmentOutput.ToLocalAnchor, CommitmentOutput.ToRemoteAnchor)) - assert(outputs.find(_.commitmentOutput == CommitmentOutput.ToLocalAnchor).get.output.amount == anchorAmount) - assert(outputs.find(_.commitmentOutput == CommitmentOutput.ToRemoteAnchor).get.output.amount == anchorAmount) - assert(outputs.find(_.commitmentOutput == CommitmentOutput.ToLocal).get.output.amount.toMilliSatoshi == spec.toLocal - commitFeeAndAnchorCost) - assert(outputs.find(_.commitmentOutput == CommitmentOutput.ToRemote).get.output.amount.toMilliSatoshi == spec.toRemote) + assert(outputs.size == 4) + assert(outputs.find(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToLocalAnchor]).get.output.amount == anchorAmount) + assert(outputs.find(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToRemoteAnchor]).get.output.amount == anchorAmount) + assert(outputs.find(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToLocal]).get.output.amount.toMilliSatoshi == spec.toLocal - commitFeeAndAnchorCost) + assert(outputs.find(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToRemote]).get.output.amount.toMilliSatoshi == spec.toRemote) } { val toRemoteFundeeBelowDust = spec.copy(toRemote = belowDust) val outputs = makeCommitTxOutputs(localPaysCommitTxFees = true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, toRemoteFundeeBelowDust, UnsafeLegacyAnchorOutputsCommitmentFormat) - assert(outputs.map(_.commitmentOutput).toSet == Set(CommitmentOutput.ToLocal, CommitmentOutput.ToLocalAnchor)) - assert(outputs.find(_.commitmentOutput == CommitmentOutput.ToLocalAnchor).get.output.amount == anchorAmount) - assert(outputs.find(_.commitmentOutput == CommitmentOutput.ToLocal).get.output.amount.toMilliSatoshi == spec.toLocal - commitFeeAndAnchorCost) + assert(outputs.size == 2) + assert(outputs.find(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToLocalAnchor]).get.output.amount == anchorAmount) + assert(outputs.find(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToLocal]).get.output.amount.toMilliSatoshi == spec.toLocal - commitFeeAndAnchorCost) } { val toLocalFunderBelowDust = spec.copy(toLocal = belowDustWithFeeAndAnchors) val outputs = makeCommitTxOutputs(localPaysCommitTxFees = true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, toLocalFunderBelowDust, UnsafeLegacyAnchorOutputsCommitmentFormat) - assert(outputs.map(_.commitmentOutput).toSet == Set(CommitmentOutput.ToRemote, CommitmentOutput.ToRemoteAnchor)) - assert(outputs.find(_.commitmentOutput == CommitmentOutput.ToRemoteAnchor).get.output.amount == anchorAmount) - assert(outputs.find(_.commitmentOutput == CommitmentOutput.ToRemote).get.output.amount.toMilliSatoshi == spec.toRemote) + assert(outputs.size == 2) + assert(outputs.find(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToRemoteAnchor]).get.output.amount == anchorAmount) + assert(outputs.find(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToRemote]).get.output.amount.toMilliSatoshi == spec.toRemote) } { val toRemoteFunderBelowDust = spec.copy(toRemote = belowDustWithFeeAndAnchors) val outputs = makeCommitTxOutputs(localPaysCommitTxFees = false, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, toRemoteFunderBelowDust, UnsafeLegacyAnchorOutputsCommitmentFormat) - assert(outputs.map(_.commitmentOutput).toSet == Set(CommitmentOutput.ToLocal, CommitmentOutput.ToLocalAnchor)) - assert(outputs.find(_.commitmentOutput == CommitmentOutput.ToLocalAnchor).get.output.amount == anchorAmount) - assert(outputs.find(_.commitmentOutput == CommitmentOutput.ToLocal).get.output.amount.toMilliSatoshi == spec.toLocal) + assert(outputs.size == 2) + assert(outputs.find(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToLocalAnchor]).get.output.amount == anchorAmount) + assert(outputs.find(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToLocal]).get.output.amount.toMilliSatoshi == spec.toLocal) } { val toLocalFundeeBelowDust = spec.copy(toLocal = belowDust) val outputs = makeCommitTxOutputs(localPaysCommitTxFees = false, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, toLocalFundeeBelowDust, UnsafeLegacyAnchorOutputsCommitmentFormat) - assert(outputs.map(_.commitmentOutput).toSet == Set(CommitmentOutput.ToRemote, CommitmentOutput.ToRemoteAnchor)) - assert(outputs.find(_.commitmentOutput == CommitmentOutput.ToRemoteAnchor).get.output.amount == anchorAmount) - assert(outputs.find(_.commitmentOutput == CommitmentOutput.ToRemote).get.output.amount.toMilliSatoshi == spec.toRemote - commitFeeAndAnchorCost) + assert(outputs.size == 2) + assert(outputs.find(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToRemoteAnchor]).get.output.amount == anchorAmount) + assert(outputs.find(_.commitmentOutput.isInstanceOf[CommitmentOutput.ToRemote]).get.output.amount.toMilliSatoshi == spec.toRemote - commitFeeAndAnchorCost) } { val allBelowDust = spec.copy(toLocal = belowDust, toRemote = belowDust) @@ -491,7 +490,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { val walletPriv = randomKey() val walletPub = walletPriv.publicKey val finalPubKeyScript = Script.write(Script.pay2wpkh(walletPub)) - val commitInput = Funding.makeFundingInputInfo(randomTxId(), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey) + val (commitInput, redeemInfo) = Funding.makeFundingInputInfo(randomTxId(), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, UnsafeLegacyAnchorOutputsCommitmentFormat) // htlc1, htlc2a and htlc2b are regular IN/OUT htlcs val paymentPreimage1 = randomBytes32() @@ -529,7 +528,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { val (commitTx, commitTxOutputs, htlcTimeoutTxs, htlcSuccessTxs) = { val commitTxNumber = 0x404142434445L val outputs = makeCommitTxOutputs(localPaysCommitTxFees = true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, spec, UnsafeLegacyAnchorOutputsCommitmentFormat) - val txInfo = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.publicKey, remotePaymentPriv.publicKey, localIsChannelOpener = true, outputs) + val txInfo = makeCommitTx(commitInput, redeemInfo, commitTxNumber, localPaymentPriv.publicKey, remotePaymentPriv.publicKey, localIsChannelOpener = true, outputs) val localSig = txInfo.sign(localPaymentPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty) val remoteSig = txInfo.sign(remotePaymentPriv, TxOwner.Remote, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty) val commitTx = Transactions.addSigs(txInfo, localFundingPriv.publicKey, remoteFundingPriv.publicKey, localSig, remoteSig) @@ -546,7 +545,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { assert(htlcSuccessTxs.map(_.htlcId).toSet == Set(1, 2, 4)) val zeroFeeOutputs = makeCommitTxOutputs(localPaysCommitTxFees = true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, spec, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) - val zeroFeeCommitTx = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.publicKey, remotePaymentPriv.publicKey, localIsChannelOpener = true, zeroFeeOutputs) + val zeroFeeCommitTx = makeCommitTx(commitInput, redeemInfo, commitTxNumber, localPaymentPriv.publicKey, remotePaymentPriv.publicKey, localIsChannelOpener = true, zeroFeeOutputs) val zeroFeeHtlcTxs = makeHtlcTxs(zeroFeeCommitTx.tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, spec.htlcTxFeerate(ZeroFeeHtlcTxAnchorOutputsCommitmentFormat), zeroFeeOutputs, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) assert(zeroFeeHtlcTxs.length == 7) val zeroFeeConfirmationTargets = zeroFeeHtlcTxs.map(tx => tx.htlcId -> tx.confirmationTarget.confirmBefore.toLong).toMap @@ -565,7 +564,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { { // local spends main delayed output - val Right(claimMainOutputTx) = makeClaimLocalDelayedOutputTx(commitTx.tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val Right(claimMainOutputTx) = makeClaimLocalDelayedOutputTx(commitTx.tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) val localSig = claimMainOutputTx.sign(localDelayedPaymentPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty) val signedTx = addSigs(claimMainOutputTx, localSig) assert(checkSpendable(signedTx).isSuccess) @@ -577,7 +576,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { } { // remote spends main delayed output - val Right(claimRemoteDelayedOutputTx) = makeClaimRemoteDelayedOutputTx(commitTx.tx, localDustLimit, remotePaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val Right(claimRemoteDelayedOutputTx) = makeClaimRemoteDelayedOutputTx(commitTx.tx, localDustLimit, remotePaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) val localSig = claimRemoteDelayedOutputTx.sign(remotePaymentPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty) val signedTx = addSigs(claimRemoteDelayedOutputTx, localSig) assert(checkSpendable(signedTx).isSuccess) @@ -589,7 +588,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { OutPoint(randomTxId(), 3) -> TxOut(walletAmount, Script.pay2wpkh(walletPub)), OutPoint(randomTxId(), 0) -> TxOut(walletAmount, Script.pay2wpkh(walletPub)), ) - val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, localFundingPriv.publicKey, ConfirmationTarget.Absolute(BlockHeight(0))).map(anchorTx => { + val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, localFundingPriv.publicKey, ConfirmationTarget.Absolute(BlockHeight(0)), UnsafeLegacyAnchorOutputsCommitmentFormat).map(anchorTx => { val walletTxIn = walletInputs.map { case (outpoint, _) => TxIn(outpoint, ByteVector.empty, 0) } val unsignedTx = anchorTx.tx.copy(txIn = anchorTx.tx.txIn ++ walletTxIn) val sig1 = unsignedTx.signInput(1, Script.pay2pkh(walletPub), SIGHASH_ALL, walletAmount, SigVersion.SIGVERSION_WITNESS_V0, walletPriv) @@ -610,7 +609,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { } { // remote spends remote anchor - val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, remoteFundingPriv.publicKey, ConfirmationTarget.Absolute(BlockHeight(0))) + val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, remoteFundingPriv.publicKey, ConfirmationTarget.Absolute(BlockHeight(0)), UnsafeLegacyAnchorOutputsCommitmentFormat) assert(checkSpendable(claimAnchorOutputTx).isFailure) val localSig = claimAnchorOutputTx.sign(remoteFundingPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty) val signedTx = addSigs(claimAnchorOutputTx, localSig) @@ -618,7 +617,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { } { // remote spends local main delayed output with revocation key - val Right(mainPenaltyTx) = makeMainPenaltyTx(commitTx.tx, localDustLimit, localRevocationPriv.publicKey, finalPubKeyScript, toLocalDelay, localDelayedPaymentPriv.publicKey, feeratePerKw) + val Right(mainPenaltyTx) = makeMainPenaltyTx(commitTx.tx, localDustLimit, localRevocationPriv.publicKey, finalPubKeyScript, toLocalDelay, localDelayedPaymentPriv.publicKey, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) val sig = mainPenaltyTx.sign(localRevocationPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty) val signed = addSigs(mainPenaltyTx, sig) assert(checkSpendable(signed).isSuccess) @@ -641,12 +640,12 @@ class TransactionsSpec extends AnyFunSuite with Logging { } { // local spends delayed output of htlc1 timeout tx - val Right(htlcDelayed) = makeHtlcDelayedTx(htlcTimeoutTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val Right(htlcDelayed) = makeHtlcDelayedTx(htlcTimeoutTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) val localSig = htlcDelayed.sign(localDelayedPaymentPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty) val signedTx = addSigs(htlcDelayed, localSig) assert(checkSpendable(signedTx).isSuccess) // local can't claim delayed output of htlc3 timeout tx because it is below the dust limit - val htlcDelayed1 = makeHtlcDelayedTx(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val htlcDelayed1 = makeHtlcDelayedTx(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) assert(htlcDelayed1 == Left(OutputNotFound)) } { @@ -657,34 +656,34 @@ class TransactionsSpec extends AnyFunSuite with Logging { val signedTx = addSigs(htlcSuccessTx, localSig, remoteSig, paymentPreimage, UnsafeLegacyAnchorOutputsCommitmentFormat) assert(checkSpendable(signedTx).isSuccess) // check remote sig - assert(htlcSuccessTx.checkSig(remoteSig, remoteHtlcPriv.publicKey, TxOwner.Remote, UnsafeLegacyAnchorOutputsCommitmentFormat)) + assert(htlcSuccessTx.checkSig(remoteSig, remoteHtlcPriv.publicKey, TxOwner.Remote, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty)) // local detects when remote doesn't use the right sighash flags val invalidSighash = Seq(SIGHASH_ALL, SIGHASH_ALL | SIGHASH_ANYONECANPAY, SIGHASH_SINGLE, SIGHASH_NONE) for (sighash <- invalidSighash) { val invalidRemoteSig = htlcSuccessTx.sign(remoteHtlcPriv, sighash, Map.empty) val invalidTx = addSigs(htlcSuccessTx, localSig, invalidRemoteSig, paymentPreimage, UnsafeLegacyAnchorOutputsCommitmentFormat) assert(checkSpendable(invalidTx).isFailure) - assert(!invalidTx.checkSig(invalidRemoteSig, remoteHtlcPriv.publicKey, TxOwner.Remote, UnsafeLegacyAnchorOutputsCommitmentFormat)) + assert(!invalidTx.checkSig(invalidRemoteSig, remoteHtlcPriv.publicKey, TxOwner.Remote, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty)) } } } { // local spends delayed output of htlc2a and htlc2b success txs - val Right(htlcDelayedA) = makeHtlcDelayedTx(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) - val Right(htlcDelayedB) = makeHtlcDelayedTx(htlcSuccessTxs(2).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val Right(htlcDelayedA) = makeHtlcDelayedTx(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) + val Right(htlcDelayedB) = makeHtlcDelayedTx(htlcSuccessTxs(2).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) for (htlcDelayed <- Seq(htlcDelayedA, htlcDelayedB)) { val localSig = htlcDelayed.sign(localDelayedPaymentPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty) val signedTx = addSigs(htlcDelayed, localSig) assert(checkSpendable(signedTx).isSuccess) } // local can't claim delayed output of htlc4 success tx because it is below the dust limit - val claimHtlcDelayed1 = makeClaimLocalDelayedOutputTx(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val claimHtlcDelayed1 = makeClaimLocalDelayedOutputTx(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) assert(claimHtlcDelayed1 == Left(AmountBelowDustLimit)) } { // remote spends local->remote htlc outputs directly in case of success for ((htlc, paymentPreimage) <- (htlc1, paymentPreimage1) :: (htlc3, paymentPreimage3) :: Nil) { - val Right(claimHtlcSuccessTx) = makeClaimHtlcSuccessTx(commitTx.tx, commitTxOutputs, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) + val Right(claimHtlcSuccessTx) = makeClaimHtlcSuccessTx(commitTx.tx, commitTxOutputs, localDustLimit, finalPubKeyScript, htlc, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) val localSig = claimHtlcSuccessTx.sign(remoteHtlcPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty) val signed = addSigs(claimHtlcSuccessTx, localSig, paymentPreimage) assert(checkSpendable(signed).isSuccess) @@ -692,18 +691,18 @@ class TransactionsSpec extends AnyFunSuite with Logging { } { // remote spends htlc1's htlc-timeout tx with revocation key - val Seq(Right(claimHtlcDelayedPenaltyTx)) = makeClaimHtlcDelayedOutputPenaltyTxs(htlcTimeoutTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val Seq(Right(claimHtlcDelayedPenaltyTx)) = makeClaimHtlcDelayedOutputPenaltyTxs(htlcTimeoutTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) val sig = claimHtlcDelayedPenaltyTx.sign(localRevocationPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty) val signed = addSigs(claimHtlcDelayedPenaltyTx, sig) assert(checkSpendable(signed).isSuccess) // remote can't claim revoked output of htlc3's htlc-timeout tx because it is below the dust limit - val claimHtlcDelayedPenaltyTx1 = makeClaimHtlcDelayedOutputPenaltyTxs(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val claimHtlcDelayedPenaltyTx1 = makeClaimHtlcDelayedOutputPenaltyTxs(htlcTimeoutTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) assert(claimHtlcDelayedPenaltyTx1 == Seq(Left(AmountBelowDustLimit))) } { // remote spends remote->local htlc output directly in case of timeout for (htlc <- Seq(htlc2a, htlc2b)) { - val Right(claimHtlcTimeoutTx) = makeClaimHtlcTimeoutTx(commitTx.tx, commitTxOutputs, localDustLimit, remoteHtlcPriv.publicKey, localHtlcPriv.publicKey, localRevocationPriv.publicKey, finalPubKeyScript, htlc, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) + val Right(claimHtlcTimeoutTx) = makeClaimHtlcTimeoutTx(commitTx.tx, commitTxOutputs, localDustLimit, finalPubKeyScript, htlc, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) val localSig = claimHtlcTimeoutTx.sign(remoteHtlcPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty) val signed = addSigs(claimHtlcTimeoutTx, localSig) assert(checkSpendable(signed).isSuccess) @@ -711,15 +710,15 @@ class TransactionsSpec extends AnyFunSuite with Logging { } { // remote spends htlc2a/htlc2b's htlc-success tx with revocation key - val Seq(Right(claimHtlcDelayedPenaltyTxA)) = makeClaimHtlcDelayedOutputPenaltyTxs(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) - val Seq(Right(claimHtlcDelayedPenaltyTxB)) = makeClaimHtlcDelayedOutputPenaltyTxs(htlcSuccessTxs(2).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val Seq(Right(claimHtlcDelayedPenaltyTxA)) = makeClaimHtlcDelayedOutputPenaltyTxs(htlcSuccessTxs(1).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) + val Seq(Right(claimHtlcDelayedPenaltyTxB)) = makeClaimHtlcDelayedOutputPenaltyTxs(htlcSuccessTxs(2).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) for (claimHtlcSuccessPenaltyTx <- Seq(claimHtlcDelayedPenaltyTxA, claimHtlcDelayedPenaltyTxB)) { val sig = claimHtlcSuccessPenaltyTx.sign(localRevocationPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty) val signed = addSigs(claimHtlcSuccessPenaltyTx, sig) assert(checkSpendable(signed).isSuccess) } // remote can't claim revoked output of htlc4's htlc-success tx because it is below the dust limit - val claimHtlcDelayedPenaltyTx1 = makeClaimHtlcDelayedOutputPenaltyTxs(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val claimHtlcDelayedPenaltyTx1 = makeClaimHtlcDelayedOutputPenaltyTxs(htlcSuccessTxs(0).tx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) assert(claimHtlcDelayedPenaltyTx1 == Seq(Left(AmountBelowDustLimit))) } { @@ -727,7 +726,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { val txIn = htlcTimeoutTxs.flatMap(_.tx.txIn) ++ htlcSuccessTxs.flatMap(_.tx.txIn) val txOut = htlcTimeoutTxs.flatMap(_.tx.txOut) ++ htlcSuccessTxs.flatMap(_.tx.txOut) val aggregatedHtlcTx = Transaction(2, txIn, txOut, 0) - val claimHtlcDelayedPenaltyTxs = makeClaimHtlcDelayedOutputPenaltyTxs(aggregatedHtlcTx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw) + val claimHtlcDelayedPenaltyTxs = makeClaimHtlcDelayedOutputPenaltyTxs(aggregatedHtlcTx, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, finalPubKeyScript, feeratePerKw, UnsafeLegacyAnchorOutputsCommitmentFormat) assert(claimHtlcDelayedPenaltyTxs.size == 5) val skipped = claimHtlcDelayedPenaltyTxs.collect { case Left(reason) => reason } assert(skipped.size == 2) @@ -740,10 +739,10 @@ class TransactionsSpec extends AnyFunSuite with Logging { // remote spends offered htlc output with revocation key val script = Script.write(Scripts.htlcOffered(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, Crypto.ripemd160(htlc1.paymentHash), UnsafeLegacyAnchorOutputsCommitmentFormat)) val Some(htlcOutputIndex) = commitTxOutputs.zipWithIndex.find { - case (CommitmentOutputLink(_, _, OutHtlc(OutgoingHtlc(someHtlc))), _) => someHtlc.id == htlc1.id + case (CommitmentOutputLink(_, OutHtlc(_, OutgoingHtlc(someHtlc))), _) => someHtlc.id == htlc1.id case _ => false }.map(_._2) - val Right(htlcPenaltyTx) = makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, script, localDustLimit, finalPubKeyScript, feeratePerKw) + val Right(htlcPenaltyTx) = makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, RedeemInfo.SegwitV0(script), localDustLimit, finalPubKeyScript, feeratePerKw) val sig = htlcPenaltyTx.sign(localRevocationPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty) val signed = addSigs(htlcPenaltyTx, sig, localRevocationPriv.publicKey) assert(checkSpendable(signed).isSuccess) @@ -753,10 +752,10 @@ class TransactionsSpec extends AnyFunSuite with Logging { for (htlc <- Seq(htlc2a, htlc2b)) { val script = Script.write(Scripts.htlcReceived(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, Crypto.ripemd160(htlc.paymentHash), htlc.cltvExpiry, UnsafeLegacyAnchorOutputsCommitmentFormat)) val Some(htlcOutputIndex) = commitTxOutputs.zipWithIndex.find { - case (CommitmentOutputLink(_, _, InHtlc(IncomingHtlc(someHtlc))), _) => someHtlc.id == htlc.id + case (CommitmentOutputLink(_, InHtlc(_, IncomingHtlc(someHtlc))), _) => someHtlc.id == htlc.id case _ => false }.map(_._2) - val Right(htlcPenaltyTx) = makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, script, localDustLimit, finalPubKeyScript, feeratePerKw) + val Right(htlcPenaltyTx) = makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, RedeemInfo.SegwitV0(script), localDustLimit, finalPubKeyScript, feeratePerKw) val sig = htlcPenaltyTx.sign(localRevocationPriv, TxOwner.Local, UnsafeLegacyAnchorOutputsCommitmentFormat, Map.empty) val signed = addSigs(htlcPenaltyTx, sig, localRevocationPriv.publicKey) assert(checkSpendable(signed).isSuccess) @@ -1032,7 +1031,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { val remotePaymentPriv = PrivateKey(hex"a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6") val localHtlcPriv = PrivateKey(hex"a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7") val remoteHtlcPriv = PrivateKey(hex"a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8a8") - val commitInput = Funding.makeFundingInputInfo(TxId.fromValidHex("a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey) + val (commitInput, redeemInfo) = Funding.makeFundingInputInfo(TxId.fromValidHex("a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, DefaultCommitmentFormat) // htlc1 and htlc2 are two regular incoming HTLCs with different amounts. // htlc2 and htlc3 have the same amounts and should be sorted according to their scriptPubKey @@ -1061,7 +1060,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { val commitTxNumber = 0x404142434446L val (commitTx, outputs, htlcTxs) = { val outputs = makeCommitTxOutputs(localPaysCommitTxFees = true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, spec, DefaultCommitmentFormat) - val txInfo = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.publicKey, remotePaymentPriv.publicKey, localIsChannelOpener = true, outputs) + val txInfo = makeCommitTx(commitInput, redeemInfo, commitTxNumber, localPaymentPriv.publicKey, remotePaymentPriv.publicKey, localIsChannelOpener = true, outputs) val localSig = txInfo.sign(localPaymentPriv, TxOwner.Local, DefaultCommitmentFormat, Map.empty) val remoteSig = txInfo.sign(remotePaymentPriv, TxOwner.Remote, DefaultCommitmentFormat, Map.empty) val commitTx = Transactions.addSigs(txInfo, localFundingPriv.publicKey, remoteFundingPriv.publicKey, localSig, remoteSig) @@ -1085,21 +1084,21 @@ class TransactionsSpec extends AnyFunSuite with Logging { assert(htlcIds == Seq(1, 2, 3, 4, 5) || htlcIds == Seq(1, 2, 4, 3, 5)) assert(htlcOut2.publicKeyScript.toHex < htlcOut3.publicKeyScript.toHex) - assert(outputs.find(_.commitmentOutput == OutHtlc(OutgoingHtlc(htlc2))).map(_.output.publicKeyScript).contains(htlcOut2.publicKeyScript)) - assert(outputs.find(_.commitmentOutput == OutHtlc(OutgoingHtlc(htlc3))).map(_.output.publicKeyScript).contains(htlcOut3.publicKeyScript)) - assert(outputs.find(_.commitmentOutput == OutHtlc(OutgoingHtlc(htlc4))).map(_.output.publicKeyScript).contains(htlcOut4.publicKeyScript)) - assert(outputs.find(_.commitmentOutput == OutHtlc(OutgoingHtlc(htlc5))).map(_.output.publicKeyScript).contains(htlcOut5.publicKeyScript)) + assert(outputs.collectFirst { case CommitmentOutputLink(output, OutHtlc(_, OutgoingHtlc(htlc))) if htlc == htlc2 => output.publicKeyScript }.contains(htlcOut2.publicKeyScript)) + assert(outputs.collectFirst { case CommitmentOutputLink(output, OutHtlc(_, OutgoingHtlc(htlc))) if htlc == htlc3 => output.publicKeyScript }.contains(htlcOut3.publicKeyScript)) + assert(outputs.collectFirst { case CommitmentOutputLink(output, OutHtlc(_, OutgoingHtlc(htlc))) if htlc == htlc4 => output.publicKeyScript }.contains(htlcOut4.publicKeyScript)) + assert(outputs.collectFirst { case CommitmentOutputLink(output, OutHtlc(_, OutgoingHtlc(htlc))) if htlc == htlc5 => output.publicKeyScript }.contains(htlcOut5.publicKeyScript)) } test("find our output in closing tx") { - val commitInput = Funding.makeFundingInputInfo(randomTxId(), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey) + val (commitInput, redeemInfo) = Funding.makeFundingInputInfo(randomTxId(), 0, Btc(1), localFundingPriv.publicKey, remoteFundingPriv.publicKey, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val localPubKeyScript = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32()).publicKey)) val remotePubKeyScript = Script.write(Script.pay2wpkh(PrivateKey(randomBytes32()).publicKey)) { // Different amounts, both outputs untrimmed, local is funder: val spec = CommitmentSpec(Set.empty, feeratePerKw, 150_000_000 msat, 250_000_000 msat) - val closingTx = makeClosingTx(commitInput, localPubKeyScript, remotePubKeyScript, localPaysClosingFees = true, localDustLimit, 1000 sat, spec) + val closingTx = makeClosingTx(commitInput, redeemInfo, localPubKeyScript, remotePubKeyScript, localPaysClosingFees = true, localDustLimit, 1000 sat, spec) assert(closingTx.tx.txOut.length == 2) assert(closingTx.toLocalOutput !== None) val toLocal = closingTx.toLocalOutput.get @@ -1111,7 +1110,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { { // Different amounts, both outputs untrimmed, local is closer (option_simple_close): val spec = CommitmentSpec(Set.empty, feeratePerKw, 150_000_000 msat, 250_000_000 msat) - val closingTxs = makeSimpleClosingTxs(commitInput, spec, SimpleClosingTxFee.PaidByUs(5_000 sat), 0, localPubKeyScript, remotePubKeyScript) + val closingTxs = makeSimpleClosingTxs(commitInput, redeemInfo, spec, SimpleClosingTxFee.PaidByUs(5_000 sat), 0, localPubKeyScript, remotePubKeyScript) assert(closingTxs.localAndRemote_opt.nonEmpty) assert(closingTxs.localOnly_opt.nonEmpty) assert(closingTxs.remoteOnly_opt.isEmpty) @@ -1126,7 +1125,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { // Remote is using OP_RETURN (option_simple_close): we set their output amount to 0 sat. val spec = CommitmentSpec(Set.empty, feeratePerKw, 150_000_000 msat, 1_500_000 msat) val remotePubKeyScript = Script.write(OP_RETURN :: OP_PUSHDATA(hex"deadbeef") :: Nil) - val closingTxs = makeSimpleClosingTxs(commitInput, spec, SimpleClosingTxFee.PaidByUs(5_000 sat), 0, localPubKeyScript, remotePubKeyScript) + val closingTxs = makeSimpleClosingTxs(commitInput, redeemInfo, spec, SimpleClosingTxFee.PaidByUs(5_000 sat), 0, localPubKeyScript, remotePubKeyScript) assert(closingTxs.localAndRemote_opt.nonEmpty) assert(closingTxs.localOnly_opt.nonEmpty) assert(closingTxs.remoteOnly_opt.isEmpty) @@ -1144,7 +1143,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { // Remote is using OP_RETURN (option_simple_close) and paying the fees: we set their output amount to 0 sat. val spec = CommitmentSpec(Set.empty, feeratePerKw, 150_000_000 msat, 10_000_000 msat) val remotePubKeyScript = Script.write(OP_RETURN :: OP_PUSHDATA(hex"deadbeef") :: Nil) - val closingTxs = makeSimpleClosingTxs(commitInput, spec, SimpleClosingTxFee.PaidByThem(5_000 sat), 0, localPubKeyScript, remotePubKeyScript) + val closingTxs = makeSimpleClosingTxs(commitInput, redeemInfo, spec, SimpleClosingTxFee.PaidByThem(5_000 sat), 0, localPubKeyScript, remotePubKeyScript) assert(closingTxs.localAndRemote_opt.nonEmpty) assert(closingTxs.localOnly_opt.nonEmpty) assert(closingTxs.remoteOnly_opt.isEmpty) @@ -1161,7 +1160,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { { // Same amounts, both outputs untrimmed, local is fundee: val spec = CommitmentSpec(Set.empty, feeratePerKw, 150_000_000 msat, 150_000_000 msat) - val closingTx = makeClosingTx(commitInput, localPubKeyScript, remotePubKeyScript, localPaysClosingFees = false, localDustLimit, 1000 sat, spec) + val closingTx = makeClosingTx(commitInput, redeemInfo, localPubKeyScript, remotePubKeyScript, localPaysClosingFees = false, localDustLimit, 1000 sat, spec) assert(closingTx.tx.txOut.length == 2) assert(closingTx.toLocalOutput !== None) val toLocal = closingTx.toLocalOutput.get @@ -1173,7 +1172,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { { // Their output is trimmed: val spec = CommitmentSpec(Set.empty, feeratePerKw, 150_000_000 msat, 1_000 msat) - val closingTx = makeClosingTx(commitInput, localPubKeyScript, remotePubKeyScript, localPaysClosingFees = false, localDustLimit, 1000 sat, spec) + val closingTx = makeClosingTx(commitInput, redeemInfo, localPubKeyScript, remotePubKeyScript, localPaysClosingFees = false, localDustLimit, 1000 sat, spec) assert(closingTx.tx.txOut.length == 1) assert(closingTx.toLocalOutput !== None) val toLocal = closingTx.toLocalOutput.get @@ -1184,7 +1183,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { { // Their output is trimmed (option_simple_close): val spec = CommitmentSpec(Set.empty, feeratePerKw, 150_000_000 msat, 1_000_000 msat) - val closingTxs = makeSimpleClosingTxs(commitInput, spec, SimpleClosingTxFee.PaidByThem(800 sat), 0, localPubKeyScript, remotePubKeyScript) + val closingTxs = makeSimpleClosingTxs(commitInput, redeemInfo, spec, SimpleClosingTxFee.PaidByThem(800 sat), 0, localPubKeyScript, remotePubKeyScript) assert(closingTxs.all.size == 1) assert(closingTxs.localOnly_opt.nonEmpty) val toLocal = closingTxs.localOnly_opt.flatMap(_.toLocalOutput).get @@ -1196,7 +1195,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { // Their OP_RETURN output is trimmed (option_simple_close): val spec = CommitmentSpec(Set.empty, feeratePerKw, 150_000_000 msat, 1_000_000 msat) val remotePubKeyScript = Script.write(OP_RETURN :: OP_PUSHDATA(hex"deadbeef") :: Nil) - val closingTxs = makeSimpleClosingTxs(commitInput, spec, SimpleClosingTxFee.PaidByThem(1_001 sat), 0, localPubKeyScript, remotePubKeyScript) + val closingTxs = makeSimpleClosingTxs(commitInput, redeemInfo, spec, SimpleClosingTxFee.PaidByThem(1_001 sat), 0, localPubKeyScript, remotePubKeyScript) assert(closingTxs.all.size == 1) assert(closingTxs.localOnly_opt.nonEmpty) val toLocal = closingTxs.localOnly_opt.flatMap(_.toLocalOutput).get @@ -1207,14 +1206,14 @@ class TransactionsSpec extends AnyFunSuite with Logging { { // Our output is trimmed: val spec = CommitmentSpec(Set.empty, feeratePerKw, 50_000 msat, 150_000_000 msat) - val closingTx = makeClosingTx(commitInput, localPubKeyScript, remotePubKeyScript, localPaysClosingFees = true, localDustLimit, 1000 sat, spec) + val closingTx = makeClosingTx(commitInput, redeemInfo, localPubKeyScript, remotePubKeyScript, localPaysClosingFees = true, localDustLimit, 1000 sat, spec) assert(closingTx.tx.txOut.length == 1) assert(closingTx.toLocalOutput.isEmpty) } { // Our output is trimmed (option_simple_close): val spec = CommitmentSpec(Set.empty, feeratePerKw, 1_000_000 msat, 150_000_000 msat) - val closingTxs = makeSimpleClosingTxs(commitInput, spec, SimpleClosingTxFee.PaidByUs(800 sat), 0, localPubKeyScript, remotePubKeyScript) + val closingTxs = makeSimpleClosingTxs(commitInput, redeemInfo, spec, SimpleClosingTxFee.PaidByUs(800 sat), 0, localPubKeyScript, remotePubKeyScript) assert(closingTxs.all.size == 1) assert(closingTxs.remoteOnly_opt.nonEmpty) assert(closingTxs.remoteOnly_opt.flatMap(_.toLocalOutput).isEmpty) @@ -1222,7 +1221,7 @@ class TransactionsSpec extends AnyFunSuite with Logging { { // Both outputs are trimmed: val spec = CommitmentSpec(Set.empty, feeratePerKw, 50_000 msat, 10_000 msat) - val closingTx = makeClosingTx(commitInput, localPubKeyScript, remotePubKeyScript, localPaysClosingFees = true, localDustLimit, 1000 sat, spec) + val closingTx = makeClosingTx(commitInput, redeemInfo, localPubKeyScript, remotePubKeyScript, localPaysClosingFees = true, localDustLimit, 1000 sat, spec) assert(closingTx.tx.txOut.isEmpty) assert(closingTx.toLocalOutput.isEmpty) } 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 1cfebe6ec3..0c5cf8c072 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 @@ -27,7 +27,7 @@ import fr.acinq.eclair.crypto.ShaChain import fr.acinq.eclair.crypto.keymanager.{LocalChannelKeyManager, LocalNodeKeyManager} import fr.acinq.eclair.json.JsonSerializers import fr.acinq.eclair.router.Announcements -import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, CommitTx, TxOwner} +import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, CommitTx, TxOwner, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat} import fr.acinq.eclair.transactions._ import fr.acinq.eclair.wire.internal.channel.ChannelCodecs._ import fr.acinq.eclair.wire.protocol.{CommonCodecs, UpdateAddHtlc} @@ -243,7 +243,7 @@ class ChannelCodecsSpec extends AnyFunSuite { assert(newnormal.commitments.latest.localCommit.htlcTxsAndRemoteSigs.forall(_.htlcTx.tx.txIn.forall(_.witness.stack.isEmpty))) // make sure that we have extracted the remote sig of the local tx val RemoteSignature.FullSignature(remoteSig) = newnormal.commitments.latest.localCommit.commitTxAndRemoteSig.remoteSig - newnormal.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.checkSig(remoteSig, newnormal.commitments.remoteNodeId, TxOwner.Remote, newnormal.commitments.params.commitmentFormat) + newnormal.commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.checkSig(remoteSig, newnormal.commitments.remoteNodeId, TxOwner.Remote, newnormal.commitments.params.commitmentFormat, Map.empty) } } @@ -313,7 +313,7 @@ object ChannelCodecsSpec { val fundingAmount = fundingTx.txOut.head.amount val fundingTxIndex = 0 val remoteFundingPubKey = PrivateKey(ByteVector32(ByteVector.fill(32)(1)) :+ 1.toByte).publicKey - val commitmentInput = Funding.makeFundingInputInfo(fundingTx.txid, 0, fundingAmount, channelKeyManager.fundingPublicKey(localParams.fundingKeyPath, fundingTxIndex).publicKey, remoteFundingPubKey) + val (commitmentInput, redeemInfo) = Funding.makeFundingInputInfo(fundingTx.txid, 0, fundingAmount, channelKeyManager.fundingPublicKey(localParams.fundingKeyPath, fundingTxIndex).publicKey, remoteFundingPubKey, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat) val remoteSig = ByteVector64(hex"2148d2d4aac8c793eb82d31bcf22d4db707b9fd7eee1b89b4b1444c9e19ab7172bab8c3d997d29163fa0cb255c75afb8ade13617ad1350c1515e9be4a222a04d") val commitTx = Transaction( version = 2, @@ -325,7 +325,7 @@ object ChannelCodecsSpec { txOut = Nil, lockTime = 0 ) - val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, FeeratePerKw(1500 sat), 50000000 msat, 70000000 msat), CommitTxAndRemoteSig(CommitTx(commitmentInput, commitTx), remoteSig), Nil) + val localCommit = LocalCommit(0, CommitmentSpec(htlcs.toSet, FeeratePerKw(1500 sat), 50000000 msat, 70000000 msat), CommitTxAndRemoteSig(CommitTx(commitmentInput, redeemInfo, commitTx), remoteSig), Nil) val remoteCommit = RemoteCommit(0, CommitmentSpec(htlcs.map(_.opposite).toSet, FeeratePerKw(1500 sat), 50000 msat, 700000 msat), TxId.fromValidHex("0303030303030303030303030303030303030303030303030303030303030303"), PrivateKey(ByteVector.fill(32)(4)).publicKey) val channelId = htlcs.headOption.map(_.add.channelId).getOrElse(ByteVector32.Zeroes) val channelFlags = ChannelFlags(announceChannel = true) 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 ca67359422..3699a2c299 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 @@ -12,7 +12,7 @@ import fr.acinq.eclair.channel._ import fr.acinq.eclair.channel.fund.InteractiveTxBuilder.{InteractiveTxParams, PartiallySignedSharedTransaction, RequireConfirmedInputs, SharedTransaction} import fr.acinq.eclair.channel.fund.InteractiveTxSigningSession.UnsignedLocalCommit import fr.acinq.eclair.channel.fund.{InteractiveTxBuilder, InteractiveTxSigningSession} -import fr.acinq.eclair.transactions.Transactions.{CommitTx, InputInfo} +import fr.acinq.eclair.transactions.Transactions.{CommitTx, InputInfo, RedeemInfo} import fr.acinq.eclair.transactions.{CommitmentSpec, Scripts} import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec.normal import fr.acinq.eclair.wire.internal.channel.version4.ChannelCodecs4.Codecs._ @@ -124,7 +124,8 @@ class ChannelCodecs4Spec extends AnyFunSuite { test("encode/decode rbf status") { val channelId = randomBytes32() - val fundingInput = InputInfo(OutPoint(randomTxId(), 3), TxOut(175_000 sat, Script.pay2wpkh(randomKey().publicKey)), Nil) + val fundingInput = InputInfo(OutPoint(randomTxId(), 3), TxOut(175_000 sat, Script.pay2wpkh(randomKey().publicKey))) + val redeemInfo = RedeemInfo.SegwitV0(Nil) val fundingTx = SharedTransaction( sharedInput_opt = None, sharedOutput = InteractiveTxBuilder.Output.Shared(UInt64(8), ByteVector.empty, 100_000_600 msat, 74_000_400 msat, 0 msat), @@ -134,6 +135,7 @@ class ChannelCodecs4Spec extends AnyFunSuite { ) val commitTx = CommitTx( fundingInput, + redeemInfo, Transaction(2, Seq(TxIn(fundingInput.outPoint, Nil, 0)), Seq(TxOut(150_000 sat, Script.pay2wpkh(randomKey().publicKey))), 0), ) val waitingForSigs = InteractiveTxSigningSession.WaitingForSigs( @@ -180,7 +182,8 @@ class ChannelCodecs4Spec extends AnyFunSuite { createdAt = BlockHeight(1000), fundingParams = InteractiveTxParams(channelId = channelId, isInitiator = true, localContribution = 100.sat, remoteContribution = 200.sat, sharedInput_opt = Some(InteractiveTxBuilder.Multisig2of2Input( - InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(1000.sat, Script.pay2wsh(script)), script), + InputInfo(OutPoint(TxId(ByteVector32.Zeroes), 0), TxOut(1000.sat, Script.pay2wsh(script))), + RedeemInfo.SegwitV0(script), 0, PrivateKey(ByteVector.fromValidHex("02" * 32)).publicKey )),