Successfully creating p2pk and multisignature scriptSig digital signatures - they evaluate to true when run through the ScriptInterpreter

This commit is contained in:
Chris Stewart 2016-07-22 20:58:23 -05:00
parent 40697b23e0
commit 71a50be511
9 changed files with 199 additions and 19 deletions

View File

@ -46,3 +46,27 @@ trait TransactionSignatureComponent {
*/ */
def flags : Seq[ScriptFlag] def flags : Seq[ScriptFlag]
} }
object TransactionSignatureComponent {
private sealed case class TransactionSignatureComponentImpl(transaction : Transaction, inputIndex : UInt32,
scriptPubKey : ScriptPubKey, flags : Seq[ScriptFlag]) extends TransactionSignatureComponent
def apply(transaction : Transaction, inputIndex : UInt32, scriptPubKey : ScriptPubKey,
flags : Seq[ScriptFlag]) : TransactionSignatureComponent = {
TransactionSignatureComponentImpl(transaction,inputIndex,scriptPubKey, flags)
}
/**
* This factory method is used for changing the scriptPubKey inside of a txSignatureComponent
*
* @param oldTxSignatureComponent
* @param scriptPubKey
* @return
*/
def apply(oldTxSignatureComponent : TransactionSignatureComponent, scriptPubKey : ScriptPubKey) : TransactionSignatureComponent = {
TransactionSignatureComponentImpl(oldTxSignatureComponent.transaction,
oldTxSignatureComponent.inputIndex,scriptPubKey, oldTxSignatureComponent.flags)
}
}

View File

@ -0,0 +1,28 @@
package org.bitcoins.core.crypto
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.script.crypto.HashType
/**
* Created by chris on 7/21/16.
*/
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)
val signature = privateKey.sign(hash)
//append 1 byte hash type onto the end
ECDigitalSignature(signature.bytes ++ Seq(hashType.byte))
}
}
object TransactionSignatureCreator extends TransactionSignatureCreator

View File

@ -160,6 +160,17 @@ 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)
}
/** /**

View File

@ -1,7 +1,7 @@
package org.bitcoins.core.script package org.bitcoins.core.script
import org.bitcoins.core.crypto.{TransactionSignatureComponentFactory, TransactionSignatureComponent} import org.bitcoins.core.crypto.{TransactionSignatureComponent}
import org.bitcoins.core.number.UInt32 import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.script.{ScriptSignature, ScriptPubKey} import org.bitcoins.core.protocol.script.{ScriptSignature, ScriptPubKey}
import org.bitcoins.core.protocol.transaction.Transaction import org.bitcoins.core.protocol.transaction.Transaction
@ -358,7 +358,7 @@ object ScriptProgram {
*/ */
def factory(transaction: Transaction, scriptPubKey : ScriptPubKey, inputIndex : UInt32, script : Seq[ScriptToken], def factory(transaction: Transaction, scriptPubKey : ScriptPubKey, inputIndex : UInt32, script : Seq[ScriptToken],
flags : Seq[ScriptFlag]) : PreExecutionScriptProgram = { flags : Seq[ScriptFlag]) : PreExecutionScriptProgram = {
val txSignatureComponent = TransactionSignatureComponentFactory.factory(transaction,inputIndex,scriptPubKey,flags) val txSignatureComponent = TransactionSignatureComponent(transaction,inputIndex,scriptPubKey,flags)
PreExecutionScriptProgramImpl(txSignatureComponent,List(),script.toList,script.toList,List(),flags) PreExecutionScriptProgramImpl(txSignatureComponent,List(),script.toList,script.toList,List(),flags)
} }

View File

