Fixing bug wrt to script numbers not pushing the correct amount of bytes onto the stack

This commit is contained in:
Chris Stewart 2016-01-26 11:26:08 -06:00
parent 787695dcdf
commit 7c631a52b7
7 changed files with 105 additions and 17 deletions

View file

@ -70,11 +70,41 @@ trait ConstantInterpreter {
val (newStack,newScript) = opPushData(stack,slicedScript,numberOfBytes)
(newStack,newScript)
}
}
/**
* Pushes the number of bytes onto the stack that is specified by script number on the script stack
* @param stack
* @param script
* @return
*/
def pushScriptNumberBytesToStack(stack : List[ScriptToken], script : List[ScriptToken]) : (List[ScriptToken], List[ScriptToken]) = {
require(script.headOption.isDefined && script.head.isInstanceOf[ScriptNumber], "Top of script must be a script number")
require(script.size > 1, "Script size must be atleast to to push constants onto the stack")
val bytesNeeded = script.head match {
case scriptNumber : ScriptNumber => scriptNumber.opCode
}
/**
* Parses the script tokens that need to be pushed onto our stack
* @param scriptToken
* @param accum
* @return
*/
@tailrec
def takeUntilBytesNeeded(scriptTokens : List[ScriptToken], accum : List[ScriptToken]) : (List[ScriptToken],List[ScriptToken]) = {
if (accum.map(_.bytes.size).sum == bytesNeeded) (scriptTokens,accum)
else if (accum.map(_.bytes.size).sum > bytesNeeded) throw new RuntimeException("We cannot have more bytes than what our script number specified")
else takeUntilBytesNeeded(scriptTokens.tail, scriptTokens.head :: accum)
}
val (newScript,tokensToBePushed) = takeUntilBytesNeeded(script.tail,List())
(tokensToBePushed ++ stack, newScript)
}
/**
* Responsible for pushing the amount of bytes specified by the param numberOfBytes onto the stack
* @param stack

View file

@ -34,9 +34,9 @@ trait ControlOperationsInterpreter {
val (opElseIndex,opEndIfIndex) = findIndexesOpElseOpEndIf(script)
stack.head match {
case OP_0 =>
//need to remove the statements from the script since
//they should not be executed
//means that we need to execute the OP_ELSE statement if one exists
//need to remove the OP_IF expression from the script
//since it should not be executed
require(opEndIfIndex.isDefined,"Every OP_IF must have a matching OP_ENDIF statement")
//means that we have an else statement which needs to be executed
if (opElseIndex.isDefined) {
@ -50,6 +50,8 @@ trait ControlOperationsInterpreter {
(stack.tail,newScript)
}
case _ =>
//means that we need to execute the OP_IF expression
//and delete its corresponding OP_ELSE if one exists
if (opElseIndex.isDefined) {
logger.debug("OP_ELSE index: " + opElseIndex.get)
logger.debug("OP_ENDIF index: " + opEndIfIndex.get)
@ -58,16 +60,15 @@ trait ControlOperationsInterpreter {
val scriptPart1 = script.slice(1,opElseIndex.get)
val scriptWithoutOpElse = script.zipWithIndex.filter(_._2 != opElseIndex.get).map(_._1)
//val scriptPart2 = script.slice(opEndIfIndex.get,script.size)
//val newScript = scriptPart1 ++ scriptPart2
val newOpElseIndex = findOpElse(scriptWithoutOpElse)
val scriptPart2 = if (newOpElseIndex.isDefined) {
//means that we have another OP_ELSE before our OP_ENDIF.
val scriptPart2 = if (newOpElseIndex.isDefined && newOpElseIndex.get < opEndIfIndex.get) {
//the +1 is because we removed the OP_ELSE
script.slice(newOpElseIndex.get+1,script.size)
} else script.slice(opEndIfIndex.get,script.size)
val newScript = scriptPart1 ++ scriptPart2
(stack.tail,newScript)
} else (stack.tail,script.tail)

View file

@ -69,6 +69,21 @@ trait CryptoInterpreter extends ScalacoinUtil {
???
}
/**
* The input is hashed using SHA-1.
* @param stack
* @param script
* @return
*/
def opSha1(stack : List[ScriptToken], script : List[ScriptToken]) : (List[ScriptToken], List[ScriptToken]) = {
require(script.headOption.isDefined && script.head == OP_SHA1, "Script top must be OP_SHA1")
require(stack.headOption.isDefined, "We must have an element on the stack for OP_SHA1")
val constant = stack.head
val hash = ScriptConstantImpl(CryptoUtil.sha1(constant.hex))
(hash :: stack.tail, script.tail)
}
private def hashForSignature(inputScript : Seq[ScriptToken], spendingTx : Transaction,
inputIndex : Int, hashType : HashType) : String = {
@ -84,7 +99,6 @@ trait CryptoInterpreter extends ScalacoinUtil {
???
}

View file

@ -5,7 +5,7 @@ import org.scalacoin.script.arithmetic.{ArithmeticInterpreter, OP_ADD}
import org.scalacoin.script.bitwise.{OP_EQUAL, BitwiseInterpreter, OP_EQUALVERIFY}
import org.scalacoin.script.constant._
import org.scalacoin.script.control._
import org.scalacoin.script.crypto.{OP_CHECKSIG, OP_HASH160, CryptoInterpreter}
import org.scalacoin.script.crypto.{OP_SHA1, OP_CHECKSIG, OP_HASH160, CryptoInterpreter}
import org.scalacoin.script.reserved.NOP
import org.scalacoin.script.stack.{OP_DEPTH, StackInterpreter, OP_DUP}
import org.slf4j.LoggerFactory
@ -57,10 +57,11 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
case ScriptConstantImpl(x) :: t if x == "1" => throw new RuntimeException("Not implemented yet")
case ScriptConstantImpl(x) :: t if x == "0" => throw new RuntimeException("Not implemented yet")
case (scriptNumberOp : ScriptNumberOperation) :: t => loop(scriptNumberOp.scriptNumber :: stack, t)
case (scriptNumber : ScriptNumber) :: t => loop(scriptNumber :: stack, t)
case (scriptNumber : ScriptNumber) :: t => loop(pushScriptNumberBytesToStack(stack,script))
case OP_PUSHDATA1 :: t => loop(opPushData1(stack,script))
case OP_PUSHDATA2 :: t => loop(opPushData2(stack,script))
case OP_PUSHDATA4 :: t => loop(opPushData4(stack,script))
//TODO: is this right? I need to just push a constant on the input stack???
case ScriptConstantImpl(x) :: t => loop((ScriptConstantImpl(x) :: stack, t))
@ -73,6 +74,7 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
//crypto operations
case OP_HASH160 :: t => loop(hash160(stack,script))
case OP_CHECKSIG :: t => checkSig(stack,script,fullScript)
case OP_SHA1 :: t => loop(opSha1(stack,script))
//reserved operations
case (nop : NOP) :: t => loop((stack,t))

View file

@ -1,5 +1,7 @@
package org.scalacoin.util
import java.security.MessageDigest
import org.bitcoinj.core.Sha256Hash
import org.scalacoin.script.constant.{ScriptConstantImpl, ScriptConstant}
@ -40,6 +42,23 @@ trait CryptoUtil extends ScalacoinUtil {
val hash : List[Byte] = Sha256Hash.hashTwice(bytes.toArray).toList
encodeHex(hash.reverse)
}
/**
* Performs SHA1(bytes)
* @param bytes
* @return
*/
def sha1(bytes : List[Byte]) : String = {
val hashBytes = MessageDigest.getInstance("SHA-1").digest(bytes.toArray)
encodeHex(hashBytes)
}
/**
* Performs SHA1(str)
* @param hex
* @return
*/
def sha1(str : String) : String = sha1(str.map(_.toByte).toList)
}
object CryptoUtil extends CryptoUtil

