Skip to content

Commit 1cee368

Browse files
committed
Implement simple taproot channels
We add new commitment format and TLV extensions to include musig2 nonces. We update the single-funging,dual-funding and splicing workflows: We extends the interactive tx session to include: - an optional funding nonce for the shared input (i.e. the funding tx that is being spent) - a nonce for the commit tx that is being created, and another nonce that will become the channel's "next remote nonce" once the session completes The funding nonce is random and its lifecycle is bound to the interactive session. revoke_and_ack is extended to include a list of funding_tx_id -> nonce tuples (one for each active commitment). channel_restablish is extended to include the same list of funding_tx_id -> nonce tuples, as well as an optional "current commit nonce" if we got disconnected while a splice was in progress before both nodes exchanged their commit signatures: if that is the case, we need to re-send our peer's current signature and will use this nonce to compute it. We also update the simple close protocol to include closing nonces. And we allow upgrading channels to taproot during splices, with an optional channel_type TLV added to splice_init/splice_ack.
1 parent 33cd067 commit 1cee368

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1442
-396
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/Eclair.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import fr.acinq.eclair.payment.send.PaymentInitiator._
4747
import fr.acinq.eclair.payment.send.{ClearRecipient, OfferPayment, PaymentIdentifier}
4848
import fr.acinq.eclair.router.Router
4949
import fr.acinq.eclair.router.Router._
50+
import fr.acinq.eclair.transactions.Transactions.CommitmentFormat
5051
import fr.acinq.eclair.wire.protocol.OfferTypes.Offer
5152
import fr.acinq.eclair.wire.protocol._
5253
import grizzled.slf4j.Logging
@@ -96,9 +97,9 @@ trait Eclair {
9697

9798
def rbfOpen(channelId: ByteVector32, targetFeerate: FeeratePerKw, fundingFeeBudget: Satoshi, lockTime_opt: Option[Long])(implicit timeout: Timeout): Future[CommandResponse[CMD_BUMP_FUNDING_FEE]]
9899

99-
def spliceIn(channelId: ByteVector32, amountIn: Satoshi, pushAmount_opt: Option[MilliSatoshi])(implicit timeout: Timeout): Future[CommandResponse[CMD_SPLICE]]
100+
def spliceIn(channelId: ByteVector32, amountIn: Satoshi, pushAmount_opt: Option[MilliSatoshi], channelType_opt: Option[ChannelType])(implicit timeout: Timeout): Future[CommandResponse[CMD_SPLICE]]
100101

101-
def spliceOut(channelId: ByteVector32, amountOut: Satoshi, scriptOrAddress: Either[ByteVector, String])(implicit timeout: Timeout): Future[CommandResponse[CMD_SPLICE]]
102+
def spliceOut(channelId: ByteVector32, amountOut: Satoshi, scriptOrAddress: Either[ByteVector, String], channelType_opt: Option[ChannelType])(implicit timeout: Timeout): Future[CommandResponse[CMD_SPLICE]]
102103

103104
def rbfSplice(channelId: ByteVector32, targetFeerate: FeeratePerKw, fundingFeeBudget: Satoshi, lockTime_opt: Option[Long])(implicit timeout: Timeout): Future[CommandResponse[CMD_BUMP_FUNDING_FEE]]
104105

@@ -260,15 +261,15 @@ class EclairImpl(val appKit: Kit) extends Eclair with Logging with SpendFromChan
260261
)
261262
}
262263

