Fixing bug with removing signatures from p2sh redeem scripts

This commit is contained in:
Chris Stewart 2016-05-12 12:49:49 -05:00
parent ef35eb9270
commit 8a7249a656
7 changed files with 65 additions and 103 deletions

View file

@ -53,35 +53,26 @@ trait TransactionSignatureChecker extends BitcoinSLogger {
//we do this by setting the scriptPubKey inside of txSignatureComponent to the redeemScript
//instead of the p2sh scriptPubKey it was previously
//as the scriptPubKey instead of the one inside of ScriptProgram
val txSignatureComponentWithScriptPubKeyAdjusted = txSignatureComponent.scriptSignature match {
val sigsRemovedScript : Seq[ScriptToken] = txSignatureComponent.scriptSignature match {
case s : P2SHScriptSignature =>
logger.info("Replacing redeemScript in txSignature component")
logger.info("Redeem script: " + s.redeemScript)
TransactionSignatureComponentFactory.factory(txSignatureComponent,s.redeemScript)
logger.info("Signature: " + signature)
val sigsRemoved = removeSignaturesFromScript(s.signatures, s.redeemScript.asm)
sigsRemoved
case _ : P2PKHScriptSignature | _ : P2PKScriptSignature | _ : NonStandardScriptSignature
| _ : MultiSignatureScriptSignature | EmptyScriptSignature =>
logger.debug("Script before sigRemoved: " + script)
logger.debug("Signature: " + signature)
logger.debug("PubKey: " + pubKey)
val sigRemoved = if (script.contains(ScriptConstant(signature.hex))) {
//replicates this line in bitcoin core
//https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L872
val sigIndex = script.indexOf(ScriptConstant(signature.hex))
logger.debug("SigIndex: " + sigIndex)
//remove sig and it's corresponding BytesToPushOntoStack
script.slice(0,sigIndex-1) ++ script.slice(sigIndex+1,script.size)
} else script
logger.debug("Sig removed: " + sigRemoved)
val scriptPubKeySigRemoved = ScriptPubKey.fromAsm(sigRemoved)
TransactionSignatureComponentFactory.factory(txSignatureComponent,scriptPubKeySigRemoved)
val sigsRemoved = removeSignatureFromScript(signature,script)
sigsRemoved
}
val hashTypeByte = if (signature.bytes.size > 0) signature.bytes.last else 0x00.toByte
val hashType = HashTypeFactory.fromByte(hashTypeByte)
val hashForSignature = TransactionSignatureSerializer.hashForSignature(txSignatureComponentWithScriptPubKeyAdjusted.transaction,
txSignatureComponentWithScriptPubKeyAdjusted.inputIndex,
txSignatureComponentWithScriptPubKeyAdjusted.scriptPubKey.asm, hashType)
logger.debug("Tx signature component: " + txSignatureComponentWithScriptPubKeyAdjusted)
val hashForSignature = TransactionSignatureSerializer.hashForSignature(txSignatureComponent.transaction,
txSignatureComponent.inputIndex,
sigsRemovedScript, hashType)
logger.info("Hash for signature: " + BitcoinSUtil.encodeHex(hashForSignature))
val isValid = pubKey.verify(hashForSignature,signature)
if (isValid) SignatureValidationSuccess else SignatureValidationFailureIncorrectSignatures
@ -143,6 +134,43 @@ trait TransactionSignatureChecker extends BitcoinSLogger {
} else SignatureValidationFailureIncorrectSignatures
}
/**
* Removes the given digtial signature from the list of script tokens if it exists
* @param signature
* @param script
* @return
*/
def removeSignatureFromScript(signature : ECDigitalSignature, script : Seq[ScriptToken]) : Seq[ScriptToken] = {
if (script.contains(ScriptConstant(signature.hex))) {
//replicates this line in bitcoin core
//https://github.com/bitcoin/bitcoin/blob/master/src/script/interpreter.cpp#L872
val sigIndex = script.indexOf(ScriptConstant(signature.hex))
logger.debug("SigIndex: " + sigIndex)
//remove sig and it's corresponding BytesToPushOntoStack
script.slice(0,sigIndex-1) ++ script.slice(sigIndex+1,script.size)
} else script
}
/**
* Removes the list of digital signatures from the list of script tokens
* @param sigs
* @param script
* @return
*/
def removeSignaturesFromScript(sigs : Seq[ECDigitalSignature], script : Seq[ScriptToken]) : Seq[ScriptToken] = {
@tailrec
def loop(remainingSigs : Seq[ECDigitalSignature], scriptTokens : Seq[ScriptToken]) : Seq[ScriptToken] = {
remainingSigs match {
case Nil => scriptTokens
case h :: t =>
val newScriptTokens = removeSignatureFromScript(h,scriptTokens)
loop(t,newScriptTokens)
}
}
loop(sigs,script)
}
}
object TransactionSignatureChecker extends TransactionSignatureChecker

