diff --git a/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala b/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala index 8e172701a2..7e553bdf00 100644 --- a/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala +++ b/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala @@ -6,6 +6,7 @@ import org.bitcoins.core.serializers.script.{RawScriptPubKeyParser, RawScriptSig import org.bitcoins.core.script.constant._ import org.bitcoins.core.script.crypto.{HashType, HashTypeFactory, OP_CHECKMULTISIG, SIGHASH_ALL} import org.bitcoins.core.util.{BitcoinSLogger, BitcoinSUtil, BitcoinScriptUtil, Factory} +import org.slf4j.LoggerFactory import scala.util.{Failure, Success, Try} @@ -15,50 +16,44 @@ import scala.util.{Failure, Success, Try} */ 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 - * 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 - */ + * 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 + */ + 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] + /** - * Derives the hash type for a given digitalSignature - * + * Derives the hash type for a given digitalSignature * @param digitalSignature - * @return - */ + * @return + */ def hashType(digitalSignature: ECDigitalSignature) = { digitalSignature match { case EmptyDigitalSignature => SIGHASH_ALL() case sig : ECDigitalSignature => HashTypeFactory.fromByte(digitalSignature.bytes.last) } } - - - - } 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 - */ + */ 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 - */ + */ 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 - */ + */ def hashType : HashType = HashTypeFactory.fromByte(signature.bytes.last) override def signatures : Seq[ECDigitalSignature] = { @@ -104,37 +97,36 @@ trait P2PKHScriptSignature extends ScriptSignature { * [sig] [sig...] */ 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 - */ + */ 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 - */ + */ def scriptSignatureNoRedeemScript = ScriptSignature.fromAsm(splitAtRedeemScript(asm)._1) + /** - * Returns the public keys for the p2sh scriptSignature - * + * Returns the public keys for the p2sh scriptSignature * @return - */ + */ def publicKeys : Seq[ECPublicKey] = { val pubKeys : Seq[ScriptToken] = redeemScript.asm.filter(_.isInstanceOf[ScriptConstant]) .filterNot(_.isInstanceOf[ScriptNumberOperation]) pubKeys.map(k => ECFactory.publicKey(k.hex)) } + /** - * The digital signatures inside of the scriptSig - * + * The digital signatures inside of the scriptSig * @return - */ + */ def signatures : Seq[ECDigitalSignature] = { val nonRedeemScript = splitAtRedeemScript(asm)._1 val sigs = nonRedeemScript.filter(_.isInstanceOf[ScriptConstant]).filterNot(_.isInstanceOf[ScriptNumberOperation]) @@ -143,13 +135,12 @@ trait P2SHScriptSignature extends ScriptSignature { /** - * Splits the given asm into two parts - * the first part is the digital signatures - * the second part is the redeem script - * + * Splits the given asm into two parts + * the first part is the digital signatures + * the second part is the redeem script * @param asm - * @return - */ + * @return + */ def splitAtRedeemScript(asm : Seq[ScriptToken]) : (Seq[ScriptToken],Seq[ScriptToken]) = { //call .tail twice to remove the serialized redeemScript & it's bytesToPushOntoStack constant (asm.reverse.tail.tail.reverse, Seq(asm.last)) @@ -181,191 +172,180 @@ trait MultiSignatureScriptSignature extends ScriptSignature { * Signature script: */ 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 - */ + */ def hashType = HashTypeFactory.fromByte(signature.bytes.last) + /** - * PubKey scriptSignatures only have one signature - * + * PubKey scriptSignatures only have one signature * @return - */ + */ def signature : ECDigitalSignature = signatures.head + /** - * The digital signatures inside of the scriptSig - * + * The digital signatures inside of the scriptSig * @return - */ + */ def signatures : Seq[ECDigitalSignature] = { Seq(ECFactory.digitalSignature(BitcoinScriptUtil.filterPushOps(asm).head.hex)) } } + + + /** * Represents the empty script signature */ case object EmptyScriptSignature extends ScriptSignature { - def asm = List() def signatures = List() def hex = "" } object ScriptSignature extends Factory[ScriptSignature] with BitcoinSLogger { - private object P2PKHScriptSignatureImpl { - def apply(hex : String) : P2PKHScriptSignatureImpl = P2PKHScriptSignatureImpl(hex, RawScriptSignatureParser.read(hex).asm) - } - private case class P2PKHScriptSignatureImpl(hex : String, asm : Seq[ScriptToken]) extends P2PKHScriptSignature + private case class NonStandardScriptSignatureImpl(hex : String) extends NonStandardScriptSignature - private object P2SHScriptSignatureImpl { - def apply(hex : String) : P2SHScriptSignatureImpl = P2SHScriptSignatureImpl(hex, RawScriptSignatureParser.read(hex).asm) - } - private case class P2SHScriptSignatureImpl(hex : String, asm : Seq[ScriptToken]) extends P2SHScriptSignature + private case class P2PKScriptSignatureImpl(hex : String) extends P2PKScriptSignature - private object MultiSignatureScriptSignatureImpl { - def apply(hex : String) : MultiSignatureScriptSignatureImpl = MultiSignatureScriptSignatureImpl(hex, RawScriptSignatureParser.read(hex).asm) - } - private case class MultiSignatureScriptSignatureImpl(hex : String, asm : Seq[ScriptToken]) extends MultiSignatureScriptSignature + private case class MultiSignatureScriptSignatureImpl(hex : String) extends MultiSignatureScriptSignature - private object P2PKScriptSignatureImpl { - def apply(hex : String) : P2PKScriptSignatureImpl = P2PKScriptSignatureImpl(hex, RawScriptSignatureParser.read(hex).asm) - } - private case class P2PKScriptSignatureImpl(hex : String, asm : Seq[ScriptToken]) extends P2PKScriptSignature + private case class P2SHScriptSignatureImpl(hex : String) extends P2SHScriptSignature + + private case class P2PKHScriptSignatureImpl(hex : String) extends P2PKHScriptSignature /** - * Builds a script signature from a digital signature and a public key - * this is a pay to public key hash script sig - * - * @param signature - * @param pubKey - * @return - */ - def factory(signature : ECDigitalSignature, pubKey : ECPublicKey) : ScriptSignature = { - val signatureBytesToPushOntoStack = BytesToPushOntoStack(signature.bytes.size) - val pubKeyBytesToPushOntoStack = BytesToPushOntoStack(pubKey.bytes.size) - val asm : Seq[ScriptToken] = Seq(signatureBytesToPushOntoStack, ScriptConstant(signature.hex), - pubKeyBytesToPushOntoStack, ScriptConstant(pubKey.hex)) - fromAsm(asm) + * Builds a script signature from a digital signature and a public key + * this is a pay to public key hash script sig + * + * @param signature + * @param pubKey + * @return + */ + def factory(signature : ECDigitalSignature, pubKey : ECPublicKey) : ScriptSignature = { + val signatureBytesToPushOntoStack = BytesToPushOntoStack(signature.bytes.size) + val pubKeyBytesToPushOntoStack = BytesToPushOntoStack(pubKey.bytes.size) + val asm : Seq[ScriptToken] = Seq(signatureBytesToPushOntoStack, ScriptConstant(signature.hex), + pubKeyBytesToPushOntoStack, ScriptConstant(pubKey.hex)) + 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 - * @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,tokens) - 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,tokens) - case List(w : BytesToPushOntoStack, x : ScriptConstant, y : BytesToPushOntoStack, - z : ScriptConstant) => P2PKHScriptSignatureImpl(scriptSigHex,tokens) - case List(w : BytesToPushOntoStack, x : ScriptConstant) => P2PKScriptSignatureImpl(scriptSigHex,tokens) - case _ => NonStandardScriptSignatureImpl(scriptSigHex,tokens) - } + + + /** + * 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 + } - /** - * 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,tokens) - case s : P2PKHScriptPubKey => P2PKHScriptSignatureImpl(scriptSigHex,tokens) - case s : P2PKScriptPubKey => P2PKScriptSignatureImpl(scriptSigHex,tokens) - case s : MultiSignatureScriptPubKey => MultiSignatureScriptSignatureImpl(scriptSigHex,tokens) - case s : NonStandardScriptPubKey => NonStandardScriptSignatureImpl(scriptSigHex, tokens) - case EmptyScriptPubKey if (tokens.size == 0) => EmptyScriptSignature - case EmptyScriptPubKey => NonStandardScriptSignatureImpl(scriptSigHex,tokens) - } - } - - - - - /** - * 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 [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 - } + /** + * Checks if the given script tokens are a multisignature script sig + * format: OP_0 [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(hex : String) : ScriptSignature = fromHex(hex)