mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-24 23:08:31 +01:00
Successfully replicating the creation of a digital signature that bitcoin core produced
This commit is contained in:
parent
71a50be511
commit
31a419445d
6 changed files with 62 additions and 40 deletions
|
@ -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))
|
||||
|
|
|
@ -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] = {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Add table
Reference in a new issue