From 8b8b8dd34c59fd62e4ce9552e3f244b23913d744 Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Mon, 2 May 2016 21:10:29 -0500 Subject: [PATCH] Implementating base functionality for when OP_CSV should fail or be treated as a NOP --- .../bitcoins/script/constant/Constants.scala | 4 ++ .../script/locktime/LockTimeInterpreter.scala | 55 +++++++++++++++++-- .../script/locktime/LocktimeOperations.scala | 19 ++++++- .../locktime/LockTimeInterpreterTest.scala | 45 +++++++++++++-- 4 files changed, 113 insertions(+), 10 deletions(-) diff --git a/src/main/scala/org/bitcoins/script/constant/Constants.scala b/src/main/scala/org/bitcoins/script/constant/Constants.scala index 04cdfccb15..6c12346b8b 100644 --- a/src/main/scala/org/bitcoins/script/constant/Constants.scala +++ b/src/main/scala/org/bitcoins/script/constant/Constants.scala @@ -80,6 +80,10 @@ sealed trait ScriptNumber extends ScriptConstant { def > (that : ScriptNumber) : Boolean = num > that.num def >= (that : ScriptNumber) : Boolean = num >= that.num + def &(that : ScriptNumber) : ScriptNumber = ScriptNumber(num & that.num) + + def | (that : ScriptNumber) : ScriptNumber = ScriptNumber(num | that.num) + /** * This equality just checks that the underlying scala numbers are equivalent, NOT if the numbers * are bitwise equivalent in Script. For instance ScriptNumber(0x01).numEqual(ScriptNumber(0x00000000001)) == true diff --git a/src/main/scala/org/bitcoins/script/locktime/LockTimeInterpreter.scala b/src/main/scala/org/bitcoins/script/locktime/LockTimeInterpreter.scala index c4eb0f00d6..e48bdd230e 100644 --- a/src/main/scala/org/bitcoins/script/locktime/LockTimeInterpreter.scala +++ b/src/main/scala/org/bitcoins/script/locktime/LockTimeInterpreter.scala @@ -2,9 +2,10 @@ package org.bitcoins.script.locktime import org.bitcoins.protocol.transaction.TransactionConstants -import org.bitcoins.script.constant.{ScriptToken, ScriptNumber} +import org.bitcoins.script.constant.{ScriptNumber, ScriptToken} import org.bitcoins.script.result._ -import org.bitcoins.script.{ScriptProgram} +import org.bitcoins.script.ScriptProgram +import org.bitcoins.script.flag.ScriptFlagUtil import org.bitcoins.util.BitcoinSLogger /** * Created by chris on 2/8/16. @@ -29,10 +30,10 @@ trait LockTimeInterpreter extends BitcoinSLogger { require(program.script.headOption.isDefined && program.script.head == OP_CHECKLOCKTIMEVERIFY, "Script top must be OP_CHECKLOCKTIMEVERIFY") if (program.stack.size == 0) { - logger.warn("Transaction validation failing in OP_CHECKLOCKTIMEVERIFY because we have no stack items") + logger.error("Transaction validation failing in OP_CHECKLOCKTIMEVERIFY because we have no stack items") ScriptProgram(program, ScriptErrorInvalidStackOperation) } else if (program.txSignatureComponent.transaction.inputs(program.txSignatureComponent.inputIndex).sequence == TransactionConstants.sequence) { - logger.warn("Transaction validation failing in OP_CHECKLOCKTIMEVERIFY because the sequence number is 0xffffffff") + logger.error("Transaction validation failing in OP_CHECKLOCKTIMEVERIFY because the sequence number is 0xffffffff") ScriptProgram(program, ScriptErrorUnsatisfiedLocktime) } else { @@ -46,11 +47,55 @@ trait LockTimeInterpreter extends BitcoinSLogger { case s : ScriptNumber if (s < ScriptNumber(500000000) && program.txSignatureComponent.transaction.lockTime >= 500000000) => logger.warn("OP_CHECKLOCKTIMEVERIFY marks the tx as invalid if stack top < 500000000 & tx locktime >= 500000000") Some(ScriptErrorUnsatisfiedLocktime) - case _ : ScriptToken => None + case _ : ScriptNumber => None + case _ : ScriptToken => Some(ScriptErrorUnknownError) } if (isError.isDefined) ScriptProgram(program,isError.get) else ScriptProgram(program,program.stack, program.script.tail) } } + /** + * When executed, if any of the following conditions are true, the script interpreter will terminate with an error: + * 1.) the stack is empty; or + * 2.) the top item on the stack is less than 0; or + * 3.) the top item on the stack has the disable flag (1 << 31) unset; and + * the transaction version is less than 2; or + * the transaction input sequence number disable flag (1 << 31) is set; or + * the relative lock-time type is not the same; or + * the top stack item is greater than the transaction sequence (when masked according to the BIP68); + * Otherwise, script execution will continue as if a NOP had been executed. + * See BIP112 for more information + * https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki + * @param program + * @return + */ + def opCheckSequenceVerify(program : ScriptProgram) : ScriptProgram = { + if (program.stack.isEmpty) { + logger.error("Cannot execute OP_CHECKSEQUENCEVERIFY on an empty stack") + ScriptProgram(program,ScriptErrorInvalidStackOperation) + } else { + program.stack.head match { + case ScriptNumber.negativeOne => ScriptProgram(program,ScriptErrorNegativeLockTime) + case s : ScriptNumber if (ScriptFlagUtil.requireMinimalData(program.flags) && !s.isShortestEncoding) => + logger.error("Sequence number is not encoded in the shortest way possible") + ScriptProgram(program,ScriptErrorUnknownError) + case s : ScriptNumber if ((s.num & locktimeDisabledFlag) != 0) => + //see BIP68 for sematnic of locktimeDisableFalg + logger.info("Locktime disable flag was set so OP_CHECKSEQUENCEVERIFY is treated as a NOP") + ScriptProgram(program,program.script.tail,ScriptProgram.Script) + case _ => ScriptProgram(program, program.stack.tail, program.script.tail) + } + } + + } + + /** + * If bit (1 << 31) of the sequence number is set, + * then no consensus meaning is applied to the sequence number and can be included + * in any block under all currently possible circumstances. + * @return the mask that ben used with a bitwise and to indicate if the sequence number has any meaning + */ + def locktimeDisabledFlag = 1 << 31 + } diff --git a/src/main/scala/org/bitcoins/script/locktime/LocktimeOperations.scala b/src/main/scala/org/bitcoins/script/locktime/LocktimeOperations.scala index 45c0636117..8de88f1bab 100644 --- a/src/main/scala/org/bitcoins/script/locktime/LocktimeOperations.scala +++ b/src/main/scala/org/bitcoins/script/locktime/LocktimeOperations.scala @@ -21,6 +21,23 @@ case object OP_CHECKLOCKTIMEVERIFY extends LocktimeOperation { override def opCode = 177 } +/** + * When executed, if any of the following conditions are true, the script interpreter will terminate with an error: + * 1.) the stack is empty; or + * 2.) the top item on the stack is less than 0; or + * 3.) the top item on the stack has the disable flag (1 << 31) unset; and + * the transaction version is less than 2; or + * the transaction input sequence number disable flag (1 << 31) is set; or + * the relative lock-time type is not the same; or + * the top stack item is greater than the transaction sequence (when masked according to the BIP68); + * Otherwise, script execution will continue as if a NOP had been executed. + * See BIP112 for more information + * https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki + */ +case object OP_CHECKSEQUENCEVERIFY extends LocktimeOperation { + override def opCode = 178 +} + object LocktimeOperation extends ScriptOperationFactory[LocktimeOperation] { - override def operations = Seq(OP_CHECKLOCKTIMEVERIFY) + override def operations = Seq(OP_CHECKLOCKTIMEVERIFY/*, OP_CHECKSEQUENCEVERIFY*/) } \ No newline at end of file diff --git a/src/test/scala/org/bitcoins/script/locktime/LockTimeInterpreterTest.scala b/src/test/scala/org/bitcoins/script/locktime/LockTimeInterpreterTest.scala index c1e48dce63..799dd14bc0 100644 --- a/src/test/scala/org/bitcoins/script/locktime/LockTimeInterpreterTest.scala +++ b/src/test/scala/org/bitcoins/script/locktime/LockTimeInterpreterTest.scala @@ -1,12 +1,13 @@ package org.bitcoins.script.locktime -import org.bitcoins.protocol.transaction.{TransactionInput, Transaction, UpdateTransactionInputs} +import org.bitcoins.policy.Policy +import org.bitcoins.protocol.transaction.{Transaction, TransactionInput, UpdateTransactionInputs} import org.bitcoins.script.result._ -import org.bitcoins.script.{ExecutionInProgressScriptProgram, ExecutedScriptProgram, PreExecutionScriptProgram, ScriptProgram} -import org.bitcoins.script.constant.{ScriptNumber, OP_0} +import org.bitcoins.script.{ExecutedScriptProgram, ExecutionInProgressScriptProgram, PreExecutionScriptProgram, ScriptProgram} +import org.bitcoins.script.constant.{OP_0, ScriptNumber} import org.bitcoins.util.{ScriptProgramTestUtil, TestUtil} -import org.scalatest.{MustMatchers, FlatSpec} +import org.scalatest.{FlatSpec, MustMatchers} /** * Created by chris on 3/30/16. @@ -97,5 +98,41 @@ class LockTimeInterpreterTest extends FlatSpec with MustMatchers with LockTimeIn //if an error is not hit it will still be a ExecutionInProgressScriptProgram newProgram.isInstanceOf[ExecutedScriptProgram] must be (false) } + + it must "mark the script as invalid for OP_CHECKSEQUENCEVERIFY if there are no tokens on the stack" in { + val stack = List() + val script = List(OP_CHECKSEQUENCEVERIFY) + val program = ScriptProgram(TestUtil.testProgramExecutionInProgress,stack,script) + val newProgram = opCheckSequenceVerify(program) + newProgram.isInstanceOf[ExecutedScriptProgram] must be (true) + newProgram.asInstanceOf[ExecutedScriptProgram].error must be (Some(ScriptErrorInvalidStackOperation)) + } + + it must "mark the script as invalid for OP_CHECKSEQUENCEVERIFY if the stack top is negative" in { + val stack = List(ScriptNumber.negativeOne) + val script = List(OP_CHECKSEQUENCEVERIFY) + val program = ScriptProgram(TestUtil.testProgramExecutionInProgress,stack,script) + val newProgram = opCheckSequenceVerify(program) + newProgram.isInstanceOf[ExecutedScriptProgram] must be (true) + newProgram.asInstanceOf[ExecutedScriptProgram].error must be (Some(ScriptErrorNegativeLockTime)) + } + + it must "mark the script as invalid if we are requiring minimal encoding of numbers and the stack top is not minimal" in { + val stack = List(ScriptNumber("0100")) + val script = List(OP_CHECKSEQUENCEVERIFY) + val program = ScriptProgram(TestUtil.testProgramExecutionInProgress,stack,script) + val newProgram = opCheckSequenceVerify(program) + newProgram.isInstanceOf[ExecutedScriptProgram] must be (true) + newProgram.asInstanceOf[ExecutedScriptProgram].error must be (Some(ScriptErrorUnknownError)) + } + + it must "treat OP_CHECKSEQUENCEVERIFY as a NOP if the locktime disabled flag is set in the sequence number" in { + val stack = List(ScriptNumber(locktimeDisabledFlag)) + val script = List(OP_CHECKSEQUENCEVERIFY) + val program = ScriptProgram(TestUtil.testProgramExecutionInProgress,stack,script) + val newProgram = opCheckSequenceVerify(program) + newProgram.stack must be (stack) + newProgram.script.isEmpty must be (true) + } }