Adding bitcoinj util to serialize a transaction for a signature hash - makes for easier testing of the different HashType's

This commit is contained in:
Chris Stewart 2016-02-24 17:21:01 -06:00
parent a859d8342d
commit 7a94cb3976
2 changed files with 82 additions and 29 deletions

View File

@ -63,7 +63,7 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper {
}
def serialize(inputIndex : Int, script : ScriptPubKey, hashType : HashType) : Seq[Byte] = {
def serializeForSignature(inputIndex : Int, script : ScriptPubKey, hashType : HashType) : Seq[Byte] = {
// Clear input scripts in preparation for signing. If we're signing a fresh
// transaction that step isn't very helpful, but it doesn't add much cost relative to the actual
// EC math so we'll do it anyway.
@ -98,9 +98,12 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper {
else input
}
val txWithInputSigsRemoved = spendingTransaction.factory(UpdateTransactionInputs(updatedInputs))
//just need to add the hash type and hash the tx
//txWithInputSigsRemoved.bytes
val sigHashBytes : List[Byte] = List(0x00.toByte, 0x00.toByte, 0x00.toByte, hashType.byte).reverse
//check the hash type
hashType match {
case SIGHASH_NONE =>
@ -112,7 +115,7 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper {
val updatedInputs : Seq[TransactionInput] = setSequenceNumbersZero(spendingTransaction.inputs,inputIndex)
val sigHashNoneTx = txWithNoOutputs.factory(UpdateTransactionInputs(updatedInputs))
//append hash type byte onto the end of the tx bytes
CryptoUtil.doubleSHA256(sigHashNoneTx.bytes ++ List(hashType.byte))
sigHashNoneTx.bytes ++ sigHashBytes
case SIGHASH_SINGLE =>
//following this implementation from bitcoinj
//https://github.com/bitcoinj/bitcoinj/blob/09a2ca64d2134b0dcbb27b1a6eb17dda6087f448/core/src/main/java/org/bitcoinj/core/Transaction.java#L964
@ -130,7 +133,14 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper {
} else {
// In SIGHASH_SINGLE the outputs after the matching input index are deleted, and the outputs before
// that position are "nulled out". Unintuitively, the value in a "null" transaction is set to -1.
val updatedOutputs = txWithInputSigsRemoved.outputs.map(o => o.empty.factory(CurrencyUnits.negativeSatoshi))
val updatedOutputsOpt : Seq[Option[TransactionOutput]] = for {
(output,index) <- txWithInputSigsRemoved.outputs.zipWithIndex
} yield {
if (index < inputIndex) Some(output.empty.factory(CurrencyUnits.negativeSatoshi))
else None
}
val updatedOutputs : Seq[TransactionOutput] = updatedOutputsOpt.flatten
val spendingTxOutputsEmptied = txWithInputSigsRemoved.factory(UpdateTransactionOutputs(updatedOutputs))
//create blank inputs with sequence numbers set to zero EXCEPT
//the input at the inputIndex
@ -138,17 +148,20 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper {
val sigHashSingleTx = spendingTxOutputsEmptied.factory(UpdateTransactionInputs(updatedInputs))
//append hash type byte onto the end of the tx bytes
CryptoUtil.doubleSHA256(sigHashSingleTx.bytes ++ List(hashType.byte))
sigHashSingleTx.bytes ++ sigHashBytes
}
case SIGHASH_ALL =>
//just need to add the hash type and hash the tx
//txWithInputSigsRemoved.bytes
val sigHashBytes : List[Byte] = List(0x00.toByte, 0x00.toByte, 0x00.toByte, hashType.byte).reverse
CryptoUtil.doubleSHA256(txWithInputSigsRemoved.bytes ++ sigHashBytes)
txWithInputSigsRemoved.bytes ++ sigHashBytes
}
}
def hashForSignature(inputIndex : Int, script : ScriptPubKey, hashType : HashType) : Seq[Byte] = {
val serializedTxForSignature = serializeForSignature(inputIndex,script,hashType)
CryptoUtil.doubleSHA256(serializedTxForSignature)
}
/**
* Removes OP_CODESEPARATOR operations then returns the script in hex
* format

View File

@ -11,9 +11,9 @@ import org.scalacoin.protocol.transaction._
import org.scalacoin.script.ScriptOperationFactory
import org.scalacoin.script.bitwise.OP_EQUALVERIFY
import org.scalacoin.script.constant._
import org.scalacoin.script.crypto.{OP_CHECKSIG, OP_HASH160, SIGHASH_ALL, OP_CODESEPARATOR}
import org.scalacoin.script.crypto._
import org.scalacoin.script.stack.OP_DUP
import org.scalacoin.util.{BitcoinjConversions, ScalacoinUtil, TestUtil}
import org.scalacoin.util._
import org.scalatest.{FlatSpec, MustMatchers}
import scala.collection.JavaConversions._
@ -22,8 +22,12 @@ import scala.collection.JavaConversions._
* Created by chris on 2/19/16.
*/
class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers {
val params = TestNet3Params.get();
val key1 = new DumpedPrivateKey(params, "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT").getKey();
val key2 = new DumpedPrivateKey(params, "cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo").getKey();
val key3 = new DumpedPrivateKey(params, "cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg").getKey();
val multiSigScript : org.bitcoinj.script.Script = ScriptBuilder.createMultiSigOutputScript(2, util.Arrays.asList(key1, key2, key3));
val scriptPubKey = BitcoinjConversions.toScriptPubKey(multiSigScript)
"TransactionSignatureSerializer" must "serialize a given script signature without OP_CODESEPARATORS" in {
val txSerializer = new BaseTransactionSignatureSerializer(TestUtil.transaction)
val scriptPubKey = TestUtil.scriptPubKey
@ -31,14 +35,35 @@ class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers {
txSerializer.serializeScriptCode(scriptPubKey) must be (expectedScript)
}
it must "serialize a transaction for SIGHASH_ALL correctly" in {
require(scriptPubKey.hex == ScalacoinUtil.encodeHex(multiSigScript.getProgram), "Script pub key hex not the same as multiSigScript hex")
val spendingTx = Transaction.factory(bitcoinjMultiSigTransaction.bitcoinSerialize())
it must "hash a multisignature SIGHASH_ALL correctly" in {
spendingTx.hex must be (ScalacoinUtil.encodeHex(bitcoinjMultiSigTransaction.bitcoinSerialize()))
val txSignatureSerializer = new BaseTransactionSignatureSerializer(spendingTx)
val sigBytes : Seq[Byte] = txSignatureSerializer.serializeForSignature(0,scriptPubKey,SIGHASH_ALL)
val bitcoinjSerialization = ScalacoinUtil.encodeHex(
BitcoinJSignatureSerialization.serializeForSignature(bitcoinjMultiSigTransaction,0,multiSigScript.getProgram(),SIGHASH_ALL.byte)
)
ScalacoinUtil.encodeHex(sigBytes) must be (bitcoinjSerialization)
}
val params = TestNet3Params.get();
val key1 = new DumpedPrivateKey(params, "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT").getKey();
val key2 = new DumpedPrivateKey(params, "cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo").getKey();
val key3 = new DumpedPrivateKey(params, "cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg").getKey();
it must "hash a tranasction with SIGHASH_ALL correctly" in {
require(scriptPubKey.hex == ScalacoinUtil.encodeHex(multiSigScript.getProgram), "Script pub key hex not the same as multiSigScript hex")
val spendingTx = Transaction.factory(bitcoinjMultiSigTransaction.bitcoinSerialize())
spendingTx.hex must be (ScalacoinUtil.encodeHex(bitcoinjMultiSigTransaction.bitcoinSerialize()))
val txSignatureSerializer = new BaseTransactionSignatureSerializer(spendingTx)
val bitcoinsTxSigHash : Seq[Byte] = txSignatureSerializer.hashForSignature(0,scriptPubKey,SIGHASH_ALL)
val bitcoinjTxSigHash = ScalacoinUtil.encodeHex(
BitcoinJSignatureSerialization.hashForSignature(bitcoinjMultiSigTransaction,0,multiSigScript.getProgram(),SIGHASH_ALL.byte)
)
ScalacoinUtil.encodeHex(bitcoinsTxSigHash) must be (bitcoinjTxSigHash)
}
/*
it must "hash a multisignature SIGHASH_SINGLE correctly with one output and one input" in {
val multiSigScript : org.bitcoinj.script.Script = ScriptBuilder.createMultiSigOutputScript(2, util.Arrays.asList(key1, key2, key3));
val scriptPubKey = BitcoinjConversions.toScriptPubKey(multiSigScript)
require(scriptPubKey.hex == ScalacoinUtil.encodeHex(multiSigScript.getProgram), "Script pub key hex not the same as multiSigScript hex")
@ -47,9 +72,15 @@ class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers {
spendingTx.hex must be (ScalacoinUtil.encodeHex(bitcoinjMultiSigTransaction.bitcoinSerialize()))
val txSignatureSerializer = new BaseTransactionSignatureSerializer(spendingTx)
val sigBytes : Seq[Byte] = txSignatureSerializer.serialize(0,scriptPubKey,SIGHASH_ALL)
ScalacoinUtil.encodeHex(sigBytes) must be (createBitcoinjMultiSigScriptHashForSig)
}
val serialiazedTxForSig : Seq[Byte] = txSignatureSerializer.serializeForSignature(0,scriptPubKey,SIGHASH_SINGLE)
val bitcoinjSigSerialization = ScalacoinUtil.encodeHex(BitcoinJSignatureSerialization.serializeForSignature(
bitcoinjMultiSigTransaction,0,multiSigScript.getProgram,SIGHASH_SINGLE.byte))
ScalacoinUtil.encodeHex(serialiazedTxForSig) must be (bitcoinjSigSerialization)
}*/
/**
@ -58,15 +89,24 @@ class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers {
* hashes a bitcoinj tx for a signature
* @return
*/
private def createBitcoinjMultiSigScriptHashForSig : String = {
val params = TestNet3Params.get()
val key1 = new DumpedPrivateKey(params, "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT").getKey()
val key2 = new DumpedPrivateKey(params, "cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo").getKey()
val key3 = new DumpedPrivateKey(params, "cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg").getKey()
private def createBitcoinjMultiSigScriptHashForSig(hashType : HashType) : String = {
val spendTx = bitcoinjMultiSigTransaction
val multisigScript = ScriptBuilder.createMultiSigOutputScript(2, util.Arrays.asList(key1, key2, key3))
val sighash = spendTx.hashForSignature(0, multisigScript, SigHash.ALL, false)
ScalacoinUtil.encodeHex(sighash.getBytes)
val sighash : String = hashType match {
case SIGHASH_ALL => ScalacoinUtil.encodeHex(spendTx.hashForSignature(0, multiSigScript, SigHash.ALL, false).getBytes)
case SIGHASH_SINGLE => ScalacoinUtil.encodeHex(spendTx.hashForSignature(0,multiSigScript,SigHash.SINGLE, false).getBytes)
case SIGHASH_NONE => ScalacoinUtil.encodeHex(spendTx.hashForSignature(0,multiSigScript,SigHash.NONE, false).getBytes)
}
/* val bitcoinjReplicaSerialization : String = BitcoinjConversions.signatureSerialization(spendTx,0,multiSigScript.getProgram,hashType.byte)
val bitcoinjReplicaHash : String = ScalacoinUtil.encodeHex(CryptoUtil.doubleSHA256(bitcoinjReplicaSerialization))
println("Bitcoinj Replica Serialization: " + bitcoinjReplicaSerialization)
require(bitcoinjReplicaHash == sighash, "Bitcoinj replica hash and actual sighash from bitcoinj were different\n" +
"Actual bitcoinj hash: " + sighash + "\n" +
"Replica bitcoinj hash: " + bitcoinjReplicaHash)*/
sighash
//BitcoinjConversions.signatureSerialization(spendTx,0,multisigScript.getProgram,hashType.byte)
}