mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-22 14:33:06 +01:00
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:
parent
000e7a7930
commit
211339f344
35 changed files with 3426 additions and 166 deletions
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"}
|
||||
]
|
2247
core-test/.jvm/src/test/resources/script_assets_test_cp.json
Normal file
2247
core-test/.jvm/src/test/resources/script_assets_test_cp.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -753,4 +753,5 @@ object JsonTestVectors {
|
|||
|["Make diffs cleaner by leaving a comment here without comma at the end"]
|
||||
|]
|
||||
|""".stripMargin
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ class CryptoSignatureEvaluationFactoryTest extends BitcoinSUnitTest {
|
|||
Seq(OP_CHECKMULTISIG,
|
||||
OP_CHECKMULTISIGVERIFY,
|
||||
OP_CHECKSIG,
|
||||
OP_CHECKSIGVERIFY))
|
||||
OP_CHECKSIGVERIFY,
|
||||
OP_CHECKSIGADD))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -39,7 +39,11 @@ sealed abstract class Policy {
|
|||
ScriptVerifyNullFail,
|
||||
ScriptVerifyNullDummy,
|
||||
ScriptVerifyWitnessPubKeyType,
|
||||
ScriptVerifyDiscourageUpgradableWitnessProgram
|
||||
ScriptVerifyDiscourageUpgradableWitnessProgram,
|
||||
ScriptVerifyTaproot,
|
||||
ScriptVerifyDiscourageUpgradableTaprootVersion,
|
||||
ScriptVerifyDiscourageOpSuccess,
|
||||
ScriptVerifyDiscourageUpgradablePubKeyType
|
||||
)
|
||||
|
||||
def standardFlags = standardScriptVerifyFlags
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@ trait CryptoSignatureEvaluationFactory
|
|||
Vector(OP_CHECKMULTISIG,
|
||||
OP_CHECKMULTISIGVERIFY,
|
||||
OP_CHECKSIG,
|
||||
OP_CHECKSIGVERIFY)
|
||||
OP_CHECKSIGVERIFY,
|
||||
OP_CHECKSIGADD)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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]]. */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -294,7 +294,6 @@ object BitcoinSigner extends SignerUtils {
|
|||
case Some(wit) => wtx.updateWitness(inputIndex, wit)
|
||||
case None => wtx
|
||||
}
|
||||
|
||||
case _: P2WPKHWitnessV0 | _: P2WSHWitnessV0 | _: TaprootWitness =>
|
||||
wtx
|
||||
}
|
||||
|
|
|
@ -39,4 +39,5 @@ class SchnorrPublicKeyTest extends BitcoinSCryptoTest {
|
|||
assert(SchnorrPublicKey(pubKey.xCoord) == pubKey)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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("")
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue