From b37770327293b76029f7b127dd205dbb95fe390b Mon Sep 17 00:00:00 2001 From: t-bast Date: Wed, 31 Jan 2024 12:21:50 +0100 Subject: [PATCH] Move taproot signing helpers to `Transaction.kt` This is more consistent with the existing `signInput` helpers. --- .../kotlin/fr/acinq/bitcoin/Crypto.kt | 18 ------- .../kotlin/fr/acinq/bitcoin/Transaction.kt | 47 +++++++++++++++++++ .../fr/acinq/bitcoin/TaprootTestsCommon.kt | 14 +++--- .../crypto/musig2/Musig2TestsCommon.kt | 2 +- 4 files changed, 55 insertions(+), 26 deletions(-) diff --git a/src/commonMain/kotlin/fr/acinq/bitcoin/Crypto.kt b/src/commonMain/kotlin/fr/acinq/bitcoin/Crypto.kt index e38ebc98..3b4a6aaf 100644 --- a/src/commonMain/kotlin/fr/acinq/bitcoin/Crypto.kt +++ b/src/commonMain/kotlin/fr/acinq/bitcoin/Crypto.kt @@ -197,24 +197,6 @@ public object Crypto { return sig } - /** Produce a signature that will be included in the witness of a taproot key path spend. */ - @JvmStatic - public fun signTaprootKeyPath(privateKey: PrivateKey, tx: Transaction, inputIndex: Int, inputs: List, sighashType: Int, scriptTree: ScriptTree?, annex: ByteVector? = null, auxrand32: ByteVector32? = null): ByteVector64 { - val data = Transaction.hashForSigningTaprootKeyPath(tx, inputIndex, inputs, sighashType, annex) - val tweak = when (scriptTree) { - null -> TaprootTweak.NoScriptTweak - else -> TaprootTweak.ScriptTweak(scriptTree.hash()) - } - return signSchnorr(data, privateKey, tweak, auxrand32) - } - - /** Produce a signature that will be included in the witness of a taproot script path spend. */ - @JvmStatic - public fun signTaprootScriptPath(privateKey: PrivateKey, tx: Transaction, inputIndex: Int, inputs: List, sighashType: Int, tapleaf: ByteVector32, annex: ByteVector? = null, auxrand32: ByteVector32? = null): ByteVector64 { - val data = Transaction.hashForSigningTaprootScriptPath(tx, inputIndex, inputs, sighashType, tapleaf, annex) - return signSchnorr(data, privateKey, SchnorrTweak.NoTweak, auxrand32) - } - @JvmStatic public fun verifySignatureSchnorr(data: ByteVector32, signature: ByteVector, publicKey: XonlyPublicKey): Boolean { return Secp256k1.verifySchnorr(signature.toByteArray(), data.toByteArray(), publicKey.value.toByteArray()) diff --git a/src/commonMain/kotlin/fr/acinq/bitcoin/Transaction.kt b/src/commonMain/kotlin/fr/acinq/bitcoin/Transaction.kt index c16c3f53..3aea069c 100644 --- a/src/commonMain/kotlin/fr/acinq/bitcoin/Transaction.kt +++ b/src/commonMain/kotlin/fr/acinq/bitcoin/Transaction.kt @@ -798,6 +798,53 @@ public data class Transaction( return hashForSigningSchnorr(tx, inputIndex, inputs, sighashType, SigVersion.SIGVERSION_TAPSCRIPT, tapleaf, annex) } + /** + * Sign a taproot tx input, using the internal key path. + * + * @param privateKey private key. + * @param tx input transaction. + * @param inputIndex index of the tx input that is being signed. + * @param inputs list of all UTXOs spent by this transaction. + * @param sighashType signature hash type, which will be appended to the signature (if not default). + * @param scriptTree tapscript tree of the signed input, if it has script paths. + * @return the schnorr signature of this tx for this specific tx input. + */ + @JvmStatic + public fun signInputTaprootKeyPath(privateKey: PrivateKey, tx: Transaction, inputIndex: Int, inputs: List, sighashType: Int, scriptTree: ScriptTree?, annex: ByteVector? = null, auxrand32: ByteVector32? = null): ByteVector64 { + val data = hashForSigningTaprootKeyPath(tx, inputIndex, inputs, sighashType, annex) + val tweak = when (scriptTree) { + null -> Crypto.TaprootTweak.NoScriptTweak + else -> Crypto.TaprootTweak.ScriptTweak(scriptTree.hash()) + } + return Crypto.signSchnorr(data, privateKey, tweak, auxrand32) + } + + /** + * Sign a taproot tx input, using one of its script paths. + * + * @param privateKey private key. + * @param tx input transaction. + * @param inputIndex index of the tx input that is being signed. + * @param inputs list of all UTXOs spent by this transaction. + * @param sighashType signature hash type, which will be appended to the signature (if not default). + * @param tapleaf tapscript leaf hash of the script that is being spent. + * @return the schnorr signature of this tx for this specific tx input and the given script leaf. + */ + @JvmStatic + public fun signInputTaprootScriptPath( + privateKey: PrivateKey, + tx: Transaction, + inputIndex: Int, + inputs: List, + sighashType: Int, + tapleaf: ByteVector32, + annex: ByteVector? = null, + auxrand32: ByteVector32? = null + ): ByteVector64 { + val data = hashForSigningTaprootScriptPath(tx, inputIndex, inputs, sighashType, tapleaf, annex) + return Crypto.signSchnorr(data, privateKey, Crypto.SchnorrTweak.NoTweak, auxrand32) + } + @JvmStatic public fun correctlySpends(tx: Transaction, previousOutputs: Map, scriptFlags: Int) { val prevouts = tx.txIn.map { previousOutputs[it.outPoint]!! } diff --git a/src/commonTest/kotlin/fr/acinq/bitcoin/TaprootTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/bitcoin/TaprootTestsCommon.kt index afae8760..57b9e2f4 100644 --- a/src/commonTest/kotlin/fr/acinq/bitcoin/TaprootTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/bitcoin/TaprootTestsCommon.kt @@ -95,7 +95,7 @@ class TaprootTestsCommon { 0 ) val sigHashType = 0 - val sig = Crypto.signTaprootKeyPath(privateKey, tx1, 0, listOf(tx.txOut[1]), sigHashType, scriptTree = null) + val sig = Transaction.signInputTaprootKeyPath(privateKey, tx1, 0, listOf(tx.txOut[1]), sigHashType, scriptTree = null) val tx2 = tx1.updateWitness(0, Script.witnessKeyPathPay2tr(sig)) Transaction.correctlySpends(tx2, tx, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) } @@ -193,7 +193,7 @@ class TaprootTestsCommon { ) // compute all 3 signatures - val sigs = privs.map { Crypto.signTaprootScriptPath(it, tmp, 0, listOf(fundingTx.txOut[0]), SigHash.SIGHASH_DEFAULT, scriptTree.hash()) } + val sigs = privs.map { Transaction.signInputTaprootScriptPath(it, tmp, 0, listOf(fundingTx.txOut[0]), SigHash.SIGHASH_DEFAULT, scriptTree.hash()) } // one signature is not enough val tx = tmp.updateWitness(0, Script.witnessScriptPathPay2tr(internalPubkey, scriptTree, ScriptWitness(listOf(sigs[0], sigs[0], sigs[0])), scriptTree)) @@ -262,7 +262,7 @@ class TaprootTestsCommon { lockTime = 0 ) // We still need to provide the tapscript tree because it is used to tweak the private key. - val sig = Crypto.signTaprootKeyPath(privs[0], tmp, 0, listOf(fundingTx.txOut[0]), SigHash.SIGHASH_DEFAULT, scriptTree) + val sig = Transaction.signInputTaprootKeyPath(privs[0], tmp, 0, listOf(fundingTx.txOut[0]), SigHash.SIGHASH_DEFAULT, scriptTree) tmp.updateWitness(0, Script.witnessKeyPathPay2tr(sig)) } @@ -287,7 +287,7 @@ class TaprootTestsCommon { txOut = listOf(TxOut(fundingTx1.txOut[0].amount - Satoshi(5000), sweepPublicKeyScript)), lockTime = 0 ) - val sig = Crypto.signTaprootScriptPath(privs[0], tmp, 0, listOf(fundingTx.txOut[0]), SigHash.SIGHASH_DEFAULT, leaves[0].hash()) + val sig = Transaction.signInputTaprootScriptPath(privs[0], tmp, 0, listOf(fundingTx.txOut[0]), SigHash.SIGHASH_DEFAULT, leaves[0].hash()) val witness = Script.witnessScriptPathPay2tr(internalPubkey, leaves[0], ScriptWitness(listOf(sig)), scriptTree) tmp.updateWitness(0, witness) } @@ -314,7 +314,7 @@ class TaprootTestsCommon { txOut = listOf(TxOut(fundingTx2.txOut[0].amount - Satoshi(5000), sweepPublicKeyScript)), lockTime = 0 ) - val sig = Crypto.signTaprootScriptPath(privs[1], tmp, 0, listOf(fundingTx2.txOut[0]), SigHash.SIGHASH_DEFAULT, leaves[1].hash()) + val sig = Transaction.signInputTaprootScriptPath(privs[1], tmp, 0, listOf(fundingTx2.txOut[0]), SigHash.SIGHASH_DEFAULT, leaves[1].hash()) val witness = Script.witnessScriptPathPay2tr(internalPubkey, leaves[1], ScriptWitness(listOf(sig)), scriptTree) tmp.updateWitness(0, witness) } @@ -340,7 +340,7 @@ class TaprootTestsCommon { txOut = listOf(TxOut(fundingTx3.txOut[0].amount - Satoshi(5000), addressToPublicKeyScript(blockchain, "tb1qxy9hhxkw7gt76qrm4yzw4j06gkk4evryh8ayp7").result!!)), lockTime = 0 ) - val sig = Crypto.signTaprootScriptPath(privs[2], tmp, 0, listOf(fundingTx3.txOut[1]), SigHash.SIGHASH_DEFAULT, leaves[2].hash()) + val sig = Transaction.signInputTaprootScriptPath(privs[2], tmp, 0, listOf(fundingTx3.txOut[1]), SigHash.SIGHASH_DEFAULT, leaves[2].hash()) val witness = Script.witnessScriptPathPay2tr(internalPubkey, leaves[2], ScriptWitness(listOf(sig)), scriptTree) tmp.updateWitness(0, witness) } @@ -410,7 +410,7 @@ class TaprootTestsCommon { fun `parse and validate large ordinals transaction`() { val file = resourcesDir().resolve("b5a7e05f28d00e4a791759ad7b6bd6799d856693293ceeaad9b0bb93c8851f7f.bin").openReadableFile() val buffer = ByteArray(file.size) - file.readBytes(buffer, 0, buffer.size) + file.readBytes(buffer, 0, buffer.size) file.close() val tx = Transaction.read(buffer) val parentTx = Transaction.read( diff --git a/src/commonTest/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2TestsCommon.kt b/src/commonTest/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2TestsCommon.kt index 0057f295..bdf268b1 100644 --- a/src/commonTest/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2TestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/bitcoin/crypto/musig2/Musig2TestsCommon.kt @@ -368,7 +368,7 @@ class Musig2TestsCommon { txOut = listOf(TxOut(10_000.sat(), Script.pay2wpkh(userPublicKey))), lockTime = 0 ) - val sig = Crypto.signTaprootScriptPath(userRefundPrivateKey, tx, 0, swapInTx.txOut, SigHash.SIGHASH_DEFAULT, scriptTree.hash()) + val sig = Transaction.signInputTaprootScriptPath(userRefundPrivateKey, tx, 0, swapInTx.txOut, SigHash.SIGHASH_DEFAULT, scriptTree.hash()) val signedTx = tx.updateWitness(0, Script.witnessScriptPathPay2tr(internalPubKey, scriptTree, ScriptWitness(listOf(sig)), scriptTree)) Transaction.correctlySpends(signedTx, swapInTx, ScriptFlags.STANDARD_SCRIPT_VERIFY_FLAGS) }