diff --git a/src/main/scala/org/bitcoins/script/flag/ScriptFlagUtil.scala b/src/main/scala/org/bitcoins/script/flag/ScriptFlagUtil.scala index 169337e1e8..4e8e087834 100644 --- a/src/main/scala/org/bitcoins/script/flag/ScriptFlagUtil.scala +++ b/src/main/scala/org/bitcoins/script/flag/ScriptFlagUtil.scala @@ -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 diff --git a/src/main/scala/org/bitcoins/script/interpreter/ScriptInterpreter.scala b/src/main/scala/org/bitcoins/script/interpreter/ScriptInterpreter.scala index 06f0fce50a..a5cfd13c00 100644 --- a/src/main/scala/org/bitcoins/script/interpreter/ScriptInterpreter.scala +++ b/src/main/scala/org/bitcoins/script/interpreter/ScriptInterpreter.scala @@ -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") + } + } + } + } } diff --git a/src/main/scala/org/bitcoins/script/locktime/LockTimeInterpreter.scala b/src/main/scala/org/bitcoins/script/locktime/LockTimeInterpreter.scala index 76c6b908e5..b76ba5d59c 100644 --- a/src/main/scala/org/bitcoins/script/locktime/LockTimeInterpreter.scala +++ b/src/main/scala/org/bitcoins/script/locktime/LockTimeInterpreter.scala @@ -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) diff --git a/src/main/scala/org/bitcoins/util/BitcoinScriptUtil.scala b/src/main/scala/org/bitcoins/util/BitcoinScriptUtil.scala index aeec25e79a..5e0b36efc3 100644 --- a/src/main/scala/org/bitcoins/util/BitcoinScriptUtil.scala +++ b/src/main/scala/org/bitcoins/util/BitcoinScriptUtil.scala @@ -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 diff --git a/src/main/scala/org/bitcoins/util/NumberUtil.scala b/src/main/scala/org/bitcoins/util/NumberUtil.scala index 607606646e..91703e139b 100644 --- a/src/main/scala/org/bitcoins/util/NumberUtil.scala +++ b/src/main/scala/org/bitcoins/util/NumberUtil.scala @@ -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 diff --git a/src/test/scala/org/bitcoins/protocol/transaction/TransactionTest.scala b/src/test/scala/org/bitcoins/protocol/transaction/TransactionTest.scala index 66cf3dd6f6..1d0b3da1f1 100644 --- a/src/test/scala/org/bitcoins/protocol/transaction/TransactionTest.scala +++ b/src/test/scala/org/bitcoins/protocol/transaction/TransactionTest.scala @@ -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 diff --git a/src/test/scala/org/bitcoins/script/interpreter/ScriptInterpreterTest.scala b/src/test/scala/org/bitcoins/script/interpreter/ScriptInterpreterTest.scala index dd735a7baa..a25c6e29ed 100644 --- a/src/test/scala/org/bitcoins/script/interpreter/ScriptInterpreterTest.scala +++ b/src/test/scala/org/bitcoins/script/interpreter/ScriptInterpreterTest.scala @@ -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)