mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-15 12:20:06 +01:00
InputInfo Refactor (#1400)
Added OutputReference and introduced public key computation on SPKs Made InputInfo into an ADT Made UTXOSpendingInfo use InputInfo Replaced UTXOSpendingInfo with NewSpendingInfo and got non-test things compiling Made aliases for (NewSpendingInfo/NewSpendingInfoFull/NewSpendingInfoSingle)[InputInfo] Got rid of source code mention of UTXOSpendingInfo sbt compile passes tests compile Fixed all coreTest tests Renamed to UTXOInfo Some cleanup Moved redeem script and script witness accessors to companion object Responded to code review Added OutputReference and scaladocs Moved p2pkhPreImageOpt downstream and generalized to hashPreImages Fixed adding-spks.md Fixed psbts.md and txbuilder.md Renamed UTXOInfo Apply renaming to docs
This commit is contained in:
parent
2869c59a85
commit
aee88684f2
33 changed files with 1777 additions and 2368 deletions
|
@ -28,7 +28,7 @@ class WalletGUIModel() {
|
|||
taskRunner.run(
|
||||
caption = "Get New Address",
|
||||
op = {
|
||||
ConsoleCli.exec(GetNewAddress,Config.empty) match {
|
||||
ConsoleCli.exec(GetNewAddress, Config.empty) match {
|
||||
case Success(commandReturn) => address.value = commandReturn
|
||||
case Failure(err) => throw err
|
||||
}
|
||||
|
@ -46,8 +46,7 @@ class WalletGUIModel() {
|
|||
taskRunner.run(
|
||||
caption = s"Send $amount to $address",
|
||||
op = {
|
||||
ConsoleCli.exec(
|
||||
SendToAddress(BitcoinAddress(address).get,
|
||||
ConsoleCli.exec(SendToAddress(BitcoinAddress(address).get,
|
||||
Bitcoins(BigDecimal(amount)),
|
||||
satoshisPerVirtualByte = None),
|
||||
Config.empty) match {
|
||||
|
|
|
@ -50,7 +50,7 @@ case class BitcoinSAppConfig(
|
|||
}
|
||||
|
||||
def rpcPortOpt: Option[Int] = {
|
||||
if(serverConf.hasPath("rpcport")) {
|
||||
if (serverConf.hasPath("rpcport")) {
|
||||
Some(serverConf.getInt("rpcport"))
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -41,10 +41,11 @@ object Main extends App {
|
|||
}
|
||||
BitcoinSAppConfig(datadirPath)
|
||||
}
|
||||
|
||||
val rpcPortOpt: Option[Int] = {
|
||||
val portOpt = argsWithIndex.find(_._1.toLowerCase == "--rpcport")
|
||||
portOpt.map {
|
||||
case (_,idx) => args(idx+1).toInt
|
||||
case (_, idx) => args(idx + 1).toInt
|
||||
}
|
||||
}
|
||||
private val logger = HttpLoggerImpl(conf.nodeConf).getLogger
|
||||
|
@ -84,13 +85,18 @@ object Main extends App {
|
|||
val coreRoutes = CoreRoutes(Core)
|
||||
val server = rpcPortOpt match {
|
||||
case Some(rpcport) =>
|
||||
Server(nodeConf, Seq(walletRoutes, nodeRoutes, chainRoutes, coreRoutes), rpcport = rpcport)
|
||||
Server(nodeConf,
|
||||
Seq(walletRoutes, nodeRoutes, chainRoutes, coreRoutes),
|
||||
rpcport = rpcport)
|
||||
case None =>
|
||||
conf.rpcPortOpt match {
|
||||
case Some(rpcport) =>
|
||||
Server(nodeConf, Seq(walletRoutes, nodeRoutes, chainRoutes, coreRoutes), rpcport)
|
||||
Server(nodeConf,
|
||||
Seq(walletRoutes, nodeRoutes, chainRoutes, coreRoutes),
|
||||
rpcport)
|
||||
case None =>
|
||||
Server(nodeConf, Seq(walletRoutes, nodeRoutes, chainRoutes, coreRoutes))
|
||||
Server(nodeConf,
|
||||
Seq(walletRoutes, nodeRoutes, chainRoutes, coreRoutes))
|
||||
}
|
||||
}
|
||||
server.start()
|
||||
|
|
|
@ -14,8 +14,10 @@ import org.bitcoins.db.AppConfig
|
|||
|
||||
import scala.concurrent.Future
|
||||
|
||||
case class Server(conf: AppConfig, handlers: Seq[ServerRoute], rpcport: Int = 9999)(
|
||||
implicit system: ActorSystem)
|
||||
case class Server(
|
||||
conf: AppConfig,
|
||||
handlers: Seq[ServerRoute],
|
||||
rpcport: Int = 9999)(implicit system: ActorSystem)
|
||||
extends HttpLogger {
|
||||
implicit private val config: AppConfig = conf
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.bitcoins.core.psbt
|
||||
|
||||
import org.bitcoins.core.psbt.OutputPSBTRecord.{RedeemScript, WitnessScript}
|
||||
import org.bitcoins.core.wallet.utxo.BitcoinUTXOSpendingInfoFull
|
||||
import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams}
|
||||
import org.bitcoins.testkit.core.gen._
|
||||
import org.bitcoins.testkit.util.BitcoinSAsyncTest
|
||||
|
||||
|
@ -69,7 +69,8 @@ class PSBTTest extends BitcoinSAsyncTest {
|
|||
.addUTXOToInput(txOpt.get, index)
|
||||
.addSigHashTypeToInput(utxo.hashType, index)
|
||||
|
||||
(utxo.redeemScriptOpt, utxo.scriptWitnessOpt) match {
|
||||
(InputInfo.getRedeemScript(utxo.inputInfo),
|
||||
InputInfo.getScriptWitness(utxo.inputInfo)) match {
|
||||
case (Some(redeemScript), Some(scriptWitness)) =>
|
||||
partUpdatedPsbt
|
||||
.addRedeemOrWitnessScriptToInput(redeemScript, index)
|
||||
|
@ -94,7 +95,7 @@ class PSBTTest extends BitcoinSAsyncTest {
|
|||
psbtWithBuilderF.flatMap {
|
||||
case (psbtNoSigs, utxos) =>
|
||||
val infos = utxos.toVector.zipWithIndex.map {
|
||||
case (utxo: BitcoinUTXOSpendingInfoFull, index) =>
|
||||
case (utxo: ScriptSignatureParams[InputInfo], index) =>
|
||||
(index, utxo)
|
||||
}
|
||||
val signedPSBTF = infos.foldLeft(Future.successful(psbtNoSigs)) {
|
||||
|
|
|
@ -13,7 +13,7 @@ import org.bitcoins.core.psbt.InputPSBTRecord.{
|
|||
}
|
||||
import org.bitcoins.core.psbt.PSBTGlobalKeyId.XPubKeyKeyId
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.core.wallet.utxo.ConditionalPath
|
||||
import org.bitcoins.core.wallet.utxo.{ConditionalPath, InputInfo}
|
||||
import org.bitcoins.crypto.{DoubleSha256Digest, ECPublicKey, Sha256Digest, Sign}
|
||||
import org.bitcoins.testkit.util.BitcoinSAsyncTest
|
||||
import scodec.bits._
|
||||
|
@ -251,31 +251,38 @@ class PSBTUnitTest extends BitcoinSAsyncTest {
|
|||
// PSBT with one P2WSH input of a 2-of-2 multisig. witnessScript, keypaths, and global xpubs are available. Contains no signatures. Outputs filled.
|
||||
val psbt = PSBT(
|
||||
"70736274ff01005202000000019dfc6628c26c5899fe1bd3dc338665bfd55d7ada10f6220973df2d386dec12760100000000ffffffff01f03dcd1d000000001600147b3a00bfdc14d27795c2b74901d09da6ef133579000000004f01043587cf02da3fd0088000000097048b1ad0445b1ec8275517727c87b4e4ebc18a203ffa0f94c01566bd38e9000351b743887ee1d40dc32a6043724f2d6459b3b5a4d73daec8fbae0472f3bc43e20cd90c6a4fae000080000000804f01043587cf02da3fd00880000001b90452427139cd78c2cff2444be353cd58605e3e513285e528b407fae3f6173503d30a5e97c8adbc557dac2ad9a7e39c1722ebac69e668b6f2667cc1d671c83cab0cd90c6a4fae000080010000800001012b0065cd1d000000002200202c5486126c4978079a814e13715d65f36459e4d6ccaded266d0508645bafa6320105475221029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c88712103372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b52ae2206029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c887110d90c6a4fae0000800000008000000000220603372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b10d90c6a4fae0000800100008000000000002202039eff1f547a1d5f92dfa2ba7af6ac971a4bd03ba4a734b03156a256b8ad3a1ef910ede45cc500000080000000800100008000")
|
||||
val dummySigners = getDummySigners(2)
|
||||
val dummySigners = Vector(
|
||||
Sign.dummySign(ECPublicKey(
|
||||
"029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c8871")),
|
||||
Sign.dummySign(
|
||||
ECPublicKey(
|
||||
"03372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b"))
|
||||
)
|
||||
val spendingInfo =
|
||||
psbt.getUTXOSpendingInfoUsingSigners(index = 0, dummySigners)
|
||||
psbt.getSpendingInfoUsingSigners(index = 0, dummySigners)
|
||||
|
||||
assert(spendingInfo.outPoint == psbt.transaction.inputs.head.previousOutput)
|
||||
assert(spendingInfo.amount == Satoshis(500000000))
|
||||
assert(
|
||||
spendingInfo.scriptPubKey == P2WSHWitnessSPKV0.fromHash(Sha256Digest(
|
||||
spendingInfo.output.scriptPubKey == P2WSHWitnessSPKV0.fromHash(
|
||||
Sha256Digest(
|
||||
"2c5486126c4978079a814e13715d65f36459e4d6ccaded266d0508645bafa632")))
|
||||
assert(spendingInfo.signers == dummySigners)
|
||||
assert(spendingInfo.hashType == HashType.sigHashAll)
|
||||
assert(spendingInfo.redeemScriptOpt.isEmpty)
|
||||
assert(InputInfo.getRedeemScript(spendingInfo.inputInfo).isEmpty)
|
||||
assert(
|
||||
spendingInfo.scriptWitnessOpt.contains(
|
||||
P2WSHWitnessV0(RawScriptPubKey.fromAsmHex(
|
||||
InputInfo
|
||||
.getScriptWitness(spendingInfo.inputInfo)
|
||||
.contains(P2WSHWitnessV0(RawScriptPubKey.fromAsmHex(
|
||||
"5221029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c88712103372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b52ae"))))
|
||||
assert(spendingInfo.conditionalPath == ConditionalPath.NoConditionsLeft)
|
||||
assert(spendingInfo.conditionalPath == ConditionalPath.NoCondition)
|
||||
}
|
||||
|
||||
it must "fail to create a valid UTXOSpendingInfo from a PSBTInputMap with insufficient data" in {
|
||||
val psbt1 = PSBT(
|
||||
"70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0000")
|
||||
assertThrows[UnsupportedOperationException](
|
||||
psbt1.getUTXOSpendingInfoUsingSigners(index = 0,
|
||||
getDummySigners(size = 1)))
|
||||
psbt1.getSpendingInfoUsingSigners(index = 0, getDummySigners(size = 1)))
|
||||
}
|
||||
|
||||
it must "fail to create an Unknown PSBTRecord from with a known KeyId" in {
|
||||
|
|
|
@ -14,12 +14,13 @@ import org.bitcoins.core.script.interpreter.ScriptInterpreter
|
|||
import org.bitcoins.core.wallet.builder.BitcoinTxBuilder.UTXOMap
|
||||
import org.bitcoins.core.wallet.fee.{SatoshisPerByte, SatoshisPerVirtualByte}
|
||||
import org.bitcoins.core.wallet.utxo.{
|
||||
BitcoinUTXOSpendingInfoFull,
|
||||
BitcoinUTXOSpendingInfoSingle,
|
||||
ConditionalPath,
|
||||
LockTimeSpendingInfoFull,
|
||||
UTXOSpendingInfo,
|
||||
UnassignedSegwitNativeUTXOSpendingInfo
|
||||
ECSignatureParams,
|
||||
InputInfo,
|
||||
InputSigningInfo,
|
||||
LockTimeInputInfo,
|
||||
ScriptSignatureParams,
|
||||
UnassignedSegwitNativeInputInfo
|
||||
}
|
||||
import org.bitcoins.crypto.{DoubleSha256DigestBE, ECPrivateKey}
|
||||
import org.bitcoins.testkit.Implicits._
|
||||
|
@ -50,14 +51,17 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
outputs = Seq(creditingOutput),
|
||||
lockTime = tc.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
val utxo = BitcoinUTXOSpendingInfoFull(
|
||||
val utxo = ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
signers = Vector(privKey),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
hashType = HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
signer = privKey,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
|
||||
val listF =
|
||||
|
@ -91,14 +95,17 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
outputs = Vector(creditingOutput),
|
||||
lockTime = tc.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
val utxo = BitcoinUTXOSpendingInfoFull(
|
||||
val utxo = ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
signers = Vector(privKey),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
hashType = HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
signer = privKey,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
val utxoMap: UTXOMap = Map(outPoint -> utxo)
|
||||
val feeUnit = SatoshisPerVirtualByte(Satoshis.one)
|
||||
|
@ -122,14 +129,17 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
outputs = Vector(creditingOutput),
|
||||
lockTime = tc.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
val utxo = BitcoinUTXOSpendingInfoFull(
|
||||
val utxo = ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
signers = Vector(privKey),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
hashType = HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
signer = privKey,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
val utxoMap: UTXOMap = Map(outPoint -> utxo)
|
||||
val feeUnit = SatoshisPerVirtualByte(Satoshis(-1))
|
||||
|
@ -152,14 +162,19 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
Vector(creditingOutput),
|
||||
tc.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
val utxo = BitcoinUTXOSpendingInfoFull(outPoint,
|
||||
creditingOutput,
|
||||
Vector(privKey),
|
||||
None,
|
||||
None,
|
||||
HashType.sigHashAll,
|
||||
conditionalPath =
|
||||
ConditionalPath.NoConditionsLeft)
|
||||
val utxo =
|
||||
ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
privKey,
|
||||
HashType.sigHashAll
|
||||
)
|
||||
val utxoMap: UTXOMap = Map(outPoint -> utxo)
|
||||
val feeUnit = SatoshisPerVirtualByte(currencyUnit = Satoshis(1))
|
||||
val txBuilder = BitcoinTxBuilder(destinations = destinations,
|
||||
|
@ -168,7 +183,7 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
changeSPK = EmptyScriptPubKey,
|
||||
network = TestNet3)
|
||||
//trivially false
|
||||
val f = (_: Seq[BitcoinUTXOSpendingInfoFull], _: Transaction) => false
|
||||
val f = (_: Seq[ScriptSignatureParams[InputInfo]], _: Transaction) => false
|
||||
val resultFuture = txBuilder.flatMap(_.sign(f))
|
||||
recoverToSucceededIf[IllegalArgumentException] {
|
||||
resultFuture
|
||||
|
@ -188,25 +203,20 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
outputs = Vector(creditingOutput),
|
||||
lockTime = tc.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
val utxo = BitcoinUTXOSpendingInfoFull(
|
||||
|
||||
val utxo = ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
signers = Vector(privKey),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
hashType = HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
signer = privKey,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
val utxoMap: UTXOMap = Map(outPoint -> utxo)
|
||||
val utxoSpendingInfo = BitcoinUTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
signers = Vector(privKey),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
hashType = HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
|
||||
val feeUnit = SatoshisPerVirtualByte(Satoshis.one)
|
||||
val txBuilderMap = BitcoinTxBuilder(destinations = destinations,
|
||||
|
@ -215,7 +225,7 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
changeSPK = EmptyScriptPubKey,
|
||||
network = TestNet3)
|
||||
val txBuilderTuple = BitcoinTxBuilder(destinations = destinations,
|
||||
utxos = Vector(utxoSpendingInfo),
|
||||
utxos = Vector(utxo),
|
||||
feeRate = feeUnit,
|
||||
changeSPK = EmptyScriptPubKey,
|
||||
network = TestNet3)
|
||||
|
@ -235,27 +245,27 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
outputs = Vector(creditingOutput),
|
||||
lockTime = tc.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
assertThrows[IllegalArgumentException] {
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
val inputInfo = InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
signers = Vector(privKey),
|
||||
redeemScriptOpt = Some(EmptyScriptPubKey),
|
||||
scriptWitnessOpt = None,
|
||||
hashType = HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
conditionalPath = ConditionalPath.NoCondition
|
||||
)
|
||||
|
||||
assertThrows[RuntimeException] {
|
||||
ScriptSignatureParams(
|
||||
inputInfo = inputInfo,
|
||||
signer = privKey,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
BitcoinUTXOSpendingInfoSingle(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
assertThrows[RuntimeException] {
|
||||
ECSignatureParams(
|
||||
inputInfo = inputInfo,
|
||||
signer = privKey,
|
||||
redeemScriptOpt = Some(EmptyScriptPubKey),
|
||||
scriptWitnessOpt = None,
|
||||
hashType = HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -269,26 +279,32 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
tc.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
assertThrows[IllegalArgumentException] {
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
outPoint,
|
||||
creditingOutput,
|
||||
Vector(privKey),
|
||||
None,
|
||||
Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
privKey,
|
||||
HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
BitcoinUTXOSpendingInfoSingle(
|
||||
outPoint,
|
||||
creditingOutput,
|
||||
ECSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
privKey,
|
||||
None,
|
||||
Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -303,14 +319,17 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
Vector(creditingOutput),
|
||||
tc.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
val utxo = BitcoinUTXOSpendingInfoFull(
|
||||
outPoint,
|
||||
creditingOutput,
|
||||
Vector(privKey),
|
||||
None,
|
||||
Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
val utxo = ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
privKey,
|
||||
HashType.sigHashAll
|
||||
)
|
||||
val utxoMap: UTXOMap = Map(outPoint -> utxo)
|
||||
|
||||
|
@ -337,14 +356,17 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
lockTime = tc.lockTime)
|
||||
val outPoint =
|
||||
TransactionOutPoint(txId = creditingTx.txId, vout = UInt32.zero)
|
||||
val utxo = BitcoinUTXOSpendingInfoFull(
|
||||
val utxo = ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
signers = Vector(privKey),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
hashType = HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
signer = privKey,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
val utxoMap: UTXOMap = Map(outPoint -> utxo)
|
||||
|
||||
|
@ -370,27 +392,27 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
lockTime = tc.lockTime)
|
||||
val outPoint =
|
||||
TransactionOutPoint(txId = creditingTx.txId, vout = UInt32.zero)
|
||||
assertThrows[IllegalArgumentException] {
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
val inputInfo = InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
signers = Vector(privKey),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
hashType = HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
conditionalPath = ConditionalPath.NoCondition
|
||||
)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
ScriptSignatureParams(
|
||||
inputInfo = inputInfo,
|
||||
signer = privKey,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
BitcoinUTXOSpendingInfoSingle(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
ECSignatureParams(
|
||||
inputInfo = inputInfo,
|
||||
signer = privKey,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
hashType = HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -404,26 +426,32 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
tc.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
assertThrows[IllegalArgumentException] {
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
outPoint,
|
||||
creditingOutput,
|
||||
Vector(privKey),
|
||||
None,
|
||||
Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
privKey,
|
||||
HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
BitcoinUTXOSpendingInfoSingle(
|
||||
outPoint,
|
||||
creditingOutput,
|
||||
ECSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
privKey,
|
||||
None,
|
||||
Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -437,13 +465,14 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
CLTVScriptPubKey(ScriptNumber(lockTime),
|
||||
P2PKScriptPubKey(fundingPrivKey.publicKey))
|
||||
|
||||
val cltvSpendingInfo = LockTimeSpendingInfoFull(
|
||||
TransactionOutPoint(DoubleSha256DigestBE.empty, UInt32.zero),
|
||||
val cltvSpendingInfo = ScriptSignatureParams(
|
||||
LockTimeInputInfo(TransactionOutPoint(DoubleSha256DigestBE.empty,
|
||||
UInt32.zero),
|
||||
Bitcoins.one,
|
||||
cltvSPK,
|
||||
ConditionalPath.NoCondition),
|
||||
Vector(fundingPrivKey),
|
||||
HashType.sigHashAll,
|
||||
ConditionalPath.NoConditionsLeft
|
||||
HashType.sigHashAll
|
||||
)
|
||||
|
||||
val txBuilderF =
|
||||
|
@ -471,13 +500,14 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
CLTVScriptPubKey(ScriptNumber(lockTime),
|
||||
P2PKScriptPubKey(fundingPrivKey.publicKey))
|
||||
|
||||
val cltvSpendingInfo = LockTimeSpendingInfoFull(
|
||||
TransactionOutPoint(DoubleSha256DigestBE.empty, UInt32.zero),
|
||||
val cltvSpendingInfo = ScriptSignatureParams(
|
||||
LockTimeInputInfo(TransactionOutPoint(DoubleSha256DigestBE.empty,
|
||||
UInt32.zero),
|
||||
Bitcoins.one,
|
||||
cltvSPK,
|
||||
ConditionalPath.NoCondition),
|
||||
Vector(fundingPrivKey),
|
||||
HashType.sigHashAll,
|
||||
ConditionalPath.NoConditionsLeft
|
||||
HashType.sigHashAll
|
||||
)
|
||||
|
||||
val txBuilderF =
|
||||
|
@ -510,22 +540,24 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
CLTVScriptPubKey(ScriptNumber(lockTime2),
|
||||
P2PKScriptPubKey(fundingPrivKey2.publicKey))
|
||||
|
||||
val cltvSpendingInfo1 = LockTimeSpendingInfoFull(
|
||||
TransactionOutPoint(DoubleSha256DigestBE.empty, UInt32.zero),
|
||||
val cltvSpendingInfo1 = ScriptSignatureParams(
|
||||
LockTimeInputInfo(TransactionOutPoint(DoubleSha256DigestBE.empty,
|
||||
UInt32.zero),
|
||||
Bitcoins.one,
|
||||
cltvSPK1,
|
||||
ConditionalPath.NoCondition),
|
||||
Vector(fundingPrivKey1),
|
||||
HashType.sigHashAll,
|
||||
ConditionalPath.NoConditionsLeft
|
||||
HashType.sigHashAll
|
||||
)
|
||||
|
||||
val cltvSpendingInfo2 = LockTimeSpendingInfoFull(
|
||||
TransactionOutPoint(DoubleSha256DigestBE.empty, UInt32.one),
|
||||
val cltvSpendingInfo2 = ScriptSignatureParams(
|
||||
LockTimeInputInfo(TransactionOutPoint(DoubleSha256DigestBE.empty,
|
||||
UInt32.one),
|
||||
Bitcoins.one,
|
||||
cltvSPK2,
|
||||
ConditionalPath.NoCondition),
|
||||
Vector(fundingPrivKey2),
|
||||
HashType.sigHashAll,
|
||||
ConditionalPath.NoConditionsLeft
|
||||
HashType.sigHashAll
|
||||
)
|
||||
|
||||
val txBuilderF =
|
||||
|
@ -550,14 +582,17 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
val pubKey = privKey.publicKey
|
||||
|
||||
val spendingInfo =
|
||||
UnassignedSegwitNativeUTXOSpendingInfo(
|
||||
ScriptSignatureParams(
|
||||
UnassignedSegwitNativeInputInfo(
|
||||
outPoint = outPoint,
|
||||
amount = Bitcoins.one + CurrencyUnits.oneMBTC,
|
||||
scriptPubKey = P2WPKHWitnessSPKV0(pubKey),
|
||||
signers = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
scriptWitness = P2WPKHWitnessV0(pubKey),
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
Vector(pubKey)
|
||||
),
|
||||
signers = Vector(privKey),
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
|
||||
val txBuilderF = BitcoinTxBuilder(
|
||||
|
@ -575,7 +610,7 @@ class BitcoinTxBuilderTest extends BitcoinSAsyncTest {
|
|||
|
||||
def verifyScript(
|
||||
tx: Transaction,
|
||||
utxos: Vector[UTXOSpendingInfo]): Boolean = {
|
||||
utxos: Vector[InputSigningInfo[InputInfo]]): Boolean = {
|
||||
val programs: Vector[PreExecutionScriptProgram] =
|
||||
tx.inputs.zipWithIndex.toVector.map {
|
||||
case (input: TransactionInput, idx: Int) =>
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.bitcoins.core.wallet.builder
|
|||
|
||||
import org.bitcoins.core.config.RegTest
|
||||
import org.bitcoins.core.currency._
|
||||
import org.bitcoins.core.number.{Int64, UInt32}
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.script.{
|
||||
EmptyScriptPubKey,
|
||||
EmptyScriptSignature,
|
||||
|
@ -18,7 +18,7 @@ import org.bitcoins.core.protocol.transaction.{
|
|||
import org.bitcoins.core.script.control.OP_RETURN
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||
import org.bitcoins.core.wallet.utxo.EmptySpendingInfo
|
||||
import org.bitcoins.core.wallet.utxo.{EmptyInputInfo, ScriptSignatureParams}
|
||||
import org.bitcoins.crypto.DoubleSha256DigestBE
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
|
@ -54,8 +54,9 @@ class TxBuilderTest extends BitcoinSUnitTest {
|
|||
private val txBuilderF = BitcoinTxBuilder(
|
||||
Vector(output),
|
||||
Vector(
|
||||
EmptySpendingInfo(outPoint,
|
||||
Bitcoins.one + Bitcoins.one,
|
||||
ScriptSignatureParams(EmptyInputInfo(outPoint,
|
||||
Bitcoins.one + Bitcoins.one),
|
||||
Vector.empty,
|
||||
HashType.sigHashAll)),
|
||||
SatoshisPerVirtualByte(Satoshis.one),
|
||||
EmptyScriptPubKey,
|
||||
|
|
|
@ -42,11 +42,13 @@ import org.bitcoins.core.script.interpreter.ScriptInterpreter
|
|||
import org.bitcoins.core.wallet.builder.BitcoinTxBuilder
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||
import org.bitcoins.core.wallet.utxo.{
|
||||
P2WPKHV0SpendingInfo,
|
||||
P2WSHV0SpendingInfoFull,
|
||||
UTXOSpendingInfo,
|
||||
UTXOSpendingInfoSingle,
|
||||
UnassignedSegwitNativeUTXOSpendingInfo
|
||||
ECSignatureParams,
|
||||
InputInfo,
|
||||
InputSigningInfo,
|
||||
P2WPKHV0InputInfo,
|
||||
P2WSHV0InputInfo,
|
||||
ScriptSignatureParams,
|
||||
UnassignedSegwitNativeInputInfo
|
||||
}
|
||||
import org.bitcoins.crypto.ECDigitalSignature
|
||||
import org.bitcoins.testkit.core.gen.{
|
||||
|
@ -72,14 +74,17 @@ class SignerTest extends BitcoinSAsyncTest {
|
|||
it should "fail to sign a UnassignedSegwit UTXO" in {
|
||||
val p2wpkh = GenUtil.sample(CreditingTxGen.p2wpkhOutput)
|
||||
val tx = GenUtil.sample(TransactionGenerators.baseTransaction)
|
||||
val spendingInfo = UnassignedSegwitNativeUTXOSpendingInfo(
|
||||
val spendingInfo = ScriptSignatureParams(
|
||||
UnassignedSegwitNativeInputInfo(
|
||||
p2wpkh.outPoint,
|
||||
p2wpkh.amount,
|
||||
p2wpkh.scriptPubKey.asInstanceOf[WitnessScriptPubKey],
|
||||
p2wpkh.output.scriptPubKey.asInstanceOf[WitnessScriptPubKey],
|
||||
InputInfo.getScriptWitness(p2wpkh.inputInfo).get,
|
||||
p2wpkh.conditionalPath,
|
||||
p2wpkh.signers.map(_.publicKey)
|
||||
),
|
||||
p2wpkh.signers,
|
||||
p2wpkh.hashType,
|
||||
p2wpkh.scriptWitnessOpt.get,
|
||||
p2wpkh.conditionalPath
|
||||
p2wpkh.hashType
|
||||
)
|
||||
assertThrows[UnsupportedOperationException](
|
||||
BitcoinSigner.sign(spendingInfo, tx, isDummySignature = false))
|
||||
|
@ -96,7 +101,7 @@ class SignerTest extends BitcoinSAsyncTest {
|
|||
val dumbSpendingInfo = GenUtil.sample(CreditingTxGen.output)
|
||||
val p2wpkh = GenUtil
|
||||
.sample(CreditingTxGen.p2wpkhOutput)
|
||||
.asInstanceOf[P2WPKHV0SpendingInfo]
|
||||
.asInstanceOf[ScriptSignatureParams[P2WPKHV0InputInfo]]
|
||||
val tx = GenUtil.sample(TransactionGenerators.baseTransaction)
|
||||
recoverToSucceededIf[IllegalArgumentException] {
|
||||
P2WPKHSigner.sign(dumbSpendingInfo, tx, isDummySignature = false, p2wpkh)
|
||||
|
@ -107,7 +112,7 @@ class SignerTest extends BitcoinSAsyncTest {
|
|||
val dumbSpendingInfo = GenUtil.sample(CreditingTxGen.output)
|
||||
val p2wsh = GenUtil
|
||||
.sample(CreditingTxGen.p2wshOutput)
|
||||
.asInstanceOf[P2WSHV0SpendingInfoFull]
|
||||
.asInstanceOf[ScriptSignatureParams[P2WSHV0InputInfo]]
|
||||
val tx = GenUtil.sample(TransactionGenerators.baseTransaction)
|
||||
recoverToSucceededIf[IllegalArgumentException] {
|
||||
P2WSHSigner.sign(dumbSpendingInfo, tx, isDummySignature = false, p2wsh)
|
||||
|
@ -131,7 +136,7 @@ class SignerTest extends BitcoinSAsyncTest {
|
|||
signedTx <- builder.sign
|
||||
|
||||
singleSigs: Vector[Vector[ECDigitalSignature]] <- {
|
||||
val singleInfosVec: Vector[Vector[UTXOSpendingInfoSingle]] =
|
||||
val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] =
|
||||
creditingTxsInfos.toVector.map(_.toSingles)
|
||||
val sigVecFs = singleInfosVec.map { singleInfos =>
|
||||
val sigFs = singleInfos.map { singleInfo =>
|
||||
|
@ -157,7 +162,8 @@ class SignerTest extends BitcoinSAsyncTest {
|
|||
val (info, index) = infoAndIndexOpt.get
|
||||
val sigs = singleSigs(index)
|
||||
|
||||
val expectedSigs = if (info.scriptWitnessOpt.isEmpty) {
|
||||
val expectedSigs =
|
||||
if (InputInfo.getScriptWitness(info.inputInfo).isEmpty) {
|
||||
input.scriptSignature.signatures
|
||||
} else {
|
||||
signedTx
|
||||
|
@ -179,7 +185,9 @@ class SignerTest extends BitcoinSAsyncTest {
|
|||
}
|
||||
}
|
||||
|
||||
def inputIndex(spendingInfo: UTXOSpendingInfo, tx: Transaction): Int = {
|
||||
def inputIndex(
|
||||
spendingInfo: InputSigningInfo[InputInfo],
|
||||
tx: Transaction): Int = {
|
||||
tx.inputs.zipWithIndex
|
||||
.find(_._1.previousOutput == spendingInfo.outPoint) match {
|
||||
case Some((_, index)) => index
|
||||
|
@ -192,7 +200,7 @@ class SignerTest extends BitcoinSAsyncTest {
|
|||
def createProgram(
|
||||
tx: Transaction,
|
||||
idx: Int,
|
||||
utxo: UTXOSpendingInfo): PreExecutionScriptProgram = {
|
||||
utxo: InputSigningInfo[InputInfo]): PreExecutionScriptProgram = {
|
||||
val output = utxo.output
|
||||
|
||||
val spk = output.scriptPubKey
|
||||
|
@ -237,7 +245,7 @@ class SignerTest extends BitcoinSAsyncTest {
|
|||
|
||||
def verifyScripts(
|
||||
tx: Transaction,
|
||||
utxos: Vector[UTXOSpendingInfo]): Boolean = {
|
||||
utxos: Vector[InputSigningInfo[InputInfo]]): Boolean = {
|
||||
val programs: Vector[PreExecutionScriptProgram] =
|
||||
tx.inputs.zipWithIndex.toVector.map {
|
||||
case (input: TransactionInput, idx: Int) =>
|
||||
|
@ -263,7 +271,7 @@ class SignerTest extends BitcoinSAsyncTest {
|
|||
unsignedTx <- builder.unsignedTx
|
||||
|
||||
singleSigs: Vector[Vector[PartialSignature]] <- {
|
||||
val singleInfosVec: Vector[Vector[UTXOSpendingInfoSingle]] =
|
||||
val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] =
|
||||
creditingTxsInfos.toVector.map(_.toSingles)
|
||||
val sigVecFs = singleInfosVec.map { singleInfos =>
|
||||
val sigFs = singleInfos.map { singleInfo =>
|
||||
|
@ -292,7 +300,9 @@ class SignerTest extends BitcoinSAsyncTest {
|
|||
val idx = inputIndex(spendInfo, unsignedTx)
|
||||
psbt
|
||||
.addWitnessUTXOToInput(spendInfo.output, idx)
|
||||
.addScriptWitnessToInput(spendInfo.scriptWitnessOpt.get, idx)
|
||||
.addScriptWitnessToInput(
|
||||
InputInfo.getScriptWitness(spendInfo.inputInfo).get,
|
||||
idx)
|
||||
.addSignatures(singleSigs(idx), idx)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,216 @@
|
|||
package org.bitcoins.core.wallet.utxo
|
||||
|
||||
import org.bitcoins.core.currency.CurrencyUnits
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.script._
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
BaseTransaction,
|
||||
TransactionConstants,
|
||||
TransactionOutPoint,
|
||||
TransactionOutput
|
||||
}
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.crypto.{ECPrivateKey, ECPublicKey}
|
||||
import org.bitcoins.testkit.core.gen.{
|
||||
GenUtil,
|
||||
ScriptGenerators,
|
||||
TransactionGenerators
|
||||
}
|
||||
import org.bitcoins.testkit.util.BitcoinSAsyncTest
|
||||
|
||||
class InputInfoTest extends BitcoinSAsyncTest {
|
||||
|
||||
def randomSPK: ScriptPubKey = {
|
||||
GenUtil.sample(ScriptGenerators.scriptPubKey.map(_._1))
|
||||
}
|
||||
|
||||
def randomRawSPK: RawScriptPubKey = {
|
||||
GenUtil.sample(ScriptGenerators.rawScriptPubKey.map(_._1))
|
||||
}
|
||||
|
||||
def randomWitnessSPK: WitnessScriptPubKeyV0 = {
|
||||
GenUtil.sample(ScriptGenerators.witnessScriptPubKeyV0.map(_._1))
|
||||
}
|
||||
|
||||
behavior of "InputInfo"
|
||||
|
||||
it must "fail given no redeem script on P2SH" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
val p2sh = P2SHScriptPubKey(P2PKScriptPubKey(pubKey))
|
||||
val (creditingTx, _) = TransactionGenerators.buildCreditingTransaction(p2sh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, p2sh),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoCondition
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail given no script witness on P2SH-Segwit" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2sh = P2SHScriptPubKey(P2WPKHWitnessSPKV0(pubKey))
|
||||
val creditingOutput = TransactionOutput(CurrencyUnits.zero, p2sh)
|
||||
val creditingTx = BaseTransaction(version =
|
||||
TransactionConstants.validLockVersion,
|
||||
inputs = Nil,
|
||||
outputs = Vector(creditingOutput),
|
||||
lockTime = TransactionConstants.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, p2sh),
|
||||
redeemScriptOpt = Some(P2WPKHWitnessSPKV0(pubKey)),
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoCondition
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail given an unsupported script witness on P2SH-Segwit" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2sh = P2SHScriptPubKey(P2WPKHWitnessSPKV0(pubKey))
|
||||
val creditingOutput = TransactionOutput(CurrencyUnits.zero, p2sh)
|
||||
val creditingTx = BaseTransaction(version =
|
||||
TransactionConstants.validLockVersion,
|
||||
inputs = Nil,
|
||||
outputs = Vector(creditingOutput),
|
||||
lockTime = TransactionConstants.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[UnsupportedOperationException] {
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, p2sh),
|
||||
redeemScriptOpt = Some(P2WPKHWitnessSPKV0(pubKey)),
|
||||
scriptWitnessOpt = Some(EmptyScriptWitness),
|
||||
conditionalPath = ConditionalPath.NoCondition
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail given UnassignedWitnessScriptPubKey redeemScript" in {
|
||||
val unassingedWitnessSPK = UnassignedWitnessScriptPubKey.fromAsm(
|
||||
P2WPKHWitnessSPKV0(ECPublicKey.freshPublicKey).asm)
|
||||
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
val p2sh = P2SHScriptPubKey(P2PKScriptPubKey(pubKey))
|
||||
val (creditingTx, _) = TransactionGenerators.buildCreditingTransaction(p2sh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[RuntimeException] {
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, p2sh),
|
||||
redeemScriptOpt = Some(unassingedWitnessSPK),
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoCondition
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail given unsupported witness" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2wpkh = P2WPKHWitnessSPKV0(pubKey)
|
||||
val (creditingTx, _) =
|
||||
TransactionGenerators.buildCreditingTransaction(p2wpkh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[UnsupportedOperationException] {
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, p2wpkh),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(EmptyScriptWitness),
|
||||
conditionalPath = ConditionalPath.NoCondition
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail given no witness" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2wpkh = P2WPKHWitnessSPKV0(pubKey)
|
||||
val (creditingTx, _) =
|
||||
TransactionGenerators.buildCreditingTransaction(p2wpkh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, p2wpkh),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoCondition
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "successfully return UnassingedSegwitNativeUTXOSpendingInfoFull" in {
|
||||
val unassingedWitnessSPK = UnassignedWitnessScriptPubKey.fromAsm(
|
||||
P2WPKHWitnessSPKV0(ECPublicKey.freshPublicKey).asm)
|
||||
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2wpkh = P2WPKHWitnessSPKV0(pubKey)
|
||||
val (creditingTx, _) =
|
||||
TransactionGenerators.buildCreditingTransaction(p2wpkh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
val spendingInfo =
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, unassingedWitnessSPK),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoCondition
|
||||
)
|
||||
|
||||
val expectedSpendingInfo =
|
||||
UnassignedSegwitNativeInputInfo(
|
||||
outPoint = outPoint,
|
||||
amount = CurrencyUnits.zero,
|
||||
scriptPubKey = unassingedWitnessSPK,
|
||||
scriptWitness = EmptyScriptWitness,
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
pubKeys = Vector.empty
|
||||
)
|
||||
|
||||
assert(spendingInfo == expectedSpendingInfo)
|
||||
}
|
||||
|
||||
it should "fail given a NonStandardScriptPubKey" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
val p2pk = P2PKScriptPubKey(pubKey)
|
||||
val spk = NonStandardScriptPubKey.fromAsm(p2pk.asm)
|
||||
val (creditingTx, _) = TransactionGenerators.buildCreditingTransaction(p2pk)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[UnsupportedOperationException] {
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, spk),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoCondition
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,400 +0,0 @@
|
|||
package org.bitcoins.core.wallet.utxo
|
||||
|
||||
import org.bitcoins.core.currency.CurrencyUnits
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.script._
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
BaseTransaction,
|
||||
TransactionConstants,
|
||||
TransactionOutPoint,
|
||||
TransactionOutput
|
||||
}
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.crypto.{ECPrivateKey, ECPublicKey}
|
||||
import org.bitcoins.testkit.core.gen.{
|
||||
GenUtil,
|
||||
ScriptGenerators,
|
||||
TransactionGenerators
|
||||
}
|
||||
import org.bitcoins.testkit.util.BitcoinSAsyncTest
|
||||
|
||||
class UTXOSpendingInfoTest extends BitcoinSAsyncTest {
|
||||
|
||||
def randomSPK: ScriptPubKey = {
|
||||
GenUtil.sample(ScriptGenerators.scriptPubKey.map(_._1))
|
||||
}
|
||||
|
||||
def randomRawSPK: RawScriptPubKey = {
|
||||
GenUtil.sample(ScriptGenerators.rawScriptPubKey.map(_._1))
|
||||
}
|
||||
|
||||
def randomWitnessSPK: WitnessScriptPubKeyV0 = {
|
||||
GenUtil.sample(ScriptGenerators.witnessScriptPubKeyV0.map(_._1))
|
||||
}
|
||||
|
||||
behavior of "UTXOSpendingInfo"
|
||||
|
||||
it must "fail given a bad P2SH script" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
val p2sh = P2SHScriptPubKey(P2PKScriptPubKey(pubKey))
|
||||
val (creditingTx, _) = TransactionGenerators.buildCreditingTransaction(p2sh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
P2SHNoNestSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
amount = CurrencyUnits.zero,
|
||||
scriptPubKey = p2sh,
|
||||
signersWithPossibleExtra = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
redeemScript = randomRawSPK,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail given a bad P2SH-P2WPKH redeem script" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2sh = P2SHScriptPubKey(P2WPKHWitnessSPKV0(pubKey))
|
||||
val (creditingTx, _) = TransactionGenerators.buildCreditingTransaction(p2sh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
P2SHNestedSegwitV0UTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
amount = CurrencyUnits.zero,
|
||||
scriptPubKey = p2sh,
|
||||
signersWithPossibleExtra = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
redeemScript = randomWitnessSPK,
|
||||
scriptWitness = P2WPKHWitnessV0(pubKey),
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail given a bad P2SH-P2WPKH script witness" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2sh = P2SHScriptPubKey(P2WPKHWitnessSPKV0(pubKey))
|
||||
val creditingOutput = TransactionOutput(CurrencyUnits.zero, p2sh)
|
||||
val creditingTx = BaseTransaction(version =
|
||||
TransactionConstants.validLockVersion,
|
||||
inputs = Nil,
|
||||
outputs = Vector(creditingOutput),
|
||||
lockTime = TransactionConstants.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
P2SHNestedSegwitV0UTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
amount = CurrencyUnits.zero,
|
||||
scriptPubKey = p2sh,
|
||||
signersWithPossibleExtra = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
redeemScript = P2WPKHWitnessSPKV0(pubKey),
|
||||
scriptWitness = P2WPKHWitnessV0(ECPrivateKey.freshPrivateKey.publicKey),
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail given a bad P2SH-P2WSH redeem script" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2sh = P2SHScriptPubKey(P2WSHWitnessSPKV0(P2PKScriptPubKey(pubKey)))
|
||||
val (creditingTx, _) = TransactionGenerators.buildCreditingTransaction(p2sh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
P2SHNestedSegwitV0UTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
amount = CurrencyUnits.zero,
|
||||
scriptPubKey = p2sh,
|
||||
signersWithPossibleExtra = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
redeemScript = randomWitnessSPK,
|
||||
scriptWitness = P2WSHWitnessV0(P2PKScriptPubKey(pubKey)),
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail given a bad P2SH-P2WSH script witness" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2sh = P2SHScriptPubKey(P2WSHWitnessSPKV0(P2PKScriptPubKey(pubKey)))
|
||||
val (creditingTx, _) = TransactionGenerators.buildCreditingTransaction(p2sh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
P2SHNestedSegwitV0UTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
amount = CurrencyUnits.zero,
|
||||
scriptPubKey = p2sh,
|
||||
signersWithPossibleExtra = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
redeemScript = P2WSHWitnessSPKV0(P2PKScriptPubKey(pubKey)),
|
||||
scriptWitness = P2WSHWitnessV0(randomRawSPK),
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail given a bad P2WPKH script witness" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2wpkh = P2WPKHWitnessSPKV0(pubKey)
|
||||
val (creditingTx, _) =
|
||||
TransactionGenerators.buildCreditingTransaction(p2wpkh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
SegwitV0NativeUTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
amount = CurrencyUnits.zero,
|
||||
scriptPubKey = p2wpkh,
|
||||
signers = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
scriptWitness = P2WPKHWitnessV0(ECPrivateKey.freshPrivateKey.publicKey),
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail given a bad P2WSH script witness" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2wsh = P2WSHWitnessSPKV0(P2PKScriptPubKey(pubKey))
|
||||
val (creditingTx, _) =
|
||||
TransactionGenerators.buildCreditingTransaction(p2wsh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
SegwitV0NativeUTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
amount = CurrencyUnits.zero,
|
||||
scriptPubKey = p2wsh,
|
||||
signers = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
scriptWitness = P2WSHWitnessV0(randomRawSPK),
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it must "fail validation given invalid witnesses" in {
|
||||
val p2wpkh = P2WPKHWitnessSPKV0(ECPublicKey.freshPublicKey)
|
||||
val p2wsh = P2WSHWitnessSPKV0(randomRawSPK)
|
||||
val p2wshWitness = P2WSHWitnessV0(randomRawSPK)
|
||||
val p2WPKHWitness = P2WPKHWitnessV0(ECPublicKey.freshPublicKey)
|
||||
|
||||
assert(!BitcoinUTXOSpendingInfo.isValidScriptWitness(p2wpkh, p2wshWitness))
|
||||
assert(!BitcoinUTXOSpendingInfo.isValidScriptWitness(p2wsh, p2WPKHWitness))
|
||||
}
|
||||
|
||||
it must "fail given no redeem script on P2SH" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
val p2sh = P2SHScriptPubKey(P2PKScriptPubKey(pubKey))
|
||||
val (creditingTx, _) = TransactionGenerators.buildCreditingTransaction(p2sh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, p2sh),
|
||||
signers = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail given no script witness on P2SH-Segwit" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2sh = P2SHScriptPubKey(P2WPKHWitnessSPKV0(pubKey))
|
||||
val creditingOutput = TransactionOutput(CurrencyUnits.zero, p2sh)
|
||||
val creditingTx = BaseTransaction(version =
|
||||
TransactionConstants.validLockVersion,
|
||||
inputs = Nil,
|
||||
outputs = Vector(creditingOutput),
|
||||
lockTime = TransactionConstants.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, p2sh),
|
||||
signers = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
redeemScriptOpt = Some(P2WPKHWitnessSPKV0(pubKey)),
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail given an unsupported script witness on P2SH-Segwit" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2sh = P2SHScriptPubKey(P2WPKHWitnessSPKV0(pubKey))
|
||||
val creditingOutput = TransactionOutput(CurrencyUnits.zero, p2sh)
|
||||
val creditingTx = BaseTransaction(version =
|
||||
TransactionConstants.validLockVersion,
|
||||
inputs = Nil,
|
||||
outputs = Vector(creditingOutput),
|
||||
lockTime = TransactionConstants.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[UnsupportedOperationException] {
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, p2sh),
|
||||
signers = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
redeemScriptOpt = Some(P2WPKHWitnessSPKV0(pubKey)),
|
||||
scriptWitnessOpt = Some(EmptyScriptWitness),
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail given UnassignedWitnessScriptPubKey redeemScript" in {
|
||||
val unassingedWitnessSPK = UnassignedWitnessScriptPubKey.fromAsm(
|
||||
P2WPKHWitnessSPKV0(ECPublicKey.freshPublicKey).asm)
|
||||
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
val p2sh = P2SHScriptPubKey(P2PKScriptPubKey(pubKey))
|
||||
val (creditingTx, _) = TransactionGenerators.buildCreditingTransaction(p2sh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[UnsupportedOperationException] {
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, p2sh),
|
||||
signers = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
redeemScriptOpt = Some(unassingedWitnessSPK),
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail given unsupported witness" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2wpkh = P2WPKHWitnessSPKV0(pubKey)
|
||||
val (creditingTx, _) =
|
||||
TransactionGenerators.buildCreditingTransaction(p2wpkh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[UnsupportedOperationException] {
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, p2wpkh),
|
||||
signers = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(EmptyScriptWitness),
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail given no witness" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2wpkh = P2WPKHWitnessSPKV0(pubKey)
|
||||
val (creditingTx, _) =
|
||||
TransactionGenerators.buildCreditingTransaction(p2wpkh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, p2wpkh),
|
||||
signers = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "successfully return UnassingedSegwitNativeUTXOSpendingInfoFull" in {
|
||||
val unassingedWitnessSPK = UnassignedWitnessScriptPubKey.fromAsm(
|
||||
P2WPKHWitnessSPKV0(ECPublicKey.freshPublicKey).asm)
|
||||
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val p2wpkh = P2WPKHWitnessSPKV0(pubKey)
|
||||
val (creditingTx, _) =
|
||||
TransactionGenerators.buildCreditingTransaction(p2wpkh)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
val spendingInfo =
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, unassingedWitnessSPK),
|
||||
signers = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
|
||||
val expectedSpendingInfo =
|
||||
UnassignedSegwitNativeUTXOSpendingInfo(
|
||||
outPoint = outPoint,
|
||||
amount = CurrencyUnits.zero,
|
||||
scriptPubKey = unassingedWitnessSPK,
|
||||
signers = Vector(privKey),
|
||||
hashType = HashType.sigHashAll,
|
||||
scriptWitness = EmptyScriptWitness,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
|
||||
assert(spendingInfo == expectedSpendingInfo)
|
||||
}
|
||||
|
||||
it should "fail given a NonStandardScriptPubKey" in {
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
val p2pk = P2PKScriptPubKey(pubKey)
|
||||
val spk = NonStandardScriptPubKey.fromAsm(p2pk.asm)
|
||||
val (creditingTx, _) = TransactionGenerators.buildCreditingTransaction(p2pk)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
|
||||
assertThrows[UnsupportedOperationException] {
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
outPoint = outPoint,
|
||||
output = TransactionOutput(CurrencyUnits.zero, spk),
|
||||
signers = Vector(privKey),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
hashType = HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -524,7 +524,7 @@ object ConditionalScriptSignature
|
|||
nestedScriptSig: ScriptSignature,
|
||||
conditionalPath: ConditionalPath): ConditionalScriptSignature = {
|
||||
conditionalPath match {
|
||||
case ConditionalPath.NoConditionsLeft =>
|
||||
case ConditionalPath.NoCondition =>
|
||||
throw new IllegalArgumentException("ConditionalPath cannot be empty")
|
||||
case ConditionalPath.nonNestedTrue =>
|
||||
ConditionalScriptSignature(nestedScriptSig, condition = true)
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package org.bitcoins.core.protocol.transaction
|
||||
|
||||
import org.bitcoins.crypto.{Factory, NetworkElement}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
/** Represents a generic on-chain output */
|
||||
case class OutputReference(
|
||||
outPoint: TransactionOutPoint,
|
||||
output: TransactionOutput)
|
||||
extends NetworkElement {
|
||||
override def bytes: ByteVector = {
|
||||
outPoint.bytes ++ output.bytes
|
||||
}
|
||||
}
|
||||
|
||||
object EmptyOutputReference
|
||||
extends OutputReference(EmptyTransactionOutPoint, EmptyTransactionOutput) {
|
||||
override def toString: String = "EmptyOutputReference"
|
||||
}
|
||||
|
||||
object OutputReference extends Factory[OutputReference] {
|
||||
override def fromBytes(bytes: ByteVector): OutputReference = {
|
||||
val (outPointBytes, outputBytes) = bytes.splitAt(36)
|
||||
val outPoint = TransactionOutPoint(outPointBytes)
|
||||
val output = TransactionOutput(outputBytes)
|
||||
|
||||
OutputReference(outPoint, output)
|
||||
}
|
||||
}
|
|
@ -129,7 +129,7 @@ case class PSBT(
|
|||
def sign(
|
||||
inputIndex: Int,
|
||||
signer: Sign,
|
||||
conditionalPath: ConditionalPath = ConditionalPath.NoConditionsLeft,
|
||||
conditionalPath: ConditionalPath = ConditionalPath.NoCondition,
|
||||
isDummySignature: Boolean = false)(
|
||||
implicit ec: ExecutionContext): Future[PSBT] = {
|
||||
BitcoinSigner.sign(psbt = this,
|
||||
|
@ -140,21 +140,22 @@ case class PSBT(
|
|||
}
|
||||
|
||||
/**
|
||||
* Takes the InputPSBTMap at the given index and returns a UTXOSpendingInfoFull
|
||||
* Takes the InputPSBTMap at the given index and returns a NewSpendingInfoFull
|
||||
* that can be used to sign the input
|
||||
* @param index index of the InputPSBTMap
|
||||
* @param signers Signers that will be used to sign the input
|
||||
* @param conditionalPath Path that should be used for the script
|
||||
* @return A corresponding UTXOSpendingInfoFull
|
||||
* @return A corresponding NewSpendingInfoFull
|
||||
*/
|
||||
def getUTXOSpendingInfoUsingSigners(
|
||||
def getSpendingInfoUsingSigners(
|
||||
index: Int,
|
||||
signers: Vector[Sign],
|
||||
conditionalPath: ConditionalPath = ConditionalPath.NoConditionsLeft): UTXOSpendingInfoFull = {
|
||||
conditionalPath: ConditionalPath = ConditionalPath.NoCondition): ScriptSignatureParams[
|
||||
InputInfo] = {
|
||||
require(index >= 0 && index < inputMaps.size,
|
||||
s"Index must be within 0 and the number of inputs, got: $index")
|
||||
inputMaps(index)
|
||||
.toUTXOSpendingInfoUsingSigners(transaction.inputs(index),
|
||||
.toUTXOSatisfyingInfoUsingSigners(transaction.inputs(index),
|
||||
signers,
|
||||
conditionalPath)
|
||||
}
|
||||
|
@ -771,11 +772,12 @@ object PSBT extends Factory[PSBT] {
|
|||
}
|
||||
|
||||
/**
|
||||
* Wraps a Vector of pairs of UTXOSpendingInfos and the Transactions whose outputs are spent.
|
||||
* Wraps a Vector of pairs of NewSpendingInfos and the Transactions whose outputs are spent.
|
||||
* Note that this Transaction is only necessary when the output is non-segwit.
|
||||
*/
|
||||
case class SpendingInfoAndNonWitnessTxs(
|
||||
infoAndTxOpts: Vector[(UTXOSpendingInfoFull, Option[BaseTransaction])]) {
|
||||
infoAndTxOpts: Vector[
|
||||
(ScriptSignatureParams[InputInfo], Option[BaseTransaction])]) {
|
||||
val length: Int = infoAndTxOpts.length
|
||||
|
||||
def matchesInputs(inputs: Seq[TransactionInput]): Boolean = {
|
||||
|
@ -786,8 +788,9 @@ object PSBT extends Factory[PSBT] {
|
|||
}
|
||||
}
|
||||
|
||||
def map[T](
|
||||
func: (UTXOSpendingInfoFull, Option[BaseTransaction]) => T): Vector[T] = {
|
||||
def map[T](func: (
|
||||
ScriptSignatureParams[InputInfo],
|
||||
Option[BaseTransaction]) => T): Vector[T] = {
|
||||
infoAndTxOpts.map { case (info, txOpt) => func(info, txOpt) }
|
||||
}
|
||||
}
|
||||
|
@ -821,10 +824,10 @@ object PSBT extends Factory[PSBT] {
|
|||
spendingInfoAndNonWitnessTxs: SpendingInfoAndNonWitnessTxs,
|
||||
finalized: Boolean)(implicit ec: ExecutionContext): Future[PSBT] = {
|
||||
require(spendingInfoAndNonWitnessTxs.length == unsignedTx.inputs.length,
|
||||
"Must have a UTXOSpendingInfo for every input")
|
||||
"Must have a NewSpendingInfo for every input")
|
||||
require(
|
||||
spendingInfoAndNonWitnessTxs.matchesInputs(unsignedTx.inputs),
|
||||
"UTXOSpendingInfos must correspond to transaction inputs"
|
||||
"NewSpendingInfos must correspond to transaction inputs"
|
||||
)
|
||||
val emptySigTx = BitcoinTxBuilder.emptyAllScriptSigs(unsignedTx)
|
||||
val btx = emptySigTx match {
|
||||
|
@ -838,9 +841,9 @@ object PSBT extends Factory[PSBT] {
|
|||
val inputMapFs = spendingInfoAndNonWitnessTxs.map {
|
||||
case (info, txOpt) =>
|
||||
if (finalized) {
|
||||
InputPSBTMap.finalizedFromUTXOSpendingInfo(info, unsignedTx, txOpt)
|
||||
InputPSBTMap.finalizedFromNewSpendingInfo(info, unsignedTx, txOpt)
|
||||
} else {
|
||||
InputPSBTMap.fromUTXOSpendingInfo(info, unsignedTx, txOpt)
|
||||
InputPSBTMap.fromUTXOInfo(info, unsignedTx, txOpt)
|
||||
}
|
||||
}
|
||||
val outputMaps = unsignedTx.outputs.map(_ => OutputPSBTMap.empty).toVector
|
||||
|
|
|
@ -495,28 +495,35 @@ case class InputPSBTMap(elements: Vector[InputPSBTRecord])
|
|||
}
|
||||
|
||||
/**
|
||||
* Takes the InputPSBTMap returns a UTXOSpendingInfoFull
|
||||
* Takes the InputPSBTMap returns a NewSpendingInfoFull
|
||||
* that can be used to sign the input
|
||||
* @param txIn The transaction input that this InputPSBTMap represents
|
||||
* @param signers Signers that will be used to sign the input
|
||||
* @param conditionalPath Path that should be used for the script
|
||||
* @return A corresponding UTXOSpendingInfoFull
|
||||
* @return A corresponding NewSpendingInfoFull
|
||||
*/
|
||||
def toUTXOSpendingInfoUsingSigners(
|
||||
def toUTXOSatisfyingInfoUsingSigners(
|
||||
txIn: TransactionInput,
|
||||
signers: Vector[Sign],
|
||||
conditionalPath: ConditionalPath = ConditionalPath.NoConditionsLeft): UTXOSpendingInfoFull = {
|
||||
conditionalPath: ConditionalPath = ConditionalPath.NoCondition): ScriptSignatureParams[
|
||||
InputInfo] = {
|
||||
require(!isFinalized, s"Cannot update an InputPSBTMap that is finalized")
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
toUTXOSpendingInfoSingle(txIn, signers.head, conditionalPath),
|
||||
signers
|
||||
|
||||
val infoSingle =
|
||||
toUTXOSigningInfo(txIn, signers.head, conditionalPath)
|
||||
|
||||
ScriptSignatureParams(
|
||||
infoSingle.inputInfo,
|
||||
signers,
|
||||
infoSingle.hashType
|
||||
)
|
||||
}
|
||||
|
||||
def toUTXOSpendingInfoSingle(
|
||||
def toUTXOSigningInfo(
|
||||
txIn: TransactionInput,
|
||||
signer: Sign,
|
||||
conditionalPath: ConditionalPath = ConditionalPath.NoConditionsLeft): UTXOSpendingInfoSingle = {
|
||||
conditionalPath: ConditionalPath = ConditionalPath.NoCondition): ECSignatureParams[
|
||||
InputInfo] = {
|
||||
require(!isFinalized, s"Cannot update an InputPSBTMap that is finalized")
|
||||
val outPoint = txIn.previousOutput
|
||||
|
||||
|
@ -530,7 +537,7 @@ case class InputPSBTMap(elements: Vector[InputPSBTRecord])
|
|||
tx.outputs(txIn.previousOutput.vout.toInt)
|
||||
} else {
|
||||
throw new UnsupportedOperationException(
|
||||
"Not enough information in the InputPSBTMap to get a valid UTXOSpendingInfo")
|
||||
"Not enough information in the InputPSBTMap to get a valid NewSpendingInfo")
|
||||
}
|
||||
|
||||
val redeemScriptVec = getRecords(RedeemScriptKeyId)
|
||||
|
@ -555,13 +562,14 @@ case class InputPSBTMap(elements: Vector[InputPSBTRecord])
|
|||
if (hashTypeVec.size == 1) hashTypeVec.head.hashType
|
||||
else HashType.sigHashAll
|
||||
|
||||
BitcoinUTXOSpendingInfoSingle(outPoint,
|
||||
val inputInfo = InputInfo(outPoint,
|
||||
output,
|
||||
signer,
|
||||
redeemScriptOpt,
|
||||
scriptWitnessOpt,
|
||||
hashType,
|
||||
conditionalPath)
|
||||
conditionalPath,
|
||||
Vector(signer.publicKey))
|
||||
|
||||
ECSignatureParams(inputInfo, signer, hashType)
|
||||
}
|
||||
|
||||
private def changeToWitnessUTXO(
|
||||
|
@ -621,12 +629,12 @@ case class InputPSBTMap(elements: Vector[InputPSBTRecord])
|
|||
object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
|
||||
import org.bitcoins.core.psbt.InputPSBTRecord._
|
||||
|
||||
/** Constructs a finalized InputPSBTMap from a UTXOSpendingInfoFull,
|
||||
/** Constructs a finalized InputPSBTMap from a NewSpendingInfoFull,
|
||||
* the corresponding PSBT's unsigned transaction, and if this is
|
||||
* a non-witness spend, the transaction being spent
|
||||
*/
|
||||
def finalizedFromUTXOSpendingInfo(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
def finalizedFromNewSpendingInfo(
|
||||
spendingInfo: ScriptSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
nonWitnessTxOpt: Option[Transaction])(
|
||||
implicit ec: ExecutionContext): Future[InputPSBTMap] = {
|
||||
|
@ -634,12 +642,11 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
|
|||
.sign(spendingInfo, unsignedTx, isDummySignature = false)
|
||||
|
||||
sigComponentF.map { sigComponent =>
|
||||
val utxos = spendingInfo match {
|
||||
case _: SegwitV0NativeUTXOSpendingInfoFull |
|
||||
_: P2SHNestedSegwitV0UTXOSpendingInfoFull =>
|
||||
val utxos = spendingInfo.inputInfo match {
|
||||
case _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo =>
|
||||
Vector(WitnessUTXO(spendingInfo.output))
|
||||
case _: RawScriptUTXOSpendingInfoFull | _: P2SHNoNestSpendingInfo |
|
||||
_: UnassignedSegwitNativeUTXOSpendingInfo =>
|
||||
case _: RawInputInfo | _: P2SHNonSegwitInputInfo |
|
||||
_: UnassignedSegwitNativeInputInfo =>
|
||||
nonWitnessTxOpt match {
|
||||
case None => Vector.empty
|
||||
case Some(nonWitnessTx) =>
|
||||
|
@ -666,11 +673,11 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
|
|||
}
|
||||
|
||||
/** Constructs a full (ready to be finalized) but unfinalized InputPSBTMap
|
||||
* from a UTXOSpendingInfoFull, the corresponding PSBT's unsigned transaction,
|
||||
* from a NewSpendingInfoFull, the corresponding PSBT's unsigned transaction,
|
||||
* and if this is a non-witness spend, the transaction being spent
|
||||
*/
|
||||
def fromUTXOSpendingInfo(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
def fromUTXOInfo(
|
||||
spendingInfo: ScriptSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
nonWitnessTxOpt: Option[Transaction])(
|
||||
implicit ec: ExecutionContext): Future[InputPSBTMap] = {
|
||||
|
@ -685,12 +692,11 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
|
|||
sigFs.map { sigs =>
|
||||
val builder = Vector.newBuilder[InputPSBTRecord]
|
||||
|
||||
spendingInfo match {
|
||||
case _: SegwitV0NativeUTXOSpendingInfoFull |
|
||||
_: P2SHNestedSegwitV0UTXOSpendingInfoFull =>
|
||||
spendingInfo.inputInfo match {
|
||||
case _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo =>
|
||||
builder.+=(WitnessUTXO(spendingInfo.output))
|
||||
case _: RawScriptUTXOSpendingInfoFull | _: P2SHNoNestSpendingInfo |
|
||||
_: UnassignedSegwitNativeUTXOSpendingInfo =>
|
||||
case _: RawInputInfo | _: P2SHNonSegwitInputInfo |
|
||||
_: UnassignedSegwitNativeInputInfo =>
|
||||
nonWitnessTxOpt match {
|
||||
case None => ()
|
||||
case Some(nonWitnessTx) =>
|
||||
|
@ -703,20 +709,20 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
|
|||
val sigHashType = SigHashType(spendingInfo.hashType)
|
||||
builder.+=(sigHashType)
|
||||
|
||||
spendingInfo match {
|
||||
case p2sh: P2SHNoNestSpendingInfo =>
|
||||
spendingInfo.inputInfo match {
|
||||
case p2sh: P2SHNonSegwitInputInfo =>
|
||||
builder.+=(RedeemScript(p2sh.redeemScript))
|
||||
case p2sh: P2SHNestedSegwitV0UTXOSpendingInfoFull =>
|
||||
case p2sh: P2SHNestedSegwitV0InputInfo =>
|
||||
builder.+=(RedeemScript(p2sh.redeemScript))
|
||||
p2sh.scriptWitness match {
|
||||
case p2wsh: P2WSHWitnessV0 =>
|
||||
builder.+=(WitnessScript(p2wsh.redeemScript))
|
||||
case _: P2WPKHWitnessV0 => ()
|
||||
}
|
||||
case p2wsh: P2WSHV0SpendingInfo =>
|
||||
case p2wsh: P2WSHV0InputInfo =>
|
||||
builder.+=(WitnessScript(p2wsh.scriptWitness.redeemScript))
|
||||
case _: RawScriptUTXOSpendingInfoFull | _: P2WPKHV0SpendingInfo |
|
||||
_: UnassignedSegwitNativeUTXOSpendingInfo =>
|
||||
case _: RawInputInfo | _: P2WPKHV0InputInfo |
|
||||
_: UnassignedSegwitNativeInputInfo =>
|
||||
()
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ sealed abstract class TxBuilder {
|
|||
*/
|
||||
def utxoMap: TxBuilder.UTXOMap
|
||||
|
||||
def utxos: Seq[UTXOSpendingInfoFull] = utxoMap.values.toSeq
|
||||
def utxos: Seq[ScriptSignatureParams[InputInfo]] = utxoMap.values.toSeq
|
||||
|
||||
/** This represents the rate, in [[org.bitcoins.core.wallet.fee.FeeUnit FeeUnit]], we
|
||||
* should pay for this transaction */
|
||||
|
@ -84,7 +84,8 @@ sealed abstract class TxBuilder {
|
|||
def outPoints: Seq[TransactionOutPoint] = utxoMap.keys.toSeq
|
||||
|
||||
/** The script witnesses that are needed in this transaction */
|
||||
def scriptWitOpt: Seq[Option[ScriptWitness]] = utxos.map(_.scriptWitnessOpt)
|
||||
def scriptWitOpt: Seq[Option[ScriptWitness]] =
|
||||
utxos.map(utxo => InputInfo.getScriptWitness(utxo.inputInfo))
|
||||
|
||||
/**
|
||||
* The unsigned version of the tx with dummy signatures instead of real signatures in
|
||||
|
@ -113,7 +114,7 @@ sealed abstract class BitcoinTxBuilder extends TxBuilder {
|
|||
override def utxoMap: BitcoinTxBuilder.UTXOMap
|
||||
|
||||
override def sign(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
val f: (Seq[BitcoinUTXOSpendingInfoFull], Transaction) => Boolean = {
|
||||
val f: (Seq[ScriptSignatureParams[InputInfo]], Transaction) => Boolean = {
|
||||
(_, _) =>
|
||||
true
|
||||
}
|
||||
|
@ -187,7 +188,9 @@ sealed abstract class BitcoinTxBuilder extends TxBuilder {
|
|||
* @return the signed transaction, or a [[TxBuilderError]] indicating what went wrong when signing the tx
|
||||
*/
|
||||
def sign(
|
||||
invariants: (Seq[BitcoinUTXOSpendingInfoFull], Transaction) => Boolean)(
|
||||
invariants: (
|
||||
Seq[ScriptSignatureParams[InputInfo]],
|
||||
Transaction) => Boolean)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
val utxos = utxoMap.values.toList
|
||||
val signedTxWithFee = unsignedTx.flatMap { utx: Transaction =>
|
||||
|
@ -212,7 +215,7 @@ sealed abstract class BitcoinTxBuilder extends TxBuilder {
|
|||
}
|
||||
|
||||
private def loop(
|
||||
remaining: List[BitcoinUTXOSpendingInfoFull],
|
||||
remaining: List[ScriptSignatureParams[InputInfo]],
|
||||
txInProgress: Transaction,
|
||||
dummySignatures: Boolean)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = remaining match {
|
||||
|
@ -229,7 +232,7 @@ sealed abstract class BitcoinTxBuilder extends TxBuilder {
|
|||
* @return either the transaction with the signed input added, or a [[TxBuilderError]]
|
||||
*/
|
||||
private def signAndAddInput(
|
||||
utxo: BitcoinUTXOSpendingInfoFull,
|
||||
utxo: ScriptSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
dummySignatures: Boolean)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
|
@ -238,10 +241,10 @@ sealed abstract class BitcoinTxBuilder extends TxBuilder {
|
|||
if (idx.isEmpty) {
|
||||
Future.fromTry(TxBuilderError.MissingOutPoint)
|
||||
} else {
|
||||
utxo match {
|
||||
case _: UnassignedSegwitNativeUTXOSpendingInfo =>
|
||||
utxo.inputInfo match {
|
||||
case _: UnassignedSegwitNativeInputInfo =>
|
||||
Future.fromTry(TxBuilderError.NoSigner)
|
||||
case _: BitcoinUTXOSpendingInfoFull =>
|
||||
case _: InputInfo =>
|
||||
BitcoinSigner
|
||||
.sign(utxo, unsignedTx, dummySignatures)
|
||||
.map(_.transaction)
|
||||
|
@ -279,7 +282,7 @@ sealed abstract class BitcoinTxBuilder extends TxBuilder {
|
|||
* See BIP65 for more info
|
||||
*/
|
||||
private def calcLockTime(
|
||||
utxos: Seq[BitcoinUTXOSpendingInfoFull]): Try[UInt32] = {
|
||||
utxos: Seq[ScriptSignatureParams[InputInfo]]): Try[UInt32] = {
|
||||
def computeNextLockTime(
|
||||
currentLockTimeOpt: Option[UInt32],
|
||||
locktime: Long): Try[UInt32] = {
|
||||
|
@ -311,14 +314,14 @@ sealed abstract class BitcoinTxBuilder extends TxBuilder {
|
|||
|
||||
@tailrec
|
||||
def loop(
|
||||
remaining: Seq[BitcoinUTXOSpendingInfoFull],
|
||||
remaining: Seq[ScriptSignatureParams[InputInfo]],
|
||||
currentLockTimeOpt: Option[UInt32]): Try[UInt32] =
|
||||
remaining match {
|
||||
case Nil =>
|
||||
Success(currentLockTimeOpt.getOrElse(TransactionConstants.lockTime))
|
||||
case spendingInfo +: newRemaining =>
|
||||
spendingInfo match {
|
||||
case lockTime: LockTimeSpendingInfoFull =>
|
||||
spendingInfo.inputInfo match {
|
||||
case lockTime: LockTimeInputInfo =>
|
||||
lockTime.scriptPubKey match {
|
||||
case _: CSVScriptPubKey =>
|
||||
loop(newRemaining, currentLockTimeOpt)
|
||||
|
@ -332,7 +335,7 @@ sealed abstract class BitcoinTxBuilder extends TxBuilder {
|
|||
case _: Failure[UInt32] => result
|
||||
}
|
||||
}
|
||||
case p2pkWithTimeout: P2PKWithTimeoutSpendingInfo =>
|
||||
case p2pkWithTimeout: P2PKWithTimeoutInputInfo =>
|
||||
if (p2pkWithTimeout.isBeforeTimeout) {
|
||||
loop(newRemaining, currentLockTimeOpt)
|
||||
} else {
|
||||
|
@ -346,17 +349,21 @@ sealed abstract class BitcoinTxBuilder extends TxBuilder {
|
|||
case _: Failure[UInt32] => result
|
||||
}
|
||||
}
|
||||
case p2sh: P2SHSpendingInfoFull =>
|
||||
loop(p2sh.nestedSpendingInfo +: newRemaining, currentLockTimeOpt)
|
||||
case p2wsh: P2WSHV0SpendingInfoFull =>
|
||||
loop(p2wsh.nestedSpendingInfo +: newRemaining, currentLockTimeOpt)
|
||||
case conditional: ConditionalSpendingInfoFull =>
|
||||
loop(conditional.nestedSpendingInfo +: newRemaining,
|
||||
currentLockTimeOpt)
|
||||
case _: P2WPKHV0SpendingInfo |
|
||||
_: UnassignedSegwitNativeUTXOSpendingInfo |
|
||||
_: P2PKSpendingInfo | _: P2PKHSpendingInfo |
|
||||
_: MultiSignatureSpendingInfoFull | _: EmptySpendingInfo =>
|
||||
case p2sh: P2SHInputInfo =>
|
||||
val nestedSpendingInfo =
|
||||
p2sh.nestedInputInfo.withSignFrom(spendingInfo)
|
||||
loop(nestedSpendingInfo +: newRemaining, currentLockTimeOpt)
|
||||
case p2wsh: P2WSHV0InputInfo =>
|
||||
val nestedSpendingInfo =
|
||||
p2wsh.nestedInputInfo.withSignFrom(spendingInfo)
|
||||
loop(nestedSpendingInfo +: newRemaining, currentLockTimeOpt)
|
||||
case conditional: ConditionalInputInfo =>
|
||||
val nestedSpendingInfo =
|
||||
conditional.nestedInputInfo.withSignFrom(spendingInfo)
|
||||
loop(nestedSpendingInfo +: newRemaining, currentLockTimeOpt)
|
||||
case _: P2WPKHV0InputInfo | _: UnassignedSegwitNativeInputInfo |
|
||||
_: P2PKInputInfo | _: P2PKHInputInfo |
|
||||
_: MultiSignatureInputInfo | _: EmptyInputInfo =>
|
||||
// none of these scripts affect the locktime of a tx
|
||||
loop(newRemaining, currentLockTimeOpt)
|
||||
}
|
||||
|
@ -372,17 +379,17 @@ sealed abstract class BitcoinTxBuilder extends TxBuilder {
|
|||
* See BIP68/112 and BIP65 for more info
|
||||
*/
|
||||
private def calcSequenceForInputs(
|
||||
utxos: Seq[UTXOSpendingInfoFull],
|
||||
utxos: Seq[ScriptSignatureParams[InputInfo]],
|
||||
isRBFEnabled: Boolean): Seq[TransactionInput] = {
|
||||
@tailrec
|
||||
def loop(
|
||||
remaining: Seq[UTXOSpendingInfoFull],
|
||||
remaining: Seq[ScriptSignatureParams[InputInfo]],
|
||||
accum: Seq[TransactionInput]): Seq[TransactionInput] =
|
||||
remaining match {
|
||||
case Nil => accum.reverse
|
||||
case spendingInfo +: newRemaining =>
|
||||
spendingInfo match {
|
||||
case lockTime: LockTimeSpendingInfoFull =>
|
||||
spendingInfo.inputInfo match {
|
||||
case lockTime: LockTimeInputInfo =>
|
||||
val sequence = lockTime.scriptPubKey match {
|
||||
case csv: CSVScriptPubKey => solveSequenceForCSV(csv.locktime)
|
||||
case _: CLTVScriptPubKey => UInt32.zero
|
||||
|
@ -391,7 +398,7 @@ sealed abstract class BitcoinTxBuilder extends TxBuilder {
|
|||
EmptyScriptSignature,
|
||||
sequence)
|
||||
loop(newRemaining, input +: accum)
|
||||
case p2pkWithTimeout: P2PKWithTimeoutSpendingInfo =>
|
||||
case p2pkWithTimeout: P2PKWithTimeoutInputInfo =>
|
||||
if (p2pkWithTimeout.isBeforeTimeout) {
|
||||
val sequence =
|
||||
if (isRBFEnabled) UInt32.zero
|
||||
|
@ -407,16 +414,21 @@ sealed abstract class BitcoinTxBuilder extends TxBuilder {
|
|||
UInt32.zero)
|
||||
loop(newRemaining, input +: accum)
|
||||
}
|
||||
case p2sh: P2SHSpendingInfoFull =>
|
||||
loop(p2sh.nestedSpendingInfo +: newRemaining, accum)
|
||||
case p2wsh: P2WSHV0SpendingInfoFull =>
|
||||
loop(p2wsh.nestedSpendingInfo +: newRemaining, accum)
|
||||
case conditional: ConditionalSpendingInfoFull =>
|
||||
loop(conditional.nestedSpendingInfo +: newRemaining, accum)
|
||||
case _: P2WPKHV0SpendingInfo |
|
||||
_: UnassignedSegwitNativeUTXOSpendingInfo |
|
||||
_: P2PKSpendingInfo | _: P2PKHSpendingInfo |
|
||||
_: MultiSignatureSpendingInfoFull | _: EmptySpendingInfo =>
|
||||
case p2sh: P2SHInputInfo =>
|
||||
val nestedSpendingInfo =
|
||||
p2sh.nestedInputInfo.withSignFrom(spendingInfo)
|
||||
loop(nestedSpendingInfo +: newRemaining, accum)
|
||||
case p2wsh: P2WSHV0InputInfo =>
|
||||
val nestedSpendingInfo =
|
||||
p2wsh.nestedInputInfo.withSignFrom(spendingInfo)
|
||||
loop(nestedSpendingInfo +: newRemaining, accum)
|
||||
case conditional: ConditionalInputInfo =>
|
||||
val nestedSpendingInfo =
|
||||
conditional.nestedInputInfo.withSignFrom(spendingInfo)
|
||||
loop(nestedSpendingInfo +: newRemaining, accum)
|
||||
case _: P2WPKHV0InputInfo | _: UnassignedSegwitNativeInputInfo |
|
||||
_: P2PKInputInfo | _: P2PKHInputInfo |
|
||||
_: MultiSignatureInputInfo | _: EmptyInputInfo =>
|
||||
//none of these script types affect the sequence number of a tx
|
||||
//the sequence only needs to be adjustd if we have replace by fee (RBF) enabled
|
||||
//see BIP125 for more information
|
||||
|
@ -438,7 +450,7 @@ object TxBuilder {
|
|||
|
||||
/** This contains all the information needed to create a valid
|
||||
* [[org.bitcoins.core.protocol.transaction.TransactionInput TransactionInput]] that spends this utxo */
|
||||
type UTXOMap = Map[TransactionOutPoint, UTXOSpendingInfoFull]
|
||||
type UTXOMap = Map[TransactionOutPoint, ScriptSignatureParams[InputInfo]]
|
||||
private val logger = BitcoinSLogger.logger
|
||||
|
||||
/** Runs various sanity checks on the final version of the signed transaction from TxBuilder */
|
||||
|
@ -557,7 +569,7 @@ object TxBuilder {
|
|||
}
|
||||
|
||||
object BitcoinTxBuilder {
|
||||
type UTXOMap = Map[TransactionOutPoint, BitcoinUTXOSpendingInfoFull]
|
||||
type UTXOMap = Map[TransactionOutPoint, ScriptSignatureParams[InputInfo]]
|
||||
|
||||
private case class BitcoinTxBuilderImpl(
|
||||
destinations: Seq[TransactionOutput],
|
||||
|
@ -593,25 +605,18 @@ object BitcoinTxBuilder {
|
|||
|
||||
def apply(
|
||||
destinations: Seq[TransactionOutput],
|
||||
utxos: Seq[BitcoinUTXOSpendingInfoFull],
|
||||
utxos: Seq[ScriptSignatureParams[InputInfo]],
|
||||
feeRate: FeeUnit,
|
||||
changeSPK: ScriptPubKey,
|
||||
network: BitcoinNetwork): Future[BitcoinTxBuilder] = {
|
||||
@tailrec
|
||||
def loop(utxos: Seq[UTXOSpendingInfoFull], accum: UTXOMap): UTXOMap =
|
||||
def loop(
|
||||
utxos: Seq[ScriptSignatureParams[InputInfo]],
|
||||
accum: UTXOMap): UTXOMap =
|
||||
utxos match {
|
||||
case Nil => accum
|
||||
case h +: t =>
|
||||
val u = BitcoinUTXOSpendingInfoFull(
|
||||
outPoint = h.outPoint,
|
||||
output = h.output,
|
||||
signers = h.signers,
|
||||
redeemScriptOpt = h.redeemScriptOpt,
|
||||
scriptWitnessOpt = h.scriptWitnessOpt,
|
||||
hashType = h.hashType,
|
||||
conditionalPath = h.conditionalPath
|
||||
)
|
||||
val result: BitcoinTxBuilder.UTXOMap = accum.updated(h.outPoint, u)
|
||||
val result: BitcoinTxBuilder.UTXOMap = accum.updated(h.outPoint, h)
|
||||
loop(t, result)
|
||||
}
|
||||
val map = loop(utxos, Map.empty)
|
||||
|
|
|
@ -5,14 +5,14 @@ import org.bitcoins.core.protocol.transaction.{
|
|||
Transaction,
|
||||
WitnessTransaction
|
||||
}
|
||||
import org.bitcoins.core.wallet.utxo.{BitcoinUTXOSpendingInfo, UTXOSpendingInfo}
|
||||
import org.bitcoins.core.wallet.utxo.{InputInfo, InputSigningInfo}
|
||||
|
||||
/**
|
||||
* This meant to represent the class used to 'fund' an
|
||||
* unsigned [[org.bitcoins.core.protocol.transaction.Transaction Transaction]].
|
||||
* This is useful for when we have multiple [[org.bitcoins.core.config.NetworkParameters NetworkParameters]]
|
||||
* that each have their own transaction type. I.e. we should only be able to have
|
||||
* BitcoinTransactions paired with [[org.bitcoins.core.wallet.utxo.BitcoinUTXOSpendingInfo BitcoinUTXOSpendingInfo]],
|
||||
* BitcoinTransactions paired with [[org.bitcoins.core.wallet.utxo.InputSigningInfo[InputInfo] UTXOInfo]],
|
||||
* the same would apply for litecoin etc.
|
||||
*/
|
||||
sealed abstract class FundingInfo {
|
||||
|
@ -21,28 +21,28 @@ sealed abstract class FundingInfo {
|
|||
def transaction: Transaction
|
||||
|
||||
/** The utxos used to fund the tx */
|
||||
def utxos: Seq[UTXOSpendingInfo]
|
||||
def utxos: Seq[InputSigningInfo[InputInfo]]
|
||||
}
|
||||
|
||||
sealed abstract class BitcoinFundingInfo extends FundingInfo {
|
||||
override def utxos: Seq[BitcoinUTXOSpendingInfo]
|
||||
override def utxos: Seq[InputSigningInfo[InputInfo]]
|
||||
}
|
||||
|
||||
object BitcoinFundingInfo {
|
||||
private case class BitcoinFundingInfoImpl(
|
||||
transaction: Transaction,
|
||||
utxos: Seq[BitcoinUTXOSpendingInfo])
|
||||
utxos: Seq[InputSigningInfo[InputInfo]])
|
||||
extends BitcoinFundingInfo
|
||||
|
||||
def apply(
|
||||
tx: BaseTransaction,
|
||||
utxos: Seq[BitcoinUTXOSpendingInfo]): BitcoinFundingInfo = {
|
||||
utxos: Seq[InputSigningInfo[InputInfo]]): BitcoinFundingInfo = {
|
||||
BitcoinFundingInfoImpl(tx, utxos)
|
||||
}
|
||||
|
||||
def apply(
|
||||
wtx: WitnessTransaction,
|
||||
utxos: Seq[BitcoinUTXOSpendingInfo]): BitcoinFundingInfo = {
|
||||
utxos: Seq[InputSigningInfo[InputInfo]]): BitcoinFundingInfo = {
|
||||
BitcoinFundingInfoImpl(wtx, utxos)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ sealed abstract class SignerUtils {
|
|||
}
|
||||
|
||||
def signSingle(
|
||||
spendingInfo: UTXOSpendingInfoSingle,
|
||||
spendingInfo: ECSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean)(
|
||||
implicit ec: ExecutionContext): Future[PartialSignature] = {
|
||||
|
@ -56,7 +56,7 @@ sealed abstract class SignerUtils {
|
|||
protected val flags: Seq[ScriptFlag] = Policy.standardFlags
|
||||
|
||||
protected def relevantInfo(
|
||||
spendingInfo: UTXOSpendingInfo,
|
||||
spendingInfo: InputSigningInfo[InputInfo],
|
||||
unsignedTx: Transaction): (Seq[Sign], TransactionOutput, UInt32, HashType) = {
|
||||
(spendingInfo.signers,
|
||||
spendingInfo.output,
|
||||
|
@ -65,7 +65,7 @@ sealed abstract class SignerUtils {
|
|||
}
|
||||
|
||||
protected def inputIndex(
|
||||
spendingInfo: UTXOSpendingInfo,
|
||||
spendingInfo: InputSigningInfo[InputInfo],
|
||||
tx: Transaction): UInt32 = {
|
||||
tx.inputs.zipWithIndex
|
||||
.find(_._1.previousOutput == spendingInfo.outPoint) match {
|
||||
|
@ -77,7 +77,7 @@ sealed abstract class SignerUtils {
|
|||
}
|
||||
|
||||
protected def sigComponent(
|
||||
spendingInfo: UTXOSpendingInfo,
|
||||
spendingInfo: InputSigningInfo[InputInfo],
|
||||
unsignedTx: Transaction): TxSigComponent = {
|
||||
val index = inputIndex(spendingInfo, unsignedTx)
|
||||
|
||||
|
@ -85,7 +85,7 @@ sealed abstract class SignerUtils {
|
|||
case _: WitnessScriptPubKey =>
|
||||
val wtx = {
|
||||
val noWitnessWtx = WitnessTransaction.toWitnessTx(unsignedTx)
|
||||
spendingInfo.scriptWitnessOpt match {
|
||||
InputInfo.getScriptWitness(spendingInfo.inputInfo) match {
|
||||
case None =>
|
||||
noWitnessWtx
|
||||
case Some(scriptWitness) =>
|
||||
|
@ -96,17 +96,19 @@ sealed abstract class SignerUtils {
|
|||
WitnessTxSigComponent(wtx, index, spendingInfo.output, flags)
|
||||
case _: P2SHScriptPubKey =>
|
||||
val emptyInput = unsignedTx.inputs(index.toInt)
|
||||
val redeemScript = InputInfo.getRedeemScript(spendingInfo.inputInfo).get
|
||||
val newInput = TransactionInput(
|
||||
emptyInput.previousOutput,
|
||||
P2SHScriptSignature(EmptyScriptSignature,
|
||||
spendingInfo.redeemScriptOpt.get),
|
||||
P2SHScriptSignature(EmptyScriptSignature, redeemScript),
|
||||
emptyInput.sequence)
|
||||
val updatedTx = unsignedTx.updateInput(index.toInt, newInput)
|
||||
spendingInfo.redeemScriptOpt.get match {
|
||||
redeemScript match {
|
||||
case _: WitnessScriptPubKey =>
|
||||
val wtx = WitnessTransaction.toWitnessTx(updatedTx)
|
||||
val updatedWtx =
|
||||
wtx.updateWitness(index.toInt, spendingInfo.scriptWitnessOpt.get)
|
||||
wtx.updateWitness(
|
||||
index.toInt,
|
||||
InputInfo.getScriptWitness(spendingInfo.inputInfo).get)
|
||||
|
||||
WitnessTxSigComponentP2SH(updatedWtx,
|
||||
index,
|
||||
|
@ -122,8 +124,7 @@ sealed abstract class SignerUtils {
|
|||
}
|
||||
|
||||
/** The class used to represent a signing process for a specific [[org.bitcoins.core.protocol.script.ScriptPubKey]] type */
|
||||
sealed abstract class Signer[-SpendingInfo <: UTXOSpendingInfoFull]
|
||||
extends SignerUtils {
|
||||
sealed abstract class Signer[-InputType <: InputInfo] extends SignerUtils {
|
||||
|
||||
/**
|
||||
* The method used to sign a bitcoin unspent transaction output
|
||||
|
@ -133,7 +134,7 @@ sealed abstract class Signer[-SpendingInfo <: UTXOSpendingInfoFull]
|
|||
* @return
|
||||
*/
|
||||
def sign(
|
||||
spendingInfo: SpendingInfo,
|
||||
spendingInfo: ScriptSignatureParams[InputType],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean)(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
|
@ -150,14 +151,14 @@ sealed abstract class Signer[-SpendingInfo <: UTXOSpendingInfoFull]
|
|||
* @param spendingInfo - The information required for signing
|
||||
* @param unsignedTx the external Transaction that needs an input signed
|
||||
* @param isDummySignature - do not sign the tx for real, just use a dummy signature this is useful for fee estimation
|
||||
* @param spendingInfoToSatisfy - specifies the UTXOSpendingInfo whose ScriptPubKey needs a ScriptSignature to be generated
|
||||
* @param spendingInfoToSatisfy - specifies the NewSpendingInfo whose ScriptPubKey needs a ScriptSignature to be generated
|
||||
* @return
|
||||
*/
|
||||
def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: ScriptSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: SpendingInfo)(
|
||||
spendingInfoToSatisfy: ScriptSignatureParams[InputType])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent]
|
||||
|
||||
/** Creates a BaseTxSigComponent by replacing the unsignedTx input at inputIndex
|
||||
|
@ -192,15 +193,10 @@ sealed abstract class Signer[-SpendingInfo <: UTXOSpendingInfoFull]
|
|||
}
|
||||
}
|
||||
|
||||
/** Represents all signers for the bitcoin protocol, we could add another network later like litecoin */
|
||||
sealed abstract class BitcoinSigner[
|
||||
-SpendingInfo <: BitcoinUTXOSpendingInfoFull]
|
||||
extends Signer[SpendingInfo]
|
||||
|
||||
object BitcoinSigner extends SignerUtils {
|
||||
|
||||
def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: ScriptSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean)(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
|
@ -208,45 +204,68 @@ object BitcoinSigner extends SignerUtils {
|
|||
}
|
||||
|
||||
def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: ScriptSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: UTXOSpendingInfoFull)(
|
||||
spendingInfoToSatisfy: ScriptSignatureParams[InputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
spendingInfoToSatisfy match {
|
||||
case empty: EmptySpendingInfo =>
|
||||
EmptySigner.sign(spendingInfo, unsignedTx, isDummySignature, empty)
|
||||
case p2pk: P2PKSpendingInfo =>
|
||||
P2PKSigner.sign(spendingInfo, unsignedTx, isDummySignature, p2pk)
|
||||
case p2pkh: P2PKHSpendingInfo =>
|
||||
P2PKHSigner.sign(spendingInfo, unsignedTx, isDummySignature, p2pkh)
|
||||
case p2pKWithTimeout: P2PKWithTimeoutSpendingInfo =>
|
||||
def spendingFrom[Info <: InputInfo](
|
||||
inputInfo: Info): ScriptSignatureParams[Info] = {
|
||||
spendingInfoToSatisfy.copy(inputInfo = inputInfo)
|
||||
}
|
||||
|
||||
spendingInfoToSatisfy.inputInfo match {
|
||||
case empty: EmptyInputInfo =>
|
||||
EmptySigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
spendingFrom(empty))
|
||||
case p2pk: P2PKInputInfo =>
|
||||
P2PKSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
spendingFrom(p2pk))
|
||||
case p2pkh: P2PKHInputInfo =>
|
||||
P2PKHSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
spendingFrom(p2pkh))
|
||||
case p2pKWithTimeout: P2PKWithTimeoutInputInfo =>
|
||||
P2PKWithTimeoutSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
p2pKWithTimeout)
|
||||
case p2sh: P2SHSpendingInfoFull =>
|
||||
P2SHSigner.sign(spendingInfo, unsignedTx, isDummySignature, p2sh)
|
||||
case multiSig: MultiSignatureSpendingInfoFull =>
|
||||
spendingFrom(p2pKWithTimeout))
|
||||
case p2sh: P2SHInputInfo =>
|
||||
P2SHSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
spendingFrom(p2sh))
|
||||
case multiSig: MultiSignatureInputInfo =>
|
||||
MultiSigSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
multiSig)
|
||||
case lockTime: LockTimeSpendingInfoFull =>
|
||||
spendingFrom(multiSig))
|
||||
case lockTime: LockTimeInputInfo =>
|
||||
LockTimeSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
lockTime)
|
||||
case conditional: ConditionalSpendingInfoFull =>
|
||||
spendingFrom(lockTime))
|
||||
case conditional: ConditionalInputInfo =>
|
||||
ConditionalSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
conditional)
|
||||
case p2wpkh: P2WPKHV0SpendingInfo =>
|
||||
P2WPKHSigner.sign(spendingInfo, unsignedTx, isDummySignature, p2wpkh)
|
||||
case pw2sh: P2WSHV0SpendingInfoFull =>
|
||||
P2WSHSigner.sign(spendingInfo, unsignedTx, isDummySignature, pw2sh)
|
||||
case _: UnassignedSegwitNativeUTXOSpendingInfo =>
|
||||
spendingFrom(conditional))
|
||||
case p2wpkh: P2WPKHV0InputInfo =>
|
||||
P2WPKHSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
spendingFrom(p2wpkh))
|
||||
case pw2sh: P2WSHV0InputInfo =>
|
||||
P2WSHSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
spendingFrom(pw2sh))
|
||||
case _: UnassignedSegwitNativeInputInfo =>
|
||||
throw new UnsupportedOperationException("Unsupported Segwit version")
|
||||
}
|
||||
}
|
||||
|
@ -265,7 +284,7 @@ object BitcoinSigner extends SignerUtils {
|
|||
psbt: PSBT,
|
||||
inputIndex: Int,
|
||||
signer: Sign,
|
||||
conditionalPath: ConditionalPath = ConditionalPath.NoConditionsLeft,
|
||||
conditionalPath: ConditionalPath = ConditionalPath.NoCondition,
|
||||
isDummySignature: Boolean = false)(
|
||||
implicit ec: ExecutionContext): Future[PSBT] = {
|
||||
// if already signed by this signer
|
||||
|
@ -282,9 +301,7 @@ object BitcoinSigner extends SignerUtils {
|
|||
val spendingInfo =
|
||||
psbt
|
||||
.inputMaps(inputIndex)
|
||||
.toUTXOSpendingInfoSingle(tx.inputs(inputIndex),
|
||||
signer,
|
||||
conditionalPath)
|
||||
.toUTXOSigningInfo(tx.inputs(inputIndex), signer, conditionalPath)
|
||||
|
||||
val txToSign = spendingInfo.output.scriptPubKey match {
|
||||
case _: WitnessScriptPubKey =>
|
||||
|
@ -330,20 +347,19 @@ object BitcoinSigner extends SignerUtils {
|
|||
}
|
||||
|
||||
/** Represents a SingleKeyBitcoinSigner which signs a RawScriptPubKey */
|
||||
sealed abstract class RawSingleKeyBitcoinSigner[
|
||||
-SpendingInfo <: RawScriptUTXOSpendingInfoFull with RawScriptUTXOSpendingInfoSingle]
|
||||
extends BitcoinSigner[SpendingInfo] {
|
||||
sealed abstract class RawSingleKeyBitcoinSigner[-InputType <: RawInputInfo]
|
||||
extends Signer[InputType] {
|
||||
|
||||
def keyAndSigToScriptSig(
|
||||
key: ECPublicKey,
|
||||
sig: ECDigitalSignature,
|
||||
spendingInfo: SpendingInfo): ScriptSignature
|
||||
spendingInfo: InputSigningInfo[InputType]): ScriptSignature
|
||||
|
||||
override def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: ScriptSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: SpendingInfo)(
|
||||
spendingInfoToSatisfy: ScriptSignatureParams[InputType])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
val (_, output, inputIndex, _) =
|
||||
relevantInfo(spendingInfo, unsignedTx)
|
||||
|
@ -365,13 +381,13 @@ sealed abstract class RawSingleKeyBitcoinSigner[
|
|||
}
|
||||
|
||||
/** For signing EmptyScriptPubKeys in tests, should probably not be used in real life. */
|
||||
sealed abstract class EmptySigner extends BitcoinSigner[EmptySpendingInfo] {
|
||||
sealed abstract class EmptySigner extends Signer[EmptyInputInfo] {
|
||||
|
||||
override def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: ScriptSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: EmptySpendingInfo)(
|
||||
spendingInfoToSatisfy: ScriptSignatureParams[EmptyInputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx)
|
||||
|
||||
|
@ -389,12 +405,12 @@ object EmptySigner extends EmptySigner
|
|||
|
||||
/** Used to sign a [[org.bitcoins.core.protocol.script.P2PKScriptPubKey]] */
|
||||
sealed abstract class P2PKSigner
|
||||
extends RawSingleKeyBitcoinSigner[P2PKSpendingInfo] {
|
||||
extends RawSingleKeyBitcoinSigner[P2PKInputInfo] {
|
||||
|
||||
override def keyAndSigToScriptSig(
|
||||
key: ECPublicKey,
|
||||
sig: ECDigitalSignature,
|
||||
spendingInfo: P2PKSpendingInfo): ScriptSignature = {
|
||||
spendingInfo: InputSigningInfo[P2PKInputInfo]): ScriptSignature = {
|
||||
P2PKScriptSignature(sig)
|
||||
}
|
||||
}
|
||||
|
@ -403,12 +419,12 @@ object P2PKSigner extends P2PKSigner
|
|||
|
||||
/** Used to sign a [[org.bitcoins.core.protocol.script.P2PKHScriptPubKey]] */
|
||||
sealed abstract class P2PKHSigner
|
||||
extends RawSingleKeyBitcoinSigner[P2PKHSpendingInfo] {
|
||||
extends RawSingleKeyBitcoinSigner[P2PKHInputInfo] {
|
||||
|
||||
override def keyAndSigToScriptSig(
|
||||
key: ECPublicKey,
|
||||
sig: ECDigitalSignature,
|
||||
spendingInfo: P2PKHSpendingInfo): ScriptSignature = {
|
||||
spendingInfo: InputSigningInfo[P2PKHInputInfo]): ScriptSignature = {
|
||||
P2PKHScriptSignature(sig, key)
|
||||
}
|
||||
}
|
||||
|
@ -416,26 +432,25 @@ sealed abstract class P2PKHSigner
|
|||
object P2PKHSigner extends P2PKHSigner
|
||||
|
||||
sealed abstract class P2PKWithTimeoutSigner
|
||||
extends RawSingleKeyBitcoinSigner[P2PKWithTimeoutSpendingInfo] {
|
||||
extends RawSingleKeyBitcoinSigner[P2PKWithTimeoutInputInfo] {
|
||||
|
||||
override def keyAndSigToScriptSig(
|
||||
key: ECPublicKey,
|
||||
sig: ECDigitalSignature,
|
||||
spendingInfo: P2PKWithTimeoutSpendingInfo): ScriptSignature = {
|
||||
P2PKWithTimeoutScriptSignature(spendingInfo.isBeforeTimeout, sig)
|
||||
spendingInfo: InputSigningInfo[P2PKWithTimeoutInputInfo]): ScriptSignature = {
|
||||
P2PKWithTimeoutScriptSignature(spendingInfo.inputInfo.isBeforeTimeout, sig)
|
||||
}
|
||||
}
|
||||
|
||||
object P2PKWithTimeoutSigner extends P2PKWithTimeoutSigner
|
||||
|
||||
sealed abstract class MultiSigSigner
|
||||
extends BitcoinSigner[MultiSignatureSpendingInfoFull] {
|
||||
sealed abstract class MultiSigSigner extends Signer[MultiSignatureInputInfo] {
|
||||
|
||||
override def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: ScriptSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: MultiSignatureSpendingInfoFull)(
|
||||
spendingInfoToSatisfy: ScriptSignatureParams[MultiSignatureInputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
val (_, output, inputIndex, _) =
|
||||
relevantInfo(spendingInfo, unsignedTx)
|
||||
|
@ -460,12 +475,12 @@ sealed abstract class MultiSigSigner
|
|||
object MultiSigSigner extends MultiSigSigner
|
||||
|
||||
/** Used to sign a [[org.bitcoins.core.protocol.script.P2SHScriptPubKey]] */
|
||||
sealed abstract class P2SHSigner extends BitcoinSigner[P2SHSpendingInfoFull] {
|
||||
sealed abstract class P2SHSigner extends Signer[P2SHInputInfo] {
|
||||
override def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: ScriptSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: P2SHSpendingInfoFull)(
|
||||
spendingInfoToSatisfy: ScriptSignatureParams[P2SHInputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
if (spendingInfoToSatisfy != spendingInfo) {
|
||||
Future.fromTry(TxBuilderError.WrongSigner)
|
||||
|
@ -481,11 +496,12 @@ sealed abstract class P2SHSigner extends BitcoinSigner[P2SHSpendingInfoFull] {
|
|||
val updatedTx =
|
||||
unsignedTx.updateInput(inputIndex.toInt, input)
|
||||
|
||||
val nestedSpendingInfo = spendingInfoToSatisfy.copy(
|
||||
inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo)
|
||||
|
||||
val signedTxEither =
|
||||
BitcoinSigner
|
||||
.sign(spendingInfoToSatisfy.nestedSpendingInfo,
|
||||
updatedTx,
|
||||
isDummySignature)
|
||||
.sign(nestedSpendingInfo, updatedTx, isDummySignature)
|
||||
.map(_.transaction)
|
||||
|
||||
signedTxEither.map { signedTx =>
|
||||
|
@ -493,7 +509,7 @@ sealed abstract class P2SHSigner extends BitcoinSigner[P2SHSpendingInfoFull] {
|
|||
|
||||
val p2sh =
|
||||
P2SHScriptSignature(i.scriptSignature,
|
||||
spendingInfoToSatisfy.redeemScript)
|
||||
spendingInfoToSatisfy.inputInfo.redeemScript)
|
||||
|
||||
val signedInput =
|
||||
TransactionInput(i.previousOutput, p2sh, i.sequence)
|
||||
|
@ -525,13 +541,13 @@ sealed abstract class P2SHSigner extends BitcoinSigner[P2SHSpendingInfoFull] {
|
|||
|
||||
object P2SHSigner extends P2SHSigner
|
||||
|
||||
sealed abstract class P2WPKHSigner extends BitcoinSigner[P2WPKHV0SpendingInfo] {
|
||||
sealed abstract class P2WPKHSigner extends Signer[P2WPKHV0InputInfo] {
|
||||
|
||||
override def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: ScriptSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: P2WPKHV0SpendingInfo)(
|
||||
spendingInfoToSatisfy: ScriptSignatureParams[P2WPKHV0InputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
if (spendingInfoToSatisfy != spendingInfo) {
|
||||
Future.fromTry(TxBuilderError.WrongSigner)
|
||||
|
@ -545,7 +561,8 @@ sealed abstract class P2WPKHSigner extends BitcoinSigner[P2WPKHV0SpendingInfo] {
|
|||
|
||||
val unsignedTxWitness = TransactionWitness(
|
||||
wtx.witness
|
||||
.updated(inputIndex.toInt, spendingInfoToSatisfy.scriptWitness)
|
||||
.updated(inputIndex.toInt,
|
||||
spendingInfoToSatisfy.inputInfo.scriptWitness)
|
||||
.toVector)
|
||||
|
||||
val unsignedWtx = WitnessTransaction(wtx.version,
|
||||
|
@ -596,14 +613,13 @@ sealed abstract class P2WPKHSigner extends BitcoinSigner[P2WPKHV0SpendingInfo] {
|
|||
}
|
||||
object P2WPKHSigner extends P2WPKHSigner
|
||||
|
||||
sealed abstract class P2WSHSigner
|
||||
extends BitcoinSigner[P2WSHV0SpendingInfoFull] {
|
||||
sealed abstract class P2WSHSigner extends Signer[P2WSHV0InputInfo] {
|
||||
|
||||
override def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: ScriptSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: P2WSHV0SpendingInfoFull)(
|
||||
spendingInfoToSatisfy: ScriptSignatureParams[P2WSHV0InputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
if (spendingInfoToSatisfy != spendingInfo) {
|
||||
Future.fromTry(TxBuilderError.WrongSigner)
|
||||
|
@ -612,16 +628,20 @@ sealed abstract class P2WSHSigner
|
|||
|
||||
val wtx = WitnessTransaction
|
||||
.toWitnessTx(unsignedTx)
|
||||
.updateWitness(inputIndex.toInt, spendingInfoToSatisfy.scriptWitness)
|
||||
.updateWitness(inputIndex.toInt,
|
||||
spendingInfoToSatisfy.inputInfo.scriptWitness)
|
||||
|
||||
val signedSigComponentF = BitcoinSigner.sign(
|
||||
spendingInfo,
|
||||
val nestedSpendingInfo = spendingInfoToSatisfy.copy(
|
||||
inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo)
|
||||
|
||||
val signedSigComponentF = BitcoinSigner.sign(spendingInfo,
|
||||
wtx,
|
||||
isDummySignature,
|
||||
spendingInfoToSatisfy.nestedSpendingInfo)
|
||||
nestedSpendingInfo)
|
||||
|
||||
val scriptWitF = signedSigComponentF.map { signedSigComponent =>
|
||||
P2WSHWitnessV0(spendingInfoToSatisfy.scriptWitness.redeemScript,
|
||||
P2WSHWitnessV0(
|
||||
spendingInfoToSatisfy.inputInfo.scriptWitness.redeemScript,
|
||||
signedSigComponent.scriptSignature)
|
||||
}
|
||||
|
||||
|
@ -640,19 +660,21 @@ sealed abstract class P2WSHSigner
|
|||
}
|
||||
object P2WSHSigner extends P2WSHSigner
|
||||
|
||||
sealed abstract class LockTimeSigner
|
||||
extends BitcoinSigner[LockTimeSpendingInfoFull] {
|
||||
sealed abstract class LockTimeSigner extends Signer[LockTimeInputInfo] {
|
||||
|
||||
override def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: ScriptSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: LockTimeSpendingInfoFull)(
|
||||
spendingInfoToSatisfy: ScriptSignatureParams[LockTimeInputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
val nestedSpendingInfo = spendingInfoToSatisfy.copy(
|
||||
inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo)
|
||||
|
||||
BitcoinSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
spendingInfoToSatisfy.nestedSpendingInfo)
|
||||
nestedSpendingInfo)
|
||||
}
|
||||
}
|
||||
object LockTimeSigner extends LockTimeSigner
|
||||
|
@ -660,26 +682,27 @@ object LockTimeSigner extends LockTimeSigner
|
|||
/** Delegates to get a ScriptSignature for the case being
|
||||
* spent and then adds an OP_TRUE or OP_FALSE
|
||||
*/
|
||||
sealed abstract class ConditionalSigner
|
||||
extends BitcoinSigner[ConditionalSpendingInfoFull] {
|
||||
sealed abstract class ConditionalSigner extends Signer[ConditionalInputInfo] {
|
||||
|
||||
override def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: ScriptSignatureParams[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: ConditionalSpendingInfoFull)(
|
||||
spendingInfoToSatisfy: ScriptSignatureParams[ConditionalInputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx)
|
||||
|
||||
val missingOpSigComponentF = BitcoinSigner.sign(
|
||||
spendingInfo,
|
||||
val nestedSpendingInfo = spendingInfoToSatisfy.copy(
|
||||
inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo)
|
||||
|
||||
val missingOpSigComponentF = BitcoinSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
spendingInfoToSatisfy.nestedSpendingInfo)
|
||||
nestedSpendingInfo)
|
||||
|
||||
val scriptSigF = missingOpSigComponentF.map { sigComponent =>
|
||||
ConditionalScriptSignature(sigComponent.scriptSignature,
|
||||
spendingInfoToSatisfy.condition)
|
||||
spendingInfoToSatisfy.inputInfo.condition)
|
||||
}
|
||||
|
||||
updateScriptSigInSigComponent(unsignedTx,
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package org.bitcoins.core.wallet.utxo
|
||||
|
||||
/** Represents the spending branch being taken in a ScriptPubKey's execution
|
||||
*
|
||||
* If you over-specify a path, such as giving a condition where none is needed,
|
||||
* then the remaining over-specified path will be ignored.
|
||||
*
|
||||
* For example, if you wanted to spend a ConditionalScriptPubKey(P2PK1, P2PK2)
|
||||
* (which looks like OP_IF <P2PK1> OP_ELSE <P2PK2> OP_ENDIF) with the P2PK1 case,
|
||||
* then you would construct a ConditionalSpendingInfo using nonNestedTrue as your
|
||||
* ConditionalPath. Otherwise if you wanted to use P2PK2 you would use nonNestedFalse.
|
||||
*/
|
||||
sealed trait ConditionalPath {
|
||||
def headOption: Option[Boolean]
|
||||
}
|
||||
|
||||
sealed trait Conditional extends ConditionalPath {
|
||||
def condition: Boolean
|
||||
|
||||
override def headOption: Option[Boolean] = Some(condition)
|
||||
}
|
||||
|
||||
object ConditionalPath {
|
||||
case object NoCondition extends ConditionalPath {
|
||||
override val headOption: Option[Boolean] = None
|
||||
}
|
||||
|
||||
case class ConditionTrue(nextCondition: ConditionalPath) extends Conditional {
|
||||
override val condition: Boolean = true
|
||||
}
|
||||
case class ConditionFalse(nextCondition: ConditionalPath)
|
||||
extends Conditional {
|
||||
override val condition: Boolean = false
|
||||
}
|
||||
|
||||
val nonNestedTrue: ConditionalPath = ConditionTrue(NoCondition)
|
||||
val nonNestedFalse: ConditionalPath = ConditionFalse(NoCondition)
|
||||
|
||||
def fromBranch(branch: Vector[Boolean]): ConditionalPath = {
|
||||
if (branch.isEmpty) {
|
||||
NoCondition
|
||||
} else {
|
||||
if (branch.head) {
|
||||
ConditionTrue(fromBranch(branch.tail))
|
||||
} else {
|
||||
ConditionFalse(fromBranch(branch.tail))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,467 @@
|
|||
package org.bitcoins.core.wallet.utxo
|
||||
|
||||
import org.bitcoins.core.currency.CurrencyUnit
|
||||
import org.bitcoins.core.protocol.script._
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
OutputReference,
|
||||
TransactionOutPoint,
|
||||
TransactionOutput
|
||||
}
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.crypto.{ECPublicKey, NetworkElement, Sign}
|
||||
|
||||
/** An InputInfo contains all information other than private keys about
|
||||
* a particular spending condition in a UTXO.
|
||||
*
|
||||
* Note that while some pieces of information (TxOutPoint, amount, etc.)
|
||||
* apply to all input types, other pieces are specific to particular ones
|
||||
* such as a witness to a SegWit input.
|
||||
*/
|
||||
sealed trait InputInfo {
|
||||
def outPoint: TransactionOutPoint
|
||||
|
||||
def amount: CurrencyUnit
|
||||
|
||||
def scriptPubKey: ScriptPubKey
|
||||
|
||||
def output: TransactionOutput = {
|
||||
TransactionOutput(amount, scriptPubKey)
|
||||
}
|
||||
|
||||
def outputReference: OutputReference = {
|
||||
OutputReference(outPoint, output)
|
||||
}
|
||||
|
||||
def conditionalPath: ConditionalPath
|
||||
|
||||
def pubKeys: Vector[ECPublicKey]
|
||||
|
||||
def toSpendingInfo(
|
||||
signers: Vector[Sign],
|
||||
hashType: HashType): ScriptSignatureParams[InputInfo] = {
|
||||
ScriptSignatureParams(this, signers, hashType)
|
||||
}
|
||||
|
||||
def toSpendingInfo(
|
||||
signer: Sign,
|
||||
hashType: HashType): ECSignatureParams[InputInfo] = {
|
||||
ECSignatureParams(this, signer, hashType)
|
||||
}
|
||||
|
||||
def withSignFrom(
|
||||
signerMaterial: ScriptSignatureParams[InputInfo]): ScriptSignatureParams[
|
||||
this.type] = {
|
||||
signerMaterial.copy(inputInfo = this)
|
||||
}
|
||||
|
||||
def withSignFrom(
|
||||
signerMaterial: ECSignatureParams[InputInfo]): ECSignatureParams[
|
||||
this.type] = {
|
||||
signerMaterial.copy(inputInfo = this)
|
||||
}
|
||||
}
|
||||
|
||||
object InputInfo {
|
||||
|
||||
def getRedeemScript(inputInfo: InputInfo): Option[ScriptPubKey] = {
|
||||
inputInfo match {
|
||||
case _: RawInputInfo | _: SegwitV0NativeInputInfo |
|
||||
_: UnassignedSegwitNativeInputInfo =>
|
||||
None
|
||||
case info: P2SHInputInfo => Some(info.redeemScript)
|
||||
}
|
||||
}
|
||||
|
||||
def getScriptWitness(inputInfo: InputInfo): Option[ScriptWitness] = {
|
||||
inputInfo match {
|
||||
case _: RawInputInfo | _: P2SHNonSegwitInputInfo => None
|
||||
case info: SegwitV0NativeInputInfo => Some(info.scriptWitness)
|
||||
case info: P2SHNestedSegwitV0InputInfo => Some(info.scriptWitness)
|
||||
case info: UnassignedSegwitNativeInputInfo => Some(info.scriptWitness)
|
||||
}
|
||||
}
|
||||
|
||||
def getHashPreImages(inputInfo: InputInfo): Vector[NetworkElement] = {
|
||||
inputInfo match {
|
||||
case info: P2WSHV0InputInfo => info.hashPreImages
|
||||
case info: P2SHInputInfo => info.hashPreImages
|
||||
case info: P2PKHInputInfo => Vector(info.pubKey)
|
||||
case info: LockTimeInputInfo => info.hashPreImages
|
||||
case info: ConditionalInputInfo => info.hashPreImages
|
||||
case _: UnassignedSegwitNativeInputInfo | _: EmptyInputInfo |
|
||||
_: P2PKInputInfo | _: P2PKWithTimeoutInputInfo |
|
||||
_: MultiSignatureInputInfo | _: P2WPKHV0InputInfo =>
|
||||
Vector.empty
|
||||
}
|
||||
}
|
||||
|
||||
def getPKHPreImage(inputInfo: InputInfo): Option[ECPublicKey] = {
|
||||
getHashPreImages(inputInfo).collectFirst {
|
||||
case pubKey: ECPublicKey => pubKey
|
||||
}
|
||||
}
|
||||
|
||||
def apply(
|
||||
outPoint: TransactionOutPoint,
|
||||
output: TransactionOutput,
|
||||
redeemScriptOpt: Option[ScriptPubKey],
|
||||
scriptWitnessOpt: Option[ScriptWitness],
|
||||
conditionalPath: ConditionalPath,
|
||||
hashPreImages: Vector[NetworkElement] = Vector.empty): InputInfo = {
|
||||
output.scriptPubKey match {
|
||||
case _: P2SHScriptPubKey =>
|
||||
redeemScriptOpt match {
|
||||
case None =>
|
||||
throw new IllegalArgumentException(
|
||||
"Redeem Script must be defined for P2SH.")
|
||||
case Some(redeemScript) =>
|
||||
redeemScript match {
|
||||
case _: WitnessScriptPubKeyV0 =>
|
||||
val witness = scriptWitnessOpt match {
|
||||
case Some(witness: ScriptWitnessV0) => witness
|
||||
case None =>
|
||||
throw new IllegalArgumentException(
|
||||
"Script Witness must be defined for (nested) Segwit input")
|
||||
case Some(_: ScriptWitness) =>
|
||||
throw new UnsupportedOperationException(
|
||||
"Only v0 Segwit is currently supported")
|
||||
}
|
||||
P2SHNestedSegwitV0InputInfo(outPoint,
|
||||
output.value,
|
||||
witness,
|
||||
conditionalPath,
|
||||
hashPreImages)
|
||||
case nonWitnessSPK: RawScriptPubKey =>
|
||||
P2SHNonSegwitInputInfo(outPoint,
|
||||
output.value,
|
||||
nonWitnessSPK,
|
||||
conditionalPath,
|
||||
hashPreImages)
|
||||
case _: P2SHScriptPubKey =>
|
||||
throw new IllegalArgumentException("Cannot have nested P2SH")
|
||||
case _: UnassignedWitnessScriptPubKey =>
|
||||
throw new UnsupportedOperationException(
|
||||
s"Unsupported ScriptPubKey ${output.scriptPubKey}")
|
||||
}
|
||||
}
|
||||
case _: WitnessScriptPubKeyV0 =>
|
||||
val witness = scriptWitnessOpt match {
|
||||
case Some(witness: ScriptWitnessV0) => witness
|
||||
case None =>
|
||||
throw new IllegalArgumentException(
|
||||
"Script Witness must be defined for Segwit input")
|
||||
case Some(_: ScriptWitness) =>
|
||||
throw new UnsupportedOperationException(
|
||||
"Only v0 Segwit is currently supported")
|
||||
}
|
||||
SegwitV0NativeInputInfo(outPoint,
|
||||
output.value,
|
||||
witness,
|
||||
conditionalPath,
|
||||
hashPreImages)
|
||||
case wspk: UnassignedWitnessScriptPubKey =>
|
||||
UnassignedSegwitNativeInputInfo(
|
||||
outPoint,
|
||||
output.value,
|
||||
wspk,
|
||||
scriptWitnessOpt.getOrElse(EmptyScriptWitness),
|
||||
conditionalPath,
|
||||
Vector.empty)
|
||||
case rawSPK: RawScriptPubKey =>
|
||||
RawInputInfo(outPoint,
|
||||
output.value,
|
||||
rawSPK,
|
||||
conditionalPath,
|
||||
hashPreImages)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed trait RawInputInfo extends InputInfo {
|
||||
override def scriptPubKey: RawScriptPubKey
|
||||
}
|
||||
|
||||
object RawInputInfo {
|
||||
|
||||
def apply(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: RawScriptPubKey,
|
||||
conditionalPath: ConditionalPath,
|
||||
hashPreImages: Vector[NetworkElement] = Vector.empty): RawInputInfo = {
|
||||
scriptPubKey match {
|
||||
case p2pk: P2PKScriptPubKey => P2PKInputInfo(outPoint, amount, p2pk)
|
||||
case p2pkh: P2PKHScriptPubKey =>
|
||||
hashPreImages.collectFirst {
|
||||
case pubKey: ECPublicKey => pubKey
|
||||
} match {
|
||||
case None =>
|
||||
throw new IllegalArgumentException(
|
||||
"P2PKH pre-image must be specified for P2PKH ScriptPubKey")
|
||||
case Some(p2pkhPreImage) =>
|
||||
require(
|
||||
P2PKHScriptPubKey(p2pkhPreImage) == p2pkh,
|
||||
s"Specified P2PKH pre-image ($p2pkhPreImage) does not match $p2pkh")
|
||||
|
||||
P2PKHInputInfo(outPoint, amount, p2pkhPreImage)
|
||||
}
|
||||
case p2pkWithTimeout: P2PKWithTimeoutScriptPubKey =>
|
||||
conditionalPath.headOption match {
|
||||
case None =>
|
||||
throw new IllegalArgumentException(
|
||||
"ConditionalPath must be specified for P2PKWithTimeout")
|
||||
case Some(beforeTimeout) =>
|
||||
P2PKWithTimeoutInputInfo(outPoint,
|
||||
amount,
|
||||
p2pkWithTimeout,
|
||||
beforeTimeout)
|
||||
}
|
||||
case multiSig: MultiSignatureScriptPubKey =>
|
||||
MultiSignatureInputInfo(outPoint, amount, multiSig)
|
||||
case conditional: ConditionalScriptPubKey =>
|
||||
ConditionalInputInfo(outPoint,
|
||||
amount,
|
||||
conditional,
|
||||
conditionalPath,
|
||||
hashPreImages)
|
||||
case lockTime: LockTimeScriptPubKey =>
|
||||
LockTimeInputInfo(outPoint,
|
||||
amount,
|
||||
lockTime,
|
||||
conditionalPath,
|
||||
hashPreImages)
|
||||
case EmptyScriptPubKey =>
|
||||
EmptyInputInfo(outPoint, amount)
|
||||
case _: NonStandardScriptPubKey | _: WitnessCommitment =>
|
||||
throw new UnsupportedOperationException(
|
||||
s"Currently unsupported ScriptPubKey $scriptPubKey")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class EmptyInputInfo(outPoint: TransactionOutPoint, amount: CurrencyUnit)
|
||||
extends RawInputInfo {
|
||||
override def scriptPubKey: EmptyScriptPubKey.type = EmptyScriptPubKey
|
||||
override def conditionalPath: ConditionalPath =
|
||||
ConditionalPath.NoCondition
|
||||
override def pubKeys: Vector[ECPublicKey] = Vector.empty
|
||||
}
|
||||
|
||||
case class P2PKInputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: P2PKScriptPubKey)
|
||||
extends RawInputInfo {
|
||||
override def conditionalPath: ConditionalPath =
|
||||
ConditionalPath.NoCondition
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = Vector(scriptPubKey.publicKey)
|
||||
}
|
||||
|
||||
case class P2PKHInputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
pubKey: ECPublicKey)
|
||||
extends RawInputInfo {
|
||||
override def scriptPubKey: P2PKHScriptPubKey = P2PKHScriptPubKey(pubKey)
|
||||
|
||||
override def conditionalPath: ConditionalPath =
|
||||
ConditionalPath.NoCondition
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = Vector(pubKey)
|
||||
}
|
||||
|
||||
case class P2PKWithTimeoutInputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: P2PKWithTimeoutScriptPubKey,
|
||||
isBeforeTimeout: Boolean)
|
||||
extends RawInputInfo {
|
||||
override def conditionalPath: ConditionalPath = {
|
||||
if (isBeforeTimeout) {
|
||||
ConditionalPath.nonNestedTrue
|
||||
} else {
|
||||
ConditionalPath.nonNestedFalse
|
||||
}
|
||||
}
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] =
|
||||
Vector(scriptPubKey.pubKey, scriptPubKey.timeoutPubKey)
|
||||
}
|
||||
|
||||
case class MultiSignatureInputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: MultiSignatureScriptPubKey)
|
||||
extends RawInputInfo {
|
||||
override def conditionalPath: ConditionalPath =
|
||||
ConditionalPath.NoCondition
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = scriptPubKey.publicKeys.toVector
|
||||
}
|
||||
|
||||
case class ConditionalInputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: ConditionalScriptPubKey,
|
||||
conditionalPath: ConditionalPath,
|
||||
hashPreImages: Vector[NetworkElement] = Vector.empty)
|
||||
extends RawInputInfo {
|
||||
lazy val (condition: Boolean, nextConditionalPath: ConditionalPath) =
|
||||
conditionalPath match {
|
||||
case ConditionalPath.ConditionTrue(nextCondition) =>
|
||||
(true, nextCondition)
|
||||
case ConditionalPath.ConditionFalse(nextCondition) =>
|
||||
(false, nextCondition)
|
||||
case ConditionalPath.NoCondition =>
|
||||
throw new IllegalArgumentException("Must specify True or False")
|
||||
}
|
||||
|
||||
val nestedInputInfo: RawInputInfo = {
|
||||
val nestedSPK = if (condition) {
|
||||
scriptPubKey.trueSPK
|
||||
} else {
|
||||
scriptPubKey.falseSPK
|
||||
}
|
||||
|
||||
RawInputInfo(outPoint,
|
||||
amount,
|
||||
nestedSPK,
|
||||
nextConditionalPath,
|
||||
hashPreImages)
|
||||
}
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||
}
|
||||
|
||||
case class LockTimeInputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: LockTimeScriptPubKey,
|
||||
conditionalPath: ConditionalPath,
|
||||
hashPreImages: Vector[NetworkElement] = Vector.empty
|
||||
) extends RawInputInfo {
|
||||
|
||||
val nestedInputInfo: RawInputInfo = RawInputInfo(
|
||||
outPoint,
|
||||
amount,
|
||||
scriptPubKey.nestedScriptPubKey,
|
||||
conditionalPath,
|
||||
hashPreImages)
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||
}
|
||||
|
||||
sealed trait SegwitV0NativeInputInfo extends InputInfo {
|
||||
def scriptWitness: ScriptWitnessV0
|
||||
}
|
||||
|
||||
object SegwitV0NativeInputInfo {
|
||||
|
||||
def apply(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptWitness: ScriptWitnessV0,
|
||||
conditionalPath: ConditionalPath,
|
||||
hashPreImages: Vector[NetworkElement] = Vector.empty): SegwitV0NativeInputInfo = {
|
||||
scriptWitness match {
|
||||
case p2wpkh: P2WPKHWitnessV0 =>
|
||||
P2WPKHV0InputInfo(outPoint, amount, p2wpkh.pubKey)
|
||||
case p2wsh: P2WSHWitnessV0 =>
|
||||
P2WSHV0InputInfo(outPoint,
|
||||
amount,
|
||||
p2wsh,
|
||||
conditionalPath,
|
||||
hashPreImages)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class P2WPKHV0InputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
pubKey: ECPublicKey)
|
||||
extends SegwitV0NativeInputInfo {
|
||||
override def scriptPubKey: P2WPKHWitnessSPKV0 = P2WPKHWitnessSPKV0(pubKey)
|
||||
|
||||
override def scriptWitness: P2WPKHWitnessV0 = P2WPKHWitnessV0(pubKey)
|
||||
|
||||
override def conditionalPath: ConditionalPath =
|
||||
ConditionalPath.NoCondition
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = Vector(pubKey)
|
||||
}
|
||||
|
||||
case class P2WSHV0InputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptWitness: P2WSHWitnessV0,
|
||||
conditionalPath: ConditionalPath,
|
||||
hashPreImages: Vector[NetworkElement] = Vector.empty)
|
||||
extends SegwitV0NativeInputInfo {
|
||||
override def scriptPubKey: P2WSHWitnessSPKV0 =
|
||||
P2WSHWitnessSPKV0(scriptWitness.redeemScript)
|
||||
|
||||
val nestedInputInfo: RawInputInfo =
|
||||
RawInputInfo(outPoint,
|
||||
amount,
|
||||
scriptWitness.redeemScript,
|
||||
conditionalPath,
|
||||
hashPreImages)
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||
}
|
||||
|
||||
case class UnassignedSegwitNativeInputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: WitnessScriptPubKey,
|
||||
scriptWitness: ScriptWitness,
|
||||
conditionalPath: ConditionalPath,
|
||||
pubKeys: Vector[ECPublicKey])
|
||||
extends InputInfo
|
||||
|
||||
sealed trait P2SHInputInfo extends InputInfo {
|
||||
def hashPreImages: Vector[NetworkElement]
|
||||
|
||||
def redeemScript: ScriptPubKey
|
||||
|
||||
override def scriptPubKey: P2SHScriptPubKey = P2SHScriptPubKey(redeemScript)
|
||||
|
||||
def nestedInputInfo: InputInfo
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||
}
|
||||
|
||||
case class P2SHNonSegwitInputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
redeemScript: RawScriptPubKey,
|
||||
conditionalPath: ConditionalPath,
|
||||
hashPreImages: Vector[NetworkElement] = Vector.empty)
|
||||
extends P2SHInputInfo {
|
||||
|
||||
override val nestedInputInfo: RawInputInfo =
|
||||
RawInputInfo(outPoint, amount, redeemScript, conditionalPath, hashPreImages)
|
||||
}
|
||||
|
||||
case class P2SHNestedSegwitV0InputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptWitness: ScriptWitnessV0,
|
||||
conditionalPath: ConditionalPath,
|
||||
hashPreImages: Vector[NetworkElement] = Vector.empty)
|
||||
extends P2SHInputInfo {
|
||||
|
||||
override def redeemScript: WitnessScriptPubKeyV0 = scriptWitness match {
|
||||
case p2wpkh: P2WPKHWitnessV0 => P2WPKHWitnessSPKV0(p2wpkh.pubKey)
|
||||
case p2wsh: P2WSHWitnessV0 => P2WSHWitnessSPKV0(p2wsh.redeemScript)
|
||||
}
|
||||
|
||||
override val nestedInputInfo: SegwitV0NativeInputInfo =
|
||||
SegwitV0NativeInputInfo(outPoint,
|
||||
amount,
|
||||
scriptWitness,
|
||||
conditionalPath,
|
||||
hashPreImages)
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package org.bitcoins.core.wallet.utxo
|
||||
|
||||
import org.bitcoins.core.currency.CurrencyUnit
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
OutputReference,
|
||||
TransactionOutPoint,
|
||||
TransactionOutput
|
||||
}
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.crypto.Sign
|
||||
|
||||
/** Stores the information required to generate a signature (ECSignatureParams)
|
||||
* or to generate a script signature (ScriptSignatureParams) for a given satisfaction
|
||||
* condition on a UTXO.
|
||||
*/
|
||||
sealed trait InputSigningInfo[+InputType <: InputInfo] {
|
||||
def inputInfo: InputType
|
||||
def hashType: HashType
|
||||
def signers: Vector[Sign]
|
||||
|
||||
private val keysToSignFor = inputInfo.pubKeys
|
||||
require(signers.map(_.publicKey).forall(keysToSignFor.contains),
|
||||
s"Cannot have signers that do not sign for one of $keysToSignFor")
|
||||
|
||||
def outputReference: OutputReference = inputInfo.outputReference
|
||||
def amount: CurrencyUnit = inputInfo.amount
|
||||
def output: TransactionOutput = inputInfo.output
|
||||
def outPoint: TransactionOutPoint = inputInfo.outPoint
|
||||
def conditionalPath: ConditionalPath = inputInfo.conditionalPath
|
||||
}
|
||||
|
||||
/** Stores the information needed to generate a ScriptSignature for a specific
|
||||
* spending condition on a UTXO.
|
||||
*/
|
||||
case class ScriptSignatureParams[+InputType <: InputInfo](
|
||||
inputInfo: InputType,
|
||||
signers: Vector[Sign],
|
||||
hashType: HashType)
|
||||
extends InputSigningInfo[InputType] {
|
||||
|
||||
def signer: Sign = {
|
||||
require(
|
||||
signers.length == 1,
|
||||
"This method is for spending infos with a single signer, if you mean signers.head be explicit")
|
||||
|
||||
signers.head
|
||||
}
|
||||
|
||||
def toSingle(index: Int): ECSignatureParams[InputType] = {
|
||||
ECSignatureParams(inputInfo, signers(index), hashType)
|
||||
}
|
||||
|
||||
def toSingles: Vector[ECSignatureParams[InputType]] = {
|
||||
signers.map { signer =>
|
||||
ECSignatureParams(inputInfo, signer, hashType)
|
||||
}
|
||||
}
|
||||
|
||||
def mapInfo[T <: InputInfo](
|
||||
func: InputType => T): ScriptSignatureParams[T] = {
|
||||
this.copy(inputInfo = func(this.inputInfo))
|
||||
}
|
||||
}
|
||||
|
||||
object ScriptSignatureParams {
|
||||
|
||||
def apply[InputType <: InputInfo](
|
||||
inputInfo: InputType,
|
||||
signer: Sign,
|
||||
hashType: HashType): ScriptSignatureParams[InputType] =
|
||||
ScriptSignatureParams(inputInfo, Vector(signer), hashType)
|
||||
}
|
||||
|
||||
/** Stores the information needed to generate an ECDigitalSignature for
|
||||
* a use in spending a UTXO.
|
||||
*/
|
||||
case class ECSignatureParams[+InputType <: InputInfo](
|
||||
inputInfo: InputType,
|
||||
signer: Sign,
|
||||
hashType: HashType)
|
||||
extends InputSigningInfo[InputType] {
|
||||
override def signers: Vector[Sign] = Vector(signer)
|
||||
|
||||
def mapInfo[T <: InputInfo](func: InputType => T): ECSignatureParams[T] = {
|
||||
this.copy(inputInfo = func(this.inputInfo))
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -30,7 +30,7 @@ import org.bitcoins.core.protocol.script.{Script, ScriptPubKey, ScriptWitness, S
|
|||
import org.bitcoins.core.currency._
|
||||
import org.bitcoins.core.number._
|
||||
import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
|
||||
import org.bitcoins.core.wallet.utxo.{ConditionalPath, MultiSignatureSpendingInfoSingle, MultiSignatureSpendingInfoFull, ConditionalSpendingInfoSingle, ConditionalSpendingInfoFull}
|
||||
import org.bitcoins.core.wallet.utxo.ConditionalPath
|
||||
import org.bitcoins.testkit.core.gen._
|
||||
import org.bitcoins.testkit.core.gen.ScriptGenerators._
|
||||
|
||||
|
@ -47,28 +47,56 @@ sealed trait RawScriptPubKey extends Script
|
|||
implicit def realToFakeRawSPK(real: org.bitcoins.core.protocol.script.RawScriptPubKey): RawScriptPubKey = ???
|
||||
implicit def fakeToRealRawSPK(fake: RawScriptPubKey): org.bitcoins.core.protocol.script.RawScriptPubKey = ???
|
||||
|
||||
sealed trait RawScriptUTXOSpendingInfo {
|
||||
def signer: Sign
|
||||
def signers: Vector[Sign]
|
||||
def requiredSigs: Int
|
||||
sealed trait InputInfo {
|
||||
def pubKeys: Vector[ECPublicKey]
|
||||
def conditionalPath: ConditionalPath
|
||||
def scriptPubKey: Any
|
||||
}
|
||||
sealed trait RawScriptUTXOSpendingInfoFull extends RawScriptUTXOSpendingInfo {
|
||||
def signer: Sign = signers.head
|
||||
def toSingle(signerIndex: Int): Any = ???
|
||||
def toSingles: Vector[Any] = ???
|
||||
|
||||
sealed trait RawInputInfo extends InputInfo
|
||||
|
||||
trait MultiSignatureInputInfo extends RawInputInfo
|
||||
|
||||
trait ConditionalInputInfo extends RawInputInfo {
|
||||
def nestedInputInfo: RawInputInfo
|
||||
def condition: Boolean
|
||||
}
|
||||
implicit def realToFakeRawSpendingInfoFull(real: org.bitcoins.core.wallet.utxo.RawScriptUTXOSpendingInfoFull): RawScriptUTXOSpendingInfoFull = ???
|
||||
implicit def fakeToRealRawSpendingInfoFull(fake: RawScriptUTXOSpendingInfoFull): org.bitcoins.core.wallet.utxo.RawScriptUTXOSpendingInfoFull = ???
|
||||
|
||||
sealed trait UTXOSpendingInfoFull
|
||||
implicit def realToFakeSpendingInfoFull(real: org.bitcoins.core.wallet.utxo.UTXOSpendingInfoFull): UTXOSpendingInfoFull = ???
|
||||
implicit def fakeToRealSpendingInfoFull(fake: UTXOSpendingInfoFull): org.bitcoins.core.wallet.utxo.UTXOSpendingInfoFull = ???
|
||||
object RawInputInfo {
|
||||
def apply(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: RawScriptPubKey,
|
||||
conditionalPath: ConditionalPath,
|
||||
hashPreImages: Vector[NetworkElement] = Vector.empty): RawInputInfo = ???
|
||||
}
|
||||
|
||||
sealed trait UTXOSpendingInfoSingle
|
||||
implicit def realToFakeSpendingInfoSingle(real: org.bitcoins.core.wallet.utxo.UTXOSpendingInfoSingle): UTXOSpendingInfoSingle = ???
|
||||
implicit def fakeToRealSpendingInfoSingle(fake: UTXOSpendingInfoSingle): org.bitcoins.core.wallet.utxo.UTXOSpendingInfoSingle = ???
|
||||
sealed trait UTXOInfo[+InputType <: InputInfo] {
|
||||
def inputInfo: InputType
|
||||
def hashType: HashType
|
||||
def signers: Vector[ECPrivateKey]
|
||||
}
|
||||
|
||||
case class UTXOSatisfyingInfo[+InputType <: InputInfo](
|
||||
inputInfo: InputType,
|
||||
signers: Vector[ECPrivateKey],
|
||||
hashType: HashType)
|
||||
extends UTXOInfo[InputType] {
|
||||
def toSingle(index: Int): UTXOSigningInfo[InputType] = ???
|
||||
|
||||
def toSingles: Vector[UTXOSigningInfo[InputType]] = {
|
||||
signers.map { signer =>
|
||||
UTXOSigningInfo(inputInfo, signer, hashType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class UTXOSigningInfo[+InputType <: InputInfo](
|
||||
inputInfo: InputType,
|
||||
signer: ECPrivateKey,
|
||||
hashType: HashType)
|
||||
extends UTXOInfo[InputType] {
|
||||
override def signers: Vector[ECPrivateKey] = Vector(signer)
|
||||
}
|
||||
|
||||
sealed trait ScriptSignature extends Script
|
||||
implicit def realToFakeRawScriptSig(real: org.bitcoins.core.protocol.script.ScriptSignature): ScriptSignature = ???
|
||||
|
@ -76,45 +104,50 @@ implicit def fakeToRealRawScriptSig(fake: ScriptSignature): org.bitcoins.core.pr
|
|||
implicit def realMultiToFakeRawScriptSigInFuture(real: Future[MultiSignatureScriptSignature]): Future[ScriptSignature] = ???
|
||||
implicit def realConditionalToFakeRawScriptSigInFuture(real: Future[ConditionalScriptSignature]): Future[ScriptSignature] = ???
|
||||
|
||||
sealed trait BitcoinUTXOSpendingInfoFull
|
||||
sealed trait RawScriptUTXOSpendingInfoSingle extends RawScriptUTXOSpendingInfo {
|
||||
override def signers: Vector[Sign] = Vector(signer)
|
||||
override def requiredSigs: Int = 1
|
||||
}
|
||||
sealed trait BitcoinSigner[-SpendingInfo] {
|
||||
sealed abstract class Signer[-InputType <: InputInfo] {
|
||||
|
||||
def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: UTXOSatisfyingInfo[InputType],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean)(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
sign(
|
||||
spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
spendingInfoToSatisfy = spendingInfo
|
||||
)
|
||||
}
|
||||
|
||||
def sign(
|
||||
spendingInfo: UTXOSatisfyingInfo[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: SpendingInfo)(
|
||||
spendingInfoToSatisfy: UTXOSatisfyingInfo[InputType])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent]
|
||||
|
||||
def signSingle(
|
||||
spendingInfo: UTXOSpendingInfoSingle,
|
||||
spendingInfo: UTXOSigningInfo[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean)(
|
||||
implicit ec: ExecutionContext): Future[PartialSignature] = ???
|
||||
}
|
||||
sealed trait RawSingleKeyBitcoinSigner[-SpendingInfo <: RawScriptUTXOSpendingInfoFull with RawScriptUTXOSpendingInfoSingle] {
|
||||
sealed abstract class RawSingleKeyBitcoinSigner[-InputType <: RawInputInfo]
|
||||
extends Signer[InputType] {
|
||||
|
||||
def keyAndSigToScriptSig(
|
||||
key: ECPublicKey,
|
||||
sig: ECDigitalSignature,
|
||||
spendingInfo: SpendingInfo): ScriptSignature
|
||||
spendingInfo: UTXOInfo[InputType]): ScriptSignature
|
||||
|
||||
def sign(
|
||||
spendingInfo: SpendingInfo,
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean)(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = ???
|
||||
|
||||
def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
override def sign(
|
||||
spendingInfo: UTXOSatisfyingInfo[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: SpendingInfo)(
|
||||
spendingInfoToSatisfy: UTXOSatisfyingInfo[InputType])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = ???
|
||||
}
|
||||
|
||||
/*
|
||||
object RawScriptUTXOSpendingInfoFull {
|
||||
def apply(
|
||||
outPoint: TransactionOutPoint,
|
||||
|
@ -133,18 +166,26 @@ object RawScriptUTXOSpendingInfoSingle {
|
|||
signer: Sign,
|
||||
hashType: HashType,
|
||||
conditionalPath: ConditionalPath): RawScriptUTXOSpendingInfoSingle = ???
|
||||
}
|
||||
}*/
|
||||
|
||||
object BitcoinSigner {
|
||||
def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: UTXOSatisfyingInfo[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean)(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
sign(spendingInfo, unsignedTx, isDummySignature, spendingInfo)
|
||||
}
|
||||
|
||||
def sign(
|
||||
spendingInfo: UTXOSatisfyingInfo[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: UTXOSpendingInfoFull)(
|
||||
spendingInfoToSatisfy: UTXOSatisfyingInfo[InputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = ???
|
||||
|
||||
def signSingle(
|
||||
spendingInfo: UTXOSpendingInfoSingle,
|
||||
spendingInfo: UTXOSigningInfo[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean)(
|
||||
implicit ec: ExecutionContext): Future[PartialSignature] = ???
|
||||
|
@ -153,20 +194,20 @@ object BitcoinSigner {
|
|||
def asm: Seq[ScriptToken] = ???
|
||||
def tokens: Seq[ScriptToken] = ???
|
||||
def scriptPubKey: RawScriptPubKey = ???
|
||||
def spendingInfoToSatisfy: UTXOSpendingInfoFull = ???
|
||||
def spendingInfoToSatisfy: UTXOSatisfyingInfo[InputInfo] = ???
|
||||
def conditionalPath: ConditionalPath = ???
|
||||
def outPoint: TransactionOutPoint = ???
|
||||
def amount: CurrencyUnit = ???
|
||||
def signer: Sign = ???
|
||||
def hashType: HashType = ???
|
||||
def beforeTimeout: Boolean = ???
|
||||
def spendingInfo: UTXOSpendingInfoFull = ???
|
||||
def spendingInfo: UTXOSatisfyingInfo[InputInfo] = ???
|
||||
def unsignedTx: Transaction = ???
|
||||
def isDummySignature: Boolean = ???
|
||||
def min: Int = ???
|
||||
def max: Int = ???
|
||||
implicit def ec: ExecutionContext = ???
|
||||
def relevantInfo(spendingInfo: UTXOSpendingInfoFull, unsignedTx: Transaction): (Seq[Sign], TransactionOutput, UInt32, HashType) = ???
|
||||
def relevantInfo(spendingInfo: UTXOSatisfyingInfo[InputInfo], unsignedTx: Transaction): (Seq[Sign], TransactionOutput, UInt32, HashType) = ???
|
||||
def updateScriptSigInSigComponent(
|
||||
unsignedTx: Transaction,
|
||||
inputIndex: Int,
|
||||
|
@ -177,7 +218,9 @@ def build(
|
|||
spk: ScriptPubKey,
|
||||
signers: Seq[Sign],
|
||||
redeemScript: Option[ScriptPubKey],
|
||||
scriptWitness: Option[ScriptWitness]): Gen[BitcoinUTXOSpendingInfoFull] = ???
|
||||
scriptWitness: Option[ScriptWitness]): Gen[UTXOSatisfyingInfo[InputInfo]] = ???
|
||||
def spendingFrom[Info <: InputInfo](
|
||||
inputInfo: Info): UTXOSatisfyingInfo[Info] = ???
|
||||
```
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
@ -454,156 +497,63 @@ tokens match {
|
|||
}
|
||||
```
|
||||
|
||||
## Step 6: Create Relevant BitcoinUTXOSpendingInfo
|
||||
## Step 6: Create Relevant InputInfo
|
||||
|
||||
`BitcoinUTXOSpendingInfo` is the Bitcoin-S data structure for the information required to spend from a given `ScriptPubKey`. Hence, when defining new script types, it is important to define how they are spent as well so that they can be useful.
|
||||
`InputInfo` is the Bitcoin-S data structure for the information required to spend from a specific condition of a given `ScriptPubKey` other than private keys. Hence, when defining new script types, it is important to define how they are spent as well so that they can be useful.
|
||||
|
||||
There are three distinct kinds of scripts when it comes to signing in Bitcoin-S: scripts that have nesting (such as `ConditionalScriptPubKey`, `LockTimeScriptPubKey`), scripts without nesting but which involve multiple signing keys (such as `MultiSignatureScriptPubKey`), and scripts without nesting and which require only one key for signing (such as `P2PKWithTimeoutScriptPubKey`, `P2PKHScriptPubKey`). We will cover each of these cases in turn, starting with the last case as it applies to our example of `P2PKWithTimeout`.
|
||||
There are two distinct kinds of scripts when it comes to signing in Bitcoin-S: scripts that have nesting (such as `ConditionalScriptPubKey`, `P2SHScriptPubKey`) and scripts without nesting (such as `MultiSignatureScriptPubKey`, `P2PKWithTimeoutScriptPubKey`, `P2PKHScriptPubKey`). We will cover each of these cases in turn, starting with the latter case as it applies to our example of `P2PKWithTimeout`. In both cases, please make sure to validate any parameter data using `require` statements when necessary, but make things correct by construction instead whenever possible. For example, if there is a redeem script and a `ScriptPubKey` which must wrap this redeem script, take as a parameter only the redeem script and construct the `ScriptPubKey` internally so that it is sure to be consistent.
|
||||
|
||||
### Non-Nested Single-Key Spending Info
|
||||
### Non-Nesting Input Info
|
||||
|
||||
This is the easiest case and only requires creating a new `case class` in `UTXOSpendingInfo.scala` which extends `RawScriptUTXOSpendingInfoFull with RawScriptUTXOSpendingInfoSingle` and which contains in its parameters, all of the info required for spending. Make sure to also validate any data in these parameters using `require` statements. Make sure to `override` both `requiredSigs: Int = 1` and `conditionalPath: ConditionalPath` to be either whatever is needed based on your parameters, or `ConditionalPath.NoConditionsLeft` if your script does not use any conditionals. Here is what this looks like for `P2PKWithTimeout`:
|
||||
We create a new `case class` in `InputInfo.scala` which extends `RawInputInfo` and which contains in its parameters, all of the info required for spending a specific condition other than private keys and `HashType`. Here is what this looks like for `P2PKWithTimeout`:
|
||||
|
||||
```scala mdoc:silent
|
||||
case class P2PKWithTimeoutSpendingInfo(
|
||||
case class P2PKWithTimeoutInputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: P2PKWithTimeoutScriptPubKey,
|
||||
override val signer: Sign,
|
||||
hashType: HashType,
|
||||
isBeforeTimeout: Boolean)
|
||||
extends RawScriptUTXOSpendingInfoFull
|
||||
with RawScriptUTXOSpendingInfoSingle {
|
||||
require(
|
||||
scriptPubKey.pubKey == signer.publicKey || scriptPubKey.timeoutPubKey == signer.publicKey,
|
||||
"Signer pubkey must match ScriptPubKey")
|
||||
|
||||
override val requiredSigs: Int = 1
|
||||
|
||||
override def conditionalPath: ConditionalPath =
|
||||
extends RawInputInfo {
|
||||
override def conditionalPath: ConditionalPath = {
|
||||
if (isBeforeTimeout) {
|
||||
ConditionalPath.nonNestedTrue
|
||||
} else {
|
||||
ConditionalPath.nonNestedFalse
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Non-Nested Multi-Key Spending Info
|
||||
|
||||
For new script types which require multiple keys to spend, we must make two separate `case class`es, one for normal spending (which extends `RawScriptUTXOSpendingInfoFull`) and one for signing a transaction with a single key (which extends `RawScriptUTXOSpendingInfoSingle`). We then make a `sealed trait` for the both of them to extend. In this `trait`, you want to make sure to `override def scriptPubKey` to have your new `ScriptPubKey` type as well as overriding any other members of `RawScriptUTXOSpendingInfo` which are common to both classes. In total, this all works out to the following for `MutliSignatureScriptPubKey`:
|
||||
|
||||
```scala mdoc:compile-only
|
||||
sealed trait MultiSignatureSpendingInfo extends RawScriptUTXOSpendingInfo {
|
||||
override def conditionalPath: ConditionalPath =
|
||||
ConditionalPath.NoConditionsLeft
|
||||
override def scriptPubKey: MultiSignatureScriptPubKey
|
||||
}
|
||||
|
||||
case class MultiSignatureSpendingInfoSingle(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: MultiSignatureScriptPubKey,
|
||||
signer: Sign,
|
||||
hashType: HashType
|
||||
) extends MultiSignatureSpendingInfo
|
||||
with RawScriptUTXOSpendingInfoSingle
|
||||
|
||||
case class MultiSignatureSpendingInfoFull(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: MultiSignatureScriptPubKey,
|
||||
private val signersWithPossibleExtra: Vector[Sign],
|
||||
hashType: HashType
|
||||
) extends RawScriptUTXOSpendingInfoFull
|
||||
with MultiSignatureSpendingInfo {
|
||||
require(signersWithPossibleExtra.length >= scriptPubKey.requiredSigs,
|
||||
s"Not enough signers!: $this")
|
||||
|
||||
override val requiredSigs: Int = scriptPubKey.requiredSigs
|
||||
|
||||
override val signers: Vector[Sign] =
|
||||
signersWithPossibleExtra.take(requiredSigs)
|
||||
|
||||
override def toSingle(signerIndex: Int): MultiSignatureSpendingInfoSingle = {
|
||||
MultiSignatureSpendingInfoSingle(outPoint,
|
||||
amount,
|
||||
scriptPubKey,
|
||||
signers(signerIndex),
|
||||
hashType)
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override def toSingles: Vector[MultiSignatureSpendingInfoSingle] = {
|
||||
signers.map { signer =>
|
||||
MultiSignatureSpendingInfoSingle(outPoint,
|
||||
amount,
|
||||
scriptPubKey,
|
||||
signer,
|
||||
hashType)
|
||||
}
|
||||
}
|
||||
override def pubKeys: Vector[ECPublicKey] =
|
||||
Vector(scriptPubKey.pubKey, scriptPubKey.timeoutPubKey)
|
||||
}
|
||||
```
|
||||
|
||||
### Nested Spending Info
|
||||
|
||||
This case is very similar to the above where we need to create two case classes one for normal spending (which extends `RawScriptUTXOSpendingInfoFull`) and one for signing a transaction with a single key (which extends `RawScriptUTXOSpendingInfoSingle`). As well as needing to create a `sealed trait` for them both to extend which overrides `scriptPubKey` as well as any other commonalities from the sub-classes. The one new thing in the nested case is that we must create a `def nestedSpendingInfo: RawScriptUTXOSpendingInfo` in our `trait` and make sure to override `signers` and `requiredSigs` from this `nestedSpendingInfo` in the subclass extending `RawScriptUTXOSpendingInfoFull`. For the case of spending `LockTimeScriptPubKey`s, this looks like the following:
|
||||
The one new thing in the nested case is that we must create a `val nestedSpendingInfo: RawInputInfo` and make sure to pass on `hashPreImages: Vector[NetworkElement]` to the nested `InputInfo`, and pull public keys from the `nestedInputInfo`. For the case of spending `LockTimeScriptPubKey`s, this looks like the following:
|
||||
|
||||
```scala mdoc:compile-only
|
||||
sealed trait LockTimeSpendingInfo extends RawScriptUTXOSpendingInfo {
|
||||
override def scriptPubKey: LockTimeScriptPubKey
|
||||
|
||||
def nestedSpendingInfo: RawScriptUTXOSpendingInfo
|
||||
}
|
||||
|
||||
case class LockTimeSpendingInfoSingle(
|
||||
case class LockTimeInputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: LockTimeScriptPubKey,
|
||||
signer: Sign,
|
||||
hashType: HashType,
|
||||
conditionalPath: ConditionalPath
|
||||
) extends LockTimeSpendingInfo
|
||||
with RawScriptUTXOSpendingInfoSingle {
|
||||
override val nestedSpendingInfo: RawScriptUTXOSpendingInfoSingle = {
|
||||
RawScriptUTXOSpendingInfoSingle(outPoint,
|
||||
conditionalPath: ConditionalPath,
|
||||
hashPreImages: Vector[NetworkElement] = Vector.empty
|
||||
) extends RawInputInfo {
|
||||
|
||||
val nestedInputInfo: RawInputInfo = RawInputInfo(
|
||||
outPoint,
|
||||
amount,
|
||||
scriptPubKey.nestedScriptPubKey,
|
||||
signer,
|
||||
hashType,
|
||||
conditionalPath)
|
||||
}
|
||||
}
|
||||
conditionalPath,
|
||||
hashPreImages)
|
||||
|
||||
case class LockTimeSpendingInfoFull(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: LockTimeScriptPubKey,
|
||||
private val signersWithPossibleExtra: Vector[Sign],
|
||||
hashType: HashType,
|
||||
conditionalPath: ConditionalPath
|
||||
) extends RawScriptUTXOSpendingInfoFull
|
||||
with LockTimeSpendingInfo {
|
||||
|
||||
override val nestedSpendingInfo: RawScriptUTXOSpendingInfoFull = {
|
||||
RawScriptUTXOSpendingInfoFull(outPoint,
|
||||
amount,
|
||||
scriptPubKey.nestedScriptPubKey,
|
||||
signersWithPossibleExtra,
|
||||
hashType,
|
||||
conditionalPath)
|
||||
}
|
||||
|
||||
override val signers: Vector[Sign] = nestedSpendingInfo.signers
|
||||
|
||||
override val requiredSigs: Int = nestedSpendingInfo.requiredSigs
|
||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||
}
|
||||
```
|
||||
|
||||
## Step 7: Add to Relevant Apply Methods
|
||||
|
||||
Now that we have created our new `RawScriptUTXOSpendingInfoFull` and `RawScriptUTXOSpendingInfoSingle`, possibly in the same class, we need to add them to the general-purpose spending info constructors. This means adding a `case` to both `RawScriptUTXOSpendingInfoSingle.apply` and `RawScriptUTXOSpendingInfoFull.apply` for your new `ScriptPubKey` type which constructs your relevant `RawScriptUTXOSpendingInfoSingle` and `RawScriptUTXOSpendingInfoFull` from generic types (given as parameters in the `apply` methods). For `P2PKWithTimeout`, both of these cases look like the following:
|
||||
Now that we have created our new `RawInputInfo`, we need to add them to the general-purpose input info constructors. This means adding a `case` to `RawInputInfo.apply` for your new `ScriptPubKey` type which constructs your relevant `RawInputInfo` from generic types (given as parameters in the `apply` methods). For `P2PKWithTimeout`, this looks like the following:
|
||||
|
||||
```scala mdoc:compile-only
|
||||
scriptPubKey match {
|
||||
|
@ -614,11 +564,9 @@ Now that we have created our new `RawScriptUTXOSpendingInfoFull` and `RawScriptU
|
|||
throw new IllegalArgumentException(
|
||||
"ConditionalPath must be specified for P2PKWithTimeout")
|
||||
case Some(beforeTimeout) =>
|
||||
P2PKWithTimeoutSpendingInfo(outPoint,
|
||||
P2PKWithTimeoutInputInfo(outPoint,
|
||||
amount,
|
||||
p2pkWithTimeout,
|
||||
signer,
|
||||
hashType,
|
||||
beforeTimeout)
|
||||
}
|
||||
//...
|
||||
|
@ -627,21 +575,21 @@ Now that we have created our new `RawScriptUTXOSpendingInfoFull` and `RawScriptU
|
|||
|
||||
## Step 8: Create a Signer
|
||||
|
||||
We must now add signing functionality for our new script type within `Signer.scala`. Once again, we have three different cases depending on your new script type.
|
||||
We must now add signing functionality for our new script type within `Signer.scala`. This time, we have three different cases depending on your new script type.
|
||||
|
||||
### Non-Nested Single-Key Spending Info
|
||||
|
||||
For this case, all we must do is create a new class which extends `RawSingleKeyBitcoinSigner` and implements `keyAndSigToScriptSig`. For `P2PKWithTimeout` this looks like the following:
|
||||
For the non-nested case where only a single key is required, all we must do is create a new class which extends `RawSingleKeyBitcoinSigner` and implements `keyAndSigToScriptSig`. For `P2PKWithTimeout` this looks like the following:
|
||||
|
||||
```scala mdoc:silent
|
||||
sealed abstract class P2PKWithTimeoutSigner
|
||||
extends RawSingleKeyBitcoinSigner[P2PKWithTimeoutSpendingInfo] {
|
||||
extends RawSingleKeyBitcoinSigner[P2PKWithTimeoutInputInfo] {
|
||||
|
||||
override def keyAndSigToScriptSig(
|
||||
key: ECPublicKey,
|
||||
sig: ECDigitalSignature,
|
||||
spendingInfo: P2PKWithTimeoutSpendingInfo): ScriptSignature = {
|
||||
P2PKWithTimeoutScriptSignature(spendingInfo.isBeforeTimeout, sig)
|
||||
spendingInfo: UTXOInfo[P2PKWithTimeoutInputInfo]): ScriptSignature = {
|
||||
P2PKWithTimeoutScriptSignature(spendingInfo.inputInfo.isBeforeTimeout, sig)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -650,17 +598,16 @@ object P2PKWithTimeoutSigner extends P2PKWithTimeoutSigner
|
|||
|
||||
### Non-Nested Multi-Key Spending Info
|
||||
|
||||
In this case we must create a new `BitcoinSignerFull`, which requires implementing the `sign` function. For `MultiSignature` this looks like the following:
|
||||
In the non-nested case where multiple keys are required, we must create a new `Signer`, which requires implementing the `sign` function. For `MultiSignature` this looks like the following:
|
||||
|
||||
```scala mdoc:compile-only
|
||||
sealed abstract class MultiSigSigner
|
||||
extends BitcoinSigner[MultiSignatureSpendingInfoFull] {
|
||||
sealed abstract class MultiSigSigner extends Signer[MultiSignatureInputInfo] {
|
||||
|
||||
override def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: UTXOSatisfyingInfo[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: MultiSignatureSpendingInfoFull)(
|
||||
spendingInfoToSatisfy: UTXOSatisfyingInfo[MultiSignatureInputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
val (_, output, inputIndex, _) =
|
||||
relevantInfo(spendingInfo, unsignedTx)
|
||||
|
@ -687,32 +634,33 @@ object MultiSigSigner extends MultiSigSigner
|
|||
|
||||
### Nested Spending Info
|
||||
|
||||
When signing for a nested script structure, we must create a new `BitcoinSignerFull`. You will need to make a delegating call with the `nestedSpendingInfo` to `BitcoinSigner.sign`, but you may also need to do whatever else is needed with the nested result to construct a correct `ScriptSignature`. For `ConditionalScriptSignature`, this all looks like:
|
||||
When signing for a nested script structure, we must create a new `Signer`. You will need to make a delegating call with the `nestedSpendingInfo` to `BitcoinSigner.sign`, but you may also need to do whatever else is needed with the nested result to construct a correct `ScriptSignature`. For `ConditionalScriptSignature`, this all looks like:
|
||||
|
||||
```scala mdoc:compile-only
|
||||
/** Delegates to get a ScriptSignature for the case being
|
||||
* spent and then adds an OP_TRUE or OP_FALSE
|
||||
*/
|
||||
sealed abstract class ConditionalSigner
|
||||
extends BitcoinSigner[ConditionalSpendingInfoFull] {
|
||||
sealed abstract class ConditionalSigner extends Signer[ConditionalInputInfo] {
|
||||
|
||||
override def sign(
|
||||
spendingInfo: UTXOSpendingInfoFull,
|
||||
spendingInfo: UTXOSatisfyingInfo[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: ConditionalSpendingInfoFull)(
|
||||
spendingInfoToSatisfy: UTXOSatisfyingInfo[ConditionalInputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx)
|
||||
|
||||
val missingOpSigComponentF = BitcoinSigner.sign(
|
||||
spendingInfo,
|
||||
val nestedSpendingInfo = spendingInfoToSatisfy.copy(
|
||||
inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo)
|
||||
|
||||
val missingOpSigComponentF = BitcoinSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
spendingInfoToSatisfy.nestedSpendingInfo)
|
||||
nestedSpendingInfo)
|
||||
|
||||
val scriptSigF = missingOpSigComponentF.map { sigComponent =>
|
||||
ConditionalScriptSignature(sigComponent.scriptSignature,
|
||||
spendingInfoToSatisfy.condition)
|
||||
spendingInfoToSatisfy.inputInfo.condition)
|
||||
}
|
||||
|
||||
updateScriptSigInSigComponent(unsignedTx,
|
||||
|
@ -726,16 +674,16 @@ object ConditionalSigner extends ConditionalSigner
|
|||
|
||||
## Step 9: Add to BitcoinSigner.sign
|
||||
|
||||
We must now add the new signing functionality from the previous step to the general-purpose signing functions by adding a new `case` for your new `ScriptPubKey` type in the `match` within `BitcoinSigner.sign`. In the case of `P2PKWithTimeout`, this looks like:
|
||||
We must now add the new signing functionality from the previous step to the general-purpose signing functions by adding a new `case` for your new `InputInfo` type in the `match` within `BitcoinSigner.sign`. In the case of `P2PKWithTimeout`, this looks like:
|
||||
|
||||
```scala mdoc:compile-only
|
||||
spendingInfoToSatisfy match {
|
||||
//...
|
||||
case p2pKWithTimeout: P2PKWithTimeoutSpendingInfo =>
|
||||
case p2pKWithTimeout: P2PKWithTimeoutInputInfo =>
|
||||
P2PKWithTimeoutSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
p2pKWithTimeout)
|
||||
spendingFrom(p2pKWithTimeout))
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
@ -791,7 +739,7 @@ We now add this `Gen` to `scriptSignature: Gen[ScriptSignature]` as well as addi
|
|||
|
||||
### ScriptPubKey with Paired ScriptSignature Generator
|
||||
|
||||
Lastly, we need to construct a generator that returns both a `ScriptPubKey` and a `ScriptSignature` signing that that `ScriptPubKey`. All keys used in signing should also be returned. This all should be done by using the above `ScriptPubKey` generator, then constructing an `ScriptSignature` for your type where all actual signatures are `EmptyDigitalSignature`s. A `SpendingInfoFull` should then be constructed for the generated `ScriptPubKey` (using the private keys generated in the same line). Finally, a `TxSignatureComponent` should be created by using the new `Signer` for our script type. From this `TxSignatureComponent`, a `ScriptSignature` is readily available. For `P2PKWithTimeout`, this generator looks like:
|
||||
Lastly, we need to construct a generator that returns both a `ScriptPubKey` and a `ScriptSignature` signing that that `ScriptPubKey`. All keys used in signing should also be returned. This all should be done by using the above `ScriptPubKey` generator, then constructing an `ScriptSignature` for your type where all actual signatures are `EmptyDigitalSignature`s. A `UTXOSatisfyingInfo` should then be constructed for the generated `ScriptPubKey` (using the private keys generated in the same line). Finally, a `TxSignatureComponent` should be created by using the new `Signer` for our script type. From this `TxSignatureComponent`, a `ScriptSignature` is readily available. For `P2PKWithTimeout`, this generator looks like:
|
||||
|
||||
```scala mdoc:compile-only
|
||||
def signedP2PKWithTimeoutScriptSignature: Gen[
|
||||
|
@ -800,20 +748,21 @@ Lastly, we need to construct a generator that returns both a `ScriptPubKey` and
|
|||
(spk, privKeys) <- p2pkWithTimeoutScriptPubKey
|
||||
hashType <- CryptoGenerators.hashType
|
||||
} yield {
|
||||
val privKey = privKeys.head
|
||||
val emptyScriptSig = P2PKWithTimeoutScriptSignature(beforeTimeout = true,
|
||||
EmptyDigitalSignature)
|
||||
val (creditingTx, outputIndex) =
|
||||
TransactionGenerators.buildCreditingTransaction(spk)
|
||||
val (spendingTx, inputIndex) = TransactionGenerators
|
||||
.buildSpendingTransaction(creditingTx, emptyScriptSig, outputIndex)
|
||||
val spendingInfo = P2PKWithTimeoutSpendingInfo(
|
||||
val spendingInfo = UTXOSatisfyingInfo(
|
||||
P2PKWithTimeoutInputInfo(
|
||||
TransactionOutPoint(creditingTx.txIdBE, inputIndex),
|
||||
creditingTx.outputs(outputIndex.toInt).value,
|
||||
spk,
|
||||
privKey,
|
||||
hashType,
|
||||
isBeforeTimeout = true)
|
||||
isBeforeTimeout = true),
|
||||
privKeys.toVector,
|
||||
hashType
|
||||
)
|
||||
val txSigComponentF = P2PKWithTimeoutSigner.sign(spendingInfo,
|
||||
spendingTx,
|
||||
isDummySignature = false)
|
||||
|
@ -821,7 +770,7 @@ Lastly, we need to construct a generator that returns both a `ScriptPubKey` and
|
|||
val signedScriptSig =
|
||||
txSigComponent.scriptSignature.asInstanceOf[ConditionalScriptSignature]
|
||||
|
||||
(signedScriptSig, spk, privKey)
|
||||
(signedScriptSig, spk, privKeys.head)
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -832,13 +781,13 @@ I strongly advise you also look at at least one other `Gen` of this kind before
|
|||
Now that we have generators constructed for `ScriptPubKey`s, `ScriptSignature`s and their pairings completed, we will create a generator for our type's `SpendingInfoFull`. This should usually be as simple as mapping on the `ScriptPubKey` generator in `ScriptGenerators` and calling `build` (within `CreditinTxGen.scala`). We then also create another generator which returns lists of `SpendingInfo`s generated by the previous `Gen`. For `P2PKWithTimeout`, this looks like:
|
||||
|
||||
```scala mdoc:compile-only
|
||||
def p2pkWithTimeoutOutput: Gen[BitcoinUTXOSpendingInfoFull] = {
|
||||
def p2pkWithTimeoutOutput: Gen[UTXOSatisfyingInfo[InputInfo]] = {
|
||||
ScriptGenerators.p2pkWithTimeoutScriptPubKey.flatMap { p2pkWithTimeout =>
|
||||
build(p2pkWithTimeout._1, Seq(p2pkWithTimeout._2.head), None, None)
|
||||
}
|
||||
}
|
||||
|
||||
def p2pkWithTimeoutOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] = {
|
||||
def p2pkWithTimeoutOutputs: Gen[Seq[UTXOSatisfyingInfo[InputInfo]]] = {
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, p2pkWithTimeoutOutput))
|
||||
}
|
||||
```
|
||||
|
|
|
@ -26,8 +26,9 @@ import org.bitcoins.core.psbt.PSBT
|
|||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.core.wallet.signer.BitcoinSigner
|
||||
import org.bitcoins.core.wallet.utxo.{
|
||||
BitcoinUTXOSpendingInfoSingle,
|
||||
ConditionalPath
|
||||
ConditionalPath,
|
||||
InputInfo,
|
||||
ECSignatureParams
|
||||
}
|
||||
import scodec.bits._
|
||||
|
||||
|
@ -111,21 +112,21 @@ val psbtFirstSigF =
|
|||
.sign(inputIndex = 0, signer = privKey0)
|
||||
.flatMap(_.sign(inputIndex = 0, signer = privKey1))
|
||||
|
||||
// Alternatively, you can use produce a signature with a BitcoinUTXOSpendingInfoSingle
|
||||
// Alternatively, you can use produce a signature with a ECSignatureParams
|
||||
// using the BitcoinSigner will return a PartialSignature that can be added to a PSBT
|
||||
|
||||
// First we need to declare out spendingInfoSingle
|
||||
val outPoint = unsignedTransaction.inputs.head.previousOutput
|
||||
val output = utxo0.outputs(outPoint.vout.toInt)
|
||||
|
||||
val spendingInfoSingle = BitcoinUTXOSpendingInfoSingle(
|
||||
outPoint = outPoint,
|
||||
val spendingInfoSingle = ECSignatureParams(
|
||||
InputInfo(outPoint = outPoint,
|
||||
output = output,
|
||||
signer = privKey0,
|
||||
redeemScriptOpt = Some(redeemScript0),
|
||||
scriptWitnessOpt = None,
|
||||
hashType = HashType.sigHashAll,
|
||||
conditionalPath = ConditionalPath.NoConditionsLeft
|
||||
conditionalPath = ConditionalPath.NoCondition),
|
||||
signer = privKey0,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
|
||||
// Then we can sign the transaction
|
||||
|
|
|
@ -76,19 +76,20 @@ val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
|||
|
||||
// this contains all the information we need to
|
||||
// validly sign the UTXO above
|
||||
val utxoSpendingInfo = BitcoinUTXOSpendingInfoFull(outPoint = outPoint,
|
||||
val utxoInfo = ScriptSignatureParams(inputInfo = InputInfo(outPoint = outPoint,
|
||||
output = utxo,
|
||||
signers = Vector(privKey),
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
hashType =
|
||||
HashType.sigHashAll,
|
||||
conditionalPath =
|
||||
ConditionalPath.NoConditionsLeft)
|
||||
ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)),
|
||||
signers = Vector(privKey),
|
||||
hashType =
|
||||
HashType.sigHashAll)
|
||||
|
||||
// all of the UTXO spending information, since we are only
|
||||
//spending one UTXO, this is just one element
|
||||
val utxos: Vector[BitcoinUTXOSpendingInfoFull] = Vector(utxoSpendingInfo)
|
||||
val utxos: Vector[ScriptSignatureParams[InputInfo]] = Vector(utxoInfo)
|
||||
|
||||
// this is how much we are going to pay as a fee to the network
|
||||
// for this example, we are going to pay 1 satoshi per byte
|
||||
|
@ -117,7 +118,7 @@ val txBuilder: BitcoinTxBuilder = {
|
|||
// Let's finally produce a validly signed tx!
|
||||
// The 'sign' method is going produce a validly signed transaction
|
||||
// This is going to iterate through each of the UTXOs and use
|
||||
// the corresponding UTXOSpendingInfo to produce a validly
|
||||
// the corresponding ScriptSignatureParams to produce a validly
|
||||
// signed input. This UTXO has:
|
||||
// 1: one input
|
||||
// 2: outputs (destination and change outputs)
|
||||
|
|
|
@ -7,9 +7,10 @@ import org.bitcoins.core.protocol.transaction._
|
|||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.core.script.interpreter.ScriptInterpreter
|
||||
import org.bitcoins.core.wallet.utxo.{
|
||||
BitcoinUTXOSpendingInfoFull,
|
||||
ConditionalPath,
|
||||
P2SHNestedSegwitV0UTXOSpendingInfoFull
|
||||
InputInfo,
|
||||
P2SHNestedSegwitV0InputInfo,
|
||||
ScriptSignatureParams
|
||||
}
|
||||
import org.bitcoins.crypto.Sign
|
||||
import org.scalacheck.Gen
|
||||
|
@ -29,7 +30,7 @@ sealed abstract class CreditingTxGen {
|
|||
}
|
||||
|
||||
/** Generator for non-script hash based output */
|
||||
def nonSHOutput: Gen[BitcoinUTXOSpendingInfoFull] = {
|
||||
def nonSHOutput: Gen[ScriptSignatureParams[InputInfo]] = {
|
||||
Gen.oneOf(
|
||||
p2pkOutput,
|
||||
p2pkhOutput,
|
||||
|
@ -41,14 +42,14 @@ sealed abstract class CreditingTxGen {
|
|||
)
|
||||
}
|
||||
|
||||
def nonSHOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] =
|
||||
def nonSHOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] =
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, nonSHOutput))
|
||||
|
||||
def basicOutput: Gen[BitcoinUTXOSpendingInfoFull] = {
|
||||
def basicOutput: Gen[ScriptSignatureParams[InputInfo]] = {
|
||||
Gen.oneOf(p2pkOutput, p2pkhOutput, multiSigOutput)
|
||||
}
|
||||
|
||||
def nonP2WSHOutput: Gen[BitcoinUTXOSpendingInfoFull] = {
|
||||
def nonP2WSHOutput: Gen[ScriptSignatureParams[InputInfo]] = {
|
||||
//note, cannot put a p2wpkh here
|
||||
Gen.oneOf(p2pkOutput,
|
||||
p2pkhOutput,
|
||||
|
@ -59,7 +60,7 @@ sealed abstract class CreditingTxGen {
|
|||
}
|
||||
|
||||
/** Only for use in constructing P2SH outputs */
|
||||
private def nonP2SHOutput: Gen[BitcoinUTXOSpendingInfoFull] = {
|
||||
private def nonP2SHOutput: Gen[ScriptSignatureParams[InputInfo]] = {
|
||||
Gen
|
||||
.oneOf(
|
||||
p2pkOutput,
|
||||
|
@ -72,15 +73,11 @@ sealed abstract class CreditingTxGen {
|
|||
p2wshOutput
|
||||
)
|
||||
.suchThat(output =>
|
||||
!ScriptGenerators.redeemScriptTooBig(output.scriptPubKey))
|
||||
!ScriptGenerators.redeemScriptTooBig(output.output.scriptPubKey))
|
||||
.suchThat {
|
||||
case P2SHNestedSegwitV0UTXOSpendingInfoFull(_,
|
||||
case ScriptSignatureParams(
|
||||
P2SHNestedSegwitV0InputInfo(_, _, witness, _, _),
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
witness: P2WSHWitnessV0,
|
||||
_) =>
|
||||
witness.stack.exists(_.length > ScriptInterpreter.MAX_PUSH_SIZE)
|
||||
case _ => true
|
||||
|
@ -102,11 +99,11 @@ sealed abstract class CreditingTxGen {
|
|||
|
||||
private val cltvOutputGens = Vector(cltvOutput)
|
||||
|
||||
def output: Gen[BitcoinUTXOSpendingInfoFull] =
|
||||
def output: Gen[ScriptSignatureParams[InputInfo]] =
|
||||
Gen.oneOf(nonCltvOutputGens).flatMap(identity)
|
||||
|
||||
/** Either a list of non-CLTV outputs or a single CLTV output, with proportional probability */
|
||||
def outputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] = {
|
||||
def outputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] = {
|
||||
val cltvGen = Gen
|
||||
.oneOf(cltvOutput, p2pkWithTimeoutOutput)
|
||||
.map { output =>
|
||||
|
@ -121,23 +118,23 @@ sealed abstract class CreditingTxGen {
|
|||
}
|
||||
|
||||
/** Generates a crediting tx with a p2pk spk at the returned index */
|
||||
def p2pkOutput: Gen[BitcoinUTXOSpendingInfoFull] =
|
||||
def p2pkOutput: Gen[ScriptSignatureParams[InputInfo]] =
|
||||
ScriptGenerators.p2pkScriptPubKey.flatMap { p2pk =>
|
||||
build(p2pk._1, Seq(p2pk._2), None, None)
|
||||
}
|
||||
|
||||
/** Generates multiple crediting txs with p2pk spks at the returned index */
|
||||
def p2pkOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] = {
|
||||
def p2pkOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] = {
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, p2pkOutput))
|
||||
}
|
||||
|
||||
def p2pkWithTimeoutOutput: Gen[BitcoinUTXOSpendingInfoFull] = {
|
||||
def p2pkWithTimeoutOutput: Gen[ScriptSignatureParams[InputInfo]] = {
|
||||
ScriptGenerators.p2pkWithTimeoutScriptPubKey.flatMap { p2pkWithTimeout =>
|
||||
build(p2pkWithTimeout._1, Seq(p2pkWithTimeout._2.head), None, None)
|
||||
}
|
||||
}
|
||||
|
||||
def p2pkWithTimeoutOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] = {
|
||||
def p2pkWithTimeoutOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] = {
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, p2pkWithTimeoutOutput))
|
||||
}
|
||||
|
||||
|
@ -145,39 +142,40 @@ sealed abstract class CreditingTxGen {
|
|||
* Generates a transaction that has a p2pkh output at the returned index. This
|
||||
* output can be spent by the returned ECPrivateKey
|
||||
*/
|
||||
def p2pkhOutput: Gen[BitcoinUTXOSpendingInfoFull] =
|
||||
def p2pkhOutput: Gen[ScriptSignatureParams[InputInfo]] =
|
||||
ScriptGenerators.p2pkhScriptPubKey.flatMap { p2pkh =>
|
||||
build(p2pkh._1, Seq(p2pkh._2), None, None)
|
||||
}
|
||||
|
||||
/** Generates a sequence of p2pkh outputs at the returned index */
|
||||
def p2pkhOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] = {
|
||||
def p2pkhOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] = {
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, p2pkhOutput))
|
||||
}
|
||||
|
||||
def multiSigOutput: Gen[BitcoinUTXOSpendingInfoFull] =
|
||||
def multiSigOutput: Gen[ScriptSignatureParams[InputInfo]] =
|
||||
ScriptGenerators.multiSigScriptPubKey.flatMap { multisig =>
|
||||
build(multisig._1, multisig._2, None, None)
|
||||
}
|
||||
|
||||
def multiSigOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] = {
|
||||
def multiSigOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] = {
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, multiSigOutput))
|
||||
}
|
||||
|
||||
def multiSignatureWithTimeoutOutput: Gen[BitcoinUTXOSpendingInfoFull] = {
|
||||
def multiSignatureWithTimeoutOutput: Gen[ScriptSignatureParams[InputInfo]] = {
|
||||
ScriptGenerators.multiSignatureWithTimeoutScriptPubKey.flatMap {
|
||||
case (conditional, keys) =>
|
||||
build(conditional, keys, None, None)
|
||||
}
|
||||
}
|
||||
|
||||
def multiSignatureWithTimeoutOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] = {
|
||||
def multiSignatureWithTimeoutOutputs: Gen[
|
||||
Seq[ScriptSignatureParams[InputInfo]]] = {
|
||||
Gen
|
||||
.choose(min, max)
|
||||
.flatMap(n => Gen.listOfN(n, multiSignatureWithTimeoutOutput))
|
||||
}
|
||||
|
||||
def conditionalOutput: Gen[BitcoinUTXOSpendingInfoFull] = {
|
||||
def conditionalOutput: Gen[ScriptSignatureParams[InputInfo]] = {
|
||||
ScriptGenerators
|
||||
.nonLocktimeConditionalScriptPubKey(ScriptGenerators.defaultMaxDepth)
|
||||
.flatMap {
|
||||
|
@ -186,34 +184,39 @@ sealed abstract class CreditingTxGen {
|
|||
}
|
||||
}
|
||||
|
||||
def conditionalOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] = {
|
||||
def conditionalOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] = {
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, conditionalOutput))
|
||||
}
|
||||
|
||||
def p2shOutput: Gen[BitcoinUTXOSpendingInfoFull] = nonP2SHOutput.flatMap {
|
||||
o =>
|
||||
def p2shOutput: Gen[ScriptSignatureParams[InputInfo]] =
|
||||
nonP2SHOutput.flatMap { o =>
|
||||
CryptoGenerators.hashType.map { hashType =>
|
||||
val oldOutput = o.output
|
||||
val redeemScript = o.output.scriptPubKey
|
||||
val p2sh = P2SHScriptPubKey(redeemScript)
|
||||
val updatedOutput = TransactionOutput(oldOutput.value, p2sh)
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
val scriptWitnessOpt = InputInfo.getScriptWitness(o.inputInfo)
|
||||
|
||||
ScriptSignatureParams(
|
||||
InputInfo(
|
||||
TransactionOutPoint(o.outPoint.txId, o.outPoint.vout),
|
||||
updatedOutput,
|
||||
o.signers,
|
||||
Some(redeemScript),
|
||||
o.scriptWitnessOpt,
|
||||
hashType,
|
||||
computeAllTrueConditionalPath(redeemScript, None, o.scriptWitnessOpt)
|
||||
scriptWitnessOpt,
|
||||
computeAllTrueConditionalPath(redeemScript, None, scriptWitnessOpt),
|
||||
o.signers.map(_.publicKey)
|
||||
),
|
||||
o.signers,
|
||||
hashType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def p2shOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] = {
|
||||
def p2shOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] = {
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, p2shOutput))
|
||||
}
|
||||
|
||||
def cltvOutput: Gen[BitcoinUTXOSpendingInfoFull] =
|
||||
def cltvOutput: Gen[ScriptSignatureParams[InputInfo]] =
|
||||
TransactionGenerators.spendableCLTVValues.flatMap {
|
||||
case (scriptNum, _) =>
|
||||
basicOutput.flatMap { o =>
|
||||
|
@ -221,23 +224,26 @@ sealed abstract class CreditingTxGen {
|
|||
val oldOutput = o.output
|
||||
val csvSPK = CLTVScriptPubKey(scriptNum, oldOutput.scriptPubKey)
|
||||
val updatedOutput = TransactionOutput(oldOutput.value, csvSPK)
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
ScriptSignatureParams(
|
||||
InputInfo(
|
||||
TransactionOutPoint(o.outPoint.txId, o.outPoint.vout),
|
||||
updatedOutput,
|
||||
InputInfo.getRedeemScript(o.inputInfo),
|
||||
InputInfo.getScriptWitness(o.inputInfo),
|
||||
ConditionalPath.NoCondition,
|
||||
o.signers.map(_.publicKey)
|
||||
),
|
||||
o.signers,
|
||||
o.redeemScriptOpt,
|
||||
o.scriptWitnessOpt,
|
||||
hashType,
|
||||
ConditionalPath.NoConditionsLeft
|
||||
hashType
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def cltvOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] =
|
||||
def cltvOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] =
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, cltvOutput))
|
||||
|
||||
def csvOutput: Gen[BitcoinUTXOSpendingInfoFull] =
|
||||
def csvOutput: Gen[ScriptSignatureParams[InputInfo]] =
|
||||
TransactionGenerators.spendableCSVValues.flatMap {
|
||||
case (scriptNum, _) =>
|
||||
basicOutput.flatMap { o =>
|
||||
|
@ -245,61 +251,64 @@ sealed abstract class CreditingTxGen {
|
|||
val oldOutput = o.output
|
||||
val csvSPK = CSVScriptPubKey(scriptNum, oldOutput.scriptPubKey)
|
||||
val updatedOutput = TransactionOutput(oldOutput.value, csvSPK)
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
ScriptSignatureParams(
|
||||
InputInfo(
|
||||
TransactionOutPoint(o.outPoint.txId, o.outPoint.vout),
|
||||
updatedOutput,
|
||||
InputInfo.getRedeemScript(o.inputInfo),
|
||||
InputInfo.getScriptWitness(o.inputInfo),
|
||||
ConditionalPath.NoCondition,
|
||||
o.signers.map(_.publicKey)
|
||||
),
|
||||
o.signers,
|
||||
o.redeemScriptOpt,
|
||||
o.scriptWitnessOpt,
|
||||
hashType,
|
||||
ConditionalPath.NoConditionsLeft
|
||||
hashType
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def csvOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] =
|
||||
def csvOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] =
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, csvOutput))
|
||||
|
||||
def p2wpkhOutput: Gen[BitcoinUTXOSpendingInfoFull] =
|
||||
def p2wpkhOutput: Gen[ScriptSignatureParams[InputInfo]] =
|
||||
ScriptGenerators.p2wpkhSPKV0.flatMap { witSPK =>
|
||||
val scriptWit = P2WPKHWitnessV0(witSPK._2.head.publicKey)
|
||||
build(witSPK._1, witSPK._2, None, Some(scriptWit))
|
||||
}
|
||||
|
||||
def p2wpkhOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] =
|
||||
def p2wpkhOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] =
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, p2wpkhOutput))
|
||||
|
||||
def p2wshOutput: Gen[BitcoinUTXOSpendingInfoFull] =
|
||||
def p2wshOutput: Gen[ScriptSignatureParams[InputInfo]] =
|
||||
nonP2WSHOutput
|
||||
.suchThat(output =>
|
||||
!ScriptGenerators.redeemScriptTooBig(output.scriptPubKey))
|
||||
!ScriptGenerators.redeemScriptTooBig(output.output.scriptPubKey))
|
||||
.flatMap {
|
||||
case BitcoinUTXOSpendingInfoFull(_, txOutput, signer, _, _, _, _) =>
|
||||
val spk = txOutput.scriptPubKey
|
||||
case ScriptSignatureParams(info, signers, _) =>
|
||||
val spk = info.scriptPubKey
|
||||
spk match {
|
||||
case rspk: RawScriptPubKey =>
|
||||
val scriptWit = P2WSHWitnessV0(rspk)
|
||||
val witSPK = P2WSHWitnessSPKV0(rspk)
|
||||
build(witSPK, signer, None, Some(scriptWit))
|
||||
build(witSPK, signers, None, Some(scriptWit))
|
||||
case _ =>
|
||||
throw new IllegalArgumentException(
|
||||
"nonP2WSHOutput created a non RawScriptPubKey")
|
||||
}
|
||||
}
|
||||
|
||||
def p2wshOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] =
|
||||
def p2wshOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] =
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, p2wshOutput))
|
||||
|
||||
/** A nested output is a p2sh/p2wsh wrapped output */
|
||||
def nestedOutput: Gen[BitcoinUTXOSpendingInfoFull] = {
|
||||
def nestedOutput: Gen[ScriptSignatureParams[InputInfo]] = {
|
||||
Gen.oneOf(p2wshOutput, p2shOutput)
|
||||
}
|
||||
|
||||
def nestedOutputs: Gen[Seq[BitcoinUTXOSpendingInfoFull]] =
|
||||
def nestedOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] =
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, nestedOutput))
|
||||
|
||||
def random: Gen[BitcoinUTXOSpendingInfoFull] = nonEmptyOutputs.flatMap {
|
||||
def random: Gen[ScriptSignatureParams[InputInfo]] = nonEmptyOutputs.flatMap {
|
||||
outputs =>
|
||||
Gen.choose(0, outputs.size - 1).flatMap { outputIndex: Int =>
|
||||
ScriptGenerators.scriptPubKey.flatMap {
|
||||
|
@ -308,21 +317,25 @@ sealed abstract class CreditingTxGen {
|
|||
CryptoGenerators.hashType.map { hashType: HashType =>
|
||||
val tc = TransactionConstants
|
||||
val signers: Vector[Sign] = keys.toVector
|
||||
val creditingTx = BaseTransaction(tc.validLockVersion,
|
||||
val creditingTx =
|
||||
BaseTransaction(tc.validLockVersion,
|
||||
Nil,
|
||||
outputs,
|
||||
tc.lockTime)
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
ScriptSignatureParams(
|
||||
InputInfo(
|
||||
TransactionOutPoint(creditingTx.txId,
|
||||
UInt32.apply(outputIndex)),
|
||||
TransactionOutput(
|
||||
creditingTx.outputs(outputIndex).value,
|
||||
creditingTx.outputs(outputIndex).scriptPubKey),
|
||||
signers,
|
||||
Some(spk),
|
||||
Some(wit),
|
||||
hashType,
|
||||
ConditionalPath.NoConditionsLeft
|
||||
ConditionalPath.NoCondition,
|
||||
signers.map(_.publicKey)
|
||||
),
|
||||
signers,
|
||||
hashType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -330,7 +343,7 @@ sealed abstract class CreditingTxGen {
|
|||
}
|
||||
}
|
||||
|
||||
def randoms: Gen[Seq[BitcoinUTXOSpendingInfoFull]] =
|
||||
def randoms: Gen[Seq[ScriptSignatureParams[InputInfo]]] =
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, random))
|
||||
|
||||
private def computeAllTrueConditionalPath(
|
||||
|
@ -347,7 +360,7 @@ sealed abstract class CreditingTxGen {
|
|||
None,
|
||||
None)
|
||||
case _: RawScriptPubKey | _: P2WPKHWitnessSPKV0 =>
|
||||
ConditionalPath.NoConditionsLeft
|
||||
ConditionalPath.NoCondition
|
||||
case _: P2SHScriptPubKey =>
|
||||
redeemScript match {
|
||||
case None =>
|
||||
|
@ -374,7 +387,8 @@ sealed abstract class CreditingTxGen {
|
|||
spk: ScriptPubKey,
|
||||
signers: Seq[Sign],
|
||||
redeemScript: Option[ScriptPubKey],
|
||||
scriptWitness: Option[ScriptWitness]): Gen[BitcoinUTXOSpendingInfoFull] =
|
||||
scriptWitness: Option[ScriptWitness]): Gen[
|
||||
ScriptSignatureParams[InputInfo]] =
|
||||
nonEmptyOutputs.flatMap { outputs =>
|
||||
CryptoGenerators.hashType.flatMap { hashType =>
|
||||
Gen.choose(0, outputs.size - 1).map { idx =>
|
||||
|
@ -382,24 +396,27 @@ sealed abstract class CreditingTxGen {
|
|||
val updated = outputs.updated(idx, TransactionOutput(old.value, spk))
|
||||
val tc = TransactionConstants
|
||||
val btx = BaseTransaction(tc.version, Nil, updated, tc.lockTime)
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
ScriptSignatureParams(
|
||||
InputInfo(
|
||||
TransactionOutPoint(btx.txId, UInt32.apply(idx)),
|
||||
TransactionOutput(old.value, spk),
|
||||
signers.toVector,
|
||||
redeemScript,
|
||||
scriptWitness,
|
||||
hashType,
|
||||
computeAllTrueConditionalPath(spk, redeemScript, scriptWitness)
|
||||
computeAllTrueConditionalPath(spk, redeemScript, scriptWitness),
|
||||
signers.toVector.map(_.publicKey)
|
||||
),
|
||||
signers.toVector,
|
||||
hashType
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def inputsAndOutputs(
|
||||
outputsToUse: Gen[Seq[BitcoinUTXOSpendingInfoFull]] = outputs,
|
||||
outputsToUse: Gen[Seq[ScriptSignatureParams[InputInfo]]] = outputs,
|
||||
destinationGenerator: CurrencyUnit => Gen[Seq[TransactionOutput]] =
|
||||
TransactionGenerators.smallOutputs): Gen[
|
||||
(Seq[BitcoinUTXOSpendingInfoFull], Seq[TransactionOutput])] = {
|
||||
(Seq[ScriptSignatureParams[InputInfo]], Seq[TransactionOutput])] = {
|
||||
inputsAndP2SHOutputs(
|
||||
outputsToUse,
|
||||
destinationGenerator.andThen(_.map(_.map(x => (x, ScriptPubKey.empty)))))
|
||||
|
@ -407,11 +424,11 @@ sealed abstract class CreditingTxGen {
|
|||
}
|
||||
|
||||
def inputsAndP2SHOutputs(
|
||||
outputsToUse: Gen[Seq[BitcoinUTXOSpendingInfoFull]] = outputs,
|
||||
outputsToUse: Gen[Seq[ScriptSignatureParams[InputInfo]]] = outputs,
|
||||
destinationGenerator: CurrencyUnit => Gen[
|
||||
Seq[(TransactionOutput, ScriptPubKey)]] =
|
||||
TransactionGenerators.smallP2SHOutputs): Gen[(
|
||||
Seq[BitcoinUTXOSpendingInfoFull],
|
||||
Seq[ScriptSignatureParams[InputInfo]],
|
||||
Seq[(TransactionOutput, ScriptPubKey)])] = {
|
||||
outputsToUse
|
||||
.flatMap { creditingTxsInfo =>
|
||||
|
|
|
@ -15,10 +15,7 @@ import org.bitcoins.core.psbt.PSBTInputKeyId.PartialSignatureKeyId
|
|||
import org.bitcoins.core.psbt._
|
||||
import org.bitcoins.core.wallet.builder.BitcoinTxBuilder
|
||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||
import org.bitcoins.core.wallet.utxo.{
|
||||
BitcoinUTXOSpendingInfoFull,
|
||||
UTXOSpendingInfoFull
|
||||
}
|
||||
import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams}
|
||||
import org.scalacheck.Gen
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
|
@ -149,7 +146,7 @@ object PSBTGenerators {
|
|||
}
|
||||
|
||||
def psbtToBeSigned(implicit ec: ExecutionContext): Gen[
|
||||
Future[(PSBT, Seq[UTXOSpendingInfoFull])]] = {
|
||||
Future[(PSBT, Seq[ScriptSignatureParams[InputInfo]])]] = {
|
||||
psbtWithBuilder(finalized = false).map { psbtAndBuilderF =>
|
||||
psbtAndBuilderF.flatMap {
|
||||
case (psbt, builder) =>
|
||||
|
@ -167,7 +164,7 @@ object PSBTGenerators {
|
|||
|
||||
def spendingInfoAndNonWitnessTxsFromSpendingInfos(
|
||||
unsignedTx: Transaction,
|
||||
creditingTxsInfo: Vector[UTXOSpendingInfoFull]): SpendingInfoAndNonWitnessTxs = {
|
||||
creditingTxsInfo: Vector[ScriptSignatureParams[InputInfo]]): SpendingInfoAndNonWitnessTxs = {
|
||||
val elements = unsignedTx.inputs.toVector.map { input =>
|
||||
val infoOpt =
|
||||
creditingTxsInfo.find(_.outPoint == input.previousOutput)
|
||||
|
@ -189,7 +186,7 @@ object PSBTGenerators {
|
|||
|
||||
def psbtAndBuilderFromInputs(
|
||||
finalized: Boolean,
|
||||
creditingTxsInfo: Seq[BitcoinUTXOSpendingInfoFull],
|
||||
creditingTxsInfo: Seq[ScriptSignatureParams[InputInfo]],
|
||||
destinations: Seq[TransactionOutput],
|
||||
changeSPK: ScriptPubKey,
|
||||
network: BitcoinNetwork,
|
||||
|
|
|
@ -20,10 +20,11 @@ import org.bitcoins.core.wallet.signer.{
|
|||
P2PKWithTimeoutSigner
|
||||
}
|
||||
import org.bitcoins.core.wallet.utxo.{
|
||||
MultiSignatureSpendingInfoFull,
|
||||
P2PKHSpendingInfo,
|
||||
P2PKSpendingInfo,
|
||||
P2PKWithTimeoutSpendingInfo
|
||||
MultiSignatureInputInfo,
|
||||
P2PKHInputInfo,
|
||||
P2PKInputInfo,
|
||||
P2PKWithTimeoutInputInfo,
|
||||
ScriptSignatureParams
|
||||
}
|
||||
import org.bitcoins.crypto.{
|
||||
ECDigitalSignature,
|
||||
|
@ -263,7 +264,7 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
pubKeys = privateKeys.map(_.publicKey)
|
||||
multiSignatureScriptPubKey = MultiSignatureScriptPubKey(requiredSigs,
|
||||
pubKeys)
|
||||
} yield (multiSignatureScriptPubKey, privateKeys)
|
||||
} yield (multiSignatureScriptPubKey, privateKeys.take(requiredSigs))
|
||||
|
||||
def smallMultiSigScriptPubKey: Gen[
|
||||
(MultiSignatureScriptPubKey, Seq[ECPrivateKey])] =
|
||||
|
@ -272,7 +273,7 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
pubKeys = privateKeys.map(_.publicKey)
|
||||
multiSignatureScriptPubKey = MultiSignatureScriptPubKey(requiredSigs,
|
||||
pubKeys)
|
||||
} yield (multiSignatureScriptPubKey, privateKeys)
|
||||
} yield (multiSignatureScriptPubKey, privateKeys.take(requiredSigs))
|
||||
|
||||
/** Generates a random P2SHScriptPubKey as well as it's corresponding private keys and redeem script */
|
||||
def p2shScriptPubKey: Gen[
|
||||
|
@ -579,10 +580,10 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
creditingTx,
|
||||
scriptSig,
|
||||
outputIndex)
|
||||
spendingInfo = P2PKSpendingInfo(
|
||||
TransactionOutPoint(creditingTx.txIdBE, inputIndex),
|
||||
spendingInfo = ScriptSignatureParams(
|
||||
P2PKInputInfo(TransactionOutPoint(creditingTx.txIdBE, inputIndex),
|
||||
creditingTx.outputs(outputIndex.toInt).value,
|
||||
scriptPubKey,
|
||||
scriptPubKey),
|
||||
privateKey,
|
||||
hashType)
|
||||
txSigComponentFuture = P2PKSigner.sign(spendingInfo, spendingTx, false)
|
||||
|
@ -613,12 +614,13 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
creditingTx,
|
||||
EmptyScriptSignature,
|
||||
outputIndex)
|
||||
spendingInfo = P2PKHSpendingInfo(
|
||||
TransactionOutPoint(creditingTx.txIdBE, inputIndex),
|
||||
spendingInfo = ScriptSignatureParams(
|
||||
P2PKHInputInfo(TransactionOutPoint(creditingTx.txIdBE, inputIndex),
|
||||
creditingTx.outputs(outputIndex.toInt).value,
|
||||
scriptPubKey,
|
||||
privateKey.publicKey),
|
||||
privateKey,
|
||||
hashType)
|
||||
hashType
|
||||
)
|
||||
txSigComponentFuture = P2PKHSigner.sign(spendingInfo, unsignedTx, false)
|
||||
txSigComponent = Await.result(txSigComponentFuture, timeout)
|
||||
signedScriptSig = txSigComponent.scriptSignature
|
||||
|
@ -638,13 +640,15 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
TransactionGenerators.buildCreditingTransaction(spk)
|
||||
val (spendingTx, inputIndex) = TransactionGenerators
|
||||
.buildSpendingTransaction(creditingTx, emptyScriptSig, outputIndex)
|
||||
val spendingInfo = P2PKWithTimeoutSpendingInfo(
|
||||
val spendingInfo = ScriptSignatureParams(
|
||||
P2PKWithTimeoutInputInfo(
|
||||
TransactionOutPoint(creditingTx.txIdBE, inputIndex),
|
||||
creditingTx.outputs(outputIndex.toInt).value,
|
||||
spk,
|
||||
isBeforeTimeout = true),
|
||||
privKey,
|
||||
hashType,
|
||||
isBeforeTimeout = true)
|
||||
hashType
|
||||
)
|
||||
val txSigComponentF = P2PKWithTimeoutSigner.sign(spendingInfo,
|
||||
spendingTx,
|
||||
isDummySignature = false)
|
||||
|
@ -669,7 +673,8 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
MultiSignatureScriptPubKey,
|
||||
Seq[ECPrivateKey])] =
|
||||
for {
|
||||
(privateKeys, requiredSigs) <- CryptoGenerators.privateKeySeqWithRequiredSigs
|
||||
(privateKeysWithExtra, requiredSigs) <- CryptoGenerators.privateKeySeqWithRequiredSigs
|
||||
privateKeys = privateKeysWithExtra.take(requiredSigs)
|
||||
hashType <- CryptoGenerators.hashType
|
||||
publicKeys = privateKeys.map(_.publicKey)
|
||||
multiSigScriptPubKey = MultiSignatureScriptPubKey(requiredSigs,
|
||||
|
@ -682,12 +687,14 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger {
|
|||
creditingTx,
|
||||
scriptSig,
|
||||
outputIndex)
|
||||
spendingInfo = MultiSignatureSpendingInfoFull(
|
||||
spendingInfo = ScriptSignatureParams(
|
||||
MultiSignatureInputInfo(
|
||||
TransactionOutPoint(creditingTx.txIdBE, inputIndex),
|
||||
creditingTx.outputs(outputIndex.toInt).value,
|
||||
multiSigScriptPubKey,
|
||||
multiSigScriptPubKey),
|
||||
privateKeys.toVector,
|
||||
hashType)
|
||||
hashType
|
||||
)
|
||||
txSigComponentFuture = MultiSigSigner.sign(spendingInfo,
|
||||
spendingTx,
|
||||
false)
|
||||
|
|
|
@ -279,7 +279,7 @@ object TransactionGenerators extends BitcoinSLogger {
|
|||
*/
|
||||
def signedMultiSigTransaction: Gen[(BaseTxSigComponent, Seq[ECPrivateKey])] =
|
||||
for {
|
||||
(signedScriptSig, scriptPubKey, privateKey) <- ScriptGenerators
|
||||
(signedScriptSig, scriptPubKey, privateKeys) <- ScriptGenerators
|
||||
.signedMultiSignatureScriptSignature
|
||||
(creditingTx, outputIndex) = buildCreditingTransaction(scriptPubKey)
|
||||
(signedTx, inputIndex) = buildSpendingTransaction(creditingTx,
|
||||
|
@ -291,7 +291,7 @@ object TransactionGenerators extends BitcoinSLogger {
|
|||
inputIndex,
|
||||
output,
|
||||
Policy.standardScriptVerifyFlags)
|
||||
} yield (signedTxSignatureComponent, privateKey)
|
||||
} yield (signedTxSignatureComponent, privateKeys)
|
||||
|
||||
/**
|
||||
* Creates a transaction which contains a
|
||||
|
|
|
@ -248,7 +248,7 @@ abstract class Wallet
|
|||
_ = require(diff.isEmpty,
|
||||
s"Not all OutPoints belong to this wallet, diff $diff")
|
||||
|
||||
utxos = utxoDbs.map(_.toUTXOSpendingInfo(keyManager))
|
||||
utxos = utxoDbs.map(_.toUTXOInfo(keyManager))
|
||||
|
||||
changeAddr <- getNewChangeAddress(fromAccount.hdAccount)
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ trait FundTransactionHandling extends WalletLogger { self: WalletApi =>
|
|||
* which can be used with signing, or you can just directly call [[BitcoinTxBuilder.sign sign]] to sign the transaction with this instance
|
||||
* of [[BitcoinTxBuilder]]
|
||||
*
|
||||
* If you pass in a [[org.bitcoins.keymanager.KeyManager]], the [[org.bitcoins.core.wallet.utxo.UTXOSpendingInfo.signers signers]]
|
||||
* If you pass in a [[org.bitcoins.keymanager.KeyManager]], the [[org.bitcoins.core.wallet.utxo.ScriptSignatureParams.signers signers]]
|
||||
* will be populated with valid signers that can be used to produce valid [[org.bitcoins.crypto.ECDigitalSignature signatures]]
|
||||
*
|
||||
* If you do not pass in a key manager, the transaction built by [[BitcoinTxBuilder txbuilder]] will contain [[org.bitcoins.core.protocol.script.EmptyScriptSignature EmptyScriptSignature]]
|
||||
|
@ -110,9 +110,9 @@ trait FundTransactionHandling extends WalletLogger { self: WalletApi =>
|
|||
case (utxo, addrInfo) =>
|
||||
keyManagerOpt match {
|
||||
case Some(km) =>
|
||||
utxo.toUTXOSpendingInfo(keyManager = km)
|
||||
utxo.toUTXOInfo(keyManager = km)
|
||||
case None =>
|
||||
utxo.toUTXOSpendingInfo(sign = Sign.dummySign(addrInfo.pubkey))
|
||||
utxo.toUTXOInfo(sign = Sign.dummySign(addrInfo.pubkey))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,8 +13,9 @@ import org.bitcoins.core.protocol.transaction.{
|
|||
}
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.core.wallet.utxo.{
|
||||
BitcoinUTXOSpendingInfoFull,
|
||||
ConditionalPath,
|
||||
InputInfo,
|
||||
ScriptSignatureParams,
|
||||
TxoState
|
||||
}
|
||||
import org.bitcoins.crypto.{DoubleSha256DigestBE, Sign}
|
||||
|
@ -172,23 +173,27 @@ sealed trait SpendingInfoDb extends DbRowAutoInc[SpendingInfoDb] {
|
|||
/** Converts a non-sensitive DB representation of a UTXO into
|
||||
* a signable (and sensitive) real-world UTXO
|
||||
*/
|
||||
def toUTXOSpendingInfo(
|
||||
keyManager: BIP39KeyManager): BitcoinUTXOSpendingInfoFull = {
|
||||
def toUTXOInfo(
|
||||
keyManager: BIP39KeyManager): ScriptSignatureParams[InputInfo] = {
|
||||
|
||||
val sign: Sign = keyManager.toSign(privKeyPath = privKeyPath)
|
||||
|
||||
toUTXOSpendingInfo(sign = sign)
|
||||
toUTXOInfo(sign = sign)
|
||||
}
|
||||
|
||||
def toUTXOSpendingInfo(sign: Sign): BitcoinUTXOSpendingInfoFull = {
|
||||
BitcoinUTXOSpendingInfoFull(
|
||||
def toUTXOInfo(sign: Sign): ScriptSignatureParams[InputInfo] = {
|
||||
ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint,
|
||||
output,
|
||||
Vector(sign),
|
||||
redeemScriptOpt,
|
||||
scriptWitnessOpt,
|
||||
hashType,
|
||||
ConditionalPath.NoConditionsLeft) // TODO: Migrate to add the Column for this (default: NoConditionsLeft)
|
||||
ConditionalPath.NoCondition, // TODO: Migrate to add the Column for this (default: NoConditionsLeft)
|
||||
Vector(sign.publicKey)
|
||||
),
|
||||
Vector(sign),
|
||||
hashType
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ val utxoSpendingInfo = BitcoinUTXOSpendingInfo(outPoint = outPoint,
|
|||
hashType =
|
||||
HashType.sigHashAll,
|
||||
conditionalPath =
|
||||
ConditionalPath.NoConditionsLeft)
|
||||
ConditionalPath.NoCondition)
|
||||
|
||||
// all of the UTXO spending information, since we are only
|
||||
//spending one UTXO, this is just one element
|
||||
|
|
Loading…
Add table
Reference in a new issue