From dfd3353cc4d5b97aed054e58f2bf89aca826123f Mon Sep 17 00:00:00 2001 From: Nadav Kohen Date: Thu, 21 May 2020 09:47:08 -0500 Subject: [PATCH] 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 --- .../commons/serializers/JsonWriters.scala | 3 +- .../rpc/client/common/MempoolRpc.scala | 16 +- .../core/config/NetworkParametersTest.scala | 2 +- .../bitcoins/core/consensus/MerkleTest.scala | 3 +- .../TransactionSignatureSerializerTest.scala | 2 +- .../bitcoins/core/p2p/NetworkHeaderTest.scala | 3 +- .../core/protocol/CompactSizeUIntTest.scala | 2 +- .../protocol/blockchain/ChainParamsTest.scala | 2 +- .../blockchain/MerkleBlockTests.scala | 3 +- .../blockchain/PartialMerkleTreeTests.scala | 4 +- .../script/ScriptPubKeyFactoryTest.scala | 2 +- .../script/ScriptSignatureFactoryTest.scala | 3 +- .../protocol/script/ScriptSignatureTest.scala | 7 +- .../transaction/TransactionTest.scala | 11 +- .../protocol/transaction/TxUtilTest.scala | 73 ++++ .../org/bitcoins/core/psbt/PSBTTest.scala | 14 +- .../script/ScriptConstantFactoryTest.scala | 2 +- .../script/ScriptOperationFactoryTest.scala | 2 +- .../constant/ScriptNumberFactoryTest.scala | 2 +- .../constant/ScriptNumberUtilTest.scala | 2 +- .../script/stack/StackInterpreterTest.scala | 3 +- .../RawBlockHeaderSerializerTest.scala | 3 +- .../blockchain/RawBlockSerializerTest.scala | 3 +- .../RawMerkleBlockSerializerTest.scala | 4 +- .../RawFilterAddMessageSerializerTest.scala | 2 +- .../RawFilterLoadMessageSerializerTest.scala | 2 +- .../RawGetBlocksMessageSerializerTest.scala | 3 +- .../RawHeadersMessageSerializerTest.scala | 4 +- .../messages/RawInventorySerializerTest.scala | 3 +- .../RawMerkleBlockMessageSerializerTest.scala | 3 +- .../RawRejectMessageSerializerTest.scala | 2 +- .../RawTransactionMessageSerializerTest.scala | 2 +- .../RawVersionMessageSerializerTest.scala | 2 +- .../script/RawScriptPubKeyParserTest.scala | 2 +- .../script/RawScriptSignatureParserTest.scala | 2 +- .../serializers/script/ScriptParserTest.scala | 2 +- .../RawBaseTransactionParserTest.scala | 21 +- .../RawTransactionInputParserTest.scala | 3 +- .../RawTransactionOutPointParserTest.scala | 2 +- .../RawTransactionOutputParserTest.scala | 2 +- .../RawWitnessTransactionParserTest.scala | 7 +- .../org/bitcoins/core/util/Base58Test.scala | 1 - .../bitcoins/core/util/BitcoinSUtilSpec.scala | 1 - .../bitcoins/core/util/BitcoinSUtilTest.scala | 1 - .../bitcoins/core/util/CryptoUtilTest.scala | 2 +- .../bitcoins/core/util/NumberUtilSpec.scala | 1 - ...onInteractiveWithChangeFinalizerTest.scala | 195 +++++++++ .../core/wallet/builder/RawTxSignerTest.scala | 395 ++++++++++++++++++ .../core/wallet/signer/SignerTest.scala | 42 +- .../wallet/utxo/InputSigningInfoTest.scala | 175 ++++++++ .../org/bitcoins/core/bloom/BloomFilter.scala | 3 +- .../org/bitcoins/core/consensus/Merkle.scala | 6 +- .../core/crypto/ECPrivateKeyUtil.scala | 4 +- .../org/bitcoins/core/crypto/ExtKey.scala | 1 - .../crypto/TransactionSignatureChecker.scala | 9 +- .../TransactionSignatureSerializer.scala | 4 +- .../bitcoins/core/crypto/TxSigComponent.scala | 17 +- .../org/bitcoins/core/gcs/BlockFilter.scala | 3 +- .../org/bitcoins/core/number/NumberType.scala | 4 +- .../bitcoins/core/p2p/NetworkPayload.scala | 2 +- .../org/bitcoins/core/protocol/Address.scala | 2 +- .../core/protocol/CompactSizeUInt.scala | 3 +- .../core/protocol/ln/routing/LnRoute.scala | 3 +- .../core/protocol/script/ScriptFactory.scala | 4 +- .../core/protocol/script/ScriptPubKey.scala | 1 - .../protocol/script/ScriptSignature.scala | 5 +- .../core/protocol/script/ScriptWitness.scala | 3 +- .../core/protocol/script/WitnessVersion.scala | 9 +- .../core/protocol/transaction/InputUtil.scala | 122 ++++++ .../protocol/transaction/Transaction.scala | 231 ++++++---- .../transaction/TransactionWitness.scala | 4 +- .../core/protocol/transaction/TxUtil.scala | 321 ++++++++++++++ .../scala/org/bitcoins/core/psbt/PSBT.scala | 11 +- .../org/bitcoins/core/psbt/PSBTMap.scala | 5 +- .../org/bitcoins/core/psbt/PSBTRecord.scala | 3 +- .../core/script/ScriptOperationFactory.scala | 3 +- .../script/constant/ConstantInterpreter.scala | 3 +- .../core/script/constant/Constants.scala | 38 +- .../script/constant/ScriptNumberUtil.scala | 2 +- .../core/script/crypto/CryptoOperations.scala | 18 +- .../CryptoSignatureEvaluationFactory.scala | 6 +- .../interpreter/ScriptInterpreter.scala | 5 +- .../script/locktime/LocktimeOperations.scala | 3 +- .../script/reserved/ReservedOperations.scala | 30 +- .../core/script/splice/SpliceOperations.scala | 3 +- .../serializers/RawBitcoinSerializer.scala | 2 +- .../RawBitcoinSerializerHelper.scala | 2 +- .../blockchain/RawMerkleBlockSerializer.scala | 3 +- .../serializers/script/ScriptParser.scala | 3 +- .../scala/org/bitcoins/core/util/Base58.scala | 2 +- .../core/util/BitcoinScriptUtil.scala | 2 +- .../org/bitcoins/core/util/BytesUtil.scala | 53 +++ .../org/bitcoins/core/util/NumberUtil.scala | 1 - .../builder/FinalizedTxWithSigningInfo.scala | 28 ++ .../core/wallet/builder/RawTxBuilder.scala | 182 ++++++++ .../wallet/builder/RawTxBuilderResult.scala | 31 ++ .../core/wallet/builder/RawTxFinalizer.scala | 205 +++++++++ .../core/wallet/builder/RawTxSigner.scala | 148 +++++++ .../core/wallet/builder/TxBuilderError.scala | 8 +- .../bitcoins/core/wallet/signer/Signer.scala | 8 +- .../bitcoins/core/wallet/utxo/InputInfo.scala | 34 +- .../bitcoins/crypto/BouncyCastleUtil.scala | 2 +- ...{BytesUtil.scala => CryptoBytesUtil.scala} | 8 +- .../bitcoins/crypto/ECDigitalSignature.scala | 4 +- .../scala/org/bitcoins/crypto/ECKey.scala | 13 +- .../scala/org/bitcoins/crypto/Factory.scala | 4 +- docs/core/txbuilder.md | 87 ++-- .../eclair/rpc/client/EclairRpcClient.scala | 4 +- .../testkit/core/gen/PSBTGenerators.scala | 60 +-- .../testkit/util/BitcoinSAsyncTest.scala | 3 +- .../org/bitcoins/testkit/util/TestUtil.scala | 23 +- .../scala/org/bitcoins/wallet/Wallet.scala | 59 ++- .../internal/FundTransactionHandling.scala | 81 ++-- .../wallet/models/TransactionDb.scala | 3 +- .../org/bitcoins/zmq/ZMQSubscriberTest.scala | 2 +- 115 files changed, 2549 insertions(+), 455 deletions(-) create mode 100644 core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TxUtilTest.scala create mode 100644 core-test/src/test/scala/org/bitcoins/core/wallet/builder/NonInteractiveWithChangeFinalizerTest.scala create mode 100644 core-test/src/test/scala/org/bitcoins/core/wallet/builder/RawTxSignerTest.scala create mode 100644 core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputSigningInfoTest.scala create mode 100644 core/src/main/scala/org/bitcoins/core/protocol/transaction/InputUtil.scala create mode 100644 core/src/main/scala/org/bitcoins/core/protocol/transaction/TxUtil.scala create mode 100644 core/src/main/scala/org/bitcoins/core/util/BytesUtil.scala create mode 100644 core/src/main/scala/org/bitcoins/core/wallet/builder/FinalizedTxWithSigningInfo.scala create mode 100644 core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxBuilder.scala create mode 100644 core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxBuilderResult.scala create mode 100644 core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxFinalizer.scala create mode 100644 core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxSigner.scala rename crypto/src/main/scala/org/bitcoins/crypto/{BytesUtil.scala => CryptoBytesUtil.scala} (94%) diff --git a/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonWriters.scala b/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonWriters.scala index 970570349d..e9fea5de93 100644 --- a/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonWriters.scala +++ b/app-commons/src/main/scala/org/bitcoins/commons/serializers/JsonWriters.scala @@ -11,7 +11,8 @@ import org.bitcoins.core.protocol.ln.currency.MilliSatoshis import org.bitcoins.core.protocol.script.ScriptPubKey import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput} import org.bitcoins.core.script.crypto._ -import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest, DoubleSha256DigestBE} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE} import play.api.libs.json._ import scala.collection.mutable diff --git a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MempoolRpc.scala b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MempoolRpc.scala index c4e85d0e30..b61461801e 100644 --- a/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MempoolRpc.scala +++ b/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/client/common/MempoolRpc.scala @@ -99,15 +99,19 @@ trait MempoolRpc { self: Client => getMemPoolEntry(txid.flip) } - def getMemPoolEntryOpt(txid: DoubleSha256Digest): Future[Option[GetMemPoolEntryResult]] = { + def getMemPoolEntryOpt( + txid: DoubleSha256Digest): Future[Option[GetMemPoolEntryResult]] = { getMemPoolEntryOpt(txid.flip) } - def getMemPoolEntryOpt(txid: DoubleSha256DigestBE): Future[Option[GetMemPoolEntryResult]] = { - getMemPoolEntry(txid).map(Some(_)) - .recover { case _: BitcoindException.InvalidAddressOrKey => - None - } + def getMemPoolEntryOpt( + txid: DoubleSha256DigestBE): Future[Option[GetMemPoolEntryResult]] = { + getMemPoolEntry(txid) + .map(Some(_)) + .recover { + case _: BitcoindException.InvalidAddressOrKey => + None + } } def getMemPoolInfo: Future[GetMemPoolInfoResult] = { diff --git a/core-test/src/test/scala/org/bitcoins/core/config/NetworkParametersTest.scala b/core-test/src/test/scala/org/bitcoins/core/config/NetworkParametersTest.scala index a419cea010..146d63f7e3 100644 --- a/core-test/src/test/scala/org/bitcoins/core/config/NetworkParametersTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/config/NetworkParametersTest.scala @@ -1,6 +1,6 @@ package org.bitcoins.core.config -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest /** diff --git a/core-test/src/test/scala/org/bitcoins/core/consensus/MerkleTest.scala b/core-test/src/test/scala/org/bitcoins/core/consensus/MerkleTest.scala index c89f4e7fc5..ecbf7c0699 100644 --- a/core-test/src/test/scala/org/bitcoins/core/consensus/MerkleTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/consensus/MerkleTest.scala @@ -2,7 +2,8 @@ package org.bitcoins.core.consensus import org.bitcoins.core.protocol.blockchain.{Block, MainNetChainParams} import org.bitcoins.core.protocol.transaction.{Transaction, WitnessTransaction} -import org.bitcoins.crypto.{BytesUtil, CryptoUtil, DoubleSha256Digest} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.{CryptoUtil, DoubleSha256Digest} import org.bitcoins.testkit.util.BitcoinSUnitTest import scala.util.Try diff --git a/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureSerializerTest.scala index 2c11db8ee8..d19b06f039 100644 --- a/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureSerializerTest.scala @@ -8,7 +8,7 @@ import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.script.crypto._ import org.bitcoins.core.serializers.script.ScriptParser import org.bitcoins.core.util._ -import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest} +import org.bitcoins.crypto.DoubleSha256Digest import org.scalatest.{FlatSpec, MustMatchers} import scala.util.Try diff --git a/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkHeaderTest.scala b/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkHeaderTest.scala index bd2895c1af..c7fd7cc5f1 100644 --- a/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkHeaderTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/p2p/NetworkHeaderTest.scala @@ -5,7 +5,8 @@ import org.bitcoins.core.number.UInt32 import org.bitcoins.testkit.node.NodeTestUtil import org.bitcoins.testkit.util.BitcoinSUnitTest import org.bitcoins.core.config.MainNet -import org.bitcoins.crypto.{BytesUtil, CryptoUtil} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.CryptoUtil import scala.util.Random import scodec.bits.ByteVector diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/CompactSizeUIntTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/CompactSizeUIntTest.scala index 3180f8f23f..f424cbf2b8 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/CompactSizeUIntTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/CompactSizeUIntTest.scala @@ -2,7 +2,7 @@ package org.bitcoins.core.protocol import org.bitcoins.core.number.UInt64 import org.bitcoins.core.protocol.script.ScriptSignature -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil} import scodec.bits.ByteVector diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/ChainParamsTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/ChainParamsTest.scala index 17fbd66dbf..5b0253ecaa 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/ChainParamsTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/ChainParamsTest.scala @@ -10,7 +10,7 @@ import org.bitcoins.core.protocol.transaction.{ TransactionConstants, TransactionOutput } -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest /** diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/MerkleBlockTests.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/MerkleBlockTests.scala index 48393d29a0..0b3311b4b8 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/MerkleBlockTests.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/MerkleBlockTests.scala @@ -3,7 +3,8 @@ package org.bitcoins.core.protocol.blockchain import org.bitcoins.core.bloom._ import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.transaction.TransactionOutPoint -import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest, ECPublicKey} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.{DoubleSha256Digest, ECPublicKey} import org.bitcoins.testkit.util.BitcoinSUnitTest /** diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/PartialMerkleTreeTests.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/PartialMerkleTreeTests.scala index ff81436d52..fd29fe1469 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/PartialMerkleTreeTests.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/blockchain/PartialMerkleTreeTests.scala @@ -2,8 +2,8 @@ package org.bitcoins.core.protocol.blockchain import org.bitcoins.core.bloom.{BloomFilter, BloomUpdateAll} import org.bitcoins.core.number.UInt32 -import org.bitcoins.core.util.{Leaf, Node} -import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest} +import org.bitcoins.core.util.{BytesUtil, Leaf, Node} +import org.bitcoins.crypto.DoubleSha256Digest import org.bitcoins.testkit.util.BitcoinSUnitTest import scodec.bits.BitVector diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/script/ScriptPubKeyFactoryTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/script/ScriptPubKeyFactoryTest.scala index 91b8dab20e..94042c16ef 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/script/ScriptPubKeyFactoryTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/script/ScriptPubKeyFactoryTest.scala @@ -1,6 +1,6 @@ package org.bitcoins.core.protocol.script -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil} /** diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/script/ScriptSignatureFactoryTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/script/ScriptSignatureFactoryTest.scala index b390786df4..74409261c9 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/script/ScriptSignatureFactoryTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/script/ScriptSignatureFactoryTest.scala @@ -1,6 +1,7 @@ package org.bitcoins.core.protocol.script -import org.bitcoins.crypto.{BytesUtil, ECDigitalSignature, ECPublicKey} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey} import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil} import scodec.bits.ByteVector diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/script/ScriptSignatureTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/script/ScriptSignatureTest.scala index 6b7197e8ce..c50d32cb25 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/script/ScriptSignatureTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/script/ScriptSignatureTest.scala @@ -7,14 +7,15 @@ import org.bitcoins.core.policy.Policy import org.bitcoins.core.protocol.script.testprotocol.SignatureHashTestCase import org.bitcoins.core.protocol.transaction.{ BaseTransaction, + NonWitnessTransaction, Transaction, TransactionOutput, WitnessTransaction } import org.bitcoins.core.script.crypto.{HashType, SIGHASH_ALL} import org.bitcoins.core.serializers.script.RawScriptSignatureParser -import org.bitcoins.core.util.BitcoinSLogger -import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest, ECDigitalSignature} +import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil} +import org.bitcoins.crypto.{DoubleSha256Digest, ECDigitalSignature} import org.bitcoins.testkit.util.TestUtil import org.scalatest.{FlatSpec, MustMatchers} import scodec.bits.ByteVector @@ -147,7 +148,7 @@ class ScriptSignatureTest extends FlatSpec with MustMatchers { Transaction(testCase.transaction.hex) must be(testCase.transaction) val output = TransactionOutput(CurrencyUnits.zero, testCase.script) val txSigComponent = testCase.transaction match { - case btx: BaseTransaction => + case btx: NonWitnessTransaction => BaseTxSigComponent(btx, testCase.inputIndex, output, diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala index 4212a33302..6b444aabdd 100644 --- a/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala @@ -13,7 +13,6 @@ import org.bitcoins.core.protocol.transaction.testprotocol.CoreTransactionTestCa import org.bitcoins.core.script.PreExecutionScriptProgram import org.bitcoins.core.script.interpreter.ScriptInterpreter import org.bitcoins.core.script.result.ScriptOk -import org.bitcoins.core.serializers.transaction.RawBaseTransactionParser import org.bitcoins.crypto.CryptoUtil import org.bitcoins.testkit.core.gen.TransactionGenerators import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil} @@ -55,7 +54,7 @@ class TransactionTest extends BitcoinSUnitTest { it must "derive the correct txid from the transaction contents" in { //https://btc.blockr.io/api/v1/tx/raw/cddda897b0e9322937ee1f4fd5d6147d60f04a0f4d3b461e4f87066ac3918f2a - val tx = RawBaseTransactionParser.read( + val tx = BaseTransaction( "01000000020df1e23002ddf909aec026b1cf0c3b6b7943c042f22e25dbd0441855e6b39ee900000000fdfd00004730440220028c02f14654a0cc12c7e3229adb09d5d35bebb6ba1057e39adb1b2706607b0d0220564fab12c6da3d5acef332406027a7ff1cbba980175ffd880e1ba1bf40598f6b014830450221009362f8d67b60773745e983d07ba10efbe566127e244b724385b2ca2e47292dda022033def393954c320653843555ddbe7679b35cc1cacfe1dad923977de8cd6cc6d7014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffffd11533b0f283fca193e361a91ca7ddfc66592e20fd6eaf5dc0f1ef5fed05818000000000fdfe0000483045022100b4062edd75b5b3117f28ba937ed737b10378f762d7d374afabf667180dedcc62022005d44c793a9d787197e12d5049da5e77a09046014219b31e9c6b89948f648f1701483045022100b3b0c0273fc2c531083701f723e03ea3d9111e4bbca33bdf5b175cec82dcab0802206650462db37f9b4fe78da250a3b339ab11e11d84ace8f1b7394a1f6db0960ba4014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffff02500f1e00000000001976a9147ecaa33ef3cd6169517e43188ad3c034db091f5e88ac204e0000000000001976a914321908115d8a138942f98b0b53f86c9a1848501a88ac00000000") tx.txId.flip.bytes must be( @@ -195,7 +194,7 @@ class TransactionTest extends BitcoinSUnitTest { scriptPubKey match { case p2sh: P2SHScriptPubKey => tx match { - case btx: BaseTransaction => + case btx: NonWitnessTransaction => BaseTxSigComponent(transaction = btx, inputIndex = UInt32(inputIndex), output = TransactionOutput(amount, p2sh), @@ -209,7 +208,7 @@ class TransactionTest extends BitcoinSUnitTest { } case wit: WitnessScriptPubKey => tx match { - case btx: BaseTransaction => + case btx: NonWitnessTransaction => BaseTxSigComponent(transaction = btx, inputIndex = UInt32(inputIndex), output = TransactionOutput(amount, wit), @@ -275,7 +274,7 @@ class TransactionTest extends BitcoinSUnitTest { scriptPubKey match { case p2sh: P2SHScriptPubKey => tx match { - case btx: BaseTransaction => + case btx: NonWitnessTransaction => BaseTxSigComponent( transaction = btx, inputIndex = UInt32(inputIndex), @@ -290,7 +289,7 @@ class TransactionTest extends BitcoinSUnitTest { } case wit: WitnessScriptPubKey => tx match { - case btx: BaseTransaction => + case btx: NonWitnessTransaction => BaseTxSigComponent( transaction = btx, inputIndex = UInt32(inputIndex), diff --git a/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TxUtilTest.scala b/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TxUtilTest.scala new file mode 100644 index 0000000000..087d71751f --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TxUtilTest.scala @@ -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) + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTTest.scala b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTTest.scala index 454c755740..f76434942f 100644 --- a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTTest.scala @@ -56,7 +56,7 @@ class PSBTTest extends BitcoinSAsyncTest { it must "correctly update PSBTs' inputs" in { forAllAsync(PSBTGenerators.psbtToBeSigned)(_.flatMap { - case (fullPsbt, utxos) => + case (fullPsbt, utxos, _) => val emptyPsbt = PSBT.fromUnsignedTx(fullPsbt.transaction) val infoAndTxs = PSBTGenerators @@ -93,7 +93,7 @@ class PSBTTest extends BitcoinSAsyncTest { it must "correctly construct and sign a PSBT" in { forAllAsync(PSBTGenerators.psbtToBeSigned) { psbtWithBuilderF => psbtWithBuilderF.flatMap { - case (psbtNoSigs, utxos) => + case (psbtNoSigs, utxos, _) => val infos = utxos.toVector.zipWithIndex.map { case (utxo: ScriptSignatureParams[InputInfo], index) => (index, utxo) @@ -173,19 +173,17 @@ class PSBTTest extends BitcoinSAsyncTest { val maxFee = crediting - spending val fee = GenUtil.sample(CurrencyUnitGenerator.feeUnit(maxFee)) for { - (psbt, _) <- PSBTGenerators.psbtAndBuilderFromInputs( + (psbt, _, _) <- PSBTGenerators.psbtAndBuilderFromInputs( finalized = false, creditingTxsInfo = creditingTxsInfo, destinations = destinations, changeSPK = changeSPK, - network = network, fee = fee) - (expected, _) <- PSBTGenerators.psbtAndBuilderFromInputs( + (expected, _, _) <- PSBTGenerators.psbtAndBuilderFromInputs( finalized = true, creditingTxsInfo = creditingTxsInfo, destinations = destinations, changeSPK = changeSPK, - network = network, fee = fee) } yield { val finalizedPsbtOpt = psbt.finalizePSBT @@ -198,8 +196,8 @@ class PSBTTest extends BitcoinSAsyncTest { it must "agree with TxBuilder.sign given UTXOSpendingInfos" in { forAllAsync(PSBTGenerators.finalizedPSBTWithBuilder) { psbtAndBuilderF => for { - (psbt, builder) <- psbtAndBuilderF - signedTx <- builder.sign + (psbt, builder, fee) <- psbtAndBuilderF + signedTx <- builder.sign(fee) } yield { val txT = psbt.extractTransactionAndValidate assert(txT.isSuccess, txT.failed) diff --git a/core-test/src/test/scala/org/bitcoins/core/script/ScriptConstantFactoryTest.scala b/core-test/src/test/scala/org/bitcoins/core/script/ScriptConstantFactoryTest.scala index 8487d213a9..e9c1fa13a8 100644 --- a/core-test/src/test/scala/org/bitcoins/core/script/ScriptConstantFactoryTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/script/ScriptConstantFactoryTest.scala @@ -1,7 +1,7 @@ package org.bitcoins.core.script import org.bitcoins.core.script.constant.ScriptConstant -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest /** diff --git a/core-test/src/test/scala/org/bitcoins/core/script/ScriptOperationFactoryTest.scala b/core-test/src/test/scala/org/bitcoins/core/script/ScriptOperationFactoryTest.scala index 662b44c8e8..221e8d3420 100644 --- a/core-test/src/test/scala/org/bitcoins/core/script/ScriptOperationFactoryTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/script/ScriptOperationFactoryTest.scala @@ -8,7 +8,7 @@ import org.bitcoins.core.script.crypto.OP_RIPEMD160 import org.bitcoins.core.script.locktime.OP_CHECKLOCKTIMEVERIFY import org.bitcoins.core.script.splice.OP_SUBSTR import org.bitcoins.core.script.stack.OP_TOALTSTACK -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest /** diff --git a/core-test/src/test/scala/org/bitcoins/core/script/constant/ScriptNumberFactoryTest.scala b/core-test/src/test/scala/org/bitcoins/core/script/constant/ScriptNumberFactoryTest.scala index 0256b6c716..df3edfa950 100644 --- a/core-test/src/test/scala/org/bitcoins/core/script/constant/ScriptNumberFactoryTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/script/constant/ScriptNumberFactoryTest.scala @@ -1,6 +1,6 @@ package org.bitcoins.core.script.constant -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest /** diff --git a/core-test/src/test/scala/org/bitcoins/core/script/constant/ScriptNumberUtilTest.scala b/core-test/src/test/scala/org/bitcoins/core/script/constant/ScriptNumberUtilTest.scala index e6006a932a..b19a478130 100644 --- a/core-test/src/test/scala/org/bitcoins/core/script/constant/ScriptNumberUtilTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/script/constant/ScriptNumberUtilTest.scala @@ -1,6 +1,6 @@ package org.bitcoins.core.script.constant -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest /** diff --git a/core-test/src/test/scala/org/bitcoins/core/script/stack/StackInterpreterTest.scala b/core-test/src/test/scala/org/bitcoins/core/script/stack/StackInterpreterTest.scala index b17a5b467d..0bfcb0fc16 100644 --- a/core-test/src/test/scala/org/bitcoins/core/script/stack/StackInterpreterTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/script/stack/StackInterpreterTest.scala @@ -3,8 +3,7 @@ package org.bitcoins.core.script.stack import org.bitcoins.core.script.constant._ import org.bitcoins.core.script.result._ import org.bitcoins.core.script.ExecutedScriptProgram -import org.bitcoins.core.util.ScriptProgramTestUtil -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.{BytesUtil, ScriptProgramTestUtil} import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil} /** diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/blockchain/RawBlockHeaderSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/blockchain/RawBlockHeaderSerializerTest.scala index 4805d39299..c67bb51e34 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/blockchain/RawBlockHeaderSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/blockchain/RawBlockHeaderSerializerTest.scala @@ -1,7 +1,8 @@ package org.bitcoins.core.serializers.blockchain import org.bitcoins.core.number.{Int32, UInt32} -import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.DoubleSha256Digest import org.bitcoins.testkit.util.BitcoinSUnitTest import scodec.bits.ByteVector diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/blockchain/RawBlockSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/blockchain/RawBlockSerializerTest.scala index 8cd9ea7705..525584710c 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/blockchain/RawBlockSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/blockchain/RawBlockSerializerTest.scala @@ -3,7 +3,8 @@ package org.bitcoins.core.serializers.blockchain import org.bitcoins.core.number.{Int32, UInt32, UInt64} import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.protocol.transaction.Transaction -import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.DoubleSha256Digest import org.bitcoins.testkit.util.BitcoinSUnitTest import scodec.bits.ByteVector diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/blockchain/RawMerkleBlockSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/blockchain/RawMerkleBlockSerializerTest.scala index 4b143f85fa..44490cc144 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/blockchain/RawMerkleBlockSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/blockchain/RawMerkleBlockSerializerTest.scala @@ -6,8 +6,8 @@ import org.bitcoins.core.protocol.blockchain.{ MerkleBlock, PartialMerkleTree } -import org.bitcoins.core.util.{Leaf, Node} -import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest} +import org.bitcoins.core.util.{BytesUtil, Leaf, Node} +import org.bitcoins.crypto.DoubleSha256Digest import org.bitcoins.testkit.util.BitcoinSUnitTest import scodec.bits.BitVector diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawFilterAddMessageSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawFilterAddMessageSerializerTest.scala index 073fddca4b..18bcf30d11 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawFilterAddMessageSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawFilterAddMessageSerializerTest.scala @@ -1,6 +1,6 @@ package org.bitcoins.core.serializers.p2p.messages -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest class RawFilterAddMessageSerializerTest extends BitcoinSUnitTest { diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawFilterLoadMessageSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawFilterLoadMessageSerializerTest.scala index f54cb5f223..823afc0e6d 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawFilterLoadMessageSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawFilterLoadMessageSerializerTest.scala @@ -3,7 +3,7 @@ package org.bitcoins.core.serializers.p2p.messages import org.bitcoins.core.bloom.BloomUpdateNone import org.bitcoins.core.number.{UInt32, UInt64} import org.bitcoins.core.protocol.CompactSizeUInt -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest /** diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawGetBlocksMessageSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawGetBlocksMessageSerializerTest.scala index bf764e68b2..7f07f2c2bd 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawGetBlocksMessageSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawGetBlocksMessageSerializerTest.scala @@ -3,7 +3,8 @@ package org.bitcoins.core.serializers.p2p.messages import org.bitcoins.core.number.UInt64 import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.p2p._ -import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.DoubleSha256Digest import org.bitcoins.testkit.util.BitcoinSUnitTest /** diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawHeadersMessageSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawHeadersMessageSerializerTest.scala index 1620526b42..bcb847f1cc 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawHeadersMessageSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawHeadersMessageSerializerTest.scala @@ -2,8 +2,8 @@ package org.bitcoins.core.serializers.p2p.messages import org.bitcoins.core.number.{UInt32, UInt64} import org.bitcoins.core.protocol.CompactSizeUInt -import org.bitcoins.core.util.BitcoinSLogger -import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest} +import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil} +import org.bitcoins.crypto.DoubleSha256Digest import org.bitcoins.testkit.util.BitcoinSUnitTest /** diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawInventorySerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawInventorySerializerTest.scala index 45e7ac7dd3..a952837c55 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawInventorySerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawInventorySerializerTest.scala @@ -1,7 +1,8 @@ package org.bitcoins.core.serializers.p2p.messages import org.bitcoins.core.p2p.TypeIdentifier.MsgTx -import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.DoubleSha256Digest import org.bitcoins.testkit.util.BitcoinSUnitTest /** diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawMerkleBlockMessageSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawMerkleBlockMessageSerializerTest.scala index 68e3b1c5b7..60842e00c6 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawMerkleBlockMessageSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawMerkleBlockMessageSerializerTest.scala @@ -2,7 +2,8 @@ package org.bitcoins.core.serializers.p2p.messages import org.bitcoins.core.number.{UInt32, UInt64} import org.bitcoins.core.protocol.CompactSizeUInt -import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.DoubleSha256Digest import org.bitcoins.testkit.util.BitcoinSUnitTest import scodec.bits.BitVector diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawRejectMessageSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawRejectMessageSerializerTest.scala index eadf8472e6..d4a354cf3f 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawRejectMessageSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawRejectMessageSerializerTest.scala @@ -2,7 +2,7 @@ package org.bitcoins.core.serializers.p2p.messages import org.bitcoins.core.number.UInt64 import org.bitcoins.core.protocol.CompactSizeUInt -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest /** diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawTransactionMessageSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawTransactionMessageSerializerTest.scala index 84f0cd9291..fd8d66ff05 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawTransactionMessageSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawTransactionMessageSerializerTest.scala @@ -1,6 +1,6 @@ package org.bitcoins.core.serializers.p2p.messages -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.node.NodeTestUtil import org.bitcoins.testkit.util.BitcoinSUnitTest diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawVersionMessageSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawVersionMessageSerializerTest.scala index 4b1496e870..c6b1ea269a 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawVersionMessageSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/p2p/messages/RawVersionMessageSerializerTest.scala @@ -5,7 +5,7 @@ import java.net.InetSocketAddress import org.bitcoins.core.number.{Int32, Int64, UInt64} import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.p2p._ -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest class RawVersionMessageSerializerTest extends BitcoinSUnitTest { diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/script/RawScriptPubKeyParserTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/script/RawScriptPubKeyParserTest.scala index 690e56be67..dc4c977e24 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/script/RawScriptPubKeyParserTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/script/RawScriptPubKeyParserTest.scala @@ -5,7 +5,7 @@ import org.bitcoins.core.script.bitwise.OP_EQUALVERIFY import org.bitcoins.core.script.constant._ import org.bitcoins.core.script.crypto.{OP_CHECKSIG, OP_HASH160} import org.bitcoins.core.script.stack.OP_DUP -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil} import scodec.bits.ByteVector diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/script/RawScriptSignatureParserTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/script/RawScriptSignatureParserTest.scala index 71f5e057c2..eeae33b585 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/script/RawScriptSignatureParserTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/script/RawScriptSignatureParserTest.scala @@ -2,7 +2,7 @@ package org.bitcoins.core.serializers.script import org.bitcoins.core.protocol.script.{EmptyScriptSignature, ScriptSignature} import org.bitcoins.core.script.constant._ -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil} import scodec.bits.ByteVector diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/script/ScriptParserTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/script/ScriptParserTest.scala index 05935fcdc0..c26e61a036 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/script/ScriptParserTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/script/ScriptParserTest.scala @@ -13,7 +13,7 @@ import org.bitcoins.core.script.locktime.OP_CHECKLOCKTIMEVERIFY import org.bitcoins.core.script.reserved.OP_NOP import org.bitcoins.core.script.splice.OP_SIZE import org.bitcoins.core.script.stack.{OP_DROP, OP_DUP, OP_PICK, OP_SWAP} -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil} import scodec.bits.ByteVector diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawBaseTransactionParserTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawBaseTransactionParserTest.scala index 706ffd2302..c7fa2c8d89 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawBaseTransactionParserTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawBaseTransactionParserTest.scala @@ -2,10 +2,11 @@ package org.bitcoins.core.serializers.transaction import org.bitcoins.core.number.{Int32, UInt32} import org.bitcoins.core.protocol.transaction.{ + BaseTransaction, Transaction, TransactionConstants } -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.{BitcoinSUnitTest, TestUtil} import scodec.bits.ByteVector @@ -15,7 +16,7 @@ import scodec.bits.ByteVector class RawBaseTransactionParserTest extends BitcoinSUnitTest { val encode = BytesUtil.encodeHex(_: ByteVector) "RawBaseTransactionParser" must "parse a raw transaction" in { - val tx: Transaction = RawBaseTransactionParser.read(TestUtil.rawTransaction) + val tx: Transaction = BaseTransaction(TestUtil.rawTransaction) tx.version must be(Int32.one) tx.inputs.size must be(2) tx.outputs.size must be(2) @@ -29,7 +30,7 @@ class RawBaseTransactionParserTest extends BitcoinSUnitTest { //txid bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74 val rawTx = "0100000002fc37adbd036fb51b3f4f6f70474270939d6ff8c4ea697639f2b57dd6359e3070010000008b483045022100ad8e961fe3c22b2647d92b078f4c0cf81b3106ea5bf8b900ab8646aa4430216f022071d4edc2b5588be20ac4c2d07edd8ed069e10b2402d3dce2d3b835ccd075f283014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746e0000000036219231b3043efdfb9405bbc2610baa73e340dddfe9c2a07b09bd3785ca6330000000008b483045022100cb097f8720d0c4665e8771fff5181b30584fd9e7d437fae21b440c94fe76d56902206f9b539ae26ec9688c54272d6a3309d93f17fb9835f382fff1ebeead84af2763014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746effffffff02905f0100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988aca0860100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988ac77d3a655" - val tx: Transaction = RawBaseTransactionParser.read(rawTx) + val tx: Transaction = BaseTransaction(rawTx) tx.txId.hex must be( BytesUtil.flipEndianness( "bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74")) @@ -40,8 +41,8 @@ class RawBaseTransactionParserTest extends BitcoinSUnitTest { //txid bdc221db675c06dbee2ae75d33e31cad4e2555efea10c337ff32c8cdf97f8e74 val rawTxWithLockTime = "0100000002fc37adbd036fb51b3f4f6f70474270939d6ff8c4ea697639f2b57dd6359e3070010000008b483045022100ad8e961fe3c22b2647d92b078f4c0cf81b3106ea5bf8b900ab8646aa4430216f022071d4edc2b5588be20ac4c2d07edd8ed069e10b2402d3dce2d3b835ccd075f283014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746e0000000036219231b3043efdfb9405bbc2610baa73e340dddfe9c2a07b09bd3785ca6330000000008b483045022100cb097f8720d0c4665e8771fff5181b30584fd9e7d437fae21b440c94fe76d56902206f9b539ae26ec9688c54272d6a3309d93f17fb9835f382fff1ebeead84af2763014104fa79182bbc26c708b5d9f36b8635947d4a834ea356cf612ede08395c295f962e0b1dc2557aba34188640e51a58ed547f2c89c8265cd0c04ff890d8435648746effffffff02905f0100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988aca0860100000000001976a914a45bc47d00c3d2b0d0ea37cbf74b94cd1986ea7988ac77d3a655" - val tx = RawBaseTransactionParser.read(rawTxWithLockTime) - val serializedTx = RawBaseTransactionParser.write(tx) + val tx = BaseTransaction(rawTxWithLockTime) + val serializedTx = tx.bytes encode(serializedTx) must be(rawTxWithLockTime) } @@ -50,20 +51,20 @@ class RawBaseTransactionParserTest extends BitcoinSUnitTest { val rawTx = "01000000020df1e23002ddf909aec026b1cf0c3b6b7943c042f22e25dbd0441855e6b39ee900000000fdfd00004730440220028c02f14654a0cc12c7e3229adb09d5d35bebb6ba1057e39adb1b2706607b0d0220564fab12c6da3d5acef332406027a7ff1cbba980175ffd880e1ba1bf40598f6b014830450221009362f8d67b60773745e983d07ba10efbe566127e244b724385b2ca2e47292dda022033def393954c320653843555ddbe7679b35cc1cacfe1dad923977de8cd6cc6d7014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffffd11533b0f283fca193e361a91ca7ddfc66592e20fd6eaf5dc0f1ef5fed05818000000000fdfe0000483045022100b4062edd75b5b3117f28ba937ed737b10378f762d7d374afabf667180dedcc62022005d44c793a9d787197e12d5049da5e77a09046014219b31e9c6b89948f648f1701483045022100b3b0c0273fc2c531083701f723e03ea3d9111e4bbca33bdf5b175cec82dcab0802206650462db37f9b4fe78da250a3b339ab11e11d84ace8f1b7394a1f6db0960ba4014c695221025e9adcc3d65c11346c8a6069d6ebf5b51b348d1d6dc4b95e67480c34dc0bc75c21030585b3c80f4964bf0820086feda57c8e49fa1eab925db7c04c985467973df96521037753a5e3e9c4717d3f81706b38a6fb82b5fb89d29e580d7b98a37fea8cdefcad53aeffffffff02500f1e00000000001976a9147ecaa33ef3cd6169517e43188ad3c034db091f5e88ac204e0000000000001976a914321908115d8a138942f98b0b53f86c9a1848501a88ac00000000" - val tx = RawBaseTransactionParser.read(rawTx) - val serializedTx = RawBaseTransactionParser.write(tx) + val tx = BaseTransaction(rawTx) + val serializedTx = tx.bytes encode(serializedTx) must be(rawTx) } it must "read then write a simple raw transaction with one input and two outputs" in { val rawTx = TestUtil.simpleRawTransaction - val tx = RawBaseTransactionParser.read(rawTx) - val serializedTx = RawBaseTransactionParser.write(tx) + val tx = BaseTransaction(rawTx) + val serializedTx = tx.bytes encode(serializedTx) must be(rawTx) } it must "parse a transaction with one input and two outputs" in { - val tx = RawBaseTransactionParser.read(TestUtil.parentSimpleRawTransaction) + val tx = BaseTransaction(TestUtil.parentSimpleRawTransaction) tx.inputs.size must be(1) tx.inputs.head.scriptSignature.hex must be( "6a4730440220048e15422cf62349dc586ffb8c749d40280781edd5064ff27a5910ff5cf225a802206a82685dbc2cf195d158c29309939d5a3cd41a889db6f766f3809fff35722305012103dcfc9882c1b3ae4e03fb6cac08bdb39e284e81d70c7aa8b27612457b2774509b") diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawTransactionInputParserTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawTransactionInputParserTest.scala index 4b58846859..77af82f2fe 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawTransactionInputParserTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawTransactionInputParserTest.scala @@ -5,8 +5,7 @@ import org.bitcoins.core.protocol.transaction.{ TransactionConstants, TransactionInput } -import org.bitcoins.core.util.BitcoinSLogger -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil} import org.bitcoins.testkit.util.TestUtil import org.scalatest.{FlatSpec, MustMatchers} import scodec.bits.ByteVector diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawTransactionOutPointParserTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawTransactionOutPointParserTest.scala index 9331eb725b..3cfa2796ca 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawTransactionOutPointParserTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawTransactionOutPointParserTest.scala @@ -1,7 +1,7 @@ package org.bitcoins.core.serializers.transaction import org.bitcoins.core.number.UInt32 -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest import scodec.bits.ByteVector diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawTransactionOutputParserTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawTransactionOutputParserTest.scala index 510b97c708..05234bf502 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawTransactionOutputParserTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawTransactionOutputParserTest.scala @@ -9,7 +9,7 @@ import org.bitcoins.core.protocol.transaction.{ import org.bitcoins.core.script.bitwise.OP_EQUAL import org.bitcoins.core.script.constant.{BytesToPushOntoStack, ScriptConstant} import org.bitcoins.core.script.crypto.OP_HASH160 -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest import scodec.bits.ByteVector diff --git a/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawWitnessTransactionParserTest.scala b/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawWitnessTransactionParserTest.scala index 78d2983315..301dfb0521 100644 --- a/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawWitnessTransactionParserTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/serializers/transaction/RawWitnessTransactionParserTest.scala @@ -1,6 +1,7 @@ package org.bitcoins.core.serializers.transaction -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.protocol.transaction.WitnessTransaction +import org.bitcoins.core.util.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest class RawWitnessTransactionParserTest extends BitcoinSUnitTest { @@ -8,8 +9,8 @@ class RawWitnessTransactionParserTest extends BitcoinSUnitTest { "RawWitnessTransactionParser" must "serialize and deserialize a wtx" in { val hex = "0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f85603000000171600141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b928ffffffff019caef505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02483045022100f764287d3e99b1474da9bec7f7ed236d6c81e793b20c4b5aa1f3051b9a7daa63022016a198031d5554dbb855bdbe8534776a4be6958bd8d530dc001c32b828f6f0ab0121038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990ac00000000" - val wtx = RawWitnessTransactionParser.read(hex) - val bytes = RawWitnessTransactionParser.write(wtx) + val wtx = WitnessTransaction(hex) + val bytes = wtx.bytes BytesUtil.encodeHex(bytes) must be(hex) } } diff --git a/core-test/src/test/scala/org/bitcoins/core/util/Base58Test.scala b/core-test/src/test/scala/org/bitcoins/core/util/Base58Test.scala index 1a03f6be19..73eed0c35b 100644 --- a/core-test/src/test/scala/org/bitcoins/core/util/Base58Test.scala +++ b/core-test/src/test/scala/org/bitcoins/core/util/Base58Test.scala @@ -1,7 +1,6 @@ package org.bitcoins.core.util import org.bitcoins.core.util.testprotocol._ -import org.bitcoins.crypto.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest import spray.json._ diff --git a/core-test/src/test/scala/org/bitcoins/core/util/BitcoinSUtilSpec.scala b/core-test/src/test/scala/org/bitcoins/core/util/BitcoinSUtilSpec.scala index bbf68073a1..f701580c0a 100644 --- a/core-test/src/test/scala/org/bitcoins/core/util/BitcoinSUtilSpec.scala +++ b/core-test/src/test/scala/org/bitcoins/core/util/BitcoinSUtilSpec.scala @@ -1,6 +1,5 @@ package org.bitcoins.core.util -import org.bitcoins.crypto.BytesUtil import org.bitcoins.testkit.core.gen.StringGenerators import org.scalacheck.{Prop, Properties} diff --git a/core-test/src/test/scala/org/bitcoins/core/util/BitcoinSUtilTest.scala b/core-test/src/test/scala/org/bitcoins/core/util/BitcoinSUtilTest.scala index 4cb515b256..8a839ca846 100644 --- a/core-test/src/test/scala/org/bitcoins/core/util/BitcoinSUtilTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/util/BitcoinSUtilTest.scala @@ -1,6 +1,5 @@ package org.bitcoins.core.util -import org.bitcoins.crypto.BytesUtil import org.bitcoins.testkit.util.BitcoinSUnitTest import scodec.bits.BitVector diff --git a/core-test/src/test/scala/org/bitcoins/core/util/CryptoUtilTest.scala b/core-test/src/test/scala/org/bitcoins/core/util/CryptoUtilTest.scala index 4bfdff5bcb..3a1c4d7260 100644 --- a/core-test/src/test/scala/org/bitcoins/core/util/CryptoUtilTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/util/CryptoUtilTest.scala @@ -1,6 +1,6 @@ package org.bitcoins.core.util -import org.bitcoins.crypto.{BytesUtil, CryptoUtil} +import org.bitcoins.crypto.CryptoUtil import org.bitcoins.testkit.core.gen.CryptoGenerators import org.bitcoins.testkit.util.BitcoinSUnitTest import scodec.bits._ diff --git a/core-test/src/test/scala/org/bitcoins/core/util/NumberUtilSpec.scala b/core-test/src/test/scala/org/bitcoins/core/util/NumberUtilSpec.scala index a30cf9291b..5965d01c96 100644 --- a/core-test/src/test/scala/org/bitcoins/core/util/NumberUtilSpec.scala +++ b/core-test/src/test/scala/org/bitcoins/core/util/NumberUtilSpec.scala @@ -2,7 +2,6 @@ package org.bitcoins.core.util import org.bitcoins.testkit.core.gen.NumberGenerator import org.bitcoins.core.number.UInt8 -import org.bitcoins.crypto.BytesUtil import org.scalacheck.{Prop, Properties} /** diff --git a/core-test/src/test/scala/org/bitcoins/core/wallet/builder/NonInteractiveWithChangeFinalizerTest.scala b/core-test/src/test/scala/org/bitcoins/core/wallet/builder/NonInteractiveWithChangeFinalizerTest.scala new file mode 100644 index 0000000000..abc36ebfef --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/wallet/builder/NonInteractiveWithChangeFinalizerTest.scala @@ -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 + ) + } + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/wallet/builder/RawTxSignerTest.scala b/core-test/src/test/scala/org/bitcoins/core/wallet/builder/RawTxSignerTest.scala new file mode 100644 index 0000000000..d4f27051ef --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/wallet/builder/RawTxSignerTest.scala @@ -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)) + } + } + } +} diff --git a/core-test/src/test/scala/org/bitcoins/core/wallet/signer/SignerTest.scala b/core-test/src/test/scala/org/bitcoins/core/wallet/signer/SignerTest.scala index 7f6fee725d..cdab0dcc52 100644 --- a/core-test/src/test/scala/org/bitcoins/core/wallet/signer/SignerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/wallet/signer/SignerTest.scala @@ -39,7 +39,10 @@ import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature import org.bitcoins.core.psbt.PSBT import org.bitcoins.core.script.PreExecutionScriptProgram import org.bitcoins.core.script.interpreter.ScriptInterpreter -import org.bitcoins.core.wallet.builder.BitcoinTxBuilder +import org.bitcoins.core.wallet.builder.{ + NonInteractiveWithChangeFinalizer, + RawTxSigner +} import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte import org.bitcoins.core.wallet.utxo.{ ECSignatureParams, @@ -52,7 +55,6 @@ import org.bitcoins.core.wallet.utxo.{ } import org.bitcoins.crypto.ECDigitalSignature import org.bitcoins.testkit.core.gen.{ - ChainParamsGenerator, CreditingTxGen, GenUtil, ScriptGenerators, @@ -121,19 +123,19 @@ class SignerTest extends BitcoinSAsyncTest { it must "sign a mix of spks in a tx and then verify that single signing agrees" in { forAllAsync(CreditingTxGen.inputsAndOutputs(), - ScriptGenerators.scriptPubKey, - ChainParamsGenerator.bitcoinNetworkParams) { - case ((creditingTxsInfos, destinations), changeSPK, network) => + ScriptGenerators.scriptPubKey) { + case ((creditingTxsInfos, destinations), (changeSPK, _)) => val fee = SatoshisPerVirtualByte(Satoshis(1000)) for { - builder <- BitcoinTxBuilder(destinations, - creditingTxsInfos, - fee, - changeSPK._1, - network) - unsignedTx <- builder.unsignedTx - signedTx <- builder.sign + unsignedTx <- NonInteractiveWithChangeFinalizer.txFrom( + destinations, + creditingTxsInfos, + fee, + changeSPK) + signedTx <- RawTxSigner.sign(unsignedTx, + creditingTxsInfos.toVector, + fee) singleSigs: Vector[Vector[ECDigitalSignature]] <- { val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] = @@ -257,18 +259,16 @@ class SignerTest extends BitcoinSAsyncTest { it must "sign p2wsh inputs correctly when provided no witness data" in { forAllAsync(CreditingTxGen.inputsAndOutputs(CreditingTxGen.p2wshOutputs), - ScriptGenerators.scriptPubKey, - ChainParamsGenerator.bitcoinNetworkParams) { - case ((creditingTxsInfos, destinations), changeSPK, network) => + ScriptGenerators.scriptPubKey) { + case ((creditingTxsInfos, destinations), (changeSPK, _)) => val fee = SatoshisPerVirtualByte(Satoshis(100)) for { - builder <- BitcoinTxBuilder(destinations, - creditingTxsInfos, - fee, - changeSPK._1, - network) - unsignedTx <- builder.unsignedTx + unsignedTx <- NonInteractiveWithChangeFinalizer.txFrom( + destinations, + creditingTxsInfos, + fee, + changeSPK) singleSigs: Vector[Vector[PartialSignature]] <- { val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] = diff --git a/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputSigningInfoTest.scala b/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputSigningInfoTest.scala new file mode 100644 index 0000000000..e659a69645 --- /dev/null +++ b/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputSigningInfoTest.scala @@ -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 + ) + } + } +} diff --git a/core/src/main/scala/org/bitcoins/core/bloom/BloomFilter.scala b/core/src/main/scala/org/bitcoins/core/bloom/BloomFilter.scala index b1c610d151..a0a25de7e2 100644 --- a/core/src/main/scala/org/bitcoins/core/bloom/BloomFilter.scala +++ b/core/src/main/scala/org/bitcoins/core/bloom/BloomFilter.scala @@ -10,13 +10,12 @@ import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutPoint} import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.script.constant.{ScriptConstant, ScriptToken} import org.bitcoins.core.serializers.bloom.RawBloomFilterSerializer -import org.bitcoins.core.util.BitcoinSLogger +import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil} import scodec.bits.{BitVector, ByteVector} import scala.annotation.tailrec import scala.util.hashing.MurmurHash3 import org.bitcoins.crypto.{ - BytesUtil, CryptoUtil, DoubleSha256Digest, ECPublicKey, diff --git a/core/src/main/scala/org/bitcoins/core/consensus/Merkle.scala b/core/src/main/scala/org/bitcoins/core/consensus/Merkle.scala index df0db980f7..506fa6f737 100644 --- a/core/src/main/scala/org/bitcoins/core/consensus/Merkle.scala +++ b/core/src/main/scala/org/bitcoins/core/consensus/Merkle.scala @@ -2,7 +2,7 @@ package org.bitcoins.core.consensus import org.bitcoins.core.protocol.blockchain.Block import org.bitcoins.core.protocol.transaction.{ - BaseTransaction, + NonWitnessTransaction, Transaction, WitnessTransaction } @@ -94,8 +94,8 @@ trait Merkle extends BitcoinSLogger { def computeBlockWitnessMerkleTree(block: Block): MerkleTree = { val coinbaseWTxId = DoubleSha256Digest.empty val hashes = block.transactions.tail.map { - case wtx: WitnessTransaction => wtx.wTxId - case btx: BaseTransaction => btx.txId + case wtx: WitnessTransaction => wtx.wTxId + case btx: NonWitnessTransaction => btx.txId } build(coinbaseWTxId +: hashes) } diff --git a/core/src/main/scala/org/bitcoins/core/crypto/ECPrivateKeyUtil.scala b/core/src/main/scala/org/bitcoins/core/crypto/ECPrivateKeyUtil.scala index d6e13b97b5..da6eb1a387 100644 --- a/core/src/main/scala/org/bitcoins/core/crypto/ECPrivateKeyUtil.scala +++ b/core/src/main/scala/org/bitcoins/core/crypto/ECPrivateKeyUtil.scala @@ -1,8 +1,8 @@ package org.bitcoins.core.crypto import org.bitcoins.core.config.{NetworkParameters, Networks} -import org.bitcoins.core.util.Base58 -import org.bitcoins.crypto.{BytesUtil, CryptoUtil, ECPrivateKey} +import org.bitcoins.core.util.{Base58, BytesUtil} +import org.bitcoins.crypto.{CryptoUtil, ECPrivateKey} import scodec.bits.ByteVector import scala.util.{Failure, Success, Try} diff --git a/core/src/main/scala/org/bitcoins/core/crypto/ExtKey.scala b/core/src/main/scala/org/bitcoins/core/crypto/ExtKey.scala index 1c14688999..8b331de1e6 100644 --- a/core/src/main/scala/org/bitcoins/core/crypto/ExtKey.scala +++ b/core/src/main/scala/org/bitcoins/core/crypto/ExtKey.scala @@ -7,7 +7,6 @@ import org.bitcoins.core.util._ import org.bitcoins.crypto.{ BaseECKey, BouncyCastleUtil, - BytesUtil, CryptoUtil, ECDigitalSignature, ECPrivateKey, diff --git a/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureChecker.scala b/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureChecker.scala index 5836fbedd2..801ea2eae7 100644 --- a/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureChecker.scala +++ b/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureChecker.scala @@ -8,13 +8,8 @@ import org.bitcoins.core.script.crypto._ import org.bitcoins.core.script.flag.{ScriptFlag, ScriptFlagUtil} import org.bitcoins.core.script.result.ScriptErrorWitnessPubKeyType import org.bitcoins.core.policy.Policy -import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil} -import org.bitcoins.crypto.{ - BytesUtil, - DERSignatureUtil, - ECDigitalSignature, - ECPublicKey -} +import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil, BytesUtil} +import org.bitcoins.crypto.{DERSignatureUtil, ECDigitalSignature, ECPublicKey} import scodec.bits.ByteVector import scala.annotation.tailrec diff --git a/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala b/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala index 1311e1d7d0..a1aaf980c0 100644 --- a/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala +++ b/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala @@ -7,8 +7,8 @@ import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.script.constant.ScriptToken import org.bitcoins.core.script.crypto._ import org.bitcoins.core.serializers.transaction.RawTransactionOutputParser -import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil} -import org.bitcoins.crypto.{BytesUtil, CryptoUtil, DoubleSha256Digest} +import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil, BytesUtil} +import org.bitcoins.crypto.{CryptoUtil, DoubleSha256Digest} import scodec.bits.ByteVector /** diff --git a/core/src/main/scala/org/bitcoins/core/crypto/TxSigComponent.scala b/core/src/main/scala/org/bitcoins/core/crypto/TxSigComponent.scala index a1eece3fbf..44d13e2e02 100644 --- a/core/src/main/scala/org/bitcoins/core/crypto/TxSigComponent.scala +++ b/core/src/main/scala/org/bitcoins/core/crypto/TxSigComponent.scala @@ -53,7 +53,7 @@ object TxSigComponent { output.scriptPubKey match { case _: WitnessScriptPubKey => transaction match { - case _: BaseTransaction => + case _: NonWitnessTransaction => throw new IllegalArgumentException( s"Cannot spend from segwit output ($output) with a base transaction ($transaction)") case wtx: WitnessTransaction => @@ -64,7 +64,7 @@ object TxSigComponent { if (WitnessScriptPubKey.isWitnessScriptPubKey( p2shScriptSig.redeemScript.asm)) { transaction match { - case _: BaseTransaction => + case _: NonWitnessTransaction => throw new IllegalArgumentException( s"Cannot spend from segwit output ($output) with a base transaction ($transaction)") case wtx: WitnessTransaction => @@ -77,6 +77,19 @@ object TxSigComponent { BaseTxSigComponent(transaction, inputIndex, output, flags) } } + + def getScriptWitness( + txSigComponent: TxSigComponent): Option[ScriptWitnessV0] = { + txSigComponent.transaction match { + case _: NonWitnessTransaction => None + case wtx: WitnessTransaction => + val witness = wtx.witness.witnesses(txSigComponent.inputIndex.toInt) + witness match { + case EmptyScriptWitness => None + case witness: ScriptWitnessV0 => Some(witness) + } + } + } } /** diff --git a/core/src/main/scala/org/bitcoins/core/gcs/BlockFilter.scala b/core/src/main/scala/org/bitcoins/core/gcs/BlockFilter.scala index e9663bc7e4..b4bd19c5e8 100644 --- a/core/src/main/scala/org/bitcoins/core/gcs/BlockFilter.scala +++ b/core/src/main/scala/org/bitcoins/core/gcs/BlockFilter.scala @@ -5,7 +5,8 @@ import org.bitcoins.core.protocol.blockchain.Block import org.bitcoins.core.protocol.script.{EmptyScriptPubKey, ScriptPubKey} import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutput} import org.bitcoins.core.script.control.OP_RETURN -import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.DoubleSha256Digest import scodec.bits.ByteVector object BlockFilter { diff --git a/core/src/main/scala/org/bitcoins/core/number/NumberType.scala b/core/src/main/scala/org/bitcoins/core/number/NumberType.scala index a6160fd2d6..2a14345be2 100644 --- a/core/src/main/scala/org/bitcoins/core/number/NumberType.scala +++ b/core/src/main/scala/org/bitcoins/core/number/NumberType.scala @@ -1,7 +1,7 @@ package org.bitcoins.core.number -import org.bitcoins.core.util.NumberUtil -import org.bitcoins.crypto.{BytesUtil, Factory, NetworkElement} +import org.bitcoins.core.util.{BytesUtil, NumberUtil} +import org.bitcoins.crypto.{Factory, NetworkElement} import scodec.bits.{ByteOrdering, ByteVector} import scala.util.{Failure, Success, Try} diff --git a/core/src/main/scala/org/bitcoins/core/p2p/NetworkPayload.scala b/core/src/main/scala/org/bitcoins/core/p2p/NetworkPayload.scala index 81d994446b..6f11ef9bad 100644 --- a/core/src/main/scala/org/bitcoins/core/p2p/NetworkPayload.scala +++ b/core/src/main/scala/org/bitcoins/core/p2p/NetworkPayload.scala @@ -10,9 +10,9 @@ import org.bitcoins.core.protocol.blockchain.{Block, BlockHeader, MerkleBlock} import org.bitcoins.core.protocol.transaction.Transaction import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.serializers.p2p.messages._ +import org.bitcoins.core.util.BytesUtil import org.bitcoins.core.wallet.fee.{SatoshisPerByte, SatoshisPerKiloByte} import org.bitcoins.crypto.{ - BytesUtil, DoubleSha256Digest, Factory, HashDigest, diff --git a/core/src/main/scala/org/bitcoins/core/protocol/Address.scala b/core/src/main/scala/org/bitcoins/core/protocol/Address.scala index 783fb2c736..41c19194d1 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/Address.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/Address.scala @@ -1,11 +1,11 @@ package org.bitcoins.core.protocol + import org.bitcoins.core.config.{MainNet, TestNet3, _} import org.bitcoins.core.number.{UInt5, UInt8} import org.bitcoins.core.protocol.script._ import org.bitcoins.core.script.constant.ScriptConstant import org.bitcoins.core.util._ import org.bitcoins.crypto.{ - BytesUtil, CryptoUtil, ECPublicKey, HashDigest, diff --git a/core/src/main/scala/org/bitcoins/core/protocol/CompactSizeUInt.scala b/core/src/main/scala/org/bitcoins/core/protocol/CompactSizeUInt.scala index 2b6d3015f1..bdb26e8fe2 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/CompactSizeUInt.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/CompactSizeUInt.scala @@ -2,7 +2,8 @@ package org.bitcoins.core.protocol import org.bitcoins.core.number.{UInt32, UInt64} import org.bitcoins.core.protocol.script.ScriptSignature -import org.bitcoins.crypto.{BytesUtil, Factory, NetworkElement} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.{Factory, NetworkElement} import scodec.bits.ByteVector /** diff --git a/core/src/main/scala/org/bitcoins/core/protocol/ln/routing/LnRoute.scala b/core/src/main/scala/org/bitcoins/core/protocol/ln/routing/LnRoute.scala index e0464b28b8..48e649cf5f 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/ln/routing/LnRoute.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/ln/routing/LnRoute.scala @@ -9,7 +9,8 @@ import org.bitcoins.core.protocol.ln.fee.{ FeeBaseMSat, FeeProportionalMillionths } -import org.bitcoins.crypto.{BytesUtil, ECPublicKey, NetworkElement} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.{ECPublicKey, NetworkElement} import scodec.bits.ByteVector /** diff --git a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptFactory.scala b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptFactory.scala index fe2171e2ce..0aea447bcd 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptFactory.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptFactory.scala @@ -2,8 +2,8 @@ package org.bitcoins.core.protocol.script import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.script.constant.ScriptToken -import org.bitcoins.core.util.BitcoinScriptUtil -import org.bitcoins.crypto.{BytesUtil, Factory} +import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil} +import org.bitcoins.crypto.Factory import scodec.bits.ByteVector /** diff --git a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala index a0d485ddc1..32df95d3fe 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptPubKey.scala @@ -18,7 +18,6 @@ import org.bitcoins.core.script.reserved.UndefinedOP_NOP import org.bitcoins.core.script.stack.{OP_DROP, OP_DUP} import org.bitcoins.core.util._ import org.bitcoins.crypto.{ - BytesUtil, CryptoUtil, DoubleSha256Digest, ECPublicKey, diff --git a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala index 36d0b7ef0d..00464261a7 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala @@ -4,7 +4,7 @@ import org.bitcoins.core.script.constant._ import org.bitcoins.core.serializers.script.ScriptParser import org.bitcoins.core.util._ import org.bitcoins.core.wallet.utxo.ConditionalPath -import org.bitcoins.crypto.{BytesUtil, ECDigitalSignature, ECPublicKey} +import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey} import scala.util.{Failure, Success, Try} @@ -125,7 +125,8 @@ object P2PKHScriptSignature extends ScriptFactory[P2PKHScriptSignature] { _: ScriptConstant, _: BytesToPushOntoStack, z: ScriptConstant) => - if ((z.bytes.length == 33 || z.bytes.length == 65) && ECPublicKey.isFullyValid(z.bytes)) true + if ((z.bytes.length == 33 || z.bytes.length == 65) && ECPublicKey + .isFullyValid(z.bytes)) true else !P2SHScriptSignature.isRedeemScript(z) case _ => false } diff --git a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptWitness.scala b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptWitness.scala index c29858b89b..cb4af4de30 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptWitness.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptWitness.scala @@ -2,9 +2,8 @@ package org.bitcoins.core.protocol.script import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.serializers.script.RawScriptWitnessParser -import org.bitcoins.core.util.BitcoinScriptUtil +import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil} import org.bitcoins.crypto.{ - BytesUtil, ECDigitalSignature, ECPublicKey, EmptyDigitalSignature, diff --git a/core/src/main/scala/org/bitcoins/core/protocol/script/WitnessVersion.scala b/core/src/main/scala/org/bitcoins/core/protocol/script/WitnessVersion.scala index 0d456ac9e8..726dd1a68c 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/script/WitnessVersion.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/script/WitnessVersion.scala @@ -3,13 +3,8 @@ package org.bitcoins.core.protocol.script import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.script.constant._ import org.bitcoins.core.script.result._ -import org.bitcoins.core.util.BitcoinSLogger -import org.bitcoins.crypto.{ - BytesUtil, - CryptoUtil, - Sha256Digest, - Sha256Hash160Digest -} +import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil} +import org.bitcoins.crypto.{CryptoUtil, Sha256Digest, Sha256Hash160Digest} /** * Created by chris on 11/10/16. diff --git a/core/src/main/scala/org/bitcoins/core/protocol/transaction/InputUtil.scala b/core/src/main/scala/org/bitcoins/core/protocol/transaction/InputUtil.scala new file mode 100644 index 0000000000..d92dc6ca3b --- /dev/null +++ b/core/src/main/scala/org/bitcoins/core/protocol/transaction/InputUtil.scala @@ -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) + } +} diff --git a/core/src/main/scala/org/bitcoins/core/protocol/transaction/Transaction.scala b/core/src/main/scala/org/bitcoins/core/protocol/transaction/Transaction.scala index 798b5cc627..4a8e3bec5e 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/transaction/Transaction.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/transaction/Transaction.scala @@ -1,8 +1,9 @@ package org.bitcoins.core.protocol.transaction import org.bitcoins.core.number.{Int32, UInt32} -import org.bitcoins.core.protocol.script.ScriptWitness -import org.bitcoins.core.serializers.transaction.{RawBaseTransactionParser, RawWitnessTransactionParser} +import org.bitcoins.core.protocol.script.{EmptyScriptWitness, ScriptWitness} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.core.wallet.builder.RawTxBuilder import org.bitcoins.crypto._ import scodec.bits.ByteVector @@ -64,7 +65,7 @@ sealed abstract class Transaction extends NetworkElement { * [[https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#Transaction_size_calculations]] */ def baseSize: Long = this match { - case btx: BaseTransaction => btx.byteSize + case btx: NonWitnessTransaction => btx.byteSize case wtx: WitnessTransaction => BaseTransaction(wtx.version, wtx.inputs, wtx.outputs, wtx.lockTime).baseSize } @@ -85,7 +86,7 @@ sealed abstract class Transaction extends NetworkElement { def updateInput(idx: Int, i: TransactionInput): Transaction = { val updatedInputs = inputs.updated(idx, i) this match { - case _: BaseTransaction => + case _: NonWitnessTransaction => BaseTransaction(version, updatedInputs, outputs, lockTime) case wtx: WitnessTransaction => WitnessTransaction(version, @@ -97,39 +98,101 @@ sealed abstract class Transaction extends NetworkElement { } } -sealed abstract class BaseTransaction extends Transaction { - override def bytes = RawBaseTransactionParser.write(this) - override def weight = byteSize * 4 +object Transaction extends Factory[Transaction] { + def newBuilder: RawTxBuilder = RawTxBuilder() + override def fromBytes(bytes: ByteVector): Transaction = { + //see BIP141 for marker/flag bytes + //https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#transaction-id + if (bytes(4) == WitnessTransaction.marker && bytes(5) == WitnessTransaction.flag) { + //this throw/catch is _still_ necessary for the case where we have unsigned base transactions + //with zero inputs and 1 output which is serialized as "0001" at bytes 4 and 5. + //these transactions will not have a script witness associated with them making them invalid + //witness transactions (you need to have a witness to be considered a witness tx) + //see: https://github.com/bitcoin-s/bitcoin-s/blob/01d89df1b7c6bc4b1594406d54d5e6019705c654/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala#L88 + try { + WitnessTransaction.fromBytes(bytes) + } catch { + case scala.util.control.NonFatal(_) => + BaseTransaction.fromBytes(bytes) + } + } else { + BaseTransaction.fromBytes(bytes) + } + } } -case object EmptyTransaction extends BaseTransaction { - override def txId = DoubleSha256Digest.empty - override def version = TransactionConstants.version - override def inputs = Nil - override def outputs = Nil - override def lockTime = TransactionConstants.lockTime +sealed abstract class NonWitnessTransaction extends Transaction { + override def weight: Long = byteSize * 4 + + override def bytes: ByteVector = { + val versionBytes = version.bytes.reverse + val inputBytes = BytesUtil.writeCmpctSizeUInt(inputs) + val outputBytes = BytesUtil.writeCmpctSizeUInt(outputs) + val lockTimeBytes = lockTime.bytes.reverse + + versionBytes ++ inputBytes ++ outputBytes ++ lockTimeBytes + } } -sealed abstract class WitnessTransaction extends Transaction { +case class BaseTransaction( + version: Int32, + inputs: Seq[TransactionInput], + outputs: Seq[TransactionOutput], + lockTime: UInt32) + extends NonWitnessTransaction + +object BaseTransaction extends Factory[BaseTransaction] { + override def fromBytes(bytes: ByteVector): BaseTransaction = { + val versionBytes = bytes.take(4) + val version = Int32(versionBytes.reverse) + val txInputBytes = bytes.slice(4, bytes.size) + val (inputs, outputBytes) = + BytesUtil.parseCmpctSizeUIntSeq(txInputBytes, TransactionInput) + val (outputs, lockTimeBytes) = + BytesUtil.parseCmpctSizeUIntSeq(outputBytes, TransactionOutput) + val lockTime = UInt32(lockTimeBytes.take(4).reverse) + + BaseTransaction(version, inputs, outputs, lockTime) + } + + def unapply(tx: NonWitnessTransaction): Option[ + (Int32, Seq[TransactionInput], Seq[TransactionOutput], UInt32)] = { + Some(tx.version, tx.inputs, tx.outputs, tx.lockTime) + } +} + +case object EmptyTransaction extends NonWitnessTransaction { + override def txId: DoubleSha256Digest = DoubleSha256Digest.empty + override def version: Int32 = TransactionConstants.version + override def inputs: Vector[TransactionInput] = Vector.empty + override def outputs: Vector[TransactionOutput] = Vector.empty + override def lockTime: UInt32 = TransactionConstants.lockTime + + def toBaseTx: BaseTransaction = + BaseTransaction(version, inputs, outputs, lockTime) +} + +case class WitnessTransaction( + version: Int32, + inputs: Seq[TransactionInput], + outputs: Seq[TransactionOutput], + lockTime: UInt32, + witness: TransactionWitness) + extends Transaction { require( inputs.length == witness.length, s"Must have same amount of inputs and witnesses in witness tx, inputs=${inputs.length} witnesses=${witness.length}" ) - /** The txId for the witness transaction from satoshi's original serialization */ - override def txId: DoubleSha256Digest = { - val btx = BaseTransaction(version, inputs, outputs, lockTime) - btx.txId + def toBaseTx: BaseTransaction = { + BaseTransaction(version, inputs, outputs, lockTime) } - /** - * The witness used to evaluate - * [[org.bitcoins.core.protocol.script.ScriptSignature ScriptSignature]]/ - * [[org.bitcoins.core.protocol.script.ScriptPubKey ScriptPubKey]]s inside of a SegWit tx. - * [[https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki BIP141]] - */ - def witness: TransactionWitness + /** The txId for the witness transaction from satoshi's original serialization */ + override def txId: DoubleSha256Digest = { + toBaseTx.txId + } /** * The witness transaction id as defined by @@ -145,10 +208,32 @@ sealed abstract class WitnessTransaction extends Transaction { * [[https://github.com/bitcoin/bitcoin/blob/5961b23898ee7c0af2626c46d5d70e80136578d3/src/consensus/validation.h#L96]] */ override def weight: Long = { - val base = BaseTransaction(version, inputs, outputs, lockTime) - base.byteSize * 3 + byteSize + toBaseTx.byteSize * 3 + byteSize + } + + /** + * Writes a [[org.bitcoins.core.protocol.transaction.WitnessTransaction WitnessTransaction]] to a hex string + * This is unique from BaseTransaction.bytes in the fact + * that it adds a 'marker' and 'flag' to indicate that this tx is a + * [[org.bitcoins.core.protocol.transaction.WitnessTransaction WitnessTransaction]] and has extra + * witness data attached to it. + * See [[https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki BIP144]] for more info. + * Functionality inside of Bitcoin Core: + * [[https://github.com/bitcoin/bitcoin/blob/e8cfe1ee2d01c493b758a67ad14707dca15792ea/src/primitives/transaction.h#L282-L287s]] + */ + override def bytes: ByteVector = { + val versionBytes = version.bytes.reverse + val inputBytes = BytesUtil.writeCmpctSizeUInt(inputs) + val outputBytes = BytesUtil.writeCmpctSizeUInt(outputs) + val witnessBytes = witness.bytes + val lockTimeBytes = lockTime.bytes.reverse + // notice we use the old serialization format if all witnesses are empty + // https://github.com/bitcoin/bitcoin/blob/e8cfe1ee2d01c493b758a67ad14707dca15792ea/src/primitives/transaction.h#L276-L281 + if (witness.exists(_ != EmptyScriptWitness)) { + val witConstant = ByteVector(0.toByte, 1.toByte) + versionBytes ++ witConstant ++ inputBytes ++ outputBytes ++ witnessBytes ++ lockTimeBytes + } else toBaseTx.bytes } - override def bytes: ByteVector = RawWitnessTransactionParser.write(this) /** * Updates the [[org.bitcoins.core.protocol.script.ScriptWitness ScriptWitness]] at the given index and @@ -159,73 +244,43 @@ sealed abstract class WitnessTransaction extends Transaction { val txWit = witness.updated(idx, scriptWit) WitnessTransaction(version, inputs, outputs, lockTime, txWit) } - -} - -object Transaction extends Factory[Transaction] { - - override def fromBytes(bytes: ByteVector): Transaction = { - //see BIP141 for marker/flag bytes - //https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#transaction-id - if (bytes(4) == WitnessTransaction.marker && bytes(5) == WitnessTransaction.flag) { - //this throw/catch is _still_ necessary for the case where we have unsigned base transactions - //with zero inputs and 1 output which is serialized as "0001" at bytes 4 and 5. - //these transactions will not have a script witness associated with them making them invalid - //witness transactions (you need to have a witness to be considered a witness tx) - //see: https://github.com/bitcoin-s/bitcoin-s/blob/01d89df1b7c6bc4b1594406d54d5e6019705c654/core-test/src/test/scala/org/bitcoins/core/protocol/transaction/TransactionTest.scala#L88 - try { - RawWitnessTransactionParser.read(bytes) - } catch { - case scala.util.control.NonFatal(_) => - RawBaseTransactionParser.read(bytes) - } - } else { - RawBaseTransactionParser.read(bytes) - } - } -} - -object BaseTransaction extends Factory[BaseTransaction] { - private case class BaseTransactionImpl( - version: Int32, - inputs: Seq[TransactionInput], - outputs: Seq[TransactionOutput], - lockTime: UInt32) - extends BaseTransaction - - override def fromBytes(bytes: ByteVector): BaseTransaction = - RawBaseTransactionParser.read(bytes) - - def apply( - version: Int32, - inputs: Seq[TransactionInput], - outputs: Seq[TransactionOutput], - lockTime: UInt32): BaseTransaction = - BaseTransactionImpl(version, inputs, outputs, lockTime) } object WitnessTransaction extends Factory[WitnessTransaction] { - private case class WitnessTransactionImpl( - version: Int32, - inputs: Seq[TransactionInput], - outputs: Seq[TransactionOutput], - lockTime: UInt32, - witness: TransactionWitness) - extends WitnessTransaction - def apply( - version: Int32, - inputs: Seq[TransactionInput], - outputs: Seq[TransactionOutput], - lockTime: UInt32, - witness: TransactionWitness): WitnessTransaction = - WitnessTransactionImpl(version, inputs, outputs, lockTime, witness) + /** + * This read function is unique to BaseTransaction.fromBytes + * in the fact that it reads a 'marker' and 'flag' byte to indicate that this tx is a + * [[org.bitcoins.core.protocol.transaction.WitnessTransaction WitnessTransaction]]. + * See [[https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki BIP144 ]] for more details. + * Functionality inside of Bitcoin Core: + * [[https://github.com/bitcoin/bitcoin/blob/e8cfe1ee2d01c493b758a67ad14707dca15792ea/src/primitives/transaction.h#L244-L251]] + */ + override def fromBytes(bytes: ByteVector): WitnessTransaction = { + val versionBytes = bytes.take(4) + val version = Int32(versionBytes.reverse) + val marker = bytes(4) + require( + marker.toInt == 0, + "Incorrect marker for witness transaction, the marker MUST be 0 for the marker according to BIP141, got: " + marker) + val flag = bytes(5) + require( + flag.toInt != 0, + "Incorrect flag for witness transaction, this must NOT be 0 according to BIP141, got: " + flag) + val txInputBytes = bytes.slice(6, bytes.size) + val (inputs, outputBytes) = + BytesUtil.parseCmpctSizeUIntSeq(txInputBytes, TransactionInput) + val (outputs, witnessBytes) = + BytesUtil.parseCmpctSizeUIntSeq(outputBytes, TransactionOutput) + val witness = TransactionWitness(witnessBytes, inputs.size) + val lockTimeBytes = witnessBytes.drop(witness.byteSize) + val lockTime = UInt32(lockTimeBytes.take(4).reverse) - override def fromBytes(bytes: ByteVector): WitnessTransaction = - RawWitnessTransactionParser.read(bytes) + WitnessTransaction(version, inputs, outputs, lockTime, witness) + } def toWitnessTx(tx: Transaction): WitnessTransaction = tx match { - case btx: BaseTransaction => + case btx: NonWitnessTransaction => WitnessTransaction(btx.version, btx.inputs, btx.outputs, diff --git a/core/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionWitness.scala b/core/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionWitness.scala index d5ff1477f3..049652f2f2 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionWitness.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/transaction/TransactionWitness.scala @@ -2,8 +2,8 @@ package org.bitcoins.core.protocol.transaction import org.bitcoins.core.protocol.script.{EmptyScriptWitness, ScriptWitness} import org.bitcoins.core.serializers.transaction.RawTransactionWitnessParser -import org.bitcoins.core.util.SeqWrapper -import org.bitcoins.crypto.{BytesUtil, NetworkElement} +import org.bitcoins.core.util.{BytesUtil, SeqWrapper} +import org.bitcoins.crypto.NetworkElement import scodec.bits.ByteVector /** diff --git a/core/src/main/scala/org/bitcoins/core/protocol/transaction/TxUtil.scala b/core/src/main/scala/org/bitcoins/core/protocol/transaction/TxUtil.scala new file mode 100644 index 0000000000..a8d7ddc575 --- /dev/null +++ b/core/src/main/scala/org/bitcoins/core/protocol/transaction/TxUtil.scala @@ -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(()) + } + } +} diff --git a/core/src/main/scala/org/bitcoins/core/psbt/PSBT.scala b/core/src/main/scala/org/bitcoins/core/psbt/PSBT.scala index a2ce2a9d8e..1cfc61d531 100644 --- a/core/src/main/scala/org/bitcoins/core/psbt/PSBT.scala +++ b/core/src/main/scala/org/bitcoins/core/psbt/PSBT.scala @@ -11,7 +11,6 @@ import org.bitcoins.core.script.crypto.HashType import org.bitcoins.core.script.interpreter.ScriptInterpreter import org.bitcoins.core.script.result.ScriptOk import org.bitcoins.core.util.BitcoinSLogger -import org.bitcoins.core.wallet.builder.BitcoinTxBuilder import org.bitcoins.core.wallet.signer.BitcoinSigner import org.bitcoins.core.wallet.utxo._ import org.bitcoins.crypto.{ @@ -582,7 +581,7 @@ case class PSBT( new RuntimeException( s"Input $index was invalid: $inputResult")) } - case (Some(_), _: BaseTransaction) => + case (Some(_), _: NonWitnessTransaction) => Failure(new RuntimeException( s"Extracted program is not witness transaction, but input $index has WitnessUTXO record")) case (None, _) => @@ -647,7 +646,7 @@ case class PSBT( witness) } else { transaction match { - case btx: BaseTransaction => + case btx: NonWitnessTransaction => BaseTransaction(btx.version, newInputs, btx.outputs, btx.lockTime) case wtx: WitnessTransaction => WitnessTransaction(wtx.version, @@ -761,7 +760,7 @@ object PSBT extends Factory[PSBT] { val btx = unsignedTx match { case wtx: WitnessTransaction => BaseTransaction(wtx.version, wtx.inputs, wtx.outputs, wtx.lockTime) - case base: BaseTransaction => base + case base: NonWitnessTransaction => base } val globalMap = GlobalPSBTMap( Vector(GlobalPSBTRecord.UnsignedTransaction(btx))) @@ -829,11 +828,11 @@ object PSBT extends Factory[PSBT] { spendingInfoAndNonWitnessTxs.matchesInputs(unsignedTx.inputs), "NewSpendingInfos must correspond to transaction inputs" ) - val emptySigTx = BitcoinTxBuilder.emptyAllScriptSigs(unsignedTx) + val emptySigTx = TxUtil.emptyAllScriptSigs(unsignedTx) val btx = emptySigTx match { case wtx: WitnessTransaction => BaseTransaction(wtx.version, wtx.inputs, wtx.outputs, wtx.lockTime) - case base: BaseTransaction => base + case base: NonWitnessTransaction => base } val globalMap = GlobalPSBTMap( diff --git a/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala b/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala index 1214fb3fa8..29a2866225 100644 --- a/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala +++ b/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala @@ -4,7 +4,7 @@ import org.bitcoins.core.byteVectorOrdering import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.transaction.{ - BaseTransaction, + NonWitnessTransaction, Transaction, TransactionInput, TransactionOutput, @@ -657,7 +657,8 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] { val scriptSig = FinalizedScriptSig(sigComponent.scriptSignature) sigComponent.transaction match { - case _: BaseTransaction => InputPSBTMap(utxos ++ Vector(scriptSig)) + case _: NonWitnessTransaction => + InputPSBTMap(utxos ++ Vector(scriptSig)) case wtx: WitnessTransaction => val witness = wtx.witness(sigComponent.inputIndex.toInt) val scriptWitness = FinalizedScriptWitness(witness) diff --git a/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala b/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala index 96de08e52c..e6145782e3 100644 --- a/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala +++ b/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala @@ -7,6 +7,7 @@ import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.transaction.{ BaseTransaction, + NonWitnessTransaction, Transaction, TransactionOutput } @@ -62,7 +63,7 @@ sealed trait GlobalPSBTRecord extends PSBTRecord { object GlobalPSBTRecord extends Factory[GlobalPSBTRecord] { import org.bitcoins.core.psbt.PSBTGlobalKeyId._ - case class UnsignedTransaction(transaction: BaseTransaction) + case class UnsignedTransaction(transaction: NonWitnessTransaction) extends GlobalPSBTRecord { require( transaction.inputs.forall(_.scriptSignature == EmptyScriptSignature), diff --git a/core/src/main/scala/org/bitcoins/core/script/ScriptOperationFactory.scala b/core/src/main/scala/org/bitcoins/core/script/ScriptOperationFactory.scala index a73f1543e9..ec07fd851d 100644 --- a/core/src/main/scala/org/bitcoins/core/script/ScriptOperationFactory.scala +++ b/core/src/main/scala/org/bitcoins/core/script/ScriptOperationFactory.scala @@ -9,8 +9,7 @@ import org.bitcoins.core.script.locktime.LocktimeOperation import org.bitcoins.core.script.reserved.ReservedOperation import org.bitcoins.core.script.splice.SpliceOperation import org.bitcoins.core.script.stack.StackOperation -import org.bitcoins.core.util.BitcoinSLogger -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.{BitcoinSLogger, BytesUtil} import scodec.bits.ByteVector /** diff --git a/core/src/main/scala/org/bitcoins/core/script/constant/ConstantInterpreter.scala b/core/src/main/scala/org/bitcoins/core/script/constant/ConstantInterpreter.scala index 90d3fc9c0a..f02c6586e9 100644 --- a/core/src/main/scala/org/bitcoins/core/script/constant/ConstantInterpreter.scala +++ b/core/src/main/scala/org/bitcoins/core/script/constant/ConstantInterpreter.scala @@ -6,8 +6,7 @@ import org.bitcoins.core.script.{ } import org.bitcoins.core.script.flag.ScriptFlagUtil import org.bitcoins.core.script.result._ -import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil} -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil, BytesUtil} import scala.annotation.tailrec diff --git a/core/src/main/scala/org/bitcoins/core/script/constant/Constants.scala b/core/src/main/scala/org/bitcoins/core/script/constant/Constants.scala index cf0409b499..7793964ba6 100644 --- a/core/src/main/scala/org/bitcoins/core/script/constant/Constants.scala +++ b/core/src/main/scala/org/bitcoins/core/script/constant/Constants.scala @@ -2,8 +2,8 @@ package org.bitcoins.core.script.constant import org.bitcoins.core.number.Int64 import org.bitcoins.core.script.ScriptOperationFactory -import org.bitcoins.core.util.BitcoinScriptUtil -import org.bitcoins.crypto.{BytesUtil, Factory, NetworkElement} +import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil} +import org.bitcoins.crypto.{Factory, NetworkElement} import scodec.bits.ByteVector import scala.util.{Failure, Try} @@ -358,23 +358,23 @@ object ScriptNumberOperation operations.find(_.underlying == underlying) override val operations = Vector(OP_0, - OP_1, - OP_1NEGATE, - OP_2, - OP_3, - OP_4, - OP_5, - OP_6, - OP_7, - OP_8, - OP_9, - OP_10, - OP_11, - OP_12, - OP_13, - OP_14, - OP_15, - OP_16) + OP_1, + OP_1NEGATE, + OP_2, + OP_3, + OP_4, + OP_5, + OP_6, + OP_7, + OP_8, + OP_9, + OP_10, + OP_11, + OP_12, + OP_13, + OP_14, + OP_15, + OP_16) } diff --git a/core/src/main/scala/org/bitcoins/core/script/constant/ScriptNumberUtil.scala b/core/src/main/scala/org/bitcoins/core/script/constant/ScriptNumberUtil.scala index 3c6870c34c..aa6e54e1c7 100644 --- a/core/src/main/scala/org/bitcoins/core/script/constant/ScriptNumberUtil.scala +++ b/core/src/main/scala/org/bitcoins/core/script/constant/ScriptNumberUtil.scala @@ -1,6 +1,6 @@ package org.bitcoins.core.script.constant -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import scodec.bits.ByteVector /** diff --git a/core/src/main/scala/org/bitcoins/core/script/crypto/CryptoOperations.scala b/core/src/main/scala/org/bitcoins/core/script/crypto/CryptoOperations.scala index 9565a9a7ad..33d528c584 100644 --- a/core/src/main/scala/org/bitcoins/core/script/crypto/CryptoOperations.scala +++ b/core/src/main/scala/org/bitcoins/core/script/crypto/CryptoOperations.scala @@ -83,13 +83,13 @@ case object OP_CHECKMULTISIGVERIFY extends CryptoSignatureEvaluation { object CryptoOperation extends ScriptOperationFactory[CryptoOperation] { override val operations = Vector(OP_CHECKMULTISIG, - OP_CHECKMULTISIGVERIFY, - OP_CHECKSIG, - OP_CHECKSIGVERIFY, - OP_CODESEPARATOR, - OP_HASH160, - OP_HASH256, - OP_RIPEMD160, - OP_SHA1, - OP_SHA256) + OP_CHECKMULTISIGVERIFY, + OP_CHECKSIG, + OP_CHECKSIGVERIFY, + OP_CODESEPARATOR, + OP_HASH160, + OP_HASH256, + OP_RIPEMD160, + OP_SHA1, + OP_SHA256) } diff --git a/core/src/main/scala/org/bitcoins/core/script/crypto/CryptoSignatureEvaluationFactory.scala b/core/src/main/scala/org/bitcoins/core/script/crypto/CryptoSignatureEvaluationFactory.scala index 74726e0ac1..8d316adfdf 100644 --- a/core/src/main/scala/org/bitcoins/core/script/crypto/CryptoSignatureEvaluationFactory.scala +++ b/core/src/main/scala/org/bitcoins/core/script/crypto/CryptoSignatureEvaluationFactory.scala @@ -11,9 +11,9 @@ trait CryptoSignatureEvaluationFactory /** The current [[CryptoSignatureEvaluation]] operations. */ override val operations = Vector(OP_CHECKMULTISIG, - OP_CHECKMULTISIGVERIFY, - OP_CHECKSIG, - OP_CHECKSIGVERIFY) + OP_CHECKMULTISIGVERIFY, + OP_CHECKSIG, + OP_CHECKSIGVERIFY) } diff --git a/core/src/main/scala/org/bitcoins/core/script/interpreter/ScriptInterpreter.scala b/core/src/main/scala/org/bitcoins/core/script/interpreter/ScriptInterpreter.scala index 6aec58912d..cfbd2e2b66 100644 --- a/core/src/main/scala/org/bitcoins/core/script/interpreter/ScriptInterpreter.scala +++ b/core/src/main/scala/org/bitcoins/core/script/interpreter/ScriptInterpreter.scala @@ -24,8 +24,7 @@ import org.bitcoins.core.script.reserved._ import org.bitcoins.core.script.result._ import org.bitcoins.core.script.splice._ import org.bitcoins.core.script.stack._ -import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil} -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil, BytesUtil} import scala.annotation.tailrec import scala.util.{Failure, Success, Try} @@ -1160,7 +1159,7 @@ sealed abstract class ScriptInterpreter extends BitcoinSLogger { b.transaction match { case wtx: WitnessTransaction => wtx.witness(txSigComponent.inputIndex.toInt).stack.nonEmpty - case _: BaseTransaction => false + case _: NonWitnessTransaction => false } case r: WitnessTxSigComponentRebuilt => r.transaction.witness(txSigComponent.inputIndex.toInt).stack.nonEmpty diff --git a/core/src/main/scala/org/bitcoins/core/script/locktime/LocktimeOperations.scala b/core/src/main/scala/org/bitcoins/core/script/locktime/LocktimeOperations.scala index 197f1d434e..7faf4df424 100644 --- a/core/src/main/scala/org/bitcoins/core/script/locktime/LocktimeOperations.scala +++ b/core/src/main/scala/org/bitcoins/core/script/locktime/LocktimeOperations.scala @@ -39,5 +39,6 @@ case object OP_CHECKSEQUENCEVERIFY extends LocktimeOperation { } object LocktimeOperation extends ScriptOperationFactory[LocktimeOperation] { - override val operations = Vector(OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY) + override val operations = + Vector(OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY) } diff --git a/core/src/main/scala/org/bitcoins/core/script/reserved/ReservedOperations.scala b/core/src/main/scala/org/bitcoins/core/script/reserved/ReservedOperations.scala index cdb26b8efd..ff99f2483e 100644 --- a/core/src/main/scala/org/bitcoins/core/script/reserved/ReservedOperations.scala +++ b/core/src/main/scala/org/bitcoins/core/script/reserved/ReservedOperations.scala @@ -98,19 +98,19 @@ object ReservedOperation extends ScriptOperationFactory[ReservedOperation] { override val operations = Vector(OP_RESERVED, - OP_VER, - OP_VERIF, - OP_VERNOTIF, - OP_RESERVED, - OP_RESERVED1, - OP_RESERVED2, - OP_NOP, - OP_NOP1, - OP_NOP4, - OP_NOP5, - OP_NOP6, - OP_NOP7, - OP_NOP8, - OP_NOP9, - OP_NOP10) ++ undefinedOpCodes + OP_VER, + OP_VERIF, + OP_VERNOTIF, + OP_RESERVED, + OP_RESERVED1, + OP_RESERVED2, + OP_NOP, + OP_NOP1, + OP_NOP4, + OP_NOP5, + OP_NOP6, + OP_NOP7, + OP_NOP8, + OP_NOP9, + OP_NOP10) ++ undefinedOpCodes } diff --git a/core/src/main/scala/org/bitcoins/core/script/splice/SpliceOperations.scala b/core/src/main/scala/org/bitcoins/core/script/splice/SpliceOperations.scala index 00c6f71e5d..3cc068ccaa 100644 --- a/core/src/main/scala/org/bitcoins/core/script/splice/SpliceOperations.scala +++ b/core/src/main/scala/org/bitcoins/core/script/splice/SpliceOperations.scala @@ -29,5 +29,6 @@ case object OP_SIZE extends SpliceOperation { } object SpliceOperation extends ScriptOperationFactory[SpliceOperation] { - override val operations = Vector(OP_CAT, OP_LEFT, OP_RIGHT, OP_SIZE, OP_SUBSTR) + override val operations = + Vector(OP_CAT, OP_LEFT, OP_RIGHT, OP_SIZE, OP_SUBSTR) } diff --git a/core/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializer.scala b/core/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializer.scala index 26c9c93d0b..f8791aabd1 100644 --- a/core/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializer.scala +++ b/core/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializer.scala @@ -1,7 +1,7 @@ package org.bitcoins.core.serializers import org.bitcoins.core.util.BitcoinSLogger -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import scodec.bits.ByteVector /** diff --git a/core/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializerHelper.scala b/core/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializerHelper.scala index 1c3cc9cda1..832b8bc3d2 100644 --- a/core/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializerHelper.scala +++ b/core/src/main/scala/org/bitcoins/core/serializers/RawBitcoinSerializerHelper.scala @@ -20,7 +20,7 @@ sealed abstract class RawSerializerHelper { bytes: ByteVector, constructor: ByteVector => T): (Seq[T], ByteVector) = { val count = CompactSizeUInt.parse(bytes) - val (_,payload) = bytes.splitAt(count.byteSize.toInt) + val (_, payload) = bytes.splitAt(count.byteSize.toInt) var counter = 0 val b = Vector.newBuilder[T] @tailrec diff --git a/core/src/main/scala/org/bitcoins/core/serializers/blockchain/RawMerkleBlockSerializer.scala b/core/src/main/scala/org/bitcoins/core/serializers/blockchain/RawMerkleBlockSerializer.scala index 640bb53390..8ed13c2e33 100644 --- a/core/src/main/scala/org/bitcoins/core/serializers/blockchain/RawMerkleBlockSerializer.scala +++ b/core/src/main/scala/org/bitcoins/core/serializers/blockchain/RawMerkleBlockSerializer.scala @@ -4,7 +4,8 @@ import org.bitcoins.core.number.{UInt32, UInt64} import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.protocol.blockchain.MerkleBlock import org.bitcoins.core.serializers.RawBitcoinSerializer -import org.bitcoins.crypto.{BytesUtil, DoubleSha256Digest} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.DoubleSha256Digest import scodec.bits.{BitVector, ByteVector} import scala.annotation.tailrec diff --git a/core/src/main/scala/org/bitcoins/core/serializers/script/ScriptParser.scala b/core/src/main/scala/org/bitcoins/core/serializers/script/ScriptParser.scala index aae297123f..e44b4599a7 100644 --- a/core/src/main/scala/org/bitcoins/core/serializers/script/ScriptParser.scala +++ b/core/src/main/scala/org/bitcoins/core/serializers/script/ScriptParser.scala @@ -3,7 +3,8 @@ package org.bitcoins.core.serializers.script import org.bitcoins.core.number.UInt32 import org.bitcoins.core.script._ import org.bitcoins.core.script.constant._ -import org.bitcoins.crypto.{BytesUtil, Factory} +import org.bitcoins.core.util.BytesUtil +import org.bitcoins.crypto.Factory import scodec.bits.ByteVector import scala.annotation.tailrec diff --git a/core/src/main/scala/org/bitcoins/core/util/Base58.scala b/core/src/main/scala/org/bitcoins/core/util/Base58.scala index c7173de6e9..7ad25d547d 100644 --- a/core/src/main/scala/org/bitcoins/core/util/Base58.scala +++ b/core/src/main/scala/org/bitcoins/core/util/Base58.scala @@ -2,7 +2,7 @@ package org.bitcoins.core.util import org.bitcoins.core.crypto.ECPrivateKeyUtil import org.bitcoins.core.protocol.blockchain._ -import org.bitcoins.crypto.{BytesUtil, CryptoUtil} +import org.bitcoins.crypto.CryptoUtil import scodec.bits.ByteVector import scala.annotation.tailrec diff --git a/core/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala b/core/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala index 1c103e6d48..d63c5016eb 100644 --- a/core/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala +++ b/core/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala @@ -25,7 +25,7 @@ import org.bitcoins.core.script.result.{ } import org.bitcoins.core.script.ExecutionInProgressScriptProgram import org.bitcoins.core.serializers.script.ScriptParser -import org.bitcoins.crypto.{BytesUtil, ECDigitalSignature, ECPublicKey} +import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey} import scodec.bits.ByteVector import scala.annotation.tailrec diff --git a/core/src/main/scala/org/bitcoins/core/util/BytesUtil.scala b/core/src/main/scala/org/bitcoins/core/util/BytesUtil.scala new file mode 100644 index 0000000000..506ff347ec --- /dev/null +++ b/core/src/main/scala/org/bitcoins/core/util/BytesUtil.scala @@ -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 diff --git a/core/src/main/scala/org/bitcoins/core/util/NumberUtil.scala b/core/src/main/scala/org/bitcoins/core/util/NumberUtil.scala index 9af843a93a..d1ac8d9d04 100644 --- a/core/src/main/scala/org/bitcoins/core/util/NumberUtil.scala +++ b/core/src/main/scala/org/bitcoins/core/util/NumberUtil.scala @@ -5,7 +5,6 @@ import java.math.BigInteger import org.bitcoins.core.number._ import org.bitcoins.core.protocol.blockchain.BlockHeader import org.bitcoins.core.protocol.blockchain.BlockHeader.TargetDifficultyHelper -import org.bitcoins.crypto.BytesUtil import scodec.bits.{BitVector, ByteVector} import scala.math.BigInt diff --git a/core/src/main/scala/org/bitcoins/core/wallet/builder/FinalizedTxWithSigningInfo.scala b/core/src/main/scala/org/bitcoins/core/wallet/builder/FinalizedTxWithSigningInfo.scala new file mode 100644 index 0000000000..87af09eb15 --- /dev/null +++ b/core/src/main/scala/org/bitcoins/core/wallet/builder/FinalizedTxWithSigningInfo.scala @@ -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) + } +} diff --git a/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxBuilder.scala b/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxBuilder.scala new file mode 100644 index 0000000000..c29aa2c103 --- /dev/null +++ b/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxBuilder.scala @@ -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 + } +} diff --git a/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxBuilderResult.scala b/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxBuilderResult.scala new file mode 100644 index 0000000000..2201d4d69a --- /dev/null +++ b/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxBuilderResult.scala @@ -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) + } +} diff --git a/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxFinalizer.scala b/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxFinalizer.scala new file mode 100644 index 0000000000..3e793f291d --- /dev/null +++ b/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxFinalizer.scala @@ -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()) + } +} diff --git a/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxSigner.scala b/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxSigner.scala new file mode 100644 index 0000000000..555c298980 --- /dev/null +++ b/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxSigner.scala @@ -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) + } + } +} diff --git a/core/src/main/scala/org/bitcoins/core/wallet/builder/TxBuilderError.scala b/core/src/main/scala/org/bitcoins/core/wallet/builder/TxBuilderError.scala index 42615e7f58..e42ff3dfc4 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/builder/TxBuilderError.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/builder/TxBuilderError.scala @@ -1,5 +1,7 @@ package org.bitcoins.core.wallet.builder +import org.bitcoins.core.protocol.transaction.TransactionOutput + import scala.util.Failure /** @@ -183,8 +185,10 @@ object TxBuilderError { /** Means we have a output on this transaction below * [[org.bitcoins.core.policy.Policy.dustThreshold Policy.dustThreshold]] */ - val OutputBelowDustThreshold = Failure(new IllegalArgumentException( - "The p2p network discourages outputs below the dustThreshold, this tx won't be relayed")) + def OutputBelowDustThreshold( + belowDustOutputs: Seq[TransactionOutput]): Failure[Nothing] = + Failure(new IllegalArgumentException( + s"The p2p network discourages outputs below the dustThreshold, $belowDustOutputs, this tx won't be relayed")) val UnknownError = Failure(new IllegalArgumentException) } diff --git a/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala b/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala index ad75d88bb6..69806b6811 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala @@ -178,7 +178,7 @@ sealed abstract class Signer[-InputType <: InputInfo] extends SignerUtils { unsignedInput.sequence) val signedInputs = unsignedTx.inputs.updated(inputIndex, signedInput) val signedTx = unsignedTx match { - case btx: BaseTransaction => + case btx: NonWitnessTransaction => BaseTransaction(btx.version, signedInputs, btx.outputs, btx.lockTime) case wtx: WitnessTransaction => WitnessTransaction(wtx.version, @@ -306,7 +306,7 @@ object BitcoinSigner extends SignerUtils { val txToSign = spendingInfo.output.scriptPubKey match { case _: WitnessScriptPubKey => tx match { - case btx: BaseTransaction => + case btx: NonWitnessTransaction => val witnesses = psbt.inputMaps.map { map => map.witnessScriptOpt.map(scriptWit => P2WSHWitnessV0(scriptWit.witnessScript)) @@ -518,7 +518,7 @@ sealed abstract class P2SHSigner extends Signer[P2SHInputInfo] { signedTx.inputs.updated(inputIndex.toInt, signedInput) val finalTx = signedTx match { - case btx: BaseTransaction => + case btx: NonWitnessTransaction => BaseTransaction(version = btx.version, inputs = signedInputs, outputs = btx.outputs, @@ -603,7 +603,7 @@ sealed abstract class P2WPKHSigner extends Signer[P2WPKHV0InputInfo] { } } - case btx: BaseTransaction => + case btx: NonWitnessTransaction => val wtx = WitnessTransaction.toWitnessTx(btx) sign(spendingInfoToSatisfy, wtx, isDummySignature) diff --git a/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputInfo.scala b/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputInfo.scala index 49dd4755b1..92afa3cac7 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputInfo.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputInfo.scala @@ -36,6 +36,8 @@ sealed trait InputInfo { def pubKeys: Vector[ECPublicKey] + def requiredSigs: Int + def toSpendingInfo( signers: Vector[Sign], hashType: HashType): ScriptSignatureParams[InputInfo] = { @@ -48,6 +50,15 @@ sealed trait InputInfo { ECSignatureParams(this, signer, hashType) } + def genericWithSignFrom( + signerMaterial: InputSigningInfo[InputInfo]): InputSigningInfo[ + this.type] = { + signerMaterial match { + case info: ScriptSignatureParams[InputInfo] => withSignFrom(info) + case info: ECSignatureParams[InputInfo] => withSignFrom(info) + } + } + def withSignFrom( signerMaterial: ScriptSignatureParams[InputInfo]): ScriptSignatureParams[ this.type] = { @@ -245,6 +256,7 @@ case class EmptyInputInfo(outPoint: TransactionOutPoint, amount: CurrencyUnit) override def conditionalPath: ConditionalPath = ConditionalPath.NoCondition override def pubKeys: Vector[ECPublicKey] = Vector.empty + override def requiredSigs: Int = 0 } case class P2PKInputInfo( @@ -256,6 +268,8 @@ case class P2PKInputInfo( ConditionalPath.NoCondition override def pubKeys: Vector[ECPublicKey] = Vector(scriptPubKey.publicKey) + + override def requiredSigs: Int = 1 } case class P2PKHInputInfo( @@ -269,6 +283,8 @@ case class P2PKHInputInfo( ConditionalPath.NoCondition override def pubKeys: Vector[ECPublicKey] = Vector(pubKey) + + override def requiredSigs: Int = 1 } case class P2PKWithTimeoutInputInfo( @@ -287,6 +303,8 @@ case class P2PKWithTimeoutInputInfo( override def pubKeys: Vector[ECPublicKey] = Vector(scriptPubKey.pubKey, scriptPubKey.timeoutPubKey) + + override def requiredSigs: Int = 1 } case class MultiSignatureInputInfo( @@ -298,6 +316,8 @@ case class MultiSignatureInputInfo( ConditionalPath.NoCondition override def pubKeys: Vector[ECPublicKey] = scriptPubKey.publicKeys.toVector + + override def requiredSigs: Int = scriptPubKey.requiredSigs } case class ConditionalInputInfo( @@ -332,6 +352,8 @@ case class ConditionalInputInfo( } override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys + + override def requiredSigs: Int = nestedInputInfo.requiredSigs } case class LockTimeInputInfo( @@ -350,6 +372,8 @@ case class LockTimeInputInfo( hashPreImages) override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys + + override def requiredSigs: Int = nestedInputInfo.requiredSigs } sealed trait SegwitV0NativeInputInfo extends InputInfo { @@ -390,6 +414,8 @@ case class P2WPKHV0InputInfo( ConditionalPath.NoCondition override def pubKeys: Vector[ECPublicKey] = Vector(pubKey) + + override def requiredSigs: Int = 1 } case class P2WSHV0InputInfo( @@ -410,6 +436,8 @@ case class P2WSHV0InputInfo( hashPreImages) override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys + + override def requiredSigs: Int = nestedInputInfo.requiredSigs } case class UnassignedSegwitNativeInputInfo( @@ -419,7 +447,9 @@ case class UnassignedSegwitNativeInputInfo( scriptWitness: ScriptWitness, conditionalPath: ConditionalPath, pubKeys: Vector[ECPublicKey]) - extends InputInfo + extends InputInfo { + override def requiredSigs: Int = pubKeys.length +} sealed trait P2SHInputInfo extends InputInfo { def hashPreImages: Vector[NetworkElement] @@ -431,6 +461,8 @@ sealed trait P2SHInputInfo extends InputInfo { def nestedInputInfo: InputInfo override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys + + override def requiredSigs: Int = nestedInputInfo.requiredSigs } case class P2SHNonSegwitInputInfo( diff --git a/crypto/src/main/scala/org/bitcoins/crypto/BouncyCastleUtil.scala b/crypto/src/main/scala/org/bitcoins/crypto/BouncyCastleUtil.scala index 545d9462db..8b0f837707 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/BouncyCastleUtil.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/BouncyCastleUtil.scala @@ -57,7 +57,7 @@ object BouncyCastleUtil { val pubBytes = ByteVector(point.getEncoded(privateKey.isCompressed)) require( ECPublicKey.isFullyValid(pubBytes), - s"Bouncy Castle failed to generate a valid public key, got: ${BytesUtil + s"Bouncy Castle failed to generate a valid public key, got: ${CryptoBytesUtil .encodeHex(pubBytes)}") ECPublicKey(pubBytes) } diff --git a/crypto/src/main/scala/org/bitcoins/crypto/BytesUtil.scala b/crypto/src/main/scala/org/bitcoins/crypto/CryptoBytesUtil.scala similarity index 94% rename from crypto/src/main/scala/org/bitcoins/crypto/BytesUtil.scala rename to crypto/src/main/scala/org/bitcoins/crypto/CryptoBytesUtil.scala index c279fbf89f..64dd16a6ea 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/BytesUtil.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/CryptoBytesUtil.scala @@ -7,7 +7,7 @@ import scala.math.BigInt /** * Created by chris on 2/26/16. */ -trait BytesUtil { +trait CryptoBytesUtil { def decodeHex(hex: String): ByteVector = { if (hex.isEmpty) ByteVector.empty else ByteVector.fromHex(hex).get @@ -43,7 +43,7 @@ trait BytesUtil { } def encodeHex(bigInt: BigInt): String = - BytesUtil.encodeHex(ByteVector(bigInt.toByteArray)) + CryptoBytesUtil.encodeHex(ByteVector(bigInt.toByteArray)) /** Tests if a given string is a hexadecimal string. */ def isHex(str: String): Boolean = { @@ -54,7 +54,7 @@ trait BytesUtil { /** Converts a two character hex string to its byte representation. */ def hexToByte(hex: String): Byte = { require(hex.length == 2) - BytesUtil.decodeHex(hex).head + CryptoBytesUtil.decodeHex(hex).head } /** Flips the endianness of the give hex string. */ @@ -94,4 +94,4 @@ trait BytesUtil { } } -object BytesUtil extends BytesUtil +object CryptoBytesUtil extends CryptoBytesUtil diff --git a/crypto/src/main/scala/org/bitcoins/crypto/ECDigitalSignature.scala b/crypto/src/main/scala/org/bitcoins/crypto/ECDigitalSignature.scala index ccaccdbe85..e12ba2e34e 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/ECDigitalSignature.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/ECDigitalSignature.scala @@ -8,7 +8,7 @@ import scodec.bits.ByteVector sealed abstract class ECDigitalSignature { require(r.signum == 1 || r.signum == 0, s"r must not be negative, got $r") require(s.signum == 1 || s.signum == 0, s"s must not be negative, got $s") - def hex: String = BytesUtil.encodeHex(bytes) + def hex: String = CryptoBytesUtil.encodeHex(bytes) def bytes: ByteVector @@ -162,7 +162,7 @@ object ECDigitalSignature extends Factory[ECDigitalSignature] { * the second 32 is the value */ def fromRS(hex: String): ECDigitalSignature = { - val bytes = BytesUtil.decodeHex(hex) + val bytes = CryptoBytesUtil.decodeHex(hex) fromRS(bytes) } } diff --git a/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala b/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala index b1eb878518..b225d68b76 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala @@ -88,9 +88,10 @@ sealed abstract class ECPrivateKey val pubKeyBytes: Array[Byte] = NativeSecp256k1.computePubkey(bytes.toArray, isCompressed) val pubBytes = ByteVector(pubKeyBytes) - require(ECPublicKey.isFullyValid(pubBytes), - s"secp256k1 failed to generate a valid public key, got: ${BytesUtil - .encodeHex(pubBytes)}") + require( + ECPublicKey.isFullyValid(pubBytes), + s"secp256k1 failed to generate a valid public key, got: ${CryptoBytesUtil + .encodeHex(pubBytes)}") ECPublicKey(pubBytes) } @@ -140,11 +141,11 @@ object ECPrivateKey extends Factory[ECPrivateKey] { else throw new IllegalArgumentException( "Private keys cannot be greater than 33 bytes in size, got: " + - BytesUtil.encodeHex(bytes) + " which is of size: " + bytes.size) + CryptoBytesUtil.encodeHex(bytes) + " which is of size: " + bytes.size) } def fromHex(hex: String, isCompressed: Boolean): ECPrivateKey = - fromBytes(BytesUtil.decodeHex(hex), isCompressed) + fromBytes(CryptoBytesUtil.decodeHex(hex), isCompressed) /** Generates a fresh [[org.bitcoins.crypto.ECPrivateKey ECPrivateKey]] that has not been used before. */ def apply(): ECPrivateKey = ECPrivateKey(true) @@ -221,7 +222,7 @@ sealed abstract class ECPublicKey extends BaseECKey { } def verify(hex: String, signature: ECDigitalSignature): Boolean = - verify(BytesUtil.decodeHex(hex), signature) + verify(CryptoBytesUtil.decodeHex(hex), signature) override def toString: String = "ECPublicKey(" + hex + ")" diff --git a/crypto/src/main/scala/org/bitcoins/crypto/Factory.scala b/crypto/src/main/scala/org/bitcoins/crypto/Factory.scala index 4a1167cc5b..81e506fb3e 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/Factory.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/Factory.scala @@ -9,10 +9,10 @@ import scodec.bits.ByteVector abstract class Factory[+T] { /** Creates a T out of a hex string. */ - def fromHex(hex: String): T = fromBytes(BytesUtil.decodeHex(hex)) + def fromHex(hex: String): T = fromBytes(CryptoBytesUtil.decodeHex(hex)) /** Creates a T out of a hex string in little endian. */ - def fromHexLE(hex: String): T = fromBytesLE(BytesUtil.decodeHex(hex)) + def fromHexLE(hex: String): T = fromBytesLE(CryptoBytesUtil.decodeHex(hex)) /** Creates a T out of a sequence of bytes. */ def fromBytes(bytes: ByteVector): T diff --git a/docs/core/txbuilder.md b/docs/core/txbuilder.md index 56dca9d3cc..4d4063f0ed 100644 --- a/docs/core/txbuilder.md +++ b/docs/core/txbuilder.md @@ -3,8 +3,7 @@ id: txbuilder title: TxBuilder Example --- -Bitcoin-S features a transaction builder that constructs and signs Bitcoin -transactions. Here's an example of how to use it +Bitcoin-S features a transaction building API that allows you to construct and sign Bitcoin transactions. Here's an example of how to use it ```scala mdoc:invisible import scala.concurrent._ @@ -29,6 +28,9 @@ import wallet.utxo._ implicit val ec: ExecutionContext = ExecutionContext.Implicits.global +// Initialize a transaction builder +val builder = RawTxBuilder() + // generate a fresh private key that we are going to use in the scriptpubkey val privKey = ECPrivateKey.freshPrivateKey val pubKey = privKey.publicKey @@ -48,7 +50,7 @@ val destinationPrivKey = ECPrivateKey.freshPrivateKey // the amount we are sending -- 5000 satoshis -- to the destinationSPK val destinationAmount = 5000.satoshis -// the script that corresponds to destination private key, this is what is protecting the money +// the script that corresponds to destination private key, this is what is receiving the money val destinationSPK = P2PKHScriptPubKey(pubKey = destinationPrivKey.publicKey) @@ -56,12 +58,15 @@ val destinationSPK = // we could add more destinations here if we // wanted to batch transactions val destinations = { - val destination1 = TransactionOutput(value = destinationAmount, + val destination0 = TransactionOutput(value = destinationAmount, scriptPubKey = destinationSPK) - Vector(destination1) + Vector(destination0) } +// Add the destinations to the tx builder +builder ++= destinations + // we have to fabricate a transaction that contains the // UTXO we are trying to spend. If this were a real blockchain // we would need to reference the UTXO set @@ -73,23 +78,19 @@ val creditingTx = BaseTransaction(version = Int32.one, // this is the information we need from the crediting TX // to properly "link" it in the transaction we are creating val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero) +val input = TransactionInput( + outPoint, + EmptyScriptSignature, + sequenceNumber = UInt32.zero) -// this contains all the information we need to -// validly sign the UTXO above -val utxoInfo = ScriptSignatureParams(inputInfo = InputInfo(outPoint = outPoint, - output = utxo, - redeemScriptOpt = None, - scriptWitnessOpt = None, - conditionalPath = - ConditionalPath.NoCondition, - hashPreImages = Vector(privKey.publicKey)), - signers = Vector(privKey), - hashType = - HashType.sigHashAll) +// Add a new input to our builder +builder += input -// all of the UTXO spending information, since we are only -//spending one UTXO, this is just one element -val utxos: Vector[ScriptSignatureParams[InputInfo]] = Vector(utxoInfo) +// We can now generate a RawTxBuilderResult ready to be finalized +val builderResult = builder.result() + +// this contains the information needed to analyze our input during finalization +val inputInfo = P2PKHInputInfo(outPoint, amount, privKey.publicKey) // this is how much we are going to pay as a fee to the network // for this example, we are going to pay 1 satoshi per byte @@ -98,24 +99,28 @@ val feeRate = SatoshisPerByte(1.satoshi) val changePrivKey = ECPrivateKey.freshPrivateKey val changeSPK = P2PKHScriptPubKey(pubKey = changePrivKey.publicKey) -// the network we are on, for this example we are using -// the regression test network. This is a network you control -// on your own machine -val networkParams = RegTest +// We chose a finalizer that adds a change output to our tx based on a fee rate +val finalizer = NonInteractiveWithChangeFinalizer( + Vector(inputInfo), + feeRate, + changeSPK) -// Yay! Now we have a TxBuilder object that we can use -// to sign the TX. -val txBuilder: BitcoinTxBuilder = { - val builderF = BitcoinTxBuilder( - destinations = destinations, - utxos = utxos, - feeRate = feeRate, - changeSPK = changeSPK, - network = networkParams) - Await.result(builderF, 30.seconds) -} +// We can now finalize the tx builder result from earlier with this finalizer +val unsignedTxF: Future[Transaction] = finalizer.buildTx(builderResult) -// Let's finally produce a validly signed tx! +// We now turn to signing the unsigned transaction +// this contains all the information we need to +// validly sign the UTXO above +val utxoInfo = ScriptSignatureParams(inputInfo = inputInfo, + signers = Vector(privKey), + hashType = + HashType.sigHashAll) + +// all of the UTXO spending information, since we only have +// one input, this is just one element +val utxoInfos: Vector[ScriptSignatureParams[InputInfo]] = Vector(utxoInfo) + +// Yay! Now we use the RawTxSigner object to sign the tx. // The 'sign' method is going produce a validly signed transaction // This is going to iterate through each of the UTXOs and use // the corresponding ScriptSignatureParams to produce a validly @@ -124,8 +129,14 @@ val txBuilder: BitcoinTxBuilder = { // 2: outputs (destination and change outputs) // 3: a fee rate of 1 satoshi/byte val signedTx: Transaction = { - val signF = txBuilder.sign - Await.result(signF, 30.seconds) + val signedTxF = unsignedTxF.flatMap { unsignedTx => + RawTxSigner.sign( + utx = unsignedTx, + utxoInfos = utxoInfos, + expectedFeeRate = feeRate + ) + } + Await.result(signedTxF, 30.seconds) } ``` diff --git a/eclair-rpc/src/main/scala/org/bitcoins/eclair/rpc/client/EclairRpcClient.scala b/eclair-rpc/src/main/scala/org/bitcoins/eclair/rpc/client/EclairRpcClient.scala index 4c0cd32ee3..3c898e8449 100644 --- a/eclair-rpc/src/main/scala/org/bitcoins/eclair/rpc/client/EclairRpcClient.scala +++ b/eclair-rpc/src/main/scala/org/bitcoins/eclair/rpc/client/EclairRpcClient.scala @@ -30,9 +30,9 @@ import org.bitcoins.core.protocol.ln.{ } import org.bitcoins.core.protocol.script.ScriptPubKey import org.bitcoins.core.protocol.{Address, BitcoinAddress} -import org.bitcoins.core.util.{FutureUtil, StartStop} +import org.bitcoins.core.util.{BytesUtil, FutureUtil, StartStop} import org.bitcoins.core.wallet.fee.SatoshisPerByte -import org.bitcoins.crypto.{BytesUtil, Sha256Digest} +import org.bitcoins.crypto.Sha256Digest import org.bitcoins.eclair.rpc.api._ import org.bitcoins.eclair.rpc.config.EclairInstance import org.bitcoins.eclair.rpc.network.NodeUri diff --git a/testkit/src/main/scala/org/bitcoins/testkit/core/gen/PSBTGenerators.scala b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/PSBTGenerators.scala index 140da61d8c..373fa1943e 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/core/gen/PSBTGenerators.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/PSBTGenerators.scala @@ -1,19 +1,25 @@ package org.bitcoins.testkit.core.gen -import org.bitcoins.core.config.BitcoinNetwork import org.bitcoins.core.currency.CurrencyUnit import org.bitcoins.core.number.{Int32, UInt32} +import org.bitcoins.core.policy.Policy import org.bitcoins.core.protocol.script.ScriptPubKey import org.bitcoins.core.protocol.transaction.{ BaseTransaction, + InputUtil, Transaction, - TransactionOutput + TransactionOutput, + TxUtil } import org.bitcoins.core.psbt.GlobalPSBTRecord.Version import org.bitcoins.core.psbt.PSBT.SpendingInfoAndNonWitnessTxs import org.bitcoins.core.psbt.PSBTInputKeyId.PartialSignatureKeyId import org.bitcoins.core.psbt._ -import org.bitcoins.core.wallet.builder.BitcoinTxBuilder +import org.bitcoins.core.wallet.builder.{ + FinalizedTxWithSigningInfo, + NonInteractiveWithChangeFinalizer, + RawTxBuilder +} import org.bitcoins.core.wallet.fee.FeeUnit import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams} import org.scalacheck.Gen @@ -146,10 +152,10 @@ object PSBTGenerators { } def psbtToBeSigned(implicit ec: ExecutionContext): Gen[ - Future[(PSBT, Seq[ScriptSignatureParams[InputInfo]])]] = { + Future[(PSBT, Seq[ScriptSignatureParams[InputInfo]], FeeUnit)]] = { psbtWithBuilder(finalized = false).map { psbtAndBuilderF => psbtAndBuilderF.flatMap { - case (psbt, builder) => + case (psbt, FinalizedTxWithSigningInfo(_, infos), fee) => val newInputsMaps = psbt.inputMaps.map { map => InputPSBTMap(map.elements.filterNot(element => PSBTInputKeyId.fromBytes(element.key) == PartialSignatureKeyId)) @@ -157,7 +163,8 @@ object PSBTGenerators { Future.successful( PSBT(psbt.globalMap, newInputsMaps, psbt.outputMaps), - builder.utxos) + infos, + fee) } } } @@ -189,14 +196,21 @@ object PSBTGenerators { creditingTxsInfo: Seq[ScriptSignatureParams[InputInfo]], destinations: Seq[TransactionOutput], changeSPK: ScriptPubKey, - network: BitcoinNetwork, - fee: FeeUnit)( - implicit ec: ExecutionContext): Future[(PSBT, BitcoinTxBuilder)] = { - val builderF = - BitcoinTxBuilder(destinations, creditingTxsInfo, fee, changeSPK, network) + fee: FeeUnit)(implicit ec: ExecutionContext): Future[ + (PSBT, FinalizedTxWithSigningInfo, FeeUnit)] = { + val lockTime = TxUtil.calcLockTime(creditingTxsInfo).get + val inputs = + InputUtil.calcSequenceForInputs(creditingTxsInfo, Policy.isRBFEnabled) + + val builder = RawTxBuilder().setLockTime(lockTime) ++= destinations ++= inputs + val finalizer = NonInteractiveWithChangeFinalizer( + creditingTxsInfo.toVector.map(_.inputInfo), + fee, + changeSPK) + builder.setFinalizer(finalizer) + for { - builder <- builderF - unsignedTx <- builder.unsignedTx + unsignedTx <- builder.setFinalizer(finalizer).buildTx() orderedTxInfos = spendingInfoAndNonWitnessTxsFromSpendingInfos( unsignedTx, @@ -209,15 +223,16 @@ object PSBTGenerators { PSBT.fromUnsignedTxAndInputs(unsignedTx, orderedTxInfos) } } - } yield (psbt, builder) + } yield (psbt, + FinalizedTxWithSigningInfo(unsignedTx, creditingTxsInfo.toVector), + fee) } - def psbtWithBuilder(finalized: Boolean)( - implicit ec: ExecutionContext): Gen[Future[(PSBT, BitcoinTxBuilder)]] = { + def psbtWithBuilder(finalized: Boolean)(implicit ec: ExecutionContext): Gen[ + Future[(PSBT, FinalizedTxWithSigningInfo, FeeUnit)]] = { for { (creditingTxsInfo, destinations) <- CreditingTxGen.inputsAndOutputs() (changeSPK, _) <- ScriptGenerators.scriptPubKey - network <- ChainParamsGenerator.bitcoinNetworkParams maxFee = { val crediting = creditingTxsInfo.foldLeft(0L)(_ + _.amount.satoshis.toLong) @@ -230,7 +245,6 @@ object PSBTGenerators { creditingTxsInfo = creditingTxsInfo, destinations = destinations, changeSPK = changeSPK, - network = network, fee = fee) } } @@ -240,12 +254,11 @@ object PSBTGenerators { outputGen: CurrencyUnit => Gen[Seq[(TransactionOutput, ScriptPubKey)]] = TransactionGenerators.smallP2SHOutputs)( implicit ec: ExecutionContext): Gen[ - Future[(PSBT, BitcoinTxBuilder, Seq[ScriptPubKey])]] = { + Future[(PSBT, FinalizedTxWithSigningInfo, Seq[ScriptPubKey])]] = { for { (creditingTxsInfo, outputs) <- CreditingTxGen.inputsAndP2SHOutputs( destinationGenerator = outputGen) changeSPK <- ScriptGenerators.scriptPubKey - network <- ChainParamsGenerator.bitcoinNetworkParams maxFee = { val destinations = outputs.map(_._1) val crediting = @@ -259,7 +272,6 @@ object PSBTGenerators { creditingTxsInfo = creditingTxsInfo, destinations = outputs.map(_._1), changeSPK = changeSPK._1, - network = network, fee = fee) pAndB.map(p => (p._1, p._2, outputs.map(_._2))) @@ -268,12 +280,12 @@ object PSBTGenerators { def psbtWithBuilderAndP2WSHOutputs(finalized: Boolean)( implicit ec: ExecutionContext): Gen[ - Future[(PSBT, BitcoinTxBuilder, Seq[ScriptPubKey])]] = + Future[(PSBT, FinalizedTxWithSigningInfo, Seq[ScriptPubKey])]] = psbtWithBuilderAndP2SHOutputs(finalized, TransactionGenerators.smallP2WSHOutputs) - def finalizedPSBTWithBuilder( - implicit ec: ExecutionContext): Gen[Future[(PSBT, BitcoinTxBuilder)]] = { + def finalizedPSBTWithBuilder(implicit ec: ExecutionContext): Gen[ + Future[(PSBT, FinalizedTxWithSigningInfo, FeeUnit)]] = { psbtWithBuilder(finalized = true) } diff --git a/testkit/src/main/scala/org/bitcoins/testkit/util/BitcoinSAsyncTest.scala b/testkit/src/main/scala/org/bitcoins/testkit/util/BitcoinSAsyncTest.scala index 35a99d94f3..e1b9447500 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/util/BitcoinSAsyncTest.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/util/BitcoinSAsyncTest.scala @@ -198,7 +198,8 @@ trait BaseAsyncTest /** Runs all property based tests in parallel. This is a convinient optimization * for synchronous property based tests */ - def forAllParallel[A](gen: Gen[A])(func: A => Assertion): Future[Assertion] = { + def forAllParallel[A](gen: Gen[A])( + func: A => Assertion): Future[Assertion] = { forAllAsync(gen) { a: A => Future { func(a) diff --git a/testkit/src/main/scala/org/bitcoins/testkit/util/TestUtil.scala b/testkit/src/main/scala/org/bitcoins/testkit/util/TestUtil.scala index 6e55451725..da5d33fc0f 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/util/TestUtil.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/util/TestUtil.scala @@ -5,12 +5,25 @@ import org.bitcoins.core.currency.CurrencyUnits import org.bitcoins.core.number.UInt32 import org.bitcoins.core.policy.Policy import org.bitcoins.core.protocol.{Bech32Address, BitcoinAddress} -import org.bitcoins.core.protocol.script.{EmptyScriptPubKey, P2SHScriptSignature, ScriptPubKey, ScriptSignature} -import org.bitcoins.core.protocol.transaction.{Transaction, TransactionInput, TransactionOutput} +import org.bitcoins.core.protocol.script.{ + EmptyScriptPubKey, + P2SHScriptSignature, + ScriptPubKey, + ScriptSignature +} +import org.bitcoins.core.protocol.transaction.{ + Transaction, + TransactionInput, + TransactionOutput +} import org.bitcoins.core.script.PreExecutionScriptProgram import org.bitcoins.core.script.bitwise.{OP_EQUAL, OP_EQUALVERIFY} import org.bitcoins.core.script.constant._ -import org.bitcoins.core.script.crypto.{OP_CHECKMULTISIG, OP_CHECKSIG, OP_HASH160} +import org.bitcoins.core.script.crypto.{ + OP_CHECKMULTISIG, + OP_CHECKSIG, + OP_HASH160 +} import org.bitcoins.core.script.stack.OP_DUP import org.bitcoins.core.serializers.script.RawScriptPubKeyParser import org.bitcoins.core.serializers.transaction.RawTransactionInputParser @@ -22,7 +35,9 @@ object TestUtil { def testBitcoinAddress = BitcoinAddress("n3p1ct69ao3qxWvEvzLhLtWG2zJGTjN3EV") def testP2SHAddress = BitcoinAddress("2MzYbQdkSVp5wVyMRp6A5PHPuQNHpiaTbCj") - val bech32Address: Bech32Address = Bech32Address.fromString("bcrt1qq6w6pu6zq90az9krn53zlkvgyzkyeglzukyepf").get + + val bech32Address: Bech32Address = + Bech32Address.fromString("bcrt1qq6w6pu6zq90az9krn53zlkvgyzkyeglzukyepf").get def bitcoinAddress = BitcoinAddress("1C4kYhyLftmkn48YarSoLupxHfYFo8kp64") def multiSigAddress = BitcoinAddress("342ftSRCvFHfCeFFBuz4xwbeqnDw6BGUey") diff --git a/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala b/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala index 9fcdfe9f1d..c269c173cb 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala @@ -5,7 +5,6 @@ import java.time.Instant import org.bitcoins.commons.jsonmodels.wallet.CoinSelectionAlgo import org.bitcoins.core.api.{ChainQueryApi, NodeApi} import org.bitcoins.core.bloom.{BloomFilter, BloomUpdateAll} -import org.bitcoins.core.config.BitcoinNetwork import org.bitcoins.core.crypto.ExtPublicKey import org.bitcoins.core.currency._ import org.bitcoins.core.hd.{HDAccount, HDCoin, HDPurposes} @@ -16,9 +15,17 @@ import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.script.constant.ScriptConstant import org.bitcoins.core.script.control.OP_RETURN import org.bitcoins.core.util.BitcoinScriptUtil -import org.bitcoins.core.wallet.builder.BitcoinTxBuilder +import org.bitcoins.core.wallet.builder.{ + NonInteractiveWithChangeFinalizer, + RawTxBuilderWithFinalizer, + RawTxSigner +} import org.bitcoins.core.wallet.fee.FeeUnit -import org.bitcoins.core.wallet.utxo.TxoState +import org.bitcoins.core.wallet.utxo.{ + InputInfo, + ScriptSignatureParams, + TxoState +} import org.bitcoins.core.wallet.utxo.TxoState.{ ConfirmedReceived, PendingConfirmationsReceived @@ -213,17 +220,23 @@ abstract class Wallet } yield updatedInfos } - /** Takes a [[BitcoinTxBuilder]] for a transaction to be sent, and completes it by: - * signing the transaction, then correctly processing the it and logging it + /** Takes a [[RawTxBuilderWithFinalizer]] for a transaction to be sent, and completes it by: + * finalizing and signing the transaction, then correctly processing and logging it */ - private def finishSend(txBuilder: BitcoinTxBuilder): Future[Transaction] = { + private def finishSend( + txBuilder: RawTxBuilderWithFinalizer, + utxoInfos: Vector[ScriptSignatureParams[InputInfo]], + sentAmount: CurrencyUnit, + feeRate: FeeUnit): Future[Transaction] = { for { - signed <- txBuilder.sign + utx <- txBuilder.buildTx() + signed <- RawTxSigner.sign(utx, utxoInfos, feeRate) ourOuts <- findOurOuts(signed) + creditingAmount = utxoInfos.foldLeft(CurrencyUnits.zero)(_ + _.amount) _ <- processOurTransaction(transaction = signed, - feeRate = txBuilder.feeRate, - inputAmount = txBuilder.creditingAmount, - sentAmount = txBuilder.destinationAmount, + feeRate = feeRate, + inputAmount = creditingAmount, + sentAmount = sentAmount, blockHashOpt = None) } yield { logger.debug( @@ -259,13 +272,13 @@ abstract class Wallet changeAddr <- getNewChangeAddress(fromAccount.hdAccount) output = TransactionOutput(amount, address.scriptPubKey) - txBuilder <- BitcoinTxBuilder( + txBuilder = NonInteractiveWithChangeFinalizer.txBuilderFrom( Vector(output), utxos, feeRate, - changeAddr.scriptPubKey, - networkParameters.asInstanceOf[BitcoinNetwork]) - tx <- finishSend(txBuilder) + changeAddr.scriptPubKey) + + tx <- finishSend(txBuilder, utxos, amount, feeRate) } yield tx } @@ -282,14 +295,14 @@ abstract class Wallet logger.info(s"Sending $amount to $address at feerate $feeRate") val destination = TransactionOutput(amount, address.scriptPubKey) for { - txBuilder <- fundRawTransactionInternal( + (txBuilder, utxoInfos) <- fundRawTransactionInternal( destinations = Vector(destination), feeRate = feeRate, fromAccount = fromAccount, keyManagerOpt = Some(keyManager), coinSelectionAlgo = algo) - tx <- finishSend(txBuilder) + tx <- finishSend(txBuilder, utxoInfos, amount, feeRate) } yield tx } @@ -355,12 +368,14 @@ abstract class Wallet fromAccount: AccountDb, reserveUtxos: Boolean): Future[Transaction] = { for { - txBuilder <- fundRawTransactionInternal(destinations = outputs, - feeRate = feeRate, - fromAccount = fromAccount, - keyManagerOpt = Some(keyManager), - markAsReserved = reserveUtxos) - tx <- finishSend(txBuilder) + (txBuilder, utxoInfos) <- fundRawTransactionInternal( + destinations = outputs, + feeRate = feeRate, + fromAccount = fromAccount, + keyManagerOpt = Some(keyManager), + markAsReserved = reserveUtxos) + sentAmount = outputs.foldLeft(CurrencyUnits.zero)(_ + _.value) + tx <- finishSend(txBuilder, utxoInfos, sentAmount, feeRate) } yield tx } diff --git a/wallet/src/main/scala/org/bitcoins/wallet/internal/FundTransactionHandling.scala b/wallet/src/main/scala/org/bitcoins/wallet/internal/FundTransactionHandling.scala index e9c878505b..43eb7a5b24 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/internal/FundTransactionHandling.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/internal/FundTransactionHandling.scala @@ -1,15 +1,22 @@ package org.bitcoins.wallet.internal import org.bitcoins.commons.jsonmodels.wallet.CoinSelectionAlgo -import org.bitcoins.core.config.BitcoinNetwork import org.bitcoins.core.consensus.Consensus +import org.bitcoins.core.policy.Policy import org.bitcoins.core.protocol.transaction.{ EmptyTransactionOutPoint, + InputUtil, Transaction, - TransactionOutput + TransactionOutput, + TxUtil +} +import org.bitcoins.core.wallet.builder.{ + NonInteractiveWithChangeFinalizer, + RawTxBuilder, + RawTxBuilderWithFinalizer } -import org.bitcoins.core.wallet.builder.BitcoinTxBuilder import org.bitcoins.core.wallet.fee.FeeUnit +import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams} import org.bitcoins.crypto.Sign import org.bitcoins.keymanager.bip39.BIP39KeyManager import org.bitcoins.wallet.WalletLogger @@ -44,17 +51,16 @@ trait FundTransactionHandling extends WalletLogger { self: WalletApi => fromAccount = fromAccount, keyManagerOpt = None, markAsReserved = markAsReserved) - txBuilderF.flatMap(_.unsignedTx) + txBuilderF.flatMap(_._1.buildTx()) } - /** This returns a [[BitcoinTxBuilder]] that can be used to generate a unsigned transaction with [[BitcoinTxBuilder.unsignedTx unsignedTx]] - * which can be used with signing, or you can just directly call [[BitcoinTxBuilder.sign sign]] to sign the transaction with this instance - * of [[BitcoinTxBuilder]] + /** This returns a [[RawTxBuilder]] that can be used to generate an unsigned transaction with [[RawTxBuilder.result()]] + * which can be used with signing. * * If you pass in a [[org.bitcoins.keymanager.KeyManager]], the [[org.bitcoins.core.wallet.utxo.ScriptSignatureParams.signers signers]] * will be populated with valid signers that can be used to produce valid [[org.bitcoins.crypto.ECDigitalSignature signatures]] * - * If you do not pass in a key manager, the transaction built by [[BitcoinTxBuilder txbuilder]] will contain [[org.bitcoins.core.protocol.script.EmptyScriptSignature EmptyScriptSignature]] + * If you do not pass in a key manager, the transaction built by [[RawTxBuilder txbuilder]] will contain [[org.bitcoins.core.protocol.script.EmptyScriptSignature EmptyScriptSignature]] * * Currently utxos are funded with [[CoinSelector.accumulateLargest() accumulateLargest]] coin seleciton algorithm * */ @@ -64,7 +70,8 @@ trait FundTransactionHandling extends WalletLogger { self: WalletApi => fromAccount: AccountDb, keyManagerOpt: Option[BIP39KeyManager], coinSelectionAlgo: CoinSelectionAlgo = CoinSelectionAlgo.AccumulateLargest, - markAsReserved: Boolean = false): Future[BitcoinTxBuilder] = { + markAsReserved: Boolean = false): Future[ + (RawTxBuilderWithFinalizer, Vector[ScriptSignatureParams[InputInfo]])] = { val utxosF = for { utxos <- listUtxos(fromAccount.hdAccount) @@ -121,35 +128,35 @@ trait FundTransactionHandling extends WalletLogger { self: WalletApi => } } - txBuilder <- { - - logger.info({ - val utxosStr = utxoSpendingInfos - .map { utxo => - import utxo.outPoint - s"${outPoint.txId.hex}:${outPoint.vout.toInt}" - } - .mkString(", ") - s"Spending UTXOs: $utxosStr" - }) - - utxoSpendingInfos.zipWithIndex.foreach { - case (utxo, index) => - logger.info(s"UTXO $index details: ${utxo.output}") - } - - networkParameters match { - case b: BitcoinNetwork => - BitcoinTxBuilder(destinations = destinations, - utxos = utxoSpendingInfos, - feeRate = feeRate, - changeSPK = change.scriptPubKey, - network = b) - } - - } } yield { - txBuilder + logger.info({ + val utxosStr = utxoSpendingInfos + .map { utxo => + import utxo.outPoint + s"${outPoint.txId.hex}:${outPoint.vout.toInt}" + } + .mkString(", ") + s"Spending UTXOs: $utxosStr" + }) + + utxoSpendingInfos.zipWithIndex.foreach { + case (utxo, index) => + logger.info(s"UTXO $index details: ${utxo.output}") + } + + val inputs = + InputUtil.calcSequenceForInputs(utxoSpendingInfos, Policy.isRBFEnabled) + + val lockTime = TxUtil.calcLockTime(utxoSpendingInfos).get + + val txBuilder = RawTxBuilder().setLockTime(lockTime) ++= destinations ++= inputs + + val finalizer = NonInteractiveWithChangeFinalizer( + utxoSpendingInfos.map(_.inputInfo), + feeRate, + change.scriptPubKey) + + (txBuilder.setFinalizer(finalizer), utxoSpendingInfos) } txBuilderF diff --git a/wallet/src/main/scala/org/bitcoins/wallet/models/TransactionDb.scala b/wallet/src/main/scala/org/bitcoins/wallet/models/TransactionDb.scala index e58dafdf6d..ae6231d70f 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/models/TransactionDb.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/models/TransactionDb.scala @@ -6,6 +6,7 @@ import org.bitcoins.core.protocol.script.EmptyScriptSignature import org.bitcoins.core.protocol.transaction.{ BaseTransaction, EmptyWitness, + NonWitnessTransaction, Transaction, TransactionInput, WitnessTransaction @@ -51,7 +52,7 @@ object TransactionDb { def fromTransaction(tx: Transaction): TransactionDb = { val (unsignedTx, wTxIdBEOpt) = tx match { - case btx: BaseTransaction => + case btx: NonWitnessTransaction => val unsignedInputs = btx.inputs.map( input => TransactionInput(input.previousOutput, diff --git a/zmq/src/test/scala/org/bitcoins/zmq/ZMQSubscriberTest.scala b/zmq/src/test/scala/org/bitcoins/zmq/ZMQSubscriberTest.scala index 4f8f3ae019..b6bd408ebd 100644 --- a/zmq/src/test/scala/org/bitcoins/zmq/ZMQSubscriberTest.scala +++ b/zmq/src/test/scala/org/bitcoins/zmq/ZMQSubscriberTest.scala @@ -2,7 +2,7 @@ package org.bitcoins.zmq import java.net.InetSocketAddress -import org.bitcoins.crypto.BytesUtil +import org.bitcoins.core.util.BytesUtil import org.scalatest.{AsyncFlatSpec, MustMatchers} import org.slf4j.LoggerFactory import org.zeromq.{ZFrame, ZMQ, ZMsg}