Implementing OP_RIPEMD160, OP_SHA256, OP_HASH256 script operations, fixing bug inside of CryptoUtil with serializing to and from hex

This commit is contained in:
Chris Stewart 2016-02-07 14:04:51 -06:00
parent a9926e6cba
commit d2cf8df898
4 changed files with 134 additions and 27 deletions

View File

@ -12,17 +12,62 @@ import org.scalacoin.util.{CryptoUtil, ScalacoinUtil}
*/
trait CryptoInterpreter extends ScalacoinUtil {
def hash160(program : ScriptProgram) : ScriptProgram = {
require(program.stack.headOption.isDefined, "The top of the stack must be defined")
/**
* The input is hashed twice: first with SHA-256 and then with RIPEMD-160.
* @param program
* @return
*/
def opHash160(program : ScriptProgram) : ScriptProgram = {
require(program.stack.headOption.isDefined, "The top of the stack must be defined for OP_HASH160")
require(program.script.headOption.isDefined && program.script.head == OP_HASH160, "Script operation must be OP_HASH160")
val stackTop = program.stack.head
val hash = stackTop match {
case ScriptConstantImpl(x) => CryptoUtil.sha256Hash160(x)
case _ => throw new RuntimeException("Stack top should be of type ScriptConstant to call hash160 on it")
}
val hash = ScriptConstantImpl(ScalacoinUtil.encodeHex(CryptoUtil.sha256Hash160(stackTop.bytes)))
ScriptProgramImpl(hash :: program.stack, program.script.tail,program.transaction, program.altStack)
}
/**
* The input is hashed using RIPEMD-160.
* @param program
* @return
*/
def opRipeMd160(program : ScriptProgram) : ScriptProgram = {
require(program.stack.headOption.isDefined, "The top of the stack must be defined for OP_RIPEMD160")
require(program.script.headOption.isDefined && program.script.head == OP_RIPEMD160, "Script operation must be OP_RIPEMD160")
val stackTop = program.stack.head
val hash = CryptoUtil.ripeMd160(stackTop.bytes)
val newStackTop = ScriptConstantImpl(ScalacoinUtil.encodeHex(hash))
ScriptProgramImpl(newStackTop :: program.stack.tail, program.script.tail,program.transaction, program.altStack)
}
/**
* The input is hashed using SHA-256.
* @param program
* @return
*/
def opSha256(program : ScriptProgram) : ScriptProgram = {
require(program.stack.headOption.isDefined, "The top of the stack must be defined for OP_SHA256")
require(program.script.headOption.isDefined && program.script.head == OP_SHA256, "Script operation must be OP_SHA256")
val stackTop = program.stack.head
val hash = CryptoUtil.sha256(stackTop.bytes)
val newStackTop = ScriptConstantImpl(ScalacoinUtil.encodeHex(hash))
ScriptProgramImpl(newStackTop :: program.stack.tail, program.script.tail,program.transaction, program.altStack)
}
/**
* The input is hashed two times with SHA-256.
* @param program
* @return
*/
def opHash256(program : ScriptProgram) : ScriptProgram = {
require(program.stack.headOption.isDefined, "The top of the stack must be defined for OP_HASH256")
require(program.script.headOption.isDefined && program.script.head == OP_HASH256, "Script operation must be OP_HASH256")
val stackTop = program.stack.head
val hash = CryptoUtil.doubleSHA256(stackTop.bytes)
val newStackTop = ScriptConstantImpl(ScalacoinUtil.encodeHex(hash))
ScriptProgramImpl(newStackTop :: program.stack.tail, program.script.tail,program.transaction, program.altStack)
}
/**
* The entire transaction's outputs, inputs, and script (from the most
* recently-executed OP_CODESEPARATOR to the end) are hashed.
@ -70,10 +115,10 @@ trait CryptoInterpreter extends ScalacoinUtil {
???
}
/**
* The input is hashed using SHA-1.
* @param stack
* @param script
* @param program
* @return
*/
def opSha1(program : ScriptProgram) : ScriptProgram = {

View File

@ -8,7 +8,7 @@ import org.scalacoin.script.arithmetic._
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_SHA1, OP_CHECKSIG, OP_HASH160, CryptoInterpreter}
import org.scalacoin.script.crypto._
import org.scalacoin.script.reserved.NOP
import org.scalacoin.script.stack._
import org.slf4j.LoggerFactory
@ -121,10 +121,12 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
else false
//crypto operations
case OP_HASH160 :: t => loop(hash160(program))
case OP_HASH160 :: t => loop(opHash160(program))
case OP_CHECKSIG :: t => checkSig(program.stack,program.script,fullScript)
case OP_SHA1 :: t => loop(opSha1(program))
case OP_RIPEMD160 :: t => loop(opRipeMd160(program))
case OP_SHA256 :: t => loop(opSha256(program))
case OP_HASH256 :: t => loop(opHash256(program))
//reserved operations
case (nop : NOP) :: t => loop(ScriptProgramImpl(program.stack,t,program.transaction,program.altStack))

View File

@ -4,6 +4,7 @@ import java.security.MessageDigest
import org.bitcoinj.core.Sha256Hash
import org.scalacoin.script.constant.{ScriptConstantImpl, ScriptConstant}
import org.spongycastle.crypto.digests.RIPEMD160Digest
/**
* Created by chris on 1/14/16.
@ -17,30 +18,43 @@ trait CryptoUtil extends ScalacoinUtil {
* @param hex
* @return
*/
def sha256Hash160(hex : String) : ScriptConstant = {
val bytes = decodeHex(hex)
def sha256Hash160(hex : String) : List[Byte] = sha256Hash160(decodeHex(hex))
def sha256Hash160(bytes : List[Byte]) = {
val hash = org.bitcoinj.core.Utils.sha256hash160(bytes.toArray)
ScriptConstantImpl(encodeHex(hash))
hash.toList
}
/**
* Performs sha256(sha256(hex))
* @param hex
* @return
*/
def doubleSHA256(hex : String) : String = {
doubleSHA256(decodeHex(hex))
}
def doubleSHA256(hex : String) : List[Byte] = doubleSHA256(decodeHex(hex))
/**
* Performs sha256(sha256(hex))
* @param hex
* @return
*/
def doubleSHA256(bytes : List[Byte]) : String = {
def doubleSHA256(bytes : List[Byte]) : List[Byte] = {
val hash : List[Byte] = Sha256Hash.hashTwice(bytes.toArray).toList
encodeHex(hash.reverse)
hash
}
/**
* Takes sha256(hex)
* @param str
* @return
*/
def sha256(hex : String) : List[Byte] = sha256(decodeHex(hex))
/**
* Takes sha256(bytes)
* @param bytes
* @return
*/
def sha256(bytes : List[Byte]) : List[Byte] = {
val hash : List[Byte] = Sha256Hash.hash(bytes.toArray).toList
hash
}
/**
@ -53,11 +67,37 @@ trait CryptoUtil extends ScalacoinUtil {
/**
* Performs SHA1(str)
* Performs SHA1(hex)
* @param hex
* @return
*/
def sha1(str : String) : List[Byte] = sha1(str.map(_.toByte).toList)
def sha1(hex : String) : List[Byte] = sha1(decodeHex(hex))
/**
* Performs RIPEMD160(hex)
* @param str
* @return
*/
def ripeMd160(hex : String) : List[Byte] = ripeMd160(decodeHex(hex))
/**
* Performs RIPEMD160(bytes)
* @param bytes
* @return
*/
def ripeMd160(bytes : List[Byte]) : List[Byte] = {
//from this tutorial http://rosettacode.org/wiki/RIPEMD-160#Scala
val messageDigest = new RIPEMD160Digest
val raw = bytes.toArray
messageDigest.update(raw, 0, raw.length)
val out = Array.fill[Byte](messageDigest.getDigestSize())(0)
messageDigest.doFinal(out, 0)
out.toList
}
}
object CryptoUtil extends CryptoUtil

View File

@ -13,8 +13,28 @@ class CryptoUtilTest extends FlatSpec with MustMatchers {
ScalacoinUtil.encodeHex(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"
ScalacoinUtil.encodeHex(CryptoUtil.sha1(str)) must be ("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12")
it must "perform the correct RIPEMD160 on a string" in {
val str = ""
val expectedDigest = "9c1185a5c5e9fc54612808977ee8f548b2258d31"
ScalacoinUtil.encodeHex(CryptoUtil.ripeMd160(str)) must be (expectedDigest)
}
it must "perform a RIPEMD160 on a SHA256 hash to generate a bitcoin address" in {
//https://bitcoin.stackexchange.com/questions/37040/ripemd160sha256publickey-where-am-i-going-wrong
val str = "ea571f53cb3a9865d3dc74735e0c16643d319c6ad81e199b9c8408cecbcec7bb"
val expected = "5238c71458e464d9ff90299abca4a1d7b9cb76ab"
ScalacoinUtil.encodeHex(CryptoUtil.ripeMd160(str)) must be (expected)
}
it must "perform a single SHA256 hash" in {
val hex = ""
val strBytes = ScalacoinUtil.decodeHex(hex)
val expected = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
ScalacoinUtil.encodeHex(CryptoUtil.sha256(strBytes)) must be (expected)
ScalacoinUtil.encodeHex(CryptoUtil.sha256(hex)) must be (expected)
}
}