From 31a419445d55cf562c01611559d5a36f4c0e16ef Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Mon, 25 Jul 2016 11:24:13 -0500 Subject: [PATCH] Successfully replicating the creation of a digital signature that bitcoin core produced --- .../crypto/TransactionSignatureCreator.scala | 6 +-- .../TransactionSignatureSerializer.scala | 29 +++++--------- .../core/protocol/script/ScriptPubKey.scala | 2 +- .../scala/org/bitcoins/core/util/Base58.scala | 6 +-- .../core/crypto/ECPrivateKeyTest.scala | 21 +++++++++- .../TransactionSignatureCreatorTest.scala | 38 +++++++++++++------ 6 files changed, 62 insertions(+), 40 deletions(-) diff --git a/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureCreator.scala b/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureCreator.scala index fcdd5d1958..1a4c991f6e 100644 --- a/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureCreator.scala +++ b/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureCreator.scala @@ -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)) diff --git a/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala b/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala index 02df69755d..977bedec88 100644 --- a/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala +++ b/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala @@ -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] = { diff --git a/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala b/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala index 0154387a65..17b468cfcf 100644 --- a/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala +++ b/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala @@ -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) diff --git a/src/main/scala/org/bitcoins/core/util/Base58.scala b/src/main/scala/org/bitcoins/core/util/Base58.scala index b7c2a20d83..381737bad0 100644 --- a/src/main/scala/org/bitcoins/core/util/Base58.scala +++ b/src/main/scala/org/bitcoins/core/util/Base58.scala @@ -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 } /** diff --git a/src/test/scala/org/bitcoins/core/crypto/ECPrivateKeyTest.scala b/src/test/scala/org/bitcoins/core/crypto/ECPrivateKeyTest.scala index 083c674710..7f6f3ed8fc 100644 --- a/src/test/scala/org/bitcoins/core/crypto/ECPrivateKeyTest.scala +++ b/src/test/scala/org/bitcoins/core/crypto/ECPrivateKeyTest.scala @@ -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") + } } diff --git a/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala b/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala index 31e9fdd02c..3ac2a63820 100644 --- a/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala +++ b/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala @@ -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))