mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-25 07:17:32 +01:00
Fixing bug with removing signatures from p2sh redeem scripts
This commit is contained in:
parent
ef35eb9270
commit
8a7249a656
7 changed files with 65 additions and 103 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}*/
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue