Skip to content

Commit 71a3df4

Browse files
committed
Add high-level helpers for taproot channels
We add a new specific commitment format for taproot channels, and high-level methods for creating and spending taproot channel transactions.
1 parent 650681f commit 71a3df4

35 files changed

+1055
-464
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ case class NodeParams(nodeKeyManager: NodeKeyManager,
134134
min = (commitmentFeerate * feerateTolerance.ratioLow).max(minimumFeerate),
135135
max = (commitmentFormat match {
136136
case Transactions.DefaultCommitmentFormat => commitmentFeerate * feerateTolerance.ratioHigh
137-
case _: Transactions.AnchorOutputsCommitmentFormat => (commitmentFeerate * feerateTolerance.ratioHigh).max(feerateTolerance.anchorOutputMaxCommitFeerate)
137+
case _: Transactions.AnchorOutputsCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat => (commitmentFeerate * feerateTolerance.ratioHigh).max(feerateTolerance.anchorOutputMaxCommitFeerate)
138138
}).max(minimumFeerate),
139139
)
140140
RecommendedFeerates(chainHash, fundingFeerate, commitmentFeerate, TlvStream(fundingRange, commitmentRange))

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ trait SpendFromChannelAddress {
4343
inputTx <- appKit.wallet.getTransaction(outPoint.txid)
4444
localFundingPubkey = appKit.nodeParams.channelKeyManager.fundingPublicKey(fundingKeyPath, fundingTxIndex)
4545
fundingRedeemScript = multiSig2of2(localFundingPubkey.publicKey, remoteFundingPubkey)
46-
inputInfo = InputInfo(outPoint, inputTx.txOut(outPoint.index.toInt), fundingRedeemScript)
46+
inputInfo = InputInfo.SegwitInput(outPoint, inputTx.txOut(outPoint.index.toInt), fundingRedeemScript)
4747
localSig = appKit.nodeParams.channelKeyManager.sign(
4848
Transactions.SpliceTx(inputInfo, unsignedTx), // classify as splice, doesn't really matter
4949
localFundingPubkey,

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

Lines changed: 4 additions & 4 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, SimpleTaprootChannelCommitmentFormat, 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 | SimpleTaprootChannelCommitmentFormat => 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 | SimpleTaprootChannelCommitmentFormat => false
8989
}
9090
}
9191
}
@@ -121,7 +121,7 @@ case class OnChainFeeConf(feeTargets: FeeTargets,
121121

122122
commitmentFormat match {
123123
case Transactions.DefaultCommitmentFormat => networkFeerate
124-
case _: Transactions.AnchorOutputsCommitmentFormat =>
124+
case _: Transactions.AnchorOutputsCommitmentFormat | Transactions.SimpleTaprootChannelCommitmentFormat =>
125125
val targetFeerate = networkFeerate.min(feerateToleranceFor(remoteNodeId).anchorOutputMaxCommitFeerate)
126126
// We make sure the feerate is always greater than the propagation threshold.
127127
targetFeerate.max(networkMinFee * 1.25)

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

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import fr.acinq.eclair.payment.relay.Relayer.RelayFees
3333
import fr.acinq.eclair.router.Announcements
3434
import fr.acinq.eclair.transactions.DirectedHtlc._
3535
import fr.acinq.eclair.transactions.Scripts._
36+
import fr.acinq.eclair.transactions.Transactions.InputInfo.RedeemPath
3637
import fr.acinq.eclair.transactions.Transactions._
3738
import fr.acinq.eclair.transactions._
3839
import fr.acinq.eclair.wire.protocol._
@@ -275,7 +276,7 @@ object Helpers {
275276

276277
for {
277278
script_opt <- extractShutdownScript(accept.temporaryChannelId, localFeatures, remoteFeatures, accept.upfrontShutdownScript_opt)
278-
fundingScript = Funding.makeFundingPubKeyScript(open.fundingPubkey, accept.fundingPubkey)
279+
fundingScript = Funding.makeFundingPubKeyScript(open.fundingPubkey, accept.fundingPubkey, channelType.commitmentFormat)
279280
liquidityPurchase_opt <- LiquidityAds.validateRemoteFunding(open.requestFunding_opt, remoteNodeId, accept.temporaryChannelId, fundingScript, accept.fundingAmount, open.fundingFeerate, isChannelCreation = true, accept.willFund_opt)
280281
} yield {
281282
val channelFeatures = ChannelFeatures(channelType, localFeatures, remoteFeatures, open.channelFlags.announceChannel)
@@ -372,13 +373,20 @@ object Helpers {
372373
}
373374

374375
object Funding {
376+
def makeFundingPubKeyScript(localFundingKey: PublicKey, remoteFundingKey: PublicKey, commitmentFormat: CommitmentFormat): ByteVector = commitmentFormat match {
377+
case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat => write(pay2wsh(multiSig2of2(localFundingKey, remoteFundingKey)))
378+
case SimpleTaprootChannelCommitmentFormat => write(Taproot.musig2FundingScript(localFundingKey, remoteFundingKey))
379+
}
375380

376-
def makeFundingPubKeyScript(localFundingKey: PublicKey, remoteFundingKey: PublicKey): ByteVector = write(pay2wsh(multiSig2of2(localFundingKey, remoteFundingKey)))
377-
378-
def makeFundingInputInfo(fundingTxId: TxId, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey): InputInfo.SegwitInput = {
379-
val fundingScript = multiSig2of2(fundingPubkey1, fundingPubkey2)
380-
val fundingTxOut = TxOut(fundingSatoshis, pay2wsh(fundingScript))
381-
InputInfo.SegwitInput(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, write(fundingScript))
381+
def makeFundingInputInfo(fundingTxId: TxId, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey, commitmentFormat: CommitmentFormat): InputInfo = commitmentFormat match {
382+
case SimpleTaprootChannelCommitmentFormat =>
383+
val fundingScript = Taproot.musig2FundingScript(fundingPubkey1, fundingPubkey2)
384+
val fundingTxOut = TxOut(fundingSatoshis, fundingScript)
385+
InputInfo.TaprootInput(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, Taproot.musig2Aggregate(fundingPubkey1, fundingPubkey2), RedeemPath.KeyPath(None))
386+
case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat =>
387+
val fundingScript = multiSig2of2(fundingPubkey1, fundingPubkey2)
388+
val fundingTxOut = TxOut(fundingSatoshis, pay2wsh(fundingScript))
389+
InputInfo.SegwitInput(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, write(fundingScript))
382390
}
383391

384392
/**
@@ -441,7 +449,7 @@ object Helpers {
441449

442450
val fundingPubKey = keyManager.fundingPublicKey(localParams.fundingKeyPath, fundingTxIndex)
443451
val channelKeyPath = keyManager.keyPath(localParams, channelConfig)
444-
val commitmentInput = makeFundingInputInfo(fundingTxId, fundingTxOutputIndex, fundingAmount, fundingPubKey.publicKey, remoteFundingPubKey)
452+
val commitmentInput = makeFundingInputInfo(fundingTxId, fundingTxOutputIndex, fundingAmount, fundingPubKey.publicKey, remoteFundingPubKey, params.commitmentFormat)
445453
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, localCommitmentIndex)
446454
val (localCommitTx, _) = Commitment.makeLocalTxs(keyManager, channelConfig, channelFeatures, localCommitmentIndex, localParams, remoteParams, fundingTxIndex, remoteFundingPubKey, commitmentInput, localPerCommitmentPoint, localSpec)
447455
val (remoteCommitTx, htlcTxs) = Commitment.makeRemoteTxs(keyManager, channelConfig, channelFeatures, remoteCommitmentIndex, localParams, remoteParams, fundingTxIndex, remoteFundingPubKey, commitmentInput, remotePerCommitmentPoint, remoteSpec)
@@ -679,7 +687,7 @@ object Helpers {
679687
case DefaultCommitmentFormat =>
680688
// we "MUST set fee_satoshis less than or equal to the base fee of the final commitment transaction"
681689
requestedFeerate.min(commitment.localCommit.spec.commitTxFeerate)
682-
case _: AnchorOutputsCommitmentFormat => requestedFeerate
690+
case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => requestedFeerate
683691
}
684692
// NB: we choose a minimum fee that ensures the tx will easily propagate while allowing low fees since we can
685693
// always use CPFP to speed up confirmation if necessary.
@@ -909,13 +917,25 @@ object Helpers {
909917

910918
def claimAnchors(keyManager: ChannelKeyManager, commitment: FullCommitment, lcp: LocalCommitPublished, confirmationTarget: ConfirmationTarget)(implicit log: LoggingAdapter): LocalCommitPublished = {
911919
if (shouldUpdateAnchorTxs(lcp.claimAnchorTxs, confirmationTarget)) {
912-
val localFundingPubKey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey
920+
val (localAnchorKey, remoteAnchorKey) = commitment.params.commitmentFormat match {
921+
case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat =>
922+
// The public keys used in this case are the channel funding public keys.
923+
val localFundingPubkey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey
924+
(localFundingPubkey, commitment.remoteFundingPubKey)
925+
case SimpleTaprootChannelCommitmentFormat =>
926+
// The public keys used in this case are the payment public keys: we don't want to reveal individual
927+
// funding public keys since we're using musig2.
928+
val channelKeyPath = keyManager.keyPath(commitment.localParams, commitment.params.channelConfig)
929+
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, commitment.localCommit.index)
930+
val localDelayedPaymentPubkey = Generators.derivePubKey(keyManager.delayedPaymentPoint(channelKeyPath).publicKey, localPerCommitmentPoint)
931+
(localDelayedPaymentPubkey, commitment.remoteParams.paymentBasepoint)
932+
}
913933
val claimAnchorTxs = List(
914934
withTxGenerationLog("local-anchor") {
915-
Transactions.makeClaimLocalAnchorOutputTx(lcp.commitTx, localFundingPubKey, confirmationTarget)
935+
Transactions.makeClaimLocalAnchorOutputTx(lcp.commitTx, localAnchorKey, confirmationTarget)
916936
},
917937
withTxGenerationLog("remote-anchor") {
918-
Transactions.makeClaimRemoteAnchorOutputTx(lcp.commitTx, commitment.remoteFundingPubKey)
938+
Transactions.makeClaimRemoteAnchorOutputTx(lcp.commitTx, remoteAnchorKey)
919939
}
920940
).flatten
921941
lcp.copy(claimAnchorTxs = claimAnchorTxs)
@@ -1038,13 +1058,23 @@ object Helpers {
10381058

10391059
def claimAnchors(keyManager: ChannelKeyManager, commitment: FullCommitment, rcp: RemoteCommitPublished, confirmationTarget: ConfirmationTarget)(implicit log: LoggingAdapter): RemoteCommitPublished = {
10401060
if (shouldUpdateAnchorTxs(rcp.claimAnchorTxs, confirmationTarget)) {
1041-
val localFundingPubkey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey
1061+
val (localAnchorKey, remoteAnchorKey) = commitment.params.commitmentFormat match {
1062+
case DefaultCommitmentFormat | _: AnchorOutputsCommitmentFormat =>
1063+
// The public keys used in this case are the channel funding public keys.
1064+
val localFundingPubkey = keyManager.fundingPublicKey(commitment.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey
1065+
(localFundingPubkey, commitment.remoteFundingPubKey)
1066+
case SimpleTaprootChannelCommitmentFormat =>
1067+
val channelKeyPath = keyManager.keyPath(commitment.localParams, commitment.params.channelConfig)
1068+
val localPaymentPubkey = commitment.localParams.walletStaticPaymentBasepoint.getOrElse(keyManager.paymentPoint(channelKeyPath).publicKey)
1069+
val remoteDelayedPaymentPubkey = Generators.derivePubKey(commitment.remoteParams.delayedPaymentBasepoint, commitment.remoteCommit.remotePerCommitmentPoint)
1070+
(localPaymentPubkey, remoteDelayedPaymentPubkey)
1071+
}
10421072
val claimAnchorTxs = List(
10431073
withTxGenerationLog("local-anchor") {
1044-
Transactions.makeClaimLocalAnchorOutputTx(rcp.commitTx, localFundingPubkey, confirmationTarget)
1074+
Transactions.makeClaimLocalAnchorOutputTx(rcp.commitTx, localAnchorKey, confirmationTarget)
10451075
},
10461076
withTxGenerationLog("remote-anchor") {
1047-
Transactions.makeClaimRemoteAnchorOutputTx(rcp.commitTx, commitment.remoteFundingPubKey)
1077+
Transactions.makeClaimRemoteAnchorOutputTx(rcp.commitTx, remoteAnchorKey)
10481078
}
10491079
).flatten
10501080
rcp.copy(claimAnchorTxs = claimAnchorTxs)
@@ -1077,7 +1107,7 @@ object Helpers {
10771107
Transactions.addSigs(claimMain, localPubkey, sig)
10781108
})
10791109
}
1080-
case _: AnchorOutputsCommitmentFormat => withTxGenerationLog("remote-main-delayed") {
1110+
case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => withTxGenerationLog("remote-main-delayed") {
10811111
Transactions.makeClaimRemoteDelayedOutputTx(tx, params.localParams.dustLimit, localPaymentPoint, finalScriptPubKey, feeratePerKwMain).map(claimMain => {
10821112
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), TxOwner.Local, params.commitmentFormat, Map.empty)
10831113
Transactions.addSigs(claimMain, sig)
@@ -1224,7 +1254,7 @@ object Helpers {
12241254
Transactions.addSigs(claimMain, localPaymentPubkey, sig)
12251255
})
12261256
}
1227-
case _: AnchorOutputsCommitmentFormat => withTxGenerationLog("remote-main-delayed") {
1257+
case _: AnchorOutputsCommitmentFormat | SimpleTaprootChannelCommitmentFormat => withTxGenerationLog("remote-main-delayed") {
12281258
Transactions.makeClaimRemoteDelayedOutputTx(commitTx, localParams.dustLimit, localPaymentPoint, finalScriptPubKey, feerateMain).map(claimMain => {
12291259
val sig = keyManager.sign(claimMain, keyManager.paymentPoint(channelKeyPath), TxOwner.Local, commitmentFormat, Map.empty)
12301260
Transactions.addSigs(claimMain, sig)

0 commit comments

Comments
 (0)