Implementing NULLFAIL flag inside of script_tests.json

This commit is contained in:
Chris Stewart 2016-12-06 10:09:09 -06:00
parent 2b3010b808
commit 2a2c551f7d
6 changed files with 86 additions and 196 deletions

View file

@ -80,8 +80,7 @@ trait TransactionSignatureChecker extends BitcoinSLogger {
* This is a helper function to check digital signatures against public keys
* if the signature does not match this public key, check it against the next
* public key in the sequence
*
* @param txSignatureComponent the tx signature component that contains all relevant transaction information
* @param txSignatureComponent the tx signature component that contains all relevant transaction information
* @param script the script state this is needed in case there is an OP_CODESEPARATOR inside the script
* @param sigs the signatures that are being checked for validity
* @param pubKeys the public keys which are needed to verify that the signatures are correct
@ -92,19 +91,19 @@ trait TransactionSignatureChecker extends BitcoinSLogger {
final def multiSignatureEvaluator(txSignatureComponent : TransactionSignatureComponent, script : Seq[ScriptToken],
sigs : List[ECDigitalSignature], pubKeys : List[ECPublicKey], flags : Seq[ScriptFlag],
requiredSigs : Long) : TransactionSignatureCheckerResult = {
logger.info("Signatures inside of helper: " + sigs)
logger.info("Public keys inside of helper: " + pubKeys)
logger.debug("Signatures inside of helper: " + sigs)
logger.debug("Public keys inside of helper: " + pubKeys)
if (sigs.size > pubKeys.size) {
//this is how bitcoin core treats this. If there are ever any more
//signatures than public keys remaining we immediately return
//false https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L955-L959
logger.info("We have more sigs than we have public keys remaining")
logger.warn("We have more sigs than we have public keys remaining")
SignatureValidationFailureIncorrectSignatures
}
else if (requiredSigs > sigs.size) {
//for the case when we do not have enough sigs left to check to meet the required signature threshold
//https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L914-915
logger.info("We do not have enough sigs to meet the threshold of requireSigs in the multiSignatureScriptPubKey")
logger.warn("We do not have enough sigs to meet the threshold of requireSigs in the multiSignatureScriptPubKey")
SignatureValidationFailureSignatureCount
}
else if (sigs.nonEmpty && pubKeys.nonEmpty) {

View file

@ -1,8 +1,10 @@
package org.bitcoins.core.script.control
import org.bitcoins.core.protocol.script.SigVersionWitnessV0
import org.bitcoins.core.script.result._
import org.bitcoins.core.script.{ScriptProgram}
import org.bitcoins.core.script.ScriptProgram
import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.flag.ScriptFlagUtil
import org.bitcoins.core.util._
import org.slf4j.LoggerFactory
@ -15,15 +17,14 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
/**
* If the top stack value is not 0, the statements are executed. The top stack value is removed.
*
* @param program
* @return
*/
/** If the top stack value is not 0, the statements are executed. The top stack value is removed. */
def opIf(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_IF, "Script top was not OP_IF")
val sigVersion = program.txSignatureComponent.sigVersion
val flags = program.flags
val minimalIfEnabled = ScriptFlagUtil.minimalIfEnabled(flags)
val binaryTree = parseBinaryTree(program.script)
val stackTop = program.stack.headOption
logger.debug("Parsed binary tree: " + binaryTree)
if (!checkMatchingOpIfOpNotIfOpEndIf(program.originalScript)) {
logger.error("We do not have a matching OP_ENDIF for every OP_IF we have")
@ -31,6 +32,13 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
} else if (program.stack.isEmpty) {
logger.error("We do not have any stack elements for our OP_IF")
ScriptProgram(program,ScriptErrorUnbalancedConditional)
} else if (sigVersion == SigVersionWitnessV0 && minimalIfEnabled
&& (stackTop.get.bytes.size > 1 ||
(stackTop.get.bytes.size == 1 && stackTop.get.bytes.head != 1))) {
//see: https://github.com/bitcoin/bitcoin/blob/528472111b4965b1a99c4bcf08ac5ec93d87f10f/src/script/interpreter.cpp#L447-L452
//https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2016-August/013014.html
logger.error("OP_IF argument was not minimally encoded, got: " + stackTop)
ScriptProgram(program, ScriptErrorMinimalIf)
} else if (program.stackTopIsTrue) {
logger.debug("OP_IF stack top was true")
logger.debug("Stack top: " + program.stack)
@ -40,7 +48,7 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
val newScript = newTreeWithoutOpElse.toList
logger.debug("New script after removing OP_ELSE branch " + newScript)
ScriptProgram(program, program.stack.tail,newScript.tail)
} else {
} else {
logger.debug("OP_IF stack top was false")
//remove the OP_IF
val scriptWithoutOpIf : BinaryTree[ScriptToken] = removeFirstOpIf(binaryTree)
@ -50,15 +58,15 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
}
/**
* If the top stack value is 0, the statements are executed. The top stack value is removed.
*
* @param program
* @return
*/
/** If the top stack value is 0, the statements are executed. The top stack value is removed. */
def opNotIf(program : ScriptProgram) : ScriptProgram = {
//TODO: Try and reduce this down to using OP_IF by inverting the stack top
require(program.script.headOption.isDefined && program.script.head == OP_NOTIF, "Script top was not OP_NOTIF")
val binaryTree = parseBinaryTree(program.script)
val sigVersion = program.txSignatureComponent.sigVersion
val flags = program.flags
val minimalIfEnabled = ScriptFlagUtil.minimalIfEnabled(flags)
val stackTop = program.stack.headOption
logger.debug("Parsed binary tree: " + binaryTree)
if (!checkMatchingOpIfOpNotIfOpEndIf(program.originalScript)) {
@ -67,6 +75,13 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
} else if (program.stack.isEmpty) {
logger.error("We do not have any stack elements for our OP_NOTIF")
ScriptProgram(program,ScriptErrorUnbalancedConditional)
} else if (sigVersion == SigVersionWitnessV0 && minimalIfEnabled
&& (stackTop.get.bytes.size > 1 ||
(stackTop.get.bytes.size == 1 && stackTop.get.bytes.head != 1))) {
//see: https://github.com/bitcoin/bitcoin/blob/528472111b4965b1a99c4bcf08ac5ec93d87f10f/src/script/interpreter.cpp#L447-L452
//https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2016-August/013014.html
logger.error("OP_NOTIF argument was not minimally encoded, got: " + stackTop)
ScriptProgram(program, ScriptErrorMinimalIf)
} else if (program.stackTopIsTrue) {
//remove the OP_NOTIF
val scriptWithoutOpIf : BinaryTree[ScriptToken] = removeFirstOpIf(binaryTree)
@ -80,12 +95,7 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
}
}
/**
* Evaluates the OP_ELSE operator
*
* @param program
* @return
*/
/** Evaluates the OP_ELSE operator */
def opElse(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_ELSE, "First script opt must be OP_ELSE")
@ -116,12 +126,7 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
}
/**
* Evaluates an OP_ENDIF operator
*
* @param program
* @return
*/
/** Evaluates an OP_ENDIF operator */
def opEndIf(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_ENDIF, "Script top must be OP_ENDIF")
if (!checkMatchingOpIfOpNotIfOpEndIf(program.originalScript)) {
@ -138,7 +143,6 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
* with a scriptPubKey consisting of OP_RETURN followed by exactly one pushdata op. Such outputs are provably unspendable,
* reducing their cost to the network. Currently it is usually considered non-standard (though valid) for a transaction to
* have more than one OP_RETURN output or an OP_RETURN output with more than one pushdata op.
*
* @param program
* @return
*/
@ -148,12 +152,7 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
}
/**
* Marks transaction as invalid if top stack value is not true.
*
* @param program
* @return
*/
/** Marks transaction as invalid if top stack value is not true. */
def opVerify(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_VERIFY, "Script top must be OP_VERIFY")
program.stack.size > 0 match {
@ -169,23 +168,13 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
}
/**
* Parses a list of script tokens into its corresponding binary tree
*
* @param script
* @return
*/
/** Parses a list of script tokens into its corresponding binary tree */
def parseBinaryTree(script : List[ScriptToken]) : BinaryTree[ScriptToken] = {
val bTree = loop(script,Empty)
bTree
}
/**
* The loop that parses a list of script tokens into a binary tree
*
* @param script
* @return
*/
/** The loop that parses a list of script tokens into a binary tree */
@tailrec
private def loop(script : List[ScriptToken], tree : BinaryTree[ScriptToken]) : BinaryTree[ScriptToken] = {
/* logger.debug("Script : " + script)
@ -217,7 +206,6 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
/**
* Inserts a sub tree into the parse tree of Script.
*
* @param tree the parse tree of the control flow of the Script program
* @param subTree the parse tree that needs to be inserted into the control flow of the program
* @return the full parse tree combined
@ -250,12 +238,7 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
}
/**
* Parses an OP_IF script token
*
* @param script
* @return
*/
/** Parses an OP_IF script token */
private def parseOpIf(script : List[ScriptToken]) : (List[ScriptToken],BinaryTree[ScriptToken]) = script match {
case OP_IF :: t => (t, Node(OP_IF,Empty,Empty))
case h :: t => throw new IllegalArgumentException("Cannot parse " + h + " as an OP_IF")
@ -263,24 +246,14 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
}
/**
* Parses an OP_NOTIF script token
*
* @param script
* @return
*/
/** Parses an OP_NOTIF script token */
private def parseOpNotIf(script : List[ScriptToken]) : (List[ScriptToken],BinaryTree[ScriptToken]) = script match {
case OP_NOTIF :: t => (t, Node(OP_NOTIF,Empty,Empty))
case h :: t => throw new IllegalArgumentException("Cannot parse " + h + " as an OP_NOTIF")
case Nil => (script,Empty)
}
/**
* Parses and OP_ELSE expression
*
* @param script
* @return
*/
/** Parses and OP_ELSE expression */
private def parseOpElse(script : List[ScriptToken]) : (List[ScriptToken],BinaryTree[ScriptToken]) = script match {
case OP_ELSE :: t => (t,Node(OP_ELSE,Empty,Empty))
case h :: t => throw new RuntimeException("Cannot parse " + h + " as an OP_ELSE")
@ -310,12 +283,7 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
/**
* Returns the first index of an OP_ENDIF
*
* @param script
* @return
*/
/** Returns the first index of an OP_ENDIF */
def findFirstOpEndIf(script : List[ScriptToken]) : Option[Int] = {
val index = script.indexOf(OP_ENDIF)
index match {
@ -325,24 +293,14 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
}
/**
* Finds the last OP_ENDIF in the given script
*
* @param script
* @return
*/
/** Finds the last OP_ENDIF in the given script */
def findLastOpEndIf(script : List[ScriptToken]) : Option[Int] = {
val lastOpEndIf = findFirstOpEndIf(script.reverse)
if (lastOpEndIf.isDefined) Some(script.size - lastOpEndIf.get - 1)
else None
}
/**
* Returns the first index of an OP_ENDIF
*
* @param script
* @return
*/
/** Returns the first index of an OP_ENDIF */
def findFirstOpElse(script : List[ScriptToken]) : Option[Int] = {
val index = script.indexOf(OP_ELSE)
index match {
@ -351,12 +309,7 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
}
}
/**
* Removes the first OP_ELSE expression encountered in the script
*
* @param script
* @return
*/
/** Removes the first OP_ELSE expression encountered in the script */
def removeFirstOpElse(script : List[ScriptToken]) : List[ScriptToken] = {
if (script.contains(OP_ELSE)) {
val firstOpElseIndex = findFirstOpElse(script)
@ -372,12 +325,7 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
}
/**
* Removes the first OP_ELSE {expression} in a binary tree
*
* @param tree
* @return
*/
/** Removes the first OP_ELSE {expression} in a binary tree */
def removeFirstOpElse(tree : BinaryTree[ScriptToken]) : BinaryTree[ScriptToken] = {
tree match {
case Empty => Empty
@ -408,12 +356,7 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
}
/**
* Removes the first OP_IF { expression } encountered in the script
*
* @param script
* @return
*/
/** Removes the first OP_IF { expression } encountered in the script */
def removeFirstOpIf(script : List[ScriptToken]) : List[ScriptToken] = {
val firstOpIfIndex = script.indexOf(OP_IF)
val matchingOpEndIfIndex = findMatchingOpEndIf(script)
@ -432,24 +375,14 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
}
/**
* Removes the first occurrence of OP_IF or OP_NOTIF in the binary tree
*
* @param tree
* @return
*/
/** Removes the first occurrence of OP_IF or OP_NOTIF in the binary tree */
def removeFirstOpIf(tree : BinaryTree[ScriptToken]) : BinaryTree[ScriptToken] = {
require(tree.value.isDefined && (tree.value.get == OP_IF || tree.value.get == OP_NOTIF) , "Top of the tree must be OP_IF or OP_NOTIF to remove the OP_IF or OP_NOTIF")
if (tree.right.isDefined && tree.right.get.value == Some(OP_ELSE)) tree.right.getOrElse(Empty)
else tree.findFirstDFS[ScriptToken](OP_ENDIF)().getOrElse(Empty)
}
/**
* Finds the indexes of our OP_ELSE (if it exists) and our OP_ENDIF
*
* @param script
* @return
*/
/** Finds the indexes of our OP_ELSE (if it exists) and our OP_ENDIF */
def findFirstIndexesOpElseOpEndIf(script : List[ScriptToken]) : (Option[Int],Option[Int]) = {
val indexOpElse = findFirstOpElse(script)
val indexOpEndIf = findFirstOpEndIf(script)
@ -457,12 +390,7 @@ trait ControlOperationsInterpreter extends BitcoinSLogger {
}
/**
* Returns the index of the matching OP_ENDIF for the OP_IF statement
*
* @param script
* @return
*/
/** Returns the index of the matching OP_ENDIF for the OP_IF statement */
def findMatchingOpEndIf(script : List[ScriptToken]) : Int = {
val matchingOpEndIfIndex = findLastOpEndIf(script)
require(matchingOpEndIfIndex.isDefined, "Every OP_IF must have a matching OP_ENDIF: " + script)

View file

@ -16,52 +16,32 @@ import scala.annotation.tailrec
*/
trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger {
/**
* The input is hashed twice: first with SHA-256 and then with RIPEMD-160.
* @param program
* @return
*/
/** The input is hashed twice: first with SHA-256 and then with RIPEMD-160. */
def opHash160(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_HASH160, "Script operation must be OP_HASH160")
executeHashFunction(program, CryptoUtil.sha256Hash160(_ : Seq[Byte]))
}
/**
* The input is hashed using RIPEMD-160.
* @param program
* @return
*/
/** The input is hashed using RIPEMD-160. */
def opRipeMd160(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_RIPEMD160, "Script operation must be OP_RIPEMD160")
executeHashFunction(program, CryptoUtil.ripeMd160(_ : Seq[Byte]))
}
/**
* The input is hashed using SHA-256.
* @param program
* @return
*/
/** The input is hashed using SHA-256. */
def opSha256(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_SHA256, "Script operation must be OP_SHA256")
executeHashFunction(program, CryptoUtil.sha256(_ : Seq[Byte]))
}
/**
* The input is hashed two times with SHA-256.
* @param program
* @return
*/
/** The input is hashed two times with SHA-256. */
def opHash256(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_HASH256, "Script operation must be OP_HASH256")
executeHashFunction(program, CryptoUtil.doubleSHA256(_ : Seq[Byte]))
}
/**
* The input is hashed using SHA-1.
* @param program
* @return
*/
/** The input is hashed using SHA-1. */
def opSha1(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_SHA1, "Script top must be OP_SHA1")
executeHashFunction(program, CryptoUtil.sha1(_ : Seq[Byte]))
@ -87,8 +67,8 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger
} else {
val pubKey = ECPublicKey(executionInProgressScriptProgram.stack.head.bytes)
val signature = ECDigitalSignature(executionInProgressScriptProgram.stack.tail.head.bytes)
if (ScriptFlagUtil.requiresStrictDerEncoding(executionInProgressScriptProgram.flags) &&
val flags = executionInProgressScriptProgram.flags
if (ScriptFlagUtil.requiresStrictDerEncoding(flags) &&
!DERSignatureUtil.isValidSignatureEncoding(signature)) {
//this means all of the signatures must encoded according to BIP66 strict dersig
//https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki
@ -96,9 +76,9 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger
logger.error("Since the ScriptVerifyDerSig flag is set the signature being checked must be a strict dersig signature as per BIP 66\n" +
"Sig: " + signature.hex)
ScriptProgram(executionInProgressScriptProgram,ScriptErrorSigDer)
} else if (BitcoinScriptUtil.isValidPubKeyEncoding(pubKey,executionInProgressScriptProgram.flags).isDefined) {
} else if (BitcoinScriptUtil.isValidPubKeyEncoding(pubKey,flags).isDefined) {
logger.error("Pubkey had an invalid encoding")
val err = BitcoinScriptUtil.isValidPubKeyEncoding(pubKey,executionInProgressScriptProgram.flags).get
val err = BitcoinScriptUtil.isValidPubKeyEncoding(pubKey,flags).get
logger.error("PubKey error: " + err)
ScriptProgram(executionInProgressScriptProgram,err)
} else {
@ -107,16 +87,15 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger
val removedOpCodeSeparatorsScript = removeOpCodeSeparator(executionInProgressScriptProgram)
logger.debug("Program after removing OP_CODESEPARATOR: " + removedOpCodeSeparatorsScript)
val result = TransactionSignatureChecker.checkSignature(executionInProgressScriptProgram.txSignatureComponent,
removedOpCodeSeparatorsScript, pubKey, signature, program.flags)
removedOpCodeSeparatorsScript, pubKey, signature, flags)
logger.debug("signature verification isValid: " + result)
result match {
case SignatureValidationSuccess => ScriptProgram(program,
OP_TRUE :: restOfStack,program.script.tail)
OP_TRUE :: restOfStack, program.script.tail)
case err : SignatureValidationError =>
if (ScriptFlagUtil.requireScriptVerifyNullFail(program.flags) && signature.bytes.nonEmpty) {
ScriptProgram(executionInProgressScriptProgram,ScriptErrorSigNullFail)
if (ScriptFlagUtil.requireScriptVerifyNullFail(flags) && signature.bytes.nonEmpty) {
ScriptProgram(executionInProgressScriptProgram, ScriptErrorSigNullFail)
} else {
err match {
case SignatureValidationFailureNotStrictDerEncoding =>
@ -148,11 +127,7 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger
}
}
/**
* Runs OP_CHECKSIG with an OP_VERIFY afterwards
* @param program
* @return
*/
/** Runs OP_CHECKSIG with an OP_VERIFY afterwards */
def opCheckSigVerify(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_CHECKSIGVERIFY,
"Script top must be OP_CHECKSIGVERIFY")
@ -174,10 +149,7 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger
/**
* All of the signature checking words will only match signatures to the data
* after the most recently-executed OP_CODESEPARATOR.
* @param program
* @return
*/
* after the most recently-executed OP_CODESEPARATOR. */
def opCodeSeparator(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_CODESEPARATOR, "Script top must be OP_CODESEPARATOR")
val e = program match {
@ -203,16 +175,14 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger
* signatures must be placed in the scriptSig using the same order as their corresponding public keys
* were placed in the scriptPubKey or redeemScript. If all signatures are valid, 1 is returned, 0 otherwise.
* Due to a bug, one extra unused value is removed from the stack.
* @param program
* @return
*/
@tailrec
final def opCheckMultiSig(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_CHECKMULTISIG, "Script top must be OP_CHECKMULTISIG")
val flags = program.flags
program match {
case preExecutionScriptProgram : PreExecutionScriptProgram =>
opCheckMultiSig(ScriptProgram.toExecutionInProgress(preExecutionScriptProgram))
opCheckMultiSig(preExecutionScriptProgram)
case executedScriptProgram : ExecutedScriptProgram =>
executedScriptProgram
case executionInProgressScriptProgram : ExecutionInProgressScriptProgram =>
@ -225,7 +195,7 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger
if (nPossibleSignatures < ScriptNumber.zero) {
logger.error("We cannot have the number of pubkeys in the script be negative")
ScriptProgram(program,ScriptErrorPubKeyCount)
} else if (ScriptFlagUtil.requireMinimalData(program.flags) && !nPossibleSignatures.isShortestEncoding) {
} else if (ScriptFlagUtil.requireMinimalData(flags) && !nPossibleSignatures.isShortestEncoding) {
logger.error("The required signatures and the possible signatures must be encoded as the shortest number possible")
ScriptProgram(executionInProgressScriptProgram, ScriptErrorUnknownError)
} else if (program.stack.size < 2) {
@ -234,7 +204,7 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger
} else {
val mRequiredSignatures: ScriptNumber = BitcoinScriptUtil.numRequiredSignaturesOnStack(program)
if (ScriptFlagUtil.requireMinimalData(program.flags) && !mRequiredSignatures.isShortestEncoding) {
if (ScriptFlagUtil.requireMinimalData(flags) && !mRequiredSignatures.isShortestEncoding) {
logger.error("The required signatures val must be the shortest encoding as possible")
return ScriptProgram(executionInProgressScriptProgram, ScriptErrorUnknownError)
}
@ -273,7 +243,7 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger
//this is because of a bug in bitcoin core for the implementation of OP_CHECKMULTISIG
//https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L966
ScriptProgram(executionInProgressScriptProgram,ScriptErrorInvalidStackOperation)
} else if (ScriptFlagUtil.requireNullDummy(program.flags) &&
} else if (ScriptFlagUtil.requireNullDummy(flags) &&
!(Seq(Some(OP_0), Some(ScriptNumber.zero)).contains(stackWithoutPubKeysAndSignatures.headOption))) {
logger.error("Script flag null dummy was set however the first element in the script signature was not an OP_0, stackWithoutPubKeysAndSignatures: " + stackWithoutPubKeysAndSignatures)
ScriptProgram(executionInProgressScriptProgram,ScriptErrorSigNullDummy)
@ -283,7 +253,7 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger
val isValidSignatures: TransactionSignatureCheckerResult =
TransactionSignatureChecker.multiSignatureEvaluator(executionInProgressScriptProgram.txSignatureComponent,
removedOpCodeSeparatorsScript, signatures,
pubKeys, program.flags, mRequiredSignatures.underlying)
pubKeys, flags, mRequiredSignatures.underlying)
//remove the extra op for OP_CHECKMULTISIG from the stack
val restOfStack = stackWithoutPubKeysAndSignatures.tail
@ -301,9 +271,14 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger
//https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki#specification
ScriptProgram(executionInProgressScriptProgram, ScriptErrorSigDer)
case SignatureValidationFailureIncorrectSignatures =>
//this means that signature verification failed, however all signatures were encoded correctly
//just push a ScriptFalse onto the stack
ScriptProgram(executionInProgressScriptProgram, OP_FALSE :: restOfStack, program.script.tail)
val nullFailEnabled = ScriptFlagUtil.requireScriptVerifyNullFail(flags)
if (nullFailEnabled && signatures.exists(_.bytes.nonEmpty)) {
ScriptProgram(executionInProgressScriptProgram,ScriptErrorSigNullFail) }
else {
//this means that signature verification failed, however all signatures were encoded correctly
//just push a ScriptFalse onto the stack
ScriptProgram(executionInProgressScriptProgram, OP_FALSE :: restOfStack, program.script.tail)
}
case SignatureValidationFailureSignatureCount =>
//means that we did not have enough signatures for OP_CHECKMULTISIG
ScriptProgram(executionInProgressScriptProgram, ScriptErrorInvalidStackOperation)
@ -324,11 +299,7 @@ trait CryptoInterpreter extends ControlOperationsInterpreter with BitcoinSLogger
}
/**
* Runs OP_CHECKMULTISIG with an OP_VERIFY afterwards
* @param program
* @return
*/
/** Runs OP_CHECKMULTISIG with an OP_VERIFY afterwards */
def opCheckMultiSigVerify(program : ScriptProgram) : ScriptProgram = {
require(program.script.headOption.isDefined && program.script.head == OP_CHECKMULTISIGVERIFY, "Script top must be OP_CHECKMULTISIGVERIFY")
if (program.stack.size < 3) {

View file

@ -101,6 +101,10 @@ trait ScriptFlagUtil {
def requireScriptVerifyNullFail(flags: Seq[ScriptFlag]): Boolean = flags.contains(ScriptVerifyNullFail)
def requireScriptVerifyWitnessPubKeyType(flags: Seq[ScriptFlag]): Boolean = flags.contains(ScriptVerifyWitnessPubKeyType)
/** Requires that the argument to OP_IF/OP_NOTIF be minimally encoded
* See: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2016-August/013014.html */
def minimalIfEnabled(flags: Seq[ScriptFlag]): Boolean = flags.contains(ScriptVerifyMinimalIf)
}

View file

@ -368,7 +368,6 @@ trait ScriptInterpreter extends CryptoInterpreter with StackInterpreter with Con
val newOpCount = calcOpCount(opCount,OP_CHECKMULTISIG) + BitcoinScriptUtil.numPossibleSignaturesOnStack(program).toInt
loop(newProgram,newOpCount)
}
case OP_CHECKMULTISIGVERIFY :: t =>
opCheckMultiSigVerify(p) match {
case newProgram : ExecutedScriptProgram =>

View file

@ -31,17 +31,7 @@ class ScriptInterpreterTest extends FlatSpec with MustMatchers with ScriptInterp
//use this to represent a single test case from script_valid.json
/* val lines =
"""
| [[
| [
| "",
| 0.00000000
| ],
| "0x47 0x304402200a5c6163f07b8d3b013c4d1d6dba25e780b39658d79ba37af7057a3b7f15ffa102201fd9b4eaa9943f734928b99a83592c2e7bf342ea2680f6a2bb705167966b742001",
| "0x41 0x0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 CHECKSIG",
| "P2SH,WITNESS",
| "WITNESS_UNEXPECTED",
| "P2PK with witness"
| ]]
| [["0 0x09 0x300602010102010101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", "DERSIG,NULLFAIL", "NULLFAIL", "BIP66-compliant but not NULLFAIL-compliant"]]
""".stripMargin*/
val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close()
val json = lines.parseJson
@ -68,7 +58,6 @@ class ScriptInterpreterTest extends FlatSpec with MustMatchers with ScriptInterp
inputIndex, flags, w, amount)
case None => ScriptProgram(tx, scriptPubKey, inputIndex, flags)
}
withClue(testCase.raw) {
ScriptInterpreter.run(program) must equal (testCase.expectedResult)
}