mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-20 02:11:40 +01:00
Adding factories for OutPoints, modifying many other factories to add more functionality
This commit is contained in:
parent
1b5ee89929
commit
dd19248eff
@ -1,5 +1,6 @@
|
||||
package org.scalacoin.crypto
|
||||
|
||||
import org.scalacoin.currency.CurrencyUnits
|
||||
import org.scalacoin.marshallers.RawBitcoinSerializerHelper
|
||||
import org.scalacoin.marshallers.transaction.RawTransactionOutputParser
|
||||
import org.scalacoin.protocol.script.{ScriptSignatureFactory, ScriptSignatureImpl, ScriptPubKeyFactory, ScriptPubKey}
|
||||
@ -33,7 +34,7 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper {
|
||||
* @param script
|
||||
* @return
|
||||
*/
|
||||
def serializeScriptCode(script : Seq[ScriptToken]) : Seq[Byte] = removeOpCodeSeparators(script)
|
||||
def serializeScriptCode(script : Seq[ScriptToken]) : ScriptPubKey = removeOpCodeSeparators(script)
|
||||
|
||||
|
||||
def serializeInput(input : TransactionInput, nType : Int, nVersion : Int) : String = ???
|
||||
@ -62,15 +63,16 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper {
|
||||
|
||||
}
|
||||
|
||||
def serialize(inputIndex : Int, nType : Int, script : ScriptPubKey, hashType : HashType) : Seq[Byte] = {
|
||||
def serialize(inputIndex : Int, script : ScriptPubKey, hashType : HashType) : Seq[Byte] = {
|
||||
// Clear input scripts in preparation for signing. If we're signing a fresh
|
||||
// transaction that step isn't very helpful, but it doesn't add much cost relative to the actual
|
||||
// EC math so we'll do it anyway.
|
||||
val txWithInputSigsRemoved = for {
|
||||
val inputSigsRemoved = for {
|
||||
input <- spendingTransaction.inputs
|
||||
} yield input.factory(ScriptSignatureFactory.empty)
|
||||
|
||||
|
||||
|
||||
// This step has no purpose beyond being synchronized with Bitcoin Core's bugs. OP_CODESEPARATOR
|
||||
// is a legacy holdover from a previous, broken design of executing scripts that shipped in Bitcoin 0.1.
|
||||
// It was seriously flawed and would have let anyone take anyone elses money. Later versions switched to
|
||||
@ -78,32 +80,39 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper {
|
||||
// OP_CODESEPARATOR instruction having no purpose as it was only meant to be used internally, not actually
|
||||
// ever put into scripts. Deleting OP_CODESEPARATOR is a step that should never be required but if we don't
|
||||
// do it, we could split off the main chain.
|
||||
val scriptWithOpCodeSeparatorsRemoved : Seq[Byte] = serializeScriptCode(script.asm)
|
||||
val scriptWithOpCodeSeparatorsRemoved : ScriptPubKey = serializeScriptCode(script.asm)
|
||||
|
||||
val inputToSign = spendingTransaction.inputs(inputIndex)
|
||||
val inputToSign = inputSigsRemoved(inputIndex)
|
||||
|
||||
// Set the input to the script of its output. Bitcoin Core does this but the step has no obvious purpose as
|
||||
// the signature covers the hash of the prevout transaction which obviously includes the output script
|
||||
// already. Perhaps it felt safer to him in some way, or is another leftover from how the code was written.
|
||||
val inputWithConnectedScript = inputToSign.factory(script)
|
||||
val inputWithConnectedScript = inputToSign.factory(scriptWithOpCodeSeparatorsRemoved)
|
||||
val updatedInputs = for {
|
||||
(input,index) <- inputSigsRemoved.zipWithIndex
|
||||
} yield {
|
||||
if (index == inputIndex) inputWithConnectedScript
|
||||
else input
|
||||
}
|
||||
//update the input at index i with inputWithConnectScript
|
||||
|
||||
val txWithInputSigsRemoved = spendingTransaction.factory(UpdateTransactionInputs(updatedInputs))
|
||||
//check the hash type
|
||||
|
||||
hashType match {
|
||||
case SIGHASH_NONE =>
|
||||
//following this implementation from bitcoinj
|
||||
//https://github.com/bitcoinj/bitcoinj/blob/09a2ca64d2134b0dcbb27b1a6eb17dda6087f448/core/src/main/java/org/bitcoinj/core/Transaction.java#L957
|
||||
//means that no outputs are signed at all
|
||||
val txWithNoOutputs = spendingTransaction.emptyOutputs
|
||||
val txWithNoOutputs = txWithInputSigsRemoved.emptyOutputs
|
||||
//set the sequence number of all inputs to 0 EXCEPT the input at inputIndex
|
||||
val updatedInputs : Seq[TransactionInput] = for {
|
||||
(input,index) <- spendingTransaction.inputs.zipWithIndex
|
||||
} yield {
|
||||
if (index == inputIndex) input
|
||||
else input.factory(0)
|
||||
}
|
||||
val updatedTransactionWithInputsNoOutputs = txWithNoOutputs.factory(UpdateTransactionInputs(updatedInputs))
|
||||
|
||||
CryptoUtil.doubleSHA256(updatedTransactionWithInputsNoOutputs.bytes)
|
||||
val updatedInputs : Seq[TransactionInput] = setSequenceNumbersZero(spendingTransaction.inputs,inputIndex)
|
||||
val sigHashNoneTx = txWithNoOutputs.factory(UpdateTransactionInputs(updatedInputs))
|
||||
//append hash type byte onto the end of the tx bytes
|
||||
CryptoUtil.doubleSHA256(sigHashNoneTx.bytes ++ List(hashType.byte))
|
||||
case SIGHASH_SINGLE =>
|
||||
//following this implementation from bitcoinj
|
||||
//https://github.com/bitcoinj/bitcoinj/blob/09a2ca64d2134b0dcbb27b1a6eb17dda6087f448/core/src/main/java/org/bitcoinj/core/Transaction.java#L964
|
||||
if (inputIndex >= spendingTransaction.outputs.size) {
|
||||
// comment copied from bitcoinj
|
||||
// The input index is beyond the number of outputs, it's a buggy signature made by a broken
|
||||
@ -118,14 +127,20 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper {
|
||||
} else {
|
||||
// In SIGHASH_SINGLE the outputs after the matching input index are deleted, and the outputs before
|
||||
// that position are "nulled out". Unintuitively, the value in a "null" transaction is set to -1.
|
||||
val updatedOutputs = txWithInputSigsRemoved.outputs.map(o => o.empty.factory(CurrencyUnits.negativeSatoshi))
|
||||
val spendingTxOutputsEmptied = txWithInputSigsRemoved.factory(UpdateTransactionOutputs(updatedOutputs))
|
||||
//create blank inputs with sequence numbers set to zero EXCEPT
|
||||
//the input at the inputIndex
|
||||
val updatedInputs = setSequenceNumbersZero(spendingTxOutputsEmptied.inputs,inputIndex)
|
||||
|
||||
//create blank outputs with sequence numbers set to zero EXCEPT
|
||||
//the output at the inputIndex
|
||||
//val outputs = spendingTransaction.outputs.map(_)
|
||||
|
||||
val sigHashSingleTx = spendingTxOutputsEmptied.factory(UpdateTransactionInputs(updatedInputs))
|
||||
//append hash type byte onto the end of the tx bytes
|
||||
CryptoUtil.doubleSHA256(sigHashSingleTx.bytes ++ List(hashType.byte))
|
||||
}
|
||||
case SIGHASH_ALL =>
|
||||
//just need to add the hash type and hash the tx
|
||||
CryptoUtil.doubleSHA256(txWithInputSigsRemoved.bytes ++ List(hashType.byte))
|
||||
}
|
||||
???
|
||||
|
||||
}
|
||||
|
||||
@ -134,12 +149,27 @@ trait TransactionSignatureSerializer extends RawBitcoinSerializerHelper {
|
||||
* format
|
||||
* @return
|
||||
*/
|
||||
def removeOpCodeSeparators(script : Seq[ScriptToken]) : Seq[Byte] = {
|
||||
def removeOpCodeSeparators(script : Seq[ScriptToken]) : ScriptPubKey = {
|
||||
val scriptWithoutOpCodeSeparators : String = script.filterNot(_ == OP_CODESEPARATOR).map(_.hex).mkString
|
||||
val scriptWithoutOpCodeSeparatorSize = addPrecedingZero((scriptWithoutOpCodeSeparators.size / 2).toHexString)
|
||||
val expectedScript : ScriptPubKey = ScriptPubKeyFactory.factory(
|
||||
scriptWithoutOpCodeSeparatorSize + scriptWithoutOpCodeSeparators)
|
||||
expectedScript.bytes
|
||||
expectedScript
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input's sequence number to zero EXCEPT for the input at inputIndex
|
||||
* @param inputs
|
||||
* @param inputIndex
|
||||
* @return
|
||||
*/
|
||||
private def setSequenceNumbersZero(inputs : Seq[TransactionInput], inputIndex : Int) : Seq[TransactionInput] = {
|
||||
for {
|
||||
(input,index) <- inputs.zipWithIndex
|
||||
} yield {
|
||||
if (index == inputIndex) input
|
||||
else input.factory(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,6 +62,7 @@ case class MilliBitcoins(override val value : Double) extends CurrencyUnit(value
|
||||
}
|
||||
|
||||
object CurrencyUnits {
|
||||
def negativeSatoshi = Satoshis(-1)
|
||||
def oneSatoshi = Satoshis(1)
|
||||
def oneMilliBit = Satoshis(100000)
|
||||
def tenMilliBits = Satoshis(1000000)
|
||||
|
@ -25,6 +25,12 @@ trait Transaction extends TransactionElement with TransactionFactory {
|
||||
override def hex = RawTransactionParser.write(this)
|
||||
}
|
||||
|
||||
object Transaction extends Transaction {
|
||||
override def version = TransactionConstants.version
|
||||
override def inputs = Seq()
|
||||
override def outputs = Seq()
|
||||
override def lockTime = TransactionConstants.lockTime
|
||||
}
|
||||
case class TransactionImpl(version : Long, inputs : Seq[TransactionInput],
|
||||
outputs : Seq[TransactionOutput], lockTime : Long) extends Transaction
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
package org.scalacoin.protocol.transaction
|
||||
|
||||
import org.scalacoin.marshallers.transaction.{RawTransactionParser, RawTransactionInputParser}
|
||||
|
||||
/**
|
||||
* Created by chris on 2/21/16.
|
||||
*/
|
||||
|
||||
trait TransactionFactory { this : Transaction =>
|
||||
|
||||
|
||||
@ -38,10 +39,24 @@ trait TransactionFactory { this : Transaction =>
|
||||
*/
|
||||
def emptyOutputs : Transaction = TransactionImpl(version,inputs,Seq(),lockTime)
|
||||
|
||||
def empty : Transaction = TransactionImpl(TransactionConstants.version,Seq(),Seq(),TransactionConstants.lockTime)
|
||||
|
||||
/**
|
||||
* Creates a transaction object from a sequence of bytes
|
||||
* @param bytes
|
||||
* @return
|
||||
*/
|
||||
def factory(bytes : Seq[Byte]) : Transaction = RawTransactionParser.read(bytes)
|
||||
|
||||
/**
|
||||
* Creates a transction object from its hexadecimal representation
|
||||
* @param hex
|
||||
* @return
|
||||
*/
|
||||
def factory(hex : String) : Transaction = RawTransactionParser.read(hex)
|
||||
|
||||
}
|
||||
|
||||
sealed trait TransactionFactoryHelper
|
||||
case class UpdateTransactionOutputs(outputs : Seq[TransactionOutput]) extends TransactionFactoryHelper
|
||||
case class UpdateTransactionInputs(inputs : Seq[TransactionInput]) extends TransactionFactoryHelper
|
||||
|
||||
|
@ -5,7 +5,7 @@ import org.scalacoin.marshallers.transaction.{RawTransactionOutPointParser, Tran
|
||||
/**
|
||||
* Created by chris on 12/26/15.
|
||||
*/
|
||||
trait TransactionOutPoint extends TransactionElement {
|
||||
trait TransactionOutPoint extends TransactionElement with TransactionOutPointFactory {
|
||||
def txId : String
|
||||
def vout : Int
|
||||
|
||||
@ -15,4 +15,9 @@ trait TransactionOutPoint extends TransactionElement {
|
||||
override def hex = RawTransactionOutPointParser.write(this)
|
||||
}
|
||||
|
||||
object TransactionOutPoint extends TransactionOutPoint {
|
||||
def txId : String = ""
|
||||
def vout = -1
|
||||
|
||||
}
|
||||
case class TransactionOutPointImpl(txId : String, vout : Int) extends TransactionOutPoint
|
@ -5,12 +5,18 @@ import java.util
|
||||
import org.bitcoinj.core.DumpedPrivateKey
|
||||
import org.bitcoinj.core.Transaction.SigHash
|
||||
import org.bitcoinj.params.TestNet3Params
|
||||
import org.bitcoinj.script.ScriptBuilder
|
||||
import org.scalacoin.protocol.script.{ScriptPubKey, ScriptPubKeyFactory}
|
||||
import org.scalacoin.script.constant.{OP_2, OP_0, OP_1, ScriptToken}
|
||||
import org.scalacoin.script.crypto.OP_CODESEPARATOR
|
||||
import org.bitcoinj.script.{ScriptChunk, ScriptBuilder}
|
||||
import org.scalacoin.protocol.script.{UpdateScriptPubKeyAsm, UpdateScriptPubKeyBytes, ScriptPubKey, ScriptPubKeyFactory}
|
||||
import org.scalacoin.protocol.transaction.{UpdateTransactionOutputs, Transaction}
|
||||
import org.scalacoin.script.ScriptOperationFactory
|
||||
import org.scalacoin.script.bitwise.OP_EQUALVERIFY
|
||||
import org.scalacoin.script.constant._
|
||||
import org.scalacoin.script.crypto.{OP_CHECKSIG, OP_HASH160, SIGHASH_ALL, OP_CODESEPARATOR}
|
||||
import org.scalacoin.script.stack.OP_DUP
|
||||
import org.scalacoin.util.{ScalacoinUtil, TestUtil}
|
||||
import org.scalatest.{FlatSpec, MustMatchers}
|
||||
import scala.collection.JavaConversions._
|
||||
|
||||
|
||||
/**
|
||||
* Created by chris on 2/19/16.
|
||||
@ -27,21 +33,42 @@ class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers {
|
||||
|
||||
it must "serialize a given script with only OP_CODESEPARATORs" in {
|
||||
val txSerializer = new BaseTransactionSignatureSerializer(TestUtil.transaction)
|
||||
val script = List(OP_CODESEPARATOR)
|
||||
txSerializer.serializeScriptCode(script) must be ("00")
|
||||
val scriptPubKey = ScriptPubKeyFactory.factory(UpdateScriptPubKeyAsm(List(OP_CODESEPARATOR)))
|
||||
txSerializer.serializeScriptCode(scriptPubKey.asm).hex must be ("00")
|
||||
}
|
||||
|
||||
it must "serialize a given script with mixed in OP_CODESEPARATORs" in {
|
||||
val txSerializer = new BaseTransactionSignatureSerializer(TestUtil.transaction)
|
||||
val script = List(OP_CODESEPARATOR, OP_1, OP_CODESEPARATOR, OP_0, OP_CODESEPARATOR, OP_2)
|
||||
txSerializer.serializeScriptCode(script) must be ("03510052")
|
||||
val scriptPubKey = ScriptPubKeyFactory.factory(UpdateScriptPubKeyAsm(script))
|
||||
txSerializer.serializeScriptCode(scriptPubKey.asm).hex must be ("03510052")
|
||||
}
|
||||
|
||||
|
||||
it must "hash a multisignature SIGHASH_ALL correctly" in {
|
||||
|
||||
|
||||
val params = TestNet3Params.get();
|
||||
val key1 = new DumpedPrivateKey(params, "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT").getKey();
|
||||
val key2 = new DumpedPrivateKey(params, "cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo").getKey();
|
||||
val key3 = new DumpedPrivateKey(params, "cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg").getKey();
|
||||
val multiSigScript : org.bitcoinj.script.Script = ScriptBuilder.createMultiSigOutputScript(2, util.Arrays.asList(key1, key2, key3));
|
||||
val scriptPubKey = bitcoinjScriptToScriptPubKey(multiSigScript)
|
||||
|
||||
val creditingTx = Transaction.factory("01000000013df681ff83b43b6585fa32dd0e12b0b502e6481e04ee52ff0fdaf55a16a4ef61000000006b483045022100a84acca7906c13c5895a1314c165d33621cdcf8696145080895cbf301119b7cf0220730ff511106aa0e0a8570ff00ee57d7a6f24e30f592a10cae1deffac9e13b990012102b8d567bcd6328fd48a429f9cf4b315b859a58fd28c5088ef3cb1d98125fc4e8dffffffff02364f1c00000000001976a91439a02793b418de8ec748dd75382656453dc99bcb88ac40420f000000000017a9145780b80be32e117f675d6e0ada13ba799bf248e98700000000")
|
||||
val output = creditingTx.outputs(1)
|
||||
val address = new org.bitcoinj.core.Address(params, "n3CFiCmBXVt5d3HXKQ15EFZyhPz4yj5F3H");
|
||||
val addressHash = new ScriptConstantImpl(address.getHash160.toList)
|
||||
val outputScript : Seq[ScriptToken] = Seq(OP_DUP, OP_HASH160, addressHash, OP_EQUALVERIFY, OP_CHECKSIG)
|
||||
|
||||
val spendingTx = Transaction.empty.factory(UpdateTransactionOutputs())
|
||||
|
||||
|
||||
|
||||
val txSignatureSerializer = new BaseTransactionSignatureSerializer(Transaction.empty)
|
||||
|
||||
val bitcoinjSigHash = createBitcoinjMultiSigScriptHashForSig
|
||||
txSignatureSerializer.serialize(0,scriptPubKey,SIGHASH_ALL) must be (bitcoinjSigHash)
|
||||
}
|
||||
|
||||
|
||||
@ -52,15 +79,37 @@ class TransactionSignatureSerializerTest extends FlatSpec with MustMatchers {
|
||||
* @return
|
||||
*/
|
||||
private def createBitcoinjMultiSigScriptHashForSig : String = {
|
||||
//https://github.com/bitcoinj/bitcoinj/blob/master/core/src/test/java/org/bitcoinj/script/ScriptTest.java#L127
|
||||
val txHex = "01000000013df681ff83b43b6585fa32dd0e12b0b502e6481e04ee52ff0fdaf55a16a4ef61000000006b483045022100a84acca7906c13c5895a1314c165d33621cdcf8696145080895cbf301119b7cf0220730ff511106aa0e0a8570ff00ee57d7a6f24e30f592a10cae1deffac9e13b990012102b8d567bcd6328fd48a429f9cf4b315b859a58fd28c5088ef3cb1d98125fc4e8dffffffff02364f1c00000000001976a91439a02793b418de8ec748dd75382656453dc99bcb88ac40420f000000000017a9145780b80be32e117f675d6e0ada13ba799bf248e98700000000"
|
||||
val params = TestNet3Params.get();
|
||||
val spendTx = new org.bitcoinj.core.Transaction(params);
|
||||
val key1 = new DumpedPrivateKey(params, "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT").getKey();
|
||||
val key2 = new DumpedPrivateKey(params, "cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo").getKey();
|
||||
val key3 = new DumpedPrivateKey(params, "cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg").getKey();
|
||||
|
||||
|
||||
val creditingTx = new org.bitcoinj.core.Transaction(params,ScalacoinUtil.decodeHex(txHex).toArray)
|
||||
val output = creditingTx.getOutput(1);
|
||||
val spendTx = new org.bitcoinj.core.Transaction(params);
|
||||
val address = new org.bitcoinj.core.Address(params, "n3CFiCmBXVt5d3HXKQ15EFZyhPz4yj5F3H");
|
||||
val outputScript = org.bitcoinj.script.ScriptBuilder.createOutputScript(address);
|
||||
spendTx.addOutput(output.getValue(), outputScript)
|
||||
spendTx.addInput(output);
|
||||
val multisigScript = ScriptBuilder.createMultiSigOutputScript(2, util.Arrays.asList(key1, key2, key3));
|
||||
val sighash = spendTx.hashForSignature(0, multisigScript, SigHash.ALL, false);
|
||||
ScalacoinUtil.encodeHex(sighash.getBytes)
|
||||
|
||||
}
|
||||
|
||||
private def bitcoinjScriptToScriptPubKey(bitcoinjScript : org.bitcoinj.script.Script) : ScriptPubKey = {
|
||||
|
||||
val parsedScriptTokens : List[ScriptToken] = bitcoinjScript.getChunks.toList.map { chunk =>
|
||||
println("Chunk: " + chunk)
|
||||
if (chunk.isOpCode) ScriptOperationFactory.fromByte(chunk.opcode.toByte).get
|
||||
else new ScriptConstantImpl(chunk.data.toList)
|
||||
}
|
||||
val scriptPubKey = ScriptPubKeyFactory.factory(UpdateScriptPubKeyAsm(parsedScriptTokens))
|
||||
println(scriptPubKey)
|
||||
scriptPubKey
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user