mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-24 15:02:17 +01:00
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:
parent
22fa7af6dc
commit
c800777981
7 changed files with 83 additions and 81 deletions
|
@ -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 =>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}*/
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue