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:
Nadav Kohen 2020-05-15 10:14:15 -06:00 committed by GitHub
parent 2869c59a85
commit aee88684f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 1777 additions and 2368 deletions

View file

@ -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 {

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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)) {

View file

@ -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 {

View file

@ -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) =>

View file

@ -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,

View file

@ -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)
}

View file

@ -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
)
}
}
}

View file

@ -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
)
}
}
}

View file

@ -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)

View file

@ -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)
}
}

View file

@ -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

View file

@ -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 =>
()
}

View file

@ -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)

View file

@ -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)
}
}

View file

@ -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,

View file

@ -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))
}
}
}
}

View file

@ -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)
}

View file

@ -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))
}
}

View file

@ -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))
}
```

View file

@ -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

View file

@ -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)

View file

@ -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 =>

View file

@ -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,

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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))
}
}

View file

@ -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
)
}
}

View file

@ -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