Major refactor of types for ScriptOperations, overarching type is now ScriptToken

This commit is contained in:
Chris Stewart 2016-01-08 14:40:27 -06:00
parent b70bf70dd2
commit 731648ece1
13 changed files with 115 additions and 87 deletions

View File

@ -4,12 +4,15 @@ package org.scalacoin.script
* Created by chris on 1/6/16.
*/
trait ScriptOperation {
trait ScriptToken
trait ScriptOperation extends ScriptToken {
def opCode : Int
def hex : String = Integer.toHexString(opCode)
}
sealed trait Constant extends ScriptOperation
sealed trait ScriptConstant extends ScriptToken
/**
@ -17,168 +20,164 @@ sealed trait Constant extends ScriptOperation
*
* @param str
*/
case class ConstantImpl(str : String) extends ScriptOperation {
//TODO: Get around this op code some how, constants don't have op codes
override def opCode = -1
}
case class ScriptConstantImpl(str : String) extends ScriptConstant
/**
* An empty array of bytes is pushed onto the stack. (This is not a no-op: an item is added to the stack.)
*/
case object OP_0 extends Constant {
case object OP_0 extends ScriptOperation {
override def opCode = 0
}
/**
* An empty array of bytes is pushed onto the stack. (This is not a no-op: an item is added to the stack.)
*/
case object OP_FALSE extends Constant {
case object OP_FALSE extends ScriptOperation {
override def opCode = OP_0.opCode
}
/**
* The next byte contains the number of bytes to be pushed onto the stack.
*/
case object OP_PUSHDATA1 extends Constant {
case object OP_PUSHDATA1 extends ScriptOperation {
override def opCode = 76
}
/**
* The next two bytes contain the number of bytes to be pushed onto the stack.
*/
case object OP_PUSHDATA2 extends Constant {
case object OP_PUSHDATA2 extends ScriptOperation {
override def opCode = 77
}
/**
* The next four bytes contain the number of bytes to be pushed onto the stack.
*/
case object OP_PUSHDATA4 extends Constant {
case object OP_PUSHDATA4 extends ScriptOperation {
override def opCode = 78
}
/**
* The number -1 is pushed onto the stack.
*/
case object OP_1NEGATE extends Constant {
case object OP_1NEGATE extends ScriptOperation {
override def opCode = 79
}
/**
* The number 1 is pushed onto the stack.
*/
case object OP_TRUE extends Constant {
case object OP_TRUE extends ScriptOperation {
override def opCode = 81
}
/**
* The number 1 is pushed onto the stack.
*/
case object OP_1 extends Constant {
case object OP_1 extends ScriptOperation {
override def opCode = OP_TRUE.opCode
}
/**
* The number 2 is pushed onto the stack.
*/
case object OP_2 extends Constant {
case object OP_2 extends ScriptOperation {
override def opCode = 82
}
/**
* The number 3 is pushed onto the stack.
*/
case object OP_3 extends Constant {
case object OP_3 extends ScriptOperation {
override def opCode = 83
}
/**
* The number 4 is pushed onto the stack.
*/
case object OP_4 extends Constant {
case object OP_4 extends ScriptOperation {
override def opCode = 84
}
/**
* The number 5 is pushed onto the stack.
*/
case object OP_5 extends Constant {
case object OP_5 extends ScriptOperation {
override def opCode = 85
}
/**
* The number 6 is pushed onto the stack.
*/
case object OP_6 extends Constant {
case object OP_6 extends ScriptOperation {
override def opCode = 86
}
/**
* The number 7 is pushed onto the stack.
*/
case object OP_7 extends Constant {
case object OP_7 extends ScriptOperation {
override def opCode = 87
}
/**
* The number 8 is pushed onto the stack.
*/
case object OP_8 extends Constant {
case object OP_8 extends ScriptOperation {
override def opCode = 88
}
/**
* The number 9 is pushed onto the stack.
*/
case object OP_9 extends Constant {
case object OP_9 extends ScriptOperation {
override def opCode = 89
}
/**
* The number 10 is pushed onto the stack.
*/
case object OP_10 extends Constant {
case object OP_10 extends ScriptOperation {
override def opCode = 90
}
/**
* The number 11 is pushed onto the stack.
*/
case object OP_11 extends Constant {
case object OP_11 extends ScriptOperation {
override def opCode = 91
}
/**
* The number 12 is pushed onto the stack.
*/
case object OP_12 extends Constant {
case object OP_12 extends ScriptOperation {
override def opCode = 92
}
/**
* The number 13 is pushed onto the stack.
*/
case object OP_13 extends Constant {
case object OP_13 extends ScriptOperation {
override def opCode = 93
}
/**
* The number 14 is pushed onto the stack.
*/
case object OP_14 extends Constant {
case object OP_14 extends ScriptOperation {
override def opCode = 94
}
/**
* The number 15 is pushed onto the stack.
*/
case object OP_15 extends Constant {
case object OP_15 extends ScriptOperation {
override def opCode = 95
}
/**
* The number 16 is pushed onto the stack.
*/
case object OP_16 extends Constant {
case object OP_16 extends ScriptOperation {
override def opCode = 96
}

View File

@ -1,6 +1,6 @@
package org.scalacoin.script.bitwise
import org.scalacoin.script.ScriptOperation
import org.scalacoin.script.{ScriptConstantImpl, ScriptToken, ScriptOperation}
import org.scalacoin.script.control.{OP_VERIFY, ControlOperationsInterpreter}
/**
@ -14,11 +14,11 @@ trait BitwiseInterpreter extends ControlOperationsInterpreter {
* @param script
* @return
*/
def equal(stack : List[String], script : List[ScriptOperation]) : (List[String], List[ScriptOperation]) = {
def equal(stack : List[ScriptToken], script : List[ScriptToken]) : (List[ScriptToken], List[ScriptToken]) = {
require(stack.size > 1, "Stack size must be 2 or more to compare the top two values")
require(script.headOption.isDefined && script.head == OP_EQUAL, "Script operation must be OP_EQUAL")
val newStack = stack match {
case h :: h1 :: t => if (h == h1) "1" :: t else "0" :: t
case h :: h1 :: t => if (h == h1) ScriptConstantImpl("1") :: t else ScriptConstantImpl("0") :: t
}
(newStack,script.tail)
@ -30,7 +30,7 @@ trait BitwiseInterpreter extends ControlOperationsInterpreter {
* @param script
* @return
*/
def equalVerify(stack : List[String], script : List[ScriptOperation]) : Boolean = {
def equalVerify(stack : List[ScriptToken], script : List[ScriptToken]) : Boolean = {
require(stack.size > 1, "Stack size must be 2 or more to compare the top two values")
require(script.headOption.isDefined && script.head == OP_EQUALVERIFY, "Script operation must be OP_EQUALVERIFY")
//first replace OP_EQUALVERIFY with OP_EQUAL and OP_VERIFY

View File

@ -1,6 +1,6 @@
package org.scalacoin.script.control
import org.scalacoin.script.ScriptOperation
import org.scalacoin.script.{ScriptConstantImpl, ScriptToken, ScriptOperation}
/**
* Created by chris on 1/6/16.
@ -14,9 +14,9 @@ trait ControlOperationsInterpreter {
* @param script
* @return
*/
def verify(stack : List[String], script : List[ScriptOperation]) : Boolean = {
def verify(stack : List[ScriptToken], script : List[ScriptToken]) : Boolean = {
require(stack.size > 0, "Stack must not be empty to verify it")
require(script.headOption.isDefined && script.head == OP_VERIFY, "Top of script stack must be OP_VERIFY")
if (stack.head == "1") true else false
if (stack.head == ScriptConstantImpl("1")) true else false
}
}

View File

@ -1,6 +1,6 @@
package org.scalacoin.script.crypto
import org.scalacoin.script.ScriptOperation
import org.scalacoin.script.{ScriptConstantImpl, ScriptConstant, ScriptToken, ScriptOperation}
import org.scalacoin.util.ScalacoinUtil
@ -9,11 +9,14 @@ import org.scalacoin.util.ScalacoinUtil
*/
trait CryptoInterpreter extends ScalacoinUtil {
def hash160(stack : List[String], script : List[ScriptOperation]) : (List[String], List[ScriptOperation]) = {
def hash160(stack : List[ScriptToken], script : List[ScriptToken]) : (List[ScriptToken], List[ScriptToken]) = {
require(stack.headOption.isDefined, "The top of the stack must be defined")
require(script.headOption.isDefined && script.head == OP_HASH160, "Script operation must be OP_HASH160")
val stackTop = stack.head
val hash = sha256Hash160(stackTop)
val hash = stackTop match {
case ScriptConstantImpl(x) => sha256Hash160(x)
case _ => throw new RuntimeException("Stack top should be of type ScriptConstant to call hash160 on it")
}
(hash :: stack, script.tail)
}
@ -44,10 +47,10 @@ trait CryptoInterpreter extends ScalacoinUtil {
* @param hex
* @return
*/
private def sha256Hash160(hex : String) = {
private def sha256Hash160(hex : String) : ScriptConstant = {
val bytes = decodeHex(hex)
val hash = org.bitcoinj.core.Utils.sha256hash160(bytes)
encodeHex(hash)
ScriptConstantImpl(encodeHex(hash))
}
}

View File

@ -1,6 +1,6 @@
package org.scalacoin.script.interpreter
import org.scalacoin.script.{ConstantImpl, ScriptOperation}
import org.scalacoin.script.{ScriptToken, ScriptConstantImpl}
import org.scalacoin.script.bitwise.{OP_EQUAL, BitwiseInterpreter, OP_EQUALVERIFY}
import org.scalacoin.script.control.ControlOperationsInterpreter
import org.scalacoin.script.crypto.{OP_CHECKSIG, OP_HASH160, CryptoInterpreter}
@ -24,20 +24,20 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
* @return
*/
def run(inputScript : List[String], outputScript : List[ScriptOperation]) : Boolean = {
def run(inputScript : List[ScriptToken], outputScript : List[ScriptToken]) : Boolean = {
@tailrec
def loop(scripts : (List[String], List[ScriptOperation])) : Boolean = {
def loop(scripts : (List[ScriptToken], List[ScriptToken])) : Boolean = {
val (inputScript,outputScript) = (scripts._1, scripts._2)
outputScript match {
case OP_DUP :: t => loop(opDup(inputScript,outputScript))
case OP_HASH160 :: t => loop(hash160(inputScript,outputScript))
case OP_EQUAL :: t => loop(equal(inputScript, outputScript))
//TODO: Implement these
case ConstantImpl(x) :: t if x == "1" => throw new RuntimeException("Not implemented yet")
case ConstantImpl(x) :: t if x == "0" => throw new RuntimeException("Not implemented yet")
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")
//TODO: is this right? I need to just push a constant on the input stack???
case ConstantImpl(x) :: t => loop(x :: inputScript, outputScript.tail)
case ScriptConstantImpl(x) :: t => loop((ScriptConstantImpl(x) :: inputScript, t))
//these cases result in our boolean result
case OP_EQUALVERIFY :: t => equalVerify(inputScript,outputScript)
/*case OP_CHECKSIG :: t => checkSig(inputScript,outputScript)*/

View File

@ -1,6 +1,6 @@
package org.scalacoin.script.parsing
import org.scalacoin.script.ScriptOperation
import org.scalacoin.script._
import scala.annotation.tailrec
@ -16,25 +16,24 @@ trait ScriptParser extends {
* @tparam T
* @return
*/
def parseInputScript(str : String) : List[String] = {
str.split(" ").toList
}
def parseInputScript(str : String) : List[ScriptToken] = ???
/**
* Parses an output script of a transaction
* @param str
* @return
*/
/* def parseOutputScript(str : String) : List[ScriptOperation] = {
def parseOutputScript(str : String) : List[ScriptToken] = {
@tailrec
def loop(operations : List[String], accum : List[ScriptOperation]) : List[ScriptOperation] = {
def loop(operations : List[String], accum : List[ScriptToken]) : List[ScriptToken] = {
operations match {
case h :: t if ()
case h :: t if (ScriptOperationFactory.fromString(h).isDefined) =>
loop(t,ScriptOperationFactory.fromString(h).get :: accum)
case h :: t => loop(t, ScriptConstantImpl(h) :: accum)
case h :: t if (h == "0") => loop(t, OP_0 :: accum)
case Nil => accum
}
}
loop(str.split(" ").toList, List())
}*/
loop(str.split(" ").toList.reverse, List())
}
}

View File

@ -1,6 +1,6 @@
package org.scalacoin.script.stack
import org.scalacoin.script.ScriptOperation
import org.scalacoin.script.{ScriptToken, ScriptOperation}
/**
* Created by chris on 1/6/16.
@ -13,7 +13,7 @@ trait StackInterpreter {
* @param script
* @return
*/
def opDup(stack : List[String], script : List[ScriptOperation]) : (List[String], List[ScriptOperation]) = {
def opDup(stack : List[ScriptToken], script : List[ScriptToken]) : (List[ScriptToken], List[ScriptToken]) = {
require(script.headOption.isDefined && script.head == OP_DUP, "Top of the script stack must be OP_DUP")
require(stack.headOption.isDefined, "Cannot duplicate the top element on an empty stack")
stack match {

View File

@ -1,19 +1,20 @@
package org.scalacoin.script.bitwise
import org.scalacoin.script.ScriptConstantImpl
import org.scalatest.{MustMatchers, FlatSpec}
/**
* Created by chris on 1/6/16.
*/
class BitwiseInterpreterTest extends FlatSpec with MustMatchers with BitwiseInterpreter {
private val pubKeyHash = "5238C71458E464D9FF90299ABCA4A1D7B9CB76AB".toLowerCase
private val pubKeyHash = ScriptConstantImpl("5238C71458E464D9FF90299ABCA4A1D7B9CB76AB".toLowerCase)
"BitwiseInterpreter" must "evaluate OP_EQUAL" in {
val stack = List(pubKeyHash, pubKeyHash)
val script = List(OP_EQUAL)
val (newStack,newScript) = equal(stack,script)
newStack.head.toInt must be (1)
newStack.head must be (ScriptConstantImpl("1"))
}
it must "throw an exception for OP_EQUAL when we don't have enough items on the stack" in {
@ -36,7 +37,7 @@ class BitwiseInterpreterTest extends FlatSpec with MustMatchers with BitwiseInte
}
it must "evaluate OP_EQUALVERIFY to false given two different pub keys" in {
val uniquePubKey = pubKeyHash +"0"
val uniquePubKey = ScriptConstantImpl(pubKeyHash +"0")
val stack = List(pubKeyHash,uniquePubKey)
val script = List(OP_EQUALVERIFY)
val result = equalVerify(stack,script)

View File

@ -1,5 +1,6 @@
package org.scalacoin.script.control
import org.scalacoin.script.ScriptConstantImpl
import org.scalatest.{MustMatchers, FlatSpec}
/**
@ -8,21 +9,21 @@ import org.scalatest.{MustMatchers, FlatSpec}
class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with ControlOperationsInterpreter {
"ControlOperationsInterpreter" must "have OP_VERIFY evaluate to true with '1' on the stack" in {
val stack = List("1")
val stack = List(ScriptConstantImpl("1"))
val script = List(OP_VERIFY)
val result = verify(stack,script)
result must be (true)
}
it must "have OP_VERIFY evaluate to false with '0' on the stack" in {
val stack = List("0")
val stack = List(ScriptConstantImpl("0"))
val script = List(OP_VERIFY)
val result = verify(stack,script)
result must be (false)
}
it must "have OP_VERIFY evaluate to false with '2' on the stack" in {
val stack = List("2")
val stack = List(ScriptConstantImpl("2"))
val script = List(OP_VERIFY)
val result = verify(stack,script)
result must be (false)
@ -38,7 +39,7 @@ class ControlOperationsInterpreterTest extends FlatSpec with MustMatchers with C
it must "fail for verify when there is nothing on the script stack" in {
intercept[IllegalArgumentException] {
val stack = List("1")
val stack = List(ScriptConstantImpl("1"))
val script = List()
val result = verify(stack,script)
}

View File

@ -1,7 +1,7 @@
package org.scalacoin.script.interpreter
import org.scalacoin.script.bitwise.OP_EQUALVERIFY
import org.scalacoin.script.{ConstantImpl, ScriptOperation}
import org.scalacoin.script.{ ScriptOperation}
import org.scalacoin.script.crypto.{OP_CHECKSIG, OP_HASH160}
import org.scalacoin.script.stack.OP_DUP
import org.scalatest.{MustMatchers, FlatSpec}

View File

@ -10,12 +10,22 @@ class ScriptParserTest extends FlatSpec with MustMatchers with ScriptParser {
"ScriptParser" must "parse an input script" in {
val parsedInput = parseInputScript(TestUtil.p2pkhInputScriptNotParsed)
parsedInput must be (TestUtil.p2pkhInputScript)
/* val parsedInput = parseInputScript(TestUtil.p2pkhInputScriptNotParsedAsm)
parsedInput must be (TestUtil.p2pkhInputScriptAsm)*/
}
it must "parse an output script" in {
val parsedOutput = parseOutputScript(TestUtil.p2pkhOutputScriptNotParsed)
parsedOutput must be (TestUtil.p2pkhOutputScript)
it must "parse a pay-to-pubkey-hash output script" in {
val parsedOutput = parseOutputScript(TestUtil.p2pkhOutputScriptNotParsedAsm)
parsedOutput must be (TestUtil.p2pkhOutputScriptAsm)
}
it must "parse a pay-to-script-hash output script" in {
val parsedOutput = parseOutputScript(TestUtil.p2shOutputScriptNotParsedAsm)
parsedOutput must be (TestUtil.p2shOutputScriptAsm)
}
/* it must "parse a pay-to-script-hash input script" in {
val parsedInput = parseInput
}*/
}

View File

@ -1,26 +1,26 @@
package org.scalacoin.script.stack
import org.scalacoin.script.ScriptConstantImpl
import org.scalatest.{FlatSpec, MustMatchers}
/**
* Created by chris on 1/6/16.
*/
class StackInterpreterTest extends FlatSpec with MustMatchers with StackInterpreter {
val stack = List(ScriptConstantImpl("Hello"),ScriptConstantImpl("World"))
"StackInterpreter" must "duplicate elements on top of the stack" in {
val stack = List("Hello","World")
val script = List(OP_DUP)
val (newStack,newScript) = opDup(stack,script)
newStack.head must be ("Hello")
newStack(1) must be ("Hello")
newStack(2) must be ("World")
newStack.head must be (ScriptConstantImpl("Hello"))
newStack(1) must be (ScriptConstantImpl("Hello"))
newStack(2) must be (ScriptConstantImpl("World"))
}
it must "throw an exception when calling opDup without an OP_DUP on top of the script stack" in {
intercept[IllegalArgumentException] {
val stack = List("Hello","World")
val script = List()
val (newStack,newScript) = opDup(stack,script)
}

View File

@ -1,8 +1,8 @@
package org.scalacoin.util
import org.scalacoin.protocol.{AssetAddress, BitcoinAddress}
import org.scalacoin.script.ConstantImpl
import org.scalacoin.script.bitwise.OP_EQUALVERIFY
import org.scalacoin.script.ScriptConstantImpl
import org.scalacoin.script.bitwise.{OP_EQUAL, OP_EQUALVERIFY}
import org.scalacoin.script.crypto.{OP_CHECKSIG, OP_HASH160}
import org.scalacoin.script.stack.OP_DUP
@ -17,11 +17,26 @@ object TestUtil {
val multiSigAddress = BitcoinAddress("342ftSRCvFHfCeFFBuz4xwbeqnDw6BGUey")
val assetAddress = AssetAddress("akJsoCcyh34FGPotxfEoSXGwFPCNAkyCgTA")
val p2pkhInputScriptNotParsed =
val p2pkhInputScriptNotParsedAsm =
"3044022016ffdbb7c57634903c5e018fcfc48d59f4e37dc4bc3bbc9ba4e6ee39150bca030220119c2241a931819bc1a75d3596e4029d803d1cd6de123bf8a1a1a2c3665e1fac01" +
" 02af7dad03e682fcd0427b5c24140c220ac9d8abe286c15f8cf5bf77eed19c3652"
val p2pkhInputScript = List("3044022016ffdbb7c57634903c5e018fcfc48d59f4e37dc4bc3bbc9ba4e6ee39150bca030220119c2241a931819bc1a75d3596e4029d803d1cd6de123bf8a1a1a2c3665e1fac01",
val p2pkhInputScriptAsm = List("3044022016ffdbb7c57634903c5e018fcfc48d59f4e37dc4bc3bbc9ba4e6ee39150bca030220119c2241a931819bc1a75d3596e4029d803d1cd6de123bf8a1a1a2c3665e1fac01",
"02af7dad03e682fcd0427b5c24140c220ac9d8abe286c15f8cf5bf77eed19c3652")
val p2pkhOutputScriptNotParsed = "OP_DUP OP_HASH160 e2e7c1ab3f807151e832dd1accb3d4f5d7d19b4b OP_EQUALVERIFY OP_CHECKSIG"
val p2pkhOutputScript = List(OP_DUP,OP_HASH160,ConstantImpl("e2e7c1ab3f807151e832dd1accb3d4f5d7d19b4b"),OP_EQUALVERIFY, OP_CHECKSIG)
val p2pkhOutputScriptNotParsedAsm = "OP_DUP OP_HASH160 e2e7c1ab3f807151e832dd1accb3d4f5d7d19b4b OP_EQUALVERIFY OP_CHECKSIG"
val p2pkhOutputScriptAsm = List(OP_DUP,OP_HASH160,ScriptConstantImpl("e2e7c1ab3f807151e832dd1accb3d4f5d7d19b4b"),OP_EQUALVERIFY, OP_CHECKSIG)
val p2shInputScriptNotParsedAsm =
"""0
|3045022100884c8a4776f4aa2a70f25f6bc0071929ade0ff4987844347e051e018c267aae402201fcec5dd052e7b01198bb57e1b58696c38ccd9d0b408c55047cac89b47287b4101
|3045022100b064d492712a080b726ecf37de2957b783fa411edae33bd13005e62d6a45d02302201b82b632df54cf1204758c2b5a3599f1f9a80da3d508951695bfcf8d2c2cce0f01
|522102632178d046673c9729d828cfee388e121f497707f810c131e0d3fc0fe0bd66d62103a0951ec7d3a9da9de171617026442fcd30f34d66100fab539853b43f508787d452ae
|""".stripMargin
val p2shInputScript = List(
)
val p2shOutputScriptNotParsedAsm = "OP_HASH160 8ce5408cfeaddb7ccb2545ded41ef47810945484 OP_EQUAL"
val p2shOutputScriptAsm = List(OP_HASH160, ScriptConstantImpl("8ce5408cfeaddb7ccb2545ded41ef47810945484"), OP_EQUAL)
}