mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 18:47:38 +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.script.ScriptPubKey
|
||||||
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput}
|
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput}
|
||||||
import org.bitcoins.core.script.crypto._
|
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 play.api.libs.json._
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
|
@ -99,13 +99,17 @@ trait MempoolRpc { self: Client =>
|
||||||
getMemPoolEntry(txid.flip)
|
getMemPoolEntry(txid.flip)
|
||||||
}
|
}
|
||||||
|
|
||||||
def getMemPoolEntryOpt(txid: DoubleSha256Digest): Future[Option[GetMemPoolEntryResult]] = {
|
def getMemPoolEntryOpt(
|
||||||
|
txid: DoubleSha256Digest): Future[Option[GetMemPoolEntryResult]] = {
|
||||||
getMemPoolEntryOpt(txid.flip)
|
getMemPoolEntryOpt(txid.flip)
|
||||||
}
|
}
|
||||||
|
|
||||||
def getMemPoolEntryOpt(txid: DoubleSha256DigestBE): Future[Option[GetMemPoolEntryResult]] = {
|
def getMemPoolEntryOpt(
|
||||||
getMemPoolEntry(txid).map(Some(_))
|
txid: DoubleSha256DigestBE): Future[Option[GetMemPoolEntryResult]] = {
|
||||||
.recover { case _: BitcoindException.InvalidAddressOrKey =>
|
getMemPoolEntry(txid)
|
||||||
|
.map(Some(_))
|
||||||
|
.recover {
|
||||||
|
case _: BitcoindException.InvalidAddressOrKey =>
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.bitcoins.core.config
|
package org.bitcoins.core.config
|
||||||
|
|
||||||
import org.bitcoins.crypto.BytesUtil
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
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.blockchain.{Block, MainNetChainParams}
|
||||||
import org.bitcoins.core.protocol.transaction.{Transaction, WitnessTransaction}
|
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 org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
|
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
|
|
@ -8,7 +8,7 @@ import org.bitcoins.core.protocol.transaction._
|
||||||
import org.bitcoins.core.script.crypto._
|
import org.bitcoins.core.script.crypto._
|
||||||
import org.bitcoins.core.serializers.script.ScriptParser
|
import org.bitcoins.core.serializers.script.ScriptParser
|
||||||
import org.bitcoins.core.util._
|
import org.bitcoins.core.util._
|
||||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
import org.bitcoins.crypto.DoubleSha256Digest
|
||||||
import org.scalatest.{FlatSpec, MustMatchers}
|
import org.scalatest.{FlatSpec, MustMatchers}
|
||||||
|
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
|
|
@ -5,7 +5,8 @@ import org.bitcoins.core.number.UInt32
|
||||||
import org.bitcoins.testkit.node.NodeTestUtil
|
import org.bitcoins.testkit.node.NodeTestUtil
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
import org.bitcoins.core.config.MainNet
|
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 scala.util.Random
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
|
@ -2,7 +2,7 @@ package org.bitcoins.core.protocol
|
||||||
|
|
||||||
import org.bitcoins.core.number.UInt64
|
import org.bitcoins.core.number.UInt64
|
||||||
import org.bitcoins.core.protocol.script.ScriptSignature
|
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 org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import org.bitcoins.core.protocol.transaction.{
|
||||||
TransactionConstants,
|
TransactionConstants,
|
||||||
TransactionOutput
|
TransactionOutput
|
||||||
}
|
}
|
||||||
import org.bitcoins.crypto.BytesUtil
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
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.bloom._
|
||||||
import org.bitcoins.core.number.UInt32
|
import org.bitcoins.core.number.UInt32
|
||||||
import org.bitcoins.core.protocol.transaction.TransactionOutPoint
|
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
|
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.bloom.{BloomFilter, BloomUpdateAll}
|
||||||
import org.bitcoins.core.number.UInt32
|
import org.bitcoins.core.number.UInt32
|
||||||
import org.bitcoins.core.util.{Leaf, Node}
|
import org.bitcoins.core.util.{BytesUtil, Leaf, Node}
|
||||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
import org.bitcoins.crypto.DoubleSha256Digest
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
import scodec.bits.BitVector
|
import scodec.bits.BitVector
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.bitcoins.core.protocol.script
|
package org.bitcoins.core.protocol.script
|
||||||
|
|
||||||
import org.bitcoins.crypto.BytesUtil
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.bitcoins.core.protocol.script
|
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 org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||||
import scodec.bits.ByteVector
|
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.script.testprotocol.SignatureHashTestCase
|
||||||
import org.bitcoins.core.protocol.transaction.{
|
import org.bitcoins.core.protocol.transaction.{
|
||||||
BaseTransaction,
|
BaseTransaction,
|
||||||
|
NonWitnessTransaction,
|
||||||
Transaction,
|
Transaction,
|
||||||
TransactionOutput,
|
TransactionOutput,
|
||||||
WitnessTransaction
|
WitnessTransaction
|
||||||
}
|
}
|
||||||
import org.bitcoins.core.script.crypto.{HashType, SIGHASH_ALL}
|
import org.bitcoins.core.script.crypto.{HashType, SIGHASH_ALL}
|
||||||
import org.bitcoins.core.serializers.script.RawScriptSignatureParser
|
import org.bitcoins.core.serializers.script.RawScriptSignatureParser
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil}
|
||||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest, ECDigitalSignature}
|
import org.bitcoins.crypto.{DoubleSha256Digest, ECDigitalSignature}
|
||||||
import org.bitcoins.testkit.util.TestUtil
|
import org.bitcoins.testkit.util.TestUtil
|
||||||
import org.scalatest.{FlatSpec, MustMatchers}
|
import org.scalatest.{FlatSpec, MustMatchers}
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
@ -147,7 +148,7 @@ class ScriptSignatureTest extends FlatSpec with MustMatchers {
|
||||||
Transaction(testCase.transaction.hex) must be(testCase.transaction)
|
Transaction(testCase.transaction.hex) must be(testCase.transaction)
|
||||||
val output = TransactionOutput(CurrencyUnits.zero, testCase.script)
|
val output = TransactionOutput(CurrencyUnits.zero, testCase.script)
|
||||||
val txSigComponent = testCase.transaction match {
|
val txSigComponent = testCase.transaction match {
|
||||||
case btx: BaseTransaction =>
|
case btx: NonWitnessTransaction =>
|
||||||
BaseTxSigComponent(btx,
|
BaseTxSigComponent(btx,
|
||||||
testCase.inputIndex,
|
testCase.inputIndex,
|
||||||
output,
|
output,
|
||||||
|
|
|
@ -13,7 +13,6 @@ import org.bitcoins.core.protocol.transaction.testprotocol.CoreTransactionTestCa
|
||||||
import org.bitcoins.core.script.PreExecutionScriptProgram
|
import org.bitcoins.core.script.PreExecutionScriptProgram
|
||||||
import org.bitcoins.core.script.interpreter.ScriptInterpreter
|
import org.bitcoins.core.script.interpreter.ScriptInterpreter
|
||||||
import org.bitcoins.core.script.result.ScriptOk
|
import org.bitcoins.core.script.result.ScriptOk
|
||||||
import org.bitcoins.core.serializers.transaction.RawBaseTransactionParser
|
|
||||||
import org.bitcoins.crypto.CryptoUtil
|
import org.bitcoins.crypto.CryptoUtil
|
||||||
import org.bitcoins.testkit.core.gen.TransactionGenerators
|
import org.bitcoins.testkit.core.gen.TransactionGenerators
|
||||||
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
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 {
|
it must "derive the correct txid from the transaction contents" in {
|
||||||
|
|
||||||
//https://btc.blockr.io/api/v1/tx/raw/cddda897b0e9322937ee1f4fd5d6147d60f04a0f4d3b461e4f87066ac3918f2a
|
//https://btc.blockr.io/api/v1/tx/raw/cddda897b0e9322937ee1f4fd5d6147d60f04a0f4d3b461e4f87066ac3918f2a
|
||||||
val tx = RawBaseTransactionParser.read(
|
val tx = BaseTransaction(
|
||||||
"01000000020df1e23002ddf909aec026b1cf0c3b6b7943c042f22e25dbd0441855e6b39ee900000000fdfd00004730440220028c02f14654a0cc12c7e3229adb09d5d35bebb6ba1057e39adb1b2706607b0d0220564fab12c6da3d5acef332406027a7ff1cbba980175ffd880e1ba1bf40598f6b014830450221009362f8d67b60773745e983d07ba10efbe566127e244b724385b2ca2e47292dda022033def393954c320653843555ddbe7679b35cc1cacfe1dad923977de8cd6cc6d7014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffffd11533b0f283fca193e361a91ca7ddfc66592e20fd6eaf5dc0f1ef5fed05818000000000fdfe0000483045022100b4062edd75b5b3117f28ba937ed737b10378f762d7d374afabf667180dedcc62022005d44c793a9d787197e12d5049da5e77a09046014219b31e9c6b89948f648f1701483045022100b3b0c0273fc2c531083701f723e03ea3d9111e4bbca33bdf5b175cec82dcab0802206650462db37f9b4fe78da250a3b339ab11e11d84ace8f1b7394a1f6db0960ba4014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffff02500f1e00000000001976a9147ecaa33ef3cd6169517e43188ad3c034db091f5e88ac204e0000000000001976a914321908115d8a138942f98b0b53f86c9a1848501a88ac00000000")
|
"01000000020df1e23002ddf909aec026b1cf0c3b6b7943c042f22e25dbd0441855e6b39ee900000000fdfd00004730440220028c02f14654a0cc12c7e3229adb09d5d35bebb6ba1057e39adb1b2706607b0d0220564fab12c6da3d5acef332406027a7ff1cbba980175ffd880e1ba1bf40598f6b014830450221009362f8d67b60773745e983d07ba10efbe566127e244b724385b2ca2e47292dda022033def393954c320653843555ddbe7679b35cc1cacfe1dad923977de8cd6cc6d7014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffffd11533b0f283fca193e361a91ca7ddfc66592e20fd6eaf5dc0f1ef5fed05818000000000fdfe0000483045022100b4062edd75b5b3117f28ba937ed737b10378f762d7d374afabf667180dedcc62022005d44c793a9d787197e12d5049da5e77a09046014219b31e9c6b89948f648f1701483045022100b3b0c0273fc2c531083701f723e03ea3d9111e4bbca33bdf5b175cec82dcab0802206650462db37f9b4fe78da250a3b339ab11e11d84ace8f1b7394a1f6db0960ba4014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffff02500f1e00000000001976a9147ecaa33ef3cd6169517e43188ad3c034db091f5e88ac204e0000000000001976a914321908115d8a138942f98b0b53f86c9a1848501a88ac00000000")
|
||||||
|
|
||||||
tx.txId.flip.bytes must be(
|
tx.txId.flip.bytes must be(
|
||||||
|
@ -195,7 +194,7 @@ class TransactionTest extends BitcoinSUnitTest {
|
||||||
scriptPubKey match {
|
scriptPubKey match {
|
||||||
case p2sh: P2SHScriptPubKey =>
|
case p2sh: P2SHScriptPubKey =>
|
||||||
tx match {
|
tx match {
|
||||||
case btx: BaseTransaction =>
|
case btx: NonWitnessTransaction =>
|
||||||
BaseTxSigComponent(transaction = btx,
|
BaseTxSigComponent(transaction = btx,
|
||||||
inputIndex = UInt32(inputIndex),
|
inputIndex = UInt32(inputIndex),
|
||||||
output = TransactionOutput(amount, p2sh),
|
output = TransactionOutput(amount, p2sh),
|
||||||
|
@ -209,7 +208,7 @@ class TransactionTest extends BitcoinSUnitTest {
|
||||||
}
|
}
|
||||||
case wit: WitnessScriptPubKey =>
|
case wit: WitnessScriptPubKey =>
|
||||||
tx match {
|
tx match {
|
||||||
case btx: BaseTransaction =>
|
case btx: NonWitnessTransaction =>
|
||||||
BaseTxSigComponent(transaction = btx,
|
BaseTxSigComponent(transaction = btx,
|
||||||
inputIndex = UInt32(inputIndex),
|
inputIndex = UInt32(inputIndex),
|
||||||
output = TransactionOutput(amount, wit),
|
output = TransactionOutput(amount, wit),
|
||||||
|
@ -275,7 +274,7 @@ class TransactionTest extends BitcoinSUnitTest {
|
||||||
scriptPubKey match {
|
scriptPubKey match {
|
||||||
case p2sh: P2SHScriptPubKey =>
|
case p2sh: P2SHScriptPubKey =>
|
||||||
tx match {
|
tx match {
|
||||||
case btx: BaseTransaction =>
|
case btx: NonWitnessTransaction =>
|
||||||
BaseTxSigComponent(
|
BaseTxSigComponent(
|
||||||
transaction = btx,
|
transaction = btx,
|
||||||
inputIndex = UInt32(inputIndex),
|
inputIndex = UInt32(inputIndex),
|
||||||
|
@ -290,7 +289,7 @@ class TransactionTest extends BitcoinSUnitTest {
|
||||||
}
|
}
|
||||||
case wit: WitnessScriptPubKey =>
|
case wit: WitnessScriptPubKey =>
|
||||||
tx match {
|
tx match {
|
||||||
case btx: BaseTransaction =>
|
case btx: NonWitnessTransaction =>
|
||||||
BaseTxSigComponent(
|
BaseTxSigComponent(
|
||||||
transaction = btx,
|
transaction = btx,
|
||||||
inputIndex = UInt32(inputIndex),
|
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 {
|
it must "correctly update PSBTs' inputs" in {
|
||||||
forAllAsync(PSBTGenerators.psbtToBeSigned)(_.flatMap {
|
forAllAsync(PSBTGenerators.psbtToBeSigned)(_.flatMap {
|
||||||
case (fullPsbt, utxos) =>
|
case (fullPsbt, utxos, _) =>
|
||||||
val emptyPsbt = PSBT.fromUnsignedTx(fullPsbt.transaction)
|
val emptyPsbt = PSBT.fromUnsignedTx(fullPsbt.transaction)
|
||||||
|
|
||||||
val infoAndTxs = PSBTGenerators
|
val infoAndTxs = PSBTGenerators
|
||||||
|
@ -93,7 +93,7 @@ class PSBTTest extends BitcoinSAsyncTest {
|
||||||
it must "correctly construct and sign a PSBT" in {
|
it must "correctly construct and sign a PSBT" in {
|
||||||
forAllAsync(PSBTGenerators.psbtToBeSigned) { psbtWithBuilderF =>
|
forAllAsync(PSBTGenerators.psbtToBeSigned) { psbtWithBuilderF =>
|
||||||
psbtWithBuilderF.flatMap {
|
psbtWithBuilderF.flatMap {
|
||||||
case (psbtNoSigs, utxos) =>
|
case (psbtNoSigs, utxos, _) =>
|
||||||
val infos = utxos.toVector.zipWithIndex.map {
|
val infos = utxos.toVector.zipWithIndex.map {
|
||||||
case (utxo: ScriptSignatureParams[InputInfo], index) =>
|
case (utxo: ScriptSignatureParams[InputInfo], index) =>
|
||||||
(index, utxo)
|
(index, utxo)
|
||||||
|
@ -173,19 +173,17 @@ class PSBTTest extends BitcoinSAsyncTest {
|
||||||
val maxFee = crediting - spending
|
val maxFee = crediting - spending
|
||||||
val fee = GenUtil.sample(CurrencyUnitGenerator.feeUnit(maxFee))
|
val fee = GenUtil.sample(CurrencyUnitGenerator.feeUnit(maxFee))
|
||||||
for {
|
for {
|
||||||
(psbt, _) <- PSBTGenerators.psbtAndBuilderFromInputs(
|
(psbt, _, _) <- PSBTGenerators.psbtAndBuilderFromInputs(
|
||||||
finalized = false,
|
finalized = false,
|
||||||
creditingTxsInfo = creditingTxsInfo,
|
creditingTxsInfo = creditingTxsInfo,
|
||||||
destinations = destinations,
|
destinations = destinations,
|
||||||
changeSPK = changeSPK,
|
changeSPK = changeSPK,
|
||||||
network = network,
|
|
||||||
fee = fee)
|
fee = fee)
|
||||||
(expected, _) <- PSBTGenerators.psbtAndBuilderFromInputs(
|
(expected, _, _) <- PSBTGenerators.psbtAndBuilderFromInputs(
|
||||||
finalized = true,
|
finalized = true,
|
||||||
creditingTxsInfo = creditingTxsInfo,
|
creditingTxsInfo = creditingTxsInfo,
|
||||||
destinations = destinations,
|
destinations = destinations,
|
||||||
changeSPK = changeSPK,
|
changeSPK = changeSPK,
|
||||||
network = network,
|
|
||||||
fee = fee)
|
fee = fee)
|
||||||
} yield {
|
} yield {
|
||||||
val finalizedPsbtOpt = psbt.finalizePSBT
|
val finalizedPsbtOpt = psbt.finalizePSBT
|
||||||
|
@ -198,8 +196,8 @@ class PSBTTest extends BitcoinSAsyncTest {
|
||||||
it must "agree with TxBuilder.sign given UTXOSpendingInfos" in {
|
it must "agree with TxBuilder.sign given UTXOSpendingInfos" in {
|
||||||
forAllAsync(PSBTGenerators.finalizedPSBTWithBuilder) { psbtAndBuilderF =>
|
forAllAsync(PSBTGenerators.finalizedPSBTWithBuilder) { psbtAndBuilderF =>
|
||||||
for {
|
for {
|
||||||
(psbt, builder) <- psbtAndBuilderF
|
(psbt, builder, fee) <- psbtAndBuilderF
|
||||||
signedTx <- builder.sign
|
signedTx <- builder.sign(fee)
|
||||||
} yield {
|
} yield {
|
||||||
val txT = psbt.extractTransactionAndValidate
|
val txT = psbt.extractTransactionAndValidate
|
||||||
assert(txT.isSuccess, txT.failed)
|
assert(txT.isSuccess, txT.failed)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package org.bitcoins.core.script
|
package org.bitcoins.core.script
|
||||||
|
|
||||||
import org.bitcoins.core.script.constant.ScriptConstant
|
import org.bitcoins.core.script.constant.ScriptConstant
|
||||||
import org.bitcoins.crypto.BytesUtil
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
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.locktime.OP_CHECKLOCKTIMEVERIFY
|
||||||
import org.bitcoins.core.script.splice.OP_SUBSTR
|
import org.bitcoins.core.script.splice.OP_SUBSTR
|
||||||
import org.bitcoins.core.script.stack.OP_TOALTSTACK
|
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
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.bitcoins.core.script.constant
|
package org.bitcoins.core.script.constant
|
||||||
|
|
||||||
import org.bitcoins.crypto.BytesUtil
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.bitcoins.core.script.constant
|
package org.bitcoins.core.script.constant
|
||||||
|
|
||||||
import org.bitcoins.crypto.BytesUtil
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
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.constant._
|
||||||
import org.bitcoins.core.script.result._
|
import org.bitcoins.core.script.result._
|
||||||
import org.bitcoins.core.script.ExecutedScriptProgram
|
import org.bitcoins.core.script.ExecutedScriptProgram
|
||||||
import org.bitcoins.core.util.ScriptProgramTestUtil
|
import org.bitcoins.core.util.{BytesUtil, ScriptProgramTestUtil}
|
||||||
import org.bitcoins.crypto.BytesUtil
|
|
||||||
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package org.bitcoins.core.serializers.blockchain
|
package org.bitcoins.core.serializers.blockchain
|
||||||
|
|
||||||
import org.bitcoins.core.number.{Int32, UInt32}
|
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 org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
import scodec.bits.ByteVector
|
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.number.{Int32, UInt32, UInt64}
|
||||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||||
import org.bitcoins.core.protocol.transaction.Transaction
|
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 org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ import org.bitcoins.core.protocol.blockchain.{
|
||||||
MerkleBlock,
|
MerkleBlock,
|
||||||
PartialMerkleTree
|
PartialMerkleTree
|
||||||
}
|
}
|
||||||
import org.bitcoins.core.util.{Leaf, Node}
|
import org.bitcoins.core.util.{BytesUtil, Leaf, Node}
|
||||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
import org.bitcoins.crypto.DoubleSha256Digest
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
import scodec.bits.BitVector
|
import scodec.bits.BitVector
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.bitcoins.core.serializers.p2p.messages
|
package org.bitcoins.core.serializers.p2p.messages
|
||||||
|
|
||||||
import org.bitcoins.crypto.BytesUtil
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
|
|
||||||
class RawFilterAddMessageSerializerTest extends 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.bloom.BloomUpdateNone
|
||||||
import org.bitcoins.core.number.{UInt32, UInt64}
|
import org.bitcoins.core.number.{UInt32, UInt64}
|
||||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||||
import org.bitcoins.crypto.BytesUtil
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
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.number.UInt64
|
||||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||||
import org.bitcoins.core.p2p._
|
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
|
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.number.{UInt32, UInt64}
|
||||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil}
|
||||||
import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest}
|
import org.bitcoins.crypto.DoubleSha256Digest
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package org.bitcoins.core.serializers.p2p.messages
|
package org.bitcoins.core.serializers.p2p.messages
|
||||||
|
|
||||||
import org.bitcoins.core.p2p.TypeIdentifier.MsgTx
|
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
|
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.number.{UInt32, UInt64}
|
||||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
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 org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
import scodec.bits.BitVector
|
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.number.UInt64
|
||||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||||
import org.bitcoins.crypto.BytesUtil
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.bitcoins.core.serializers.p2p.messages
|
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.node.NodeTestUtil
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
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.number.{Int32, Int64, UInt64}
|
||||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||||
import org.bitcoins.core.p2p._
|
import org.bitcoins.core.p2p._
|
||||||
import org.bitcoins.crypto.BytesUtil
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
|
|
||||||
class RawVersionMessageSerializerTest extends 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.constant._
|
||||||
import org.bitcoins.core.script.crypto.{OP_CHECKSIG, OP_HASH160}
|
import org.bitcoins.core.script.crypto.{OP_CHECKSIG, OP_HASH160}
|
||||||
import org.bitcoins.core.script.stack.OP_DUP
|
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 org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||||
import scodec.bits.ByteVector
|
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.protocol.script.{EmptyScriptSignature, ScriptSignature}
|
||||||
import org.bitcoins.core.script.constant._
|
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 org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||||
import scodec.bits.ByteVector
|
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.reserved.OP_NOP
|
||||||
import org.bitcoins.core.script.splice.OP_SIZE
|
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.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 org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||||
import scodec.bits.ByteVector
|
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.number.{Int32, UInt32}
|
||||||
import org.bitcoins.core.protocol.transaction.{
|
import org.bitcoins.core.protocol.transaction.{
|
||||||
|
BaseTransaction,
|
||||||
Transaction,
|
Transaction,
|
||||||
TransactionConstants
|
TransactionConstants
|
||||||
}
|
}
|
||||||
import org.bitcoins.crypto.BytesUtil
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil}
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ import scodec.bits.ByteVector
|
||||||
class RawBaseTransactionParserTest extends BitcoinSUnitTest {
|
class RawBaseTransactionParserTest extends BitcoinSUnitTest {
|
||||||
val encode = BytesUtil.encodeHex(_: ByteVector)
|
val encode = BytesUtil.encodeHex(_: ByteVector)
|
||||||
"RawBaseTransactionParser" must "parse a raw transaction" in {
|
"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.version must be(Int32.one)
|
||||||
tx.inputs.size must be(2)
|
tx.inputs.size must be(2)
|
||||||
tx.outputs.size must be(2)
|
tx.outputs.size must be(2)
|
||||||
|
@ -29,7 +30,7 @@ class RawBaseTransactionParserTest extends BitcoinSUnitTest {
|
||||||
//txid bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74
|
//txid bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74
|
||||||
val rawTx =
|
val rawTx =
|
||||||
"0100000002fc37adbd036fb51b3f4f6f70474270939d6ff8c4ea697639f2b57dd6359e3070010000008b483045022100ad8e961fe3c22b2647d92b078f4c0cf81b3106ea5bf8b900ab8646aa4430216f022071d4edc2b5588be20ac4c2d07edd8ed069e10b2402d3dce2d3b835ccd075f283014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746e0000000036219231b3043efdfb9405bbc2610baa73e340dddfe9c2a07b09bd3785ca6330000000008b483045022100cb097f8720d0c4665e8771fff5181b30584fd9e7d437fae21b440c94fe76d56902206f9b539ae26ec9688c54272d6a3309d93f17fb9835f382fff1ebeead84af2763014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746effffffff02905f0100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988aca0860100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988ac77d3a655"
|
"0100000002fc37adbd036fb51b3f4f6f70474270939d6ff8c4ea697639f2b57dd6359e3070010000008b483045022100ad8e961fe3c22b2647d92b078f4c0cf81b3106ea5bf8b900ab8646aa4430216f022071d4edc2b5588be20ac4c2d07edd8ed069e10b2402d3dce2d3b835ccd075f283014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746e0000000036219231b3043efdfb9405bbc2610baa73e340dddfe9c2a07b09bd3785ca6330000000008b483045022100cb097f8720d0c4665e8771fff5181b30584fd9e7d437fae21b440c94fe76d56902206f9b539ae26ec9688c54272d6a3309d93f17fb9835f382fff1ebeead84af2763014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746effffffff02905f0100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988aca0860100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988ac77d3a655"
|
||||||
val tx: Transaction = RawBaseTransactionParser.read(rawTx)
|
val tx: Transaction = BaseTransaction(rawTx)
|
||||||
tx.txId.hex must be(
|
tx.txId.hex must be(
|
||||||
BytesUtil.flipEndianness(
|
BytesUtil.flipEndianness(
|
||||||
"bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74"))
|
"bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74"))
|
||||||
|
@ -40,8 +41,8 @@ class RawBaseTransactionParserTest extends BitcoinSUnitTest {
|
||||||
//txid bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74
|
//txid bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74
|
||||||
val rawTxWithLockTime =
|
val rawTxWithLockTime =
|
||||||
"0100000002fc37adbd036fb51b3f4f6f70474270939d6ff8c4ea697639f2b57dd6359e3070010000008b483045022100ad8e961fe3c22b2647d92b078f4c0cf81b3106ea5bf8b900ab8646aa4430216f022071d4edc2b5588be20ac4c2d07edd8ed069e10b2402d3dce2d3b835ccd075f283014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746e0000000036219231b3043efdfb9405bbc2610baa73e340dddfe9c2a07b09bd3785ca6330000000008b483045022100cb097f8720d0c4665e8771fff5181b30584fd9e7d437fae21b440c94fe76d56902206f9b539ae26ec9688c54272d6a3309d93f17fb9835f382fff1ebeead84af2763014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746effffffff02905f0100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988aca0860100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988ac77d3a655"
|
"0100000002fc37adbd036fb51b3f4f6f70474270939d6ff8c4ea697639f2b57dd6359e3070010000008b483045022100ad8e961fe3c22b2647d92b078f4c0cf81b3106ea5bf8b900ab8646aa4430216f022071d4edc2b5588be20ac4c2d07edd8ed069e10b2402d3dce2d3b835ccd075f283014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746e0000000036219231b3043efdfb9405bbc2610baa73e340dddfe9c2a07b09bd3785ca6330000000008b483045022100cb097f8720d0c4665e8771fff5181b30584fd9e7d437fae21b440c94fe76d56902206f9b539ae26ec9688c54272d6a3309d93f17fb9835f382fff1ebeead84af2763014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746effffffff02905f0100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988aca0860100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988ac77d3a655"
|
||||||
val tx = RawBaseTransactionParser.read(rawTxWithLockTime)
|
val tx = BaseTransaction(rawTxWithLockTime)
|
||||||
val serializedTx = RawBaseTransactionParser.write(tx)
|
val serializedTx = tx.bytes
|
||||||
encode(serializedTx) must be(rawTxWithLockTime)
|
encode(serializedTx) must be(rawTxWithLockTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,20 +51,20 @@ class RawBaseTransactionParserTest extends BitcoinSUnitTest {
|
||||||
val rawTx =
|
val rawTx =
|
||||||
"01000000020df1e23002ddf909aec026b1cf0c3b6b7943c042f22e25dbd0441855e6b39ee900000000fdfd00004730440220028c02f14654a0cc12c7e3229adb09d5d35bebb6ba1057e39adb1b2706607b0d0220564fab12c6da3d5acef332406027a7ff1cbba980175ffd880e1ba1bf40598f6b014830450221009362f8d67b60773745e983d07ba10efbe566127e244b724385b2ca2e47292dda022033def393954c320653843555ddbe7679b35cc1cacfe1dad923977de8cd6cc6d7014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffffd11533b0f283fca193e361a91ca7ddfc66592e20fd6eaf5dc0f1ef5fed05818000000000fdfe0000483045022100b4062edd75b5b3117f28ba937ed737b10378f762d7d374afabf667180dedcc62022005d44c793a9d787197e12d5049da5e77a09046014219b31e9c6b89948f648f1701483045022100b3b0c0273fc2c531083701f723e03ea3d9111e4bbca33bdf5b175cec82dcab0802206650462db37f9b4fe78da250a3b339ab11e11d84ace8f1b7394a1f6db0960ba4014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffff02500f1e00000000001976a9147ecaa33ef3cd6169517e43188ad3c034db091f5e88ac204e0000000000001976a914321908115d8a138942f98b0b53f86c9a1848501a88ac00000000"
|
"01000000020df1e23002ddf909aec026b1cf0c3b6b7943c042f22e25dbd0441855e6b39ee900000000fdfd00004730440220028c02f14654a0cc12c7e3229adb09d5d35bebb6ba1057e39adb1b2706607b0d0220564fab12c6da3d5acef332406027a7ff1cbba980175ffd880e1ba1bf40598f6b014830450221009362f8d67b60773745e983d07ba10efbe566127e244b724385b2ca2e47292dda022033def393954c320653843555ddbe7679b35cc1cacfe1dad923977de8cd6cc6d7014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffffd11533b0f283fca193e361a91ca7ddfc66592e20fd6eaf5dc0f1ef5fed05818000000000fdfe0000483045022100b4062edd75b5b3117f28ba937ed737b10378f762d7d374afabf667180dedcc62022005d44c793a9d787197e12d5049da5e77a09046014219b31e9c6b89948f648f1701483045022100b3b0c0273fc2c531083701f723e03ea3d9111e4bbca33bdf5b175cec82dcab0802206650462db37f9b4fe78da250a3b339ab11e11d84ace8f1b7394a1f6db0960ba4014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffff02500f1e00000000001976a9147ecaa33ef3cd6169517e43188ad3c034db091f5e88ac204e0000000000001976a914321908115d8a138942f98b0b53f86c9a1848501a88ac00000000"
|
||||||
|
|
||||||
val tx = RawBaseTransactionParser.read(rawTx)
|
val tx = BaseTransaction(rawTx)
|
||||||
val serializedTx = RawBaseTransactionParser.write(tx)
|
val serializedTx = tx.bytes
|
||||||
encode(serializedTx) must be(rawTx)
|
encode(serializedTx) must be(rawTx)
|
||||||
}
|
}
|
||||||
|
|
||||||
it must "read then write a simple raw transaction with one input and two outputs" in {
|
it must "read then write a simple raw transaction with one input and two outputs" in {
|
||||||
val rawTx = TestUtil.simpleRawTransaction
|
val rawTx = TestUtil.simpleRawTransaction
|
||||||
val tx = RawBaseTransactionParser.read(rawTx)
|
val tx = BaseTransaction(rawTx)
|
||||||
val serializedTx = RawBaseTransactionParser.write(tx)
|
val serializedTx = tx.bytes
|
||||||
encode(serializedTx) must be(rawTx)
|
encode(serializedTx) must be(rawTx)
|
||||||
}
|
}
|
||||||
|
|
||||||
it must "parse a transaction with one input and two outputs" in {
|
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.size must be(1)
|
||||||
tx.inputs.head.scriptSignature.hex must be(
|
tx.inputs.head.scriptSignature.hex must be(
|
||||||
"6a4730440220048e15422cf62349dc586ffb8c749d40280781edd5064ff27a5910ff5cf225a802206a82685dbc2cf195d158c29309939d5a3cd41a889db6f766f3809fff35722305012103dcfc9882c1b3ae4e03fb6cac08bdb39e284e81d70c7aa8b27612457b2774509b")
|
"6a4730440220048e15422cf62349dc586ffb8c749d40280781edd5064ff27a5910ff5cf225a802206a82685dbc2cf195d158c29309939d5a3cd41a889db6f766f3809fff35722305012103dcfc9882c1b3ae4e03fb6cac08bdb39e284e81d70c7aa8b27612457b2774509b")
|
||||||
|
|
|
@ -5,8 +5,7 @@ import org.bitcoins.core.protocol.transaction.{
|
||||||
TransactionConstants,
|
TransactionConstants,
|
||||||
TransactionInput
|
TransactionInput
|
||||||
}
|
}
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil}
|
||||||
import org.bitcoins.crypto.BytesUtil
|
|
||||||
import org.bitcoins.testkit.util.TestUtil
|
import org.bitcoins.testkit.util.TestUtil
|
||||||
import org.scalatest.{FlatSpec, MustMatchers}
|
import org.scalatest.{FlatSpec, MustMatchers}
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package org.bitcoins.core.serializers.transaction
|
package org.bitcoins.core.serializers.transaction
|
||||||
|
|
||||||
import org.bitcoins.core.number.UInt32
|
import org.bitcoins.core.number.UInt32
|
||||||
import org.bitcoins.crypto.BytesUtil
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
import scodec.bits.ByteVector
|
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.bitwise.OP_EQUAL
|
||||||
import org.bitcoins.core.script.constant.{BytesToPushOntoStack, ScriptConstant}
|
import org.bitcoins.core.script.constant.{BytesToPushOntoStack, ScriptConstant}
|
||||||
import org.bitcoins.core.script.crypto.OP_HASH160
|
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 org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.bitcoins.core.serializers.transaction
|
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
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
|
|
||||||
class RawWitnessTransactionParserTest extends BitcoinSUnitTest {
|
class RawWitnessTransactionParserTest extends BitcoinSUnitTest {
|
||||||
|
@ -8,8 +9,8 @@ class RawWitnessTransactionParserTest extends BitcoinSUnitTest {
|
||||||
"RawWitnessTransactionParser" must "serialize and deserialize a wtx" in {
|
"RawWitnessTransactionParser" must "serialize and deserialize a wtx" in {
|
||||||
val hex =
|
val hex =
|
||||||
"0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f85603000000171600141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b928ffffffff019caef505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02483045022100f764287d3e99b1474da9bec7f7ed236d6c81e793b20c4b5aa1f3051b9a7daa63022016a198031d5554dbb855bdbe8534776a4be6958bd8d530dc001c32b828f6f0ab0121038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990ac00000000"
|
"0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f85603000000171600141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b928ffffffff019caef505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02483045022100f764287d3e99b1474da9bec7f7ed236d6c81e793b20c4b5aa1f3051b9a7daa63022016a198031d5554dbb855bdbe8534776a4be6958bd8d530dc001c32b828f6f0ab0121038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990ac00000000"
|
||||||
val wtx = RawWitnessTransactionParser.read(hex)
|
val wtx = WitnessTransaction(hex)
|
||||||
val bytes = RawWitnessTransactionParser.write(wtx)
|
val bytes = wtx.bytes
|
||||||
BytesUtil.encodeHex(bytes) must be(hex)
|
BytesUtil.encodeHex(bytes) must be(hex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package org.bitcoins.core.util
|
package org.bitcoins.core.util
|
||||||
|
|
||||||
import org.bitcoins.core.util.testprotocol._
|
import org.bitcoins.core.util.testprotocol._
|
||||||
import org.bitcoins.crypto.BytesUtil
|
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
import spray.json._
|
import spray.json._
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.bitcoins.core.util
|
package org.bitcoins.core.util
|
||||||
|
|
||||||
import org.bitcoins.crypto.BytesUtil
|
|
||||||
import org.bitcoins.testkit.core.gen.StringGenerators
|
import org.bitcoins.testkit.core.gen.StringGenerators
|
||||||
import org.scalacheck.{Prop, Properties}
|
import org.scalacheck.{Prop, Properties}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.bitcoins.core.util
|
package org.bitcoins.core.util
|
||||||
|
|
||||||
import org.bitcoins.crypto.BytesUtil
|
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
import scodec.bits.BitVector
|
import scodec.bits.BitVector
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.bitcoins.core.util
|
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.core.gen.CryptoGenerators
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
|
|
|
@ -2,7 +2,6 @@ package org.bitcoins.core.util
|
||||||
|
|
||||||
import org.bitcoins.testkit.core.gen.NumberGenerator
|
import org.bitcoins.testkit.core.gen.NumberGenerator
|
||||||
import org.bitcoins.core.number.UInt8
|
import org.bitcoins.core.number.UInt8
|
||||||
import org.bitcoins.crypto.BytesUtil
|
|
||||||
import org.scalacheck.{Prop, Properties}
|
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.psbt.PSBT
|
||||||
import org.bitcoins.core.script.PreExecutionScriptProgram
|
import org.bitcoins.core.script.PreExecutionScriptProgram
|
||||||
import org.bitcoins.core.script.interpreter.ScriptInterpreter
|
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.fee.SatoshisPerVirtualByte
|
||||||
import org.bitcoins.core.wallet.utxo.{
|
import org.bitcoins.core.wallet.utxo.{
|
||||||
ECSignatureParams,
|
ECSignatureParams,
|
||||||
|
@ -52,7 +55,6 @@ import org.bitcoins.core.wallet.utxo.{
|
||||||
}
|
}
|
||||||
import org.bitcoins.crypto.ECDigitalSignature
|
import org.bitcoins.crypto.ECDigitalSignature
|
||||||
import org.bitcoins.testkit.core.gen.{
|
import org.bitcoins.testkit.core.gen.{
|
||||||
ChainParamsGenerator,
|
|
||||||
CreditingTxGen,
|
CreditingTxGen,
|
||||||
GenUtil,
|
GenUtil,
|
||||||
ScriptGenerators,
|
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 {
|
it must "sign a mix of spks in a tx and then verify that single signing agrees" in {
|
||||||
forAllAsync(CreditingTxGen.inputsAndOutputs(),
|
forAllAsync(CreditingTxGen.inputsAndOutputs(),
|
||||||
ScriptGenerators.scriptPubKey,
|
ScriptGenerators.scriptPubKey) {
|
||||||
ChainParamsGenerator.bitcoinNetworkParams) {
|
case ((creditingTxsInfos, destinations), (changeSPK, _)) =>
|
||||||
case ((creditingTxsInfos, destinations), changeSPK, network) =>
|
|
||||||
val fee = SatoshisPerVirtualByte(Satoshis(1000))
|
val fee = SatoshisPerVirtualByte(Satoshis(1000))
|
||||||
|
|
||||||
for {
|
for {
|
||||||
builder <- BitcoinTxBuilder(destinations,
|
unsignedTx <- NonInteractiveWithChangeFinalizer.txFrom(
|
||||||
|
destinations,
|
||||||
creditingTxsInfos,
|
creditingTxsInfos,
|
||||||
fee,
|
fee,
|
||||||
changeSPK._1,
|
changeSPK)
|
||||||
network)
|
signedTx <- RawTxSigner.sign(unsignedTx,
|
||||||
unsignedTx <- builder.unsignedTx
|
creditingTxsInfos.toVector,
|
||||||
signedTx <- builder.sign
|
fee)
|
||||||
|
|
||||||
singleSigs: Vector[Vector[ECDigitalSignature]] <- {
|
singleSigs: Vector[Vector[ECDigitalSignature]] <- {
|
||||||
val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] =
|
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 {
|
it must "sign p2wsh inputs correctly when provided no witness data" in {
|
||||||
forAllAsync(CreditingTxGen.inputsAndOutputs(CreditingTxGen.p2wshOutputs),
|
forAllAsync(CreditingTxGen.inputsAndOutputs(CreditingTxGen.p2wshOutputs),
|
||||||
ScriptGenerators.scriptPubKey,
|
ScriptGenerators.scriptPubKey) {
|
||||||
ChainParamsGenerator.bitcoinNetworkParams) {
|
case ((creditingTxsInfos, destinations), (changeSPK, _)) =>
|
||||||
case ((creditingTxsInfos, destinations), changeSPK, network) =>
|
|
||||||
val fee = SatoshisPerVirtualByte(Satoshis(100))
|
val fee = SatoshisPerVirtualByte(Satoshis(100))
|
||||||
|
|
||||||
for {
|
for {
|
||||||
builder <- BitcoinTxBuilder(destinations,
|
unsignedTx <- NonInteractiveWithChangeFinalizer.txFrom(
|
||||||
|
destinations,
|
||||||
creditingTxsInfos,
|
creditingTxsInfos,
|
||||||
fee,
|
fee,
|
||||||
changeSPK._1,
|
changeSPK)
|
||||||
network)
|
|
||||||
unsignedTx <- builder.unsignedTx
|
|
||||||
|
|
||||||
singleSigs: Vector[Vector[PartialSignature]] <- {
|
singleSigs: Vector[Vector[PartialSignature]] <- {
|
||||||
val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] =
|
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.protocol.CompactSizeUInt
|
||||||
import org.bitcoins.core.script.constant.{ScriptConstant, ScriptToken}
|
import org.bitcoins.core.script.constant.{ScriptConstant, ScriptToken}
|
||||||
import org.bitcoins.core.serializers.bloom.RawBloomFilterSerializer
|
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 scodec.bits.{BitVector, ByteVector}
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.util.hashing.MurmurHash3
|
import scala.util.hashing.MurmurHash3
|
||||||
import org.bitcoins.crypto.{
|
import org.bitcoins.crypto.{
|
||||||
BytesUtil,
|
|
||||||
CryptoUtil,
|
CryptoUtil,
|
||||||
DoubleSha256Digest,
|
DoubleSha256Digest,
|
||||||
ECPublicKey,
|
ECPublicKey,
|
||||||
|
|
|
@ -2,7 +2,7 @@ package org.bitcoins.core.consensus
|
||||||
|
|
||||||
import org.bitcoins.core.protocol.blockchain.Block
|
import org.bitcoins.core.protocol.blockchain.Block
|
||||||
import org.bitcoins.core.protocol.transaction.{
|
import org.bitcoins.core.protocol.transaction.{
|
||||||
BaseTransaction,
|
NonWitnessTransaction,
|
||||||
Transaction,
|
Transaction,
|
||||||
WitnessTransaction
|
WitnessTransaction
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ trait Merkle extends BitcoinSLogger {
|
||||||
val coinbaseWTxId = DoubleSha256Digest.empty
|
val coinbaseWTxId = DoubleSha256Digest.empty
|
||||||
val hashes = block.transactions.tail.map {
|
val hashes = block.transactions.tail.map {
|
||||||
case wtx: WitnessTransaction => wtx.wTxId
|
case wtx: WitnessTransaction => wtx.wTxId
|
||||||
case btx: BaseTransaction => btx.txId
|
case btx: NonWitnessTransaction => btx.txId
|
||||||
}
|
}
|
||||||
build(coinbaseWTxId +: hashes)
|
build(coinbaseWTxId +: hashes)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package org.bitcoins.core.crypto
|
package org.bitcoins.core.crypto
|
||||||
|
|
||||||
import org.bitcoins.core.config.{NetworkParameters, Networks}
|
import org.bitcoins.core.config.{NetworkParameters, Networks}
|
||||||
import org.bitcoins.core.util.Base58
|
import org.bitcoins.core.util.{Base58, BytesUtil}
|
||||||
import org.bitcoins.crypto.{BytesUtil, CryptoUtil, ECPrivateKey}
|
import org.bitcoins.crypto.{CryptoUtil, ECPrivateKey}
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import org.bitcoins.core.util._
|
||||||
import org.bitcoins.crypto.{
|
import org.bitcoins.crypto.{
|
||||||
BaseECKey,
|
BaseECKey,
|
||||||
BouncyCastleUtil,
|
BouncyCastleUtil,
|
||||||
BytesUtil,
|
|
||||||
CryptoUtil,
|
CryptoUtil,
|
||||||
ECDigitalSignature,
|
ECDigitalSignature,
|
||||||
ECPrivateKey,
|
ECPrivateKey,
|
||||||
|
|
|
@ -8,13 +8,8 @@ import org.bitcoins.core.script.crypto._
|
||||||
import org.bitcoins.core.script.flag.{ScriptFlag, ScriptFlagUtil}
|
import org.bitcoins.core.script.flag.{ScriptFlag, ScriptFlagUtil}
|
||||||
import org.bitcoins.core.script.result.ScriptErrorWitnessPubKeyType
|
import org.bitcoins.core.script.result.ScriptErrorWitnessPubKeyType
|
||||||
import org.bitcoins.core.policy.Policy
|
import org.bitcoins.core.policy.Policy
|
||||||
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil}
|
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil, BytesUtil}
|
||||||
import org.bitcoins.crypto.{
|
import org.bitcoins.crypto.{DERSignatureUtil, ECDigitalSignature, ECPublicKey}
|
||||||
BytesUtil,
|
|
||||||
DERSignatureUtil,
|
|
||||||
ECDigitalSignature,
|
|
||||||
ECPublicKey
|
|
||||||
}
|
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
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.constant.ScriptToken
|
||||||
import org.bitcoins.core.script.crypto._
|
import org.bitcoins.core.script.crypto._
|
||||||
import org.bitcoins.core.serializers.transaction.RawTransactionOutputParser
|
import org.bitcoins.core.serializers.transaction.RawTransactionOutputParser
|
||||||
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil}
|
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil, BytesUtil}
|
||||||
import org.bitcoins.crypto.{BytesUtil, CryptoUtil, DoubleSha256Digest}
|
import org.bitcoins.crypto.{CryptoUtil, DoubleSha256Digest}
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -53,7 +53,7 @@ object TxSigComponent {
|
||||||
output.scriptPubKey match {
|
output.scriptPubKey match {
|
||||||
case _: WitnessScriptPubKey =>
|
case _: WitnessScriptPubKey =>
|
||||||
transaction match {
|
transaction match {
|
||||||
case _: BaseTransaction =>
|
case _: NonWitnessTransaction =>
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
s"Cannot spend from segwit output ($output) with a base transaction ($transaction)")
|
s"Cannot spend from segwit output ($output) with a base transaction ($transaction)")
|
||||||
case wtx: WitnessTransaction =>
|
case wtx: WitnessTransaction =>
|
||||||
|
@ -64,7 +64,7 @@ object TxSigComponent {
|
||||||
if (WitnessScriptPubKey.isWitnessScriptPubKey(
|
if (WitnessScriptPubKey.isWitnessScriptPubKey(
|
||||||
p2shScriptSig.redeemScript.asm)) {
|
p2shScriptSig.redeemScript.asm)) {
|
||||||
transaction match {
|
transaction match {
|
||||||
case _: BaseTransaction =>
|
case _: NonWitnessTransaction =>
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
s"Cannot spend from segwit output ($output) with a base transaction ($transaction)")
|
s"Cannot spend from segwit output ($output) with a base transaction ($transaction)")
|
||||||
case wtx: WitnessTransaction =>
|
case wtx: WitnessTransaction =>
|
||||||
|
@ -77,6 +77,19 @@ object TxSigComponent {
|
||||||
BaseTxSigComponent(transaction, inputIndex, output, flags)
|
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.script.{EmptyScriptPubKey, ScriptPubKey}
|
||||||
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutput}
|
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutput}
|
||||||
import org.bitcoins.core.script.control.OP_RETURN
|
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
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
object BlockFilter {
|
object BlockFilter {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package org.bitcoins.core.number
|
package org.bitcoins.core.number
|
||||||
|
|
||||||
import org.bitcoins.core.util.NumberUtil
|
import org.bitcoins.core.util.{BytesUtil, NumberUtil}
|
||||||
import org.bitcoins.crypto.{BytesUtil, Factory, NetworkElement}
|
import org.bitcoins.crypto.{Factory, NetworkElement}
|
||||||
import scodec.bits.{ByteOrdering, ByteVector}
|
import scodec.bits.{ByteOrdering, ByteVector}
|
||||||
|
|
||||||
import scala.util.{Failure, Success, Try}
|
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.transaction.Transaction
|
||||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||||
import org.bitcoins.core.serializers.p2p.messages._
|
import org.bitcoins.core.serializers.p2p.messages._
|
||||||
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import org.bitcoins.core.wallet.fee.{SatoshisPerByte, SatoshisPerKiloByte}
|
import org.bitcoins.core.wallet.fee.{SatoshisPerByte, SatoshisPerKiloByte}
|
||||||
import org.bitcoins.crypto.{
|
import org.bitcoins.crypto.{
|
||||||
BytesUtil,
|
|
||||||
DoubleSha256Digest,
|
DoubleSha256Digest,
|
||||||
Factory,
|
Factory,
|
||||||
HashDigest,
|
HashDigest,
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package org.bitcoins.core.protocol
|
package org.bitcoins.core.protocol
|
||||||
|
|
||||||
import org.bitcoins.core.config.{MainNet, TestNet3, _}
|
import org.bitcoins.core.config.{MainNet, TestNet3, _}
|
||||||
import org.bitcoins.core.number.{UInt5, UInt8}
|
import org.bitcoins.core.number.{UInt5, UInt8}
|
||||||
import org.bitcoins.core.protocol.script._
|
import org.bitcoins.core.protocol.script._
|
||||||
import org.bitcoins.core.script.constant.ScriptConstant
|
import org.bitcoins.core.script.constant.ScriptConstant
|
||||||
import org.bitcoins.core.util._
|
import org.bitcoins.core.util._
|
||||||
import org.bitcoins.crypto.{
|
import org.bitcoins.crypto.{
|
||||||
BytesUtil,
|
|
||||||
CryptoUtil,
|
CryptoUtil,
|
||||||
ECPublicKey,
|
ECPublicKey,
|
||||||
HashDigest,
|
HashDigest,
|
||||||
|
|
|
@ -2,7 +2,8 @@ package org.bitcoins.core.protocol
|
||||||
|
|
||||||
import org.bitcoins.core.number.{UInt32, UInt64}
|
import org.bitcoins.core.number.{UInt32, UInt64}
|
||||||
import org.bitcoins.core.protocol.script.ScriptSignature
|
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
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,7 +9,8 @@ import org.bitcoins.core.protocol.ln.fee.{
|
||||||
FeeBaseMSat,
|
FeeBaseMSat,
|
||||||
FeeProportionalMillionths
|
FeeProportionalMillionths
|
||||||
}
|
}
|
||||||
import org.bitcoins.crypto.{BytesUtil, ECPublicKey, NetworkElement}
|
import org.bitcoins.core.util.BytesUtil
|
||||||
|
import org.bitcoins.crypto.{ECPublicKey, NetworkElement}
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,8 +2,8 @@ package org.bitcoins.core.protocol.script
|
||||||
|
|
||||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||||
import org.bitcoins.core.script.constant.ScriptToken
|
import org.bitcoins.core.script.constant.ScriptToken
|
||||||
import org.bitcoins.core.util.BitcoinScriptUtil
|
import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil}
|
||||||
import org.bitcoins.crypto.{BytesUtil, Factory}
|
import org.bitcoins.crypto.Factory
|
||||||
import scodec.bits.ByteVector
|
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.script.stack.{OP_DROP, OP_DUP}
|
||||||
import org.bitcoins.core.util._
|
import org.bitcoins.core.util._
|
||||||
import org.bitcoins.crypto.{
|
import org.bitcoins.crypto.{
|
||||||
BytesUtil,
|
|
||||||
CryptoUtil,
|
CryptoUtil,
|
||||||
DoubleSha256Digest,
|
DoubleSha256Digest,
|
||||||
ECPublicKey,
|
ECPublicKey,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import org.bitcoins.core.script.constant._
|
||||||
import org.bitcoins.core.serializers.script.ScriptParser
|
import org.bitcoins.core.serializers.script.ScriptParser
|
||||||
import org.bitcoins.core.util._
|
import org.bitcoins.core.util._
|
||||||
import org.bitcoins.core.wallet.utxo.ConditionalPath
|
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}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
|
@ -125,7 +125,8 @@ object P2PKHScriptSignature extends ScriptFactory[P2PKHScriptSignature] {
|
||||||
_: ScriptConstant,
|
_: ScriptConstant,
|
||||||
_: BytesToPushOntoStack,
|
_: BytesToPushOntoStack,
|
||||||
z: ScriptConstant) =>
|
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)
|
else !P2SHScriptSignature.isRedeemScript(z)
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,8 @@ package org.bitcoins.core.protocol.script
|
||||||
|
|
||||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||||
import org.bitcoins.core.serializers.script.RawScriptWitnessParser
|
import org.bitcoins.core.serializers.script.RawScriptWitnessParser
|
||||||
import org.bitcoins.core.util.BitcoinScriptUtil
|
import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil}
|
||||||
import org.bitcoins.crypto.{
|
import org.bitcoins.crypto.{
|
||||||
BytesUtil,
|
|
||||||
ECDigitalSignature,
|
ECDigitalSignature,
|
||||||
ECPublicKey,
|
ECPublicKey,
|
||||||
EmptyDigitalSignature,
|
EmptyDigitalSignature,
|
||||||
|
|
|
@ -3,13 +3,8 @@ package org.bitcoins.core.protocol.script
|
||||||
import org.bitcoins.core.protocol.CompactSizeUInt
|
import org.bitcoins.core.protocol.CompactSizeUInt
|
||||||
import org.bitcoins.core.script.constant._
|
import org.bitcoins.core.script.constant._
|
||||||
import org.bitcoins.core.script.result._
|
import org.bitcoins.core.script.result._
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil}
|
||||||
import org.bitcoins.crypto.{
|
import org.bitcoins.crypto.{CryptoUtil, Sha256Digest, Sha256Hash160Digest}
|
||||||
BytesUtil,
|
|
||||||
CryptoUtil,
|
|
||||||
Sha256Digest,
|
|
||||||
Sha256Hash160Digest
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by chris on 11/10/16.
|
* 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
|
package org.bitcoins.core.protocol.transaction
|
||||||
|
|
||||||
import org.bitcoins.core.number.{Int32, UInt32}
|
import org.bitcoins.core.number.{Int32, UInt32}
|
||||||
import org.bitcoins.core.protocol.script.ScriptWitness
|
import org.bitcoins.core.protocol.script.{EmptyScriptWitness, ScriptWitness}
|
||||||
import org.bitcoins.core.serializers.transaction.{RawBaseTransactionParser, RawWitnessTransactionParser}
|
import org.bitcoins.core.util.BytesUtil
|
||||||
|
import org.bitcoins.core.wallet.builder.RawTxBuilder
|
||||||
import org.bitcoins.crypto._
|
import org.bitcoins.crypto._
|
||||||
import scodec.bits.ByteVector
|
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]]
|
* [[https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#Transaction_size_calculations]]
|
||||||
*/
|
*/
|
||||||
def baseSize: Long = this match {
|
def baseSize: Long = this match {
|
||||||
case btx: BaseTransaction => btx.byteSize
|
case btx: NonWitnessTransaction => btx.byteSize
|
||||||
case wtx: WitnessTransaction =>
|
case wtx: WitnessTransaction =>
|
||||||
BaseTransaction(wtx.version, wtx.inputs, wtx.outputs, wtx.lockTime).baseSize
|
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 = {
|
def updateInput(idx: Int, i: TransactionInput): Transaction = {
|
||||||
val updatedInputs = inputs.updated(idx, i)
|
val updatedInputs = inputs.updated(idx, i)
|
||||||
this match {
|
this match {
|
||||||
case _: BaseTransaction =>
|
case _: NonWitnessTransaction =>
|
||||||
BaseTransaction(version, updatedInputs, outputs, lockTime)
|
BaseTransaction(version, updatedInputs, outputs, lockTime)
|
||||||
case wtx: WitnessTransaction =>
|
case wtx: WitnessTransaction =>
|
||||||
WitnessTransaction(version,
|
WitnessTransaction(version,
|
||||||
|
@ -97,39 +98,101 @@ sealed abstract class Transaction extends NetworkElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed abstract class BaseTransaction extends Transaction {
|
object Transaction extends Factory[Transaction] {
|
||||||
override def bytes = RawBaseTransactionParser.write(this)
|
def newBuilder: RawTxBuilder = RawTxBuilder()
|
||||||
override def weight = byteSize * 4
|
|
||||||
|
|
||||||
|
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 {
|
sealed abstract class NonWitnessTransaction extends Transaction {
|
||||||
override def txId = DoubleSha256Digest.empty
|
override def weight: Long = byteSize * 4
|
||||||
override def version = TransactionConstants.version
|
|
||||||
override def inputs = Nil
|
override def bytes: ByteVector = {
|
||||||
override def outputs = Nil
|
val versionBytes = version.bytes.reverse
|
||||||
override def lockTime = TransactionConstants.lockTime
|
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(
|
require(
|
||||||
inputs.length == witness.length,
|
inputs.length == witness.length,
|
||||||
s"Must have same amount of inputs and witnesses in witness tx, inputs=${inputs.length} witnesses=${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 */
|
def toBaseTx: BaseTransaction = {
|
||||||
override def txId: DoubleSha256Digest = {
|
BaseTransaction(version, inputs, outputs, lockTime)
|
||||||
val btx = BaseTransaction(version, inputs, outputs, lockTime)
|
|
||||||
btx.txId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** The txId for the witness transaction from satoshi's original serialization */
|
||||||
* The witness used to evaluate
|
override def txId: DoubleSha256Digest = {
|
||||||
* [[org.bitcoins.core.protocol.script.ScriptSignature ScriptSignature]]/
|
toBaseTx.txId
|
||||||
* [[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 witness transaction id as defined by
|
* 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]]
|
* [[https://github.com/bitcoin/bitcoin/blob/5961b23898ee7c0af2626c46d5d70e80136578d3/src/consensus/validation.h#L96]]
|
||||||
*/
|
*/
|
||||||
override def weight: Long = {
|
override def weight: Long = {
|
||||||
val base = BaseTransaction(version, inputs, outputs, lockTime)
|
toBaseTx.byteSize * 3 + byteSize
|
||||||
base.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
|
* 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)
|
val txWit = witness.updated(idx, scriptWit)
|
||||||
WitnessTransaction(version, inputs, outputs, lockTime, txWit)
|
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] {
|
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,
|
* This read function is unique to BaseTransaction.fromBytes
|
||||||
inputs: Seq[TransactionInput],
|
* in the fact that it reads a 'marker' and 'flag' byte to indicate that this tx is a
|
||||||
outputs: Seq[TransactionOutput],
|
* [[org.bitcoins.core.protocol.transaction.WitnessTransaction WitnessTransaction]].
|
||||||
lockTime: UInt32,
|
* See [[https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki BIP144 ]] for more details.
|
||||||
witness: TransactionWitness): WitnessTransaction =
|
* Functionality inside of Bitcoin Core:
|
||||||
WitnessTransactionImpl(version, inputs, outputs, lockTime, witness)
|
* [[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 =
|
WitnessTransaction(version, inputs, outputs, lockTime, witness)
|
||||||
RawWitnessTransactionParser.read(bytes)
|
}
|
||||||
|
|
||||||
def toWitnessTx(tx: Transaction): WitnessTransaction = tx match {
|
def toWitnessTx(tx: Transaction): WitnessTransaction = tx match {
|
||||||
case btx: BaseTransaction =>
|
case btx: NonWitnessTransaction =>
|
||||||
WitnessTransaction(btx.version,
|
WitnessTransaction(btx.version,
|
||||||
btx.inputs,
|
btx.inputs,
|
||||||
btx.outputs,
|
btx.outputs,
|
||||||
|
|
|
@ -2,8 +2,8 @@ package org.bitcoins.core.protocol.transaction
|
||||||
|
|
||||||
import org.bitcoins.core.protocol.script.{EmptyScriptWitness, ScriptWitness}
|
import org.bitcoins.core.protocol.script.{EmptyScriptWitness, ScriptWitness}
|
||||||
import org.bitcoins.core.serializers.transaction.RawTransactionWitnessParser
|
import org.bitcoins.core.serializers.transaction.RawTransactionWitnessParser
|
||||||
import org.bitcoins.core.util.SeqWrapper
|
import org.bitcoins.core.util.{BytesUtil, SeqWrapper}
|
||||||
import org.bitcoins.crypto.{BytesUtil, NetworkElement}
|
import org.bitcoins.crypto.NetworkElement
|
||||||
import scodec.bits.ByteVector
|
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.interpreter.ScriptInterpreter
|
||||||
import org.bitcoins.core.script.result.ScriptOk
|
import org.bitcoins.core.script.result.ScriptOk
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
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.signer.BitcoinSigner
|
||||||
import org.bitcoins.core.wallet.utxo._
|
import org.bitcoins.core.wallet.utxo._
|
||||||
import org.bitcoins.crypto.{
|
import org.bitcoins.crypto.{
|
||||||
|
@ -582,7 +581,7 @@ case class PSBT(
|
||||||
new RuntimeException(
|
new RuntimeException(
|
||||||
s"Input $index was invalid: $inputResult"))
|
s"Input $index was invalid: $inputResult"))
|
||||||
}
|
}
|
||||||
case (Some(_), _: BaseTransaction) =>
|
case (Some(_), _: NonWitnessTransaction) =>
|
||||||
Failure(new RuntimeException(
|
Failure(new RuntimeException(
|
||||||
s"Extracted program is not witness transaction, but input $index has WitnessUTXO record"))
|
s"Extracted program is not witness transaction, but input $index has WitnessUTXO record"))
|
||||||
case (None, _) =>
|
case (None, _) =>
|
||||||
|
@ -647,7 +646,7 @@ case class PSBT(
|
||||||
witness)
|
witness)
|
||||||
} else {
|
} else {
|
||||||
transaction match {
|
transaction match {
|
||||||
case btx: BaseTransaction =>
|
case btx: NonWitnessTransaction =>
|
||||||
BaseTransaction(btx.version, newInputs, btx.outputs, btx.lockTime)
|
BaseTransaction(btx.version, newInputs, btx.outputs, btx.lockTime)
|
||||||
case wtx: WitnessTransaction =>
|
case wtx: WitnessTransaction =>
|
||||||
WitnessTransaction(wtx.version,
|
WitnessTransaction(wtx.version,
|
||||||
|
@ -761,7 +760,7 @@ object PSBT extends Factory[PSBT] {
|
||||||
val btx = unsignedTx match {
|
val btx = unsignedTx match {
|
||||||
case wtx: WitnessTransaction =>
|
case wtx: WitnessTransaction =>
|
||||||
BaseTransaction(wtx.version, wtx.inputs, wtx.outputs, wtx.lockTime)
|
BaseTransaction(wtx.version, wtx.inputs, wtx.outputs, wtx.lockTime)
|
||||||
case base: BaseTransaction => base
|
case base: NonWitnessTransaction => base
|
||||||
}
|
}
|
||||||
val globalMap = GlobalPSBTMap(
|
val globalMap = GlobalPSBTMap(
|
||||||
Vector(GlobalPSBTRecord.UnsignedTransaction(btx)))
|
Vector(GlobalPSBTRecord.UnsignedTransaction(btx)))
|
||||||
|
@ -829,11 +828,11 @@ object PSBT extends Factory[PSBT] {
|
||||||
spendingInfoAndNonWitnessTxs.matchesInputs(unsignedTx.inputs),
|
spendingInfoAndNonWitnessTxs.matchesInputs(unsignedTx.inputs),
|
||||||
"NewSpendingInfos must correspond to transaction inputs"
|
"NewSpendingInfos must correspond to transaction inputs"
|
||||||
)
|
)
|
||||||
val emptySigTx = BitcoinTxBuilder.emptyAllScriptSigs(unsignedTx)
|
val emptySigTx = TxUtil.emptyAllScriptSigs(unsignedTx)
|
||||||
val btx = emptySigTx match {
|
val btx = emptySigTx match {
|
||||||
case wtx: WitnessTransaction =>
|
case wtx: WitnessTransaction =>
|
||||||
BaseTransaction(wtx.version, wtx.inputs, wtx.outputs, wtx.lockTime)
|
BaseTransaction(wtx.version, wtx.inputs, wtx.outputs, wtx.lockTime)
|
||||||
case base: BaseTransaction => base
|
case base: NonWitnessTransaction => base
|
||||||
}
|
}
|
||||||
|
|
||||||
val globalMap = GlobalPSBTMap(
|
val globalMap = GlobalPSBTMap(
|
||||||
|
|
|
@ -4,7 +4,7 @@ import org.bitcoins.core.byteVectorOrdering
|
||||||
import org.bitcoins.core.number.UInt32
|
import org.bitcoins.core.number.UInt32
|
||||||
import org.bitcoins.core.protocol.script._
|
import org.bitcoins.core.protocol.script._
|
||||||
import org.bitcoins.core.protocol.transaction.{
|
import org.bitcoins.core.protocol.transaction.{
|
||||||
BaseTransaction,
|
NonWitnessTransaction,
|
||||||
Transaction,
|
Transaction,
|
||||||
TransactionInput,
|
TransactionInput,
|
||||||
TransactionOutput,
|
TransactionOutput,
|
||||||
|
@ -657,7 +657,8 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
|
||||||
val scriptSig =
|
val scriptSig =
|
||||||
FinalizedScriptSig(sigComponent.scriptSignature)
|
FinalizedScriptSig(sigComponent.scriptSignature)
|
||||||
sigComponent.transaction match {
|
sigComponent.transaction match {
|
||||||
case _: BaseTransaction => InputPSBTMap(utxos ++ Vector(scriptSig))
|
case _: NonWitnessTransaction =>
|
||||||
|
InputPSBTMap(utxos ++ Vector(scriptSig))
|
||||||
case wtx: WitnessTransaction =>
|
case wtx: WitnessTransaction =>
|
||||||
val witness = wtx.witness(sigComponent.inputIndex.toInt)
|
val witness = wtx.witness(sigComponent.inputIndex.toInt)
|
||||||
val scriptWitness = FinalizedScriptWitness(witness)
|
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.script._
|
||||||
import org.bitcoins.core.protocol.transaction.{
|
import org.bitcoins.core.protocol.transaction.{
|
||||||
BaseTransaction,
|
BaseTransaction,
|
||||||
|
NonWitnessTransaction,
|
||||||
Transaction,
|
Transaction,
|
||||||
TransactionOutput
|
TransactionOutput
|
||||||
}
|
}
|
||||||
|
@ -62,7 +63,7 @@ sealed trait GlobalPSBTRecord extends PSBTRecord {
|
||||||
|
|
||||||
object GlobalPSBTRecord extends Factory[GlobalPSBTRecord] {
|
object GlobalPSBTRecord extends Factory[GlobalPSBTRecord] {
|
||||||
import org.bitcoins.core.psbt.PSBTGlobalKeyId._
|
import org.bitcoins.core.psbt.PSBTGlobalKeyId._
|
||||||
case class UnsignedTransaction(transaction: BaseTransaction)
|
case class UnsignedTransaction(transaction: NonWitnessTransaction)
|
||||||
extends GlobalPSBTRecord {
|
extends GlobalPSBTRecord {
|
||||||
require(
|
require(
|
||||||
transaction.inputs.forall(_.scriptSignature == EmptyScriptSignature),
|
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.reserved.ReservedOperation
|
||||||
import org.bitcoins.core.script.splice.SpliceOperation
|
import org.bitcoins.core.script.splice.SpliceOperation
|
||||||
import org.bitcoins.core.script.stack.StackOperation
|
import org.bitcoins.core.script.stack.StackOperation
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil}
|
||||||
import org.bitcoins.crypto.BytesUtil
|
|
||||||
import scodec.bits.ByteVector
|
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.flag.ScriptFlagUtil
|
||||||
import org.bitcoins.core.script.result._
|
import org.bitcoins.core.script.result._
|
||||||
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil}
|
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil, BytesUtil}
|
||||||
import org.bitcoins.crypto.BytesUtil
|
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@ package org.bitcoins.core.script.constant
|
||||||
|
|
||||||
import org.bitcoins.core.number.Int64
|
import org.bitcoins.core.number.Int64
|
||||||
import org.bitcoins.core.script.ScriptOperationFactory
|
import org.bitcoins.core.script.ScriptOperationFactory
|
||||||
import org.bitcoins.core.util.BitcoinScriptUtil
|
import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil}
|
||||||
import org.bitcoins.crypto.{BytesUtil, Factory, NetworkElement}
|
import org.bitcoins.crypto.{Factory, NetworkElement}
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
import scala.util.{Failure, Try}
|
import scala.util.{Failure, Try}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package org.bitcoins.core.script.constant
|
package org.bitcoins.core.script.constant
|
||||||
|
|
||||||
import org.bitcoins.crypto.BytesUtil
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import scodec.bits.ByteVector
|
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.result._
|
||||||
import org.bitcoins.core.script.splice._
|
import org.bitcoins.core.script.splice._
|
||||||
import org.bitcoins.core.script.stack._
|
import org.bitcoins.core.script.stack._
|
||||||
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil}
|
import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil, BytesUtil}
|
||||||
import org.bitcoins.crypto.BytesUtil
|
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.util.{Failure, Success, Try}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
@ -1160,7 +1159,7 @@ sealed abstract class ScriptInterpreter extends BitcoinSLogger {
|
||||||
b.transaction match {
|
b.transaction match {
|
||||||
case wtx: WitnessTransaction =>
|
case wtx: WitnessTransaction =>
|
||||||
wtx.witness(txSigComponent.inputIndex.toInt).stack.nonEmpty
|
wtx.witness(txSigComponent.inputIndex.toInt).stack.nonEmpty
|
||||||
case _: BaseTransaction => false
|
case _: NonWitnessTransaction => false
|
||||||
}
|
}
|
||||||
case r: WitnessTxSigComponentRebuilt =>
|
case r: WitnessTxSigComponentRebuilt =>
|
||||||
r.transaction.witness(txSigComponent.inputIndex.toInt).stack.nonEmpty
|
r.transaction.witness(txSigComponent.inputIndex.toInt).stack.nonEmpty
|
||||||
|
|
|
@ -39,5 +39,6 @@ case object OP_CHECKSEQUENCEVERIFY extends LocktimeOperation {
|
||||||
}
|
}
|
||||||
|
|
||||||
object LocktimeOperation extends ScriptOperationFactory[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] {
|
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
|
package org.bitcoins.core.serializers
|
||||||
|
|
||||||
import org.bitcoins.core.util.BitcoinSLogger
|
import org.bitcoins.core.util.BitcoinSLogger
|
||||||
import org.bitcoins.crypto.BytesUtil
|
import org.bitcoins.core.util.BytesUtil
|
||||||
import scodec.bits.ByteVector
|
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.CompactSizeUInt
|
||||||
import org.bitcoins.core.protocol.blockchain.MerkleBlock
|
import org.bitcoins.core.protocol.blockchain.MerkleBlock
|
||||||
import org.bitcoins.core.serializers.RawBitcoinSerializer
|
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 scodec.bits.{BitVector, ByteVector}
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
|
|
@ -3,7 +3,8 @@ package org.bitcoins.core.serializers.script
|
||||||
import org.bitcoins.core.number.UInt32
|
import org.bitcoins.core.number.UInt32
|
||||||
import org.bitcoins.core.script._
|
import org.bitcoins.core.script._
|
||||||
import org.bitcoins.core.script.constant._
|
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 scodec.bits.ByteVector
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
|
|
@ -2,7 +2,7 @@ package org.bitcoins.core.util
|
||||||
|
|
||||||
import org.bitcoins.core.crypto.ECPrivateKeyUtil
|
import org.bitcoins.core.crypto.ECPrivateKeyUtil
|
||||||
import org.bitcoins.core.protocol.blockchain._
|
import org.bitcoins.core.protocol.blockchain._
|
||||||
import org.bitcoins.crypto.{BytesUtil, CryptoUtil}
|
import org.bitcoins.crypto.CryptoUtil
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
|
|
@ -25,7 +25,7 @@ import org.bitcoins.core.script.result.{
|
||||||
}
|
}
|
||||||
import org.bitcoins.core.script.ExecutionInProgressScriptProgram
|
import org.bitcoins.core.script.ExecutionInProgressScriptProgram
|
||||||
import org.bitcoins.core.serializers.script.ScriptParser
|
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 scodec.bits.ByteVector
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
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.number._
|
||||||
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
||||||
import org.bitcoins.core.protocol.blockchain.BlockHeader.TargetDifficultyHelper
|
import org.bitcoins.core.protocol.blockchain.BlockHeader.TargetDifficultyHelper
|
||||||
import org.bitcoins.crypto.BytesUtil
|
|
||||||
import scodec.bits.{BitVector, ByteVector}
|
import scodec.bits.{BitVector, ByteVector}
|
||||||
|
|
||||||
import scala.math.BigInt
|
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
|
package org.bitcoins.core.wallet.builder
|
||||||
|
|
||||||
|
import org.bitcoins.core.protocol.transaction.TransactionOutput
|
||||||
|
|
||||||
import scala.util.Failure
|
import scala.util.Failure
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -183,8 +185,10 @@ object TxBuilderError {
|
||||||
|
|
||||||
/** Means we have a output on this transaction below
|
/** Means we have a output on this transaction below
|
||||||
* [[org.bitcoins.core.policy.Policy.dustThreshold Policy.dustThreshold]] */
|
* [[org.bitcoins.core.policy.Policy.dustThreshold Policy.dustThreshold]] */
|
||||||
val OutputBelowDustThreshold = Failure(new IllegalArgumentException(
|
def OutputBelowDustThreshold(
|
||||||
"The p2p network discourages outputs below the dustThreshold, this tx won't be relayed"))
|
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)
|
val UnknownError = Failure(new IllegalArgumentException)
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,7 +178,7 @@ sealed abstract class Signer[-InputType <: InputInfo] extends SignerUtils {
|
||||||
unsignedInput.sequence)
|
unsignedInput.sequence)
|
||||||
val signedInputs = unsignedTx.inputs.updated(inputIndex, signedInput)
|
val signedInputs = unsignedTx.inputs.updated(inputIndex, signedInput)
|
||||||
val signedTx = unsignedTx match {
|
val signedTx = unsignedTx match {
|
||||||
case btx: BaseTransaction =>
|
case btx: NonWitnessTransaction =>
|
||||||
BaseTransaction(btx.version, signedInputs, btx.outputs, btx.lockTime)
|
BaseTransaction(btx.version, signedInputs, btx.outputs, btx.lockTime)
|
||||||
case wtx: WitnessTransaction =>
|
case wtx: WitnessTransaction =>
|
||||||
WitnessTransaction(wtx.version,
|
WitnessTransaction(wtx.version,
|
||||||
|
@ -306,7 +306,7 @@ object BitcoinSigner extends SignerUtils {
|
||||||
val txToSign = spendingInfo.output.scriptPubKey match {
|
val txToSign = spendingInfo.output.scriptPubKey match {
|
||||||
case _: WitnessScriptPubKey =>
|
case _: WitnessScriptPubKey =>
|
||||||
tx match {
|
tx match {
|
||||||
case btx: BaseTransaction =>
|
case btx: NonWitnessTransaction =>
|
||||||
val witnesses = psbt.inputMaps.map { map =>
|
val witnesses = psbt.inputMaps.map { map =>
|
||||||
map.witnessScriptOpt.map(scriptWit =>
|
map.witnessScriptOpt.map(scriptWit =>
|
||||||
P2WSHWitnessV0(scriptWit.witnessScript))
|
P2WSHWitnessV0(scriptWit.witnessScript))
|
||||||
|
@ -518,7 +518,7 @@ sealed abstract class P2SHSigner extends Signer[P2SHInputInfo] {
|
||||||
signedTx.inputs.updated(inputIndex.toInt, signedInput)
|
signedTx.inputs.updated(inputIndex.toInt, signedInput)
|
||||||
|
|
||||||
val finalTx = signedTx match {
|
val finalTx = signedTx match {
|
||||||
case btx: BaseTransaction =>
|
case btx: NonWitnessTransaction =>
|
||||||
BaseTransaction(version = btx.version,
|
BaseTransaction(version = btx.version,
|
||||||
inputs = signedInputs,
|
inputs = signedInputs,
|
||||||
outputs = btx.outputs,
|
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)
|
val wtx = WitnessTransaction.toWitnessTx(btx)
|
||||||
|
|
||||||
sign(spendingInfoToSatisfy, wtx, isDummySignature)
|
sign(spendingInfoToSatisfy, wtx, isDummySignature)
|
||||||
|
|
|
@ -36,6 +36,8 @@ sealed trait InputInfo {
|
||||||
|
|
||||||
def pubKeys: Vector[ECPublicKey]
|
def pubKeys: Vector[ECPublicKey]
|
||||||
|
|
||||||
|
def requiredSigs: Int
|
||||||
|
|
||||||
def toSpendingInfo(
|
def toSpendingInfo(
|
||||||
signers: Vector[Sign],
|
signers: Vector[Sign],
|
||||||
hashType: HashType): ScriptSignatureParams[InputInfo] = {
|
hashType: HashType): ScriptSignatureParams[InputInfo] = {
|
||||||
|
@ -48,6 +50,15 @@ sealed trait InputInfo {
|
||||||
ECSignatureParams(this, signer, hashType)
|
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(
|
def withSignFrom(
|
||||||
signerMaterial: ScriptSignatureParams[InputInfo]): ScriptSignatureParams[
|
signerMaterial: ScriptSignatureParams[InputInfo]): ScriptSignatureParams[
|
||||||
this.type] = {
|
this.type] = {
|
||||||
|
@ -245,6 +256,7 @@ case class EmptyInputInfo(outPoint: TransactionOutPoint, amount: CurrencyUnit)
|
||||||
override def conditionalPath: ConditionalPath =
|
override def conditionalPath: ConditionalPath =
|
||||||
ConditionalPath.NoCondition
|
ConditionalPath.NoCondition
|
||||||
override def pubKeys: Vector[ECPublicKey] = Vector.empty
|
override def pubKeys: Vector[ECPublicKey] = Vector.empty
|
||||||
|
override def requiredSigs: Int = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
case class P2PKInputInfo(
|
case class P2PKInputInfo(
|
||||||
|
@ -256,6 +268,8 @@ case class P2PKInputInfo(
|
||||||
ConditionalPath.NoCondition
|
ConditionalPath.NoCondition
|
||||||
|
|
||||||
override def pubKeys: Vector[ECPublicKey] = Vector(scriptPubKey.publicKey)
|
override def pubKeys: Vector[ECPublicKey] = Vector(scriptPubKey.publicKey)
|
||||||
|
|
||||||
|
override def requiredSigs: Int = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
case class P2PKHInputInfo(
|
case class P2PKHInputInfo(
|
||||||
|
@ -269,6 +283,8 @@ case class P2PKHInputInfo(
|
||||||
ConditionalPath.NoCondition
|
ConditionalPath.NoCondition
|
||||||
|
|
||||||
override def pubKeys: Vector[ECPublicKey] = Vector(pubKey)
|
override def pubKeys: Vector[ECPublicKey] = Vector(pubKey)
|
||||||
|
|
||||||
|
override def requiredSigs: Int = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
case class P2PKWithTimeoutInputInfo(
|
case class P2PKWithTimeoutInputInfo(
|
||||||
|
@ -287,6 +303,8 @@ case class P2PKWithTimeoutInputInfo(
|
||||||
|
|
||||||
override def pubKeys: Vector[ECPublicKey] =
|
override def pubKeys: Vector[ECPublicKey] =
|
||||||
Vector(scriptPubKey.pubKey, scriptPubKey.timeoutPubKey)
|
Vector(scriptPubKey.pubKey, scriptPubKey.timeoutPubKey)
|
||||||
|
|
||||||
|
override def requiredSigs: Int = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
case class MultiSignatureInputInfo(
|
case class MultiSignatureInputInfo(
|
||||||
|
@ -298,6 +316,8 @@ case class MultiSignatureInputInfo(
|
||||||
ConditionalPath.NoCondition
|
ConditionalPath.NoCondition
|
||||||
|
|
||||||
override def pubKeys: Vector[ECPublicKey] = scriptPubKey.publicKeys.toVector
|
override def pubKeys: Vector[ECPublicKey] = scriptPubKey.publicKeys.toVector
|
||||||
|
|
||||||
|
override def requiredSigs: Int = scriptPubKey.requiredSigs
|
||||||
}
|
}
|
||||||
|
|
||||||
case class ConditionalInputInfo(
|
case class ConditionalInputInfo(
|
||||||
|
@ -332,6 +352,8 @@ case class ConditionalInputInfo(
|
||||||
}
|
}
|
||||||
|
|
||||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||||
|
|
||||||
|
override def requiredSigs: Int = nestedInputInfo.requiredSigs
|
||||||
}
|
}
|
||||||
|
|
||||||
case class LockTimeInputInfo(
|
case class LockTimeInputInfo(
|
||||||
|
@ -350,6 +372,8 @@ case class LockTimeInputInfo(
|
||||||
hashPreImages)
|
hashPreImages)
|
||||||
|
|
||||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||||
|
|
||||||
|
override def requiredSigs: Int = nestedInputInfo.requiredSigs
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed trait SegwitV0NativeInputInfo extends InputInfo {
|
sealed trait SegwitV0NativeInputInfo extends InputInfo {
|
||||||
|
@ -390,6 +414,8 @@ case class P2WPKHV0InputInfo(
|
||||||
ConditionalPath.NoCondition
|
ConditionalPath.NoCondition
|
||||||
|
|
||||||
override def pubKeys: Vector[ECPublicKey] = Vector(pubKey)
|
override def pubKeys: Vector[ECPublicKey] = Vector(pubKey)
|
||||||
|
|
||||||
|
override def requiredSigs: Int = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
case class P2WSHV0InputInfo(
|
case class P2WSHV0InputInfo(
|
||||||
|
@ -410,6 +436,8 @@ case class P2WSHV0InputInfo(
|
||||||
hashPreImages)
|
hashPreImages)
|
||||||
|
|
||||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||||
|
|
||||||
|
override def requiredSigs: Int = nestedInputInfo.requiredSigs
|
||||||
}
|
}
|
||||||
|
|
||||||
case class UnassignedSegwitNativeInputInfo(
|
case class UnassignedSegwitNativeInputInfo(
|
||||||
|
@ -419,7 +447,9 @@ case class UnassignedSegwitNativeInputInfo(
|
||||||
scriptWitness: ScriptWitness,
|
scriptWitness: ScriptWitness,
|
||||||
conditionalPath: ConditionalPath,
|
conditionalPath: ConditionalPath,
|
||||||
pubKeys: Vector[ECPublicKey])
|
pubKeys: Vector[ECPublicKey])
|
||||||
extends InputInfo
|
extends InputInfo {
|
||||||
|
override def requiredSigs: Int = pubKeys.length
|
||||||
|
}
|
||||||
|
|
||||||
sealed trait P2SHInputInfo extends InputInfo {
|
sealed trait P2SHInputInfo extends InputInfo {
|
||||||
def hashPreImages: Vector[NetworkElement]
|
def hashPreImages: Vector[NetworkElement]
|
||||||
|
@ -431,6 +461,8 @@ sealed trait P2SHInputInfo extends InputInfo {
|
||||||
def nestedInputInfo: InputInfo
|
def nestedInputInfo: InputInfo
|
||||||
|
|
||||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||||
|
|
||||||
|
override def requiredSigs: Int = nestedInputInfo.requiredSigs
|
||||||
}
|
}
|
||||||
|
|
||||||
case class P2SHNonSegwitInputInfo(
|
case class P2SHNonSegwitInputInfo(
|
||||||
|
|
|
@ -57,7 +57,7 @@ object BouncyCastleUtil {
|
||||||
val pubBytes = ByteVector(point.getEncoded(privateKey.isCompressed))
|
val pubBytes = ByteVector(point.getEncoded(privateKey.isCompressed))
|
||||||
require(
|
require(
|
||||||
ECPublicKey.isFullyValid(pubBytes),
|
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)}")
|
.encodeHex(pubBytes)}")
|
||||||
ECPublicKey(pubBytes)
|
ECPublicKey(pubBytes)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import scala.math.BigInt
|
||||||
/**
|
/**
|
||||||
* Created by chris on 2/26/16.
|
* Created by chris on 2/26/16.
|
||||||
*/
|
*/
|
||||||
trait BytesUtil {
|
trait CryptoBytesUtil {
|
||||||
|
|
||||||
def decodeHex(hex: String): ByteVector = {
|
def decodeHex(hex: String): ByteVector = {
|
||||||
if (hex.isEmpty) ByteVector.empty else ByteVector.fromHex(hex).get
|
if (hex.isEmpty) ByteVector.empty else ByteVector.fromHex(hex).get
|
||||||
|
@ -43,7 +43,7 @@ trait BytesUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
def encodeHex(bigInt: BigInt): String =
|
def encodeHex(bigInt: BigInt): String =
|
||||||
BytesUtil.encodeHex(ByteVector(bigInt.toByteArray))
|
CryptoBytesUtil.encodeHex(ByteVector(bigInt.toByteArray))
|
||||||
|
|
||||||
/** Tests if a given string is a hexadecimal string. */
|
/** Tests if a given string is a hexadecimal string. */
|
||||||
def isHex(str: String): Boolean = {
|
def isHex(str: String): Boolean = {
|
||||||
|
@ -54,7 +54,7 @@ trait BytesUtil {
|
||||||
/** Converts a two character hex string to its byte representation. */
|
/** Converts a two character hex string to its byte representation. */
|
||||||
def hexToByte(hex: String): Byte = {
|
def hexToByte(hex: String): Byte = {
|
||||||
require(hex.length == 2)
|
require(hex.length == 2)
|
||||||
BytesUtil.decodeHex(hex).head
|
CryptoBytesUtil.decodeHex(hex).head
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Flips the endianness of the give hex string. */
|
/** 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 {
|
sealed abstract class ECDigitalSignature {
|
||||||
require(r.signum == 1 || r.signum == 0, s"r must not be negative, got $r")
|
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")
|
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
|
def bytes: ByteVector
|
||||||
|
|
||||||
|
@ -162,7 +162,7 @@ object ECDigitalSignature extends Factory[ECDigitalSignature] {
|
||||||
* the second 32 is the value
|
* the second 32 is the value
|
||||||
*/
|
*/
|
||||||
def fromRS(hex: String): ECDigitalSignature = {
|
def fromRS(hex: String): ECDigitalSignature = {
|
||||||
val bytes = BytesUtil.decodeHex(hex)
|
val bytes = CryptoBytesUtil.decodeHex(hex)
|
||||||
fromRS(bytes)
|
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