diff --git a/eclair-core/pom.xml b/eclair-core/pom.xml
index 20f3814e34..3d0b506735 100644
--- a/eclair-core/pom.xml
+++ b/eclair-core/pom.xml
@@ -188,6 +188,21 @@
4.1.94.Final
+
+ fr.acinq.bitcoin
+ bitcoin-kmp-jvm
+ 0.20.0-SNAPSHOT
+
+
+ fr.acinq.secp256k1
+ secp256k1-kmp-jvm
+ 0.15.0
+
+
+ fr.acinq.secp256k1
+ secp256k1-kmp-jni-jvm
+ 0.15.0
+
fr.acinq
bitcoin-lib_${scala.version.short}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala
index 678edb8bfd..b17e88119f 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala
@@ -1143,7 +1143,7 @@ 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.redeemScriptOrScriptTree == Left(fundingScript)
}
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala
index 4c3d1777aa..b2bbf97f18 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala
@@ -358,8 +358,17 @@ 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.redeemScriptOrScriptTree match {
+ case Left(redeemScript) => fr.acinq.bitcoin.Script.parse(redeemScript)
+ case _ => 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), fr.acinq.bitcoin.SigHash.SIGHASH_ALL, java.util.Map.of())
+ .updateWitnessInput(
+ locallySignedTx.txInfo.input.outPoint, locallySignedTx.txInfo.input.txOut,
+ null,
+ witnessScript,
+ fr.acinq.bitcoin.SigHash.SIGHASH_ALL,
+ java.util.Map.of(), null, null, java.util.Map.of())
.flatMap(_.finalizeWitnessInput(0, locallySignedTx.txInfo.tx.txIn.head.witness))
psbt match {
case Left(failure) =>
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalOnChainKeyManager.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalOnChainKeyManager.scala
index 0aa38d8194..bdbab30cf9 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalOnChainKeyManager.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalOnChainKeyManager.scala
@@ -202,7 +202,7 @@ class LocalOnChainKeyManager(override val walletName: String, seed: ByteVector,
require(kmp2scala(input.getWitnessUtxo.publicKeyScript) == expectedScript, s"script mismatch (expected=$expectedScript, actual=${input.getWitnessUtxo.publicKeyScript}): bitcoin core may be malicious")
// Update the input with the right script for a p2wpkh input, which is a *p2pkh* script, then sign and finalize.
- val updated: Either[UpdateFailure, Psbt] = psbt.updateWitnessInput(psbt.global.tx.txIn.get(pos).outPoint, input.getWitnessUtxo, null, Script.pay2pkh(pub), SigHash.SIGHASH_ALL, input.getDerivationPaths)
+ val updated: Either[UpdateFailure, Psbt] = psbt.updateWitnessInput(psbt.global.tx.txIn.get(pos).outPoint, input.getWitnessUtxo, null, Script.pay2pkh(pub), SigHash.SIGHASH_ALL, input.getDerivationPaths, null, null, java.util.Map.of())
val signed = updated.flatMap(_.sign(priv, pos))
val finalized = signed.flatMap(s => {
require(s.getSig.last.toInt == SigHash.SIGHASH_ALL, "signature must end with SIGHASH_ALL")
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala
index 4d2019438a..438b5b6f82 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala
@@ -16,10 +16,10 @@
package fr.acinq.eclair.transactions
-import fr.acinq.bitcoin.ScriptFlags
+import fr.acinq.bitcoin.{ScriptFlags, ScriptTree}
import fr.acinq.bitcoin.SigHash._
import fr.acinq.bitcoin.SigVersion._
-import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, ripemd160}
+import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, XonlyPublicKey, ripemd160}
import fr.acinq.bitcoin.scalacompat.Script._
import fr.acinq.bitcoin.scalacompat._
import fr.acinq.eclair._
@@ -94,9 +94,22 @@ object Transactions {
// @formatter:off
case class OutputInfo(index: Long, amount: Satoshi, publicKeyScript: ByteVector)
- case class InputInfo(outPoint: OutPoint, txOut: TxOut, redeemScript: ByteVector)
+
+ /**
+ * to spend the output of a taproot transactions, we need to know the script tree and internal key used to build this output
+ */
+ case class ScriptTreeAndInternalKey(scriptTree: ScriptTree, internalKey: XonlyPublicKey) {
+ val publicKeyScript: ByteVector = Script.write(Script.pay2tr(internalKey, Some(scriptTree)))
+ }
+
+ case class InputInfo(outPoint: OutPoint, txOut: TxOut, redeemScriptOrScriptTree: Either[ByteVector, ScriptTreeAndInternalKey]) {
+ val redeemScriptOrEmptyScript: ByteVector = redeemScriptOrScriptTree.swap.getOrElse(ByteVector.empty) // TODO: use the actual script tree for taproot transactions, once we implement them
+ }
+
object InputInfo {
- def apply(outPoint: OutPoint, txOut: TxOut, redeemScript: Seq[ScriptElt]) = new InputInfo(outPoint, txOut, Script.write(redeemScript))
+ def apply(outPoint: OutPoint, txOut: TxOut, redeemScript: ByteVector) = new InputInfo(outPoint, txOut, Left(redeemScript))
+ def apply(outPoint: OutPoint, txOut: TxOut, redeemScript: Seq[ScriptElt]) = new InputInfo(outPoint, txOut, Left(Script.write(redeemScript)))
+ def apply(outPoint: OutPoint, txOut: TxOut, scriptTree: ScriptTreeAndInternalKey) = new InputInfo(outPoint, txOut, Right(scriptTree))
}
/** Owner of a given transaction (local/remote). */
@@ -125,12 +138,12 @@ object Transactions {
// NB: the tx may have multiple inputs, we will only sign the one provided in txinfo.input. Bear in mind that the
// signature will be invalidated if other inputs are added *afterwards* and sighashType was SIGHASH_ALL.
val inputIndex = tx.txIn.zipWithIndex.find(_._1.outPoint == input.outPoint).get._2
- Transactions.sign(tx, input.redeemScript, input.txOut.amount, key, sighashType, inputIndex)
+ Transactions.sign(tx, input.redeemScriptOrEmptyScript, input.txOut.amount, key, sighashType, inputIndex)
}
def checkSig(sig: ByteVector64, pubKey: PublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): Boolean = {
val sighash = this.sighash(txOwner, commitmentFormat)
- val data = Transaction.hashForSigning(tx, inputIndex = 0, input.redeemScript, sighash, input.txOut.amount, SIGVERSION_WITNESS_V0)
+ val data = Transaction.hashForSigning(tx, inputIndex = 0, input.redeemScriptOrEmptyScript, sighash, input.txOut.amount, SIGVERSION_WITNESS_V0)
Crypto.verifySignature(data, sig, pubKey)
}
}
@@ -872,7 +885,7 @@ object Transactions {
// NB: the tx may have multiple inputs, we will only sign the one provided in txinfo.input. Bear in mind that the
// signature will be invalidated if other inputs are added *afterwards* and sighashType was SIGHASH_ALL.
val inputIndex = txinfo.tx.txIn.zipWithIndex.find(_._1.outPoint == txinfo.input.outPoint).get._2
- sign(txinfo.tx, txinfo.input.redeemScript, txinfo.input.txOut.amount, key, sighashType, inputIndex)
+ sign(txinfo.tx, txinfo.input.redeemScriptOrEmptyScript, txinfo.input.txOut.amount, key, sighashType, inputIndex)
}
def addSigs(commitTx: CommitTx, localFundingPubkey: PublicKey, remoteFundingPubkey: PublicKey, localSig: ByteVector64, remoteSig: ByteVector64): CommitTx = {
@@ -881,32 +894,32 @@ object Transactions {
}
def addSigs(mainPenaltyTx: MainPenaltyTx, revocationSig: ByteVector64): MainPenaltyTx = {
- val witness = Scripts.witnessToLocalDelayedWithRevocationSig(revocationSig, mainPenaltyTx.input.redeemScript)
+ val witness = Scripts.witnessToLocalDelayedWithRevocationSig(revocationSig, mainPenaltyTx.input.redeemScriptOrEmptyScript)
mainPenaltyTx.copy(tx = mainPenaltyTx.tx.updateWitness(0, witness))
}
def addSigs(htlcPenaltyTx: HtlcPenaltyTx, revocationSig: ByteVector64, revocationPubkey: PublicKey): HtlcPenaltyTx = {
- val witness = Scripts.witnessHtlcWithRevocationSig(revocationSig, revocationPubkey, htlcPenaltyTx.input.redeemScript)
+ val witness = Scripts.witnessHtlcWithRevocationSig(revocationSig, revocationPubkey, htlcPenaltyTx.input.redeemScriptOrEmptyScript)
htlcPenaltyTx.copy(tx = htlcPenaltyTx.tx.updateWitness(0, witness))
}
def addSigs(htlcSuccessTx: HtlcSuccessTx, localSig: ByteVector64, remoteSig: ByteVector64, paymentPreimage: ByteVector32, commitmentFormat: CommitmentFormat): HtlcSuccessTx = {
- val witness = witnessHtlcSuccess(localSig, remoteSig, paymentPreimage, htlcSuccessTx.input.redeemScript, commitmentFormat)
+ val witness = witnessHtlcSuccess(localSig, remoteSig, paymentPreimage, htlcSuccessTx.input.redeemScriptOrEmptyScript, commitmentFormat)
htlcSuccessTx.copy(tx = htlcSuccessTx.tx.updateWitness(0, witness))
}
def addSigs(htlcTimeoutTx: HtlcTimeoutTx, localSig: ByteVector64, remoteSig: ByteVector64, commitmentFormat: CommitmentFormat): HtlcTimeoutTx = {
- val witness = witnessHtlcTimeout(localSig, remoteSig, htlcTimeoutTx.input.redeemScript, commitmentFormat)
+ val witness = witnessHtlcTimeout(localSig, remoteSig, htlcTimeoutTx.input.redeemScriptOrEmptyScript, commitmentFormat)
htlcTimeoutTx.copy(tx = htlcTimeoutTx.tx.updateWitness(0, witness))
}
def addSigs(claimHtlcSuccessTx: ClaimHtlcSuccessTx, localSig: ByteVector64, paymentPreimage: ByteVector32): ClaimHtlcSuccessTx = {
- val witness = witnessClaimHtlcSuccessFromCommitTx(localSig, paymentPreimage, claimHtlcSuccessTx.input.redeemScript)
+ val witness = witnessClaimHtlcSuccessFromCommitTx(localSig, paymentPreimage, claimHtlcSuccessTx.input.redeemScriptOrEmptyScript)
claimHtlcSuccessTx.copy(tx = claimHtlcSuccessTx.tx.updateWitness(0, witness))
}
def addSigs(claimHtlcTimeoutTx: ClaimHtlcTimeoutTx, localSig: ByteVector64): ClaimHtlcTimeoutTx = {
- val witness = witnessClaimHtlcTimeoutFromCommitTx(localSig, claimHtlcTimeoutTx.input.redeemScript)
+ val witness = witnessClaimHtlcTimeoutFromCommitTx(localSig, claimHtlcTimeoutTx.input.redeemScriptOrEmptyScript)
claimHtlcTimeoutTx.copy(tx = claimHtlcTimeoutTx.tx.updateWitness(0, witness))
}
@@ -916,27 +929,27 @@ object Transactions {
}
def addSigs(claimRemoteDelayedOutputTx: ClaimRemoteDelayedOutputTx, localSig: ByteVector64): ClaimRemoteDelayedOutputTx = {
- val witness = witnessClaimToRemoteDelayedFromCommitTx(localSig, claimRemoteDelayedOutputTx.input.redeemScript)
+ val witness = witnessClaimToRemoteDelayedFromCommitTx(localSig, claimRemoteDelayedOutputTx.input.redeemScriptOrEmptyScript)
claimRemoteDelayedOutputTx.copy(tx = claimRemoteDelayedOutputTx.tx.updateWitness(0, witness))
}
def addSigs(claimDelayedOutputTx: ClaimLocalDelayedOutputTx, localSig: ByteVector64): ClaimLocalDelayedOutputTx = {
- val witness = witnessToLocalDelayedAfterDelay(localSig, claimDelayedOutputTx.input.redeemScript)
+ val witness = witnessToLocalDelayedAfterDelay(localSig, claimDelayedOutputTx.input.redeemScriptOrEmptyScript)
claimDelayedOutputTx.copy(tx = claimDelayedOutputTx.tx.updateWitness(0, witness))
}
def addSigs(htlcDelayedTx: HtlcDelayedTx, localSig: ByteVector64): HtlcDelayedTx = {
- val witness = witnessToLocalDelayedAfterDelay(localSig, htlcDelayedTx.input.redeemScript)
+ val witness = witnessToLocalDelayedAfterDelay(localSig, htlcDelayedTx.input.redeemScriptOrEmptyScript)
htlcDelayedTx.copy(tx = htlcDelayedTx.tx.updateWitness(0, witness))
}
def addSigs(claimAnchorOutputTx: ClaimLocalAnchorOutputTx, localSig: ByteVector64): ClaimLocalAnchorOutputTx = {
- val witness = witnessAnchor(localSig, claimAnchorOutputTx.input.redeemScript)
+ val witness = witnessAnchor(localSig, claimAnchorOutputTx.input.redeemScriptOrEmptyScript)
claimAnchorOutputTx.copy(tx = claimAnchorOutputTx.tx.updateWitness(0, witness))
}
def addSigs(claimHtlcDelayedPenalty: ClaimHtlcDelayedOutputPenaltyTx, revocationSig: ByteVector64): ClaimHtlcDelayedOutputPenaltyTx = {
- val witness = Scripts.witnessToLocalDelayedWithRevocationSig(revocationSig, claimHtlcDelayedPenalty.input.redeemScript)
+ val witness = Scripts.witnessToLocalDelayedWithRevocationSig(revocationSig, claimHtlcDelayedPenalty.input.redeemScriptOrEmptyScript)
claimHtlcDelayedPenalty.copy(tx = claimHtlcDelayedPenalty.tx.updateWitness(0, witness))
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala
index e1ae2a8e2e..046ae615a3 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelCodecs0.scala
@@ -125,10 +125,14 @@ private[channel] object ChannelCodecs0 {
closingTx => closingTx.tx
)
- val inputInfoCodec: Codec[InputInfo] = (
+ private case class InputInfoLegacy(outPoint: OutPoint, txOut: TxOut, redeemScript: ByteVector)
+
+ private val inputInfoLegacyCodec: Codec[InputInfoLegacy] = (
("outPoint" | outPointCodec) ::
("txOut" | txOutCodec) ::
- ("redeemScript" | varsizebinarydata)).as[InputInfo].decodeOnly
+ ("redeemScript" | varsizebinarydata)).as[InputInfoLegacy]
+
+ val inputInfoCodec: Codec[InputInfo] = inputInfoLegacyCodec.xmap[InputInfo](legacy => InputInfo(legacy.outPoint, legacy.txOut, Left(legacy.redeemScript)), _ => ???).decodeOnly
private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0)))
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala
index e5fb015785..390c0b5335 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version0/ChannelTypes0.scala
@@ -48,7 +48,7 @@ private[channel] object ChannelTypes0 {
// modified: we don't use the InputInfo in closing business logic, so we don't need to fill everything (this part
// assumes that we only have standard channels, no anchor output channels - which was the case before version2).
val input = childTx.txIn.head.outPoint
- InputInfo(input, parentTx.txOut(input.index.toInt), Nil)
+ InputInfo(input, parentTx.txOut(input.index.toInt), ByteVector.fromValidHex("deadbeef"))
}
case class LocalCommitPublished(commitTx: Transaction, claimMainDelayedOutputTx: Option[Transaction], htlcSuccessTxs: List[Transaction], htlcTimeoutTxs: List[Transaction], claimHtlcDelayedTxs: List[Transaction], irrevocablySpent: Map[OutPoint, TxId]) {
@@ -97,7 +97,7 @@ private[channel] object ChannelTypes0 {
val htlcPenaltyTxsNew = htlcPenaltyTxs.map(tx => HtlcPenaltyTx(getPartialInputInfo(commitTx, tx), tx))
val claimHtlcDelayedPenaltyTxsNew = claimHtlcDelayedPenaltyTxs.map(tx => {
// We don't have all the `InputInfo` data, but it's ok: we only use the tx that is fully signed.
- ClaimHtlcDelayedOutputPenaltyTx(InputInfo(tx.txIn.head.outPoint, TxOut(Satoshi(0), Nil), Nil), tx)
+ ClaimHtlcDelayedOutputPenaltyTx(InputInfo(tx.txIn.head.outPoint, TxOut(Satoshi(0), Nil), ByteVector.fromValidHex("deadbeef")), tx) // FIXME: use proper value when we upgrade InputInfo to use `Either`
})
channel.RevokedCommitPublished(commitTx, claimMainOutputTxNew, mainPenaltyTxNew, htlcPenaltyTxsNew, claimHtlcDelayedPenaltyTxsNew, irrevocablySpentNew)
}
@@ -108,7 +108,7 @@ private[channel] object ChannelTypes0 {
* the raw transaction. It provides more information for auditing but is not used for business logic, so we can safely
* put dummy values in the migration.
*/
- def migrateClosingTx(tx: Transaction): ClosingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(Satoshi(0), Nil), Nil), tx, None)
+ def migrateClosingTx(tx: Transaction): ClosingTx = ClosingTx(InputInfo(tx.txIn.head.outPoint, TxOut(Satoshi(0), Nil), ByteVector.fromValidHex("deadbeef")), tx, None)
case class HtlcTxAndSigs(txinfo: HtlcTx, localSig: ByteVector64, remoteSig: ByteVector64)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala
index 1f75e8242b..8e2f22056e 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1.scala
@@ -97,10 +97,14 @@ private[channel] object ChannelCodecs1 {
closingTx => closingTx.tx
)
- val inputInfoCodec: Codec[InputInfo] = (
+ private case class InputInfoLegacy(outPoint: OutPoint, txOut: TxOut, redeemScript: ByteVector)
+
+ private val inputInfoLegacyCodec: Codec[InputInfoLegacy] = (
("outPoint" | outPointCodec) ::
("txOut" | txOutCodec) ::
- ("redeemScript" | lengthDelimited(bytes))).as[InputInfo]
+ ("redeemScript" | lengthDelimited(bytes))).as[InputInfoLegacy]
+
+ val inputInfoCodec: Codec[InputInfo] = inputInfoLegacyCodec.xmap[InputInfo](legacy => InputInfo(legacy.outPoint, legacy.txOut, Left(legacy.redeemScript)), _ => ???).decodeOnly
private val defaultConfirmationTarget: Codec[ConfirmationTarget.Absolute] = provide(ConfirmationTarget.Absolute(BlockHeight(0)))
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala
index 8d49b376f9..c85b07feff 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala
@@ -101,10 +101,14 @@ private[channel] object ChannelCodecs2 {
val txCodec: Codec[Transaction] = lengthDelimited(bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d)))
- val inputInfoCodec: Codec[InputInfo] = (
+ private case class InputInfoLegacy(outPoint: OutPoint, txOut: TxOut, redeemScript: ByteVector)
+
+ private val inputInfoLegacyCodec: Codec[InputInfoLegacy] = (
("outPoint" | outPointCodec) ::
("txOut" | txOutCodec) ::
- ("redeemScript" | lengthDelimited(bytes))).as[InputInfo]
+ ("redeemScript" | lengthDelimited(bytes))).as[InputInfoLegacy]
+
+ val inputInfoCodec: Codec[InputInfo] = inputInfoLegacyCodec.xmap[InputInfo](legacy => InputInfo(legacy.outPoint, legacy.txOut, Left(legacy.redeemScript)), _ => ???).decodeOnly
val outputInfoCodec: Codec[OutputInfo] = (
("index" | uint32) ::
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala
index a3a98f7d0e..36521f3db7 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version3/ChannelCodecs3.scala
@@ -113,10 +113,14 @@ private[channel] object ChannelCodecs3 {
val txCodec: Codec[Transaction] = lengthDelimited(bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d)))
- val inputInfoCodec: Codec[InputInfo] = (
+ private case class InputInfoLegacy(outPoint: OutPoint, txOut: TxOut, redeemScript: ByteVector)
+
+ private val inputInfoLegacyCodec: Codec[InputInfoLegacy] = (
("outPoint" | outPointCodec) ::
("txOut" | txOutCodec) ::
- ("redeemScript" | lengthDelimited(bytes))).as[InputInfo]
+ ("redeemScript" | lengthDelimited(bytes))).as[InputInfoLegacy]
+
+ val inputInfoCodec: Codec[InputInfo] = inputInfoLegacyCodec.xmap[InputInfo](legacy => InputInfo(legacy.outPoint, legacy.txOut, Left(legacy.redeemScript)), _ => ???).decodeOnly
val outputInfoCodec: Codec[OutputInfo] = (
("index" | uint32) ::
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala
index ec4fbe1326..953e4c254d 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4.scala
@@ -1,5 +1,7 @@
package fr.acinq.eclair.wire.internal.channel.version4
+import fr.acinq.bitcoin.ScriptTree
+import fr.acinq.bitcoin.io.ByteArrayInput
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
import fr.acinq.bitcoin.scalacompat.DeterministicWallet.KeyPath
import fr.acinq.bitcoin.scalacompat.{OutPoint, ScriptWitness, Transaction, TxOut}
@@ -109,10 +111,26 @@ private[channel] object ChannelCodecs4 {
val txCodec: Codec[Transaction] = lengthDelimited(bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d)))
- val inputInfoCodec: Codec[InputInfo] = (
+ val scriptTreeCodec: Codec[ScriptTree] = lengthDelimited(bytes.xmap(d => ScriptTree.read(new ByteArrayInput(d.toArray)), d => ByteVector.view(d.write())))
+
+ val scriptTreeAndInternalKey: Codec[ScriptTreeAndInternalKey] = (scriptTreeCodec :: xonlyPublicKey).as[ScriptTreeAndInternalKey]
+
+ private case class InputInfoEx(outPoint: OutPoint, txOut: TxOut, redeemScript: ByteVector, redeemScriptOrScriptTree: Either[ByteVector, ScriptTreeAndInternalKey], dummy: Boolean)
+
+ // To support the change from redeemScript to "either redeem script or script tree" while remaining backwards-compatible with the previous version 4 codec, we use
+ // the redeem script itself as a left/write indicator: empty -> right, not empty -> left
+ private val inputInfoExCodec: Codec[InputInfoEx] = (
("outPoint" | outPointCodec) ::
("txOut" | txOutCodec) ::
- ("redeemScript" | lengthDelimited(bytes))).as[InputInfo]
+ (("redeemScript" | lengthDelimited(bytes)) >>:~ { redeemScript =>
+ ("redeemScriptOrScriptTree" | either(provide(redeemScript.isEmpty), provide(redeemScript), scriptTreeAndInternalKey)) :: ("dummy" | provide(false))
+ })
+ ).as[InputInfoEx]
+
+ val inputInfoCodec: Codec[InputInfo] = inputInfoExCodec.xmap(
+ iex => InputInfo(iex.outPoint, iex.txOut, iex.redeemScriptOrScriptTree),
+ i => InputInfoEx(i.outPoint, i.txOut, i.redeemScriptOrScriptTree.swap.toOption.getOrElse(ByteVector.empty), i.redeemScriptOrScriptTree, false)
+ )
val outputInfoCodec: Codec[OutputInfo] = (
("index" | uint32) ::
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala
index 812cf0f8a6..988d7f3e45 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/CommonCodecs.scala
@@ -16,7 +16,7 @@
package fr.acinq.eclair.wire.protocol
-import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey}
+import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey, XonlyPublicKey}
import fr.acinq.bitcoin.scalacompat.{BlockHash, ByteVector32, ByteVector64, Satoshi, Transaction, TxHash, TxId}
import fr.acinq.eclair.blockchain.fee.FeeratePerKw
import fr.acinq.eclair.channel.{ChannelFlags, RealScidStatus, ShortIds}
@@ -166,6 +166,8 @@ object CommonCodecs {
(wire: BitVector) => bytes(33).decode(wire).map(_.map(b => PublicKey(b)))
)
+ val xonlyPublicKey: Codec[XonlyPublicKey] = publicKey.xmap(p => p.xOnly, x => x.publicKey)
+
val rgb: Codec[Color] = bytes(3).xmap(buf => Color(buf(0), buf(1), buf(2)), t => ByteVector(t.r, t.g, t.b))
val txCodec: Codec[Transaction] = bytes.xmap(d => Transaction.read(d.toArray), d => Transaction.write(d))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/DummyOnChainWallet.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/DummyOnChainWallet.scala
index a3c6c04863..8501193c9f 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/DummyOnChainWallet.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/DummyOnChainWallet.scala
@@ -204,7 +204,7 @@ class SingleKeyOnChainWallet extends OnChainWallet with OnchainPubkeyCache {
case (currentPsbt, (txIn, index)) => inputs.find(_.txid == txIn.outPoint.txid) match {
case Some(inputTx) =>
val sig = Transaction.signInput(tx, index, Script.pay2pkh(pubkey), SigHash.SIGHASH_ALL, inputTx.txOut.head.amount, SigVersion.SIGVERSION_WITNESS_V0, privkey)
- val updated = currentPsbt.updateWitnessInput(txIn.outPoint, inputTx.txOut(txIn.outPoint.index.toInt), null, Script.pay2pkh(pubkey).map(scala2kmp).asJava, null, java.util.Map.of()).getRight
+ val updated = currentPsbt.updateWitnessInput(txIn.outPoint, inputTx.txOut(txIn.outPoint.index.toInt), null, Script.pay2pkh(pubkey).map(scala2kmp).asJava, null, java.util.Map.of(), null, null, java.util.Map.of()).getRight
updated.finalizeWitnessInput(txIn.outPoint, Script.witnessPay2wpkh(pubkey, sig)).getRight
case None => currentPsbt
}
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala
index fd091a0803..dfde6aa86e 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/blockchain/bitcoind/BitcoinCoreClientSpec.scala
@@ -254,7 +254,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
// We sign our external input.
val externalSig = Transaction.signInput(fundedTx2.tx, 0, inputScript1, SigHash.SIGHASH_ALL, 250_000 sat, SigVersion.SIGVERSION_WITNESS_V0, alicePriv)
val psbt = new Psbt(fundedTx2.tx)
- .updateWitnessInput(outpoint1, txOut1, null, null, null, java.util.Map.of()).getRight
+ .updateWitnessInput(outpoint1, txOut1, null, null, null, java.util.Map.of(), null, null, java.util.Map.of()).getRight
.finalizeWitnessInput(0, Script.witnessMultiSigMofN(Seq(alicePriv, bobPriv).map(_.publicKey), Seq(externalSig))).getRight
// And let bitcoind sign the wallet input.
walletExternalFunds.signPsbt(psbt, fundedTx2.tx.txIn.indices, Nil).pipeTo(sender.ref)
@@ -284,7 +284,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
// We sign our external input.
val externalSig = Transaction.signInput(fundedTx.tx, 0, inputScript2, SigHash.SIGHASH_ALL, 300_000 sat, SigVersion.SIGVERSION_WITNESS_V0, alicePriv)
val psbt = new Psbt(fundedTx.tx)
- .updateWitnessInput(externalOutpoint, tx2.txOut(0), null, null, null, java.util.Map.of()).getRight
+ .updateWitnessInput(externalOutpoint, tx2.txOut(0), null, null, null, java.util.Map.of(), null, null, java.util.Map.of()).getRight
.finalizeWitnessInput(0, Script.witnessMultiSigMofN(Seq(alicePriv, carolPriv).map(_.publicKey), Seq(externalSig))).getRight
// bitcoind signs the wallet input.
walletExternalFunds.signPsbt(psbt, fundedTx.tx.txIn.indices, Nil).pipeTo(sender.ref)
@@ -320,7 +320,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
// We sign our external input.
val externalSig = Transaction.signInput(fundedTx.tx, 0, inputScript2, SigHash.SIGHASH_ALL, 300_000 sat, SigVersion.SIGVERSION_WITNESS_V0, alicePriv)
val psbt = new Psbt(fundedTx.tx)
- .updateWitnessInput(OutPoint(tx2, 0), tx2.txOut(0), null, null, null, java.util.Map.of()).getRight
+ .updateWitnessInput(OutPoint(tx2, 0), tx2.txOut(0), null, null, null, java.util.Map.of(), null, null, java.util.Map.of()).getRight
.finalizeWitnessInput(0, Script.witnessMultiSigMofN(Seq(alicePriv, carolPriv).map(_.publicKey), Seq(externalSig))).getRight
// bitcoind signs the wallet input.
walletExternalFunds.signPsbt(psbt, fundedTx.tx.txIn.indices, Nil).pipeTo(sender.ref)
@@ -816,7 +816,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
val nonWalletWitness = ScriptWitness(Seq(nonWalletSig, nonWalletKey.publicKey.value))
val txWithSignedNonWalletInput = txWithNonWalletInput.updateWitness(0, nonWalletWitness)
val psbt = new Psbt(txWithSignedNonWalletInput)
- val updated: Either[UpdateFailure, Psbt] = psbt.updateWitnessInput(psbt.global.tx.txIn.get(0).outPoint, txToRemote.txOut(0), null, fr.acinq.bitcoin.Script.pay2pkh(nonWalletKey.publicKey), SigHash.SIGHASH_ALL, psbt.getInput(0).getDerivationPaths)
+ val updated: Either[UpdateFailure, Psbt] = psbt.updateWitnessInput(psbt.global.tx.txIn.get(0).outPoint, txToRemote.txOut(0), null, fr.acinq.bitcoin.Script.pay2pkh(nonWalletKey.publicKey), SigHash.SIGHASH_ALL, psbt.getInput(0).getDerivationPaths, null, null, java.util.Map.of())
val Right(psbt1) = updated.flatMap(_.finalizeWitnessInput(0, nonWalletWitness))
bitcoinClient.signPsbt(psbt1, txWithSignedNonWalletInput.txIn.indices.tail, Nil).pipeTo(sender.ref)
val signTxResponse2 = sender.expectMsgType[ProcessPsbtResponse]
@@ -848,7 +848,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
val nonWalletWitness = ScriptWitness(Seq(nonWalletSig, nonWalletKey.publicKey.value))
val txWithSignedUnconfirmedInput = txWithUnconfirmedInput.updateWitness(0, nonWalletWitness)
val psbt = new Psbt(txWithSignedUnconfirmedInput)
- val Right(psbt1) = psbt.updateWitnessInput(psbt.global.tx.txIn.get(0).outPoint, unconfirmedTx.txOut(0), null, fr.acinq.bitcoin.Script.pay2pkh(nonWalletKey.publicKey), SigHash.SIGHASH_ALL, psbt.getInput(0).getDerivationPaths)
+ val Right(psbt1) = psbt.updateWitnessInput(psbt.global.tx.txIn.get(0).outPoint, unconfirmedTx.txOut(0), null, fr.acinq.bitcoin.Script.pay2pkh(nonWalletKey.publicKey), SigHash.SIGHASH_ALL, psbt.getInput(0).getDerivationPaths, null, null, java.util.Map.of())
.flatMap(_.finalizeWitnessInput(0, nonWalletWitness))
bitcoinClient.signPsbt(psbt1, txWithSignedUnconfirmedInput.txIn.indices.tail, Nil).pipeTo(sender.ref)
assert(sender.expectMsgType[ProcessPsbtResponse].complete)
@@ -1521,7 +1521,7 @@ class BitcoinCoreClientSpec extends TestKitBaseClass with BitcoindService with A
val txOut = Seq(TxOut(outputAmount, Script.pay2wpkh(randomKey().publicKey)))
var psbt = new Psbt(Transaction(2, txIn, txOut, 0))
(fromInput until toInput).foreach { i =>
- psbt = psbt.updateWitnessInput(OutPoint(parentTx, i), parentTx.txOut(i), null, null, null, psbt.getInput(i - fromInput).getDerivationPaths).getRight
+ psbt = psbt.updateWitnessInput(OutPoint(parentTx, i), parentTx.txOut(i), null, null, null, psbt.getInput(i - fromInput).getDerivationPaths, null, null, java.util.Map.of()).getRight
psbt = psbt.finalizeWitnessInput(i - fromInput, ScriptWitness(Seq(ByteVector(1), bigInputScript))).getRight
}
val signedTx: Transaction = psbt.extract().getRight
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunderSpec.scala
index ff7591e67a..1577b1bcea 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunderSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunderSpec.scala
@@ -42,7 +42,7 @@ class ReplaceableTxFunderSpec extends TestKitBaseClass with AnyFunSuiteLike {
val commitInput = Funding.makeFundingInputInfo(randomTxId(), 1, 500 sat, PlaceHolderPubKey, PlaceHolderPubKey)
val commitTx = Transaction(
2,
- Seq(TxIn(commitInput.outPoint, commitInput.redeemScript, 0, Scripts.witness2of2(PlaceHolderSig, PlaceHolderSig, PlaceHolderPubKey, PlaceHolderPubKey))),
+ Seq(TxIn(commitInput.outPoint, commitInput.redeemScriptOrEmptyScript, 0, Scripts.witness2of2(PlaceHolderSig, PlaceHolderSig, PlaceHolderPubKey, PlaceHolderPubKey))),
Seq(TxOut(330 sat, Script.pay2wsh(anchorScript))),
0
)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalOnChainKeyManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalOnChainKeyManagerSpec.scala
index 9a4d4303f6..70f4f3864b 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalOnChainKeyManagerSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/keymanager/LocalOnChainKeyManagerSpec.scala
@@ -54,13 +54,13 @@ class LocalOnChainKeyManagerSpec extends AnyFunSuite {
txOut = TxOut(Satoshi(1000_000), Script.pay2wpkh(getPublicKey(0))) :: Nil, lockTime = 0)
val Right(psbt) = for {
- p0 <- new Psbt(tx).updateWitnessInput(OutPoint(utxos(0), 0), utxos(0).txOut(0), null, Script.pay2pkh(getPublicKey(0)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(0), bip32paths(0)))
- p1 <- p0.updateWitnessInput(OutPoint(utxos(1), 0), utxos(1).txOut(0), null, Script.pay2pkh(getPublicKey(1)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(1), bip32paths(1)))
- p2 <- p1.updateWitnessInput(OutPoint(utxos(2), 0), utxos(2).txOut(0), null, Script.pay2pkh(getPublicKey(2)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(2), bip32paths(2)))
+ p0 <- new Psbt(tx).updateWitnessInput(OutPoint(utxos(0), 0), utxos(0).txOut(0), null, Script.pay2pkh(getPublicKey(0)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(0), bip32paths(0)), null, null, java.util.Map.of())
+ p1 <- p0.updateWitnessInput(OutPoint(utxos(1), 0), utxos(1).txOut(0), null, Script.pay2pkh(getPublicKey(1)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(1), bip32paths(1)), null, null, java.util.Map.of())
+ p2 <- p1.updateWitnessInput(OutPoint(utxos(2), 0), utxos(2).txOut(0), null, Script.pay2pkh(getPublicKey(2)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(2), bip32paths(2)), null, null, java.util.Map.of())
p3 <- p2.updateNonWitnessInput(utxos(0), 0, null, null, java.util.Map.of())
p4 <- p3.updateNonWitnessInput(utxos(1), 0, null, null, java.util.Map.of())
p5 <- p4.updateNonWitnessInput(utxos(2), 0, null, null, java.util.Map.of())
- p6 <- p5.updateWitnessOutput(0, null, null, java.util.Map.of(getPublicKey(0), bip32paths(0)))
+ p6 <- p5.updateWitnessOutput(0, null, null, java.util.Map.of(getPublicKey(0), bip32paths(0)), null, java.util.Map.of())
} yield p6
{
@@ -78,38 +78,38 @@ class LocalOnChainKeyManagerSpec extends AnyFunSuite {
}
{
// provide a wrong derivation path for the first input
- val updated = psbt.updateWitnessInput(OutPoint(utxos(0), 0), utxos(0).txOut(0), null, Script.pay2pkh(getPublicKey(0)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(0), bip32paths(2))).getRight // wrong bip32 path
+ val updated = psbt.updateWitnessInput(OutPoint(utxos(0), 0), utxos(0).txOut(0), null, Script.pay2pkh(getPublicKey(0)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(0), bip32paths(2)), null, null, java.util.Map.of()).getRight // wrong bip32 path
val Failure(error) = onChainKeyManager.sign(updated, Seq(0, 1, 2), Seq(0))
assert(error.getMessage.contains("derived public key doesn't match"))
}
{
// provide a wrong derivation path for the first output
- val updated = psbt.updateWitnessOutput(0, null, null, java.util.Map.of(getPublicKey(0), bip32paths(1))).getRight // wrong path
+ val updated = psbt.updateWitnessOutput(0, null, null, java.util.Map.of(getPublicKey(0), bip32paths(1)), null, java.util.Map.of()).getRight // wrong path
val Failure(error) = onChainKeyManager.sign(updated, Seq(0, 1, 2), Seq(0))
assert(error.getMessage.contains("could not verify output 0"))
}
{
// lie about the amount being spent
- val updated = psbt.updateWitnessInput(OutPoint(utxos(0), 0), utxos(0).txOut(0).copy(amount = Satoshi(10)), null, Script.pay2pkh(getPublicKey(0)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(0), bip32paths(0))).getRight
+ val updated = psbt.updateWitnessInput(OutPoint(utxos(0), 0), utxos(0).txOut(0).copy(amount = Satoshi(10)), null, Script.pay2pkh(getPublicKey(0)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(0), bip32paths(0)), null, null, java.util.Map.of()).getRight
val Failure(error) = onChainKeyManager.sign(updated, Seq(0, 1, 2), Seq(0))
assert(error.getMessage.contains("utxo mismatch"))
}
{
// do not provide non-witness utxo for utxo #2
val Right(psbt) = for {
- p0 <- new Psbt(tx).updateWitnessInput(OutPoint(utxos(0), 0), utxos(0).txOut(0), null, Script.pay2pkh(getPublicKey(0)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(0), bip32paths(0)))
- p1 <- p0.updateWitnessInput(OutPoint(utxos(1), 0), utxos(1).txOut(0), null, Script.pay2pkh(getPublicKey(1)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(1), bip32paths(1)))
- p2 <- p1.updateWitnessInput(OutPoint(utxos(2), 0), utxos(2).txOut(0), null, Script.pay2pkh(getPublicKey(2)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(2), bip32paths(2)))
+ p0 <- new Psbt(tx).updateWitnessInput(OutPoint(utxos(0), 0), utxos(0).txOut(0), null, Script.pay2pkh(getPublicKey(0)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(0), bip32paths(0)), null, null, java.util.Map.of())
+ p1 <- p0.updateWitnessInput(OutPoint(utxos(1), 0), utxos(1).txOut(0), null, Script.pay2pkh(getPublicKey(1)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(1), bip32paths(1)), null, null, java.util.Map.of())
+ p2 <- p1.updateWitnessInput(OutPoint(utxos(2), 0), utxos(2).txOut(0), null, Script.pay2pkh(getPublicKey(2)).map(scala2kmp).asJava, null, java.util.Map.of(getPublicKey(2), bip32paths(2)), null, null, java.util.Map.of())
p3 <- p2.updateNonWitnessInput(utxos(0), 0, null, null, java.util.Map.of())
p4 <- p3.updateNonWitnessInput(utxos(1), 0, null, null, java.util.Map.of())
- p5 <- p4.updateWitnessOutput(0, null, null, java.util.Map.of(getPublicKey(0), bip32paths(0)))
+ p5 <- p4.updateWitnessOutput(0, null, null, java.util.Map.of(getPublicKey(0), bip32paths(0)), null, java.util.Map.of())
} yield p5
val Failure(error) = onChainKeyManager.sign(psbt, Seq(0, 1, 2), Seq(0))
assert(error.getMessage.contains("non-witness utxo is missing"))
}
{
// use sighash type != SIGHASH_ALL
- val updated = psbt.updateWitnessInput(OutPoint(utxos(0), 0), utxos(0).txOut(0), null, Script.pay2pkh(getPublicKey(0)).map(scala2kmp).asJava, SigHash.SIGHASH_SINGLE, java.util.Map.of(getPublicKey(0), bip32paths(0))).getRight
+ val updated = psbt.updateWitnessInput(OutPoint(utxos(0), 0), utxos(0).txOut(0), null, Script.pay2pkh(getPublicKey(0)).map(scala2kmp).asJava, SigHash.SIGHASH_SINGLE, java.util.Map.of(getPublicKey(0), bip32paths(0)), null, null, java.util.Map.of()).getRight
val Failure(error) = onChainKeyManager.sign(updated, Seq(0, 1, 2), Seq(0))
assert(error.getMessage.contains("input sighash must be SIGHASH_ALL"))
}
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 1fb1c89cec..20f6d1f800 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
@@ -140,8 +140,8 @@ trait TestVectorsSpec extends AnyFunSuite with Logging {
logger.info(s"remotekey: ${Remote.payment_privkey.publicKey}")
logger.info(s"local_delayedkey: ${Local.delayed_payment_privkey.publicKey}")
logger.info(s"local_revocation_key: ${Local.revocation_pubkey}")
- logger.info(s"# funding wscript = ${commitmentInput.redeemScript}")
- assert(commitmentInput.redeemScript == hex"5221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae")
+ logger.info(s"# funding wscript = ${commitmentInput.redeemScriptOrScriptTree}")
+ assert(commitmentInput.redeemScriptOrScriptTree == Left(hex"5221023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb21030e9f7b623d2ccc7c9bd44d66d5ce21ce504c0acf6385a132cec6d3c39fa711c152ae"))
val paymentPreimages = Seq(
ByteVector32(hex"0000000000000000000000000000000000000000000000000000000000000000"),
@@ -250,7 +250,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging {
case tx: HtlcSuccessTx =>
val localSig = tx.sign(Local.htlc_privkey, TxOwner.Local, commitmentFormat)
val remoteSig = tx.sign(Remote.htlc_privkey, TxOwner.Remote, commitmentFormat)
- val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript))
+ val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScriptOrEmptyScript))
val preimage = paymentPreimages.find(p => Crypto.sha256(p) == tx.paymentHash).get
val tx1 = Transactions.addSigs(tx, localSig, remoteSig, preimage, commitmentFormat)
Transaction.correctlySpends(tx1.tx, Seq(commitTx.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
@@ -262,7 +262,7 @@ trait TestVectorsSpec extends AnyFunSuite with Logging {
case tx: HtlcTimeoutTx =>
val localSig = tx.sign(Local.htlc_privkey, TxOwner.Local, commitmentFormat)
val remoteSig = tx.sign(Remote.htlc_privkey, TxOwner.Remote, commitmentFormat)
- val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScript))
+ val htlcIndex = htlcScripts.indexOf(Script.parse(tx.input.redeemScriptOrEmptyScript))
val tx1 = Transactions.addSigs(tx, localSig, remoteSig, commitmentFormat)
Transaction.correctlySpends(tx1.tx, Seq(commitTx.tx), ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS)
logger.info(s"# signature for output #${tx.input.outPoint.index} (htlc-timeout for htlc #$htlcIndex)")
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala
index 4a86251654..06701647e5 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version4/ChannelCodecs4Spec.scala
@@ -124,7 +124,7 @@ class ChannelCodecs4Spec extends AnyFunSuite {
test("encode/decode rbf status") {
val channelId = randomBytes32()
- val fundingInput = InputInfo(OutPoint(randomTxId(), 3), TxOut(175_000 sat, Script.pay2wpkh(randomKey().publicKey)), Nil)
+ val fundingInput = InputInfo(OutPoint(randomTxId(), 3), TxOut(175_000 sat, Script.pay2wpkh(randomKey().publicKey)), Script.pay2wpkh(randomKey().publicKey))
val fundingTx = SharedTransaction(
sharedInput_opt = None,
sharedOutput = InteractiveTxBuilder.Output.Shared(UInt64(8), ByteVector.empty, 100_000_600 msat, 74_000_400 msat, 0 msat),