Successfully replicating the creation of a digital signature that bitcoin core produced

This commit is contained in:
Chris Stewart 2016-07-25 11:24:13 -05:00
parent 71a50be511
commit 31a419445d
6 changed files with 62 additions and 40 deletions

View file

@ -12,13 +12,11 @@ trait TransactionSignatureCreator {
* Creates a signature from a tx signature component
* @param txSignatureComponent contains the tx, inputIndex which specify which input we are creating a sig for
* @param privateKey the private key which we are signing the hash with
* @param scriptPubKey the scriptPubKey which we are spending
* @param hashType the procedure to use for hashing to transaction
* @return
*/
def createSig(txSignatureComponent: TransactionSignatureComponent, privateKey: ECPrivateKey,
scriptPubKey: ScriptPubKey, hashType: HashType): ECDigitalSignature = {
val hash = TransactionSignatureSerializer.hashForSignature(txSignatureComponent,scriptPubKey,hashType)
def createSig(txSignatureComponent: TransactionSignatureComponent, privateKey: ECPrivateKey, hashType: HashType): ECDigitalSignature = {
val hash = TransactionSignatureSerializer.hashForSignature(txSignatureComponent, hashType)
val signature = privateKey.sign(hash)
//append 1 byte hash type onto the end
ECDigitalSignature(signature.bytes ++ Seq(hashType.byte))

View file

@ -25,7 +25,6 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit
/**
* Bitcoin Core's bug is that SignatureHash was supposed to return a hash and on this codepath it
* actually returns the constant "1" to indicate an error
*
* @return
*/
private def errorHash : DoubleSha256Digest = DoubleSha256Digest(BitcoinSUtil.decodeHex("0100000000000000000000000000000000000000000000000000000000000000"))
@ -137,7 +136,6 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit
* Serializes then hashes a transaction for signing
* this is an implementation of it's bitcoinj equivalent found here
* https://github.com/bitcoinj/bitcoinj/blob/master/core/src/main/java/org/bitcoinj/core/Transaction.java#L924
*
* @param inputIndex
* @param script
* @param hashType
@ -149,7 +147,7 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit
if (inputIndex >= UInt32(spendingTransaction.inputs.size)) {
logger.warn("Our inputIndex is out of the range of the inputs in the spending transaction")
errorHash
} else if(hashType == SIGHASH_SINGLE && inputIndex >= UInt32(spendingTransaction.outputs.size)) {
} else if(hashType == SIGHASH_SINGLE && inputIndex >= UInt32(spendingTransaction.outputs.size)) {
logger.warn("When we have a SIGHASH_SINGLE we cannot have more inputs than outputs")
errorHash
} else {
@ -163,19 +161,18 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit
/**
* Wrapper function for hashForSignature
* @param txSignatureComponent this contains the transaction and inputIndex for hashForSignature
* @param scriptPubKey
* @param hashType
* @return
*/
def hashForSignature(txSignatureComponent: TransactionSignatureComponent, scriptPubKey: ScriptPubKey, hashType: HashType): DoubleSha256Digest = {
hashForSignature(txSignatureComponent.transaction,txSignatureComponent.inputIndex,scriptPubKey.asm,hashType)
def hashForSignature(txSignatureComponent: TransactionSignatureComponent, hashType: HashType): DoubleSha256Digest = {
hashForSignature(txSignatureComponent.transaction,txSignatureComponent.inputIndex,
txSignatureComponent.scriptPubKey.asm,hashType)
}
/**
* Sets the input's sequence number to zero EXCEPT for the input at inputIndex
*
* @param inputs
* @param inputIndex
* @return
@ -191,7 +188,6 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit
/**
* Updates an input at the given inputIndex and returns the updated sequence of inputs
*
* @param inputs
* @param updatedInput
* @param inputIndex
@ -208,7 +204,6 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit
/**
* Executes the SIGHASH_NONE procedure on a spending transaction for the input specified by inputIndex
*
* @param spendingTransaction
* @param inputIndex
* @return
@ -227,7 +222,6 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit
/**
* Executes the SIGHASH_SINGLE procedure on a spending transaction for the input specified by inputIndex
*
* @param spendingTransaction
* @param inputIndex
* @return
@ -240,13 +234,13 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit
val updatedOutputsOpt : Seq[Option[TransactionOutput]] = for {
(output,index) <- spendingTransaction.outputs.zipWithIndex
} yield {
if (UInt32(index) < inputIndex) {
logger.debug("Updating tx output to null in bitcoin core")
Some(EmptyTransactionOutput)
}
else if (UInt32(index) == inputIndex) Some(output)
else None
if (UInt32(index) < inputIndex) {
logger.debug("Updating tx output to null in bitcoin core")
Some(EmptyTransactionOutput)
}
else if (UInt32(index) == inputIndex) Some(output)
else None
}
val updatedOutputs : Seq[TransactionOutput] = updatedOutputsOpt.flatten
val spendingTxOutputsEmptied = Transaction(spendingTransaction,UpdateTransactionOutputs(updatedOutputs))
@ -260,7 +254,6 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit
/**
* Executes the SIGHASH_ALL procedure on a spending transaction at inputIndex
*
* @param spendingTransaction
* @param inputIndex
* @return
@ -271,7 +264,6 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit
/**
* Executes the SIGHASH_ANYONECANPAY procedure on a spending transaction at inputIndex
*
* @param spendingTransaction
* @param input
* @return
@ -285,7 +277,6 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper with Bit
/**
* Removes OP_CODESEPARATOR operations then returns the script
* format
*
* @return
*/
def removeOpCodeSeparators(script : Seq[ScriptToken]) : Seq[ScriptToken] = {

View file

@ -43,7 +43,7 @@ object P2PKHScriptPubKey extends Factory[P2PKHScriptPubKey] {
}
def apply(pubKey : ECPublicKey) = {
val hash = CryptoUtil.ripeMd160(pubKey.bytes)
val hash = CryptoUtil.sha256Hash160(pubKey.bytes)
val bytesToPushOntoStack = BytesToPushOntoStack(hash.bytes.size)
val asm = Seq(OP_DUP, OP_HASH160, bytesToPushOntoStack, ScriptConstant(hash.bytes), OP_EQUALVERIFY, OP_CHECKSIG)
val scriptPubKey = ScriptPubKey.fromAsm(asm)

View file

@ -30,7 +30,7 @@ trait Base58 extends BitcoinSLogger {
val splitSeqs = decoded.splitAt(decoded.length - 4)
val data : Seq[Byte] = splitSeqs._1
val checksum : Seq[Byte] = splitSeqs._2
val actualChecksum : Seq[Byte] = CryptoUtil.doubleSHA256(data).bytes.slice(0, 4)
val actualChecksum : Seq[Byte] = CryptoUtil.doubleSHA256(data).bytes.take(4)
if (checksum == actualChecksum) Success(data)
else Failure(new IllegalArgumentException("checksums don't validate"))
}
@ -140,8 +140,8 @@ trait Base58 extends BitcoinSLogger {
* @return
*/
def isValid(base58 : String) : Boolean = validityChecks(base58) match {
case Success(bool) => bool
case Failure(exception) => false
case Success(bool) => bool
case Failure(exception) => false
}
/**

View file

@ -1,12 +1,13 @@
package org.bitcoins.core.crypto
import org.bitcoins.core.util.{BitcoinJTestUtil, BitcoinSUtil, CryptoTestUtil}
import org.bitcoins.core.config.TestNet3
import org.bitcoins.core.util.{BitcoinJTestUtil, BitcoinSLogger, BitcoinSUtil, CryptoTestUtil}
import org.scalatest.{FlatSpec, MustMatchers}
/**
* Created by chris on 3/7/16.
*/
class ECPrivateKeyTest extends FlatSpec with MustMatchers {
class ECPrivateKeyTest extends FlatSpec with MustMatchers with BitcoinSLogger {
"ECPrivateKey" must "have the same byte representation as a bitcoinj private key" in {
val bitcoinjPrivateKey = CryptoTestUtil.bitcoinjPrivateKey.getPrivateKeyAsHex
@ -73,4 +74,20 @@ class ECPrivateKeyTest extends FlatSpec with MustMatchers {
ECPrivateKey() must not equal (ECPrivateKey())
}
it must "serialize a private key to WIF and then be able to deserialize it" in {
val hex = "2cecbfb72f8d5146d7fe7e5a3f80402c6dd688652c332dff2e44618d2d3372"
val privKey = ECPrivateKey(hex)
val wif = privKey.toWIF(TestNet3)
logger.error("WIF: " + wif)
val privKeyFromWIF = ECPrivateKey.fromWIFToPrivateKey(wif)
privKeyFromWIF must be (privKey)
}
it must "correctly decode a private key from WIF" in {
val privateKey = ECPrivateKey.fromWIFToPrivateKey("cTPg4Zc5Jis2EZXy3NXShgbn487GWBTapbU63BerLDZM3w2hQSjC")
//derived hex on bitcore's playground
privateKey.hex must be ("ad59fb6aadf617fb0f93469741fcd9a9f48700f1d1f465ddc0f26fa7f7bfa1ac")
}
}

View file

@ -3,7 +3,7 @@ package org.bitcoins.core.crypto
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.policy.Policy
import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput}
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.script.ScriptProgram
import org.bitcoins.core.script.crypto.SIGHASH_ALL
import org.bitcoins.core.script.interpreter.ScriptInterpreter
@ -19,19 +19,35 @@ class TransactionSignatureCreatorTest extends FlatSpec with MustMatchers with Bi
"TransactionSignatureCreator" must "create a signature for a scriptSignature in a transaction" in {
//this is a signed tx, but since TransactionSignatureSerializer removes scriptSigs, it will work for testing this
//from fe6ef8e20a9ca9cb5d59cb1c0f30eff2b23be2e3cc2bf4b4cfff519414e9a300 on testnet
/* val expectedSig = ECDigitalSignature("30440220357864ae2beba3d6ec34c0ce42262c1c12939502f0f8f4bd338c9d8b307593420220656687c327589dc3e464700fa7b784c7efc2b465c627a60c2f1ce402d05fc39d01")
//"30440220357864ae2beba3d6ec34c0ce42262c1c12939502f0f8f4bd338c9d8b307593420220656687c327589dc3e464700fa7b784c7efc2b465c627a60c2f1ce402d05fc39d01"
val expectedSig = ECDigitalSignature("30440220357864ae2beba3d6ec34c0ce42262c1c12939502f0f8f4bd338c9d8b307593420220656687c327589dc3e464700fa7b784c7efc2b465c627a60c2f1ce402d05fc39d01")
val rawTx = "01000000021d50bf7c05b6169ea8d8fb5b79dd2978bbd2ac756a656a777279da43b19fd9d9000000006b4830450221008f2c818a55045a1c9dcda54fcd5b6377f5d09723a9ccd8c71df76ee4bdf7c16802201817cbd71d8148a5d53b11d33c9c58ad1086fe7ddf308da2a7cceb7d85df293e01210381c82dc267a958be06f1c920dc635bcd191d698c167e67a45a882a551c57ce1dfeffffffd4a6a37abfe003a9d10155df215e662f88d5b878b908d1a3772a9fbd195d008d010000006a4730440220357864ae2beba3d6ec34c0ce42262c1c12939502f0f8f4bd338c9d8b307593420220656687c327589dc3e464700fa7b784c7efc2b465c627a60c2f1ce402d05fc39d0121036301d848aec3dfc47789a63ee3c85c6d3bf757162ef77cb1580981b422838ed7feffffff0200e1f505000000001976a9146d39bac171d0bf450698fa0ebd93f51e79dcb6ac88ac35a96d00000000001976a914e11753f499ac7a910148e53156ab273557ed517e88acd6090b00"
val transaction = Transaction(rawTx)
val scriptPubKey = ScriptPubKey("76a914d7b4717a934386601ac3f980d01b48c83b8a0b4b88ac")
val txSignatureComponent = TransactionSignatureComponent(transaction,UInt32.one,scriptPubKey, Policy.standardScriptVerifyFlags)
val txSignatureComponent = TransactionSignatureComponent(transaction, UInt32.one, scriptPubKey, Policy.standardScriptVerifyFlags)
val privateKey = ECPrivateKey.fromWIFToPrivateKey("cTPg4Zc5Jis2EZXy3NXShgbn487GWBTapbU63BerLDZM3w2hQSjC")
val txSignature = TransactionSignatureCreator.createSig(txSignatureComponent,privateKey,scriptPubKey, SIGHASH_ALL())
val txSignature = TransactionSignatureCreator.createSig(txSignatureComponent, privateKey, SIGHASH_ALL())
logger.error("generated sig: " + txSignature)
txSignature.r must be (expectedSig.r)
txSignature.s must be (expectedSig.s)
txSignature.hex must be (expectedSig.hex)*/
txSignature.hex must be (expectedSig.hex)
}
it must "create the correct digital signature for a transaction with 1 input" in {
//66f48fa8ef5db20a3b4be6b13f024b6e23480fd83df26ffbe7449110b113a665 on testnet
val expectedSig = ECDigitalSignature("3044022075b4ab08ff34799ee6f8048a5044be98dff493fc5a0b8a36dcaee3bd7a9993ae02207bc532ceab09c10f1d54035d03ff9aad0e1004c3e0325a8b97b6be04b7d6c3a201")
val rawTx = "0100000001b8a1278696acfa85f1f576836aa30d335207b69bdaff43d9464cc1db40fe19ae000000006a473044022075b4ab08ff34799ee6f8048a5044be98dff493fc5a0b8a36dcaee3bd7a9993ae02207bc532ceab09c10f1d54035d03ff9aad0e1004c3e0325a8b97b6be04b7d6c3a2012102a01aaa27b468ec3fb2ae0c2a9fa1d5dce9b79b35062178f479156d8daa6c0e50feffffff02a0860100000000001976a914775bd9c79a9e988c0d6177a9205a611a50b7229188acb6342900000000001976a914f23a46f930320ab3cc7ad8c1660325f4c434d11688ac63b70d00"
val transaction = Transaction(rawTx)
val scriptPubKey = ScriptPubKey("76a914cd0385f813ec73f8fc340b7069daf566878a0d6b88ac")
val txSignatureComponent = TransactionSignatureComponent(transaction, UInt32.zero, scriptPubKey, Policy.standardScriptVerifyFlags)
val privateKey = ECPrivateKey.fromWIFToPrivateKey("cTTh7jNtZhg3vHTjvYK8zcHkLfsMAS8iqL7pfZ6eVAVHHF8fN1qy")
val txSignature = TransactionSignatureCreator.createSig(txSignatureComponent, privateKey, SIGHASH_ALL())
logger.error("Generated signature: " + txSignature)
txSignature.r must be (expectedSig.r)
txSignature.s must be (expectedSig.s)
txSignature.hex must be (expectedSig.hex)
}
it must "create a p2pk scriptPubKey, create a crediting tx for scriptPubKey, " +
"then create spending tx and make sure it evaluates to true in the interpreter" in {
val privateKey = ECPrivateKey()
@ -41,7 +57,7 @@ class TransactionSignatureCreatorTest extends FlatSpec with MustMatchers with Bi
val scriptSig = P2PKScriptSignature(EmptyDigitalSignature)
val (spendingTx,inputIndex) = TransactionTestUtil.buildSpendingTransaction(creditingTx,scriptSig,outputIndex)
val txSignatureComponent = TransactionSignatureComponent(spendingTx,inputIndex,scriptPubKey,Policy.standardScriptVerifyFlags)
val txSignature = TransactionSignatureCreator.createSig(txSignatureComponent,privateKey,scriptPubKey, SIGHASH_ALL())
val txSignature = TransactionSignatureCreator.createSig(txSignatureComponent,privateKey,SIGHASH_ALL())
//add the signature to the scriptSig instead of having an empty scriptSig
val signedScriptSig = P2PKScriptSignature(txSignature)
@ -57,14 +73,14 @@ class TransactionSignatureCreatorTest extends FlatSpec with MustMatchers with Bi
it must "create a p2pkh scriptPubKey, create a crediting tx for the scriptPubkey" +
"then create a spending tx and make sure it evaluates to true in the interpreter" in {
/* val privateKey = ECPrivateKey()
val privateKey = ECPrivateKey()
val publicKey = privateKey.publicKey
val scriptPubKey = P2PKHScriptPubKey(publicKey)
val (creditingTx,outputIndex) = TransactionTestUtil.buildCreditingTransaction(scriptPubKey)
val scriptSig = P2PKHScriptSignature(EmptyDigitalSignature,publicKey)
val (spendingTx,inputIndex) = TransactionTestUtil.buildSpendingTransaction(creditingTx,scriptSig,outputIndex)
val txSignatureComponent = TransactionSignatureComponent(spendingTx,inputIndex,scriptPubKey,Policy.standardScriptVerifyFlags)
val txSignature = TransactionSignatureCreator.createSig(txSignatureComponent,privateKey,scriptPubKey, SIGHASH_ALL())
val txSignature = TransactionSignatureCreator.createSig(txSignatureComponent,privateKey, SIGHASH_ALL())
//add the signature to the scriptSig instead of having an empty scriptSig
val signedScriptSig = P2PKHScriptSignature(txSignature,publicKey)
@ -75,7 +91,7 @@ class TransactionSignatureCreatorTest extends FlatSpec with MustMatchers with Bi
val result = ScriptInterpreter.run(program)
result must be (ScriptOk)*/
result must be (ScriptOk)
}
it must "create a multisignature scriptPubKey, create a crediting tx for the scriptPubkey, " +
@ -87,7 +103,7 @@ class TransactionSignatureCreatorTest extends FlatSpec with MustMatchers with Bi
val scriptSig = MultiSignatureScriptSignature(Seq(EmptyDigitalSignature))
val (spendingTx,inputIndex) = TransactionTestUtil.buildSpendingTransaction(creditingTx,scriptSig,outputIndex)
val txSignatureComponent = TransactionSignatureComponent(spendingTx,inputIndex,scriptPubKey,Policy.standardScriptVerifyFlags)
val txSignature = TransactionSignatureCreator.createSig(txSignatureComponent,privateKey,scriptPubKey, SIGHASH_ALL())
val txSignature = TransactionSignatureCreator.createSig(txSignatureComponent,privateKey, SIGHASH_ALL())
//add the signature to the scriptSig instead of having an empty scriptSig
val signedScriptSig = MultiSignatureScriptSignature(Seq(txSignature))