mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-24 06:57:51 +01:00
Implementing NULLFAIL flag inside of script_tests.json
This commit is contained in:
parent
2b3010b808
commit
2a2c551f7d
6 changed files with 86 additions and 196 deletions
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 =>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue