diff --git a/src/commonMain/kotlin/fr/acinq/lightning/channel/Helpers.kt b/src/commonMain/kotlin/fr/acinq/lightning/channel/Helpers.kt index c37a6a3e0..6aabdd0fe 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/channel/Helpers.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/channel/Helpers.kt @@ -356,7 +356,7 @@ object Helpers { // used only to compute tx weights and estimate fees private val dummyPublicKey by lazy { PrivateKey(ByteArray(32) { 1.toByte() }).publicKey() } - private fun isValidFinalScriptPubkey(scriptPubKey: ByteArray, allowAnySegwit: Boolean): Boolean { + private fun isValidFinalScriptPubkey(scriptPubKey: ByteArray, allowAnySegwit: Boolean, allowOpReturn: Boolean): Boolean { return runTrying { val script = Script.parse(scriptPubKey) when { @@ -366,12 +366,13 @@ object Helpers { Script.isPay2wsh(script) -> true // option_shutdown_anysegwit doesn't cover segwit v0 Script.isNativeWitnessScript(script) && script[0] != OP_0 -> allowAnySegwit + script[0] == OP_RETURN -> allowOpReturn else -> false } }.getOrElse { false } } - fun isValidFinalScriptPubkey(scriptPubKey: ByteVector, allowAnySegwit: Boolean): Boolean = isValidFinalScriptPubkey(scriptPubKey.toByteArray(), allowAnySegwit) + fun isValidFinalScriptPubkey(scriptPubKey: ByteVector, allowAnySegwit: Boolean, allowOpReturn: Boolean): Boolean = isValidFinalScriptPubkey(scriptPubKey.toByteArray(), allowAnySegwit, allowOpReturn) private fun firstClosingFee(commitment: FullCommitment, localScriptPubkey: ByteArray, remoteScriptPubkey: ByteArray, requestedFeerate: ClosingFeerates): ClosingFees { // this is just to estimate the weight which depends on the size of the pubkey scripts @@ -404,8 +405,9 @@ object Helpers { closingFees: ClosingFees ): Pair { val allowAnySegwit = Features.canUseFeature(commitment.params.localParams.features, commitment.params.remoteParams.features, Feature.ShutdownAnySegwit) - require(isValidFinalScriptPubkey(localScriptPubkey, allowAnySegwit)) { "invalid localScriptPubkey" } - require(isValidFinalScriptPubkey(remoteScriptPubkey, allowAnySegwit)) { "invalid remoteScriptPubkey" } + val allowOpReturn = Features.canUseFeature(commitment.params.localParams.features, commitment.params.remoteParams.features, Feature.SimpleClose) + require(isValidFinalScriptPubkey(localScriptPubkey, allowAnySegwit, allowOpReturn)) { "invalid localScriptPubkey" } + require(isValidFinalScriptPubkey(remoteScriptPubkey, allowAnySegwit, allowOpReturn)) { "invalid remoteScriptPubkey" } val dustLimit = commitment.params.localParams.dustLimit.max(commitment.params.remoteParams.dustLimit) val closingTx = Transactions.makeClosingTx(commitment.commitInput, localScriptPubkey, remoteScriptPubkey, commitment.params.localParams.isInitiator, dustLimit, closingFees.preferred, commitment.localCommit.spec) val localClosingSig = Transactions.sign(closingTx, channelKeys.fundingKey(commitment.fundingTxIndex)) @@ -439,17 +441,7 @@ object Helpers { * The various dust limits are detailed in https://github.com/lightningnetwork/lightning-rfc/blob/master/03-transactions.md#dust-limits */ fun checkClosingDustAmounts(closingTx: ClosingTx): Boolean { - return closingTx.tx.txOut.all { txOut -> - val publicKeyScript = Script.parse(txOut.publicKeyScript) - when { - Script.isPay2pkh(publicKeyScript) -> txOut.amount >= 546.sat - Script.isPay2sh(publicKeyScript) -> txOut.amount >= 540.sat - Script.isPay2wpkh(publicKeyScript) -> txOut.amount >= 294.sat - Script.isPay2wsh(publicKeyScript) -> txOut.amount >= 330.sat - Script.isNativeWitnessScript(publicKeyScript) -> txOut.amount >= 354.sat - else -> txOut.amount >= 546.sat - } - } + return closingTx.tx.txOut.all { txOut -> txOut.amount >= Transactions.dustLimit(txOut.publicKeyScript) } } /** diff --git a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt index be6fb9ec9..2633b1689 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt @@ -92,12 +92,13 @@ data class Normal( } is ChannelCommand.Close.MutualClose -> { val allowAnySegwit = Features.canUseFeature(commitments.params.localParams.features, commitments.params.remoteParams.features, Feature.ShutdownAnySegwit) + val allowOpReturn = Features.canUseFeature(commitments.params.localParams.features, commitments.params.remoteParams.features, Feature.SimpleClose) val localScriptPubkey = cmd.scriptPubKey ?: commitments.params.localParams.defaultFinalScriptPubKey when { localShutdown != null -> handleCommandError(cmd, ClosingAlreadyInProgress(channelId), channelUpdate) commitments.changes.localHasUnsignedOutgoingHtlcs() -> handleCommandError(cmd, CannotCloseWithUnsignedOutgoingHtlcs(channelId), channelUpdate) commitments.changes.localHasUnsignedOutgoingUpdateFee() -> handleCommandError(cmd, CannotCloseWithUnsignedOutgoingUpdateFee(channelId), channelUpdate) - !Helpers.Closing.isValidFinalScriptPubkey(localScriptPubkey, allowAnySegwit) -> handleCommandError(cmd, InvalidFinalScript(channelId), channelUpdate) + !Helpers.Closing.isValidFinalScriptPubkey(localScriptPubkey, allowAnySegwit, allowOpReturn) -> handleCommandError(cmd, InvalidFinalScript(channelId), channelUpdate) else -> { val shutdown = Shutdown(channelId, localScriptPubkey) val newState = this@Normal.copy(localShutdown = shutdown, closingFeerates = cmd.feerates) @@ -273,6 +274,7 @@ data class Normal( } is Shutdown -> { val allowAnySegwit = Features.canUseFeature(commitments.params.localParams.features, commitments.params.remoteParams.features, Feature.ShutdownAnySegwit) + val allowOpReturn = Features.canUseFeature(commitments.params.localParams.features, commitments.params.remoteParams.features, Feature.SimpleClose) // they have pending unsigned htlcs => they violated the spec, close the channel // they don't have pending unsigned htlcs // we have pending unsigned htlcs @@ -288,7 +290,7 @@ data class Normal( // there are pending signed changes => go to SHUTDOWN // there are no changes => go to NEGOTIATING when { - !Helpers.Closing.isValidFinalScriptPubkey(cmd.message.scriptPubKey, allowAnySegwit) -> handleLocalError(cmd, InvalidFinalScript(channelId)) + !Helpers.Closing.isValidFinalScriptPubkey(cmd.message.scriptPubKey, allowAnySegwit, allowOpReturn) -> handleLocalError(cmd, InvalidFinalScript(channelId)) commitments.changes.remoteHasUnsignedOutgoingHtlcs() -> handleLocalError(cmd, CannotCloseWithUnsignedOutgoingHtlcs(channelId)) commitments.changes.remoteHasUnsignedOutgoingUpdateFee() -> handleLocalError(cmd, CannotCloseWithUnsignedOutgoingUpdateFee(channelId)) commitments.changes.localHasUnsignedOutgoingHtlcs() -> { @@ -400,7 +402,7 @@ data class Normal( add(ChannelAction.Disconnect) } Pair(this@Normal.copy(spliceStatus = SpliceStatus.None), actions) - } else if (spliceStatus.command.spliceOut?.scriptPubKey?.let { Helpers.Closing.isValidFinalScriptPubkey(it, allowAnySegwit = true) } == false) { + } else if (spliceStatus.command.spliceOut?.scriptPubKey?.let { Helpers.Closing.isValidFinalScriptPubkey(it, allowAnySegwit = true, allowOpReturn = true) } == false) { logger.warning { "cannot do splice: invalid splice-out script" } spliceStatus.command.replyTo.complete(ChannelCommand.Commitment.Splice.Response.Failure.InvalidSpliceOutPubKeyScript) val actions = buildList { diff --git a/src/commonMain/kotlin/fr/acinq/lightning/transactions/Transactions.kt b/src/commonMain/kotlin/fr/acinq/lightning/transactions/Transactions.kt index b826695a4..238095c38 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/transactions/Transactions.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/transactions/Transactions.kt @@ -196,6 +196,7 @@ object Transactions { Script.isPay2wpkh(script) -> 294.sat Script.isPay2wsh(script) -> 330.sat Script.isNativeWitnessScript(script) -> 354.sat + script[0] == OP_RETURN -> 0.sat // OP_RETURN is never dust else -> 546.sat } }.getOrElse { 546.sat } diff --git a/src/commonTest/kotlin/fr/acinq/lightning/channel/HelpersTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/channel/HelpersTestsCommon.kt index 1ebc2f2e1..fb6caecd8 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/channel/HelpersTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/channel/HelpersTestsCommon.kt @@ -62,7 +62,8 @@ class HelpersTestsCommon : LightningTestSuite() { TxOut(540.sat, Script.pay2sh(Hex.decode("0000000000000000000000000000000000000000"))), TxOut(294.sat, Script.pay2wpkh(randomKey().publicKey())), TxOut(330.sat, Script.pay2wsh(Hex.decode("0000000000000000000000000000000000000000"))), - TxOut(354.sat, Script.pay2tr(randomKey().publicKey().xOnly())) + TxOut(354.sat, Script.pay2tr(randomKey().publicKey().xOnly())), + TxOut(0.sat, listOf(OP_RETURN, OP_PUSHDATA(Hex.decode("deadbeef")))), ) fun toClosingTx(txOut: List): Transactions.TransactionWithInputInfo.ClosingTx {