View file

@ -3,7 +3,7 @@ package org.scalacoin.script.interpreter
import java.io.File
import org.scalacoin.script.bitwise.{OP_EQUAL, OP_EQUALVERIFY}
import org.scalacoin.script.constant.{OP_0, OP_PUSHDATA1, ScriptToken}
import org.scalacoin.script.constant._
import org.scalacoin.script.crypto.{OP_CHECKSIG, OP_HASH160}
import org.scalacoin.script.interpreter.testprotocol.{CoreTestCaseProtocol, CoreTestCase}
import org.scalacoin.script.stack.OP_DUP
@ -30,12 +30,14 @@ class ScriptInterpreterTest extends FlatSpec with MustMatchers with ScriptInterp
}
it must "evaluate a script with OP_PUSHDATA operations" in {
val stack = List()
val script = List(OP_PUSHDATA1, OP_0, OP_0, OP_EQUAL)
it must "evaluate a script that asks to push 20 bytes onto the stack correctly" in {
val stack = List(ScriptConstantImpl("68ca4fec736264c13b859bac43d5173df6871682"))
val script = List(ScriptNumberImpl(20), ScriptConstantImpl("68ca4fec736264c13b859bac43d5173df6871682"), OP_EQUAL)
run(stack,script) must be (true)
}
it must "evaluate all valid scripts from the bitcoin core script_valid.json" in {
import CoreTestCaseProtocol._

View file

@ -0,0 +1,20 @@
package org.scalacoin.util
import org.scalatest.{FlatSpec, MustMatchers}
/**
* Created by chris on 1/26/16.
*/
class CryptoUtilTest extends FlatSpec with MustMatchers {
"CryptoUtil" must "perform a SHA-1 hash" in {
val hash = CryptoUtil.sha1("")
hash must be ("da39a3ee5e6b4b0d3255bfef95601890afd80709")
}
it must "perform the correct SHA-1 hash on a mneomnic seed" in {
val str = "The quick brown fox jumps over the lazy dog"
CryptoUtil.sha1(str) must be ("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12")
}
}