Fixing parsing issue with OP_PUSHDATA operations, implementing finding digital signatures contained in ScriptSignatures

This commit is contained in:
Chris Stewart 2016-02-17 21:26:07 -06:00
parent 6bbcfa8cff
commit de8b7e15ca
6 changed files with 120 additions and 13 deletions

View File

@ -202,13 +202,50 @@ trait ScriptParser extends ScalacoinUtil {
val (constant,newTail) = sliceConstant(bytesToPushOntoStack,tail)
val scriptConstant = new ScriptConstantImpl(constant)
ParsingHelper(newTail,scriptConstant :: bytesToPushOntoStack :: accum)
case OP_PUSHDATA1 => parseOpPushData(op,accum,tail)
case OP_PUSHDATA2 => parseOpPushData(op,accum,tail)
case OP_PUSHDATA4 => parseOpPushData(op,accum,tail)
case _ =>
//means that we need to push the operation onto the stack
ParsingHelper(tail,op :: accum)
}
}
/**
* Parses OP_PUSHDATA operations correctly. Slices the appropriate amount of bytes off of the tail and pushes
* them onto the accumulator.
* @param op
* @param accum
* @param tail
* @return
*/
private def parseOpPushData(op : ScriptOperation, accum : List[ScriptToken], tail : List[Byte]) : ParsingHelper[Byte] = {
op match {
case OP_PUSHDATA1 =>
//next byte is size of the script constant
val bytesToPushOntoStack = BytesToPushOntoStackImpl(Integer.parseInt(ScalacoinUtil.encodeHex(tail.head), 16))
val scriptConstant = new ScriptConstantImpl(tail.slice(1,bytesToPushOntoStack.num+1))
ParsingHelper[Byte](tail.slice(bytesToPushOntoStack.num+1,tail.size),
scriptConstant :: bytesToPushOntoStack :: op :: accum)
case OP_PUSHDATA2 =>
//next 2 bytes is the size of the script constant
val scriptConstantHex = ScalacoinUtil.encodeHex(tail.slice(0,2))
val bytesToPushOntoStack = BytesToPushOntoStackImpl(Integer.parseInt(scriptConstantHex, 16))
val scriptConstant = new ScriptConstantImpl(tail.slice(2,bytesToPushOntoStack.num + 2))
ParsingHelper[Byte](tail.slice(bytesToPushOntoStack.num + 2,tail.size),
scriptConstant :: bytesToPushOntoStack :: op :: accum)
case OP_PUSHDATA4 =>
//nextt 4 bytes is the size of the script constant
val scriptConstantHex = ScalacoinUtil.encodeHex(tail.slice(0,4))
val bytesToPushOntoStack = BytesToPushOntoStackImpl(Integer.parseInt(scriptConstantHex, 16))
val scriptConstant = new ScriptConstantImpl(tail.slice(4,bytesToPushOntoStack.num + 4))
ParsingHelper[Byte](tail.slice(bytesToPushOntoStack.num + 4,tail.size),
scriptConstant :: bytesToPushOntoStack :: op :: accum)
case _ => throw new RuntimeException("parseOpPushData can only parse OP_PUSHDATA operations")
}
}
/**
* Parses an operation if the tail is a List[String]
* If the operation is a bytesToPushOntoStack, it pushes the number of bytes onto the stack

View File

@ -1,8 +1,8 @@
package org.scalacoin.protocol.script
import org.scalacoin.marshallers.transaction.TransactionElement
import org.scalacoin.script.constant.{OP_0, ScriptToken}
import org.scalacoin.script.crypto.{HashType, HashTypeFactory}
import org.scalacoin.script.constant._
import org.scalacoin.script.crypto.{OP_CHECKMULTISIG, HashType, HashTypeFactory}
import org.scalacoin.util.ScalacoinUtil
import org.slf4j.LoggerFactory
@ -15,17 +15,27 @@ trait ScriptSignature extends TransactionElement {
def asm : Seq[ScriptToken]
def hex : String
def signature : ScriptToken = {
def signatures : Seq[ScriptToken] = {
if (asm.headOption.isDefined && asm.head == OP_0) {
//must be p2sh because of bug that forces p2sh scripts
//to begin with OP_0
asm(2)
} else asm(1)
//scripSig for p2sh input script
//OP_0 <scriptSig> <scriptSig> ... <scriptSig> <redeemScript>
//this list still contains the ByteToPushOntoStack operations
//need to filter those out
val scriptSigs = asm.slice(1,asm.size-1)
//filter out all of the PUSHDATA / BytesToPushOntoStack operations
scriptSigs.filterNot( op => op.isInstanceOf[BytesToPushOntoStack]
|| op == OP_PUSHDATA1
|| op == OP_PUSHDATA2
|| op == OP_PUSHDATA4)
} else Seq(asm(1))
}
def hashType : HashType = {
require(HashTypeFactory.fromByte(signature.bytes.last).isDefined,
"Hash type could not be read for this scriptSig: " + signature.hex)
HashTypeFactory.fromByte(signature.bytes.last).get
require(HashTypeFactory.fromByte(signatures.head.bytes.last).isDefined,
"Hash type could not be read for this scriptSig: " + signatures.head.hex)
HashTypeFactory.fromByte(signatures.head.bytes.last).get
}
}

View File

@ -1,7 +1,7 @@
package org.scalacoin.marshallers.script
import org.scalacoin.protocol.script.ScriptSignature
import org.scalacoin.script.constant.{BytesToPushOntoStackImpl, ScriptConstantImpl}
import org.scalacoin.script.constant.{OP_PUSHDATA1, ScriptConstantImpl, BytesToPushOntoStackImpl, OP_0}
import org.scalacoin.util.TestUtil
import org.scalatest.{FlatSpec, MustMatchers}
@ -33,4 +33,26 @@ class RawScriptSignatureParserTest extends FlatSpec with MustMatchers with RawSc
scriptSig.asm must be (Seq(BytesToPushOntoStackImpl(72), ScriptConstantImpl("3045022100ad8e961fe3c22b2647d92b078f4c0cf81b3106ea5bf8b900ab8646aa4430216f022071d4edc2b5588be20ac4c2d07edd8ed069e10b2402d3dce2d3b835ccd075f28301"), BytesToPushOntoStackImpl(65), ScriptConstantImpl("04fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746e")))
}
it must "parse a raw scriptSig for a p2sh address with a lot of signatures" in {
TestUtil.p2shInputScriptLargeSignature.asm must be (Seq(OP_0,
BytesToPushOntoStackImpl(72),
ScriptConstantImpl("3045022100a077d4fe9a81411ecb796c254d8b4e0bc73ff86a42288bc3b3ecfa1ef26c00dd02202389bf96cf38c14c3a6ccb8c688339f3fd880b724322862547a8ee3b547a9df901"),
BytesToPushOntoStackImpl(71),
ScriptConstantImpl("304402207c0692464998e7f3869f8501cdd25bbcd9d32b6fd34ae8aeae643b422a8dfd42022057eb16f8ca1f34e88babc9f8beb4c2521eb5c4dea41f8902a70d045f1c132a4401"),
BytesToPushOntoStackImpl(71),
ScriptConstantImpl("3044022024233923253c73569f4b34723a5495698bc124b099c5542a5997d13fba7d18a802203c317bddc070276c6f6c79cb3415413e608af30e4759e31b0d53eab3ca0acd4e01"),
BytesToPushOntoStackImpl(72),
ScriptConstantImpl("30450221009b9f0d8b945717d2fca3685093d547a3928d122b8894903ed51e2248303213bc022008b376422c9f2cd713b9d10b5b106d1c56c5893dcc01ae300253ed2234bdb63f01"),
BytesToPushOntoStackImpl(71),
ScriptConstantImpl("30440220257b57cb09386d82c4328461f8fe200c2f381d6b635e2a2f4ea40c8d945e9ec102201ec67d58d51a309af4d8896e9147a42944e9f9833a456f733ea5fa6954ed2fed01"),
OP_PUSHDATA1,
BytesToPushOntoStackImpl(241),
ScriptConstantImpl("55210269992fb441ae56968e5b77d46a3e53b69f136444ae65a94041fc937bdb28d93321021df31471281d4478df85bfce08a10aab82601dca949a79950f8ddf7002bd915a2102174c82021492c2c6dfcbfa4187d10d38bed06afb7fdcd72c880179fddd641ea121033f96e43d72c33327b6a4631ccaa6ea07f0b106c88b9dc71c9000bb6044d5e88a210313d8748790f2a86fb524579b46ce3c68fedd58d2a738716249a9f7d5458a15c221030b632eeb079eb83648886122a04c7bf6d98ab5dfb94cf353ee3e9382a4c2fab02102fb54a7fcaa73c307cfd70f3fa66a2e4247a71858ca731396343ad30c7c4009ce57ae")
)
)
}
}

View File

@ -117,6 +117,13 @@ class ScriptParserTest extends FlatSpec with MustMatchers with ScriptParser with
parse(str) must be (expectedScript)
}
it must "parse a OP_PUSHDATA1 correct from a scriptSig" in {
//https://tbtc.blockr.io/api/v1/tx/raw/5d254a872c9197c683ea9111fb5c0e2e0f49280a89961c45b9fea76834d335fe
val str = "4cf1" +
"55210269992fb441ae56968e5b77d46a3e53b69f136444ae65a94041fc937bdb28d93321021df31471281d4478df85bfce08a10aab82601dca949a79950f8ddf7002bd915a2102174c82021492c2c6dfcbfa4187d10d38bed06afb7fdcd72c880179fddd641ea121033f96e43d72c33327b6a4631ccaa6ea07f0b106c88b9dc71c9000bb6044d5e88a210313d8748790f2a86fb524579b46ce3c68fedd58d2a738716249a9f7d5458a15c221030b632eeb079eb83648886122a04c7bf6d98ab5dfb94cf353ee3e9382a4c2fab02102fb54a7fcaa73c307cfd70f3fa66a2e4247a71858ca731396343ad30c7c4009ce57ae"
parse(str) must be (Seq(OP_PUSHDATA1, BytesToPushOntoStackImpl(241), ScriptConstantImpl("55210269992fb441ae56968e5b77d46a3e53b69f136444ae65a94041fc937bdb28d93321021df31471281d4478df85bfce08a10aab82601dca949a79950f8ddf7002bd915a2102174c82021492c2c6dfcbfa4187d10d38bed06afb7fdcd72c880179fddd641ea121033f96e43d72c33327b6a4631ccaa6ea07f0b106c88b9dc71c9000bb6044d5e88a210313d8748790f2a86fb524579b46ce3c68fedd58d2a738716249a9f7d5458a15c221030b632eeb079eb83648886122a04c7bf6d98ab5dfb94cf353ee3e9382a4c2fab02102fb54a7fcaa73c307cfd70f3fa66a2e4247a71858ca731396343ad30c7c4009ce57ae") ))
}
it must "parse bytes from a string" in {
val str = "0xFF00"

View File

@ -1,6 +1,7 @@
package org.scalacoin.protocol.script
import org.scalacoin.script.crypto.SIGHASH_ALL
import org.scalacoin.script.constant.ScriptConstantImpl
import org.scalacoin.script.crypto.{SIGHASH_SINGLE, SIGHASH_ALL}
import org.scalacoin.util.{TestUtil, ScalacoinUtil}
import org.scalatest.{FlatSpec, MustMatchers}
@ -11,7 +12,7 @@ class ScriptSignatureTest extends FlatSpec with MustMatchers {
"ScriptSignature" must "find the digital signature for the transaction inside of a p2pkh script signature" in {
val scriptSig = ScriptSignatureFactory.factory(TestUtil.rawScriptSig)
scriptSig.signature.hex must be ("3045022100ad8e961fe3c22b2647d92b078f4c0cf81b3106ea5bf8b900ab8646aa4430216f022071d4edc2b5588be20ac4c2d07edd8ed069e10b2402d3dce2d3b835ccd075f28301")
scriptSig.signatures.head.hex must be ("3045022100ad8e961fe3c22b2647d92b078f4c0cf81b3106ea5bf8b900ab8646aa4430216f022071d4edc2b5588be20ac4c2d07edd8ed069e10b2402d3dce2d3b835ccd075f28301")
}
it must "derive the signature hash type from the signature" in {
@ -22,12 +23,31 @@ class ScriptSignatureTest extends FlatSpec with MustMatchers {
it must "find the digital signature for a p2sh script signature" in {
val scriptSig = TestUtil.p2shInputScript
scriptSig.signature.hex must be ("304402207df6dd8dad22d49c3c83d8031733c32a53719278eb7985d3b35b375d776f84f102207054f9209a1e87d55feafc90aa04c33008e5bae9191da22aeaa16efde96f41f001")
scriptSig.signatures.head.hex must be ("304402207df6dd8dad22d49c3c83d8031733c32a53719278eb7985d3b35b375d776f84f102207054f9209a1e87d55feafc90aa04c33008e5bae9191da22aeaa16efde96f41f001")
}
it must "find all the digital signatures for a p2sh script signature with a large amount of sigs" in {
val scriptSig = TestUtil.p2shInputScriptLargeSignature
println(scriptSig.asm)
scriptSig.signatures must be (Seq(
ScriptConstantImpl("3045022100a077d4fe9a81411ecb796c254d8b4e0bc73ff86a42288bc3b3ecfa1ef26c00dd02202389bf96cf38c14c3a6ccb8c688339f3fd880b724322862547a8ee3b547a9df901"),
ScriptConstantImpl("304402207c0692464998e7f3869f8501cdd25bbcd9d32b6fd34ae8aeae643b422a8dfd42022057eb16f8ca1f34e88babc9f8beb4c2521eb5c4dea41f8902a70d045f1c132a4401"),
ScriptConstantImpl("3044022024233923253c73569f4b34723a5495698bc124b099c5542a5997d13fba7d18a802203c317bddc070276c6f6c79cb3415413e608af30e4759e31b0d53eab3ca0acd4e01"),
ScriptConstantImpl("30450221009b9f0d8b945717d2fca3685093d547a3928d122b8894903ed51e2248303213bc022008b376422c9f2cd713b9d10b5b106d1c56c5893dcc01ae300253ed2234bdb63f01"),
ScriptConstantImpl("30440220257b57cb09386d82c4328461f8fe200c2f381d6b635e2a2f4ea40c8d945e9ec102201ec67d58d51a309af4d8896e9147a42944e9f9833a456f733ea5fa6954ed2fed01")
))
}
it must "find the hash type for a p2sh script signature" in {
TestUtil.p2shInputScript.hashType must be (SIGHASH_ALL)
}
it must "find the digital signature and hash type for a SIGHASH_SINGLE" in {
TestUtil.p2shInputScriptSigHashSingle.signatures.head.hex must be ("3045022100dfcfafcea73d83e1c54d444a19fb30d17317f922c19e2ff92dcda65ad09cba24022001e7a805c5672c49b222c5f2f1e67bb01f87215fb69df184e7c16f66c1f87c2903")
TestUtil.p2shInputScriptSigHashSingle.hashType must be (SIGHASH_SINGLE)
}
}

View File

@ -48,10 +48,21 @@ object TestUtil {
ScriptConstantImpl("512102b022902a0fdd71e831c37e4136c2754a59887be0618fb75336d7ab67e2982ff551ae")
)
val p2shOutputScript = "a914eda8ae08b5c9f973f49543e90a7c292367b3337c87"
val p2shOutputScriptNotParsedAsm = "OP_HASH160 eda8ae08b5c9f973f49543e90a7c292367b3337c OP_EQUAL"
val p2shOutputScriptAsm = List(OP_HASH160, BytesToPushOntoStackImpl(20), ScriptConstantImpl("eda8ae08b5c9f973f49543e90a7c292367b3337c"), OP_EQUAL)
//https://btc.blockr.io/api/v1/tx/raw/791fe035d312dcf9196b48649a5c9a027198f623c0a5f5bd4cc311b8864dd0cf
val rawP2shInputScriptSigHashSingle = "00483045022100dfcfafcea73d83e1c54d444a19fb30d17317f922c19e2ff92dcda65ad09cba24022001e7a805c5672c49b222c5f2f1e67bb01f87215fb69df184e7c16f66c1f87c290347304402204a657ab8358a2edb8fd5ed8a45f846989a43655d2e8f80566b385b8f5a70dab402207362f870ce40f942437d43b6b99343419b14fb18fa69bee801d696a39b3410b8034c695221023927b5cd7facefa7b85d02f73d1e1632b3aaf8dd15d4f9f359e37e39f05611962103d2c0e82979b8aba4591fe39cffbf255b3b9c67b3d24f94de79c5013420c67b802103ec010970aae2e3d75eef0b44eaa31d7a0d13392513cd0614ff1c136b3b1020df53ae"
def p2shInputScriptSigHashSingle = RawScriptSignatureParser.read(rawP2shInputScriptSigHashSingle)
//p2sh input with large amount of signatures
//https://tbtc.blockr.io/api/v1/tx/raw/5d254a872c9197c683ea9111fb5c0e2e0f49280a89961c45b9fea76834d335fe
val rawP2shInputScriptLargeSignature = "00483045022100a077d4fe9a81411ecb796c254d8b4e0bc73ff86a42288bc3b3ecfa1ef26c00dd02202389bf96cf38c14c3a6ccb8c688339f3fd880b724322862547a8ee3b547a9df90147304402207c0692464998e7f3869f8501cdd25bbcd9d32b6fd34ae8aeae643b422a8dfd42022057eb16f8ca1f34e88babc9f8beb4c2521eb5c4dea41f8902a70d045f1c132a4401473044022024233923253c73569f4b34723a5495698bc124b099c5542a5997d13fba7d18a802203c317bddc070276c6f6c79cb3415413e608af30e4759e31b0d53eab3ca0acd4e014830450221009b9f0d8b945717d2fca3685093d547a3928d122b8894903ed51e2248303213bc022008b376422c9f2cd713b9d10b5b106d1c56c5893dcc01ae300253ed2234bdb63f014730440220257b57cb09386d82c4328461f8fe200c2f381d6b635e2a2f4ea40c8d945e9ec102201ec67d58d51a309af4d8896e9147a42944e9f9833a456f733ea5fa6954ed2fed01" +
"4cf155210269992fb441ae56968e5b77d46a3e53b69f136444ae65a94041fc937bdb28d93321021df31471281d4478df85bfce08a10aab82601dca949a79950f8ddf7002bd915a2102174c82021492c2c6dfcbfa4187d10d38bed06afb7fdcd72c880179fddd641ea121033f96e43d72c33327b6a4631ccaa6ea07f0b106c88b9dc71c9000bb6044d5e88a210313d8748790f2a86fb524579b46ce3c68fedd58d2a738716249a9f7d5458a15c221030b632eeb079eb83648886122a04c7bf6d98ab5dfb94cf353ee3e9382a4c2fab02102fb54a7fcaa73c307cfd70f3fa66a2e4247a71858ca731396343ad30c7c4009ce57ae"
val p2shInputScriptLargeSignature = RawScriptSignatureParser.read(rawP2shInputScriptLargeSignature)
//txid on testnet 44e504f5b7649d215be05ad9f09026dee95201244a3b218013c504a6a49a26ff
//this tx has multiple inputs and outputs
val rawTransaction = "01000000" +