Merge pull request #113 from Christewart/tx_builder

Tx builder
This commit is contained in:
Chris Stewart 2018-03-13 11:48:46 -05:00 committed by GitHub
commit bd2271db63
21 changed files with 630 additions and 179 deletions

View File

@ -2,6 +2,8 @@
testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "2")
//testOptions in Test += Tests.Argument("-oF")
coverageExcludedPackages := ".*gen"
coverageMinimum := 90
@ -16,6 +18,5 @@ mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) => {
assemblyOption in assembly := (assemblyOption in assembly).value.copy(includeScala = false)
//testOptions in Test += Tests.Argument("-oF")
scalacOptions ++= Seq("-Xmax-classfile-name", "140")

View File

@ -54,7 +54,7 @@ sealed abstract class MainNet extends BitcoinNetwork {
object MainNet extends MainNet
sealed abstract class TestNet3 extends NetworkParameters {
sealed abstract class TestNet3 extends BitcoinNetwork {
override def chainParams = TestNetChainParams
override def port = 18333
override def rpcPort = 18332
@ -67,8 +67,8 @@ sealed abstract class TestNet3 extends NetworkParameters {
object TestNet3 extends TestNet3
sealed abstract class RegTest extends NetworkParameters {
override def chainParams: ChainParams = RegTestNetChainParams
sealed abstract class RegTest extends BitcoinNetwork {
override def chainParams = RegTestNetChainParams
override def port = 18444
override def rpcPort = TestNet3.rpcPort
override def dnsSeeds = Nil

View File

@ -42,7 +42,7 @@ sealed abstract class TransactionSignatureSerializer {
val inputSigsRemoved = for {
input <- spendingTransaction.inputs
s = input.scriptSignature
} yield TransactionInput(input,NonStandardScriptSignature(s.compactSizeUInt.hex))
} yield TransactionInput(input.previousOutput,NonStandardScriptSignature(s.compactSizeUInt.hex), input.sequence)
//make sure all scriptSigs have empty asm
inputSigsRemoved.map(input =>
@ -65,7 +65,8 @@ sealed abstract class TransactionSignatureSerializer {
// 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 = TransactionInput(inputToSign,scriptWithOpCodeSeparatorsRemoved)
val scriptSig = ScriptSignature.fromAsm(scriptWithOpCodeSeparatorsRemoved)
val inputWithConnectedScript = TransactionInput(inputToSign.previousOutput,scriptSig, inputToSign.sequence)
//update the input at index i with inputWithConnectScript
val updatedInputs = for {
@ -232,7 +233,7 @@ sealed abstract class TransactionSignatureSerializer {
(input,index) <- inputs.zipWithIndex
} yield {
if (UInt32(index) == inputIndex) input
else TransactionInput(input,UInt32.zero)
else TransactionInput(input.previousOutput, input.scriptSignature,UInt32.zero)
}
/** Executes the [[SIGHASH_NONE]] procedure on a spending transaction for the input specified by inputIndex. */

View File

@ -9,7 +9,7 @@ import org.bitcoins.core.script.flag._
* Mimics the policy files found in bitcoin core
* https://github.com/bitcoin/bitcoin/blob/master/src/policy/policy.h
*/
trait Policy {
sealed abstract class Policy {
/**
* Mandatory script verification flags that all new blocks must comply with for
@ -49,6 +49,8 @@ trait Policy {
/** Max fee for a transaction is set to 10 mBTC right now */
def maxFee: CurrencyUnit = Satoshis(Int64(10)) * CurrencyUnits.oneMBTC
def isRBFEnabled: Boolean = true
}
object Policy extends Policy

View File

@ -77,7 +77,7 @@ sealed abstract class ChainParams {
val const = ScriptConstant(timestampBytes)
val scriptSignature = ScriptSignature.fromAsm(Seq(BytesToPushOntoStack(4), ScriptNumber(486604799),
BytesToPushOntoStack(1), ScriptNumber(4)) ++ BitcoinScriptUtil.calculatePushOp(const) ++ Seq(const))
val input = TransactionInput(scriptSignature)
val input = CoinbaseInput(scriptSignature)
val output = TransactionOutput(amount,scriptPubKey)
val tx = BaseTransaction(TransactionConstants.version,Seq(input), Seq(output), TransactionConstants.lockTime)
val prevBlockHash = DoubleSha256Digest("0000000000000000000000000000000000000000000000000000000000000000")

View File

@ -122,34 +122,6 @@ object Transaction extends Factory[Transaction] {
btx
}
}
@deprecated("", "2018/02/16")
def apply(oldTx : Transaction, lockTime : UInt32): Transaction = oldTx match {
case btx: BaseTransaction =>
BaseTransaction(btx.version,btx.inputs,btx.outputs,lockTime)
case wtx: WitnessTransaction =>
WitnessTransaction(wtx.version,wtx.inputs,wtx.outputs,lockTime,wtx.witness)
}
@deprecated("", "2018/02/16")
def apply(oldTx : Transaction, updatedInputs : UpdateTransactionInputs): Transaction = oldTx match {
case btx: BaseTransaction =>
BaseTransaction(btx.version,updatedInputs.inputs,btx.outputs,btx.lockTime)
case wtx: WitnessTransaction =>
WitnessTransaction(wtx.version,updatedInputs.inputs,wtx.outputs,wtx.lockTime,wtx.witness)
}
@deprecated("", "2018/02/16")
def apply(oldTx : Transaction, updatedOutputs : UpdateTransactionOutputs) : Transaction = oldTx match {
case btx: BaseTransaction =>
BaseTransaction(btx.version,btx.inputs,updatedOutputs.outputs,btx.lockTime)
case wtx: WitnessTransaction =>
WitnessTransaction(wtx.version,wtx.inputs,updatedOutputs.outputs,wtx.lockTime,wtx.witness)
}
@deprecated("Dangerous was you can lose TransactionWitness, use BaseTransaction", "2018/02/16")
def apply(version : UInt32, inputs : Seq[TransactionInput],
outputs : Seq[TransactionOutput], lockTime : UInt32) : Transaction = {
BaseTransaction(version,inputs,outputs,lockTime)
}
}
object BaseTransaction extends Factory[BaseTransaction] {

View File

@ -2,8 +2,7 @@ package org.bitcoins.core.protocol.transaction
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.protocol.script.{ScriptPubKey, ScriptSignature}
import org.bitcoins.core.script.constant.ScriptToken
import org.bitcoins.core.protocol.script.{EmptyScriptSignature, ScriptSignature}
import org.bitcoins.core.serializers.transaction.RawTransactionInputParser
import org.bitcoins.core.util.Factory
@ -11,31 +10,26 @@ import org.bitcoins.core.util.Factory
* Created by chris on 12/26/15.
* Algebraic data type that represents a transaction input
*/
sealed trait TransactionInput extends NetworkElement {
sealed abstract class TransactionInput extends NetworkElement {
def previousOutput : TransactionOutPoint
def scriptSignature : ScriptSignature
def sequence : UInt32
//https://bitcoin.org/en/developer-reference#txin
override def size = previousOutput.size + scriptSignature.size + 4
override def bytes = RawTransactionInputParser.write(this)
}
case object EmptyTransactionInput extends TransactionInput {
override def previousOutput = TransactionInput.empty.previousOutput
override def scriptSignature = TransactionInput.empty.scriptSignature
override def sequence = TransactionInput.empty.sequence
override def previousOutput = EmptyTransactionOutPoint
override def scriptSignature = EmptyScriptSignature
override def sequence = TransactionConstants.sequence
}
/**
* This represents a coinbase input - these always have a EmptyTransactionOutPoint
* and arbitrary data inside the script signature
*/
sealed trait CoinbaseInput extends TransactionInput {
sealed abstract class CoinbaseInput extends TransactionInput {
override def previousOutput = EmptyTransactionOutPoint
override def sequence = TransactionConstants.sequence
}
@ -43,66 +37,25 @@ sealed trait CoinbaseInput extends TransactionInput {
object TransactionInput extends Factory[TransactionInput] {
private def factory(oldInput : TransactionInput, scriptSig : ScriptSignature) : TransactionInput = {
apply(oldInput.previousOutput,scriptSig,oldInput.sequence)
}
private sealed case class TransactionInputImpl(previousOutput : TransactionOutPoint,
private case class TransactionInputImpl(previousOutput : TransactionOutPoint,
scriptSignature : ScriptSignature, sequence : UInt32) extends TransactionInput
private sealed case class CoinbaseInputImpl(
scriptSignature : ScriptSignature) extends CoinbaseInput
private def factory(oldInput : TransactionInput, scriptPubKey: ScriptPubKey) : TransactionInput = {
val scriptSig = ScriptSignature(scriptPubKey.hex)
factory(oldInput,scriptSig)
}
private def factory(oldInput : TransactionInput,sequenceNumber : UInt32) : TransactionInput = {
TransactionInputImpl(oldInput.previousOutput, oldInput.scriptSignature,sequenceNumber)
}
/** Creates a transaction input from a given output and the output's transaction */
private def factory(oldInput : TransactionInput,output : TransactionOutput, outputsTransaction : Transaction) : TransactionInput = {
val outPoint = TransactionOutPoint(output,outputsTransaction)
factory(oldInput,outPoint)
}
private def factory(oldInput : TransactionInput, outPoint: TransactionOutPoint) : TransactionInput = {
TransactionInputImpl(outPoint,oldInput.scriptSignature,oldInput.sequence)
}
private def factory(outPoint : TransactionOutPoint, scriptSignature : ScriptSignature, sequenceNumber : UInt32) : TransactionInput = {
outPoint match {
case EmptyTransactionOutPoint => CoinbaseInputImpl(scriptSignature)
case _ : TransactionOutPoint => TransactionInputImpl(outPoint, scriptSignature, sequenceNumber)
}
}
def empty : TransactionInput = {
TransactionInputImpl(EmptyTransactionOutPoint,
ScriptSignature.empty,TransactionConstants.sequence)
}
def empty: TransactionInput = EmptyTransactionInput
def fromBytes(bytes : Seq[Byte]) : TransactionInput = RawTransactionInputParser.read(bytes)
def apply(oldInput : TransactionInput, scriptSig : ScriptSignature) : TransactionInput = factory(oldInput,scriptSig)
def apply(oldInput : TransactionInput, script : Seq[ScriptToken]) : TransactionInput = {
val scriptSig = ScriptSignature.fromAsm(script)
apply(oldInput,scriptSig)
def apply(outPoint : TransactionOutPoint, scriptSignature : ScriptSignature,
sequenceNumber : UInt32) : TransactionInput = outPoint match {
case EmptyTransactionOutPoint => CoinbaseInput(scriptSignature)
case _: TransactionOutPoint => TransactionInputImpl(outPoint,scriptSignature,sequenceNumber)
}
def apply(oldInput : TransactionInput, scriptPubKey: ScriptPubKey) : TransactionInput = factory(oldInput, scriptPubKey)
def apply(oldInput : TransactionInput,sequenceNumber : UInt32) : TransactionInput = factory(oldInput, sequenceNumber)
def apply(oldInput : TransactionInput,output : TransactionOutput, outputsTransaction : Transaction) : TransactionInput = factory(oldInput,output,outputsTransaction)
}
def apply(oldInput : TransactionInput, outPoint: TransactionOutPoint) : TransactionInput = factory(oldInput,outPoint)
def apply(outPoint : TransactionOutPoint, scriptSignature : ScriptSignature, sequenceNumber : UInt32) : TransactionInput = factory(outPoint,scriptSignature,sequenceNumber)
object CoinbaseInput {
private case class CoinbaseInputImpl(scriptSignature : ScriptSignature) extends CoinbaseInput
/**
* Creates a coinbase input - coinbase inputs always have an empty outpoint
* @param scriptSignature this can contain anything, miners use this to signify support for various protocol BIPs

View File

@ -9,16 +9,13 @@ import org.bitcoins.core.util.{BitcoinSUtil, Factory}
* Created by chris on 12/26/15.
*
*/
sealed trait TransactionOutPoint extends NetworkElement {
sealed abstract class TransactionOutPoint extends NetworkElement {
/** The transaction id for the crediting transaction for this input */
def txId : DoubleSha256Digest
/** The output index in the parent transaction for the output we are spending */
def vout : UInt32
//https://bitcoin.org/en/developer-reference#outpoint
override def size = 36
override def bytes = RawTransactionOutPointParser.write(this)
}
@ -36,29 +33,14 @@ case object EmptyTransactionOutPoint extends TransactionOutPoint {
object TransactionOutPoint extends Factory[TransactionOutPoint] {
private sealed case class TransactionOutPointImpl(txId : DoubleSha256Digest, vout : UInt32) extends TransactionOutPoint
/**
* Creates a transaction outpoint from a TransactionOutput & it's parent transaction
*
* @param output
* @return
*/
private def factory(output : TransactionOutput, parentTransaction : Transaction) : TransactionOutPoint = {
val indexOfOutput = UInt32(parentTransaction.outputs.indexOf(output))
if (indexOfOutput.toInt == (-1)) throw new RuntimeException("This output is not contained in the parent transaction")
else factory(parentTransaction.txId,indexOfOutput)
}
private def factory(txId : DoubleSha256Digest, index : UInt32) = {
if (txId == EmptyTransactionOutPoint.txId && index == EmptyTransactionOutPoint.vout) {
EmptyTransactionOutPoint
} else TransactionOutPointImpl(txId, index)
}
private case class TransactionOutPointImpl(txId : DoubleSha256Digest, vout : UInt32) extends TransactionOutPoint
def fromBytes(bytes : Seq[Byte]) : TransactionOutPoint = RawTransactionOutPointParser.read(bytes)
def apply(output : TransactionOutput,parentTransaction : Transaction) : TransactionOutPoint = factory(output,parentTransaction)
def apply(txId : DoubleSha256Digest, index: UInt32) : TransactionOutPoint = factory(txId,index)
def apply(txId : DoubleSha256Digest, index: UInt32) : TransactionOutPoint = {
if (txId == EmptyTransactionOutPoint.txId && index == EmptyTransactionOutPoint.vout) {
EmptyTransactionOutPoint
} else TransactionOutPointImpl(txId,index)
}
}

View File

@ -25,23 +25,12 @@ case object EmptyTransactionOutput extends TransactionOutput {
override def scriptPubKey = ScriptPubKey.empty
}
case class TransactionOutputImpl(value : CurrencyUnit, scriptPubKey: ScriptPubKey) extends TransactionOutput
object TransactionOutput extends Factory[TransactionOutput] {
private case class TransactionOutputImpl(value : CurrencyUnit, scriptPubKey: ScriptPubKey) extends TransactionOutput
def fromBytes(bytes : Seq[Byte]) : TransactionOutput = RawTransactionOutputParser.read(bytes)
def apply(oldOutput : TransactionOutput, newCurrencyUnit: CurrencyUnit) : TransactionOutput = {
TransactionOutput(newCurrencyUnit,oldOutput.scriptPubKey)
}
def apply(oldOutput : TransactionOutput, newScriptPubKey : ScriptPubKey) : TransactionOutput = {
TransactionOutput(oldOutput.value,newScriptPubKey)
}
def apply(currencyUnit: CurrencyUnit, scriptPubKey: ScriptPubKey) : TransactionOutput = {
TransactionOutputImpl(currencyUnit,scriptPubKey)
}
}

View File

@ -25,8 +25,13 @@ case object EmptyWitness extends TransactionWitness {
object TransactionWitness {
private case class TransactionWitnessImpl(witnesses: Seq[ScriptWitness]) extends TransactionWitness
def apply(witnesses: Seq[ScriptWitness]): TransactionWitness = TransactionWitnessImpl(witnesses)
def apply(witnesses: Seq[ScriptWitness]): TransactionWitness = {
if (witnesses.exists(_ != EmptyScriptWitness)) {
TransactionWitnessImpl(witnesses)
} else {
EmptyWitness
}
}
/** Creates a [[TransactionWitness]] from a Seq[Option[ScriptWitness]].
* This constructor is for convinience if a certain input does not spend a [[org.bitcoins.core.protocol.script.WitnessScriptPubKey]]
* It simply transforms the `None` types to [[EmptyScriptWitness]] and then calls the normal TransactionWitness constructor

View File

@ -20,7 +20,4 @@ abstract class RawBitcoinSerializer[T] {
/** Takes a type T and writes it into the appropriate hexadecimal serialization for type T. */
def write(t : T) : Seq[Byte]
def logger: Logger = BitcoinSLogger.logger
}

View File

@ -9,22 +9,6 @@ import org.bitcoins.core.util.BitcoinSLogger
*/
sealed abstract class RawSerializerHelper {
private val logger = BitcoinSLogger.logger
/** Adds the amount padding bytes needed to fix the size of the hex string
* for instance, vouts are required to be 4 bytes. If the number is just 1
* it will only take 1 byte. We need to pad the byte with an extra 3 bytes so the result is
* 01000000 instead of just 01. */
def addPadding(charactersNeeded : Int, hex : String) : String = {
val paddingNeeded = charactersNeeded - hex.size
val padding = for { i <- 0 until paddingNeeded} yield "0"
val paddedHex = hex + padding.mkString
paddedHex
}
/** Adds a preceding zero to a hex string.
* Example: if '1' was passed in, it would return the hex string '01'.*/
def addPrecedingZero(hex : String) = {
if (hex.size == 1) "0" + hex else hex
}
/** Used parse a byte sequence to a Seq[TransactionInput], Seq[TransactionOutput], etc
* Makes sure that we parse the correct amount of elements

View File

@ -0,0 +1,121 @@
package org.bitcoins.core.wallet.builder
/** Represents an error that can be returned by the [[org.bitcoins.core.wallet.builder.TxBuilder]]
* if it failed to sign a set of utxos
*/
sealed abstract class TxBuilderError
object TxBuilderError {
/** This error indicates that the transaction failed to pass the invariants the user wanted to hold
* true after the signing process was complete. An example of this is the transaction is too big,
* or the fee level was too high or low.
*/
case object FailedUserInvariants extends TxBuilderError
/** Means that we gave too many [[org.bitcoins.core.wallet.signer.Signer.Sign]] for the TxBuilder
* to use during the signing process for a utxo.
* An example of this occurring is if we gave 2 private keys to sign a p2pkh spk.
* A p2pkh only requires one private key to sign the utxo.
*/
case object TooManySigners extends TxBuilderError
/** Means that you are using the wrong [[org.bitcoins.core.wallet.signer.Signer]] to
* sign the given [[org.bitcoins.core.protocol.script.ScriptPubKey]]
*/
case object WrongSigner extends TxBuilderError
/** Means that the [[org.bitcoins.core.protocol.script.ScriptWitnessV0]] you passed as an argument does
* not hash to the commitment inside of [[org.bitcoins.core.protocol.script.P2WSHWitnessSPKV0]]
*/
case object WrongWitness extends TxBuilderError
/** Means that the redeem script you passed as an argument does not hash to the commitment
* inside of the [[org.bitcoins.core.protocol.script.P2SHScriptPubKey]]
*/
case object WrongRedeemScript extends TxBuilderError
/** Means that you passed the wrong public key for a [[org.bitcoins.core.protocol.script.P2PKHScriptPubKey]] or a
* [[org.bitcoins.core.protocol.script.P2WPKHWitnessSPKV0]] that you are trying to spend
*/
case object WrongPublicKey extends TxBuilderError
/** Can occurr when we are trying to sign a [[org.bitcoins.core.protocol.script.P2SHScriptPubKey]] but
* we do not have a redeem script for that p2sh spk.
*/
case object NoRedeemScript extends TxBuilderError
/** Can occurr when we are trying to sign a [[org.bitcoins.core.protocol.script.WitnessScriptPubKey]]
* but we do not have a [[org.bitcoins.core.protocol.script.ScriptWitness]] for that witness spk
*/
case object NoWitness extends TxBuilderError
/** We expected a [[org.bitcoins.core.protocol.script.WitnessScriptPubKeyV0]], but got a non witness spk type */
case object NonWitnessSPK extends TxBuilderError
/** We cannot have a [[org.bitcoins.core.protocol.script.WitnessScriptPubKey]] nested inside of another [[org.bitcoins.core.protocol.script.ScriptPubKey]] */
case object NestedWitnessSPK extends TxBuilderError
/** We cannot have a [[org.bitcoins.core.protocol.script.P2SHScriptPubKey]] nested inside of another spk */
case object NestedP2SHSPK extends TxBuilderError
/** Means that there is no signer defined for the given [[org.bitcoins.core.protocol.script.ScriptPubKey]] type.
* An example of a spk with no signer that is defined is [[org.bitcoins.core.protocol.script.WitnessCommitment]]
*/
case object NoSigner extends TxBuilderError
/** Means that you specified a fee that was too large for the change output you provided.
* This may happen if you have a transaction with a lot of inputs, or the change output you provided
* is a output that contains a very small amount of bitcoin.
* */
case object FeeToLarge extends TxBuilderError
/** Means that the [[TxBuilder.destinations]] outputs you specified when creating the [[TxBuilder]] are NOT
* all included in the final signed tx
*/
case object MissingDestinationOutput extends TxBuilderError
/** Means that you provided a outpoint in the [[TxBuilder.utxoMap]] that does not
* exist inside of [[TxBuilder.creditingTxs]]. You cannot spend an outpoint that was not
* passed into the txbuilder originally
*/
case object MissingCreditingTx extends TxBuilderError
/** Means that the script we are signing for requires a public key, but we did not pass one in
* as a parameter inside of [[org.bitcoins.core.wallet.signer.Signer.Sign]]
*/
case object MissingPublicKey extends TxBuilderError
case object MissingOutPoint extends TxBuilderError
/** Means that the signed version of this transaction has MORE outputs than what was specified
* when building the [[TxBuilder]]. [[TxBuilder.destinations]] && [[TxBuilder.changeOutput]] should
* be the only outputs in the signedTx
*/
case object ExtraOutputsAdded extends TxBuilderError
/** Means that the transaction spends outpoints that were not given when creating
* the [[TxBuilder]], aka, we should only spend outpoints in [[TxBuilder.outPoints]]
*/
case object ExtraOutPoints extends TxBuilderError
/** Means that this transaction attempts to print satoshis out of thin air */
case object MintsMoney extends TxBuilderError
/** Means that the fee was too low for [[TxBuilder.feeRate]] */
case object LowFee extends TxBuilderError
/** Means tha this transaction pays too high of a fee for [[TxBuilder.feeRate]] */
case object HighFee extends TxBuilderError
/** Indicates we are spending multiple [[org.bitcoins.core.protocol.script.CLTVScriptPubKey]],
* and that one of those spk's outputs are locked by block height, while the other is locked by
* a time stamp. Since there is only one locktime field on a transaction, we cannot satisfy both of these
* locktimes simultaneously.
*/
case object IncompatibleLockTimes extends TxBuilderError
/** Means we have a output on this transaction below [[org.bitcoins.core.policy.Policy.dustThreshold]] */
case object OutputBelowDustThreshold extends TxBuilderError
case object UnknownError extends TxBuilderError
}

View File

@ -0,0 +1,29 @@
package org.bitcoins.core.wallet.fee
import org.bitcoins.core.currency.{CurrencyUnit, Satoshis}
import org.bitcoins.core.number.Int64
import org.bitcoins.core.protocol.transaction.Transaction
/** This is meant to be an abstract type that represents different fee unit measurements for
* blockchains
*/
sealed abstract class FeeUnit {
def currencyUnit: CurrencyUnit
def *(tx: Transaction): CurrencyUnit = calc(tx)
def calc(tx: Transaction): CurrencyUnit = Satoshis(Int64(tx.size * toLong))
def toLong: Long = currencyUnit.satoshis.toLong
}
/** Meant to represent the different fee unit types for the bitcoin protocol
* [[https://en.bitcoin.it/wiki/Weight_units]]
*/
sealed abstract class BitcoinFeeUnit
case class SatoshisPerByte(currencyUnit: CurrencyUnit) extends FeeUnit
/** A 'virtual byte' (also known as virtual size) is a new weight measurement that
* was created with segregated witness (BIP141). Now 1 'virtual byte'
* has the weight of 4 bytes in the [[org.bitcoins.core.protocol.transaction.TransactionWitness]]
* of a [[org.bitcoins.core.protocol.transaction.WitnessTransaction]]
*/
case class SatoshisPerVirtualByte(currencyUnit: CurrencyUnit) extends FeeUnit

View File

@ -0,0 +1,410 @@
package org.bitcoins.core.wallet.signer
import 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._
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.util.BitcoinSLogger
import org.bitcoins.core.wallet.builder.TxBuilderError
/** The class used to represent a signing process for a specific [[org.bitcoins.core.protocol.script.ScriptPubKey]] type */
sealed abstract class Signer {
/**
* The method used to sign a bitcoin unspent transaction output
* @param signers the [[Signer.Sign]] needed to sign the utxo
* @param output the utxo we are spending
* @param unsignedTx the unsigned transaction which is spending the utxo
* @param inputIndex the input index inside of the unsigned transaction which spends the utxo
* @param hashType the signature hashing algorithm we should use to sign the utxo
* @return
*/
def sign(signers: Seq[Signer.Sign], output: TransactionOutput, unsignedTx: Transaction,
inputIndex: UInt32, hashType: HashType): Either[TxSigComponent, TxBuilderError]
}
object Signer {
/** This is meant to be an abstraction for a [[org.bitcoins.core.crypto.ECPrivateKey]], sometimes we will not
* have direct access to a private key in memory -- for instance if that key is on a hardware device -- so we need to create an
* abstraction of the signing process. Fundamentally a private key takes in a Seq[Byte] and returns a [[ECDigitalSignature]]
* That is what this abstraction is meant to represent. If you have a [[ECPrivateKey]] in your application, you can get it's
* [[Sign]] type by doing this:
*
* val key = ECPrivateKey()
* val sign: Seq[Byte] => ECDigitalSignature = (key.sign(_: Seq[Byte]), key.publicKey)
*
* If you have a hardware wallet, you will need to implement the protocol to send a message to the hardware device. The
* type signature of the function you implement must be Seq[Byte] => ECDigitalSignature
*
* TODO: Investigate turning this into Seq[Byte] => Future[ECDigitalSignature]
*/
type Sign = (Seq[Byte] => ECDigitalSignature, Option[ECPublicKey])
}
/** Represents all signers for the bitcoin protocol, we could add another network later like litecoin */
sealed abstract class BitcoinSigner extends Signer
/** Used to sign a [[org.bitcoins.core.protocol.script.P2PKScriptPubKey]] */
sealed abstract class P2PKSigner extends BitcoinSigner {
override def sign(signers: Seq[Signer.Sign], output: TransactionOutput, unsignedTx: Transaction,
inputIndex: UInt32, hashType: HashType): Either[TxSigComponent, TxBuilderError] = {
val spk = output.scriptPubKey
if (signers.size != 1) {
Right(TxBuilderError.TooManySigners)
} else {
val signer = signers.head._1
val unsignedInput = unsignedTx.inputs(inputIndex.toInt)
val flags = Policy.standardFlags
val amount = output.value
val signed: Either[TxSigComponent,TxBuilderError] = spk match {
case p2wshSPK: P2WSHWitnessSPKV0 =>
val wtx = unsignedTx match {
case btx: BaseTransaction => WitnessTransaction(btx.version, btx.inputs,
btx.outputs, btx.lockTime, EmptyWitness)
case wtx: WitnessTransaction => wtx
}
val redeemScript = wtx.witness.witnesses(inputIndex.toInt) match {
case x: P2WSHWitnessV0 => Left(x.redeemScript)
case _: P2WPKHWitnessV0 => Right(TxBuilderError.NoRedeemScript)
case EmptyScriptWitness => Right(TxBuilderError.NoWitness)
}
val sigComponent = WitnessTxSigComponentRaw(wtx,inputIndex,p2wshSPK,flags,amount)
val signature = TransactionSignatureCreator.createSig(sigComponent,signer,hashType)
val p2pkScriptSig = P2PKScriptSignature(signature)
val scriptWit = redeemScript.left.map(s => P2WSHWitnessV0(s,p2pkScriptSig))
val signedWitness = scriptWit.left.map(s => TransactionWitness(wtx.witness.witnesses.updated(inputIndex.toInt,s)))
val signedWTx = signedWitness.left.map { wit =>
WitnessTransaction(wtx.version,wtx.inputs,wtx.outputs,wtx.lockTime,wit)
}
signedWTx.left.map(wtx => WitnessTxSigComponentRaw(wtx,inputIndex,p2wshSPK,flags,amount))
case _: P2PKScriptPubKey =>
val sigComponent = BaseTxSigComponent(unsignedTx,inputIndex,spk,flags)
val signature = TransactionSignatureCreator.createSig(sigComponent,signer,hashType)
val p2pkScriptSig = P2PKScriptSignature(signature)
val signedInput = TransactionInput(unsignedInput.previousOutput,p2pkScriptSig,unsignedInput.sequence)
val signedInputs = unsignedTx.inputs.updated(inputIndex.toInt,signedInput)
val signedTx = unsignedTx match {
case btx: BaseTransaction => BaseTransaction(btx.version, signedInputs,
btx.outputs,btx.lockTime)
case wtx: WitnessTransaction => WitnessTransaction(wtx.version, signedInputs,
wtx.outputs,wtx.lockTime, wtx.witness)
}
Left(BaseTxSigComponent(signedTx,inputIndex,spk, flags))
case lock: LockTimeScriptPubKey =>
lock.nestedScriptPubKey match {
case _: P2PKScriptPubKey =>
val sigComponent = BaseTxSigComponent(unsignedTx,inputIndex,lock,flags)
val signature = TransactionSignatureCreator.createSig(sigComponent,signer,hashType)
val p2pkScriptSig = P2PKScriptSignature(signature)
val signedInput = TransactionInput(unsignedInput.previousOutput,p2pkScriptSig,unsignedInput.sequence)
val signedInputs = unsignedTx.inputs.updated(inputIndex.toInt,signedInput)
val signedTx = unsignedTx match {
case btx: BaseTransaction => BaseTransaction(btx.version, signedInputs,
btx.outputs,btx.lockTime)
case wtx: WitnessTransaction => WitnessTransaction(wtx.version, signedInputs,
wtx.outputs,wtx.lockTime, wtx.witness)
}
Left(BaseTxSigComponent(signedTx, inputIndex, lock, flags))
case _: P2PKHScriptPubKey | _: MultiSignatureScriptPubKey | _: P2SHScriptPubKey
| _: P2WPKHWitnessSPKV0 | _: P2WSHWitnessSPKV0 | _: NonStandardScriptPubKey
| _: CLTVScriptPubKey | _:CSVScriptPubKey
| _: WitnessCommitment | EmptyScriptPubKey | _: UnassignedWitnessScriptPubKey
| _: EscrowTimeoutScriptPubKey => Right(TxBuilderError.WrongSigner)
}
case _: P2PKHScriptPubKey | _: MultiSignatureScriptPubKey | _: P2SHScriptPubKey
| _: P2WPKHWitnessSPKV0 | _: NonStandardScriptPubKey
| _: WitnessCommitment | EmptyScriptPubKey | _: UnassignedWitnessScriptPubKey
| _: EscrowTimeoutScriptPubKey => Right(TxBuilderError.WrongSigner)
}
signed
}
}
}
object P2PKSigner extends P2PKSigner
/** Used to sign a [[org.bitcoins.core.protocol.script.P2PKHScriptPubKey]] */
sealed abstract class P2PKHSigner extends BitcoinSigner {
override def sign(signers: Seq[Signer.Sign], output: TransactionOutput, unsignedTx: Transaction,
inputIndex: UInt32, hashType: HashType): Either[TxSigComponent, TxBuilderError] = {
val spk = output.scriptPubKey
if (signers.size != 1) {
Right(TxBuilderError.TooManySigners)
} else if (signers.head._2.isEmpty) {
Right(TxBuilderError.MissingPublicKey)
} else {
val signer = signers.head._1
val pubKey = signers.head._2.get
val unsignedInput = unsignedTx.inputs(inputIndex.toInt)
val flags = Policy.standardFlags
val amount = output.value
val signed: Either[TxSigComponent, TxBuilderError] = spk match {
case p2wshSPK: P2WSHWitnessSPKV0 =>
val wtx = unsignedTx match {
case btx: BaseTransaction => WitnessTransaction(btx.version, btx.inputs,
btx.outputs, btx.lockTime, EmptyWitness)
case wtx: WitnessTransaction => wtx
}
val redeemScript = wtx.witness.witnesses(inputIndex.toInt) match {
case EmptyScriptWitness | _: P2WPKHWitnessV0 => Right(TxBuilderError.WrongWitness)
case p2wsh: P2WSHWitnessV0 => Left(p2wsh.redeemScript)
}
val sigComponent = WitnessTxSigComponentRaw(wtx, inputIndex, p2wshSPK, flags, amount)
val signature = TransactionSignatureCreator.createSig(sigComponent, signer, hashType)
val p2pkhScriptSig = P2PKHScriptSignature(signature,pubKey)
val scriptWit = redeemScript.left.flatMap {
case p2pkh: P2PKHScriptPubKey =>
if (p2pkh != P2PKHScriptPubKey(pubKey)) {
Right(TxBuilderError.WrongPublicKey)
} else Left(P2WSHWitnessV0(p2pkh, p2pkhScriptSig))
case lock: LockTimeScriptPubKey =>
lock.nestedScriptPubKey match {
case p2pkh: P2PKHScriptPubKey =>
if (p2pkh != P2PKHScriptPubKey(pubKey)) {
Right(TxBuilderError.WrongPublicKey)
} else {
Left(P2WSHWitnessV0(lock,p2pkhScriptSig))
}
case _: P2PKScriptPubKey | _: MultiSignatureScriptPubKey | _: P2SHScriptPubKey
| _: P2WPKHWitnessSPKV0 | _: P2WSHWitnessSPKV0 | _: NonStandardScriptPubKey
| _: CLTVScriptPubKey | _: CSVScriptPubKey
| _: WitnessCommitment | EmptyScriptPubKey | _: UnassignedWitnessScriptPubKey
| _: EscrowTimeoutScriptPubKey => Right(TxBuilderError.WrongSigner)
}
case _: P2PKScriptPubKey | _: MultiSignatureScriptPubKey | _: P2SHScriptPubKey
| _: P2WPKHWitnessSPKV0 | _: P2WSHWitnessSPKV0 | _: NonStandardScriptPubKey
| _: WitnessCommitment | EmptyScriptPubKey | _: UnassignedWitnessScriptPubKey
| _: EscrowTimeoutScriptPubKey => Right(TxBuilderError.WrongSigner)
}
val signedWitness = scriptWit.left.map(wit => TransactionWitness(wtx.witness.witnesses.updated(inputIndex.toInt, wit)))
val signedWTx = signedWitness.left.map(txWit => WitnessTransaction(wtx.version, wtx.inputs,
wtx.outputs, wtx.lockTime, txWit))
signedWTx.left.map(wtx => WitnessTxSigComponentRaw(wtx, inputIndex, p2wshSPK, flags, amount))
case p2pkh: P2PKHScriptPubKey =>
if (p2pkh != P2PKHScriptPubKey(pubKey)) {
Right(TxBuilderError.WrongPublicKey)
} else {
val sigComponent = BaseTxSigComponent(unsignedTx, inputIndex, p2pkh, flags)
val signature = TransactionSignatureCreator.createSig(sigComponent, signer, hashType)
val p2pkhScriptSig = P2PKHScriptSignature(signature, pubKey)
val signedInput = TransactionInput(unsignedInput.previousOutput, p2pkhScriptSig, unsignedInput.sequence)
val signedInputs = unsignedTx.inputs.updated(inputIndex.toInt, signedInput)
val signedTx = unsignedTx match {
case btx: BaseTransaction => BaseTransaction(btx.version, signedInputs,
btx.outputs, btx.lockTime)
case wtx: WitnessTransaction => WitnessTransaction(wtx.version, signedInputs,
wtx.outputs, wtx.lockTime, wtx.witness)
}
Left(BaseTxSigComponent(signedTx, inputIndex, p2pkh, flags))
}
case lock : LockTimeScriptPubKey =>
lock.nestedScriptPubKey match {
case p2pkh: P2PKHScriptPubKey =>
if (p2pkh != P2PKHScriptPubKey(pubKey)) {
Right(TxBuilderError.WrongPublicKey)
} else {
val sigComponent = BaseTxSigComponent(unsignedTx, inputIndex, lock, flags)
val signature = TransactionSignatureCreator.createSig(sigComponent, signer, hashType)
val p2pkhScriptSig = P2PKHScriptSignature(signature, pubKey)
val signedInput = TransactionInput(unsignedInput.previousOutput, p2pkhScriptSig, unsignedInput.sequence)
val signedInputs = unsignedTx.inputs.updated(inputIndex.toInt, signedInput)
val signedTx = unsignedTx match {
case btx: BaseTransaction => BaseTransaction(btx.version, signedInputs,
btx.outputs, btx.lockTime)
case wtx: WitnessTransaction => WitnessTransaction(wtx.version, signedInputs,
wtx.outputs, wtx.lockTime, wtx.witness)
}
Left(BaseTxSigComponent(signedTx, inputIndex, lock, flags))
}
case _: P2PKScriptPubKey | _: MultiSignatureScriptPubKey | _: P2SHScriptPubKey
| _: P2WPKHWitnessSPKV0 | _: P2WSHWitnessSPKV0 | _: NonStandardScriptPubKey
| _: CLTVScriptPubKey | _: CSVScriptPubKey
| _: WitnessCommitment | EmptyScriptPubKey | _: UnassignedWitnessScriptPubKey
| _: EscrowTimeoutScriptPubKey => Right(TxBuilderError.WrongSigner)
}
case _: P2PKScriptPubKey | _: MultiSignatureScriptPubKey | _: P2SHScriptPubKey
| _: P2WPKHWitnessSPKV0 | _: NonStandardScriptPubKey
| _: WitnessCommitment | EmptyScriptPubKey | _: UnassignedWitnessScriptPubKey
| _: EscrowTimeoutScriptPubKey => Right(TxBuilderError.WrongSigner)
}
signed
}
}
}
object P2PKHSigner extends P2PKHSigner
sealed abstract class MultiSigSigner extends BitcoinSigner {
private val logger = BitcoinSLogger.logger
override def sign(signersWithPubKeys: Seq[Signer.Sign], output: TransactionOutput, unsignedTx: Transaction,
inputIndex: UInt32, hashType: HashType): Either[TxSigComponent, TxBuilderError] = {
val spk = output.scriptPubKey
val signers = signersWithPubKeys.map(_._1)
val unsignedInput = unsignedTx.inputs(inputIndex.toInt)
val flags = Policy.standardFlags
val amount = output.value
val signed: Either[TxSigComponent, TxBuilderError] = spk match {
case p2wshSPK: P2WSHWitnessSPKV0 =>
val wtx = unsignedTx match {
case btx: BaseTransaction => WitnessTransaction(btx.version, btx.inputs,
btx.outputs, btx.lockTime, EmptyWitness)
case wtx: WitnessTransaction => wtx
}
val witness = wtx.witness.witnesses(inputIndex.toInt)
val multiSigSPK: Either[(MultiSignatureScriptPubKey,ScriptPubKey), TxBuilderError] = witness match {
case _: P2WPKHWitnessV0 => Right(TxBuilderError.WrongSigner)
case EmptyScriptWitness => Right(TxBuilderError.NoWitness)
case p2wsh: P2WSHWitnessV0 =>
p2wsh.redeemScript match {
case lock: LockTimeScriptPubKey =>
lock.nestedScriptPubKey match {
case m: MultiSignatureScriptPubKey => Left((m,lock))
case _: P2PKScriptPubKey | _: P2PKHScriptPubKey | _: P2SHScriptPubKey | _: P2WPKHWitnessV0
| _: P2WSHWitnessSPKV0 | _: WitnessCommitment | _: EscrowTimeoutScriptPubKey | _: CSVScriptPubKey
| _: CLTVScriptPubKey | _: NonStandardScriptPubKey | _: UnassignedWitnessScriptPubKey
| _: P2WPKHWitnessSPKV0 | EmptyScriptPubKey =>
Right(TxBuilderError.WrongSigner)
}
case m: MultiSignatureScriptPubKey => Left((m,m))
case _: P2PKScriptPubKey | _: P2PKHScriptPubKey | _: P2SHScriptPubKey | _: P2WPKHWitnessV0
| _: P2WSHWitnessSPKV0 | _: WitnessCommitment | _: EscrowTimeoutScriptPubKey
| _: NonStandardScriptPubKey | _: P2WPKHWitnessSPKV0 | _: UnassignedWitnessScriptPubKey
| EmptyScriptPubKey =>
Right(TxBuilderError.WrongSigner)
}
}
val requiredSigs = multiSigSPK.left.map(_._1.requiredSigs)
val sigComponent = WitnessTxSigComponentRaw(wtx, inputIndex, p2wshSPK, flags, amount)
val signatures = requiredSigs.left.map { r =>
0.until(r).map(i => TransactionSignatureCreator.createSig(sigComponent, signers(i), hashType))
}
val multiSigScriptSig = signatures.left.map(s => MultiSignatureScriptSignature(s))
val scriptWit = multiSigSPK.left.flatMap { case (_,redeem) =>
multiSigScriptSig.left.map { scriptSig =>
P2WSHWitnessV0(redeem, scriptSig)
}
}
val signedWitness = scriptWit.left.map(wit => TransactionWitness(wtx.witness.witnesses.updated(inputIndex.toInt, wit)))
val signedWTx: Either[WitnessTransaction,TxBuilderError] = signedWitness.left.map { txWit =>
WitnessTransaction(wtx.version, wtx.inputs, wtx.outputs, wtx.lockTime, txWit)
}
signedWTx.left.map { wtx =>
WitnessTxSigComponentRaw(wtx,inputIndex,p2wshSPK,flags,amount)
}
case multiSigSPK: MultiSignatureScriptPubKey =>
val requiredSigs = multiSigSPK.requiredSigs
if (signers.size < requiredSigs) {
Right(TxBuilderError.WrongSigner)
} else {
val sigComponent = BaseTxSigComponent(unsignedTx, inputIndex, multiSigSPK, flags)
val signatures = 0.until(requiredSigs).map(i => TransactionSignatureCreator.createSig(sigComponent, signers(i), hashType))
val multiSigScriptSig = MultiSignatureScriptSignature(signatures)
val signedInput = TransactionInput(unsignedInput.previousOutput, multiSigScriptSig, unsignedInput.sequence)
val signedInputs = unsignedTx.inputs.updated(inputIndex.toInt, signedInput)
val signedTx = unsignedTx match {
case btx: BaseTransaction => BaseTransaction(btx.version, signedInputs,
btx.outputs, btx.lockTime)
case wtx: WitnessTransaction => WitnessTransaction(wtx.version, signedInputs,
wtx.outputs, wtx.lockTime, wtx.witness)
}
Left(BaseTxSigComponent(signedTx, inputIndex, multiSigSPK, Policy.standardFlags))
}
case lock : LockTimeScriptPubKey =>
val nested = lock.nestedScriptPubKey
val multiSigSPK = nested match {
case m: MultiSignatureScriptPubKey => Left(m)
case _: P2PKScriptPubKey | _: P2PKHScriptPubKey | _: MultiSignatureScriptPubKey | _: P2SHScriptPubKey
| _: P2WPKHWitnessSPKV0 | _: P2WSHWitnessSPKV0 | _: CLTVScriptPubKey | _: CSVScriptPubKey
| _: UnassignedWitnessScriptPubKey | _: NonStandardScriptPubKey | _: WitnessCommitment
| _: EscrowTimeoutScriptPubKey | EmptyScriptPubKey => Right(TxBuilderError.WrongSigner)
}
val requiredSigs = multiSigSPK.left.map(_.requiredSigs)
val sigComponent = BaseTxSigComponent(unsignedTx, inputIndex, lock, flags)
val signatures = requiredSigs.left.flatMap { n =>
if (signers.size < n) {
Right(TxBuilderError.WrongSigner)
} else {
val sigs = 0.until(n).map { i =>
TransactionSignatureCreator.createSig(sigComponent, signers(i), hashType)
}
Left(sigs)
}
}
val tx = signatures.left.map { sigs =>
val multiSigScriptSig = MultiSignatureScriptSignature(sigs)
val signedInput = TransactionInput(unsignedInput.previousOutput, multiSigScriptSig, unsignedInput.sequence)
val signedInputs = unsignedTx.inputs.updated(inputIndex.toInt, signedInput)
val signedTx = unsignedTx match {
case btx: BaseTransaction => BaseTransaction(btx.version, signedInputs,
btx.outputs, btx.lockTime)
case wtx: WitnessTransaction => WitnessTransaction(wtx.version, signedInputs,
wtx.outputs, wtx.lockTime, wtx.witness)
}
signedTx
}
tx.left.flatMap { t =>
multiSigSPK.left.map { m =>
BaseTxSigComponent(t, inputIndex, m, flags)
}
}
case _: P2PKScriptPubKey | _: P2PKHScriptPubKey | _: P2SHScriptPubKey
| _: P2WPKHWitnessSPKV0 | _: NonStandardScriptPubKey
| _: WitnessCommitment | EmptyScriptPubKey | _: UnassignedWitnessScriptPubKey
| _: EscrowTimeoutScriptPubKey =>
Right(TxBuilderError.WrongSigner)
}
signed
}
}
object MultiSigSigner extends MultiSigSigner
sealed abstract class P2WPKHSigner extends BitcoinSigner {
override def sign(signers: Seq[Signer.Sign], output: TransactionOutput, unsignedTx: Transaction,
inputIndex: UInt32, hashType: HashType): Either[TxSigComponent, TxBuilderError] = unsignedTx match {
case wtx: WitnessTransaction =>
if (signers.size != 1) {
Right(TxBuilderError.TooManySigners)
} else if (signers.head._2.isEmpty) {
Right(TxBuilderError.MissingPublicKey)
} else {
val signer = signers.head._1
val pubKey = signers.head._2.get
val unsignedScriptWit = P2WPKHWitnessV0(pubKey)
val unsignedTxWitness = TransactionWitness(wtx.witness.witnesses.updated(inputIndex.toInt, unsignedScriptWit))
val unsignedWtx = WitnessTransaction(wtx.version, wtx.inputs, wtx.outputs, wtx.lockTime, unsignedTxWitness)
val witSPK = output.scriptPubKey match {
case p2wpkh: P2WPKHWitnessSPKV0 =>
if (p2wpkh != P2WPKHWitnessSPKV0(pubKey)) {
Right(TxBuilderError.WrongPublicKey)
} else Left(p2wpkh)
case _: P2PKScriptPubKey | _: P2PKHScriptPubKey | _: MultiSignatureScriptPubKey | _: P2SHScriptPubKey
| _: P2WSHWitnessSPKV0 | _: NonStandardScriptPubKey | _: CLTVScriptPubKey | _: CSVScriptPubKey
| _: WitnessCommitment | EmptyScriptPubKey | _: UnassignedWitnessScriptPubKey
| _: EscrowTimeoutScriptPubKey =>
Right(TxBuilderError.NonWitnessSPK)
}
val result = witSPK.left.map { w =>
val wtxComp = WitnessTxSigComponentRaw(unsignedWtx, inputIndex, w, Policy.standardFlags, output.value)
val signature = TransactionSignatureCreator.createSig(wtxComp,signer,hashType)
val scriptWitness = P2WPKHWitnessV0(pubKey,signature)
val signedTxWitness = TransactionWitness(unsignedWtx.witness.witnesses.updated(inputIndex.toInt,scriptWitness))
val signedTx = WitnessTransaction(unsignedWtx.version, unsignedWtx.inputs, unsignedWtx.outputs,
unsignedWtx.lockTime, signedTxWitness)
WitnessTxSigComponentRaw(signedTx,inputIndex,w,Policy.standardFlags,output.value)
}
result
}
case btx: BaseTransaction =>
val wtx = WitnessTransaction(btx.version, btx.inputs, btx.outputs, btx.lockTime, EmptyWitness)
sign(signers,output,wtx,inputIndex,hashType)
}
}
object P2WPKHSigner extends P2WPKHSigner

View File

@ -22,14 +22,6 @@ class TransactionSignatureCreatorSpec extends Properties("TransactionSignatureCr
result == ScriptOk
}
property("generate a valid signature for a p2pkh transaction") =
Prop.forAll(TransactionGenerators.signedP2PKHTransaction) {
case (txSignatureComponent: TxSigComponent, _) =>
//run it through the interpreter
val program = ScriptProgram(txSignatureComponent)
val result = ScriptInterpreter.run(program)
result == ScriptOk
}
property("generate valid signatures for a multisignature transaction") =
Prop.forAllNoShrink(TransactionGenerators.signedMultiSigTransaction) {
case (txSignatureComponent: TxSigComponent, _) =>

View File

@ -3,7 +3,7 @@ package org.bitcoins.core.protocol.blockchain
import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.number.Int64
import org.bitcoins.core.protocol.script.{ScriptPubKey, ScriptSignature}
import org.bitcoins.core.protocol.transaction.{TransactionConstants, TransactionInput, TransactionOutput}
import org.bitcoins.core.protocol.transaction.{CoinbaseInput, TransactionConstants, TransactionOutput}
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinSUtil}
import org.scalatest.{FlatSpec, MustMatchers}
@ -17,7 +17,7 @@ class ChainParamsTest extends FlatSpec with MustMatchers {
val genesisTransaction = genesisBlock.transactions.head
val expectedGenesisScriptSig = ScriptSignature("4D04FFFF001D0104455468652054696D65732030332F4A616E2F32303039204368616E63656C6C6F72206F6E206272696E6B206F66207365636F6E64206261696C6F757420666F722062616E6B73".toLowerCase())
val expectedGenesisInput = TransactionInput(expectedGenesisScriptSig)
val expectedGenesisInput = CoinbaseInput(expectedGenesisScriptSig)
val expectedGenesisScriptPubKey = ScriptPubKey("434104678AFDB0FE5548271967F1A67130B7105CD6A828E03909A67962E0EA1F61DEB649F6BC3F4CEF38C4F35504E51EC112DE5C384DF7BA0B8D578A4C702B6BF11D5FAC".toLowerCase)
val expectedGenesisOutput = TransactionOutput(Satoshis(Int64(5000000000L)),expectedGenesisScriptPubKey)
"ChainParams" must "generate correct block hex for genesis block" in {

View File

@ -1,16 +1,19 @@
package org.bitcoins.core.protocol.transaction
import org.bitcoins.core.gen.TransactionGenerators
import org.bitcoins.core.util.BitcoinSLogger
import org.scalacheck.{Prop, Properties}
/**
* Created by chris on 6/24/16.
*/
class TransactionInputSpec extends Properties("TranactionInputSpec") {
property("Serialization symmetry") =
private val logger = BitcoinSLogger.logger
property("Serialization symmetry") = {
Prop.forAllNoShrink(TransactionGenerators.input) { input =>
val result = TransactionInput(input.hex) == input
result
}
}
}

View File

@ -1,6 +1,6 @@
package org.bitcoins.core.protocol.transaction
import org.bitcoins.core.protocol.script.EmptyScriptSignature
import org.bitcoins.core.protocol.script.{EmptyScriptSignature, P2PKHScriptSignature, P2PKScriptSignature}
import org.bitcoins.core.util.TestUtil
import org.scalatest.{FlatSpec, MustMatchers}
@ -37,5 +37,11 @@ class TransactionInputTest extends FlatSpec with MustMatchers {
(TransactionInput(input.hex) == input) must be (true)
}
it must "serialize and deserialize a coinbase input" in {
val c= CoinbaseInput(P2PKScriptSignature("4847304502210092d4e6183970b5e082d87563afbcfb3e1f38e801d89f036fd2935c394d6cc364022032b2a419e19f00b6f32f88c4427cf5e2a97f298b7d4e45efb5f723d84257ca03"))
TransactionInput(c.previousOutput, c.scriptSignature, c.sequence) must be (c)
c.hex must be (TransactionInput(c.previousOutput,c.scriptSignature,c.sequence).hex)
}
}

View File

@ -173,9 +173,7 @@ class TransactionTest extends FlatSpec with MustMatchers {
//use this to represent a single test case from script_valid.json
/* val lines =
"""
|[ [[["0000000000000000000000000000000000000000000000000000000000000100", 0, "0x60 0x02 0x0001", 2000]],
| "01000000010001000000000000000000000000000000000000000000000000000000000000000000000151ffffffff010000000000000000015100000000", "P2SH,WITNESS"]
|]
|[[[["0000000000000000000000000000000000000000000000000000000000000000",-1,"1"]], "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0151ffffffff010000000000000000015100000000", "P2SH"]]
""".stripMargin*/
val lines = try source.getLines.filterNot(_.isEmpty).map(_.trim) mkString "\n" finally source.close()
val json = lines.parseJson

View File

@ -33,7 +33,8 @@ class LockTimeInterpreterTest extends FlatSpec with MustMatchers {
it must "mark the transaction as invalid if the stack top is negative" in {
val stack = Seq(ScriptNumber(-1))
val script = Seq(OP_CHECKLOCKTIMEVERIFY)
val txInputAdjustedSequenceNumber = TransactionInput(TestUtil.transaction.inputs(0),UInt32.zero)
val oldInput = TestUtil.transaction.inputs(0)
val txInputAdjustedSequenceNumber = TransactionInput(oldInput.previousOutput, oldInput.scriptSignature, UInt32.zero)
val emptyTx = EmptyTransaction
val txAdjustedSequenceNumber = BaseTransaction(emptyTx.version,Seq(txInputAdjustedSequenceNumber), emptyTx.outputs, emptyTx.lockTime)
val adjustedLockTimeTx = BaseTransaction(txAdjustedSequenceNumber.version,txAdjustedSequenceNumber.inputs,txAdjustedSequenceNumber.outputs,UInt32.zero)
@ -47,7 +48,8 @@ class LockTimeInterpreterTest extends FlatSpec with MustMatchers {
it must "mark the transaction as invalid if the locktime on the tx is < 500000000 && stack top is >= 500000000" in {
val stack = Seq(ScriptNumber(500000000))
val script = Seq(OP_CHECKLOCKTIMEVERIFY)
val txInputAdjustedSequenceNumber = TransactionInput(TestUtil.transaction.inputs(0),UInt32.zero)
val oldInput = TestUtil.transaction.inputs(0)
val txInputAdjustedSequenceNumber = TransactionInput(oldInput.previousOutput, oldInput.scriptSignature, UInt32.zero)
val emptyTx = EmptyTransaction
val txAdjustedSequenceNumber = BaseTransaction(emptyTx.version,Seq(txInputAdjustedSequenceNumber), emptyTx.outputs, emptyTx.lockTime)
val adjustedLockTimeTx = BaseTransaction(txAdjustedSequenceNumber.version,txAdjustedSequenceNumber.inputs,txAdjustedSequenceNumber.outputs,UInt32.zero)
@ -61,7 +63,8 @@ class LockTimeInterpreterTest extends FlatSpec with MustMatchers {
it must "mark the transaction as invalid if the locktime on the tx is >= 500000000 && stack top is < 500000000" in {
val stack = Seq(ScriptNumber(499999999))
val script = Seq(OP_CHECKLOCKTIMEVERIFY)
val txInputAdjustedSequenceNumber = TransactionInput(TestUtil.transaction.inputs(0),UInt32.zero)
val oldInput = TestUtil.transaction.inputs(0)
val txInputAdjustedSequenceNumber = TransactionInput(oldInput.previousOutput, oldInput.scriptSignature, UInt32.zero)
val emptyTx = EmptyTransaction
val txAdjustedSequenceNumber = BaseTransaction(emptyTx.version,Seq(txInputAdjustedSequenceNumber), emptyTx.outputs, emptyTx.lockTime)
val adjustedLockTimeTx = BaseTransaction(txAdjustedSequenceNumber.version,txAdjustedSequenceNumber.inputs,txAdjustedSequenceNumber.outputs,UInt32.zero)
@ -76,7 +79,8 @@ class LockTimeInterpreterTest extends FlatSpec with MustMatchers {
it must "mark the transaction as invalid if the stack top item is greater than the tx locktime" in {
val stack = Seq(ScriptNumber(499999999))
val script = Seq(OP_CHECKLOCKTIMEVERIFY)
val txInputAdjustedSequenceNumber = TransactionInput(TestUtil.transaction.inputs(0),UInt32.zero)
val oldInput = TestUtil.transaction.inputs(0)
val txInputAdjustedSequenceNumber = TransactionInput(oldInput.previousOutput, oldInput.scriptSignature, UInt32.zero)
val emptyTx = EmptyTransaction
val txAdjustedSequenceNumber = BaseTransaction(emptyTx.version,Seq(txInputAdjustedSequenceNumber), emptyTx.outputs, emptyTx.lockTime)
val adjustedLockTimeTx = BaseTransaction(txAdjustedSequenceNumber.version,txAdjustedSequenceNumber.inputs,txAdjustedSequenceNumber.outputs,UInt32.zero)
@ -94,7 +98,8 @@ class LockTimeInterpreterTest extends FlatSpec with MustMatchers {
it must "mark the transaction as valid if the locktime on the tx is < 500000000 && stack top is < 500000000" in {
val stack = Seq(ScriptNumber(0))
val script = Seq(OP_CHECKLOCKTIMEVERIFY)
val txInputAdjustedSequenceNumber = TransactionInput(TestUtil.transaction.inputs(0),UInt32.zero)
val oldInput = TestUtil.transaction.inputs(0)
val txInputAdjustedSequenceNumber = TransactionInput(oldInput.previousOutput, oldInput.scriptSignature, UInt32.zero)
val emptyTx = EmptyTransaction
val txAdjustedSequenceNumber = BaseTransaction(emptyTx.version,Seq(txInputAdjustedSequenceNumber),
emptyTx.outputs, emptyTx.lockTime)
@ -113,7 +118,8 @@ class LockTimeInterpreterTest extends FlatSpec with MustMatchers {
it must "mark the transaction as valid if the locktime on the tx is >= 500000000 && stack top is >= 500000000" in {
val stack = Seq(ScriptNumber(500000000))
val script = Seq(OP_CHECKLOCKTIMEVERIFY)
val txInputAdjustedSequenceNumber = TransactionInput(TestUtil.transaction.inputs(0),UInt32.zero)
val oldInput = TestUtil.transaction.inputs(0)
val txInputAdjustedSequenceNumber = TransactionInput(oldInput.previousOutput, oldInput.scriptSignature, UInt32.zero)
val emptyTx = EmptyTransaction
val txAdjustedSequenceNumber = BaseTransaction(emptyTx.version, Seq(txInputAdjustedSequenceNumber),
emptyTx.outputs, emptyTx.lockTime)