diff --git a/build.sbt b/build.sbt index 68e9266519..07b29cbf6e 100644 --- a/build.sbt +++ b/build.sbt @@ -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") diff --git a/src/main/scala/org/bitcoins/core/config/NetworkParameters.scala b/src/main/scala/org/bitcoins/core/config/NetworkParameters.scala index d6482172b0..e2edc86520 100644 --- a/src/main/scala/org/bitcoins/core/config/NetworkParameters.scala +++ b/src/main/scala/org/bitcoins/core/config/NetworkParameters.scala @@ -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 diff --git a/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala b/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala index 00cb2b3fc6..69c6557dd8 100644 --- a/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala +++ b/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala @@ -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. */ diff --git a/src/main/scala/org/bitcoins/core/policy/Policy.scala b/src/main/scala/org/bitcoins/core/policy/Policy.scala index 11413564c7..4a38ab132e 100644 --- a/src/main/scala/org/bitcoins/core/policy/Policy.scala +++ b/src/main/scala/org/bitcoins/core/policy/Policy.scala @@ -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 diff --git a/src/main/scala/org/bitcoins/core/protocol/blockchain/ChainParams.scala b/src/main/scala/org/bitcoins/core/protocol/blockchain/ChainParams.scala index 660ccfa259..d7269dd066 100644 --- a/src/main/scala/org/bitcoins/core/protocol/blockchain/ChainParams.scala +++ b/src/main/scala/org/bitcoins/core/protocol/blockchain/ChainParams.scala @@ -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") diff --git a/src/main/scala/org/bitcoins/core/protocol/transaction/Transaction.scala b/src/main/scala/org/bitcoins/core/protocol/transaction/Transaction.scala index cb390ae633..944b775f88 100644 --- a/src/main/scala/org/bitcoins/core/protocol/transaction/Transaction.scala +++ b/src/main/scala/org/bitcoins/core/protocol/transaction/Transaction.scala @@ -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] { diff --git a/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionInput.scala b/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionInput.scala index 9651302b20..c87b9303d5 100644 --- a/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionInput.scala +++ b/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionInput.scala @@ -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 diff --git a/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionOutPoint.scala b/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionOutPoint.scala index 870a6e1d19..9ab0e3935c 100644 --- a/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionOutPoint.scala +++ b/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionOutPoint.scala @@ -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) + } } diff --git a/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionOutput.scala b/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionOutput.scala index 05c8031eec..641b440572 100644 --- a/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionOutput.scala +++ b/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionOutput.scala @@ -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) } - } \ No newline at end of file diff --git a/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionWitness.scala b/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionWitness.scala index e040f5466e..86fd7485a8 100644 --- a/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionWitness.scala +++ b/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionWitness.scala @@ -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 diff --git a/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializer.scala b/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializer.scala index 7cb43acc35..01a3d5222f 100644 --- a/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializer.scala +++ b/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializer.scala @@ -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 - } diff --git a/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializerHelper.scala b/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializerHelper.scala index db397aca44..530f1dfa3f 100644 --- a/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializerHelper.scala +++ b/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializerHelper.scala @@ -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 diff --git a/src/main/scala/org/bitcoins/core/wallet/builder/TxBuilderError.scala b/src/main/scala/org/bitcoins/core/wallet/builder/TxBuilderError.scala new file mode 100644 index 0000000000..c8cff0fdc9 --- /dev/null +++ b/src/main/scala/org/bitcoins/core/wallet/builder/TxBuilderError.scala @@ -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 + +} \ No newline at end of file diff --git a/src/main/scala/org/bitcoins/core/wallet/fee/FeeUnit.scala b/src/main/scala/org/bitcoins/core/wallet/fee/FeeUnit.scala new file mode 100644 index 0000000000..f0493d1b9d --- /dev/null +++ b/src/main/scala/org/bitcoins/core/wallet/fee/FeeUnit.scala @@ -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 diff --git a/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala b/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala new file mode 100644 index 0000000000..8c89640012 --- /dev/null +++ b/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala @@ -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 \ No newline at end of file diff --git a/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorSpec.scala b/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorSpec.scala index 1943129833..e40572cdb7 100644 --- a/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorSpec.scala +++ b/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorSpec.scala @@ -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, _) => diff --git a/src/test/scala/org/bitcoins/core/protocol/blockchain/ChainParamsTest.scala b/src/test/scala/org/bitcoins/core/protocol/blockchain/ChainParamsTest.scala index b947f1cc68..27bd640e7f 100644 --- a/src/test/scala/org/bitcoins/core/protocol/blockchain/ChainParamsTest.scala +++ b/src/test/scala/org/bitcoins/core/protocol/blockchain/ChainParamsTest.scala @@ -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 { diff --git a/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionInputSpec.scala b/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionInputSpec.scala index 89c44c857a..2fbe7c6258 100644 --- a/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionInputSpec.scala +++ b/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionInputSpec.scala @@ -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 } + } } + diff --git a/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionInputTest.scala b/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionInputTest.scala index a1d0c5e17a..774f821220 100644 --- a/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionInputTest.scala +++ b/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionInputTest.scala @@ -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) + } + } diff --git a/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala b/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala index 5e892020e4..0cb8149941 100644 --- a/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala +++ b/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala @@ -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 diff --git a/src/test/scala/org/bitcoins/core/script/locktime/LockTimeInterpreterTest.scala b/src/test/scala/org/bitcoins/core/script/locktime/LockTimeInterpreterTest.scala index d141bf84fa..9cca5de80f 100644 --- a/src/test/scala/org/bitcoins/core/script/locktime/LockTimeInterpreterTest.scala +++ b/src/test/scala/org/bitcoins/core/script/locktime/LockTimeInterpreterTest.scala @@ -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)