Add static test vectors for Taproot (#3769)

* Add static test vectors for Taproot and the ability to parse those test cases

Add TaprootWitness data structure, get parsing working for first static test case

WIP: Distinguish between TaprootKeyPath and TaprootScriptPath

Remove invariants and make val to method in TaprootScriptPath so we can parse test cases

Add TaprootTestCase.{txSigComponents, programs} methods

Try to run test case

WIP

Wrap failure case in Try

Get first test case passing

Fix building of sig component for p2sh

WIP test case legacy/pk-wrongkey

Get more test cases passing

Move where MAX_PUSH_SIZE is checked for segwit

Get another test case passing

Add links to bitcoin core in test case

Fix stack parsing for witness

Get success test cases passing (without signature verification?)

Add failure test cases

Fix basic compile failures except in javascript projects

Get basic TaprootKeyPath parsing working from ScriptWitness

Get invariants implemented correctly for TaprootScriptPath

WIP

WIP

Get first taproot signature serialization test working

Get tagged hash working correctly

Rework test framework, get 2nd success test case passing

Get compile working with rebase

Implement computeTapleafHash with a unit test case

Add scaffolding of computing merkle root test case

Implement computeTaprootMerkleRoot() with a unit test

Implement computeTapTweakHash() with a unit test

WIP: checkTapTweak()

WIP

Implement computeTapTweakHash() unit test

Rebase onto master

Get verifyTaprootCommitment() passing unit test

Refactors to be more readable

* WIP: Tapscript signature checking

* Get taproot script path signature serialization working for unit test

* Add carve out for unknown public key types

* WIP: OP_CHECKSIGADD

* Add test case to detect annex and compute its hash

* Get test case passing when using upgradable public keys with an annex on the stack

* Fix missing pattern match

* Fix bug with tapscript SIGHASH_ALL and add test case

* Add check if taproot flag is enabled

* Get signature verification working with annex hash

* Implement correct handling of fail case for OP_CHECKSIGADD

* Get test case passing

* DRY

* Fix bug, now we only allow tapscript sig checking when pubkey is 32 bytes in size

* Refactor evalChecksigTapscript to use XOnlyPubKey

* Get signature serialization working with OP_CODESEPARATOR

* Get SIGHASH_ANYONECANPAY|SINGLE example working

* Fix bug in BIP342 impl where we don't count op codes if the version is taproot

* Fix OP_CODESEPARATOR bug

* Implement calculating of OP_CODESEPARATOR idx relative to other opcodes, not push operations

* Fix OP_CHECKSIG tapscript bug where we didn't push OP_FALSE onto stack in case of signature validation failure

* Add annex to TaprootKeyPath

* Get signature chcking working with tapscript keypath annex

* Cleanup test framework code a bit to avoid casting exceptions

* Implement handling of OP_SUCCESS

* WIP: Segwit v0 serialization with nonstandard sighash flag

* Fix hash bug in segwit v0 serialization

* WIP

* Fix bug where we weren't defaulting to SIGHASH_DEFAULT when using tapscript

* Add disabled opcodes to OP_SUCCESS case

* Fix parsing for witnesses in test case

* Get a SIGHASH_SINGLE test case working

* Clean up rebase

* Fix default hash type in TaprootKeyPath

* Implement opCodeSeparator counting that does NOT work when OP_CODESEPARATOR is is not executed inside of an OP_IF, otherwise is very simple for the base case

* Cherry-pick ben's commits & rebase

* Remove script size limit for tap scripts

* Fix incorrect handling of unassigned spk

* Fix invariant

* get correct test case failing

* WIP: SIGHASH_ALL_ANYONECANPAY test case

* Cleanup logging/println

* Refactors & fix regressions in some simple unit tests

* Remove logback in core to get the entire project compiling again

* Make TapscriptPath.hasAnnex() more robust against exceptions

* Add validation of XOnlyPubKey to control block

* Implement known leaf versions in the control block

* Add TaprootUnknownPath and UnknownControlBlock

* Fix rebase

* Fix interpreter bug where v0 segwit wasn't failing when a wrong program was used

* Cleanup println

* Clean up println pt2

* Re-enable -Xfatal-warnings

* Turn off logback-test.xml

* Parallelize taproot success test cases

* Try to bump timeout

* Optimization: Reduce number of intersections in ScriptInterpreter.run()

* Ben's code review

* Take ben's clean stack bugfix

Co-authored-by: benthecarman <benthecarman@live.com>
This commit is contained in:
Chris Stewart 2022-07-07 14:53:28 -05:00 committed by GitHub
parent 000e7a7930
commit 211339f344
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 3426 additions and 166 deletions

View file

@ -85,7 +85,6 @@ object SerializedTransaction {
pubKey = None,
signature = None,
stack = Some(p2wsh.stack.toVector.tail)))
case taprootWitness: TaprootWitness =>
throw new UnsupportedOperationException(
s"Taproot not supported, got=$taprootWitness")

View file

@ -6,13 +6,7 @@ import org.bitcoins.core.config.RegTest
import org.bitcoins.core.crypto.ECPrivateKeyUtil
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit, Satoshis}
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.script.{
EmptyScriptWitness,
P2WPKHWitnessV0,
P2WSHWitnessV0,
ScriptSignature,
TaprootWitness
}
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.protocol.{Bech32Address, BitcoinAddress, P2PKHAddress}
import org.bitcoins.core.wallet.fee.SatoshisPerByte

View file

@ -0,0 +1,3 @@
[
{"tx": "0200000001d3d2f0976d67794790c9f874f40bc591fe8ebd8ad83377c1a9f5cf5d51930312f10100000082e96cc4016a4ca601000000001600145fdfb9b9d231797765d05fc245c9714dbb14e8407c2ae64f", "prevouts": ["0e92ba0100000000225120995c260ccfd5c31ba34ba028a98486a9bf1ed13cf91fbb2dbfc73a35ac9d5ccf"], "index": 0, "success": {"scriptSig": "", "witness": ["61", "8eb72183c9f60f81051641ec1119f4ca1048f83f6ebbfa3805731c0479a8c5d8bafbb397947548494548d50e6f6b436d2fda68af07b70b7f66f56c98db81bdc41bf45c0240e4cb2f2d96a2680887aed487322c91ce1c7e839db721c93b3195599ed1cc6539704683b9970669af51e2f3a8a75aa7b1d13b278bca97ad494a5d9220"]}, "failure": {"scriptSig": "", "witness": ["61", "c0b72183c9f60f81051641ec1119f4ca1048f83f6ebbfa3805731c0479a8c5d8ba7442ba539034b2067bd698804af25c3f58d06009743f0ed3b18819eff684c0368a7e9d8f4a71cf5b77c9ed0a0e8c125a5496e4c4f36e045b98cc64f611b56efb2712f4444661e1b026ee1a556cb176264536b2c722475596ff9c7b511a208212"]}, "flags": "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY,TAPROOT", "comment": "unkver/bare"}
]

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,218 @@
package org.bitcoins.core.protocol.transaction
import org.bitcoins.core.crypto.{TaprootTxSigComponent, TxSigComponent}
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.script.PreExecutionScriptProgram
import org.bitcoins.core.script.flag.{ScriptFlag, ScriptFlagFactory}
import org.bitcoins.core.script.util.PreviousOutputMap
import scodec.bits.ByteVector
import upickle.default._
import scala.util.{Failure, Success, Try}
case class TaprootTestCase(
tx: Transaction,
prevouts: Vector[TransactionOutput],
index: Int,
success: (ScriptSignature, Option[ScriptWitness]),
failure: Try[(ScriptSignature, Option[ScriptWitness])],
flags: Vector[ScriptFlag],
`final`: Option[Boolean],
comment: String) {
def successTxSigComponent: TxSigComponent = {
buildSigComponent(successTx)
}
/** Returns the failed tx sig component iff an exception wasn't
* thrown during constructino of the tx sig component
*/
def failureTxSigComponentsOpt: Option[TxSigComponent] = {
failureTxT.map { witTx =>
buildSigComponent(witTx)
}.toOption
}
private def buildSigComponent(tx: Transaction): TxSigComponent = {
val outpoints = tx.inputs.map(_.previousOutput)
val output = prevouts(index)
require(
prevouts.length == outpoints.length,
s"prevOutputs.length=${prevouts.length} outpoints.length=${outpoints.length}")
val outputMap: Map[TransactionOutPoint, TransactionOutput] =
outpoints.zip(prevouts).toMap
output.scriptPubKey match {
case _: TaprootScriptPubKey =>
tx match {
case wtx: WitnessTransaction =>
TaprootTxSigComponent(transaction = wtx,
UInt32(index),
PreviousOutputMap(outputMap),
flags)
case nonWitTx: NonWitnessTransaction =>
TxSigComponent(transaction = nonWitTx,
UInt32(index),
output,
PreviousOutputMap.empty,
flags)
}
case _: ScriptPubKey =>
TxSigComponent(transaction = tx,
inputIndex = UInt32(index),
output,
PreviousOutputMap.empty,
flags)
}
}
def txSigComponents: Vector[TxSigComponent] = {
successTxSigComponent +: failureTxSigComponentsOpt.toVector
}
def successProgram: PreExecutionScriptProgram = {
PreExecutionScriptProgram(successTxSigComponent)
}
/** Returns a program that should fail if defined */
def failProgramOpt: Option[PreExecutionScriptProgram] = {
failureTxSigComponentsOpt.map { case failureTxSigComponent =>
PreExecutionScriptProgram(failureTxSigComponent)
}
}
/** Builds a success witness tx with both the scriptSig/witness added */
private def successTx: Transaction = {
updateTxWithWitness(scriptSig = success._1, witnessOpt = success._2)
}
private def failureTxT: Try[Transaction] = {
failure.map { case (scriptSig, witness) =>
updateTxWithWitness(scriptSig, witness)
}
}
private def updateTxWithWitness(
scriptSig: ScriptSignature,
witnessOpt: Option[ScriptWitness]): Transaction = {
val curInput = tx.inputs(index)
val inputWithScriptSig =
TransactionInput(curInput.previousOutput, scriptSig, curInput.sequence)
val withScriptSig =
tx.updateInput(index, inputWithScriptSig)
witnessOpt match {
case Some(witness) =>
withScriptSig match {
case wtx: WitnessTransaction =>
wtx.updateWitness(index, witness)
case btx: BaseTransaction =>
val w = WitnessTransaction.toWitnessTx(btx)
w.updateWitness(index, witness)
case EmptyTransaction =>
sys.error(s"Cannot have empty transaction")
}
case None =>
withScriptSig
}
}
override def toString: String = {
s"""
|tx=$tx
|prevouts=$prevouts
|success=$success
|failure=$failure
|flags=$flags
|comment=$comment
|""".stripMargin
}
}
object TaprootTestCase {
implicit val taprootTransactionTestCaseR: Reader[TaprootTestCase] = {
reader[ujson.Obj].map { obj =>
try {
val transaction = Transaction.fromHex(obj("tx").str)
val prevouts = obj("prevouts").arr.toVector.map {
case str: ujson.Str =>
TransactionOutput.fromHex(str.value)
case x =>
sys.error(s"Expected string for prevouts, got=$x")
}
val index = obj("index").num.toInt
val success = obj("success") match {
case success: ujson.Obj =>
val scriptSig = ScriptSignature.fromAsmHex(success("scriptSig").str)
val stack = success("witness").arr
.map(_.str)
.map(ByteVector.fromValidHex(_))
val scriptWitnessT = {
prevouts(index).scriptPubKey match {
case _: TaprootScriptPubKey =>
val t = Try(TaprootWitness.fromStack(stack.toVector.reverse))
t match {
case Success(_) => t
case Failure(_) =>
Try(TaprootUnknownPath(stack.toVector.reverse))
}
case _ =>
Try(ScriptWitness(stack.toVector.reverse))
}
}
(scriptSig, scriptWitnessT.toOption)
case x => sys.error(s"Expected obj for success object, got=$x")
}
val failure = Try {
obj("failure") match {
case success: ujson.Obj =>
val scriptSig =
ScriptSignature.fromAsmHex(success("scriptSig").str)
val stack = success("witness").arr
.map(_.str)
.map(ByteVector.fromValidHex(_))
val scriptWitnessT = {
prevouts(index).scriptPubKey match {
case _: TaprootScriptPubKey =>
Try(TaprootWitness.fromStack(stack.toVector.reverse))
case _ =>
Try(ScriptWitness(stack.toVector.reverse))
}
}
(scriptSig, scriptWitnessT.toOption)
case x =>
sys.error(s"Expected obj for success object, got=$x")
}
}
val flags = ScriptFlagFactory.fromList(obj("flags").str).toVector
val finals = obj.value.get("final").map {
case b: ujson.Bool => b.bool
case x => sys.error(s"Expected bool for failure object, got=$x")
}
val comment = obj("comment").str
TaprootTestCase(tx = transaction,
prevouts = prevouts,
index = index,
success = success,
failure = failure,
flags = flags,
`final` = finals,
comment = comment)
} catch {
case scala.util.control.NonFatal(exn) =>
println(s"Failed to parse obj=${obj("comment").str}")
throw exn
}
}
}
}