@ -70,6 +70,30 @@ trait Base58 extends BitcoinSLogger {
def encode(byte : Byte) : String = encode(Seq(byte)) def encode(byte : Byte) : String = encode(Seq(byte))
/**
* Encodes a pubkey hash to a base 58 address on the corresponding network
* @param hash the result of Sha256(RipeMD160(pubkey))
* @param network the network on which this address is being generated for
* @return
*/
def encodePubKeyHashToAddress(hash: Sha256Hash160Digest, network: NetworkParameters): Address = {
val versionByte: Byte = network.p2pkhNetworkByte
val bytes = Seq(versionByte) ++ hash.bytes
val checksum = CryptoUtil.doubleSHA256(bytes).bytes.take(4)
Address(encode(bytes ++ checksum))
}
/**
* Determines the version byte of an address given the address type and network
* @param addressType "pubkey" or "script"
* @param isTestnet Boolean
* @return
*/
private def findVersionByte(addressType : String, isTestnet : Boolean) : Byte = isTestnet match {
case true => if (addressType == "pubkey") TestNet3.p2pkhNetworkByte else TestNet3.p2shNetworkByte
case false => if (addressType == "pubkey") MainNet.p2pkhNetworkByte else MainNet.p2shNetworkByte
}
/** /**
* Encodes a private key into Wallet Import Format (WIF) * Encodes a private key into Wallet Import Format (WIF)
* https://en.bitcoin.it/wiki/Wallet_import_format * https://en.bitcoin.it/wiki/Wallet_import_format

View File

@ -70,7 +70,7 @@ class ECPrivateKeyTest extends FlatSpec with MustMatchers {
} }
it must "create a fresh private key" in { it must "create a fresh private key" in {
ECPrivateKey.apply().isInstanceOf[ECPrivateKey] must be (true) ECPrivateKey() must not equal (ECPrivateKey())
} }
} }

View File

@ -0,0 +1,106 @@
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.script.ScriptProgram
import org.bitcoins.core.script.crypto.SIGHASH_ALL
import org.bitcoins.core.script.interpreter.ScriptInterpreter
import org.bitcoins.core.script.result.ScriptOk
import org.bitcoins.core.util.{BitcoinSLogger, TransactionTestUtil}
import org.scalatest.{FlatSpec, MustMatchers}
/**
* Created by chris on 7/21/16.
*/
class TransactionSignatureCreatorTest extends FlatSpec with MustMatchers with BitcoinSLogger {
"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")
val rawTx = "01000000021d50bf7c05b6169ea8d8fb5b79dd2978bbd2ac756a656a777279da43b19fd9d9000000006b4830450221008f2c818a55045a1c9dcda54fcd5b6377f5d09723a9ccd8c71df76ee4bdf7c16802201817cbd71d8148a5d53b11d33c9c58ad1086fe7ddf308da2a7cceb7d85df293e01210381c82dc267a958be06f1c920dc635bcd191d698c167e67a45a882a551c57ce1dfeffffffd4a6a37abfe003a9d10155df215e662f88d5b878b908d1a3772a9fbd195d008d010000006a4730440220357864ae2beba3d6ec34c0ce42262c1c12939502f0f8f4bd338c9d8b307593420220656687c327589dc3e464700fa7b784c7efc2b465c627a60c2f1ce402d05fc39d0121036301d848aec3dfc47789a63ee3c85c6d3bf757162ef77cb1580981b422838ed7feffffff0200e1f505000000001976a9146d39bac171d0bf450698fa0ebd93f51e79dcb6ac88ac35a96d00000000001976a914e11753f499ac7a910148e53156ab273557ed517e88acd6090b00"
val transaction = Transaction(rawTx)
val scriptPubKey = ScriptPubKey("76a914d7b4717a934386601ac3f980d01b48c83b8a0b4b88ac")
val txSignatureComponent = TransactionSignatureComponent(transaction,UInt32.one,scriptPubKey, Policy.standardScriptVerifyFlags)
val privateKey = ECPrivateKey.fromWIFToPrivateKey("cTPg4Zc5Jis2EZXy3NXShgbn487GWBTapbU63BerLDZM3w2hQSjC")
val txSignature = TransactionSignatureCreator.createSig(txSignatureComponent,privateKey,scriptPubKey, SIGHASH_ALL())
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()
val publicKey = privateKey.publicKey
val scriptPubKey = P2PKScriptPubKey(publicKey)
val (creditingTx,outputIndex) = TransactionTestUtil.buildCreditingTransaction(scriptPubKey)
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())
//add the signature to the scriptSig instead of having an empty scriptSig
val signedScriptSig = P2PKScriptSignature(txSignature)
val (signedTx,_) = TransactionTestUtil.buildSpendingTransaction(creditingTx,signedScriptSig,outputIndex)
//run it through the interpreter
val program = ScriptProgram(signedTx,scriptPubKey,inputIndex, Policy.standardScriptVerifyFlags)
val result = ScriptInterpreter.run(program)
result must be (ScriptOk)
}
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 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())
//add the signature to the scriptSig instead of having an empty scriptSig
val signedScriptSig = P2PKHScriptSignature(txSignature,publicKey)
val (signedTx,_) = TransactionTestUtil.buildSpendingTransaction(creditingTx,signedScriptSig,outputIndex)
//run it through the interpreter
val program = ScriptProgram(signedTx,scriptPubKey,inputIndex, Policy.standardScriptVerifyFlags)
val result = ScriptInterpreter.run(program)
result must be (ScriptOk)*/
}
it must "create a multisignature 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 publicKey = privateKey.publicKey
val scriptPubKey = MultiSignatureScriptPubKey(1,Seq(publicKey))
val (creditingTx,outputIndex) = TransactionTestUtil.buildCreditingTransaction(scriptPubKey)
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())
//add the signature to the scriptSig instead of having an empty scriptSig
val signedScriptSig = MultiSignatureScriptSignature(Seq(txSignature))
val (signedTx,_) = TransactionTestUtil.buildSpendingTransaction(creditingTx,signedScriptSig,outputIndex)
//run it through the interpreter
val program = ScriptProgram(signedTx,scriptPubKey,inputIndex, Policy.standardScriptVerifyFlags)
val result = ScriptInterpreter.run(program)
result must be (ScriptOk)
}
}

