Implementating base functionality for when OP_CSV should fail or be treated as a NOP

This commit is contained in:
Chris Stewart 2016-05-02 21:10:29 -05:00
parent 83bce905a1
commit 8b8b8dd34c
4 changed files with 113 additions and 10 deletions

View file

@ -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

View file

@ -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
}

View file

@ -21,6 +21,23 @@ case object OP_CHECKLOCKTIMEVERIFY extends LocktimeOperation {
override def opCode = 177
}
object LocktimeOperation extends ScriptOperationFactory[LocktimeOperation] {
override def operations = Seq(OP_CHECKLOCKTIMEVERIFY)
/**
* 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/*, OP_CHECKSEQUENCEVERIFY*/)
}

View file

@ -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)
}
}