Fixing issue where we have the wrong version of ScriptSignature in our tree

This commit is contained in:
Chris Stewart 2016-05-31 08:39:03 -05:00
parent 40929aa5bf
commit 81f2cf3ba2

View file

@ -6,6 +6,7 @@ import org.bitcoins.core.serializers.script.{RawScriptPubKeyParser, RawScriptSig
import org.bitcoins.core.script.constant._ import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.crypto.{HashType, HashTypeFactory, OP_CHECKMULTISIG, SIGHASH_ALL} import org.bitcoins.core.script.crypto.{HashType, HashTypeFactory, OP_CHECKMULTISIG, SIGHASH_ALL}
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinSUtil, BitcoinScriptUtil, Factory} import org.bitcoins.core.util.{BitcoinSLogger, BitcoinSUtil, BitcoinScriptUtil, Factory}
import org.slf4j.LoggerFactory
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}
@ -15,50 +16,44 @@ import scala.util.{Failure, Success, Try}
*/ */
sealed trait ScriptSignature extends NetworkElement with BitcoinSLogger { sealed trait ScriptSignature extends NetworkElement with BitcoinSLogger {
/**
* Representation of a scriptSignature in a parsed assembly format
* this data structure can be run through the script interpreter to
* see if a script evaluates to true
* @return
*/
def asm : Seq[ScriptToken]
/** /**
* The digital signatures contained inside of the script signature * Representation of a scriptSignature in a parsed assembly format
* p2pkh script signatures only have one sig * this data structure can be run through the script interpreter to
* p2pk script signatures only have one sigs * see if a script evaluates to true
* p2sh script signatures can have m sigs * @return
* multisignature scripts can have m sigs */
* @return lazy val asm : Seq[ScriptToken] = ScriptParser.fromHex(hex)
*/
/**
* The digital signatures contained inside of the script signature
* p2pkh script signatures only have one sig
* p2pk script signatures only have one sigs
* p2sh script signatures can have m sigs
* multisignature scripts can have m sigs
* @return
*/
def signatures : Seq[ECDigitalSignature] def signatures : Seq[ECDigitalSignature]
/** /**
* Derives the hash type for a given digitalSignature * Derives the hash type for a given digitalSignature
*
* @param digitalSignature * @param digitalSignature
* @return * @return
*/ */
def hashType(digitalSignature: ECDigitalSignature) = { def hashType(digitalSignature: ECDigitalSignature) = {
digitalSignature match { digitalSignature match {
case EmptyDigitalSignature => SIGHASH_ALL() case EmptyDigitalSignature => SIGHASH_ALL()
case sig : ECDigitalSignature => HashTypeFactory.fromByte(digitalSignature.bytes.last) case sig : ECDigitalSignature => HashTypeFactory.fromByte(digitalSignature.bytes.last)
} }
} }
} }
trait NonStandardScriptSignature extends ScriptSignature { trait NonStandardScriptSignature extends ScriptSignature {
def signatures : Seq[ECDigitalSignature] = ??? def signatures : Seq[ECDigitalSignature] = Seq()
} }
object NonStandardScriptSignatureImpl {
def apply(hex : String) : NonStandardScriptSignatureImpl = NonStandardScriptSignatureImpl(hex, RawScriptSignatureParser.read(hex).asm)
}
case class NonStandardScriptSignatureImpl(hex : String, asm : Seq[ScriptToken]) extends NonStandardScriptSignature
/** /**
@ -71,24 +66,22 @@ trait P2PKHScriptSignature extends ScriptSignature {
/** /**
* P2PKH scriptSigs only have one signature * P2PKH scriptSigs only have one signature
*
* @return * @return
*/ */
def signature : ECDigitalSignature = signatures.head def signature : ECDigitalSignature = signatures.head
/** /**
* Gives us the public key inside of a p2pkh script signature * Gives us the public key inside of a p2pkh script signature
*
* @return * @return
*/ */
def publicKey : ECPublicKey = ECFactory.publicKey(asm.last.bytes) def publicKey : ECPublicKey = ECFactory.publicKey(asm.last.bytes)
/** /**
* Returns the hash type for the p2pkh script signature * Returns the hash type for the p2pkh script signature
*
* @return * @return
*/ */
def hashType : HashType = HashTypeFactory.fromByte(signature.bytes.last) def hashType : HashType = HashTypeFactory.fromByte(signature.bytes.last)
override def signatures : Seq[ECDigitalSignature] = { override def signatures : Seq[ECDigitalSignature] = {
@ -104,37 +97,36 @@ trait P2PKHScriptSignature extends ScriptSignature {
* <sig> [sig] [sig...] <redeemScript> * <sig> [sig] [sig...] <redeemScript>
*/ */
trait P2SHScriptSignature extends ScriptSignature { trait P2SHScriptSignature extends ScriptSignature {
/** /**
* The redeemScript represents the conditions that must be satisfied to spend the output * The redeemScript represents the conditions that must be satisfied to spend the output
*
* @return * @return
*/ */
def redeemScript : ScriptPubKey = ScriptPubKey(asm.last.bytes) def redeemScript : ScriptPubKey = ScriptPubKey(asm.last.bytes)
/** /**
* Returns the script signature of this p2shScriptSig with no serialized redeemScript * Returns the script signature of this p2shScriptSig with no serialized redeemScript
*
* @return * @return
*/ */
def scriptSignatureNoRedeemScript = ScriptSignature.fromAsm(splitAtRedeemScript(asm)._1) def scriptSignatureNoRedeemScript = ScriptSignature.fromAsm(splitAtRedeemScript(asm)._1)
/** /**
* Returns the public keys for the p2sh scriptSignature * Returns the public keys for the p2sh scriptSignature
*
* @return * @return
*/ */
def publicKeys : Seq[ECPublicKey] = { def publicKeys : Seq[ECPublicKey] = {
val pubKeys : Seq[ScriptToken] = redeemScript.asm.filter(_.isInstanceOf[ScriptConstant]) val pubKeys : Seq[ScriptToken] = redeemScript.asm.filter(_.isInstanceOf[ScriptConstant])
.filterNot(_.isInstanceOf[ScriptNumberOperation]) .filterNot(_.isInstanceOf[ScriptNumberOperation])
pubKeys.map(k => ECFactory.publicKey(k.hex)) pubKeys.map(k => ECFactory.publicKey(k.hex))
} }
/** /**
* The digital signatures inside of the scriptSig * The digital signatures inside of the scriptSig
*
* @return * @return
*/ */
def signatures : Seq[ECDigitalSignature] = { def signatures : Seq[ECDigitalSignature] = {
val nonRedeemScript = splitAtRedeemScript(asm)._1 val nonRedeemScript = splitAtRedeemScript(asm)._1
val sigs = nonRedeemScript.filter(_.isInstanceOf[ScriptConstant]).filterNot(_.isInstanceOf[ScriptNumberOperation]) val sigs = nonRedeemScript.filter(_.isInstanceOf[ScriptConstant]).filterNot(_.isInstanceOf[ScriptNumberOperation])
@ -143,13 +135,12 @@ trait P2SHScriptSignature extends ScriptSignature {
/** /**
* Splits the given asm into two parts * Splits the given asm into two parts
* the first part is the digital signatures * the first part is the digital signatures
* the second part is the redeem script * the second part is the redeem script
*
* @param asm * @param asm
* @return * @return
*/ */
def splitAtRedeemScript(asm : Seq[ScriptToken]) : (Seq[ScriptToken],Seq[ScriptToken]) = { def splitAtRedeemScript(asm : Seq[ScriptToken]) : (Seq[ScriptToken],Seq[ScriptToken]) = {
//call .tail twice to remove the serialized redeemScript & it's bytesToPushOntoStack constant //call .tail twice to remove the serialized redeemScript & it's bytesToPushOntoStack constant
(asm.reverse.tail.tail.reverse, Seq(asm.last)) (asm.reverse.tail.tail.reverse, Seq(asm.last))
@ -181,191 +172,180 @@ trait MultiSignatureScriptSignature extends ScriptSignature {
* Signature script: <sig> * Signature script: <sig>
*/ */
trait P2PKScriptSignature extends ScriptSignature { trait P2PKScriptSignature extends ScriptSignature {
/** /**
* Returns the hash type for the signature inside of the p2pk script signature * Returns the hash type for the signature inside of the p2pk script signature
*
* @return * @return
*/ */
def hashType = HashTypeFactory.fromByte(signature.bytes.last) def hashType = HashTypeFactory.fromByte(signature.bytes.last)
/** /**
* PubKey scriptSignatures only have one signature * PubKey scriptSignatures only have one signature
*
* @return * @return
*/ */
def signature : ECDigitalSignature = signatures.head def signature : ECDigitalSignature = signatures.head
/** /**
* The digital signatures inside of the scriptSig * The digital signatures inside of the scriptSig
*
* @return * @return
*/ */
def signatures : Seq[ECDigitalSignature] = { def signatures : Seq[ECDigitalSignature] = {
Seq(ECFactory.digitalSignature(BitcoinScriptUtil.filterPushOps(asm).head.hex)) Seq(ECFactory.digitalSignature(BitcoinScriptUtil.filterPushOps(asm).head.hex))
} }
} }
/** /**
* Represents the empty script signature * Represents the empty script signature
*/ */
case object EmptyScriptSignature extends ScriptSignature { case object EmptyScriptSignature extends ScriptSignature {
def asm = List()
def signatures = List() def signatures = List()
def hex = "" def hex = ""
} }
object ScriptSignature extends Factory[ScriptSignature] with BitcoinSLogger { object ScriptSignature extends Factory[ScriptSignature] with BitcoinSLogger {
private object P2PKHScriptSignatureImpl { private case class NonStandardScriptSignatureImpl(hex : String) extends NonStandardScriptSignature
def apply(hex : String) : P2PKHScriptSignatureImpl = P2PKHScriptSignatureImpl(hex, RawScriptSignatureParser.read(hex).asm)
}
private case class P2PKHScriptSignatureImpl(hex : String, asm : Seq[ScriptToken]) extends P2PKHScriptSignature
private object P2SHScriptSignatureImpl { private case class P2PKScriptSignatureImpl(hex : String) extends P2PKScriptSignature
def apply(hex : String) : P2SHScriptSignatureImpl = P2SHScriptSignatureImpl(hex, RawScriptSignatureParser.read(hex).asm)
}
private case class P2SHScriptSignatureImpl(hex : String, asm : Seq[ScriptToken]) extends P2SHScriptSignature
private object MultiSignatureScriptSignatureImpl { private case class MultiSignatureScriptSignatureImpl(hex : String) extends MultiSignatureScriptSignature
def apply(hex : String) : MultiSignatureScriptSignatureImpl = MultiSignatureScriptSignatureImpl(hex, RawScriptSignatureParser.read(hex).asm)
}
private case class MultiSignatureScriptSignatureImpl(hex : String, asm : Seq[ScriptToken]) extends MultiSignatureScriptSignature
private object P2PKScriptSignatureImpl { private case class P2SHScriptSignatureImpl(hex : String) extends P2SHScriptSignature
def apply(hex : String) : P2PKScriptSignatureImpl = P2PKScriptSignatureImpl(hex, RawScriptSignatureParser.read(hex).asm)
} private case class P2PKHScriptSignatureImpl(hex : String) extends P2PKHScriptSignature
private case class P2PKScriptSignatureImpl(hex : String, asm : Seq[ScriptToken]) extends P2PKScriptSignature
/** /**
* Builds a script signature from a digital signature and a public key * Builds a script signature from a digital signature and a public key
* this is a pay to public key hash script sig * this is a pay to public key hash script sig
* *
* @param signature * @param signature
* @param pubKey * @param pubKey
* @return * @return
*/ */
def factory(signature : ECDigitalSignature, pubKey : ECPublicKey) : ScriptSignature = { def factory(signature : ECDigitalSignature, pubKey : ECPublicKey) : ScriptSignature = {
val signatureBytesToPushOntoStack = BytesToPushOntoStack(signature.bytes.size) val signatureBytesToPushOntoStack = BytesToPushOntoStack(signature.bytes.size)
val pubKeyBytesToPushOntoStack = BytesToPushOntoStack(pubKey.bytes.size) val pubKeyBytesToPushOntoStack = BytesToPushOntoStack(pubKey.bytes.size)
val asm : Seq[ScriptToken] = Seq(signatureBytesToPushOntoStack, ScriptConstant(signature.hex), val asm : Seq[ScriptToken] = Seq(signatureBytesToPushOntoStack, ScriptConstant(signature.hex),
pubKeyBytesToPushOntoStack, ScriptConstant(pubKey.hex)) pubKeyBytesToPushOntoStack, ScriptConstant(pubKey.hex))
fromAsm(asm) fromAsm(asm)
}
/**
* Returns an empty script signature
*
* @return
*/
def empty : ScriptSignature = EmptyScriptSignature
def fromBytes(bytes : Seq[Byte]) : ScriptSignature = RawScriptSignatureParser.read(bytes)
/**
* Creates a scriptSignature from the list of script tokens
*
* @param tokens
* @return
*/
def fromAsm(tokens : Seq[ScriptToken]) : ScriptSignature = {
val scriptSigHex = tokens.map(_.hex).mkString
tokens match {
case Nil => EmptyScriptSignature
case _ if (tokens.size > 1 && isRedeemScript(tokens.last)) =>
P2SHScriptSignatureImpl(scriptSigHex)
case _ if (isMultiSignatureScriptSignature(tokens)) =>
//the head of the asm does not neccessarily have to be an OP_0 if the NULLDUMMY script
//flag is not set. It can be any script number operation
MultiSignatureScriptSignatureImpl(scriptSigHex)
case List(w : BytesToPushOntoStack, x : ScriptConstant, y : BytesToPushOntoStack,
z : ScriptConstant) => P2PKHScriptSignatureImpl(scriptSigHex)
case List(w : BytesToPushOntoStack, x : ScriptConstant) => P2PKScriptSignatureImpl(scriptSigHex)
case _ => NonStandardScriptSignatureImpl(scriptSigHex)
} }
}
/**
* Returns an empty script signature
*
* @return
*/
def empty : ScriptSignature = EmptyScriptSignature
def fromBytes(bytes : Seq[Byte]) : ScriptSignature = { /**
RawScriptSignatureParser.read(bytes) * Creates a script signature from the given tokens and scriptPubKey
*
* @param tokens the script signature's tokens
* @param scriptPubKey the scriptPubKey which the script signature is trying to spend
* @return
*/
def fromScriptPubKey(tokens : Seq[ScriptToken], scriptPubKey : ScriptPubKey) : ScriptSignature = {
val scriptSigHex = tokens.map(_.hex).mkString
scriptPubKey match {
case s : P2SHScriptPubKey => P2SHScriptSignatureImpl(scriptSigHex)
case s : P2PKHScriptPubKey => P2PKHScriptSignatureImpl(scriptSigHex)
case s : P2PKScriptPubKey => P2PKScriptSignatureImpl(scriptSigHex)
case s : MultiSignatureScriptPubKey => MultiSignatureScriptSignatureImpl(scriptSigHex)
case s : NonStandardScriptPubKey => NonStandardScriptSignatureImpl(scriptSigHex)
case EmptyScriptPubKey if (tokens.size == 0) => EmptyScriptSignature
case EmptyScriptPubKey => NonStandardScriptSignatureImpl(scriptSigHex)
} }
}
/**
* Creates a scriptSignature from the list of script tokens
* /**
* @param tokens * Detects if the given script token is a redeem script
* @return *
*/ * @param token
def fromAsm(tokens : Seq[ScriptToken]) : ScriptSignature = { * @return
val scriptSigHex = tokens.map(_.hex).mkString */
tokens match { private def isRedeemScript(token : ScriptToken) : Boolean = {
case Nil => EmptyScriptSignature logger.debug("Checking if last token is redeem script")
case _ if (tokens.size > 1 && isRedeemScript(tokens.last)) => val redeemScriptTry : Try[ScriptPubKey] = parseRedeemScript(token)
P2SHScriptSignatureImpl(scriptSigHex,tokens) redeemScriptTry match {
case _ if (isMultiSignatureScriptSignature(tokens)) => case Success(redeemScript) =>
//the head of the asm does not neccessarily have to be an OP_0 if the NULLDUMMY script logger.debug("Possible redeemScript: " + redeemScript)
//flag is not set. It can be any script number operation redeemScript match {
MultiSignatureScriptSignatureImpl(scriptSigHex,tokens) case x : P2PKHScriptPubKey => true
case List(w : BytesToPushOntoStack, x : ScriptConstant, y : BytesToPushOntoStack, case x : MultiSignatureScriptPubKey => true
z : ScriptConstant) => P2PKHScriptSignatureImpl(scriptSigHex,tokens) case x : P2SHScriptPubKey => true
case List(w : BytesToPushOntoStack, x : ScriptConstant) => P2PKScriptSignatureImpl(scriptSigHex,tokens) case x : P2PKScriptPubKey => true
case _ => NonStandardScriptSignatureImpl(scriptSigHex,tokens) case x : NonStandardScriptPubKey => false
} case EmptyScriptPubKey => false
}
case Failure(_) => false
} }
}
/**
* Parses a redeem script from the given script token
*
* @param scriptToken
* @return
*/
def parseRedeemScript(scriptToken : ScriptToken) : Try[ScriptPubKey] = {
val redeemScript : Try[ScriptPubKey] = Try(ScriptPubKey(scriptToken.bytes))
redeemScript
}
/** /**
* Creates a script signature from the given tokens and scriptPubKey * Checks if the given script tokens are a multisignature script sig
* * format: OP_0 <A sig> [B sig] [C sig...]
* @param tokens the script signature's tokens *
* @param scriptPubKey the scriptPubKey which the script signature is trying to spend * @param asm the asm to check if it falls in the multisignature script sig format
* @return * @return boolean indicating if the scriptsignature is a multisignature script signature
*/ */
def fromScriptPubKey(tokens : Seq[ScriptToken], scriptPubKey : ScriptPubKey) : ScriptSignature = { def isMultiSignatureScriptSignature(asm : Seq[ScriptToken]) : Boolean = {
val scriptSigHex = tokens.map(_.hex).mkString asm.isEmpty match {
scriptPubKey match { case true => false
case s : P2SHScriptPubKey => P2SHScriptSignatureImpl(scriptSigHex,tokens) case false if (asm.size == 1) => false
case s : P2PKHScriptPubKey => P2PKHScriptSignatureImpl(scriptSigHex,tokens) case false =>
case s : P2PKScriptPubKey => P2PKScriptSignatureImpl(scriptSigHex,tokens) val firstTokenIsScriptNumberOperation = asm.head.isInstanceOf[ScriptNumberOperation]
case s : MultiSignatureScriptPubKey => MultiSignatureScriptSignatureImpl(scriptSigHex,tokens) val restOfScriptIsPushOpsOrScriptConstants = asm.tail.map(
case s : NonStandardScriptPubKey => NonStandardScriptSignatureImpl(scriptSigHex, tokens) token => token.isInstanceOf[ScriptConstant] || StackPushOperationFactory.isPushOperation(token)
case EmptyScriptPubKey if (tokens.size == 0) => EmptyScriptSignature ).exists(_ == false)
case EmptyScriptPubKey => NonStandardScriptSignatureImpl(scriptSigHex,tokens) logger.debug("First number is script op: " + firstTokenIsScriptNumberOperation)
} logger.debug("tail is true: " +restOfScriptIsPushOpsOrScriptConstants )
} firstTokenIsScriptNumberOperation && !restOfScriptIsPushOpsOrScriptConstants
/**
* Detects if the given script token is a redeem script
*
* @param token
* @return
*/
private def isRedeemScript(token : ScriptToken) : Boolean = {
logger.debug("Checking if last token is redeem script")
val redeemScriptTry : Try[ScriptPubKey] = parseRedeemScript(token)
redeemScriptTry match {
case Success(redeemScript) =>
logger.debug("Possible redeemScript: " + redeemScript)
redeemScript match {
case x : P2PKHScriptPubKey => true
case x : MultiSignatureScriptPubKey => true
case x : P2SHScriptPubKey => true
case x : P2PKScriptPubKey => true
case x : NonStandardScriptPubKey => false
case EmptyScriptPubKey => false
}
case Failure(_) => false
}
}
/**
* Parses a redeem script from the given script token
*
* @param scriptToken
* @return
*/
def parseRedeemScript(scriptToken : ScriptToken) : Try[ScriptPubKey] = {
val redeemScript : Try[ScriptPubKey] = Try(ScriptPubKey(scriptToken.bytes))
redeemScript
}
/**
* Checks if the given script tokens are a multisignature script sig
* format: OP_0 <A sig> [B sig] [C sig...]
*
* @param asm the asm to check if it falls in the multisignature script sig format
* @return boolean indicating if the scriptsignature is a multisignature script signature
*/
def isMultiSignatureScriptSignature(asm : Seq[ScriptToken]) : Boolean = {
asm.isEmpty match {
case true => false
case false if (asm.size == 1) => false
case false =>
val firstTokenIsScriptNumberOperation = asm.head.isInstanceOf[ScriptNumberOperation]
val restOfScriptIsPushOpsOrScriptConstants = asm.tail.map(
token => token.isInstanceOf[ScriptConstant] || StackPushOperationFactory.isPushOperation(token)
).exists(_ == false)
logger.debug("First number is script op: " + firstTokenIsScriptNumberOperation)
logger.debug("tail is true: " +restOfScriptIsPushOpsOrScriptConstants )
firstTokenIsScriptNumberOperation && !restOfScriptIsPushOpsOrScriptConstants
}
} }
}
def apply(bytes: Seq[Byte]) : ScriptSignature = fromBytes(bytes) def apply(bytes: Seq[Byte]) : ScriptSignature = fromBytes(bytes)
def apply(hex : String) : ScriptSignature = fromHex(hex) def apply(hex : String) : ScriptSignature = fromHex(hex)