Fixing bug in SIGHASH_SINGLE implementation - now added a regression test in the translated bitcoinj hashForSignature & my home grown translated version to make sure the hashes are identical

This commit is contained in:
Chris Stewart 2016-03-09 18:19:12 -06:00
parent 22fa7af6dc
commit c800777981
7 changed files with 83 additions and 81 deletions

View file

@ -82,6 +82,7 @@ trait TransactionSignatureChecker {
} yield { } yield {
val hashType = p2shScriptSignature.hashType(sig) val hashType = p2shScriptSignature.hashType(sig)
val hashForSig = TransactionSignatureSerializer.hashForSignature(spendingTransaction,inputIndex,x,hashType) val hashForSig = TransactionSignatureSerializer.hashForSignature(spendingTransaction,inputIndex,x,hashType)
logger.info("pubKey: " + pubKey)
logger.info("sig: " + sig) logger.info("sig: " + sig)
logger.info("Hash for sig: " + BitcoinSUtil.encodeHex(hashForSig)) logger.info("Hash for sig: " + BitcoinSUtil.encodeHex(hashForSig))
pubKey.verify(hashForSig, sig) pubKey.verify(hashForSig, sig)
@ -89,7 +90,7 @@ trait TransactionSignatureChecker {
logger.info("P2SH sigs: " + p2shScriptSignature.signatures) logger.info("P2SH sigs: " + p2shScriptSignature.signatures)
logger.info("P2SH pub keys: " + p2shScriptSignature.publicKeys) logger.info("P2SH pub keys: " + p2shScriptSignature.publicKeys)
logger.info("P2SH sigs & keys: " + p2shScriptSignature.signatures.zip(p2shScriptSignature.publicKeys)) logger.info("P2SH sigs & keys: " + p2shScriptSignature.signatures.zip(p2shScriptSignature.publicKeys))
logger.info("Redeem script: " + p2shScriptSignature.redeemScript) logger.info("Redeem script: " + p2shScriptSignature.redeemScript.asm)
logger.info("Results from checking p2sh scriptSig: " + result) logger.info("Results from checking p2sh scriptSig: " + result)
!result.contains(false) !result.contains(false)
case x : MultiSignatureScriptPubKey => case x : MultiSignatureScriptPubKey =>

View file

@ -263,6 +263,7 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper {
(output,index) <- spendingTransaction.outputs.zipWithIndex (output,index) <- spendingTransaction.outputs.zipWithIndex
} yield { } yield {
if (index < inputIndex) Some(output.empty.factory(CurrencyUnits.negativeSatoshi)) if (index < inputIndex) Some(output.empty.factory(CurrencyUnits.negativeSatoshi))
else if (index == inputIndex) Some(output)
else None else None
} }
val updatedOutputs : Seq[TransactionOutput] = updatedOutputsOpt.flatten val updatedOutputs : Seq[TransactionOutput] = updatedOutputsOpt.flatten

View file

@ -68,7 +68,7 @@ trait ScriptParser extends Factory[List[ScriptToken]] {
* @return * @return
*/ */
private def parse(str : String) : List[ScriptToken] = { private def parse(str : String) : List[ScriptToken] = {
logger.info("Parsing string: " + str + " into a list of script tokens") logger.debug("Parsing string: " + str + " into a list of script tokens")
@tailrec @tailrec
def loop(operations : List[String], accum : List[ScriptToken]) : List[ScriptToken] = { def loop(operations : List[String], accum : List[ScriptToken]) : List[ScriptToken] = {
@ -168,7 +168,7 @@ trait ScriptParser extends Factory[List[ScriptToken]] {
* @return * @return
*/ */
private def parse(bytes : List[Byte]) : List[ScriptToken] = { private def parse(bytes : List[Byte]) : List[ScriptToken] = {
logger.info("Parsing byte list: " + bytes + " into a list of script tokens") logger.debug("Parsing byte list: " + bytes + " into a list of script tokens")
@tailrec @tailrec
def loop(bytes : List[Byte], accum : List[ScriptToken]) : List[ScriptToken] = { def loop(bytes : List[Byte], accum : List[ScriptToken]) : List[ScriptToken] = {
logger.debug("Byte to be parsed: " + bytes.headOption) logger.debug("Byte to be parsed: " + bytes.headOption)
@ -206,7 +206,7 @@ trait ScriptParser extends Factory[List[ScriptToken]] {
* @return * @return
*/ */
private def isRedeemScript(token : ScriptToken) : Boolean = { private def isRedeemScript(token : ScriptToken) : Boolean = {
logger.info("Checking if last token is redeem script") logger.debug("Checking if last token is redeem script")
val tryRedeemScript = parseRedeemScript(token) val tryRedeemScript = parseRedeemScript(token)
tryRedeemScript match { tryRedeemScript match {
case Success(redeemScript) => case Success(redeemScript) =>

View file

@ -9,8 +9,6 @@ import org.scalacoin.script.crypto.{OP_CHECKMULTISIG, HashType, HashTypeFactory}
import org.scalacoin.util.{BitcoinSUtil} import org.scalacoin.util.{BitcoinSUtil}
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
//TODO: Need to add a scriptPubKey field to this script signature
//this corresponds to the output script that this scriptSignature is input for
/** /**
* Created by chris on 12/26/15. * Created by chris on 12/26/15.
* *
@ -62,60 +60,10 @@ sealed trait ScriptSignature extends TransactionElement {
} }
/**
* Splits the given asm into two parts
* the first part is the digital signatures
* the second part is the redeem script
* @param asm
* @return
*/
def splitAtRedeemScript(asm : Seq[ScriptToken]) : (Seq[ScriptToken],Seq[ScriptToken]) = {
//using the first instance of a ScriptNumberOperation (i.e. OP_2, OP_3 etc...) as the beginning
//of the redeemScript
val result : Option[(ScriptToken,Int)] = asm.headOption match {
case Some(OP_0) =>
//skip the first index since OP_0 is put in input scripts because of a bug
//in the original bitcoin implementation
val r = asm.tail.zipWithIndex.find { case (token, index) => (token.isInstanceOf[ScriptNumberOperation])}
//need to increment the result by one since the index is relative to the
//tail of the list
r.map(res => (res._1,res._2+1))
case Some(_) => asm.zipWithIndex.find { case (token, index) => (token.isInstanceOf[ScriptNumberOperation]) }
case None => asm.zipWithIndex.find { case (token, index) => (token.isInstanceOf[ScriptNumberOperation]) }
}
if (result.isDefined) asm.splitAt(result.get._2)
else (asm,List())
}
} }
trait NonStandardScriptSignature extends ScriptSignature { trait NonStandardScriptSignature extends ScriptSignature {
def signatures : Seq[ECDigitalSignature] = { def signatures : Seq[ECDigitalSignature] = ???
if (asm.headOption.isDefined && asm.head == OP_0 && asm.contains(OP_CHECKMULTISIG)) {
//must be p2sh because of bug that forces p2sh scripts
//to begin with OP_0
//scripSig for p2sh input script
//OP_0 <scriptSig> <scriptSig> ... <scriptSig> <redeemScript>
val (scriptSigs,_) = splitAtRedeemScript(asm)
logger.info("Script sigs: " + scriptSigs)
//filter out all of the PUSHDATA / BytesToPushOntoStack operations
val scriptSigsWithoutPushOps = filterPushOps(scriptSigs)
//remove the OP_0 that precedes every p2sh input script
val scriptSigsWithoutPushOpsAndOp0 = scriptSigsWithoutPushOps.tail
scriptSigsWithoutPushOpsAndOp0.map(sig => ECFactory.digitalSignature(sig.bytes))
} else if (asm.headOption.isDefined && asm.head == OP_0) {
//this means we have a traditional multisignature scriptSig
val scriptSigs = asm.slice(1,asm.size)
val scriptSigsWithoutPushOps = filterPushOps(scriptSigs)
//remove the OP_0 that precedes every multisignature input script
val scriptSigsWithoutPushOpsAndOp0 = scriptSigsWithoutPushOps.tail
scriptSigsWithoutPushOpsAndOp0.map(sig => ECFactory.digitalSignature(sig.bytes))
scriptSigsWithoutPushOps.map(sig => ECFactory.digitalSignature(sig.bytes))
} else Seq(ECFactory.digitalSignature(asm(1).bytes))
}
} }
case class NonStandardScriptSignatureImpl(hex : String) extends NonStandardScriptSignature case class NonStandardScriptSignatureImpl(hex : String) extends NonStandardScriptSignature
@ -128,6 +76,13 @@ case class NonStandardScriptSignatureImpl(hex : String) extends NonStandardScrip
*/ */
trait P2PKHScriptSignature extends ScriptSignature { trait P2PKHScriptSignature extends ScriptSignature {
/**
* 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 * @return
@ -173,6 +128,34 @@ trait P2SHScriptSignature extends ScriptSignature {
sigs.map(s => ECFactory.digitalSignature(s.hex)) sigs.map(s => ECFactory.digitalSignature(s.hex))
} }
/**
* Splits the given asm into two parts
* the first part is the digital signatures
* the second part is the redeem script
* @param asm
* @return
*/
def splitAtRedeemScript(asm : Seq[ScriptToken]) : (Seq[ScriptToken],Seq[ScriptToken]) = {
//using the first instance of a ScriptNumberOperation (i.e. OP_2, OP_3 etc...) as the beginning
//of the redeemScript
val result : Option[(ScriptToken,Int)] = asm.headOption match {
case Some(OP_0) =>
//skip the first index since OP_0 is put in input scripts because of a bug
//in the original bitcoin implementation
val r = asm.tail.zipWithIndex.find { case (token, index) => (token.isInstanceOf[ScriptNumberOperation])}
//need to increment the result by one since the index is relative to the
//tail of the list
r.map(res => (res._1,res._2+1))
case Some(_) => asm.zipWithIndex.find { case (token, index) => (token.isInstanceOf[ScriptNumberOperation]) }
case None => asm.zipWithIndex.find { case (token, index) => (token.isInstanceOf[ScriptNumberOperation]) }
}
if (result.isDefined) asm.splitAt(result.get._2)
else (asm,List())
}
} }
/** /**
@ -189,7 +172,7 @@ trait MultiSignatureScriptSignature extends ScriptSignature {
def signatures : Seq[ECDigitalSignature] = { def signatures : Seq[ECDigitalSignature] = {
asm.filter(_.isInstanceOf[ScriptConstant]) asm.filter(_.isInstanceOf[ScriptConstant])
.filterNot(_.isInstanceOf[ScriptNumberOperation]) .filterNot(_.isInstanceOf[ScriptNumberOperation])
.map( sig => ECFactory.digitalSignature(sig.hex)) .map(sig => ECFactory.digitalSignature(sig.hex))
} }
} }
@ -198,6 +181,13 @@ trait MultiSignatureScriptSignature extends ScriptSignature {
* https://bitcoin.org/en/developer-guide#pubkey * https://bitcoin.org/en/developer-guide#pubkey
*/ */
trait PubKeyScriptSignature extends ScriptSignature { trait PubKeyScriptSignature extends ScriptSignature {
/**
* 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 * @return

View file

@ -21,7 +21,6 @@ class TransactionSignatureCheckerTest extends FlatSpec with MustMatchers {
TransactionTestUtil.transactionWithSpendingInputAndCreditingOutput TransactionTestUtil.transactionWithSpendingInputAndCreditingOutput
val scriptSig : ScriptSignature = spendingInput.scriptSignature val scriptSig : ScriptSignature = spendingInput.scriptSignature
val pubKey : ECPublicKey = ECFactory.publicKey(scriptSig.asm.last.bytes) val pubKey : ECPublicKey = ECFactory.publicKey(scriptSig.asm.last.bytes)
val digitalSignature = scriptSig.signatures.head
TransactionSignatureChecker.checkSignature(spendingTx,inputIndex,creditingOutput.scriptPubKey, TransactionSignatureChecker.checkSignature(spendingTx,inputIndex,creditingOutput.scriptPubKey,
pubKey) must be (true) pubKey) must be (true)
} }
@ -32,11 +31,10 @@ class TransactionSignatureCheckerTest extends FlatSpec with MustMatchers {
TransactionSignatureChecker.checkSignature(spendingTx,inputIndex,multiSigScriptPubKey) must be (true) TransactionSignatureChecker.checkSignature(spendingTx,inputIndex,multiSigScriptPubKey) must be (true)
} }
it must "" in { it must "" in {
val (spendingTx,input,inputIndex,creditingOutput) = BitcoinJTestUtil.p2shTransactionWithSpendingInputAndCreditingOutput val (spendingTx,input,inputIndex,creditingOutput) = BitcoinJTestUtil.p2shTransactionWithSpendingInputAndCreditingOutput
input.getScriptSig.correctlySpends(spendingTx,inputIndex,creditingOutput.getScriptPubKey) input.getScriptSig.correctlySpends(spendingTx,inputIndex,creditingOutput.getScriptPubKey)
println(input.getScriptSig.getScriptType)
} }

View file

@ -284,18 +284,20 @@ class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers {
val (spendingTx,spendingInput,inputIndex,creditingOutput) = val (spendingTx,spendingInput,inputIndex,creditingOutput) =
TransactionTestUtil.p2shTransactionWithSpendingInputAndCreditingOutput TransactionTestUtil.p2shTransactionWithSpendingInputAndCreditingOutput
val bitcoinjTx = BitcoinjConversions.transaction(spendingTx) for {
val hashType = spendingInput.scriptSignature.hashType(spendingInput.scriptSignature.signatures.head) signature <- spendingInput.scriptSignature.signatures
val bitcoinjSerializeForSig : Seq[Byte] = BitcoinJSignatureSerialization.serializeForSignature( } yield {
bitcoinjTx, inputIndex, creditingOutput.scriptPubKey.bytes.toArray, hashType.byte //needs to be inside yield statement because of mutability issues
) val bitcoinjTx = BitcoinjConversions.transaction(spendingTx)
val hashType = spendingInput.scriptSignature.hashType(spendingInput.scriptSignature.signatures.head)
val bitcoinjHashForSig : Seq[Byte] = BitcoinJSignatureSerialization.serializeForSignature(
val serializedTxForSig : String = BitcoinSUtil.encodeHex( bitcoinjTx, inputIndex, creditingOutput.scriptPubKey.bytes.toArray, hashType.byte
TransactionSignatureSerializer.serializeForSignature(spendingTx,inputIndex,creditingOutput.scriptPubKey,hashType )
)) val hashedTxForSig : String = BitcoinSUtil.encodeHex(
TransactionSignatureSerializer.serializeForSignature(spendingTx,inputIndex,creditingOutput.scriptPubKey,hashType
serializedTxForSig must be (BitcoinSUtil.encodeHex(bitcoinjSerializeForSig)) ))
hashedTxForSig must be (BitcoinSUtil.encodeHex(bitcoinjHashForSig))
}
} }
@ -304,15 +306,27 @@ class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers {
TransactionTestUtil.p2shTransactionWithSpendingInputAndCreditingOutput TransactionTestUtil.p2shTransactionWithSpendingInputAndCreditingOutput
val bitcoinjTx = BitcoinjConversions.transaction(spendingTx) val bitcoinjTx = BitcoinjConversions.transaction(spendingTx)
val hashType = spendingInput.scriptSignature.hashType(spendingInput.scriptSignature.signatures.head) val bitcoinjTx1 = BitcoinjConversions.transaction(spendingTx)
val bitcoinjHashForSig : Seq[Byte] = BitcoinJSignatureSerialization.hashForSignature( val bitcoinjHashForSig : Seq[Byte] = BitcoinJSignatureSerialization.hashForSignature(
bitcoinjTx, inputIndex, creditingOutput.scriptPubKey.bytes.toArray, hashType.byte bitcoinjTx, inputIndex, creditingOutput.scriptPubKey.bytes.toArray, SIGHASH_ALL.byte
) )
val hashedTxForSig : String = BitcoinSUtil.encodeHex( bitcoinjTx1.hashForSignature(inputIndex,creditingOutput.bytes.toArray,SIGHASH_ALL.byte) must be (
TransactionSignatureSerializer.hashForSignature(spendingTx,inputIndex,creditingOutput.scriptPubKey,hashType BitcoinSUtil.encodeHex(bitcoinjHashForSig)
)) )
/* for {
signature <- spendingInput.scriptSignature.signatures
} yield {
//needs to be inside yield statement because of mutability issues
hashedTxForSig must be (BitcoinSUtil.encodeHex(bitcoinjHashForSig)) val hashType = spendingInput.scriptSignature.hashType(spendingInput.scriptSignature.signatures.head)
val bitcoinjHashForSig : Seq[Byte] = BitcoinJSignatureSerialization.hashForSignature(
bitcoinjTx, inputIndex, creditingOutput.scriptPubKey.bytes.toArray, hashType.byte
)
val hashedTxForSig : String = BitcoinSUtil.encodeHex(
TransactionSignatureSerializer.hashForSignature(spendingTx,inputIndex,creditingOutput.scriptPubKey,hashType
))
hashedTxForSig must be (BitcoinSUtil.encodeHex(bitcoinjHashForSig))
}*/
} }

View file

@ -63,8 +63,6 @@ object TestUtil {
//p2sh script for a 2 of 2 //p2sh script for a 2 of 2
//https://tbtc.blockr.io/api/v1/tx/raw/2f18c646a2b2ee8ee1f295bb5a0f5cc51c5e820a123a14b0c0e170f9777518bb //https://tbtc.blockr.io/api/v1/tx/raw/2f18c646a2b2ee8ee1f295bb5a0f5cc51c5e820a123a14b0c0e170f9777518bb
val rawP2shInputScript2Of2 = "0047304402207d764cb90c9fd84b74d33a47cf3a0ffead9ded98333776becd6acd32c4426dac02203905a0d064e7f53d07793e86136571b6e4f700c1cfb888174e84d78638335b8101483045022100906aaca39f022acd8b7a38fd2f92aca9e9f35cfeaee69a6f13e1d083ae18222602204c9ed96fc6c4de56fd85c679fc59c16ee1ccc80c42563b86174e1a506fc007c8014752210369d26ebd086523384a0f89f293d4c327a65fa73332d8efd1097cb35231295b832102480863e5c4a4e9763f5380c44fcfe6a3b7787397076cf9ea1049303a9d34f72152ae" val rawP2shInputScript2Of2 = "0047304402207d764cb90c9fd84b74d33a47cf3a0ffead9ded98333776becd6acd32c4426dac02203905a0d064e7f53d07793e86136571b6e4f700c1cfb888174e84d78638335b8101483045022100906aaca39f022acd8b7a38fd2f92aca9e9f35cfeaee69a6f13e1d083ae18222602204c9ed96fc6c4de56fd85c679fc59c16ee1ccc80c42563b86174e1a506fc007c8014752210369d26ebd086523384a0f89f293d4c327a65fa73332d8efd1097cb35231295b832102480863e5c4a4e9763f5380c44fcfe6a3b7787397076cf9ea1049303a9d34f72152ae"
val rawP2SH2Of2Tx = "0100000001d69b8ece3059c429a83707cde2db9d8a76897b5d418c4a784a5f52d40063518f00000000da0047304402207d764cb90c9fd84b74d33a47cf3a0ffead9ded98333776becd6acd32c4426dac02203905a0d064e7f53d07793e86136571b6e4f700c1cfb888174e84d78638335b8101483045022100906aaca39f022acd8b7a38fd2f92aca9e9f35cfeaee69a6f13e1d083ae18222602204c9ed96fc6c4de56fd85c679fc59c16ee1ccc80c42563b86174e1a506fc007c8014752210369d26ebd086523384a0f89f293d4c327a65fa73332d8efd1097cb35231295b832102480863e5c4a4e9763f5380c44fcfe6a3b7787397076cf9ea1049303a9d34f72152ae0000000001b7e4ca00000000001976a914c59529a317559cfa818ddd625b7b980435b333dc88acb6917056" val rawP2SH2Of2Tx = "0100000001d69b8ece3059c429a83707cde2db9d8a76897b5d418c4a784a5f52d40063518f00000000da0047304402207d764cb90c9fd84b74d33a47cf3a0ffead9ded98333776becd6acd32c4426dac02203905a0d064e7f53d07793e86136571b6e4f700c1cfb888174e84d78638335b8101483045022100906aaca39f022acd8b7a38fd2f92aca9e9f35cfeaee69a6f13e1d083ae18222602204c9ed96fc6c4de56fd85c679fc59c16ee1ccc80c42563b86174e1a506fc007c8014752210369d26ebd086523384a0f89f293d4c327a65fa73332d8efd1097cb35231295b832102480863e5c4a4e9763f5380c44fcfe6a3b7787397076cf9ea1049303a9d34f72152ae0000000001b7e4ca00000000001976a914c59529a317559cfa818ddd625b7b980435b333dc88acb6917056"
def p2shInputScript2Of2 = RawScriptSignatureParser.read(rawP2shInputScript2Of2) def p2shInputScript2Of2 = RawScriptSignatureParser.read(rawP2shInputScript2Of2)