From e6899b20b1f6176f89c2f7615612bdd77938c69d Mon Sep 17 00:00:00 2001 From: Nadav Kohen Date: Tue, 23 Mar 2021 17:05:58 -0500 Subject: [PATCH] Made ECPrivateKey signing synchronous and got src compiling (#2652) Fixed tests De-futured tx buidling, finalizing and signing Responded to review --- .../bitcoins/rpc/common/WalletRpcTest.scala | 5 +- .../bitcoins/core/crypto/ExtSignTest.scala | 17 +- .../TransactionSignatureCreatorTest.scala | 40 ++- .../TransactionSignatureSerializerTest.scala | 34 +-- .../core/psbt/PSBTSerializerTest.scala | 16 +- .../org/bitcoins/core/psbt/PSBTTest.scala | 277 ++++++++--------- .../org/bitcoins/core/psbt/PSBTUnitTest.scala | 22 +- .../wallet/builder/RawTxFinalizerTest.scala | 63 ++-- .../core/wallet/builder/RawTxSignerTest.scala | 122 ++++---- ...ShufflingNonInteractiveFinalizerTest.scala | 37 +-- .../StandardNonInteractiveFinalizerTest.scala | 10 +- .../core/wallet/signer/SignerTest.scala | 234 +++++++------- .../core/wallet/utxo/InputInfoTest.scala | 41 ++- .../org/bitcoins/core/crypto/ExtKey.scala | 21 +- .../org/bitcoins/core/crypto/ExtSign.scala | 31 +- .../core/protocol/transaction/TxUtil.scala | 81 +++-- .../scala/org/bitcoins/core/psbt/PSBT.scala | 19 +- .../org/bitcoins/core/psbt/PSBTMap.scala | 127 ++++---- .../builder/FinalizedTxWithSigningInfo.scala | 8 +- .../core/wallet/builder/RawTxBuilder.scala | 3 +- .../core/wallet/builder/RawTxFinalizer.scala | 215 +++++++------ .../core/wallet/builder/RawTxSigner.scala | 95 +++--- .../bitcoins/core/wallet/signer/Signer.scala | 287 ++++++++---------- .../bitcoins/crypto/SignWithEntropyTest.scala | 31 +- .../scala/org/bitcoins/crypto/SignTest.scala | 10 +- .../scala/org/bitcoins/crypto/ECKey.scala | 23 +- .../main/scala/org/bitcoins/crypto/Sign.scala | 192 ++++++++---- docs/core/psbts.md | 56 ++-- docs/core/txbuilder.md | 18 +- .../testkitcore/gen/PSBTGenerators.scala | 142 ++++----- .../testkitcore/gen/ScriptGenerators.scala | 24 +- .../wallet/AddressTagIntegrationTest.scala | 4 +- .../wallet/FundTransactionHandlingTest.scala | 5 +- .../scala/org/bitcoins/wallet/Wallet.scala | 21 +- .../internal/FundTransactionHandling.scala | 2 +- 35 files changed, 1107 insertions(+), 1226 deletions(-) diff --git a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/WalletRpcTest.scala b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/WalletRpcTest.scala index 216f93ec95..1bbb883cb5 100644 --- a/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/WalletRpcTest.scala +++ b/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/WalletRpcTest.scala @@ -578,7 +578,8 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest { output = prevTx.outputs(outPoint.vout.toInt) privKey <- client.dumpPrivKey( BitcoinAddress.fromScriptPubKey(output.scriptPubKey, RegTest)) - partialSig <- BitcoinSigner.signSingle( + } yield { + val partialSig = BitcoinSigner.signSingle( ECSignatureParams( P2WPKHV0InputInfo(outPoint, output.value, privKey.publicKey), prevTx, @@ -586,7 +587,7 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest { HashType.sigHashAll), transaction, isDummySignature = false) - } yield { + signedTx match { case btx: NonWitnessTransaction => assert( diff --git a/core-test/src/test/scala/org/bitcoins/core/crypto/ExtSignTest.scala b/core-test/src/test/scala/org/bitcoins/core/crypto/ExtSignTest.scala index b07556718d..de4f44af73 100644 --- a/core-test/src/test/scala/org/bitcoins/core/crypto/ExtSignTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/crypto/ExtSignTest.scala @@ -1,9 +1,9 @@ package org.bitcoins.core.crypto import org.bitcoins.testkitcore.gen.{CryptoGenerators, HDGenerators} -import org.bitcoins.testkitcore.util.BitcoinSJvmTest +import org.bitcoins.testkitcore.util.BitcoinSUnitTest -class ExtSignTest extends BitcoinSJvmTest { +class ExtSignTest extends BitcoinSUnitTest { it must "be able to sign something that extends ExtSignKey" in { forAll(CryptoGenerators.extPrivateKey, CryptoGenerators.sha256Digest) { @@ -14,15 +14,12 @@ class ExtSignTest extends BitcoinSJvmTest { } it must "be able to sign a specific path of a ext key" in { - forAllAsync(CryptoGenerators.extPrivateKey, - CryptoGenerators.sha256Digest, - HDGenerators.bip32Path) { case (extPrivKey, hash, path) => - val sigF = extPrivKey.deriveAndSignFuture(hash.bytes, path) + forAll(CryptoGenerators.extPrivateKey, + CryptoGenerators.sha256Digest, + HDGenerators.bip32Path) { case (extPrivKey, hash, path) => + val sig = extPrivKey.deriveAndSign(hash.bytes, path) val childPubKey = extPrivKey.deriveChildPubKey(path).get - sigF.map { sig => - assert(childPubKey.key.verify(hash, sig)) - } - + assert(childPubKey.key.verify(hash, sig)) } } } diff --git a/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala b/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala index 2b3c74c43d..06eba876bd 100644 --- a/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala @@ -121,46 +121,40 @@ class TransactionSignatureCreatorTest extends BitcoinSJvmTest { it should "have old and new createSig functions agree" in { - forAllAsync(CreditingTxGen.inputsAndOutputs(), - ScriptGenerators.scriptPubKey) { + forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) { case ((creditingTxsInfo, destinations), (changeSPK, _)) => val fee = SatoshisPerVirtualByte(Satoshis(100)) - val unsignedTxF = StandardNonInteractiveFinalizer + val spendingTx = StandardNonInteractiveFinalizer .txFrom(outputs = destinations, utxos = creditingTxsInfo, feeRate = fee, changeSPK = changeSPK) - val correctSigsF = unsignedTxF.flatMap { spendingTx => - val assertFs = creditingTxsInfo.flatMap { signInfo => + val correctSigs = + creditingTxsInfo.flatMap { signInfo => signInfo.signers.map { signer => val txSignatureComponent = TxSigComponent(signInfo.inputInfo, spendingTx) - @nowarn val oldSigF = + val oldSig = TransactionSignatureCreator.createSig(txSignatureComponent, - signer.signFunction, + signer.sign(_), signInfo.hashType) - for { - oldSig <- oldSigF - newSig <- - TransactionSignatureCreator.createSig(spendingTx, - signInfo, - signer.signFunction, - signInfo.hashType) - } yield { - (oldSig.r == newSig.r) && - (oldSig.s == newSig.s) && - (oldSig.hex == newSig.hex) - } + + val newSig = + TransactionSignatureCreator.createSig(spendingTx, + signInfo, + signer.sign(_), + signInfo.hashType) + + (oldSig.r == newSig.r) && + (oldSig.s == newSig.s) && + (oldSig.hex == newSig.hex) } } - Future.sequence(assertFs) - } - - correctSigsF.map(x => assert(x.forall(_ == true))) + assert(correctSigs.forall(_ == true)) } } 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 a94fc7ca81..c12be7c1d5 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 @@ -12,13 +12,13 @@ import org.bitcoins.core.wallet.builder.StandardNonInteractiveFinalizer import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte import org.bitcoins.crypto.DoubleSha256Digest import org.bitcoins.testkitcore.gen.{CreditingTxGen, ScriptGenerators} -import org.bitcoins.testkitcore.util.BitcoinSJvmTest +import org.bitcoins.testkitcore.util.BitcoinSUnitTest import scala.util.Try /** Created by chris on 2/19/16. */ -class TransactionSignatureSerializerTest extends BitcoinSJvmTest { +class TransactionSignatureSerializerTest extends BitcoinSUnitTest { "TransactionSignatureSerializer" must "correctly serialize an input that is being checked where another input in the same tx is using SIGHASH_ANYONECANPAY" in { //this is from a test case inside of tx_valid.json @@ -406,18 +406,17 @@ class TransactionSignatureSerializerTest extends BitcoinSJvmTest { } it should "have old and new serializeForSignature functions agree" in { - forAllAsync(CreditingTxGen.inputsAndOutputs(), - ScriptGenerators.scriptPubKey) { + forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) { case ((creditingTxsInfo, destinations), (changeSPK, _)) => val fee = SatoshisPerVirtualByte(Satoshis(100)) - val unsignedTxF = StandardNonInteractiveFinalizer + val spendingTx = StandardNonInteractiveFinalizer .txFrom(outputs = destinations, utxos = creditingTxsInfo, feeRate = fee, changeSPK = changeSPK) - val correctScriptsF = unsignedTxF.map { spendingTx => + val correctScripts = creditingTxsInfo.flatMap { signInfo => signInfo.signers.map { _ => val txSigComponent = @@ -437,25 +436,23 @@ class TransactionSignatureSerializerTest extends BitcoinSJvmTest { oldBytes == newBytes } } - } - correctScriptsF.map(x => assert(x.forall(_ == true))) + assert(correctScripts.forall(_ == true)) } } it should "have old and new hashForSignature functions agree" in { - forAllAsync(CreditingTxGen.inputsAndOutputs(), - ScriptGenerators.scriptPubKey) { + forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) { case ((creditingTxsInfo, destinations), (changeSPK, _)) => val fee = SatoshisPerVirtualByte(Satoshis(100)) - val unsignedTxF = StandardNonInteractiveFinalizer + val spendingTx = StandardNonInteractiveFinalizer .txFrom(outputs = destinations, utxos = creditingTxsInfo, feeRate = fee, changeSPK = changeSPK) - val correctHashesF = unsignedTxF.map { spendingTx => + val correctHashes = creditingTxsInfo.flatMap { signInfo => signInfo.signers.map { _ => val txSigComponent = @@ -480,25 +477,23 @@ class TransactionSignatureSerializerTest extends BitcoinSJvmTest { oldHash == newHash } } - } - correctHashesF.map(x => assert(x.forall(_ == true))) + assert(correctHashes.forall(_ == true)) } } it should "have old and new calculateScriptForSigning functions agree" in { - forAllAsync(CreditingTxGen.inputsAndOutputs(), - ScriptGenerators.scriptPubKey) { + forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) { case ((creditingTxsInfo, destinations), (changeSPK, _)) => val fee = SatoshisPerVirtualByte(Satoshis(100)) - val unsignedTxF = StandardNonInteractiveFinalizer + val spendingTx = StandardNonInteractiveFinalizer .txFrom(outputs = destinations, utxos = creditingTxsInfo, feeRate = fee, changeSPK = changeSPK) - val correctScriptsF = unsignedTxF.map { spendingTx => + val correctScripts = creditingTxsInfo.flatMap { signInfo => signInfo.signers.map { _ => val txSigComponent = @@ -518,9 +513,8 @@ class TransactionSignatureSerializerTest extends BitcoinSJvmTest { oldScript == newScript } } - } - correctScriptsF.map(x => assert(x.forall(_ == true))) + assert(correctScripts.forall(_ == true)) } } } diff --git a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTSerializerTest.scala index 54c3055a61..877f8723da 100644 --- a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTSerializerTest.scala @@ -13,13 +13,13 @@ import org.bitcoins.core.psbt.InputPSBTRecord.ProofOfReservesCommitment import org.bitcoins.core.script.crypto.HashType import org.bitcoins.crypto.ECPublicKey import org.bitcoins.testkitcore.gen.PSBTGenerators -import org.bitcoins.testkitcore.util.BitcoinSJvmTest +import org.bitcoins.testkitcore.util.BitcoinSUnitTest import scodec.bits._ /** Test vectors are taken directly from the BIP 174 reference sheet * https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#test-vectors */ -class PSBTSerializerTest extends BitcoinSJvmTest { +class PSBTSerializerTest extends BitcoinSUnitTest { val validPsbts: Vector[ByteVector] = Vector( // PSBT with one P2PKH input. Outputs are empty @@ -243,18 +243,14 @@ class PSBTSerializerTest extends BitcoinSJvmTest { } it must "fail to serialize PSBTs with unknown version numbers" in { - forAllAsync(PSBTGenerators.psbtWithUnknownVersion) { psbtF => - psbtF.map { psbt => - assertThrows[IllegalArgumentException](PSBT(psbt.bytes)) - } + forAll(PSBTGenerators.psbtWithUnknownVersion) { psbt => + assertThrows[IllegalArgumentException](PSBT(psbt.bytes)) } } it must "have serialization symmetry" in { - forAllAsync(PSBTGenerators.arbitraryPSBT) { psbtF => - psbtF.map { psbt => - assert(PSBT.fromBytes(psbt.bytes) == psbt) - } + forAll(PSBTGenerators.arbitraryPSBT) { psbt => + assert(PSBT.fromBytes(psbt.bytes) == psbt) } } 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 b0557d6c42..7346bc8320 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 @@ -10,13 +10,12 @@ import org.bitcoins.core.psbt.OutputPSBTRecord.{RedeemScript, WitnessScript} import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams} import org.bitcoins.crypto.CryptoUtil import org.bitcoins.testkitcore.gen._ -import org.bitcoins.testkitcore.util.BitcoinSJvmTest +import org.bitcoins.testkitcore.util.BitcoinSUnitTest import scodec.bits._ -import scala.concurrent.Future import scala.util.{Failure, Success} -class PSBTTest extends BitcoinSJvmTest { +class PSBTTest extends BitcoinSUnitTest { behavior of "PSBT" @@ -24,198 +23,178 @@ class PSBTTest extends BitcoinSJvmTest { generatorDrivenConfigNewCode it must "correctly combine PSBTs" in { - forAllAsync(PSBTGenerators.arbitraryPSBT) { psbtF => - psbtF.map { psbt => - val global = psbt.globalMap - val inputs = psbt.inputMaps - val outputs = psbt.outputMaps + forAll(PSBTGenerators.arbitraryPSBT) { psbt => + val global = psbt.globalMap + val inputs = psbt.inputMaps + val outputs = psbt.outputMaps - val newGlobal = PSBTGenerators.pruneGlobal(global) - val newInputs = - inputs.map(input => - InputPSBTMap(PSBTGenerators.pruneVec(input.elements))) - val newOutputs = - outputs.map(output => - OutputPSBTMap(PSBTGenerators.pruneVec(output.elements))) + val newGlobal = PSBTGenerators.pruneGlobal(global) + val newInputs = + inputs.map(input => + InputPSBTMap(PSBTGenerators.pruneVec(input.elements))) + val newOutputs = + outputs.map(output => + OutputPSBTMap(PSBTGenerators.pruneVec(output.elements))) - val psbt1 = PSBT(newGlobal, newInputs, newOutputs) + val psbt1 = PSBT(newGlobal, newInputs, newOutputs) - val oppositeGlobalElements = global.elements.filterNot(e => - newGlobal.elements.contains(e)) :+ global.unsignedTransaction - val oppositeGlobal = GlobalPSBTMap(oppositeGlobalElements.distinct) - val oppositeInputs = inputs.zip(newInputs).map { case (map, pruned) => - InputPSBTMap(map.elements.filterNot(e => pruned.elements.contains(e))) - } - val oppositeOutputs = - outputs.zip(newOutputs).map { case (map, pruned) => - OutputPSBTMap( - map.elements.filterNot(e => pruned.elements.contains(e))) - } - - val psbt2 = PSBT(oppositeGlobal, oppositeInputs, oppositeOutputs) - - assert(psbt1.combinePSBT(psbt2) == psbt) - assert(psbt2.combinePSBT(psbt1) == psbt) + val oppositeGlobalElements = global.elements.filterNot(e => + newGlobal.elements.contains(e)) :+ global.unsignedTransaction + val oppositeGlobal = GlobalPSBTMap(oppositeGlobalElements.distinct) + val oppositeInputs = inputs.zip(newInputs).map { case (map, pruned) => + InputPSBTMap(map.elements.filterNot(e => pruned.elements.contains(e))) } + val oppositeOutputs = + outputs.zip(newOutputs).map { case (map, pruned) => + OutputPSBTMap( + map.elements.filterNot(e => pruned.elements.contains(e))) + } + + val psbt2 = PSBT(oppositeGlobal, oppositeInputs, oppositeOutputs) + + assert(psbt1.combinePSBT(psbt2) == psbt) + assert(psbt2.combinePSBT(psbt1) == psbt) } } it must "correctly update PSBTs' inputs" in { - forAllAsync(PSBTGenerators.psbtToBeSigned)(_.flatMap { - case (fullPsbt, utxos, _) => - val emptyPsbt = PSBT.fromUnsignedTx(fullPsbt.transaction) + forAll(PSBTGenerators.psbtToBeSigned) { case (fullPsbt, utxos, _) => + val emptyPsbt = PSBT.fromUnsignedTx(fullPsbt.transaction) - val infoAndTxs = PSBTGenerators.orderSpendingInfos(fullPsbt.transaction, - utxos.toVector) - val updatedPSBT = - infoAndTxs.zipWithIndex - .foldLeft(emptyPsbt) { - case (psbt, (utxo, index)) => - val partUpdatedPsbt = psbt - .addUTXOToInput(utxo.prevTransaction, index) - .addSigHashTypeToInput(utxo.hashType, index) - - (InputInfo.getRedeemScript(utxo.inputInfo), - InputInfo.getScriptWitness(utxo.inputInfo)) match { - case (Some(redeemScript), Some(scriptWitness)) => - partUpdatedPsbt - .addRedeemOrWitnessScriptToInput(redeemScript, index) - .addScriptWitnessToInput(scriptWitness, index) - case (Some(redeemScript), None) => - partUpdatedPsbt - .addRedeemOrWitnessScriptToInput(redeemScript, index) - case (None, Some(scriptWitness)) => - partUpdatedPsbt - .addScriptWitnessToInput(scriptWitness, index) - case (None, None) => - partUpdatedPsbt - } + val infoAndTxs = + PSBTGenerators.orderSpendingInfos(fullPsbt.transaction, utxos.toVector) + val updatedPSBT = + infoAndTxs.zipWithIndex + .foldLeft(emptyPsbt) { case (psbt, (utxo, index)) => + val partUpdatedPsbt = psbt + .addUTXOToInput(utxo.prevTransaction, index) + .addSigHashTypeToInput(utxo.hashType, index) + (InputInfo.getRedeemScript(utxo.inputInfo), + InputInfo.getScriptWitness(utxo.inputInfo)) match { + case (Some(redeemScript), Some(scriptWitness)) => + partUpdatedPsbt + .addRedeemOrWitnessScriptToInput(redeemScript, index) + .addScriptWitnessToInput(scriptWitness, index) + case (Some(redeemScript), None) => + partUpdatedPsbt + .addRedeemOrWitnessScriptToInput(redeemScript, index) + case (None, Some(scriptWitness)) => + partUpdatedPsbt + .addScriptWitnessToInput(scriptWitness, index) + case (None, None) => + partUpdatedPsbt } - assert(updatedPSBT == fullPsbt) - }) + + } + + assert(updatedPSBT == fullPsbt) + } } it must "correctly construct and sign a PSBT" in { - forAllAsync(PSBTGenerators.psbtToBeSigned) { psbtWithBuilderF => - psbtWithBuilderF.flatMap { case (psbtNoSigs, utxos, _) => - val infos = utxos.toVector.zipWithIndex.map { - case (utxo: ScriptSignatureParams[InputInfo], index) => - (index, utxo) - } - val signedPSBTF = infos.foldLeft(Future.successful(psbtNoSigs)) { - case (unsignedPSBTF, (index, info)) => - unsignedPSBTF.flatMap { unsignedPSBT => - info.toSingles.foldLeft(Future.successful(unsignedPSBT)) { - (psbtToSignF, singleInfo) => - psbtToSignF.flatMap( - _.sign(index, - singleInfo.signer, - singleInfo.conditionalPath)) - } - } - } - signedPSBTF.map { signedPSBT => - val finalizedPsbtT = signedPSBT.finalizePSBT - finalizedPsbtT match { - case Success(finalizedPsbt) => - val txT = finalizedPsbt.extractTransactionAndValidate - assert(txT.isSuccess, txT.failed) - case Failure(exception) => fail(exception) + forAll(PSBTGenerators.psbtToBeSigned) { case (psbtNoSigs, utxos, _) => + val infos = utxos.toVector.zipWithIndex.map { + case (utxo: ScriptSignatureParams[InputInfo], index) => + (index, utxo) + } + val signedPSBT = infos.foldLeft(psbtNoSigs) { + case (unsignedPSBT, (index, info)) => + info.toSingles.foldLeft(unsignedPSBT) { (psbtToSign, singleInfo) => + psbtToSign.sign(index, + singleInfo.signer, + singleInfo.conditionalPath) } - } + } + val finalizedPsbtT = signedPSBT.finalizePSBT + finalizedPsbtT match { + case Success(finalizedPsbt) => + val txT = finalizedPsbt.extractTransactionAndValidate + assert(txT.isSuccess, txT.failed) + case Failure(exception) => fail(exception) } } } it must "add Redeem Scripts to outputs" in { - forAllAsync( - PSBTGenerators.psbtWithBuilderAndP2SHOutputs(finalized = false)) { - psbtWithBuilderF => - psbtWithBuilderF.flatMap { case (psbtEmptyOutputs, _, redeemScripts) => - val psbtWithOutputs = - redeemScripts.zipWithIndex.foldLeft(psbtEmptyOutputs)((psbt, spk) => - psbt.addRedeemOrWitnessScriptToOutput(spk._1, spk._2)) + forAll(PSBTGenerators.psbtWithBuilderAndP2SHOutputs(finalized = false)) { + case (psbtEmptyOutputs, _, redeemScripts) => + val psbtWithOutputs = + redeemScripts.zipWithIndex.foldLeft(psbtEmptyOutputs)((psbt, spk) => + psbt.addRedeemOrWitnessScriptToOutput(spk._1, spk._2)) - val allOutputsValid = - psbtWithOutputs.outputMaps.zip(redeemScripts).forall { - case (map, spk) => - map.redeemScriptOpt.contains(RedeemScript(spk)) - } - assert(allOutputsValid) - } + val allOutputsValid = + psbtWithOutputs.outputMaps.zip(redeemScripts).forall { + case (map, spk) => + map.redeemScriptOpt.contains(RedeemScript(spk)) + } + assert(allOutputsValid) } } it must "add Witness Scripts to outputs" in { - forAllAsync( - PSBTGenerators.psbtWithBuilderAndP2WSHOutputs(finalized = false)) { - psbtWithBuilderF => - psbtWithBuilderF.flatMap { case (psbtEmptyOutputs, _, redeemScripts) => - val psbtWithOutputs = - redeemScripts.zipWithIndex.foldLeft(psbtEmptyOutputs)((psbt, spk) => - psbt.addRedeemOrWitnessScriptToOutput(spk._1, spk._2)) + forAll(PSBTGenerators.psbtWithBuilderAndP2WSHOutputs(finalized = false)) { + case (psbtEmptyOutputs, _, redeemScripts) => + val psbtWithOutputs = + redeemScripts.zipWithIndex.foldLeft(psbtEmptyOutputs)((psbt, spk) => + psbt.addRedeemOrWitnessScriptToOutput(spk._1, spk._2)) - val allOutputsValid = - psbtWithOutputs.outputMaps.zip(redeemScripts).forall { - case (map, spk) => - map.witnessScriptOpt.contains(WitnessScript(spk)) + val allOutputsValid = + psbtWithOutputs.outputMaps.zip(redeemScripts).forall { + case (map, spk) => + map.witnessScriptOpt.contains(WitnessScript(spk)) - } - assert(allOutputsValid) - } + } + assert(allOutputsValid) } } it must "correctly construct and finalize PSBTs from UTXOSpendingInfo" in { - forAllAsync(CreditingTxGen.inputsAndOutputs(), - ScriptGenerators.scriptPubKey, - ChainParamsGenerator.bitcoinNetworkParams) { + forAll(CreditingTxGen.inputsAndOutputs(), + ScriptGenerators.scriptPubKey, + ChainParamsGenerator.bitcoinNetworkParams) { case ((creditingTxsInfo, destinations), (changeSPK, _), _) => val crediting = creditingTxsInfo.foldLeft(0L)(_ + _.amount.satoshis.toLong) val spending = destinations.foldLeft(0L)(_ + _.value.satoshis.toLong) val maxFee = crediting - spending val fee = GenUtil.sample(FeeUnitGen.feeUnit(maxFee)) - for { - (psbt, _, _) <- - PSBTGenerators.psbtAndBuilderFromInputs(finalized = false, - creditingTxsInfo = - creditingTxsInfo, - destinations = destinations, - changeSPK = changeSPK, - fee = fee) - (expected, _, _) <- - PSBTGenerators.psbtAndBuilderFromInputs(finalized = true, - creditingTxsInfo = - creditingTxsInfo, - destinations = destinations, - changeSPK = changeSPK, - fee = fee) - } yield { - val finalizedPsbtOpt = psbt.finalizePSBT - assert(finalizedPsbtOpt.isSuccess, psbt.hex) - assert(finalizedPsbtOpt.get == expected) - } + + val (psbt, _, _) = + PSBTGenerators.psbtAndBuilderFromInputs(finalized = false, + creditingTxsInfo = + creditingTxsInfo, + destinations = destinations, + changeSPK = changeSPK, + fee = fee) + val (expected, _, _) = + PSBTGenerators.psbtAndBuilderFromInputs(finalized = true, + creditingTxsInfo = + creditingTxsInfo, + destinations = destinations, + changeSPK = changeSPK, + fee = fee) + + val finalizedPsbtOpt = psbt.finalizePSBT + assert(finalizedPsbtOpt.isSuccess, psbt.hex) + assert(finalizedPsbtOpt.get == expected) } } it must "agree with TxBuilder.sign given UTXOSpendingInfos" in { - forAllAsync(PSBTGenerators.finalizedPSBTWithBuilder) { psbtAndBuilderF => - for { - (psbt, builder, fee) <- psbtAndBuilderF - signedTx <- builder.sign(fee) - } yield { + forAll(PSBTGenerators.finalizedPSBTWithBuilder) { + case (psbt, builder, fee) => + val signedTx = builder.sign(fee) + val txT = psbt.extractTransactionAndValidate assert(txT.isSuccess, txT.failed) assert(txT.get == signedTx) - } } } it must "correctly serialize RIPEMD160 hash preimages" in { - forAllAsync(StringGenerators.hexString) { hex => + forAll(StringGenerators.hexString) { hex => val preimage = ByteVector.fromValidHex(hex) val ripeMd160 = RIPEMD160PreImage(preimage) @@ -227,7 +206,7 @@ class PSBTTest extends BitcoinSJvmTest { } it must "correctly serialize SHA256 hash preimages" in { - forAllAsync(StringGenerators.hexString) { hex => + forAll(StringGenerators.hexString) { hex => val preimage = ByteVector.fromValidHex(hex) val sha256 = SHA256PreImage(preimage) @@ -239,7 +218,7 @@ class PSBTTest extends BitcoinSJvmTest { } it must "correctly serialize HASH160 hash preimages" in { - forAllAsync(StringGenerators.hexString) { hex => + forAll(StringGenerators.hexString) { hex => val preimage = ByteVector.fromValidHex(hex) val hash160 = HASH160PreImage(preimage) @@ -251,7 +230,7 @@ class PSBTTest extends BitcoinSJvmTest { } it must "correctly serialize HASH256 hash preimages" in { - forAllAsync(StringGenerators.hexString) { hex => + forAll(StringGenerators.hexString) { hex => val preimage = ByteVector.fromValidHex(hex) val hash256 = HASH256PreImage(preimage) diff --git a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala index ef23961fab..1623d9fff0 100644 --- a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala @@ -16,13 +16,13 @@ import org.bitcoins.core.script.constant._ import org.bitcoins.core.script.crypto.HashType import org.bitcoins.core.wallet.utxo.{ConditionalPath, InputInfo} import org.bitcoins.crypto._ -import org.bitcoins.testkitcore.util.BitcoinSJvmTest -import org.bitcoins.testkitcore.util.TransactionTestUtil.{dummyPSBT, dummyTx} +import org.bitcoins.testkitcore.util.BitcoinSUnitTest +import org.bitcoins.testkitcore.util.TransactionTestUtil._ import scodec.bits._ import scala.util.{Failure, Success} -class PSBTUnitTest extends BitcoinSJvmTest { +class PSBTUnitTest extends BitcoinSUnitTest { behavior of "PSBT" @@ -368,16 +368,14 @@ class PSBTUnitTest extends BitcoinSJvmTest { assert(missingSigs.forall(expectedPubKeyHashes.contains)) } - for { - firstSig0 <- unsignedPsbt.sign(inputIndex = 0, signer = privKey0) - signedPsbt0 <- firstSig0.sign(inputIndex = 1, signer = privKey1) + val firstSig0 = unsignedPsbt.sign(inputIndex = 0, signer = privKey0) + val signedPsbt0 = firstSig0.sign(inputIndex = 1, signer = privKey1) - firstSig1 <- unsignedPsbt.sign(inputIndex = 0, signer = privKey2) - signedPsbt1 <- firstSig1.sign(inputIndex = 1, signer = privKey3) - } yield { - assert(signedPsbt0 == expectedPsbt0) - assert(signedPsbt1 == expectedPsbt1) - } + val firstSig1 = unsignedPsbt.sign(inputIndex = 0, signer = privKey2) + val signedPsbt1 = firstSig1.sign(inputIndex = 1, signer = privKey3) + + assert(signedPsbt0 == expectedPsbt0) + assert(signedPsbt1 == expectedPsbt1) } it must "successfully change a NonWitnessUTXO to a WitnessUTXO when compressing" in { diff --git a/core-test/src/test/scala/org/bitcoins/core/wallet/builder/RawTxFinalizerTest.scala b/core-test/src/test/scala/org/bitcoins/core/wallet/builder/RawTxFinalizerTest.scala index d9d3776eef..c0dfe682eb 100644 --- a/core-test/src/test/scala/org/bitcoins/core/wallet/builder/RawTxFinalizerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/wallet/builder/RawTxFinalizerTest.scala @@ -4,16 +4,14 @@ import org.bitcoins.core.currency.Satoshis import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.script.{EmptyScriptSignature, ScriptPubKey} import org.bitcoins.core.protocol.transaction._ -import org.bitcoins.core.util.FutureUtil import org.bitcoins.crypto.DoubleSha256DigestBE import org.bitcoins.testkitcore.gen.CreditingTxGen -import org.bitcoins.testkitcore.util.BitcoinSJvmTest +import org.bitcoins.testkitcore.util.BitcoinSUnitTest import org.scalatest.Assertion -import scala.concurrent.Future import scala.util.Random -class RawTxFinalizerTest extends BitcoinSJvmTest { +class RawTxFinalizerTest extends BitcoinSUnitTest { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = generatorDrivenConfigNewCode @@ -137,60 +135,59 @@ class RawTxFinalizerTest extends BitcoinSJvmTest { } it must "shuffle inputs" in { - forAllAsync(CreditingTxGen.inputsAndOutputs()) { case (inputs, outputs) => + forAll(CreditingTxGen.inputsAndOutputs()) { case (inputs, outputs) => val txBuilder = RawTxBuilder() ++= inputs ++= outputs val finalized = txBuilder.setFinalizer(ShuffleInputsFinalizer) - val txsF = - FutureUtil.foldLeftAsync(Vector.empty[Transaction], 0 to 20) { - (accum, _) => finalized.buildTx().map(_ +: accum) + val txs = + 0.to(20).foldLeft(Vector.empty[Transaction]) { (accum, _) => + finalized.buildTx() +: accum } - txsF.map(txs => - assert( - inputs.size <= 1 || txs.exists( - _.inputs.map(_.previousOutput) != inputs.map(_.outPoint)))) + + assert( + inputs.size <= 1 || txs.exists( + _.inputs.map(_.previousOutput) != inputs.map(_.outPoint))) } } it must "shuffle outputs" in { - forAllAsync(CreditingTxGen.inputsAndOutputs()) { case (inputs, outputs) => + forAll(CreditingTxGen.inputsAndOutputs()) { case (inputs, outputs) => val txBuilder = RawTxBuilder() ++= inputs ++= outputs val finalized = txBuilder.setFinalizer(ShuffleOutputsFinalizer) - val txsF = - FutureUtil.foldLeftAsync(Vector.empty[Transaction], 0 to 20) { - (accum, _) => finalized.buildTx().map(_ +: accum) + val txs = + 0.to(20).foldLeft(Vector.empty[Transaction]) { (accum, _) => + finalized.buildTx() +: accum } - txsF.map(txs => - assert(outputs.size <= 1 || txs.exists(_.outputs != outputs))) + + assert(outputs.size <= 1 || txs.exists(_.outputs != outputs)) } } it must "shuffle input and outputs" in { - forAllAsync(CreditingTxGen.inputsAndOutputs()) { case (inputs, outputs) => + forAll(CreditingTxGen.inputsAndOutputs()) { case (inputs, outputs) => val txBuilder = RawTxBuilder() ++= inputs ++= outputs val finalized = txBuilder.setFinalizer(ShuffleFinalizer) - val txsF = - FutureUtil.foldLeftAsync(Vector.empty[Transaction], 0 to 20) { - (accum, _) => finalized.buildTx().map(_ +: accum) + val txs = + 0.to(20).foldLeft(Vector.empty[Transaction]) { (accum, _) => + finalized.buildTx() +: accum } - txsF.map { txs => - assert( - inputs.size <= 1 || txs.exists( - _.inputs.map(_.previousOutput) != inputs.map(_.outPoint))) - assert(outputs.size <= 1 || txs.exists(_.outputs != outputs)) - } + + assert( + inputs.size <= 1 || txs.exists( + _.inputs.map(_.previousOutput) != inputs.map(_.outPoint))) + assert(outputs.size <= 1 || txs.exists(_.outputs != outputs)) } } def testBIP69Finalizer( sortedInputs: Vector[TransactionInput], - sortedOutputs: Vector[TransactionOutput]): Future[Assertion] = { + sortedOutputs: Vector[TransactionOutput]): Assertion = { val inputs = Random.shuffle(sortedInputs) val outputs = Random.shuffle(sortedOutputs) val txBuilder = RawTxBuilder() ++= inputs ++= outputs - txBuilder.setFinalizer(BIP69Finalizer).buildTx().map { tx => - assert(tx.inputs == sortedInputs) - assert(tx.outputs == sortedOutputs) - } + val tx = txBuilder.setFinalizer(BIP69Finalizer).buildTx() + + assert(tx.inputs == sortedInputs) + assert(tx.outputs == sortedOutputs) } } 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 index b6b410c907..20ebebe7f1 100644 --- 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 @@ -22,9 +22,9 @@ import org.bitcoins.crypto.{ } import org.bitcoins.testkitcore.Implicits._ import org.bitcoins.testkitcore.gen.{CreditingTxGen, ScriptGenerators} -import org.bitcoins.testkitcore.util.BitcoinSJvmTest +import org.bitcoins.testkitcore.util.BitcoinSUnitTest -class RawTxSignerTest extends BitcoinSJvmTest { +class RawTxSignerTest extends BitcoinSUnitTest { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = generatorDrivenConfigNewCode @@ -58,16 +58,15 @@ class RawTxSignerTest extends BitcoinSJvmTest { ) val utxos = Vector(utxo) val feeUnit = SatoshisPerVirtualByte(currencyUnit = Satoshis(1)) - val utxF = StandardNonInteractiveFinalizer.txFrom(outputs = destinations, - utxos = utxos, - feeRate = feeUnit, - changeSPK = - EmptyScriptPubKey) + val utx = RawFinalizerFactory.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)) + assertThrows[IllegalArgumentException] { + RawTxSigner.sign(utx, utxos, feeUnit, f) } } @@ -97,14 +96,13 @@ class RawTxSignerTest extends BitcoinSJvmTest { val utxos = Vector(utxo) val feeUnit = SatoshisPerVirtualByte(Satoshis.one) - val utxF = StandardNonInteractiveFinalizer.txFrom(outputs = destinations, - utxos = utxos, - feeRate = feeUnit, - changeSPK = - EmptyScriptPubKey) + val utx = RawFinalizerFactory.txFrom(outputs = destinations, + utxos = utxos, + feeRate = feeUnit, + changeSPK = EmptyScriptPubKey) - recoverToSucceededIf[IllegalArgumentException] { - utxF.flatMap(utx => RawTxSigner.sign(utx, utxos, feeUnit)) + assertThrows[IllegalArgumentException] { + RawTxSigner.sign(utx, utxos, feeUnit) } } @@ -136,14 +134,13 @@ class RawTxSignerTest extends BitcoinSJvmTest { val utxos = Vector(utxo) val feeUnit = SatoshisPerVirtualByte(Satoshis.one) - val utxF = StandardNonInteractiveFinalizer.txFrom(outputs = destinations, - utxos = utxos, - feeRate = feeUnit, - changeSPK = - EmptyScriptPubKey) + val utx = RawFinalizerFactory.txFrom(outputs = destinations, + utxos = utxos, + feeRate = feeUnit, + changeSPK = EmptyScriptPubKey) - recoverToSucceededIf[IllegalArgumentException] { - utxF.flatMap(utx => RawTxSigner.sign(utx, utxos, feeUnit)) + assertThrows[IllegalArgumentException] { + RawTxSigner.sign(utx, utxos, feeUnit) } } @@ -176,7 +173,7 @@ class RawTxSignerTest extends BitcoinSJvmTest { val utxos = Vector(cltvSpendingInfo) val feeUnit = SatoshisPerByte(Satoshis.one) - val utxF = + val utx = StandardNonInteractiveFinalizer.txFrom( outputs = Vector( TransactionOutput(Bitcoins.one - CurrencyUnits.oneMBTC, @@ -186,9 +183,9 @@ class RawTxSignerTest extends BitcoinSJvmTest { changeSPK = EmptyScriptPubKey ) - utxF - .flatMap(utx => RawTxSigner.sign(utx, utxos, feeUnit)) - .map(tx => assert(tx.lockTime == UInt32(lockTime))) + val tx = RawTxSigner.sign(utx, utxos, feeUnit) + + assert(tx.lockTime == UInt32(lockTime)) } it should "succeed to sign a cltv spk that uses a block height locktime" in { @@ -220,7 +217,7 @@ class RawTxSignerTest extends BitcoinSJvmTest { val utxos = Vector(cltvSpendingInfo) val feeUnit = SatoshisPerByte(Satoshis.one) - val utxF = + val utx = StandardNonInteractiveFinalizer.txFrom( outputs = Vector( TransactionOutput(Bitcoins.one - CurrencyUnits.oneMBTC, @@ -230,9 +227,9 @@ class RawTxSignerTest extends BitcoinSJvmTest { changeSPK = EmptyScriptPubKey ) - utxF - .flatMap(utx => RawTxSigner.sign(utx, utxos, feeUnit)) - .map(tx => assert(tx.lockTime == UInt32(lockTime))) + val tx = RawTxSigner.sign(utx, utxos, feeUnit) + + 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 { @@ -274,43 +271,38 @@ class RawTxSignerTest extends BitcoinSJvmTest { val utxos = Vector(cltvSpendingInfo1, cltvSpendingInfo2) val feeRate = SatoshisPerByte(Satoshis.one) - val utxF = - StandardNonInteractiveFinalizer.txFrom( - Vector( - TransactionOutput(Bitcoins.one + Bitcoins.one - CurrencyUnits.oneMBTC, - EmptyScriptPubKey)), - utxos, - feeRate, - EmptyScriptPubKey - ) + val utx = RawFinalizerFactory.txFrom( + Vector( + TransactionOutput(Bitcoins.one + Bitcoins.one - CurrencyUnits.oneMBTC, + EmptyScriptPubKey)), + utxos, + UInt32.zero, + feeRate, + EmptyScriptPubKey + ) - recoverToSucceededIf[IllegalArgumentException]( - utxF.flatMap(utx => RawTxSigner.sign(utx, utxos, feeRate)) + assertThrows[IllegalArgumentException]( + RawTxSigner.sign(utx, utxos, feeRate) ) } it should "sign a mix of spks in a tx and then have it verified" in { - forAllAsync(CreditingTxGen.inputsAndOutputs(), - ScriptGenerators.scriptPubKey) { + forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) { case ((creditingTxsInfo, destinations), (changeSPK, _)) => val fee = SatoshisPerVirtualByte(Satoshis(1000)) - val utxF = + val utx = StandardNonInteractiveFinalizer.txFrom(outputs = destinations, utxos = creditingTxsInfo, feeRate = fee, changeSPK = changeSPK) - val txF = utxF.flatMap(utx => - RawTxSigner.sign(utx, creditingTxsInfo.toVector, fee)) + val tx = RawTxSigner.sign(utx, creditingTxsInfo.toVector, fee) - txF.map { tx => - assert(BitcoinScriptUtil.verifyScript(tx, creditingTxsInfo.toVector)) - } + assert(BitcoinScriptUtil.verifyScript(tx, creditingTxsInfo.toVector)) } } it should "dummy sign a mix of spks in a tx and fill it with dummy signatures" in { - forAllAsync(CreditingTxGen.inputsAndOutputs(), - ScriptGenerators.scriptPubKey) { + forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) { case ((creditingTxsInfo, destinations), (changeSPK, _)) => val fee = SatoshisPerVirtualByte(Satoshis(1000)) @@ -324,20 +316,19 @@ class RawTxSignerTest extends BitcoinSJvmTest { HashType.sigHashAll) } - val utxF = + val utx = StandardNonInteractiveFinalizer.txFrom(outputs = destinations, utxos = dummySpendingInfos, feeRate = fee, changeSPK = changeSPK) - val txF = utxF.flatMap(utx => - RawTxSigner.sign(utx, - dummySpendingInfos.toVector, - RawTxSigner.emptyInvariant, - dummySign = true)) + val tx = RawTxSigner.sign(utx, + dummySpendingInfos.toVector, + RawTxSigner.emptyInvariant, + dummySign = true) // Can't use BitcoinScriptUtil.verifyScript because it will pass for things // with EmptyScriptPubKeys or Multisig with 0 required sigs - txF.map { + tx match { case EmptyTransaction => succeed case btx: BaseTransaction => @@ -360,21 +351,18 @@ class RawTxSignerTest extends BitcoinSJvmTest { } it should "sign a mix of p2sh/p2wsh in a tx and then have it verified" in { - forAllAsync(CreditingTxGen.inputsAndOutputs(CreditingTxGen.nestedOutputs), - ScriptGenerators.scriptPubKey) { + forAll(CreditingTxGen.inputsAndOutputs(CreditingTxGen.nestedOutputs), + ScriptGenerators.scriptPubKey) { case ((creditingTxsInfo, destinations), (changeSPK, _)) => val fee = SatoshisPerByte(Satoshis(1000)) - val utxF = + val utx = StandardNonInteractiveFinalizer.txFrom(outputs = destinations, utxos = creditingTxsInfo, feeRate = fee, changeSPK = changeSPK) - val txF = utxF.flatMap(utx => - RawTxSigner.sign(utx, creditingTxsInfo.toVector, fee)) + val tx = RawTxSigner.sign(utx, creditingTxsInfo.toVector, fee) - txF.map { tx => - assert(BitcoinScriptUtil.verifyScript(tx, creditingTxsInfo.toVector)) - } + assert(BitcoinScriptUtil.verifyScript(tx, creditingTxsInfo.toVector)) } } } diff --git a/core-test/src/test/scala/org/bitcoins/core/wallet/builder/ShufflingNonInteractiveFinalizerTest.scala b/core-test/src/test/scala/org/bitcoins/core/wallet/builder/ShufflingNonInteractiveFinalizerTest.scala index 36cb94ff81..47543abc31 100644 --- a/core-test/src/test/scala/org/bitcoins/core/wallet/builder/ShufflingNonInteractiveFinalizerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/wallet/builder/ShufflingNonInteractiveFinalizerTest.scala @@ -5,7 +5,6 @@ import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.script.crypto.HashType -import org.bitcoins.core.util.FutureUtil import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte import org.bitcoins.core.wallet.utxo.{ ConditionalPath, @@ -20,9 +19,9 @@ import org.bitcoins.testkitcore.gen.{ FeeUnitGen, ScriptGenerators } -import org.bitcoins.testkitcore.util.BitcoinSJvmTest +import org.bitcoins.testkitcore.util.BitcoinSUnitTest -class ShufflingNonInteractiveFinalizerTest extends BitcoinSJvmTest { +class ShufflingNonInteractiveFinalizerTest extends BitcoinSUnitTest { behavior of "ShufflingNonInteractiveFinalizer" private val (spk, privKey) = ScriptGenerators.p2pkhScriptPubKey.sampleSome @@ -120,7 +119,7 @@ class ShufflingNonInteractiveFinalizerTest extends BitcoinSJvmTest { val utxos = Vector(utxo) val feeUnit = SatoshisPerVirtualByte(Satoshis.one) - recoverToSucceededIf[IllegalArgumentException] { + assertThrows[IllegalArgumentException] { ShufflingNonInteractiveFinalizer.txFrom(outputs = destinations, utxos = utxos, feeRate = feeUnit, @@ -154,7 +153,7 @@ class ShufflingNonInteractiveFinalizerTest extends BitcoinSJvmTest { val utxos = Vector(utxo) val feeUnit = SatoshisPerVirtualByte(Satoshis(-1)) - recoverToSucceededIf[IllegalArgumentException] { + assertThrows[IllegalArgumentException] { ShufflingNonInteractiveFinalizer.txFrom(outputs = destinations, utxos = utxos, feeRate = feeUnit, @@ -182,7 +181,7 @@ class ShufflingNonInteractiveFinalizerTest extends BitcoinSJvmTest { hashType = HashType.sigHashAll ) - recoverToSucceededIf[UnsupportedOperationException] { + assertThrows[UnsupportedOperationException] { ShufflingNonInteractiveFinalizer.txFrom( Vector(TransactionOutput(Bitcoins.one, EmptyScriptPubKey)), Vector(spendingInfo), @@ -193,24 +192,20 @@ class ShufflingNonInteractiveFinalizerTest extends BitcoinSJvmTest { } it must "create a shuffled transaction with a ShufflingNonInteractiveFinalizer" in { - forAllAsync(CreditingTxGen.inputsAndOutputs(), - FeeUnitGen.feeUnit(100), - ScriptGenerators.scriptPubKey) { + forAll(CreditingTxGen.inputsAndOutputs(), + FeeUnitGen.feeUnit(100), + ScriptGenerators.scriptPubKey) { case ((inputs, outputs), feeRate, (changeSpk, _)) => - val txsF = - FutureUtil.foldLeftAsync(Vector.empty[Transaction], 0 to 20) { - (accum, _) => - ShufflingNonInteractiveFinalizer - .txFrom(outputs, inputs, feeRate, changeSpk) - .map(_ +: accum) + val txs = + 0.to(20).foldLeft(Vector.empty[Transaction]) { (accum, _) => + ShufflingNonInteractiveFinalizer + .txFrom(outputs, inputs, feeRate, changeSpk) +: accum } - txsF.map { txs => - assert( - inputs.size <= 1 || txs.exists( - _.inputs.map(_.previousOutput) != inputs.map(_.outPoint))) - assert(outputs.size <= 1 || txs.exists(_.outputs != outputs)) - } + assert( + inputs.size <= 1 || txs.exists( + _.inputs.map(_.previousOutput) != inputs.map(_.outPoint))) + assert(outputs.size <= 1 || txs.exists(_.outputs != outputs)) } } } diff --git a/core-test/src/test/scala/org/bitcoins/core/wallet/builder/StandardNonInteractiveFinalizerTest.scala b/core-test/src/test/scala/org/bitcoins/core/wallet/builder/StandardNonInteractiveFinalizerTest.scala index 82308c80aa..f5c08768fb 100644 --- a/core-test/src/test/scala/org/bitcoins/core/wallet/builder/StandardNonInteractiveFinalizerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/wallet/builder/StandardNonInteractiveFinalizerTest.scala @@ -15,9 +15,9 @@ import org.bitcoins.core.wallet.utxo.{ import org.bitcoins.crypto.{DoubleSha256DigestBE, ECPrivateKey, ECPublicKey} import org.bitcoins.testkitcore.Implicits._ import org.bitcoins.testkitcore.gen.ScriptGenerators -import org.bitcoins.testkitcore.util.BitcoinSJvmTest +import org.bitcoins.testkitcore.util.BitcoinSUnitTest -class StandardNonInteractiveFinalizerTest extends BitcoinSJvmTest { +class StandardNonInteractiveFinalizerTest extends BitcoinSUnitTest { behavior of "StandardNonInteractiveFinalizer" private val (spk, privKey) = ScriptGenerators.p2pkhScriptPubKey.sampleSome @@ -115,7 +115,7 @@ class StandardNonInteractiveFinalizerTest extends BitcoinSJvmTest { val utxos = Vector(utxo) val feeUnit = SatoshisPerVirtualByte(Satoshis.one) - recoverToSucceededIf[IllegalArgumentException] { + assertThrows[IllegalArgumentException] { StandardNonInteractiveFinalizer.txFrom(outputs = destinations, utxos = utxos, feeRate = feeUnit, @@ -149,7 +149,7 @@ class StandardNonInteractiveFinalizerTest extends BitcoinSJvmTest { val utxos = Vector(utxo) val feeUnit = SatoshisPerVirtualByte(Satoshis(-1)) - recoverToSucceededIf[IllegalArgumentException] { + assertThrows[IllegalArgumentException] { StandardNonInteractiveFinalizer.txFrom(outputs = destinations, utxos = utxos, feeRate = feeUnit, @@ -177,7 +177,7 @@ class StandardNonInteractiveFinalizerTest extends BitcoinSJvmTest { hashType = HashType.sigHashAll ) - recoverToSucceededIf[UnsupportedOperationException] { + assertThrows[UnsupportedOperationException] { StandardNonInteractiveFinalizer.txFrom( Vector(TransactionOutput(Bitcoins.one, EmptyScriptPubKey)), Vector(spendingInfo), 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 e31e00c560..935d8b64df 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 @@ -28,14 +28,11 @@ import org.bitcoins.testkitcore.gen.{ ScriptGenerators, TransactionGenerators } -import org.bitcoins.testkitcore.util.BitcoinSJvmTest +import org.bitcoins.testkitcore.util.BitcoinSUnitTest import scala.annotation.nowarn -import scala.concurrent.{ExecutionContext, Future} -class SignerTest extends BitcoinSJvmTest { - - implicit val ec: ExecutionContext = ExecutionContext.global +class SignerTest extends BitcoinSUnitTest { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = generatorDrivenConfigNewCode @@ -75,7 +72,7 @@ class SignerTest extends BitcoinSJvmTest { .sample(CreditingTxGen.p2wpkhOutput) .asInstanceOf[ScriptSignatureParams[P2WPKHV0InputInfo]] val tx = GenUtil.sample(TransactionGenerators.baseTransaction) - recoverToSucceededIf[IllegalArgumentException] { + assertThrows[IllegalArgumentException] { P2WPKHSigner.sign(dumbSpendingInfo, tx, isDummySignature = false, p2wpkh) } } @@ -86,116 +83,104 @@ class SignerTest extends BitcoinSJvmTest { .sample(CreditingTxGen.p2wshOutput) .asInstanceOf[ScriptSignatureParams[P2WSHV0InputInfo]] val tx = GenUtil.sample(TransactionGenerators.baseTransaction) - recoverToSucceededIf[IllegalArgumentException] { + assertThrows[IllegalArgumentException] { P2WSHSigner.sign(dumbSpendingInfo, tx, isDummySignature = false, p2wsh) } } it must "sign a mix of spks in a tx and then verify that single signing agrees" in { - forAllAsync(CreditingTxGen.inputsAndOutputs(), - ScriptGenerators.scriptPubKey) { + forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) { case ((creditingTxsInfos, destinations), (changeSPK, _)) => val fee = SatoshisPerVirtualByte(Satoshis(1000)) - for { - unsignedTx <- - StandardNonInteractiveFinalizer.txFrom(destinations, - creditingTxsInfos, - fee, - changeSPK) - signedTx <- - RawTxSigner.sign(unsignedTx, creditingTxsInfos.toVector, fee) + val unsignedTx = + StandardNonInteractiveFinalizer.txFrom(destinations, + creditingTxsInfos, + fee, + changeSPK) - singleSigs: Vector[Vector[ECDigitalSignature]] <- { - val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] = - creditingTxsInfos.toVector.map(_.toSingles) - val sigVecFs = singleInfosVec.map { singleInfos => - val sigFs = singleInfos.map { singleInfo => - val keyAndSigF = - BitcoinSigner.signSingle(singleInfo, - unsignedTx, - isDummySignature = false) + val signedTx = + RawTxSigner.sign(unsignedTx, creditingTxsInfos.toVector, fee) - keyAndSigF.map(_.signature) + val singleSigs: Vector[Vector[ECDigitalSignature]] = { + val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] = + creditingTxsInfos.toVector.map(_.toSingles) + singleInfosVec.map { singleInfos => + singleInfos.map { singleInfo => + val keyAndSig = + BitcoinSigner.signSingle(singleInfo, + unsignedTx, + isDummySignature = false) + + keyAndSig.signature + } + } + } + + signedTx.inputs.zipWithIndex.foreach { case (input, inputIndex) => + val infoAndIndexOpt = creditingTxsInfos.zipWithIndex + .find(_._1.outPoint == input.previousOutput) + assert(infoAndIndexOpt.isDefined) + val (info, index) = infoAndIndexOpt.get + val sigs = singleSigs(index) + + val expectedSigs = + if (InputInfo.getScriptWitness(info.inputInfo).isEmpty) { + input.scriptSignature.signatures + } else { + signedTx + .asInstanceOf[WitnessTransaction] + .witness + .witnesses(inputIndex) match { + case p2wpkh: P2WPKHWitnessV0 => Vector(p2wpkh.signature) + case p2wsh: P2WSHWitnessV0 => p2wsh.signatures + case EmptyScriptWitness => Vector.empty } - - Future.sequence(sigFs) } - Future.sequence(sigVecFs) - } - } yield { - signedTx.inputs.zipWithIndex.foreach { case (input, inputIndex) => - val infoAndIndexOpt = creditingTxsInfos.zipWithIndex - .find(_._1.outPoint == input.previousOutput) - assert(infoAndIndexOpt.isDefined) - val (info, index) = infoAndIndexOpt.get - val sigs = singleSigs(index) - - val expectedSigs = - if (InputInfo.getScriptWitness(info.inputInfo).isEmpty) { - input.scriptSignature.signatures - } else { - signedTx - .asInstanceOf[WitnessTransaction] - .witness - .witnesses(inputIndex) match { - case p2wpkh: P2WPKHWitnessV0 => Vector(p2wpkh.signature) - case p2wsh: P2WSHWitnessV0 => p2wsh.signatures - case EmptyScriptWitness => Vector.empty - } - } - - assert(sigs.length == expectedSigs.length) - assert(sigs.forall(expectedSigs.contains)) - } - - succeed + assert(sigs.length == expectedSigs.length) + assert(sigs.forall(expectedSigs.contains)) } + + succeed } } it should "have old and new doSign functions agree" in { - forAllAsync(CreditingTxGen.inputsAndOutputs(), - ScriptGenerators.scriptPubKey) { + forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) { case ((creditingTxsInfo, destinations), (changeSPK, _)) => val fee = SatoshisPerVirtualByte(Satoshis(100)) - val unsignedTxF = StandardNonInteractiveFinalizer + val spendingTx = StandardNonInteractiveFinalizer .txFrom(outputs = destinations, utxos = creditingTxsInfo, feeRate = fee, changeSPK = changeSPK) - val correctSigsF = unsignedTxF.flatMap { spendingTx => - val assertFs = creditingTxsInfo.flatMap { signInfo => + val correctSigs = + creditingTxsInfo.flatMap { signInfo => signInfo.signers.map { signer => val txSignatureComponent = TxSigComponent(signInfo.inputInfo, spendingTx) - @nowarn val oldSigF = BitcoinSigner.doSign(txSignatureComponent, - signer.signFunction, - signInfo.hashType, - isDummySignature = - false) - for { - oldSig <- oldSigF - newSig <- BitcoinSigner.doSign(spendingTx, - signInfo, - signer.signFunction, - signInfo.hashType, - isDummySignature = false) - } yield { - (oldSig.r == newSig.r) && - (oldSig.s == newSig.s) && - (oldSig.hex == newSig.hex) - } + @nowarn val oldSig = BitcoinSigner.doSign(txSignatureComponent, + signer.sign, + signInfo.hashType, + isDummySignature = + false) + + val newSig = BitcoinSigner.doSign(spendingTx, + signInfo, + signer.sign, + signInfo.hashType, + isDummySignature = false) + + (oldSig.r == newSig.r) && + (oldSig.s == newSig.s) && + (oldSig.hex == newSig.hex) } } - Future.sequence(assertFs) - } - - correctSigsF.map(x => assert(x.forall(_ == true))) + assert(correctSigs.forall(_ == true)) } } @@ -270,58 +255,51 @@ class SignerTest extends BitcoinSJvmTest { } it must "sign p2wsh inputs correctly when provided no witness data" in { - forAllAsync(CreditingTxGen.inputsAndOutputs(CreditingTxGen.p2wshOutputs), - ScriptGenerators.scriptPubKey) { + forAll(CreditingTxGen.inputsAndOutputs(CreditingTxGen.p2wshOutputs), + ScriptGenerators.scriptPubKey) { case ((creditingTxsInfos, destinations), (changeSPK, _)) => val fee = SatoshisPerVirtualByte(Satoshis(100)) - for { - unsignedTx <- - StandardNonInteractiveFinalizer.txFrom(destinations, - creditingTxsInfos, - fee, - changeSPK) + val unsignedTx = + StandardNonInteractiveFinalizer.txFrom(destinations, + creditingTxsInfos, + fee, + changeSPK) - singleSigs: Vector[Vector[PartialSignature]] <- { - val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] = - creditingTxsInfos.toVector.map(_.toSingles) - val sigVecFs = singleInfosVec.map { singleInfos => - val sigFs = singleInfos.map { singleInfo => - val wtx = - WitnessTransaction(unsignedTx.version, - unsignedTx.inputs, - unsignedTx.outputs, - unsignedTx.lockTime, - EmptyWitness.fromInputs(unsignedTx.inputs)) - BitcoinSigner.signSingle(singleInfo, - wtx, - isDummySignature = false) + val singleSigs: Vector[Vector[PartialSignature]] = { + val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] = + creditingTxsInfos.toVector.map(_.toSingles) + singleInfosVec.map { singleInfos => + singleInfos.map { singleInfo => + val wtx = + WitnessTransaction(unsignedTx.version, + unsignedTx.inputs, + unsignedTx.outputs, + unsignedTx.lockTime, + EmptyWitness.fromInputs(unsignedTx.inputs)) + BitcoinSigner.signSingle(singleInfo, + wtx, + isDummySignature = false) - } - - Future.sequence(sigFs) } - - Future.sequence(sigVecFs) } - } yield { - - val psbt = - creditingTxsInfos.foldLeft(PSBT.fromUnsignedTx(unsignedTx)) { - (psbt, spendInfo) => - val idx = inputIndex(spendInfo, unsignedTx) - psbt - .addUTXOToInput(spendInfo.prevTransaction, idx) - .addScriptWitnessToInput( - InputInfo.getScriptWitness(spendInfo.inputInfo).get, - idx) - .addSignatures(singleSigs(idx), idx) - } - - val signedTx = psbt.finalizePSBT.get.extractTransactionAndValidate - - assert(verifyScripts(signedTx.get, creditingTxsInfos.toVector)) } + + val psbt = + creditingTxsInfos.foldLeft(PSBT.fromUnsignedTx(unsignedTx)) { + (psbt, spendInfo) => + val idx = inputIndex(spendInfo, unsignedTx) + psbt + .addUTXOToInput(spendInfo.prevTransaction, idx) + .addScriptWitnessToInput( + InputInfo.getScriptWitness(spendInfo.inputInfo).get, + idx) + .addSignatures(singleSigs(idx), idx) + } + + val signedTx = psbt.finalizePSBT.get.extractTransactionAndValidate + + assert(verifyScripts(signedTx.get, creditingTxsInfos.toVector)) } } } diff --git a/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputInfoTest.scala b/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputInfoTest.scala index 1505368076..a3a59dbd99 100644 --- a/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputInfoTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputInfoTest.scala @@ -12,9 +12,9 @@ import org.bitcoins.testkitcore.gen.{ ScriptGenerators, TransactionGenerators } -import org.bitcoins.testkitcore.util.BitcoinSJvmTest +import org.bitcoins.testkitcore.util.BitcoinSUnitTest -class InputInfoTest extends BitcoinSJvmTest { +class InputInfoTest extends BitcoinSUnitTest { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = generatorDrivenConfigNewCode @@ -214,7 +214,7 @@ class InputInfoTest extends BitcoinSJvmTest { } it must "successfully compute maxWitnessLengths" in { - forAllAsync(CreditingTxGen.output) { scriptSigParams => + forAll(CreditingTxGen.output) { scriptSigParams => val dummyTx = BaseTransaction( TransactionConstants.validLockVersion, Vector( @@ -225,22 +225,19 @@ class InputInfoTest extends BitcoinSJvmTest { UInt32.zero ) - val maxWitnessLenF = BitcoinSigner + val maxWitnessLen = BitcoinSigner .sign(scriptSigParams, unsignedTx = dummyTx, isDummySignature = true) - .map(_.transaction) - .map { - case wtx: WitnessTransaction => wtx.witness.head.byteSize.toInt - case _: NonWitnessTransaction => 0 - } - - maxWitnessLenF.map { expectedLen => - assert(scriptSigParams.maxWitnessLen == expectedLen) + .transaction match { + case wtx: WitnessTransaction => wtx.witness.head.byteSize.toInt + case _: NonWitnessTransaction => 0 } + + assert(scriptSigParams.maxWitnessLen == maxWitnessLen) } } it must "successfully compute maxScriptSigLengths" in { - forAllAsync(CreditingTxGen.output) { scriptSigParams => + forAll(CreditingTxGen.output) { scriptSigParams => val dummyTx = BaseTransaction( TransactionConstants.validLockVersion, Vector( @@ -251,18 +248,16 @@ class InputInfoTest extends BitcoinSJvmTest { UInt32.zero ) - val maxScriptSigF = BitcoinSigner + val maxScriptSig = BitcoinSigner .sign(scriptSigParams, unsignedTx = dummyTx, isDummySignature = true) - .map(_.transaction) - .map { tx => - tx.inputs.head.scriptSignature - } + .transaction + .inputs + .head + .scriptSignature - maxScriptSigF.map { scriptSig => - assert(InputInfo.maxScriptSigLen( - scriptSigParams.inputInfo) == scriptSig.byteSize, - scriptSig.hex) - } + assert(InputInfo.maxScriptSigLen( + scriptSigParams.inputInfo) == maxScriptSig.byteSize, + maxScriptSig.hex) } } } 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 6f062e5fb2..54509bf9ef 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.crypto._ import scodec.bits.{ByteVector, HexStringSyntax} import scala.annotation.tailrec -import scala.concurrent.Future import scala.util.{Failure, Success, Try} /** Represents an extended key as defined by BIP32 @@ -225,21 +224,21 @@ sealed abstract class ExtPrivateKey override def publicKey: ECPublicKey = key.publicKey - override def signFunction: ByteVector => Future[ECDigitalSignature] = { - key.signFunction + override def sign(bytes: ByteVector): ECDigitalSignature = { + key.sign(bytes) } - override def signWithEntropyFunction: ( - ByteVector, - ByteVector) => Future[ECDigitalSignature] = { - key.signWithEntropyFunction + override def signWithEntropy( + bytes: ByteVector, + entropy: ByteVector): ECDigitalSignature = { + key.signWithEntropy(bytes, entropy) } /** Signs the given bytes with the given [[BIP32Path path]] */ - override def deriveAndSignFuture: ( - ByteVector, - BIP32Path) => Future[ECDigitalSignature] = { case (bytes, path) => - deriveChildPrivKey(path).signFunction(bytes) + override def deriveAndSign( + bytes: ByteVector, + path: BIP32Path): ECDigitalSignature = { + deriveChildPrivKey(path).sign(bytes) } override def toStringSensitive: String = { diff --git a/core/src/main/scala/org/bitcoins/core/crypto/ExtSign.scala b/core/src/main/scala/org/bitcoins/core/crypto/ExtSign.scala index 90ba937d28..cc2230bf82 100644 --- a/core/src/main/scala/org/bitcoins/core/crypto/ExtSign.scala +++ b/core/src/main/scala/org/bitcoins/core/crypto/ExtSign.scala @@ -1,19 +1,38 @@ package org.bitcoins.core.crypto import org.bitcoins.core.hd.BIP32Path -import org.bitcoins.crypto.{ECDigitalSignature, Sign} +import org.bitcoins.crypto.{AsyncSign, ECDigitalSignature, Sign} import scodec.bits.ByteVector -import scala.concurrent.duration.DurationInt -import scala.concurrent.{Await, Future} +import scala.concurrent.Future /** A signing interface for [[ExtKey]] */ -trait ExtSign extends Sign { +trait AsyncExtSign extends AsyncSign { - def deriveAndSignFuture: (ByteVector, BIP32Path) => Future[ECDigitalSignature] + def asyncDeriveAndSign( + bytes: ByteVector, + path: BIP32Path): Future[ECDigitalSignature] + + /** First derives the child key that corresponds to [[BIP32Path path]] and then signs */ + def asyncSign( + bytes: ByteVector, + path: BIP32Path): Future[ECDigitalSignature] = { + asyncDeriveAndSign(bytes, path) + } +} + +trait ExtSign extends AsyncExtSign with Sign { + + def deriveAndSign(bytes: ByteVector, path: BIP32Path): ECDigitalSignature + + override def asyncDeriveAndSign( + bytes: ByteVector, + path: BIP32Path): Future[ECDigitalSignature] = { + Future.successful(deriveAndSign(bytes, path)) + } /** First derives the child key that corresponds to [[BIP32Path path]] and then signs */ def sign(bytes: ByteVector, path: BIP32Path): ECDigitalSignature = { - Await.result(deriveAndSignFuture(bytes, path), 30.seconds) + deriveAndSign(bytes, path) } } 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 index b9b88b6a07..056ae9f593 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/transaction/TxUtil.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/transaction/TxUtil.scala @@ -18,7 +18,6 @@ import org.bitcoins.core.wallet.utxo._ import org.bitcoins.crypto.{DummyECDigitalSignature, Sign} import scala.annotation.tailrec -import scala.concurrent.{Await, ExecutionContext, Future} import scala.util.{Failure, Success, Try} object TxUtil { @@ -133,8 +132,7 @@ object TxUtil { def buildDummyTx( utxos: Vector[InputInfo], - outputs: Vector[TransactionOutput])(implicit - ec: ExecutionContext): Future[Transaction] = { + outputs: Vector[TransactionOutput]): Transaction = { val dummySpendingInfos = utxos.map { inputInfo => val mockSigners = inputInfo.pubKeys.take(inputInfo.requiredSigs).map(Sign.dummySign) @@ -153,13 +151,12 @@ object TxUtil { val txBuilder = RawTxBuilder() ++= dummyInputs ++= outputs val withFinalizer = txBuilder.setFinalizer(AddWitnessDataFinalizer(utxos)) - for { - utx <- withFinalizer.buildTx() - signed <- RawTxSigner.sign(utx, - dummySpendingInfos, - RawTxSigner.emptyInvariant, - dummySign = true) - } yield signed + val utx = withFinalizer.buildTx() + + RawTxSigner.sign(utx, + dummySpendingInfos, + RawTxSigner.emptyInvariant, + dummySign = true) } /** Inserts script signatures and (potentially) witness data to a given @@ -170,14 +167,15 @@ object TxUtil { * 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 dummyInputAndWitnessFs = inputInfos.zipWithIndex.map { + def addDummySigs( + utx: Transaction, + inputInfos: Vector[InputInfo]): Transaction = { + val dummyInputAndWitnesses = inputInfos.zipWithIndex.map { case (inputInfo, index) => val mockSigners = inputInfo.pubKeys.take(inputInfo.requiredSigs).map { pubKey => - Sign(_ => Future.successful(DummyECDigitalSignature), - (_, _) => Future.successful(DummyECDigitalSignature), + Sign(_ => DummyECDigitalSignature, + (_, _) => DummyECDigitalSignature, pubKey) } @@ -186,36 +184,30 @@ object TxUtil { 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) - } - } + val tx = + BitcoinSigner + .sign(mockSpendingInfo, utx, isDummySignature = true) + .transaction - (tx.inputs(index), witnessOpt) - } + 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(dummyInputAndWitnessFs).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) - } + val inputs = dummyInputAndWitnesses.map(_._1) + val txWitnesses = dummyInputAndWitnesses.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) } } @@ -293,10 +285,7 @@ object TxUtil { val expectedTx = if (isSigned) { tx } else { - import scala.concurrent.ExecutionContext.Implicits.global - import scala.concurrent.duration.DurationInt - - Await.result(TxUtil.addDummySigs(tx, inputInfos), 20.seconds) + TxUtil.addDummySigs(tx, inputInfos) } val actualFee = creditingAmount - spentAmount 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 06296187a0..8893e6907e 100644 --- a/core/src/main/scala/org/bitcoins/core/psbt/PSBT.scala +++ b/core/src/main/scala/org/bitcoins/core/psbt/PSBT.scala @@ -16,7 +16,6 @@ import org.bitcoins.crypto._ import scodec.bits._ import scala.annotation.tailrec -import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success, Try} case class PSBT( @@ -222,8 +221,7 @@ case class PSBT( inputIndex: Int, signer: Sign, conditionalPath: ConditionalPath = ConditionalPath.NoCondition, - isDummySignature: Boolean = false)(implicit - ec: ExecutionContext): Future[PSBT] = { + isDummySignature: Boolean = false): PSBT = { require( inputMaps.size == 1 || !inputMaps(inputIndex).isBIP143Vulnerable, "This input map is susceptible to the BIP 143 vulnerability, add the non-witness utxo to be safe" @@ -942,8 +940,8 @@ object PSBT extends Factory[PSBT] with StringFactory[PSBT] { */ def fromUnsignedTxAndInputs( unsignedTx: Transaction, - spendingInfoAndNonWitnessTxs: Vector[ScriptSignatureParams[InputInfo]])( - implicit ec: ExecutionContext): Future[PSBT] = { + spendingInfoAndNonWitnessTxs: Vector[ + ScriptSignatureParams[InputInfo]]): PSBT = { fromUnsignedTxAndInputs(unsignedTx, spendingInfoAndNonWitnessTxs, finalized = false) @@ -954,15 +952,14 @@ object PSBT extends Factory[PSBT] with StringFactory[PSBT] { */ def finalizedFromUnsignedTxAndInputs( unsignedTx: Transaction, - spendingInfos: Vector[ScriptSignatureParams[InputInfo]])(implicit - ec: ExecutionContext): Future[PSBT] = { + spendingInfos: Vector[ScriptSignatureParams[InputInfo]]): PSBT = { fromUnsignedTxAndInputs(unsignedTx, spendingInfos, finalized = true) } private def fromUnsignedTxAndInputs( unsignedTx: Transaction, spendingInfos: Vector[ScriptSignatureParams[InputInfo]], - finalized: Boolean)(implicit ec: ExecutionContext): Future[PSBT] = { + finalized: Boolean): PSBT = { require(spendingInfos.length == unsignedTx.inputs.length, "Must have a SpendingInfo for every input") require( @@ -980,7 +977,7 @@ object PSBT extends Factory[PSBT] with StringFactory[PSBT] { val globalMap = GlobalPSBTMap( Vector(GlobalPSBTRecord.UnsignedTransaction(btx))) - val inputMapFs = spendingInfos.map { info => + val inputMaps = spendingInfos.map { info => if (finalized) { InputPSBTMap.finalizedFromSpendingInfo(info, unsignedTx) } else { @@ -989,8 +986,6 @@ object PSBT extends Factory[PSBT] with StringFactory[PSBT] { } val outputMaps = unsignedTx.outputs.map(_ => OutputPSBTMap.empty).toVector - Future.sequence(inputMapFs).map { inputMaps => - PSBT(globalMap, inputMaps, outputMaps) - } + PSBT(globalMap, inputMaps, outputMaps) } } 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 ef92767a0e..0e1e86184f 100644 --- a/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala +++ b/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala @@ -12,7 +12,6 @@ import org.bitcoins.crypto._ import scodec.bits.ByteVector import scala.annotation.tailrec -import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success, Try} sealed trait PSBTMap[+RecordType <: PSBTRecord] extends NetworkElement { @@ -839,36 +838,33 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] { */ def finalizedFromSpendingInfo( spendingInfo: ScriptSignatureParams[InputInfo], - unsignedTx: Transaction)(implicit - ec: ExecutionContext): Future[InputPSBTMap] = { - val sigComponentF = BitcoinSigner + unsignedTx: Transaction): InputPSBTMap = { + val sigComponent = BitcoinSigner .sign(spendingInfo, unsignedTx, isDummySignature = false) - sigComponentF.map { sigComponent => - val utxos = spendingInfo.inputInfo match { - case _: UnassignedSegwitNativeInputInfo => - Vector(WitnessUTXO(spendingInfo.output)) - case _: RawInputInfo | _: P2SHNonSegwitInputInfo | - _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo => - Vector(NonWitnessOrUnknownUTXO(spendingInfo.prevTransaction)) - } + val utxos = spendingInfo.inputInfo match { + case _: UnassignedSegwitNativeInputInfo => + Vector(WitnessUTXO(spendingInfo.output)) + case _: RawInputInfo | _: P2SHNonSegwitInputInfo | + _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo => + Vector(NonWitnessOrUnknownUTXO(spendingInfo.prevTransaction)) + } - val scriptSig = - FinalizedScriptSig(sigComponent.scriptSignature) - sigComponent.transaction match { - case _: NonWitnessTransaction => - InputPSBTMap(utxos ++ Vector(scriptSig)) - case wtx: WitnessTransaction => - val witness = wtx.witness(sigComponent.inputIndex.toInt) - val scriptWitness = FinalizedScriptWitness(witness) - val finalizedSigs = - if (witness != EmptyScriptWitness) { - Vector(scriptSig, scriptWitness) - } else { - Vector(scriptSig) - } - InputPSBTMap(utxos ++ finalizedSigs) - } + val scriptSig = + FinalizedScriptSig(sigComponent.scriptSignature) + sigComponent.transaction match { + case _: NonWitnessTransaction => + InputPSBTMap(utxos ++ Vector(scriptSig)) + case wtx: WitnessTransaction => + val witness = wtx.witness(sigComponent.inputIndex.toInt) + val scriptWitness = FinalizedScriptWitness(witness) + val finalizedSigs = + if (witness != EmptyScriptWitness) { + Vector(scriptSig, scriptWitness) + } else { + Vector(scriptSig) + } + InputPSBTMap(utxos ++ finalizedSigs) } } @@ -878,53 +874,48 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] { */ def fromUTXOInfo( spendingInfo: ScriptSignatureParams[InputInfo], - unsignedTx: Transaction)(implicit - ec: ExecutionContext): Future[InputPSBTMap] = { - val sigsF = spendingInfo.toSingles.map { spendingInfoSingle => + unsignedTx: Transaction): InputPSBTMap = { + val sigs = spendingInfo.toSingles.map { spendingInfoSingle => BitcoinSigner.signSingle(spendingInfoSingle, unsignedTx, isDummySignature = false) } - val sigFs = Future.sequence(sigsF) + val builder = Vector.newBuilder[InputPSBTRecord] - sigFs.map { sigs => - val builder = Vector.newBuilder[InputPSBTRecord] - - spendingInfo.inputInfo match { - case _: UnassignedSegwitNativeInputInfo => - builder.+=(WitnessUTXO(spendingInfo.output)) - case _: RawInputInfo | _: P2SHNonSegwitInputInfo | - _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo => - builder.+=(NonWitnessOrUnknownUTXO(spendingInfo.prevTransaction)) - } - - builder.++=(sigs) - - val sigHashType = SigHashType(spendingInfo.hashType) - builder.+=(sigHashType) - - spendingInfo.inputInfo match { - case p2sh: P2SHNonSegwitInputInfo => - builder.+=(RedeemScript(p2sh.redeemScript)) - case p2sh: P2SHNestedSegwitV0InputInfo => - builder.+=(RedeemScript(p2sh.redeemScript)) - p2sh.scriptWitness match { - case p2wsh: P2WSHWitnessV0 => - builder.+=(WitnessScript(p2wsh.redeemScript)) - case _: P2WPKHWitnessV0 => () - } - case p2wsh: P2WSHV0InputInfo => - builder.+=(WitnessScript(p2wsh.scriptWitness.redeemScript)) - case _: RawInputInfo | _: P2WPKHV0InputInfo | - _: UnassignedSegwitNativeInputInfo => - () - } - - val inputMap = InputPSBTMap(builder.result()) - - inputMap + spendingInfo.inputInfo match { + case _: UnassignedSegwitNativeInputInfo => + builder.+=(WitnessUTXO(spendingInfo.output)) + case _: RawInputInfo | _: P2SHNonSegwitInputInfo | + _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo => + builder.+=(NonWitnessOrUnknownUTXO(spendingInfo.prevTransaction)) } + + builder.++=(sigs) + + val sigHashType = SigHashType(spendingInfo.hashType) + builder.+=(sigHashType) + + spendingInfo.inputInfo match { + case p2sh: P2SHNonSegwitInputInfo => + builder.+=(RedeemScript(p2sh.redeemScript)) + case p2sh: P2SHNestedSegwitV0InputInfo => + builder.+=(RedeemScript(p2sh.redeemScript)) + p2sh.scriptWitness match { + case p2wsh: P2WSHWitnessV0 => + builder.+=(WitnessScript(p2wsh.redeemScript)) + case _: P2WPKHWitnessV0 => () + } + case p2wsh: P2WSHV0InputInfo => + builder.+=(WitnessScript(p2wsh.scriptWitness.redeemScript)) + case _: RawInputInfo | _: P2WPKHV0InputInfo | + _: UnassignedSegwitNativeInputInfo => + () + } + + val inputMap = InputPSBTMap(builder.result()) + + inputMap } override def constructMap(elements: Vector[InputPSBTRecord]): InputPSBTMap = 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 index 676580f556..57dcfd6ea2 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/builder/FinalizedTxWithSigningInfo.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/builder/FinalizedTxWithSigningInfo.scala @@ -4,8 +4,6 @@ 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. */ @@ -13,8 +11,7 @@ case class FinalizedTxWithSigningInfo( finalizedTx: Transaction, infos: Vector[ScriptSignatureParams[InputInfo]]) { - def sign(expectedFeeRate: FeeUnit)(implicit - ec: ExecutionContext): Future[Transaction] = { + def sign(expectedFeeRate: FeeUnit): Transaction = { RawTxSigner.sign(this, expectedFeeRate) } @@ -22,8 +19,7 @@ case class FinalizedTxWithSigningInfo( expectedFeeRate: FeeUnit, invariants: ( Vector[ScriptSignatureParams[InputInfo]], - Transaction) => Boolean)(implicit - ec: ExecutionContext): Future[Transaction] = { + Transaction) => Boolean): 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 index 6cc15cc5d2..a33dc3da8e 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxBuilder.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxBuilder.scala @@ -4,7 +4,6 @@ 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) @@ -149,7 +148,7 @@ case class RawTxBuilderWithFinalizer[F <: RawTxFinalizer]( finalizer: F) { /** Completes the builder and finalizes the result */ - def buildTx()(implicit ec: ExecutionContext): Future[Transaction] = { + def buildTx(): Transaction = { finalizer.buildTx(builder.result()) } 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 index 01d3575f3b..f0f189f1a6 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxFinalizer.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxFinalizer.scala @@ -8,7 +8,6 @@ import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.wallet.fee.{FeeUnit, SatoshisPerVirtualByte} import org.bitcoins.core.wallet.utxo.{InputInfo, InputSigningInfo} -import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Random, Success, Try} /** This trait is responsible for converting RawTxBuilderResults into @@ -33,8 +32,7 @@ import scala.util.{Failure, Random, Success, Try} trait RawTxFinalizer { /** Constructs a finalized (unsigned) transaction */ - def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] + def buildTx(txBuilderResult: RawTxBuilderResult): Transaction /** The result of buildTx is converted into a RawTxBuilderResult * by taking that transactions inputs (in order), outputs (in order), @@ -43,18 +41,14 @@ trait RawTxFinalizer { */ def andThen(other: RawTxFinalizer): RawTxFinalizer = { // this.buildTx above gets shadowed below, so this allows us to call it - def thisBuildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = + def thisBuildTx(txBuilderResult: RawTxBuilderResult): Transaction = this.buildTx(txBuilderResult) new RawTxFinalizer { - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { - for { - firstFinalizedTx <- thisBuildTx(txBuilderResult) - composedFinalizedTx <- - other.buildTx(RawTxBuilderResult.fromTransaction(firstFinalizedTx)) - } yield composedFinalizedTx + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { + val thisResult = + RawTxBuilderResult.fromTransaction(thisBuildTx(txBuilderResult)) + other.buildTx(thisResult) } } } @@ -87,20 +81,56 @@ abstract class FinalizerFactory[T <: RawTxFinalizer] { outputs: Seq[TransactionOutput], utxos: Seq[InputSigningInfo[InputInfo]], feeRate: FeeUnit, - changeSPK: ScriptPubKey)(implicit - ec: ExecutionContext): Future[Transaction] = { - val builderF = Future(txBuilderFrom(outputs, utxos, feeRate, changeSPK)) + changeSPK: ScriptPubKey): Transaction = { + val builder = txBuilderFrom(outputs, utxos, feeRate, changeSPK) - builderF.flatMap(_.buildTx()) + builder.buildTx() } } /** 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) + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { + txBuilderResult.toBaseTransaction + } +} + +object RawFinalizerFactory extends FinalizerFactory[RawFinalizer.type] { + + override def txFinalizerFrom( + inputs: Vector[InputInfo], + feeRate: FeeUnit, + changeSPK: ScriptPubKey): RawFinalizer.type = { + RawFinalizer + } + + def txBuilderWithLockTimeFrom( + outputs: Seq[TransactionOutput], + utxos: Seq[InputSigningInfo[InputInfo]], + lockTime: UInt32, + feeRate: FeeUnit, + changeSPK: ScriptPubKey, + defaultSequence: UInt32 = Policy.sequence): RawTxBuilderWithFinalizer[ + RawFinalizer.type] = { + val inputs = InputUtil.calcSequenceForInputs(utxos, defaultSequence) + val builder = RawTxBuilder().setLockTime(lockTime) ++= outputs ++= inputs + val finalizer = + txFinalizerFrom(utxos.toVector.map(_.inputInfo), feeRate, changeSPK) + + builder.setFinalizer(finalizer) + } + + def txFrom( + outputs: Seq[TransactionOutput], + utxos: Seq[InputSigningInfo[InputInfo]], + lockTime: UInt32, + feeRate: FeeUnit, + changeSPK: ScriptPubKey): Transaction = { + val builder = + txBuilderWithLockTimeFrom(outputs, utxos, lockTime, feeRate, changeSPK) + + builder.buildTx() } } @@ -109,24 +139,22 @@ case object RawFinalizer extends RawTxFinalizer { */ case object FilterDustFinalizer extends RawTxFinalizer { - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { val filteredOutputs = txBuilderResult.outputs.filter(_.value >= Policy.dustThreshold) - Future.successful( - txBuilderResult.toBaseTransaction.copy(outputs = filteredOutputs)) + + txBuilderResult.toBaseTransaction.copy(outputs = filteredOutputs) } } case object BIP69Finalizer extends RawTxFinalizer { - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { val sortedInputs = txBuilderResult.inputs.sorted val sortedOutputs = txBuilderResult.outputs.sorted - Future.successful( - txBuilderResult.toBaseTransaction.copy(inputs = sortedInputs, - outputs = sortedOutputs)) + + txBuilderResult.toBaseTransaction.copy(inputs = sortedInputs, + outputs = sortedOutputs) } } @@ -140,8 +168,7 @@ case class SanityCheckFinalizer( changeSPKs: Vector[ScriptPubKey] = Vector.empty) extends RawTxFinalizer { - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { val tx = txBuilderResult.toBaseTransaction val passInOutChecksT = @@ -154,7 +181,10 @@ case class SanityCheckFinalizer( TxUtil.sanityChecks(isSigned = false, inputInfos, expectedFeeRate, tx) } - Future.fromTry(passChecksT.map(_ => tx)) + passChecksT match { + case Success(_) => tx + case Failure(err) => throw err + } } } @@ -205,8 +235,7 @@ case class ChangeFinalizer( changeSPK: ScriptPubKey) extends RawTxFinalizer { - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { val outputsWithDummyChange = txBuilderResult.outputs :+ TransactionOutput(Satoshis.zero, changeSPK) @@ -220,22 +249,20 @@ case class ChangeFinalizer( val txDummyChange = txBuilderResult.toBaseTransaction.copy(outputs = outputsWithDummyChange) - val dummyTxF = TxUtil.addDummySigs(txDummyChange, inputInfos) + val dummyTx = TxUtil.addDummySigs(txDummyChange, inputInfos) - dummyTxF.map { dummyTx => - val fee = feeRate.calc(dummyTx) - val change = totalCrediting - totalSpending - fee + val fee = feeRate.calc(dummyTx) + val change = totalCrediting - totalSpending - fee - val newChangeOutput = TransactionOutput(change, changeSPK) + val newChangeOutput = TransactionOutput(change, changeSPK) - val newOutputs = if (change <= Policy.dustThreshold) { - txBuilderResult.outputs - } else { - txBuilderResult.outputs :+ newChangeOutput - } - - txBuilderResult.toBaseTransaction.copy(outputs = newOutputs) + val newOutputs = if (change <= Policy.dustThreshold) { + txBuilderResult.outputs + } else { + txBuilderResult.outputs :+ newChangeOutput } + + txBuilderResult.toBaseTransaction.copy(outputs = newOutputs) } } @@ -250,8 +277,7 @@ case class ChangeFinalizer( case class AddWitnessDataFinalizer(inputInfos: Vector[InputInfo]) extends RawTxFinalizer { - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { val result = txBuilderResult.toBaseTransaction @@ -264,14 +290,11 @@ case class AddWitnessDataFinalizer(inputInfos: Vector[InputInfo]) val witnesses = sortedInputInfos.map(InputInfo.getScriptWitness) TransactionWitness.fromWitOpt(witnesses) match { case _: EmptyWitness => - Future.successful(txBuilderResult.toBaseTransaction) + txBuilderResult.toBaseTransaction case wit: TransactionWitness => - val wtx = - WitnessTransaction - .toWitnessTx(txBuilderResult.toBaseTransaction) - .copy(witness = wit) - - Future.successful(wtx) + WitnessTransaction + .toWitnessTx(txBuilderResult.toBaseTransaction) + .copy(witness = wit) } } } @@ -286,8 +309,7 @@ case class StandardNonInteractiveFinalizer( changeSPK: ScriptPubKey) extends RawTxFinalizer { - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { val addChange = ChangeFinalizer(inputInfos, feeRate, changeSPK) val sanityCheck = SanityCheckFinalizer( @@ -326,29 +348,27 @@ case class ShufflingNonInteractiveFinalizer( changeSPK: ScriptPubKey) extends RawTxFinalizer { - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { val addChange = ChangeFinalizer(inputInfos, feeRate, changeSPK) val shuffled = addChange.andThen(ShuffleFinalizer) - shuffled.buildTx(txBuilderResult).flatMap { tempTx => - val tempTxBuilderResult = RawTxBuilderResult.fromTransaction(tempTx) + val tempTx = shuffled.buildTx(txBuilderResult) + val tempTxBuilderResult = RawTxBuilderResult.fromTransaction(tempTx) - val shuffledInputInfos = tempTxBuilderResult.inputs - .map(input => inputInfos.find(_.outPoint == input.previousOutput).get) + val shuffledInputInfos = tempTxBuilderResult.inputs + .map(input => inputInfos.find(_.outPoint == input.previousOutput).get) - val sanityCheck = - SanityCheckFinalizer(inputInfos = shuffledInputInfos, - expectedOutputSPKs = - tempTxBuilderResult.outputs.map(_.scriptPubKey), - expectedFeeRate = feeRate, - changeSPKs = Vector(changeSPK)) + val sanityCheck = + SanityCheckFinalizer(inputInfos = shuffledInputInfos, + expectedOutputSPKs = + tempTxBuilderResult.outputs.map(_.scriptPubKey), + expectedFeeRate = feeRate, + changeSPKs = Vector(changeSPK)) - sanityCheck - .andThen(AddWitnessDataFinalizer(shuffledInputInfos)) - .buildTx(tempTxBuilderResult) - } + sanityCheck + .andThen(AddWitnessDataFinalizer(shuffledInputInfos)) + .buildTx(tempTxBuilderResult) } } @@ -376,8 +396,7 @@ case class AddFutureFeeFinalizer( changeSPKs: Vector[ScriptPubKey]) extends RawTxFinalizer { - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { val changeOutputs = txBuilderResult.outputs.filter(output => changeSPKs.contains(output.scriptPubKey)) @@ -409,7 +428,10 @@ case class AddFutureFeeFinalizer( txBuilderResult.toBaseTransaction.copy(outputs = outputs) } - Future.fromTry(txT) + txT match { + case Success(tx) => tx + case Failure(err) => throw err + } } } @@ -421,17 +443,16 @@ case class AddFutureFeeFinalizer( case class SubtractFromOutputFinalizer(spk: ScriptPubKey, subAmt: CurrencyUnit) extends RawTxFinalizer { - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { txBuilderResult.outputs.zipWithIndex.find(_._1.scriptPubKey == spk) match { case Some((output, index)) => val newOutput = output.copy(value = output.value - subAmt) val newOutputs = txBuilderResult.outputs.updated(index, newOutput) - Future.successful( - txBuilderResult.toBaseTransaction.copy(outputs = newOutputs)) + + txBuilderResult.toBaseTransaction.copy(outputs = newOutputs) case None => - Future.failed(new RuntimeException( - s"Did not find expected SPK $spk in ${txBuilderResult.outputs.map(_.scriptPubKey)}")) + throw new RuntimeException( + s"Did not find expected SPK $spk in ${txBuilderResult.outputs.map(_.scriptPubKey)}") } } } @@ -446,22 +467,18 @@ case class SubtractFeeFromOutputsFinalizer( spks: Vector[ScriptPubKey]) extends RawTxFinalizer { - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { - val dummyTxF = + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { + val dummyTx = TxUtil.addDummySigs(txBuilderResult.toBaseTransaction, inputInfos) - val outputsAfterFeeF = dummyTxF.map { dummyTx => + val outputsAfterFee = SubtractFeeFromOutputsFinalizer.subtractFees( dummyTx, feeRate, spks ) - } - outputsAfterFeeF.map { outputsAfterFee => - txBuilderResult.toBaseTransaction.copy(outputs = outputsAfterFee) - } + txBuilderResult.toBaseTransaction.copy(outputs = outputsAfterFee) } } @@ -549,8 +566,7 @@ case class DualFundingTxFinalizer( lazy val (acceptFutureFee, acceptFundingFee) = computeFees(acceptInputs, acceptPayoutSPK, acceptChangeSPK) - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { val addOfferFutureFee = AddFutureFeeFinalizer(fundingSPK, offerFutureFee, Vector(offerChangeSPK)) val addAcceptFutureFee = AddFutureFeeFinalizer(fundingSPK, @@ -573,8 +589,7 @@ case class DualFundingTxFinalizer( /** Shuffles in the inputs and outputs of the Transaction into a random order */ case object ShuffleFinalizer extends RawTxFinalizer { - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { ShuffleInputsFinalizer .andThen(ShuffleOutputsFinalizer) .buildTx(txBuilderResult) @@ -584,21 +599,19 @@ case object ShuffleFinalizer extends RawTxFinalizer { /** Shuffles in the inputs of the Transaction into a random order */ case object ShuffleInputsFinalizer extends RawTxFinalizer { - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { val shuffledInputs = Random.shuffle(txBuilderResult.inputs) - Future.successful( - txBuilderResult.toBaseTransaction.copy(inputs = shuffledInputs)) + + txBuilderResult.toBaseTransaction.copy(inputs = shuffledInputs) } } /** Shuffles in the outputs of the Transaction into a random order */ case object ShuffleOutputsFinalizer extends RawTxFinalizer { - override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit - ec: ExecutionContext): Future[Transaction] = { + override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = { val shuffledOutputs = Random.shuffle(txBuilderResult.outputs) - Future.successful( - txBuilderResult.toBaseTransaction.copy(outputs = shuffledOutputs)) + + txBuilderResult.toBaseTransaction.copy(outputs = shuffledOutputs) } } 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 index 9de687d27b..ebfc689075 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxSigner.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxSigner.scala @@ -11,9 +11,6 @@ import org.bitcoins.core.wallet.utxo.{ UnassignedSegwitNativeInputInfo } -import scala.concurrent.{ExecutionContext, Future} -import scala.util.{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. * @@ -51,21 +48,20 @@ object RawTxSigner { def sign( utx: Transaction, - utxoInfos: Vector[ScriptSignatureParams[InputInfo]])(implicit - ec: ExecutionContext): Future[Transaction] = { + utxoInfos: Vector[ScriptSignatureParams[InputInfo]]): Transaction = { sign(utx, utxoInfos, emptyInvariant, dummySign = false) } - def sign(txWithInfo: FinalizedTxWithSigningInfo, expectedFeeRate: FeeUnit)( - implicit ec: ExecutionContext): Future[Transaction] = { + def sign( + txWithInfo: FinalizedTxWithSigningInfo, + expectedFeeRate: FeeUnit): Transaction = { sign(txWithInfo.finalizedTx, txWithInfo.infos, expectedFeeRate) } def sign( utx: Transaction, utxoInfos: Vector[ScriptSignatureParams[InputInfo]], - expectedFeeRate: FeeUnit)(implicit - ec: ExecutionContext): Future[Transaction] = { + expectedFeeRate: FeeUnit): Transaction = { val invariants = feeInvariant(expectedFeeRate) @@ -78,8 +74,7 @@ object RawTxSigner { expectedFeeRate: FeeUnit, userInvariants: ( Vector[ScriptSignatureParams[InputInfo]], - Transaction) => Boolean)(implicit - ec: ExecutionContext): Future[Transaction] = { + Transaction) => Boolean): Transaction = { val invariants = addFeeRateInvariant(expectedFeeRate, userInvariants) @@ -91,8 +86,7 @@ object RawTxSigner { expectedFeeRate: FeeUnit, userInvariants: ( Vector[ScriptSignatureParams[InputInfo]], - Transaction) => Boolean)(implicit - ec: ExecutionContext): Future[Transaction] = { + Transaction) => Boolean): Transaction = { val invariants = addFeeRateInvariant(expectedFeeRate, userInvariants) @@ -108,8 +102,7 @@ object RawTxSigner { invariants: ( Vector[ScriptSignatureParams[InputInfo]], Transaction) => Boolean, - dummySign: Boolean)(implicit - ec: ExecutionContext): Future[Transaction] = { + dummySign: Boolean): Transaction = { require( utxoInfos.length == utx.inputs.length, s"Must provide exactly one UTXOSatisfyingInfo per input, ${utxoInfos.length} != ${utx.inputs.length}") @@ -119,70 +112,56 @@ object RawTxSigner { utx.inputs.exists(_.previousOutput == utxo.outPoint)), "All UTXOSatisfyingInfos must correspond to an input.") - val signedTxF = + val signedTx = if ( utxoInfos.exists( _.inputInfo.isInstanceOf[UnassignedSegwitNativeInputInfo]) ) { - Future.fromTry(TxBuilderError.NoSigner) + throw TxBuilderError.NoSigner.exception } else { val builder = RawTxBuilder() .setVersion(utx.version) .setLockTime(utx.lockTime) ++= utx.outputs - val inputAndWitnessFs = utxoInfos.map { utxo => - val txSigCompF = + val inputsAndWitnesses = utxoInfos.map { utxo => + val txSigComp = BitcoinSigner.sign(utxo, utx, isDummySignature = dummySign) - txSigCompF.map { txSigComp => - val scriptWitnessOpt = TxSigComponent.getScriptWitness(txSigComp) + val scriptWitnessOpt = TxSigComponent.getScriptWitness(txSigComp) - (txSigComp.input, scriptWitnessOpt) - } + (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 + utx.inputs.foreach { unsignedInput => + val (input, witnessOpt) = inputsAndWitnesses + .find(_._1.previousOutput == unsignedInput.previousOutput) + .get - witnessesBuilder += witnessOpt - builder += input - } - } + witnessesBuilder += witnessOpt + builder += input + } - for { - _ <- inputsAddedToBuilderF - btx <- builder.setFinalizer(RawFinalizer).buildTx() - } yield { - val txWitness = - TransactionWitness.fromWitOpt(witnessesBuilder.result()) + val btx = builder.setFinalizer(RawFinalizer).buildTx() - txWitness match { - case EmptyWitness(_) => btx - case _: TransactionWitness => - WitnessTransaction(btx.version, - btx.inputs, - btx.outputs, - btx.lockTime, - txWitness) - } + 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)) { - Success(signedTx) - } else { - TxBuilderError.FailedUserInvariants - } - } - - Future.fromTry(txT) + if (invariants(utxoInfos, signedTx)) { + signedTx + } else { + throw TxBuilderError.FailedUserInvariants.exception } } } 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 15c98e4307..4fa08cc433 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 @@ -14,19 +14,16 @@ import org.bitcoins.core.wallet.utxo._ import org.bitcoins.crypto._ import scodec.bits.ByteVector -import scala.concurrent.{ExecutionContext, Future} - sealed abstract class SignerUtils { @deprecated("use an InputSigningInfo[InputInfo] instead", since = "6/23/2020") def doSign( sigComponent: TxSigComponent, - sign: ByteVector => Future[ECDigitalSignature], + sign: ByteVector => ECDigitalSignature, hashType: HashType, - isDummySignature: Boolean)(implicit - ec: ExecutionContext): Future[ECDigitalSignature] = { + isDummySignature: Boolean): ECDigitalSignature = { if (isDummySignature) { - Future.successful(DummyECDigitalSignature) + DummyECDigitalSignature } else { TransactionSignatureCreator.createSig(sigComponent, sign, hashType) } @@ -35,12 +32,11 @@ sealed abstract class SignerUtils { def doSign( unsignedTx: Transaction, signingInfo: InputSigningInfo[InputInfo], - sign: ByteVector => Future[ECDigitalSignature], + sign: ByteVector => ECDigitalSignature, hashType: HashType, - isDummySignature: Boolean)(implicit - ec: ExecutionContext): Future[ECDigitalSignature] = { + isDummySignature: Boolean): ECDigitalSignature = { if (isDummySignature) { - Future.successful(LowRDummyECDigitalSignature) + LowRDummyECDigitalSignature } else { TransactionSignatureCreator.createSig(unsignedTx, signingInfo, @@ -52,8 +48,7 @@ sealed abstract class SignerUtils { def signSingle( spendingInfo: ECSignatureParams[InputInfo], unsignedTx: Transaction, - isDummySignature: Boolean)(implicit - ec: ExecutionContext): Future[PartialSignature] = { + isDummySignature: Boolean): PartialSignature = { val tx = spendingInfo.inputInfo match { case _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo | @@ -63,17 +58,15 @@ sealed abstract class SignerUtils { unsignedTx } - val signatureF = doSign( + val signature = doSign( unsignedTx = tx, signingInfo = spendingInfo, - sign = spendingInfo.signer.signLowRFuture, + sign = spendingInfo.signer.signLowR, hashType = spendingInfo.hashType, isDummySignature = isDummySignature ) - signatureF.map { sig => - PartialSignature(spendingInfo.signer.publicKey, sig) - } + PartialSignature(spendingInfo.signer.publicKey, signature) } protected val flags: Seq[ScriptFlag] = Policy.standardFlags @@ -116,8 +109,7 @@ sealed abstract class Signer[-InputType <: InputInfo] extends SignerUtils { def sign( spendingInfo: ScriptSignatureParams[InputType], unsignedTx: Transaction, - isDummySignature: Boolean)(implicit - ec: ExecutionContext): Future[TxSigComponent] = { + isDummySignature: Boolean): TxSigComponent = { sign( spendingInfo, unsignedTx, @@ -137,8 +129,7 @@ sealed abstract class Signer[-InputType <: InputInfo] extends SignerUtils { spendingInfo: ScriptSignatureParams[InputInfo], unsignedTx: Transaction, isDummySignature: Boolean, - spendingInfoToSatisfy: ScriptSignatureParams[InputType])(implicit - ec: ExecutionContext): Future[TxSigComponent] + spendingInfoToSatisfy: ScriptSignatureParams[InputType]): TxSigComponent /** Creates a BaseTxSigComponent by replacing the unsignedTx input at inputIndex * with a signed one using the given ScriptSignature @@ -147,28 +138,25 @@ sealed abstract class Signer[-InputType <: InputInfo] extends SignerUtils { unsignedTx: Transaction, inputIndex: Int, output: TransactionOutput, - scriptSignatureF: Future[ScriptSignature])(implicit - ec: ExecutionContext): Future[BaseTxSigComponent] = { + scriptSignature: ScriptSignature): BaseTxSigComponent = { val unsignedInput = unsignedTx.inputs(inputIndex) - scriptSignatureF.map { signature => - val signedInput = TransactionInput(unsignedInput.previousOutput, - signature, - unsignedInput.sequence) - val signedInputs = unsignedTx.inputs.updated(inputIndex, signedInput) - val signedTx = unsignedTx match { - case btx: NonWitnessTransaction => - BaseTransaction(btx.version, signedInputs, btx.outputs, btx.lockTime) - case wtx: WitnessTransaction => - WitnessTransaction(wtx.version, - signedInputs, - wtx.outputs, - wtx.lockTime, - wtx.witness) - } - - BaseTxSigComponent(signedTx, UInt32(inputIndex), output, flags) + val signedInput = TransactionInput(unsignedInput.previousOutput, + scriptSignature, + unsignedInput.sequence) + val signedInputs = unsignedTx.inputs.updated(inputIndex, signedInput) + val signedTx = unsignedTx match { + case btx: NonWitnessTransaction => + BaseTransaction(btx.version, signedInputs, btx.outputs, btx.lockTime) + case wtx: WitnessTransaction => + WitnessTransaction(wtx.version, + signedInputs, + wtx.outputs, + wtx.lockTime, + wtx.witness) } + + BaseTxSigComponent(signedTx, UInt32(inputIndex), output, flags) } } @@ -177,8 +165,7 @@ object BitcoinSigner extends SignerUtils { def sign( spendingInfo: ScriptSignatureParams[InputInfo], unsignedTx: Transaction, - isDummySignature: Boolean)(implicit - ec: ExecutionContext): Future[TxSigComponent] = { + isDummySignature: Boolean): TxSigComponent = { sign(spendingInfo, unsignedTx, isDummySignature, spendingInfo) } @@ -186,8 +173,8 @@ object BitcoinSigner extends SignerUtils { spendingInfo: ScriptSignatureParams[InputInfo], unsignedTx: Transaction, isDummySignature: Boolean, - spendingInfoToSatisfy: ScriptSignatureParams[InputInfo])(implicit - ec: ExecutionContext): Future[TxSigComponent] = { + spendingInfoToSatisfy: ScriptSignatureParams[ + InputInfo]): TxSigComponent = { def spendingFrom[Info <: InputInfo]( inputInfo: Info): ScriptSignatureParams[Info] = { spendingInfoToSatisfy.copy(inputInfo = inputInfo) @@ -263,8 +250,7 @@ object BitcoinSigner extends SignerUtils { inputIndex: Int, signer: Sign, conditionalPath: ConditionalPath = ConditionalPath.NoCondition, - isDummySignature: Boolean = false)(implicit - ec: ExecutionContext): Future[PSBT] = { + isDummySignature: Boolean = false): PSBT = { // if already signed by this signer if ( psbt @@ -272,9 +258,8 @@ object BitcoinSigner extends SignerUtils { .partialSignatures .exists(_.pubKey == signer.publicKey) ) { - Future.failed( - new IllegalArgumentException( - "Input has already been signed with this key")) + throw new IllegalArgumentException( + "Input has already been signed with this key") } val tx = psbt.transaction @@ -317,12 +302,10 @@ object BitcoinSigner extends SignerUtils { case _: ScriptPubKey => tx } - val partialSignatureF = + val partialSignature = signSingle(spendingInfo, txToSign, isDummySignature) - partialSignatureF.map { partialSignature => - psbt.addSignature(partialSignature, inputIndex) - } + psbt.addSignature(partialSignature, inputIndex) } } @@ -339,24 +322,23 @@ sealed abstract class RawSingleKeyBitcoinSigner[-InputType <: RawInputInfo] spendingInfo: ScriptSignatureParams[InputInfo], unsignedTx: Transaction, isDummySignature: Boolean, - spendingInfoToSatisfy: ScriptSignatureParams[InputType])(implicit - ec: ExecutionContext): Future[TxSigComponent] = { + spendingInfoToSatisfy: ScriptSignatureParams[ + InputType]): TxSigComponent = { val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx) - val partialSignatureF = + val partialSignature = signSingle(spendingInfo.toSingle(0), unsignedTx, isDummySignature) - val scriptSigF = partialSignatureF.map { partialSignature => + val scriptSig = keyAndSigToScriptSig(partialSignature.pubKey, partialSignature.signature, spendingInfoToSatisfy) - } updateScriptSigInSigComponent(unsignedTx, inputIndex.toInt, output, - scriptSigF) + scriptSig) } } @@ -367,12 +349,11 @@ sealed abstract class EmptySigner extends Signer[EmptyInputInfo] { spendingInfo: ScriptSignatureParams[InputInfo], unsignedTx: Transaction, isDummySignature: Boolean, - spendingInfoToSatisfy: ScriptSignatureParams[EmptyInputInfo])(implicit - ec: ExecutionContext): Future[TxSigComponent] = { + spendingInfoToSatisfy: ScriptSignatureParams[ + EmptyInputInfo]): TxSigComponent = { val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx) - val satisfyEmptyScriptSig = - Future.successful(TrivialTrueScriptSignature) + val satisfyEmptyScriptSig = TrivialTrueScriptSignature updateScriptSigInSigComponent(unsignedTx, inputIndex.toInt, @@ -431,25 +412,23 @@ sealed abstract class MultiSigSigner extends Signer[MultiSignatureInputInfo] { spendingInfo: ScriptSignatureParams[InputInfo], unsignedTx: Transaction, isDummySignature: Boolean, - spendingInfoToSatisfy: ScriptSignatureParams[MultiSignatureInputInfo])( - implicit ec: ExecutionContext): Future[TxSigComponent] = { + spendingInfoToSatisfy: ScriptSignatureParams[ + MultiSignatureInputInfo]): TxSigComponent = { val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx) - val keysAndSigsF = spendingInfo.toSingles.map { spendingInfoSingle => + val keysAndSigs = spendingInfo.toSingles.map { spendingInfoSingle => signSingle(spendingInfoSingle, unsignedTx, isDummySignature) } - val signaturesF = Future.sequence(keysAndSigsF).map(_.map(_.signature)) + val signatures = keysAndSigs.map(_.signature) - val scriptSigF = signaturesF.map { sigs => - MultiSignatureScriptSignature(sigs) - } + val scriptSig = MultiSignatureScriptSignature(signatures) updateScriptSigInSigComponent(unsignedTx, inputIndex.toInt, output, - scriptSigF) + scriptSig) } } @@ -462,10 +441,10 @@ sealed abstract class P2SHSigner extends Signer[P2SHInputInfo] { spendingInfo: ScriptSignatureParams[InputInfo], unsignedTx: Transaction, isDummySignature: Boolean, - spendingInfoToSatisfy: ScriptSignatureParams[P2SHInputInfo])(implicit - ec: ExecutionContext): Future[TxSigComponent] = { + spendingInfoToSatisfy: ScriptSignatureParams[ + P2SHInputInfo]): TxSigComponent = { if (spendingInfoToSatisfy != spendingInfo) { - Future.fromTry(TxBuilderError.WrongSigner) + throw TxBuilderError.WrongSigner.exception } else { val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx) @@ -481,42 +460,40 @@ sealed abstract class P2SHSigner extends Signer[P2SHInputInfo] { val nestedSpendingInfo = spendingInfoToSatisfy.copy( inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo) - val signedTxEither = + val signedTx = BitcoinSigner .sign(nestedSpendingInfo, updatedTx, isDummySignature) - .map(_.transaction) + .transaction - signedTxEither.map { signedTx => - val i = signedTx.inputs(inputIndex.toInt) + val i = signedTx.inputs(inputIndex.toInt) - val p2sh = - P2SHScriptSignature(i.scriptSignature, - spendingInfoToSatisfy.inputInfo.redeemScript) + val p2sh = + P2SHScriptSignature(i.scriptSignature, + spendingInfoToSatisfy.inputInfo.redeemScript) - val signedInput = - TransactionInput(i.previousOutput, p2sh, i.sequence) + val signedInput = + TransactionInput(i.previousOutput, p2sh, i.sequence) - val signedInputs = - signedTx.inputs.updated(inputIndex.toInt, signedInput) + val signedInputs = + signedTx.inputs.updated(inputIndex.toInt, signedInput) - val finalTx = signedTx match { - case btx: NonWitnessTransaction => - BaseTransaction(version = btx.version, - inputs = signedInputs, - outputs = btx.outputs, - lockTime = btx.lockTime) - case wtx: WitnessTransaction => - WitnessTransaction(version = wtx.version, - inputs = signedInputs, - outputs = wtx.outputs, - lockTime = wtx.lockTime, - witness = wtx.witness) - } - P2SHTxSigComponent(transaction = finalTx, - inputIndex = inputIndex, - output = output, - flags = flags) + val finalTx = signedTx match { + case btx: NonWitnessTransaction => + BaseTransaction(version = btx.version, + inputs = signedInputs, + outputs = btx.outputs, + lockTime = btx.lockTime) + case wtx: WitnessTransaction => + WitnessTransaction(version = wtx.version, + inputs = signedInputs, + outputs = wtx.outputs, + lockTime = wtx.lockTime, + witness = wtx.witness) } + P2SHTxSigComponent(transaction = finalTx, + inputIndex = inputIndex, + output = output, + flags = flags) } } } @@ -529,10 +506,10 @@ sealed abstract class P2WPKHSigner extends Signer[P2WPKHV0InputInfo] { spendingInfo: ScriptSignatureParams[InputInfo], unsignedTx: Transaction, isDummySignature: Boolean, - spendingInfoToSatisfy: ScriptSignatureParams[P2WPKHV0InputInfo])(implicit - ec: ExecutionContext): Future[TxSigComponent] = { + spendingInfoToSatisfy: ScriptSignatureParams[ + P2WPKHV0InputInfo]): TxSigComponent = { if (spendingInfoToSatisfy != spendingInfo) { - Future.fromTry(TxBuilderError.WrongSigner) + throw TxBuilderError.WrongSigner.exception } else { val (_, output, inputIndex, hashType) = relevantInfo(spendingInfo, unsignedTx) @@ -554,36 +531,32 @@ sealed abstract class P2WPKHSigner extends Signer[P2WPKHV0InputInfo] { unsignedTxWitness) val witSPK = output.scriptPubKey match { - case p2wpkh: P2WPKHWitnessSPKV0 => Future.successful(p2wpkh) + case p2wpkh: P2WPKHWitnessSPKV0 => p2wpkh case _: UnassignedWitnessScriptPubKey | _: P2WSHWitnessSPKV0 => - Future.fromTry(TxBuilderError.WrongSigner) + throw TxBuilderError.WrongSigner.exception case _: NonWitnessScriptPubKey => - Future.fromTry(TxBuilderError.NonWitnessSPK) + throw TxBuilderError.NonWitnessSPK.exception } - witSPK.flatMap { w => - val witOutput = TransactionOutput(output.value, w) + val witOutput = TransactionOutput(output.value, witSPK) - val signature = - doSign(unsignedTx, - spendingInfo, - signer.signLowRFuture, - hashType, - isDummySignature) + val signature = + doSign(unsignedTx, + spendingInfo, + signer.signLowR, + hashType, + isDummySignature) - signature.map { sig => - val scriptWitness = P2WPKHWitnessV0(pubKey, sig) - val signedTxWitness = - wtx.witness.updated(inputIndex.toInt, scriptWitness) - val signedTx = WitnessTransaction(unsignedWtx.version, - unsignedWtx.inputs, - unsignedWtx.outputs, - unsignedWtx.lockTime, - signedTxWitness) - WitnessTxSigComponentRaw(signedTx, inputIndex, witOutput, flags) - } + val scriptWitness = P2WPKHWitnessV0(pubKey, signature) + val signedTxWitness = + wtx.witness.updated(inputIndex.toInt, scriptWitness) + val signedTx = WitnessTransaction(unsignedWtx.version, + unsignedWtx.inputs, + unsignedWtx.outputs, + unsignedWtx.lockTime, + signedTxWitness) + WitnessTxSigComponentRaw(signedTx, inputIndex, witOutput, flags) - } case btx: NonWitnessTransaction => val wtx = WitnessTransaction.toWitnessTx(btx) @@ -600,10 +573,10 @@ sealed abstract class P2WSHSigner extends Signer[P2WSHV0InputInfo] { spendingInfo: ScriptSignatureParams[InputInfo], unsignedTx: Transaction, isDummySignature: Boolean, - spendingInfoToSatisfy: ScriptSignatureParams[P2WSHV0InputInfo])(implicit - ec: ExecutionContext): Future[TxSigComponent] = { + spendingInfoToSatisfy: ScriptSignatureParams[ + P2WSHV0InputInfo]): TxSigComponent = { if (spendingInfoToSatisfy != spendingInfo) { - Future.fromTry(TxBuilderError.WrongSigner) + throw TxBuilderError.WrongSigner.exception } else { val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx) @@ -615,27 +588,24 @@ sealed abstract class P2WSHSigner extends Signer[P2WSHV0InputInfo] { val nestedSpendingInfo = spendingInfoToSatisfy.copy( inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo) - val signedSigComponentF = BitcoinSigner.sign(spendingInfo, - wtx, - isDummySignature, - nestedSpendingInfo) + val signedSigComponent = BitcoinSigner.sign(spendingInfo, + wtx, + isDummySignature, + nestedSpendingInfo) - val scriptWitF = signedSigComponentF.map { signedSigComponent => + val scriptWit = P2WSHWitnessV0( spendingInfoToSatisfy.inputInfo.scriptWitness.redeemScript, signedSigComponent.scriptSignature) - } - scriptWitF.map { scriptWit => - val signedWitness = - wtx.witness.updated(inputIndex.toInt, scriptWit) - val signedWTx = WitnessTransaction(wtx.version, - wtx.inputs, - wtx.outputs, - wtx.lockTime, - signedWitness) - WitnessTxSigComponentRaw(signedWTx, inputIndex, output, flags) - } + val signedWitness = + wtx.witness.updated(inputIndex.toInt, scriptWit) + val signedWTx = WitnessTransaction(wtx.version, + wtx.inputs, + wtx.outputs, + wtx.lockTime, + signedWitness) + WitnessTxSigComponentRaw(signedWTx, inputIndex, output, flags) } } } @@ -647,8 +617,8 @@ sealed abstract class LockTimeSigner extends Signer[LockTimeInputInfo] { spendingInfo: ScriptSignatureParams[InputInfo], unsignedTx: Transaction, isDummySignature: Boolean, - spendingInfoToSatisfy: ScriptSignatureParams[LockTimeInputInfo])(implicit - ec: ExecutionContext): Future[TxSigComponent] = { + spendingInfoToSatisfy: ScriptSignatureParams[ + LockTimeInputInfo]): TxSigComponent = { val nestedSpendingInfo = spendingInfoToSatisfy.copy( inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo) @@ -669,27 +639,26 @@ sealed abstract class ConditionalSigner extends Signer[ConditionalInputInfo] { spendingInfo: ScriptSignatureParams[InputInfo], unsignedTx: Transaction, isDummySignature: Boolean, - spendingInfoToSatisfy: ScriptSignatureParams[ConditionalInputInfo])( - implicit ec: ExecutionContext): Future[TxSigComponent] = { + spendingInfoToSatisfy: ScriptSignatureParams[ + ConditionalInputInfo]): TxSigComponent = { val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx) val nestedSpendingInfo = spendingInfoToSatisfy.copy( inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo) - val missingOpSigComponentF = BitcoinSigner.sign(spendingInfo, - unsignedTx, - isDummySignature, - nestedSpendingInfo) + val missingOpSigComponent = BitcoinSigner.sign(spendingInfo, + unsignedTx, + isDummySignature, + nestedSpendingInfo) - val scriptSigF = missingOpSigComponentF.map { sigComponent => - ConditionalScriptSignature(sigComponent.scriptSignature, + val scriptSig = + ConditionalScriptSignature(missingOpSigComponent.scriptSignature, spendingInfoToSatisfy.inputInfo.condition) - } updateScriptSigInSigComponent(unsignedTx, inputIndex.toInt, output, - scriptSigF) + scriptSig) } } object ConditionalSigner extends ConditionalSigner diff --git a/crypto-test/.jvm/src/test/scala/org/bitcoins/crypto/SignWithEntropyTest.scala b/crypto-test/.jvm/src/test/scala/org/bitcoins/crypto/SignWithEntropyTest.scala index 320b14028d..4ddfb941d8 100644 --- a/crypto-test/.jvm/src/test/scala/org/bitcoins/crypto/SignWithEntropyTest.scala +++ b/crypto-test/.jvm/src/test/scala/org/bitcoins/crypto/SignWithEntropyTest.scala @@ -1,6 +1,6 @@ package org.bitcoins.crypto -class SignWithEntropyTest extends BitcoinSCryptoAsyncTest { +class SignWithEntropyTest extends BitcoinSCryptoTest { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = generatorDrivenConfigNewCode @@ -13,31 +13,26 @@ class SignWithEntropyTest extends BitcoinSCryptoAsyncTest { behavior of "SignWithEntropy" it must "sign arbitrary data correctly with low R values" in { - forAllAsync(CryptoGenerators.sha256Digest) { hash => + forAll(CryptoGenerators.sha256Digest) { hash => val bytes = hash.bytes - for { - sig1 <- privKey.signLowRFuture(bytes) - sig2 <- privKey.signLowRFuture(bytes) // Check for determinism - } yield { - assert(pubKey.verify(bytes, sig1)) - assert( - sig1.bytes.length <= 70 - ) // This assertion fails if Low R is not used - assert(sig1.bytes == sig2.bytes) - assert(sig1 == sig2) - } + val sig1 = privKey.signLowR(bytes) + val sig2 = privKey.signLowR(bytes) // Check for determinism + assert(pubKey.verify(bytes, sig1)) + assert( + sig1.bytes.length <= 70 + ) // This assertion fails if Low R is not used + assert(sig1.bytes == sig2.bytes) + assert(sig1 == sig2) } } it must "sign arbitrary pieces of data with arbitrary entropy correctly" in { - forAllAsync(CryptoGenerators.sha256Digest, CryptoGenerators.sha256Digest) { + forAll(CryptoGenerators.sha256Digest, CryptoGenerators.sha256Digest) { case (hash, entropy) => - val sigF = privKey.signWithEntropyFunction(hash.bytes, entropy.bytes) + val sig = privKey.signWithEntropy(hash.bytes, entropy.bytes) - sigF.map { sig => - assert(pubKey.verify(hash.bytes, sig)) - } + assert(pubKey.verify(hash.bytes, sig)) } } } diff --git a/crypto-test/src/test/scala/org/bitcoins/crypto/SignTest.scala b/crypto-test/src/test/scala/org/bitcoins/crypto/SignTest.scala index 0ddd3320c5..e3450531d6 100644 --- a/crypto-test/src/test/scala/org/bitcoins/crypto/SignTest.scala +++ b/crypto-test/src/test/scala/org/bitcoins/crypto/SignTest.scala @@ -2,7 +2,7 @@ package org.bitcoins.crypto import scodec.bits.ByteVector -class SignTest extends BitcoinSCryptoAsyncTest { +class SignTest extends BitcoinSCryptoTest { implicit override val generatorDrivenConfig: PropertyCheckConfiguration = generatorDrivenConfigNewCode @@ -30,12 +30,10 @@ class SignTest extends BitcoinSCryptoAsyncTest { } it must "sign arbitrary pieces of data correctly" in { - forAllAsync(CryptoGenerators.sha256Digest) { hash => - val sigF = privKey.signFunction(hash.bytes) + forAll(CryptoGenerators.sha256Digest) { hash => + val sig = privKey.sign(hash.bytes) - sigF.map { sig => - assert(pubKey.verify(hash.bytes, sig)) - } + assert(pubKey.verify(hash.bytes, sig)) } } } diff --git a/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala b/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala index 73865c0439..d84966937e 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/ECKey.scala @@ -4,8 +4,7 @@ import scodec.bits.ByteVector import java.math.BigInteger import scala.annotation.tailrec -import scala.concurrent.ExecutionContext.Implicits -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.ExecutionContext /** Created by chris on 2/16/16. */ @@ -18,11 +17,6 @@ sealed abstract class ECPrivateKey with Sign with MaskedToString { - override def signFunction: ByteVector => Future[ECDigitalSignature] = { - bytes => - Future.successful(sign(bytes)) - } - /** Signs a given sequence of bytes with the signingKey * @param dataToSign the bytes to be signed * @return the digital signature @@ -33,23 +27,12 @@ sealed abstract class ECPrivateKey def sign(hash: HashDigest): ECDigitalSignature = sign(hash.bytes) - def signFuture(hash: HashDigest)(implicit - ec: ExecutionContext): Future[ECDigitalSignature] = - Future(sign(hash)) - override def signWithEntropy( bytes: ByteVector, entropy: ByteVector): ECDigitalSignature = { CryptoUtil.signWithEntropy(this, bytes, entropy) } - override def signWithEntropyFunction: ( - ByteVector, - ByteVector) => Future[ECDigitalSignature] = { case (bytes, entropy) => - import scala.concurrent.ExecutionContext.Implicits.global - Future(signWithEntropy(bytes, entropy)) - } - def schnorrSign(dataToSign: ByteVector): SchnorrDigitalSignature = { val auxRand = ECPrivateKey.freshPrivateKey.bytes schnorrSign(dataToSign, auxRand) @@ -156,7 +139,7 @@ object ECPrivateKey extends Factory[ECPrivateKey] { def fromBytes(bytes: ByteVector, isCompressed: Boolean): ECPrivateKey = { if (bytes.size == 32) - ECPrivateKeyImpl(bytes, isCompressed, Implicits.global) + ECPrivateKeyImpl(bytes, isCompressed, ExecutionContext.global) else if (bytes.size < 32) { //means we need to pad the private key with 0 bytes so we have 32 bytes ECPrivateKey.fromBytes(bytes.padLeft(32), isCompressed) @@ -279,7 +262,7 @@ object ECPublicKey extends Factory[ECPublicKey] { } override def fromBytes(bytes: ByteVector): ECPublicKey = { - ECPublicKeyImpl(bytes, Implicits.global) + ECPublicKeyImpl(bytes, ExecutionContext.global) } def apply(): ECPublicKey = freshPublicKey diff --git a/crypto/src/main/scala/org/bitcoins/crypto/Sign.scala b/crypto/src/main/scala/org/bitcoins/crypto/Sign.scala index f573996138..bca18e06eb 100644 --- a/crypto/src/main/scala/org/bitcoins/crypto/Sign.scala +++ b/crypto/src/main/scala/org/bitcoins/crypto/Sign.scala @@ -2,8 +2,8 @@ package org.bitcoins.crypto import scodec.bits.ByteVector -import scala.concurrent.duration.DurationInt -import scala.concurrent.{Await, ExecutionContext, Future} +import scala.annotation.tailrec +import scala.concurrent.{ExecutionContext, Future} /** This is meant to be an abstraction for a [[org.bitcoins.crypto.ECPrivateKey]], sometimes we will not * have direct access to a private key in memory -- for instance if that key is on a hardware device -- so we need to create an @@ -17,11 +17,8 @@ import scala.concurrent.{Await, ExecutionContext, Future} * If you have a hardware wallet, you will need to implement the protocol to send a message to the hardware device. The * type signature of the function you implement must be scodec.bits.ByteVector => Future[ECDigitalSignature] */ -trait Sign { - def signFunction: ByteVector => Future[ECDigitalSignature] - - def signFuture(bytes: ByteVector): Future[ECDigitalSignature] = - signFunction(bytes) +trait AsyncSign { + def asyncSign(bytes: ByteVector): Future[ECDigitalSignature] /** Note that using this function to generate digital signatures with specific * properties (by trying a bunch of entropy values) can reduce privacy as it will @@ -32,88 +29,70 @@ trait Sign { * In short, ALL USES OF THIS FUNCTION THAT SIGN THE SAME DATA WITH DIFFERENT ENTROPY * HAVE THE POTENTIAL TO CAUSE REDUCTIONS IN SECURITY AND PRIVACY, BEWARE! */ - def signWithEntropyFunction: ( - ByteVector, - ByteVector) => Future[ECDigitalSignature] - - /** Note that using this function to generate digital signatures with specific - * properties (by trying a bunch of entropy values) can reduce privacy as it will - * fingerprint your wallet. Additionally it could lead to a loss of entropy in - * the resulting nonce should the property you are interested in cause a constraint - * on the input space. - * - * In short, ALL USES OF THIS FUNCTION THAT SIGN THE SAME DATA WITH DIFFERENT ENTROPY - * HAVE THE POTENTIAL TO CAUSE REDUCTIONS IN SECURITY AND PRIVACY, BEWARE! - */ - def signWithEntropyFuture( + def asyncSignWithEntropy( bytes: ByteVector, - entropy: ByteVector): Future[ECDigitalSignature] = - signWithEntropyFunction(bytes, entropy) + entropy: ByteVector): Future[ECDigitalSignature] - private def signLowRFuture(bytes: ByteVector, startAt: Long)(implicit + private def asyncSignLowR(bytes: ByteVector, startAt: Long)(implicit ec: ExecutionContext): Future[ECDigitalSignature] = { - val sigF: Future[ECDigitalSignature] = if (startAt == 0) { - signFunction(bytes) - } else { + val sigF = if (startAt == 0) { // On first try, use normal signing + asyncSign(bytes) + } else { // Subsequently, use additional entropy val startBytes = ByteVector.fromLong(startAt).padLeft(32).reverse - signWithEntropyFunction(bytes, startBytes) + asyncSignWithEntropy(bytes, startBytes) } sigF.flatMap { sig => if (sig.bytes.length <= 70) { Future.successful(sig) } else { - signLowRFuture(bytes, startAt + 1) + asyncSignLowR(bytes, startAt + 1) } } } - def signLowRFuture(bytes: ByteVector)(implicit + def asyncSignLowR(bytes: ByteVector)(implicit ec: ExecutionContext): Future[ECDigitalSignature] = { - signLowRFuture(bytes, startAt = 0) - } - - def sign(bytes: ByteVector): ECDigitalSignature = { - Await.result(signFuture(bytes), 30.seconds) - } - - def signLowR(bytes: ByteVector)(implicit - ec: ExecutionContext): ECDigitalSignature = { - Await.result(signLowRFuture(bytes), 30.seconds) - } - - def signWithEntropy( - bytes: ByteVector, - entropy: ByteVector): ECDigitalSignature = { - Await.result(signWithEntropyFuture(bytes, entropy), 30.seconds) + asyncSignLowR(bytes, startAt = 0) } def publicKey: ECPublicKey } -object Sign { +object AsyncSign { - private case class SignImpl( - signFunction: ByteVector => Future[ECDigitalSignature], - signWithEntropyFunction: ( + private case class AsyncSignImpl( + asyncSignFunction: ByteVector => Future[ECDigitalSignature], + asyncSignWithEntropyFunction: ( ByteVector, ByteVector) => Future[ECDigitalSignature], - publicKey: ECPublicKey) - extends Sign + override val publicKey: ECPublicKey) + extends AsyncSign { - def apply( - signFunction: ByteVector => Future[ECDigitalSignature], - signWithEntropyFunction: ( - ByteVector, - ByteVector) => Future[ECDigitalSignature], - pubKey: ECPublicKey): Sign = { - SignImpl(signFunction, signWithEntropyFunction, pubKey) + override def asyncSign(bytes: ByteVector): Future[ECDigitalSignature] = { + asyncSignFunction(bytes) + } + + override def asyncSignWithEntropy( + bytes: ByteVector, + entropy: ByteVector): Future[ECDigitalSignature] = { + asyncSignWithEntropyFunction(bytes, entropy) + } } - def constant(sig: ECDigitalSignature, pubKey: ECPublicKey): Sign = { - SignImpl(_ => Future.successful(sig), - (_, _) => Future.successful(sig), - pubKey) + def apply( + asyncSign: ByteVector => Future[ECDigitalSignature], + asyncSignWithEntropy: ( + ByteVector, + ByteVector) => Future[ECDigitalSignature], + pubKey: ECPublicKey): AsyncSign = { + AsyncSignImpl(asyncSign, asyncSignWithEntropy, pubKey) + } + + def constant(sig: ECDigitalSignature, pubKey: ECPublicKey): AsyncSign = { + AsyncSignImpl(_ => Future.successful(sig), + (_, _) => Future.successful(sig), + pubKey) } /** This dummySign function is useful for the case where we do not have the @@ -123,6 +102,93 @@ object Sign { * the public key is still useful here though because it can be used to match against * a specific private key on another server */ + def dummySign(publicKey: ECPublicKey): AsyncSign = { + constant(EmptyDigitalSignature, publicKey) + } +} + +trait Sign extends AsyncSign { + def sign(bytes: ByteVector): ECDigitalSignature + + override def asyncSign(bytes: ByteVector): Future[ECDigitalSignature] = { + Future.successful(sign(bytes)) + } + + /** Note that using this function to generate digital signatures with specific + * properties (by trying a bunch of entropy values) can reduce privacy as it will + * fingerprint your wallet. Additionally it could lead to a loss of entropy in + * the resulting nonce should the property you are interested in cause a constraint + * on the input space. + * + * In short, ALL USES OF THIS FUNCTION THAT SIGN THE SAME DATA WITH DIFFERENT ENTROPY + * HAVE THE POTENTIAL TO CAUSE REDUCTIONS IN SECURITY AND PRIVACY, BEWARE! + */ + def signWithEntropy( + bytes: ByteVector, + entropy: ByteVector): ECDigitalSignature + + override def asyncSignWithEntropy( + bytes: ByteVector, + entropy: ByteVector): Future[ECDigitalSignature] = { + Future.successful(signWithEntropy(bytes, entropy)) + } + + @tailrec + private def signLowR(bytes: ByteVector, startAt: Long): ECDigitalSignature = { + val sig = if (startAt == 0) { // On first try, use normal signing + sign(bytes) + } else { // Subsequently, use additional entropy + val startBytes = ByteVector.fromLong(startAt).padLeft(32).reverse + signWithEntropy(bytes, startBytes) + } + + if (sig.bytes.length <= 70) { + sig + } else { + signLowR(bytes, startAt + 1) + } + } + + def signLowR(bytes: ByteVector): ECDigitalSignature = { + signLowR(bytes, startAt = 0) + } + + override def asyncSignLowR(bytes: ByteVector)(implicit + ec: ExecutionContext): Future[ECDigitalSignature] = { + Future.successful(signLowR(bytes)) + } +} + +object Sign { + + private case class SignImpl( + signFunction: ByteVector => ECDigitalSignature, + signWithEntropyFunction: (ByteVector, ByteVector) => ECDigitalSignature, + override val publicKey: ECPublicKey) + extends Sign { + + override def sign(bytes: ByteVector): ECDigitalSignature = { + signFunction(bytes) + } + + override def signWithEntropy( + bytes: ByteVector, + entropy: ByteVector): ECDigitalSignature = { + signWithEntropyFunction(bytes, entropy) + } + } + + def apply( + sign: ByteVector => ECDigitalSignature, + signWithEntropy: (ByteVector, ByteVector) => ECDigitalSignature, + pubKey: ECPublicKey): Sign = { + SignImpl(sign, signWithEntropy, pubKey) + } + + def constant(sig: ECDigitalSignature, pubKey: ECPublicKey): Sign = { + SignImpl(_ => sig, (_, _) => sig, pubKey) + } + def dummySign(publicKey: ECPublicKey): Sign = { constant(EmptyDigitalSignature, publicKey) } diff --git a/docs/core/psbts.md b/docs/core/psbts.md index ed6cb379f0..ec66ad9fd6 100644 --- a/docs/core/psbts.md +++ b/docs/core/psbts.md @@ -33,7 +33,7 @@ import org.bitcoins.core.wallet.utxo.{ import scodec.bits._ import scala.concurrent.duration.DurationInt -import scala.concurrent.{Await, ExecutionContext, ExecutionContextExecutor} +import scala.concurrent.{ExecutionContext, ExecutionContextExecutor} ``` ```scala mdoc:compile-only @@ -107,10 +107,10 @@ val privKey0 = ECPrivateKeyUtil.fromWIFToPrivateKey( val privKey1 = ECPrivateKeyUtil.fromWIFToPrivateKey( "cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d") -val psbtFirstSigF = +val psbtFirstSig = psbtWithSigHashFlags .sign(inputIndex = 0, signer = privKey0) - .flatMap(_.sign(inputIndex = 0, signer = privKey1)) + .sign(inputIndex = 0, signer = privKey1) // Alternatively, you can use produce a signature with a ECSignatureParams // using the BitcoinSigner will return a PartialSignature that can be added to a PSBT @@ -131,44 +131,40 @@ val spendingInfoSingle = ECSignatureParams( ) // Then we can sign the transaction -val signatureF = BitcoinSigner.signSingle( +val signature = BitcoinSigner.signSingle( spendingInfo = spendingInfoSingle, unsignedTx = unsignedTransaction, isDummySignature = false) // We can then add the signature to the PSBT // Note: this signature could be produced by us or another party -signatureF.map(sig => psbtWithSigHashFlags.addSignature(sig, inputIndex = 0)) +psbtWithSigHashFlags.addSignature(signature, inputIndex = 0) // With our first input signed we can now move on to showing how another party could sign our second input - val signedTransactionF = psbtFirstSigF.map { psbtFirstSig => - // In this scenario, let's say that the second input does not belong to us and we need - // another party to sign it. In this case we would need to send the PSBT to the other party. - // The two standard formats for this are in byte form or in base64 you can access these easily. - val bytes = psbtFirstSig.bytes - val base64 = psbtFirstSig.base64 +// In this scenario, let's say that the second input does not belong to us and we need +// another party to sign it. In this case we would need to send the PSBT to the other party. +// The two standard formats for this are in byte form or in base64 you can access these easily. +val bytes = psbtFirstSig.bytes +val base64 = psbtFirstSig.base64 - // After the other party has signed their input they can send us back the PSBT with the signatures - // To import we can use any of these functions - val fromBytes = PSBT.fromBytes( - hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8872202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") +// After the other party has signed their input they can send us back the PSBT with the signatures +// To import we can use any of these functions +val fromBytes = PSBT.fromBytes( + hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8872202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") - val fromBase64 = PSBT.fromBase64( - "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD") +val fromBase64 = PSBT.fromBase64( + "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD") - // After we've imported the PSBT we can combine it with our own signed PSBT so we can - // have one PSBT with all of the necessary data - val combinedPSBT = fromBase64.combinePSBT(psbtFirstSig) +// After we've imported the PSBT we can combine it with our own signed PSBT so we can +// have one PSBT with all of the necessary data +val combinedPSBT = fromBase64.combinePSBT(psbtFirstSig) - // Now that the PSBT has all the necessary data, we can finalize it and extract the transaction - // This will return a Try[PSBT] and will fail if you do not have all the required fields filled - val finalizedPSBT = combinedPSBT.finalizePSBT +// Now that the PSBT has all the necessary data, we can finalize it and extract the transaction +// This will return a Try[PSBT] and will fail if you do not have all the required fields filled +val finalizedPSBT = combinedPSBT.finalizePSBT - // After it has been finalized we can extract the fully signed transaction that is ready - // to be broadcast to the network. - // You can also use extractTransactionAndValidate that will validate if the transaction is valid - finalizedPSBT.get.extractTransaction - } - -Await.result(signedTransactionF, 30.seconds) +// After it has been finalized we can extract the fully signed transaction that is ready +// to be broadcast to the network. +// You can also use extractTransactionAndValidate that will validate if the transaction is valid +finalizedPSBT.get.extractTransaction ``` \ No newline at end of file diff --git a/docs/core/txbuilder.md b/docs/core/txbuilder.md index 30f847a974..5466b7132d 100644 --- a/docs/core/txbuilder.md +++ b/docs/core/txbuilder.md @@ -106,7 +106,7 @@ val finalizer = StandardNonInteractiveFinalizer( changeSPK) // We can now finalize the tx builder result from earlier with this finalizer -val unsignedTxF: Future[Transaction] = finalizer.buildTx(builderResult) +val unsignedTx: Transaction = finalizer.buildTx(builderResult) // We now turn to signing the unsigned transaction // this contains all the information we need to @@ -129,16 +129,12 @@ val utxoInfos: Vector[ScriptSignatureParams[InputInfo]] = Vector(utxoInfo) // 1: one input // 2: outputs (destination and change outputs) // 3: a fee rate of 1 satoshi/byte -val signedTx: Transaction = { - val signedTxF = unsignedTxF.flatMap { unsignedTx => - RawTxSigner.sign( - utx = unsignedTx, - utxoInfos = utxoInfos, - expectedFeeRate = feeRate - ) - } - Await.result(signedTxF, 30.seconds) -} +val signedTx: Transaction = + RawTxSigner.sign( + utx = unsignedTx, + utxoInfos = utxoInfos, + expectedFeeRate = feeRate + ) ``` ```scala mdoc:to-string diff --git a/testkit-core/src/main/scala/org/bitcoins/testkitcore/gen/PSBTGenerators.scala b/testkit-core/src/main/scala/org/bitcoins/testkitcore/gen/PSBTGenerators.scala index f29d3dcbae..5fed719d35 100644 --- a/testkit-core/src/main/scala/org/bitcoins/testkitcore/gen/PSBTGenerators.scala +++ b/testkit-core/src/main/scala/org/bitcoins/testkitcore/gen/PSBTGenerators.scala @@ -18,7 +18,6 @@ import org.scalacheck.Gen import scodec.bits.ByteVector import scala.annotation.tailrec -import scala.concurrent.{ExecutionContext, Future} object PSBTGenerators { @@ -107,54 +106,46 @@ object PSBTGenerators { .map(_.groupBy(_.key).map(_._2.head).toVector) } - def psbtWithUnknowns(implicit ec: ExecutionContext): Gen[Future[PSBT]] = { + def psbtWithUnknowns: Gen[PSBT] = { for { - psbtF <- Gen.frequency((6, fullNonFinalizedPSBT), (1, finalizedPSBT)) + psbt <- Gen.frequency((6, fullNonFinalizedPSBT), (1, finalizedPSBT)) globals <- unknownGlobals inputs <- unknownInputs outputs <- unknownOutputs } yield { - psbtF.map { psbt => - val newGlobal = GlobalPSBTMap(psbt.globalMap.elements ++ globals) - val newInputMaps = - psbt.inputMaps.map(map => InputPSBTMap(map.elements ++ inputs)) - val newOutputMaps = - psbt.outputMaps.map(map => OutputPSBTMap(map.elements ++ outputs)) + val newGlobal = GlobalPSBTMap(psbt.globalMap.elements ++ globals) + val newInputMaps = + psbt.inputMaps.map(map => InputPSBTMap(map.elements ++ inputs)) + val newOutputMaps = + psbt.outputMaps.map(map => OutputPSBTMap(map.elements ++ outputs)) - PSBT(newGlobal, newInputMaps, newOutputMaps) - } + PSBT(newGlobal, newInputMaps, newOutputMaps) } } - def psbtWithUnknownVersion(implicit - ec: ExecutionContext): Gen[Future[PSBT]] = { + def psbtWithUnknownVersion: Gen[PSBT] = { for { - psbtF <- psbtWithUnknowns + psbt <- psbtWithUnknowns versionNumber <- Gen.choose(min = PSBT.knownVersions.last.toLong, max = UInt32.max.toLong) } yield { - psbtF.map { psbt => - val newGlobal = GlobalPSBTMap( - psbt.globalMap.elements :+ Version(UInt32(versionNumber))) + val newGlobal = GlobalPSBTMap( + psbt.globalMap.elements :+ Version(UInt32(versionNumber))) - PSBT(newGlobal, psbt.inputMaps, psbt.outputMaps) - } + PSBT(newGlobal, psbt.inputMaps, psbt.outputMaps) } } - def psbtToBeSigned(implicit ec: ExecutionContext): Gen[ - Future[(PSBT, Seq[ScriptSignatureParams[InputInfo]], FeeUnit)]] = { - psbtWithBuilder(finalized = false).map { psbtAndBuilderF => - psbtAndBuilderF.flatMap { - case (psbt, FinalizedTxWithSigningInfo(_, infos), fee) => - val newInputsMaps = psbt.inputMaps.map { map => - InputPSBTMap(map.elements.filterNot(element => - PSBTInputKeyId.fromBytes(element.key) == PartialSignatureKeyId)) - } + def psbtToBeSigned: Gen[ + (PSBT, Seq[ScriptSignatureParams[InputInfo]], FeeUnit)] = { + psbtWithBuilder(finalized = false).map { + case (psbt, FinalizedTxWithSigningInfo(_, infos), fee) => + val newInputsMaps = psbt.inputMaps.map { map => + InputPSBTMap(map.elements.filterNot(element => + PSBTInputKeyId.fromBytes(element.key) == PartialSignatureKeyId)) + } - Future.successful( - (PSBT(psbt.globalMap, newInputsMaps, psbt.outputMaps), infos, fee)) - } + (PSBT(psbt.globalMap, newInputsMaps, psbt.outputMaps), infos, fee) } } @@ -179,8 +170,7 @@ object PSBTGenerators { creditingTxsInfo: Seq[ScriptSignatureParams[InputInfo]], destinations: Seq[TransactionOutput], changeSPK: ScriptPubKey, - fee: FeeUnit)(implicit - ec: ExecutionContext): Future[(PSBT, FinalizedTxWithSigningInfo, FeeUnit)] = { + fee: FeeUnit): (PSBT, FinalizedTxWithSigningInfo, FeeUnit) = { val lockTime = TxUtil.calcLockTime(creditingTxsInfo).get val inputs = InputUtil.calcSequenceForInputs(creditingTxsInfo) @@ -193,25 +183,25 @@ object PSBTGenerators { changeSPK) builder.setFinalizer(finalizer) - for { - unsignedTx <- builder.setFinalizer(finalizer).buildTx() + val unsignedTx = builder.setFinalizer(finalizer).buildTx() - orderedTxInfos = orderSpendingInfos(unsignedTx, creditingTxsInfo.toVector) + val orderedTxInfos = + orderSpendingInfos(unsignedTx, creditingTxsInfo.toVector) - psbt <- { - if (finalized) { - PSBT.finalizedFromUnsignedTxAndInputs(unsignedTx, orderedTxInfos) - } else { - PSBT.fromUnsignedTxAndInputs(unsignedTx, orderedTxInfos) - } + val psbt = + if (finalized) { + PSBT.finalizedFromUnsignedTxAndInputs(unsignedTx, orderedTxInfos) + } else { + PSBT.fromUnsignedTxAndInputs(unsignedTx, orderedTxInfos) } - } yield (psbt, - FinalizedTxWithSigningInfo(unsignedTx, creditingTxsInfo.toVector), - fee) + + (psbt, + FinalizedTxWithSigningInfo(unsignedTx, creditingTxsInfo.toVector), + fee) } - def psbtWithBuilder(finalized: Boolean)(implicit ec: ExecutionContext): Gen[ - Future[(PSBT, FinalizedTxWithSigningInfo, FeeUnit)]] = { + def psbtWithBuilder( + finalized: Boolean): Gen[(PSBT, FinalizedTxWithSigningInfo, FeeUnit)] = { for { (creditingTxsInfo, destinations) <- CreditingTxGen.inputsAndOutputs() (changeSPK, _) <- ScriptGenerators.scriptPubKey @@ -234,9 +224,8 @@ object PSBTGenerators { def psbtWithBuilderAndP2SHOutputs( finalized: Boolean, outputGen: CurrencyUnit => Gen[Seq[(TransactionOutput, ScriptPubKey)]] = - TransactionGenerators.smallP2SHOutputs)(implicit - ec: ExecutionContext): Gen[ - Future[(PSBT, FinalizedTxWithSigningInfo, Seq[ScriptPubKey])]] = { + TransactionGenerators.smallP2SHOutputs): Gen[ + (PSBT, FinalizedTxWithSigningInfo, Seq[ScriptPubKey])] = { for { (creditingTxsInfo, outputs) <- CreditingTxGen.inputsAndP2SHOutputs(destinationGenerator = outputGen) @@ -250,34 +239,33 @@ object PSBTGenerators { } fee <- FeeUnitGen.feeUnit(maxFee) } yield { - val pAndB = psbtAndBuilderFromInputs(finalized = finalized, - creditingTxsInfo = creditingTxsInfo, - destinations = outputs.map(_._1), - changeSPK = changeSPK._1, - fee = fee) + val p = psbtAndBuilderFromInputs(finalized = finalized, + creditingTxsInfo = creditingTxsInfo, + destinations = outputs.map(_._1), + changeSPK = changeSPK._1, + fee = fee) - pAndB.map(p => (p._1, p._2, outputs.map(_._2))) + (p._1, p._2, outputs.map(_._2)) } } - def psbtWithBuilderAndP2WSHOutputs(finalized: Boolean)(implicit - ec: ExecutionContext): Gen[ - Future[(PSBT, FinalizedTxWithSigningInfo, Seq[ScriptPubKey])]] = + def psbtWithBuilderAndP2WSHOutputs(finalized: Boolean): Gen[ + (PSBT, FinalizedTxWithSigningInfo, Seq[ScriptPubKey])] = psbtWithBuilderAndP2SHOutputs(finalized, TransactionGenerators.smallP2WSHOutputs) - def finalizedPSBTWithBuilder(implicit ec: ExecutionContext): Gen[ - Future[(PSBT, FinalizedTxWithSigningInfo, FeeUnit)]] = { + def finalizedPSBTWithBuilder: Gen[ + (PSBT, FinalizedTxWithSigningInfo, FeeUnit)] = { psbtWithBuilder(finalized = true) } - def finalizedPSBT(implicit ec: ExecutionContext): Gen[Future[PSBT]] = { - finalizedPSBTWithBuilder.map(_.map(_._1)) + def finalizedPSBT: Gen[PSBT] = { + finalizedPSBTWithBuilder.map(_._1) } /** Generates a PSBT that is ready to be finalized but where no input map has been finalized */ - def fullNonFinalizedPSBT(implicit ec: ExecutionContext): Gen[Future[PSBT]] = { - psbtWithBuilder(finalized = false).map(_.map(_._1)) + def fullNonFinalizedPSBT: Gen[PSBT] = { + psbtWithBuilder(finalized = false).map(_._1) } def pruneGlobal(globalMap: GlobalPSBTMap): GlobalPSBTMap = { @@ -300,21 +288,19 @@ object PSBTGenerators { /** Generates an arbitrary unfinalized PSBT by generating a full unfinalized PSBT * and randomly removing records */ - def arbitraryPSBT(implicit ec: ExecutionContext): Gen[Future[PSBT]] = { - psbtWithUnknowns.map { psbtF => - psbtF.map { psbt => - val global = psbt.globalMap - val inputs = psbt.inputMaps - val outputs = psbt.outputMaps + def arbitraryPSBT: Gen[PSBT] = { + psbtWithUnknowns.map { psbt => + val global = psbt.globalMap + val inputs = psbt.inputMaps + val outputs = psbt.outputMaps - val newGlobal = pruneGlobal(global) - val newInputs = - inputs.map(input => InputPSBTMap(pruneVec(input.elements))) - val newOutputs = - outputs.map(output => OutputPSBTMap(pruneVec(output.elements))) + val newGlobal = pruneGlobal(global) + val newInputs = + inputs.map(input => InputPSBTMap(pruneVec(input.elements))) + val newOutputs = + outputs.map(output => OutputPSBTMap(pruneVec(output.elements))) - PSBT(newGlobal, newInputs, newOutputs) - } + PSBT(newGlobal, newInputs, newOutputs) } } } diff --git a/testkit-core/src/main/scala/org/bitcoins/testkitcore/gen/ScriptGenerators.scala b/testkit-core/src/main/scala/org/bitcoins/testkitcore/gen/ScriptGenerators.scala index e1d5e83e62..c721f643b0 100644 --- a/testkit-core/src/main/scala/org/bitcoins/testkitcore/gen/ScriptGenerators.scala +++ b/testkit-core/src/main/scala/org/bitcoins/testkitcore/gen/ScriptGenerators.scala @@ -29,8 +29,6 @@ import org.scalacheck.Gen import scodec.bits.ByteVector import scala.annotation.tailrec -import scala.concurrent.Await -import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration.DurationInt //TODO: Need to provide generators for [[NonStandardScriptSignature]] and [[NonStandardScriptPubKey]] @@ -591,8 +589,9 @@ sealed abstract class ScriptGenerators { privateKey, hashType ) - txSigComponentFuture = P2PKSigner.sign(spendingInfo, spendingTx, false) - txSigComponent = Await.result(txSigComponentFuture, timeout) + txSigComponent = P2PKSigner.sign(spendingInfo, + spendingTx, + isDummySignature = false) //add the signature to the scriptSig instead of having an empty scriptSig signedScriptSig = txSigComponent.scriptSignature @@ -628,8 +627,9 @@ sealed abstract class ScriptGenerators { privateKey, hashType ) - txSigComponentFuture = P2PKHSigner.sign(spendingInfo, unsignedTx, false) - txSigComponent = Await.result(txSigComponentFuture, timeout) + txSigComponent = P2PKHSigner.sign(spendingInfo, + unsignedTx, + isDummySignature = false) signedScriptSig = txSigComponent.scriptSignature .asInstanceOf[P2PKHScriptSignature] @@ -658,10 +658,9 @@ sealed abstract class ScriptGenerators { privKey, hashType ) - val txSigComponentF = P2PKWithTimeoutSigner.sign(spendingInfo, - spendingTx, - isDummySignature = false) - val txSigComponent = Await.result(txSigComponentF, timeout) + val txSigComponent = P2PKWithTimeoutSigner.sign(spendingInfo, + spendingTx, + isDummySignature = false) val signedScriptSig = txSigComponent.scriptSignature.asInstanceOf[ConditionalScriptSignature] @@ -706,9 +705,8 @@ sealed abstract class ScriptGenerators { privateKeys.toVector, hashType ) - txSigComponentFuture = - MultiSigSigner.sign(spendingInfo, spendingTx, false) - txSigComponent = Await.result(txSigComponentFuture, timeout) + txSigComponent = + MultiSigSigner.sign(spendingInfo, spendingTx, isDummySignature = false) signedScriptSig = txSigComponent.scriptSignature .asInstanceOf[MultiSignatureScriptSignature] diff --git a/wallet-test/src/test/scala/org/bitcoins/wallet/AddressTagIntegrationTest.scala b/wallet-test/src/test/scala/org/bitcoins/wallet/AddressTagIntegrationTest.scala index 040d65f0f7..4f233f818f 100644 --- a/wallet-test/src/test/scala/org/bitcoins/wallet/AddressTagIntegrationTest.scala +++ b/wallet-test/src/test/scala/org/bitcoins/wallet/AddressTagIntegrationTest.scala @@ -83,8 +83,8 @@ class AddressTagIntegrationTest extends BitcoinSWalletTest { fromAccount = account, fromTagOpt = Some(exampleTag)) } - utx <- txBuilder.buildTx() - signedTx <- RawTxSigner.sign(utx, utxoInfos, feeRate) + utx = txBuilder.buildTx() + signedTx = RawTxSigner.sign(utx, utxoInfos, feeRate) _ <- wallet.processTransaction(signedTx, None) utxos <- wallet.listUtxos() diff --git a/wallet-test/src/test/scala/org/bitcoins/wallet/FundTransactionHandlingTest.scala b/wallet-test/src/test/scala/org/bitcoins/wallet/FundTransactionHandlingTest.scala index f013592a9d..2c75c80476 100644 --- a/wallet-test/src/test/scala/org/bitcoins/wallet/FundTransactionHandlingTest.scala +++ b/wallet-test/src/test/scala/org/bitcoins/wallet/FundTransactionHandlingTest.scala @@ -250,9 +250,10 @@ class FundTransactionHandlingTest fromTagOpt = Some(tag), markAsReserved = true ) - utx <- txBuilder.buildTx() - tx <- RawTxSigner.sign(utx, utxoInfos, feeRate) } yield { + val utx = txBuilder.buildTx() + val tx = RawTxSigner.sign(utx, utxoInfos, feeRate) + assert(tx.inputs.forall(input => expectedUtxos.exists(_.outPoint == input.previousOutput))) } diff --git a/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala b/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala index 1c5f887903..29ad2ae18b 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala @@ -385,9 +385,10 @@ abstract class Wallet sentAmount: CurrencyUnit, feeRate: FeeUnit, newTags: Vector[AddressTag]): Future[Transaction] = { + val utx = txBuilder.buildTx() + val signed = RawTxSigner.sign(utx, utxoInfos, feeRate) + for { - utx <- txBuilder.buildTx() - signed <- RawTxSigner.sign(utx, utxoInfos, feeRate) ourOuts <- findOurOuts(signed) creditingAmount = utxoInfos.foldLeft(CurrencyUnits.zero)(_ + _.amount) _ <- processOurTransaction(transaction = signed, @@ -449,7 +450,7 @@ abstract class Wallet withFinalizer = txBuilder.setFinalizer(finalizer) - tmp <- withFinalizer.buildTx() + tmp = withFinalizer.buildTx() _ = require( tmp.outputs.size == 1, @@ -786,14 +787,14 @@ abstract class Wallet ourXpubs = accountDbs.map(_.xpub) utxos <- spendingInfoDAO.findAll() txs <- transactionDAO.findByTxIds(inputTxIds.keys.toVector) - - updated = txs.foldLeft(psbt) { (accum, tx) => + } yield { + val updated = txs.foldLeft(psbt) { (accum, tx) => val index = inputTxIds(tx.txIdBE) accum.addUTXOToInput(tx.transaction, index) } - signed <- - FutureUtil.foldLeftAsync(updated, updated.inputMaps.zipWithIndex) { + val signed = + updated.inputMaps.zipWithIndex.foldLeft(updated) { case (unsigned, (input, index)) => val xpubKeyPaths = input.BIP32DerivationPaths .filter { path => @@ -822,7 +823,7 @@ abstract class Wallet val keyPaths = xpubKeyPaths ++ utxoPath - FutureUtil.foldLeftAsync(withData, keyPaths) { (accum, hdPath) => + keyPaths.foldLeft(withData) { (accum, hdPath) => val sign = keyManager.toSign(hdPath) // Only sign if that key doesn't have a signature yet if (!input.partialSignatures.exists(_.pubKey == sign.publicKey)) { @@ -830,11 +831,11 @@ abstract class Wallet s"Signing input $index with key ${sign.publicKey.hex}") accum.sign(index, sign) } else { - Future.successful(accum) + accum } } } - } yield { + if (updated == signed) { logger.warn("Did not find any keys or utxos that belong to this wallet") } 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 d7b3622655..3467615f61 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/internal/FundTransactionHandling.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/internal/FundTransactionHandling.scala @@ -40,7 +40,7 @@ trait FundTransactionHandling extends WalletLogger { self: Wallet => fromAccount = fromAccount, fromTagOpt = fromTagOpt, markAsReserved = markAsReserved) - .flatMap(_._1.buildTx()) + .map(_._1.buildTx()) } /** This returns a [[RawTxBuilder]] that can be used to generate an unsigned transaction with [[RawTxBuilder.result()]]