Skip to content

Commit 0180082

Browse files
committed
Add a new commitment format for taproot channels that use the old (and unsafe) anchor output format
1 parent f4175e8 commit 0180082

File tree

12 files changed

+773
-811
lines changed

12 files changed

+773
-811
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/blockchain/fee/OnChainFeeConf.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
2020
import fr.acinq.bitcoin.scalacompat.Satoshi
2121
import fr.acinq.eclair.BlockHeight
2222
import fr.acinq.eclair.transactions.Transactions
23-
import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, SimpleTaprootChannelsStagingCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat}
23+
import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, SimpleTaprootChannelsStagingCommitmentFormat, SimpleTaprootChannelsStagingLegacyCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat}
2424

2525
// @formatter:off
2626
sealed trait ConfirmationPriority extends Ordered[ConfirmationPriority] {
@@ -77,15 +77,15 @@ case class FeerateTolerance(ratioLow: Double, ratioHigh: Double, anchorOutputMax
7777
def isProposedFeerateTooHigh(commitmentFormat: CommitmentFormat, networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = {
7878
commitmentFormat match {
7979
case Transactions.DefaultCommitmentFormat => networkFeerate * ratioHigh < proposedFeerate
80-
case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | UnsafeLegacyAnchorOutputsCommitmentFormat | SimpleTaprootChannelsStagingCommitmentFormat => networkFeerate * ratioHigh < proposedFeerate
80+
case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | UnsafeLegacyAnchorOutputsCommitmentFormat | SimpleTaprootChannelsStagingCommitmentFormat | SimpleTaprootChannelsStagingLegacyCommitmentFormat => networkFeerate * ratioHigh < proposedFeerate
8181
}
8282
}
8383

8484
def isProposedFeerateTooLow(commitmentFormat: CommitmentFormat, networkFeerate: FeeratePerKw, proposedFeerate: FeeratePerKw): Boolean = {
8585
commitmentFormat match {
8686
case Transactions.DefaultCommitmentFormat => proposedFeerate < networkFeerate * ratioLow
8787
// When using anchor outputs, we allow low feerates: fees will be set with CPFP and RBF at broadcast time.
88-
case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | UnsafeLegacyAnchorOutputsCommitmentFormat | SimpleTaprootChannelsStagingCommitmentFormat => false
88+
case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | UnsafeLegacyAnchorOutputsCommitmentFormat | SimpleTaprootChannelsStagingCommitmentFormat | SimpleTaprootChannelsStagingLegacyCommitmentFormat => false
8989
}
9090
}
9191
}

eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ case class ChannelFeatures(features: Set[PermanentChannelFeature]) {
3434
val paysDirectlyToWallet: Boolean = hasFeature(Features.StaticRemoteKey) && !hasFeature(Features.AnchorOutputs) && !hasFeature(Features.AnchorOutputsZeroFeeHtlcTx) && !hasFeature((Features.SimpleTaprootStaging))
3535
/** Legacy option_anchor_outputs is used for Phoenix, because Phoenix doesn't have an on-chain wallet to pay for fees. */
3636
val commitmentFormat: CommitmentFormat = if (hasFeature(Features.SimpleTaprootStaging)) {
37-
SimpleTaprootChannelsStagingCommitmentFormat
37+
if (hasFeature(Features.AnchorOutputs)) SimpleTaprootChannelsStagingLegacyCommitmentFormat
38+
else SimpleTaprootChannelsStagingCommitmentFormat
3839
} else if (hasFeature(Features.AnchorOutputs)) {
3940
UnsafeLegacyAnchorOutputsCommitmentFormat
4041
} else if (hasFeature(Features.AnchorOutputsZeroFeeHtlcTx)) {
@@ -136,7 +137,7 @@ object ChannelTypes {
136137
override def features: Set[ChannelTypeFeature] = Set(
137138
if (scidAlias) Some(Features.ScidAlias) else None,
138139
if (zeroConf) Some(Features.ZeroConf) else None,
139-
Some(Features.SimpleTaprootStaging)
140+
Some(Features.SimpleTaprootStaging),
140141
).flatten
141142
override def paysDirectlyToWallet: Boolean = false
142143
override def commitmentFormat: CommitmentFormat = SimpleTaprootChannelsStagingCommitmentFormat

eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala

Lines changed: 44 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -267,16 +267,15 @@ object LocalCommit {
267267
case class RemoteCommit(index: Long, spec: CommitmentSpec, txid: TxId, remotePerCommitmentPoint: PublicKey) {
268268
def sign(keyManager: ChannelKeyManager, params: ChannelParams, fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo, remoteNonce_opt: Option[IndividualNonce])(implicit log: LoggingAdapter): CommitSig = {
269269
val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(keyManager, params.channelConfig, params.channelFeatures, index, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, remotePerCommitmentPoint, spec)
270-
val (sig, tlvStream) = params.commitmentFormat match {
271-
case SimpleTaprootChannelsStagingCommitmentFormat =>
272-
val localNonce = keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingTxIndex, keyManager.keyPath(params.localParams, params.channelConfig), index)
273-
val Some(remoteNonce) = remoteNonce_opt
274-
val Right(localPartialSigOfRemoteTx) = keyManager.partialSign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), remoteFundingPubKey, TxOwner.Remote, localNonce, remoteNonce)
275-
val tlvStream: TlvStream[CommitSigTlv] = TlvStream(CommitSigTlv.PartialSignatureWithNonceTlv(PartialSignatureWithNonce(localPartialSigOfRemoteTx, localNonce._2)))
276-
(ByteVector64.Zeroes, tlvStream)
277-
case _ =>
278-
val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), TxOwner.Remote, params.commitmentFormat)
279-
(sig, TlvStream[CommitSigTlv]())
270+
val (sig, tlvStream) = if (params.commitmentFormat.useTaproot) {
271+
val localNonce = keyManager.verificationNonce(params.localParams.fundingKeyPath, fundingTxIndex, keyManager.keyPath(params.localParams, params.channelConfig), index)
272+
val Some(remoteNonce) = remoteNonce_opt
273+
val Right(localPartialSigOfRemoteTx) = keyManager.partialSign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), remoteFundingPubKey, TxOwner.Remote, localNonce, remoteNonce)
274+
val tlvStream: TlvStream[CommitSigTlv] = TlvStream(CommitSigTlv.PartialSignatureWithNonceTlv(PartialSignatureWithNonce(localPartialSigOfRemoteTx, localNonce._2)))
275+
(ByteVector64.Zeroes, tlvStream)
276+
} else {
277+
val sig = keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), TxOwner.Remote, params.commitmentFormat)
278+
(sig, TlvStream[CommitSigTlv]())
280279
}
281280
val channelKeyPath = keyManager.keyPath(params.localParams, params.channelConfig)
282281
val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index)
@@ -654,22 +653,19 @@ case class Commitment(fundingTxIndex: Long,
654653
// remote commitment will include all local proposed changes + remote acked changes
655654
val spec = CommitmentSpec.reduce(remoteCommit.spec, changes.remoteChanges.acked, changes.localChanges.proposed)
656655
val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(keyManager, params.channelConfig, params.channelFeatures, remoteCommit.index + 1, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, remoteNextPerCommitmentPoint, spec)
657-
val sig = params.commitmentFormat match {
658-
case SimpleTaprootChannelsStagingCommitmentFormat =>
659-
ByteVector64.Zeroes
660-
case _ =>
661-
keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), TxOwner.Remote, params.commitmentFormat)
656+
val sig = if (params.commitmentFormat.useTaproot) {
657+
ByteVector64.Zeroes
658+
} else {
659+
keyManager.sign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), TxOwner.Remote, params.commitmentFormat)
662660
}
663-
val partialSig: Set[CommitSigTlv] = params.commitmentFormat match {
664-
case SimpleTaprootChannelsStagingCommitmentFormat =>
665-
// we generate a new nonce each time we sign their commit tx
666-
val localNonce = keyManager.signingNonce(params.localParams.fundingKeyPath, fundingTxIndex)
667-
val Some(remoteNonce) = nextRemoteNonce_opt
668-
val Right(psig) = keyManager.partialSign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), remoteFundingPubKey, TxOwner.Remote, localNonce, remoteNonce)
669-
log.debug(s"sendCommit: creating partial sig $psig for remote commit tx ${remoteCommitTx.tx.txid} with remote nonce $remoteNonce and remoteNextPerCommitmentPoint = $remoteNextPerCommitmentPoint")
670-
Set(CommitSigTlv.PartialSignatureWithNonceTlv(PartialSignatureWithNonce(psig, localNonce._2)))
671-
case _ =>
672-
Set.empty
661+
val partialSig: Set[CommitSigTlv] = if (params.commitmentFormat.useTaproot) {
662+
val localNonce = keyManager.signingNonce(params.localParams.fundingKeyPath, fundingTxIndex)
663+
val Some(remoteNonce) = nextRemoteNonce_opt
664+
val Right(psig) = keyManager.partialSign(remoteCommitTx, keyManager.fundingPublicKey(params.localParams.fundingKeyPath, fundingTxIndex), remoteFundingPubKey, TxOwner.Remote, localNonce, remoteNonce)
665+
log.debug(s"sendCommit: creating partial sig $psig for remote commit tx ${remoteCommitTx.tx.txid} with remote nonce $remoteNonce and remoteNextPerCommitmentPoint = $remoteNextPerCommitmentPoint")
666+
Set(CommitSigTlv.PartialSignatureWithNonceTlv(PartialSignatureWithNonce(psig, localNonce._2)))
667+
} else {
668+
Set.empty
673669
}
674670
val sortedHtlcTxs: Seq[TransactionWithInputInfo] = htlcTxs.sortBy(_.input.outPoint.index)
675671
val channelKeyPath = keyManager.keyPath(params.localParams, params.channelConfig)
@@ -1057,12 +1053,11 @@ case class Commitments(params: ChannelParams,
10571053
remoteNextCommitInfo match {
10581054
case Right(_) if !changes.localHasChanges => Left(CannotSignWithoutChanges(channelId))
10591055
case Right(remoteNextPerCommitmentPoint) =>
1060-
val (active1, sigs) = this.params.commitmentFormat match {
1061-
case SimpleTaprootChannelsStagingCommitmentFormat =>
1062-
require(active.size <= nextRemoteNonces.size, s"we have ${active.size} commitments but ${nextRemoteNonces.size} remote musig2 nonces")
1063-
active.zip(nextRemoteNonces).map { case (c, n) => c.sendCommit(keyManager, params, changes, remoteNextPerCommitmentPoint, active.size, Some(n)) } unzip
1064-
case _ =>
1065-
active.map(_.sendCommit(keyManager, params, changes, remoteNextPerCommitmentPoint, active.size, None)).unzip
1056+
val (active1, sigs) = if (this.params.commitmentFormat.useTaproot) {
1057+
require(active.size <= nextRemoteNonces.size, s"we have ${active.size} commitments but ${nextRemoteNonces.size} remote musig2 nonces")
1058+
active.zip(nextRemoteNonces).map { case (c, n) => c.sendCommit(keyManager, params, changes, remoteNextPerCommitmentPoint, active.size, Some(n)) } unzip
1059+
} else {
1060+
active.map(_.sendCommit(keyManager, params, changes, remoteNextPerCommitmentPoint, active.size, None)).unzip
10661061
}
10671062
val commitments1 = copy(
10681063
changes = changes.copy(
@@ -1088,9 +1083,10 @@ case class Commitments(params: ChannelParams,
10881083
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, localCommitIndex + 1)
10891084
// Signatures are sent in order (most recent first), calling `zip` will drop trailing sigs that are for deactivated/pruned commitments.
10901085
val active1 = active.zip(commits).map { case (commitment, commit) =>
1091-
val localNonce_opt = params.commitmentFormat match {
1092-
case SimpleTaprootChannelsStagingCommitmentFormat => Some(keyManager.verificationNonce(params.localParams.fundingKeyPath, commitment.fundingTxIndex, channelKeyPath, localCommitIndex + 1))
1093-
case _ => None
1086+
val localNonce_opt = if (params.commitmentFormat.useTaproot) {
1087+
Some(keyManager.verificationNonce(params.localParams.fundingKeyPath, commitment.fundingTxIndex, channelKeyPath, localCommitIndex + 1))
1088+
} else {
1089+
None
10941090
}
10951091
commitment.receiveCommit(keyManager, params, changes, localPerCommitmentPoint, commit, localNonce_opt) match {
10961092
case Left(f) => return Left(f)
@@ -1100,16 +1096,15 @@ case class Commitments(params: ChannelParams,
11001096
// we will send our revocation preimage + our next revocation hash
11011097
val localPerCommitmentSecret = keyManager.commitmentSecret(channelKeyPath, localCommitIndex)
11021098
val localNextPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, localCommitIndex + 2)
1103-
val tlvStream: TlvStream[RevokeAndAckTlv] = params.commitmentFormat match {
1104-
case SimpleTaprootChannelsStagingCommitmentFormat =>
1105-
val nonces = this.active.map(c => {
1106-
val n = keyManager.verificationNonce(params.localParams.fundingKeyPath, c.fundingTxIndex, channelKeyPath, localCommitIndex + 2)
1107-
log.debug(s"revokeandack: creating verification nonce ${n._2} fundingIndex = ${c.fundingTxIndex} commit index = ${localCommitIndex + 2}")
1108-
n
1109-
})
1110-
TlvStream(RevokeAndAckTlv.NextLocalNoncesTlv(nonces.map(_._2).toList))
1111-
case _ =>
1112-
TlvStream.empty
1099+
val tlvStream: TlvStream[RevokeAndAckTlv] = if (params.commitmentFormat.useTaproot) {
1100+
val nonces = this.active.map(c => {
1101+
val n = keyManager.verificationNonce(params.localParams.fundingKeyPath, c.fundingTxIndex, channelKeyPath, localCommitIndex + 2)
1102+
log.debug(s"revokeandack: creating verification nonce ${n._2} fundingIndex = ${c.fundingTxIndex} commit index = ${localCommitIndex + 2}")
1103+
n
1104+
})
1105+
TlvStream(RevokeAndAckTlv.NextLocalNoncesTlv(nonces.map(_._2).toList))
1106+
} else {
1107+
TlvStream.empty
11131108
}
11141109
val revocation = RevokeAndAck(
11151110
channelId = channelId,
@@ -1132,7 +1127,7 @@ case class Commitments(params: ChannelParams,
11321127
remoteNextCommitInfo match {
11331128
case Right(_) => Left(UnexpectedRevocation(channelId))
11341129
case Left(_) if revocation.perCommitmentSecret.publicKey != active.head.remoteCommit.remotePerCommitmentPoint => Left(InvalidRevocation(channelId))
1135-
case Left(_) if this.params.commitmentFormat == SimpleTaprootChannelsStagingCommitmentFormat && revocation.nexLocalNonces.isEmpty => Left(MissingNextLocalNonce(channelId))
1130+
case Left(_) if this.params.commitmentFormat.useTaproot && revocation.nexLocalNonces.isEmpty => Left(MissingNextLocalNonce(channelId))
11361131
case Left(_) =>
11371132
// Since htlcs are shared across all commitments, we generate the actions only once based on the first commitment.
11381133
val receivedHtlcs = changes.remoteChanges.signed.collect {
@@ -1224,9 +1219,10 @@ case class Commitments(params: ChannelParams,
12241219
active.forall { commitment =>
12251220
val localFundingKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey
12261221
val remoteFundingKey = commitment.remoteFundingPubKey
1227-
val fundingScript = params.commitmentFormat match {
1228-
case SimpleTaprootChannelsStagingCommitmentFormat => Script.write(Scripts.musig2FundingScript(localFundingKey, remoteFundingKey))
1229-
case _ => Script.write(Scripts.multiSig2of2(localFundingKey, remoteFundingKey))
1222+
val fundingScript = if (params.commitmentFormat.useTaproot) {
1223+
Script.write(Scripts.musig2FundingScript(localFundingKey, remoteFundingKey))
1224+
} else {
1225+
Script.write(Scripts.multiSig2of2(localFundingKey, remoteFundingKey))
12301226
}
12311227
commitment.commitInput.redeemScriptOrScriptTree == Left(fundingScript)
12321228
}

0 commit comments

Comments
 (0)