263-
override def spliceIn(channelId: ByteVector32, amountIn: Satoshi, pushAmount_opt: Option[MilliSatoshi])(implicit timeout: Timeout): Future[CommandResponse[CMD_SPLICE]] = {
264+
override def spliceIn(channelId: ByteVector32, amountIn: Satoshi, pushAmount_opt: Option[MilliSatoshi], channelType_opt: Option[ChannelType])(implicit timeout: Timeout): Future[CommandResponse[CMD_SPLICE]] = {
264265
val spliceIn = SpliceIn(additionalLocalFunding = amountIn, pushAmount = pushAmount_opt.getOrElse(0.msat))
265266
sendToChannelTyped(
266267
channel = Left(channelId),
267-
cmdBuilder = CMD_SPLICE(_, spliceIn_opt = Some(spliceIn), spliceOut_opt = None, requestFunding_opt = None)
268+
cmdBuilder = CMD_SPLICE(_, spliceIn_opt = Some(spliceIn), spliceOut_opt = None, requestFunding_opt = None, channelType_opt = channelType_opt)
268269
)
269270
}
270271

271-
override def spliceOut(channelId: ByteVector32, amountOut: Satoshi, scriptOrAddress: Either[ByteVector, String])(implicit timeout: Timeout): Future[CommandResponse[CMD_SPLICE]] = {
272+
override def spliceOut(channelId: ByteVector32, amountOut: Satoshi, scriptOrAddress: Either[ByteVector, String], channelType_opt: Option[ChannelType])(implicit timeout: Timeout): Future[CommandResponse[CMD_SPLICE]] = {
272273
val script = scriptOrAddress match {
273274
case Left(script) => script
274275
case Right(address) => addressToPublicKeyScript(this.appKit.nodeParams.chainHash, address) match {
@@ -279,7 +280,7 @@ class EclairImpl(val appKit: Kit) extends Eclair with Logging with SpendFromChan
279280
val spliceOut = SpliceOut(amount = amountOut, scriptPubKey = script)
280281
sendToChannelTyped(
281282
channel = Left(channelId),
282-
cmdBuilder = CMD_SPLICE(_, spliceIn_opt = None, spliceOut_opt = Some(spliceOut), requestFunding_opt = None)
283+
cmdBuilder = CMD_SPLICE(_, spliceIn_opt = None, spliceOut_opt = Some(spliceOut), requestFunding_opt = None, channelType_opt = channelType_opt)
283284
)
284285
}
285286

eclair-core/src/main/scala/fr/acinq/eclair/Features.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,16 @@ object Features {
341341
val mandatory = 154
342342
}
343343

344+
case object SimpleTaprootChannelsPhoenix extends Feature with InitFeature with NodeFeature with ChannelTypeFeature {
345+
val rfcName = "option_simple_taproot_phoenix_tweaked"
346+
val mandatory = 564
347+
}
348+
349+
case object SimpleTaprootChannelsStaging extends Feature with InitFeature with NodeFeature with ChannelTypeFeature {
350+
val rfcName = "option_simple_taproot_staging"
351+
val mandatory = 180
352+
}
353+
344354
/**
345355
* Activate this feature to provide on-the-fly funding to remote nodes, as specified in bLIP 36: https://github.com/lightning/blips/blob/master/blip-0036.md.
346356
* TODO: add NodeFeature once bLIP is merged.
@@ -384,6 +394,8 @@ object Features {
384394
ZeroConf,
385395
KeySend,
386396
SimpleClose,
397+
SimpleTaprootChannelsPhoenix,
398+
SimpleTaprootChannelsStaging,
387399
WakeUpNotificationClient,
388400
TrampolinePaymentPrototype,
389401
AsyncPaymentPrototype,
@@ -403,6 +415,8 @@ object Features {
403415
TrampolinePaymentPrototype -> (PaymentSecret :: Nil),
404416
KeySend -> (VariableLengthOnion :: Nil),
405417
SimpleClose -> (ShutdownAnySegwit :: Nil),
418+
SimpleTaprootChannelsPhoenix -> (ChannelType :: SimpleClose :: Nil),
419+
SimpleTaprootChannelsStaging -> (ChannelType :: SimpleClose :: Nil),
406420
AsyncPaymentPrototype -> (TrampolinePaymentPrototype :: Nil),
407421
OnTheFlyFunding -> (SplicePrototype :: Nil),
408422
FundingFeeCredit -> (OnTheFlyFunding :: Nil)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ sealed trait ChannelFundingCommand extends Command {
250250
}
251251
case class SpliceIn(additionalLocalFunding: Satoshi, pushAmount: MilliSatoshi = 0 msat)
252252
case class SpliceOut(amount: Satoshi, scriptPubKey: ByteVector)
253-
final case class CMD_SPLICE(replyTo: akka.actor.typed.ActorRef[CommandResponse[ChannelFundingCommand]], spliceIn_opt: Option[SpliceIn], spliceOut_opt: Option[SpliceOut], requestFunding_opt: Option[LiquidityAds.RequestFunding]) extends ChannelFundingCommand {
253+
final case class CMD_SPLICE(replyTo: akka.actor.typed.ActorRef[CommandResponse[ChannelFundingCommand]], spliceIn_opt: Option[SpliceIn], spliceOut_opt: Option[SpliceOut], requestFunding_opt: Option[LiquidityAds.RequestFunding], channelType_opt:Option[ChannelType]) extends ChannelFundingCommand {
254254
require(spliceIn_opt.isDefined || spliceOut_opt.isDefined, "there must be a splice-in or a splice-out")
255255
val additionalLocalFunding: Satoshi = spliceIn_opt.map(_.additionalLocalFunding).getOrElse(0 sat)
256256
val pushAmount: MilliSatoshi = spliceIn_opt.map(_.pushAmount).getOrElse(0 msat)

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,9 @@ case class ForbiddenDuringQuiescence (override val channelId: Byte
153153
case class ConcurrentRemoteSplice (override val channelId: ByteVector32) extends ChannelException(channelId, "splice attempt canceled, remote initiated splice before us")
154154
case class TooManySmallHtlcs (override val channelId: ByteVector32, number: Long, below: MilliSatoshi) extends ChannelJammingException(channelId, s"too many small htlcs: $number HTLCs below $below")
155155
case class ConfidenceTooLow (override val channelId: ByteVector32, confidence: Double, occupancy: Double) extends ChannelJammingException(channelId, s"confidence too low: confidence=$confidence occupancy=$occupancy")
156+
case class MissingNonce (override val channelId: ByteVector32, fundingTxId: TxId) extends ChannelException(channelId, s"next nonce for funding tx $fundingTxId is missing")
157+
case class InvalidNonce (override val channelId: ByteVector32, fundingTxId: TxId) extends ChannelException(channelId, s"next nonce for funding tx $fundingTxId is not valid")
158+
case class MissingFundingNonce (override val channelId: ByteVector32) extends ChannelException(channelId, "missing funding nonce")
159+
case class InvalidFundingNonce (override val channelId: ByteVector32) extends ChannelException(channelId, "invalid funding nonce")
160+
case class MissingShutdownNonce (override val channelId: ByteVector32) extends ChannelException(channelId, "missing shutdown nonce")
156161
// @formatter:on

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

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package fr.acinq.eclair.channel
1818

19-
import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, DefaultCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat}
19+
import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, DefaultCommitmentFormat, LegacySimpleTaprootChannelCommitmentFormat, SimpleTaprootChannelCommitmentFormat, UnsafeLegacyAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxAnchorOutputsCommitmentFormat, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat}
2020
import fr.acinq.eclair.{ChannelTypeFeature, FeatureSupport, Features, InitFeature, PermanentChannelFeature}
2121

2222
/**
@@ -122,6 +122,29 @@ object ChannelTypes {
122122
override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxAnchorOutputsCommitmentFormat
123123
override def toString: String = s"anchor_outputs_zero_fee_htlc_tx${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
124124
}
125+
case class SimpleTaprootChannelsStagingLegacy(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType {
126+
/** Known channel-type features */
127+
override def features: Set[ChannelTypeFeature] = Set(
128+
if (scidAlias) Some(Features.ScidAlias) else None,
129+
if (zeroConf) Some(Features.ZeroConf) else None,
130+
Some(Features.SimpleTaprootChannelsPhoenix),
131+
).flatten
132+
override def paysDirectlyToWallet: Boolean = false
133+
override def commitmentFormat: CommitmentFormat = LegacySimpleTaprootChannelCommitmentFormat
134+
override def toString: String = s"simple_taproot_channel_staging_legacy${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
135+
}
136+
case class SimpleTaprootChannelsStagingZeroFee(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType {
137+
/** Known channel-type features */
138+
override def features: Set[ChannelTypeFeature] = Set(
139+
if (scidAlias) Some(Features.ScidAlias) else None,
140+
if (zeroConf) Some(Features.ZeroConf) else None,
141+
Some(Features.SimpleTaprootChannelsStaging),
142+
).flatten
143+
override def paysDirectlyToWallet: Boolean = false
144+
override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat
145+
override def toString: String = s"simple_taproot_channel_staging_serofee${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
146+
}
147+
125148
case class UnsupportedChannelType(featureBits: Features[InitFeature]) extends ChannelType {
126149
override def features: Set[InitFeature] = featureBits.activated.keySet
127150
override def toString: String = s"0x${featureBits.toByteVector.toHex}"
@@ -144,7 +167,16 @@ object ChannelTypes {
144167
AnchorOutputsZeroFeeHtlcTx(),
145168
AnchorOutputsZeroFeeHtlcTx(zeroConf = true),
146169
AnchorOutputsZeroFeeHtlcTx(scidAlias = true),
147-
AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true))
170+
AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true),
171+
SimpleTaprootChannelsStagingLegacy(),
172+
SimpleTaprootChannelsStagingLegacy(zeroConf = true),
173+
SimpleTaprootChannelsStagingLegacy(scidAlias = true),
174+
SimpleTaprootChannelsStagingLegacy(scidAlias = true, zeroConf = true),
175+
SimpleTaprootChannelsStagingZeroFee(),
176+
SimpleTaprootChannelsStagingZeroFee(zeroConf = true),
177+
SimpleTaprootChannelsStagingZeroFee(scidAlias = true),
178+
SimpleTaprootChannelsStagingZeroFee(scidAlias = true, zeroConf = true),
179+
)
148180
.map(channelType => Features(channelType.features.map(_ -> FeatureSupport.Mandatory).toMap) -> channelType)
149181
.toMap
150182

@@ -157,7 +189,11 @@ object ChannelTypes {
157189

158190
val scidAlias = canUse(Features.ScidAlias) && !announceChannel // alias feature is incompatible with public channel
159191
val zeroConf = canUse(Features.ZeroConf)
160-
if (canUse(Features.AnchorOutputsZeroFeeHtlcTx)) {
192+
if (canUse(Features.SimpleTaprootChannelsStaging)) {
193+
SimpleTaprootChannelsStagingZeroFee(scidAlias, zeroConf)
194+
} else if (canUse(Features.SimpleTaprootChannelsPhoenix)) {
195+
SimpleTaprootChannelsStagingLegacy(scidAlias, zeroConf)
196+
} else if (canUse(Features.AnchorOutputsZeroFeeHtlcTx)) {
161197
AnchorOutputsZeroFeeHtlcTx(scidAlias, zeroConf)
162198
} else if (canUse(Features.AnchorOutputs)) {
163199
AnchorOutputs(scidAlias, zeroConf)

0 commit comments

Comments
 (0)