Use official feature bit for `option_simple_taproot` (#3144)
What changed, and why it matters
This commit updates the Eclair Lightning node software to use the official protocol feature bit and name for a new kind of Bitcoin payment channel called 'taproot channels.' It turns taproot channel support on by default, but only for private (unannounced) channels, because the public-announcement rules are not finalized. The change is mostly a rename and feature-bit swap from an earlier experimental/staging version, plus adding guards so users cannot accidentally open a public taproot channel. It is a protocol-alignment change, not a fix for an active security bug.
Treat this as a normal feature-alignment commit. Reviewers should verify that the new feature bit and dependency set do not accidentally enable taproot channels in contexts where they are unsupported (e.g., public/announced channels, incompatible peers), and that the new test vectors pass. Node operators upgrading to this release should be aware that taproot channels are now enabled by default for private channels and can disable them via `eclair.features.option_simple_taproot = disabled` if desired.
Security signals we found
Protocol feature bit migration from staging/experimental to official BOLT specification
Default activation of a new channel type (taproot) that affects transaction construction and key aggregation
Added explicit guardrails against opening publicly announced taproot channels
New BOLT3 reference test vectors for taproot commitment transactions included
No evidence of a vulnerability fix, CVE, or security advisory in the commit materials
Evidence from the diff
The commit replaces the experimental option_simple_taproot_staging feature (bit 180) with the official option_simple_taproot feature (bit 80) per lightning/bolts#995. It renames SimpleTaprootChannelsStaging to SimpleTaprootChannel, updates channel-type strings, feature dependencies, default configuration, and test vectors. It also adds explicit rejection of announced/public taproot channels in OpenChannelInterceptor for both initiator and non-initiator flows, and makes taproot the preferred channel type for private channels when both peers support it. A large set of BOLT3 reference test vectors for the simple-taproot commitment format is added, and tests are updated to exercise the new feature bit and rejection logic.
Changed components
eclair-core/src/main/scala/fr/acinq/eclair/Features.scalaeclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scalaeclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scalaeclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scalaeclair-core/src/main/resources/reference.confeclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scalaeclair-core test suites (InteractiveTxBuilderSpec, ChannelStateTestsHelperMethods, WaitForOpenChannelStateSpec, OpenChannelInterceptorSpec, TestVectorsSpec, LightningMessageCodecsSpec)Inspect captured patch +587 / −36
diff --git a/docs/release-notes/eclair-vnext.md b/docs/release-notes/eclair-vnext.md
index 85b01d1..b81810e 100644
--- a/docs/release-notes/eclair-vnext.md
+++ b/docs/release-notes/eclair-vnext.md
@@ -42,6 +42,28 @@ Node operators should instead create a new splice transaction (with `splicein` o
Note that eclair had already introduced support for a splicing prototype in v0.9.0, which helped improve the BOLT proposal.
We're removing support for the previous splicing prototype feature: users that depended on this protocol must upgrade to create official splice transactions.
+### Taproot Channels (without announcements)
+
+This release adds support for taproot channels, as specified in [the BOLTs](https://github.com/lightning/bolts/pull/995).
+Taproot channels improve privacy and cost less on-chain fees by using musig2 for the channel output.
+This release is fully compatible with the `lnd` implementation of taproot channels.
+
+We don't support public taproot channels yet, as the gossip mechanism for this isn't finalized yet.
+It is thus only possible to open "private" (unannounced) taproot channels.
+You may follow progress on the specification for public taproot channels [here](https://github.com/lightning/bolts/pull/1059).
+
+This feature is active by default. To disable it, add the following to your `eclair.conf`:
+
+```conf
+eclair.features.option_simple_taproot = disabled
+```
+
+To open a taproot channel with a node that supports the `option_simple_taproot` feature, use the following command:
+
+```sh
+$ eclair-cli open --nodeId=<node_id> --fundingSatoshis=<funding_amount> --channelType=simple_taproot_channel --announceChannel=false
+```
+
### Remove support for non-anchor channels
We remove the code used to support legacy channels that don't use anchor outputs or taproot.
diff --git a/eclair-core/src/main/resources/reference.conf b/eclair-core/src/main/resources/reference.conf
index fb90bf4..64a988c 100644
--- a/eclair-core/src/main/resources/reference.conf
+++ b/eclair-core/src/main/resources/reference.conf
@@ -89,6 +89,7 @@ eclair {
keysend = disabled
option_simple_close = optional
option_splice = optional
+ option_simple_taproot = optional
trampoline_payment_prototype = disabled
async_payment_prototype = disabled
on_the_fly_funding = disabled
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala
index 2325bea..d093969 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/Features.scala
@@ -399,6 +399,11 @@ object Features {
val mandatory = 62
}
+ case object SimpleTaprootChannels extends Feature with InitFeature with NodeFeature with ChannelTypeFeature {
+ val rfcName = "option_simple_taproot"
+ val mandatory = 80
+ }
+
case object PhoenixZeroReserve extends Feature with InitFeature with ChannelTypeFeature with PermanentChannelFeature {
val rfcName = "phoenix_zero_reserve"
val mandatory = 128
@@ -438,11 +443,6 @@ object Features {
val mandatory = 564
}
- case object SimpleTaprootChannelsStaging extends Feature with InitFeature with NodeFeature with ChannelTypeFeature {
- val rfcName = "option_simple_taproot_staging"
- val mandatory = 180
- }
-
/**
* 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.
* TODO: add NodeFeature once bLIP is merged.
@@ -487,8 +487,8 @@ object Features {
KeySend,
SimpleClose,
Splicing,
+ SimpleTaprootChannels,
SimpleTaprootChannelsPhoenix,
- SimpleTaprootChannelsStaging,
WakeUpNotificationClient,
TrampolinePaymentPrototype,
AsyncPaymentPrototype,
@@ -509,6 +509,7 @@ object Features {
TrampolinePaymentPrototype -> (PaymentSecret :: Nil),
KeySend -> (VariableLengthOnion :: Nil),
SimpleClose -> (ShutdownAnySegwit :: Nil),
+ SimpleTaprootChannels -> (ChannelType :: SimpleClose :: Nil),
SimpleTaprootChannelsPhoenix -> (ChannelType :: SimpleClose :: Nil),
AsyncPaymentPrototype -> (TrampolinePaymentPrototype :: Nil),
FundingFeeCredit -> (OnTheFlyFunding :: Nil)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
index f411ac9..9fa95e3 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala
@@ -134,7 +134,7 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
// We use the most likely commitment format, even though there is no guarantee that this is the one that will be used.
val commitmentFormat = if (Features.canUseFeature(localFeatures, remoteFeatures, Features.SimpleTaprootChannelsPhoenix)) {
PhoenixSimpleTaprootChannelCommitmentFormat
- } else if (Features.canUseFeature(localFeatures, remoteFeatures, Features.SimpleTaprootChannelsStaging)) {
+ } else if (Features.canUseFeature(localFeatures, remoteFeatures, Features.SimpleTaprootChannels)) {
ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat
} else {
ZeroFeeHtlcTxAnchorOutputsCommitmentFormat
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala
index 311e960..f153793 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelFeatures.scala
@@ -95,14 +95,14 @@ object ChannelTypes {
override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxAnchorOutputsCommitmentFormat
override def toString: String = s"anchor_outputs_zero_fee_htlc_tx${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
}
- case class SimpleTaprootChannelsStaging(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType {
+ case class SimpleTaprootChannel(scidAlias: Boolean = false, zeroConf: Boolean = false) extends SupportedChannelType {
override def features: Set[ChannelTypeFeature] = Set(
if (scidAlias) Some(Features.ScidAlias) else None,
if (zeroConf) Some(Features.ZeroConf) else None,
- Some(Features.SimpleTaprootChannelsStaging),
+ Some(Features.SimpleTaprootChannels),
).flatten
override def commitmentFormat: CommitmentFormat = ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat
- override def toString: String = s"simple_taproot_channel_staging${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
+ override def toString: String = s"simple_taproot_channel${if (scidAlias) "+scid_alias" else ""}${if (zeroConf) "+zeroconf" else ""}"
}
case class UnsupportedChannelType(featureBits: Features[InitFeature]) extends ChannelType {
@@ -127,10 +127,10 @@ object ChannelTypes {
AnchorOutputsZeroFeeHtlcTx(zeroConf = true),
AnchorOutputsZeroFeeHtlcTx(scidAlias = true),
AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true),
- SimpleTaprootChannelsStaging(),
- SimpleTaprootChannelsStaging(zeroConf = true),
- SimpleTaprootChannelsStaging(scidAlias = true),
- SimpleTaprootChannelsStaging(scidAlias = true, zeroConf = true),
+ SimpleTaprootChannel(),
+ SimpleTaprootChannel(zeroConf = true),
+ SimpleTaprootChannel(scidAlias = true),
+ SimpleTaprootChannel(scidAlias = true, zeroConf = true),
SimpleTaprootChannelsPhoenix,
).map {
channelType => Features(channelType.features.map(_ -> FeatureSupport.Mandatory).toMap) -> channelType
@@ -150,8 +150,12 @@ object ChannelTypes {
/** Returns our preferred channel type for public channels, if supported by our peer. */
def preferredForPublicChannels(localFeatures: Features[InitFeature], remoteFeatures: Features[InitFeature], announceChannel: Boolean): Option[SupportedChannelType] = {
- if (Features.canUseFeature(localFeatures, remoteFeatures, Features.AnchorOutputsZeroFeeHtlcTx)) {
- Some(AnchorOutputsZeroFeeHtlcTx(scidAlias = !announceChannel && Features.canUseFeature(localFeatures, remoteFeatures, Features.ScidAlias)))
+ val useScidAlias = !announceChannel && Features.canUseFeature(localFeatures, remoteFeatures, Features.ScidAlias)
+ if (!announceChannel && Features.canUseFeature(localFeatures, remoteFeatures, Features.SimpleTaprootChannels)) {
+ // We currently only support unannounced taproot channels.
+ Some(SimpleTaprootChannel(scidAlias = useScidAlias))
+ } else if (Features.canUseFeature(localFeatures, remoteFeatures, Features.AnchorOutputsZeroFeeHtlcTx)) {
+ Some(AnchorOutputsZeroFeeHtlcTx(scidAlias = useScidAlias))
} else {
None
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala
index 9c2e062..718aa86 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/OpenChannelInterceptor.scala
@@ -119,6 +119,9 @@ private class OpenChannelInterceptor(peer: ActorRef[Any],
} else if (channelType_opt.isEmpty) {
request.replyTo ! OpenChannelResponse.Rejected("channel_type must be provided and compatible with our peer's features")
waitForRequest()
+ } else if (announceChannel && channelType_opt.exists(_.isInstanceOf[ChannelTypes.SimpleTaprootChannel])) {
+ request.replyTo ! OpenChannelResponse.Rejected("public taproot channels aren't supported yet: announce_channel must be set to false")
+ waitForRequest()
} else {
val dualFunded = Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.DualFunding)
val upfrontShutdownScript = Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.UpfrontShutdownScript)
@@ -132,6 +135,10 @@ private class OpenChannelInterceptor(peer: ActorRef[Any],
private def sanityCheckNonInitiator(request: OpenChannelNonInitiator): Behavior[Command] = {
ChannelTypes.areCompatible(request.temporaryChannelId, request.localFeatures, request.channelType_opt) match {
+ case Right(_: ChannelTypes.SimpleTaprootChannel) if request.channelFlags.announceChannel =>
+ context.log.warn("ignoring remote channel open: public taproot channels aren't supported yet")
+ sendFailure("public taproot channels aren't supported yet: announce_channel must be set to false", request)
+ waitForRequest()
case Right(channelType) =>
val dualFunded = Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.DualFunding)
val upfrontShutdownScript = Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.UpfrontShutdownScript)
diff --git a/eclair-core/src/test/resources/bolt3-tx-test-vectors-simple-taproot-commitment-format.json b/eclair-core/src/test/resources/bolt3-tx-test-vectors-simple-taproot-commitment-format.json
new file mode 100644
index 0000000..26686d3
--- /dev/null
+++ b/eclair-core/src/test/resources/bolt3-tx-test-vectors-simple-taproot-commitment-format.json
@@ -0,0 +1,309 @@
+{
+ "params": {
+ "seed": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "funding_amount_satoshis": 10000000,
+ "dust_limit_satoshis": 354,
+ "csv_delay": 144,
+ "commit_height": 42,
+ "nums_point": "02dca094751109d0bd055d03565874e8276dd53e926b44e3bd1bb6bf4bc130a279",
+ "keys": {
+ "local_funding_privkey": "20ae2d254ab29afd3dcbf8744a5b88d06070f55a4bd5532483a093ac4db91277",
+ "local_funding_pubkey": "03b7203dec7c13896b6ff1f58b24f84458c441720a12b5a57426397e22f0a8c78b",
+ "remote_funding_privkey": "f0c5500a9dbd7cdcd46ced7bdeb937d4dcbf90f9b9357626e7ee54ab024c3df0",
+ "remote_funding_pubkey": "02956e6845a6f346f97c5e028c0f8ab38a76b0124fd7184deab60f682b3e657fdb",
+ "local_payment_basepoint_secret": "277975b5b081a9cbc4834e066d7bb494e4fde4f7637257dd3d312a0ae7cb7754",
+ "local_payment_basepoint": "03955b6085296cbd2447a1dde0f7e273e19b83e83de1814993b1517aaf193b7f33",
+ "remote_payment_basepoint_secret": "f1cd3a5ca44b52baf4eacb849fbf06e75aace97477b8bfe31d2b814dbbb562b1",
+ "remote_payment_basepoint": "03595f2ef2a51d2250a21077dbea4a7fc3ce550f10676996bf63719e2a71d1f4c9",
+ "local_delayed_payment_basepoint_secret": "83ccf0b638c514db5ebefdc6cbf901505e2bb20edb2bb7248ce1a51523325f9b",
+ "local_delayed_payment_basepoint": "02ae68d8ff4c59864c03a42bbff6c07f9ae18047e0daa9bc40d07c410f9a0f7899",
+ "remote_revocation_basepoint_secret": "36c4175b91cff9731a63d1472b5b1c4cf3e7b688e87d5fb806b2e8350484e68d",
+ "remote_revocation_basepoint": "02c354121ef71922b5cb32fa685c08ac0014b558f96e28f383c45eb28b7da264c3",
+ "local_htlc_basepoint_secret": "786eb5024e4851bea3ddc6e40036c81b1efcf50eeed440eedefe5245bde6fc14",
+ "local_htlc_basepoint": "033ce88bf3c8333e242996964ac91ee7cd945bfe4c49668ea10f3211f3d418fbc8",
+ "remote_htlc_basepoint_secret": "51c9b6cf8279def85e3925bc8f16fc0ff100ee7b03ce7c954149ca29c834b684",
+ "remote_htlc_basepoint": "02932dfbf6737001e3c516696ae3dcd323fd91a01ce7898f7f91ab98eebacc323e",
+ "local_per_commit_secret": "037b507180b3985cea6396d6a70987cea11ccd05fde49e943a3ea0fe56ee33ed",
+ "local_per_commit_point": "02a0f5a09017c1dec2d30dd54a25dc4037fc5a2aa3832ee3c7b58f3a88a0836287",
+ "derived_local_delayed_pubkey": "0315ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05",
+ "derived_revocation_pubkey": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0",
+ "derived_local_htlc_pubkey": "0271e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739e",
+ "derived_remote_htlc_pubkey": "032deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47d",
+ "derived_remote_payment_pubkey": "03595f2ef2a51d2250a21077dbea4a7fc3ce550f10676996bf63719e2a71d1f4c9"
+ }
+ },
+ "scripts": {
+ "funding": {
+ "funding_tx_hex": "02000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000018096980000000000225120d0ebb4909d563a7ae1213fddede4ae54132fba0ef0b97ee3f8469191fecd348e00000000",
+ "combined_key": "d0ebb4909d563a7ae1213fddede4ae54132fba0ef0b97ee3f8469191fecd348e",
+ "pkscript": "5120d0ebb4909d563a7ae1213fddede4ae54132fba0ef0b97ee3f8469191fecd348e"
+ },
+ "to_local": {
+ "scripts": {
+ "revocation": "2015ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c057520d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0ac",
+ "settle": "2015ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05ad029000b2"
+ },
+ "leaf_hashes": {
+ "revocation": "8fcd64d212bbbf1bcec2360bbf229963240d05992fc2efb482fe6dca85b9469a",
+ "settle": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62"
+ },
+ "tapscript_root": "b8b76c2e893ca785072f0d7393e35d5bd72adf8b7ff2a53538aa664378a38a36",
+ "internal_key": "02dca094751109d0bd055d03565874e8276dd53e926b44e3bd1bb6bf4bc130a279",
+ "output_key": "023e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff3",
+ "pkscript": "51203e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff3"
+ },
+ "to_remote": {
+ "scripts": {
+ "settle": "20595f2ef2a51d2250a21077dbea4a7fc3ce550f10676996bf63719e2a71d1f4c9ad51b2"
+ },
+ "leaf_hashes": {
+ "settle": "63ce35b16eb8f8687293d5a88c1d8ada3236843b79ca315fe9dd7c47f30f2bc9"
+ },
+ "tapscript_root": "63ce35b16eb8f8687293d5a88c1d8ada3236843b79ca315fe9dd7c47f30f2bc9",
+ "internal_key": "02dca094751109d0bd055d03565874e8276dd53e926b44e3bd1bb6bf4bc130a279",
+ "output_key": "023609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec30174408",
+ "pkscript": "51203609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec30174408"
+ },
+ "local_anchor": {
+ "scripts": {
+ "sweep": "60b2"
+ },
+ "leaf_hashes": {
+ "sweep": "2b88a8f3f52386d61d5b3f2d822df659c35214d7360ed05352ad7ddc1ab03912"
+ },
+ "tapscript_root": "2b88a8f3f52386d61d5b3f2d822df659c35214d7360ed05352ad7ddc1ab03912",
+ "internal_key": "0315ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05",
+ "output_key": "02f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344e",
+ "pkscript": "5120f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344e"
+ },
+ "remote_anchor": {
+ "scripts": {
+ "sweep": "60b2"
+ },
+ "leaf_hashes": {
+ "sweep": "2b88a8f3f52386d61d5b3f2d822df659c35214d7360ed05352ad7ddc1ab03912"
+ },
+ "tapscript_root": "2b88a8f3f52386d61d5b3f2d822df659c35214d7360ed05352ad7ddc1ab03912",
+ "internal_key": "03595f2ef2a51d2250a21077dbea4a7fc3ce550f10676996bf63719e2a71d1f4c9",
+ "output_key": "021249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac4",
+ "pkscript": "51201249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac4"
+ },
+ "offered_htlc_local_commit": {
+ "scripts": {
+ "success": "82012088a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dad51b2",
+ "timeout": "2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac"
+ },
+ "leaf_hashes": {
+ "success": "cd4b7ba74d132998f2bcea85f76082f5018e614c86f27f2631b6569c4914320f",
+ "timeout": "dd0bd08b3df902c399f5493a682f6c50c476c89e233ba454e89a234d2d16ffe3"
+ },
+ "tapscript_root": "f36c8bd45002c5264cfce9944211e7bc6ea974a6b90cf99a87812d18acf28a2a",
+ "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0",
+ "output_key": "033e5c3be9f4ce7ae07c28ad5e0eb0ab617c06eeb82b8d6ef10a5bf561848df5f0",
+ "pkscript": "51203e5c3be9f4ce7ae07c28ad5e0eb0ab617c06eeb82b8d6ef10a5bf561848df5f0"
+ },
+ "offered_htlc_remote_commit": {
+ "scripts": {
+ "success": "82012088a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dad51b2",
+ "timeout": "2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac"
+ },
+ "leaf_hashes": {
+ "success": "cd4b7ba74d132998f2bcea85f76082f5018e614c86f27f2631b6569c4914320f",
+ "timeout": "dd0bd08b3df902c399f5493a682f6c50c476c89e233ba454e89a234d2d16ffe3"
+ },
+ "tapscript_root": "f36c8bd45002c5264cfce9944211e7bc6ea974a6b90cf99a87812d18acf28a2a",
+ "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0",
+ "output_key": "033e5c3be9f4ce7ae07c28ad5e0eb0ab617c06eeb82b8d6ef10a5bf561848df5f0",
+ "pkscript": "51203e5c3be9f4ce7ae07c28ad5e0eb0ab617c06eeb82b8d6ef10a5bf561848df5f0"
+ },
+ "accepted_htlc_local_commit": {
+ "scripts": {
+ "success": "82012088a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dad2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739eac",
+ "timeout": "2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead51b26902f401b1"
+ },
+ "leaf_hashes": {
+ "success": "69192ca730d4480044ade8741b8bd0845a32880aebaf58bc6f9186f8d2be8cbf",
+ "timeout": "4da43c795365bf757ed1e9656d12ea744b4cf52b01719a3ea94e6569115623f0"
+ },
+ "tapscript_root": "1a990caa4bb0ed41ceb19e7466fcea5d9b31e3da968f348f6223201c5831d0a3",
+ "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0",
+ "output_key": "029aadbdd9aff986e5ea086cf53ae062972d33d0a5c7f5fb986dafec7fa6d7e6ea",
+ "pkscript": "51209aadbdd9aff986e5ea086cf53ae062972d33d0a5c7f5fb986dafec7fa6d7e6ea"
+ },
+ "accepted_htlc_remote_commit": {
+ "scripts": {
+ "success": "82012088a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dad2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739eac",
+ "timeout": "2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead51b26902f401b1"
+ },
+ "leaf_hashes": {
+ "success": "69192ca730d4480044ade8741b8bd0845a32880aebaf58bc6f9186f8d2be8cbf",
+ "timeout": "4da43c795365bf757ed1e9656d12ea744b4cf52b01719a3ea94e6569115623f0"
+ },
+ "tapscript_root": "1a990caa4bb0ed41ceb19e7466fcea5d9b31e3da968f348f6223201c5831d0a3",
+ "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0",
+ "output_key": "029aadbdd9aff986e5ea086cf53ae062972d33d0a5c7f5fb986dafec7fa6d7e6ea",
+ "pkscript": "51209aadbdd9aff986e5ea086cf53ae062972d33d0a5c7f5fb986dafec7fa6d7e6ea"
+ },
+ "second_level_htlc_success": {
+ "scripts": {
+ "success": "2015ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05ad029000b2"
+ },
+ "leaf_hashes": {
+ "success": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62"
+ },
+ "tapscript_root": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62",
+ "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0",
+ "output_key": "02df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0",
+ "pkscript": "5120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0"
+ },
+ "second_level_htlc_timeout": {
+ "scripts": {
+ "success": "2015ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05ad029000b2"
+ },
+ "leaf_hashes": {
+ "success": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62"
+ },
+ "tapscript_root": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62",
+ "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0",
+ "output_key": "02df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0",
+ "pkscript": "5120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0"
+ }
+ },
+ "transactions": [
+ {
+ "name": "simple commitment tx with no HTLCs",
+ "local_balance_msat": 7000000000,
+ "remote_balance_msat": 3000000000,
+ "fee_per_kw": 15000,
+ "htlcs": null,
+ "local_sec_nonce": "22a453171ba4a634da1addcf660d63d8e23fb63169a2a7206f4e23290e4cc59bb3c3011fe3c31cb4f1192a2df56c2e52350ce0a82060fadf2404af9f81652c5f03b7203dec7c13896b6ff1f58b24f84458c441720a12b5a57426397e22f0a8c78b",
+ "remote_sec_nonce": "ccdaea6955c7bd9fcb082dc67e32809a5bdd3a3edf770cbb990116c45f4b006e4b56aa81f8e555d6bd1906b159af16d0ac352690ccff6f6a99c43842ac2606ca02956e6845a6f346f97c5e028c0f8ab38a76b0124fd7184deab60f682b3e657fdb",
+ "local_nonce": "025f2272ea289c5fe9d52d411f5a50a6d4882341bf0ecb201d5675850a2ba0b09d025bc489cf67752134ba81f8d7f1146d7455baf3190de75a6a661e2405212991a9",
+ "remote_nonce": "02d324627074522af8cf4287caf1e073a3493550b99aed2697e58f476ec402e272039c25fc616207e15917b7145cefcb4c9c702580baf255597d2fa115564a74a130",
+ "remote_partial_sig": "3fa93659d4c2d590eadbd422595a37597ed58607e026a91f4e6e19329134a931",
+ "expected_commitment_tx_hex": "020000000001015474cba49124ab0c4327c244bb2907059585c4af3fa5f3469701534120fec0170000000000c5fe1780044a010000000000002251201249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac44a01000000000000225120f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344ec0c62d00000000002251203609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec3017440874946a00000000002251203e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff30140a4a9eb512a2f4094efdd2c566f1f20cc8a6e2c307a4a44cc3f9fea7fa147dd7038f1b048aa43fa0b4009175c1c37c37b96c01058541f9e1b61110fce4e831d9f55dc1920",
+ "htlc_descs": null
+ },
+ {
+ "name": "commitment tx with five HTLCs untrimmed",
+ "local_balance_msat": 6988000000,
+ "remote_balance_msat": 3000000000,
+ "fee_per_kw": 644,
+ "htlcs": [
+ {
+ "incoming": true,
+ "amount_msat": 1000000,
+ "expiry": 500,
+ "preimage": "0000000000000000000000000000000000000000000000000000000000000000"
+ },
+ {
+ "incoming": true,
+ "amount_msat": 2000000,
+ "expiry": 501,
+ "preimage": "0101010101010101010101010101010101010101010101010101010101010101"
+ },
+ {
+ "incoming": false,
+ "amount_msat": 2000000,
+ "expiry": 502,
+ "preimage": "0202020202020202020202020202020202020202020202020202020202020202"
+ },
+ {
+ "incoming": false,
+ "amount_msat": 3000000,
+ "expiry": 503,
+ "preimage": "0303030303030303030303030303030303030303030303030303030303030303"
+ },
+ {
+ "incoming": true,
+ "amount_msat": 4000000,
+ "expiry": 504,
+ "preimage": "0404040404040404040404040404040404040404040404040404040404040404"
+ }
+ ],
+ "local_sec_nonce": "22a453171ba4a634da1addcf660d63d8e23fb63169a2a7206f4e23290e4cc59bb3c3011fe3c31cb4f1192a2df56c2e52350ce0a82060fadf2404af9f81652c5f03b7203dec7c13896b6ff1f58b24f84458c441720a12b5a57426397e22f0a8c78b",
+ "remote_sec_nonce": "8c40a8ab26f1bd7d58010e991c72b0f102df6d0f050df28f56cefdafb9d06479958ea0a72180184e338b1b77fdf20690d5d980aac5c34cf123b189e373dba33e02956e6845a6f346f97c5e028c0f8ab38a76b0124fd7184deab60f682b3e657fdb",
+ "local_nonce": "025f2272ea289c5fe9d52d411f5a50a6d4882341bf0ecb201d5675850a2ba0b09d025bc489cf67752134ba81f8d7f1146d7455baf3190de75a6a661e2405212991a9",
+ "remote_nonce": "038a4018a074b5ddfc1424551e871bd739259c4e209f47177eb67c70cfbdb1e57c03c75684ad3a42d8c86a3eddb83b8160d67ef272078b44f21a8f889ee25e2459d3",
+ "remote_partial_sig": "46efde50f08c128aa6472bbd50ea156fb9bde7b013f5e042f700729c94053613",
+ "expected_commitment_tx_hex": "020000000001015474cba49124ab0c4327c244bb2907059585c4af3fa5f3469701534120fec0170000000000c5fe1780094a010000000000002251201249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac44a01000000000000225120f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344ee8030000000000002251209ce82cd1b1f6f975049d58019a7145a3ec9680079969cf929d7d2c4bc9b30637d0070000000000002251208937f8afbc80cf4ba773f1adc3d63ea26259f80f5a3ba622211906d2e7e6e23dd007000000000000225120bf9ae94dda9b5b88485cc67a966ec946b237d19626916dee034b789ebd7fd5fcb80b0000000000002251208fe2e1306e414e896dfd879475b5c1a6a01d4e79b32c0544aa185ccb73c392aaa00f000000000000225120d93389ba5cdde8570d3ba73487ff7fc9f8c3816645009e42110fe5239f5a3e62c0c62d00000000002251203609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec30174408b3996a00000000002251203e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff301409dfe3b178022d975e4b86bd1f04bccfc7576363dbaf58f2ac682136ad89cbeb1a1d07eca1e0bc547b5c5c1133214565e5dfdc230bc7d4736aa7e1be3fb8269d355dc1920",
+ "htlc_descs": [
+ {
+ "remote_partial_sig_hex": "fc97f7cfb97e1e48792b0ed174704cd98c886d368ede5adeb6288f0b350c8f88d076488973d0656da72d072e03eb9eb8b31737850894bca0924d8fdd63ddea69",
+ "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f02000000000100000001e803000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba00541fc97f7cfb97e1e48792b0ed174704cd98c886d368ede5adeb6288f0b350c8f88d076488973d0656da72d072e03eb9eb8b31737850894bca0924d8fdd63ddea6983405bd66541625ba684bb6ff0def3c542bb88d5195a760cb616a5d09dec6823238ccdde18c99d4f3634394d9c23d0e198babc609f464d9b4552664ca5d3b985758f2000000000000000000000000000000000000000000000000000000000000000005f82012088a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc6882071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c0d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0e5e8fd071b9ade6367122afbd8acacc1a6727ddb6d478612af30827590027e0300000000"
+ },
+ {
+ "remote_partial_sig_hex": "c99d8d1ca721d1d4b9796cde49698fd46bb3faddd2bc7215de291ae9bd85cd9b2ab9bf40e36c752b698098b0986abe92b3cb21de8d6a388d9a6988d468e981e5",
+ "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f03000000000100000001d007000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba00541c99d8d1ca721d1d4b9796cde49698fd46bb3faddd2bc7215de291ae9bd85cd9b2ab9bf40e36c752b698098b0986abe92b3cb21de8d6a388d9a6988d468e981e583406a52ec691b47371892192e7222a76d978c18038b3f12479977366a09d2db91e66091332158d8f0b4e125394e1bf2d7ab40ab564c49e0686ffad56eadd6a1297e2001010101010101010101010101010101010101010101010101010101010101015f82012088a9144b6b2e5444c2639cc0fb7bcea5afba3f3cdce239882071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c0d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0127d1790461eff920f14ba7cff2093c44b8a83e6f0a959fa60e04cf8c435cf4b00000000"
+ },
+ {
+ "remote_partial_sig_hex": "b361ed8b70a09f4128fe610db649abcaafbc9b8600136e6f1b9735b4781cd65681faa32c29fef2a1485cc569c655b31b8eb75ab593749dd7f7d788fdec489133",
+ "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f04000000000100000001d007000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba00441b361ed8b70a09f4128fe610db649abcaafbc9b8600136e6f1b9735b4781cd65681faa32c29fef2a1485cc569c655b31b8eb75ab593749dd7f7d788fdec48913383403e94dbe70a26fc19b0d0053dfbf3da4c3e75dacf4abe337866645ffe21834a152f2edf7e520f352f104eef17fefa5a1535d692012cc766d36bd54f658c5c797f442071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c1d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a040b30263c4d7cd1fa6544e8bc8cd9efe857d7b5fd691c958936c3a2e0df2232ef6010000"
+ },
+ {
+ "remote_partial_sig_hex": "9d1193d1bae8793ec502aa705a43a140e9f4c46607b4b8b414f76923c746a81cc320ffef6575f4c1ba55795770b41885dc90ed19025522805b1d0f5f5d440ebd",
+ "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f05000000000100000001b80b000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba004419d1193d1bae8793ec502aa705a43a140e9f4c46607b4b8b414f76923c746a81cc320ffef6575f4c1ba55795770b41885dc90ed19025522805b1d0f5f5d440ebd8340b149f17aab590815fbe4b19796dfc98aa857e81cdc1d15c82d64a2b626f40af8ae2a884197d74071e9b5648c7b5380db1084ac8ed2a8ed54ca9d589e6b5f7d8a442071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c0d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a064c44563d1bd58fa25c5c3ca7303c75849b6b3d91bf2e28f27068db4319b4c2ff7010000"
+ },
+ {
+ "remote_partial_sig_hex": "58338a2d50a03ea615f0e0e295b12413308e6381412c14d276c4b39a2f1d17193a689cbe2ad0d33a63344cff36daa2edc94aa566d743ad6526ddb5cdb5819ea0",
+ "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f06000000000100000001a00f000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0054158338a2d50a03ea615f0e0e295b12413308e6381412c14d276c4b39a2f1d17193a689cbe2ad0d33a63344cff36daa2edc94aa566d743ad6526ddb5cdb5819ea08340efcfcda15da18a2791a80700b2716d2386ea67649a5e43a505dbe22bd110cc2352e558d62e7abd7afe7640d2ee414affd88555378bfe05aa28d0fa289e8d1b542004040404040404040404040404040404040404040404040404040404040404045f82012088a91418bc1a114ccf9c052d3d23e28d3b0a9d12274342882071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c1d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a06c3390c812b2596986592f02c7f22e4f857fb553805ff9ac1c2bda361c47c3fb00000000"
+ }
+ ]
+ },
+ {
+ "name": "commitment tx with some HTLCs trimmed",
+ "local_balance_msat": 6988000000,
+ "remote_balance_msat": 3000000000,
+ "fee_per_kw": 644,
+ "dust_limit_satoshis": 2500,
+ "htlcs": [
+ {
+ "incoming": true,
+ "amount_msat": 1000000,
+ "expiry": 500,
+ "preimage": "0000000000000000000000000000000000000000000000000000000000000000"
+ },
+ {
+ "incoming": true,
+ "amount_msat": 2000000,
+ "expiry": 501,
+ "preimage": "0101010101010101010101010101010101010101010101010101010101010101"
+ },
+ {
+ "incoming": false,
+ "amount_msat": 2000000,
+ "expiry": 502,
+ "preimage": "0202020202020202020202020202020202020202020202020202020202020202"
+ },
+ {
+ "incoming": false,
+ "amount_msat": 3000000,
+ "expiry": 503,
+ "preimage": "0303030303030303030303030303030303030303030303030303030303030303"
+ },
+ {
+ "incoming": true,
+ "amount_msat": 4000000,
+ "expiry": 504,
+ "preimage": "0404040404040404040404040404040404040404040404040404040404040404"
+ }
+ ],
+ "local_sec_nonce": "22a453171ba4a634da1addcf660d63d8e23fb63169a2a7206f4e23290e4cc59bb3c3011fe3c31cb4f1192a2df56c2e52350ce0a82060fadf2404af9f81652c5f03b7203dec7c13896b6ff1f58b24f84458c441720a12b5a57426397e22f0a8c78b",
+ "remote_sec_nonce": "8c5bd39820b3e20d65bf72e530078bf5e8ba057c1654d0a15778a0d3225e0233eb8d7b1d3574e40e57a2cd12dc3b33193c0ac17545564df3ff88021f3c30033702956e6845a6f346f97c5e028c0f8ab38a76b0124fd7184deab60f682b3e657fdb",
+ "local_nonce": "025f2272ea289c5fe9d52d411f5a50a6d4882341bf0ecb201d5675850a2ba0b09d025bc489cf67752134ba81f8d7f1146d7455baf3190de75a6a661e2405212991a9",
+ "remote_nonce": "03fd9fa808377737b105f7df362ed513e3946f2bb49dfbca5c2ce2be138ff0607502e4c73701eae82afa7a01993f62321648a6235ef0c958e35766a9e53e4eaf9d34",
+ "remote_partial_sig": "3e454598e0661188da0e4cf1b806b13c627adb7ab38bab27418cc976769771c3",
+ "expected_commitment_tx_hex": "020000000001015474cba49124ab0c4327c244bb2907059585c4af3fa5f3469701534120fec0170000000000c5fe1780064a010000000000002251201249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac44a01000000000000225120f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344eb80b0000000000002251208fe2e1306e414e896dfd879475b5c1a6a01d4e79b32c0544aa185ccb73c392aaa00f000000000000225120d93389ba5cdde8570d3ba73487ff7fc9f8c3816645009e42110fe5239f5a3e62c0c62d00000000002251203609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec30174408009b6a00000000002251203e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff30140dfd9604ad0b4fed040382f4829e20a0ef4b5558d0189178a5e9c26a8b4bd8547fd563ee60884daf91c8a1ae71e08b452a93ad3bdf5c572f642d42606736dc5f755dc1920",
+ "htlc_descs": [
+ {
+ "remote_partial_sig_hex": "7058caa9a075344e095d95736bb6a00c5b7259f439e60e7e1c6cd641a20d6a25c13d2930ebe51a69ffea78daa12b07237179c8b0584c75499f7bfa4f5afa9776",
+ "resolution_tx_hex": "020000000001018c47e10e0d210da9e50d73b1adda7a598f79ced25806dd0fa6807bb78780dbbb02000000000100000001b80b000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba004417058caa9a075344e095d95736bb6a00c5b7259f439e60e7e1c6cd641a20d6a25c13d2930ebe51a69ffea78daa12b07237179c8b0584c75499f7bfa4f5afa97768340d3a4bcb6c20446364506f75e1d1c4da8ad5ae2f6e5a7ad544965e48f28c02a4e250b0a958a4b2d9d78632771b8f5ea739b63bbf98a6c7511a2456c0d78844642442071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c0d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a064c44563d1bd58fa25c5c3ca7303c75849b6b3d91bf2e28f27068db4319b4c2ff7010000"
+ },
+ {
+ "remote_partial_sig_hex": "818132325cc01e441876615f30c3df5c27df1722db49005dff3856226a41f34c776f98a35dd74fe2f863ea23ac611e06d0aac890227346b1fc53dc62c789d372",
+ "resolution_tx_hex": "020000000001018c47e10e0d210da9e50d73b1adda7a598f79ced25806dd0fa6807bb78780dbbb03000000000100000001a00f000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba00541818132325cc01e441876615f30c3df5c27df1722db49005dff3856226a41f34c776f98a35dd74fe2f863ea23ac611e06d0aac890227346b1fc53dc62c789d37283408e0222c158069b3f19ec0798b3516b79cc2e6293abb1120d54c41c7bae3aa2579c5c3586ad68275a9f99ac14e9a1e48b9c3c7100fd834585264a943c523bea642004040404040404040404040404040404040404040404040404040404040404045f82012088a91418bc1a114ccf9c052d3d23e28d3b0a9d12274342882071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c1d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a06c3390c812b2596986592f02c7f22e4f857fb553805ff9ac1c2bda361c47c3fb00000000"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala
index def27c0..e8e996c 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/InteractiveTxBuilderSpec.scala
@@ -484,7 +484,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit
val targetFeerate = FeeratePerKw(2500 sat)
val fundingA = 150_000 sat
val utxosA = Seq(80_000 sat, 120_000 sat)
- withFixture(ChannelTypes.SimpleTaprootChannelsStaging(), fundingA, utxosA, 0 sat, Nil, targetFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = false, forRemote = false)) { f =>
+ withFixture(ChannelTypes.SimpleTaprootChannel(), fundingA, utxosA, 0 sat, Nil, targetFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = false, forRemote = false)) { f =>
import f._
alice ! Start(alice2bob.ref)
@@ -2001,7 +2001,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit
val utxosA = Seq(340_000 sat, 40_000 sat, 35_000 sat)
val fundingB1 = 80_000 sat
val utxosB = Seq(290_000 sat, 20_000 sat, 15_000 sat)
- withFixture(ChannelTypes.SimpleTaprootChannelsStaging(), fundingA1, utxosA, fundingB1, utxosB, targetFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = false, forRemote = false)) { f =>
+ withFixture(ChannelTypes.SimpleTaprootChannel(), fundingA1, utxosA, fundingB1, utxosB, targetFeerate, 660 sat, 0, RequireConfirmedInputs(forLocal = false, forRemote = false)) { f =>
import f._
val probe = TestProbe()
@@ -2460,7 +2460,7 @@ class InteractiveTxBuilderSpec extends TestKitBaseClass with AnyFunSuiteLike wit
}
test("invalid tx_signatures (missing shared input signature, taproot)") {
- testTxSignaturesMissingSharedInputSigs(ChannelTypes.SimpleTaprootChannelsStaging())
+ testTxSignaturesMissingSharedInputSigs(ChannelTypes.SimpleTaprootChannel())
}
test("invalid commitment index") {
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala
index 50e79fd..f68e53a 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/ChannelStateTestsHelperMethods.scala
@@ -263,8 +263,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.DualFunding))(_.updated(Features.DualFunding, FeatureSupport.Optional))
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.SimpleClose))(_.updated(Features.SimpleClose, FeatureSupport.Optional))
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.AnchorOutputsPhoenix))(_.removed(Features.AnchorOutputsZeroFeeHtlcTx).updated(Features.AnchorOutputs, FeatureSupport.Optional))
- .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaprootPhoenix))(_.removed(Features.SimpleTaprootChannelsStaging).updated(Features.SimpleTaprootChannelsPhoenix, FeatureSupport.Optional).updated(Features.PhoenixZeroReserve, FeatureSupport.Optional))
- .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaproot))(_.updated(Features.SimpleTaprootChannelsStaging, FeatureSupport.Optional))
+ .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaproot))(_.updated(Features.SimpleTaprootChannels, FeatureSupport.Optional))
+ .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaprootPhoenix))(_.removed(Features.SimpleTaprootChannels).updated(Features.SimpleTaprootChannelsPhoenix, FeatureSupport.Optional).updated(Features.PhoenixZeroReserve, FeatureSupport.Optional))
)
val nodeParamsB1 = nodeParamsB.copy(features = nodeParamsB.features
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.DisableWumbo))(_.removed(Features.Wumbo))
@@ -275,8 +275,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.SimpleClose))(_.updated(Features.SimpleClose, FeatureSupport.Optional))
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.DisableSplice))(_.removed(Features.Splicing))
.modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.AnchorOutputsPhoenix))(_.removed(Features.AnchorOutputsZeroFeeHtlcTx).updated(Features.AnchorOutputs, FeatureSupport.Optional))
- .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaprootPhoenix))(_.removed(Features.SimpleTaprootChannelsStaging).updated(Features.SimpleTaprootChannelsPhoenix, FeatureSupport.Optional).updated(Features.PhoenixZeroReserve, FeatureSupport.Optional))
- .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaproot))(_.updated(Features.SimpleTaprootChannelsStaging, FeatureSupport.Optional))
+ .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaproot))(_.updated(Features.SimpleTaprootChannels, FeatureSupport.Optional))
+ .modify(_.activated).usingIf(tags.contains(ChannelStateTestsTags.OptionSimpleTaprootPhoenix))(_.removed(Features.SimpleTaprootChannels).updated(Features.SimpleTaprootChannelsPhoenix, FeatureSupport.Optional).updated(Features.PhoenixZeroReserve, FeatureSupport.Optional))
)
(nodeParamsA1, nodeParamsB1)
}
@@ -287,8 +287,8 @@ trait ChannelStateTestsBase extends Assertions with Eventually {
val scidAlias = canUse(Features.ScidAlias) && !announceChannel // alias feature is incompatible with public channel
val zeroConf = canUse(Features.ZeroConf)
- if (canUse(Features.SimpleTaprootChannelsStaging)) {
- ChannelTypes.SimpleTaprootChannelsStaging(scidAlias, zeroConf)
+ if (canUse(Features.SimpleTaprootChannels)) {
+ ChannelTypes.SimpleTaprootChannel(scidAlias, zeroConf)
} else if (canUse(Features.SimpleTaprootChannelsPhoenix)) {
ChannelTypes.SimpleTaprootChannelsPhoenix
} else if (canUse(Features.AnchorOutputsZeroFeeHtlcTx)) {
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala
index 3248890..84054ed 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/a/WaitForOpenChannelStateSpec.scala
@@ -78,7 +78,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
test("recv OpenChannel (simple taproot channels)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
- assert(open.channelType_opt.contains(ChannelTypes.SimpleTaprootChannelsStaging()))
+ assert(open.channelType_opt.contains(ChannelTypes.SimpleTaprootChannel()))
alice2bob.forward(bob)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CREATED)
assert(bob.stateData.asInstanceOf[DATA_WAIT_FOR_FUNDING_CREATED].commitmentFormat == ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat)
@@ -88,7 +88,7 @@ class WaitForOpenChannelStateSpec extends TestKitBaseClass with FixtureAnyFunSui
test("recv OpenChannel (simple taproot channels, missing nonce)", Tag(ChannelStateTestsTags.OptionSimpleTaproot)) { f =>
import f._
val open = alice2bob.expectMsgType[OpenChannel]
- assert(open.channelType_opt.contains(ChannelTypes.SimpleTaprootChannelsStaging()))
+ assert(open.channelType_opt.contains(ChannelTypes.SimpleTaprootChannel()))
assert(open.commitNonce_opt.isDefined)
alice2bob.forward(bob, open.copy(tlvStream = open.tlvStream.copy(records = open.tlvStream.records.filterNot(_.isInstanceOf[ChannelTlv.NextLocalNonceTlv]))))
val error = bob2alice.expectMsgType[Error]
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala
index b8badd7..85f9600 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/io/OpenChannelInterceptorSpec.scala
@@ -167,6 +167,33 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory
assert(peer.expectMessageType[SpawnChannelNonInitiator].addFunding_opt.isEmpty)
}
+ test("reject open channel request for public taproot channels") { f =>
+ import f._
+
+ // We reject remote public taproot channels.
+ val open = createOpenChannelMessage(ChannelTypes.SimpleTaprootChannel()).copy(channelFlags = ChannelFlags(announceChannel = true))
+ val taprootFeatures = defaultFeatures.add(Features.SimpleTaprootChannels, FeatureSupport.Optional)
+ val openChannelNonInitiator = OpenChannelNonInitiator(remoteNodeId, Left(open), taprootFeatures, taprootFeatures, peerConnection.ref, remoteAddress)
+ openChannelInterceptor ! openChannelNonInitiator
+ assert(peer.expectMessageType[OutgoingMessage].msg.asInstanceOf[Error].toAscii.contains("public taproot channels aren't supported yet"))
+ eventListener.expectMessageType[ChannelAborted]
+
+ // We make sure that we don't try to open public taproot channels.
+ val probe = TestProbe[Any]()
+ val openChannelInitiator = Peer.OpenChannel(remoteNodeId, 500_000 sat, Some(ChannelTypes.SimpleTaprootChannel()), None, None, None, None, Some(ChannelFlags(announceChannel = true)), None)
+ openChannelInterceptor ! OpenChannelInitiator(probe.ref, remoteNodeId, openChannelInitiator, taprootFeatures, taprootFeatures)
+ assert(probe.expectMessageType[OpenChannelResponse.Rejected].reason.contains("public taproot channels aren't supported yet"))
+
+ // If we want to announce the channel, we fallback to anchor outputs.
+ openChannelInterceptor ! OpenChannelInitiator(probe.ref, remoteNodeId, openChannelInitiator.copy(channelType_opt = None), taprootFeatures, taprootFeatures)
+ assert(peer.expectMessageType[Peer.SpawnChannelInitiator].channelType == ChannelTypes.AnchorOutputsZeroFeeHtlcTx())
+
+ // If we don't want to announce the channel, we can use taproot.
+ openChannelInterceptor ! OpenChannelInitiator(probe.ref, remoteNodeId, openChannelInitiator.copy(channelFlags_opt = Some(ChannelFlags(announceChannel = false))), taprootFeatures, taprootFeatures)
+ assert(peer.expectMessageType[Peer.SpawnChannelInitiator].channelType == ChannelTypes.SimpleTaprootChannel())
+ probe.expectNoMessage(100 millis)
+ }
+
test("reject open channel request if rejected by the plugin") { f =>
import f._
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala
index 1cdf94c..83b2e5d 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/transactions/TestVectorsSpec.scala
@@ -19,18 +19,21 @@ package fr.acinq.eclair.transactions
import fr.acinq.bitcoin.ScriptFlags
import fr.acinq.bitcoin.SigHash.SIGHASH_ALL
import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
+import fr.acinq.bitcoin.scalacompat.Musig2.{IndividualNonce, LocalNonce, SecretNonce}
import fr.acinq.bitcoin.scalacompat.Transaction.encodeWitnessEcdsaSig
-import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto, Satoshi, SatoshiLong, Script, Transaction}
+import fr.acinq.bitcoin.scalacompat.{ByteVector32, ByteVector64, Crypto, KotlinUtils, Satoshi, SatoshiLong, Script, ScriptWitness, Transaction}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
-import fr.acinq.eclair.crypto.keymanager.{ChannelKeys, LocalCommitmentKeys, RemoteCommitmentKeys}
-import fr.acinq.eclair.reputation.Reputation
+import fr.acinq.eclair.crypto.keymanager.{ChannelKeys, CommitmentPublicKeys, LocalCommitmentKeys, RemoteCommitmentKeys}
import fr.acinq.eclair.transactions.Transactions._
import fr.acinq.eclair.wire.protocol.UpdateAddHtlc
import fr.acinq.eclair.{ChannelTypeFeature, CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, TestConstants}
import grizzled.slf4j.Logging
+import org.json4s.DefaultFormats
+import org.json4s.jackson.JsonMethods
import org.scalatest.funsuite.AnyFunSuite
import scodec.bits._
+import java.io.File
import scala.io.Source
trait TestVectorsSpec extends AnyFunSuite with Logging {
@@ -457,3 +460,180 @@ class AnchorOutputsZeroFeeHtlcTxTestVectorSpec extends TestVectorsSpec {
override def channelFeatures: Set[ChannelTypeFeature] = Set(Features.StaticRemoteKey, Features.AnchorOutputsZeroFeeHtlcTx)
// @formatter:on
}
+
+class SimpleTaprootCommitmentsTestVectorSpec extends AnyFunSuite {
+
+ implicit val formats: DefaultFormats.type = DefaultFormats
+
+ case class TestFixture(params: TestParams, scripts: TestScripts, transactions: Seq[TestVector]) {
+ val fundingTx: Transaction = Transaction.read(scripts.funding.funding_tx_hex)
+ val fundingInfo: RedeemInfo = makeFundingScript(
+ params.keys.localFundingKey.publicKey,
+ params.keys.remoteFundingKey.publicKey,
+ ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat
+ )
+ val commitInput: InputInfo = makeFundingInputInfo(
+ fundingTx.txid,
+ 0,
+ Satoshi(params.funding_amount_satoshis),
+ params.keys.localFundingKey.publicKey,
+ params.keys.remoteFundingKey.publicKey,
+ ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat
+ )
+ }
+
+ case class TestParams(seed: String,
+ funding_amount_satoshis: Long,
+ dust_limit_satoshis: Long,
+ csv_delay: Int,
+ commit_height: Long,
+ nums_point: String,
+ keys: TestKeys) {
+ val dustLimit: Satoshi = Satoshi(dust_limit_satoshis)
+ val toSelfDelay: CltvExpiryDelta = CltvExpiryDelta(csv_delay)
+ // Private keys used to generate and sign our local commitment transaction and HTLC transactions.
+ val localKeys: LocalCommitmentKeys = LocalCommitmentKeys(
+ ourDelayedPaymentKey = ChannelKeys.derivePerCommitmentKey(PrivateKey(ByteVector.fromValidHex(keys.local_delayed_payment_basepoint_secret)), PublicKey(ByteVector.fromValidHex(keys.local_per_commit_point))),
+ theirPaymentPublicKey = PrivateKey(ByteVector.fromValidHex(keys.remote_payment_basepoint_secret)).publicKey,
+ ourPaymentBasePoint = PrivateKey(ByteVector.fromValidHex(keys.local_payment_basepoint_secret)).publicKey,
+ ourHtlcKey = ChannelKeys.derivePerCommitmentKey(PrivateKey(ByteVector.fromValidHex(keys.local_htlc_basepoint_secret)), PublicKey(ByteVector.fromValidHex(keys.local_per_commit_point))),
+ theirHtlcPublicKey = ChannelKeys.remotePerCommitmentPublicKey(PrivateKey(ByteVector.fromValidHex(keys.remote_htlc_basepoint_secret)).publicKey, PublicKey(ByteVector.fromValidHex(keys.local_per_commit_point))),
+ revocationPublicKey = ChannelKeys.revocationPublicKey(PublicKey(ByteVector.fromValidHex(keys.remote_revocation_basepoint)), PublicKey(ByteVector.fromValidHex(keys.local_per_commit_point)))
+ )
+ // Keys used to sign our HTLC transactions by the remote peer.
+ val remoteKeys: RemoteCommitmentKeys = RemoteCommitmentKeys(
+ ourPaymentKey = PrivateKey(ByteVector.fromValidHex(keys.remote_payment_basepoint_secret)),
+ theirDelayedPaymentPublicKey = localKeys.ourDelayedPaymentKey.publicKey,
+ ourPaymentBasePoint = PrivateKey(ByteVector.fromValidHex(keys.remote_payment_basepoint_secret)).publicKey,
+ ourHtlcKey = ChannelKeys.derivePerCommitmentKey(PrivateKey(ByteVector.fromValidHex(keys.remote_htlc_basepoint_secret)), PublicKey(ByteVector.fromValidHex(keys.local_per_commit_point))),
+ theirHtlcPublicKey = localKeys.ourHtlcKey.publicKey,
+ revocationPublicKey = localKeys.revocationPublicKey
+ )
+ }
+
+ case class TestKeys(local_funding_privkey: String,
+ local_funding_pubkey: String,
+ remote_funding_privkey: String,
+ remote_funding_pubkey: String,
+ local_payment_basepoint_secret: String,
+ local_payment_basepoint: String,
+ remote_payment_basepoint_secret: String,
+ remote_payment_basepoint: String,
+ local_delayed_payment_basepoint_secret: String,
+ local_delayed_payment_basepoint: String,
+ remote_revocation_basepoint_secret: String,
+ remote_revocation_basepoint: String,
+ local_htlc_basepoint_secret: String,
+ local_htlc_basepoint: String,
+ remote_htlc_basepoint_secret: String,
+ remote_htlc_basepoint: String,
+ local_per_commit_secret: String,
+ local_per_commit_point: String,
+ derived_local_delayed_pubkey: String,
+ derived_revocation_pubkey: String,
+ derived_local_htlc_pubkey: String,
+ derived_remote_htlc_pubkey: String,
+ derived_remote_payment_pubkey: String) {
+ val localFundingKey: PrivateKey = PrivateKey(ByteVector.fromValidHex(local_funding_privkey))
+ val remoteFundingKey: PrivateKey = PrivateKey(ByteVector.fromValidHex(remote_funding_privkey))
+ }
+
+ case class TestScripts(funding: TestFundingTx)
+
+ case class TestFundingTx(funding_tx_hex: String)
+
+ case class TestVector(name: String,
+ local_balance_msat: Long,
+ remote_balance_msat: Long,
+ fee_per_kw: Long,
+ dust_limit_satoshis: Option[Long],
+ local_nonce: String,
+ local_sec_nonce: String,
+ remote_nonce: String,
+ remote_sec_nonce: String,
+ htlcs: Seq[TestHtlc],
+ remote_partial_sig: String,
+ expected_commitment_tx_hex: String,
+ htlc_descs: Seq[TestHtlcTx]) {
+ val dustLimitOverride_opt: Option[Satoshi] = dust_limit_satoshis.map(_.sat)
+ val spec = CommitmentSpec(
+ htlcs = htlcs.zipWithIndex.map {
+ case (htlc, i) if htlc.incoming => IncomingHtlc(htlc.withId(i))
+ case (htlc, i) => OutgoingHtlc(htlc.withId(i))
+ }.toSet,
+ commitTxFeerate = FeeratePerKw(Satoshi(fee_per_kw)),
+ toLocal = MilliSatoshi(local_balance_msat),
+ toRemote = MilliSatoshi(remote_balance_msat),
+ )
+ }
+
+ case class TestHtlc(incoming: Boolean, amount_msat: Long, expiry: Long, preimage: String) {
+ def withId(id: Long): UpdateAddHtlc = UpdateAddHtlc(ByteVector32.Zeroes, id, MilliSatoshi(amount_msat), Crypto.sha256(ByteVector32.fromValidHex(preimage)), CltvExpiry(expiry), TestConstants.emptyOnionPacket, None, accountable = false, None)
+ }
+
+ case class TestHtlcTx(remote_partial_sig_hex: String, resolution_tx_hex: String)
+
+ /** Secret nonces in test vectors use a custom encoding. */
+ private def deserializeSecretNonce(hex: String): SecretNonce = {
+ val serialized = ByteVector.fromValidHex(hex)
+ // In test vectors, secret nonces are serialized as: <scalar_1> <scalar_2> <compressed_public_key>
+ // We expect secret nonces serialized as: <magic> <scalar_1> <scalar_2> <public_key_x> <public_key_y>
+ // Where we use a different endianness for the public key coordinates than the test vectors.
+ val uncompressedPublicKey = PublicKey(serialized.takeRight(33)).toUncompressedBin
+ val publicKeyX = uncompressedPublicKey.drop(1).take(32).reverse
+ val publicKeyY = uncompressedPublicKey.takeRight(32).reverse
+ val sec = new fr.acinq.bitcoin.crypto.musig2.SecretNonce(KotlinUtils.scala2kmp(hex"220EDCF1" ++ serialized.take(64) ++ publicKeyX ++ publicKeyY))
+ SecretNonce(sec)
+ }
+
+ test("simple-taproot-commitments (Bolt3 reference test vector)") {
+ val src = Source.fromFile(new File(getClass.getResource("/bolt3-tx-test-vectors-simple-taproot-commitment-format.json").getFile))
+ val f = JsonMethods.parse(src.mkString).extract[TestFixture]
+ src.close()
+ import f._
+ import f.params._
+
+ // We verify that keys are generated correctly.
+ assert(localKeys.publicKeys == CommitmentPublicKeys(
+ localDelayedPaymentPublicKey = PublicKey(ByteVector.fromValidHex(keys.derived_local_delayed_pubkey)),
+ remotePaymentPublicKey = PublicKey(ByteVector.fromValidHex(keys.derived_remote_payment_pubkey)),
+ localHtlcPublicKey = PublicKey(ByteVector.fromValidHex(keys.derived_local_htlc_pubkey)),
+ remoteHtlcPublicKey = PublicKey(ByteVector.fromValidHex(keys.derived_remote_htlc_pubkey)),
+ revocationPublicKey = PublicKey(ByteVector.fromValidHex(keys.derived_revocation_pubkey)),
+ ))
+
+ transactions.foreach(t => {
+ // We verify that commitment transactions match.
+ val dustLimit = t.dustLimitOverride_opt.getOrElse(f.params.dustLimit)
+ val outputs = makeCommitTxOutputs(keys.localFundingKey.publicKey, keys.remoteFundingKey.publicKey, localKeys.publicKeys, payCommitTxFees = true, dustLimit, toSelfDelay, t.spec, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat)
+ val txInfo = makeCommitTx(commitInput, commit_height, localKeys.ourPaymentBasePoint, remoteKeys.ourPaymentBasePoint, localIsChannelOpener = true, outputs)
+ val localNonce = LocalNonce(deserializeSecretNonce(t.local_sec_nonce), IndividualNonce(ByteVector.fromValidHex(t.local_nonce)))
+ val remoteNonce = LocalNonce(deserializeSecretNonce(t.remote_sec_nonce), IndividualNonce(ByteVector.fromValidHex(t.remote_nonce)))
+ val localSig = txInfo.partialSign(keys.localFundingKey, keys.remoteFundingKey.publicKey, localNonce, Seq(localNonce.publicNonce, remoteNonce.publicNonce)).toOption.get
+ val remoteSig = txInfo.partialSign(keys.remoteFundingKey, keys.localFundingKey.publicKey, remoteNonce, Seq(localNonce.publicNonce, remoteNonce.publicNonce)).toOption.get
+ assert(remoteSig.partialSig.toHex == t.remote_partial_sig)
+ val commitTx = txInfo.aggregateSigs(keys.localFundingKey.publicKey, keys.remoteFundingKey.publicKey, localSig, remoteSig).toOption.get
+ Transaction.correctlySpends(commitTx, Seq(fundingTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
+ assert(commitTx.toString() == t.expected_commitment_tx_hex)
+ // We verify that HTLC transactions match.
+ val unsignedHtlcTxs = makeHtlcTxs(commitTx, outputs, ZeroFeeHtlcTxSimpleTaprootChannelCommitmentFormat)
+ assert(unsignedHtlcTxs.size == t.htlc_descs.size)
+ assert(unsignedHtlcTxs.map(_.input.outPoint) == t.htlc_descs.map(d => Transaction.read(d.resolution_tx_hex).txIn.head.outPoint))
+ val preimages = t.htlcs.map(htlc => Crypto.sha256(ByteVector.fromValidHex(htlc.preimage)) -> ByteVector32.fromValidHex(htlc.preimage)).toMap
+ unsignedHtlcTxs.zip(t.htlc_descs).foreach { case (unsignedHtlcTx, desc) =>
+ val expectedTx = Transaction.read(desc.resolution_tx_hex)
+ Transaction.correctlySpends(expectedTx, Seq(commitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
+ assert(unsignedHtlcTx.tx == expectedTx.updateWitness(0, ScriptWitness.empty))
+ val remoteSig = ByteVector64.fromValidHex(desc.remote_partial_sig_hex)
+ assert(unsignedHtlcTx.checkRemoteSig(localKeys, remoteSig))
+ val signedTx = unsignedHtlcTx match {
+ case htlcTx: UnsignedHtlcSuccessTx => htlcTx.addRemoteSig(localKeys, remoteSig, preimages(htlcTx.paymentHash)).sign()
+ case htlcTx: UnsignedHtlcTimeoutTx => htlcTx.addRemoteSig(localKeys, remoteSig).sign()
+ }
+ Transaction.correctlySpends(signedTx, Seq(commitTx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
+ assert(signedTx.toString() == desc.resolution_tx_hex)
+ }
+ })
+ }
+
+}
\ No newline at end of file
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala
index 3db9f6f..1296c6a 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala
@@ -311,7 +311,7 @@ class LightningMessageCodecsSpec extends AnyFunSuite {
defaultEncoded ++ hex"0000" ++ hex"0107 04400000101000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputs(scidAlias = true, zeroConf = true)))),
defaultEncoded ++ hex"0000" ++ hex"0107 04400000401000" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true)))),
// taproot channel type + nonce
- defaultEncoded ++ hex"0000" ++ hex"01 17 1000000000000000000000000000000000400000000000" ++ hex"04 42 2062534ccb3be5a8997843f3b6bc530a94cbc60eceb538674ceedd62d8be07f2dfa5df6acf3ded7444268d56925bb2c33afe71a55f4fa88f3985451a681415930f6b" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.SimpleTaprootChannelsStaging(scidAlias = true)), ChannelTlv.NextLocalNonceTlv(nonce)))
+ defaultEncoded ++ hex"0000" ++ hex"01 0b 0100000000400000000000" ++ hex"04 42 2062534ccb3be5a8997843f3b6bc530a94cbc60eceb538674ceedd62d8be07f2dfa5df6acf3ded7444268d56925bb2c33afe71a55f4fa88f3985451a681415930f6b" -> defaultOpen.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.SimpleTaprootChannel(scidAlias = true)), ChannelTlv.NextLocalNonceTlv(nonce)))
)
for ((encoded, expected) <- testCases) {
@@ -385,7 +385,7 @@ class LightningMessageCodecsSpec extends AnyFunSuite {
defaultEncoded ++ hex"0000" -> defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty))), // empty upfront_shutdown_script
defaultEncoded ++ hex"0000" ++ hex"0100" -> defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.UnsupportedChannelType(Features.empty)))), // empty upfront_shutdown_script with channel type
defaultEncoded ++ hex"0004 01abcdef" -> defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(hex"01abcdef"))), // non-empty upfront_shutdown_script
- defaultEncoded ++ hex"0000" ++ hex"01 17 1000000000000000000000000000000000000000000000" ++ hex"04 42 2062534ccb3be5a8997843f3b6bc530a94cbc60eceb538674ceedd62d8be07f2dfa5df6acf3ded7444268d56925bb2c33afe71a55f4fa88f3985451a681415930f6b" -> defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.SimpleTaprootChannelsStaging()), ChannelTlv.NextLocalNonceTlv(nonce))), // empty upfront_shutdown_script with taproot channel type and nonce
+ defaultEncoded ++ hex"0000" ++ hex"01 0b 0100000000000000000000" ++ hex"04 42 2062534ccb3be5a8997843f3b6bc530a94cbc60eceb538674ceedd62d8be07f2dfa5df6acf3ded7444268d56925bb2c33afe71a55f4fa88f3985451a681415930f6b" -> defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty), ChannelTlv.ChannelTypeTlv(ChannelTypes.SimpleTaprootChannel()), ChannelTlv.NextLocalNonceTlv(nonce))), // empty upfront_shutdown_script with taproot channel type and nonce
defaultEncoded ++ hex"0004 01abcdef" ++ hex"0103401000" -> defaultAccept.copy(tlvStream = TlvStream(ChannelTlv.UpfrontShutdownScriptTlv(hex"01abcdef"), ChannelTlv.ChannelTypeTlv(ChannelTypes.AnchorOutputsZeroFeeHtlcTx()))), // non-empty upfront_shutdown_script with channel type
defaultEncoded ++ hex"0000 0302002a 050102" -> defaultAccept.copy(tlvStream = TlvStream(Set[AcceptChannelTlv](ChannelTlv.UpfrontShutdownScriptTlv(ByteVector.empty)), Set(GenericTlv(UInt64(3), hex"002a"), GenericTlv(UInt64(5), hex"02")))), // empty upfront_shutdown_script + unknown odd tlv records
defaultEncoded ++ hex"0002 1234 0303010203" -> defaultAccept.copy(tlvStream = TlvStream(Set[AcceptChannelTlv](ChannelTlv.UpfrontShutdownScriptTlv(hex"1234")), Set(GenericTlv(UInt64(3), hex"010203")))), // non-empty upfront_shutdown_script + unknown odd tlv records
diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala
index 3a41b1f..fb20416 100644
--- a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala
+++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/Channel.scala
@@ -37,10 +37,10 @@ trait Channel {
ChannelTypes.AnchorOutputsZeroFeeHtlcTx(zeroConf = true),
ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true),
ChannelTypes.AnchorOutputsZeroFeeHtlcTx(scidAlias = true, zeroConf = true),
- ChannelTypes.SimpleTaprootChannelsStaging(),
- ChannelTypes.SimpleTaprootChannelsStaging(zeroConf = true),
- ChannelTypes.SimpleTaprootChannelsStaging(scidAlias = true),
- ChannelTypes.SimpleTaprootChannelsStaging(scidAlias = true, zeroConf = true),
+ ChannelTypes.SimpleTaprootChannel(),
+ ChannelTypes.SimpleTaprootChannel(zeroConf = true),
+ ChannelTypes.SimpleTaprootChannel(scidAlias = true),
+ ChannelTypes.SimpleTaprootChannel(scidAlias = true, zeroConf = true)
).map(ct => ct.toString -> ct).toMap // we use the toString method as name in the api
val open: Route = postRequest("open") { implicit t =>
Why this scored 37/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.