Skip to content

Commit

Permalink
fix one more witness test
Browse files Browse the repository at this point in the history
  • Loading branch information
h0ngcha0 committed Jan 6, 2018
1 parent 308b0fa commit 537dde6
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 65 deletions.
48 changes: 26 additions & 22 deletions src/main/scala/me/hongchao/bitcoin4s/script/Interpreter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -374,28 +374,32 @@ object Interpreter {
case head :: Nil =>
tailRecMEvaluated(head.bytes.toBoolean())
case head :: tail =>
val maybeRebuiltScriptPubkeyAndStackFromWitness = tryRebuildScriptPubkeyAndStackFromWitness(state.scriptPubKey, state.scriptWitnessStack)

if (state.ScriptFlags.witness() && maybeRebuiltScriptPubkeyAndStackFromWitness.isDefined) {
val (rebuiltScriptPubkey, rebuiltStack) = maybeRebuiltScriptPubkeyAndStackFromWitness.get

for {
_ <- setState(state.copy(
currentScript = rebuiltScriptPubkey,
stack = rebuiltStack,
altStack = Seq.empty,
opCount = 0,
scriptWitness = Some(rebuiltScriptPubkey),
sigVersion = SigVersion.SIGVERSION_WITNESS_V0,
scriptExecutionStage = ExecutingScriptWitness
))
_ <- checkInvalidOpCode()
_ <- checkDisabledOpCode()
_ <- checkMaxPushSize()
_ <- checkMaxScriptSize()
} yield {
Left(None)
if (state.ScriptFlags.witness()) {

tryRebuildScriptPubkeyAndStackFromWitness(state.scriptPubKey, state.scriptWitnessStack) match {
case Some((rebuiltScriptPubkey, rebuiltStack)) =>
for {
_ <- setState(state.copy(
currentScript = rebuiltScriptPubkey,
stack = rebuiltStack,
altStack = Seq.empty,
opCount = 0,
scriptWitness = Some(rebuiltScriptPubkey),
sigVersion = SigVersion.SIGVERSION_WITNESS_V0,
scriptExecutionStage = ExecutingScriptWitness
))
_ <- checkInvalidOpCode()
_ <- checkDisabledOpCode()
_ <- checkMaxPushSize()
_ <- checkMaxScriptSize()
} yield {
Left(None)
}

case None =>
tailRecMAbort(GeneralError(OP_UNKNOWN, state))
}

} else if (state.ScriptFlags.p2sh() && isP2SHScript(state.scriptPubKey)) {
getSerializedScript(state.scriptSig) match {
case Some(serializedScript) =>
Expand Down Expand Up @@ -482,7 +486,7 @@ object Interpreter {
// P2WPSH
for {
last <- witnessStack.lastOption
scriptPubKey <- (Hash.Hash256(last.bytes.toArray).toSeq == witnessHash.bytes).option(Parser.parse(last.bytes))
scriptPubKey <- (Hash.Sha256(last.bytes.toArray).toSeq == witnessHash.bytes).option(Parser.parse(last.bytes))
} yield {
(scriptPubKey, removePushOps(witnessStack.dropRight(1)))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package me.hongchao.bitcoin4s.script

import me.hongchao.bitcoin4s.script.OpCodes.OP_UNKNOWN

sealed trait InterpreterError extends RuntimeException with Product {
val opCode: ScriptOpCode
def opCode: ScriptOpCode
val state: InterpreterState
val description: String

Expand Down Expand Up @@ -139,4 +141,9 @@ object InterpreterError {
case class NotImplemented(opCode: ScriptOpCode, state: InterpreterState) extends InterpreterError {
val description: String = "Not implemented"
}

// FIXME: Error should all be specific
case class GeneralError(opCode: ScriptOpCode, state: InterpreterState) extends InterpreterError {
val description: String = "General error"
}
}
80 changes: 41 additions & 39 deletions src/main/scala/me/hongchao/bitcoin4s/script/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,52 +69,54 @@ object Parser {
}

@tailrec
private def parse(bytes: Seq[Byte], acc: Seq[Seq[ScriptElement]]): Seq[Seq[ScriptElement]] = bytes match {
case Nil => acc
case head :: tail =>
val opCode = OpCodes.all
.find(_.hex == head.toHex)
.orElse {
val opCodeValue = Integer.parseInt(head.toHex, 16)
val isInvalidOpCode = (opCodeValue > OP_NOP10.value) && (opCodeValue < OP_INVALIDOPCODE.value)
isInvalidOpCode.option(OP_INVALIDOPCODE)
}
.getOrElse {
// FIXME: better exception
throw new RuntimeException(s"No opcode found: $bytes")
}

def pushData(opCode: ScriptOpCode, numberOfBytesToPush: Int, restOfData: Seq[Byte]): (Seq[Byte], Seq[Seq[ScriptElement]]) = {
val maybeBytesToPush = restOfData.takeOpt(numberOfBytesToPush)
val restOfBytes = restOfData.drop(numberOfBytesToPush)
val maybeConstantToBePushed = maybeBytesToPush.map { bytesToPush =>
(bytesToPush.isEmpty).option(OP_0).getOrElse(ScriptConstant(bytesToPush))
private def parse(bytes: Seq[Byte], acc: Seq[Seq[ScriptElement]]): Seq[Seq[ScriptElement]] = {
bytes match {
case Nil => acc
case head +: tail =>
val opCode = OpCodes.all
.find(_.hex == head.toHex)
.orElse {
val opCodeValue = Integer.parseInt(head.toHex, 16)
val isInvalidOpCode = (opCodeValue > OP_NOP10.value) && (opCodeValue < OP_INVALIDOPCODE.value)
isInvalidOpCode.option(OP_INVALIDOPCODE)
}
.getOrElse {
// FIXME: better exception
throw new RuntimeException(s"No opcode found: $bytes")
}

def pushData(opCode: ScriptOpCode, numberOfBytesToPush: Int, restOfData: Seq[Byte]): (Seq[Byte], Seq[Seq[ScriptElement]]) = {
val maybeBytesToPush = restOfData.takeOpt(numberOfBytesToPush)
val restOfBytes = restOfData.drop(numberOfBytesToPush)
val maybeConstantToBePushed = maybeBytesToPush.map { bytesToPush =>
(bytesToPush.isEmpty).option(OP_0).getOrElse(ScriptConstant(bytesToPush))
}

(restOfBytes, (Seq(opCode) ++ maybeConstantToBePushed.toList) +: acc)
}

(restOfBytes, (Seq(opCode) ++ maybeConstantToBePushed.toList) +: acc)
}
val (restOfBytes, newAcc) = opCode match {
case OP_PUSHDATA(value) =>
pushData(opCode, value.toInt, tail)

val (restOfBytes, newAcc) = opCode match {
case OP_PUSHDATA(value) =>
pushData(opCode, value.toInt, tail)
case OP_PUSHDATA1 =>
val numberOfBytesToPush = bytesToUInt8(tail.forceTake(1))
pushData(opCode, numberOfBytesToPush, tail.drop(1))

case OP_PUSHDATA1 =>
val numberOfBytesToPush = bytesToUInt8(tail.forceTake(1))
pushData(opCode, numberOfBytesToPush, tail.drop(1))
case OP_PUSHDATA2 =>
val numberOfBytesToPush = bytesToUInt16(tail.forceTake(2))
pushData(opCode, numberOfBytesToPush, tail.drop(2))

case OP_PUSHDATA2 =>
val numberOfBytesToPush = bytesToUInt16(tail.forceTake(2))
pushData(opCode, numberOfBytesToPush, tail.drop(2))
case OP_PUSHDATA4 =>
val numberOfBytesToPush = bytesToUInt32(tail.forceTake(4))
pushData(opCode, numberOfBytesToPush, tail.drop(4))

case OP_PUSHDATA4 =>
val numberOfBytesToPush = bytesToUInt32(tail.forceTake(4))
pushData(opCode, numberOfBytesToPush, tail.drop(4))

case otherOpCode =>
(tail, Seq(otherOpCode) +: acc)
}
case otherOpCode =>
(tail, Seq(otherOpCode) +: acc)
}

parse(restOfBytes, newAcc)
parse(restOfBytes, newAcc)
}
}

private def bytesAndLength(dataBytes: Seq[Byte]): Seq[Byte] = {
Expand Down
7 changes: 4 additions & 3 deletions src/test/scala/me/hongchao/bitcoin4s/script/ScriptSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,10 @@ class ScriptSpec extends Spec with ScriptTestRunner {
(checkedExpectedResults ++ notCheckedExpectedResults) should contain theSameElementsAs ExpectedResult.all

val filteredScriptTests = scriptTests.filter { test =>
checkedExpectedResults.contains(test.expectedResult) && test.comments.equals("Basic P2WPKH")
//checkedExpectedResults.contains(test.expectedResult)
}
checkedExpectedResults.contains(test.expectedResult)
// && test.raw.contains("Invalid witness script")
// checkedExpectedResults.contains(test.expectedResult)
}.take(907)

filteredScriptTests.zipWithIndex.foreach(Function.tupled(run))
}
Expand Down

0 comments on commit 537dde6

Please sign in to comment.