1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-03-13 03:14:26 +01:00

Fixup: SimpleTaprootChannelCommitmentFormat should not be a subtype of AnchorOutputsCommitmentFormat

This commit is contained in:
sstone 2025-03-10 11:12:53 +01:00
parent 17908a3690
commit 1c4215fb3c
No known key found for this signature in database
GPG key ID: E04E48E72C205463
15 changed files with 126 additions and 115 deletions

View file

@ -134,7 +134,7 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
min = (commitmentFeerate * feerateTolerance.ratioLow).max(minimumFeerate), min = (commitmentFeerate * feerateTolerance.ratioLow).max(minimumFeerate),
max = (commitmentFormat match { max = (commitmentFormat match {
case Transactions.DefaultCommitmentFormat => commitmentFeerate * feerateTolerance.ratioHigh case Transactions.DefaultCommitmentFormat => commitmentFeerate * feerateTolerance.ratioHigh
case _: Transactions.AnchorOutputsCommitmentFormat => (commitmentFeerate * feerateTolerance.ratioHigh).max(feerateTolerance.anchorOutputMaxCommitFeerate) case _: Transactions.AnchorOutputsCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat => (commitmentFeerate * feerateTolerance.ratioHigh).max(feerateTolerance.anchorOutputMaxCommitFeerate)
}).max(minimumFeerate), }).max(minimumFeerate),
) )
RecommendedFeerates(chainHash, fundingFeerate, commitmentFeerate, TlvStream(fundingRange, commitmentRange)) RecommendedFeerates(chainHash, fundingFeerate, commitmentFeerate, TlvStream(fundingRange, commitmentRange))

View file