View File

@ -100,28 +100,15 @@ class Base58Test extends FlatSpec with MustMatchers with BitcoinSLogger {
for { for {
testCase <- testCases testCase <- testCases
} yield { } yield {
testCase must be (Base58ValidTestCaseImpl(testCase.addressOrWIFPrivKey, testCase.hashOrPrivKey, testCase.configParams))
//if testCase is an Address, it must have a valid base58 representation //if testCase is an Address, it must have a valid base58 representation
if (testCase.addressOrWIFPrivKey.isLeft) { if (testCase.addressOrWIFPrivKey.isLeft) {
Base58.isValid(testCase.addressOrWIFPrivKey.left.get.value) must be (true) Base58.isValid(testCase.addressOrWIFPrivKey.left.get.value) must be (true)
} }
else { else {
//logger.debug(testCase.addressOrWIFPrivKey.right.get + " should be true, but showed as " + Base58.isValid(testCase.addressOrWIFPrivKey.right.get)) logger.info(testCase.addressOrWIFPrivKey.right.get + " should be true, but showed as " + Base58.isValid(testCase.addressOrWIFPrivKey.right.get))
Base58.isValid(testCase.addressOrWIFPrivKey.right.get) must be (true) Base58.isValid(testCase.addressOrWIFPrivKey.right.get) must be (true)
} }
} }
//first, second and 48th test cases:
testCases.head must be (Base58ValidTestCaseImpl(Left(Address("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i")),
Left(Sha256Hash160Digest("65a16059864a2fdbc7c99a4723a8395bc6f188eb")),
ConfigParamsImpl(Left("pubkey"), false, false)))
testCases(1) must be (Base58ValidTestCaseImpl(Left(Address("3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou")),
Left(Sha256Hash160Digest("74f209f6ea907e2ea48f74fae05782ae8a665257")), ConfigParamsImpl(Left("script"), false, false)))
testCases(47) must be (Base58ValidTestCaseImpl(Right("cMxXusSihaX58wpJ3tNuuUcZEQGt6DKJ1wEpxys88FFaQCYjku9h"),
Right(ECPrivateKey("0b3b34f0958d8a268193a9814da92c3e8b58b4a4378a542863e34ac289cd830c")),
ConfigParamsImpl(Right(true), true, true)))
} }
it must "read base58_keys_invalid.json and return each as an invalid base58 string" in { it must "read base58_keys_invalid.json and return each as an invalid base58 string" in {
@ -152,7 +139,7 @@ class Base58Test extends FlatSpec with MustMatchers with BitcoinSLogger {
Base58.decodeCheck("3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou").isSuccess must be (true) Base58.decodeCheck("3CMNFxN1oHBc4R1EpboAL5yzHGgE611Xou").isSuccess must be (true)
} }
it must "encode a private key to WIF, then decode it from WIF to hex private key" in { it must "decode a private key from WIF, then encode that decoded private key to WIF" in {
val WIF = "5Kd3NBUAdUnhyzenEwVLy9pBKxSwXvE9FMPyR4UKZvpe6E3AgLr" val WIF = "5Kd3NBUAdUnhyzenEwVLy9pBKxSwXvE9FMPyR4UKZvpe6E3AgLr"
val privateKey = ECPrivateKey.fromWIFToPrivateKey(WIF) val privateKey = ECPrivateKey.fromWIFToPrivateKey(WIF)
Base58.encodePrivateKeyToWIF(privateKey, false, false) must be (WIF) Base58.encodePrivateKeyToWIF(privateKey, false, false) must be (WIF)

View File

@ -9,7 +9,7 @@ import org.bitcoins.core.crypto.{ECPrivateKey}
*/ */
trait CryptoTestUtil { trait CryptoTestUtil {
def privateKeyBase58 = "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT" def privateKeyBase58 = "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT"
def privateKeyBytes = Base58.decode(privateKeyBase58) def privateKeyBytes = Base58.decodeCheck(privateKeyBase58).get
def privateKeyHex = BitcoinSUtil.encodeHex(privateKeyBytes) def privateKeyHex = BitcoinSUtil.encodeHex(privateKeyBytes)
def bitcoinjDumpedPrivateKey = new DumpedPrivateKey(BitcoinJTestUtil.params,privateKeyBase58) def bitcoinjDumpedPrivateKey = new DumpedPrivateKey(BitcoinJTestUtil.params,privateKeyBase58)
def bitcoinjPrivateKey = bitcoinjDumpedPrivateKey.getKey def bitcoinjPrivateKey = bitcoinjDumpedPrivateKey.getKey