mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-25 07:17:32 +01:00
Refactoring control flow inside of ScriptInterpreter - loop() now has its own function and cleaned up flow for handling different types of scriptPubKeys
This commit is contained in:
parent
811d15af81
commit
48e22e0fd7
7 changed files with 327 additions and 266 deletions
|
@ -32,6 +32,13 @@ trait ScriptFlagUtil {
|
|||
flags.contains(ScriptVerifyCheckLocktimeVerify)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the p2sh flag is enabled
|
||||
* @param flags
|
||||
* @return
|
||||
*/
|
||||
def p2shEnabled(flags : Seq[ScriptFlag]) : Boolean = flags.contains(ScriptVerifyP2SH)
|
||||
|
||||
/**
|
||||
* Checks if the script flag for checksequenceverify is enabled
|
||||
* @param flags
|
||||
|
|
|
@ -34,258 +34,82 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
|
|||
*/
|
||||
private lazy val maxScriptOps = 201
|
||||
|
||||
//TODO: Think about this more to change this from being mutable to immutable - perhaps
|
||||
//passing this as an implicit argument to our loop function
|
||||
/**
|
||||
* The current operation count that a script has performed
|
||||
* This is limited by bitcoin core to be 201 operations
|
||||
*
|
||||
*/
|
||||
private var opCount = 0
|
||||
|
||||
/**
|
||||
* Runs an entire script though our script programming language and
|
||||
* returns true or false depending on if the script was valid
|
||||
*
|
||||
* @param program the program to be interpreted
|
||||
* @return
|
||||
*/
|
||||
def run(program : PreExecutionScriptProgram) : ScriptResult = {
|
||||
//TODO: Think about this more to change this from being mutable to immutable - perhaps
|
||||
//passing this as an implicit argument to our loop function
|
||||
/**
|
||||
* The current operation count that a script has performed
|
||||
* This is limited by bitcoin core to be 201 operations
|
||||
*
|
||||
*/
|
||||
var opCount = 0
|
||||
/**
|
||||
*
|
||||
* @param program
|
||||
* @return boolean this boolean represents if the program hit any invalid states within the execution
|
||||
* this does NOT indicate if the final value of the stack is true/false
|
||||
* @return program the final state of the program after being evaluated by the interpreter
|
||||
*/
|
||||
@tailrec
|
||||
def loop(program : ScriptProgram) : ExecutedScriptProgram = {
|
||||
logger.debug("Stack: " + program.stack)
|
||||
logger.debug("Script: " + program.script)
|
||||
val scriptSig = program.txSignatureComponent.scriptSignature
|
||||
val scriptPubKey = program.txSignatureComponent.scriptPubKey
|
||||
val executedProgram : ExecutedScriptProgram = if (ScriptFlagUtil.requirePushOnly(program.flags)
|
||||
&& !BitcoinScriptUtil.isPushOnly(program.script)) {
|
||||
logger.error("We can only have push operations inside of the script sig when the SIGPUSHONLY flag is set")
|
||||
val programBeingExecuted = ScriptProgram.toExecutionInProgress(program)
|
||||
ScriptProgram(programBeingExecuted,ScriptErrorSigPushOnly)
|
||||
} else if (scriptSig.isInstanceOf[P2SHScriptSignature] && ScriptFlagUtil.p2shEnabled(program.flags) &&
|
||||
!BitcoinScriptUtil.isPushOnly(scriptSig.asm)) {
|
||||
logger.error("P2SH scriptSigs are required to be push only by definition - see BIP16")
|
||||
val programBeingExecuted = ScriptProgram.toExecutionInProgress(program)
|
||||
ScriptProgram(programBeingExecuted,ScriptErrorSigPushOnly)
|
||||
} else {
|
||||
val scriptSigExecutedProgram = loop(program)
|
||||
scriptPubKey match {
|
||||
case p2shScriptPubKey : P2SHScriptPubKey if (ScriptFlagUtil.p2shEnabled(program.flags)) =>
|
||||
val hashCheckProgram = ScriptProgram(program, scriptSigExecutedProgram.stack, p2shScriptPubKey.asm)
|
||||
val hashesMatchProgram = loop(hashCheckProgram)
|
||||
hashesMatchProgram.stackTopIsTrue match {
|
||||
case true =>
|
||||
logger.info("Hashes matched between the p2shScriptSignature & the p2shScriptPubKey")
|
||||
//we need to run the deserialized redeemScript & the scriptSignature without the serialized redeemScript
|
||||
val stack = scriptSigExecutedProgram.stack
|
||||
|
||||
if (opCount > maxScriptOps && !program.isInstanceOf[ExecutedScriptProgram]) {
|
||||
logger.error("We have reached the maximum amount of script operations allowed")
|
||||
logger.error("Here are the remaining operations in the script: " + program.script)
|
||||
loop(ScriptProgram(program,ScriptErrorOpCount))
|
||||
} else if (program.script.flatMap(_.bytes).size > 10000 && !program.isInstanceOf[ExecutedScriptProgram]) {
|
||||
logger.error("We cannot run a script that is larger than 10,000 bytes")
|
||||
program match {
|
||||
case p : PreExecutionScriptProgram =>
|
||||
loop(ScriptProgram(ScriptProgram.toExecutionInProgress(p), ScriptErrorScriptSize))
|
||||
case _ : ExecutionInProgressScriptProgram | _ : ExecutedScriptProgram =>
|
||||
loop(ScriptProgram(program, ScriptErrorScriptSize))
|
||||
}
|
||||
} else {
|
||||
program match {
|
||||
case p : PreExecutionScriptProgram => loop(ScriptProgram.toExecutionInProgress(p,Some(p.stack)))
|
||||
case p : ExecutedScriptProgram =>
|
||||
//reset opCount variable to zero since we may need to count the ops
|
||||
//in the scriptPubKey - we don't want the op count of the scriptSig
|
||||
//to count towards the scriptPubKey op count
|
||||
logger.info("Final op count: " + opCount)
|
||||
opCount = 0
|
||||
p
|
||||
case p : ExecutionInProgressScriptProgram =>
|
||||
//increment the op count
|
||||
if (p.script.headOption.isDefined &&
|
||||
BitcoinScriptUtil.countsTowardsScriptOpLimit(p.script.head)) opCount = opCount + 1
|
||||
p.script match {
|
||||
//if at any time we see that the program is not valid
|
||||
//cease script execution
|
||||
case _ if !p.script.intersect(Seq(OP_VERIF, OP_VERNOTIF)).isEmpty =>
|
||||
logger.error("Script is invalid even when a OP_VERIF or OP_VERNOTIF occurs in an unexecuted OP_IF branch")
|
||||
loop(ScriptProgram(p, ScriptErrorBadOpCode))
|
||||
//disabled splice operation
|
||||
case _ if !p.script.intersect(Seq(OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT)).isEmpty =>
|
||||
logger.error("Script is invalid because it contains a disabled splice operation")
|
||||
loop(ScriptProgram(p, ScriptErrorDisabledOpCode))
|
||||
//disabled bitwise operations
|
||||
case _ if !p.script.intersect(Seq(OP_INVERT, OP_AND, OP_OR, OP_XOR)).isEmpty =>
|
||||
logger.error("Script is invalid because it contains a disabled bitwise operation")
|
||||
loop(ScriptProgram(p, ScriptErrorDisabledOpCode))
|
||||
//disabled arithmetic operations
|
||||
case _ if !p.script.intersect(Seq(OP_MUL, OP_2MUL, OP_DIV, OP_2DIV, OP_MOD, OP_LSHIFT, OP_RSHIFT)).isEmpty =>
|
||||
logger.error("Script is invalid because it contains a disabled arithmetic operation")
|
||||
loop(ScriptProgram(p, ScriptErrorDisabledOpCode))
|
||||
//program cannot contain a push operation > 520 bytes
|
||||
case _ if (p.script.exists(token => token.bytes.size > 520)) =>
|
||||
logger.error("We have a script constant that is larger than 520 bytes, this is illegal: " + p.script)
|
||||
loop(ScriptProgram(p, ScriptErrorPushSize))
|
||||
//program stack size cannot be greater than 1000 elements
|
||||
case _ if ((p.stack.size + p.altStack.size) > 1000) =>
|
||||
logger.error("We cannot have a stack + alt stack size larger than 1000 elements")
|
||||
loop(ScriptProgram(p, ScriptErrorStackSize))
|
||||
logger.debug("P2sh stack: " + stack)
|
||||
val redeemScript = ScriptPubKey(stack.head.bytes)
|
||||
val p2shRedeemScriptProgram = ScriptProgram(scriptSigExecutedProgram.txSignatureComponent,stack.tail,
|
||||
redeemScript.asm)
|
||||
if (ScriptFlagUtil.requirePushOnly(p2shRedeemScriptProgram.flags) && !BitcoinScriptUtil.isPushOnly(redeemScript.asm)) {
|
||||
logger.error("p2sh redeem script must be push only operations whe SIGPUSHONLY flag is set")
|
||||
ScriptProgram(p2shRedeemScriptProgram,ScriptErrorSigPushOnly)
|
||||
} else loop(p2shRedeemScriptProgram)
|
||||
case false =>
|
||||
logger.warn("P2SH scriptPubKey hash did not match the hash for the serialized redeemScript")
|
||||
hashesMatchProgram
|
||||
}
|
||||
case _ : MultiSignatureScriptPubKey | _ : P2SHScriptPubKey | _ : P2PKHScriptPubKey |
|
||||
_ : P2PKScriptPubKey | _ : NonStandardScriptPubKey | EmptyScriptPubKey =>
|
||||
logger.info("Stack state after scriptSig execution: " + scriptSigExecutedProgram.stack)
|
||||
if (!scriptSigExecutedProgram.error.isDefined) {
|
||||
logger.debug("We do not check a redeemScript against a non p2sh scriptSig")
|
||||
//now run the scriptPubKey script through the interpreter with the scriptSig as the stack arguments
|
||||
val scriptPubKeyProgram = ScriptProgram(scriptSigExecutedProgram.txSignatureComponent,
|
||||
scriptSigExecutedProgram.stack,scriptSigExecutedProgram.txSignatureComponent.scriptPubKey.asm)
|
||||
require(scriptPubKeyProgram.script == scriptSigExecutedProgram.txSignatureComponent.scriptPubKey.asm)
|
||||
val scriptPubKeyExecutedProgram : ExecutedScriptProgram = loop(scriptPubKeyProgram)
|
||||
|
||||
//stack operations
|
||||
case OP_DUP :: t => loop(opDup(p))
|
||||
case OP_DEPTH :: t => loop(opDepth(p))
|
||||
case OP_TOALTSTACK :: t => loop(opToAltStack(p))
|
||||
case OP_FROMALTSTACK :: t => loop(opFromAltStack(p))
|
||||
case OP_DROP :: t => loop(opDrop(p))
|
||||
case OP_IFDUP :: t => loop(opIfDup(p))
|
||||
case OP_NIP :: t => loop(opNip(p))
|
||||
case OP_OVER :: t => loop(opOver(p))
|
||||
case OP_PICK :: t => loop(opPick(p))
|
||||
case OP_ROLL :: t => loop(opRoll(p))
|
||||
case OP_ROT :: t => loop(opRot(p))
|
||||
case OP_2ROT :: t => loop(op2Rot(p))
|
||||
case OP_2DROP :: t => loop(op2Drop(p))
|
||||
case OP_SWAP :: t => loop(opSwap(p))
|
||||
case OP_TUCK :: t => loop(opTuck(p))
|
||||
case OP_2DUP :: t => loop(op2Dup(p))
|
||||
case OP_3DUP :: t => loop(op3Dup(p))
|
||||
case OP_2OVER :: t => loop(op2Over(p))
|
||||
case OP_2SWAP :: t => loop(op2Swap(p))
|
||||
logger.info("Stack state after scriptPubKey execution: " + scriptPubKeyExecutedProgram.stack)
|
||||
|
||||
//arithmetic operations
|
||||
case OP_ADD :: t => loop(opAdd(p))
|
||||
case OP_1ADD :: t => loop(op1Add(p))
|
||||
case OP_1SUB :: t => loop(op1Sub(p))
|
||||
case OP_SUB :: t => loop(opSub(p))
|
||||
case OP_ABS :: t => loop(opAbs(p))
|
||||
case OP_NEGATE :: t => loop(opNegate(p))
|
||||
case OP_NOT :: t => loop(opNot(p))
|
||||
case OP_0NOTEQUAL :: t => loop(op0NotEqual(p))
|
||||
case OP_BOOLAND :: t => loop(opBoolAnd(p))
|
||||
case OP_BOOLOR :: t => loop(opBoolOr(p))
|
||||
case OP_NUMEQUAL :: t => loop(opNumEqual(p))
|
||||
case OP_NUMEQUALVERIFY :: t => loop(opNumEqualVerify(p))
|
||||
case OP_NUMNOTEQUAL :: t => loop(opNumNotEqual(p))
|
||||
case OP_LESSTHAN :: t => loop(opLessThan(p))
|
||||
case OP_GREATERTHAN :: t => loop(opGreaterThan(p))
|
||||
case OP_LESSTHANOREQUAL :: t => loop(opLessThanOrEqual(p))
|
||||
case OP_GREATERTHANOREQUAL :: t => loop(opGreaterThanOrEqual(p))
|
||||
case OP_MIN :: t => loop(opMin(p))
|
||||
case OP_MAX :: t => loop(opMax(p))
|
||||
case OP_WITHIN :: t => loop(opWithin(p))
|
||||
|
||||
//bitwise operations
|
||||
case OP_EQUAL :: t =>
|
||||
val newProgram = opEqual(p)
|
||||
loop(newProgram)
|
||||
|
||||
case OP_EQUALVERIFY :: t => loop(opEqualVerify(p))
|
||||
|
||||
case OP_0 :: t => loop(ScriptProgram(p, ScriptNumber.zero :: p.stack, t))
|
||||
case (scriptNumberOp : ScriptNumberOperation) :: t =>
|
||||
loop(ScriptProgram(p, ScriptNumber(scriptNumberOp.num) :: p.stack, t))
|
||||
case (bytesToPushOntoStack: BytesToPushOntoStack) :: t =>
|
||||
loop(pushScriptNumberBytesToStack(p))
|
||||
case (scriptNumber: ScriptNumber) :: t =>
|
||||
loop(ScriptProgram(p, scriptNumber :: p.stack, t))
|
||||
case OP_PUSHDATA1 :: t => loop(opPushData1(p))
|
||||
case OP_PUSHDATA2 :: t => loop(opPushData2(p))
|
||||
case OP_PUSHDATA4 :: t => loop(opPushData4(p))
|
||||
|
||||
case (x : ScriptConstant) :: t => loop(ScriptProgram(p, x :: p.stack, t))
|
||||
|
||||
//control operations
|
||||
case OP_IF :: t => loop(opIf(p))
|
||||
case OP_NOTIF :: t => loop(opNotIf(p))
|
||||
case OP_ELSE :: t => loop(opElse(p))
|
||||
case OP_ENDIF :: t => loop(opEndIf(p))
|
||||
case OP_RETURN :: t => loop(opReturn(p))
|
||||
|
||||
case OP_VERIFY :: t => loop(opVerify(p))
|
||||
|
||||
//crypto operations
|
||||
case OP_HASH160 :: t => loop(opHash160(p))
|
||||
case OP_CHECKSIG :: t => loop(opCheckSig(p))
|
||||
case OP_CHECKSIGVERIFY :: t => loop(opCheckSigVerify(p))
|
||||
case OP_SHA1 :: t => loop(opSha1(p))
|
||||
case OP_RIPEMD160 :: t => loop(opRipeMd160(p))
|
||||
case OP_SHA256 :: t => loop(opSha256(p))
|
||||
case OP_HASH256 :: t => loop(opHash256(p))
|
||||
case OP_CODESEPARATOR :: t => loop(opCodeSeparator(p))
|
||||
case OP_CHECKMULTISIG :: t =>
|
||||
opCheckMultiSig(p) match {
|
||||
case newProgram : ExecutedScriptProgram =>
|
||||
//script was marked invalid for other reasons, don't need to update the opcount
|
||||
loop(newProgram)
|
||||
case newProgram : ExecutionInProgressScriptProgram =>
|
||||
opCount = opCount + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).num.toInt
|
||||
loop(newProgram)
|
||||
case newProgram : PreExecutionScriptProgram =>
|
||||
opCount = opCount + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).num.toInt
|
||||
loop(newProgram)
|
||||
}
|
||||
|
||||
case OP_CHECKMULTISIGVERIFY :: t =>
|
||||
opCheckMultiSigVerify(p) match {
|
||||
case newProgram : ExecutedScriptProgram =>
|
||||
//script was marked invalid for other reasons, don't need to update the opcount
|
||||
loop(newProgram)
|
||||
case newProgram : ExecutionInProgressScriptProgram =>
|
||||
opCount = opCount + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).num.toInt
|
||||
loop(newProgram)
|
||||
case newProgram : PreExecutionScriptProgram =>
|
||||
opCount = opCount + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).num.toInt
|
||||
loop(newProgram)
|
||||
}
|
||||
//reserved operations
|
||||
case OP_NOP :: t =>
|
||||
//script discourage upgradeable flag does not apply to a OP_NOP
|
||||
loop(ScriptProgram(p, p.stack, t))
|
||||
|
||||
//if we see an OP_NOP and the DISCOURAGE_UPGRADABLE_OP_NOPS flag is set we must fail our program
|
||||
case (nop: NOP) :: t if ScriptFlagUtil.discourageUpgradableNOPs(p.flags) =>
|
||||
logger.error("We cannot execute a NOP when the ScriptVerifyDiscourageUpgradableNOPs is set")
|
||||
loop(ScriptProgram(p, ScriptErrorDiscourageUpgradableNOPs))
|
||||
case (nop: NOP) :: t => loop(ScriptProgram(p, p.stack, t))
|
||||
case OP_RESERVED :: t =>
|
||||
logger.error("OP_RESERVED automatically marks transaction invalid")
|
||||
loop(ScriptProgram(p,ScriptErrorBadOpCode))
|
||||
case OP_VER :: t =>
|
||||
logger.error("Transaction is invalid when executing OP_VER")
|
||||
loop(ScriptProgram(p,ScriptErrorBadOpCode))
|
||||
case OP_RESERVED1 :: t =>
|
||||
logger.error("Transaction is invalid when executing OP_RESERVED1")
|
||||
loop(ScriptProgram(p,ScriptErrorBadOpCode))
|
||||
case OP_RESERVED2 :: t =>
|
||||
logger.error("Transaction is invalid when executing OP_RESERVED2")
|
||||
loop(ScriptProgram(p,ScriptErrorBadOpCode))
|
||||
|
||||
case (reservedOperation : ReservedOperation) :: t =>
|
||||
logger.error("Undefined operation found which automatically fails the script: " + reservedOperation)
|
||||
loop(ScriptProgram(p,ScriptErrorBadOpCode))
|
||||
//splice operations
|
||||
case OP_SIZE :: t => loop(opSize(p))
|
||||
|
||||
//locktime operations
|
||||
case OP_CHECKLOCKTIMEVERIFY :: t =>
|
||||
//check if CLTV is enforced yet
|
||||
if (ScriptFlagUtil.checkLockTimeVerifyEnabled(p.flags)) loop(opCheckLockTimeVerify(p))
|
||||
//if not, check to see if we should discourage p
|
||||
else if (ScriptFlagUtil.discourageUpgradableNOPs(p.flags)) {
|
||||
logger.error("We cannot execute a NOP when the ScriptVerifyDiscourageUpgradableNOPs is set")
|
||||
loop(ScriptProgram(p, ScriptErrorDiscourageUpgradableNOPs))
|
||||
}
|
||||
//in this case, just reat OP_CLTV just like a NOP and remove it from the stack
|
||||
else loop(ScriptProgram(p, p.script.tail, ScriptProgram.Script))
|
||||
case OP_CHECKSEQUENCEVERIFY :: t =>
|
||||
//check if CLTV is enforced yet
|
||||
if (ScriptFlagUtil.checkSequenceVerifyEnabled(p.flags)) loop(opCheckSequenceVerify(p))
|
||||
//if not, check to see if we should discourage p
|
||||
else if (ScriptFlagUtil.discourageUpgradableNOPs(p.flags)) {
|
||||
logger.error("We cannot execute a NOP when the ScriptVerifyDiscourageUpgradableNOPs is set")
|
||||
loop(ScriptProgram(p, ScriptErrorDiscourageUpgradableNOPs))
|
||||
}
|
||||
//in this case, just reat OP_CSV just like a NOP and remove it from the stack
|
||||
else loop(ScriptProgram(p, p.script.tail, ScriptProgram.Script))
|
||||
//no more script operations to run, return whether the program is valid and the final state of the program
|
||||
case Nil => loop(ScriptProgram.toExecutedProgram(p))
|
||||
case h :: t => throw new RuntimeException(h + " was unmatched")
|
||||
}
|
||||
}
|
||||
//if the program is valid, return if the stack top is true
|
||||
//else the program is false since something illegal happened during script evaluation
|
||||
scriptPubKeyExecutedProgram
|
||||
} else scriptSigExecutedProgram
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
val executedProgram : ExecutedScriptProgram = program.txSignatureComponent.scriptSignature match {
|
||||
case scriptSig : ScriptSignature if (ScriptFlagUtil.requirePushOnly(program.flags) &&
|
||||
!BitcoinScriptUtil.isPushOnly(scriptSig.asm)) =>
|
||||
logger.error("We can only have push operations inside of the script sig when the SIGPUSHONLY flag is set")
|
||||
val programBeingExecuted = ScriptProgram.toExecutionInProgress(program)
|
||||
ScriptProgram(programBeingExecuted,ScriptErrorSigPushOnly)
|
||||
|
||||
//if the P2SH script flag is not set, we evaluate a p2sh scriptSig just like any other scriptSig
|
||||
case scriptSig : P2SHScriptSignature if (program.flags.contains(ScriptVerifyP2SH)) =>
|
||||
if (!BitcoinScriptUtil.isPushOnly(scriptSig.asm)) {
|
||||
|
@ -335,8 +159,8 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
|
|||
scriptPubKeyExecutedProgram
|
||||
} else scriptSigExecutedProgram
|
||||
|
||||
}
|
||||
|
||||
}*/
|
||||
logger.debug("Executed Script Program: " + executedProgram)
|
||||
if (executedProgram.error.isDefined) executedProgram.error.get
|
||||
else if (executedProgram.stackTopIsTrue && executedProgram.flags.contains(ScriptVerifyCleanStack)) {
|
||||
//require that the stack after execution has exactly one element on it
|
||||
|
@ -348,6 +172,234 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
|
|||
else ScriptErrorEvalFalse
|
||||
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param program
|
||||
* @return boolean this boolean represents if the program hit any invalid states within the execution
|
||||
* this does NOT indicate if the final value of the stack is true/false
|
||||
* @return program the final state of the program after being evaluated by the interpreter
|
||||
*/
|
||||
@tailrec
|
||||
private def loop(program : ScriptProgram) : ExecutedScriptProgram = {
|
||||
logger.debug("Stack: " + program.stack)
|
||||
logger.debug("Script: " + program.script)
|
||||
|
||||
if (opCount > maxScriptOps && !program.isInstanceOf[ExecutedScriptProgram]) {
|
||||
logger.error("We have reached the maximum amount of script operations allowed")
|
||||
logger.error("Here are the remaining operations in the script: " + program.script)
|
||||
loop(ScriptProgram(program,ScriptErrorOpCount))
|
||||
} else if (program.script.flatMap(_.bytes).size > 10000 && !program.isInstanceOf[ExecutedScriptProgram]) {
|
||||
logger.error("We cannot run a script that is larger than 10,000 bytes")
|
||||
program match {
|
||||
case p : PreExecutionScriptProgram =>
|
||||
loop(ScriptProgram(ScriptProgram.toExecutionInProgress(p), ScriptErrorScriptSize))
|
||||
case _ : ExecutionInProgressScriptProgram | _ : ExecutedScriptProgram =>
|
||||
loop(ScriptProgram(program, ScriptErrorScriptSize))
|
||||
}
|
||||
} else {
|
||||
program match {
|
||||
case p : PreExecutionScriptProgram => loop(ScriptProgram.toExecutionInProgress(p,Some(p.stack)))
|
||||
case p : ExecutedScriptProgram =>
|
||||
//reset opCount variable to zero since we may need to count the ops
|
||||
//in the scriptPubKey - we don't want the op count of the scriptSig
|
||||
//to count towards the scriptPubKey op count
|
||||
logger.info("Final op count: " + opCount)
|
||||
opCount = 0
|
||||
p
|
||||
case p : ExecutionInProgressScriptProgram =>
|
||||
//increment the op count
|
||||
if (p.script.headOption.isDefined &&
|
||||
BitcoinScriptUtil.countsTowardsScriptOpLimit(p.script.head)) opCount = opCount + 1
|
||||
p.script match {
|
||||
//if at any time we see that the program is not valid
|
||||
//cease script execution
|
||||
case _ if !p.script.intersect(Seq(OP_VERIF, OP_VERNOTIF)).isEmpty =>
|
||||
logger.error("Script is invalid even when a OP_VERIF or OP_VERNOTIF occurs in an unexecuted OP_IF branch")
|
||||
loop(ScriptProgram(p, ScriptErrorBadOpCode))
|
||||
//disabled splice operation
|
||||
case _ if !p.script.intersect(Seq(OP_CAT, OP_SUBSTR, OP_LEFT, OP_RIGHT)).isEmpty =>
|
||||
logger.error("Script is invalid because it contains a disabled splice operation")
|
||||
loop(ScriptProgram(p, ScriptErrorDisabledOpCode))
|
||||
//disabled bitwise operations
|
||||
case _ if !p.script.intersect(Seq(OP_INVERT, OP_AND, OP_OR, OP_XOR)).isEmpty =>
|
||||
logger.error("Script is invalid because it contains a disabled bitwise operation")
|
||||
loop(ScriptProgram(p, ScriptErrorDisabledOpCode))
|
||||
//disabled arithmetic operations
|
||||
case _ if !p.script.intersect(Seq(OP_MUL, OP_2MUL, OP_DIV, OP_2DIV, OP_MOD, OP_LSHIFT, OP_RSHIFT)).isEmpty =>
|
||||
logger.error("Script is invalid because it contains a disabled arithmetic operation")
|
||||
loop(ScriptProgram(p, ScriptErrorDisabledOpCode))
|
||||
//program cannot contain a push operation > 520 bytes
|
||||
case _ if (p.script.exists(token => token.bytes.size > 520)) =>
|
||||
logger.error("We have a script constant that is larger than 520 bytes, this is illegal: " + p.script)
|
||||
loop(ScriptProgram(p, ScriptErrorPushSize))
|
||||
//program stack size cannot be greater than 1000 elements
|
||||
case _ if ((p.stack.size + p.altStack.size) > 1000) =>
|
||||
logger.error("We cannot have a stack + alt stack size larger than 1000 elements")
|
||||
loop(ScriptProgram(p, ScriptErrorStackSize))
|
||||
|
||||
//stack operations
|
||||
case OP_DUP :: t => loop(opDup(p))
|
||||
case OP_DEPTH :: t => loop(opDepth(p))
|
||||
case OP_TOALTSTACK :: t => loop(opToAltStack(p))
|
||||
case OP_FROMALTSTACK :: t => loop(opFromAltStack(p))
|
||||
case OP_DROP :: t => loop(opDrop(p))
|
||||
case OP_IFDUP :: t => loop(opIfDup(p))
|
||||
case OP_NIP :: t => loop(opNip(p))
|
||||
case OP_OVER :: t => loop(opOver(p))
|
||||
case OP_PICK :: t => loop(opPick(p))
|
||||
case OP_ROLL :: t => loop(opRoll(p))
|
||||
case OP_ROT :: t => loop(opRot(p))
|
||||
case OP_2ROT :: t => loop(op2Rot(p))
|
||||
case OP_2DROP :: t => loop(op2Drop(p))
|
||||
case OP_SWAP :: t => loop(opSwap(p))
|
||||
case OP_TUCK :: t => loop(opTuck(p))
|
||||
case OP_2DUP :: t => loop(op2Dup(p))
|
||||
case OP_3DUP :: t => loop(op3Dup(p))
|
||||
case OP_2OVER :: t => loop(op2Over(p))
|
||||
case OP_2SWAP :: t => loop(op2Swap(p))
|
||||
|
||||
//arithmetic operations
|
||||
case OP_ADD :: t => loop(opAdd(p))
|
||||
case OP_1ADD :: t => loop(op1Add(p))
|
||||
case OP_1SUB :: t => loop(op1Sub(p))
|
||||
case OP_SUB :: t => loop(opSub(p))
|
||||
case OP_ABS :: t => loop(opAbs(p))
|
||||
case OP_NEGATE :: t => loop(opNegate(p))
|
||||
case OP_NOT :: t => loop(opNot(p))
|
||||
case OP_0NOTEQUAL :: t => loop(op0NotEqual(p))
|
||||
case OP_BOOLAND :: t => loop(opBoolAnd(p))
|
||||
case OP_BOOLOR :: t => loop(opBoolOr(p))
|
||||
case OP_NUMEQUAL :: t => loop(opNumEqual(p))
|
||||
case OP_NUMEQUALVERIFY :: t => loop(opNumEqualVerify(p))
|
||||
case OP_NUMNOTEQUAL :: t => loop(opNumNotEqual(p))
|
||||
case OP_LESSTHAN :: t => loop(opLessThan(p))
|
||||
case OP_GREATERTHAN :: t => loop(opGreaterThan(p))
|
||||
case OP_LESSTHANOREQUAL :: t => loop(opLessThanOrEqual(p))
|
||||
case OP_GREATERTHANOREQUAL :: t => loop(opGreaterThanOrEqual(p))
|
||||
case OP_MIN :: t => loop(opMin(p))
|
||||
case OP_MAX :: t => loop(opMax(p))
|
||||
case OP_WITHIN :: t => loop(opWithin(p))
|
||||
|
||||
//bitwise operations
|
||||
case OP_EQUAL :: t =>
|
||||
val newProgram = opEqual(p)
|
||||
loop(newProgram)
|
||||
|
||||
case OP_EQUALVERIFY :: t => loop(opEqualVerify(p))
|
||||
|
||||
case OP_0 :: t => loop(ScriptProgram(p, ScriptNumber.zero :: p.stack, t))
|
||||
case (scriptNumberOp : ScriptNumberOperation) :: t =>
|
||||
loop(ScriptProgram(p, ScriptNumber(scriptNumberOp.num) :: p.stack, t))
|
||||
case (bytesToPushOntoStack: BytesToPushOntoStack) :: t =>
|
||||
loop(pushScriptNumberBytesToStack(p))
|
||||
case (scriptNumber: ScriptNumber) :: t =>
|
||||
loop(ScriptProgram(p, scriptNumber :: p.stack, t))
|
||||
case OP_PUSHDATA1 :: t => loop(opPushData1(p))
|
||||
case OP_PUSHDATA2 :: t => loop(opPushData2(p))
|
||||
case OP_PUSHDATA4 :: t => loop(opPushData4(p))
|
||||
|
||||
case (x : ScriptConstant) :: t => loop(ScriptProgram(p, x :: p.stack, t))
|
||||
|
||||
//control operations
|
||||
case OP_IF :: t => loop(opIf(p))
|
||||
case OP_NOTIF :: t => loop(opNotIf(p))
|
||||
case OP_ELSE :: t => loop(opElse(p))
|
||||
case OP_ENDIF :: t => loop(opEndIf(p))
|
||||
case OP_RETURN :: t => loop(opReturn(p))
|
||||
|
||||
case OP_VERIFY :: t => loop(opVerify(p))
|
||||
|
||||
//crypto operations
|
||||
case OP_HASH160 :: t => loop(opHash160(p))
|
||||
case OP_CHECKSIG :: t => loop(opCheckSig(p))
|
||||
case OP_CHECKSIGVERIFY :: t => loop(opCheckSigVerify(p))
|
||||
case OP_SHA1 :: t => loop(opSha1(p))
|
||||
case OP_RIPEMD160 :: t => loop(opRipeMd160(p))
|
||||
case OP_SHA256 :: t => loop(opSha256(p))
|
||||
case OP_HASH256 :: t => loop(opHash256(p))
|
||||
case OP_CODESEPARATOR :: t => loop(opCodeSeparator(p))
|
||||
case OP_CHECKMULTISIG :: t =>
|
||||
opCheckMultiSig(p) match {
|
||||
case newProgram : ExecutedScriptProgram =>
|
||||
//script was marked invalid for other reasons, don't need to update the opcount
|
||||
loop(newProgram)
|
||||
case newProgram : ExecutionInProgressScriptProgram =>
|
||||
opCount = opCount + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).num.toInt
|
||||
loop(newProgram)
|
||||
case newProgram : PreExecutionScriptProgram =>
|
||||
opCount = opCount + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).num.toInt
|
||||
loop(newProgram)
|
||||
}
|
||||
|
||||
case OP_CHECKMULTISIGVERIFY :: t =>
|
||||
opCheckMultiSigVerify(p) match {
|
||||
case newProgram : ExecutedScriptProgram =>
|
||||
//script was marked invalid for other reasons, don't need to update the opcount
|
||||
loop(newProgram)
|
||||
case newProgram : ExecutionInProgressScriptProgram =>
|
||||
opCount = opCount + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).num.toInt
|
||||
loop(newProgram)
|
||||
case newProgram : PreExecutionScriptProgram =>
|
||||
opCount = opCount + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).num.toInt
|
||||
loop(newProgram)
|
||||
}
|
||||
//reserved operations
|
||||
case OP_NOP :: t =>
|
||||
//script discourage upgradeable flag does not apply to a OP_NOP
|
||||
loop(ScriptProgram(p, p.stack, t))
|
||||
|
||||
//if we see an OP_NOP and the DISCOURAGE_UPGRADABLE_OP_NOPS flag is set we must fail our program
|
||||
case (nop: NOP) :: t if ScriptFlagUtil.discourageUpgradableNOPs(p.flags) =>
|
||||
logger.error("We cannot execute a NOP when the ScriptVerifyDiscourageUpgradableNOPs is set")
|
||||
loop(ScriptProgram(p, ScriptErrorDiscourageUpgradableNOPs))
|
||||
case (nop: NOP) :: t => loop(ScriptProgram(p, p.stack, t))
|
||||
case OP_RESERVED :: t =>
|
||||
logger.error("OP_RESERVED automatically marks transaction invalid")
|
||||
loop(ScriptProgram(p,ScriptErrorBadOpCode))
|
||||
case OP_VER :: t =>
|
||||
logger.error("Transaction is invalid when executing OP_VER")
|
||||
loop(ScriptProgram(p,ScriptErrorBadOpCode))
|
||||
case OP_RESERVED1 :: t =>
|
||||
logger.error("Transaction is invalid when executing OP_RESERVED1")
|
||||
loop(ScriptProgram(p,ScriptErrorBadOpCode))
|
||||
case OP_RESERVED2 :: t =>
|
||||
logger.error("Transaction is invalid when executing OP_RESERVED2")
|
||||
loop(ScriptProgram(p,ScriptErrorBadOpCode))
|
||||
|
||||
case (reservedOperation : ReservedOperation) :: t =>
|
||||
logger.error("Undefined operation found which automatically fails the script: " + reservedOperation)
|
||||
loop(ScriptProgram(p,ScriptErrorBadOpCode))
|
||||
//splice operations
|
||||
case OP_SIZE :: t => loop(opSize(p))
|
||||
|
||||
//locktime operations
|
||||
case OP_CHECKLOCKTIMEVERIFY :: t =>
|
||||
//check if CLTV is enforced yet
|
||||
if (ScriptFlagUtil.checkLockTimeVerifyEnabled(p.flags)) loop(opCheckLockTimeVerify(p))
|
||||
//if not, check to see if we should discourage p
|
||||
else if (ScriptFlagUtil.discourageUpgradableNOPs(p.flags)) {
|
||||
logger.error("We cannot execute a NOP when the ScriptVerifyDiscourageUpgradableNOPs is set")
|
||||
loop(ScriptProgram(p, ScriptErrorDiscourageUpgradableNOPs))
|
||||
}
|
||||
//in this case, just reat OP_CLTV just like a NOP and remove it from the stack
|
||||
else loop(ScriptProgram(p, p.script.tail, ScriptProgram.Script))
|
||||
case OP_CHECKSEQUENCEVERIFY :: t =>
|
||||
//check if CLTV is enforced yet
|
||||
if (ScriptFlagUtil.checkSequenceVerifyEnabled(p.flags)) loop(opCheckSequenceVerify(p))
|
||||
//if not, check to see if we should discourage p
|
||||
else if (ScriptFlagUtil.discourageUpgradableNOPs(p.flags)) {
|
||||
logger.error("We cannot execute a NOP when the ScriptVerifyDiscourageUpgradableNOPs is set")
|
||||
loop(ScriptProgram(p, ScriptErrorDiscourageUpgradableNOPs))
|
||||
}
|
||||
//in this case, just reat OP_CSV just like a NOP and remove it from the stack
|
||||
else loop(ScriptProgram(p, p.script.tail, ScriptProgram.Script))
|
||||
//no more script operations to run, return whether the program is valid and the final state of the program
|
||||
case Nil => loop(ScriptProgram.toExecutedProgram(p))
|
||||
case h :: t => throw new RuntimeException(h + " was unmatched")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ trait LockTimeInterpreter extends BitcoinSLogger {
|
|||
ScriptProgram(program, ScriptErrorUnsatisfiedLocktime)
|
||||
case s : ScriptNumber =>
|
||||
if (checkSequence(program,s)) {
|
||||
ScriptProgram(program, program.stack.tail, program.script.tail)
|
||||
ScriptProgram(program, program.stack, program.script.tail)
|
||||
} else {
|
||||
logger.error("Stack top sequence and transaction input's sequence number comparison failed")
|
||||
ScriptProgram(program, ScriptErrorUnsatisfiedLocktime)
|
||||
|
|
|
@ -135,9 +135,10 @@ trait BitcoinScriptUtil {
|
|||
* @return
|
||||
*/
|
||||
def isPushOnly(script : Seq[ScriptToken]) : Boolean = {
|
||||
@tailrec
|
||||
def loop(tokens: Seq[ScriptToken], accum: List[Boolean]): Seq[Boolean] = tokens match {
|
||||
case h :: t => h match {
|
||||
case scriptOp: ScriptOperation => loop(t, (scriptOp.opCode < OP_16.opCode) :: accum)
|
||||
case scriptOp : ScriptOperation => loop(t, (scriptOp.opCode < OP_16.opCode) :: accum)
|
||||
case _ : ScriptToken => loop(t, true :: accum)
|
||||
}
|
||||
case Nil => accum
|
||||
|
|
|
@ -26,7 +26,6 @@ trait NumberUtil extends BitcoinSLogger {
|
|||
* @return
|
||||
*/
|
||||
def toLong(bytes : Seq[Byte]) : Long = {
|
||||
logger.debug("bytes: " + bytes)
|
||||
val reversedBytes = bytes.reverse
|
||||
if (bytes.size == 1 && bytes.head == -128) {
|
||||
//the case for negative zero
|
||||
|
|
|
@ -49,14 +49,14 @@ class TransactionTest extends FlatSpec with MustMatchers with BitcoinSLogger {
|
|||
|
||||
|
||||
//use this to represent a single test case from script_valid.json
|
||||
/* val lines =
|
||||
val lines =
|
||||
"""
|
||||
|[
|
||||
|[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "65535 NOP3 1"]],
|
||||
|"020000000100010000000000000000000000000000000000000000000000000000000000000000000000ffff00000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"]
|
||||
|[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7c17aff532f22beb54069942f9bf567a66133eaf EQUAL"]],
|
||||
|"0200000001000100000000000000000000000000000000000000000000000000000000000000000000030251b2010000000100000000000000000000000000", "P2SH,CHECKSEQUENCEVERIFY"]
|
||||
|]
|
||||
""".stripMargin*/
|
||||
/* val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close()
|
||||
""".stripMargin
|
||||
//val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close()
|
||||
val json = lines.parseJson
|
||||
val testCasesOpt : Seq[Option[CoreTransactionTestCase]] = json.convertTo[Seq[Option[CoreTransactionTestCase]]]
|
||||
val testCases : Seq[CoreTransactionTestCase] = testCasesOpt.flatten
|
||||
|
@ -79,25 +79,25 @@ class TransactionTest extends FlatSpec with MustMatchers with BitcoinSLogger {
|
|||
withClue(testCase.raw) {
|
||||
ScriptInterpreter.run(program) must equal (ScriptOk)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
it must "read all of the tx_invalid.json's contents and return a ScriptError" in {
|
||||
/* it must "read all of the tx_invalid.json's contents and return a ScriptError" in {
|
||||
|
||||
|
||||
val source = Source.fromURL(getClass.getResource("/tx_invalid.json"))
|
||||
|
||||
|
||||
//use this to represent a single test case from script_valid.json
|
||||
/* val lines =
|
||||
"""
|
||||
|[
|
||||
|[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "DUP HASH160 0x14 0x5b6462475454710f3c22f5fdf0b40704c92f25c3 EQUALVERIFY CHECKSIGVERIFY 1 0x4c 0x47 0x3044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a01"]],
|
||||
|"01000000010001000000000000000000000000000000000000000000000000000000000000000000006a473044022067288ea50aa799543a536ff9306f8e1cba05b9c6b10951175b924f96732555ed022026d7b5265f38d21541519e4a1e55044d5b9e17e15cdbaf29ae3792e99e883e7a012103ba8c8b86dea131c22ab967e6dd99bdae8eff7a1f75a2c35f1f944109e3fe5e22ffffffff010000000000000000015100000000", "P2SH"]
|
||||
|
|
||||
|]
|
||||
""".stripMargin*/
|
||||
val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close()
|
||||
val lines =
|
||||
"""
|
||||
|[
|
||||
|[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "HASH160 0x14 0x7a052c840ba73af26755de42cf01cc9e0a49fef0 EQUAL"]],
|
||||
|"010000000100010000000000000000000000000000000000000000000000000000000000000000000009085768617420697320ffffffff010000000000000000015100000000", "P2SH"]
|
||||
|
|
||||
|]
|
||||
""".stripMargin
|
||||
//val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close()
|
||||
val json = lines.parseJson
|
||||
val testCasesOpt : Seq[Option[CoreTransactionTestCase]] = json.convertTo[Seq[Option[CoreTransactionTestCase]]]
|
||||
val testCases : Seq[CoreTransactionTestCase] = testCasesOpt.flatten
|
||||
|
@ -121,7 +121,7 @@ class TransactionTest extends FlatSpec with MustMatchers with BitcoinSLogger {
|
|||
ScriptInterpreter.run(program).isInstanceOf[ScriptError] must equal (true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
private def findInput(tx : Transaction, outPoint : TransactionOutPoint) : (TransactionInput,Int) = {
|
||||
tx.inputs.zipWithIndex.find{case (input,index) => input.previousOutput == outPoint}.get
|
||||
|
|
|
@ -33,19 +33,21 @@ class ScriptInterpreterTest extends FlatSpec with MustMatchers with ScriptInterp
|
|||
val source = Source.fromURL(getClass.getResource("/script_tests.json"))
|
||||
|
||||
|
||||
/* //use this to represent a single test case from script_valid.json
|
||||
val lines =
|
||||
//use this to represent a single test case from script_valid.json
|
||||
/* val lines =
|
||||
"""
|
||||
|
|
||||
|[["4294967296", "CHECKSEQUENCEVERIFY", "CHECKSEQUENCEVERIFY", "UNSATISFIED_LOCKTIME",
|
||||
"CSV fails if stack top bit 1 << 31 is not set, and tx version < 2"]]
|
||||
| [[
|
||||
| "0x47 0x3044022003fef42ed6c7be8917441218f525a60e2431be978e28b7aca4d7a532cc413ae8022067a1f82c74e8d69291b90d148778405c6257bbcfc2353cc38a3e1f22bf44254601 0x23 0x210279be667ef9dcbbac54a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac",
|
||||
| "HASH160 0x14 0x23b0ad3477f2178bc0b3eed26e4e6316f4e83aa1 EQUAL",
|
||||
| "P2SH",
|
||||
| "EVAL_FALSE",
|
||||
| "P2SH(P2PK), bad redeemscript"
|
||||
| ]]
|
||||
""".stripMargin*/
|
||||
val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close()
|
||||
val json = lines.parseJson
|
||||
val testCasesOpt : Seq[Option[CoreTestCase]] = json.convertTo[Seq[Option[CoreTestCase]]]
|
||||
val testCases : Seq[CoreTestCase] = testCasesOpt.flatten
|
||||
|
||||
println(testCases)
|
||||
for {
|
||||
testCase <- testCases
|
||||
(creditingTx,outputIndex) = TransactionTestUtil.buildCreditingTransaction(testCase.scriptPubKey.scriptPubKey)
|
||||
|
|
Loading…
Add table
Reference in a new issue