Skip to content

Commit

Permalink
Add support for taproot outputs to our "input info" class (#2895)
Browse files Browse the repository at this point in the history
* 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 <pm47@users.noreply.github.com>
  • Loading branch information
sstone and pm47 authored Jan 8, 2025
1 parent e99fa2e commit db93cbe
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ object LocalCommit {
fundingTxIndex: Long, remoteFundingPubKey: PublicKey, commitInput: InputInfo,
commit: CommitSig, localCommitIndex: Long, spec: CommitmentSpec, localPerCommitmentPoint: PublicKey): Either[ChannelException, LocalCommit] = {
val (localCommitTx, htlcTxs) = Commitment.makeLocalTxs(keyManager, params.channelConfig, params.channelFeatures, localCommitIndex, params.localParams, params.remoteParams, fundingTxIndex, remoteFundingPubKey, commitInput, localPerCommitmentPoint, spec)
if (!checkSig(localCommitTx, commit.signature, remoteFundingPubKey, TxOwner.Remote, params.commitmentFormat)) {
if (!localCommitTx.checkSig(commit.signature, remoteFundingPubKey, TxOwner.Remote, params.commitmentFormat)) {
return Left(InvalidCommitmentSignature(params.channelId, fundingTxId, fundingTxIndex, localCommitTx.tx))
}
val sortedHtlcTxs = htlcTxs.sortBy(_.input.outPoint.index)
Expand All @@ -235,7 +235,7 @@ object LocalCommit {
val remoteHtlcPubkey = Generators.derivePubKey(params.remoteParams.htlcBasepoint, localPerCommitmentPoint)
val htlcTxsAndRemoteSigs = sortedHtlcTxs.zip(commit.htlcSignatures).toList.map {
case (htlcTx: HtlcTx, remoteSig) =>
if (!checkSig(htlcTx, remoteSig, remoteHtlcPubkey, TxOwner.Remote, params.commitmentFormat)) {
if (!htlcTx.checkSig(remoteSig, remoteHtlcPubkey, TxOwner.Remote, params.commitmentFormat)) {
return Left(InvalidHtlcSignature(params.channelId, htlcTx.tx.txid))
}
HtlcTxAndRemoteSig(htlcTx, remoteSig)
Expand Down Expand Up @@ -1142,7 +1142,10 @@ case class Commitments(params: ChannelParams,
val localFundingKey = keyManager.fundingPublicKey(params.localParams.fundingKeyPath, commitment.fundingTxIndex).publicKey
val remoteFundingKey = commitment.remoteFundingPubKey
val fundingScript = Script.write(Scripts.multiSig2of2(localFundingKey, remoteFundingKey))
commitment.commitInput.redeemScript == fundingScript
commitment.commitInput match {
case InputInfo.SegwitInput(_, _, redeemScript) => redeemScript == fundingScript
case _: InputInfo.TaprootInput => false
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,10 +376,10 @@ object Helpers {

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

def makeFundingInputInfo(fundingTxId: TxId, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey): InputInfo = {
def makeFundingInputInfo(fundingTxId: TxId, fundingTxOutputIndex: Int, fundingSatoshis: Satoshi, fundingPubkey1: PublicKey, fundingPubkey2: PublicKey): InputInfo.SegwitInput = {
val fundingScript = multiSig2of2(fundingPubkey1, fundingPubkey2)
val fundingTxOut = TxOut(fundingSatoshis, pay2wsh(fundingScript))
InputInfo(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, write(fundingScript))
InputInfo.SegwitInput(OutPoint(fundingTxId, fundingTxOutputIndex), fundingTxOut, write(fundingScript))
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,12 +358,16 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
import fr.acinq.bitcoin.scalacompat.KotlinUtils._

// We create a PSBT with the non-wallet input already signed:
val witnessScript = locallySignedTx.txInfo.input match {
case InputInfo.SegwitInput(_, _, redeemScript) => fr.acinq.bitcoin.Script.parse(redeemScript)
case _: InputInfo.TaprootInput => null
}
val psbt = new Psbt(locallySignedTx.txInfo.tx)
.updateWitnessInput(
locallySignedTx.txInfo.input.outPoint,
locallySignedTx.txInfo.input.txOut,
null,
fr.acinq.bitcoin.Script.parse(locallySignedTx.txInfo.input.redeemScript),
witnessScript,
fr.acinq.bitcoin.SigHash.SIGHASH_ALL,
java.util.Map.of(),
null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, ByteVector32, ByteVector6
import fr.acinq.eclair.crypto.Generators
import fr.acinq.eclair.crypto.Monitoring.{Metrics, Tags}
import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.transactions.Transactions
import fr.acinq.eclair.transactions.Transactions.{CommitmentFormat, TransactionWithInputInfo, TxOwner}
import fr.acinq.eclair.{KamonExt, randomLong}
import grizzled.slf4j.Logging
Expand Down Expand Up @@ -113,7 +112,7 @@ class LocalChannelKeyManager(seed: ByteVector, chainHash: BlockHash) extends Cha
Metrics.SignTxCount.withTags(tags).increment()
KamonExt.time(Metrics.SignTxDuration.withTags(tags)) {
val privateKey = privateKeys.get(publicKey.path)
Transactions.sign(tx, privateKey.privateKey, txOwner, commitmentFormat)
tx.sign(privateKey.privateKey, txOwner, commitmentFormat)
}
}

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

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

Expand Down
Loading

0 comments on commit db93cbe

Please sign in to comment.