Skip to content

Commit db93cbe

Browse files
sstonepm47
andauthored
Add support for taproot outputs to our "input info" class (#2895)
* Refactor tx signing (no functional changes) * Upgrade input info class to allow spending from taproot transactions Our InputInfo class contains a tx output and the matching redeem script, which is enough to spend segwit v0 transactions. For taproot transactions, instead of a redeem script, we need a script tree instead, and the appropriate internal pubkey. * Use specific segwit and taproot input info types We now use specific subtypes for segwit inputs (which include a redeem script) and taproot inputs (which include a script tree and an internal key). Older codecs have been modified to always return a SegwitInput. v4 codec is modified and uses an empty redeem script as a marker to specify that a script tree is being used, which makes it compatible with the current v4 codec. Current (v4) codecs only handle segwit inputs. Support for taproot inputs will be added to v5 codecs. --------- Co-authored-by: Pierre-Marie Padiou <[email protected]>
1 parent e99fa2e commit db93cbe

File tree

13 files changed

+173
-126
lines changed

13 files changed

+173
-126
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ object LocalCommit {
225225
fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo,
226226
commit: CommitSig, localCommitIndex: Long, spec: CommitmentSpec, localPerCommitmentPoint: PublicKey): Either[ChannelException, LocalCommit] = {
227227
val (localCommitTx, htlcTxs) = Commitment.makeLocalTxs(keyManager, params.channelConfig, params.channelFeatures, localCommitIndex, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, localPerCommitmentPoint, spec)
228-
if (!checkSig(localCommitTx, commit.signature, remoteFundingPubKey, TxOwner.Remote, params.commitmentFormat)) {
228+
if (!localCommitTx.checkSig(commit.signature, remoteFundingPubKey, TxOwner.Remote, params.commitmentFormat)) {
229229
return Left(InvalidCommitmentSignature(params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx))
230230
}
231231
val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index)
@@ -235,7 +235,7 @@ object LocalCommit {
235235
val remoteHtlcPubkey = Generators.derivePubKey(params.remoteParams.htlcBasepoint, localPerCommitmentPoint)
236236
val htlcTxsAndRemoteSigs = sortedHtlcTxs.zip(commit.htlcSignatures).toList.map {
237237
case (htlcTx: HtlcTx, remoteSig) =>
238-
if (!checkSig(htlcTx, remoteSig, remoteHtlcPubkey, TxOwner.Remote, params.commitmentFormat)) {
238+
if (!htlcTx.checkSig(remoteSig, remoteHtlcPubkey, TxOwner.Remote, params.commitmentFormat)) {
239239
return Left(InvalidHtlcSignature(params.channelId, htlcTx.tx.txid))
240240
}
241241
HtlcTxAndRemoteSig(htlcTx, remoteSig)
@@ -1142,7 +1142,10 @@ case class Commitments(params: ChannelParams,
11421142
val localFundingKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey
11431143
val remoteFundingKey = commitment.remoteFundingPubKey
11441144
val fundingScript = Script.write(Scripts.multiSig2of2(localFundingKey, remoteFundingKey))
1145-
commitment.commitInput.redeemScript == fundingScript
1145+
commitment.commitInput match {
1146+
case InputInfo.SegwitInput(_, _, redeemScript) => redeemScript == fundingScript
1147+
case _: InputInfo.TaprootInput => false
1148+
}
11461149
}
11471150
}
11481151

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,10 +376,10 @@ object Helpers {
376376

377377
def makeFundingPubKeyScript(localFundingKey: PublicKey, remoteFundingKey: PublicKey): ByteVector = write(pay2wsh(multiSig2of2(localFundingKey, remoteFundingKey)))
378378

379-
def makeFundingInputInfo(fundingTxId: TxId, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey): InputInfo = {
379+
def makeFundingInputInfo(fundingTxId: TxId, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey): InputInfo.SegwitInput = {
380380
val fundingScript = multiSig2of2(fundingPubkey1, fundingPubkey2)
381381
val fundingTxOut = TxOut(fundingSatoshis, pay2wsh(fundingScript))
382-
InputInfo(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, write(fundingScript))
382+
InputInfo.SegwitInput(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, write(fundingScript))
383383
}
384384

385385
/**

eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,12 +358,16 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
358358
import fr.acinq.bitcoin.scalacompat.KotlinUtils._
359359

360360
// We create a PSBT with the non-wallet input already signed:
361+
val witnessScript = locallySignedTx.txInfo.input match {
362+
case InputInfo.SegwitInput(_, _, redeemScript) => fr.acinq.bitcoin.Script.parse(redeemScript)
363+
case _: InputInfo.TaprootInput => null
364+
}
361365
val psbt = new Psbt(locallySignedTx.txInfo.tx)
362366
.updateWitnessInput(
363367
locallySignedTx.txInfo.input.outPoint,
364368
locallySignedTx.txInfo.input.txOut,
365369
null,
366-
fr.acinq.bitcoin.Script.parse(locallySignedTx.txInfo.input.redeemScript),
370+
witnessScript,
367371
fr.acinq.bitcoin.SigHash.SIGHASH_ALL,
368372
java.util.Map.of(),
369373
null,

eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManager.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, ByteVector32, ByteVector6
2323
import fr.acinq.eclair.crypto.Generators
2424
import fr.acinq.eclair.crypto.Monitoring.{Metrics, Tags}
2525
import fr.acinq.eclair.router.Announcements
26-
import fr.acinq.eclair.transactions.Transactions
2726
import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, TransactionWithInputInfo, TxOwner}
2827
import fr.acinq.eclair.{KamonExt, randomLong}
2928
import grizzled.slf4j.Logging
@@ -113,7 +112,7 @@ class LocalChannelKeyManager(seed: ByteVector, chainHash: BlockHash) extends Cha
113112
Metrics.SignTxCount.withTags(tags).increment()
114113
KamonExt.time(Metrics.SignTxDuration.withTags(tags)) {
115114
val privateKey = privateKeys.get(publicKey.path)
116-
Transactions.sign(tx, privateKey.privateKey, txOwner, commitmentFormat)
115+
tx.sign(privateKey.privateKey, txOwner, commitmentFormat)
117116
}
118117
}
119118

@@ -134,7 +133,7 @@ class LocalChannelKeyManager(seed: ByteVector, chainHash: BlockHash) extends Cha
134133
KamonExt.time(Metrics.SignTxDuration.withTags(tags)) {
135134
val privateKey = privateKeys.get(publicKey.path)
136135
val currentKey = Generators.derivePrivKey(privateKey.privateKey, remotePoint)
137-
Transactions.sign(tx, currentKey, txOwner, commitmentFormat)
136+
tx.sign(currentKey, txOwner, commitmentFormat)
138137
}
139138
}
140139

@@ -154,7 +153,7 @@ class LocalChannelKeyManager(seed: ByteVector, chainHash: BlockHash) extends Cha
154153
KamonExt.time(Metrics.SignTxDuration.withTags(tags)) {
155154
val privateKey = privateKeys.get(publicKey.path)
156155
val currentKey = Generators.revocationPrivKey(privateKey.privateKey, remoteSecret)
157-
Transactions.sign(tx, currentKey, txOwner, commitmentFormat)
156+
tx.sign(currentKey, txOwner, commitmentFormat)
158157
}
159158
}
160159

0 commit comments

Comments
 (0)