commonMain.fr.acinq.bitcoin.Script.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bitcoin-kmp-jvm Show documentation
Show all versions of bitcoin-kmp-jvm Show documentation
A simple Kotlin Multiplatform library which implements most of the bitcoin protocol
The newest version!
/*
* Copyright 2020 ACINQ SAS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fr.acinq.bitcoin
import fr.acinq.bitcoin.io.ByteArrayInput
import fr.acinq.bitcoin.io.ByteArrayOutput
import fr.acinq.bitcoin.io.Input
import fr.acinq.bitcoin.io.Output
import fr.acinq.secp256k1.Hex
import fr.acinq.secp256k1.Secp256k1
import kotlin.jvm.JvmStatic
public object Script {
public const val MAX_SCRIPT_SIZE: Int = 10000
public const val MAX_SCRIPT_ELEMENT_SIZE: Int = 520
public const val MAX_OPS_PER_SCRIPT: Int = 201
public const val MAX_STACK_SIZE: Int = 1000
public const val LOCKTIME_THRESHOLD: Long = 500000000L
public const val WITNESS_V0_SCRIPTHASH_SIZE: Int = 32
public const val WITNESS_V0_KEYHASH_SIZE: Int = 20
public const val WITNESS_V1_TAPROOT_SIZE: Int = 32
public const val TAPROOT_LEAF_MASK: Int = 0xfe
public const val TAPROOT_LEAF_TAPSCRIPT: Int = 0xc0
// Validation weight per passing signature (Tapscript only, see BIP 342).
public const val VALIDATION_WEIGHT_PER_SIGOP_PASSED: Int = 50
// How much weight budget is added to the witness size (Tapscript only, see BIP 342).
public const val VALIDATION_WEIGHT_OFFSET: Int = 50
public val True: ByteVector = ByteVector("01")
public val False: ByteVector = ByteVector.empty
public fun isOpSuccess(opcode: Int): Boolean {
return opcode == 80 || opcode == 98 || (opcode in 126..129) ||
(opcode in 131..134) || (opcode in 137..138) ||
(opcode in 141..142) || (opcode in 149..153) ||
(opcode in 187..254)
}
public fun scriptIterator(script: ByteArray): Iterator = scriptIterator(ByteArrayInput(script))
public fun scriptIterator(input: Input): Iterator {
return object : Iterator {
override fun hasNext(): Boolean = input.availableBytes > 0
override fun next(): ScriptElt {
val code = input.read()
return when {
code == 0 -> OP_0
code in 1 until 0x4c -> OP_PUSHDATA(BtcSerializer.bytes(input, code), code)
code == 0x4c -> OP_PUSHDATA(BtcSerializer.bytes(input, BtcSerializer.uint8(input).toInt()), 0x4c)
code == 0x4d -> OP_PUSHDATA(BtcSerializer.bytes(input, BtcSerializer.uint16(input).toInt()), 0x4d)
code == 0x4e -> OP_PUSHDATA(BtcSerializer.bytes(input, BtcSerializer.uint32(input).toLong()), 0x4e)
ScriptEltMapping.code2elt.containsKey(code) -> ScriptEltMapping.code2elt.getValue(code)
else -> OP_INVALID(code)
}
}
}
}
/**
* parse a script from a input stream of binary data
*/
@JvmStatic
public fun parse(input: Input): List = scriptIterator(input).asSequence().toList()
@JvmStatic
public fun parse(blob: ByteArray): List = parse(ByteArrayInput(blob))
@JvmStatic
public fun parse(blob: ByteVector): List = parse(blob.toByteArray())
@JvmStatic
public fun parse(hex: String): List = parse(Hex.decode(hex))
@JvmStatic
public fun write(script: List, out: Output) {
script.forEach { head ->
when (head) {
is OP_PUSHDATA -> {
when {
head.code < 0x4c -> {
require(head.data.size() == head.code)
out.write(head.data.size())
}
head.code == 0x4c -> {
require(head.data.size() <= 0xff)
BtcSerializer.writeUInt8(0x4Cu, out)
BtcSerializer.writeUInt8(head.data.size().toUByte(), out)
}
head.code == 0x4d -> {
require(head.data.size() <= 0xffff)
BtcSerializer.writeUInt8(0x4Du, out)
BtcSerializer.writeUInt16(head.data.size().toUShort(), out)
}
head.code == 0x4e -> {
require(head.data.size() <= 0xffffffff)
BtcSerializer.writeUInt8(0x4Eu, out)
BtcSerializer.writeUInt32(head.data.size().toUInt(), out)
}
else -> error("invalid OP_PUSHADATA opcode ${head.code}")
}
out.write(head.data.toByteArray())
}
else -> {
out.write(head.code)
}
}
}
}
@JvmStatic
public fun write(script: List): ByteArray {
val out = ByteArrayOutput()
write(script, out)
return out.toByteArray()
}
@JvmStatic
public fun isUpgradableNop(op: ScriptElt): Boolean = when (op) {
OP_NOP1, OP_NOP4, OP_NOP5, OP_NOP6, OP_NOP7, OP_NOP8, OP_NOP9, OP_NOP10 -> true
else -> false
}
@JvmStatic
public fun isSimpleValue(op: ScriptElt): Boolean = when (op) {
OP_1NEGATE, OP_0, OP_1, OP_2, OP_3, OP_4, OP_5, OP_6, OP_7, OP_8, OP_9, OP_10, OP_11, OP_12, OP_13, OP_14, OP_15, OP_16 -> true
else -> false
}
@JvmStatic
public fun simpleValue(op: ScriptElt): Byte {
require(isSimpleValue(op)) {}
val value = if (op == OP_0) 0 else (op.code - 0x50)
return value.toByte()
}
@JvmStatic
public fun fromSimpleValue(value: Byte): ScriptElt = when (value.toInt()) {
0 -> OP_0
in -1..16 -> ScriptEltMapping.code2elt.getValue(value + 0x50)
else -> throw IllegalArgumentException("cannot convert $value to a simple value operator")
}
@JvmStatic
public fun isDisabled(op: ScriptElt): Boolean = when (op) {
OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT, OP_INVERT, OP_AND, OP_OR, OP_XOR, OP_2MUL, OP_2DIV, OP_MUL, OP_MUL, OP_DIV, OP_MOD, OP_LSHIFT, OP_RSHIFT -> true
else -> false
}
@JvmStatic
public fun cost(op: ScriptElt): Int = when {
op.code > OP_16.code -> 1
else -> 0
}
@JvmStatic
public fun encodeNumber(value: Long): ByteVector {
if (value == 0L) return ByteVector.empty
else {
val result = arrayListOf()
val neg = value < 0
var absvalue = if (neg) -value else value
while (absvalue > 0) {
result.add((absvalue and 0xff).toByte())
absvalue = absvalue.shr(8)
}
// - If the most significant byte is >= 0x80 and the value is positive, push a
// new zero-byte to make the significant byte < 0x80 again.
// - If the most significant byte is >= 0x80 and the value is negative, push a
// new 0x80 byte that will be popped off when converting to an integral.
// - If the most significant byte is < 0x80 and the value is negative, add
// 0x80 to it, since it will be subtracted and interpreted as a negative when
// converting to an integral.
if ((result.last().toInt() and 0x80) != 0) {
result.add(if (neg) 0x80.toByte() else 0)
} else if (neg) {
result[result.lastIndex] = (result[result.lastIndex].toInt() or 0x80).toByte()
}
return result.toByteArray().byteVector()
}
}
@JvmStatic
public fun encodeNumber(value: Int): ByteVector = encodeNumber(value.toLong())
@JvmStatic
public fun decodeNumber(input: ByteArray, checkMinimalEncoding: Boolean, maximumSize: Int = 4): Long {
return when {
input.isEmpty() -> 0
input.size > maximumSize -> throw RuntimeException("number cannot be encoded on more than $maximumSize bytes")
else -> {
if (checkMinimalEncoding) {
// Check that the number is encoded with the minimum possible
// number of bytes.
//
// If the most-significant-byte - excluding the sign bit - is zero
// then we're not minimal. Note how this test also rejects the
// negative-zero encoding, 0x80.
if ((input.last().toInt() and 0x7f) == 0) {
// One exception: if there's more than one byte and the most
// significant bit of the second-most-significant-byte is set
// it would conflict with the sign bit. An example of this case
// is +-255, which encode to 0xff00 and 0xff80 respectively.
// (big-endian).
if (input.size <= 1 || (input[input.size - 2].toInt() and 0x80) == 0) {
throw RuntimeException("non-minimally encoded script number")
}
}
}
var result = 0L
for (i in 0..input.lastIndex) {
result = result or (input[i].toLong() and 0xffL).shl(8 * i)
}
// If the input vector's most significant byte is 0x80, remove it from
// the result's msb and return a negative.
if ((input.last().toInt() and 0x80) != 0)
-(result and (0x80L.shl(8 * (input.size - 1))).inv())
else
result
}
}
}
@JvmStatic
public fun decodeNumber(input: ByteVector, checkMinimalEncoding: Boolean, maximumSize: Int = 4): Long =
decodeNumber(input.toByteArray(), checkMinimalEncoding, maximumSize)
private fun castToBoolean(input: List): Boolean {
return if (input.isEmpty()) false
else {
val input1 = input.reversed()
when {
input1.first() == 0x80.toByte() && !input1.tail().any { it != 0.toByte() } -> false
input.any { it != 0.toByte() } -> true
else -> false
}
}
}
private fun castToBoolean(input: ByteArray): Boolean = castToBoolean(input.asList())
private fun castToBoolean(input: ByteVector): Boolean = castToBoolean(input.toByteArray())
@JvmStatic
public fun isPushOnly(script: List): Boolean = !script.any {
when {
isSimpleValue(it) -> false
it is OP_PUSHDATA -> false
else -> true
}
}
@JvmStatic
public fun isPayToScript(script: ByteArray): Boolean =
script.size == 23 && script[0] == OP_HASH160.code.toByte() && script[1] == 0x14.toByte() && script[22] == OP_EQUAL.code.toByte()
@JvmStatic
public fun isNativeWitnessScript(script: List): Boolean = when {
script.size != 2 -> false
!setOf(OP_0, OP_1, OP_2, OP_3, OP_4, OP_5, OP_6, OP_7, OP_8, OP_9, OP_10, OP_11, OP_12, OP_13, OP_14, OP_15, OP_16).contains(script[0]) -> false
else -> when (val program = script[1]) {
is OP_PUSHDATA -> program.data.size() in 2..40
else -> false
}
}
@JvmStatic
public fun isNativeWitnessScript(script: ByteVector): Boolean = runCatching { parse(script) }.map { isNativeWitnessScript(it) }.getOrDefault(false)
@JvmStatic
public fun pushSize(op: ScriptElt): Int? = when (op) {
is OP_PUSHDATA -> op.data.size()
else -> null
}
@JvmStatic
public fun getWitnessVersion(script: List): Int? = when {
script.size != 2 -> null
script[0] == OP_0 && (script[1].isPush(20) || script[1].isPush(32)) -> 0
simpleValue(script[0]) in 1..16 && pushSize(script[1]) in 2..40 -> simpleValue(script[0]).toInt()
else -> null
}
@JvmStatic
public fun getWitnessVersion(script: ByteVector): Int? = runCatching { parse(script) }.map { getWitnessVersion(it) }.getOrDefault(null)
/**
* Creates a m-of-n multisig script.
*
* @param m is the number of required signatures
* @param pubkeys are the public keys signatures will be checked against (there should be at least as many public keys
* as required signatures)
* @return a multisig redeem script
*/
@JvmStatic
public fun createMultiSigMofN(m: Int, pubkeys: List): List {
require(m in 1..16) { "number of required signatures is $m, should be between 1 and 16" }
require(pubkeys.count() in 1..16) { "number of public keys is ${pubkeys.size}, should be between 1 and 16" }
require(m <= pubkeys.count()) { "The required number of signatures shouldn't be greater than the number of public keys" }
val op_m = ScriptEltMapping.code2elt.getValue(m + 0x50)
// 1 -> OP_1, 2 -> OP_2, ... 16 -> OP_16
val op_n = ScriptEltMapping.code2elt.getValue(pubkeys.count() + 0x50)
return listOf(op_m) + pubkeys.map { OP_PUSHDATA(it) } + listOf(op_n, OP_CHECKMULTISIG)
}
/**
* @param pubKeys are the public keys signatures will be checked against.
* @param sigs are the signatures for a subset of the public keys.
* @return script witness for the pay-to-witness-script-hash script containing a multisig script.
*/
@JvmStatic
public fun witnessMultiSigMofN(pubKeys: List, sigs: List): ScriptWitness {
val redeemScript = write(createMultiSigMofN(sigs.size, pubKeys))
return ScriptWitness(listOf(ByteVector.empty) + sigs + listOf(ByteVector(redeemScript)))
}
@JvmStatic
public fun isPay2pkh(script: List): Boolean {
return when {
script.size == 5 && script[0] == OP_DUP && script[1] == OP_HASH160 && script[2].isPush(20) && script[3] == OP_EQUALVERIFY && script[4] == OP_CHECKSIG -> true
else -> false
}
}
@JvmStatic
public fun isPay2pkh(script: ByteArray): Boolean = isPay2pkh(parse(script))
@JvmStatic
public fun isPay2sh(script: List): Boolean {
return when {
script.size == 3 && script[0] == OP_HASH160 && script[1].isPush(20) && script[2] == OP_EQUAL -> true
else -> false
}
}
@JvmStatic
public fun isPay2sh(script: ByteArray): Boolean = isPay2sh(parse(script))
@JvmStatic
public fun isPay2wpkh(script: List): Boolean {
return when {
script.size == 2 && script[0] == OP_0 && script[1].isPush(20) -> true
else -> false
}
}
@JvmStatic
public fun isPay2wpkh(script: ByteArray): Boolean = isPay2wpkh(parse(script))
@JvmStatic
public fun isPay2wsh(script: List): Boolean {
return when {
script.size == 2 && script[0] == OP_0 && script[1].isPush(32) -> true
else -> false
}
}
@JvmStatic
public fun isPay2wsh(script: ByteArray): Boolean = isPay2wsh(parse(script))
@JvmStatic
public fun isPay2tr(script: List): Boolean {
return when {
script.size == 2 && script[0] == OP_1 && script[1].isPush(32) -> true
else -> false
}
}
@JvmStatic
public fun isPay2tr(script: ByteArray): Boolean = isPay2tr(parse(script))
@JvmStatic
public fun isPay2tr(script: ByteVector): Boolean = isPay2tr(script.toByteArray())
/**
* @param pubKeyHash public key hash
* @return a pay-to-public-key-hash script
*/
@JvmStatic
public fun pay2pkh(pubKeyHash: ByteArray): List {
require(pubKeyHash.size == 20) { "pubkey hash length must be 20 bytes" }
return listOf(OP_DUP, OP_HASH160, OP_PUSHDATA(pubKeyHash), OP_EQUALVERIFY, OP_CHECKSIG)
}
/**
* @param pubKey public key
* @return a pay-to-public-key-hash script
*/
@JvmStatic
public fun pay2pkh(pubKey: PublicKey): List = pay2pkh(pubKey.hash160())
/**
* @param script bitcoin script
* @return a pay-to-script script
*/
@JvmStatic
public fun pay2sh(script: List): List = pay2sh(write(script))
/**
* @param script bitcoin script
* @return a pay-to-script script
*/
@JvmStatic
public fun pay2sh(script: ByteArray): List = listOf(OP_HASH160, OP_PUSHDATA(Crypto.hash160(script)), OP_EQUAL)
/**
* @param script bitcoin script
* @return a pay-to-witness-script script
*/
@JvmStatic
public fun pay2wsh(script: List): List = pay2wsh(write(script))
/**
* @param script bitcoin script
* @return a pay-to-witness-script script
*/
@JvmStatic
public fun pay2wsh(script: ByteArray): List = listOf(OP_0, OP_PUSHDATA(Crypto.sha256(script)))
/**
* @param script bitcoin script
* @return a pay-to-witness-script script
*/
@JvmStatic
public fun pay2wsh(script: ByteVector): List = pay2wsh(script.toByteArray())
/**
* @param pubKeyHash public key hash
* @return a pay-to-witness-public-key-hash script
*/
@JvmStatic
public fun pay2wpkh(pubKeyHash: ByteArray): List {
require(pubKeyHash.size == 20) { "pubkey hash length must be 20 bytes" }
return listOf(OP_0, OP_PUSHDATA(pubKeyHash))
}
/**
* @param pubKey public key
* @return a pay-to-witness-public-key-hash script
*/
@JvmStatic
public fun pay2wpkh(pubKey: PublicKey): List = pay2wpkh(pubKey.hash160())
/**
* @param pubKey public key
* @param sig signature matching the public key
* @return script witness for the corresponding pay-to-witness-public-key-hash script
*/
@JvmStatic
public fun witnessPay2wpkh(pubKey: PublicKey, sig: ByteVector): ScriptWitness = ScriptWitness(listOf(sig, pubKey.value))
/**
* @param outputKey public key exposed by the taproot script (tweaked based on the tapscripts).
* @return a pay-to-taproot script.
*/
@JvmStatic
public fun pay2tr(outputKey: XonlyPublicKey): List = listOf(OP_1, OP_PUSHDATA(outputKey.value))
/**
* @param internalKey internal public key that will be tweaked with the [scripts] provided.
* @param scripts optional spending scripts that can be used instead of key-path spending.
*/
@JvmStatic
public fun pay2tr(internalKey: XonlyPublicKey, scripts: ScriptTree?): List = pay2tr(internalKey, scripts?.hash())
/**
* @param internalKey internal public key that will be tweaked with the [scriptsRoot] provided.
* @param scriptsRoot optional merkle root of the spending scripts that can be used instead of key-path spending.
*/
@JvmStatic
public fun pay2tr(internalKey: XonlyPublicKey, scriptsRoot: ByteVector32?): List {
val tweak = when (scriptsRoot) {
null -> Crypto.TaprootTweak.NoScriptTweak
else -> Crypto.TaprootTweak.ScriptTweak(scriptsRoot)
}
val (publicKey, _) = internalKey.outputKey(tweak)
return pay2tr(publicKey)
}
/** NB: callers must ensure that they use the correct [Crypto.TaprootTweak] when generating their signature. */
@JvmStatic
public fun witnessKeyPathPay2tr(sig: ByteVector64, sighash: Int = SigHash.SIGHASH_DEFAULT): ScriptWitness = when (sighash) {
SigHash.SIGHASH_DEFAULT -> ScriptWitness(listOf(sig))
else -> ScriptWitness(listOf(sig.concat(sighash.toByte())))
}
/**
* @param internalKey taproot internal public key.
* @param script script that is spent (must exist in the [scriptTree]).
* @param witness witness for the spent [script].
* @param scriptTree tapscript tree.
*/
@JvmStatic
public fun witnessScriptPathPay2tr(internalKey: XonlyPublicKey, script: ScriptTree.Leaf, witness: ScriptWitness, scriptTree: ScriptTree): ScriptWitness {
val controlBlock = ControlBlock.build(internalKey, scriptTree, script)
return ScriptWitness(witness.stack + script.script + controlBlock)
}
public fun removeSignature(script: List, signature: ByteVector): List {
val toRemove = OP_PUSHDATA(signature)
return script.filterNot { it == toRemove }
}
public fun removeSignatures(script: List, sigs: List): List =
sigs.fold(script, Script::removeSignature)
public fun checkLockTime(lockTime: Long, tx: Transaction, inputIndex: Int): Boolean {
// There are two kinds of nLockTime: lock-by-blockheight
// and lock-by-blocktime, distinguished by whether
// nLockTime < LOCKTIME_THRESHOLD.
//
// We want to compare apples to apples, so fail the script
// unless the type of nLockTime being tested is the same as
// the nLockTime in the transaction.
if (!((tx.lockTime < Transaction.LOCKTIME_THRESHOLD && lockTime < Transaction.LOCKTIME_THRESHOLD) || (tx.lockTime >= Transaction.LOCKTIME_THRESHOLD && lockTime >= Transaction.LOCKTIME_THRESHOLD))) {
return false
}
// Now that we know we're comparing apples-to-apples, the
// comparison is a simple numeric one.
if (lockTime > tx.lockTime)
return false
// Finally the nLockTime feature can be disabled and thus
// CHECKLOCKTIMEVERIFY bypassed if every txin has been
// finalized by setting nSequence to maxint. The
// transaction would be allowed into the blockchain, making
// the opcode ineffective.
//
// Testing if this vin is not final is sufficient to
// prevent this condition. Alternatively we could test all
// inputs, but testing just this input minimizes the data
// required to prove correct CHECKLOCKTIMEVERIFY execution.
if (tx.txIn.elementAt(inputIndex).isFinal)
return false
return true
}
public fun checkSequence(sequence: Long, tx: Transaction, inputIndex: Int): Boolean {
// Relative lock times are supported by comparing the passed
// in operand to the sequence number of the input.
val txToSequence = tx.txIn.elementAt(inputIndex).sequence
// Fail if the transaction's version number is not set high
// enough to trigger BIP 68 rules.
if (tx.version < 2)
return false
// Sequence numbers with their most significant bit set are not
// consensus constrained. Testing that the transaction's sequence
// number do not have this bit set prevents using this property
// to get around a CHECKSEQUENCEVERIFY check.
if ((txToSequence and TxIn.SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0L)
return false
// Mask off any bits that do not have consensus-enforced meaning
// before doing the integer comparisons
val nLockTimeMask = TxIn.SEQUENCE_LOCKTIME_TYPE_FLAG or TxIn.SEQUENCE_LOCKTIME_MASK
val txToSequenceMasked = txToSequence and nLockTimeMask
val nSequenceMasked = sequence and nLockTimeMask
// There are two kinds of nSequence: lock-by-blockheight
// and lock-by-blocktime, distinguished by whether
// nSequenceMasked < CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG.
//
// We want to compare apples to apples, so fail the script
// unless the type of nSequenceMasked being tested is the same as
// the nSequenceMasked in the transaction.
if (!((txToSequenceMasked < TxIn.SEQUENCE_LOCKTIME_TYPE_FLAG && nSequenceMasked < TxIn.SEQUENCE_LOCKTIME_TYPE_FLAG) || (txToSequenceMasked >= TxIn.SEQUENCE_LOCKTIME_TYPE_FLAG && nSequenceMasked >= TxIn.SEQUENCE_LOCKTIME_TYPE_FLAG))) {
return false
}
// Now that we know we're comparing apples-to-apples, the
// comparison is a simple numeric one.
if (nSequenceMasked > txToSequenceMasked)
return false
return true
}
public fun sigHashType(sig: ByteArray): Int = when {
sig.size == 64 -> SigHash.SIGHASH_DEFAULT
sig.size == 65 && sig[64].toInt() == SigHash.SIGHASH_DEFAULT -> error("invalid sig hashtype")
sig.size == 65 -> sig[64].toInt() and 0xff
else -> error("invalid signature")
}
public fun sigHashType(sig: ByteVector): Int = sigHashType(sig.toByteArray())
public fun List.tail(): List {
require(this.isNotEmpty()) { "tail of empty list" }
return this.drop(1)
}
public fun List.dropCheck(n: Int): List {
require(this.size >= n) { "cannot drop $n elements on a list of $size elements" }
return this.drop(n)
}
/**
* Execution context of a tx script. A script is always executed in the "context" of a transaction that is being
* verified.
*
* @param tx transaction that is being verified
* @param inputIndex 0-based index of the tx input that is being processed
*/
public data class Context(val tx: Transaction, val inputIndex: Int, val amount: Satoshi, val prevouts: List) {
internal var executionData: ExecutionData = ExecutionData.empty
init {
require(inputIndex >= 0 && inputIndex < tx.txIn.count()) { "invalid input index" }
}
}
public data class ExecutionData(val annex: ByteVector?, val tapleafHash: ByteVector32?, val validationWeightLeft: Int? = null, val codeSeparatorPos: Long = 0xFFFFFFFFL) {
public companion object {
public val empty: ExecutionData = ExecutionData(null, null, null, 0xFFFFFFFFL)
}
}
public object ControlBlock {
/**
* Build a control block to add to the witness of a taproot transaction when spending with the script path.
* It includes information to re-compute the merkle root from the script you are using.
*
* For example, suppose you have the following script tree:
* root
* / \
* / \ #3
* #1 #2
*
* To recompute its merkle root you need to provide either:
* - if you're spending with script #1: leaves #2 and #3
* - if you're spending with script #2: leaves #1 and #3
* - if you're spending with script #3: branch(#1, #2)
*
* @param internalPubKey internal public key.
* @param scriptTree tapscript tree.
* @param spendingScript script leaf we're spending (must exist in the [scriptTree]).
*/
@JvmStatic
public fun build(internalPubKey: XonlyPublicKey, scriptTree: ScriptTree, spendingScript: ScriptTree.Leaf): ByteVector {
val merkleProof = scriptTree.merkleProof(spendingScript.hash())
require(merkleProof != null) { "cannot build control block: the spending script leaf cannot be found in the script tree provided" }
return build(internalPubKey, scriptTree.hash(), merkleProof, spendingScript.leafVersion)
}
/**
* @param internalPubKey internal public key.
* @param merkleRoot merkle root of the tapscript tree.
* @param merkleProof merkle proof of the spent script leaf: must not contain the script leaf nor the merkle root.
* @param leafVersion script version of the spent script leaf.
*/
@JvmStatic
public fun build(internalPubKey: XonlyPublicKey, merkleRoot: ByteVector32, merkleProof: ByteArray, leafVersion: Int): ByteVector {
val (_, parity) = internalPubKey.outputKey(merkleRoot)
val controlByte = (leafVersion + (if (parity) 1 else 0)).toByte()
return ByteVector.empty.concat(controlByte).concat(internalPubKey.value).concat(merkleProof)
}
}
public class Runner(
public val context: Context,
public val scriptFlag: Int = ScriptFlags.MANDATORY_SCRIPT_VERIFY_FLAGS,
) {
public companion object {
/**
* This class represents the state of the script execution engine
*
* @param conditions current "position" wrt if/notif/else/endif
* @param altstack initial alternate stack
* @param opCount initial op count
* @param scriptCode initial script (can be modified by OP_CODESEPARATOR for example)
*/
public data class State(
val conditions: List,
val altstack: List,
val opCount: Int,
val scriptCode: List
)
}
public fun checkSignatureEcdsa(pubKey: ByteArray, sigBytes: ByteArray, scriptCode: ByteArray, signatureVersion: Int): Boolean {
return when {
sigBytes.isEmpty() -> false
!Crypto.checkSignatureEncoding(sigBytes, scriptFlag) -> throw RuntimeException("invalid signature encoding")
!Crypto.checkPubKeyEncoding(pubKey, scriptFlag, signatureVersion) -> throw RuntimeException("invalid public key encoding")
!Crypto.isPubKeyValid(pubKey) -> false // see how this is different from above ?
else -> {
val sigHashFlags = sigBytes.last().toInt() and 0xff
// sig hash is the last byte
val sigBytes1 = sigBytes.dropLast(1).toByteArray() // drop sig hash
if (sigBytes1.isEmpty()) false
else {
val hash = Transaction.hashForSigning(context.tx, context.inputIndex, scriptCode, sigHashFlags, context.amount, signatureVersion)
// signature is normalized here, but high-S correctness has already been checked
val normalized = Crypto.normalize(sigBytes1).first
val pub = PublicKey.parse(pubKey)
val result = Crypto.verifySignature(hash, normalized, pub)
result
}
}
}
}
public fun checkSignatureSchnorr(pubKey: ByteArray, sigBytes: ByteArray, signatureVersion: Int): Boolean {
require(signatureVersion == SigVersion.SIGVERSION_TAPSCRIPT)
if (sigBytes.isNotEmpty()) {
require(context.executionData.validationWeightLeft != null)
context.executionData.validationWeightLeft?.let {
val weightLeft = it - VALIDATION_WEIGHT_PER_SIGOP_PASSED
context.executionData = context.executionData.copy(validationWeightLeft = weightLeft)
require(weightLeft >= 0) { "tapscript weight validation failed" }
}
}
return when {
pubKey.isEmpty() -> error("invalid pubkey")
pubKey.size == 32 && sigBytes.isEmpty() -> false
pubKey.size == 32 -> {
val sighashType = sigHashType(sigBytes)
val hash = Transaction.hashForSigningSchnorr(
context.tx,
context.inputIndex,
context.prevouts,
sighashType,
signatureVersion,
context.executionData.tapleafHash,
context.executionData.annex,
context.executionData.codeSeparatorPos
)
val result = Secp256k1.verifySchnorr(sigBytes.take(64).toByteArray(), hash.toByteArray(), pubKey)
require(result) { "Invalid Schnorr signature" }
result
}
else -> {
require((scriptFlag and ScriptFlags.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) == 0) { "invalid pubkey type" }
sigBytes.isNotEmpty()
}
}
}
/**
* @param pubKey public key
* @param sigBytes signature, in Bitcoin format (DER encoded + 1 trailing sighash bytes)
* @param scriptCode current script code
* @param signatureVersion version (legacy or segwit)
* @return true if the signature is valid
*/
public fun checkSignature(pubKey: ByteArray, sigBytes: ByteArray, scriptCode: ByteArray, signatureVersion: Int): Boolean {
return when (signatureVersion) {
SigVersion.SIGVERSION_BASE, SigVersion.SIGVERSION_WITNESS_V0 -> checkSignatureEcdsa(pubKey, sigBytes, scriptCode, signatureVersion)
SigVersion.SIGVERSION_TAPROOT -> false // Key path spending in Taproot has no script, so this is unreachable.
SigVersion.SIGVERSION_TAPSCRIPT -> checkSignatureSchnorr(pubKey, sigBytes, signatureVersion)
else -> error("invalid signature version")
}
}
public fun checkSignature(pubKey: ByteVector, sigBytes: ByteVector, scriptCode: ByteVector, signatureVersion: Int): Boolean =
checkSignature(pubKey.toByteArray(), sigBytes.toByteArray(), scriptCode.toByteArray(), signatureVersion)
public tailrec fun checkSignatures(pubKeys: List, sigs: List, scriptCode: ByteVector, signatureVersion: Int): Boolean {
return when {
sigs.isEmpty() -> true
sigs.count() > pubKeys.count() -> false
!Crypto.checkSignatureEncoding(sigs.first().toByteArray(), scriptFlag) -> throw RuntimeException("invalid signature")
checkSignature(pubKeys.first(), sigs.first(), scriptCode, signatureVersion) -> checkSignatures(pubKeys.tail(), sigs.tail(), scriptCode, signatureVersion)
else -> checkSignatures(pubKeys.tail(), sigs, scriptCode, signatureVersion)
}
}
public fun checkMinimalEncoding(): Boolean = (scriptFlag and ScriptFlags.SCRIPT_VERIFY_MINIMALDATA) != 0
public fun decodeNumber(input: ByteVector, maximumSize: Int = 4): Long =
decodeNumber(input.toByteArray(), checkMinimalEncoding(), maximumSize)
public fun decodeNumber(input: ByteArray, maximumSize: Int = 4): Long =
decodeNumber(input, checkMinimalEncoding(), maximumSize)
public fun run(script: ByteArray, signatureVersion: Int): List {
return run(script, listOf(), signatureVersion)
}
public fun run(script: List, signatureVersion: Int): List =
run(script, listOf(), signatureVersion)
public fun run(script: ByteArray, stack: List, signatureVersion: Int): List {
if (signatureVersion == SigVersion.SIGVERSION_BASE || signatureVersion == SigVersion.SIGVERSION_WITNESS_V0) {
require(script.size <= MAX_SCRIPT_SIZE) { "Script is too large" }
}
return run(parse(script), stack, signatureVersion)
}
public fun run(script: ByteVector, stack: List, signatureVersion: Int): List =
run(script.toByteArray(), stack, signatureVersion)
public fun run(
script: List,
stack: List,
signatureVersion: Int
): List {
stack.forEach { require(it.size() <= MAX_SCRIPT_ELEMENT_SIZE) { "item is bigger than maximum push size" } }
return runInternal(script, stack, signatureVersion)
}
/**
* internal script execution loop.
*/
private fun runInternal(
script: List,
inputStack: List,
signatureVersion: Int
): List {
val stack = inputStack.toMutableList()
val altstack = mutableListOf()
// conditions is a stack of boolean that is checked by each IF/NOTIF instruction
// each time we execute IF/NOTIF, we insert the boolean that is checked by IF/NOTIF into our "conditions" stack
// each time we execute ELSE, we flip the head our "conditions" stack
// and each time we execute ENDIF we remove the head of our "conditions" stack
// if any value in our "conditions" stack is false, it means that we're in an IF branch that is not executed
// OP_1 // conditions = []
// OP_IF
// OP_CHECKSIG // conditions = [true]
// OP_IF //
// OP_2 // conditions = [false, true] (we assume CHECKSIG failed), this branch will not be executed
// OP_ELSE
// OP_3 // conditions = [true, true]
// OP_ELSE
// OP_PUSHDATA("deadbeef") // conditions = [false], this branch will not be executed
// OP_ENDIF
// OP_CHECKSIG // conditions = []
val conditions = mutableListOf()
var opCount = 0
var scriptCode: List = script
for (currentPos in script.indices) {
val op = script[currentPos]
if (isDisabled(op)) throw RuntimeException("$op is disabled")
if (signatureVersion == SigVersion.SIGVERSION_BASE || signatureVersion == SigVersion.SIGVERSION_WITNESS_V0) {
// Note how OP_RESERVED does not count towards the opcode limit.
opCount += cost(op)
require(opCount <= MAX_OPS_PER_SCRIPT) { "Operation limit exceeded" }
}
when {
op == OP_CODESEPARATOR && signatureVersion == SigVersion.SIGVERSION_BASE && (scriptFlag and ScriptFlags.SCRIPT_VERIFY_CONST_SCRIPTCODE) != 0 -> throw RuntimeException("Using OP_CODESEPARATOR in non-witness script")
op == OP_VERIF -> throw RuntimeException("OP_VERIF is always invalid")
op == OP_VERNOTIF -> throw RuntimeException("OP_VERNOTIF is always invalid")
op is OP_PUSHDATA && op.data.size() > MAX_SCRIPT_ELEMENT_SIZE -> throw RuntimeException("Push value size limit exceeded")
// check whether we are in a non-executed IF branch
op == OP_IF && conditions.any { !it } -> {
conditions.add(0, false)
}
op == OP_IF && stack.isEmpty() -> throw RuntimeException("Invalid OP_IF construction")
op == OP_IF -> {
val stackhead = stack.removeFirst()
when {
stackhead == True && signatureVersion == SigVersion.SIGVERSION_WITNESS_V0 && (scriptFlag and ScriptFlags.SCRIPT_VERIFY_MINIMALIF) != 0 -> conditions.add(0, true)
stackhead == False && signatureVersion == SigVersion.SIGVERSION_WITNESS_V0 && (scriptFlag and ScriptFlags.SCRIPT_VERIFY_MINIMALIF) != 0 -> conditions.add(0, false)
signatureVersion == SigVersion.SIGVERSION_WITNESS_V0 && (scriptFlag and ScriptFlags.SCRIPT_VERIFY_MINIMALIF) != 0 -> throw RuntimeException("OP_IF argument must be minimal")
signatureVersion == SigVersion.SIGVERSION_TAPSCRIPT && stackhead != True && stackhead != False -> throw RuntimeException("OP_IF argument must be minimal")
castToBoolean(stackhead) -> conditions.add(0, true)
else -> conditions.add(0, false)
}
}
op == OP_NOTIF && conditions.any { !it } -> {
conditions.add(0, true)
}
op == OP_NOTIF && stack.isEmpty() -> throw RuntimeException("Invalid OP_NOTIF construction")
op == OP_NOTIF -> {
val stackhead = stack.removeFirst()
when {
stackhead == False && signatureVersion == SigVersion.SIGVERSION_WITNESS_V0 && (scriptFlag and ScriptFlags.SCRIPT_VERIFY_MINIMALIF) != 0 -> conditions.add(0, true)
stackhead == True && signatureVersion == SigVersion.SIGVERSION_WITNESS_V0 && (scriptFlag and ScriptFlags.SCRIPT_VERIFY_MINIMALIF) != 0 -> conditions.add(0, false)
signatureVersion == SigVersion.SIGVERSION_WITNESS_V0 && (scriptFlag and ScriptFlags.SCRIPT_VERIFY_MINIMALIF) != 0 -> throw RuntimeException("OP_NOTIF argument must be minimal")
signatureVersion == SigVersion.SIGVERSION_TAPSCRIPT && stackhead != True && stackhead != False -> throw RuntimeException("OP_IF argument must be minimal")
castToBoolean(stackhead) -> conditions.add(0, false)
else -> conditions.add(0, true)
}
}
op == OP_ELSE && conditions.isEmpty() -> throw RuntimeException("Invalid OP_ELSE construction")
op == OP_ELSE -> {
conditions[0] = !conditions[0]
}
op == OP_ENDIF && conditions.isEmpty() -> throw RuntimeException("Invalid OP_ENDIF construction")
op == OP_ENDIF -> {
conditions.removeFirst()
}
conditions.any { !it } -> {} // do nothing, we're in an IF branch that is not executed
// and now, things that are checked only in an executed IF branch
op == OP_0 -> stack.add(0, False)
isSimpleValue(op) -> stack.add(0, encodeNumber(simpleValue(op).toInt()))
op == OP_NOP -> {}
isUpgradableNop(op) && ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0) -> throw RuntimeException("use of upgradable NOP is discouraged")
isUpgradableNop(op) -> {}
op == OP_1ADD && stack.isEmpty() -> throw RuntimeException("cannot run OP_1ADD on an empty stack")
op == OP_1ADD -> {
stack[0] = encodeNumber(decodeNumber(stack.first()) + 1)
}
op == OP_1SUB && stack.isEmpty() -> throw RuntimeException("cannot run OP_1SUB on an empty stack")
op == OP_1SUB -> {
stack[0] = encodeNumber(decodeNumber(stack.first()) - 1)
}
op == OP_ABS && stack.isEmpty() -> throw RuntimeException("cannot run OP_ABS on an empty stack")
op == OP_ABS -> {
stack[0] = encodeNumber(kotlin.math.abs(decodeNumber(stack.first())))
}
op == OP_ADD && stack.size < 2 -> throw RuntimeException("cannot run OP_ADD on a stack with less than 2 elements")
op == OP_ADD -> {
val x = decodeNumber(stack.removeFirst())
val y = decodeNumber(stack.removeFirst())
val result = x + y
stack.add(0, encodeNumber(result))
}
op == OP_BOOLAND && stack.size < 2 -> throw RuntimeException("cannot run OP_BOOLAND on a stack with less than 2 elements")
op == OP_BOOLAND -> {
val x = decodeNumber(stack.removeFirst())
val y = decodeNumber(stack.removeFirst())
val result = if (x != 0L && y != 0L) 1L else 0L
stack.add(0, encodeNumber(result))
}
op == OP_BOOLOR && stack.size < 2 -> throw RuntimeException("cannot run OP_BOOLOR on a stack with less than 2 elements")
op == OP_BOOLOR -> {
val x = decodeNumber(stack.removeFirst())
val y = decodeNumber(stack.removeFirst())
val result = if (x != 0L || y != 0L) 1L else 0L
stack.add(0, encodeNumber(result))
}
op == OP_CHECKLOCKTIMEVERIFY && ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY) != 0) && stack.isEmpty() -> throw RuntimeException("cannot run OP_CHECKLOCKTIMEVERIFY on an empty stack")
op == OP_CHECKLOCKTIMEVERIFY && ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY) != 0) -> {
// Note that elsewhere numeric opcodes are limited to
// operands in the range -2**31+1 to 2**31-1, however it is
// legal for opcodes to produce results exceeding that
// range. This limitation is implemented by CScriptNum's
// default 4-byte limit.
//
// If we kept to that limit we'd have a year 2038 problem,
// even though the nLockTime field in transactions
// themselves is uint32 which only becomes meaningless
// after the year 2106.
//
// Thus as a special case we tell CScriptNum to accept up
// to 5-byte bignums, which are good until 2**39-1, well
// beyond the 2**32-1 limit of the nLockTime field itself.
val locktime = decodeNumber(stack.first(), maximumSize = 5)
if (locktime < 0) throw RuntimeException("CLTV lock time cannot be negative")
if (!checkLockTime(locktime, context.tx, context.inputIndex)) throw RuntimeException("unsatisfied CLTV lock time")
}
op == OP_CHECKLOCKTIMEVERIFY && ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0) -> throw RuntimeException("use of upgradable NOP is discouraged")
op == OP_CHECKLOCKTIMEVERIFY -> {}
op == OP_CHECKSEQUENCEVERIFY && ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY) != 0) && stack.isEmpty() -> throw RuntimeException("cannot run OP_CHECKSEQUENCEVERIFY on an empty stack")
op == OP_CHECKSEQUENCEVERIFY && ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY) != 0) -> {
// nSequence, like nLockTime, is a 32-bit unsigned integer
// field. See the comment in CHECKLOCKTIMEVERIFY regarding
// 5-byte numeric operands.
val sequence = decodeNumber(stack.first(), maximumSize = 5)
// In the rare event that the argument may be < 0 due to
// some arithmetic being done first, you can always use
// 0 MAX CHECKSEQUENCEVERIFY.
if (sequence < 0) throw RuntimeException("CSV lock time cannot be negative")
// To provide for future soft-fork extensibility, if the
// operand has the disabled lock-time flag set,
// CHECKSEQUENCEVERIFY behaves as a NOP.
if ((sequence and TxIn.SEQUENCE_LOCKTIME_DISABLE_FLAG) == 0L) {
// Actually compare the specified inverse sequence number
// with the input.
if (!checkSequence(sequence, context.tx, context.inputIndex)) throw RuntimeException("unsatisfied CSV lock time")
}
}
op == OP_CHECKSEQUENCEVERIFY && ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) != 0) -> throw RuntimeException("use of upgradable NOP is discouraged")
op == OP_CHECKSEQUENCEVERIFY -> {}
op == OP_CHECKSIG && stack.size < 2 -> throw RuntimeException("Cannot perform OP_CHECKSIG on a stack with less than 2 elements")
op == OP_CHECKSIG || op == OP_CHECKSIGVERIFY -> {
val pubKey = stack.removeFirst()
val sigBytes = stack.removeFirst()
// remove signature from script
val scriptCode1 = if (signatureVersion == SigVersion.SIGVERSION_BASE) {
val scriptCode1 = removeSignature(scriptCode, sigBytes)
if (scriptCode1.size != scriptCode.size && (scriptFlag and ScriptFlags.SCRIPT_VERIFY_CONST_SCRIPTCODE) != 0) {
throw RuntimeException("Signature is found in scriptCode")
}
scriptCode1
} else {
scriptCode
}
val success = checkSignature(pubKey.toByteArray(), sigBytes.toByteArray(), write(scriptCode1), signatureVersion)
if (!success && (scriptFlag and ScriptFlags.SCRIPT_VERIFY_NULLFAIL) != 0) {
require(sigBytes.isEmpty()) { "Signature must be zero for failed CHECKSIG operation" }
}
if (op == OP_CHECKSIGVERIFY) {
require(success) { "OP_CHECKSIGVERIFY failed" }
} else {
stack.add(0, if (success) True else False)
}
}
op == OP_CHECKSIGADD -> {
// OP_CHECKSIGADD is only available in Tapscript
require(signatureVersion != SigVersion.SIGVERSION_BASE && signatureVersion != SigVersion.SIGVERSION_WITNESS_V0) { "invalid opcode" }
require(stack.size >= 3) { "Cannot perform OP_CHECKSIGADD on a stack with less than 3 elements" }
val pubKey = stack.removeFirst()
val num = decodeNumber(stack.removeFirst())
val sigBytes = stack.removeFirst()
val success = checkSignature(pubKey.toByteArray(), sigBytes.toByteArray(), write(scriptCode), signatureVersion)
stack.add(0, encodeNumber(num + (if (success) 1 else 0)))
}
op == OP_CHECKMULTISIG || op == OP_CHECKMULTISIGVERIFY -> {
require(signatureVersion != SigVersion.SIGVERSION_TAPSCRIPT) { "invalid OP_CHECKMULTISIG operation" }
// pop public keys
val m = decodeNumber(stack.removeFirst()).toInt()
if (m < 0 || m > 20) throw RuntimeException("OP_CHECKMULTISIG: invalid number of public keys")
opCount += m
if (opCount > MAX_OPS_PER_SCRIPT) throw RuntimeException("operation count is over the limit")
val pubKeys = (1..m).map { stack.removeFirst() }
// pop signatures
val n = decodeNumber(stack.removeFirst()).toInt()
if (n < 0 || n > m) throw RuntimeException("OP_CHECKMULTISIG: invalid number of signatures")
// check that we have at least n + 1 items on the stack (+1 because of a bug in the reference client)
require(stack.size >= n + 1) { "invalid stack operation" }
val sigs = (1..n).map { stack.removeFirst() }
if ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_NULLDUMMY) != 0) require(stack.first().size() == 0) { "multisig dummy is not empty" }
stack.removeFirst()
// Drop the signature in pre-segwit scripts but not segwit scripts
val scriptCode1 = if (signatureVersion == SigVersion.SIGVERSION_BASE) {
val scriptCode1 = removeSignatures(scriptCode, sigs)
if (scriptCode1.size != scriptCode.size && (scriptFlag and ScriptFlags.SCRIPT_VERIFY_CONST_SCRIPTCODE) != 0) {
throw RuntimeException("Signature is found in scriptCode")
}
scriptCode1
} else {
scriptCode
}
val success = checkSignatures(pubKeys, sigs, write(scriptCode1).byteVector(), signatureVersion)
if (!success && (scriptFlag and ScriptFlags.SCRIPT_VERIFY_NULLFAIL) != 0) {
sigs.forEach { require(it.isEmpty()) { "Signature must be zero for failed CHECKMULTISIG operation" } }
}
if (op == OP_CHECKMULTISIGVERIFY) {
require(success) { "OP_CHECKMULTISIGVERIFY failed" }
} else {
stack.add(0, if (success) True else False)
}
}
op == OP_CODESEPARATOR -> {
this.context.executionData = this.context.executionData.copy(codeSeparatorPos = currentPos.toLong())
scriptCode = script.drop(currentPos + 1)
}
op == OP_DEPTH -> {
stack.add(0, encodeNumber(stack.size))
}
op == OP_SIZE && stack.isEmpty() -> throw RuntimeException("Cannot run OP_SIZE on an empty stack")
op == OP_SIZE -> {
stack.add(0, encodeNumber(stack.first().size()))
}
op == OP_DROP -> {
stack.removeFirst()
}
op == OP_2DROP -> {
stack.removeFirst()
stack.removeFirst()
}
op == OP_DUP -> {
stack.add(0, stack.first())
}
op == OP_2DUP && stack.size < 2 -> throw RuntimeException("Cannot perform OP_2DUP on a stack with less than 2 elements")
op == OP_2DUP -> {
val x1 = stack[0]
val x2 = stack[1]
stack.addAll(0, listOf(x1, x2))
}
op == OP_3DUP && stack.size < 3 -> throw RuntimeException("Cannot perform OP_3DUP on a stack with less than 2 elements")
op == OP_3DUP -> {
val x1 = stack[0]
val x2 = stack[1]
val x3 = stack[2]
stack.addAll(0, listOf(x1, x2, x3))
}
op == OP_EQUAL && stack.size < 2 -> throw RuntimeException("Cannot perform OP_EQUAL on a stack with less than 2 elements")
op == OP_EQUAL -> {
val x1 = stack.removeFirst()
val x2 = stack.removeFirst()
stack.add(0, if (x1 != x2) False else True)
}
op == OP_EQUALVERIFY && stack.size < 2 -> throw RuntimeException("Cannot perform OP_EQUALVERIFY on a stack with less than 2 elements")
op == OP_EQUALVERIFY -> {
val x1 = stack.removeFirst()
val x2 = stack.removeFirst()
require(x1 == x2) { "OP_EQUALVERIFY failed: elements are different" }
}
op == OP_FROMALTSTACK -> {
stack.add(0, altstack.removeFirst())
}
op == OP_HASH160 -> {
stack[0] = Crypto.hash160(stack.first()).byteVector()
}
op == OP_HASH256 -> {
stack[0] = Crypto.hash256(stack.first()).byteVector()
}
op == OP_IFDUP && stack.isEmpty() -> throw RuntimeException("Cannot perform OP_IFDUP on an empty stack")
op == OP_IFDUP -> {
if (castToBoolean(stack.first())) stack.add(0, stack.first())
}
op == OP_LESSTHAN && stack.size < 2 -> throw RuntimeException("Cannot perform OP_LESSTHAN on a stack with less than 2 elements")
op == OP_LESSTHAN -> {
val x1 = decodeNumber(stack.removeFirst())
val x2 = decodeNumber(stack.removeFirst())
val result = if (x2 < x1) 1 else 0
stack.add(0, encodeNumber(result))
}
op == OP_LESSTHANOREQUAL && stack.size < 2 -> throw RuntimeException("Cannot perform OP_LESSTHANOREQUAL on a stack with less than 2 elements")
op == OP_LESSTHANOREQUAL -> {
val x1 = decodeNumber(stack.removeFirst())
val x2 = decodeNumber(stack.removeFirst())
val result = if (x2 <= x1) 1 else 0
stack.add(0, encodeNumber(result))
}
op == OP_GREATERTHAN && stack.size < 2 -> throw RuntimeException("Cannot perform OP_GREATERTHAN on a stack with less than 2 elements")
op == OP_GREATERTHAN -> {
val x1 = decodeNumber(stack.removeFirst())
val x2 = decodeNumber(stack.removeFirst())
val result = if (x2 > x1) 1 else 0
stack.add(0, encodeNumber(result))
}
op == OP_GREATERTHANOREQUAL && stack.size < 2 -> throw RuntimeException("Cannot perform OP_GREATERTHANOREQUAL on a stack with less than 2 elements")
op == OP_GREATERTHANOREQUAL -> {
val x1 = decodeNumber(stack.removeFirst())
val x2 = decodeNumber(stack.removeFirst())
val result = if (x2 >= x1) 1 else 0
stack.add(0, encodeNumber(result))
}
op == OP_MAX && stack.size < 2 -> throw RuntimeException("Cannot perform OP_MAX on a stack with less than 2 elements")
op == OP_MAX -> {
val x1 = decodeNumber(stack.removeFirst())
val x2 = decodeNumber(stack.removeFirst())
val result = if (x2 > x1) x2 else x1
stack.add(0, encodeNumber(result))
}
op == OP_MIN && stack.size < 2 -> throw RuntimeException("Cannot perform OP_MIN on a stack with less than 2 elements")
op == OP_MIN -> {
val x1 = decodeNumber(stack.removeFirst())
val x2 = decodeNumber(stack.removeFirst())
val result = if (x2 < x1) x2 else x1
stack.add(0, encodeNumber(result))
}
op == OP_NEGATE && stack.isEmpty() -> throw RuntimeException("Cannot perform OP_NEGATE on an empty stack")
op == OP_NEGATE -> {
stack[0] = encodeNumber(-decodeNumber(stack.first()))
}
op == OP_NIP && stack.size < 2 -> throw RuntimeException("Cannot perform OP_NIP on a stack with less than 2 elements")
op == OP_NIP -> {
stack.removeAt(1)
}
op == OP_NOT && stack.isEmpty() -> throw RuntimeException("Cannot perform OP_NOT on an empty stack")
op == OP_NOT -> {
stack[0] = encodeNumber(if (decodeNumber(stack.first()) == 0L) 1 else 0)
}
op == OP_0NOTEQUAL && stack.isEmpty() -> throw RuntimeException("Cannot perform OP_0NOTEQUAL on an empty stack")
op == OP_0NOTEQUAL -> {
stack[0] = encodeNumber(if (decodeNumber(stack.first()) == 0L) 0 else 1)
}
op == OP_NUMEQUAL && stack.size < 2 -> throw RuntimeException("Cannot perform OP_NUMEQUAL on a stack with less than 2 elements")
op == OP_NUMEQUAL -> {
val x1 = decodeNumber(stack.removeFirst())
val x2 = decodeNumber(stack.removeFirst())
val result = if (x1 == x2) 1 else 0
stack.add(0, encodeNumber(result))
}
op == OP_NUMEQUALVERIFY && stack.size < 2 -> throw RuntimeException("Cannot perform OP_NUMEQUALVERIFY on a stack with less than 2 elements")
op == OP_NUMEQUALVERIFY -> {
val x1 = decodeNumber(stack.removeFirst())
val x2 = decodeNumber(stack.removeFirst())
require(x1 == x2) { "OP_NUMEQUALVERIFY failed" }
}
op == OP_NUMNOTEQUAL && stack.size < 2 -> throw RuntimeException("Cannot perform OP_NUMNOTEQUAL on a stack with less than 2 elements")
op == OP_NUMNOTEQUAL -> {
val x1 = decodeNumber(stack.removeFirst())
val x2 = decodeNumber(stack.removeFirst())
val result = if (x1 != x2) 1 else 0
stack.add(0, encodeNumber(result))
}
op == OP_OVER && stack.size < 2 -> throw RuntimeException("Cannot perform OP_OVER on a stack with less than 2 elements")
op == OP_OVER -> {
stack.add(0, stack[1])
}
op == OP_2OVER && stack.size < 4 -> throw RuntimeException("Cannot perform OP_2OVER on a stack with less than 2 elements")
op == OP_2OVER -> {
stack.addAll(0, listOf(stack[2], stack[3]))
}
op == OP_PICK && stack.isEmpty() -> throw RuntimeException("Cannot perform OP_PICK on an empty stack")
op == OP_PICK -> {
val n = decodeNumber(stack.removeFirst()).toInt()
require(stack.size > n) { "Cannot perform OP_PICK on a stack with less than ${n + 1} elements" }
stack.add(0, stack[n])
}
op is OP_PUSHDATA && ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_MINIMALDATA) != 0) && !OP_PUSHDATA.isMinimal(op.data.toByteArray(), op.code) -> throw RuntimeException("not minimal push")
op is OP_PUSHDATA -> stack.add(0, op.data)
op == OP_ROLL && stack.isEmpty() -> throw RuntimeException("Cannot perform OP_ROLL on an empty stack")
op == OP_ROLL -> {
val n = decodeNumber(stack.removeFirst()).toInt()
require(stack.size > n) { "Cannot perform OP_ROLL on a stack with less than ${n + 1} elements" }
val xn = stack.removeAt(n)
stack.add(0, xn)
}
op == OP_ROT && stack.size < 3 -> throw RuntimeException("Cannot perform OP_ROT on a stack with less than 3 elements")
op == OP_ROT -> {
val x0 = stack[0]
val x1 = stack[1]
val x2 = stack[2]
stack[0] = x2
stack[1] = x0
stack[2] = x1
}
op == OP_2ROT && stack.size < 6 -> throw RuntimeException("Cannot perform OP_2ROT on a stack with less than 6 elements")
op == OP_2ROT -> {
val x0 = stack[0]
val x1 = stack[1]
val x2 = stack[2]
val x3 = stack[3]
val x4 = stack[4]
val x5 = stack[5]
stack[0] = x4
stack[1] = x5
stack[2] = x0
stack[3] = x1
stack[4] = x2
stack[5] = x3
}
op == OP_RIPEMD160 -> {
stack[0] = Crypto.ripemd160(stack.first()).byteVector()
}
op == OP_SHA1 -> {
stack[0] = Crypto.sha1(stack.first()).byteVector()
}
op == OP_SHA256 -> {
stack[0] = Crypto.sha256(stack.first()).byteVector()
}
op == OP_SUB && stack.size < 2 -> throw RuntimeException("Cannot perform OP_SUB on a stack with less than 2 elements")
op == OP_SUB -> {
val x1 = decodeNumber(stack.removeFirst())
val x2 = decodeNumber(stack.removeFirst())
val result = x2 - x1
stack.add(0, encodeNumber(result))
}
op == OP_SWAP && stack.size < 2 -> throw RuntimeException("Cannot perform OP_SWAP on a stack with less than 2 elements")
op == OP_SWAP -> {
val x0 = stack[0]
val x1 = stack[1]
stack[0] = x1
stack[1] = x0
}
op == OP_2SWAP && stack.size < 4 -> throw RuntimeException("Cannot perform OP_2SWAP on a stack with less than 4 elements")
op == OP_2SWAP -> {
val x0 = stack[0]
val x1 = stack[1]
val x2 = stack[2]
val x3 = stack[3]
stack[0] = x2
stack[1] = x3
stack[2] = x0
stack[3] = x1
}
op == OP_TOALTSTACK -> {
altstack.add(0, stack.removeFirst())
}
op == OP_TUCK && stack.size < 2 -> throw RuntimeException("Cannot perform OP_TUCK on a stack with less than 2 elements")
op == OP_TUCK -> {
val x0 = stack[0]
val x1 = stack[1]
stack[0] = x1
stack[1] = x0
stack.add(0, x0)
}
op == OP_VERIFY && stack.isEmpty() -> throw RuntimeException("Cannot perform OP_VERIFY on an empty stack")
op == OP_VERIFY -> {
val x = stack.removeFirst()
require(castToBoolean(x)) { "OP_VERIFY failed" }
}
op == OP_WITHIN && stack.size < 3 -> throw RuntimeException("Cannot perform OP_WITHIN on a stack with less than 3 elements")
op == OP_WITHIN -> {
val max = decodeNumber(stack.removeFirst())
val min = decodeNumber(stack.removeFirst())
val n = decodeNumber(stack.removeFirst())
val result = if (n in min until max) 1 else 0
stack.add(0, encodeNumber(result))
}
else -> {
throw RuntimeException("unexpected operator $op")
}
}
require(stack.size + altstack.size <= MAX_STACK_SIZE) { "stack is too large: stack size = ${stack.size} alt stack size = ${altstack.size}" }
}
require(conditions.isEmpty()) { "IF/ENDIF imbalance" }
return stack
}
public fun verifyWitnessProgram(witness: ScriptWitness, witnessVersion: Long, program: ByteArray, isP2sh: Boolean = false) {
// check that the input stack contains a single "1" element, as it should be if script execution was correct
fun checkFinalStack(stack: List) {
require(stack.size == 1)
require(castToBoolean(stack.first()))
}
// reset taproot execution data
this.context.executionData = ExecutionData.empty
when {
witnessVersion == 0L && program.size == WITNESS_V0_KEYHASH_SIZE -> {
// P2WPKH, program is simply the pubkey hash
require(witness.stack.count() == 2) { "Invalid witness program, should have 2 items" }
val finalStack = run(listOf(OP_DUP, OP_HASH160, OP_PUSHDATA(program), OP_EQUALVERIFY, OP_CHECKSIG), witness.stack.reversed(), SigVersion.SIGVERSION_WITNESS_V0)
checkFinalStack(finalStack)
}
witnessVersion == 0L && program.size == WITNESS_V0_SCRIPTHASH_SIZE -> {
// P2WPSH, program is the hash of the script, and witness is the stack + the script
val check = Crypto.sha256(witness.stack.last())
require(check.contentEquals(program)) { "witness program mismatch" }
val finalStack = run(witness.stack.last(), witness.stack.dropLast(1).reversed(), SigVersion.SIGVERSION_WITNESS_V0)
checkFinalStack(finalStack)
}
witnessVersion == 0L -> throw IllegalArgumentException("Invalid witness program length: ${program.size}")
witnessVersion == 1L && program.size == WITNESS_V1_TAPROOT_SIZE && !isP2sh -> {
// BIP341 Taproot: 32-byte non-P2SH witness v1 program (which encodes a P2C-tweaked pubkey)
if ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_TAPROOT) == 0) return
require(witness.stack.isNotEmpty()) { "Witness program cannot be empty" }
val (stack, annex) = when {
witness.stack.size >= 2 && !witness.stack.last().isEmpty() && witness.stack.last()[0] == 0x50.toByte() -> Pair(witness.stack.dropLast(1), witness.stack.last())
else -> Pair(witness.stack, null)
}
this.context.executionData = this.context.executionData.copy(annex = annex)
// Key path spending (stack size is 1 after removing optional annex)
if (stack.size == 1) {
val sig = stack.first()
val pub = XonlyPublicKey(program.byteVector32())
val hashType = sigHashType(sig)
val hash = Transaction.hashForSigningSchnorr(
context.tx,
context.inputIndex,
context.prevouts,
hashType,
SigVersion.SIGVERSION_TAPROOT,
context.executionData.tapleafHash,
context.executionData.annex,
context.executionData.codeSeparatorPos
)
require(Secp256k1.verifySchnorr(sig.take(64).toByteArray(), hash.toByteArray(), pub.value.toByteArray())) { " invalid Schnorr signature " }
return
} else {
val outputKey = XonlyPublicKey(program.byteVector32())
val script = stack[stack.size - 2]
val control = stack[stack.size - 1]
require((control.size() - 33).mod(32) == 0) { "invalid control block size" }
require((control.size() - 33) / 32 in 0..128) { "invalid control block size" }
val leafVersion = control[0].toInt() and TAPROOT_LEAF_MASK
val internalKey = XonlyPublicKey(control.slice(1, 33).toByteArray().byteVector32())
val tapleafHash = ScriptTree.Leaf(script, leafVersion).hash()
this.context.executionData = this.context.executionData.copy(tapleafHash = tapleafHash)
// split input buffer into 32 bytes chunks (input buffer size MUST be a multiple of 32 !!)
tailrec fun split32(input: ByteVector, acc: List = listOf()): List = when {
input.size() == 0 -> acc
else -> split32(input.drop(32), acc + input.take(32).toByteArray().byteVector32())
}
val leaves = split32(control.drop(33))
val merkleRoot = leaves.fold(tapleafHash) { a, b ->
Crypto.taggedHash(if (LexicographicalOrdering.isLessThan(a, b)) a.toByteArray() + b.toByteArray() else b.toByteArray() + a.toByteArray(), "TapBranch")
}
val parity = (control[0].toInt() and 0x01) == 0x01
require(Pair(outputKey, parity) == internalKey.outputKey(Crypto.TaprootTweak.ScriptTweak(merkleRoot)))
if (leafVersion == TAPROOT_LEAF_TAPSCRIPT) {
this.context.executionData = this.context.executionData.copy(validationWeightLeft = ScriptWitness.write(witness).size + VALIDATION_WEIGHT_OFFSET)
tailrec fun hasOpSuccess(it: Iterator): Boolean = when {
!it.hasNext() -> false
isOpSuccess(it.next().code) -> true
else -> hasOpSuccess(it)
}
if (hasOpSuccess(scriptIterator(script.toByteArray()))) {
require(scriptFlag and ScriptFlags.SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS == 0) { "OP_SUCCESSx reserved for soft-fork upgrades" }
return
}
val stack1 = stack.dropLast(2).reversed()
require(stack1.size <= MAX_STACK_SIZE) { "Stack size limit exceeded" }
val finalStack = run(script, stack1, SigVersion.SIGVERSION_TAPSCRIPT)
checkFinalStack(finalStack)
} else {
require(scriptFlag and ScriptFlags.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION == 0) { "Taproot version $leafVersion reserved for soft-fork upgrades" }
}
}
}
(scriptFlag and ScriptFlags.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) != 0 -> throw IllegalArgumentException("Witness version $witnessVersion reserved for soft-fork upgrades")
else -> {
// Higher version witness scripts return true for future softfork compatibility
return
}
}
}
public fun verifyScripts(scriptSig: ByteArray, scriptPubKey: ByteArray): Boolean =
verifyScripts(scriptSig, scriptPubKey, ScriptWitness.empty)
public fun verifyScripts(scriptSig: ByteVector, scriptPubKey: ByteVector, witness: ScriptWitness): Boolean =
verifyScripts(scriptSig.toByteArray(), scriptPubKey.toByteArray(), witness)
/**
* verify a script sig/script pubkey pair:
*
* - parse and run script sig
* - parse and run script pubkey using the stack generated by the previous step
* - check the final stack
* - extract and run embedded pay2sh scripts if any and check the stack again
*
*
* @param scriptSig signature script
* @param scriptPubKey public key script
* @return true if the scripts were successfully verified
*/
public fun verifyScripts(scriptSig: ByteArray, scriptPubKey: ByteArray, witness: ScriptWitness): Boolean {
fun checkStack(stack: List): Boolean = when {
stack.isEmpty() -> false
!castToBoolean(stack.first()) -> false
(scriptFlag and ScriptFlags.SCRIPT_VERIFY_CLEANSTACK) != 0 -> {
if ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_P2SH) == 0) throw RuntimeException("illegal script flag")
stack.size == 1
}
else -> true
}
if ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_WITNESS) != 0) {
// We can't check for correct unexpected witness data if P2SH was off, so require
// that WITNESS implies P2SH. Otherwise, going from WITNESS->P2SH+WITNESS would be
// possible, which is not a softfork.
require((scriptFlag and ScriptFlags.SCRIPT_VERIFY_P2SH) != 0)
}
val ssig = parse(scriptSig)
if (((scriptFlag and ScriptFlags.SCRIPT_VERIFY_SIGPUSHONLY) != 0) && !isPushOnly(ssig)) throw RuntimeException("signature script is not PUSH-only")
val stack = run(scriptSig, listOf(), signatureVersion = 0)
val spub = parse(scriptPubKey)
val stack0 = run(scriptPubKey, stack, signatureVersion = 0)
require(stack0.isNotEmpty()) { "Script verification failed, stack should not be empty" }
require(castToBoolean(stack0.first())) { "Script verification failed, stack starts with 'false'" }
var hadWitness = false
fun isWitnessProgram(script: List): Boolean =
script.size == 2 && isSimpleValue(script[0]) && simpleValue(script[0]).toInt() in 0..16 && script[1] is OP_PUSHDATA
val stack1 = if ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_WITNESS) != 0 && isWitnessProgram(spub)) {
val witnessVersion = simpleValue(spub[0])
val program = spub[1] as OP_PUSHDATA
when {
OP_PUSHDATA.isMinimal(program.data.toByteArray(), program.code) && program.data.size() in 2..40 -> {
hadWitness = true
require(ssig.isEmpty()) { "Malleated segwit script" }
verifyWitnessProgram(witness, witnessVersion.toLong(), program.data.toByteArray(), isP2sh = false)
stack0.take(1)
}
else -> stack0
}
} else stack0
val stack2 = if (((scriptFlag and ScriptFlags.SCRIPT_VERIFY_P2SH) != 0) && isPayToScript(scriptPubKey)) {
// scriptSig must be literals-only or validation fails
if (!isPushOnly(ssig)) throw RuntimeException("signature script is not PUSH-only")
// pay to script:
// script sig is built as sig1 :: ... :: sigN :: serialized_script :: Nil
// and script pubkey is HASH160 :: hash :: EQUAL :: Nil
// if we got here after running script pubkey, it means that hash == HASH160(serialized script)
// and stack would be serialized_script :: sigN :: ... :: sig1 :: Nil
// we pop the first element of the stack, deserialize it and run it against the rest of the stack
val stackp2sh = run(stack.first(), stack.tail(), 0)
require(stackp2sh.isNotEmpty()) { "Script verification failed, stack should not be empty" }
require(castToBoolean(stackp2sh.first())) { "Script verification failed, stack starts with 'false'" }
if ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_WITNESS) != 0) {
val program = parse(stack.first())
when {
program.size == 2 && isSimpleValue(program[0]) && pushSize(program[1]) in 2..40 -> {
hadWitness = true
val witnessVersion = simpleValue(program[0])
verifyWitnessProgram(witness, witnessVersion.toLong(), (program[1] as OP_PUSHDATA).data.toByteArray(), isP2sh = true)
stackp2sh.take(1)
}
else -> stackp2sh
}
} else stackp2sh
} else stack1
if ((scriptFlag and ScriptFlags.SCRIPT_VERIFY_WITNESS) != 0 && !hadWitness) {
require(witness.isNull())
}
return checkStack(stack2)
}
}
}