Skip to content

Commit

Permalink
Move taproot signing helpers to Transaction.kt
Browse files Browse the repository at this point in the history
This is more consistent with the existing `signInput` helpers.
  • Loading branch information
t-bast committed Feb 1, 2024
1 parent 81d65ad commit b7ca44e
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 26 deletions.
18 changes: 0 additions & 18 deletions src/commonMain/kotlin/fr/acinq/bitcoin/Crypto.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<TxOut>, 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<TxOut>, 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())
Expand Down
47 changes: 47 additions & 0 deletions src/commonMain/kotlin/fr/acinq/bitcoin/Transaction.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<TxOut>, 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<TxOut>,
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<OutPoint, TxOut>, scriptFlags: Int) {
val prevouts = tx.txIn.map { previousOutputs[it.outPoint]!! }
Expand Down
14 changes: 7 additions & 7 deletions src/commonTest/kotlin/fr/acinq/bitcoin/TaprootTestsCommon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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))
}

Expand All @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -340,7 +340,7 @@ class TaprootTestsCommon {
txOut = listOf(TxOut(fundingTx3.txOut[0].amount - Satoshi(5000), addressToPublicKeyScript(blockchain, "tb1qxy9hhxkw7gt76qrm4yzw4j06gkk4evryh8ayp7").right!!)),
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)
}
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,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)
}
Expand Down

0 comments on commit b7ca44e

Please sign in to comment.