View file

@ -0,0 +1,98 @@
package org.bitcoins.core.protocol.transaction
import org.bitcoins.core.protocol.script.{ScriptSignature, TaprootKeyPath}
import org.bitcoins.core.script.flag.ScriptVerifyTaproot
import org.bitcoins.core.script.interpreter.ScriptInterpreter
import org.bitcoins.core.script.result.ScriptOk
import org.bitcoins.testkitcore.util.BitcoinSJvmTest
import org.scalatest.Assertion
import org.scalatest.time.Span
import scodec.bits.ByteVector
import scala.concurrent.Future
import scala.concurrent.duration.DurationInt
class TaprootTxTests extends BitcoinSJvmTest {
behavior of "Taproot test cases"
//these static test vectors take forever
override lazy val timeLimit: Span = 10.minutes
//these tests are from
//https://raw.githubusercontent.com/bitcoin-core/qa-assets/main/unit_test_data/script_assets_test.json
lazy val url = getClass.getResource("/script_assets_test_cp.json")
lazy val lines = {
scala.io.Source.fromURL(url).getLines().mkString
}
lazy val testCases: Seq[TaprootTestCase] = {
upickle.default.read[Seq[TaprootTestCase]](lines)
}
it must "parse a taproot test case" in {
//https://github.com/bitcoin/bitcoin/blob/v22.0/test/functional/feature_taproot.py#L1112
//https://github.com/bitcoin/bitcoin/blob/3820090bd619ac85ab35eff376c03136fe4a9f04/src/test/script_tests.cpp#L1673
val first = testCases.head
val expectedTxHex =
"f705d6e8019870958e85d1d8f94aa6d74746ba974db0f5ccae49a49b32dcada4e19de4eb5ecb00000000925977cc01f9875c000000000016001431d2b00cd4687ceb34008d9894de84062def14aa05406346"
val expectedPrevOutHex =
"b4eae1010000000022512039f7e9232896f8100485e38afa652044f855e734a13b840a3f220cbd5d911ad5"
assert(first.flags.exists(_ == ScriptVerifyTaproot))
assert(first.tx.hex == expectedTxHex)
assert(first.prevouts.map(_.hex) == Vector(expectedPrevOutHex))
assert(first.index == 0)
assert(first.success._1 == ScriptSignature.empty)
val witHex =
"25e45bd4d4b8fcd5933861355a2d376aad8daf1af1588e5fb6dfcea22d0d809acda6fadca11e97f5b5c85af99df27cb24fa69b08fa6c790234cdc671d3af5a7302"
val witBytes = ByteVector.fromValidHex(witHex)
val expectedWitness = TaprootKeyPath.fromStack(Vector(witBytes))
assert(first.success._2.get == expectedWitness)
assert(first.`final` == Some(true))
}
it must "run the success test cases through the script interpreter" in {
//execute in parallel as running test cases sequentially takes 17 minutes on CI
val groupedTestCases =
testCases.grouped(Runtime.getRuntime.availableProcessors())
val execute: Vector[Future[Vector[Assertion]]] = {
groupedTestCases
.map(cases => executeSuccessTestCases(cases.toVector))
.toVector
}
Future
.sequence(execute)
.map(_.flatten)
.map(_ => succeed)
}
private def executeSuccessTestCases(
testCases: Vector[TaprootTestCase]): Future[Vector[Assertion]] = {
Future {
testCases.map { testCase =>
withClue(testCase.comment) {
val result = ScriptInterpreter.run(testCase.successProgram)
assert(result == ScriptOk)
}
}
}
}
it must "run the failure test cases through the script interpreter" ignore {
testCases.foreach { testCase =>
testCase.failureTxSigComponentsOpt match {
case Some(_) =>
withClue(testCase.comment) {
val result = ScriptInterpreter.run(testCase.failProgramOpt.get)
assert(result != ScriptOk)
}
case None =>
()
}
}
succeed
}
}

View file

