mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-15 20:30:17 +01:00
Spending Info ADT use (#840)
* Replaced TxBuilder signAndAddInput match with UTXOSpendingInfo match, tests don't pass * Fixed tests!
This commit is contained in:
parent
04edc40b7e
commit
310ccbb838
5 changed files with 283 additions and 188 deletions
|
@ -20,6 +20,10 @@ class UTXOSpendingInfoTest extends BitcoinSAsyncTest {
|
|||
ScriptGenerators.scriptPubKey.map(_._1).sample.get
|
||||
}
|
||||
|
||||
def randomNonWitnessSPK: NonWitnessScriptPubKey = {
|
||||
ScriptGenerators.nonWitnessScriptPubKey.map(_._1).sample.get
|
||||
}
|
||||
|
||||
def randomWitnessSPK: WitnessScriptPubKeyV0 = {
|
||||
ScriptGenerators.witnessScriptPubKeyV0.map(_._1).sample.get
|
||||
}
|
||||
|
@ -34,12 +38,12 @@ class UTXOSpendingInfoTest extends BitcoinSAsyncTest {
|
|||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
P2SHSpendingInfo(outPoint = outPoint,
|
||||
amount = CurrencyUnits.zero,
|
||||
scriptPubKey = p2sh,
|
||||
signers = Seq(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
redeemScript = randomSPK)
|
||||
P2SHNoNestSpendingInfo(outPoint = outPoint,
|
||||
amount = CurrencyUnits.zero,
|
||||
scriptPubKey = p2sh,
|
||||
signers = Seq(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
redeemScript = randomNonWitnessSPK)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,19 +26,22 @@ import scala.util.{Failure, Success, Try}
|
|||
*/
|
||||
sealed abstract class ScriptPubKey extends Script
|
||||
|
||||
sealed trait NonWitnessScriptPubKey extends ScriptPubKey
|
||||
|
||||
/**
|
||||
* Represents a
|
||||
* [[https://bitcoin.org/en/developer-guide#pay-to-public-key-hash-p2pkh pay-to-pubkey hash script pubkey]]
|
||||
*
|
||||
* Format: `OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG`
|
||||
*/
|
||||
sealed trait P2PKHScriptPubKey extends ScriptPubKey {
|
||||
sealed trait P2PKHScriptPubKey extends NonWitnessScriptPubKey {
|
||||
|
||||
def pubKeyHash: Sha256Hash160Digest =
|
||||
Sha256Hash160Digest(asm(asm.length - 3).bytes)
|
||||
}
|
||||
|
||||
object P2PKHScriptPubKey extends ScriptFactory[P2PKHScriptPubKey] {
|
||||
|
||||
private case class P2PKHScriptPubKeyImpl(
|
||||
override val asm: Vector[ScriptToken])
|
||||
extends P2PKHScriptPubKey {
|
||||
|
@ -89,7 +92,7 @@ object P2PKHScriptPubKey extends ScriptFactory[P2PKHScriptPubKey] {
|
|||
* https://bitcoin.org/en/developer-guide#multisig
|
||||
* Format: <m> <A pubkey> [B pubkey] [C pubkey...] <n> OP_CHECKMULTISIG
|
||||
*/
|
||||
sealed trait MultiSignatureScriptPubKey extends ScriptPubKey {
|
||||
sealed trait MultiSignatureScriptPubKey extends NonWitnessScriptPubKey {
|
||||
|
||||
/** Returns the amount of required signatures for this multisignature script pubkey output */
|
||||
def requiredSigs: Int = {
|
||||
|
@ -259,7 +262,7 @@ object MultiSignatureScriptPubKey
|
|||
* Represents a [[https://bitcoin.org/en/developer-guide#pay-to-script-hash-p2sh pay-to-scripthash public key]]
|
||||
* Format: `OP_HASH160 <Hash160(redeemScript)> OP_EQUAL`
|
||||
*/
|
||||
sealed trait P2SHScriptPubKey extends ScriptPubKey {
|
||||
sealed trait P2SHScriptPubKey extends NonWitnessScriptPubKey {
|
||||
|
||||
/** The hash of the script for which this scriptPubKey is being created from */
|
||||
def scriptHash: Sha256Hash160Digest =
|
||||
|
@ -267,6 +270,7 @@ sealed trait P2SHScriptPubKey extends ScriptPubKey {
|
|||
}
|
||||
|
||||
object P2SHScriptPubKey extends ScriptFactory[P2SHScriptPubKey] {
|
||||
|
||||
private case class P2SHScriptPubKeyImpl(override val asm: Vector[ScriptToken])
|
||||
extends P2SHScriptPubKey {
|
||||
override def toString = "P2SHScriptPubKeyImpl(" + hex + ")"
|
||||
|
@ -309,7 +313,7 @@ object P2SHScriptPubKey extends ScriptFactory[P2SHScriptPubKey] {
|
|||
* Represents a [[https://bitcoin.org/en/developer-guide#pubkey pay to public key script public key]]
|
||||
* Format: `<pubkey> OP_CHECKSIG`
|
||||
*/
|
||||
sealed trait P2PKScriptPubKey extends ScriptPubKey {
|
||||
sealed trait P2PKScriptPubKey extends NonWitnessScriptPubKey {
|
||||
|
||||
def publicKey: ECPublicKey =
|
||||
ECPublicKey(BitcoinScriptUtil.filterPushOps(asm).head.bytes)
|
||||
|
@ -346,7 +350,7 @@ object P2PKScriptPubKey extends ScriptFactory[P2PKScriptPubKey] {
|
|||
|
||||
}
|
||||
|
||||
sealed trait LockTimeScriptPubKey extends ScriptPubKey {
|
||||
sealed trait LockTimeScriptPubKey extends NonWitnessScriptPubKey {
|
||||
|
||||
/** Determines the nested `ScriptPubKey` inside the `LockTimeScriptPubKey` */
|
||||
def nestedScriptPubKey: ScriptPubKey = {
|
||||
|
@ -398,6 +402,7 @@ object LockTimeScriptPubKey extends ScriptFactory[LockTimeScriptPubKey] {
|
|||
sealed trait CLTVScriptPubKey extends LockTimeScriptPubKey
|
||||
|
||||
object CLTVScriptPubKey extends ScriptFactory[CLTVScriptPubKey] {
|
||||
|
||||
private case class CLTVScriptPubKeyImpl(override val asm: Vector[ScriptToken])
|
||||
extends CLTVScriptPubKey {
|
||||
override def toString = "CLTVScriptPubKeyImpl(" + hex + ")"
|
||||
|
@ -483,6 +488,7 @@ object CLTVScriptPubKey extends ScriptFactory[CLTVScriptPubKey] {
|
|||
sealed trait CSVScriptPubKey extends LockTimeScriptPubKey
|
||||
|
||||
object CSVScriptPubKey extends ScriptFactory[CSVScriptPubKey] {
|
||||
|
||||
private case class CSVScriptPubKeyImpl(override val asm: Vector[ScriptToken])
|
||||
extends CSVScriptPubKey {
|
||||
override def toString = "CSVScriptPubKeyImpl(" + hex + ")"
|
||||
|
@ -545,9 +551,10 @@ object CSVScriptPubKey extends ScriptFactory[CSVScriptPubKey] {
|
|||
|
||||
}
|
||||
|
||||
sealed trait NonStandardScriptPubKey extends ScriptPubKey
|
||||
sealed trait NonStandardScriptPubKey extends NonWitnessScriptPubKey
|
||||
|
||||
object NonStandardScriptPubKey extends ScriptFactory[NonStandardScriptPubKey] {
|
||||
|
||||
private case class NonStandardScriptPubKeyImpl(
|
||||
override val asm: Vector[ScriptToken])
|
||||
extends NonStandardScriptPubKey {
|
||||
|
@ -565,7 +572,7 @@ object NonStandardScriptPubKey extends ScriptFactory[NonStandardScriptPubKey] {
|
|||
}
|
||||
|
||||
/** Represents the empty ScriptPubKey */
|
||||
case object EmptyScriptPubKey extends ScriptPubKey {
|
||||
case object EmptyScriptPubKey extends NonWitnessScriptPubKey {
|
||||
override def asm: Seq[ScriptToken] = Vector.empty
|
||||
}
|
||||
|
||||
|
@ -705,6 +712,7 @@ sealed abstract class P2WPKHWitnessSPKV0 extends WitnessScriptPubKeyV0 {
|
|||
}
|
||||
|
||||
object P2WPKHWitnessSPKV0 extends ScriptFactory[P2WPKHWitnessSPKV0] {
|
||||
|
||||
private case class P2WPKHWitnessSPKV0Impl(
|
||||
override val asm: Vector[ScriptToken])
|
||||
extends P2WPKHWitnessSPKV0
|
||||
|
@ -749,6 +757,7 @@ sealed abstract class P2WSHWitnessSPKV0 extends WitnessScriptPubKeyV0 {
|
|||
}
|
||||
|
||||
object P2WSHWitnessSPKV0 extends ScriptFactory[P2WSHWitnessSPKV0] {
|
||||
|
||||
private case class P2WSHWitnessSPKV0Impl(
|
||||
override val asm: Vector[ScriptToken])
|
||||
extends P2WSHWitnessSPKV0
|
||||
|
@ -790,6 +799,7 @@ sealed trait UnassignedWitnessScriptPubKey extends WitnessScriptPubKey {
|
|||
|
||||
object UnassignedWitnessScriptPubKey
|
||||
extends ScriptFactory[UnassignedWitnessScriptPubKey] {
|
||||
|
||||
private case class UnassignedWitnessScriptPubKeyImpl(
|
||||
override val asm: Vector[ScriptToken])
|
||||
extends UnassignedWitnessScriptPubKey {
|
||||
|
@ -815,7 +825,7 @@ object UnassignedWitnessScriptPubKey
|
|||
* See BIP141 for more info
|
||||
* [[https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#commitment-structure]]
|
||||
*/
|
||||
sealed trait WitnessCommitment extends ScriptPubKey {
|
||||
sealed trait WitnessCommitment extends NonWitnessScriptPubKey {
|
||||
|
||||
/** The commitment to the
|
||||
* [[org.bitcoins.core.protocol.transaction.WitnessTransaction WitnessTransaction]]s in the
|
||||
|
@ -825,6 +835,7 @@ sealed trait WitnessCommitment extends ScriptPubKey {
|
|||
}
|
||||
|
||||
object WitnessCommitment extends ScriptFactory[WitnessCommitment] {
|
||||
|
||||
private case class WitnessCommitmentImpl(
|
||||
override val asm: Vector[ScriptToken])
|
||||
extends WitnessCommitment {
|
||||
|
|
|
@ -19,7 +19,19 @@ import org.bitcoins.core.script.locktime.LockTimeInterpreter
|
|||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||
import org.bitcoins.core.wallet.signer._
|
||||
import org.bitcoins.core.wallet.utxo.{BitcoinUTXOSpendingInfo, UTXOSpendingInfo}
|
||||
import org.bitcoins.core.wallet.utxo.{
|
||||
BitcoinUTXOSpendingInfo,
|
||||
LockTimeSpendingInfo,
|
||||
MultiSignatureSpendingInfo,
|
||||
P2PKHSpendingInfo,
|
||||
P2PKSpendingInfo,
|
||||
P2SHNestedSegwitV0UTXOSpendingInfo,
|
||||
P2SHNoNestSpendingInfo,
|
||||
P2SHSpendingInfo,
|
||||
SegwitV0NativeUTXOSpendingInfo,
|
||||
UTXOSpendingInfo,
|
||||
UnassignedSegwitNativeUTXOSpendingInfo
|
||||
}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
@ -244,167 +256,147 @@ sealed abstract class BitcoinTxBuilder extends TxBuilder {
|
|||
unsignedTx: Transaction,
|
||||
dummySignatures: Boolean)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
val outpoint = utxo.outPoint
|
||||
val output = utxo.output
|
||||
val signers = utxo.signers
|
||||
val redeemScriptOpt = utxo.redeemScriptOpt
|
||||
val scriptWitnessOpt = utxo.scriptWitnessOpt
|
||||
val hashType = utxo.hashType
|
||||
val idx =
|
||||
unsignedTx.inputs.zipWithIndex.find(_._1.previousOutput == outpoint)
|
||||
unsignedTx.inputs.zipWithIndex.find(_._1.previousOutput == utxo.outPoint)
|
||||
if (idx.isEmpty) {
|
||||
Future.fromTry(TxBuilderError.MissingOutPoint)
|
||||
} else {
|
||||
val inputIndex = UInt32(idx.get._2)
|
||||
val oldInput = unsignedTx.inputs(inputIndex.toInt)
|
||||
|
||||
output.scriptPubKey match {
|
||||
case _: P2PKScriptPubKey =>
|
||||
utxo match {
|
||||
case p2pkInfo: P2PKSpendingInfo =>
|
||||
P2PKSigner
|
||||
.sign(utxo, unsignedTx, dummySignatures)
|
||||
.sign(p2pkInfo, unsignedTx, dummySignatures)
|
||||
.map(_.transaction)
|
||||
case _: P2PKHScriptPubKey =>
|
||||
case p2pkhInfo: P2PKHSpendingInfo =>
|
||||
P2PKHSigner
|
||||
.sign(utxo, unsignedTx, dummySignatures)
|
||||
.sign(p2pkhInfo, unsignedTx, dummySignatures)
|
||||
.map(_.transaction)
|
||||
case _: MultiSignatureScriptPubKey =>
|
||||
case multiSigInfo: MultiSignatureSpendingInfo =>
|
||||
MultiSigSigner
|
||||
.sign(utxo, unsignedTx, dummySignatures)
|
||||
.sign(multiSigInfo, unsignedTx, dummySignatures)
|
||||
.map(_.transaction)
|
||||
case _: LockTimeScriptPubKey =>
|
||||
case lockTimeInfo: LockTimeSpendingInfo =>
|
||||
LockTimeSigner
|
||||
.sign(utxo, unsignedTx, dummySignatures)
|
||||
.sign(lockTimeInfo, unsignedTx, dummySignatures)
|
||||
.map(_.transaction)
|
||||
case p2sh: P2SHScriptPubKey =>
|
||||
redeemScriptOpt match {
|
||||
|
||||
case Some(redeemScript) =>
|
||||
if (p2sh != P2SHScriptPubKey(redeemScript)) {
|
||||
|
||||
Future.fromTry(TxBuilderError.WrongRedeemScript)
|
||||
|
||||
} else {
|
||||
|
||||
val signedTxF: Future[Transaction] = redeemScript match {
|
||||
|
||||
case p2wpkh: P2WPKHWitnessSPKV0 =>
|
||||
val uwtx = WitnessTransaction.toWitnessTx(unsignedTx)
|
||||
|
||||
//breaks an abstraction inside of all of the signers
|
||||
//won't be able to be handled properly until gemini stuff
|
||||
//is open sourced
|
||||
signP2SHP2WPKH(unsignedTx = uwtx,
|
||||
inputIndex = inputIndex,
|
||||
output = output,
|
||||
p2wpkh = p2wpkh,
|
||||
utxo = utxo,
|
||||
hashType = hashType,
|
||||
dummySignatures = dummySignatures)
|
||||
|
||||
case _: P2PKScriptPubKey | _: P2PKHScriptPubKey |
|
||||
_: MultiSignatureScriptPubKey | _: LockTimeScriptPubKey |
|
||||
_: NonStandardScriptPubKey | _: WitnessCommitment |
|
||||
_: UnassignedWitnessScriptPubKey | _: P2WSHWitnessSPKV0 |
|
||||
EmptyScriptPubKey =>
|
||||
val input = TransactionInput(outpoint,
|
||||
EmptyScriptSignature,
|
||||
oldInput.sequence)
|
||||
|
||||
val updatedTx =
|
||||
unsignedTx.updateInput(inputIndex.toInt, input)
|
||||
|
||||
val updatedOutput =
|
||||
TransactionOutput(output.value, redeemScript)
|
||||
|
||||
val updatedUTXOInfo = BitcoinUTXOSpendingInfo(
|
||||
outpoint,
|
||||
updatedOutput,
|
||||
signers,
|
||||
None,
|
||||
scriptWitnessOpt,
|
||||
hashType)
|
||||
|
||||
val signedTxEither = signAndAddInput(updatedUTXOInfo,
|
||||
updatedTx,
|
||||
dummySignatures)
|
||||
|
||||
signedTxEither.map { signedTx =>
|
||||
val i = signedTx.inputs(inputIndex.toInt)
|
||||
|
||||
val p2sh =
|
||||
P2SHScriptSignature(i.scriptSignature, redeemScript)
|
||||
|
||||
val signedInput =
|
||||
TransactionInput(i.previousOutput, p2sh, i.sequence)
|
||||
|
||||
val signedInputs =
|
||||
signedTx.inputs.updated(inputIndex.toInt, signedInput)
|
||||
|
||||
signedTx 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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case _: P2SHScriptPubKey =>
|
||||
Future.fromTry(TxBuilderError.NestedP2SHSPK)
|
||||
}
|
||||
|
||||
signedTxF
|
||||
|
||||
}
|
||||
case None => Future.fromTry(TxBuilderError.NoRedeemScript)
|
||||
case segWitInfo @ SegwitV0NativeUTXOSpendingInfo(_,
|
||||
_,
|
||||
witnessSPK,
|
||||
_,
|
||||
_,
|
||||
_) =>
|
||||
witnessSPK match {
|
||||
case _: P2WPKHWitnessSPKV0 =>
|
||||
P2WPKHSigner
|
||||
.sign(segWitInfo, unsignedTx, dummySignatures)
|
||||
.map(_.transaction)
|
||||
case _: P2WSHWitnessSPKV0 =>
|
||||
P2WSHSigner
|
||||
.sign(segWitInfo, unsignedTx, dummySignatures)
|
||||
.map(_.transaction)
|
||||
}
|
||||
case p2shInfo: P2SHNoNestSpendingInfo =>
|
||||
signAndAddInputForP2SH(p2shInfo,
|
||||
unsignedTx,
|
||||
oldInput.sequence,
|
||||
inputIndex.toInt,
|
||||
dummySignatures)
|
||||
case p2shNestedSegwitInfo @ P2SHNestedSegwitV0UTXOSpendingInfo(
|
||||
_,
|
||||
amount,
|
||||
scriptPubKey,
|
||||
_,
|
||||
hashType,
|
||||
redeemWitnessScript,
|
||||
_) =>
|
||||
redeemWitnessScript match {
|
||||
case _: P2WSHWitnessSPKV0 =>
|
||||
signAndAddInputForP2SH(p2shNestedSegwitInfo,
|
||||
unsignedTx,
|
||||
oldInput.sequence,
|
||||
inputIndex.toInt,
|
||||
dummySignatures)
|
||||
case p2wpkh: P2WPKHWitnessSPKV0 =>
|
||||
val uwtx = WitnessTransaction.toWitnessTx(unsignedTx)
|
||||
|
||||
case _: P2WPKHWitnessSPKV0 =>
|
||||
P2WPKHSigner
|
||||
.sign(utxo, unsignedTx, dummySignatures)
|
||||
.map(_.transaction)
|
||||
case p2wshSPK: P2WSHWitnessSPKV0 =>
|
||||
val p2wshScriptWitF = scriptWitnessOpt match {
|
||||
case Some(EmptyScriptWitness | _: P2WPKHWitnessV0) =>
|
||||
Future.fromTry(TxBuilderError.WrongWitness)
|
||||
case Some(x: P2WSHWitnessV0) => Future.successful(x)
|
||||
case None => Future.fromTry(TxBuilderError.NoWitness)
|
||||
//breaks an abstraction inside of all of the signers
|
||||
//won't be able to be handled properly until gemini stuff
|
||||
//is open sourced
|
||||
signP2SHP2WPKH(
|
||||
unsignedTx = uwtx,
|
||||
inputIndex = inputIndex,
|
||||
output = TransactionOutput(amount, scriptPubKey),
|
||||
p2wpkh = p2wpkh,
|
||||
utxo = utxo,
|
||||
hashType = hashType,
|
||||
dummySignatures = dummySignatures
|
||||
)
|
||||
}
|
||||
val redeemScriptF = p2wshScriptWitF.map(_.redeemScript)
|
||||
val validatedRedeemScriptF = redeemScriptF.flatMap { redeemScript =>
|
||||
if (P2WSHWitnessSPKV0(redeemScript) != p2wshSPK) {
|
||||
Future.fromTry(TxBuilderError.WrongWitness)
|
||||
} else {
|
||||
Future.successful(redeemScript)
|
||||
}
|
||||
}
|
||||
val sigComponentF = validatedRedeemScriptF.flatMap {
|
||||
case _: P2PKScriptPubKey | _: P2PKHScriptPubKey |
|
||||
_: MultiSignatureScriptPubKey | _: LockTimeScriptPubKey =>
|
||||
P2WSHSigner.sign(utxo, unsignedTx, dummySignatures)
|
||||
case _: P2WPKHWitnessSPKV0 | _: P2WSHWitnessSPKV0 =>
|
||||
Future.fromTry(TxBuilderError.NestedWitnessSPK)
|
||||
case _: P2SHScriptPubKey =>
|
||||
Future.fromTry(TxBuilderError.NestedP2SHSPK)
|
||||
case _: NonStandardScriptPubKey | _: WitnessCommitment |
|
||||
EmptyScriptPubKey | _: UnassignedWitnessScriptPubKey =>
|
||||
Future.fromTry(TxBuilderError.NoSigner)
|
||||
}
|
||||
sigComponentF.map(_.transaction)
|
||||
case _: NonStandardScriptPubKey | _: WitnessCommitment |
|
||||
EmptyScriptPubKey | _: UnassignedWitnessScriptPubKey =>
|
||||
case _: UnassignedSegwitNativeUTXOSpendingInfo =>
|
||||
Future.fromTry(TxBuilderError.NoSigner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def signAndAddInputForP2SH(
|
||||
spendingInfo: P2SHSpendingInfo,
|
||||
unsignedTx: Transaction,
|
||||
sequence: UInt32,
|
||||
inputIndex: Int,
|
||||
dummySignatures: Boolean)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
require(!spendingInfo.redeemScript.isInstanceOf[P2WPKHWitnessSPKV0],
|
||||
"Should call signP2SHP2WPKH")
|
||||
|
||||
val outpoint = spendingInfo.outPoint
|
||||
val redeemScript = spendingInfo.redeemScript
|
||||
|
||||
val input =
|
||||
TransactionInput(outpoint, EmptyScriptSignature, sequence)
|
||||
|
||||
val updatedTx =
|
||||
unsignedTx.updateInput(inputIndex, input)
|
||||
|
||||
val updatedOutput =
|
||||
TransactionOutput(spendingInfo.amount, redeemScript)
|
||||
|
||||
val updatedUTXOInfo = BitcoinUTXOSpendingInfo(outpoint,
|
||||
updatedOutput,
|
||||
spendingInfo.signers,
|
||||
None,
|
||||
spendingInfo.scriptWitnessOpt,
|
||||
spendingInfo.hashType)
|
||||
|
||||
val signedTxEither =
|
||||
signAndAddInput(updatedUTXOInfo, updatedTx, dummySignatures)
|
||||
|
||||
signedTxEither.map { signedTx =>
|
||||
val i = signedTx.inputs(inputIndex.toInt)
|
||||
|
||||
val p2sh =
|
||||
P2SHScriptSignature(i.scriptSignature, redeemScript)
|
||||
|
||||
val signedInput =
|
||||
TransactionInput(i.previousOutput, p2sh, i.sequence)
|
||||
|
||||
val signedInputs =
|
||||
signedTx.inputs.updated(inputIndex.toInt, signedInput)
|
||||
|
||||
signedTx 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a valid sequence number for the given [[ScriptNumber]]
|
||||
* A transaction needs a valid sequence number to spend a OP_CHECKSEQUENCEVERIFY script.
|
||||
|
|
|
@ -3,12 +3,12 @@ package org.bitcoins.core.wallet.utxo
|
|||
import org.bitcoins.core.crypto.Sign
|
||||
import org.bitcoins.core.currency.CurrencyUnit
|
||||
import org.bitcoins.core.protocol.script.{
|
||||
CLTVScriptPubKey,
|
||||
CSVScriptPubKey,
|
||||
EmptyScriptPubKey,
|
||||
EmptyScriptWitness,
|
||||
LockTimeScriptPubKey,
|
||||
MultiSignatureScriptPubKey,
|
||||
NonStandardScriptPubKey,
|
||||
NonWitnessScriptPubKey,
|
||||
P2PKHScriptPubKey,
|
||||
P2PKScriptPubKey,
|
||||
P2SHScriptPubKey,
|
||||
|
@ -60,6 +60,7 @@ sealed abstract class UTXOSpendingInfo {
|
|||
}
|
||||
|
||||
sealed trait BitcoinUTXOSpendingInfo extends UTXOSpendingInfo {
|
||||
|
||||
protected def isValidScriptWitness(
|
||||
spk: WitnessScriptPubKeyV0,
|
||||
scriptWitness: ScriptWitnessV0): Boolean = {
|
||||
|
@ -73,7 +74,18 @@ sealed trait BitcoinUTXOSpendingInfo extends UTXOSpendingInfo {
|
|||
case p2wsh: P2WSHWitnessSPKV0 =>
|
||||
scriptWitness match {
|
||||
case witness: P2WSHWitnessV0 =>
|
||||
CryptoUtil.sha256(witness.redeemScript.asmBytes) == p2wsh.scriptHash
|
||||
val hashMatch = CryptoUtil.sha256(witness.redeemScript.asmBytes) == p2wsh.scriptHash
|
||||
val noIllegalNesting = witness.redeemScript match {
|
||||
case _: P2PKScriptPubKey | _: P2PKHScriptPubKey |
|
||||
_: MultiSignatureScriptPubKey | _: LockTimeScriptPubKey |
|
||||
_: NonStandardScriptPubKey | _: WitnessCommitment |
|
||||
EmptyScriptPubKey | _: UnassignedWitnessScriptPubKey =>
|
||||
true
|
||||
case _: P2WPKHWitnessSPKV0 | _: P2WSHWitnessSPKV0 |
|
||||
_: P2SHScriptPubKey =>
|
||||
false
|
||||
}
|
||||
hashMatch && noIllegalNesting
|
||||
case _: ScriptWitnessV0 => false
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +94,6 @@ sealed trait BitcoinUTXOSpendingInfo extends UTXOSpendingInfo {
|
|||
|
||||
object BitcoinUTXOSpendingInfo {
|
||||
|
||||
// TODO: Get rid of this and force all callers to directly call a subclass apply method
|
||||
def apply(
|
||||
outPoint: TransactionOutPoint,
|
||||
output: TransactionOutput,
|
||||
|
@ -116,13 +127,16 @@ object BitcoinUTXOSpendingInfo {
|
|||
witnessOpt.getOrElse(throw new IllegalArgumentException(
|
||||
"Script Witness must be defined for (nested) Segwit input"))
|
||||
)
|
||||
case _: ScriptPubKey =>
|
||||
P2SHSpendingInfo(outPoint,
|
||||
output.value,
|
||||
p2sh,
|
||||
signers,
|
||||
hashType,
|
||||
redeemScript)
|
||||
case nonWitnessSPK: NonWitnessScriptPubKey =>
|
||||
P2SHNoNestSpendingInfo(outPoint,
|
||||
output.value,
|
||||
p2sh,
|
||||
signers,
|
||||
hashType,
|
||||
nonWitnessSPK)
|
||||
case _: UnassignedWitnessScriptPubKey =>
|
||||
throw new UnsupportedOperationException(
|
||||
s"Unsupported ScriptPubKey ${output.scriptPubKey}")
|
||||
}
|
||||
}
|
||||
case wspk: WitnessScriptPubKeyV0 =>
|
||||
|
@ -152,19 +166,29 @@ object BitcoinUTXOSpendingInfo {
|
|||
signers,
|
||||
hashType,
|
||||
scriptWitnessOpt.getOrElse(EmptyScriptWitness))
|
||||
case _: P2PKScriptPubKey | _: P2PKHScriptPubKey |
|
||||
_: MultiSignatureScriptPubKey | _: NonStandardScriptPubKey |
|
||||
_: CLTVScriptPubKey | _: CSVScriptPubKey | _: WitnessCommitment |
|
||||
case p2pk: P2PKScriptPubKey =>
|
||||
P2PKSpendingInfo(outPoint, output.value, p2pk, signers.head, hashType)
|
||||
case p2pkh: P2PKHScriptPubKey =>
|
||||
P2PKHSpendingInfo(outPoint, output.value, p2pkh, signers.head, hashType)
|
||||
case multisig: MultiSignatureScriptPubKey =>
|
||||
MultiSignatureSpendingInfo(outPoint,
|
||||
output.value,
|
||||
multisig,
|
||||
signers.toVector,
|
||||
hashType)
|
||||
case locktime: LockTimeScriptPubKey =>
|
||||
LockTimeSpendingInfo(outPoint,
|
||||
output.value,
|
||||
locktime,
|
||||
signers.toVector,
|
||||
hashType)
|
||||
case _: NonStandardScriptPubKey | _: WitnessCommitment |
|
||||
EmptyScriptPubKey =>
|
||||
RawScriptUTXOSpendingInfo(outPoint,
|
||||
output.value,
|
||||
output.scriptPubKey,
|
||||
signers,
|
||||
hashType)
|
||||
throw new UnsupportedOperationException(
|
||||
s"Currently unsupported ScriptPubKey ${output.scriptPubKey}")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Get rid of this and force all matches to match on the ADT
|
||||
def unapply(info: BitcoinUTXOSpendingInfo): Option[
|
||||
(
|
||||
TransactionOutPoint,
|
||||
|
@ -185,18 +209,60 @@ object BitcoinUTXOSpendingInfo {
|
|||
/** This represents the information needed to be spend scripts like
|
||||
* [[org.bitcoins.core.protocol.script.P2PKHScriptPubKey p2pkh]] or [[org.bitcoins.core.protocol.script.P2PKScriptPubKey p2pk]]
|
||||
* scripts. Basically there is no nesting that requires a redeem script here*/
|
||||
case class RawScriptUTXOSpendingInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: ScriptPubKey,
|
||||
signers: Seq[Sign],
|
||||
hashType: HashType)
|
||||
extends BitcoinUTXOSpendingInfo {
|
||||
sealed trait RawScriptUTXOSpendingInfo extends BitcoinUTXOSpendingInfo {
|
||||
override val outPoint: TransactionOutPoint
|
||||
override val amount: CurrencyUnit
|
||||
override val scriptPubKey: ScriptPubKey
|
||||
override val signers: Seq[Sign]
|
||||
override val hashType: HashType
|
||||
|
||||
override val redeemScriptOpt: Option[ScriptPubKey] = None
|
||||
|
||||
override val scriptWitnessOpt: Option[ScriptWitnessV0] = None
|
||||
}
|
||||
|
||||
case class P2PKSpendingInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: P2PKScriptPubKey,
|
||||
signer: Sign,
|
||||
hashType: HashType)
|
||||
extends RawScriptUTXOSpendingInfo {
|
||||
require(scriptPubKey.publicKey == signer.publicKey,
|
||||
"Signer pubkey must match ScriptPubKey")
|
||||
|
||||
override val signers: Vector[Sign] = Vector(signer)
|
||||
}
|
||||
|
||||
case class P2PKHSpendingInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: P2PKHScriptPubKey,
|
||||
signer: Sign,
|
||||
hashType: HashType)
|
||||
extends RawScriptUTXOSpendingInfo {
|
||||
require(scriptPubKey == P2PKHScriptPubKey(signer.publicKey),
|
||||
"Signer pubkey must match ScriptPubKey")
|
||||
|
||||
override val signers: Vector[Sign] = Vector(signer)
|
||||
}
|
||||
|
||||
case class MultiSignatureSpendingInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: MultiSignatureScriptPubKey,
|
||||
signers: Vector[Sign],
|
||||
hashType: HashType
|
||||
) extends RawScriptUTXOSpendingInfo
|
||||
|
||||
case class LockTimeSpendingInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: LockTimeScriptPubKey,
|
||||
signers: Vector[Sign],
|
||||
hashType: HashType
|
||||
) extends RawScriptUTXOSpendingInfo
|
||||
|
||||
/** This is the case where we are spending a [[org.bitcoins.core.protocol.script.WitnessScriptPubKeyV0 witness v0 script]] */
|
||||
case class SegwitV0NativeUTXOSpendingInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
|
@ -229,21 +295,31 @@ case class UnassignedSegwitNativeUTXOSpendingInfo(
|
|||
override val scriptWitnessOpt: Option[ScriptWitness] = Some(scriptWitness)
|
||||
}
|
||||
|
||||
sealed trait P2SHSpendingInfo extends BitcoinUTXOSpendingInfo {
|
||||
override def outPoint: TransactionOutPoint
|
||||
override def amount: CurrencyUnit
|
||||
override def scriptPubKey: P2SHScriptPubKey
|
||||
override def signers: Seq[Sign]
|
||||
override def hashType: HashType
|
||||
def redeemScript: ScriptPubKey
|
||||
}
|
||||
|
||||
/** This is the case were we are attempting to spend a [[org.bitcoins.core.protocol.script.P2SHScriptPubKey p2sh spk]] */
|
||||
case class P2SHSpendingInfo(
|
||||
case class P2SHNoNestSpendingInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: P2SHScriptPubKey,
|
||||
signers: Seq[Sign],
|
||||
hashType: HashType,
|
||||
redeemScript: ScriptPubKey)
|
||||
extends BitcoinUTXOSpendingInfo {
|
||||
redeemScript: NonWitnessScriptPubKey)
|
||||
extends P2SHSpendingInfo {
|
||||
require(
|
||||
P2SHScriptPubKey(redeemScript) == output.scriptPubKey,
|
||||
s"Given redeem script did not match hash in output script, " +
|
||||
s"got=${P2SHScriptPubKey(redeemScript).scriptHash.hex}, " +
|
||||
s"expected=${scriptPubKey.scriptHash.hex}"
|
||||
)
|
||||
require(!redeemScript.isInstanceOf[P2SHScriptPubKey], "Illegal P2SH nesting")
|
||||
|
||||
override val redeemScriptOpt: Option[ScriptPubKey] = Some(redeemScript)
|
||||
|
||||
|
@ -261,7 +337,7 @@ case class P2SHNestedSegwitV0UTXOSpendingInfo(
|
|||
hashType: HashType,
|
||||
redeemScript: WitnessScriptPubKeyV0,
|
||||
scriptWitness: ScriptWitnessV0)
|
||||
extends BitcoinUTXOSpendingInfo {
|
||||
extends P2SHSpendingInfo {
|
||||
require(
|
||||
P2SHScriptPubKey(redeemScript) == output.scriptPubKey,
|
||||
s"Given redeem script did not match hash in output script, " +
|
||||
|
|
|
@ -139,7 +139,7 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
p2sh = P2SHScriptPubKey(randomScriptPubKey)
|
||||
} yield (p2sh, privKeys)
|
||||
|
||||
def emptyScriptPubKey: Gen[(ScriptPubKey, Seq[ECPrivateKey])] =
|
||||
def emptyScriptPubKey: Gen[(EmptyScriptPubKey.type, Seq[ECPrivateKey])] =
|
||||
(EmptyScriptPubKey, Nil)
|
||||
|
||||
/** Creates a basic version 0 P2WPKH scriptpubkey */
|
||||
|
@ -239,6 +239,19 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
)
|
||||
}
|
||||
|
||||
def nonWitnessScriptPubKey: Gen[(NonWitnessScriptPubKey, Seq[ECPrivateKey])] = {
|
||||
Gen.oneOf(
|
||||
p2pkScriptPubKey.map(privKeyToSeq),
|
||||
p2pkhScriptPubKey.map(privKeyToSeq),
|
||||
multiSigScriptPubKey,
|
||||
emptyScriptPubKey,
|
||||
cltvScriptPubKey,
|
||||
csvScriptPubKey,
|
||||
p2shScriptPubKey,
|
||||
witnessCommitment
|
||||
)
|
||||
}
|
||||
|
||||
/** Generates an arbitrary `ScriptSignature` */
|
||||
def scriptSignature: Gen[ScriptSignature] = {
|
||||
Gen.oneOf(
|
||||
|
@ -645,9 +658,8 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
} yield (scriptSig, scriptPubKey, Seq(privateKey))
|
||||
|
||||
/** Simply converts one private key in the generator to a sequence of private keys */
|
||||
private def privKeyToSeq(tuple: (ScriptPubKey, ECPrivateKey)): (
|
||||
ScriptPubKey,
|
||||
Seq[ECPrivateKey]) = {
|
||||
private def privKeyToSeq[T](
|
||||
tuple: (T, ECPrivateKey)): (T, Seq[ECPrivateKey]) = {
|
||||
val (s, key) = tuple
|
||||
(s, Seq(key))
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue