Skip to content

Commit 030131d

Browse files
committed
Implement simple taproot channels
This commit implements: - feature bits for simple taproot channels - TLV extensions for funding/closing wire messages - modifications to how we handle channel funding, splicing and mutual closing - changes to the commitment structures Since the current "simple taproot channels" proposal is not compatible with splices, we extend it to include a list of musig2 nonces (one for each active commitment transaction). Similar to how commitment points are handled, `firstRemoteNonce` and `secondRemoteNonce` fields have been added to `SpliceInit` and `SpliceAck`, encoded as a list of nonces (instead of 2 expicit nonces) We also need a nonce for the new commit tx that is being built, here it has been added to `SpliceInit` and `SpliceAck`. The closing workflow is similar to the standard "simple close" workflow: - Alice and Bob exchange `shutdown`, which includes a "closing nonce" (no changes here compared to the "simple taproot channels" spec). - Alice selects possible closing transaction (closer_output_only, closee_output_only, closer_and_closee_output) and for each of them creates a partial_signature_with_nonce using a new random local nonce and Bob's closing nonce (which she received in Bob's `shutdown` message). - Alice send a `closing_complete` message to Bob that include these partial_signature_with_nonce. - Bob receive Alice's `closing_complete` message, selects one of Alice's partial_signature_with_nonce, creates partial_signature_with_nonce using. his closing nonce and the nonce attached to the partial_signature_with_nonce and sends it to Alice in a `closing_sig` message. - Alice receives Bob's `closing_sig` and creates a partial signature for her closing tx using her closing nonce and the nonce attached Bob's partial_signature_with_nonce. - Alice combines this signature with Bob's and can broadcat her closing tx.
1 parent 65e74f6 commit 030131d

File tree

48 files changed

+3050
-513
lines changed

Some content is hidden

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

48 files changed

+3050
-513
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,11 @@ object Features {
305305
val mandatory = 60
306306
}
307307

308+
case object SimpleTaproot extends Feature with InitFeature with NodeFeature with ChannelTypeFeature {
309+
val rfcName = "option_simple_taproot"
310+
val mandatory = 80
311+
}
312+
308313
// TODO: @t-bast: update feature bits once spec-ed (currently reserved here: https://github.com/lightningnetwork/lightning-rfc/issues/605)
309314
// We're not advertising these bits yet in our announcements, clients have to assume support.
310315
// This is why we haven't added them yet to `areSupported`.
@@ -328,6 +333,11 @@ object Features {
328333
val mandatory = 154
329334
}
330335

336+
case object SimpleTaprootStaging extends Feature with InitFeature with NodeFeature with ChannelTypeFeature {
337+
val rfcName = "option_simple_taproot_staging"
338+
val mandatory = 180
339+
}
340+
331341
/**
332342
* 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.
333343
* TODO: add NodeFeature once bLIP is merged.
@@ -369,6 +379,8 @@ object Features {
369379
ZeroConf,
370380
KeySend,
371381
SimpleClose,
382+
SimpleTaproot,
383+
SimpleTaprootStaging,
372384
TrampolinePaymentPrototype,
373385
AsyncPaymentPrototype,
374386
SplicePrototype,
@@ -387,6 +399,8 @@ object Features {
387399
TrampolinePaymentPrototype -> (PaymentSecret :: Nil),
388400
KeySend -> (VariableLengthOnion :: Nil),
389401
SimpleClose -> (ShutdownAnySegwit :: Nil),
402+
SimpleTaproot -> (ChannelType :: AnchorOutputsZeroFeeHtlcTx :: StaticRemoteKey :: Nil),
403+
SimpleTaprootStaging -> (ChannelType :: AnchorOutputsZeroFeeHtlcTx :: StaticRemoteKey :: Nil),
390404
AsyncPaymentPrototype -> (TrampolinePaymentPrototype :: Nil),
391405
OnTheFlyFunding -> (SplicePrototype :: Nil),
392406
FundingFeeCredit -> (OnTheFlyFunding :: Nil)

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, 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 => 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 => false
88+
case ZeroFeeHtlcTxAnchorOutputsCommitmentFormat | UnsafeLegacyAnchorOutputsCommitmentFormat | SimpleTaprootChannelsStagingCommitmentFormat | SimpleTaprootChannelsStagingLegacyCommitmentFormat => false
8989
}
9090
}
9191
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package fr.acinq.eclair.channel
1818

1919
import akka.actor.{ActorRef, PossiblyHarmful, typed}
20+
import fr.acinq.bitcoin.crypto.musig2.{IndividualNonce, SecretNonce}
2021
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
2122
import fr.acinq.bitcoin.scalacompat.{ByteVector32, DeterministicWallet, OutPoint, Satoshi, SatoshiLong, Transaction, TxId, TxOut}
2223
import fr.acinq.eclair.blockchain.fee.{ConfirmationTarget, FeeratePerKw}
@@ -577,7 +578,7 @@ sealed trait ChannelDataWithCommitments extends PersistentChannelData {
577578
final case class DATA_WAIT_FOR_OPEN_CHANNEL(initFundee: INPUT_INIT_CHANNEL_NON_INITIATOR) extends TransientChannelData {
578579
val channelId: ByteVector32 = initFundee.temporaryChannelId
579580
}
580-
final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder: INPUT_INIT_CHANNEL_INITIATOR, lastSent: OpenChannel) extends TransientChannelData {
581+
final case class DATA_WAIT_FOR_ACCEPT_CHANNEL(initFunder: INPUT_INIT_CHANNEL_INITIATOR, lastSent: OpenChannel, nextLocalNonce: Option[kotlin.Pair[SecretNonce, IndividualNonce]] = None) extends TransientChannelData {
581582
val channelId: ByteVector32 = initFunder.temporaryChannelId
582583
}
583584
final case class DATA_WAIT_FOR_FUNDING_INTERNAL(params: ChannelParams,
@@ -594,7 +595,8 @@ final case class DATA_WAIT_FOR_FUNDING_CREATED(params: ChannelParams,
594595
pushAmount: MilliSatoshi,
595596
commitTxFeerate: FeeratePerKw,
596597
remoteFundingPubKey: PublicKey,
597-
remoteFirstPerCommitmentPoint: PublicKey) extends TransientChannelData {
598+
remoteFirstPerCommitmentPoint: PublicKey,
599+
remoteNextLocalNonce: Option[IndividualNonce]) extends TransientChannelData {
598600
val channelId: ByteVector32 = params.channelId
599601
}
600602
final case class DATA_WAIT_FOR_FUNDING_SIGNED(params: ChannelParams,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,5 @@ case class CommandUnavailableInThisState (override val channelId: Byte
151151
case class ForbiddenDuringSplice (override val channelId: ByteVector32, command: String) extends ChannelException(channelId, s"cannot process $command while splicing")
152152
case class ForbiddenDuringQuiescence (override val channelId: ByteVector32, command: String) extends ChannelException(channelId, s"cannot process $command while quiescent")
153153
case class ConcurrentRemoteSplice (override val channelId: ByteVector32) extends ChannelException(channelId, "splice attempt canceled, remote initiated splice before us")
154+
case class MissingNextLocalNonce (override val channelId: ByteVector32) extends ChannelException(channelId, "next local nonce tlv is missing")
154155
// @formatter:on

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

Lines changed: 30 additions & 6 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._
2020
import fr.acinq.eclair.{ChannelTypeFeature, FeatureSupport, Features, InitFeature, PermanentChannelFeature}
2121

2222
/**
@@ -31,9 +31,12 @@ import fr.acinq.eclair.{ChannelTypeFeature, FeatureSupport, Features, InitFeatur
3131
case class ChannelFeatures(features: Set[PermanentChannelFeature]) {
3232

3333
/** True if our main output in the remote commitment is directly sent (without any delay) to one of our wallet addresses. */
34-
val paysDirectlyToWallet: Boolean = hasFeature(Features.StaticRemoteKey) && !hasFeature(Features.AnchorOutputs) && !hasFeature(Features.AnchorOutputsZeroFeeHtlcTx)
34+
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. */
36-
val commitmentFormat: CommitmentFormat = if (hasFeature(Features.AnchorOutputs)) {
36+
val commitmentFormat: CommitmentFormat = if (hasFeature(Features.SimpleTaprootStaging)) {
37+
if (hasFeature(Features.AnchorOutputs)) SimpleTaprootChannelsStagingLegacyCommitmentFormat
38+
else SimpleTaprootChannelsStagingCommitmentFormat
39+
} else if (hasFeature(Features.AnchorOutputs)) {
3740
UnsafeLegacyAnchorOutputsCommitmentFormat
3841
} else if (hasFeature(Features.AnchorOutputsZeroFeeHtlcTx)) {
3942
ZeroFeeHtlcTxAnchorOutputsCommitmentFormat
@@ -129,6 +132,18 @@ object ChannelTypes {
129132
override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxAnchorOutputsCommitmentFormat
130133
override def toString: String = s"anchor_outputs_zero_fee_htlc_tx${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
131134
}
135+
case class SimpleTaprootChannelsStaging(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType {
136+
/** Known channel-type features */
137+
override def features: Set[ChannelTypeFeature] = Set(
138+
if (scidAlias) Some(Features.ScidAlias) else None,
139+
if (zeroConf) Some(Features.ZeroConf) else None,
140+
Some(Features.SimpleTaprootStaging),
141+
).flatten
142+
override def paysDirectlyToWallet: Boolean = false
143+
override def commitmentFormat: CommitmentFormat = SimpleTaprootChannelsStagingCommitmentFormat
144+
override def toString: String = s"simple_taproot_channel_staging${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
145+
}
146+
132147
case class UnsupportedChannelType(featureBits: Features[InitFeature]) extends ChannelType {
133148
override def features: Set[InitFeature] = featureBits.activated.keySet
134149
override def toString: String = s"0x${featureBits.toByteVector.toHex}"
@@ -151,20 +166,29 @@ object ChannelTypes {
151166
AnchorOutputsZeroFeeHtlcTx(),
152167
AnchorOutputsZeroFeeHtlcTx(zeroConf = true),
153168
AnchorOutputsZeroFeeHtlcTx(scidAlias = true),
154-
AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true))
169+
AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true),
170+
SimpleTaprootChannelsStaging(),
171+
SimpleTaprootChannelsStaging(zeroConf = true),
172+
SimpleTaprootChannelsStaging(scidAlias = true),
173+
SimpleTaprootChannelsStaging(scidAlias = true, zeroConf = true),
174+
)
155175
.map(channelType => Features(channelType.features.map(_ -> FeatureSupport.Mandatory).toMap) -> channelType)
156176
.toMap
157177

158178
// NB: Bolt 2: features must exactly match in order to identify a channel type.
159-
def fromFeatures(features: Features[InitFeature]): ChannelType = features2ChannelType.getOrElse(features, UnsupportedChannelType(features))
179+
def fromFeatures(features: Features[InitFeature]): ChannelType = {
180+
features2ChannelType.getOrElse(features, UnsupportedChannelType(features))
181+
}
160182

161183
/** Pick the channel type based on local and remote feature bits, as defined by the spec. */
162184
def defaultFromFeatures(localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean): SupportedChannelType = {
163185
def canUse(feature: InitFeature): Boolean = Features.canUseFeature(localFeatures, remoteFeatures, feature)
164186

165187
val scidAlias = canUse(Features.ScidAlias) && !announceChannel // alias feature is incompatible with public channel
166188
val zeroConf = canUse(Features.ZeroConf)
167-
if (canUse(Features.AnchorOutputsZeroFeeHtlcTx)) {
189+
if (canUse(Features.SimpleTaprootStaging)) {
190+
SimpleTaprootChannelsStaging(scidAlias, zeroConf)
191+
} else if (canUse(Features.AnchorOutputsZeroFeeHtlcTx)) {
168192
AnchorOutputsZeroFeeHtlcTx(scidAlias, zeroConf)
169193
} else if (canUse(Features.AnchorOutputs)) {
170194
AnchorOutputs(scidAlias, zeroConf)

0 commit comments

Comments
 (0)