View file

@ -28,17 +28,6 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit
*/
private def errorHash : Seq[Byte] = BitcoinSUtil.decodeHex("0100000000000000000000000000000000000000000000000000000000000000")
/**
* 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 : ScriptPubKey) : ScriptPubKey = script.filterNot(_ == OP_CODESEPARATOR)
*/
/**
* Serializes a transaction to be signed by an ECKey
* follows the bitcoinj implementation which can be found here

View file

@ -148,7 +148,6 @@ trait ScriptParser extends Factory[List[ScriptToken]] with BitcoinSLogger {
* @return
*/
private def parse(bytes : List[Byte]) : List[ScriptToken] = {
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)
@ -166,9 +165,6 @@ trait ScriptParser extends Factory[List[ScriptToken]] with BitcoinSLogger {
private def parse(bytes : Seq[Byte]) : List[ScriptToken] = parse(bytes.toList)
/**
* Parses a redeem script from the given script token
* @param scriptToken

View file

@ -3,6 +3,7 @@ package org.bitcoins.crypto
import org.bitcoins.policy.Policy
import org.bitcoins.protocol.script.ScriptSignature
import org.bitcoins.protocol.transaction._
import org.bitcoins.script.constant.ScriptConstant
import org.bitcoins.script.flag.ScriptVerifyDerSig
import org.bitcoins.util._
import org.scalatest.{FlatSpec, MustMatchers}
@ -12,65 +13,11 @@ import org.scalatest.{FlatSpec, MustMatchers}
*/
class TransactionSignatureCheckerTest extends FlatSpec with MustMatchers {
/* "TransactionSignatureChecker" must "check to see if an input spends a multisignature scriptPubKey correctly" in {
val (spendingTx,inputIndex,multiSigScriptPubKey,keys) = TransactionTestUtil.signedMultiSignatureTransaction
TransactionSignatureChecker.checkSignature(spendingTx,inputIndex,multiSigScriptPubKey,Seq(ScriptVerifyDerSig)) must be (SignatureValidationSuccess)
"TransactionSignatureChecker" must "remove the signatures from a p2sh scriptSig" in {
val p2shScriptSig = TestUtil.p2sh2Of3ScriptSig
val signatures = p2shScriptSig.signatures
val asmWithoutSigs = TransactionSignatureChecker.removeSignaturesFromScript(signatures,p2shScriptSig.asm)
val sigExists = signatures.map(sig => asmWithoutSigs.exists(_ == ScriptConstant(sig.hex)))
sigExists.exists(_ == true) must be (false)
}
it must "check to see if an input spends a p2sh scriptPubKey correctly" in {
val (spendingTx,input,inputIndex,creditingOutput) = TransactionTestUtil.p2shTransactionWithSpendingInputAndCreditingOutput
TransactionSignatureChecker.checkSignature(spendingTx,inputIndex,creditingOutput.scriptPubKey,Policy.standardScriptVerifyFlags) must be (SignatureValidationSuccess)
}
it must "check a 2/3 p2sh input script correctly" in {
val (spendingTx,input,inputIndex,creditingOutput) = TransactionTestUtil.p2sh2Of3TransactionWithSpendingInputAndCreditingOutput
TransactionSignatureChecker.checkSignature(spendingTx,inputIndex,creditingOutput.scriptPubKey,Policy.standardScriptVerifyFlags) must be (SignatureValidationSuccess)
}
it must "fail to validate a 2/3 p2sh input script if a digital signature is removed" in {
val (spendingTx,input,inputIndex,creditingOutput) = TransactionTestUtil.p2sh2Of3TransactionWithSpendingInputAndCreditingOutput
val scriptSig : ScriptSignature = spendingTx.inputs.head.scriptSignature
val newScriptSigAsm = Seq(scriptSig.asm.head) ++ scriptSig.asm.slice(3,scriptSig.asm.size)
val newScriptSigWithSignatureRemoved = ScriptSignatureFactory.fromAsm(newScriptSigAsm)
val newInput = TransactionInputFactory.factory(spendingTx.inputs(inputIndex),newScriptSigWithSignatureRemoved)
val txNewInputs = TransactionFactory.factory(EmptyTransaction,UpdateTransactionInputs(Seq(newInput)))
TransactionSignatureChecker.checkSignature(txNewInputs,inputIndex,creditingOutput.scriptPubKey,Policy.standardScriptVerifyFlags) must be (SignatureValidationFailureIncorrectSignatures)
}
it must "fail to check a transaction when strict der encoding is required but the signature is not strict der encoded" in {
val (tx,inputIndex) = TransactionTestUtil.transactionWithNonStrictDerSignature
val signature = tx.inputs(inputIndex).scriptSignature.signatures.head
val result = TransactionSignatureChecker.checkSignature(tx,inputIndex,TestUtil.scriptPubKey,
ECFactory.publicKey(""), signature, true)
result must be (SignatureValidationFailureNotStrictDerEncoding)
}
it must "check a standard p2pk transaction" in {
val (creditingTx,outputIndex) = TransactionTestUtil.buildCreditingTransaction(TestUtil.p2pkScriptPubKey)
val (spendingTx,inputIndex) =
TransactionTestUtil.buildSpendingTransaction(creditingTx,TestUtil.p2pkScriptSig,outputIndex)
val program = ScriptProgramFactory.factory(spendingTx,TestUtil.p2pkScriptPubKey,inputIndex,Seq())
val result = TransactionSignatureChecker.checkSignature(program.txSignatureComponent)
result must be (SignatureValidationSuccess)
}
it must "check a standard p2pkh transaction" in {
val (spendingTx, inputIndex, scriptPubKey) = TransactionTestUtil.p2pkhTransactionWithCreditingScriptPubKey
val program = ScriptProgramFactory.factory(spendingTx,scriptPubKey,inputIndex,Seq())
val result = TransactionSignatureChecker.checkSignature(program.txSignatureComponent)
result must be (SignatureValidationSuccess)
}
it must "fail checking a standard p2pkh transactin if the strict der encoding flag is set and we don't have a strict der encoded signature" in {
val (creditingTx,outputIndex) = TransactionTestUtil.buildCreditingTransaction(TestUtil.p2pkhScriptPubKey)
val scriptPubKey = creditingTx.outputs(outputIndex).scriptPubKey
val (spendingTx,inputIndex) = TransactionTestUtil.buildSpendingTransaction(creditingTx,
TestUtil.p2pkhScriptSigNotStrictDerEncoded,outputIndex)
val program = ScriptProgramFactory.factory(spendingTx,scriptPubKey,inputIndex,Seq(ScriptVerifyDerSig))
val result = TransactionSignatureChecker.checkSignature(program.txSignatureComponent)
result must be (SignatureValidationFailureNotStrictDerEncoding)
}*/
}

View file

@ -7,7 +7,7 @@ import org.bitcoinj.core.Transaction.SigHash
import org.bitcoinj.params.TestNet3Params
import org.bitcoinj.script.{ScriptBuilder, ScriptChunk, ScriptOpCodes}
import org.bitcoins.marshallers.script.ScriptParser
import org.bitcoins.protocol.script.{ScriptPubKey, UpdateScriptPubKeyAsm, UpdateScriptPubKeyBytes}
import org.bitcoins.protocol.script._
import org.bitcoins.protocol.transaction._
import org.bitcoins.script.ScriptOperationFactory
import org.bitcoins.script.bitwise.OP_EQUALVERIFY
@ -23,7 +23,7 @@ import scala.collection.JavaConversions._
/**
* Created by chris on 2/19/16.
*/
class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers {
class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers with BitcoinSLogger {
val scriptPubKey = BitcoinjConversions.toScriptPubKey(BitcoinJTestUtil.multiSigScript)
@ -420,13 +420,12 @@ class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers {
val inputIndex = 0
val spendingTx = Transaction(rawTx)
val scriptPubKeyFromString = ScriptParser.fromString("0x4c 0xae 0x606563686f2022553246736447566b58312b5a536e587574356542793066794778625456415675534a6c376a6a334878416945325364667657734f53474f36633338584d7439435c6e543249584967306a486956304f376e775236644546673d3d22203e20743b206f70656e73736c20656e63202d7061737320706173733a5b314a564d7751432d707269766b65792d6865785d202d64202d6165732d3235362d636263202d61202d696e207460 DROP DUP HASH160 0x14 0xbfd7436b6265aa9de506f8a994f881ff08cc2872 EQUALVERIFY CHECKSIG")
println("Script pubKey from string asm: " + scriptPubKeyFromString)
val scriptPubKey = ScriptPubKey.fromAsm(scriptPubKeyFromString)
require(scriptPubKey.hex == "4cae606563686f2022553246736447566b58312b5a536e587574356542793066794778625456415675534a6c376a6a334878416945325364667657734f53474f36633338584d7439435c6e543249584967306a486956304f376e775236644546673d3d22203e20743b206f70656e73736c20656e63202d7061737320706173733a5b314a564d7751432d707269766b65792d6865785d202d64202d6165732d3235362d636263202d61202d696e2074607576a914bfd7436b6265aa9de506f8a994f881ff08cc287288ac")
val serializedTxForSig : String = BitcoinSUtil.encodeHex(
TransactionSignatureSerializer.serializeForSignature(spendingTx,inputIndex,scriptPubKey.asm,SIGHASH_ALL(1.toByte)))
//serialization is from bitcoin core
serializedTxForSig must be ("0100000001482f7a028730a233ac9b48411a8edfb107b749e61faf7531f4257ad95d0a51c500000000ca4cae606563686f2022553246736447566b58312b5a536e587574356542793066794778625456415675534a6c376a6a334878416945325364667657734f53474f36633338584d7439435c6e543249584967306a486956304f376e775236644546673d3d22203e20743b206f70656e73736c20656e63202d7061737320706173733a5b314a564d7751432d707269766b65792d6865785d202d64202d6165732d3235362d636263202d61202d696e2074607576a914bfd7436b6265aa9de506f8a994f881ff08cc287288acffffffff0180969800000000001976a914e336d0017a9d28de99d16472f6ca6d5a3a8ebc9988ac0000000001000000")
}
}

View file

@ -49,15 +49,15 @@ class TransactionTest extends FlatSpec with MustMatchers with BitcoinSLogger {
//use this to represent a single test case from script_valid.json
/* val lines =
val lines =
"""
|[
|[[["a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944", 0, "0x21 0x038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041 CHECKSIGVERIFY CODESEPARATOR 0x21 0x038479a0fa998cd35259a2ef0a7a5c68662c1474f88ccb6d08a7677bbec7f22041 CHECKSIGVERIFY CODESEPARATOR 1"]],
|"010000000144490eda355be7480f2ec828dcc1b9903793a8008fad8cfe9b0c6b4d2f0355a900000000924830450221009c0a27f886a1d8cb87f6f595fbc3163d28f7a81ec3c4b252ee7f3ac77fd13ffa02203caa8dfa09713c8c4d7ef575c75ed97812072405d932bd11e6a1593a98b679370148304502201e3861ef39a526406bad1e20ecad06be7375ad40ddb582c9be42d26c3a0d7b240221009d0a3985e96522e59635d19cc4448547477396ce0ef17a58e7d74c3ef464292301ffffffff010000000000000000016a00000000", "P2SH"]
|[[["0000000000000000000000000000000000000000000000000000000000000100", 0, "499999999 CHECKLOCKTIMEVERIFY 1"]],
|"0100000001000100000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000ff64cd1d", "P2SH,CHECKLOCKTIMEVERIFY"]
|
|]
""".stripMargin*/
val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close()
""".stripMargin
//val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close()
val json = lines.parseJson
val testCasesOpt : Seq[Option[CoreTransactionTestCase]] = json.convertTo[Seq[Option[CoreTransactionTestCase]]]
val testCases : Seq[CoreTransactionTestCase] = testCasesOpt.flatten

View file

@ -88,7 +88,10 @@ object TestUtil {
def p2shInputScriptLargeSignature = ScriptSignature(rawP2shInputScriptLargeSignature)
def rawP2sh2Of3ScriptSig = "004730440220028c02f14654a0cc12c7e3229adb09d5d35bebb6ba1057e39adb1b2706607b0d0220564fab12c6da3d5acef332406027a7ff1cbba980175ffd880e1ba1bf40598f6b014830450221009362f8d67b60773745e983d07ba10efbe566127e244b724385b2ca2e47292dda022033def393954c320653843555ddbe7679b35cc1cacfe1dad923977de8cd6cc6d7014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53ae"
def p2sh2Of3ScriptSig = ScriptSignature(rawP2sh2Of3ScriptSig)
def p2sh2Of3ScriptSig : P2SHScriptSignature = ScriptSignature(rawP2sh2Of3ScriptSig) match {
case p2sh : P2SHScriptSignature => p2sh
case _ : ScriptSignature => throw new RuntimeException
}
//txid on testnet 44e504f5b7649d215be05ad9f09026dee95201244a3b218013c504a6a49a26ff
//this tx has multiple inputs and outputs