@ -636,6 +636,7 @@ class TransactionSignatureSerializerTest extends BitcoinSUnitTest {
val leafHash = Sha256Digest.fromHex(
"8d76c657582b87b087f36579a9ea78816d7e2a94098bc3e3c6113ed4b6315bb4")
val taprootOptions = TaprootSerializationOptions(Some(leafHash), None, None)
val serialize = TransactionSignatureSerializer.serializeForSignature(
taprootTxSigComponent,
HashType.sigHashNone,
@ -709,7 +710,9 @@ class TransactionSignatureSerializerTest extends BitcoinSUnitTest {
val leafHash = Sha256Digest.fromHex(
"0c013c8aa4ee2a624a516c877892db854d6ccc9fd1cd8b94895cff88abaccbc6")
val taprootOptions = TaprootSerializationOptions(Some(leafHash), None, None)
val serialize = TransactionSignatureSerializer.serializeForSignature(
taprootTxSigComponent,
HashType.sigHashAll,

View file

@ -7,6 +7,7 @@ class ScriptWitnessSpec extends BitcoinSUnitTest {
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode
it must "have serialization symmetry" in {
forAll(WitnessGenerators.scriptWitness) { scriptWit =>
val x = ScriptWitness(scriptWit.stack)

View file

@ -753,4 +753,5 @@ object JsonTestVectors {
|["Make diffs cleaner by leaving a comment here without comma at the end"]
|]
|""".stripMargin
}

View file

@ -4,7 +4,7 @@ import org.bitcoins.core.script.arithmetic.OP_1ADD
import org.bitcoins.core.script.bitwise.OP_EQUAL
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.control.OP_IF
import org.bitcoins.core.script.crypto.OP_RIPEMD160
import org.bitcoins.core.script.crypto.{OP_CHECKSIGADD, OP_RIPEMD160}
import org.bitcoins.core.script.locktime.OP_CHECKLOCKTIMEVERIFY
import org.bitcoins.core.script.splice.OP_SUBSTR
import org.bitcoins.core.script.stack.OP_TOALTSTACK
@ -30,6 +30,8 @@ class ScriptOperationFactoryTest extends BitcoinSUnitTest {
ScriptOperation(166.toByte) must be(OP_RIPEMD160)
ScriptOperation(177.toByte) must be(OP_CHECKLOCKTIMEVERIFY)
ScriptOperation.fromByte(0xba.toByte) must be(OP_CHECKSIGADD)
}
it must "find OP_4 from it's byte representation" in {

View file

@ -45,4 +45,8 @@ class CryptoOperationsTest extends BitcoinSUnitTest {
it must "define OP_CHECKMULTISIGVERIFY" in {
OP_CHECKMULTISIGVERIFY.opCode must be(175)
}
it must "define OP_CHECKSIGADD" in {
OP_CHECKSIGADD.opCode must be(186)
}
}

View file

@ -11,6 +11,7 @@ class CryptoSignatureEvaluationFactoryTest extends BitcoinSUnitTest {
Seq(OP_CHECKMULTISIG,
OP_CHECKMULTISIGVERIFY,
OP_CHECKSIG,
OP_CHECKSIGVERIFY))
OP_CHECKSIGVERIFY,
OP_CHECKSIGADD))
}
}

View file

@ -15,6 +15,6 @@ class ReservedOperationsFactoryTest extends BitcoinSUnitTest {
ReservedOperation("b0") must be(Some(OP_NOP1))
}
it must "find an undefined operation from its hex value" in {
ReservedOperation("ba").isDefined must be(true)
ReservedOperation("bb").isDefined must be(true)
}
}

View file

@ -66,7 +66,7 @@ sealed abstract class TransactionSignatureSerializer {
}
def serializeForSignature(
private def serializeForSignature(
spendingTransaction: Transaction,
inputIndex: UInt32,
hashType: HashType,
@ -302,7 +302,6 @@ sealed abstract class TransactionSignatureSerializer {
} else {
ByteVector.empty
}
val outputHash: ByteVector =
if (isNotSigHashSingle && isNotSigHashNone) {
val outputs = spendingTransaction.outputs
@ -318,6 +317,7 @@ sealed abstract class TransactionSignatureSerializer {
.bytes
hash
} else ByteVector.empty
val haveAnnex: Boolean = taprootOptions.haveAnnex
val annexByte = if (haveAnnex) 1.toByte else 0.toByte

View file

@ -39,7 +39,11 @@ sealed abstract class Policy {
ScriptVerifyNullFail,
ScriptVerifyNullDummy,
ScriptVerifyWitnessPubKeyType,
ScriptVerifyDiscourageUpgradableWitnessProgram
ScriptVerifyDiscourageUpgradableWitnessProgram,
ScriptVerifyTaproot,
ScriptVerifyDiscourageUpgradableTaprootVersion,
ScriptVerifyDiscourageOpSuccess,
ScriptVerifyDiscourageUpgradablePubKeyType
)
def standardFlags = standardScriptVerifyFlags

View file

@ -41,7 +41,7 @@ case class UnknownControlBlock(bytes: ByteVector) extends ControlBlock
object ControlBlock extends Factory[ControlBlock] {
override def fromBytes(bytes: ByteVector): ControlBlock = {
TapscriptControlBlock(bytes)
new TapscriptControlBlock(bytes)
}
/** invariants from: https://github.com/bitcoin/bitcoin/blob/37633d2f61697fc719390767aae740ece978b074/src/script/interpreter.cpp#L1835

View file

@ -1088,7 +1088,7 @@ object RawScriptPubKey extends ScriptFactory[RawScriptPubKey] {
object NonWitnessScriptPubKey extends ScriptFactory[NonWitnessScriptPubKey] {
val empty: NonWitnessScriptPubKey = fromAsm(Nil)
def fromAsm(asm: Seq[ScriptToken]): NonWitnessScriptPubKey = {
override def fromAsm(asm: Seq[ScriptToken]): NonWitnessScriptPubKey = {
if (P2SHScriptPubKey.isValidAsm(asm)) {
P2SHScriptPubKey(asm)
} else {
@ -1344,10 +1344,19 @@ object P2WSHWitnessSPKV0 extends ScriptFactory[P2WSHWitnessSPKV0] {
case class TaprootScriptPubKey(override val asm: Vector[ScriptToken])
extends WitnessScriptPubKey {
require(
witnessVersion == WitnessVersion1,
s"Taproot scriptpubkeys must have witnessVersion OP_1, got=$witnessVersion")
require(bytes.length == 35,
s"Taproot spks must have length 35, got=${bytes.length}")
override def witnessProgram: Seq[ScriptToken] = asm.tail.tail
override val scriptType: ScriptType = ScriptType.WITNESS_V1_TAPROOT
val pubKey: XOnlyPubKey = XOnlyPubKey.fromBytes(asm(2).bytes)
val pubKey: XOnlyPubKey = {
require(asm(2).bytes.length == 32,
s"pubKeyBytes must be 32 bytes in length, got=${asm(2).byteSize}")
XOnlyPubKey.fromBytes(asm(2).bytes)
}
}
object TaprootScriptPubKey extends ScriptFactory[TaprootScriptPubKey] {
@ -1355,7 +1364,7 @@ object TaprootScriptPubKey extends ScriptFactory[TaprootScriptPubKey] {
override def fromAsm(asm: Seq[ScriptToken]): TaprootScriptPubKey = {
buildScript(asm.toVector,
TaprootScriptPubKey.apply,
s"Given asm was not a P2WSHWitnessSPKV0, got $asm")
s"Given asm was not a TaprootScriptPubKey, got $asm")
}
def apply(xOnlyPubKey: XOnlyPubKey): TaprootScriptPubKey = {
@ -1372,7 +1381,7 @@ object TaprootScriptPubKey extends ScriptFactory[TaprootScriptPubKey] {
fromPubKey(schnorrPublicKey.toXOnly)
}
def isValidAsm(asm: Seq[ScriptToken]): Boolean = {
override def isValidAsm(asm: Seq[ScriptToken]): Boolean = {
val asmBytes = BytesUtil.toByteVector(asm)
asm.length == 3 &&
asm.headOption.contains(OP_1) &&

View file

@ -242,7 +242,7 @@ object P2SHScriptSignature extends ScriptFactory[P2SHScriptSignature] {
constructor(asm)
}
def fromAsm(asm: Seq[ScriptToken]): P2SHScriptSignature = {
override def fromAsm(asm: Seq[ScriptToken]): P2SHScriptSignature = {
buildScript(asm = asm.toVector,
constructor = P2SHScriptSignatureImpl(_),
errorMsg =
@ -251,7 +251,7 @@ object P2SHScriptSignature extends ScriptFactory[P2SHScriptSignature] {
/** Tests if the given asm tokens are a [[P2SHScriptSignature]] */
override def isValidAsm(asm: Seq[ScriptToken]): Boolean = {
//as noted above, techinically _anything_ can be a p2sh scriptsig
//as noted above, technically _anything_ can be a p2sh scriptsig
//this applies basic checks to see if it's a standard redeemScript
//rather than a non standard redeeScript.
@ -610,7 +610,7 @@ object ScriptSignature extends ScriptFactory[ScriptSignature] {
def empty: ScriptSignature = EmptyScriptSignature
/** Creates a scriptSignature from the list of script tokens */
def fromAsm(tokens: Seq[ScriptToken]): ScriptSignature =
override def fromAsm(tokens: Seq[ScriptToken]): ScriptSignature =
tokens match {
case Nil => EmptyScriptSignature
case _ if TrivialTrueScriptSignature.isValid(tokens) =>

View file

@ -190,7 +190,6 @@ object ScriptWitness extends Factory[ScriptWitness] {
P2WPKHWitnessV0(pubKey)
} else if (TaprootScriptPath.isValid(stack.toVector)) {
TaprootScriptPath.fromStack(stack.toVector)
} else if (isPubKey && stack.size == 2) {
val pubKey = ECPublicKeyBytes(stack.head)
val sig = ECDigitalSignature(stack(1))
@ -352,7 +351,7 @@ case class TaprootScriptPath(stack: Vector[ByteVector]) extends TaprootWitness {
* The annex (or the lack of thereof) is always covered by the signature and contributes to transaction weight,
* but is otherwise ignored during taproot validation.
*/
def annexOpt: Option[ByteVector] = {
override def annexOpt: Option[ByteVector] = {
if (TaprootScriptPath.hasAnnex(stack)) {
Some(stack.head)
} else {

View file

@ -1,11 +1,19 @@
package org.bitcoins.core.script
import org.bitcoins.core.crypto._
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.script.{
TaprootKeyPath,
TaprootScriptPath,
TaprootUnknownPath,
TaprootWitness
}
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.control.{OP_ELSE, OP_ENDIF, OP_IF, OP_NOTIF}
import org.bitcoins.core.script.flag.ScriptFlag
import org.bitcoins.core.script.result._
import org.bitcoins.core.util.BitcoinScriptUtil
import org.bitcoins.crypto.Sha256Digest
/** Created by chris on 2/3/16.
*/
@ -51,6 +59,42 @@ sealed trait ScriptProgram {
* @return the ExecutedScriptProgram with the given error set inside of the trait
*/
def failExecution(error: ScriptError): ExecutedScriptProgram
private def getTaprootWitness: Option[TaprootWitness] = {
txSignatureComponent match {
case taprootTxSigComponent: TaprootTxSigComponent =>
taprootTxSigComponent.witness match {
case sp: TaprootWitness =>
Some(sp)
}
case _: BaseTxSigComponent | _: WitnessTxSigComponentRebuilt |
_: WitnessTxSigComponentP2SH | _: WitnessTxSigComponentRaw =>
None
}
}
private def getTapscriptOpt: Option[TaprootScriptPath] = {
getTaprootWitness.flatMap {
case sp: TaprootScriptPath => Some(sp)
case _: TaprootKeyPath => None
case _: TaprootUnknownPath => None
}
}
/** Calculates the leaf hash if we have a tapscript, else returns None if we don't have a tapscript */
def tapLeafHashOpt: Option[Sha256Digest] = {
getTapscriptOpt.map { sp =>
val hash = TaprootScriptPath.computeTapleafHash(
TaprootScriptPath.TAPROOT_LEAF_TAPSCRIPT,
sp.script)
hash
}
}
def getAnnexHashOpt: Option[Sha256Digest] = {
getTaprootWitness.flatMap(_.annexHashOpt)
}
}
/** This represents a [[org.bitcoins.core.script.ScriptProgram ScriptProgram]]
@ -128,7 +172,11 @@ object PreExecutionScriptProgram {
/** This represents any ScriptProgram that is not PreExecution */
sealed trait StartedScriptProgram extends ScriptProgram {
/** The index of the last code separator WITH push operations in the original script */
def lastCodeSeparator: Option[Int]
def taprootSerializationOptions: TaprootSerializationOptions
}
/** Implements the counting required for O(1) handling of conditionals in Bitcoin Script.
@ -343,6 +391,12 @@ case class ExecutionInProgressScriptProgram(
newIdx: Int): ExecutionInProgressScriptProgram = {
this.copy(codeSeparatorTapscriptIdx = Some(newIdx))
}
def taprootSerializationOptions: TaprootSerializationOptions = {
TaprootSerializationOptions(tapLeafHashOpt,
getAnnexHashOpt,
codeSeparatorTapscriptIdx.map(UInt32(_)))
}
}
/** Type for a [[org.bitcoins.core.script.ScriptProgram ScriptProgram]] that has been
@ -364,6 +418,12 @@ case class ExecutedScriptProgram(
error: Option[ScriptError])
extends StartedScriptProgram {
def taprootSerializationOptions: TaprootSerializationOptions = {
TaprootSerializationOptions(tapLeafHashOpt,
getAnnexHashOpt,
codeSeparatorTapscriptIdx.map(UInt32(_)))
}
override def failExecution(error: ScriptError): ExecutedScriptProgram = {
this.copy(error = Some(error))
}

View file

@ -79,7 +79,6 @@ sealed abstract class ControlOperationsInterpreter {
program: ExecutionInProgressScriptProgram): StartedScriptProgram = {
require(program.script.headOption.contains(OP_ELSE),
"First script opt must be OP_ELSE")
program.updateScript(program.script.tail).invertCondition()
}

View file

@ -2,6 +2,12 @@ package org.bitcoins.core.script.crypto
import org.bitcoins.core.consensus.Consensus
import org.bitcoins.core.crypto._
import org.bitcoins.core.protocol.script.{
SigVersionBase,
SigVersionTaprootKeySpend,
SigVersionTapscript,
SigVersionWitnessV0
}
import org.bitcoins.core.script._
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.control.{
@ -11,12 +17,7 @@ import org.bitcoins.core.script.control.{
import org.bitcoins.core.script.flag.ScriptFlagUtil
import org.bitcoins.core.script.result._
import org.bitcoins.core.util.BitcoinScriptUtil
import org.bitcoins.crypto.{
CryptoUtil,
ECDigitalSignature,
ECPublicKeyBytes,
HashDigest
}
import org.bitcoins.crypto._
import scodec.bits.ByteVector
/** Created by chris on 1/6/16.
@ -77,19 +78,171 @@ sealed abstract class CryptoInterpreter {
if (program.stack.size < 2) {
program.failExecution(ScriptErrorInvalidStackOperation)
} else {
val pubKey = ECPublicKeyBytes(program.stack.head.bytes)
val signature = ECDigitalSignature(program.stack.tail.head.bytes)
val flags = program.flags
val restOfStack = program.stack.tail.tail
val removedOpCodeSeparatorsScript =
BitcoinScriptUtil.removeOpCodeSeparator(program)
val result = TransactionSignatureChecker.checkSignature(
program.txSignatureComponent,
removedOpCodeSeparatorsScript,
pubKey,
signature,
flags)
handleSignatureValidation(program, result, restOfStack)
program.txSignatureComponent.sigVersion match {
case SigVersionWitnessV0 | SigVersionWitnessV0 | SigVersionBase =>
val pubKey = ECPublicKeyBytes(program.stack.head.bytes)
val signature = ECDigitalSignature(program.stack.tail.head.bytes)
val removedOpCodeSeparatorsScript =
BitcoinScriptUtil.removeOpCodeSeparator(program)
val result = TransactionSignatureChecker.checkSignature(
program.txSignatureComponent,
removedOpCodeSeparatorsScript,
pubKey,
signature,
flags)
handleSignatureValidation(program = program,
result = result,
restOfStack = restOfStack,
numOpt = None)
case SigVersionTapscript =>
val tapscriptE: Either[
ScriptError,
TransactionSignatureCheckerResult] = evalChecksigTapscript(program)
tapscriptE match {
case Left(err) =>
if (
err == ScriptErrorDiscourageUpgradablePubkeyType && !ScriptFlagUtil
.discourageUpgradablePublicKey(flags)
) {
//trivially pass signature validation as required by BIP342
//when the public key type is not known and the
//SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE is NOT set
handleSignatureValidation(program = program,
result = SignatureValidationSuccess,
restOfStack = restOfStack,
numOpt = None)
} else if (err == ScriptErrorEvalFalse) {
//means signature validation failed, don't increment the stack counter
handleSignatureValidation(
program = program,
result = SignatureValidationErrorIncorrectSignatures,
restOfStack = restOfStack,
numOpt = None)
} else {
program.failExecution(err)
}
case Right(result) =>
handleSignatureValidation(program = program,
result = result,
restOfStack = restOfStack,
numOpt = None)
}
case SigVersionTaprootKeySpend =>
sys.error(s"Cannot use taproot keyspend with OP_CHECKSIG")
}
}
}
/** Gets the signature and hash type, returns None
* if the signature is the empty byte vector which trivially
* fails script interpreter validation
*/
private def getSignatureAndHashType(
stack: List[ScriptToken],
isCheckSigAdd: Boolean): Option[(SchnorrDigitalSignature, HashType)] = {
val sigBytes = {
if (isCheckSigAdd) {
stack(2).bytes
} else {
stack.tail.head.bytes
}
}
val sigHashTypeOpt: Option[(SchnorrDigitalSignature, HashType)] = {
if (sigBytes.length == 64) {
val sig = SchnorrDigitalSignature.fromBytes(sigBytes)
Some((sig, HashType.sigHashDefault))
} else if (sigBytes.length == 65) {
val hashTypeByte = sigBytes.last
val hashType = HashType.fromByte(hashTypeByte)
val sig = SchnorrDigitalSignature.fromBytes(sigBytes.dropRight(1))
Some((sig, hashType))
} else if (sigBytes.isEmpty) {
None
} else {
sys.error(
s"Incorrect length for schnorr digital signature, got=${sigBytes.length}, expected 64 or 65 sigBytes=${sigBytes}")
}
}
sigHashTypeOpt
}
private def evalChecksigTapscript(
program: ExecutionInProgressScriptProgram): Either[
ScriptError,
TransactionSignatureCheckerResult] = {
val stack = program.stack
val pubKeyBytes = stack.head.bytes
val isCheckSigAdd = program.script.head == OP_CHECKSIGADD
val sigBytes = {
if (isCheckSigAdd) {
stack(2).bytes
} else {
stack(1).bytes
}
}
val xOnlyPubKeyT = XOnlyPubKey.fromBytesT(pubKeyBytes)
val discourageUpgradablePubKey =
ScriptFlagUtil.discourageUpgradablePublicKey(program.flags)
//need to do weight validation
if (pubKeyBytes.isEmpty) {
//this failure catches two types of errors, if the pubkey is empty
//and if its using an "upgraded" pubkey from a future soft fork
//from bip342:
//If the public key size is not zero and not 32 bytes, the public key is of an unknown public key type[6] and no actual signature verification is applied.
//During script execution of signature opcodes they behave exactly as known public key types except that signature validation is considered to be successful.
//see: https://github.com/bitcoin/bitcoin/blob/9e4fbebcc8e497016563e46de4c64fa094edab2d/src/script/interpreter.cpp#L374
Left(ScriptErrorPubKeyType)
} else if (sigBytes.isEmpty) {
//fail if we don't have a signature
Left(ScriptErrorEvalFalse)
} else if (discourageUpgradablePubKey && xOnlyPubKeyT.isFailure) {
Left(ScriptErrorDiscourageUpgradablePubkeyType)
} else if (!discourageUpgradablePubKey && pubKeyBytes.length != 32) {
// if the public key is not valid, and we aren't discouraging upgradable public keys
//the script trivially succeeds so that we maintain soft fork compatability for
//new public key types in the feature
Right(SignatureValidationSuccess)
} else if (xOnlyPubKeyT.isFailure) {
//how can this key be a failure if its 32 bytes in size?
sys.error(s"Invalid pubkey with 32 bytes in size, got=${xOnlyPubKeyT}")
} else {
val helperE: Either[ScriptError, TapscriptChecksigHelper] = {
val sigHashTypeOpt = getSignatureAndHashType(stack, isCheckSigAdd)
sigHashTypeOpt match {
case Some((signature, hashType)) =>
val restOfStack =
program.stack.tail.tail //remove pubkey, signature
val helper = TapscriptChecksigHelper(pubKey = xOnlyPubKeyT.get,
signature = signature,
hashType = hashType,
restOfStack = restOfStack)
Right(helper)
case None =>
//this is because the signature was empty
Left(ScriptErrorEvalFalse)
}
}
helperE match {
case Right(helper) =>
val result = TransactionSignatureChecker.checkSigTapscript(
txSignatureComponent = program.txSignatureComponent,
pubKey = helper.pubKey.schnorrPublicKey,
signature = helper.signature,
hashType = helper.hashType,
taprootOptions = program.taprootSerializationOptions,
flags = program.flags
)
Right(result)
case Left(err) => Left(err)
}
}
}
@ -232,7 +385,10 @@ sealed abstract class CryptoInterpreter {
//remove the extra OP_0 (null dummy) for OP_CHECKMULTISIG from the stack
val restOfStack = stackWithoutPubKeysAndSignatures.tail
handleSignatureValidation(program, isValidSignatures, restOfStack)
handleSignatureValidation(program = program,
result = isValidSignatures,
restOfStack = restOfStack,
numOpt = None)
}
}
}
@ -261,6 +417,63 @@ sealed abstract class CryptoInterpreter {
}
}
def opCheckSigAdd(
program: ExecutionInProgressScriptProgram): StartedScriptProgram = {
require(
program.script.headOption.contains(OP_CHECKSIGADD),
s"Script top must be OP_CHECKSIGADD, got=${program.script.headOption}")
program.txSignatureComponent.sigVersion match {
case SigVersionBase | SigVersionWitnessV0 =>
program.failExecution(ScriptErrorBadOpCode)
case SigVersionTapscript | SigVersionTaprootKeySpend =>
if (program.stack.length < 3) {
program.failExecution(ScriptErrorInvalidStackOperation)
} else {
val flags = program.flags
val requireMinimal =
ScriptFlagUtil.requireMinimalData(program.flags)
val numT = ScriptNumber(program.stack(1).bytes, requireMinimal)
if (numT.isFailure) {
throw numT.failed.get
}
val restOfStack =
program.stack.tail.tail.tail //remove signature, num, pubkey
val tapscriptE = evalChecksigTapscript(program)
tapscriptE match {
case Left(err) =>
if (
err == ScriptErrorDiscourageUpgradablePubkeyType && !ScriptFlagUtil
.discourageUpgradablePublicKey(flags)
) {
//trivially pass signature validation as required by BIP342
//when the public key type is not known and the
//SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE is NOT set
handleSignatureValidation(program = program,
result = SignatureValidationSuccess,
restOfStack = restOfStack,
numOpt = Some(numT.get))
} else if (err == ScriptErrorEvalFalse) {
//means signature validation failed, don't increment the stack counter
handleSignatureValidation(
program = program,
result = SignatureValidationErrorIncorrectSignatures,
restOfStack = restOfStack,
numOpt = Some(numT.get))
} else {
program.failExecution(err)
}
case Right(result) =>
handleSignatureValidation(program = program,
result = result,
restOfStack = restOfStack,
numOpt = Some(numT.get))
}
}
}
}
/** This is a higher order function designed to execute a hash function on the stack top of the program
* For instance, we could pass in CryptoUtil.sha256 function as the `hashFunction` argument, which would then
* apply sha256 to the stack top
@ -281,17 +494,43 @@ sealed abstract class CryptoInterpreter {
}
}
/** Pushes the correct data onto the stack after signature validation
* Pre-tapscript this meant just pushing OP_TRUE/OP_FALSE onto the stack
* With tapscript and OP_CHECKSIGADD we increment the counter and
* push it back onto the stack in case there are more signature operations
* @param program
* @param result
* @param restOfStack
* @param numOpt
* @return
*/
private def handleSignatureValidation(
program: ExecutionInProgressScriptProgram,
result: TransactionSignatureCheckerResult,
restOfStack: Seq[ScriptToken]): StartedScriptProgram =
restOfStack: Seq[ScriptToken],
numOpt: Option[ScriptNumber]): StartedScriptProgram = {
val pushOp = {
numOpt match {
case Some(num) =>
if (result.isValid) {
num.+(ScriptNumber.one)
} else {
num
}
case None =>
if (result.isValid) {
OP_TRUE
} else {
OP_FALSE
}
}
}
result match {
case SignatureValidationSuccess =>
//means that all of the signatures were correctly encoded and
//that all of the signatures were valid signatures for the given
//public keys
program.updateStackAndScript(OP_TRUE +: restOfStack,
program.script.tail)
program.updateStackAndScript(pushOp +: restOfStack, program.script.tail)
case SignatureValidationErrorNotStrictDerEncoding =>
//this means the script fails immediately
//set the valid flag to false on the script
@ -301,8 +540,7 @@ sealed abstract class CryptoInterpreter {
case SignatureValidationErrorIncorrectSignatures =>
//this means that signature verification failed, however all signatures were encoded correctly
//just push a OP_FALSE onto the stack
program.updateStackAndScript(OP_FALSE +: restOfStack,
program.script.tail)
program.updateStackAndScript(pushOp +: restOfStack, program.script.tail)
case SignatureValidationErrorSignatureCount =>
//means that we did not have enough signatures for OP_CHECKMULTISIG
program.failExecution(ScriptErrorInvalidStackOperation)
@ -318,6 +556,13 @@ sealed abstract class CryptoInterpreter {
case SignatureValidationErrorNullFail =>
program.failExecution(ScriptErrorSigNullFail)
}
}
}
object CryptoInterpreter extends CryptoInterpreter
case class TapscriptChecksigHelper(
pubKey: XOnlyPubKey,
signature: SchnorrDigitalSignature,
hashType: HashType,
restOfStack: List[ScriptToken])

View file

@ -76,17 +76,27 @@ case object OP_CHECKMULTISIGVERIFY extends CryptoSignatureEvaluation {
override val opCode: Int = 175
}
/** Opcode added in taproot soft fork
* @see https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#rules-for-signature-opcodes
*/
case object OP_CHECKSIGADD extends CryptoSignatureEvaluation {
override val opCode: Int = 186
}
object CryptoOperation extends ScriptOperationFactory[CryptoOperation] {
override val operations =
Vector(OP_CHECKMULTISIG,
OP_CHECKMULTISIGVERIFY,
OP_CHECKSIG,
OP_CHECKSIGVERIFY,
OP_CODESEPARATOR,
OP_HASH160,
OP_HASH256,
OP_RIPEMD160,
OP_SHA1,
OP_SHA256)
Vector(
OP_CHECKMULTISIG,
OP_CHECKMULTISIGVERIFY,
OP_CHECKSIG,
OP_CHECKSIGVERIFY,
OP_CHECKSIGADD,
OP_CODESEPARATOR,
OP_HASH160,
OP_HASH256,
OP_RIPEMD160,
OP_SHA1,
OP_SHA256
)
}

View file

@ -12,7 +12,8 @@ trait CryptoSignatureEvaluationFactory
Vector(OP_CHECKMULTISIG,
OP_CHECKMULTISIGVERIFY,
OP_CHECKSIG,
OP_CHECKSIGVERIFY)
OP_CHECKSIGVERIFY,
OP_CHECKSIGADD)
}

View file

@ -11,8 +11,8 @@ trait ScriptFlagFactory extends StringFactory[ScriptFlag] {
/** All the [[ScriptFlag]]s found inside of bitcoin core
* https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.h#L31.
*/
private def flags =
Seq(
private val flags: Vector[ScriptFlag] =
Vector(
ScriptVerifyNone,
ScriptVerifyP2SH,
ScriptVerifyStrictEnc,
@ -29,7 +29,11 @@ trait ScriptFlagFactory extends StringFactory[ScriptFlag] {
ScriptVerifyDiscourageUpgradableWitnessProgram,
ScriptVerifyMinimalIf,
ScriptVerifyNullFail,
ScriptVerifyWitnessPubKeyType
ScriptVerifyWitnessPubKeyType,
ScriptVerifyTaproot,
ScriptVerifyDiscourageUpgradableTaprootVersion,
ScriptVerifyDiscourageOpSuccess,
ScriptVerifyDiscourageUpgradablePubKeyType
)
/** Takes in a string and tries to match it with a [[ScriptFlag]]. */

View file

@ -103,6 +103,18 @@ trait ScriptFlagUtil {
*/
def minimalIfEnabled(flags: Seq[ScriptFlag]): Boolean =
flags.contains(ScriptVerifyMinimalIf)
def taprootEnabled(flags: Seq[ScriptFlag]): Boolean = {
flags.contains(ScriptVerifyTaproot)
}
def discourageUpgradablePublicKey(flags: Seq[ScriptFlag]): Boolean = {
flags.contains(ScriptVerifyDiscourageUpgradablePubKeyType)
}
def discourageOpSuccess(flags: Seq[ScriptFlag]): Boolean = {
flags.contains(ScriptVerifyDiscourageOpSuccess)
}
}
object ScriptFlagUtil extends ScriptFlagUtil

View file

@ -2,7 +2,7 @@ package org.bitcoins.core.script.flag
/** Created by chris on 3/23/16.
* This represents all of the script flags found inside of
* https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.h#L31
* https://github.com/bitcoin/bitcoin/blob/986003aff93c099c400c9285b4a2ed63f4b3f180/src/script/interpreter.h#L42
* these flags indicate how to evaluate a certain script
*/
sealed trait ScriptFlag {
@ -136,3 +136,53 @@ case object ScriptVerifyWitnessPubKeyType extends ScriptFlag {
override def flag = 1 << 15
override def name = "WITNESS_PUBKEYTYPE"
}
case object ScriptVerifyConstScriptCode extends ScriptFlag {
override val flag: Int = 1 << 16
override val name = "CONST_SCRIPTCODE" //not sure if this is right
}
case object ScriptVerifyTaproot extends ScriptFlag {
override val flag: Int = 1 << 17
override val name: String = "TAPROOT"
}
// Making unknown Taproot leaf versions non-standard
//
//SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION = (1U << 18),
case object ScriptVerifyDiscourageUpgradableTaprootVersion extends ScriptFlag {
override val flag: Int = 1 << 18
override val name: String = "DISCOURAGE_UPGRADABLE_TAPROOT_VERSION"
}
// Making unknown OP_SUCCESS non-standard
//SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS = (1U << 19),
case object ScriptVerifyDiscourageOpSuccess extends ScriptFlag {
override val flag: Int = 1 << 18
override val name: String = "DISCOURAGE_OP_SUCCESS"
}
// Making unknown public key versions (in BIP 342 scripts) non-standard
//SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE = (1U << 20),
case object ScriptVerifyDiscourageUpgradablePubKeyType extends ScriptFlag {
override val flag: Int = 1 << 19
override val name: String = "DISCOURAGE_UPGRADABLE_PUBKEYTYPE"
}
object ScriptFlag {
//what is this?
//https://github.com/bitcoin/bitcoin/blob/3820090bd619ac85ab35eff376c03136fe4a9f04/src/test/script_tests.cpp#L1659
val allConsensusFlags = Vector(
ScriptVerifyP2SH,
ScriptVerifyDerSig,
ScriptVerifyNullDummy,
ScriptVerifyCheckLocktimeVerify,
ScriptVerifyCheckSequenceVerify,
ScriptVerifyWitness,
ScriptVerifyTaproot,
ScriptVerifyDiscourageUpgradableTaprootVersion,
ScriptVerifyDiscourageOpSuccess,
ScriptVerifyDiscourageUpgradablePubKeyType
)
}

View file

@ -11,7 +11,7 @@ import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.script._
import org.bitcoins.core.script.arithmetic._
import org.bitcoins.core.script.bitwise._
import org.bitcoins.core.script.constant.{ScriptToken, _}
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.control._
import org.bitcoins.core.script.crypto._
import org.bitcoins.core.script.flag._
@ -26,6 +26,7 @@ import org.bitcoins.core.script.splice._
import org.bitcoins.core.script.stack._
import org.bitcoins.core.script.util.PreviousOutputMap
import org.bitcoins.core.util._
import org.bitcoins.crypto.SchnorrPublicKey
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}
@ -97,7 +98,8 @@ sealed abstract class ScriptInterpreter {
}
}
evaluateExecutedScriptProgram(program, executedProgram)
val result = evaluateExecutedScriptProgram(program, executedProgram)
result
}
private def programFlagsViolated(
@ -196,6 +198,30 @@ sealed abstract class ScriptInterpreter {
}
}
/** Helper function to actually run a p2sh script */
private def runP2SH(
p: ExecutedScriptProgram,
s: ScriptPubKey): ExecutedScriptProgram = {
val p2shRedeemScriptProgram = PreExecutionScriptProgram(
txSignatureComponent = p.txSignatureComponent,
stack = p.stack.tail,
script = s.asm.toList,
originalScript = p.originalScript,
altStack = Nil,
flags = p.flags
)
/*ScriptProgram(p.txSignatureComponent, stack.tail,s.asm)*/
if (
ScriptFlagUtil.requirePushOnly(
p2shRedeemScriptProgram.flags) && !BitcoinScriptUtil
.isPushOnly(s.asm)
) {
p2shRedeemScriptProgram.failExecution(ScriptErrorSigPushOnly)
} else executeProgram(p2shRedeemScriptProgram)
}
/** P2SH scripts are unique in their evaluation, first the scriptSignature must be added to the stack, next the
* p2sh scriptPubKey must be run to make sure the serialized redeem script hashes to the value found in the p2sh
* scriptPubKey, then finally the serialized redeemScript is decoded and run with the arguments in the p2sh script signature
@ -210,30 +236,6 @@ sealed abstract class ScriptInterpreter {
val segwitEnabled = ScriptFlagUtil.segWitEnabled(flags)
/** Helper function to actually run a p2sh script */
def run(
p: ExecutedScriptProgram,
s: ScriptPubKey): ExecutedScriptProgram = {
val p2shRedeemScriptProgram = PreExecutionScriptProgram(
txSignatureComponent = p.txSignatureComponent,
stack = p.stack.tail,
script = s.asm.toList,
originalScript = p.originalScript,
altStack = Nil,
flags = p.flags
)
/*ScriptProgram(p.txSignatureComponent, stack.tail,s.asm)*/
if (
ScriptFlagUtil.requirePushOnly(
p2shRedeemScriptProgram.flags) && !BitcoinScriptUtil
.isPushOnly(s.asm)
) {
p2shRedeemScriptProgram.failExecution(ScriptErrorSigPushOnly)
} else executeProgram(p2shRedeemScriptProgram)
}
val scriptSig =
scriptPubKeyExecutedProgram.txSignatureComponent.scriptSignature
val scriptSigAsm: Seq[ScriptToken] = scriptSig.asm
@ -282,7 +284,7 @@ sealed abstract class ScriptInterpreter {
ScriptErrorWitnessMalleatedP2SH)
} else {
//segwit not enabled, treat as old spk
run(scriptPubKeyExecutedProgram, p2wpkh)
runP2SH(scriptPubKeyExecutedProgram, p2wpkh)
}
case p2wsh: P2WSHWitnessSPKV0 =>
@ -306,24 +308,30 @@ sealed abstract class ScriptInterpreter {
ScriptErrorWitnessMalleatedP2SH)
} else {
//treat the segwit scriptpubkey as any other redeem script
run(scriptPubKeyExecutedProgram, p2wsh)
runP2SH(scriptPubKeyExecutedProgram, p2wsh)
}
case _: TaprootScriptPubKey =>
val hasUpgradeableFlag =
flags.exists(_ == ScriptVerifyDiscourageUpgradableWitnessProgram)
if (hasUpgradeableFlag) {
scriptPubKeyExecutedProgram.failExecution(
ScriptErrorDiscourageUpgradeableWitnessProgram)
} else {
//trivially passes
scriptPubKeyExecutedProgram
}
case spk: TaprootScriptPubKey =>
throw new IllegalArgumentException(
s"Taproot not yet supported: $spk")
case s @ (_: P2SHScriptPubKey | _: P2PKHScriptPubKey |
_: P2PKWithTimeoutScriptPubKey | _: P2PKScriptPubKey |
_: MultiSignatureScriptPubKey | _: CLTVScriptPubKey |
_: CSVScriptPubKey | _: ConditionalScriptPubKey |
_: NonStandardScriptPubKey | _: WitnessCommitment |
_: UnassignedWitnessScriptPubKey | EmptyScriptPubKey) =>
run(scriptPubKeyExecutedProgram, s)
runP2SH(scriptPubKeyExecutedProgram, s)
}
} else {
scriptPubKeyExecutedProgram
}
}
}
/** Runs a segwit script through our interpreter, mimics this functionality in bitcoin core:
@ -338,22 +346,17 @@ sealed abstract class ScriptInterpreter {
case w: WitnessTxSigComponent =>
val scriptSig =
scriptPubKeyExecutedProgram.txSignatureComponent.scriptSignature
val witnessVersion = witnessScriptPubKey.witnessVersion
val witness = w.witness
//scriptsig must be empty if we have raw p2wsh
//if script pubkey is a P2SHScriptPubKey then we have P2SH(P2WSH)
(scriptSig, w.scriptPubKey) match {
case (EmptyScriptSignature, _) | (_, _: P2SHScriptPubKey) =>
if (witness.stack.exists(_.size > MAX_PUSH_SIZE)) {
Success(
scriptPubKeyExecutedProgram.failExecution(ScriptErrorPushSize)
)
} else {
verifyWitnessProgram(witnessVersion,
witness,
witnessScriptPubKey,
w)
}
verifyWitnessProgram(
scriptWitness = witness,
witnessSPK = witnessScriptPubKey,
wTxSigComponent = w,
scriptPubKeyExecutedProgram = scriptPubKeyExecutedProgram
)
case (_, _) =>
Success(
scriptPubKeyExecutedProgram.failExecution(
@ -367,12 +370,17 @@ sealed abstract class ScriptInterpreter {
case (EmptyScriptSignature, _) | (_, _: P2SHScriptPubKey) =>
witnessScriptPubKey.witnessVersion match {
case WitnessVersion0 =>
Success(
scriptPubKeyExecutedProgram.failExecution(
ScriptErrorWitnessProgramWitnessEmpty))
val f = scriptPubKeyExecutedProgram
.failExecution(ScriptErrorWitnessProgramWitnessEmpty)
Success(f)
case WitnessVersion1 =>
throw new IllegalArgumentException(
"Taproot is not yet supported")
if (ScriptFlagUtil.taprootEnabled(b.flags)) {
val f = scriptPubKeyExecutedProgram
.failExecution(ScriptErrorWitnessProgramWitnessEmpty)
Success(f)
} else {
evaluateUnassignedWitness(b)
}
case UnassignedWitness(_) =>
evaluateUnassignedWitness(b)
}
@ -387,14 +395,64 @@ sealed abstract class ScriptInterpreter {
}
}
/** Helper function to run the post segwit execution checks */
private def postSegWitProgramChecks(
evaluated: ExecutedScriptProgram): ExecutedScriptProgram = {
if (evaluated.error.isDefined) evaluated
else if (evaluated.stack.size != 1) {
// Scripts inside witness implicitly require cleanstack behaviour
//https://github.com/bitcoin/bitcoin/blob/561a7d30478b82f5d46dcf0f16e864a9608004f4/src/script/interpreter.cpp#L1464
evaluated.failExecution(ScriptErrorCleanStack)
} else if (evaluated.stackTopIsFalse)
evaluated.failExecution(ScriptErrorEvalFalse)
else evaluated
}
/** Rebuilds a [[WitnessVersion1]] witness program for execution in the script interpreter */
private def rebuildV1(
witness: TaprootWitness,
witnessSPK: WitnessScriptPubKey): Either[
ScriptError,
(Seq[ScriptToken], ScriptPubKey)] = {
require(witnessSPK.isInstanceOf[TaprootScriptPubKey],
s"WitnessScriptPubKey must be a taproot spk, got=${witnessSPK}")
val taprootSPK = witnessSPK.asInstanceOf[TaprootScriptPubKey]
val program = witnessSPK.witnessProgram
val programBytes = BytesUtil.toByteVector(program)
programBytes.size match {
case 32 =>
//valid p2tr
if (witness.stack.isEmpty) {
Left(ScriptErrorWitnessProgramWitnessEmpty)
} else {
val rebuiltE = WitnessVersion1.rebuild(witness, taprootSPK)
rebuiltE match {
case Right(rebuilt) =>
val constants = witness.stack.map(ScriptConstant(_))
witness match {
case _: TaprootKeyPath | _: TaprootScriptPath |
_: TaprootUnknownPath =>
Right((constants, rebuilt))
}
case Left(err) => Left(err)
}
}
case _ =>
//witness version 1 progarms need to be 32 bytes in size
Left(ScriptErrorWitnessProgramWrongLength)
}
}
/** Verifies a segregated witness program by running it through the interpreter
* [[https://github.com/bitcoin/bitcoin/blob/f8528134fc188abc5c7175a19680206964a8fade/src/script/interpreter.cpp#L1302]]
*/
private def verifyWitnessProgram(
witnessVersion: WitnessVersion,
scriptWitness: ScriptWitness,
witnessSPK: WitnessScriptPubKey,
wTxSigComponent: WitnessTxSigComponent): Try[ExecutedScriptProgram] = {
wTxSigComponent: WitnessTxSigComponent,
scriptPubKeyExecutedProgram: ExecutedScriptProgram): Try[
ExecutedScriptProgram] = {
/** Helper function to run the post segwit execution checks */
def postSegWitProgramChecks(
@ -444,25 +502,32 @@ sealed abstract class ScriptInterpreter {
}
}
witnessVersion match {
case WitnessVersion0 =>
witnessSPK match {
case _: WitnessScriptPubKeyV0 =>
val either: Either[ScriptError, (Seq[ScriptToken], ScriptPubKey)] =
rebuildV0(scriptWitness, witnessSPK)
either match {
case Right((stack, scriptPubKey)) =>
val newWTxSigComponent =
rebuildWTxSigComponent(wTxSigComponent, scriptPubKey)
val newProgram = newWTxSigComponent.map { comp =>
PreExecutionScriptProgram(txSignatureComponent = comp,
stack = stack.toList,
script = scriptPubKey.asm.toList,
originalScript =
scriptPubKey.asm.toList,
altStack = Nil,
flags = comp.flags)
if (stack.exists(_.bytes.length > MAX_PUSH_SIZE)) {
val fail =
scriptPubKeyExecutedProgram.failExecution(ScriptErrorPushSize)
Success(fail)
} else {
val newWTxSigComponent =
rebuildWTxSigComponent(wTxSigComponent, scriptPubKey)
val newProgram = newWTxSigComponent.map { comp =>
PreExecutionScriptProgram(txSignatureComponent = comp,
stack = stack.toList,
script = scriptPubKey.asm.toList,
originalScript =
scriptPubKey.asm.toList,
altStack = Nil,
flags = comp.flags)
}
val evaluated = newProgram.map(executeProgram)
evaluated.map(e => postSegWitProgramChecks(e))
}
val evaluated = newProgram.map(executeProgram)
evaluated.map(e => postSegWitProgramChecks(e))
case Left(err) =>
val program = ExecutedScriptProgram(
txSignatureComponent = wTxSigComponent,
@ -477,10 +542,198 @@ sealed abstract class ScriptInterpreter {
)
Success(program)
}
case WitnessVersion1 =>
evaluateUnassignedWitness(wTxSigComponent)
case UnassignedWitness(_) =>
evaluateUnassignedWitness(wTxSigComponent)
case trSPK: TaprootScriptPubKey =>
require(
scriptWitness.isInstanceOf[TaprootWitness],
s"witness must be taproot witness for witness version 1 script execution, got=$scriptWitness")
val taprootWitness = scriptWitness.asInstanceOf[TaprootWitness]
val either: Either[ScriptError, (Seq[ScriptToken], ScriptPubKey)] =
rebuildV1(taprootWitness, witnessSPK)
either match {
case Right((stack, scriptPubKey)) =>
executeTapscript(
taprootWitness = taprootWitness,
taprootSPK = trSPK,
rebuiltSPK = scriptPubKey,
stack = stack.toVector,
wTxSigComponent = wTxSigComponent,
scriptPubKeyExecutedProgram = scriptPubKeyExecutedProgram
)
case Left(err) =>
val program = ExecutedScriptProgram(
txSignatureComponent = wTxSigComponent,
stack = Nil,
script = Nil,
originalScript = Nil,
altStack = Nil,
flags = wTxSigComponent.flags,
lastCodeSeparator = None,
codeSeparatorTapscriptIdx = None,
error = Some(err)
)
Success(program)
}
case u: UnassignedWitnessScriptPubKey =>
if (u.asm.contains(OP_0)) {
//cannot have an v0 unassigned witness as according to BIP141
//a witness v0 script must be 20 bytes or 32 bytes
val program = ExecutedScriptProgram(
txSignatureComponent = wTxSigComponent,
stack = Nil,
script = Nil,
originalScript = Nil,
altStack = Nil,
flags = wTxSigComponent.flags,
lastCodeSeparator = None,
codeSeparatorTapscriptIdx = None,
error = Some(ScriptErrorWitnessProgramWrongLength)
)
Success(program)
} else {
evaluateUnassignedWitness(wTxSigComponent)
}
}
}
/** Checks if there is an opcode defined as OP_SUCCESSx in BIP342
* @see https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki#specification
*/
private def containsOpSuccess(asm: Vector[ScriptToken]): Boolean = {
val containsOPSuccess =
asm.exists(o =>
o.isInstanceOf[ReservedOperation] ||
ScriptInterpreter.bip341DisabledOpCodes.exists(_ == o))
containsOPSuccess
}
private def executeTapscript(
taprootWitness: TaprootWitness,
taprootSPK: TaprootScriptPubKey,
rebuiltSPK: ScriptPubKey,
stack: Vector[ScriptToken],
wTxSigComponent: WitnessTxSigComponent,
scriptPubKeyExecutedProgram: ExecutedScriptProgram): Try[
ExecutedScriptProgram] = {
val sigVersion = scriptPubKeyExecutedProgram.txSignatureComponent.sigVersion
if (ScriptFlagUtil.taprootEnabled(scriptPubKeyExecutedProgram.flags)) {
val containsOPSuccess = containsOpSuccess(rebuiltSPK.asm.toVector)
if (sigVersion == SigVersionTapscript && containsOPSuccess) {
handleOpSuccess(scriptPubKeyExecutedProgram)
} else {
taprootWitness match {
case keypath: TaprootKeyPath =>
val program = checkSchnorrSignature(
keypath,
taprootSPK.pubKey.schnorrPublicKey,
program = scriptPubKeyExecutedProgram)
Success(program)
case _: TaprootUnknownPath =>
//is this right? I believe to maintain softfork compatibility we
//just succeed?
Success(scriptPubKeyExecutedProgram)
case taprootScriptPath: TaprootScriptPath =>
require(
wTxSigComponent.isInstanceOf[TaprootTxSigComponent],
s"Must have taproot tx sig component to execute tapscript, got=${wTxSigComponent.getClass.getSimpleName}"
)
val taprootTxSigComponent =
wTxSigComponent.asInstanceOf[TaprootTxSigComponent]
val controlBlock = taprootScriptPath.controlBlock
val script = taprootScriptPath.script
//execdata.m_tapleaf_hash = ComputeTapleafHash(control[0] & TAPROOT_LEAF_MASK, exec_script);
val tapLeafHash = TaprootScriptPath.computeTapleafHash(
controlBlock.leafVersion,
script)
val isValidTaprootCommitment =
TaprootScriptPath.verifyTaprootCommitment(
controlBlock = controlBlock,
program = taprootSPK,
tapLeafHash = tapLeafHash)
if (!isValidTaprootCommitment) {
val p = scriptPubKeyExecutedProgram.failExecution(
ScriptErrorWitnessProgramMisMatch)
Success(p)
} else {
val isDiscouragedTaprootVersion =
scriptPubKeyExecutedProgram.flags.exists(
_ == ScriptVerifyDiscourageUpgradableTaprootVersion)
if (controlBlock.isTapLeafMask) {
//drop the control block & script in the witness
val stackNoControlBlockOrScript = {
if (scriptPubKeyExecutedProgram.getAnnexHashOpt.isDefined) {
//if we have an annex we need to drop
//annex,control block, script
stack.tail.tail.tail
} else {
//else just drop control block, script
stack.tail.tail
}
}
val newProgram = PreExecutionScriptProgram(
txSignatureComponent = taprootTxSigComponent,
stack = stackNoControlBlockOrScript.toList,
script = rebuiltSPK.asm.toList,
originalScript = rebuiltSPK.asm.toList,
altStack = Nil,
flags = taprootTxSigComponent.flags
)
val evaluated = executeProgram(newProgram)
val segwitChecks = postSegWitProgramChecks(evaluated)
Success(segwitChecks)
} else if (isDiscouragedTaprootVersion) {
val p = scriptPubKeyExecutedProgram.failExecution(
ScriptErrorDiscourageUpgradableTaprootVersion)
Success(p)
} else {
//is this right? I believe to maintain softfork compatibility we
//just succeed?
Success(scriptPubKeyExecutedProgram)
}
}
}
}
} else {
//if taproot flag not set trivially pass
Success(scriptPubKeyExecutedProgram)
}
}
private def handleOpSuccess(
scriptPubKeyExecutedProgram: ExecutedScriptProgram): Try[
ExecutedScriptProgram] = {
val discourageOpSuccess =
ScriptFlagUtil.discourageOpSuccess(scriptPubKeyExecutedProgram.flags)
val p = if (discourageOpSuccess) {
scriptPubKeyExecutedProgram.failExecution(ScriptErrorDiscourageOpSuccess)
} else {
scriptPubKeyExecutedProgram
}
Success(p)
}
/** Similar to the check schnorr signature method in bitcoin core
* @see https://github.com/bitcoin/bitcoin/blob/b71d37da2c8c8d2a9cef020731767a6929db54b4/src/script/interpreter.cpp#L1673
*/
private def checkSchnorrSignature(
witness: TaprootKeyPath,
pubKey: SchnorrPublicKey,
program: ExecutedScriptProgram): ExecutedScriptProgram = {
val scriptResult = TransactionSignatureChecker.checkSchnorrSignature(
program.txSignatureComponent,
pubKey,
witness,
program.taprootSerializationOptions)
scriptResult match {
case ScriptOk =>
// Set stack to OP_TRUE so we don't fail
// from empty stack
program.copy(stack = List(OP_TRUE))
case err: ScriptError => program.failExecution(err)
}
}
@ -488,7 +741,11 @@ sealed abstract class ScriptInterpreter {
private def executeProgram(
program: PreExecutionScriptProgram): ExecutedScriptProgram = {
val scriptByteVector = BytesUtil.toByteVector(program.script)
if (scriptByteVector.length > 10000) {
val sigVersion = program.txSignatureComponent.sigVersion
val isTaprootSigVersion =
sigVersion == SigVersionTapscript || sigVersion == SigVersionTaprootKeySpend
if (scriptByteVector.length > 10000 && !isTaprootSigVersion) {
program.failExecution(ScriptErrorScriptSize)
} else {
loop(program.toExecutionInProgress, 0)
@ -503,13 +760,31 @@ sealed abstract class ScriptInterpreter {
program: ExecutedScriptProgram): ExecutedScriptProgram = {
val countedOps = program.originalScript
.count(BitcoinScriptUtil.countsTowardsScriptOpLimit)
if (countedOps > MAX_SCRIPT_OPS && program.error.isEmpty) {
val sigVersion = program.txSignatureComponent.sigVersion
val isBaseOrSegwitV0 =
sigVersion == SigVersionBase || sigVersion == SigVersionWitnessV0
if (
isBaseOrSegwitV0 && countedOps > MAX_SCRIPT_OPS && program.error.isEmpty
) {
completeProgramExecution(program.failExecution(ScriptErrorOpCount))
} else {
program
}
}
private val disabledOpCodes = {
val arithmetic =
Seq(OP_MUL, OP_2MUL, OP_DIV, OP_2DIV, OP_MOD, OP_LSHIFT, OP_RSHIFT)
val bitwise = Seq(OP_INVERT, OP_AND, OP_OR, OP_XOR)
val splice = Seq(OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT)
arithmetic ++ bitwise ++ splice
}
private val badOpCodes = Vector(OP_VERIF, OP_VERNOTIF)
/** The execution loop for a script
*
* @param program the program whose script needs to be evaluated
@ -519,43 +794,25 @@ sealed abstract class ScriptInterpreter {
private def loop(
program: ExecutionInProgressScriptProgram,
opCount: Int): ExecutedScriptProgram = {
val scriptByteVector = BytesUtil.toByteVector(program.script)
if (opCount > MAX_SCRIPT_OPS) {
val sigVersion = program.txSignatureComponent.sigVersion
val isTaprootSigVersion =
sigVersion == SigVersionTapscript || sigVersion == SigVersionTaprootKeySpend
if (opCount > MAX_SCRIPT_OPS && !isTaprootSigVersion) {
completeProgramExecution(program.failExecution(ScriptErrorOpCount))
} else if (scriptByteVector.length > 10000) {
} else if (scriptByteVector.length > 10000 && !isTaprootSigVersion) {
completeProgramExecution(program.failExecution(ScriptErrorScriptSize))
} else {
val (nextProgram, nextOpCount) = program.script match {
//if at any time we see that the program is not valid
//cease script execution
case _
if program.script.intersect(Seq(OP_VERIF, OP_VERNOTIF)).nonEmpty =>
case _ if program.script.intersect(badOpCodes).nonEmpty =>
(program.failExecution(ScriptErrorBadOpCode), opCount)
//disabled splice operation
//disabled operations
case _
if program.script
.intersect(Seq(OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT))
.nonEmpty =>
(program.failExecution(ScriptErrorDisabledOpCode), opCount)
//disabled bitwise operations
case _
if program.script
.intersect(Seq(OP_INVERT, OP_AND, OP_OR, OP_XOR))
.nonEmpty =>
(program.failExecution(ScriptErrorDisabledOpCode), opCount)
//disabled arithmetic operations
case _
if program.script
.intersect(
Seq(OP_MUL,
OP_2MUL,
OP_DIV,
OP_2DIV,
OP_MOD,
OP_LSHIFT,
OP_RSHIFT))
.intersect(disabledOpCodes)
.nonEmpty =>
(program.failExecution(ScriptErrorDisabledOpCode), opCount)
//program cannot contain a push operation > 520 bytes
@ -934,6 +1191,11 @@ sealed abstract class ScriptInterpreter {
val newOpCount =
calcOpCount(opCount, OP_CHECKSIGVERIFY)
(programOrError, newOpCount)
case OP_CHECKSIGADD :: _ =>
val programOrError = CryptoInterpreter.opCheckSigAdd(program)
val newOpCount =
calcOpCount(opCount, OP_CHECKSIGVERIFY)
(programOrError, newOpCount)
case OP_SHA1 :: _ =>
val programOrError = CryptoInterpreter.opSha1(program)
@ -1227,4 +1489,26 @@ sealed abstract class ScriptInterpreter {
}
}
}
object ScriptInterpreter extends ScriptInterpreter
object ScriptInterpreter extends ScriptInterpreter {
val bip341DisabledOpCodes = {
Vector(OP_CAT,
OP_SUBSTR,
OP_LEFT,
OP_RIGHT,
OP_INVERT,
OP_AND,
OP_OR,
OP_XOR,
OP_RESERVED1,
OP_RESERVED2,
OP_2MUL,
OP_2DIV,
OP_MUL,
OP_DIV,
OP_MOD,
OP_LSHIFT,
OP_RSHIFT)
}
}

View file

@ -91,7 +91,7 @@ case object OP_NOP10 extends NOP {
case class UndefinedOP_NOP(opCode: Int) extends ReservedOperation
object ReservedOperation extends ScriptOperationFactory[ReservedOperation] {
lazy val undefinedOpCodes = for { i <- 0xba to 0xff } yield UndefinedOP_NOP(i)
lazy val undefinedOpCodes = for { i <- 0xbb to 0xff } yield UndefinedOP_NOP(i)
override val operations =
Vector(OP_RESERVED,

View file

@ -103,6 +103,14 @@ trait BitcoinScriptUtil {
case _: ScriptToken => false
}
def countOpCodes(opcodes: Vector[ScriptToken]): Int = {
opcodes.count(_.isInstanceOf[ScriptOperation])
}
def filterOpCodes(tokens: Vector[ScriptToken]): Vector[ScriptToken] = {
tokens.filter(_.isInstanceOf[ScriptOperation])
}
/** Counts the amount of sigops in a script.
* [[https://github.com/bitcoin/bitcoin/blob/master/src/script/script.cpp#L156-L202 Bitcoin Core script.cpp]]
* @param script the script whose sigops are being counted

View file

@ -294,7 +294,6 @@ object BitcoinSigner extends SignerUtils {
case Some(wit) => wtx.updateWitness(inputIndex, wit)
case None => wtx
}
case _: P2WPKHWitnessV0 | _: P2WSHWitnessV0 | _: TaprootWitness =>
wtx
}

View file

@ -39,4 +39,5 @@ class SchnorrPublicKeyTest extends BitcoinSCryptoTest {
assert(SchnorrPublicKey(pubKey.xCoord) == pubKey)
}
}
}

View file

@ -116,6 +116,8 @@ case class ECPublicKeyBytes(bytes: ByteVector)
/** Parse these bytes into the bitcoin-s internal public key type. */
def toPublicKey: ECPublicKey = ECPublicKey(bytes)
def toSchnorrPubKey: SchnorrPublicKey = SchnorrPublicKey.fromBytes(bytes)
override private[crypto] def fromBytes(bytes: ByteVector): this.type =
ECPublicKeyBytes(bytes).asInstanceOf[this.type]
}

View file

@ -9,6 +9,8 @@ case class SchnorrDigitalSignature(rx: SchnorrNonce, sig: FieldElement)
object SchnorrDigitalSignature extends Factory[SchnorrDigitalSignature] {
//If the sig is 65 bytes long, return sig[64] 0x00[20] and
// Verify(q, hashTapSighash(0x00 || SigMsg(sig[64], 0)), sig[0:64]).
override def fromBytes(bytes: ByteVector): SchnorrDigitalSignature = {
require(bytes.length == 64,
s"SchnorrDigitalSignature must be exactly 64 bytes, got $bytes")

View file

@ -1419,10 +1419,10 @@ abstract class DLCWallet
case EmptyScriptWitness =>
throw new RuntimeException(
"Script witness cannot be empty")
case taprootWitness: TaprootWitness =>
throw new UnsupportedOperationException(
s"Taproot not supported, got=$taprootWitness")
case witness: ScriptWitnessV0 => (input.outPoint, witness)
case _: TaprootWitness =>
throw new UnsupportedOperationException(
s"Taproot not implemented")
}
case None => throw new RuntimeException("")
}