Refactoring old requirements on stack sizes to log errors and return script programs that are marked invalid

This commit is contained in:
Chris Stewart 2016-04-17 21:02:24 -05:00
parent a25ed9aaef
commit 994c73062a
7 changed files with 255 additions and 212 deletions

View file

@ -18,7 +18,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
*/
def opAdd(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_ADD, "Script top must be OP_ADD")
require(program.stack.size > 1, "Stack size must be 2 or more perform an OP_ADD")
performBinaryArithmeticOperation(program, (x,y) => x + y)
}
@ -29,7 +28,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
*/
def op1Add(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_1ADD, "Script top must be OP_1ADD")
require(program.stack.size > 0, "Must have one item on the stack to execute OP_1ADD")
performUnaryArithmeticOperation(program, x => x + ScriptNumberFactory.one)
}
@ -40,7 +38,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
*/
def op1Sub(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_1SUB, "Script top must be OP_1SUB")
require(program.stack.size > 0, "Stack size must be 1 or more perform an OP_1SUB")
performUnaryArithmeticOperation(program, x => x - ScriptNumberFactory.one )
}
@ -52,7 +49,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
*/
def opSub(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_SUB, "Script top must be OP_SUB")
require(program.stack.size > 1, "Stack must contain two elements to do an OP_SUB")
performBinaryArithmeticOperation(program, (x,y) => y - x)
}
@ -63,7 +59,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
*/
def opAbs(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_ABS, "Script top must be OP_ABS")
require(program.stack.size > 0, "Stack size must be 1 or more perform an OP_ABS")
performUnaryArithmeticOperation(program, x => ScriptNumberFactory.fromNumber(x.num.abs))
}
@ -74,7 +69,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
*/
def opNegate(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_NEGATE, "Script top must be OP_NEGATE")
require(program.stack.size > 0, "Stack size must be 1 or more perform an OP_NEGATE")
performUnaryArithmeticOperation(program, x => x -)
}
@ -85,7 +79,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
*/
def opNot(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_NOT, "Script top must be OP_NOT")
require(program.stack.size > 0, "Stack size must be 1 or more perform an OP_NOT")
//TODO: this needs to be modified to have an exhaustive type check
performUnaryArithmeticOperation(program, x => if (program.stackTopIsFalse) OP_TRUE else OP_FALSE)
}
@ -97,7 +90,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
*/
def op0NotEqual(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_0NOTEQUAL, "Script top must be OP_0NOTEQUAL")
require(program.stack.size > 0, "Stack size must be 1 or more perform an OP_0NOTEQUAL")
performUnaryArithmeticOperation(program, x => if(x.num == 0) OP_FALSE else OP_TRUE)
}
@ -109,7 +101,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
*/
def opBoolAnd(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_BOOLAND, "Script top must be OP_BOOLAND")
require(program.stack.size > 1, "Stack size must be 2 or more perform an OP_BOOLAND")
performBinaryBooleanOperation(program,(x,y) => {
val xIsFalse = (x == ScriptNumberFactory.zero || x == OP_0)
val yIsFalse = (y == ScriptNumberFactory.zero || y == OP_0)
@ -125,8 +116,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
*/
def opBoolOr(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_BOOLOR, "Script top must be OP_BOOLOR")
require(program.stack.size > 1, "Stack size must be 2 or more perform an OP_BOOLOR")
performBinaryBooleanOperation(program, (x,y) => {
if (x == y && (x == ScriptNumberFactory.zero || x == OP_0)) false else true
})
@ -139,7 +128,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
*/
def opNumEqual(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_NUMEQUAL, "Script top must be OP_NUMEQUAL")
require(program.stack.size > 1, "Stack size must be 2 or more perform an OP_NUMEQUAL")
performBinaryBooleanOperation(program,(x,y) => x.numEqual(y))
}
@ -152,12 +140,17 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
def opNumEqualVerify(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_NUMEQUALVERIFY,
"Script top must be OP_NUMEQUALVERIFY")
require(program.stack.size > 1, "Stack size must be 2 or more perform an OP_NUMEQUALVERIFY")
val numEqualProgram = ScriptProgramFactory.factory(program, program.stack, OP_NUMEQUAL :: program.script.tail)
val numEqualResult = opNumEqual(numEqualProgram)
val verifyProgram = ScriptProgramFactory.factory(numEqualResult, numEqualResult.stack, OP_VERIFY :: numEqualResult.script)
val verifyResult = opVerify(verifyProgram)
verifyResult
if (program.stack.size < 2) {
logger.error("OP_NUMEQUALVERIFY requires two stack elements")
ScriptProgramFactory.factory(program,false)
} else {
val numEqualProgram = ScriptProgramFactory.factory(program, program.stack, OP_NUMEQUAL :: program.script.tail)
val numEqualResult = opNumEqual(numEqualProgram)
val verifyProgram = ScriptProgramFactory.factory(numEqualResult, numEqualResult.stack, OP_VERIFY :: numEqualResult.script)
val verifyResult = opVerify(verifyProgram)
verifyResult
}
}
@ -169,8 +162,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
def opNumNotEqual(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_NUMNOTEQUAL,
"Script top must be OP_NUMNOTEQUAL")
require(program.stack.size > 1, "Stack size must be 2 or more perform an OP_NUMNOTEQUAL")
performBinaryBooleanOperation(program, (x,y) => {
x.num != y.num
})
@ -185,7 +176,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
def opLessThan(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_LESSTHAN,
"Script top must be OP_LESSTHAN")
require(program.stack.size > 1, "Stack size must be 2 or more perform an OP_LESSTHAN")
performBinaryBooleanOperation(program, (x,y) => y < x)
}
@ -198,7 +188,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
def opGreaterThan(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_GREATERTHAN,
"Script top must be OP_GREATERTHAN")
require(program.stack.size > 1, "Stack size must be 2 or more perform an OP_GREATERTHAN")
performBinaryBooleanOperation(program, (x,y) => y > x)
}
@ -210,7 +199,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
def opLessThanOrEqual(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_LESSTHANOREQUAL,
"Script top must be OP_LESSTHANOREQUAL")
require(program.stack.size > 1, "Stack size must be 2 or more perform an OP_LESSTHANOREQUAL")
performBinaryBooleanOperation(program, (x,y) => y <= x)
}
@ -222,7 +210,6 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
def opGreaterThanOrEqual(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_GREATERTHANOREQUAL,
"Script top must be OP_GREATERTHANOREQUAL")
require(program.stack.size > 1, "Stack size must be 2 or more perform an OP_GREATERTHANOREQUAL")
performBinaryBooleanOperation(program, (x,y) => y >= x)
}
@ -235,18 +222,23 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
def opMin(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_MIN,
"Script top must be OP_MIN")
require(program.stack.size > 1, "Stack size must be 2 or more perform an OP_MIN")
val b = program.stack.head
val a = program.stack.tail.head
val isLessThanOrEqual = (a,b) match {
case (x : ScriptNumberOperation, y : ScriptNumber) => x.num <= y.num
case (x : ScriptNumber, y : ScriptNumberOperation) => x.num <= y.num
case (x,y) => x.toLong <= y.toLong
if (program.stack.size < 2) {
logger.error("OP_MIN requires at least two stack elements")
ScriptProgramFactory.factory(program,false)
} else {
val b = program.stack.head
val a = program.stack.tail.head
val isLessThanOrEqual = (a,b) match {
case (x : ScriptNumberOperation, y : ScriptNumber) => x.num <= y.num
case (x : ScriptNumber, y : ScriptNumberOperation) => x.num <= y.num
case (x,y) => x.toLong <= y.toLong
}
val newStackTop = if (isLessThanOrEqual) a else b
ScriptProgramFactory.factory(program, newStackTop :: program.stack.tail.tail, program.script.tail)
}
val newStackTop = if (isLessThanOrEqual) a else b
ScriptProgramFactory.factory(program, newStackTop :: program.stack.tail.tail, program.script.tail)
}
@ -257,19 +249,25 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
*/
def opMax(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_MAX,
"Script top must be OP_MIN")
require(program.stack.size > 1, "Stack size must be 2 or more perform an OP_MIN")
val b = program.stack.head
val a = program.stack.tail.head
"Script top must be OP_MAX")
if (program.stack.size < 2) {
logger.error("OP_MAX requires at least two stack elements")
ScriptProgramFactory.factory(program,false)
} else {
val b = program.stack.head
val a = program.stack.tail.head
val isGreaterThanOrEqual = (a,b) match {
case (x : ScriptNumberOperation, y : ScriptNumber) => x.num >= y.num
case (x : ScriptNumber, y : ScriptNumberOperation) => x.num >= y.num
case (x,y) => x.toLong >= y.toLong
val isGreaterThanOrEqual = (a,b) match {
case (x : ScriptNumberOperation, y : ScriptNumber) => x.num >= y.num
case (x : ScriptNumber, y : ScriptNumberOperation) => x.num >= y.num
case (x,y) => x.toLong >= y.toLong
}
val newStackTop = if (isGreaterThanOrEqual) a else b
ScriptProgramFactory.factory(program, newStackTop :: program.stack.tail.tail, program.script.tail)
}
val newStackTop = if (isGreaterThanOrEqual) a else b
ScriptProgramFactory.factory(program, newStackTop :: program.stack.tail.tail, program.script.tail)
}
@ -281,30 +279,23 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
def opWithin(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_WITHIN,
"Script top must be OP_WITHIN")
require(program.stack.size > 2, "Stack size must be 3 or more perform an OP_WITHIN")
if (program.stack.size < 3) {
logger.error("OP_WITHIN requires at least 3 elements on the stack")
ScriptProgramFactory.factory(program,false)
} else {
val c = program.stack.head
val b = program.stack.tail.head
val a = program.stack.tail.tail.head
val c = program.stack.head
val b = program.stack.tail.head
val a = program.stack.tail.tail.head
val isWithinRange = (a,b,c) match {
case (x : ScriptNumber, y : ScriptNumber, z : ScriptNumber) => x >= y && x < z
case (x,y,z) => x.toLong >= y.toLong && x.toLong < z.toLong
}
val isWithinRange = (a,b,c) match {
case (x : ScriptNumber, y : ScriptNumber, z : ScriptNumber) => x >= y && x < z
case (x,y,z) => x.toLong >= y.toLong && x.toLong < z.toLong
val newStackTop = if (isWithinRange) OP_TRUE else OP_FALSE
ScriptProgramFactory.factory(program, newStackTop :: program.stack.tail.tail.tail, program.script.tail)
}
val newStackTop = if (isWithinRange) OP_TRUE else OP_FALSE
ScriptProgramFactory.factory(program, newStackTop :: program.stack.tail.tail.tail, program.script.tail)
}
/**
* Converts a script token to an integer
* @param token
* @return
*/
private def numFromScriptToken(token : ScriptToken) : Long = token match {
case x : ScriptNumber => x.num
case x : ScriptConstantImpl => Integer.parseInt(x.hex,16)
}
@ -325,8 +316,11 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
* @return the program with the result from performing the arithmetic operation pushed onto the top of the stack
*/
private def performUnaryArithmeticOperation(program : ScriptProgram, op : ScriptNumber => ScriptNumber) : ScriptProgram = {
program.stack.head match {
case s : ScriptNumber =>
program.stack.headOption match {
case None =>
logger.error("We need one stack element for performing a unary arithmetic operation")
ScriptProgramFactory.factory(program,false)
case Some(s : ScriptNumber) =>
if (checkBitcoinIntByteSize(s)) {
val newScriptNumber = op(s)
ScriptProgramFactory.factory(program, newScriptNumber :: program.stack.tail, program.script.tail)
@ -335,11 +329,11 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
logger.error("Cannot perform arithmetic operation on a number larger than 4 bytes, here is the number: " + s)
ScriptProgramFactory.factory(program,false)
}
case s : ScriptConstant =>
case Some(s : ScriptConstant) =>
val interpretedNumber = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(s.hex))
val newProgram = ScriptProgramFactory.factory(program, interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack)
performUnaryArithmeticOperation(newProgram, op)
case s : ScriptToken =>
case Some(s : ScriptToken) =>
logger.error("Stack top must be a script number to perform an arithmetic operation")
ScriptProgramFactory.factory(program,false)
}
@ -352,36 +346,43 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
* @return the program with the result from performing the arithmetic operation pushed onto the top of the stack
*/
private def performBinaryArithmeticOperation(program : ScriptProgram, op : (ScriptNumber, ScriptNumber) => ScriptNumber) : ScriptProgram = {
(program.stack.head, program.stack.tail.head) match {
case (x : ScriptNumber, y : ScriptNumber) =>
if (checkBitcoinIntByteSize(x) && checkBitcoinIntByteSize(y)) {
val newStackTop = op(x,y)
ScriptProgramFactory.factory(program,newStackTop :: program.stack.tail.tail,program.script.tail)
}
else {
logger.error("Cannot perform arithmetic operation on a number larger than 4 bytes, one of these two numbers is larger than 4 bytes: " + x + " " + y)
if (program.stack.size < 2) {
logger.error("We must have two elements to perform a binary arithmetic operation")
ScriptProgramFactory.factory(program,false)
} else {
(program.stack.head, program.stack.tail.head) match {
case (x : ScriptNumber, y : ScriptNumber) =>
if (checkBitcoinIntByteSize(x) && checkBitcoinIntByteSize(y)) {
val newStackTop = op(x,y)
ScriptProgramFactory.factory(program,newStackTop :: program.stack.tail.tail,program.script.tail)
}
else {
logger.error("Cannot perform arithmetic operation on a number larger than 4 bytes, one of these two numbers is larger than 4 bytes: " + x + " " + y)
ScriptProgramFactory.factory(program,false)
}
case (x : ScriptConstant, y : ScriptNumber) =>
//interpret x as a number
val interpretedNumber = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(x.hex))
val newProgram = ScriptProgramFactory.factory(program, interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack)
performBinaryArithmeticOperation(newProgram, op)
case (x : ScriptNumber, y : ScriptConstant) =>
//interpret y as a number
val interpretedNumber = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(y.hex))
val newProgram = ScriptProgramFactory.factory(program, x :: interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack)
performBinaryArithmeticOperation(newProgram, op)
case (x : ScriptConstant, y : ScriptConstant) =>
//interpret x and y as a number
val interpretedNumberX = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(x.hex))
val interpretedNumberY = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(y.hex))
val newProgram = ScriptProgramFactory.factory(program, interpretedNumberX :: interpretedNumberY :: program.stack.tail.tail, ScriptProgramFactory.Stack)
performBinaryArithmeticOperation(newProgram, op)
case (x : ScriptToken, y : ScriptToken) =>
logger.error("The top two stack items must be script numbers to perform an arithmetic operation")
ScriptProgramFactory.factory(program,false)
}
case (x : ScriptConstant, y : ScriptNumber) =>
//interpret x as a number
val interpretedNumber = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(x.hex))
val newProgram = ScriptProgramFactory.factory(program, interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack)
performBinaryArithmeticOperation(newProgram, op)
case (x : ScriptNumber, y : ScriptConstant) =>
//interpret y as a number
val interpretedNumber = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(y.hex))
val newProgram = ScriptProgramFactory.factory(program, x :: interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack)
performBinaryArithmeticOperation(newProgram, op)
case (x : ScriptConstant, y : ScriptConstant) =>
//interpret x and y as a number
val interpretedNumberX = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(x.hex))
val interpretedNumberY = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(y.hex))
val newProgram = ScriptProgramFactory.factory(program, interpretedNumberX :: interpretedNumberY :: program.stack.tail.tail, ScriptProgramFactory.Stack)
performBinaryArithmeticOperation(newProgram, op)
case (x : ScriptToken, y : ScriptToken) =>
logger.error("The top two stack items must be script numbers to perform an arithmetic operation")
ScriptProgramFactory.factory(program,false)
}
}
}
/**
@ -391,35 +392,43 @@ trait ArithmeticInterpreter extends ControlOperationsInterpreter {
* @return the program with either ScriptFalse or ScriptTrue on the stack top
*/
private def performBinaryBooleanOperation(program : ScriptProgram, op : (ScriptNumber, ScriptNumber) => Boolean) : ScriptProgram = {
(program.stack.head, program.stack.tail.head) match {
case (x : ScriptNumber, y : ScriptNumber) =>
if (checkBitcoinIntByteSize(x) && checkBitcoinIntByteSize(y)) {
val newStackTop = if(op(x,y)) OP_TRUE else OP_FALSE
ScriptProgramFactory.factory(program,newStackTop :: program.stack.tail.tail,program.script.tail)
}
else {
logger.error("Cannot perform boolean operation on a number larger than 4 bytes, one of these two numbers is larger than 4 bytes: " + x + " " + y)
if (program.stack.size < 2) {
logger.error("We need two stack elements for a binary boolean operation")
ScriptProgramFactory.factory(program,false)
} else {
(program.stack.head, program.stack.tail.head) match {
case (x : ScriptNumber, y : ScriptNumber) =>
if (checkBitcoinIntByteSize(x) && checkBitcoinIntByteSize(y)) {
val newStackTop = if(op(x,y)) OP_TRUE else OP_FALSE
ScriptProgramFactory.factory(program,newStackTop :: program.stack.tail.tail,program.script.tail)
}
else {
logger.error("Cannot perform boolean operation on a number larger than 4 bytes, one of these two numbers is larger than 4 bytes: " + x + " " + y)
ScriptProgramFactory.factory(program,false)
}
case (x : ScriptConstant, y : ScriptNumber) =>
//interpret x as a number
val interpretedNumber = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(x.hex))
val newProgram = ScriptProgramFactory.factory(program, interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack)
performBinaryBooleanOperation(newProgram, op)
case (x : ScriptNumber, y : ScriptConstant) =>
//interpret y as a number
val interpretedNumber = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(y.hex))
val newProgram = ScriptProgramFactory.factory(program, x :: interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack)
performBinaryBooleanOperation(newProgram, op)
case (x : ScriptConstant, y : ScriptConstant) =>
//interpret x and y as a number
val interpretedNumberX = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(x.hex))
val interpretedNumberY = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(y.hex))
val newProgram = ScriptProgramFactory.factory(program, interpretedNumberX :: interpretedNumberY :: program.stack.tail.tail, ScriptProgramFactory.Stack)
performBinaryBooleanOperation(newProgram, op)
case (x : ScriptToken, y : ScriptToken) =>
logger.error("The top two stack items must be script numbers to perform an arithmetic operation")
ScriptProgramFactory.factory(program,false)
}
case (x : ScriptConstant, y : ScriptNumber) =>
//interpret x as a number
val interpretedNumber = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(x.hex))
val newProgram = ScriptProgramFactory.factory(program, interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack)
performBinaryBooleanOperation(newProgram, op)
case (x : ScriptNumber, y : ScriptConstant) =>
//interpret y as a number
val interpretedNumber = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(y.hex))
val newProgram = ScriptProgramFactory.factory(program, x :: interpretedNumber :: program.stack.tail, ScriptProgramFactory.Stack)
performBinaryBooleanOperation(newProgram, op)
case (x : ScriptConstant, y : ScriptConstant) =>
//interpret x and y as a number
val interpretedNumberX = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(x.hex))
val interpretedNumberY = ScriptNumberFactory.fromNumber(BitcoinSUtil.hexToLong(y.hex))
val newProgram = ScriptProgramFactory.factory(program, interpretedNumberX :: interpretedNumberY :: program.stack.tail.tail, ScriptProgramFactory.Stack)
performBinaryBooleanOperation(newProgram, op)
case (x : ScriptToken, y : ScriptToken) =>
logger.error("The top two stack items must be script numbers to perform an arithmetic operation")
ScriptProgramFactory.factory(program,false)
}
}
}
}

