mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 10:46:42 +01:00
TxBuilder Refactor (#1426)
* Refactored Transaction Created RawTxBuilder Created RawTxFinalizer as layer on top of RawTxBuilder Introduced RawTxSigner and replaced TxBuilder! Deleted TxBuilder! * fixed things after rebase * Made RawTxBuilder compatible with older versions of scala * Began responding to review * Made Finalizer take a Builder rather than the other way around * Added composition for finalizers * Ran scalafmt * Updated txbuilder example documentation * Moved tests from old TxBuilderTest files to relevant new test files * Added scaladocs
This commit is contained in:
parent
f8c60cc72f
commit
dfd3353cc4
115 changed files with 2549 additions and 455 deletions
|
@ -11,7 +11,8 @@ import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
|
|||
import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput}
|
||||
import org.bitcoins.core.script.crypto._
|
||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest, DoubleSha256DigestBE}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
|
||||
import play.api.libs.json._
|
||||
|
||||
import scala.collection.mutable
|
||||
|
|
|
@ -99,13 +99,17 @@ trait MempoolRpc { self: Client =>
|
|||
getMemPoolEntry(txid.flip)
|
||||
}
|
||||
|
||||
def getMemPoolEntryOpt(txid: DoubleSha256Digest): Future[Option[GetMemPoolEntryResult]] = {
|
||||
def getMemPoolEntryOpt(
|
||||
txid: DoubleSha256Digest): Future[Option[GetMemPoolEntryResult]] = {
|
||||
getMemPoolEntryOpt(txid.flip)
|
||||
}
|
||||
|
||||
def getMemPoolEntryOpt(txid: DoubleSha256DigestBE): Future[Option[GetMemPoolEntryResult]] = {
|
||||
getMemPoolEntry(txid).map(Some(_))
|
||||
.recover { case _: BitcoindException.InvalidAddressOrKey =>
|
||||
def getMemPoolEntryOpt(
|
||||
txid: DoubleSha256DigestBE): Future[Option[GetMemPoolEntryResult]] = {
|
||||
getMemPoolEntry(txid)
|
||||
.map(Some(_))
|
||||
.recover {
|
||||
case _: BitcoindException.InvalidAddressOrKey =>
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.bitcoins.core.config
|
||||
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,8 @@ package org.bitcoins.core.consensus
|
|||
|
||||
import org.bitcoins.core.protocol.blockchain.{Block, MainNetChainParams}
|
||||
import org.bitcoins.core.protocol.transaction.{Transaction, WitnessTransaction}
|
||||
import org.bitcoins.crypto.{BytesUtil, CryptoUtil, DoubleSha256Digest}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.{CryptoUtil, DoubleSha256Digest}
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
import scala.util.Try
|
||||
|
|
|
@ -8,7 +8,7 @@ import org.bitcoins.core.protocol.transaction._
|
|||
import org.bitcoins.core.script.crypto._
|
||||
import org.bitcoins.core.serializers.script.ScriptParser
|
||||
import org.bitcoins.core.util._
|
||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
||||
import org.bitcoins.crypto.DoubleSha256Digest
|
||||
import org.scalatest.{FlatSpec, MustMatchers}
|
||||
|
||||
import scala.util.Try
|
||||
|
|
|
@ -5,7 +5,8 @@ import org.bitcoins.core.number.UInt32
|
|||
import org.bitcoins.testkit.node.NodeTestUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import org.bitcoins.core.config.MainNet
|
||||
import org.bitcoins.crypto.{BytesUtil, CryptoUtil}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.CryptoUtil
|
||||
|
||||
import scala.util.Random
|
||||
import scodec.bits.ByteVector
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.bitcoins.core.protocol
|
|||
|
||||
import org.bitcoins.core.number.UInt64
|
||||
import org.bitcoins.core.protocol.script.ScriptSignature
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import org.bitcoins.core.protocol.transaction.{
|
|||
TransactionConstants,
|
||||
TransactionOutput
|
||||
}
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,7 +3,8 @@ package org.bitcoins.core.protocol.blockchain
|
|||
import org.bitcoins.core.bloom._
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.transaction.TransactionOutPoint
|
||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest, ECPublicKey}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.{DoubleSha256Digest, ECPublicKey}
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,8 +2,8 @@ package org.bitcoins.core.protocol.blockchain
|
|||
|
||||
import org.bitcoins.core.bloom.{BloomFilter, BloomUpdateAll}
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.util.{Leaf, Node}
|
||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
||||
import org.bitcoins.core.util.{BytesUtil, Leaf, Node}
|
||||
import org.bitcoins.crypto.DoubleSha256Digest
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import scodec.bits.BitVector
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.bitcoins.core.protocol.script
|
||||
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.bitcoins.core.protocol.script
|
||||
|
||||
import org.bitcoins.crypto.{BytesUtil, ECDigitalSignature, ECPublicKey}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey}
|
||||
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
|
|
|
@ -7,14 +7,15 @@ import org.bitcoins.core.policy.Policy
|
|||
import org.bitcoins.core.protocol.script.testprotocol.SignatureHashTestCase
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
BaseTransaction,
|
||||
NonWitnessTransaction,
|
||||
Transaction,
|
||||
TransactionOutput,
|
||||
WitnessTransaction
|
||||
}
|
||||
import org.bitcoins.core.script.crypto.{HashType, SIGHASH_ALL}
|
||||
import org.bitcoins.core.serializers.script.RawScriptSignatureParser
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest, ECDigitalSignature}
|
||||
import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil}
|
||||
import org.bitcoins.crypto.{DoubleSha256Digest, ECDigitalSignature}
|
||||
import org.bitcoins.testkit.util.TestUtil
|
||||
import org.scalatest.{FlatSpec, MustMatchers}
|
||||
import scodec.bits.ByteVector
|
||||
|
@ -147,7 +148,7 @@ class ScriptSignatureTest extends FlatSpec with MustMatchers {
|
|||
Transaction(testCase.transaction.hex) must be(testCase.transaction)
|
||||
val output = TransactionOutput(CurrencyUnits.zero, testCase.script)
|
||||
val txSigComponent = testCase.transaction match {
|
||||
case btx: BaseTransaction =>
|
||||
case btx: NonWitnessTransaction =>
|
||||
BaseTxSigComponent(btx,
|
||||
testCase.inputIndex,
|
||||
output,
|
||||
|
|
|
@ -13,7 +13,6 @@ import org.bitcoins.core.protocol.transaction.testprotocol.CoreTransactionTestCa
|
|||
import org.bitcoins.core.script.PreExecutionScriptProgram
|
||||
import org.bitcoins.core.script.interpreter.ScriptInterpreter
|
||||
import org.bitcoins.core.script.result.ScriptOk
|
||||
import org.bitcoins.core.serializers.transaction.RawBaseTransactionParser
|
||||
import org.bitcoins.crypto.CryptoUtil
|
||||
import org.bitcoins.testkit.core.gen.TransactionGenerators
|
||||
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||
|
@ -55,7 +54,7 @@ class TransactionTest extends BitcoinSUnitTest {
|
|||
it must "derive the correct txid from the transaction contents" in {
|
||||
|
||||
//https://btc.blockr.io/api/v1/tx/raw/cddda897b0e9322937ee1f4fd5d6147d60f04a0f4d3b461e4f87066ac3918f2a
|
||||
val tx = RawBaseTransactionParser.read(
|
||||
val tx = BaseTransaction(
|
||||
"01000000020df1e23002ddf909aec026b1cf0c3b6b7943c042f22e25dbd0441855e6b39ee900000000fdfd00004730440220028c02f14654a0cc12c7e3229adb09d5d35bebb6ba1057e39adb1b2706607b0d0220564fab12c6da3d5acef332406027a7ff1cbba980175ffd880e1ba1bf40598f6b014830450221009362f8d67b60773745e983d07ba10efbe566127e244b724385b2ca2e47292dda022033def393954c320653843555ddbe7679b35cc1cacfe1dad923977de8cd6cc6d7014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffffd11533b0f283fca193e361a91ca7ddfc66592e20fd6eaf5dc0f1ef5fed05818000000000fdfe0000483045022100b4062edd75b5b3117f28ba937ed737b10378f762d7d374afabf667180dedcc62022005d44c793a9d787197e12d5049da5e77a09046014219b31e9c6b89948f648f1701483045022100b3b0c0273fc2c531083701f723e03ea3d9111e4bbca33bdf5b175cec82dcab0802206650462db37f9b4fe78da250a3b339ab11e11d84ace8f1b7394a1f6db0960ba4014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffff02500f1e00000000001976a9147ecaa33ef3cd6169517e43188ad3c034db091f5e88ac204e0000000000001976a914321908115d8a138942f98b0b53f86c9a1848501a88ac00000000")
|
||||
|
||||
tx.txId.flip.bytes must be(
|
||||
|
@ -195,7 +194,7 @@ class TransactionTest extends BitcoinSUnitTest {
|
|||
scriptPubKey match {
|
||||
case p2sh: P2SHScriptPubKey =>
|
||||
tx match {
|
||||
case btx: BaseTransaction =>
|
||||
case btx: NonWitnessTransaction =>
|
||||
BaseTxSigComponent(transaction = btx,
|
||||
inputIndex = UInt32(inputIndex),
|
||||
output = TransactionOutput(amount, p2sh),
|
||||
|
@ -209,7 +208,7 @@ class TransactionTest extends BitcoinSUnitTest {
|
|||
}
|
||||
case wit: WitnessScriptPubKey =>
|
||||
tx match {
|
||||
case btx: BaseTransaction =>
|
||||
case btx: NonWitnessTransaction =>
|
||||
BaseTxSigComponent(transaction = btx,
|
||||
inputIndex = UInt32(inputIndex),
|
||||
output = TransactionOutput(amount, wit),
|
||||
|
@ -275,7 +274,7 @@ class TransactionTest extends BitcoinSUnitTest {
|
|||
scriptPubKey match {
|
||||
case p2sh: P2SHScriptPubKey =>
|
||||
tx match {
|
||||
case btx: BaseTransaction =>
|
||||
case btx: NonWitnessTransaction =>
|
||||
BaseTxSigComponent(
|
||||
transaction = btx,
|
||||
inputIndex = UInt32(inputIndex),
|
||||
|
@ -290,7 +289,7 @@ class TransactionTest extends BitcoinSUnitTest {
|
|||
}
|
||||
case wit: WitnessScriptPubKey =>
|
||||
tx match {
|
||||
case btx: BaseTransaction =>
|
||||
case btx: NonWitnessTransaction =>
|
||||
BaseTxSigComponent(
|
||||
transaction = btx,
|
||||
inputIndex = UInt32(inputIndex),
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package org.bitcoins.core.protocol.transaction
|
||||
|
||||
import org.bitcoins.core.currency._
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.script.{
|
||||
EmptyScriptPubKey,
|
||||
EmptyScriptSignature,
|
||||
NonStandardScriptPubKey
|
||||
}
|
||||
import org.bitcoins.core.script.control.OP_RETURN
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||
import org.bitcoins.core.wallet.utxo.EmptyInputInfo
|
||||
import org.bitcoins.crypto.DoubleSha256DigestBE
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
class TxUtilTest extends BitcoinSUnitTest {
|
||||
behavior of "TxUtil"
|
||||
|
||||
private val outPoint =
|
||||
TransactionOutPoint(DoubleSha256DigestBE.empty, UInt32.zero)
|
||||
private val input =
|
||||
TransactionInput(outPoint, EmptyScriptSignature, UInt32.zero)
|
||||
private val output = TransactionOutput(Bitcoins.one, EmptyScriptPubKey)
|
||||
|
||||
private val tx = BaseTransaction(TransactionConstants.validLockVersion,
|
||||
Vector(input),
|
||||
Vector(output),
|
||||
UInt32.zero)
|
||||
|
||||
private val inputInfos = Vector(
|
||||
EmptyInputInfo(outPoint, Bitcoins.one + Bitcoins.one))
|
||||
private val feeRate = SatoshisPerVirtualByte(Satoshis.one)
|
||||
|
||||
it should "detect a bad fee on the tx" in {
|
||||
val estimatedFee = 1000.sats
|
||||
val actualFee = 1.sat
|
||||
val feeRate = SatoshisPerVirtualByte(1.sat)
|
||||
TxUtil
|
||||
.isValidFeeRange(estimatedFee, actualFee, feeRate)
|
||||
.isFailure must be(true)
|
||||
|
||||
TxUtil
|
||||
.isValidFeeRange(actualFee, estimatedFee, feeRate)
|
||||
.isFailure must be(true)
|
||||
}
|
||||
|
||||
it should "detect impossibly high fees" in {
|
||||
val newOutput = TransactionOutput(Bitcoins.zero, EmptyScriptPubKey)
|
||||
val highFeeTx =
|
||||
BaseTransaction(tx.version, tx.inputs, Vector(newOutput), tx.lockTime)
|
||||
|
||||
assert(
|
||||
TxUtil
|
||||
.sanityAmountChecks(isSigned = true, inputInfos, feeRate, highFeeTx)
|
||||
.isFailure)
|
||||
}
|
||||
|
||||
it should "detect dust outputs" in {
|
||||
val newOutput = TransactionOutput(Satoshis(999), EmptyScriptPubKey)
|
||||
val ignoredOutput =
|
||||
TransactionOutput(Bitcoins.one,
|
||||
NonStandardScriptPubKey(Vector(OP_RETURN)))
|
||||
val dustTx = BaseTransaction(tx.version,
|
||||
tx.inputs,
|
||||
Vector(ignoredOutput, newOutput),
|
||||
tx.lockTime)
|
||||
|
||||
assert(
|
||||
TxUtil
|
||||
.sanityAmountChecks(isSigned = true, inputInfos, feeRate, dustTx)
|
||||
.isFailure)
|
||||
}
|
||||
}
|
|
@ -56,7 +56,7 @@ class PSBTTest extends BitcoinSAsyncTest {
|
|||
|
||||
it must "correctly update PSBTs' inputs" in {
|
||||
forAllAsync(PSBTGenerators.psbtToBeSigned)(_.flatMap {
|
||||
case (fullPsbt, utxos) =>
|
||||
case (fullPsbt, utxos, _) =>
|
||||
val emptyPsbt = PSBT.fromUnsignedTx(fullPsbt.transaction)
|
||||
|
||||
val infoAndTxs = PSBTGenerators
|
||||
|
@ -93,7 +93,7 @@ class PSBTTest extends BitcoinSAsyncTest {
|
|||
it must "correctly construct and sign a PSBT" in {
|
||||
forAllAsync(PSBTGenerators.psbtToBeSigned) { psbtWithBuilderF =>
|
||||
psbtWithBuilderF.flatMap {
|
||||
case (psbtNoSigs, utxos) =>
|
||||
case (psbtNoSigs, utxos, _) =>
|
||||
val infos = utxos.toVector.zipWithIndex.map {
|
||||
case (utxo: ScriptSignatureParams[InputInfo], index) =>
|
||||
(index, utxo)
|
||||
|
@ -173,19 +173,17 @@ class PSBTTest extends BitcoinSAsyncTest {
|
|||
val maxFee = crediting - spending
|
||||
val fee = GenUtil.sample(CurrencyUnitGenerator.feeUnit(maxFee))
|
||||
for {
|
||||
(psbt, _) <- PSBTGenerators.psbtAndBuilderFromInputs(
|
||||
(psbt, _, _) <- PSBTGenerators.psbtAndBuilderFromInputs(
|
||||
finalized = false,
|
||||
creditingTxsInfo = creditingTxsInfo,
|
||||
destinations = destinations,
|
||||
changeSPK = changeSPK,
|
||||
network = network,
|
||||
fee = fee)
|
||||
(expected, _) <- PSBTGenerators.psbtAndBuilderFromInputs(
|
||||
(expected, _, _) <- PSBTGenerators.psbtAndBuilderFromInputs(
|
||||
finalized = true,
|
||||
creditingTxsInfo = creditingTxsInfo,
|
||||
destinations = destinations,
|
||||
changeSPK = changeSPK,
|
||||
network = network,
|
||||
fee = fee)
|
||||
} yield {
|
||||
val finalizedPsbtOpt = psbt.finalizePSBT
|
||||
|
@ -198,8 +196,8 @@ class PSBTTest extends BitcoinSAsyncTest {
|
|||
it must "agree with TxBuilder.sign given UTXOSpendingInfos" in {
|
||||
forAllAsync(PSBTGenerators.finalizedPSBTWithBuilder) { psbtAndBuilderF =>
|
||||
for {
|
||||
(psbt, builder) <- psbtAndBuilderF
|
||||
signedTx <- builder.sign
|
||||
(psbt, builder, fee) <- psbtAndBuilderF
|
||||
signedTx <- builder.sign(fee)
|
||||
} yield {
|
||||
val txT = psbt.extractTransactionAndValidate
|
||||
assert(txT.isSuccess, txT.failed)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.bitcoins.core.script
|
||||
|
||||
import org.bitcoins.core.script.constant.ScriptConstant
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,7 +8,7 @@ import org.bitcoins.core.script.crypto.OP_RIPEMD160
|
|||
import org.bitcoins.core.script.locktime.OP_CHECKLOCKTIMEVERIFY
|
||||
import org.bitcoins.core.script.splice.OP_SUBSTR
|
||||
import org.bitcoins.core.script.stack.OP_TOALTSTACK
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.bitcoins.core.script.constant
|
||||
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.bitcoins.core.script.constant
|
||||
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,8 +3,7 @@ package org.bitcoins.core.script.stack
|
|||
import org.bitcoins.core.script.constant._
|
||||
import org.bitcoins.core.script.result._
|
||||
import org.bitcoins.core.script.ExecutedScriptProgram
|
||||
import org.bitcoins.core.util.ScriptProgramTestUtil
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.{BytesUtil, ScriptProgramTestUtil}
|
||||
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package org.bitcoins.core.serializers.blockchain
|
||||
|
||||
import org.bitcoins.core.number.{Int32, UInt32}
|
||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.DoubleSha256Digest
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ package org.bitcoins.core.serializers.blockchain
|
|||
import org.bitcoins.core.number.{Int32, UInt32, UInt64}
|
||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.DoubleSha256Digest
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ import org.bitcoins.core.protocol.blockchain.{
|
|||
MerkleBlock,
|
||||
PartialMerkleTree
|
||||
}
|
||||
import org.bitcoins.core.util.{Leaf, Node}
|
||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
||||
import org.bitcoins.core.util.{BytesUtil, Leaf, Node}
|
||||
import org.bitcoins.crypto.DoubleSha256Digest
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import scodec.bits.BitVector
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.bitcoins.core.serializers.p2p.messages
|
||||
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
class RawFilterAddMessageSerializerTest extends BitcoinSUnitTest {
|
||||
|
|
|
@ -3,7 +3,7 @@ package org.bitcoins.core.serializers.p2p.messages
|
|||
import org.bitcoins.core.bloom.BloomUpdateNone
|
||||
import org.bitcoins.core.number.{UInt32, UInt64}
|
||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,7 +3,8 @@ package org.bitcoins.core.serializers.p2p.messages
|
|||
import org.bitcoins.core.number.UInt64
|
||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||
import org.bitcoins.core.p2p._
|
||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.DoubleSha256Digest
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,8 +2,8 @@ package org.bitcoins.core.serializers.p2p.messages
|
|||
|
||||
import org.bitcoins.core.number.{UInt32, UInt64}
|
||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
||||
import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil}
|
||||
import org.bitcoins.crypto.DoubleSha256Digest
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package org.bitcoins.core.serializers.p2p.messages
|
||||
|
||||
import org.bitcoins.core.p2p.TypeIdentifier.MsgTx
|
||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.DoubleSha256Digest
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,8 @@ package org.bitcoins.core.serializers.p2p.messages
|
|||
|
||||
import org.bitcoins.core.number.{UInt32, UInt64}
|
||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.DoubleSha256Digest
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import scodec.bits.BitVector
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.bitcoins.core.serializers.p2p.messages
|
|||
|
||||
import org.bitcoins.core.number.UInt64
|
||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.bitcoins.core.serializers.p2p.messages
|
||||
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.node.NodeTestUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import java.net.InetSocketAddress
|
|||
import org.bitcoins.core.number.{Int32, Int64, UInt64}
|
||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||
import org.bitcoins.core.p2p._
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
class RawVersionMessageSerializerTest extends BitcoinSUnitTest {
|
||||
|
|
|
@ -5,7 +5,7 @@ import org.bitcoins.core.script.bitwise.OP_EQUALVERIFY
|
|||
import org.bitcoins.core.script.constant._
|
||||
import org.bitcoins.core.script.crypto.{OP_CHECKSIG, OP_HASH160}
|
||||
import org.bitcoins.core.script.stack.OP_DUP
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.bitcoins.core.serializers.script
|
|||
|
||||
import org.bitcoins.core.protocol.script.{EmptyScriptSignature, ScriptSignature}
|
||||
import org.bitcoins.core.script.constant._
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import org.bitcoins.core.script.locktime.OP_CHECKLOCKTIMEVERIFY
|
|||
import org.bitcoins.core.script.reserved.OP_NOP
|
||||
import org.bitcoins.core.script.splice.OP_SIZE
|
||||
import org.bitcoins.core.script.stack.{OP_DROP, OP_DUP, OP_PICK, OP_SWAP}
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
|
|
|
@ -2,10 +2,11 @@ package org.bitcoins.core.serializers.transaction
|
|||
|
||||
import org.bitcoins.core.number.{Int32, UInt32}
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
BaseTransaction,
|
||||
Transaction,
|
||||
TransactionConstants
|
||||
}
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
|
@ -15,7 +16,7 @@ import scodec.bits.ByteVector
|
|||
class RawBaseTransactionParserTest extends BitcoinSUnitTest {
|
||||
val encode = BytesUtil.encodeHex(_: ByteVector)
|
||||
"RawBaseTransactionParser" must "parse a raw transaction" in {
|
||||
val tx: Transaction = RawBaseTransactionParser.read(TestUtil.rawTransaction)
|
||||
val tx: Transaction = BaseTransaction(TestUtil.rawTransaction)
|
||||
tx.version must be(Int32.one)
|
||||
tx.inputs.size must be(2)
|
||||
tx.outputs.size must be(2)
|
||||
|
@ -29,7 +30,7 @@ class RawBaseTransactionParserTest extends BitcoinSUnitTest {
|
|||
//txid bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74
|
||||
val rawTx =
|
||||
"0100000002fc37adbd036fb51b3f4f6f70474270939d6ff8c4ea697639f2b57dd6359e3070010000008b483045022100ad8e961fe3c22b2647d92b078f4c0cf81b3106ea5bf8b900ab8646aa4430216f022071d4edc2b5588be20ac4c2d07edd8ed069e10b2402d3dce2d3b835ccd075f283014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746e0000000036219231b3043efdfb9405bbc2610baa73e340dddfe9c2a07b09bd3785ca6330000000008b483045022100cb097f8720d0c4665e8771fff5181b30584fd9e7d437fae21b440c94fe76d56902206f9b539ae26ec9688c54272d6a3309d93f17fb9835f382fff1ebeead84af2763014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746effffffff02905f0100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988aca0860100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988ac77d3a655"
|
||||
val tx: Transaction = RawBaseTransactionParser.read(rawTx)
|
||||
val tx: Transaction = BaseTransaction(rawTx)
|
||||
tx.txId.hex must be(
|
||||
BytesUtil.flipEndianness(
|
||||
"bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74"))
|
||||
|
@ -40,8 +41,8 @@ class RawBaseTransactionParserTest extends BitcoinSUnitTest {
|
|||
//txid bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74
|
||||
val rawTxWithLockTime =
|
||||
"0100000002fc37adbd036fb51b3f4f6f70474270939d6ff8c4ea697639f2b57dd6359e3070010000008b483045022100ad8e961fe3c22b2647d92b078f4c0cf81b3106ea5bf8b900ab8646aa4430216f022071d4edc2b5588be20ac4c2d07edd8ed069e10b2402d3dce2d3b835ccd075f283014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746e0000000036219231b3043efdfb9405bbc2610baa73e340dddfe9c2a07b09bd3785ca6330000000008b483045022100cb097f8720d0c4665e8771fff5181b30584fd9e7d437fae21b440c94fe76d56902206f9b539ae26ec9688c54272d6a3309d93f17fb9835f382fff1ebeead84af2763014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746effffffff02905f0100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988aca0860100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988ac77d3a655"
|
||||
val tx = RawBaseTransactionParser.read(rawTxWithLockTime)
|
||||
val serializedTx = RawBaseTransactionParser.write(tx)
|
||||
val tx = BaseTransaction(rawTxWithLockTime)
|
||||
val serializedTx = tx.bytes
|
||||
encode(serializedTx) must be(rawTxWithLockTime)
|
||||
}
|
||||
|
||||
|
@ -50,20 +51,20 @@ class RawBaseTransactionParserTest extends BitcoinSUnitTest {
|
|||
val rawTx =
|
||||
"01000000020df1e23002ddf909aec026b1cf0c3b6b7943c042f22e25dbd0441855e6b39ee900000000fdfd00004730440220028c02f14654a0cc12c7e3229adb09d5d35bebb6ba1057e39adb1b2706607b0d0220564fab12c6da3d5acef332406027a7ff1cbba980175ffd880e1ba1bf40598f6b014830450221009362f8d67b60773745e983d07ba10efbe566127e244b724385b2ca2e47292dda022033def393954c320653843555ddbe7679b35cc1cacfe1dad923977de8cd6cc6d7014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffffd11533b0f283fca193e361a91ca7ddfc66592e20fd6eaf5dc0f1ef5fed05818000000000fdfe0000483045022100b4062edd75b5b3117f28ba937ed737b10378f762d7d374afabf667180dedcc62022005d44c793a9d787197e12d5049da5e77a09046014219b31e9c6b89948f648f1701483045022100b3b0c0273fc2c531083701f723e03ea3d9111e4bbca33bdf5b175cec82dcab0802206650462db37f9b4fe78da250a3b339ab11e11d84ace8f1b7394a1f6db0960ba4014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffff02500f1e00000000001976a9147ecaa33ef3cd6169517e43188ad3c034db091f5e88ac204e0000000000001976a914321908115d8a138942f98b0b53f86c9a1848501a88ac00000000"
|
||||
|
||||
val tx = RawBaseTransactionParser.read(rawTx)
|
||||
val serializedTx = RawBaseTransactionParser.write(tx)
|
||||
val tx = BaseTransaction(rawTx)
|
||||
val serializedTx = tx.bytes
|
||||
encode(serializedTx) must be(rawTx)
|
||||
}
|
||||
|
||||
it must "read then write a simple raw transaction with one input and two outputs" in {
|
||||
val rawTx = TestUtil.simpleRawTransaction
|
||||
val tx = RawBaseTransactionParser.read(rawTx)
|
||||
val serializedTx = RawBaseTransactionParser.write(tx)
|
||||
val tx = BaseTransaction(rawTx)
|
||||
val serializedTx = tx.bytes
|
||||
encode(serializedTx) must be(rawTx)
|
||||
}
|
||||
|
||||
it must "parse a transaction with one input and two outputs" in {
|
||||
val tx = RawBaseTransactionParser.read(TestUtil.parentSimpleRawTransaction)
|
||||
val tx = BaseTransaction(TestUtil.parentSimpleRawTransaction)
|
||||
tx.inputs.size must be(1)
|
||||
tx.inputs.head.scriptSignature.hex must be(
|
||||
"6a4730440220048e15422cf62349dc586ffb8c749d40280781edd5064ff27a5910ff5cf225a802206a82685dbc2cf195d158c29309939d5a3cd41a889db6f766f3809fff35722305012103dcfc9882c1b3ae4e03fb6cac08bdb39e284e81d70c7aa8b27612457b2774509b")
|
||||
|
|
|
@ -5,8 +5,7 @@ import org.bitcoins.core.protocol.transaction.{
|
|||
TransactionConstants,
|
||||
TransactionInput
|
||||
}
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil}
|
||||
import org.bitcoins.testkit.util.TestUtil
|
||||
import org.scalatest.{FlatSpec, MustMatchers}
|
||||
import scodec.bits.ByteVector
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.bitcoins.core.serializers.transaction
|
||||
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import org.bitcoins.core.protocol.transaction.{
|
|||
import org.bitcoins.core.script.bitwise.OP_EQUAL
|
||||
import org.bitcoins.core.script.constant.{BytesToPushOntoStack, ScriptConstant}
|
||||
import org.bitcoins.core.script.crypto.OP_HASH160
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.bitcoins.core.serializers.transaction
|
||||
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.protocol.transaction.WitnessTransaction
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
class RawWitnessTransactionParserTest extends BitcoinSUnitTest {
|
||||
|
@ -8,8 +9,8 @@ class RawWitnessTransactionParserTest extends BitcoinSUnitTest {
|
|||
"RawWitnessTransactionParser" must "serialize and deserialize a wtx" in {
|
||||
val hex =
|
||||
"0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f85603000000171600141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b928ffffffff019caef505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02483045022100f764287d3e99b1474da9bec7f7ed236d6c81e793b20c4b5aa1f3051b9a7daa63022016a198031d5554dbb855bdbe8534776a4be6958bd8d530dc001c32b828f6f0ab0121038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990ac00000000"
|
||||
val wtx = RawWitnessTransactionParser.read(hex)
|
||||
val bytes = RawWitnessTransactionParser.write(wtx)
|
||||
val wtx = WitnessTransaction(hex)
|
||||
val bytes = wtx.bytes
|
||||
BytesUtil.encodeHex(bytes) must be(hex)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package org.bitcoins.core.util
|
||||
|
||||
import org.bitcoins.core.util.testprotocol._
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import spray.json._
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.bitcoins.core.util
|
||||
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.testkit.core.gen.StringGenerators
|
||||
import org.scalacheck.{Prop, Properties}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.bitcoins.core.util
|
||||
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import scodec.bits.BitVector
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.bitcoins.core.util
|
||||
|
||||
import org.bitcoins.crypto.{BytesUtil, CryptoUtil}
|
||||
import org.bitcoins.crypto.CryptoUtil
|
||||
import org.bitcoins.testkit.core.gen.CryptoGenerators
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
import scodec.bits._
|
||||
|
|
|
@ -2,7 +2,6 @@ package org.bitcoins.core.util
|
|||
|
||||
import org.bitcoins.testkit.core.gen.NumberGenerator
|
||||
import org.bitcoins.core.number.UInt8
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.scalacheck.{Prop, Properties}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
package org.bitcoins.core.wallet.builder
|
||||
|
||||
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnits, Satoshis}
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.script.{
|
||||
EmptyScriptPubKey,
|
||||
EmptyScriptSignature,
|
||||
P2PKHScriptPubKey,
|
||||
P2WPKHWitnessSPKV0,
|
||||
P2WPKHWitnessV0
|
||||
}
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
BaseTransaction,
|
||||
TransactionConstants,
|
||||
TransactionInput,
|
||||
TransactionOutPoint,
|
||||
TransactionOutput
|
||||
}
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||
import org.bitcoins.core.wallet.utxo.{
|
||||
ConditionalPath,
|
||||
InputInfo,
|
||||
ScriptSignatureParams,
|
||||
UnassignedSegwitNativeInputInfo
|
||||
}
|
||||
import org.bitcoins.crypto.{DoubleSha256DigestBE, ECPrivateKey, ECPublicKey}
|
||||
import org.bitcoins.testkit.Implicits._
|
||||
import org.bitcoins.testkit.core.gen.ScriptGenerators
|
||||
import org.bitcoins.testkit.util.BitcoinSAsyncTest
|
||||
|
||||
class NonInteractiveWithChangeFinalizerTest extends BitcoinSAsyncTest {
|
||||
behavior of "NonInteractiveWithChangeFinalizerTest"
|
||||
|
||||
private val (spk, privKey) = ScriptGenerators.p2pkhScriptPubKey.sampleSome
|
||||
|
||||
private val outPoint =
|
||||
TransactionOutPoint(DoubleSha256DigestBE.empty, UInt32.zero)
|
||||
private val input =
|
||||
TransactionInput(outPoint, EmptyScriptSignature, UInt32.zero)
|
||||
private val output = TransactionOutput(Bitcoins.one, EmptyScriptPubKey)
|
||||
|
||||
private val tx = BaseTransaction(TransactionConstants.validLockVersion,
|
||||
Vector(input),
|
||||
Vector(output),
|
||||
UInt32.zero)
|
||||
|
||||
private val changeSPK = P2PKHScriptPubKey(ECPublicKey.freshPublicKey)
|
||||
|
||||
it should "detect a missing destination" in {
|
||||
val missingOutputTx = BaseTransaction(tx.version,
|
||||
tx.inputs,
|
||||
Vector.empty[TransactionOutput],
|
||||
tx.lockTime)
|
||||
|
||||
assert(
|
||||
NonInteractiveWithChangeFinalizer
|
||||
.sanityDestinationChecks(Vector(outPoint),
|
||||
Vector(output),
|
||||
changeSPK,
|
||||
missingOutputTx)
|
||||
.isFailure)
|
||||
}
|
||||
|
||||
it should "detect extra outputs added" in {
|
||||
val newOutput = TransactionOutput(Bitcoins.max, EmptyScriptPubKey)
|
||||
val extraOutputTx = BaseTransaction(tx.version,
|
||||
tx.inputs,
|
||||
Vector(output, newOutput),
|
||||
tx.lockTime)
|
||||
|
||||
assert(
|
||||
NonInteractiveWithChangeFinalizer
|
||||
.sanityDestinationChecks(Vector(outPoint),
|
||||
Vector(output),
|
||||
changeSPK,
|
||||
extraOutputTx)
|
||||
.isFailure)
|
||||
}
|
||||
|
||||
it should "detect extra outpoints added" in {
|
||||
val newOutPoint =
|
||||
TransactionOutPoint(DoubleSha256DigestBE.empty, UInt32.one)
|
||||
val newInput =
|
||||
TransactionInput(newOutPoint, EmptyScriptSignature, UInt32.zero)
|
||||
val extraOutPointTx = BaseTransaction(tx.version,
|
||||
Vector(input, newInput),
|
||||
tx.outputs,
|
||||
tx.lockTime)
|
||||
|
||||
assert(
|
||||
NonInteractiveWithChangeFinalizer
|
||||
.sanityDestinationChecks(Vector(outPoint),
|
||||
Vector(output),
|
||||
changeSPK,
|
||||
extraOutPointTx)
|
||||
.isFailure)
|
||||
}
|
||||
|
||||
it should "failed to build a transaction that mints money out of thin air" in {
|
||||
|
||||
val creditingOutput = TransactionOutput(CurrencyUnits.zero, spk)
|
||||
val destinations =
|
||||
Vector(TransactionOutput(Satoshis.one, EmptyScriptPubKey))
|
||||
val creditingTx = BaseTransaction(version =
|
||||
TransactionConstants.validLockVersion,
|
||||
inputs = Nil,
|
||||
outputs = Vector(creditingOutput),
|
||||
lockTime = TransactionConstants.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
val utxo = ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
signer = privKey,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
val utxos = Vector(utxo)
|
||||
val feeUnit = SatoshisPerVirtualByte(Satoshis.one)
|
||||
|
||||
recoverToSucceededIf[IllegalArgumentException] {
|
||||
NonInteractiveWithChangeFinalizer.txFrom(outputs = destinations,
|
||||
utxos = utxos,
|
||||
feeRate = feeUnit,
|
||||
changeSPK = EmptyScriptPubKey)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail to build a transaction when we pass in a negative fee rate" in {
|
||||
val creditingOutput = TransactionOutput(CurrencyUnits.zero, spk)
|
||||
val destinations =
|
||||
Vector(TransactionOutput(Satoshis.one, EmptyScriptPubKey))
|
||||
val creditingTx = BaseTransaction(version =
|
||||
TransactionConstants.validLockVersion,
|
||||
inputs = Nil,
|
||||
outputs = Vector(creditingOutput),
|
||||
lockTime = TransactionConstants.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
val utxo = ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
signer = privKey,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
val utxos = Vector(utxo)
|
||||
val feeUnit = SatoshisPerVirtualByte(Satoshis(-1))
|
||||
|
||||
recoverToSucceededIf[IllegalArgumentException] {
|
||||
NonInteractiveWithChangeFinalizer.txFrom(outputs = destinations,
|
||||
utxos = utxos,
|
||||
feeRate = feeUnit,
|
||||
changeSPK = EmptyScriptPubKey)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail to construct a tx given an UnassignedSegwitNativeUTXOSpendingInfo" in {
|
||||
val outPoint = TransactionOutPoint(DoubleSha256DigestBE.empty, UInt32.zero)
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
|
||||
val spendingInfo =
|
||||
ScriptSignatureParams(
|
||||
UnassignedSegwitNativeInputInfo(
|
||||
outPoint = outPoint,
|
||||
amount = Bitcoins.one + CurrencyUnits.oneMBTC,
|
||||
scriptPubKey = P2WPKHWitnessSPKV0(pubKey),
|
||||
scriptWitness = P2WPKHWitnessV0(pubKey),
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
Vector(pubKey)
|
||||
),
|
||||
signers = Vector(privKey),
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
|
||||
recoverToSucceededIf[UnsupportedOperationException] {
|
||||
NonInteractiveWithChangeFinalizer.txFrom(
|
||||
Vector(TransactionOutput(Bitcoins.one, EmptyScriptPubKey)),
|
||||
Vector(spendingInfo),
|
||||
SatoshisPerVirtualByte(Satoshis.one),
|
||||
EmptyScriptPubKey
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,395 @@
|
|||
package org.bitcoins.core.wallet.builder
|
||||
|
||||
import org.bitcoins.core.crypto.{
|
||||
BaseTxSigComponent,
|
||||
WitnessTxSigComponentP2SH,
|
||||
WitnessTxSigComponentRaw
|
||||
}
|
||||
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnits, Satoshis}
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.policy.Policy
|
||||
import org.bitcoins.core.protocol.script.{
|
||||
CLTVScriptPubKey,
|
||||
CSVScriptPubKey,
|
||||
ConditionalScriptPubKey,
|
||||
EmptyScriptPubKey,
|
||||
MultiSignatureScriptPubKey,
|
||||
NonStandardScriptPubKey,
|
||||
P2PKHScriptPubKey,
|
||||
P2PKScriptPubKey,
|
||||
P2PKWithTimeoutScriptPubKey,
|
||||
P2SHScriptPubKey,
|
||||
P2SHScriptSignature,
|
||||
P2WSHWitnessV0,
|
||||
UnassignedWitnessScriptPubKey,
|
||||
WitnessCommitment,
|
||||
WitnessScriptPubKey,
|
||||
WitnessScriptPubKeyV0
|
||||
}
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
BaseTransaction,
|
||||
Transaction,
|
||||
TransactionConstants,
|
||||
TransactionInput,
|
||||
TransactionOutPoint,
|
||||
TransactionOutput,
|
||||
WitnessTransaction
|
||||
}
|
||||
import org.bitcoins.core.script.PreExecutionScriptProgram
|
||||
import org.bitcoins.core.script.constant.ScriptNumber
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.core.script.interpreter.ScriptInterpreter
|
||||
import org.bitcoins.core.wallet.fee.{SatoshisPerByte, SatoshisPerVirtualByte}
|
||||
import org.bitcoins.core.wallet.utxo.{
|
||||
ConditionalPath,
|
||||
InputInfo,
|
||||
InputSigningInfo,
|
||||
LockTimeInputInfo,
|
||||
ScriptSignatureParams
|
||||
}
|
||||
import org.bitcoins.crypto.{DoubleSha256DigestBE, ECPrivateKey}
|
||||
import org.bitcoins.testkit.Implicits._
|
||||
import org.bitcoins.testkit.core.gen.{CreditingTxGen, ScriptGenerators}
|
||||
import org.bitcoins.testkit.util.BitcoinSAsyncTest
|
||||
|
||||
class RawTxSignerTest extends BitcoinSAsyncTest {
|
||||
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
||||
generatorDrivenConfigNewCode
|
||||
|
||||
behavior of "RawTxSigner"
|
||||
|
||||
private val (spk, privKey) = ScriptGenerators.p2pkhScriptPubKey.sampleSome
|
||||
|
||||
it should "fail a transaction when the user invariants fail" in {
|
||||
val creditingOutput = TransactionOutput(CurrencyUnits.zero, spk)
|
||||
val destinations =
|
||||
Vector(TransactionOutput(Satoshis.one, EmptyScriptPubKey))
|
||||
val creditingTx = BaseTransaction(TransactionConstants.validLockVersion,
|
||||
Nil,
|
||||
Vector(creditingOutput),
|
||||
TransactionConstants.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
val utxo =
|
||||
ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
privKey,
|
||||
HashType.sigHashAll
|
||||
)
|
||||
val utxos = Vector(utxo)
|
||||
val feeUnit = SatoshisPerVirtualByte(currencyUnit = Satoshis(1))
|
||||
val utxF = NonInteractiveWithChangeFinalizer.txFrom(outputs = destinations,
|
||||
utxos = utxos,
|
||||
feeRate = feeUnit,
|
||||
changeSPK =
|
||||
EmptyScriptPubKey)
|
||||
//trivially false
|
||||
val f = (_: Seq[ScriptSignatureParams[InputInfo]], _: Transaction) => false
|
||||
|
||||
recoverToSucceededIf[IllegalArgumentException] {
|
||||
utxF.flatMap(utx => RawTxSigner.sign(utx, utxos, feeUnit, f))
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail to sign a p2pkh if we don't pass in the public key" in {
|
||||
val p2pkh = P2PKHScriptPubKey(privKey.publicKey)
|
||||
val creditingOutput = TransactionOutput(CurrencyUnits.zero, p2pkh)
|
||||
val destinations =
|
||||
Vector(TransactionOutput(Satoshis.one, EmptyScriptPubKey))
|
||||
val creditingTx = BaseTransaction(TransactionConstants.validLockVersion,
|
||||
Nil,
|
||||
Vector(creditingOutput),
|
||||
TransactionConstants.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
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 utxos = Vector(utxo)
|
||||
|
||||
val feeUnit = SatoshisPerVirtualByte(Satoshis.one)
|
||||
val utxF = NonInteractiveWithChangeFinalizer.txFrom(outputs = destinations,
|
||||
utxos = utxos,
|
||||
feeRate = feeUnit,
|
||||
changeSPK =
|
||||
EmptyScriptPubKey)
|
||||
|
||||
recoverToSucceededIf[IllegalArgumentException] {
|
||||
utxF.flatMap(utx => RawTxSigner.sign(utx, utxos, feeUnit))
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail to sign a p2pkh if we pass in the wrong public key" in {
|
||||
val p2pkh = P2PKHScriptPubKey(privKey.publicKey)
|
||||
val creditingOutput = TransactionOutput(CurrencyUnits.zero, p2pkh)
|
||||
val destinations =
|
||||
Vector(TransactionOutput(Satoshis.one, EmptyScriptPubKey))
|
||||
val creditingTx = BaseTransaction(version =
|
||||
TransactionConstants.validLockVersion,
|
||||
inputs = Nil,
|
||||
outputs = Vector(creditingOutput),
|
||||
lockTime = TransactionConstants.lockTime)
|
||||
val outPoint =
|
||||
TransactionOutPoint(txId = creditingTx.txId, vout = UInt32.zero)
|
||||
val utxo = ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
signer = privKey,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
val utxos = Vector(utxo)
|
||||
|
||||
val feeUnit = SatoshisPerVirtualByte(Satoshis.one)
|
||||
val utxF = NonInteractiveWithChangeFinalizer.txFrom(outputs = destinations,
|
||||
utxos = utxos,
|
||||
feeRate = feeUnit,
|
||||
changeSPK =
|
||||
EmptyScriptPubKey)
|
||||
|
||||
recoverToSucceededIf[IllegalArgumentException] {
|
||||
utxF.flatMap(utx => RawTxSigner.sign(utx, utxos, feeUnit))
|
||||
}
|
||||
}
|
||||
|
||||
it should "succeed to sign a cltv spk that uses a second-based locktime" in {
|
||||
val fundingPrivKey = ECPrivateKey.freshPrivateKey
|
||||
|
||||
val lockTime = System.currentTimeMillis / 1000
|
||||
|
||||
val cltvSPK =
|
||||
CLTVScriptPubKey(ScriptNumber(lockTime),
|
||||
P2PKScriptPubKey(fundingPrivKey.publicKey))
|
||||
|
||||
val cltvSpendingInfo = ScriptSignatureParams(
|
||||
LockTimeInputInfo(TransactionOutPoint(DoubleSha256DigestBE.empty,
|
||||
UInt32.zero),
|
||||
Bitcoins.one,
|
||||
cltvSPK,
|
||||
ConditionalPath.NoCondition),
|
||||
Vector(fundingPrivKey),
|
||||
HashType.sigHashAll
|
||||
)
|
||||
|
||||
val utxos = Vector(cltvSpendingInfo)
|
||||
val feeUnit = SatoshisPerByte(Satoshis.one)
|
||||
|
||||
val utxF =
|
||||
NonInteractiveWithChangeFinalizer.txFrom(
|
||||
outputs = Vector(
|
||||
TransactionOutput(Bitcoins.one - CurrencyUnits.oneMBTC,
|
||||
EmptyScriptPubKey)),
|
||||
utxos = utxos,
|
||||
feeRate = feeUnit,
|
||||
changeSPK = EmptyScriptPubKey
|
||||
)
|
||||
|
||||
utxF
|
||||
.flatMap(utx => RawTxSigner.sign(utx, utxos, feeUnit))
|
||||
.map(tx => assert(tx.lockTime == UInt32(lockTime)))
|
||||
}
|
||||
|
||||
it should "succeed to sign a cltv spk that uses a block height locktime" in {
|
||||
val fundingPrivKey = ECPrivateKey.freshPrivateKey
|
||||
|
||||
val lockTime = 1000
|
||||
|
||||
val cltvSPK =
|
||||
CLTVScriptPubKey(ScriptNumber(lockTime),
|
||||
P2PKScriptPubKey(fundingPrivKey.publicKey))
|
||||
|
||||
val cltvSpendingInfo = ScriptSignatureParams(
|
||||
LockTimeInputInfo(TransactionOutPoint(DoubleSha256DigestBE.empty,
|
||||
UInt32.zero),
|
||||
Bitcoins.one,
|
||||
cltvSPK,
|
||||
ConditionalPath.NoCondition),
|
||||
Vector(fundingPrivKey),
|
||||
HashType.sigHashAll
|
||||
)
|
||||
|
||||
val utxos = Vector(cltvSpendingInfo)
|
||||
val feeUnit = SatoshisPerByte(Satoshis.one)
|
||||
|
||||
val utxF =
|
||||
NonInteractiveWithChangeFinalizer.txFrom(
|
||||
outputs = Vector(
|
||||
TransactionOutput(Bitcoins.one - CurrencyUnits.oneMBTC,
|
||||
EmptyScriptPubKey)),
|
||||
utxos = utxos,
|
||||
feeRate = feeUnit,
|
||||
changeSPK = EmptyScriptPubKey
|
||||
)
|
||||
|
||||
utxF
|
||||
.flatMap(utx => RawTxSigner.sign(utx, utxos, feeUnit))
|
||||
.map(tx => assert(tx.lockTime == UInt32(lockTime)))
|
||||
}
|
||||
|
||||
it should "fail to sign a cltv spk that uses both a second-based and a block height locktime" in {
|
||||
val fundingPrivKey1 = ECPrivateKey.freshPrivateKey
|
||||
val fundingPrivKey2 = ECPrivateKey.freshPrivateKey
|
||||
|
||||
val lockTime1 = System.currentTimeMillis / 1000
|
||||
val lockTime2 = 1000
|
||||
|
||||
val cltvSPK1 =
|
||||
CLTVScriptPubKey(ScriptNumber(lockTime1),
|
||||
P2PKScriptPubKey(fundingPrivKey1.publicKey))
|
||||
val cltvSPK2 =
|
||||
CLTVScriptPubKey(ScriptNumber(lockTime2),
|
||||
P2PKScriptPubKey(fundingPrivKey2.publicKey))
|
||||
|
||||
val cltvSpendingInfo1 = ScriptSignatureParams(
|
||||
LockTimeInputInfo(TransactionOutPoint(DoubleSha256DigestBE.empty,
|
||||
UInt32.zero),
|
||||
Bitcoins.one,
|
||||
cltvSPK1,
|
||||
ConditionalPath.NoCondition),
|
||||
Vector(fundingPrivKey1),
|
||||
HashType.sigHashAll
|
||||
)
|
||||
|
||||
val cltvSpendingInfo2 = ScriptSignatureParams(
|
||||
LockTimeInputInfo(TransactionOutPoint(DoubleSha256DigestBE.empty,
|
||||
UInt32.one),
|
||||
Bitcoins.one,
|
||||
cltvSPK2,
|
||||
ConditionalPath.NoCondition),
|
||||
Vector(fundingPrivKey2),
|
||||
HashType.sigHashAll
|
||||
)
|
||||
|
||||
val utxos = Vector(cltvSpendingInfo1, cltvSpendingInfo2)
|
||||
val feeRate = SatoshisPerByte(Satoshis.one)
|
||||
|
||||
val utxF =
|
||||
NonInteractiveWithChangeFinalizer.txFrom(
|
||||
Vector(
|
||||
TransactionOutput(Bitcoins.one + Bitcoins.one - CurrencyUnits.oneMBTC,
|
||||
EmptyScriptPubKey)),
|
||||
utxos,
|
||||
feeRate,
|
||||
EmptyScriptPubKey
|
||||
)
|
||||
|
||||
recoverToSucceededIf[IllegalArgumentException](
|
||||
utxF.flatMap(utx => RawTxSigner.sign(utx, utxos, feeRate))
|
||||
)
|
||||
}
|
||||
|
||||
def verifyScript(
|
||||
tx: Transaction,
|
||||
utxos: Vector[InputSigningInfo[InputInfo]]): Boolean = {
|
||||
val programs: Vector[PreExecutionScriptProgram] =
|
||||
tx.inputs.zipWithIndex.toVector.map {
|
||||
case (input: TransactionInput, idx: Int) =>
|
||||
val outpoint = input.previousOutput
|
||||
|
||||
val creditingTx =
|
||||
utxos.find(u => u.outPoint.txId == outpoint.txId).get
|
||||
|
||||
val output = creditingTx.output
|
||||
|
||||
val spk = output.scriptPubKey
|
||||
|
||||
val amount = output.value
|
||||
|
||||
val txSigComponent = spk match {
|
||||
case witSPK: WitnessScriptPubKeyV0 =>
|
||||
val o = TransactionOutput(amount, witSPK)
|
||||
WitnessTxSigComponentRaw(tx.asInstanceOf[WitnessTransaction],
|
||||
UInt32(idx),
|
||||
o,
|
||||
Policy.standardFlags)
|
||||
case _: UnassignedWitnessScriptPubKey => ???
|
||||
case x @ (_: P2PKScriptPubKey | _: P2PKHScriptPubKey |
|
||||
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
|
||||
_: WitnessCommitment | _: CSVScriptPubKey |
|
||||
_: CLTVScriptPubKey | _: ConditionalScriptPubKey |
|
||||
_: NonStandardScriptPubKey | EmptyScriptPubKey) =>
|
||||
val o = TransactionOutput(CurrencyUnits.zero, x)
|
||||
BaseTxSigComponent(tx, UInt32(idx), o, Policy.standardFlags)
|
||||
|
||||
case _: P2SHScriptPubKey =>
|
||||
val p2shScriptSig =
|
||||
tx.inputs(idx).scriptSignature.asInstanceOf[P2SHScriptSignature]
|
||||
p2shScriptSig.redeemScript match {
|
||||
|
||||
case _: WitnessScriptPubKey =>
|
||||
WitnessTxSigComponentP2SH(
|
||||
transaction = tx.asInstanceOf[WitnessTransaction],
|
||||
inputIndex = UInt32(idx),
|
||||
output = output,
|
||||
flags = Policy.standardFlags)
|
||||
|
||||
case _ =>
|
||||
BaseTxSigComponent(tx,
|
||||
UInt32(idx),
|
||||
output,
|
||||
Policy.standardFlags)
|
||||
}
|
||||
}
|
||||
|
||||
PreExecutionScriptProgram(txSigComponent)
|
||||
}
|
||||
ScriptInterpreter.runAllVerify(programs)
|
||||
}
|
||||
|
||||
it should "sign a mix of spks in a tx and then have it verified" in {
|
||||
forAllAsync(CreditingTxGen.inputsAndOutputs(),
|
||||
ScriptGenerators.scriptPubKey) {
|
||||
case ((creditingTxsInfo, destinations), (changeSPK, _)) =>
|
||||
val fee = SatoshisPerVirtualByte(Satoshis(1000))
|
||||
val utxF =
|
||||
NonInteractiveWithChangeFinalizer.txFrom(outputs = destinations,
|
||||
utxos = creditingTxsInfo,
|
||||
feeRate = fee,
|
||||
changeSPK = changeSPK)
|
||||
val txF = utxF.flatMap(utx =>
|
||||
RawTxSigner.sign(utx, creditingTxsInfo.toVector, fee))
|
||||
|
||||
txF.map { tx =>
|
||||
assert(verifyScript(tx, creditingTxsInfo.toVector))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it should "sign a mix of p2sh/p2wsh in a tx and then have it verified" in {
|
||||
forAllAsync(CreditingTxGen.inputsAndOutputs(CreditingTxGen.nestedOutputs),
|
||||
ScriptGenerators.scriptPubKey) {
|
||||
case ((creditingTxsInfo, destinations), (changeSPK, _)) =>
|
||||
val fee = SatoshisPerByte(Satoshis(1000))
|
||||
val utxF =
|
||||
NonInteractiveWithChangeFinalizer.txFrom(outputs = destinations,
|
||||
utxos = creditingTxsInfo,
|
||||
feeRate = fee,
|
||||
changeSPK = changeSPK)
|
||||
val txF = utxF.flatMap(utx =>
|
||||
RawTxSigner.sign(utx, creditingTxsInfo.toVector, fee))
|
||||
|
||||
txF.map { tx =>
|
||||
assert(verifyScript(tx, creditingTxsInfo.toVector))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,7 +39,10 @@ import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature
|
|||
import org.bitcoins.core.psbt.PSBT
|
||||
import org.bitcoins.core.script.PreExecutionScriptProgram
|
||||
import org.bitcoins.core.script.interpreter.ScriptInterpreter
|
||||
import org.bitcoins.core.wallet.builder.BitcoinTxBuilder
|
||||
import org.bitcoins.core.wallet.builder.{
|
||||
NonInteractiveWithChangeFinalizer,
|
||||
RawTxSigner
|
||||
}
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||
import org.bitcoins.core.wallet.utxo.{
|
||||
ECSignatureParams,
|
||||
|
@ -52,7 +55,6 @@ import org.bitcoins.core.wallet.utxo.{
|
|||
}
|
||||
import org.bitcoins.crypto.ECDigitalSignature
|
||||
import org.bitcoins.testkit.core.gen.{
|
||||
ChainParamsGenerator,
|
||||
CreditingTxGen,
|
||||
GenUtil,
|
||||
ScriptGenerators,
|
||||
|
@ -121,19 +123,19 @@ class SignerTest extends BitcoinSAsyncTest {
|
|||
|
||||
it must "sign a mix of spks in a tx and then verify that single signing agrees" in {
|
||||
forAllAsync(CreditingTxGen.inputsAndOutputs(),
|
||||
ScriptGenerators.scriptPubKey,
|
||||
ChainParamsGenerator.bitcoinNetworkParams) {
|
||||
case ((creditingTxsInfos, destinations), changeSPK, network) =>
|
||||
ScriptGenerators.scriptPubKey) {
|
||||
case ((creditingTxsInfos, destinations), (changeSPK, _)) =>
|
||||
val fee = SatoshisPerVirtualByte(Satoshis(1000))
|
||||
|
||||
for {
|
||||
builder <- BitcoinTxBuilder(destinations,
|
||||
unsignedTx <- NonInteractiveWithChangeFinalizer.txFrom(
|
||||
destinations,
|
||||
creditingTxsInfos,
|
||||
fee,
|
||||
changeSPK._1,
|
||||
network)
|
||||
unsignedTx <- builder.unsignedTx
|
||||
signedTx <- builder.sign
|
||||
changeSPK)
|
||||
signedTx <- RawTxSigner.sign(unsignedTx,
|
||||
creditingTxsInfos.toVector,
|
||||
fee)
|
||||
|
||||
singleSigs: Vector[Vector[ECDigitalSignature]] <- {
|
||||
val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] =
|
||||
|
@ -257,18 +259,16 @@ class SignerTest extends BitcoinSAsyncTest {
|
|||
|
||||
it must "sign p2wsh inputs correctly when provided no witness data" in {
|
||||
forAllAsync(CreditingTxGen.inputsAndOutputs(CreditingTxGen.p2wshOutputs),
|
||||
ScriptGenerators.scriptPubKey,
|
||||
ChainParamsGenerator.bitcoinNetworkParams) {
|
||||
case ((creditingTxsInfos, destinations), changeSPK, network) =>
|
||||
ScriptGenerators.scriptPubKey) {
|
||||
case ((creditingTxsInfos, destinations), (changeSPK, _)) =>
|
||||
val fee = SatoshisPerVirtualByte(Satoshis(100))
|
||||
|
||||
for {
|
||||
builder <- BitcoinTxBuilder(destinations,
|
||||
unsignedTx <- NonInteractiveWithChangeFinalizer.txFrom(
|
||||
destinations,
|
||||
creditingTxsInfos,
|
||||
fee,
|
||||
changeSPK._1,
|
||||
network)
|
||||
unsignedTx <- builder.unsignedTx
|
||||
changeSPK)
|
||||
|
||||
singleSigs: Vector[Vector[PartialSignature]] <- {
|
||||
val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] =
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
package org.bitcoins.core.wallet.utxo
|
||||
|
||||
import org.bitcoins.core.currency.CurrencyUnits
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.script.{
|
||||
EmptyScriptPubKey,
|
||||
P2SHScriptPubKey,
|
||||
P2WPKHWitnessSPKV0,
|
||||
P2WSHWitnessSPKV0,
|
||||
P2WSHWitnessV0
|
||||
}
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
BaseTransaction,
|
||||
TransactionConstants,
|
||||
TransactionOutPoint,
|
||||
TransactionOutput
|
||||
}
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.testkit.Implicits._
|
||||
import org.bitcoins.testkit.core.gen.ScriptGenerators
|
||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||
|
||||
class InputSigningInfoTest extends BitcoinSUnitTest {
|
||||
behavior of "InputSigningInfo"
|
||||
|
||||
private val (spk, privKey) = ScriptGenerators.p2pkhScriptPubKey.sampleSome
|
||||
|
||||
it should "fail to build a tx if you have the wrong redeemscript" in {
|
||||
val p2sh = P2SHScriptPubKey(spk)
|
||||
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)
|
||||
val inputInfo = InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = Some(EmptyScriptPubKey),
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoCondition
|
||||
)
|
||||
|
||||
assertThrows[RuntimeException] {
|
||||
ScriptSignatureParams(
|
||||
inputInfo = inputInfo,
|
||||
signer = privKey,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
|
||||
assertThrows[RuntimeException] {
|
||||
ECSignatureParams(
|
||||
inputInfo = inputInfo,
|
||||
signer = privKey,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail to build a tx if you have the wrong script witness" in {
|
||||
val p2wsh = P2WSHWitnessSPKV0(spk)
|
||||
val creditingOutput = TransactionOutput(CurrencyUnits.zero, p2wsh)
|
||||
val creditingTx = BaseTransaction(TransactionConstants.validLockVersion,
|
||||
Nil,
|
||||
Vector(creditingOutput),
|
||||
TransactionConstants.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
assertThrows[IllegalArgumentException] {
|
||||
ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
privKey,
|
||||
HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
ECSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
privKey,
|
||||
HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail to sign a p2wpkh if we don't pass in the public key" in {
|
||||
val p2wpkh = P2WPKHWitnessSPKV0(pubKey = privKey.publicKey)
|
||||
val creditingOutput =
|
||||
TransactionOutput(value = CurrencyUnits.zero, scriptPubKey = p2wpkh)
|
||||
val creditingTx = BaseTransaction(version =
|
||||
TransactionConstants.validLockVersion,
|
||||
inputs = Nil,
|
||||
outputs = Vector(creditingOutput),
|
||||
lockTime = TransactionConstants.lockTime)
|
||||
val outPoint =
|
||||
TransactionOutPoint(txId = creditingTx.txId, vout = UInt32.zero)
|
||||
val inputInfo = InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
conditionalPath = ConditionalPath.NoCondition
|
||||
)
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
ScriptSignatureParams(
|
||||
inputInfo = inputInfo,
|
||||
signer = privKey,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
ECSignatureParams(
|
||||
inputInfo = inputInfo,
|
||||
signer = privKey,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it should "fail to sign a p2wpkh if we pass in the wrong public key" in {
|
||||
val p2wpkh = P2WPKHWitnessSPKV0(privKey.publicKey)
|
||||
val creditingOutput = TransactionOutput(CurrencyUnits.zero, p2wpkh)
|
||||
val creditingTx = BaseTransaction(TransactionConstants.validLockVersion,
|
||||
Nil,
|
||||
Vector(creditingOutput),
|
||||
TransactionConstants.lockTime)
|
||||
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
|
||||
assertThrows[IllegalArgumentException] {
|
||||
ScriptSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
privKey,
|
||||
HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
|
||||
assertThrows[IllegalArgumentException] {
|
||||
ECSignatureParams(
|
||||
InputInfo(
|
||||
outPoint = outPoint,
|
||||
output = creditingOutput,
|
||||
redeemScriptOpt = None,
|
||||
scriptWitnessOpt = Some(P2WSHWitnessV0(EmptyScriptPubKey)),
|
||||
conditionalPath = ConditionalPath.NoCondition,
|
||||
hashPreImages = Vector(privKey.publicKey)
|
||||
),
|
||||
privKey,
|
||||
HashType.sigHashAll
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,13 +10,12 @@ import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutPoint}
|
|||
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||
import org.bitcoins.core.script.constant.{ScriptConstant, ScriptToken}
|
||||
import org.bitcoins.core.serializers.bloom.RawBloomFilterSerializer
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil}
|
||||
import scodec.bits.{BitVector, ByteVector}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.hashing.MurmurHash3
|
||||
import org.bitcoins.crypto.{
|
||||
BytesUtil,
|
||||
CryptoUtil,
|
||||
DoubleSha256Digest,
|
||||
ECPublicKey,
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.bitcoins.core.consensus
|
|||
|
||||
import org.bitcoins.core.protocol.blockchain.Block
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
BaseTransaction,
|
||||
NonWitnessTransaction,
|
||||
Transaction,
|
||||
WitnessTransaction
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ trait Merkle extends BitcoinSLogger {
|
|||
val coinbaseWTxId = DoubleSha256Digest.empty
|
||||
val hashes = block.transactions.tail.map {
|
||||
case wtx: WitnessTransaction => wtx.wTxId
|
||||
case btx: BaseTransaction => btx.txId
|
||||
case btx: NonWitnessTransaction => btx.txId
|
||||
}
|
||||
build(coinbaseWTxId +: hashes)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package org.bitcoins.core.crypto
|
||||
|
||||
import org.bitcoins.core.config.{NetworkParameters, Networks}
|
||||
import org.bitcoins.core.util.Base58
|
||||
import org.bitcoins.crypto.{BytesUtil, CryptoUtil, ECPrivateKey}
|
||||
import org.bitcoins.core.util.{Base58, BytesUtil}
|
||||
import org.bitcoins.crypto.{CryptoUtil, ECPrivateKey}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
|
|
@ -7,7 +7,6 @@ import org.bitcoins.core.util._
|
|||
import org.bitcoins.crypto.{
|
||||
BaseECKey,
|
||||
BouncyCastleUtil,
|
||||
BytesUtil,
|
||||
CryptoUtil,
|
||||
ECDigitalSignature,
|
||||
ECPrivateKey,
|
||||
|
|
|
@ -8,13 +8,8 @@ import org.bitcoins.core.script.crypto._
|
|||
import org.bitcoins.core.script.flag.{ScriptFlag, ScriptFlagUtil}
|
||||
import org.bitcoins.core.script.result.ScriptErrorWitnessPubKeyType
|
||||
import org.bitcoins.core.policy.Policy
|
||||
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil}
|
||||
import org.bitcoins.crypto.{
|
||||
BytesUtil,
|
||||
DERSignatureUtil,
|
||||
ECDigitalSignature,
|
||||
ECPublicKey
|
||||
}
|
||||
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil, BytesUtil}
|
||||
import org.bitcoins.crypto.{DERSignatureUtil, ECDigitalSignature, ECPublicKey}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
|
|
@ -7,8 +7,8 @@ import org.bitcoins.core.protocol.transaction._
|
|||
import org.bitcoins.core.script.constant.ScriptToken
|
||||
import org.bitcoins.core.script.crypto._
|
||||
import org.bitcoins.core.serializers.transaction.RawTransactionOutputParser
|
||||
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil}
|
||||
import org.bitcoins.crypto.{BytesUtil, CryptoUtil, DoubleSha256Digest}
|
||||
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil, BytesUtil}
|
||||
import org.bitcoins.crypto.{CryptoUtil, DoubleSha256Digest}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
/**
|
||||
|
|
|
@ -53,7 +53,7 @@ object TxSigComponent {
|
|||
output.scriptPubKey match {
|
||||
case _: WitnessScriptPubKey =>
|
||||
transaction match {
|
||||
case _: BaseTransaction =>
|
||||
case _: NonWitnessTransaction =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Cannot spend from segwit output ($output) with a base transaction ($transaction)")
|
||||
case wtx: WitnessTransaction =>
|
||||
|
@ -64,7 +64,7 @@ object TxSigComponent {
|
|||
if (WitnessScriptPubKey.isWitnessScriptPubKey(
|
||||
p2shScriptSig.redeemScript.asm)) {
|
||||
transaction match {
|
||||
case _: BaseTransaction =>
|
||||
case _: NonWitnessTransaction =>
|
||||
throw new IllegalArgumentException(
|
||||
s"Cannot spend from segwit output ($output) with a base transaction ($transaction)")
|
||||
case wtx: WitnessTransaction =>
|
||||
|
@ -77,6 +77,19 @@ object TxSigComponent {
|
|||
BaseTxSigComponent(transaction, inputIndex, output, flags)
|
||||
}
|
||||
}
|
||||
|
||||
def getScriptWitness(
|
||||
txSigComponent: TxSigComponent): Option[ScriptWitnessV0] = {
|
||||
txSigComponent.transaction match {
|
||||
case _: NonWitnessTransaction => None
|
||||
case wtx: WitnessTransaction =>
|
||||
val witness = wtx.witness.witnesses(txSigComponent.inputIndex.toInt)
|
||||
witness match {
|
||||
case EmptyScriptWitness => None
|
||||
case witness: ScriptWitnessV0 => Some(witness)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,7 +5,8 @@ import org.bitcoins.core.protocol.blockchain.Block
|
|||
import org.bitcoins.core.protocol.script.{EmptyScriptPubKey, ScriptPubKey}
|
||||
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutput}
|
||||
import org.bitcoins.core.script.control.OP_RETURN
|
||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.DoubleSha256Digest
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
object BlockFilter {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.bitcoins.core.number
|
||||
|
||||
import org.bitcoins.core.util.NumberUtil
|
||||
import org.bitcoins.crypto.{BytesUtil, Factory, NetworkElement}
|
||||
import org.bitcoins.core.util.{BytesUtil, NumberUtil}
|
||||
import org.bitcoins.crypto.{Factory, NetworkElement}
|
||||
import scodec.bits.{ByteOrdering, ByteVector}
|
||||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
|
|
@ -10,9 +10,9 @@ import org.bitcoins.core.protocol.blockchain.{Block, BlockHeader, MerkleBlock}
|
|||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||
import org.bitcoins.core.serializers.p2p.messages._
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.core.wallet.fee.{SatoshisPerByte, SatoshisPerKiloByte}
|
||||
import org.bitcoins.crypto.{
|
||||
BytesUtil,
|
||||
DoubleSha256Digest,
|
||||
Factory,
|
||||
HashDigest,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package org.bitcoins.core.protocol
|
||||
|
||||
import org.bitcoins.core.config.{MainNet, TestNet3, _}
|
||||
import org.bitcoins.core.number.{UInt5, UInt8}
|
||||
import org.bitcoins.core.protocol.script._
|
||||
import org.bitcoins.core.script.constant.ScriptConstant
|
||||
import org.bitcoins.core.util._
|
||||
import org.bitcoins.crypto.{
|
||||
BytesUtil,
|
||||
CryptoUtil,
|
||||
ECPublicKey,
|
||||
HashDigest,
|
||||
|
|
|
@ -2,7 +2,8 @@ package org.bitcoins.core.protocol
|
|||
|
||||
import org.bitcoins.core.number.{UInt32, UInt64}
|
||||
import org.bitcoins.core.protocol.script.ScriptSignature
|
||||
import org.bitcoins.crypto.{BytesUtil, Factory, NetworkElement}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.{Factory, NetworkElement}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,7 +9,8 @@ import org.bitcoins.core.protocol.ln.fee.{
|
|||
FeeBaseMSat,
|
||||
FeeProportionalMillionths
|
||||
}
|
||||
import org.bitcoins.crypto.{BytesUtil, ECPublicKey, NetworkElement}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.{ECPublicKey, NetworkElement}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,8 +2,8 @@ package org.bitcoins.core.protocol.script
|
|||
|
||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||
import org.bitcoins.core.script.constant.ScriptToken
|
||||
import org.bitcoins.core.util.BitcoinScriptUtil
|
||||
import org.bitcoins.crypto.{BytesUtil, Factory}
|
||||
import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil}
|
||||
import org.bitcoins.crypto.Factory
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,6 @@ import org.bitcoins.core.script.reserved.UndefinedOP_NOP
|
|||
import org.bitcoins.core.script.stack.{OP_DROP, OP_DUP}
|
||||
import org.bitcoins.core.util._
|
||||
import org.bitcoins.crypto.{
|
||||
BytesUtil,
|
||||
CryptoUtil,
|
||||
DoubleSha256Digest,
|
||||
ECPublicKey,
|
||||
|
|
|
@ -4,7 +4,7 @@ import org.bitcoins.core.script.constant._
|
|||
import org.bitcoins.core.serializers.script.ScriptParser
|
||||
import org.bitcoins.core.util._
|
||||
import org.bitcoins.core.wallet.utxo.ConditionalPath
|
||||
import org.bitcoins.crypto.{BytesUtil, ECDigitalSignature, ECPublicKey}
|
||||
import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey}
|
||||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
|
@ -125,7 +125,8 @@ object P2PKHScriptSignature extends ScriptFactory[P2PKHScriptSignature] {
|
|||
_: ScriptConstant,
|
||||
_: BytesToPushOntoStack,
|
||||
z: ScriptConstant) =>
|
||||
if ((z.bytes.length == 33 || z.bytes.length == 65) && ECPublicKey.isFullyValid(z.bytes)) true
|
||||
if ((z.bytes.length == 33 || z.bytes.length == 65) && ECPublicKey
|
||||
.isFullyValid(z.bytes)) true
|
||||
else !P2SHScriptSignature.isRedeemScript(z)
|
||||
case _ => false
|
||||
}
|
||||
|
|
|
@ -2,9 +2,8 @@ package org.bitcoins.core.protocol.script
|
|||
|
||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||
import org.bitcoins.core.serializers.script.RawScriptWitnessParser
|
||||
import org.bitcoins.core.util.BitcoinScriptUtil
|
||||
import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil}
|
||||
import org.bitcoins.crypto.{
|
||||
BytesUtil,
|
||||
ECDigitalSignature,
|
||||
ECPublicKey,
|
||||
EmptyDigitalSignature,
|
||||
|
|
|
@ -3,13 +3,8 @@ package org.bitcoins.core.protocol.script
|
|||
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||
import org.bitcoins.core.script.constant._
|
||||
import org.bitcoins.core.script.result._
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.crypto.{
|
||||
BytesUtil,
|
||||
CryptoUtil,
|
||||
Sha256Digest,
|
||||
Sha256Hash160Digest
|
||||
}
|
||||
import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil}
|
||||
import org.bitcoins.crypto.{CryptoUtil, Sha256Digest, Sha256Hash160Digest}
|
||||
|
||||
/**
|
||||
* Created by chris on 11/10/16.
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
package org.bitcoins.core.protocol.transaction
|
||||
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.script.{
|
||||
CLTVScriptPubKey,
|
||||
CSVScriptPubKey,
|
||||
EmptyScriptSignature
|
||||
}
|
||||
import org.bitcoins.core.script.constant.ScriptNumber
|
||||
import org.bitcoins.core.script.locktime.LockTimeInterpreter
|
||||
import org.bitcoins.core.wallet.utxo.{
|
||||
ConditionalInputInfo,
|
||||
EmptyInputInfo,
|
||||
InputInfo,
|
||||
InputSigningInfo,
|
||||
LockTimeInputInfo,
|
||||
MultiSignatureInputInfo,
|
||||
P2PKHInputInfo,
|
||||
P2PKInputInfo,
|
||||
P2PKWithTimeoutInputInfo,
|
||||
P2SHInputInfo,
|
||||
P2WPKHV0InputInfo,
|
||||
P2WSHV0InputInfo,
|
||||
UnassignedSegwitNativeInputInfo
|
||||
}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
object InputUtil {
|
||||
|
||||
/**
|
||||
* Returns a valid sequence number for the given [[ScriptNumber]]
|
||||
* A transaction needs a valid sequence number to spend a OP_CHECKSEQUENCEVERIFY script.
|
||||
* See BIP68/112 for more information
|
||||
* [[https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki]]
|
||||
* [[https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki]]
|
||||
*/
|
||||
private def solveSequenceForCSV(scriptNum: ScriptNumber): UInt32 =
|
||||
if (LockTimeInterpreter.isCSVLockByBlockHeight(scriptNum)) {
|
||||
val blocksPassed = scriptNum.toLong & TransactionConstants.sequenceLockTimeMask.toLong
|
||||
UInt32(blocksPassed)
|
||||
} else {
|
||||
val n = scriptNum.toLong
|
||||
val sequence = UInt32(
|
||||
n & TransactionConstants.sequenceLockTimeMask.toLong)
|
||||
//set sequence number to indicate this is relative locktime
|
||||
sequence | TransactionConstants.sequenceLockTimeTypeFlag
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper function calculates the appropriate sequence number for each transaction input.
|
||||
* [[CLTVScriptPubKey]] and [[CSVScriptPubKey]]'s need certain sequence numbers on the inputs
|
||||
* to make them spendable.
|
||||
* See BIP68/112 and BIP65 for more info
|
||||
*/
|
||||
def calcSequenceForInputs(
|
||||
utxos: Seq[InputSigningInfo[InputInfo]],
|
||||
isRBFEnabled: Boolean): Seq[TransactionInput] = {
|
||||
@tailrec
|
||||
def loop(
|
||||
remaining: Seq[InputSigningInfo[InputInfo]],
|
||||
accum: Seq[TransactionInput]): Seq[TransactionInput] =
|
||||
remaining match {
|
||||
case Nil => accum.reverse
|
||||
case spendingInfo +: newRemaining =>
|
||||
spendingInfo.inputInfo match {
|
||||
case lockTime: LockTimeInputInfo =>
|
||||
val sequence = lockTime.scriptPubKey match {
|
||||
case csv: CSVScriptPubKey => solveSequenceForCSV(csv.locktime)
|
||||
case _: CLTVScriptPubKey => UInt32.zero
|
||||
}
|
||||
val input = TransactionInput(lockTime.outPoint,
|
||||
EmptyScriptSignature,
|
||||
sequence)
|
||||
loop(newRemaining, input +: accum)
|
||||
case p2pkWithTimeout: P2PKWithTimeoutInputInfo =>
|
||||
if (p2pkWithTimeout.isBeforeTimeout) {
|
||||
val sequence =
|
||||
if (isRBFEnabled) UInt32.zero
|
||||
else TransactionConstants.sequence
|
||||
val input =
|
||||
TransactionInput(spendingInfo.outPoint,
|
||||
EmptyScriptSignature,
|
||||
sequence)
|
||||
loop(newRemaining, input +: accum)
|
||||
} else {
|
||||
val input = TransactionInput(p2pkWithTimeout.outPoint,
|
||||
EmptyScriptSignature,
|
||||
UInt32.zero)
|
||||
loop(newRemaining, input +: accum)
|
||||
}
|
||||
case p2sh: P2SHInputInfo =>
|
||||
val nestedSpendingInfo =
|
||||
p2sh.nestedInputInfo.genericWithSignFrom(spendingInfo)
|
||||
loop(nestedSpendingInfo +: newRemaining, accum)
|
||||
case p2wsh: P2WSHV0InputInfo =>
|
||||
val nestedSpendingInfo =
|
||||
p2wsh.nestedInputInfo.genericWithSignFrom(spendingInfo)
|
||||
loop(nestedSpendingInfo +: newRemaining, accum)
|
||||
case conditional: ConditionalInputInfo =>
|
||||
val nestedSpendingInfo =
|
||||
conditional.nestedInputInfo.genericWithSignFrom(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
|
||||
val sequence =
|
||||
if (isRBFEnabled) UInt32.zero else TransactionConstants.sequence
|
||||
val input =
|
||||
TransactionInput(spendingInfo.outPoint,
|
||||
EmptyScriptSignature,
|
||||
sequence)
|
||||
loop(newRemaining, input +: accum)
|
||||
}
|
||||
}
|
||||
|
||||
loop(utxos, Nil)
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
package org.bitcoins.core.protocol.transaction
|
||||
|
||||
import org.bitcoins.core.number.{Int32, UInt32}
|
||||
import org.bitcoins.core.protocol.script.ScriptWitness
|
||||
import org.bitcoins.core.serializers.transaction.{RawBaseTransactionParser, RawWitnessTransactionParser}
|
||||
import org.bitcoins.core.protocol.script.{EmptyScriptWitness, ScriptWitness}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.core.wallet.builder.RawTxBuilder
|
||||
import org.bitcoins.crypto._
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
|
@ -64,7 +65,7 @@ sealed abstract class Transaction extends NetworkElement {
|
|||
* [[https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#Transaction_size_calculations]]
|
||||
*/
|
||||
def baseSize: Long = this match {
|
||||
case btx: BaseTransaction => btx.byteSize
|
||||
case btx: NonWitnessTransaction => btx.byteSize
|
||||
case wtx: WitnessTransaction =>
|
||||
BaseTransaction(wtx.version, wtx.inputs, wtx.outputs, wtx.lockTime).baseSize
|
||||
}
|
||||
|
@ -85,7 +86,7 @@ sealed abstract class Transaction extends NetworkElement {
|
|||
def updateInput(idx: Int, i: TransactionInput): Transaction = {
|
||||
val updatedInputs = inputs.updated(idx, i)
|
||||
this match {
|
||||
case _: BaseTransaction =>
|
||||
case _: NonWitnessTransaction =>
|
||||
BaseTransaction(version, updatedInputs, outputs, lockTime)
|
||||
case wtx: WitnessTransaction =>
|
||||
WitnessTransaction(version,
|
||||
|
@ -97,39 +98,101 @@ sealed abstract class Transaction extends NetworkElement {
|
|||
}
|
||||
}
|
||||
|
||||
sealed abstract class BaseTransaction extends Transaction {
|
||||
override def bytes = RawBaseTransactionParser.write(this)
|
||||
override def weight = byteSize * 4
|
||||
object Transaction extends Factory[Transaction] {
|
||||
def newBuilder: RawTxBuilder = RawTxBuilder()
|
||||
|
||||
override def fromBytes(bytes: ByteVector): Transaction = {
|
||||
//see BIP141 for marker/flag bytes
|
||||
//https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#transaction-id
|
||||
if (bytes(4) == WitnessTransaction.marker && bytes(5) == WitnessTransaction.flag) {
|
||||
//this throw/catch is _still_ necessary for the case where we have unsigned base transactions
|
||||
//with zero inputs and 1 output which is serialized as "0001" at bytes 4 and 5.
|
||||
//these transactions will not have a script witness associated with them making them invalid
|
||||
//witness transactions (you need to have a witness to be considered a witness tx)
|
||||
//see: https://github.com/bitcoin-s/bitcoin-s/blob/01d89df1b7c6bc4b1594406d54d5e6019705c654/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala#L88
|
||||
try {
|
||||
WitnessTransaction.fromBytes(bytes)
|
||||
} catch {
|
||||
case scala.util.control.NonFatal(_) =>
|
||||
BaseTransaction.fromBytes(bytes)
|
||||
}
|
||||
} else {
|
||||
BaseTransaction.fromBytes(bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case object EmptyTransaction extends BaseTransaction {
|
||||
override def txId = DoubleSha256Digest.empty
|
||||
override def version = TransactionConstants.version
|
||||
override def inputs = Nil
|
||||
override def outputs = Nil
|
||||
override def lockTime = TransactionConstants.lockTime
|
||||
sealed abstract class NonWitnessTransaction extends Transaction {
|
||||
override def weight: Long = byteSize * 4
|
||||
|
||||
override def bytes: ByteVector = {
|
||||
val versionBytes = version.bytes.reverse
|
||||
val inputBytes = BytesUtil.writeCmpctSizeUInt(inputs)
|
||||
val outputBytes = BytesUtil.writeCmpctSizeUInt(outputs)
|
||||
val lockTimeBytes = lockTime.bytes.reverse
|
||||
|
||||
versionBytes ++ inputBytes ++ outputBytes ++ lockTimeBytes
|
||||
}
|
||||
}
|
||||
|
||||
sealed abstract class WitnessTransaction extends Transaction {
|
||||
case class BaseTransaction(
|
||||
version: Int32,
|
||||
inputs: Seq[TransactionInput],
|
||||
outputs: Seq[TransactionOutput],
|
||||
lockTime: UInt32)
|
||||
extends NonWitnessTransaction
|
||||
|
||||
object BaseTransaction extends Factory[BaseTransaction] {
|
||||
override def fromBytes(bytes: ByteVector): BaseTransaction = {
|
||||
val versionBytes = bytes.take(4)
|
||||
val version = Int32(versionBytes.reverse)
|
||||
val txInputBytes = bytes.slice(4, bytes.size)
|
||||
val (inputs, outputBytes) =
|
||||
BytesUtil.parseCmpctSizeUIntSeq(txInputBytes, TransactionInput)
|
||||
val (outputs, lockTimeBytes) =
|
||||
BytesUtil.parseCmpctSizeUIntSeq(outputBytes, TransactionOutput)
|
||||
val lockTime = UInt32(lockTimeBytes.take(4).reverse)
|
||||
|
||||
BaseTransaction(version, inputs, outputs, lockTime)
|
||||
}
|
||||
|
||||
def unapply(tx: NonWitnessTransaction): Option[
|
||||
(Int32, Seq[TransactionInput], Seq[TransactionOutput], UInt32)] = {
|
||||
Some(tx.version, tx.inputs, tx.outputs, tx.lockTime)
|
||||
}
|
||||
}
|
||||
|
||||
case object EmptyTransaction extends NonWitnessTransaction {
|
||||
override def txId: DoubleSha256Digest = DoubleSha256Digest.empty
|
||||
override def version: Int32 = TransactionConstants.version
|
||||
override def inputs: Vector[TransactionInput] = Vector.empty
|
||||
override def outputs: Vector[TransactionOutput] = Vector.empty
|
||||
override def lockTime: UInt32 = TransactionConstants.lockTime
|
||||
|
||||
def toBaseTx: BaseTransaction =
|
||||
BaseTransaction(version, inputs, outputs, lockTime)
|
||||
}
|
||||
|
||||
case class WitnessTransaction(
|
||||
version: Int32,
|
||||
inputs: Seq[TransactionInput],
|
||||
outputs: Seq[TransactionOutput],
|
||||
lockTime: UInt32,
|
||||
witness: TransactionWitness)
|
||||
extends Transaction {
|
||||
require(
|
||||
inputs.length == witness.length,
|
||||
s"Must have same amount of inputs and witnesses in witness tx, inputs=${inputs.length} witnesses=${witness.length}"
|
||||
)
|
||||
|
||||
/** The txId for the witness transaction from satoshi's original serialization */
|
||||
override def txId: DoubleSha256Digest = {
|
||||
val btx = BaseTransaction(version, inputs, outputs, lockTime)
|
||||
btx.txId
|
||||
def toBaseTx: BaseTransaction = {
|
||||
BaseTransaction(version, inputs, outputs, lockTime)
|
||||
}
|
||||
|
||||
/**
|
||||
* The witness used to evaluate
|
||||
* [[org.bitcoins.core.protocol.script.ScriptSignature ScriptSignature]]/
|
||||
* [[org.bitcoins.core.protocol.script.ScriptPubKey ScriptPubKey]]s inside of a SegWit tx.
|
||||
* [[https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki BIP141]]
|
||||
*/
|
||||
def witness: TransactionWitness
|
||||
/** The txId for the witness transaction from satoshi's original serialization */
|
||||
override def txId: DoubleSha256Digest = {
|
||||
toBaseTx.txId
|
||||
}
|
||||
|
||||
/**
|
||||
* The witness transaction id as defined by
|
||||
|
@ -145,10 +208,32 @@ sealed abstract class WitnessTransaction extends Transaction {
|
|||
* [[https://github.com/bitcoin/bitcoin/blob/5961b23898ee7c0af2626c46d5d70e80136578d3/src/consensus/validation.h#L96]]
|
||||
*/
|
||||
override def weight: Long = {
|
||||
val base = BaseTransaction(version, inputs, outputs, lockTime)
|
||||
base.byteSize * 3 + byteSize
|
||||
toBaseTx.byteSize * 3 + byteSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a [[org.bitcoins.core.protocol.transaction.WitnessTransaction WitnessTransaction]] to a hex string
|
||||
* This is unique from BaseTransaction.bytes in the fact
|
||||
* that it adds a 'marker' and 'flag' to indicate that this tx is a
|
||||
* [[org.bitcoins.core.protocol.transaction.WitnessTransaction WitnessTransaction]] and has extra
|
||||
* witness data attached to it.
|
||||
* See [[https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki BIP144]] for more info.
|
||||
* Functionality inside of Bitcoin Core:
|
||||
* [[https://github.com/bitcoin/bitcoin/blob/e8cfe1ee2d01c493b758a67ad14707dca15792ea/src/primitives/transaction.h#L282-L287s]]
|
||||
*/
|
||||
override def bytes: ByteVector = {
|
||||
val versionBytes = version.bytes.reverse
|
||||
val inputBytes = BytesUtil.writeCmpctSizeUInt(inputs)
|
||||
val outputBytes = BytesUtil.writeCmpctSizeUInt(outputs)
|
||||
val witnessBytes = witness.bytes
|
||||
val lockTimeBytes = lockTime.bytes.reverse
|
||||
// notice we use the old serialization format if all witnesses are empty
|
||||
// https://github.com/bitcoin/bitcoin/blob/e8cfe1ee2d01c493b758a67ad14707dca15792ea/src/primitives/transaction.h#L276-L281
|
||||
if (witness.exists(_ != EmptyScriptWitness)) {
|
||||
val witConstant = ByteVector(0.toByte, 1.toByte)
|
||||
versionBytes ++ witConstant ++ inputBytes ++ outputBytes ++ witnessBytes ++ lockTimeBytes
|
||||
} else toBaseTx.bytes
|
||||
}
|
||||
override def bytes: ByteVector = RawWitnessTransactionParser.write(this)
|
||||
|
||||
/**
|
||||
* Updates the [[org.bitcoins.core.protocol.script.ScriptWitness ScriptWitness]] at the given index and
|
||||
|
@ -159,73 +244,43 @@ sealed abstract class WitnessTransaction extends Transaction {
|
|||
val txWit = witness.updated(idx, scriptWit)
|
||||
WitnessTransaction(version, inputs, outputs, lockTime, txWit)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Transaction extends Factory[Transaction] {
|
||||
|
||||
override def fromBytes(bytes: ByteVector): Transaction = {
|
||||
//see BIP141 for marker/flag bytes
|
||||
//https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#transaction-id
|
||||
if (bytes(4) == WitnessTransaction.marker && bytes(5) == WitnessTransaction.flag) {
|
||||
//this throw/catch is _still_ necessary for the case where we have unsigned base transactions
|
||||
//with zero inputs and 1 output which is serialized as "0001" at bytes 4 and 5.
|
||||
//these transactions will not have a script witness associated with them making them invalid
|
||||
//witness transactions (you need to have a witness to be considered a witness tx)
|
||||
//see: https://github.com/bitcoin-s/bitcoin-s/blob/01d89df1b7c6bc4b1594406d54d5e6019705c654/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala#L88
|
||||
try {
|
||||
RawWitnessTransactionParser.read(bytes)
|
||||
} catch {
|
||||
case scala.util.control.NonFatal(_) =>
|
||||
RawBaseTransactionParser.read(bytes)
|
||||
}
|
||||
} else {
|
||||
RawBaseTransactionParser.read(bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object BaseTransaction extends Factory[BaseTransaction] {
|
||||
private case class BaseTransactionImpl(
|
||||
version: Int32,
|
||||
inputs: Seq[TransactionInput],
|
||||
outputs: Seq[TransactionOutput],
|
||||
lockTime: UInt32)
|
||||
extends BaseTransaction
|
||||
|
||||
override def fromBytes(bytes: ByteVector): BaseTransaction =
|
||||
RawBaseTransactionParser.read(bytes)
|
||||
|
||||
def apply(
|
||||
version: Int32,
|
||||
inputs: Seq[TransactionInput],
|
||||
outputs: Seq[TransactionOutput],
|
||||
lockTime: UInt32): BaseTransaction =
|
||||
BaseTransactionImpl(version, inputs, outputs, lockTime)
|
||||
}
|
||||
|
||||
object WitnessTransaction extends Factory[WitnessTransaction] {
|
||||
private case class WitnessTransactionImpl(
|
||||
version: Int32,
|
||||
inputs: Seq[TransactionInput],
|
||||
outputs: Seq[TransactionOutput],
|
||||
lockTime: UInt32,
|
||||
witness: TransactionWitness)
|
||||
extends WitnessTransaction
|
||||
|
||||
def apply(
|
||||
version: Int32,
|
||||
inputs: Seq[TransactionInput],
|
||||
outputs: Seq[TransactionOutput],
|
||||
lockTime: UInt32,
|
||||
witness: TransactionWitness): WitnessTransaction =
|
||||
WitnessTransactionImpl(version, inputs, outputs, lockTime, witness)
|
||||
/**
|
||||
* This read function is unique to BaseTransaction.fromBytes
|
||||
* in the fact that it reads a 'marker' and 'flag' byte to indicate that this tx is a
|
||||
* [[org.bitcoins.core.protocol.transaction.WitnessTransaction WitnessTransaction]].
|
||||
* See [[https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki BIP144 ]] for more details.
|
||||
* Functionality inside of Bitcoin Core:
|
||||
* [[https://github.com/bitcoin/bitcoin/blob/e8cfe1ee2d01c493b758a67ad14707dca15792ea/src/primitives/transaction.h#L244-L251]]
|
||||
*/
|
||||
override def fromBytes(bytes: ByteVector): WitnessTransaction = {
|
||||
val versionBytes = bytes.take(4)
|
||||
val version = Int32(versionBytes.reverse)
|
||||
val marker = bytes(4)
|
||||
require(
|
||||
marker.toInt == 0,
|
||||
"Incorrect marker for witness transaction, the marker MUST be 0 for the marker according to BIP141, got: " + marker)
|
||||
val flag = bytes(5)
|
||||
require(
|
||||
flag.toInt != 0,
|
||||
"Incorrect flag for witness transaction, this must NOT be 0 according to BIP141, got: " + flag)
|
||||
val txInputBytes = bytes.slice(6, bytes.size)
|
||||
val (inputs, outputBytes) =
|
||||
BytesUtil.parseCmpctSizeUIntSeq(txInputBytes, TransactionInput)
|
||||
val (outputs, witnessBytes) =
|
||||
BytesUtil.parseCmpctSizeUIntSeq(outputBytes, TransactionOutput)
|
||||
val witness = TransactionWitness(witnessBytes, inputs.size)
|
||||
val lockTimeBytes = witnessBytes.drop(witness.byteSize)
|
||||
val lockTime = UInt32(lockTimeBytes.take(4).reverse)
|
||||
|
||||
override def fromBytes(bytes: ByteVector): WitnessTransaction =
|
||||
RawWitnessTransactionParser.read(bytes)
|
||||
WitnessTransaction(version, inputs, outputs, lockTime, witness)
|
||||
}
|
||||
|
||||
def toWitnessTx(tx: Transaction): WitnessTransaction = tx match {
|
||||
case btx: BaseTransaction =>
|
||||
case btx: NonWitnessTransaction =>
|
||||
WitnessTransaction(btx.version,
|
||||
btx.inputs,
|
||||
btx.outputs,
|
||||
|
|
|
@ -2,8 +2,8 @@ package org.bitcoins.core.protocol.transaction
|
|||
|
||||
import org.bitcoins.core.protocol.script.{EmptyScriptWitness, ScriptWitness}
|
||||
import org.bitcoins.core.serializers.transaction.RawTransactionWitnessParser
|
||||
import org.bitcoins.core.util.SeqWrapper
|
||||
import org.bitcoins.crypto.{BytesUtil, NetworkElement}
|
||||
import org.bitcoins.core.util.{BytesUtil, SeqWrapper}
|
||||
import org.bitcoins.crypto.NetworkElement
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,321 @@
|
|||
package org.bitcoins.core.protocol.transaction
|
||||
|
||||
import org.bitcoins.core.currency.{CurrencyUnit, CurrencyUnits, Satoshis}
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.policy.Policy
|
||||
import org.bitcoins.core.protocol.script.{
|
||||
CLTVScriptPubKey,
|
||||
CSVScriptPubKey,
|
||||
EmptyScriptSignature,
|
||||
EmptyScriptWitness,
|
||||
ScriptWitnessV0
|
||||
}
|
||||
import org.bitcoins.core.script.control.OP_RETURN
|
||||
import org.bitcoins.core.script.crypto.HashType
|
||||
import org.bitcoins.core.wallet.builder.RawTxSigner.logger
|
||||
import org.bitcoins.core.wallet.builder.TxBuilderError
|
||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||
import org.bitcoins.core.wallet.signer.BitcoinSigner
|
||||
import org.bitcoins.core.wallet.utxo.{
|
||||
ConditionalInputInfo,
|
||||
EmptyInputInfo,
|
||||
InputInfo,
|
||||
InputSigningInfo,
|
||||
LockTimeInputInfo,
|
||||
MultiSignatureInputInfo,
|
||||
P2PKHInputInfo,
|
||||
P2PKInputInfo,
|
||||
P2PKWithTimeoutInputInfo,
|
||||
P2SHInputInfo,
|
||||
P2WPKHV0InputInfo,
|
||||
P2WSHV0InputInfo,
|
||||
UnassignedSegwitNativeInputInfo
|
||||
}
|
||||
import org.bitcoins.crypto.{DummyECDigitalSignature, Sign}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.concurrent.{Await, ExecutionContext, Future}
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
object TxUtil {
|
||||
|
||||
/**
|
||||
* This helper function calculates the appropriate locktime for a transaction.
|
||||
* To be able to spend [[CLTVScriptPubKey]]'s you need to have the transaction's
|
||||
* locktime set to the same value (or higher) than the output it is spending.
|
||||
* See BIP65 for more info
|
||||
*/
|
||||
def calcLockTime(utxos: Seq[InputSigningInfo[InputInfo]]): Try[UInt32] = {
|
||||
def computeNextLockTime(
|
||||
currentLockTimeOpt: Option[UInt32],
|
||||
locktime: Long): Try[UInt32] = {
|
||||
val lockTimeT =
|
||||
if (locktime > UInt32.max.toLong || locktime < 0) {
|
||||
TxBuilderError.IncompatibleLockTimes
|
||||
} else Success(UInt32(locktime))
|
||||
lockTimeT.flatMap { lockTime: UInt32 =>
|
||||
currentLockTimeOpt match {
|
||||
case Some(currentLockTime) =>
|
||||
val lockTimeThreshold = TransactionConstants.locktimeThreshold
|
||||
if (currentLockTime < lockTime) {
|
||||
if (currentLockTime < lockTimeThreshold && lockTime >= lockTimeThreshold) {
|
||||
//means that we spend two different locktime types, one of the outputs spends a
|
||||
//OP_CLTV script by block height, the other spends one by time stamp
|
||||
TxBuilderError.IncompatibleLockTimes
|
||||
} else Success(lockTime)
|
||||
} else if (currentLockTime >= lockTimeThreshold && lockTime < lockTimeThreshold) {
|
||||
//means that we spend two different locktime types, one of the outputs spends a
|
||||
//OP_CLTV script by block height, the other spends one by time stamp
|
||||
TxBuilderError.IncompatibleLockTimes
|
||||
} else {
|
||||
Success(currentLockTime)
|
||||
}
|
||||
case None => Success(lockTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@tailrec
|
||||
def loop(
|
||||
remaining: Seq[InputSigningInfo[InputInfo]],
|
||||
currentLockTimeOpt: Option[UInt32]): Try[UInt32] =
|
||||
remaining match {
|
||||
case Nil =>
|
||||
Success(currentLockTimeOpt.getOrElse(TransactionConstants.lockTime))
|
||||
case spendingInfo +: newRemaining =>
|
||||
spendingInfo.inputInfo match {
|
||||
case lockTime: LockTimeInputInfo =>
|
||||
lockTime.scriptPubKey match {
|
||||
case _: CSVScriptPubKey =>
|
||||
loop(newRemaining, currentLockTimeOpt)
|
||||
case cltv: CLTVScriptPubKey =>
|
||||
val result = computeNextLockTime(currentLockTimeOpt,
|
||||
cltv.locktime.toLong)
|
||||
|
||||
result match {
|
||||
case Success(newLockTime) =>
|
||||
loop(newRemaining, Some(newLockTime))
|
||||
case _: Failure[UInt32] => result
|
||||
}
|
||||
}
|
||||
case p2pkWithTimeout: P2PKWithTimeoutInputInfo =>
|
||||
if (p2pkWithTimeout.isBeforeTimeout) {
|
||||
loop(newRemaining, currentLockTimeOpt)
|
||||
} else {
|
||||
val result = computeNextLockTime(
|
||||
currentLockTimeOpt,
|
||||
p2pkWithTimeout.scriptPubKey.lockTime.toLong)
|
||||
|
||||
result match {
|
||||
case Success(newLockTime) =>
|
||||
loop(newRemaining, Some(newLockTime))
|
||||
case _: Failure[UInt32] => result
|
||||
}
|
||||
}
|
||||
case p2sh: P2SHInputInfo =>
|
||||
val nestedSpendingInfo =
|
||||
p2sh.nestedInputInfo.genericWithSignFrom(spendingInfo)
|
||||
loop(nestedSpendingInfo +: newRemaining, currentLockTimeOpt)
|
||||
case p2wsh: P2WSHV0InputInfo =>
|
||||
val nestedSpendingInfo =
|
||||
p2wsh.nestedInputInfo.genericWithSignFrom(spendingInfo)
|
||||
loop(nestedSpendingInfo +: newRemaining, currentLockTimeOpt)
|
||||
case conditional: ConditionalInputInfo =>
|
||||
val nestedSpendingInfo =
|
||||
conditional.nestedInputInfo.genericWithSignFrom(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)
|
||||
}
|
||||
}
|
||||
|
||||
loop(utxos, None)
|
||||
}
|
||||
|
||||
/** Inserts script signatures and (potentially) witness data to a given
|
||||
* transaction using DummyECDigitalSignatures for all sigs in order
|
||||
* to produce a transaction roughly the size of the expected fully signed
|
||||
* transaction. Useful during fee estimation.
|
||||
*
|
||||
* Note that the resulting dummy-signed Transaction will have populated
|
||||
* (dummy) witness data when applicable.
|
||||
*/
|
||||
def addDummySigs(utx: Transaction, inputInfos: Vector[InputInfo])(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
val dummyInputAndWitnnessFs = inputInfos.zipWithIndex.map {
|
||||
case (inputInfo, index) =>
|
||||
val mockSigners = inputInfo.pubKeys.take(inputInfo.requiredSigs).map {
|
||||
pubKey =>
|
||||
Sign(_ => Future.successful(DummyECDigitalSignature), pubKey)
|
||||
}
|
||||
|
||||
val mockSpendingInfo =
|
||||
inputInfo.toSpendingInfo(mockSigners, HashType.sigHashAll)
|
||||
|
||||
BitcoinSigner
|
||||
.sign(mockSpendingInfo, utx, isDummySignature = true)
|
||||
.map(_.transaction)
|
||||
.map { tx =>
|
||||
val witnessOpt = tx match {
|
||||
case _: NonWitnessTransaction => None
|
||||
case wtx: WitnessTransaction =>
|
||||
wtx.witness.witnesses(index) match {
|
||||
case EmptyScriptWitness => None
|
||||
case wit: ScriptWitnessV0 => Some(wit)
|
||||
}
|
||||
}
|
||||
|
||||
(tx.inputs(index), witnessOpt)
|
||||
}
|
||||
}
|
||||
|
||||
Future.sequence(dummyInputAndWitnnessFs).map { inputsAndWitnesses =>
|
||||
val inputs = inputsAndWitnesses.map(_._1)
|
||||
val txWitnesses = inputsAndWitnesses.map(_._2)
|
||||
TransactionWitness.fromWitOpt(txWitnesses) match {
|
||||
case _: EmptyWitness =>
|
||||
BaseTransaction(utx.version, inputs, utx.outputs, utx.lockTime)
|
||||
case wit: TransactionWitness =>
|
||||
WitnessTransaction(utx.version,
|
||||
inputs,
|
||||
utx.outputs,
|
||||
utx.lockTime,
|
||||
wit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ScriptSignature for every input in the given transaction to an EmptyScriptSignature
|
||||
* as well as sets the witness to an EmptyWitness
|
||||
* @param tx Transaction to empty signatures
|
||||
* @return Transaction with no signatures
|
||||
*/
|
||||
def emptyAllScriptSigs(tx: Transaction): Transaction = {
|
||||
val newInputs = tx.inputs.map { input =>
|
||||
TransactionInput(input.previousOutput,
|
||||
EmptyScriptSignature,
|
||||
input.sequence)
|
||||
}
|
||||
|
||||
tx match {
|
||||
case btx: NonWitnessTransaction =>
|
||||
BaseTransaction(version = btx.version,
|
||||
inputs = newInputs,
|
||||
outputs = btx.outputs,
|
||||
lockTime = btx.lockTime)
|
||||
case wtx: WitnessTransaction =>
|
||||
WitnessTransaction(version = wtx.version,
|
||||
inputs = newInputs,
|
||||
outputs = wtx.outputs,
|
||||
lockTime = wtx.lockTime,
|
||||
witness = EmptyWitness.fromInputs(newInputs))
|
||||
}
|
||||
}
|
||||
|
||||
/** Runs various sanity checks on a transaction */
|
||||
def sanityChecks(
|
||||
isSigned: Boolean,
|
||||
inputInfos: Vector[InputInfo],
|
||||
expectedFeeRate: FeeUnit,
|
||||
tx: Transaction): Try[Unit] = {
|
||||
val dustT = if (isSigned) {
|
||||
sanityDustCheck(tx)
|
||||
} else {
|
||||
Success(())
|
||||
}
|
||||
|
||||
dustT.flatMap { _ =>
|
||||
sanityAmountChecks(isSigned, inputInfos, expectedFeeRate, tx)
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks that no output is beneath the dust threshold */
|
||||
def sanityDustCheck(tx: Transaction): Try[Unit] = {
|
||||
val belowDustOutputs = tx.outputs
|
||||
.filterNot(_.scriptPubKey.asm.contains(OP_RETURN))
|
||||
.filter(_.value < Policy.dustThreshold)
|
||||
|
||||
if (belowDustOutputs.nonEmpty) {
|
||||
TxBuilderError.OutputBelowDustThreshold(belowDustOutputs)
|
||||
} else {
|
||||
Success(())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the creditingAmount >= destinationAmount
|
||||
* and then does a sanity check on the transaction's fee
|
||||
*/
|
||||
def sanityAmountChecks(
|
||||
isSigned: Boolean,
|
||||
inputInfos: Vector[InputInfo],
|
||||
expectedFeeRate: FeeUnit,
|
||||
tx: Transaction): Try[Unit] = {
|
||||
val spentAmount = tx.outputs.foldLeft(CurrencyUnits.zero)(_ + _.value)
|
||||
val creditingAmount =
|
||||
inputInfos.foldLeft(CurrencyUnits.zero)(_ + _.amount)
|
||||
if (spentAmount > creditingAmount) {
|
||||
TxBuilderError.MintsMoney
|
||||
} else {
|
||||
val expectedTx = if (isSigned) {
|
||||
tx
|
||||
} else {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration.DurationInt
|
||||
|
||||
Await.result(TxUtil.addDummySigs(tx, inputInfos), 5.seconds)
|
||||
}
|
||||
|
||||
val actualFee = creditingAmount - spentAmount
|
||||
val estimatedFee = expectedFeeRate * expectedTx
|
||||
isValidFeeRange(estimatedFee, actualFee, expectedFeeRate)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the fee is within a 'valid' range
|
||||
* @param estimatedFee the estimated amount of fee we should pay
|
||||
* @param actualFee the actual amount of fee the transaction pays
|
||||
* @param feeRate the fee rate in satoshis/vbyte we paid per byte on this tx
|
||||
* @return
|
||||
*/
|
||||
def isValidFeeRange(
|
||||
estimatedFee: CurrencyUnit,
|
||||
actualFee: CurrencyUnit,
|
||||
feeRate: FeeUnit): Try[Unit] = {
|
||||
|
||||
//what the number '40' represents is the allowed variance -- in bytes -- between the size of the two
|
||||
//versions of signed tx. I believe the two signed version can vary in size because the digital
|
||||
//signature might have changed in size. It could become larger or smaller depending on the digital
|
||||
//signatures produced.
|
||||
|
||||
//Personally I think 40 seems like a little high. As you shouldn't vary more than a 2 bytes per input in the tx i think?
|
||||
//bumping for now though as I don't want to spend time debugging
|
||||
//I think there is something incorrect that errors to the low side of fee estimation
|
||||
//for p2sh(p2wpkh) txs
|
||||
|
||||
//See this link for more info on variance in size on ECDigitalSignatures
|
||||
//https://en.bitcoin.it/wiki/Elliptic_Curve_Digital_Signature_Algorithm
|
||||
|
||||
val acceptableVariance = 40 * feeRate.toLong
|
||||
val min = Satoshis(-acceptableVariance)
|
||||
val max = Satoshis(acceptableVariance)
|
||||
val difference = estimatedFee - actualFee
|
||||
if (difference <= min) {
|
||||
logger.error(
|
||||
s"Fee was too high. Estimated fee $estimatedFee, actualFee $actualFee, difference $difference, acceptableVariance $acceptableVariance")
|
||||
TxBuilderError.HighFee
|
||||
} else if (difference >= max) {
|
||||
logger.error(
|
||||
s"Fee was too low. Estimated fee $estimatedFee, actualFee $actualFee, difference $difference, acceptableVariance $acceptableVariance")
|
||||
|
||||
TxBuilderError.LowFee
|
||||
} else {
|
||||
Success(())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,6 @@ import org.bitcoins.core.script.crypto.HashType
|
|||
import org.bitcoins.core.script.interpreter.ScriptInterpreter
|
||||
import org.bitcoins.core.script.result.ScriptOk
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.core.wallet.builder.BitcoinTxBuilder
|
||||
import org.bitcoins.core.wallet.signer.BitcoinSigner
|
||||
import org.bitcoins.core.wallet.utxo._
|
||||
import org.bitcoins.crypto.{
|
||||
|
@ -582,7 +581,7 @@ case class PSBT(
|
|||
new RuntimeException(
|
||||
s"Input $index was invalid: $inputResult"))
|
||||
}
|
||||
case (Some(_), _: BaseTransaction) =>
|
||||
case (Some(_), _: NonWitnessTransaction) =>
|
||||
Failure(new RuntimeException(
|
||||
s"Extracted program is not witness transaction, but input $index has WitnessUTXO record"))
|
||||
case (None, _) =>
|
||||
|
@ -647,7 +646,7 @@ case class PSBT(
|
|||
witness)
|
||||
} else {
|
||||
transaction match {
|
||||
case btx: BaseTransaction =>
|
||||
case btx: NonWitnessTransaction =>
|
||||
BaseTransaction(btx.version, newInputs, btx.outputs, btx.lockTime)
|
||||
case wtx: WitnessTransaction =>
|
||||
WitnessTransaction(wtx.version,
|
||||
|
@ -761,7 +760,7 @@ object PSBT extends Factory[PSBT] {
|
|||
val btx = unsignedTx match {
|
||||
case wtx: WitnessTransaction =>
|
||||
BaseTransaction(wtx.version, wtx.inputs, wtx.outputs, wtx.lockTime)
|
||||
case base: BaseTransaction => base
|
||||
case base: NonWitnessTransaction => base
|
||||
}
|
||||
val globalMap = GlobalPSBTMap(
|
||||
Vector(GlobalPSBTRecord.UnsignedTransaction(btx)))
|
||||
|
@ -829,11 +828,11 @@ object PSBT extends Factory[PSBT] {
|
|||
spendingInfoAndNonWitnessTxs.matchesInputs(unsignedTx.inputs),
|
||||
"NewSpendingInfos must correspond to transaction inputs"
|
||||
)
|
||||
val emptySigTx = BitcoinTxBuilder.emptyAllScriptSigs(unsignedTx)
|
||||
val emptySigTx = TxUtil.emptyAllScriptSigs(unsignedTx)
|
||||
val btx = emptySigTx match {
|
||||
case wtx: WitnessTransaction =>
|
||||
BaseTransaction(wtx.version, wtx.inputs, wtx.outputs, wtx.lockTime)
|
||||
case base: BaseTransaction => base
|
||||
case base: NonWitnessTransaction => base
|
||||
}
|
||||
|
||||
val globalMap = GlobalPSBTMap(
|
||||
|
|
|
@ -4,7 +4,7 @@ import org.bitcoins.core.byteVectorOrdering
|
|||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.script._
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
BaseTransaction,
|
||||
NonWitnessTransaction,
|
||||
Transaction,
|
||||
TransactionInput,
|
||||
TransactionOutput,
|
||||
|
@ -657,7 +657,8 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
|
|||
val scriptSig =
|
||||
FinalizedScriptSig(sigComponent.scriptSignature)
|
||||
sigComponent.transaction match {
|
||||
case _: BaseTransaction => InputPSBTMap(utxos ++ Vector(scriptSig))
|
||||
case _: NonWitnessTransaction =>
|
||||
InputPSBTMap(utxos ++ Vector(scriptSig))
|
||||
case wtx: WitnessTransaction =>
|
||||
val witness = wtx.witness(sigComponent.inputIndex.toInt)
|
||||
val scriptWitness = FinalizedScriptWitness(witness)
|
||||
|
|
|
@ -7,6 +7,7 @@ import org.bitcoins.core.protocol.CompactSizeUInt
|
|||
import org.bitcoins.core.protocol.script._
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
BaseTransaction,
|
||||
NonWitnessTransaction,
|
||||
Transaction,
|
||||
TransactionOutput
|
||||
}
|
||||
|
@ -62,7 +63,7 @@ sealed trait GlobalPSBTRecord extends PSBTRecord {
|
|||
|
||||
object GlobalPSBTRecord extends Factory[GlobalPSBTRecord] {
|
||||
import org.bitcoins.core.psbt.PSBTGlobalKeyId._
|
||||
case class UnsignedTransaction(transaction: BaseTransaction)
|
||||
case class UnsignedTransaction(transaction: NonWitnessTransaction)
|
||||
extends GlobalPSBTRecord {
|
||||
require(
|
||||
transaction.inputs.forall(_.scriptSignature == EmptyScriptSignature),
|
||||
|
|
|
@ -9,8 +9,7 @@ import org.bitcoins.core.script.locktime.LocktimeOperation
|
|||
import org.bitcoins.core.script.reserved.ReservedOperation
|
||||
import org.bitcoins.core.script.splice.SpliceOperation
|
||||
import org.bitcoins.core.script.stack.StackOperation
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,8 +6,7 @@ import org.bitcoins.core.script.{
|
|||
}
|
||||
import org.bitcoins.core.script.flag.ScriptFlagUtil
|
||||
import org.bitcoins.core.script.result._
|
||||
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil}
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil, BytesUtil}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ package org.bitcoins.core.script.constant
|
|||
|
||||
import org.bitcoins.core.number.Int64
|
||||
import org.bitcoins.core.script.ScriptOperationFactory
|
||||
import org.bitcoins.core.util.BitcoinScriptUtil
|
||||
import org.bitcoins.crypto.{BytesUtil, Factory, NetworkElement}
|
||||
import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil}
|
||||
import org.bitcoins.crypto.{Factory, NetworkElement}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.util.{Failure, Try}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package org.bitcoins.core.script.constant
|
||||
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,8 +24,7 @@ import org.bitcoins.core.script.reserved._
|
|||
import org.bitcoins.core.script.result._
|
||||
import org.bitcoins.core.script.splice._
|
||||
import org.bitcoins.core.script.stack._
|
||||
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil}
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil, BytesUtil}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
@ -1160,7 +1159,7 @@ sealed abstract class ScriptInterpreter extends BitcoinSLogger {
|
|||
b.transaction match {
|
||||
case wtx: WitnessTransaction =>
|
||||
wtx.witness(txSigComponent.inputIndex.toInt).stack.nonEmpty
|
||||
case _: BaseTransaction => false
|
||||
case _: NonWitnessTransaction => false
|
||||
}
|
||||
case r: WitnessTxSigComponentRebuilt =>
|
||||
r.transaction.witness(txSigComponent.inputIndex.toInt).stack.nonEmpty
|
||||
|
|
|
@ -39,5 +39,6 @@ case object OP_CHECKSEQUENCEVERIFY extends LocktimeOperation {
|
|||
}
|
||||
|
||||
object LocktimeOperation extends ScriptOperationFactory[LocktimeOperation] {
|
||||
override val operations = Vector(OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY)
|
||||
override val operations =
|
||||
Vector(OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY)
|
||||
}
|
||||
|
|
|
@ -29,5 +29,6 @@ case object OP_SIZE extends SpliceOperation {
|
|||
}
|
||||
|
||||
object SpliceOperation extends ScriptOperationFactory[SpliceOperation] {
|
||||
override val operations = Vector(OP_CAT, OP_LEFT, OP_RIGHT, OP_SIZE, OP_SUBSTR)
|
||||
override val operations =
|
||||
Vector(OP_CAT, OP_LEFT, OP_RIGHT, OP_SIZE, OP_SUBSTR)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.bitcoins.core.serializers
|
||||
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,7 +4,8 @@ import org.bitcoins.core.number.{UInt32, UInt64}
|
|||
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||
import org.bitcoins.core.protocol.blockchain.MerkleBlock
|
||||
import org.bitcoins.core.serializers.RawBitcoinSerializer
|
||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.DoubleSha256Digest
|
||||
import scodec.bits.{BitVector, ByteVector}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
|
|
@ -3,7 +3,8 @@ package org.bitcoins.core.serializers.script
|
|||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.script._
|
||||
import org.bitcoins.core.script.constant._
|
||||
import org.bitcoins.crypto.{BytesUtil, Factory}
|
||||
import org.bitcoins.core.util.BytesUtil
|
||||
import org.bitcoins.crypto.Factory
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.bitcoins.core.util
|
|||
|
||||
import org.bitcoins.core.crypto.ECPrivateKeyUtil
|
||||
import org.bitcoins.core.protocol.blockchain._
|
||||
import org.bitcoins.crypto.{BytesUtil, CryptoUtil}
|
||||
import org.bitcoins.crypto.CryptoUtil
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.bitcoins.core.script.result.{
|
|||
}
|
||||
import org.bitcoins.core.script.ExecutionInProgressScriptProgram
|
||||
import org.bitcoins.core.serializers.script.ScriptParser
|
||||
import org.bitcoins.crypto.{BytesUtil, ECDigitalSignature, ECPublicKey}
|
||||
import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
|
53
core/src/main/scala/org/bitcoins/core/util/BytesUtil.scala
Normal file
53
core/src/main/scala/org/bitcoins/core/util/BytesUtil.scala
Normal file
|
@ -0,0 +1,53 @@
|
|||
package org.bitcoins.core.util
|
||||
|
||||
import org.bitcoins.core.number.UInt64
|
||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||
import org.bitcoins.crypto.{CryptoBytesUtil, Factory, NetworkElement}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
trait BytesUtil extends CryptoBytesUtil {
|
||||
|
||||
def writeCmpctSizeUInt[T <: NetworkElement](ts: Seq[T]): ByteVector = {
|
||||
val serialized = ts.map(_.bytes).foldLeft(ByteVector.empty)(_ ++ _)
|
||||
val cmpct = CompactSizeUInt(UInt64(ts.size))
|
||||
cmpct.bytes ++ serialized
|
||||
}
|
||||
|
||||
/**
|
||||
* Used parse a byte sequence to a Seq[TransactionInput], Seq[TransactionOutput], etc
|
||||
* Makes sure that we parse the correct amount of elements
|
||||
*/
|
||||
def parseCmpctSizeUIntSeq[T <: NetworkElement](
|
||||
bytes: ByteVector,
|
||||
factory: Factory[T]): (Vector[T], ByteVector) = {
|
||||
val count = CompactSizeUInt.parse(bytes)
|
||||
val payload = bytes.drop(count.byteSize.toInt)
|
||||
val builder = Vector.newBuilder[T]
|
||||
|
||||
@tailrec
|
||||
def loop(remaining: ByteVector, counter: Int = 0): ByteVector = {
|
||||
if (counter == count.num.toInt) {
|
||||
remaining
|
||||
} else {
|
||||
val parsed = factory.fromBytes(remaining)
|
||||
val newRemaining = remaining.drop(parsed.byteSize)
|
||||
|
||||
builder.+=(parsed)
|
||||
|
||||
loop(newRemaining, counter + 1)
|
||||
}
|
||||
}
|
||||
|
||||
val remaining = loop(payload)
|
||||
val result = builder.result()
|
||||
require(
|
||||
result.size == count.num.toInt,
|
||||
s"Could not parse the amount of required elements, got: ${result.size} required: ${count}")
|
||||
|
||||
(result, remaining)
|
||||
}
|
||||
}
|
||||
|
||||
object BytesUtil extends BytesUtil
|
|
@ -5,7 +5,6 @@ import java.math.BigInteger
|
|||
import org.bitcoins.core.number._
|
||||
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
||||
import org.bitcoins.core.protocol.blockchain.BlockHeader.TargetDifficultyHelper
|
||||
import org.bitcoins.crypto.BytesUtil
|
||||
import scodec.bits.{BitVector, ByteVector}
|
||||
|
||||
import scala.math.BigInt
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package org.bitcoins.core.wallet.builder
|
||||
|
||||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||
import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams}
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
/** Contains a finalized tx (output from [[RawTxFinalizer.buildTx]]) and the
|
||||
* ScriptSignatureParams needed to sign that transaction. */
|
||||
case class FinalizedTxWithSigningInfo(
|
||||
finalizedTx: Transaction,
|
||||
infos: Vector[ScriptSignatureParams[InputInfo]]) {
|
||||
|
||||
def sign(expectedFeeRate: FeeUnit)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
RawTxSigner.sign(this, expectedFeeRate)
|
||||
}
|
||||
|
||||
def sign(
|
||||
expectedFeeRate: FeeUnit,
|
||||
invariants: (
|
||||
Vector[ScriptSignatureParams[InputInfo]],
|
||||
Transaction) => Boolean)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
RawTxSigner.sign(this, expectedFeeRate, invariants)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
package org.bitcoins.core.wallet.builder
|
||||
|
||||
import org.bitcoins.core.number.{Int32, UInt32}
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
/** The mutable transaction builder which collects:
|
||||
* - Unsigned inputs (script signature will be ignored)
|
||||
* - Destination outputs (change is dealt with in the finalizer)
|
||||
* - A version number (default is [[TransactionConstants.validLockVersion]])
|
||||
* - A lock time (default is [[TransactionConstants.lockTime]])
|
||||
*
|
||||
* At a high level, RawTxBuilder is responsible only for the funding inputs
|
||||
* and logical outputs (outputs that are intended by this transaction, and not
|
||||
* outputs computed from the logical outputs such as change outputs, which are
|
||||
* the responsibility of the RawTxFinalizer) of a transaction.
|
||||
*
|
||||
* RawTxBuilder supports inline calls to += and ++= for adding inputs
|
||||
* and outputs. Note that RawTxBuilder respects the order in which inputs
|
||||
* and outputs are added when generating a RawTxBuilderResult. If you wish
|
||||
* to have some other (computed) order, this is the responsibility of either
|
||||
* the calling code (to add in the correct order) or of the RawTxFinalizer which
|
||||
* may alter the order of inputs and outputs (and should only do so if it is
|
||||
* clearly documented that this will occur, otherwise it is safe to assume that
|
||||
* RawTxFinalizers will not alter the order of inputs and outputs).
|
||||
*
|
||||
* Once a transaction is done being built and is ready to be passed to a
|
||||
* RawTransactionFinalizer, call the result method to receive a
|
||||
* [[RawTxBuilderResult]] which can be passed into [[RawTxFinalizer.buildTx]].
|
||||
*
|
||||
* If you have access to a finalizer before you are ready to call result,
|
||||
* you may call the setFinalizer method to receive an instance of type
|
||||
* [[RawTxBuilderWithFinalizer]] which is described below, and where
|
||||
* you may continue to build and then call buildTx directly.
|
||||
*
|
||||
* Note: RawTxBuilder is not thread safe.
|
||||
*/
|
||||
case class RawTxBuilder() {
|
||||
private var version: Int32 = TransactionConstants.validLockVersion
|
||||
|
||||
private val inputsBuilder: mutable.Builder[
|
||||
TransactionInput,
|
||||
Vector[TransactionInput]] = Vector.newBuilder
|
||||
|
||||
private val outputsBuilder: mutable.Builder[
|
||||
TransactionOutput,
|
||||
Vector[TransactionOutput]] = Vector.newBuilder
|
||||
|
||||
private var lockTime: UInt32 = TransactionConstants.lockTime
|
||||
|
||||
/** Returns a RawTxBuilderResult ready for a RawTxFinalizer. */
|
||||
def result(): RawTxBuilderResult = {
|
||||
RawTxBuilderResult(version,
|
||||
inputsBuilder.result(),
|
||||
outputsBuilder.result(),
|
||||
lockTime)
|
||||
}
|
||||
|
||||
/** Returns a RawTxBuilderWithFinalizer where building can continue
|
||||
* and where buildTx can be called once building is completed. */
|
||||
def setFinalizer(finalizer: RawTxFinalizer): RawTxBuilderWithFinalizer = {
|
||||
RawTxBuilderWithFinalizer(this, finalizer)
|
||||
}
|
||||
|
||||
/** Resets the RawTxBuilder as if it was just constructed. */
|
||||
def clear(): Unit = {
|
||||
version = TransactionConstants.validLockVersion
|
||||
inputsBuilder.clear()
|
||||
outputsBuilder.clear()
|
||||
lockTime = TransactionConstants.lockTime
|
||||
}
|
||||
|
||||
/** Adds a TransactionInput to be the next input. No ScriptSignature is required
|
||||
* and any given ScriptSignature will be ignored (we recommend EmptyScriptSignature). */
|
||||
def addInput(input: TransactionInput): this.type = {
|
||||
inputsBuilder += input
|
||||
this
|
||||
}
|
||||
|
||||
@inline final def +=(input: TransactionInput): this.type = addInput(input)
|
||||
|
||||
def addInputs(inputs: Iterable[TransactionInput]): this.type = {
|
||||
inputsBuilder ++= inputs
|
||||
this
|
||||
}
|
||||
|
||||
/** Adds a TransactionOuput to be the next output.
|
||||
*
|
||||
* Note that outputs like a change
|
||||
* output which are computed from other inputs and outputs should not be added here
|
||||
* and are instead the responsibility of the RawTxFinalizer.
|
||||
*/
|
||||
def addOutput(output: TransactionOutput): this.type = {
|
||||
outputsBuilder += output
|
||||
this
|
||||
}
|
||||
|
||||
@inline final def +=(output: TransactionOutput): this.type = addOutput(output)
|
||||
|
||||
def addOutputs(outputs: Iterable[TransactionOutput]): this.type = {
|
||||
outputsBuilder ++= outputs
|
||||
this
|
||||
}
|
||||
|
||||
/** Adds a collection of inputs and/or outputs to the
|
||||
* input and/or output lists
|
||||
*/
|
||||
@inline final def ++=[T >: TransactionInput with TransactionOutput](
|
||||
inputsOrOutputs: Iterable[T]): this.type = {
|
||||
val vec = inputsOrOutputs.iterator.toVector
|
||||
val inputs = vec.collect {
|
||||
case input: TransactionInput => input
|
||||
}
|
||||
val outputs = vec.collect {
|
||||
case output: TransactionOutput => output
|
||||
}
|
||||
|
||||
addInputs(inputs)
|
||||
addOutputs(outputs)
|
||||
}
|
||||
|
||||
/** Sets the transaction version */
|
||||
def setVersion(version: Int32): this.type = {
|
||||
this.version = version
|
||||
this
|
||||
}
|
||||
|
||||
/** Sets the transaction nLockTime */
|
||||
def setLockTime(lockTime: UInt32): this.type = {
|
||||
this.lockTime = lockTime
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
/** Wraps a RawTxBuilder and RawTxFinalizer pair.
|
||||
*
|
||||
* Provides access to builder methods for continuing
|
||||
* to collect inputs and outputs and also offers direct
|
||||
* access to the RawTxFinalizer's buildTx method which
|
||||
* completes the RawTxBuilder and then finalized the result.
|
||||
*/
|
||||
case class RawTxBuilderWithFinalizer(
|
||||
builder: RawTxBuilder,
|
||||
finalizer: RawTxFinalizer) {
|
||||
|
||||
/** Completes the builder and finalizes the result */
|
||||
def buildTx()(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
finalizer.buildTx(builder.result())
|
||||
}
|
||||
|
||||
def clearBuilder(): Unit = {
|
||||
builder.clear()
|
||||
}
|
||||
|
||||
@inline final def +=(input: TransactionInput): this.type = {
|
||||
builder += input
|
||||
this
|
||||
}
|
||||
|
||||
@inline final def +=(output: TransactionOutput): this.type = {
|
||||
builder += output
|
||||
this
|
||||
}
|
||||
|
||||
@inline final def ++=[T >: TransactionInput with TransactionOutput](
|
||||
inputsOrOutputs: Iterable[T]): this.type = {
|
||||
builder ++= inputsOrOutputs
|
||||
this
|
||||
}
|
||||
|
||||
def setVersion(version: Int32): this.type = {
|
||||
builder.setVersion(version)
|
||||
this
|
||||
}
|
||||
|
||||
def setLockTime(lockTime: UInt32): this.type = {
|
||||
builder.setLockTime(lockTime)
|
||||
this
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package org.bitcoins.core.wallet.builder
|
||||
|
||||
import org.bitcoins.core.number.{Int32, UInt32}
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
BaseTransaction,
|
||||
Transaction,
|
||||
TransactionInput,
|
||||
TransactionOutput
|
||||
}
|
||||
|
||||
/** Raw Transaction to be finalized by a RawTxFinalizer */
|
||||
case class RawTxBuilderResult(
|
||||
version: Int32,
|
||||
inputs: Vector[TransactionInput],
|
||||
outputs: Vector[TransactionOutput],
|
||||
lockTime: UInt32) {
|
||||
|
||||
def toBaseTransaction: BaseTransaction = {
|
||||
BaseTransaction(version, inputs, outputs, lockTime)
|
||||
}
|
||||
}
|
||||
|
||||
object RawTxBuilderResult {
|
||||
|
||||
def fromTransaction(tx: Transaction): RawTxBuilderResult = {
|
||||
RawTxBuilderResult(tx.version,
|
||||
tx.inputs.toVector,
|
||||
tx.outputs.toVector,
|
||||
tx.lockTime)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
package org.bitcoins.core.wallet.builder
|
||||
|
||||
import org.bitcoins.core.currency.{CurrencyUnit, Satoshis}
|
||||
import org.bitcoins.core.policy.Policy
|
||||
import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||
import org.bitcoins.core.wallet.utxo.{InputInfo, InputSigningInfo}
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import scala.util.{Success, Try}
|
||||
|
||||
/** This trait is responsible for converting RawTxBuilderResults into
|
||||
* finalized (unsigned) transactions. This process usually includes
|
||||
* such things as computation on inputs and outputs to generate
|
||||
* things like change outputs, or to reorder inputs or outputs.
|
||||
*
|
||||
* Once a transaction is done being finalized, its txid/wtxid should
|
||||
* not change as the RawTxSigner's only responsibility is adding signature
|
||||
* data not included in the txid/wtxid.
|
||||
*
|
||||
* RawTxFinalizer may (but is not required to) generate witness data
|
||||
* other than signatures (i.e. public keys for P2WPKH and redeem scripts
|
||||
* for P2WSH). RawTxFinalizer may not otherwise populate any other kind
|
||||
* of script signature or witness data.
|
||||
*
|
||||
* RawTxFinalizers are compose-able through the andThen method which will
|
||||
* turn the first RawTxFinalizer's finalized transaction into a RawTxBuilderResult
|
||||
* by taking that transactions inputs (in order), outputs (in order), locktime and
|
||||
* version and this RawTxBuilderResult is then given to the second RawTxFinalizer.
|
||||
*/
|
||||
sealed trait RawTxFinalizer {
|
||||
|
||||
/** Constructs a finalized (unsigned) transaction */
|
||||
def buildTx(txBuilderResult: RawTxBuilderResult)(
|
||||
implicit ec: ExecutionContext): Future[Transaction]
|
||||
|
||||
/** The result of buildTx is converted into a RawTxBuilderResult
|
||||
* by taking that transactions inputs (in order), outputs (in order),
|
||||
* locktime and version and this RawTxBuilderResult is then passed to
|
||||
* the other RawTxFinalizer's buildTx
|
||||
*/
|
||||
def andThen(other: RawTxFinalizer): RawTxFinalizer = new RawTxFinalizer {
|
||||
override def buildTx(txBuilderResult: RawTxBuilderResult)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
firstFinalizedTx <- this.buildTx(txBuilderResult)
|
||||
composedFinalizedTx <- other.buildTx(
|
||||
RawTxBuilderResult.fromTransaction(firstFinalizedTx))
|
||||
} yield composedFinalizedTx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A trivial finalizer that does no processing */
|
||||
case object RawFinalizer extends RawTxFinalizer {
|
||||
override def buildTx(txBuilderResult: RawTxBuilderResult)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
Future.successful(txBuilderResult.toBaseTransaction)
|
||||
}
|
||||
}
|
||||
|
||||
/** A simple finalizer that only removes outputs beneath the dust
|
||||
* threshold and does nothing else
|
||||
*/
|
||||
case object FilterDustFinalizer extends RawTxFinalizer {
|
||||
override def buildTx(txBuilderResult: RawTxBuilderResult)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
val filteredOutputs =
|
||||
txBuilderResult.outputs.filter(_.value >= Policy.dustThreshold)
|
||||
Future.successful(
|
||||
txBuilderResult.toBaseTransaction.copy(outputs = filteredOutputs))
|
||||
}
|
||||
}
|
||||
|
||||
/** A finalizer which takes as parameters an input info for each input, as well
|
||||
* as a fee rate and change scriptpubkey and adds a change output resulting in
|
||||
* the expected fee (after fee estimation).
|
||||
*/
|
||||
case class NonInteractiveWithChangeFinalizer(
|
||||
inputInfos: Vector[InputInfo],
|
||||
feeRate: FeeUnit,
|
||||
changeSPK: ScriptPubKey)
|
||||
extends RawTxFinalizer {
|
||||
override def buildTx(txBuilderResult: RawTxBuilderResult)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
val RawTxBuilderResult(version, inputs, outputs, lockTime) = txBuilderResult
|
||||
|
||||
val outputsWithChange = outputs :+ TransactionOutput(Satoshis.zero,
|
||||
changeSPK)
|
||||
val totalCrediting = inputInfos
|
||||
.map(_.output.value)
|
||||
.foldLeft[CurrencyUnit](Satoshis.zero)(_ + _)
|
||||
val totalSpending =
|
||||
outputs.map(_.value).foldLeft[CurrencyUnit](Satoshis.zero)(_ + _)
|
||||
val witnesses = inputInfos.map(InputInfo.getScriptWitness)
|
||||
val txNoChangeFee = TransactionWitness.fromWitOpt(witnesses) match {
|
||||
case _: EmptyWitness =>
|
||||
BaseTransaction(version, inputs, outputsWithChange, lockTime)
|
||||
case wit: TransactionWitness =>
|
||||
WitnessTransaction(version, inputs, outputsWithChange, lockTime, wit)
|
||||
}
|
||||
|
||||
val dummyTxF = TxUtil.addDummySigs(txNoChangeFee, inputInfos)
|
||||
|
||||
val txF = dummyTxF.map { dummyTx =>
|
||||
val fee = feeRate.calc(dummyTx)
|
||||
val change = totalCrediting - totalSpending - fee
|
||||
val newChangeOutput = TransactionOutput(change, changeSPK)
|
||||
val newOutputs = if (change <= Policy.dustThreshold) {
|
||||
outputs
|
||||
} else {
|
||||
outputs :+ newChangeOutput
|
||||
}
|
||||
|
||||
txNoChangeFee match {
|
||||
case btx: NonWitnessTransaction =>
|
||||
BaseTransaction(btx.version, btx.inputs, newOutputs, btx.lockTime)
|
||||
case WitnessTransaction(version, inputs, _, lockTime, witness) =>
|
||||
WitnessTransaction(version, inputs, newOutputs, lockTime, witness)
|
||||
}
|
||||
}
|
||||
|
||||
txF.flatMap { tx =>
|
||||
val passInOutChecksT =
|
||||
NonInteractiveWithChangeFinalizer.sanityDestinationChecks(
|
||||
expectedOutPoints = inputs.map(_.previousOutput),
|
||||
expectedOutputs = outputs,
|
||||
changeSPK = changeSPK,
|
||||
finalizedTx = tx)
|
||||
|
||||
val passChecksT = passInOutChecksT.flatMap { _ =>
|
||||
TxUtil.sanityChecks(isSigned = false,
|
||||
inputInfos = inputInfos,
|
||||
expectedFeeRate = feeRate,
|
||||
tx = tx)
|
||||
}
|
||||
|
||||
Future.fromTry(passChecksT.map(_ => tx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object NonInteractiveWithChangeFinalizer {
|
||||
|
||||
/** Checks that a finalized transaction contains the expected
|
||||
* inputs and outputs. */
|
||||
def sanityDestinationChecks(
|
||||
expectedOutPoints: Vector[TransactionOutPoint],
|
||||
expectedOutputs: Vector[TransactionOutput],
|
||||
changeSPK: ScriptPubKey,
|
||||
finalizedTx: Transaction): Try[Unit] = {
|
||||
//make sure we send coins to the appropriate destinations
|
||||
val isMissingDestination =
|
||||
!expectedOutputs.forall(finalizedTx.outputs.contains)
|
||||
val hasExtraOutputs =
|
||||
if (finalizedTx.outputs.size == expectedOutputs.size) {
|
||||
false
|
||||
} else {
|
||||
//the extra output should be the changeOutput
|
||||
!(finalizedTx.outputs.size == (expectedOutputs.size + 1) &&
|
||||
finalizedTx.outputs.map(_.scriptPubKey).contains(changeSPK))
|
||||
}
|
||||
val spendingTxOutPoints = finalizedTx.inputs.map(_.previousOutput)
|
||||
val hasExtraOutPoints =
|
||||
!spendingTxOutPoints.forall(expectedOutPoints.contains) ||
|
||||
expectedOutPoints.length != spendingTxOutPoints.length
|
||||
if (isMissingDestination) {
|
||||
TxBuilderError.MissingDestinationOutput
|
||||
} else if (hasExtraOutputs) {
|
||||
TxBuilderError.ExtraOutputsAdded
|
||||
} else if (hasExtraOutPoints) {
|
||||
TxBuilderError.ExtraOutPoints
|
||||
} else {
|
||||
Success(())
|
||||
}
|
||||
}
|
||||
|
||||
def txBuilderFrom(
|
||||
outputs: Seq[TransactionOutput],
|
||||
utxos: Seq[InputSigningInfo[InputInfo]],
|
||||
feeRate: FeeUnit,
|
||||
changeSPK: ScriptPubKey): RawTxBuilderWithFinalizer = {
|
||||
val inputs = InputUtil.calcSequenceForInputs(utxos, Policy.isRBFEnabled)
|
||||
val lockTime = TxUtil.calcLockTime(utxos).get
|
||||
val builder = RawTxBuilder().setLockTime(lockTime) ++= outputs ++= inputs
|
||||
val finalizer = NonInteractiveWithChangeFinalizer(
|
||||
utxos.toVector.map(_.inputInfo),
|
||||
feeRate,
|
||||
changeSPK)
|
||||
|
||||
builder.setFinalizer(finalizer)
|
||||
}
|
||||
|
||||
def txFrom(
|
||||
outputs: Seq[TransactionOutput],
|
||||
utxos: Seq[InputSigningInfo[InputInfo]],
|
||||
feeRate: FeeUnit,
|
||||
changeSPK: ScriptPubKey)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
val builderF = Future(txBuilderFrom(outputs, utxos, feeRate, changeSPK))
|
||||
|
||||
builderF.flatMap(_.buildTx())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
package org.bitcoins.core.wallet.builder
|
||||
|
||||
import org.bitcoins.core.crypto.TxSigComponent
|
||||
import org.bitcoins.core.protocol.script.ScriptWitness
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
EmptyWitness,
|
||||
Transaction,
|
||||
TransactionWitness,
|
||||
TxUtil,
|
||||
WitnessTransaction
|
||||
}
|
||||
import org.bitcoins.core.util.BitcoinSLogger
|
||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||
import org.bitcoins.core.wallet.signer.BitcoinSigner
|
||||
import org.bitcoins.core.wallet.utxo.{
|
||||
InputInfo,
|
||||
ScriptSignatureParams,
|
||||
UnassignedSegwitNativeInputInfo
|
||||
}
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
/** Transactions that have been finalized by a RawTxFinalizer are passed as inputs
|
||||
* to a sign function here in order to generate fully signed transactions.
|
||||
*
|
||||
* In the future sign methods specific to multi-party protocols will be added
|
||||
* here to support PSBT and signed transaction construction between multiple parties.
|
||||
*/
|
||||
object RawTxSigner extends BitcoinSLogger {
|
||||
|
||||
def sign(txWithInfo: FinalizedTxWithSigningInfo, expectedFeeRate: FeeUnit)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
sign(txWithInfo.finalizedTx, txWithInfo.infos, expectedFeeRate)
|
||||
}
|
||||
|
||||
def sign(
|
||||
utx: Transaction,
|
||||
utxoInfos: Vector[ScriptSignatureParams[InputInfo]],
|
||||
expectedFeeRate: FeeUnit)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
sign(utx, utxoInfos, expectedFeeRate, (_, _) => true)
|
||||
}
|
||||
|
||||
def sign(
|
||||
txWithInfo: FinalizedTxWithSigningInfo,
|
||||
expectedFeeRate: FeeUnit,
|
||||
invariants: (
|
||||
Vector[ScriptSignatureParams[InputInfo]],
|
||||
Transaction) => Boolean)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
sign(txWithInfo.finalizedTx, txWithInfo.infos, expectedFeeRate, invariants)
|
||||
}
|
||||
|
||||
def sign(
|
||||
utx: Transaction,
|
||||
utxoInfos: Vector[ScriptSignatureParams[InputInfo]],
|
||||
expectedFeeRate: FeeUnit,
|
||||
invariants: (
|
||||
Vector[ScriptSignatureParams[InputInfo]],
|
||||
Transaction) => Boolean)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
require(utxoInfos.length == utx.inputs.length,
|
||||
"Must provide exactly one UTXOSatisfyingInfo per input.")
|
||||
require(utxoInfos.distinct.length == utxoInfos.length,
|
||||
"All UTXOSatisfyingInfos must be unique. ")
|
||||
require(utxoInfos.forall(utxo =>
|
||||
utx.inputs.exists(_.previousOutput == utxo.outPoint)),
|
||||
"All UTXOSatisfyingInfos must correspond to an input.")
|
||||
|
||||
val signedTxF =
|
||||
if (utxoInfos.exists(
|
||||
_.inputInfo.isInstanceOf[UnassignedSegwitNativeInputInfo])) {
|
||||
Future.fromTry(TxBuilderError.NoSigner)
|
||||
} else {
|
||||
val builder = RawTxBuilder()
|
||||
.setVersion(utx.version)
|
||||
.setLockTime(utx.lockTime) ++= utx.outputs
|
||||
|
||||
val inputAndWitnessFs = utxoInfos.map { utxo =>
|
||||
val txSigCompF =
|
||||
BitcoinSigner.sign(utxo, utx, isDummySignature = false)
|
||||
txSigCompF.map { txSigComp =>
|
||||
val scriptWitnessOpt = TxSigComponent.getScriptWitness(txSigComp)
|
||||
|
||||
if (scriptWitnessOpt.isEmpty && InputInfo
|
||||
.getScriptWitness(utxo.inputInfo)
|
||||
.isDefined) {
|
||||
println(utxo.inputInfo)
|
||||
}
|
||||
|
||||
(txSigComp.input, scriptWitnessOpt)
|
||||
}
|
||||
}
|
||||
|
||||
val witnessesBuilder = Vector.newBuilder[Option[ScriptWitness]]
|
||||
|
||||
val inputsAddedToBuilderF = Future.sequence(inputAndWitnessFs).map {
|
||||
inputsAndWitnesses =>
|
||||
utx.inputs.foreach { unsignedInput =>
|
||||
val (input, witnessOpt) = inputsAndWitnesses
|
||||
.find(_._1.previousOutput == unsignedInput.previousOutput)
|
||||
.get
|
||||
|
||||
witnessesBuilder += witnessOpt
|
||||
builder += input
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
_ <- inputsAddedToBuilderF
|
||||
btx <- builder.setFinalizer(RawFinalizer).buildTx()
|
||||
} yield {
|
||||
val txWitness =
|
||||
TransactionWitness.fromWitOpt(witnessesBuilder.result())
|
||||
|
||||
txWitness match {
|
||||
case EmptyWitness(_) => btx
|
||||
case _: TransactionWitness =>
|
||||
WitnessTransaction(btx.version,
|
||||
btx.inputs,
|
||||
btx.outputs,
|
||||
btx.lockTime,
|
||||
txWitness)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signedTxF.flatMap { signedTx =>
|
||||
val txT: Try[Transaction] = {
|
||||
if (invariants(utxoInfos, signedTx)) {
|
||||
//final sanity checks
|
||||
TxUtil.sanityChecks(isSigned = true,
|
||||
inputInfos = utxoInfos.map(_.inputInfo),
|
||||
expectedFeeRate = expectedFeeRate,
|
||||
tx = signedTx) match {
|
||||
case Success(_) => Success(signedTx)
|
||||
case Failure(err) => Failure(err)
|
||||
}
|
||||
} else {
|
||||
TxBuilderError.FailedUserInvariants
|
||||
}
|
||||
}
|
||||
|
||||
Future.fromTry(txT)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package org.bitcoins.core.wallet.builder
|
||||
|
||||
import org.bitcoins.core.protocol.transaction.TransactionOutput
|
||||
|
||||
import scala.util.Failure
|
||||
|
||||
/**
|
||||
|
@ -183,8 +185,10 @@ object TxBuilderError {
|
|||
|
||||
/** Means we have a output on this transaction below
|
||||
* [[org.bitcoins.core.policy.Policy.dustThreshold Policy.dustThreshold]] */
|
||||
val OutputBelowDustThreshold = Failure(new IllegalArgumentException(
|
||||
"The p2p network discourages outputs below the dustThreshold, this tx won't be relayed"))
|
||||
def OutputBelowDustThreshold(
|
||||
belowDustOutputs: Seq[TransactionOutput]): Failure[Nothing] =
|
||||
Failure(new IllegalArgumentException(
|
||||
s"The p2p network discourages outputs below the dustThreshold, $belowDustOutputs, this tx won't be relayed"))
|
||||
|
||||
val UnknownError = Failure(new IllegalArgumentException)
|
||||
}
|
||||
|
|
|
@ -178,7 +178,7 @@ sealed abstract class Signer[-InputType <: InputInfo] extends SignerUtils {
|
|||
unsignedInput.sequence)
|
||||
val signedInputs = unsignedTx.inputs.updated(inputIndex, signedInput)
|
||||
val signedTx = unsignedTx match {
|
||||
case btx: BaseTransaction =>
|
||||
case btx: NonWitnessTransaction =>
|
||||
BaseTransaction(btx.version, signedInputs, btx.outputs, btx.lockTime)
|
||||
case wtx: WitnessTransaction =>
|
||||
WitnessTransaction(wtx.version,
|
||||
|
@ -306,7 +306,7 @@ object BitcoinSigner extends SignerUtils {
|
|||
val txToSign = spendingInfo.output.scriptPubKey match {
|
||||
case _: WitnessScriptPubKey =>
|
||||
tx match {
|
||||
case btx: BaseTransaction =>
|
||||
case btx: NonWitnessTransaction =>
|
||||
val witnesses = psbt.inputMaps.map { map =>
|
||||
map.witnessScriptOpt.map(scriptWit =>
|
||||
P2WSHWitnessV0(scriptWit.witnessScript))
|
||||
|
@ -518,7 +518,7 @@ sealed abstract class P2SHSigner extends Signer[P2SHInputInfo] {
|
|||
signedTx.inputs.updated(inputIndex.toInt, signedInput)
|
||||
|
||||
val finalTx = signedTx match {
|
||||
case btx: BaseTransaction =>
|
||||
case btx: NonWitnessTransaction =>
|
||||
BaseTransaction(version = btx.version,
|
||||
inputs = signedInputs,
|
||||
outputs = btx.outputs,
|
||||
|
@ -603,7 +603,7 @@ sealed abstract class P2WPKHSigner extends Signer[P2WPKHV0InputInfo] {
|
|||
}
|
||||
|
||||
}
|
||||
case btx: BaseTransaction =>
|
||||
case btx: NonWitnessTransaction =>
|
||||
val wtx = WitnessTransaction.toWitnessTx(btx)
|
||||
|
||||
sign(spendingInfoToSatisfy, wtx, isDummySignature)
|
||||
|
|
|
@ -36,6 +36,8 @@ sealed trait InputInfo {
|
|||
|
||||
def pubKeys: Vector[ECPublicKey]
|
||||
|
||||
def requiredSigs: Int
|
||||
|
||||
def toSpendingInfo(
|
||||
signers: Vector[Sign],
|
||||
hashType: HashType): ScriptSignatureParams[InputInfo] = {
|
||||
|
@ -48,6 +50,15 @@ sealed trait InputInfo {
|
|||
ECSignatureParams(this, signer, hashType)
|
||||
}
|
||||
|
||||
def genericWithSignFrom(
|
||||
signerMaterial: InputSigningInfo[InputInfo]): InputSigningInfo[
|
||||
this.type] = {
|
||||
signerMaterial match {
|
||||
case info: ScriptSignatureParams[InputInfo] => withSignFrom(info)
|
||||
case info: ECSignatureParams[InputInfo] => withSignFrom(info)
|
||||
}
|
||||
}
|
||||
|
||||
def withSignFrom(
|
||||
signerMaterial: ScriptSignatureParams[InputInfo]): ScriptSignatureParams[
|
||||
this.type] = {
|
||||
|
@ -245,6 +256,7 @@ case class EmptyInputInfo(outPoint: TransactionOutPoint, amount: CurrencyUnit)
|
|||
override def conditionalPath: ConditionalPath =
|
||||
ConditionalPath.NoCondition
|
||||
override def pubKeys: Vector[ECPublicKey] = Vector.empty
|
||||
override def requiredSigs: Int = 0
|
||||
}
|
||||
|
||||
case class P2PKInputInfo(
|
||||
|
@ -256,6 +268,8 @@ case class P2PKInputInfo(
|
|||
ConditionalPath.NoCondition
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = Vector(scriptPubKey.publicKey)
|
||||
|
||||
override def requiredSigs: Int = 1
|
||||
}
|
||||
|
||||
case class P2PKHInputInfo(
|
||||
|
@ -269,6 +283,8 @@ case class P2PKHInputInfo(
|
|||
ConditionalPath.NoCondition
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = Vector(pubKey)
|
||||
|
||||
override def requiredSigs: Int = 1
|
||||
}
|
||||
|
||||
case class P2PKWithTimeoutInputInfo(
|
||||
|
@ -287,6 +303,8 @@ case class P2PKWithTimeoutInputInfo(
|
|||
|
||||
override def pubKeys: Vector[ECPublicKey] =
|
||||
Vector(scriptPubKey.pubKey, scriptPubKey.timeoutPubKey)
|
||||
|
||||
override def requiredSigs: Int = 1
|
||||
}
|
||||
|
||||
case class MultiSignatureInputInfo(
|
||||
|
@ -298,6 +316,8 @@ case class MultiSignatureInputInfo(
|
|||
ConditionalPath.NoCondition
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = scriptPubKey.publicKeys.toVector
|
||||
|
||||
override def requiredSigs: Int = scriptPubKey.requiredSigs
|
||||
}
|
||||
|
||||
case class ConditionalInputInfo(
|
||||
|
@ -332,6 +352,8 @@ case class ConditionalInputInfo(
|
|||
}
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||
|
||||
override def requiredSigs: Int = nestedInputInfo.requiredSigs
|
||||
}
|
||||
|
||||
case class LockTimeInputInfo(
|
||||
|
@ -350,6 +372,8 @@ case class LockTimeInputInfo(
|
|||
hashPreImages)
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||
|
||||
override def requiredSigs: Int = nestedInputInfo.requiredSigs
|
||||
}
|
||||
|
||||
sealed trait SegwitV0NativeInputInfo extends InputInfo {
|
||||
|
@ -390,6 +414,8 @@ case class P2WPKHV0InputInfo(
|
|||
ConditionalPath.NoCondition
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = Vector(pubKey)
|
||||
|
||||
override def requiredSigs: Int = 1
|
||||
}
|
||||
|
||||
case class P2WSHV0InputInfo(
|
||||
|
@ -410,6 +436,8 @@ case class P2WSHV0InputInfo(
|
|||
hashPreImages)
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||
|
||||
override def requiredSigs: Int = nestedInputInfo.requiredSigs
|
||||
}
|
||||
|
||||
case class UnassignedSegwitNativeInputInfo(
|
||||
|
@ -419,7 +447,9 @@ case class UnassignedSegwitNativeInputInfo(
|
|||
scriptWitness: ScriptWitness,
|
||||
conditionalPath: ConditionalPath,
|
||||
pubKeys: Vector[ECPublicKey])
|
||||
extends InputInfo
|
||||
extends InputInfo {
|
||||
override def requiredSigs: Int = pubKeys.length
|
||||
}
|
||||
|
||||
sealed trait P2SHInputInfo extends InputInfo {
|
||||
def hashPreImages: Vector[NetworkElement]
|
||||
|
@ -431,6 +461,8 @@ sealed trait P2SHInputInfo extends InputInfo {
|
|||
def nestedInputInfo: InputInfo
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||
|
||||
override def requiredSigs: Int = nestedInputInfo.requiredSigs
|
||||
}
|
||||
|
||||
case class P2SHNonSegwitInputInfo(
|
||||
|
|
|
@ -57,7 +57,7 @@ object BouncyCastleUtil {
|
|||
val pubBytes = ByteVector(point.getEncoded(privateKey.isCompressed))
|
||||
require(
|
||||
ECPublicKey.isFullyValid(pubBytes),
|
||||
s"Bouncy Castle failed to generate a valid public key, got: ${BytesUtil
|
||||
s"Bouncy Castle failed to generate a valid public key, got: ${CryptoBytesUtil
|
||||
.encodeHex(pubBytes)}")
|
||||
ECPublicKey(pubBytes)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import scala.math.BigInt
|
|||
/**
|
||||
* Created by chris on 2/26/16.
|
||||
*/
|
||||
trait BytesUtil {
|
||||
trait CryptoBytesUtil {
|
||||
|
||||
def decodeHex(hex: String): ByteVector = {
|
||||
if (hex.isEmpty) ByteVector.empty else ByteVector.fromHex(hex).get
|
||||
|
@ -43,7 +43,7 @@ trait BytesUtil {
|
|||
}
|
||||
|
||||
def encodeHex(bigInt: BigInt): String =
|
||||
BytesUtil.encodeHex(ByteVector(bigInt.toByteArray))
|
||||
CryptoBytesUtil.encodeHex(ByteVector(bigInt.toByteArray))
|
||||
|
||||
/** Tests if a given string is a hexadecimal string. */
|
||||
def isHex(str: String): Boolean = {
|
||||
|
@ -54,7 +54,7 @@ trait BytesUtil {
|
|||
/** Converts a two character hex string to its byte representation. */
|
||||
def hexToByte(hex: String): Byte = {
|
||||
require(hex.length == 2)
|
||||
BytesUtil.decodeHex(hex).head
|
||||
CryptoBytesUtil.decodeHex(hex).head
|
||||
}
|
||||
|
||||
/** Flips the endianness of the give hex string. */
|
||||
|
@ -94,4 +94,4 @@ trait BytesUtil {
|
|||
}
|
||||
}
|
||||
|
||||
object BytesUtil extends BytesUtil
|
||||
object CryptoBytesUtil extends CryptoBytesUtil
|
|
@ -8,7 +8,7 @@ import scodec.bits.ByteVector
|
|||
sealed abstract class ECDigitalSignature {
|
||||
require(r.signum == 1 || r.signum == 0, s"r must not be negative, got $r")
|
||||
require(s.signum == 1 || s.signum == 0, s"s must not be negative, got $s")
|
||||
def hex: String = BytesUtil.encodeHex(bytes)
|
||||
def hex: String = CryptoBytesUtil.encodeHex(bytes)
|
||||
|
||||
def bytes: ByteVector
|
||||
|
||||
|
@ -162,7 +162,7 @@ object ECDigitalSignature extends Factory[ECDigitalSignature] {
|
|||
* the second 32 is the value
|
||||
*/
|
||||
def fromRS(hex: String): ECDigitalSignature = {
|
||||
val bytes = BytesUtil.decodeHex(hex)
|
||||
val bytes = CryptoBytesUtil.decodeHex(hex)
|
||||
fromRS(bytes)
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue