diff --git a/src/main/scala/org/scalacoin/crypto/TransactionSignatureSerializer.scala b/src/main/scala/org/scalacoin/crypto/TransactionSignatureSerializer.scala new file mode 100644 index 0000000000..c9935cd6b1 --- /dev/null +++ b/src/main/scala/org/scalacoin/crypto/TransactionSignatureSerializer.scala @@ -0,0 +1,81 @@ +package org.scalacoin.crypto + +import org.scalacoin.marshallers.RawBitcoinSerializerHelper +import org.scalacoin.protocol.script.{ScriptPubKeyFactory, ScriptPubKey} +import org.scalacoin.protocol.transaction.{Transaction, TransactionOutput, TransactionInput} +import org.scalacoin.script.constant.ScriptToken +import org.scalacoin.script.crypto._ + +/** + * Created by chris on 2/16/16. + * A trait used to serialize various components of a bitcoin transaction for + * hashing to compare against a digital signature + * https://github.com/bitcoin/bitcoin/blob/93c85d458ac3e2c496c1a053e1f5925f55e29100/src/script/interpreter.cpp#L1016-L1105 + */ +trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper { + + /** + * Serialized the passed in script code, skipping OP_CODESEPARATORs + * definition for CScript https://github.com/bitcoin/bitcoin/blob/93c85d458ac3e2c496c1a053e1f5925f55e29100/src/script/script.h#L373 + * @param script + * @return + */ + def serializeScriptCode(script : Seq[ScriptToken]) : String = { + val serializedScript : String = removeOpCodeSeparators(script) + serializedScript + } + + def serializeInput(input : TransactionInput, nType : Int, nVersion : Int) : String = ??? + + /** + * Serializes an output of a transaction + * https://github.com/bitcoin/bitcoin/blob/93c85d458ac3e2c496c1a053e1f5925f55e29100/src/script/interpreter.cpp#L1079 + * @param output + * @param nType + * @param nVersion + * @return + */ + def serializeOutput(output : TransactionOutput, nType : Int, nVersion : Int) : String = ??? + + def serialize(spendingTransaction : Transaction, nIn : Int, nType : Int, nVersion : Int, nHashTypeIn : HashType) : String = { + val serializedVersion =spendingTransaction.version.toHexString + val serializedNIn = nHashTypeIn match { + case SIGHASH_ANYONECANPAY => "01" + case _ => addPrecedingZero(spendingTransaction.inputs.size.toHexString) + } + val serializedInputs = for { + input <- spendingTransaction.inputs + } yield serializeInput(input,nType,nVersion) + + val serializedNOut = nHashTypeIn match { + case SIGHASH_NONE => "00" + case SIGHASH_SINGLE => addPrecedingZero(nIn.toHexString + 1) + case _ => addPrecedingZero(spendingTransaction.outputs.size.toHexString) + } + + val serializedOutputs = for { + output <- spendingTransaction.outputs + } yield serializeOutput(output, nType,nVersion) + + val serializedLockTime = addPrecedingZero(spendingTransaction.lockTime.toHexString) + + serializedVersion + serializedNIn + serializedInputs.mkString + serializedNOut + + serializedOutputs.mkString + serializedLockTime + } + + /** + * Removes OP_CODESEPARATOR operations then returns the script in hex + * format + * @return + */ + protected def removeOpCodeSeparators(script : Seq[ScriptToken]) : String = { + val scriptWithoutOpCodeSeparators : String = script.filterNot(_ == OP_CODESEPARATOR).map(_.hex).mkString + val scriptWithoutOpCodeSeparatorSize = addPrecedingZero((scriptWithoutOpCodeSeparators.size / 2).toHexString) + val expectedScript : ScriptPubKey = ScriptPubKeyFactory.factory( + scriptWithoutOpCodeSeparatorSize + scriptWithoutOpCodeSeparators) + expectedScript.hex + } +} + + +object TransactionSignatureSerializer extends TransactionSignatureSerializer diff --git a/src/main/scala/org/scalacoin/marshallers/RawBitcoinSerializer.scala b/src/main/scala/org/scalacoin/marshallers/RawBitcoinSerializer.scala index cebefaf114..1e0c9b337a 100644 --- a/src/main/scala/org/scalacoin/marshallers/RawBitcoinSerializer.scala +++ b/src/main/scala/org/scalacoin/marshallers/RawBitcoinSerializer.scala @@ -5,7 +5,7 @@ import org.scalacoin.util.ScalacoinUtil /** * Created by chris on 1/11/16. */ -trait RawBitcoinSerializer[T] { +trait RawBitcoinSerializer[T] extends RawBitcoinSerializerHelper { /** * Reads a hexadecimal value and transforms it into the native @@ -36,30 +36,4 @@ trait RawBitcoinSerializer[T] { */ def write(t : T) : String - - /** - * Adds the amount padding bytes needed to fix the size of the hex string - * for instance, vouts are required to be 4 bytes. If the number is just 1 - * it will only take 1 byte. We need to pad the byte with an extra 3 bytes so the result is - * 01000000 instead of just 01 - * @param charactersNeeded - * @param hex - * @return - */ - def addPadding(charactersNeeded : Int, hex : String) : String = { - val paddingNeeded = charactersNeeded - hex.size - val padding = for { i <- 0 until paddingNeeded} yield "0" - val paddedHex = hex + padding.mkString - paddedHex - } - - /** - * Adds a preceding zero to a hex string. - * Example: if '1' was passed in, it would return the hex string '01' - * @param hex - * @return - */ - def addPrecedingZero(hex : String) = { - if (hex.size == 1) "0" + hex else hex - } } diff --git a/src/main/scala/org/scalacoin/marshallers/RawBitcoinSerializerHelper.scala b/src/main/scala/org/scalacoin/marshallers/RawBitcoinSerializerHelper.scala new file mode 100644 index 0000000000..ad1d75ceec --- /dev/null +++ b/src/main/scala/org/scalacoin/marshallers/RawBitcoinSerializerHelper.scala @@ -0,0 +1,35 @@ +package org.scalacoin.marshallers + +/** + * Created by chris on 2/18/16. + */ +trait RawBitcoinSerializerHelper { + + /** + * Adds the amount padding bytes needed to fix the size of the hex string + * for instance, vouts are required to be 4 bytes. If the number is just 1 + * it will only take 1 byte. We need to pad the byte with an extra 3 bytes so the result is + * 01000000 instead of just 01 + * @param charactersNeeded + * @param hex + * @return + */ + def addPadding(charactersNeeded : Int, hex : String) : String = { + val paddingNeeded = charactersNeeded - hex.size + val padding = for { i <- 0 until paddingNeeded} yield "0" + val paddedHex = hex + padding.mkString + paddedHex + } + + /** + * Adds a preceding zero to a hex string. + * Example: if '1' was passed in, it would return the hex string '01' + * @param hex + * @return + */ + def addPrecedingZero(hex : String) = { + if (hex.size == 1) "0" + hex else hex + } +} + +object RawBitcoinSerializerHelper extends RawBitcoinSerializerHelper \ No newline at end of file diff --git a/src/main/scala/org/scalacoin/protocol/script/ScriptSignature.scala b/src/main/scala/org/scalacoin/protocol/script/ScriptSignature.scala index 2ad21a80b5..11d389a278 100644 --- a/src/main/scala/org/scalacoin/protocol/script/ScriptSignature.scala +++ b/src/main/scala/org/scalacoin/protocol/script/ScriptSignature.scala @@ -1,6 +1,7 @@ package org.scalacoin.protocol.script import org.scalacoin.marshallers.transaction.TransactionElement + import org.scalacoin.script.constant._ import org.scalacoin.script.crypto.{OP_CHECKMULTISIG, HashType, HashTypeFactory} import org.scalacoin.util.ScalacoinUtil @@ -22,7 +23,6 @@ trait ScriptSignature extends TransactionElement { def asm : Seq[ScriptToken] def hex : String - /** * The digital signatures contained inside of the script signature * p2pkh script signatures only have one sig diff --git a/src/test/scala/org/scalacoin/crypto/TransactionSignatureSerializerTest.scala b/src/test/scala/org/scalacoin/crypto/TransactionSignatureSerializerTest.scala new file mode 100644 index 0000000000..b71240e7c5 --- /dev/null +++ b/src/test/scala/org/scalacoin/crypto/TransactionSignatureSerializerTest.scala @@ -0,0 +1,32 @@ +package org.scalacoin.crypto + +import org.scalacoin.protocol.script.{ScriptPubKey, ScriptPubKeyFactory} +import org.scalacoin.script.constant.{OP_2, OP_0, OP_1, ScriptToken} +import org.scalacoin.script.crypto.OP_CODESEPARATOR +import org.scalacoin.util.{ScalacoinUtil, TestUtil} +import org.scalatest.{FlatSpec, MustMatchers} + +/** + * Created by chris on 2/19/16. + */ +class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers with TransactionSignatureSerializer { + + + "TransactionSignatureSerializer" must "serialize a given script signature without OP_CODESEPARATORS" in { + val scriptPubKey = TestUtil.scriptPubKey.asm + val expectedScript = removeOpCodeSeparators(scriptPubKey) + serializeScriptCode(scriptPubKey) must be (expectedScript) + } + + it must "serialize a given script with only OP_CODESEPARATORs" in { + val script = List(OP_CODESEPARATOR) + serializeScriptCode(script) must be ("00") + } + + it must "serialize a given script with mixed in OP_CODESEPARATORs" in { + val script = List(OP_CODESEPARATOR, OP_1, OP_CODESEPARATOR, OP_0, OP_CODESEPARATOR, OP_2) + serializeScriptCode(script) must be ("03510052") + } + + +} diff --git a/src/test/scala/org/scalacoin/marshallers/script/RawScriptPubKeyParserTest.scala b/src/test/scala/org/scalacoin/marshallers/script/RawScriptPubKeyParserTest.scala index d81a1ed59d..c25887b203 100644 --- a/src/test/scala/org/scalacoin/marshallers/script/RawScriptPubKeyParserTest.scala +++ b/src/test/scala/org/scalacoin/marshallers/script/RawScriptPubKeyParserTest.scala @@ -5,6 +5,7 @@ import org.scalacoin.script.bitwise.OP_EQUALVERIFY import org.scalacoin.script.constant.{BytesToPushOntoStackImpl, ScriptConstantImpl} import org.scalacoin.script.crypto.{OP_CHECKSIG, OP_HASH160} import org.scalacoin.script.stack.OP_DUP +import org.scalacoin.util.TestUtil import org.scalatest.{FlatSpec, MustMatchers} /** @@ -13,18 +14,16 @@ import org.scalatest.{FlatSpec, MustMatchers} class RawScriptPubKeyParserTest extends FlatSpec with MustMatchers with RawScriptPubKeyParser { - //scriptPubKey taken from https://bitcoin.org/en/developer-reference#raw-transaction-format - val rawScriptPubKey = "1976a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac" "RawScriptPubKeyParser" must "parse a hex string into a scriptPubKey" in { - val scriptPubKey : ScriptPubKey = read(rawScriptPubKey) + val scriptPubKey : ScriptPubKey = read(TestUtil.rawScriptPubKey) scriptPubKey.asm must be (Seq(OP_DUP,OP_HASH160, BytesToPushOntoStackImpl(20), ScriptConstantImpl("cbc20a7664f2f69e5355aa427045bc15e7c6c772"),OP_EQUALVERIFY,OP_CHECKSIG)) } it must "read then write the scriptPubKey and get the original scriptPubKey" in { - val scriptPubKey : ScriptPubKey = read(rawScriptPubKey) - write(scriptPubKey) must be (rawScriptPubKey) + val scriptPubKey : ScriptPubKey = read(TestUtil.rawScriptPubKey) + write(scriptPubKey) must be (TestUtil.rawScriptPubKey) } } diff --git a/src/test/scala/org/scalacoin/protocol/script/ScriptSignatureTest.scala b/src/test/scala/org/scalacoin/protocol/script/ScriptSignatureTest.scala index 2f0de502e7..1543cd1e2a 100644 --- a/src/test/scala/org/scalacoin/protocol/script/ScriptSignatureTest.scala +++ b/src/test/scala/org/scalacoin/protocol/script/ScriptSignatureTest.scala @@ -1,5 +1,6 @@ package org.scalacoin.protocol.script + import org.scalacoin.script.constant.ScriptConstantImpl import org.scalacoin.script.crypto.{SIGHASH_SINGLE, SIGHASH_ALL} import org.scalacoin.util.{TestUtil, ScalacoinUtil} @@ -53,7 +54,4 @@ class ScriptSignatureTest extends FlatSpec with MustMatchers { TestUtil.p2shInputScriptSigHashSingle.hashType(TestUtil.p2shInputScriptSigHashSingle.signatures.head.hex) must be (SIGHASH_SINGLE) } - - - } diff --git a/src/test/scala/org/scalacoin/util/TestUtil.scala b/src/test/scala/org/scalacoin/util/TestUtil.scala index 154da79468..458d2c048d 100644 --- a/src/test/scala/org/scalacoin/util/TestUtil.scala +++ b/src/test/scala/org/scalacoin/util/TestUtil.scala @@ -89,6 +89,7 @@ object TestUtil { //txid b30d3148927f620f5b1228ba941c211fdabdae75d0ba0b688a58accbf018f3cc val parentSimpleRawTransaction = "0100000001cda741646fada7272b900719f7ac9d68d633d0e8aa9501eed3c90afbd323bd65010000006a4730440220048e15422cf62349dc586ffb8c749d40280781edd5064ff27a5910ff5cf225a802206a82685dbc2cf195d158c29309939d5a3cd41a889db6f766f3809fff35722305012103dcfc9882c1b3ae4e03fb6cac08bdb39e284e81d70c7aa8b27612457b2774509bffffffff026c405d05000000001976a91431a420903c05a0a7de2de40c9f02ebedbacdc17288ac809698000000000017a914af575bd77c5ce7eba3bd9ce6f89774713ae62c798700000000" def parentSimpleTransaction = RawTransactionParser.read(parentSimpleRawTransaction) + //scriptPubKey taken from https://bitcoin.org/en/developer-reference#raw-transaction-format val rawScriptPubKey = "1976a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac" def scriptPubKey = RawScriptPubKeyParser.read(rawScriptPubKey)