View file

@ -56,13 +56,16 @@ trait BitwiseInterpreter extends ControlOperationsInterpreter {
* @return
*/
def opEqualVerify(program : ScriptProgram) : ScriptProgram = {
require(program.stack.size > 1, "Stack size must be 2 or more to compare the top two values")
require(program.script.headOption.isDefined && program.script.head == OP_EQUALVERIFY, "Script operation must be OP_EQUALVERIFY")
//first replace OP_EQUALVERIFY with OP_EQUAL and OP_VERIFY
val simpleScript = OP_EQUAL :: OP_VERIFY :: program.script.tail
val newProgram: ScriptProgram = opEqual(ScriptProgramFactory.factory(program, program.stack, simpleScript))
opVerify(newProgram)
program.stack.size > 1 match {
case true =>
//first replace OP_EQUALVERIFY with OP_EQUAL and OP_VERIFY
val simpleScript = OP_EQUAL :: OP_VERIFY :: program.script.tail
val newProgram: ScriptProgram = opEqual(ScriptProgramFactory.factory(program, program.stack, simpleScript))
opVerify(newProgram)
case false =>
logger.error("OP_EQUALVERIFY requires at least 2 elements on the stack")
ScriptProgramFactory.factory(program,false)
}
}
}

View file

@ -26,6 +26,9 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
if (!checkMatchingOpIfOpNotIfOpEndIf(program.originalScript)) {
logger.error("We do not have a matching OP_ENDIF for every OP_IF we have")
ScriptProgramFactory.factory(program,false)
} else if (program.stack.isEmpty) {
logger.error("We do not have any stack elements for our OP_IF")
ScriptProgramFactory.factory(program,false)
}
else if (program.stackTopIsTrue) {
logger.debug("OP_IF stack top was true")
@ -58,6 +61,9 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
if (!checkMatchingOpIfOpNotIfOpEndIf(program.originalScript)) {
logger.error("We do not have a matching OP_ENDIF for every OP_NOTIF we have")
ScriptProgramFactory.factory(program,false)
} else if (program.stack.isEmpty) {
logger.error("We do not have any stack elements for our OP_NOTIF")
ScriptProgramFactory.factory(program,false)
} else if (program.stackTopIsTrue) {
//remove the OP_NOTIF
val scriptWithoutOpIf : BinaryTree[ScriptToken] = removeFirstOpIf(binaryTree)
@ -134,13 +140,17 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
* @return
*/
def opVerify(program : ScriptProgram) : ScriptProgram = {
//TODO: There is a bug here, if the value is cast to an int and is != 0 then we need to pop the cast values
//off of the stack..
require(program.script.headOption.isDefined && program.script.head == OP_VERIFY, "Script top must be OP_VERIFY")
require(program.stack.size > 0, "Stack must have at least one element on it to run OP_VERIFY")
logger.debug("Stack for OP_VERIFY: " + program.stack)
if (program.stackTopIsFalse) ScriptProgramFactory.factory(program,false)
else ScriptProgramFactory.factory(program, program.stack.tail,program.script.tail)
program.script.size > 0 match {
case true =>
logger.debug("Stack for OP_VERIFY: " + program.stack)
if (program.stackTopIsFalse) ScriptProgramFactory.factory(program,false)
else ScriptProgramFactory.factory(program, program.stack.tail,program.script.tail)
case false =>
logger.error("OP_VERIFY requires an element to be on the stack")
ScriptProgramFactory.factory(program,false)
}
}

View file

@ -78,36 +78,40 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger
*/
def opCheckSig(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_CHECKSIG, "Script top must be OP_CHECKSIG")
require(program.stack.size > 1, "Stack must have at least 2 items on it for OP_CHECKSIG")
val pubKey = ECFactory.publicKey(program.stack.head.bytes)
val signature = ECFactory.digitalSignature(program.stack.tail.head.bytes)
if (program.flags.contains(ScriptVerifyDerSig) && !DERSignatureUtil.isStrictDEREncoding(signature)) {
//this means all of the signatures must encoded according to BIP66 strict dersig
//https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki
//script verification fails since the sig is not strictly der encoded
logger.warn("Since the ScriptVerifyDerSig flag is set the signature being checked must be a strict dersig signature as per BIP 66\n" +
"Sig: " + signature.hex)
if (program.stack.size < 2) {
logger.error("OP_CHECKSIG requires at lest two stack elements")
ScriptProgramFactory.factory(program,false)
} else {
val restOfStack = program.stack.tail.tail
val pubKey = ECFactory.publicKey(program.stack.head.bytes)
val signature = ECFactory.digitalSignature(program.stack.tail.head.bytes)
if (program.flags.contains(ScriptVerifyDerSig) && !DERSignatureUtil.isStrictDEREncoding(signature)) {
//this means all of the signatures must encoded according to BIP66 strict dersig
//https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki
//script verification fails since the sig is not strictly der encoded
logger.warn("Since the ScriptVerifyDerSig flag is set the signature being checked must be a strict dersig signature as per BIP 66\n" +
"Sig: " + signature.hex)
ScriptProgramFactory.factory(program,false)
} else {
val restOfStack = program.stack.tail.tail
val result = TransactionSignatureChecker.checkSignature(program.txSignatureComponent,pubKey,
signature,program.flags)
logger.debug("signature verification isValid: " + result)
result match {
case SignatureValidationSuccess => ScriptProgramFactory.factory(program,
ScriptTrue :: restOfStack,program.script.tail)
case SignatureValidationFailureNotStrictDerEncoding =>
ScriptProgramFactory.factory(program, ScriptFalse :: restOfStack,
program.script.tail,SignatureValidationFailureNotStrictDerEncoding.isValid)
case SignatureValidationFailureIncorrectSignatures =>
ScriptProgramFactory.factory(program, ScriptFalse :: restOfStack,program.script.tail)
val result = TransactionSignatureChecker.checkSignature(program.txSignatureComponent,pubKey,
signature,program.flags)
logger.debug("signature verification isValid: " + result)
result match {
case SignatureValidationSuccess => ScriptProgramFactory.factory(program,
ScriptTrue :: restOfStack,program.script.tail)
case SignatureValidationFailureNotStrictDerEncoding =>
ScriptProgramFactory.factory(program, ScriptFalse :: restOfStack,
program.script.tail,SignatureValidationFailureNotStrictDerEncoding.isValid)
case SignatureValidationFailureIncorrectSignatures =>
ScriptProgramFactory.factory(program, ScriptFalse :: restOfStack,program.script.tail)
}
}
}
}
@ -146,11 +150,13 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger
*/
def opCheckMultiSig(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_CHECKMULTISIG, "Script top must be OP_CHECKMULTISIG")
require(program.stack.size > 2, "Stack must contain at least 3 items for OP_CHECKMULTISIG")
if (program.flags.contains(ScriptVerifyNullDummy) && program.txSignatureComponent.scriptSignature.asm.head != OP_0) {
logger.warn("Script flag null dummy was set however the first element in the script signature was not an OP_0")
ScriptProgramFactory.factory(program,false)
} else if (program.stack.size < 3) {
logger.error("OP_CHECKMULTISIG requires at least 3 stack elements")
ScriptProgramFactory.factory(program,false)
} else {
//these next lines remove the appropriate stack/script values after the signatures have been checked
val nPossibleSignatures : Int = program.stack.head match {

View file

@ -67,9 +67,14 @@ trait StackInterpreter extends BitcoinSLogger {
*/
def opToAltStack(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_TOALTSTACK, "Top of script stack must be OP_TOALTSTACK")
require(program.stack.size > 0,"Stack must have at least one item on it for OP_TOALTSTACK")
ScriptProgramFactory.factory(program, program.stack.tail,
program.script.tail, program.stack.head :: program.altStack, ScriptProgramFactory.AltStack)
program.stack.size > 0 match {
case true => ScriptProgramFactory.factory(program, program.stack.tail,
program.script.tail, program.stack.head :: program.altStack, ScriptProgramFactory.AltStack)
case false =>
logger.error("OP_TOALTSTACK requires an element to be on the stack")
ScriptProgramFactory.factory(program,false)
}
}
/**
@ -212,11 +217,15 @@ trait StackInterpreter extends BitcoinSLogger {
*/
def op2Rot(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_2ROT, "Top of script stack must be OP_2ROT")
val newStack = program.stack match {
case h :: h1 :: h2 :: h3 :: h4 :: h5 :: t => h4 :: h5 :: h :: h1 :: h2 :: h3 :: t
case _ => throw new IllegalArgumentException("Stack must have at least 5 items on it for OP_2ROT")
program.stack match {
case h :: h1 :: h2 :: h3 :: h4 :: h5 :: t =>
val newStack = h4 :: h5 :: h :: h1 :: h2 :: h3 :: t
ScriptProgramFactory.factory(program, newStack,program.script.tail)
case _ =>
logger.error("OP_2ROT requires 6 elements on the stack")
ScriptProgramFactory.factory(program,false)
}
ScriptProgramFactory.factory(program, newStack,program.script.tail)
}
/**
@ -226,8 +235,14 @@ trait StackInterpreter extends BitcoinSLogger {
*/
def op2Drop(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_2DROP, "Top of script stack must be OP_2DROP")
require(program.stack.size > 1,"Stack must have at least 2 items on it for OP_2DROP")
ScriptProgramFactory.factory(program, program.stack.tail.tail, program.script.tail)
program.stack.size > 1 match {
case true =>
ScriptProgramFactory.factory(program, program.stack.tail.tail, program.script.tail)
case false =>
logger.error("OP_2DROP requires two elements to be on the stack")
ScriptProgramFactory.factory(program,false)
}
}

View file

@ -30,13 +30,14 @@ class ArithmeticInterpreterTest extends FlatSpec with MustMatchers with Arithmet
newProgram.script.isEmpty must be (true)
}
it must "throw an exception if we have an OP_1ADD with nothing on the stack" in {
intercept[IllegalArgumentException] {
val stack = List()
val script = List(OP_1ADD)
val program = ScriptProgramFactory.factory(TestUtil.testProgram, stack,script)
val newProgram = op1Add(program)
}
it must "mark the script as invalid if we have an OP_1ADD with nothing on the stack" in {
val stack = List()
val script = List(OP_1ADD)
val program = ScriptProgramFactory.factory(TestUtil.testProgram, stack,script)
val newProgram = op1Add(program)
newProgram.isValid must be (false)
}
it must "perform an OP_1SUB corectly" in {
@ -49,13 +50,13 @@ class ArithmeticInterpreterTest extends FlatSpec with MustMatchers with Arithmet
newProgram.script.isEmpty must be (true)
}
it must "throw an exception if we have an OP_1SUB with nothing on the stack" in {
intercept[IllegalArgumentException] {
val stack = List()
val script = List(OP_1SUB)
val program = ScriptProgramFactory.factory(TestUtil.testProgram, stack,script)
val newProgram = op1Sub(program)
}
it must "mark a script as invalid if we have an OP_1SUB with nothing on the stack" in {
val stack = List()
val script = List(OP_1SUB)
val program = ScriptProgramFactory.factory(TestUtil.testProgram, stack,script)
val newProgram = op1Sub(program)
newProgram.isValid must be (false)
}
it must "perform an OP_SUB corectly" in {
@ -68,13 +69,12 @@ class ArithmeticInterpreterTest extends FlatSpec with MustMatchers with Arithmet
newProgram.script.isEmpty must be (true)
}
it must "throw an exception if we have an OP_SUB with nothing on the stack" in {
intercept[IllegalArgumentException] {
val stack = List()
val script = List(OP_SUB)
val program = ScriptProgramFactory.factory(TestUtil.testProgram, stack,script)
val newProgram = opSub(program)
}
it must "mark a script as invalid if we have an OP_SUB with nothing on the stack" in {
val stack = List()
val script = List(OP_SUB)
val program = ScriptProgramFactory.factory(TestUtil.testProgram, stack,script)
val newProgram = opSub(program)
newProgram.isValid must be (false)
}
it must "perform an OP_ABS on a negative number corectly" in {
@ -96,13 +96,12 @@ class ArithmeticInterpreterTest extends FlatSpec with MustMatchers with Arithmet
newProgram.stack.head must be (ScriptNumberFactory.zero)
newProgram.script.isEmpty must be (true)
}
it must "throw an exception if we have an OP_ABS with nothing on the stack" in {
intercept[IllegalArgumentException] {
val stack = List()
val script = List(OP_ABS)
val program = ScriptProgramFactory.factory(TestUtil.testProgram, stack,script)
val newProgram = opAbs(program)
}
it must "mark a script as invalid if we have an OP_ABS with nothing on the stack" in {
val stack = List()
val script = List(OP_ABS)
val program = ScriptProgramFactory.factory(TestUtil.testProgram, stack,script)
val newProgram = opAbs(program)
newProgram.isValid must be (false)
}
it must "perform an OP_NEGATE on a zero correctly" in {
@ -134,13 +133,13 @@ class ArithmeticInterpreterTest extends FlatSpec with MustMatchers with Arithmet
newProgram.stack.head must be (ScriptNumberFactory.one)
newProgram.script.isEmpty must be (true)
}
it must "throw an exception if we have an OP_NEGATE with nothing on the stack" in {
intercept[IllegalArgumentException] {
val stack = List()
val script = List(OP_NEGATE)
val program = ScriptProgramFactory.factory(TestUtil.testProgram, stack,script)
val newProgram = opNegate(program)
}
it must "mark a script as invalid if we have an OP_NEGATE with nothing on the stack" in {
val stack = List()
val script = List(OP_NEGATE)
val program = ScriptProgramFactory.factory(TestUtil.testProgram, stack,script)
val newProgram = opNegate(program)
newProgram.isValid must be (false)
}
it must "perform an OP_NOT correctly where 0 is the stack top" in {

View file

@ -42,13 +42,14 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
result.isValid must be (false)
}
it must "fail for OP_VERIFY when there is nothing on the stack" in {
intercept[IllegalArgumentException] {
val stack = List()
val script = List(OP_VERIFY)
val program = ScriptProgramFactory.factory(TestUtil.testProgram, stack,script)
val result = opVerify(program)
}
it must "mark the script as invalid for OP_VERIFY when there is nothing on the stack" in {
val stack = List()
val script = List(OP_VERIFY)
val program = ScriptProgramFactory.factory(TestUtil.testProgram, stack,script)
val result = opVerify(program)
result.isValid must be (false)
}
it must "fail for verify when there is nothing on the script stack" in {