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 {
val hashType = p2shScriptSignature.hashType(sig)
val hashForSig = TransactionSignatureSerializer.hashForSignature(spendingTransaction,inputIndex,x,hashType)
logger.info("pubKey: " + pubKey)
logger.info("sig: " + sig)
logger.info("Hash for sig: " + BitcoinSUtil.encodeHex(hashForSig))
pubKey.verify(hashForSig, sig)
@ -89,7 +90,7 @@ trait TransactionSignatureChecker {
logger.info("P2SH sigs: " + p2shScriptSignature.signatures)
logger.info("P2SH pub keys: " + 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)
!result.contains(false)
case x : MultiSignatureScriptPubKey =>

View file

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

View file

@ -68,7 +68,7 @@ trait ScriptParser extends Factory[List[ScriptToken]] {
* @return
*/
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
def loop(operations : List[String], accum : List[ScriptToken]) : List[ScriptToken] = {
@ -168,7 +168,7 @@ trait ScriptParser extends Factory[List[ScriptToken]] {
* @return
*/
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
def loop(bytes : List[Byte], accum : List[ScriptToken]) : List[ScriptToken] = {
logger.debug("Byte to be parsed: " + bytes.headOption)
@ -206,7 +206,7 @@ trait ScriptParser extends Factory[List[ScriptToken]] {
* @return
*/
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)
tryRedeemScript match {
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.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.
*
@ -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 {
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))
}
def signatures : Seq[ECDigitalSignature] = ???
}
case class NonStandardScriptSignatureImpl(hex : String) extends NonStandardScriptSignature
@ -128,6 +76,13 @@ case class NonStandardScriptSignatureImpl(hex : String) extends NonStandardScrip
*/
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
* @return
@ -173,6 +128,34 @@ trait P2SHScriptSignature extends ScriptSignature {
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] = {
asm.filter(_.isInstanceOf[ScriptConstant])
.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
*/
trait PubKeyScriptSignature extends ScriptSignature {
/**
* PubKey scriptSignatures only have one signature
* @return
*/
def signature : ECDigitalSignature = signatures.head
/**
* The digital signatures inside of the scriptSig
* @return

View file

@ -21,7 +21,6 @@ class TransactionSignatureCheckerTest extends FlatSpec with MustMatchers {
TransactionTestUtil.transactionWithSpendingInputAndCreditingOutput
val scriptSig : ScriptSignature = spendingInput.scriptSignature
val pubKey : ECPublicKey = ECFactory.publicKey(scriptSig.asm.last.bytes)
val digitalSignature = scriptSig.signatures.head
TransactionSignatureChecker.checkSignature(spendingTx,inputIndex,creditingOutput.scriptPubKey,
pubKey) must be (true)
}
@ -32,11 +31,10 @@ class TransactionSignatureCheckerTest extends FlatSpec with MustMatchers {
TransactionSignatureChecker.checkSignature(spendingTx,inputIndex,multiSigScriptPubKey) must be (true)
}
it must "" in {
val (spendingTx,input,inputIndex,creditingOutput) = BitcoinJTestUtil.p2shTransactionWithSpendingInputAndCreditingOutput
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) =
TransactionTestUtil.p2shTransactionWithSpendingInputAndCreditingOutput
val bitcoinjTx = BitcoinjConversions.transaction(spendingTx)
val hashType = spendingInput.scriptSignature.hashType(spendingInput.scriptSignature.signatures.head)
val bitcoinjSerializeForSig : Seq[Byte] = BitcoinJSignatureSerialization.serializeForSignature(
bitcoinjTx, inputIndex, creditingOutput.scriptPubKey.bytes.toArray, hashType.byte
)
val serializedTxForSig : String = BitcoinSUtil.encodeHex(
TransactionSignatureSerializer.serializeForSignature(spendingTx,inputIndex,creditingOutput.scriptPubKey,hashType
))
serializedTxForSig must be (BitcoinSUtil.encodeHex(bitcoinjSerializeForSig))
for {
signature <- spendingInput.scriptSignature.signatures
} yield {
//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(
bitcoinjTx, inputIndex, creditingOutput.scriptPubKey.bytes.toArray, hashType.byte
)
val hashedTxForSig : String = BitcoinSUtil.encodeHex(
TransactionSignatureSerializer.serializeForSignature(spendingTx,inputIndex,creditingOutput.scriptPubKey,hashType
))
hashedTxForSig must be (BitcoinSUtil.encodeHex(bitcoinjHashForSig))
}
}
@ -304,15 +306,27 @@ class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers {
TransactionTestUtil.p2shTransactionWithSpendingInputAndCreditingOutput
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(
bitcoinjTx, inputIndex, creditingOutput.scriptPubKey.bytes.toArray, hashType.byte
bitcoinjTx, inputIndex, creditingOutput.scriptPubKey.bytes.toArray, SIGHASH_ALL.byte
)
val hashedTxForSig : String = BitcoinSUtil.encodeHex(
TransactionSignatureSerializer.hashForSignature(spendingTx,inputIndex,creditingOutput.scriptPubKey,hashType
))
bitcoinjTx1.hashForSignature(inputIndex,creditingOutput.bytes.toArray,SIGHASH_ALL.byte) must be (
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
//https://tbtc.blockr.io/api/v1/tx/raw/2f18c646a2b2ee8ee1f295bb5a0f5cc51c5e820a123a14b0c0e170f9777518bb
val rawP2shInputScript2Of2 = "0047304402207d764cb90c9fd84b74d33a47cf3a0ffead9ded98333776becd6acd32c4426dac02203905a0d064e7f53d07793e86136571b6e4f700c1cfb888174e84d78638335b8101483045022100906aaca39f022acd8b7a38fd2f92aca9e9f35cfeaee69a6f13e1d083ae18222602204c9ed96fc6c4de56fd85c679fc59c16ee1ccc80c42563b86174e1a506fc007c8014752210369d26ebd086523384a0f89f293d4c327a65fa73332d8efd1097cb35231295b832102480863e5c4a4e9763f5380c44fcfe6a3b7787397076cf9ea1049303a9d34f72152ae"
val rawP2SH2Of2Tx = "0100000001d69b8ece3059c429a83707cde2db9d8a76897b5d418c4a784a5f52d40063518f00000000da0047304402207d764cb90c9fd84b74d33a47cf3a0ffead9ded98333776becd6acd32c4426dac02203905a0d064e7f53d07793e86136571b6e4f700c1cfb888174e84d78638335b8101483045022100906aaca39f022acd8b7a38fd2f92aca9e9f35cfeaee69a6f13e1d083ae18222602204c9ed96fc6c4de56fd85c679fc59c16ee1ccc80c42563b86174e1a506fc007c8014752210369d26ebd086523384a0f89f293d4c327a65fa73332d8efd1097cb35231295b832102480863e5c4a4e9763f5380c44fcfe6a3b7787397076cf9ea1049303a9d34f72152ae0000000001b7e4ca00000000001976a914c59529a317559cfa818ddd625b7b980435b333dc88acb6917056"
def p2shInputScript2Of2 = RawScriptSignatureParser.read(rawP2shInputScript2Of2)