@ -121,7 +121,7 @@ case class OnChainFeeConf(feeTargets: FeeTargets,
commitmentFormat match { commitmentFormat match {
case Transactions.DefaultCommitmentFormat => networkFeerate case Transactions.DefaultCommitmentFormat => networkFeerate
case _: Transactions.AnchorOutputsCommitmentFormat => case _: Transactions.AnchorOutputsCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat =>
val targetFeerate = networkFeerate.min(feerateToleranceFor(remoteNodeId).anchorOutputMaxCommitFeerate) val targetFeerate = networkFeerate.min(feerateToleranceFor(remoteNodeId).anchorOutputMaxCommitFeerate)
// We make sure the feerate is always greater than the propagation threshold. // We make sure the feerate is always greater than the propagation threshold.
targetFeerate.max(networkMinFee * 1.25) targetFeerate.max(networkMinFee * 1.25)

View file

@ -357,20 +357,22 @@ object Helpers {
} }
object Funding { object Funding {
def makeFundingPubKeyScript(localFundingKey: PublicKey, remoteFundingKey: PublicKey, commitmentFormat: CommitmentFormat): ByteVector = if (commitmentFormat.useTaproot) { def makeFundingPubKeyScript(localFundingKey: PublicKey, remoteFundingKey: PublicKey, commitmentFormat: CommitmentFormat): ByteVector = commitmentFormat match {
write(Taproot.musig2FundingScript(localFundingKey, remoteFundingKey)) case SimpleTaprootChannelCommitmentFormat =>
} else { write(Taproot.musig2FundingScript(localFundingKey, remoteFundingKey))
write(pay2wsh(multiSig2of2(localFundingKey, remoteFundingKey))) case _ =>
write(pay2wsh(multiSig2of2(localFundingKey, remoteFundingKey)))
} }
def makeFundingInputInfo(fundingTxId: TxId, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey, commitmentFormat: CommitmentFormat): InputInfo = if (commitmentFormat.useTaproot) { def makeFundingInputInfo(fundingTxId: TxId, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey, commitmentFormat: CommitmentFormat): InputInfo = commitmentFormat match {
val fundingScript = Taproot.musig2FundingScript(fundingPubkey1, fundingPubkey2) case SimpleTaprootChannelCommitmentFormat =>
val fundingTxOut = TxOut(fundingSatoshis, fundingScript) val fundingScript = Taproot.musig2FundingScript(fundingPubkey1, fundingPubkey2)
InputInfo.TaprootInput(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, Taproot.musig2Aggregate(fundingPubkey1, fundingPubkey2), None) val fundingTxOut = TxOut(fundingSatoshis, fundingScript)
} else { InputInfo.TaprootInput(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, Taproot.musig2Aggregate(fundingPubkey1, fundingPubkey2), None)
val fundingScript = multiSig2of2(fundingPubkey1, fundingPubkey2) case _ =>
val fundingTxOut = TxOut(fundingSatoshis, pay2wsh(fundingScript)) val fundingScript = multiSig2of2(fundingPubkey1, fundingPubkey2)
InputInfo.SegwitInput(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, write(fundingScript)) val fundingTxOut = TxOut(fundingSatoshis, pay2wsh(fundingScript))
InputInfo.SegwitInput(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, write(fundingScript))
} }
/** /**
@ -661,7 +663,7 @@ object Helpers {
case DefaultCommitmentFormat => case DefaultCommitmentFormat =>
// we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction" // we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction"
requestedFeerate.min(commitment.localCommit.spec.commitTxFeerate) requestedFeerate.min(commitment.localCommit.spec.commitTxFeerate)
case _: AnchorOutputsCommitmentFormat => requestedFeerate case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => requestedFeerate
} }
// NB: we choose a minimum fee that ensures the tx will easily propagate while allowing low fees since we can // NB: we choose a minimum fee that ensures the tx will easily propagate while allowing low fees since we can
// always use CPFP to speed up confirmation if necessary. // always use CPFP to speed up confirmation if necessary.
@ -892,18 +894,18 @@ object Helpers {
def claimAnchors(keyManager: ChannelKeyManager, commitment: FullCommitment, lcp: LocalCommitPublished, confirmationTarget: ConfirmationTarget)(implicit log: LoggingAdapter): LocalCommitPublished = { def claimAnchors(keyManager: ChannelKeyManager, commitment: FullCommitment, lcp: LocalCommitPublished, confirmationTarget: ConfirmationTarget)(implicit log: LoggingAdapter): LocalCommitPublished = {
if (shouldUpdateAnchorTxs(lcp.claimAnchorTxs, confirmationTarget)) { if (shouldUpdateAnchorTxs(lcp.claimAnchorTxs, confirmationTarget)) {
val localFundingPubKey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey val localFundingPubKey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey
val localPaymentKey = if (commitment.params.commitmentFormat.useTaproot) { val localPaymentKey = commitment.params.commitmentFormat match {
val channelKeyPath = keyManager.keyPath(commitment.localParams, commitment.params.channelConfig) case SimpleTaprootChannelCommitmentFormat =>
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitment.localCommit.index) val channelKeyPath = keyManager.keyPath(commitment.localParams, commitment.params.channelConfig)
val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(channelKeyPath).publicKey, localPerCommitmentPoint) val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitment.localCommit.index)
localDelayedPaymentPubkey val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(channelKeyPath).publicKey, localPerCommitmentPoint)
} else { localDelayedPaymentPubkey
localFundingPubKey case _ =>
localFundingPubKey
} }
val remotePaymentKey = if (commitment.params.commitmentFormat.useTaproot) { val remotePaymentKey = commitment.params.commitmentFormat match {
commitment.remoteParams.paymentBasepoint case SimpleTaprootChannelCommitmentFormat => commitment.remoteParams.paymentBasepoint
} else { case _ => commitment.remoteFundingPubKey
commitment.remoteFundingPubKey
} }
val claimAnchorTxs = List( val claimAnchorTxs = List(
withTxGenerationLog("local-anchor") { withTxGenerationLog("local-anchor") {
@ -1028,17 +1030,19 @@ object Helpers {
val localFundingPubkey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey val localFundingPubkey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey
// taproot channels do not re-use the funding pubkeys for anchor outputs // taproot channels do not re-use the funding pubkeys for anchor outputs
val localPaymentKey = if (commitment.params.commitmentFormat.useTaproot) { val localPaymentKey = commitment.params.commitmentFormat match {
val channelKeyPath = keyManager.keyPath(commitment.localParams, commitment.params.channelConfig) case SimpleTaprootChannelCommitmentFormat =>
commitment.localParams.walletStaticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey) val channelKeyPath = keyManager.keyPath(commitment.localParams, commitment.params.channelConfig)
} else { commitment.localParams.walletStaticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey)
localFundingPubkey case _ =>
localFundingPubkey
} }
val remotePaymentKey = if (commitment.params.commitmentFormat.useTaproot) { val remotePaymentKey = commitment.params.commitmentFormat match {
val remoteDelayedPaymentPubkey = Generators.derivePubKey(commitment.remoteParams.delayedPaymentBasepoint, commitment.remoteCommit.remotePerCommitmentPoint) case SimpleTaprootChannelCommitmentFormat =>
remoteDelayedPaymentPubkey val remoteDelayedPaymentPubkey = Generators.derivePubKey(commitment.remoteParams.delayedPaymentBasepoint, commitment.remoteCommit.remotePerCommitmentPoint)
} else { remoteDelayedPaymentPubkey
commitment.remoteFundingPubKey case _ =>
commitment.remoteFundingPubKey
} }
val claimAnchorTxs = List( val claimAnchorTxs = List(
withTxGenerationLog("local-anchor") { withTxGenerationLog("local-anchor") {
@ -1078,7 +1082,7 @@ object Helpers {
Transactions.addSigs(claimMain, localPubkey, sig) Transactions.addSigs(claimMain, localPubkey, sig)
}) })
} }
case _: AnchorOutputsCommitmentFormat => withTxGenerationLog("remote-main-delayed") { case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => withTxGenerationLog("remote-main-delayed") {
Transactions.makeClaimRemoteDelayedOutputTx(tx, params.localParams.dustLimit, localPaymentPoint, finalScriptPubKey, feeratePerKwMain).map(claimMain => { Transactions.makeClaimRemoteDelayedOutputTx(tx, params.localParams.dustLimit, localPaymentPoint, finalScriptPubKey, feeratePerKwMain).map(claimMain => {
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), TxOwner.Local, params.commitmentFormat) val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), TxOwner.Local, params.commitmentFormat)
Transactions.addSigs(claimMain, sig) Transactions.addSigs(claimMain, sig)
@ -1214,7 +1218,7 @@ object Helpers {
Transactions.addSigs(claimMain, localPaymentPubkey, sig) Transactions.addSigs(claimMain, localPaymentPubkey, sig)
}) })
} }
case _: AnchorOutputsCommitmentFormat => withTxGenerationLog("remote-main-delayed") { case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => withTxGenerationLog("remote-main-delayed") {
Transactions.makeClaimRemoteDelayedOutputTx(commitTx, localParams.dustLimit, localPaymentPoint, finalScriptPubKey, feerateMain).map(claimMain => { Transactions.makeClaimRemoteDelayedOutputTx(commitTx, localParams.dustLimit, localPaymentPoint, finalScriptPubKey, feerateMain).map(claimMain => {
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), TxOwner.Local, commitmentFormat) val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), TxOwner.Local, commitmentFormat)
Transactions.addSigs(claimMain, sig) Transactions.addSigs(claimMain, sig)

View file

@ -237,7 +237,7 @@ trait ErrorHandlers extends CommonHandlers {
case Transactions.DefaultCommitmentFormat => case Transactions.DefaultCommitmentFormat =>
val redeemableHtlcTxs = htlcTxs.values.flatten.map(tx => PublishFinalTx(tx, tx.fee, Some(commitTx.txid))) val redeemableHtlcTxs = htlcTxs.values.flatten.map(tx => PublishFinalTx(tx, tx.fee, Some(commitTx.txid)))
List(PublishFinalTx(commitTx, commitment.commitInput.outPoint, commitment.capacity, "commit-tx", Closing.commitTxFee(commitment.commitInput, commitTx, localPaysCommitTxFees), None)) ++ (claimMainDelayedOutputTx.map(tx => PublishFinalTx(tx, tx.fee, None)) ++ redeemableHtlcTxs ++ claimHtlcDelayedTxs.map(tx => PublishFinalTx(tx, tx.fee, None))) List(PublishFinalTx(commitTx, commitment.commitInput.outPoint, commitment.capacity, "commit-tx", Closing.commitTxFee(commitment.commitInput, commitTx, localPaysCommitTxFees), None)) ++ (claimMainDelayedOutputTx.map(tx => PublishFinalTx(tx, tx.fee, None)) ++ redeemableHtlcTxs ++ claimHtlcDelayedTxs.map(tx => PublishFinalTx(tx, tx.fee, None)))
case _: Transactions.AnchorOutputsCommitmentFormat => case _: Transactions.AnchorOutputsCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat =>
val redeemableHtlcTxs = htlcTxs.values.flatten.map(tx => PublishReplaceableTx(tx, commitment, commitTx)) val redeemableHtlcTxs = htlcTxs.values.flatten.map(tx => PublishReplaceableTx(tx, commitment, commitTx))
val claimLocalAnchor = claimAnchorTxs.collect { case tx: Transactions.ClaimLocalAnchorOutputTx if !localCommitPublished.isConfirmed => PublishReplaceableTx(tx, commitment, commitTx) } val claimLocalAnchor = claimAnchorTxs.collect { case tx: Transactions.ClaimLocalAnchorOutputTx if !localCommitPublished.isConfirmed => PublishReplaceableTx(tx, commitment, commitTx) }
List(PublishFinalTx(commitTx, commitment.commitInput.outPoint, commitment.capacity, "commit-tx", Closing.commitTxFee(commitment.commitInput, commitTx, localPaysCommitTxFees), None)) ++ claimLocalAnchor ++ claimMainDelayedOutputTx.map(tx => PublishFinalTx(tx, tx.fee, None)) ++ redeemableHtlcTxs ++ claimHtlcDelayedTxs.map(tx => PublishFinalTx(tx, tx.fee, None)) List(PublishFinalTx(commitTx, commitment.commitInput.outPoint, commitment.capacity, "commit-tx", Closing.commitTxFee(commitment.commitInput, commitTx, localPaysCommitTxFees), None)) ++ claimLocalAnchor ++ claimMainDelayedOutputTx.map(tx => PublishFinalTx(tx, tx.fee, None)) ++ redeemableHtlcTxs ++ claimHtlcDelayedTxs.map(tx => PublishFinalTx(tx, tx.fee, None))

View file

@ -23,7 +23,7 @@ import fr.acinq.bitcoin.TxIn.{SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_
import fr.acinq.bitcoin.scalacompat.Crypto.{PublicKey, XonlyPublicKey} import fr.acinq.bitcoin.scalacompat.Crypto.{PublicKey, XonlyPublicKey}
import fr.acinq.bitcoin.scalacompat.Script._ import fr.acinq.bitcoin.scalacompat.Script._
import fr.acinq.bitcoin.scalacompat._ import fr.acinq.bitcoin.scalacompat._
import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, CommitmentFormat, DefaultCommitmentFormat} import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, CommitmentFormat, DefaultCommitmentFormat, SimpleTaprootChannelCommitmentFormat}
import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta} import fr.acinq.eclair.{BlockHeight, CltvExpiry, CltvExpiryDelta}
import scodec.bits.ByteVector import scodec.bits.ByteVector
@ -44,7 +44,7 @@ object Scripts {
private def htlcRemoteSighash(commitmentFormat: CommitmentFormat): Int = commitmentFormat match { private def htlcRemoteSighash(commitmentFormat: CommitmentFormat): Int = commitmentFormat match {
case DefaultCommitmentFormat => SIGHASH_ALL case DefaultCommitmentFormat => SIGHASH_ALL
case _: AnchorOutputsCommitmentFormat => SIGHASH_SINGLE | SIGHASH_ANYONECANPAY case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => SIGHASH_SINGLE | SIGHASH_ANYONECANPAY
} }
/** Sort public keys using lexicographic ordering. */ /** Sort public keys using lexicographic ordering. */
@ -191,6 +191,7 @@ object Scripts {
val addCsvDelay = commitmentFormat match { val addCsvDelay = commitmentFormat match {
case DefaultCommitmentFormat => false case DefaultCommitmentFormat => false
case _: AnchorOutputsCommitmentFormat => true case _: AnchorOutputsCommitmentFormat => true
case SimpleTaprootChannelCommitmentFormat => true
} }
// @formatter:off // @formatter:off
// To you with revocation key // To you with revocation key
@ -243,6 +244,7 @@ object Scripts {
val addCsvDelay = commitmentFormat match { val addCsvDelay = commitmentFormat match {
case DefaultCommitmentFormat => false case DefaultCommitmentFormat => false
case _: AnchorOutputsCommitmentFormat => true case _: AnchorOutputsCommitmentFormat => true
case SimpleTaprootChannelCommitmentFormat => true
} }
// @formatter:off // @formatter:off
// To you with revocation key // To you with revocation key

View file

@ -52,7 +52,6 @@ object Transactions {
def htlcSuccessWeight: Int def htlcSuccessWeight: Int
def htlcTimeoutInputWeight: Int def htlcTimeoutInputWeight: Int
def htlcSuccessInputWeight: Int def htlcSuccessInputWeight: Int
def useTaproot: Boolean = false
// @formatter:on // @formatter:on
} }
@ -98,9 +97,13 @@ object Transactions {
*/ */
case object ZeroFeeHtlcTxAnchorOutputsCommitmentFormat extends AnchorOutputsCommitmentFormat case object ZeroFeeHtlcTxAnchorOutputsCommitmentFormat extends AnchorOutputsCommitmentFormat
case object SimpleTaprootChannelCommitmentFormat extends AnchorOutputsCommitmentFormat { case object SimpleTaprootChannelCommitmentFormat extends CommitmentFormat {
override val commitWeight = 968 override val commitWeight = 968
override val useTaproot = true override val htlcOutputWeight = 172
override val htlcTimeoutWeight = 666
override val htlcSuccessWeight = 706
override val htlcTimeoutInputWeight = 452
override val htlcSuccessInputWeight = 491
} }
// @formatter:off // @formatter:off
@ -144,7 +147,10 @@ object Transactions {
Satoshi(FeeratePerKw.MinimumRelayFeeRate * vsize / 1000) Satoshi(FeeratePerKw.MinimumRelayFeeRate * vsize / 1000)
} }
/** Sighash flags to use when signing the transaction. */ /** Sighash flags to use when signing the transaction. */
def sighash(txOwner: TxOwner, commitmentFormat: CommitmentFormat): Int = if (input.isP2tr) SIGHASH_DEFAULT else SIGHASH_ALL def sighash(txOwner: TxOwner, commitmentFormat: CommitmentFormat): Int = commitmentFormat match {
case SimpleTaprootChannelCommitmentFormat => SIGHASH_DEFAULT
case _ => SIGHASH_ALL
}
def sign(key: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = { def sign(key: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = {
sign(key, sighash(txOwner, commitmentFormat)) sign(key, sighash(txOwner, commitmentFormat))
@ -218,6 +224,10 @@ object Transactions {
case TxOwner.Local => SIGHASH_ALL case TxOwner.Local => SIGHASH_ALL
case TxOwner.Remote => SIGHASH_SINGLE | SIGHASH_ANYONECANPAY case TxOwner.Remote => SIGHASH_SINGLE | SIGHASH_ANYONECANPAY
} }
case SimpleTaprootChannelCommitmentFormat => txOwner match {
case TxOwner.Local => SIGHASH_DEFAULT
case TxOwner.Remote => SIGHASH_SINGLE | SIGHASH_ANYONECANPAY
}
} }
override def confirmationTarget: ConfirmationTarget.Absolute override def confirmationTarget: ConfirmationTarget.Absolute
} }
@ -228,7 +238,7 @@ object Transactions {
override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match { override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match {
case t: InputInfo.TaprootInput => case t: InputInfo.TaprootInput =>
val Some(scriptTree: ScriptTree.Branch) = t.scriptTree_opt val Some(scriptTree: ScriptTree.Branch) = t.scriptTree_opt
Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), SigHash.SIGHASH_SINGLE | SigHash.SIGHASH_ANYONECANPAY, KotlinUtils.kmp2scala(scriptTree.getRight.hash())) Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), sighash(txOwner, commitmentFormat), KotlinUtils.kmp2scala(scriptTree.getRight.hash()))
case _: InputInfo.SegwitInput => case _: InputInfo.SegwitInput =>
super.sign(privateKey, txOwner, commitmentFormat) super.sign(privateKey, txOwner, commitmentFormat)
} }
@ -236,9 +246,8 @@ object Transactions {
override def checkSig(sig: ByteVector64, pubKey: PublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): Boolean = input match { override def checkSig(sig: ByteVector64, pubKey: PublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): Boolean = input match {
case t: InputInfo.TaprootInput => case t: InputInfo.TaprootInput =>
import KotlinUtils._ import KotlinUtils._
val sighash = this.sighash(txOwner, commitmentFormat)
val Some(scriptTree: ScriptTree.Branch) = t.scriptTree_opt val Some(scriptTree: ScriptTree.Branch) = t.scriptTree_opt
val data = Transaction.hashForSigningTaprootScriptPath(tx, inputIndex = 0, Seq(input.txOut), sighash, scriptTree.getRight.hash()) val data = Transaction.hashForSigningTaprootScriptPath(tx, inputIndex = 0, Seq(input.txOut), sighash(txOwner, commitmentFormat), scriptTree.getRight.hash())
Crypto.verifySignatureSchnorr(data, sig, pubKey.xOnly) Crypto.verifySignatureSchnorr(data, sig, pubKey.xOnly)
case _: InputInfo.SegwitInput => case _: InputInfo.SegwitInput =>
super.checkSig(sig, pubKey, txOwner, commitmentFormat) super.checkSig(sig, pubKey, txOwner, commitmentFormat)
@ -251,7 +260,7 @@ object Transactions {
override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match { override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match {
case t: InputInfo.TaprootInput => case t: InputInfo.TaprootInput =>
val Some(scriptTree: ScriptTree.Branch) = t.scriptTree_opt val Some(scriptTree: ScriptTree.Branch) = t.scriptTree_opt
Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), SigHash.SIGHASH_SINGLE | SigHash.SIGHASH_ANYONECANPAY, KotlinUtils.kmp2scala(scriptTree.getLeft.hash())) Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), sighash(txOwner, commitmentFormat), KotlinUtils.kmp2scala(scriptTree.getLeft.hash()))
case _: InputInfo.SegwitInput => case _: InputInfo.SegwitInput =>
super.sign(privateKey, txOwner, commitmentFormat) super.sign(privateKey, txOwner, commitmentFormat)
} }
@ -259,9 +268,8 @@ object Transactions {
override def checkSig(sig: ByteVector64, pubKey: PublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): Boolean = input match { override def checkSig(sig: ByteVector64, pubKey: PublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): Boolean = input match {
case t: InputInfo.TaprootInput => case t: InputInfo.TaprootInput =>
import KotlinUtils._ import KotlinUtils._
val sighash = this.sighash(txOwner, commitmentFormat)
val Some(scriptTree: ScriptTree.Branch) = t.scriptTree_opt val Some(scriptTree: ScriptTree.Branch) = t.scriptTree_opt
val data = Transaction.hashForSigningTaprootScriptPath(tx, inputIndex = 0, Seq(input.txOut), sighash, scriptTree.getLeft.hash()) val data = Transaction.hashForSigningTaprootScriptPath(tx, inputIndex = 0, Seq(input.txOut), sighash(txOwner, commitmentFormat), scriptTree.getLeft.hash())
Crypto.verifySignatureSchnorr(data, sig, pubKey.xOnly) Crypto.verifySignatureSchnorr(data, sig, pubKey.xOnly)
case _: InputInfo.SegwitInput => case _: InputInfo.SegwitInput =>
super.checkSig(sig, pubKey, txOwner, commitmentFormat) super.checkSig(sig, pubKey, txOwner, commitmentFormat)
@ -273,7 +281,7 @@ object Transactions {
override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match { override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match {
case t: InputInfo.TaprootInput => case t: InputInfo.TaprootInput =>
val Some(scriptTree: ScriptTree.Leaf) = t.scriptTree_opt val Some(scriptTree: ScriptTree.Leaf) = t.scriptTree_opt
Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), SigHash.SIGHASH_DEFAULT, KotlinUtils.kmp2scala(scriptTree.hash())) Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), sighash(txOwner, commitmentFormat), KotlinUtils.kmp2scala(scriptTree.hash()))
case _: InputInfo.SegwitInput => case _: InputInfo.SegwitInput =>
super.sign(privateKey, txOwner, commitmentFormat) super.sign(privateKey, txOwner, commitmentFormat)
} }
@ -290,7 +298,7 @@ object Transactions {
override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match { override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match {
case t: InputInfo.TaprootInput => case t: InputInfo.TaprootInput =>
val Some(htlcTree: ScriptTree.Branch) = t.scriptTree_opt val Some(htlcTree: ScriptTree.Branch) = t.scriptTree_opt
Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), SigHash.SIGHASH_DEFAULT, KotlinUtils.kmp2scala(htlcTree.getRight.hash())) Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), sighash(txOwner, commitmentFormat), KotlinUtils.kmp2scala(htlcTree.getRight.hash()))
case _: InputInfo.SegwitInput => case _: InputInfo.SegwitInput =>
super.sign(privateKey, txOwner, commitmentFormat) super.sign(privateKey, txOwner, commitmentFormat)
} }
@ -301,7 +309,7 @@ object Transactions {
override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match { override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match {
case t: InputInfo.TaprootInput => case t: InputInfo.TaprootInput =>
val Some(htlcTree: ScriptTree.Branch) = t.scriptTree_opt val Some(htlcTree: ScriptTree.Branch) = t.scriptTree_opt
Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), SigHash.SIGHASH_DEFAULT, KotlinUtils.kmp2scala(htlcTree.getLeft.hash())) Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), sighash(txOwner, commitmentFormat), KotlinUtils.kmp2scala(htlcTree.getLeft.hash()))
case _: InputInfo.SegwitInput => case _: InputInfo.SegwitInput =>
super.sign(privateKey, txOwner, commitmentFormat) super.sign(privateKey, txOwner, commitmentFormat)
} }
@ -312,7 +320,7 @@ object Transactions {
override def desc: String = "local-anchor" override def desc: String = "local-anchor"
override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match { override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match {
case _: InputInfo.TaprootInput => Transaction.signInputTaprootKeyPath(privateKey, tx, 0, Seq(input.txOut), SigHash.SIGHASH_DEFAULT, Some(Scripts.Taproot.anchorScriptTree)) case _: InputInfo.TaprootInput => Transaction.signInputTaprootKeyPath(privateKey, tx, 0, Seq(input.txOut), sighash(txOwner, commitmentFormat), Some(Scripts.Taproot.anchorScriptTree))
case _: InputInfo.SegwitInput => super.sign(privateKey, txOwner, commitmentFormat) case _: InputInfo.SegwitInput => super.sign(privateKey, txOwner, commitmentFormat)
} }
} }
@ -326,7 +334,7 @@ object Transactions {
override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match { override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match {
case t: InputInfo.TaprootInput => case t: InputInfo.TaprootInput =>
val Some(toRemoteScriptTree: ScriptTree.Leaf) = t.scriptTree_opt val Some(toRemoteScriptTree: ScriptTree.Leaf) = t.scriptTree_opt
Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), SigHash.SIGHASH_DEFAULT, KotlinUtils.kmp2scala(toRemoteScriptTree.hash())) Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), sighash(txOwner, commitmentFormat), KotlinUtils.kmp2scala(toRemoteScriptTree.hash()))
case _: InputInfo.SegwitInput => { case _: InputInfo.SegwitInput => {
super.sign(privateKey, txOwner, commitmentFormat) super.sign(privateKey, txOwner, commitmentFormat)
} }
@ -339,7 +347,7 @@ object Transactions {
override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match { override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match {
case t: InputInfo.TaprootInput => case t: InputInfo.TaprootInput =>
val Some(toLocalScriptTree: ScriptTree.Branch) = t.scriptTree_opt val Some(toLocalScriptTree: ScriptTree.Branch) = t.scriptTree_opt
Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), SigHash.SIGHASH_DEFAULT, KotlinUtils.kmp2scala(toLocalScriptTree.getLeft.hash())) Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), sighash(txOwner, commitmentFormat), KotlinUtils.kmp2scala(toLocalScriptTree.getLeft.hash()))
case _: InputInfo.SegwitInput => { case _: InputInfo.SegwitInput => {
super.sign(privateKey, txOwner, commitmentFormat) super.sign(privateKey, txOwner, commitmentFormat)
} }
@ -352,7 +360,7 @@ object Transactions {
override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match { override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match {
case t: InputInfo.TaprootInput => case t: InputInfo.TaprootInput =>
val Some(toLocalScriptTree: ScriptTree.Branch) = t.scriptTree_opt val Some(toLocalScriptTree: ScriptTree.Branch) = t.scriptTree_opt
Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), SigHash.SIGHASH_DEFAULT, KotlinUtils.kmp2scala(toLocalScriptTree.getRight.hash())) Transaction.signInputTaprootScriptPath(privateKey, tx, 0, Seq(input.txOut), sighash(txOwner, commitmentFormat), KotlinUtils.kmp2scala(toLocalScriptTree.getRight.hash()))
case _: InputInfo.SegwitInput => { case _: InputInfo.SegwitInput => {
super.sign(privateKey, txOwner, commitmentFormat) super.sign(privateKey, txOwner, commitmentFormat)
} }
@ -364,7 +372,7 @@ object Transactions {
override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match { override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match {
case t: InputInfo.TaprootInput => case t: InputInfo.TaprootInput =>
Transaction.signInputTaprootKeyPath(privateKey, tx, 0, Seq(input.txOut), SigHash.SIGHASH_DEFAULT, t.scriptTree_opt) Transaction.signInputTaprootKeyPath(privateKey, tx, 0, Seq(input.txOut), sighash(txOwner, commitmentFormat), t.scriptTree_opt)
case _: InputInfo.SegwitInput => { case _: InputInfo.SegwitInput => {
super.sign(privateKey, txOwner, commitmentFormat) super.sign(privateKey, txOwner, commitmentFormat)
} }
@ -376,7 +384,7 @@ object Transactions {
override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match { override def sign(privateKey: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = input match {
case t: InputInfo.TaprootInput => case t: InputInfo.TaprootInput =>
Transaction.signInputTaprootKeyPath(privateKey, tx, 0, Seq(input.txOut), SigHash.SIGHASH_DEFAULT, t.scriptTree_opt) Transaction.signInputTaprootKeyPath(privateKey, tx, 0, Seq(input.txOut), sighash(txOwner, commitmentFormat), t.scriptTree_opt)
case _: InputInfo.SegwitInput => { case _: InputInfo.SegwitInput => {
super.sign(privateKey, txOwner, commitmentFormat) super.sign(privateKey, txOwner, commitmentFormat)
} }
@ -384,8 +392,6 @@ object Transactions {
} }
case class ClosingTx(input: InputInfo, tx: Transaction, toLocalOutput: Option[OutputInfo]) extends TransactionWithInputInfo { case class ClosingTx(input: InputInfo, tx: Transaction, toLocalOutput: Option[OutputInfo]) extends TransactionWithInputInfo {
// these nonces are generated on the fly at during a "simple" closing session and can be forgotten once the session ends
// @volatile var localNonce_opt: Option[(SecretNonce, IndividualNonce)] = None
override def desc: String = "closing" override def desc: String = "closing"
} }
@ -529,7 +535,7 @@ object Transactions {
// This is not technically a fee (it doesn't go to miners) but it also has to be deduced from the channel initiator's main output. // This is not technically a fee (it doesn't go to miners) but it also has to be deduced from the channel initiator's main output.
val anchorsCost = commitmentFormat match { val anchorsCost = commitmentFormat match {
case DefaultCommitmentFormat => Satoshi(0) case DefaultCommitmentFormat => Satoshi(0)
case _: AnchorOutputsCommitmentFormat => AnchorOutputsCommitmentFormat.anchorAmount * 2 case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => AnchorOutputsCommitmentFormat.anchorAmount * 2
} }
txFee + anchorsCost txFee + anchorsCost
} }
@ -583,7 +589,7 @@ object Transactions {
def getHtlcTxInputSequence(commitmentFormat: CommitmentFormat): Long = commitmentFormat match { def getHtlcTxInputSequence(commitmentFormat: CommitmentFormat): Long = commitmentFormat match {
case DefaultCommitmentFormat => 0 // htlc txs immediately spend the commit tx 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 case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => 1 // htlc txs have a 1-block delay to allow CPFP carve-out on anchors
} }
/** /**
@ -645,8 +651,8 @@ object Transactions {
val outputs = collection.mutable.ArrayBuffer.empty[CommitmentOutputLink[CommitmentOutput]] val outputs = collection.mutable.ArrayBuffer.empty[CommitmentOutputLink[CommitmentOutput]]
trimOfferedHtlcs(localDustLimit, spec, commitmentFormat).foreach { htlc => trimOfferedHtlcs(localDustLimit, spec, commitmentFormat).foreach { htlc =>
commitmentFormat.useTaproot match { commitmentFormat match {
case true => case SimpleTaprootChannelCommitmentFormat =>
val offeredHtlcTree = Scripts.Taproot.offeredHtlcScriptTree(localHtlcPubkey, remoteHtlcPubkey, htlc.add.paymentHash) val offeredHtlcTree = Scripts.Taproot.offeredHtlcScriptTree(localHtlcPubkey, remoteHtlcPubkey, htlc.add.paymentHash)
outputs.append(CommitmentOutputLink( outputs.append(CommitmentOutputLink(
TxOut(htlc.add.amountMsat.truncateToSatoshi, pay2tr(localRevocationPubkey.xOnly, Some(offeredHtlcTree))), localRevocationPubkey.xOnly, offeredHtlcTree, OutHtlc(htlc) TxOut(htlc.add.amountMsat.truncateToSatoshi, pay2tr(localRevocationPubkey.xOnly, Some(offeredHtlcTree))), localRevocationPubkey.xOnly, offeredHtlcTree, OutHtlc(htlc)
@ -658,8 +664,8 @@ object Transactions {
} }
trimReceivedHtlcs(localDustLimit, spec, commitmentFormat).foreach { htlc => trimReceivedHtlcs(localDustLimit, spec, commitmentFormat).foreach { htlc =>
commitmentFormat.useTaproot match { commitmentFormat match {
case true => case SimpleTaprootChannelCommitmentFormat =>
val receivedHtlcTree = Scripts.Taproot.receivedHtlcScriptTree(localHtlcPubkey, remoteHtlcPubkey, htlc.add.paymentHash, htlc.add.cltvExpiry) val receivedHtlcTree = Scripts.Taproot.receivedHtlcScriptTree(localHtlcPubkey, remoteHtlcPubkey, htlc.add.paymentHash, htlc.add.cltvExpiry)
outputs.append(CommitmentOutputLink( outputs.append(CommitmentOutputLink(
TxOut(htlc.add.amountMsat.truncateToSatoshi, pay2tr(localRevocationPubkey.xOnly, Some(receivedHtlcTree))), localRevocationPubkey.xOnly, receivedHtlcTree, InHtlc(htlc) TxOut(htlc.add.amountMsat.truncateToSatoshi, pay2tr(localRevocationPubkey.xOnly, Some(receivedHtlcTree))), localRevocationPubkey.xOnly, receivedHtlcTree, InHtlc(htlc)
@ -679,13 +685,14 @@ object Transactions {
} // NB: we don't care if values are < 0, they will be trimmed if they are < dust limit anyway } // NB: we don't care if values are < 0, they will be trimmed if they are < dust limit anyway
if (toLocalAmount >= localDustLimit) { if (toLocalAmount >= localDustLimit) {
if (commitmentFormat.useTaproot) { commitmentFormat match {
case SimpleTaprootChannelCommitmentFormat =>
val toLocalScriptTree = Scripts.Taproot.toLocalScriptTree(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey) val toLocalScriptTree = Scripts.Taproot.toLocalScriptTree(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey)
outputs.append(CommitmentOutputLink( outputs.append(CommitmentOutputLink(
TxOut(toLocalAmount, pay2tr(XonlyPublicKey(NUMS_POINT), Some(toLocalScriptTree))), TxOut(toLocalAmount, pay2tr(XonlyPublicKey(NUMS_POINT), Some(toLocalScriptTree))),
NUMS_POINT.xOnly, toLocalScriptTree, NUMS_POINT.xOnly, toLocalScriptTree,
ToLocal)) ToLocal))
} else { case _ =>
outputs.append(CommitmentOutputLink( outputs.append(CommitmentOutputLink(
TxOut(toLocalAmount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey))), TxOut(toLocalAmount, pay2wsh(toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey))),
toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey), toLocalDelayed(localRevocationPubkey, toLocalDelay, localDelayedPaymentPubkey),
@ -695,7 +702,7 @@ object Transactions {
if (toRemoteAmount >= localDustLimit) { if (toRemoteAmount >= localDustLimit) {
commitmentFormat match { commitmentFormat match {
case _ if commitmentFormat.useTaproot => case SimpleTaprootChannelCommitmentFormat =>
val toRemoteScriptTree = Scripts.Taproot.toRemoteScriptTree(remotePaymentPubkey) val toRemoteScriptTree = Scripts.Taproot.toRemoteScriptTree(remotePaymentPubkey)
outputs.append(CommitmentOutputLink( outputs.append(CommitmentOutputLink(
TxOut(toRemoteAmount, pay2tr(XonlyPublicKey(NUMS_POINT), Some(toRemoteScriptTree))), TxOut(toRemoteAmount, pay2tr(XonlyPublicKey(NUMS_POINT), Some(toRemoteScriptTree))),
@ -713,7 +720,7 @@ object Transactions {
} }
commitmentFormat match { commitmentFormat match {
case _ if commitmentFormat.useTaproot => case SimpleTaprootChannelCommitmentFormat =>
if (toLocalAmount >= localDustLimit || hasHtlcs) { if (toLocalAmount >= localDustLimit || hasHtlcs) {
outputs.append( outputs.append(
CommitmentOutputLink.TaprootLink( CommitmentOutputLink.TaprootLink(
@ -1456,8 +1463,7 @@ object Transactions {
case t: InputInfo.TaprootInput => case t: InputInfo.TaprootInput =>
t.scriptTree_opt match { t.scriptTree_opt match {
case Some(htlcTree: ScriptTree.Branch) => case Some(htlcTree: ScriptTree.Branch) =>
val sigHash = (SigHash.SIGHASH_SINGLE | SigHash.SIGHASH_ANYONECANPAY).toByte Script.witnessScriptPathPay2tr(t.internalKey, htlcTree.getRight.asInstanceOf[ScriptTree.Leaf], ScriptWitness(Seq(Taproot.encodeSig(remoteSig, SigHash.SIGHASH_SINGLE | SigHash.SIGHASH_ANYONECANPAY), Taproot.encodeSig(localSig, SIGHASH_DEFAULT), paymentPreimage)), htlcTree)
Script.witnessScriptPathPay2tr(t.internalKey, htlcTree.getRight.asInstanceOf[ScriptTree.Leaf], ScriptWitness(Seq(remoteSig :+ sigHash, localSig :+ sigHash, paymentPreimage)), htlcTree)
case _ => throw new IllegalArgumentException("unexpected script tree leaf when building htlc successTx tx") case _ => throw new IllegalArgumentException("unexpected script tree leaf when building htlc successTx tx")
} }
} }
@ -1470,8 +1476,7 @@ object Transactions {
case t: InputInfo.TaprootInput => case t: InputInfo.TaprootInput =>
t.scriptTree_opt match { t.scriptTree_opt match {
case Some(htlcTree: ScriptTree.Branch) => case Some(htlcTree: ScriptTree.Branch) =>
val sigHash = (SigHash.SIGHASH_SINGLE | SigHash.SIGHASH_ANYONECANPAY).toByte Script.witnessScriptPathPay2tr(t.internalKey, htlcTree.getLeft.asInstanceOf[ScriptTree.Leaf], ScriptWitness(Seq(Taproot.encodeSig(remoteSig, SigHash.SIGHASH_SINGLE | SigHash.SIGHASH_ANYONECANPAY), Taproot.encodeSig(localSig, SIGHASH_DEFAULT))), htlcTree)
Script.witnessScriptPathPay2tr(t.internalKey, htlcTree.getLeft.asInstanceOf[ScriptTree.Leaf], ScriptWitness(Seq(remoteSig :+ sigHash, localSig :+ sigHash)), htlcTree)
case _ => throw new IllegalArgumentException("unexpected script tree leaf when building htlc timeout tx") case _ => throw new IllegalArgumentException("unexpected script tree leaf when building htlc timeout tx")
} }
} }

View file

@ -1572,7 +1572,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
val remoteCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.fullySignedLocalCommitTx(bob.underlyingActor.nodeParams.channelKeyManager) val remoteCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.fullySignedLocalCommitTx(bob.underlyingActor.nodeParams.channelKeyManager)
bob.stateData.asInstanceOf[DATA_NORMAL].commitments.params.commitmentFormat match { bob.stateData.asInstanceOf[DATA_NORMAL].commitments.params.commitmentFormat match {
case Transactions.DefaultCommitmentFormat => assert(remoteCommitTx.tx.txOut.size == 4) case Transactions.DefaultCommitmentFormat => assert(remoteCommitTx.tx.txOut.size == 4)
case _: AnchorOutputsCommitmentFormat => assert(remoteCommitTx.tx.txOut.size == 6) case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => assert(remoteCommitTx.tx.txOut.size == 6)
} }
probe.send(alice, WatchFundingSpentTriggered(remoteCommitTx.tx)) probe.send(alice, WatchFundingSpentTriggered(remoteCommitTx.tx))
@ -1583,7 +1583,7 @@ class ReplaceableTxPublisherSpec extends TestKitBaseClass with AnyFunSuiteLike w
bob.stateData.asInstanceOf[DATA_NORMAL].commitments.params.commitmentFormat match { bob.stateData.asInstanceOf[DATA_NORMAL].commitments.params.commitmentFormat match {
case Transactions.DefaultCommitmentFormat => () case Transactions.DefaultCommitmentFormat => ()
case _: AnchorOutputsCommitmentFormat => alice2blockchain.expectMsgType[PublishReplaceableTx] // claim anchor case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => alice2blockchain.expectMsgType[PublishReplaceableTx] // claim anchor
} }
if (!bob.stateData.asInstanceOf[DATA_NORMAL].commitments.params.channelFeatures.paysDirectlyToWallet) alice2blockchain.expectMsgType[PublishFinalTx] // claim main output if (!bob.stateData.asInstanceOf[DATA_NORMAL].commitments.params.channelFeatures.paysDirectlyToWallet) alice2blockchain.expectMsgType[PublishFinalTx] // claim main output
val claimHtlcSuccess = alice2blockchain.expectMsgType[PublishReplaceableTx] val claimHtlcSuccess = alice2blockchain.expectMsgType[PublishReplaceableTx]

View file

@ -590,7 +590,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
// all htlcs success/timeout should be published as-is, without claiming their outputs // all htlcs success/timeout should be published as-is, without claiming their outputs
s2blockchain.expectMsgAllOf(localCommitPublished.htlcTxs.values.toSeq.collect { case Some(tx) => TxPublisher.PublishFinalTx(tx, tx.fee, Some(commitTx.txid)) }: _*) s2blockchain.expectMsgAllOf(localCommitPublished.htlcTxs.values.toSeq.collect { case Some(tx) => TxPublisher.PublishFinalTx(tx, tx.fee, Some(commitTx.txid)) }: _*)
assert(localCommitPublished.claimHtlcDelayedTxs.isEmpty) assert(localCommitPublished.claimHtlcDelayedTxs.isEmpty)
case _: Transactions.AnchorOutputsCommitmentFormat => case _: Transactions.AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat =>
// all htlcs success/timeout should be published as replaceable txs, without claiming their outputs // all htlcs success/timeout should be published as replaceable txs, without claiming their outputs
val htlcTxs = localCommitPublished.htlcTxs.values.collect { case Some(tx: HtlcTx) => tx } val htlcTxs = localCommitPublished.htlcTxs.values.collect { case Some(tx: HtlcTx) => tx }
val publishedTxs = htlcTxs.map(_ => s2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx]) val publishedTxs = htlcTxs.map(_ => s2blockchain.expectMsgType[TxPublisher.PublishReplaceableTx])
@ -629,7 +629,7 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
// If anchor outputs is used, we use the anchor output to bump the fees if necessary. // If anchor outputs is used, we use the anchor output to bump the fees if necessary.
closingData.commitments.params.commitmentFormat match { closingData.commitments.params.commitmentFormat match {
case _: AnchorOutputsCommitmentFormat => case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat =>
val anchorTx = s2blockchain.expectMsgType[PublishReplaceableTx] val anchorTx = s2blockchain.expectMsgType[PublishReplaceableTx]
assert(anchorTx.txInfo.isInstanceOf[ClaimLocalAnchorOutputTx]) assert(anchorTx.txInfo.isInstanceOf[ClaimLocalAnchorOutputTx])
case Transactions.DefaultCommitmentFormat => () case Transactions.DefaultCommitmentFormat => ()

View file

@ -55,7 +55,7 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
val (initiatorPushAmount, nonInitiatorPushAmount) = if (test.tags.contains("both_push_amount")) (Some(TestConstants.initiatorPushAmount), Some(TestConstants.nonInitiatorPushAmount)) else (None, None) val (initiatorPushAmount, nonInitiatorPushAmount) = if (test.tags.contains("both_push_amount")) (Some(TestConstants.initiatorPushAmount), Some(TestConstants.nonInitiatorPushAmount)) else (None, None)
val commitFeerate = channelType.commitmentFormat match { val commitFeerate = channelType.commitmentFormat match {
case Transactions.DefaultCommitmentFormat => TestConstants.feeratePerKw case Transactions.DefaultCommitmentFormat => TestConstants.feeratePerKw
case _: Transactions.AnchorOutputsCommitmentFormat => TestConstants.anchorOutputsFeeratePerKw case _: Transactions.AnchorOutputsCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat => TestConstants.anchorOutputsFeeratePerKw
} }
val aliceListener = TestProbe() val aliceListener = TestProbe()
val bobListener = TestProbe() val bobListener = TestProbe()

View file

@ -61,7 +61,7 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS
val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags, channelFlags) val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags, channelFlags)
val commitFeerate = channelType.commitmentFormat match { val commitFeerate = channelType.commitmentFormat match {
case Transactions.DefaultCommitmentFormat => TestConstants.feeratePerKw case Transactions.DefaultCommitmentFormat => TestConstants.feeratePerKw
case _: Transactions.AnchorOutputsCommitmentFormat => TestConstants.anchorOutputsFeeratePerKw case _: Transactions.AnchorOutputsCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat => TestConstants.anchorOutputsFeeratePerKw
} }
val aliceInit = Init(aliceParams.initFeatures) val aliceInit = Init(aliceParams.initFeatures)
val bobInit = Init(bobParams.initFeatures) val bobInit = Init(bobParams.initFeatures)

View file

@ -53,7 +53,7 @@ class WaitForChannelReadyStateSpec extends TestKitBaseClass with FixtureAnyFunSu
val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags, channelFlags) val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags, channelFlags)
val commitFeerate = channelType.commitmentFormat match { val commitFeerate = channelType.commitmentFormat match {
case Transactions.DefaultCommitmentFormat => TestConstants.feeratePerKw case Transactions.DefaultCommitmentFormat => TestConstants.feeratePerKw
case _: Transactions.AnchorOutputsCommitmentFormat => TestConstants.anchorOutputsFeeratePerKw case _: Transactions.AnchorOutputsCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat => TestConstants.anchorOutputsFeeratePerKw
} }
val pushMsat = if (test.tags.contains(ChannelStateTestsTags.NoPushAmount)) None else Some(TestConstants.initiatorPushAmount) val pushMsat = if (test.tags.contains(ChannelStateTestsTags.NoPushAmount)) None else Some(TestConstants.initiatorPushAmount)
val aliceInit = Init(aliceParams.initFeatures) val aliceInit = Init(aliceParams.initFeatures)

View file

@ -72,7 +72,7 @@ class WaitForDualFundingConfirmedStateSpec extends TestKitBaseClass with Fixture
val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags, channelFlags) val (aliceParams, bobParams, channelType) = computeFeatures(setup, test.tags, channelFlags)
val commitFeerate = channelType.commitmentFormat match { val commitFeerate = channelType.commitmentFormat match {
case Transactions.DefaultCommitmentFormat => TestConstants.feeratePerKw case Transactions.DefaultCommitmentFormat => TestConstants.feeratePerKw
case _: Transactions.AnchorOutputsCommitmentFormat => TestConstants.anchorOutputsFeeratePerKw case _: Transactions.AnchorOutputsCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat => TestConstants.anchorOutputsFeeratePerKw
} }
val aliceInit = Init(aliceParams.initFeatures) val aliceInit = Init(aliceParams.initFeatures)
val bobInit = Init(bobParams.initFeatures) val bobInit = Init(bobParams.initFeatures)

View file

@ -990,7 +990,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// Bob publishes the latest commit tx. // Bob publishes the latest commit tx.
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
channelFeatures.commitmentFormat match { channelFeatures.commitmentFormat match {
case _: AnchorOutputsCommitmentFormat => assert(bobCommitTx.txOut.length == 7) // two main outputs + two anchors + 3 HTLCs case _: AnchorOutputsCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat => assert(bobCommitTx.txOut.length == 7) // two main outputs + two anchors + 3 HTLCs
case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 5) // two main outputs + 3 HTLCs case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 5) // two main outputs + 3 HTLCs
} }
val closingState = remoteClose(bobCommitTx, alice, alice2blockchain) val closingState = remoteClose(bobCommitTx, alice, alice2blockchain)
@ -1124,7 +1124,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// Bob publishes the next commit tx. // Bob publishes the next commit tx.
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
channelFeatures.commitmentFormat match { channelFeatures.commitmentFormat match {
case _: AnchorOutputsCommitmentFormat => assert(bobCommitTx.txOut.length == 7) // two main outputs + two anchors + 3 HTLCs case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => assert(bobCommitTx.txOut.length == 7) // two main outputs + two anchors + 3 HTLCs
case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 5) // two main outputs + 3 HTLCs case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 5) // two main outputs + 3 HTLCs
} }
val closingState = remoteClose(bobCommitTx, alice, alice2blockchain) val closingState = remoteClose(bobCommitTx, alice, alice2blockchain)
@ -1319,7 +1319,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// bob is nice and publishes its commitment // bob is nice and publishes its commitment
val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx val bobCommitTx = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx
channelFeatures.commitmentFormat match { channelFeatures.commitmentFormat match {
case _: AnchorOutputsCommitmentFormat => assert(bobCommitTx.txOut.length == 6) // two main outputs + two anchors + 2 HTLCs case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => assert(bobCommitTx.txOut.length == 6) // two main outputs + two anchors + 2 HTLCs
case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 4) // two main outputs + 2 HTLCs case DefaultCommitmentFormat => assert(bobCommitTx.txOut.length == 4) // two main outputs + 2 HTLCs
} }
alice ! WatchFundingSpentTriggered(bobCommitTx) alice ! WatchFundingSpentTriggered(bobCommitTx)
@ -1393,7 +1393,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
// Bob's first commit tx doesn't contain any htlc // Bob's first commit tx doesn't contain any htlc
val localCommit1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit val localCommit1 = bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit
channelFeatures.commitmentFormat match { channelFeatures.commitmentFormat match {
case _: AnchorOutputsCommitmentFormat => assert(localCommit1.commitTxAndRemoteSig.commitTx.tx.txOut.size == 4) // 2 main outputs + 2 anchors case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => assert(localCommit1.commitTxAndRemoteSig.commitTx.tx.txOut.size == 4) // 2 main outputs + 2 anchors
case DefaultCommitmentFormat => assert(localCommit1.commitTxAndRemoteSig.commitTx.tx.txOut.size == 2) // 2 main outputs case DefaultCommitmentFormat => assert(localCommit1.commitTxAndRemoteSig.commitTx.tx.txOut.size == 2) // 2 main outputs
} }
@ -1409,7 +1409,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == localCommit2.commitTxAndRemoteSig.commitTx.tx.txOut.size) assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == localCommit2.commitTxAndRemoteSig.commitTx.tx.txOut.size)
channelFeatures.commitmentFormat match { channelFeatures.commitmentFormat match {
case _: AnchorOutputsCommitmentFormat => assert(localCommit2.commitTxAndRemoteSig.commitTx.tx.txOut.size == 6) case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => assert(localCommit2.commitTxAndRemoteSig.commitTx.tx.txOut.size == 6)
case DefaultCommitmentFormat => assert(localCommit2.commitTxAndRemoteSig.commitTx.tx.txOut.size == 4) case DefaultCommitmentFormat => assert(localCommit2.commitTxAndRemoteSig.commitTx.tx.txOut.size == 4)
} }
@ -1425,7 +1425,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == localCommit3.commitTxAndRemoteSig.commitTx.tx.txOut.size) assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == localCommit3.commitTxAndRemoteSig.commitTx.tx.txOut.size)
channelFeatures.commitmentFormat match { channelFeatures.commitmentFormat match {
case _: AnchorOutputsCommitmentFormat => assert(localCommit3.commitTxAndRemoteSig.commitTx.tx.txOut.size == 8) case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => assert(localCommit3.commitTxAndRemoteSig.commitTx.tx.txOut.size == 8)
case DefaultCommitmentFormat => assert(localCommit3.commitTxAndRemoteSig.commitTx.tx.txOut.size == 6) case DefaultCommitmentFormat => assert(localCommit3.commitTxAndRemoteSig.commitTx.tx.txOut.size == 6)
} }
@ -1439,7 +1439,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == localCommit4.commitTxAndRemoteSig.commitTx.tx.txOut.size) assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == localCommit4.commitTxAndRemoteSig.commitTx.tx.txOut.size)
channelFeatures.commitmentFormat match { channelFeatures.commitmentFormat match {
case _: AnchorOutputsCommitmentFormat => assert(localCommit4.commitTxAndRemoteSig.commitTx.tx.txOut.size == 4) case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => assert(localCommit4.commitTxAndRemoteSig.commitTx.tx.txOut.size == 4)
case DefaultCommitmentFormat => assert(localCommit4.commitTxAndRemoteSig.commitTx.tx.txOut.size == 2) case DefaultCommitmentFormat => assert(localCommit4.commitTxAndRemoteSig.commitTx.tx.txOut.size == 2)
} }
@ -1820,7 +1820,7 @@ class ClosingStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
import f._ import f._
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.params.channelFeatures == channelFeatures) assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.params.channelFeatures == channelFeatures)
val initOutputCount = channelFeatures.commitmentFormat match { val initOutputCount = channelFeatures.commitmentFormat match {
case _: AnchorOutputsCommitmentFormat => 4 case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => 4
case DefaultCommitmentFormat => 2 case DefaultCommitmentFormat => 2
} }
assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == initOutputCount) assert(bob.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.commitTxAndRemoteSig.commitTx.tx.txOut.size == initOutputCount)

View file

@ -35,7 +35,7 @@ import fr.acinq.eclair.payment.receive.MultiPartHandler.ReceiveStandardPayment
import fr.acinq.eclair.payment.receive.{ForwardHandler, PaymentHandler} import fr.acinq.eclair.payment.receive.{ForwardHandler, PaymentHandler}
import fr.acinq.eclair.payment.send.PaymentInitiator.SendPaymentToNode import fr.acinq.eclair.payment.send.PaymentInitiator.SendPaymentToNode
import fr.acinq.eclair.router.Router import fr.acinq.eclair.router.Router
import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, CommitmentFormat, DefaultCommitmentFormat, TxOwner} import fr.acinq.eclair.transactions.Transactions.{AnchorOutputsCommitmentFormat, CommitmentFormat, DefaultCommitmentFormat, SimpleTaprootChannelCommitmentFormat, TxOwner}
import fr.acinq.eclair.transactions.{OutgoingHtlc, Scripts, Transactions} import fr.acinq.eclair.transactions.{OutgoingHtlc, Scripts, Transactions}
import fr.acinq.eclair.wire.protocol._ import fr.acinq.eclair.wire.protocol._
import fr.acinq.eclair.{MilliSatoshi, MilliSatoshiLong, randomBytes32} import fr.acinq.eclair.{MilliSatoshi, MilliSatoshiLong, randomBytes32}
@ -181,7 +181,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
generateBlocks(25, Some(minerAddress)) generateBlocks(25, Some(minerAddress))
val expectedTxCountC = 1 // C should have 1 recv transaction: its main output val expectedTxCountC = 1 // C should have 1 recv transaction: its main output
val expectedTxCountF = commitmentFormat match { val expectedTxCountF = commitmentFormat match {
case _: AnchorOutputsCommitmentFormat => 2 // F should have 2 recv transactions: the redeemed htlc and its main output case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => 2 // F should have 2 recv transactions: the redeemed htlc and its main output
case Transactions.DefaultCommitmentFormat => 1 // F's main output uses static_remotekey case Transactions.DefaultCommitmentFormat => 1 // F's main output uses static_remotekey
} }
awaitCond({ awaitCond({
@ -221,7 +221,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
// we then generate enough blocks so that F gets its htlc-success delayed output // we then generate enough blocks so that F gets its htlc-success delayed output
generateBlocks(25, Some(minerAddress)) generateBlocks(25, Some(minerAddress))
val expectedTxCountC = commitmentFormat match { val expectedTxCountC = commitmentFormat match {
case _: AnchorOutputsCommitmentFormat => 1 // C should have 1 recv transaction: its main output case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => 1 // C should have 1 recv transaction: its main output
case Transactions.DefaultCommitmentFormat => 0 // C's main output uses static_remotekey case Transactions.DefaultCommitmentFormat => 0 // C's main output uses static_remotekey
} }
val expectedTxCountF = 2 // F should have 2 recv transactions: the redeemed htlc and its main output val expectedTxCountF = 2 // F should have 2 recv transactions: the redeemed htlc and its main output
@ -275,7 +275,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
generateBlocks(25, Some(minerAddress)) generateBlocks(25, Some(minerAddress))
val expectedTxCountC = 2 // C should have 2 recv transactions: its main output and the htlc timeout val expectedTxCountC = 2 // C should have 2 recv transactions: its main output and the htlc timeout
val expectedTxCountF = commitmentFormat match { val expectedTxCountF = commitmentFormat match {
case _: AnchorOutputsCommitmentFormat => 1 // F should have 1 recv transaction: its main output case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => 1 // F should have 1 recv transaction: its main output
case Transactions.DefaultCommitmentFormat => 0 // F's main output uses static_remotekey case Transactions.DefaultCommitmentFormat => 0 // F's main output uses static_remotekey
} }
awaitCond({ awaitCond({
@ -330,7 +330,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
// we then generate enough blocks to confirm all delayed transactions // we then generate enough blocks to confirm all delayed transactions
generateBlocks(25, Some(minerAddress)) generateBlocks(25, Some(minerAddress))
val expectedTxCountC = commitmentFormat match { val expectedTxCountC = commitmentFormat match {
case _: AnchorOutputsCommitmentFormat => 2 // C should have 2 recv transactions: its main output and the htlc timeout case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => 2 // C should have 2 recv transactions: its main output and the htlc timeout
case Transactions.DefaultCommitmentFormat => 1 // C's main output uses static_remotekey case Transactions.DefaultCommitmentFormat => 1 // C's main output uses static_remotekey
} }
val expectedTxCountF = 1 // F should have 1 recv transaction: its main output val expectedTxCountF = 1 // F should have 1 recv transaction: its main output
@ -405,7 +405,7 @@ abstract class ChannelIntegrationSpec extends IntegrationSpec {
val localCommitF = commitmentsF.latest.localCommit val localCommitF = commitmentsF.latest.localCommit
commitmentFormat match { commitmentFormat match {
case Transactions.DefaultCommitmentFormat => assert(localCommitF.commitTxAndRemoteSig.commitTx.tx.txOut.size == 6) case Transactions.DefaultCommitmentFormat => assert(localCommitF.commitTxAndRemoteSig.commitTx.tx.txOut.size == 6)
case _: Transactions.AnchorOutputsCommitmentFormat => assert(localCommitF.commitTxAndRemoteSig.commitTx.tx.txOut.size == 8) case _: Transactions.AnchorOutputsCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat => assert(localCommitF.commitTxAndRemoteSig.commitTx.tx.txOut.size == 8)
} }
val outgoingHtlcExpiry = localCommitF.spec.htlcs.collect { case OutgoingHtlc(add) => add.cltvExpiry }.max val outgoingHtlcExpiry = localCommitF.spec.htlcs.collect { case OutgoingHtlc(add) => add.cltvExpiry }.max
val htlcTimeoutTxs = localCommitF.htlcTxsAndRemoteSigs.collect { case h@HtlcTxAndRemoteSig(_: Transactions.HtlcTimeoutTx, _) => h } val htlcTimeoutTxs = localCommitF.htlcTxsAndRemoteSigs.collect { case h@HtlcTxAndRemoteSig(_: Transactions.HtlcTimeoutTx, _) => h }

View file

@ -770,14 +770,15 @@ class TransactionsSpec extends AnyFunSuite with Logging {
val commitTxNumber = 0x404142434445L 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, commitmentFormat) val outputs = makeCommitTxOutputs(localPaysCommitTxFees = true, localDustLimit, localRevocationPriv.publicKey, toLocalDelay, localDelayedPaymentPriv.publicKey, remotePaymentPriv.publicKey, localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localFundingPriv.publicKey, remoteFundingPriv.publicKey, spec, commitmentFormat)
val txInfo = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.publicKey, remotePaymentPriv.publicKey, localIsChannelOpener = true, outputs) val txInfo = makeCommitTx(commitInput, commitTxNumber, localPaymentPriv.publicKey, remotePaymentPriv.publicKey, localIsChannelOpener = true, outputs)
val commitTx = if (commitmentFormat.useTaproot) { val commitTx = commitmentFormat match {
case SimpleTaprootChannelCommitmentFormat =>
val Right(sig) = for { val Right(sig) = for {
localPartialSig <- Musig2.signTaprootInput(localFundingPriv, txInfo.tx, 0, Seq(fundingOutput), publicKeys, secretLocalNonce, publicNonces, None) localPartialSig <- Musig2.signTaprootInput(localFundingPriv, txInfo.tx, 0, Seq(fundingOutput), publicKeys, secretLocalNonce, publicNonces, None)
remotePartialSig <- Musig2.signTaprootInput(remoteFundingPriv, txInfo.tx, 0, Seq(fundingOutput), publicKeys, secretRemoteNonce, publicNonces, None) remotePartialSig <- Musig2.signTaprootInput(remoteFundingPriv, txInfo.tx, 0, Seq(fundingOutput), publicKeys, secretRemoteNonce, publicNonces, None)
sig <- Musig2.aggregateTaprootSignatures(Seq(localPartialSig, remotePartialSig), txInfo.tx, 0, Seq(fundingOutput), publicKeys, publicNonces, None) sig <- Musig2.aggregateTaprootSignatures(Seq(localPartialSig, remotePartialSig), txInfo.tx, 0, Seq(fundingOutput), publicKeys, publicNonces, None)
} yield sig } yield sig
Transactions.addAggregatedSignature(txInfo, sig) Transactions.addAggregatedSignature(txInfo, sig)
} else { case _ =>
val localSig = txInfo.sign(localPaymentPriv, TxOwner.Local, commitmentFormat) val localSig = txInfo.sign(localPaymentPriv, TxOwner.Local, commitmentFormat)
val remoteSig = txInfo.sign(remotePaymentPriv, TxOwner.Remote, commitmentFormat) val remoteSig = txInfo.sign(remotePaymentPriv, TxOwner.Remote, commitmentFormat)
Transactions.addSigs(txInfo, localFundingPriv.publicKey, remoteFundingPriv.publicKey, localSig, remoteSig) Transactions.addSigs(txInfo, localFundingPriv.publicKey, remoteFundingPriv.publicKey, localSig, remoteSig)
@ -815,10 +816,9 @@ class TransactionsSpec extends AnyFunSuite with Logging {
} }
{ {
// local spends local anchor // local spends local anchor
val anchorKey = if (commitmentFormat.useTaproot) { val anchorKey = commitmentFormat match {
localDelayedPaymentPriv case SimpleTaprootChannelCommitmentFormat => localDelayedPaymentPriv
} else { case _ => localFundingPriv
localFundingPriv
} }
val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, anchorKey.publicKey, ConfirmationTarget.Absolute(BlockHeight(0))) val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, anchorKey.publicKey, ConfirmationTarget.Absolute(BlockHeight(0)))
assert(checkSpendable(claimAnchorOutputTx).isFailure) assert(checkSpendable(claimAnchorOutputTx).isFailure)
@ -828,10 +828,9 @@ class TransactionsSpec extends AnyFunSuite with Logging {
} }
{ {
// remote spends remote anchor // remote spends remote anchor
val anchorKey = if (commitmentFormat.useTaproot) { val anchorKey = commitmentFormat match {
remotePaymentPriv case SimpleTaprootChannelCommitmentFormat => remotePaymentPriv
} else { case _ => remoteFundingPriv
remoteFundingPriv
} }
val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, anchorKey.publicKey, ConfirmationTarget.Absolute(BlockHeight(0))) val Right(claimAnchorOutputTx) = makeClaimLocalAnchorOutputTx(commitTx.tx, anchorKey.publicKey, ConfirmationTarget.Absolute(BlockHeight(0)))
assert(checkSpendable(claimAnchorOutputTx).isFailure) assert(checkSpendable(claimAnchorOutputTx).isFailure)
@ -950,10 +949,11 @@ class TransactionsSpec extends AnyFunSuite with Logging {
val Some(htlcOutputIndex) = commitTxOutputs.map(_.filter[OutHtlc]).zipWithIndex.collectFirst { val Some(htlcOutputIndex) = commitTxOutputs.map(_.filter[OutHtlc]).zipWithIndex.collectFirst {
case (Some(co), outputIndex) if co.commitmentOutput.outgoingHtlc.add.id == htlc1.id => outputIndex case (Some(co), outputIndex) if co.commitmentOutput.outgoingHtlc.add.id == htlc1.id => outputIndex
} }
val Right(htlcPenaltyTx) = if (commitmentFormat.useTaproot) { val Right(htlcPenaltyTx) = commitmentFormat match {
case SimpleTaprootChannelCommitmentFormat =>
val scriptTree = Taproot.offeredHtlcScriptTree(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, htlc1.paymentHash) val scriptTree = Taproot.offeredHtlcScriptTree(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, htlc1.paymentHash)
makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, localRevocationPriv.publicKey.xOnly, Some(scriptTree), localDustLimit, finalPubKeyScript, feeratePerKw) makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, localRevocationPriv.publicKey.xOnly, Some(scriptTree), localDustLimit, finalPubKeyScript, feeratePerKw)
} else { case _ =>
val script = Script.write(Scripts.htlcOffered(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, Crypto.ripemd160(htlc1.paymentHash), commitmentFormat)) val script = Script.write(Scripts.htlcOffered(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, Crypto.ripemd160(htlc1.paymentHash), commitmentFormat))
makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, script, localDustLimit, finalPubKeyScript, feeratePerKw) makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, script, localDustLimit, finalPubKeyScript, feeratePerKw)
} }
@ -967,10 +967,11 @@ class TransactionsSpec extends AnyFunSuite with Logging {
val Some(htlcOutputIndex) = commitTxOutputs.map(_.filter[InHtlc]).zipWithIndex.collectFirst { val Some(htlcOutputIndex) = commitTxOutputs.map(_.filter[InHtlc]).zipWithIndex.collectFirst {
case (Some(co), outputIndex) if co.commitmentOutput.incomingHtlc.add.id == htlc.id => outputIndex case (Some(co), outputIndex) if co.commitmentOutput.incomingHtlc.add.id == htlc.id => outputIndex
} }
val Right(htlcPenaltyTx) = if (commitmentFormat.useTaproot) { val Right(htlcPenaltyTx) = commitmentFormat match {
case SimpleTaprootChannelCommitmentFormat =>
val scriptTree = Taproot.receivedHtlcScriptTree(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, htlc.paymentHash, htlc.cltvExpiry) val scriptTree = Taproot.receivedHtlcScriptTree(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, htlc.paymentHash, htlc.cltvExpiry)
makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, localRevocationPriv.publicKey.xOnly, Some(scriptTree), localDustLimit, finalPubKeyScript, feeratePerKw) makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, localRevocationPriv.publicKey.xOnly, Some(scriptTree), localDustLimit, finalPubKeyScript, feeratePerKw)
} else { case _ =>
val script = Script.write(Scripts.htlcReceived(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, Crypto.ripemd160(htlc.paymentHash), htlc.cltvExpiry, commitmentFormat)) val script = Script.write(Scripts.htlcReceived(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, localRevocationPriv.publicKey, Crypto.ripemd160(htlc.paymentHash), htlc.cltvExpiry, commitmentFormat))
makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, script, localDustLimit, finalPubKeyScript, feeratePerKw) makeHtlcPenaltyTx(commitTx.tx, htlcOutputIndex, script, localDustLimit, finalPubKeyScript, feeratePerKw)
} }
@ -1120,9 +1121,8 @@ class TransactionsSpec extends AnyFunSuite with Logging {
txOut = TxOut(25_000.sat, Taproot.htlcDelayed(localDelayedPaymentPriv.publicKey, toLocalDelay, localRevocationPriv.publicKey)) :: Nil, txOut = TxOut(25_000.sat, Taproot.htlcDelayed(localDelayedPaymentPriv.publicKey, toLocalDelay, localRevocationPriv.publicKey)) :: Nil,
lockTime = 300) lockTime = 300)
val scriptTree = Taproot.offeredHtlcScriptTree(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, paymentHash) val scriptTree = Taproot.offeredHtlcScriptTree(localHtlcPriv.publicKey, remoteHtlcPriv.publicKey, paymentHash)
val sigHash = SigHash.SIGHASH_SINGLE | SigHash.SIGHASH_ANYONECANPAY val localSig = Taproot.encodeSig(Transaction.signInputTaprootScriptPath(localHtlcPriv, tx, 0, Seq(commitTx.txOut(4)), SigHash.SIGHASH_SINGLE | SigHash.SIGHASH_ANYONECANPAY, scriptTree.getLeft.hash()), SigHash.SIGHASH_SINGLE | SigHash.SIGHASH_ANYONECANPAY)
val localSig = Taproot.encodeSig(Transaction.signInputTaprootScriptPath(localHtlcPriv, tx, 0, Seq(commitTx.txOut(4)), sigHash, scriptTree.getLeft.hash()), sigHash) val remoteSig = Taproot.encodeSig(Transaction.signInputTaprootScriptPath(remoteHtlcPriv, tx, 0, Seq(commitTx.txOut(4)), SigHash.SIGHASH_DEFAULT, scriptTree.getLeft.hash()), SigHash.SIGHASH_DEFAULT)
val remoteSig = Taproot.encodeSig(Transaction.signInputTaprootScriptPath(remoteHtlcPriv, tx, 0, Seq(commitTx.txOut(4)), sigHash, scriptTree.getLeft.hash()), sigHash)
val witness = Script.witnessScriptPathPay2tr(localRevocationPriv.xOnlyPublicKey(), scriptTree.getLeft.asInstanceOf[ScriptTree.Leaf], ScriptWitness(Seq(remoteSig, localSig)), scriptTree) val witness = Script.witnessScriptPathPay2tr(localRevocationPriv.xOnlyPublicKey(), scriptTree.getLeft.asInstanceOf[ScriptTree.Leaf], ScriptWitness(Seq(remoteSig, localSig)), scriptTree)
tx.updateWitness(0, witness) tx.updateWitness(0, witness)
} }