Made ECPrivateKey signing synchronous and got src compiling (#2652)

Fixed tests

De-futured tx buidling, finalizing and signing

Responded to review
This commit is contained in:
Nadav Kohen 2021-03-23 17:05:58 -05:00 committed by GitHub
parent 7aa3ccd974
commit e6899b20b1
35 changed files with 1107 additions and 1226 deletions

View file

@ -578,7 +578,8 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
output = prevTx.outputs(outPoint.vout.toInt) output = prevTx.outputs(outPoint.vout.toInt)
privKey <- client.dumpPrivKey( privKey <- client.dumpPrivKey(
BitcoinAddress.fromScriptPubKey(output.scriptPubKey, RegTest)) BitcoinAddress.fromScriptPubKey(output.scriptPubKey, RegTest))
partialSig <- BitcoinSigner.signSingle( } yield {
val partialSig = BitcoinSigner.signSingle(
ECSignatureParams( ECSignatureParams(
P2WPKHV0InputInfo(outPoint, output.value, privKey.publicKey), P2WPKHV0InputInfo(outPoint, output.value, privKey.publicKey),
prevTx, prevTx,
@ -586,7 +587,7 @@ class WalletRpcTest extends BitcoindFixturesCachedPairNewest {
HashType.sigHashAll), HashType.sigHashAll),
transaction, transaction,
isDummySignature = false) isDummySignature = false)
} yield {
signedTx match { signedTx match {
case btx: NonWitnessTransaction => case btx: NonWitnessTransaction =>
assert( assert(

View file

@ -1,9 +1,9 @@
package org.bitcoins.core.crypto package org.bitcoins.core.crypto
import org.bitcoins.testkitcore.gen.{CryptoGenerators, HDGenerators} 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 { it must "be able to sign something that extends ExtSignKey" in {
forAll(CryptoGenerators.extPrivateKey, CryptoGenerators.sha256Digest) { 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 { it must "be able to sign a specific path of a ext key" in {
forAllAsync(CryptoGenerators.extPrivateKey, forAll(CryptoGenerators.extPrivateKey,
CryptoGenerators.sha256Digest, CryptoGenerators.sha256Digest,
HDGenerators.bip32Path) { case (extPrivKey, hash, path) => HDGenerators.bip32Path) { case (extPrivKey, hash, path) =>
val sigF = extPrivKey.deriveAndSignFuture(hash.bytes, path) val sig = extPrivKey.deriveAndSign(hash.bytes, path)
val childPubKey = extPrivKey.deriveChildPubKey(path).get val childPubKey = extPrivKey.deriveChildPubKey(path).get
sigF.map { sig =>
assert(childPubKey.key.verify(hash, sig)) assert(childPubKey.key.verify(hash, sig))
} }
}
} }
} }

View file

@ -121,46 +121,40 @@ class TransactionSignatureCreatorTest extends BitcoinSJvmTest {
it should "have old and new createSig functions agree" in { it should "have old and new createSig functions agree" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(), forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) {
ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfo, destinations), (changeSPK, _)) => case ((creditingTxsInfo, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(100)) val fee = SatoshisPerVirtualByte(Satoshis(100))
val unsignedTxF = StandardNonInteractiveFinalizer val spendingTx = StandardNonInteractiveFinalizer
.txFrom(outputs = destinations, .txFrom(outputs = destinations,
utxos = creditingTxsInfo, utxos = creditingTxsInfo,
feeRate = fee, feeRate = fee,
changeSPK = changeSPK) changeSPK = changeSPK)
val correctSigsF = unsignedTxF.flatMap { spendingTx => val correctSigs =
val assertFs = creditingTxsInfo.flatMap { signInfo => creditingTxsInfo.flatMap { signInfo =>
signInfo.signers.map { signer => signInfo.signers.map { signer =>
val txSignatureComponent = val txSignatureComponent =
TxSigComponent(signInfo.inputInfo, spendingTx) TxSigComponent(signInfo.inputInfo, spendingTx)
@nowarn val oldSigF = val oldSig =
TransactionSignatureCreator.createSig(txSignatureComponent, TransactionSignatureCreator.createSig(txSignatureComponent,
signer.signFunction, signer.sign(_),
signInfo.hashType) signInfo.hashType)
for {
oldSig <- oldSigF val newSig =
newSig <-
TransactionSignatureCreator.createSig(spendingTx, TransactionSignatureCreator.createSig(spendingTx,
signInfo, signInfo,
signer.signFunction, signer.sign(_),
signInfo.hashType) signInfo.hashType)
} yield {
(oldSig.r == newSig.r) && (oldSig.r == newSig.r) &&
(oldSig.s == newSig.s) && (oldSig.s == newSig.s) &&
(oldSig.hex == newSig.hex) (oldSig.hex == newSig.hex)
} }
} }
}
Future.sequence(assertFs) assert(correctSigs.forall(_ == true))
}
correctSigsF.map(x => assert(x.forall(_ == true)))
} }
} }

View file

@ -12,13 +12,13 @@ import org.bitcoins.core.wallet.builder.StandardNonInteractiveFinalizer
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.crypto.DoubleSha256Digest import org.bitcoins.crypto.DoubleSha256Digest
import org.bitcoins.testkitcore.gen.{CreditingTxGen, ScriptGenerators} import org.bitcoins.testkitcore.gen.{CreditingTxGen, ScriptGenerators}
import org.bitcoins.testkitcore.util.BitcoinSJvmTest import org.bitcoins.testkitcore.util.BitcoinSUnitTest
import scala.util.Try import scala.util.Try
/** Created by chris on 2/19/16. /** 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 { "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 //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 { it should "have old and new serializeForSignature functions agree" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(), forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) {
ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfo, destinations), (changeSPK, _)) => case ((creditingTxsInfo, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(100)) val fee = SatoshisPerVirtualByte(Satoshis(100))
val unsignedTxF = StandardNonInteractiveFinalizer val spendingTx = StandardNonInteractiveFinalizer
.txFrom(outputs = destinations, .txFrom(outputs = destinations,
utxos = creditingTxsInfo, utxos = creditingTxsInfo,
feeRate = fee, feeRate = fee,
changeSPK = changeSPK) changeSPK = changeSPK)
val correctScriptsF = unsignedTxF.map { spendingTx => val correctScripts =
creditingTxsInfo.flatMap { signInfo => creditingTxsInfo.flatMap { signInfo =>
signInfo.signers.map { _ => signInfo.signers.map { _ =>
val txSigComponent = val txSigComponent =
@ -437,25 +436,23 @@ class TransactionSignatureSerializerTest extends BitcoinSJvmTest {
oldBytes == newBytes oldBytes == newBytes
} }
} }
}
correctScriptsF.map(x => assert(x.forall(_ == true))) assert(correctScripts.forall(_ == true))
} }
} }
it should "have old and new hashForSignature functions agree" in { it should "have old and new hashForSignature functions agree" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(), forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) {
ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfo, destinations), (changeSPK, _)) => case ((creditingTxsInfo, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(100)) val fee = SatoshisPerVirtualByte(Satoshis(100))
val unsignedTxF = StandardNonInteractiveFinalizer val spendingTx = StandardNonInteractiveFinalizer
.txFrom(outputs = destinations, .txFrom(outputs = destinations,
utxos = creditingTxsInfo, utxos = creditingTxsInfo,
feeRate = fee, feeRate = fee,
changeSPK = changeSPK) changeSPK = changeSPK)
val correctHashesF = unsignedTxF.map { spendingTx => val correctHashes =
creditingTxsInfo.flatMap { signInfo => creditingTxsInfo.flatMap { signInfo =>
signInfo.signers.map { _ => signInfo.signers.map { _ =>
val txSigComponent = val txSigComponent =
@ -480,25 +477,23 @@ class TransactionSignatureSerializerTest extends BitcoinSJvmTest {
oldHash == newHash oldHash == newHash
} }
} }
}
correctHashesF.map(x => assert(x.forall(_ == true))) assert(correctHashes.forall(_ == true))
} }
} }
it should "have old and new calculateScriptForSigning functions agree" in { it should "have old and new calculateScriptForSigning functions agree" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(), forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) {
ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfo, destinations), (changeSPK, _)) => case ((creditingTxsInfo, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(100)) val fee = SatoshisPerVirtualByte(Satoshis(100))
val unsignedTxF = StandardNonInteractiveFinalizer val spendingTx = StandardNonInteractiveFinalizer
.txFrom(outputs = destinations, .txFrom(outputs = destinations,
utxos = creditingTxsInfo, utxos = creditingTxsInfo,
feeRate = fee, feeRate = fee,
changeSPK = changeSPK) changeSPK = changeSPK)
val correctScriptsF = unsignedTxF.map { spendingTx => val correctScripts =
creditingTxsInfo.flatMap { signInfo => creditingTxsInfo.flatMap { signInfo =>
signInfo.signers.map { _ => signInfo.signers.map { _ =>
val txSigComponent = val txSigComponent =
@ -518,9 +513,8 @@ class TransactionSignatureSerializerTest extends BitcoinSJvmTest {
oldScript == newScript oldScript == newScript
} }
} }
}
correctScriptsF.map(x => assert(x.forall(_ == true))) assert(correctScripts.forall(_ == true))
} }
} }
} }

View file

@ -13,13 +13,13 @@ import org.bitcoins.core.psbt.InputPSBTRecord.ProofOfReservesCommitment
import org.bitcoins.core.script.crypto.HashType import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.crypto.ECPublicKey import org.bitcoins.crypto.ECPublicKey
import org.bitcoins.testkitcore.gen.PSBTGenerators import org.bitcoins.testkitcore.gen.PSBTGenerators
import org.bitcoins.testkitcore.util.BitcoinSJvmTest import org.bitcoins.testkitcore.util.BitcoinSUnitTest
import scodec.bits._ import scodec.bits._
/** Test vectors are taken directly from the BIP 174 reference sheet /** Test vectors are taken directly from the BIP 174 reference sheet
* https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#test-vectors * 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( val validPsbts: Vector[ByteVector] = Vector(
// PSBT with one P2PKH input. Outputs are empty // PSBT with one P2PKH input. Outputs are empty
@ -243,20 +243,16 @@ class PSBTSerializerTest extends BitcoinSJvmTest {
} }
it must "fail to serialize PSBTs with unknown version numbers" in { it must "fail to serialize PSBTs with unknown version numbers" in {
forAllAsync(PSBTGenerators.psbtWithUnknownVersion) { psbtF => forAll(PSBTGenerators.psbtWithUnknownVersion) { psbt =>
psbtF.map { psbt =>
assertThrows[IllegalArgumentException](PSBT(psbt.bytes)) assertThrows[IllegalArgumentException](PSBT(psbt.bytes))
} }
} }
}
it must "have serialization symmetry" in { it must "have serialization symmetry" in {
forAllAsync(PSBTGenerators.arbitraryPSBT) { psbtF => forAll(PSBTGenerators.arbitraryPSBT) { psbt =>
psbtF.map { psbt =>
assert(PSBT.fromBytes(psbt.bytes) == psbt) assert(PSBT.fromBytes(psbt.bytes) == psbt)
} }
} }
}
it must "fail to get a record from a empty ByteVector" in { it must "fail to get a record from a empty ByteVector" in {
assertThrows[IllegalArgumentException]( assertThrows[IllegalArgumentException](

View file

@ -10,13 +10,12 @@ import org.bitcoins.core.psbt.OutputPSBTRecord.{RedeemScript, WitnessScript}
import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams} import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams}
import org.bitcoins.crypto.CryptoUtil import org.bitcoins.crypto.CryptoUtil
import org.bitcoins.testkitcore.gen._ import org.bitcoins.testkitcore.gen._
import org.bitcoins.testkitcore.util.BitcoinSJvmTest import org.bitcoins.testkitcore.util.BitcoinSUnitTest
import scodec.bits._ import scodec.bits._
import scala.concurrent.Future
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
class PSBTTest extends BitcoinSJvmTest { class PSBTTest extends BitcoinSUnitTest {
behavior of "PSBT" behavior of "PSBT"
@ -24,8 +23,7 @@ class PSBTTest extends BitcoinSJvmTest {
generatorDrivenConfigNewCode generatorDrivenConfigNewCode
it must "correctly combine PSBTs" in { it must "correctly combine PSBTs" in {
forAllAsync(PSBTGenerators.arbitraryPSBT) { psbtF => forAll(PSBTGenerators.arbitraryPSBT) { psbt =>
psbtF.map { psbt =>
val global = psbt.globalMap val global = psbt.globalMap
val inputs = psbt.inputMaps val inputs = psbt.inputMaps
val outputs = psbt.outputMaps val outputs = psbt.outputMaps
@ -58,19 +56,16 @@ class PSBTTest extends BitcoinSJvmTest {
assert(psbt2.combinePSBT(psbt1) == psbt) assert(psbt2.combinePSBT(psbt1) == psbt)
} }
} }
}
it must "correctly update PSBTs' inputs" in { it must "correctly update PSBTs' inputs" in {
forAllAsync(PSBTGenerators.psbtToBeSigned)(_.flatMap { forAll(PSBTGenerators.psbtToBeSigned) { case (fullPsbt, utxos, _) =>
case (fullPsbt, utxos, _) =>
val emptyPsbt = PSBT.fromUnsignedTx(fullPsbt.transaction) val emptyPsbt = PSBT.fromUnsignedTx(fullPsbt.transaction)
val infoAndTxs = PSBTGenerators.orderSpendingInfos(fullPsbt.transaction, val infoAndTxs =
utxos.toVector) PSBTGenerators.orderSpendingInfos(fullPsbt.transaction, utxos.toVector)
val updatedPSBT = val updatedPSBT =
infoAndTxs.zipWithIndex infoAndTxs.zipWithIndex
.foldLeft(emptyPsbt) { .foldLeft(emptyPsbt) { case (psbt, (utxo, index)) =>
case (psbt, (utxo, index)) =>
val partUpdatedPsbt = psbt val partUpdatedPsbt = psbt
.addUTXOToInput(utxo.prevTransaction, index) .addUTXOToInput(utxo.prevTransaction, index)
.addSigHashTypeToInput(utxo.hashType, index) .addSigHashTypeToInput(utxo.hashType, index)
@ -92,30 +87,25 @@ class PSBTTest extends BitcoinSJvmTest {
} }
} }
assert(updatedPSBT == fullPsbt) assert(updatedPSBT == fullPsbt)
}) }
} }
it must "correctly construct and sign a PSBT" in { it must "correctly construct and sign a PSBT" in {
forAllAsync(PSBTGenerators.psbtToBeSigned) { psbtWithBuilderF => forAll(PSBTGenerators.psbtToBeSigned) { case (psbtNoSigs, utxos, _) =>
psbtWithBuilderF.flatMap { case (psbtNoSigs, utxos, _) =>
val infos = utxos.toVector.zipWithIndex.map { val infos = utxos.toVector.zipWithIndex.map {
case (utxo: ScriptSignatureParams[InputInfo], index) => case (utxo: ScriptSignatureParams[InputInfo], index) =>
(index, utxo) (index, utxo)
} }
val signedPSBTF = infos.foldLeft(Future.successful(psbtNoSigs)) { val signedPSBT = infos.foldLeft(psbtNoSigs) {
case (unsignedPSBTF, (index, info)) => case (unsignedPSBT, (index, info)) =>
unsignedPSBTF.flatMap { unsignedPSBT => info.toSingles.foldLeft(unsignedPSBT) { (psbtToSign, singleInfo) =>
info.toSingles.foldLeft(Future.successful(unsignedPSBT)) { psbtToSign.sign(index,
(psbtToSignF, singleInfo) =>
psbtToSignF.flatMap(
_.sign(index,
singleInfo.signer, singleInfo.signer,
singleInfo.conditionalPath)) singleInfo.conditionalPath)
} }
} }
}
signedPSBTF.map { signedPSBT =>
val finalizedPsbtT = signedPSBT.finalizePSBT val finalizedPsbtT = signedPSBT.finalizePSBT
finalizedPsbtT match { finalizedPsbtT match {
case Success(finalizedPsbt) => case Success(finalizedPsbt) =>
@ -125,14 +115,10 @@ class PSBTTest extends BitcoinSJvmTest {
} }
} }
} }
}
}
it must "add Redeem Scripts to outputs" in { it must "add Redeem Scripts to outputs" in {
forAllAsync( forAll(PSBTGenerators.psbtWithBuilderAndP2SHOutputs(finalized = false)) {
PSBTGenerators.psbtWithBuilderAndP2SHOutputs(finalized = false)) { case (psbtEmptyOutputs, _, redeemScripts) =>
psbtWithBuilderF =>
psbtWithBuilderF.flatMap { case (psbtEmptyOutputs, _, redeemScripts) =>
val psbtWithOutputs = val psbtWithOutputs =
redeemScripts.zipWithIndex.foldLeft(psbtEmptyOutputs)((psbt, spk) => redeemScripts.zipWithIndex.foldLeft(psbtEmptyOutputs)((psbt, spk) =>
psbt.addRedeemOrWitnessScriptToOutput(spk._1, spk._2)) psbt.addRedeemOrWitnessScriptToOutput(spk._1, spk._2))
@ -145,13 +131,10 @@ class PSBTTest extends BitcoinSJvmTest {
assert(allOutputsValid) assert(allOutputsValid)
} }
} }
}
it must "add Witness Scripts to outputs" in { it must "add Witness Scripts to outputs" in {
forAllAsync( forAll(PSBTGenerators.psbtWithBuilderAndP2WSHOutputs(finalized = false)) {
PSBTGenerators.psbtWithBuilderAndP2WSHOutputs(finalized = false)) { case (psbtEmptyOutputs, _, redeemScripts) =>
psbtWithBuilderF =>
psbtWithBuilderF.flatMap { case (psbtEmptyOutputs, _, redeemScripts) =>
val psbtWithOutputs = val psbtWithOutputs =
redeemScripts.zipWithIndex.foldLeft(psbtEmptyOutputs)((psbt, spk) => redeemScripts.zipWithIndex.foldLeft(psbtEmptyOutputs)((psbt, spk) =>
psbt.addRedeemOrWitnessScriptToOutput(spk._1, spk._2)) psbt.addRedeemOrWitnessScriptToOutput(spk._1, spk._2))
@ -165,10 +148,9 @@ class PSBTTest extends BitcoinSJvmTest {
assert(allOutputsValid) assert(allOutputsValid)
} }
} }
}
it must "correctly construct and finalize PSBTs from UTXOSpendingInfo" in { it must "correctly construct and finalize PSBTs from UTXOSpendingInfo" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(), forAll(CreditingTxGen.inputsAndOutputs(),
ScriptGenerators.scriptPubKey, ScriptGenerators.scriptPubKey,
ChainParamsGenerator.bitcoinNetworkParams) { ChainParamsGenerator.bitcoinNetworkParams) {
case ((creditingTxsInfo, destinations), (changeSPK, _), _) => case ((creditingTxsInfo, destinations), (changeSPK, _), _) =>
@ -177,45 +159,42 @@ class PSBTTest extends BitcoinSJvmTest {
val spending = destinations.foldLeft(0L)(_ + _.value.satoshis.toLong) val spending = destinations.foldLeft(0L)(_ + _.value.satoshis.toLong)
val maxFee = crediting - spending val maxFee = crediting - spending
val fee = GenUtil.sample(FeeUnitGen.feeUnit(maxFee)) val fee = GenUtil.sample(FeeUnitGen.feeUnit(maxFee))
for {
(psbt, _, _) <- val (psbt, _, _) =
PSBTGenerators.psbtAndBuilderFromInputs(finalized = false, PSBTGenerators.psbtAndBuilderFromInputs(finalized = false,
creditingTxsInfo = creditingTxsInfo =
creditingTxsInfo, creditingTxsInfo,
destinations = destinations, destinations = destinations,
changeSPK = changeSPK, changeSPK = changeSPK,
fee = fee) fee = fee)
(expected, _, _) <- val (expected, _, _) =
PSBTGenerators.psbtAndBuilderFromInputs(finalized = true, PSBTGenerators.psbtAndBuilderFromInputs(finalized = true,
creditingTxsInfo = creditingTxsInfo =
creditingTxsInfo, creditingTxsInfo,
destinations = destinations, destinations = destinations,
changeSPK = changeSPK, changeSPK = changeSPK,
fee = fee) fee = fee)
} yield {
val finalizedPsbtOpt = psbt.finalizePSBT val finalizedPsbtOpt = psbt.finalizePSBT
assert(finalizedPsbtOpt.isSuccess, psbt.hex) assert(finalizedPsbtOpt.isSuccess, psbt.hex)
assert(finalizedPsbtOpt.get == expected) assert(finalizedPsbtOpt.get == expected)
} }
} }
}
it must "agree with TxBuilder.sign given UTXOSpendingInfos" in { it must "agree with TxBuilder.sign given UTXOSpendingInfos" in {
forAllAsync(PSBTGenerators.finalizedPSBTWithBuilder) { psbtAndBuilderF => forAll(PSBTGenerators.finalizedPSBTWithBuilder) {
for { case (psbt, builder, fee) =>
(psbt, builder, fee) <- psbtAndBuilderF val signedTx = builder.sign(fee)
signedTx <- builder.sign(fee)
} yield {
val txT = psbt.extractTransactionAndValidate val txT = psbt.extractTransactionAndValidate
assert(txT.isSuccess, txT.failed) assert(txT.isSuccess, txT.failed)
assert(txT.get == signedTx) assert(txT.get == signedTx)
} }
} }
}
it must "correctly serialize RIPEMD160 hash preimages" in { it must "correctly serialize RIPEMD160 hash preimages" in {
forAllAsync(StringGenerators.hexString) { hex => forAll(StringGenerators.hexString) { hex =>
val preimage = ByteVector.fromValidHex(hex) val preimage = ByteVector.fromValidHex(hex)
val ripeMd160 = RIPEMD160PreImage(preimage) val ripeMd160 = RIPEMD160PreImage(preimage)
@ -227,7 +206,7 @@ class PSBTTest extends BitcoinSJvmTest {
} }
it must "correctly serialize SHA256 hash preimages" in { it must "correctly serialize SHA256 hash preimages" in {
forAllAsync(StringGenerators.hexString) { hex => forAll(StringGenerators.hexString) { hex =>
val preimage = ByteVector.fromValidHex(hex) val preimage = ByteVector.fromValidHex(hex)
val sha256 = SHA256PreImage(preimage) val sha256 = SHA256PreImage(preimage)
@ -239,7 +218,7 @@ class PSBTTest extends BitcoinSJvmTest {
} }
it must "correctly serialize HASH160 hash preimages" in { it must "correctly serialize HASH160 hash preimages" in {
forAllAsync(StringGenerators.hexString) { hex => forAll(StringGenerators.hexString) { hex =>
val preimage = ByteVector.fromValidHex(hex) val preimage = ByteVector.fromValidHex(hex)
val hash160 = HASH160PreImage(preimage) val hash160 = HASH160PreImage(preimage)
@ -251,7 +230,7 @@ class PSBTTest extends BitcoinSJvmTest {
} }
it must "correctly serialize HASH256 hash preimages" in { it must "correctly serialize HASH256 hash preimages" in {
forAllAsync(StringGenerators.hexString) { hex => forAll(StringGenerators.hexString) { hex =>
val preimage = ByteVector.fromValidHex(hex) val preimage = ByteVector.fromValidHex(hex)
val hash256 = HASH256PreImage(preimage) val hash256 = HASH256PreImage(preimage)

View file

@ -16,13 +16,13 @@ import org.bitcoins.core.script.constant._
import org.bitcoins.core.script.crypto.HashType import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.wallet.utxo.{ConditionalPath, InputInfo} import org.bitcoins.core.wallet.utxo.{ConditionalPath, InputInfo}
import org.bitcoins.crypto._ import org.bitcoins.crypto._
import org.bitcoins.testkitcore.util.BitcoinSJvmTest import org.bitcoins.testkitcore.util.BitcoinSUnitTest
import org.bitcoins.testkitcore.util.TransactionTestUtil.{dummyPSBT, dummyTx} import org.bitcoins.testkitcore.util.TransactionTestUtil._
import scodec.bits._ import scodec.bits._
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
class PSBTUnitTest extends BitcoinSJvmTest { class PSBTUnitTest extends BitcoinSUnitTest {
behavior of "PSBT" behavior of "PSBT"
@ -368,17 +368,15 @@ class PSBTUnitTest extends BitcoinSJvmTest {
assert(missingSigs.forall(expectedPubKeyHashes.contains)) assert(missingSigs.forall(expectedPubKeyHashes.contains))
} }
for { val firstSig0 = unsignedPsbt.sign(inputIndex = 0, signer = privKey0)
firstSig0 <- unsignedPsbt.sign(inputIndex = 0, signer = privKey0) val signedPsbt0 = firstSig0.sign(inputIndex = 1, signer = privKey1)
signedPsbt0 <- firstSig0.sign(inputIndex = 1, signer = privKey1)
val firstSig1 = unsignedPsbt.sign(inputIndex = 0, signer = privKey2)
val signedPsbt1 = firstSig1.sign(inputIndex = 1, signer = privKey3)
firstSig1 <- unsignedPsbt.sign(inputIndex = 0, signer = privKey2)
signedPsbt1 <- firstSig1.sign(inputIndex = 1, signer = privKey3)
} yield {
assert(signedPsbt0 == expectedPsbt0) assert(signedPsbt0 == expectedPsbt0)
assert(signedPsbt1 == expectedPsbt1) assert(signedPsbt1 == expectedPsbt1)
} }
}
it must "successfully change a NonWitnessUTXO to a WitnessUTXO when compressing" in { it must "successfully change a NonWitnessUTXO to a WitnessUTXO when compressing" in {
// Create non BIP-143 vulnerable witness utxo // Create non BIP-143 vulnerable witness utxo

View file

@ -4,16 +4,14 @@ import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.number.UInt32 import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.script.{EmptyScriptSignature, ScriptPubKey} import org.bitcoins.core.protocol.script.{EmptyScriptSignature, ScriptPubKey}
import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.util.FutureUtil
import org.bitcoins.crypto.DoubleSha256DigestBE import org.bitcoins.crypto.DoubleSha256DigestBE
import org.bitcoins.testkitcore.gen.CreditingTxGen import org.bitcoins.testkitcore.gen.CreditingTxGen
import org.bitcoins.testkitcore.util.BitcoinSJvmTest import org.bitcoins.testkitcore.util.BitcoinSUnitTest
import org.scalatest.Assertion import org.scalatest.Assertion
import scala.concurrent.Future
import scala.util.Random import scala.util.Random
class RawTxFinalizerTest extends BitcoinSJvmTest { class RawTxFinalizerTest extends BitcoinSUnitTest {
implicit override val generatorDrivenConfig: PropertyCheckConfiguration = implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode generatorDrivenConfigNewCode
@ -137,60 +135,59 @@ class RawTxFinalizerTest extends BitcoinSJvmTest {
} }
it must "shuffle inputs" in { it must "shuffle inputs" in {
forAllAsync(CreditingTxGen.inputsAndOutputs()) { case (inputs, outputs) => forAll(CreditingTxGen.inputsAndOutputs()) { case (inputs, outputs) =>
val txBuilder = RawTxBuilder() ++= inputs ++= outputs val txBuilder = RawTxBuilder() ++= inputs ++= outputs
val finalized = txBuilder.setFinalizer(ShuffleInputsFinalizer) val finalized = txBuilder.setFinalizer(ShuffleInputsFinalizer)
val txsF = val txs =
FutureUtil.foldLeftAsync(Vector.empty[Transaction], 0 to 20) { 0.to(20).foldLeft(Vector.empty[Transaction]) { (accum, _) =>
(accum, _) => finalized.buildTx().map(_ +: accum) finalized.buildTx() +: accum
} }
txsF.map(txs =>
assert( assert(
inputs.size <= 1 || txs.exists( inputs.size <= 1 || txs.exists(
_.inputs.map(_.previousOutput) != inputs.map(_.outPoint)))) _.inputs.map(_.previousOutput) != inputs.map(_.outPoint)))
} }
} }
it must "shuffle outputs" in { it must "shuffle outputs" in {
forAllAsync(CreditingTxGen.inputsAndOutputs()) { case (inputs, outputs) => forAll(CreditingTxGen.inputsAndOutputs()) { case (inputs, outputs) =>
val txBuilder = RawTxBuilder() ++= inputs ++= outputs val txBuilder = RawTxBuilder() ++= inputs ++= outputs
val finalized = txBuilder.setFinalizer(ShuffleOutputsFinalizer) val finalized = txBuilder.setFinalizer(ShuffleOutputsFinalizer)
val txsF = val txs =
FutureUtil.foldLeftAsync(Vector.empty[Transaction], 0 to 20) { 0.to(20).foldLeft(Vector.empty[Transaction]) { (accum, _) =>
(accum, _) => finalized.buildTx().map(_ +: 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 { 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 txBuilder = RawTxBuilder() ++= inputs ++= outputs
val finalized = txBuilder.setFinalizer(ShuffleFinalizer) val finalized = txBuilder.setFinalizer(ShuffleFinalizer)
val txsF = val txs =
FutureUtil.foldLeftAsync(Vector.empty[Transaction], 0 to 20) { 0.to(20).foldLeft(Vector.empty[Transaction]) { (accum, _) =>
(accum, _) => finalized.buildTx().map(_ +: accum) finalized.buildTx() +: accum
} }
txsF.map { txs =>
assert( assert(
inputs.size <= 1 || txs.exists( inputs.size <= 1 || txs.exists(
_.inputs.map(_.previousOutput) != inputs.map(_.outPoint))) _.inputs.map(_.previousOutput) != inputs.map(_.outPoint)))
assert(outputs.size <= 1 || txs.exists(_.outputs != outputs)) assert(outputs.size <= 1 || txs.exists(_.outputs != outputs))
} }
} }
}
def testBIP69Finalizer( def testBIP69Finalizer(
sortedInputs: Vector[TransactionInput], sortedInputs: Vector[TransactionInput],
sortedOutputs: Vector[TransactionOutput]): Future[Assertion] = { sortedOutputs: Vector[TransactionOutput]): Assertion = {
val inputs = Random.shuffle(sortedInputs) val inputs = Random.shuffle(sortedInputs)
val outputs = Random.shuffle(sortedOutputs) val outputs = Random.shuffle(sortedOutputs)
val txBuilder = RawTxBuilder() ++= inputs ++= outputs val txBuilder = RawTxBuilder() ++= inputs ++= outputs
txBuilder.setFinalizer(BIP69Finalizer).buildTx().map { tx => val tx = txBuilder.setFinalizer(BIP69Finalizer).buildTx()
assert(tx.inputs == sortedInputs) assert(tx.inputs == sortedInputs)
assert(tx.outputs == sortedOutputs) assert(tx.outputs == sortedOutputs)
} }
}
} }

View file

@ -22,9 +22,9 @@ import org.bitcoins.crypto.{
} }
import org.bitcoins.testkitcore.Implicits._ import org.bitcoins.testkitcore.Implicits._
import org.bitcoins.testkitcore.gen.{CreditingTxGen, ScriptGenerators} 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 = implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode generatorDrivenConfigNewCode
@ -58,16 +58,15 @@ class RawTxSignerTest extends BitcoinSJvmTest {
) )
val utxos = Vector(utxo) val utxos = Vector(utxo)
val feeUnit = SatoshisPerVirtualByte(currencyUnit = Satoshis(1)) val feeUnit = SatoshisPerVirtualByte(currencyUnit = Satoshis(1))
val utxF = StandardNonInteractiveFinalizer.txFrom(outputs = destinations, val utx = RawFinalizerFactory.txFrom(outputs = destinations,
utxos = utxos, utxos = utxos,
feeRate = feeUnit, feeRate = feeUnit,
changeSPK = changeSPK = EmptyScriptPubKey)
EmptyScriptPubKey)
//trivially false //trivially false
val f = (_: Seq[ScriptSignatureParams[InputInfo]], _: Transaction) => false val f = (_: Seq[ScriptSignatureParams[InputInfo]], _: Transaction) => false
recoverToSucceededIf[IllegalArgumentException] { assertThrows[IllegalArgumentException] {
utxF.flatMap(utx => RawTxSigner.sign(utx, utxos, feeUnit, f)) RawTxSigner.sign(utx, utxos, feeUnit, f)
} }
} }
@ -97,14 +96,13 @@ class RawTxSignerTest extends BitcoinSJvmTest {
val utxos = Vector(utxo) val utxos = Vector(utxo)
val feeUnit = SatoshisPerVirtualByte(Satoshis.one) val feeUnit = SatoshisPerVirtualByte(Satoshis.one)
val utxF = StandardNonInteractiveFinalizer.txFrom(outputs = destinations, val utx = RawFinalizerFactory.txFrom(outputs = destinations,
utxos = utxos, utxos = utxos,
feeRate = feeUnit, feeRate = feeUnit,
changeSPK = changeSPK = EmptyScriptPubKey)
EmptyScriptPubKey)
recoverToSucceededIf[IllegalArgumentException] { assertThrows[IllegalArgumentException] {
utxF.flatMap(utx => RawTxSigner.sign(utx, utxos, feeUnit)) RawTxSigner.sign(utx, utxos, feeUnit)
} }
} }
@ -136,14 +134,13 @@ class RawTxSignerTest extends BitcoinSJvmTest {
val utxos = Vector(utxo) val utxos = Vector(utxo)
val feeUnit = SatoshisPerVirtualByte(Satoshis.one) val feeUnit = SatoshisPerVirtualByte(Satoshis.one)
val utxF = StandardNonInteractiveFinalizer.txFrom(outputs = destinations, val utx = RawFinalizerFactory.txFrom(outputs = destinations,
utxos = utxos, utxos = utxos,
feeRate = feeUnit, feeRate = feeUnit,
changeSPK = changeSPK = EmptyScriptPubKey)
EmptyScriptPubKey)
recoverToSucceededIf[IllegalArgumentException] { assertThrows[IllegalArgumentException] {
utxF.flatMap(utx => RawTxSigner.sign(utx, utxos, feeUnit)) RawTxSigner.sign(utx, utxos, feeUnit)
} }
} }
@ -176,7 +173,7 @@ class RawTxSignerTest extends BitcoinSJvmTest {
val utxos = Vector(cltvSpendingInfo) val utxos = Vector(cltvSpendingInfo)
val feeUnit = SatoshisPerByte(Satoshis.one) val feeUnit = SatoshisPerByte(Satoshis.one)
val utxF = val utx =
StandardNonInteractiveFinalizer.txFrom( StandardNonInteractiveFinalizer.txFrom(
outputs = Vector( outputs = Vector(
TransactionOutput(Bitcoins.one - CurrencyUnits.oneMBTC, TransactionOutput(Bitcoins.one - CurrencyUnits.oneMBTC,
@ -186,9 +183,9 @@ class RawTxSignerTest extends BitcoinSJvmTest {
changeSPK = EmptyScriptPubKey changeSPK = EmptyScriptPubKey
) )
utxF val tx = RawTxSigner.sign(utx, utxos, feeUnit)
.flatMap(utx => RawTxSigner.sign(utx, utxos, feeUnit))
.map(tx => assert(tx.lockTime == UInt32(lockTime))) assert(tx.lockTime == UInt32(lockTime))
} }
it should "succeed to sign a cltv spk that uses a block height locktime" in { 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 utxos = Vector(cltvSpendingInfo)
val feeUnit = SatoshisPerByte(Satoshis.one) val feeUnit = SatoshisPerByte(Satoshis.one)
val utxF = val utx =
StandardNonInteractiveFinalizer.txFrom( StandardNonInteractiveFinalizer.txFrom(
outputs = Vector( outputs = Vector(
TransactionOutput(Bitcoins.one - CurrencyUnits.oneMBTC, TransactionOutput(Bitcoins.one - CurrencyUnits.oneMBTC,
@ -230,9 +227,9 @@ class RawTxSignerTest extends BitcoinSJvmTest {
changeSPK = EmptyScriptPubKey changeSPK = EmptyScriptPubKey
) )
utxF val tx = RawTxSigner.sign(utx, utxos, feeUnit)
.flatMap(utx => RawTxSigner.sign(utx, utxos, feeUnit))
.map(tx => assert(tx.lockTime == UInt32(lockTime))) 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 { 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 utxos = Vector(cltvSpendingInfo1, cltvSpendingInfo2)
val feeRate = SatoshisPerByte(Satoshis.one) val feeRate = SatoshisPerByte(Satoshis.one)
val utxF = val utx = RawFinalizerFactory.txFrom(
StandardNonInteractiveFinalizer.txFrom(
Vector( Vector(
TransactionOutput(Bitcoins.one + Bitcoins.one - CurrencyUnits.oneMBTC, TransactionOutput(Bitcoins.one + Bitcoins.one - CurrencyUnits.oneMBTC,
EmptyScriptPubKey)), EmptyScriptPubKey)),
utxos, utxos,
UInt32.zero,
feeRate, feeRate,
EmptyScriptPubKey EmptyScriptPubKey
) )
recoverToSucceededIf[IllegalArgumentException]( assertThrows[IllegalArgumentException](
utxF.flatMap(utx => RawTxSigner.sign(utx, utxos, feeRate)) RawTxSigner.sign(utx, utxos, feeRate)
) )
} }
it should "sign a mix of spks in a tx and then have it verified" in { it should "sign a mix of spks in a tx and then have it verified" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(), forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) {
ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfo, destinations), (changeSPK, _)) => case ((creditingTxsInfo, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(1000)) val fee = SatoshisPerVirtualByte(Satoshis(1000))
val utxF = val utx =
StandardNonInteractiveFinalizer.txFrom(outputs = destinations, StandardNonInteractiveFinalizer.txFrom(outputs = destinations,
utxos = creditingTxsInfo, utxos = creditingTxsInfo,
feeRate = fee, feeRate = fee,
changeSPK = changeSPK) changeSPK = changeSPK)
val txF = utxF.flatMap(utx => val tx = RawTxSigner.sign(utx, creditingTxsInfo.toVector, fee)
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 { it should "dummy sign a mix of spks in a tx and fill it with dummy signatures" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(), forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) {
ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfo, destinations), (changeSPK, _)) => case ((creditingTxsInfo, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(1000)) val fee = SatoshisPerVirtualByte(Satoshis(1000))
@ -324,20 +316,19 @@ class RawTxSignerTest extends BitcoinSJvmTest {
HashType.sigHashAll) HashType.sigHashAll)
} }
val utxF = val utx =
StandardNonInteractiveFinalizer.txFrom(outputs = destinations, StandardNonInteractiveFinalizer.txFrom(outputs = destinations,
utxos = dummySpendingInfos, utxos = dummySpendingInfos,
feeRate = fee, feeRate = fee,
changeSPK = changeSPK) changeSPK = changeSPK)
val txF = utxF.flatMap(utx => val tx = RawTxSigner.sign(utx,
RawTxSigner.sign(utx,
dummySpendingInfos.toVector, dummySpendingInfos.toVector,
RawTxSigner.emptyInvariant, RawTxSigner.emptyInvariant,
dummySign = true)) dummySign = true)
// Can't use BitcoinScriptUtil.verifyScript because it will pass for things // Can't use BitcoinScriptUtil.verifyScript because it will pass for things
// with EmptyScriptPubKeys or Multisig with 0 required sigs // with EmptyScriptPubKeys or Multisig with 0 required sigs
txF.map { tx match {
case EmptyTransaction => case EmptyTransaction =>
succeed succeed
case btx: BaseTransaction => 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 { it should "sign a mix of p2sh/p2wsh in a tx and then have it verified" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(CreditingTxGen.nestedOutputs), forAll(CreditingTxGen.inputsAndOutputs(CreditingTxGen.nestedOutputs),
ScriptGenerators.scriptPubKey) { ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfo, destinations), (changeSPK, _)) => case ((creditingTxsInfo, destinations), (changeSPK, _)) =>
val fee = SatoshisPerByte(Satoshis(1000)) val fee = SatoshisPerByte(Satoshis(1000))
val utxF = val utx =
StandardNonInteractiveFinalizer.txFrom(outputs = destinations, StandardNonInteractiveFinalizer.txFrom(outputs = destinations,
utxos = creditingTxsInfo, utxos = creditingTxsInfo,
feeRate = fee, feeRate = fee,
changeSPK = changeSPK) changeSPK = changeSPK)
val txF = utxF.flatMap(utx => val tx = RawTxSigner.sign(utx, creditingTxsInfo.toVector, fee)
RawTxSigner.sign(utx, creditingTxsInfo.toVector, fee))
txF.map { tx =>
assert(BitcoinScriptUtil.verifyScript(tx, creditingTxsInfo.toVector)) assert(BitcoinScriptUtil.verifyScript(tx, creditingTxsInfo.toVector))
} }
} }
}
} }

View file

@ -5,7 +5,6 @@ import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.script._
import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.script.crypto.HashType 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.fee.SatoshisPerVirtualByte
import org.bitcoins.core.wallet.utxo.{ import org.bitcoins.core.wallet.utxo.{
ConditionalPath, ConditionalPath,
@ -20,9 +19,9 @@ import org.bitcoins.testkitcore.gen.{
FeeUnitGen, FeeUnitGen,
ScriptGenerators ScriptGenerators
} }
import org.bitcoins.testkitcore.util.BitcoinSJvmTest import org.bitcoins.testkitcore.util.BitcoinSUnitTest
class ShufflingNonInteractiveFinalizerTest extends BitcoinSJvmTest { class ShufflingNonInteractiveFinalizerTest extends BitcoinSUnitTest {
behavior of "ShufflingNonInteractiveFinalizer" behavior of "ShufflingNonInteractiveFinalizer"
private val (spk, privKey) = ScriptGenerators.p2pkhScriptPubKey.sampleSome private val (spk, privKey) = ScriptGenerators.p2pkhScriptPubKey.sampleSome
@ -120,7 +119,7 @@ class ShufflingNonInteractiveFinalizerTest extends BitcoinSJvmTest {
val utxos = Vector(utxo) val utxos = Vector(utxo)
val feeUnit = SatoshisPerVirtualByte(Satoshis.one) val feeUnit = SatoshisPerVirtualByte(Satoshis.one)
recoverToSucceededIf[IllegalArgumentException] { assertThrows[IllegalArgumentException] {
ShufflingNonInteractiveFinalizer.txFrom(outputs = destinations, ShufflingNonInteractiveFinalizer.txFrom(outputs = destinations,
utxos = utxos, utxos = utxos,
feeRate = feeUnit, feeRate = feeUnit,
@ -154,7 +153,7 @@ class ShufflingNonInteractiveFinalizerTest extends BitcoinSJvmTest {
val utxos = Vector(utxo) val utxos = Vector(utxo)
val feeUnit = SatoshisPerVirtualByte(Satoshis(-1)) val feeUnit = SatoshisPerVirtualByte(Satoshis(-1))
recoverToSucceededIf[IllegalArgumentException] { assertThrows[IllegalArgumentException] {
ShufflingNonInteractiveFinalizer.txFrom(outputs = destinations, ShufflingNonInteractiveFinalizer.txFrom(outputs = destinations,
utxos = utxos, utxos = utxos,
feeRate = feeUnit, feeRate = feeUnit,
@ -182,7 +181,7 @@ class ShufflingNonInteractiveFinalizerTest extends BitcoinSJvmTest {
hashType = HashType.sigHashAll hashType = HashType.sigHashAll
) )
recoverToSucceededIf[UnsupportedOperationException] { assertThrows[UnsupportedOperationException] {
ShufflingNonInteractiveFinalizer.txFrom( ShufflingNonInteractiveFinalizer.txFrom(
Vector(TransactionOutput(Bitcoins.one, EmptyScriptPubKey)), Vector(TransactionOutput(Bitcoins.one, EmptyScriptPubKey)),
Vector(spendingInfo), Vector(spendingInfo),
@ -193,24 +192,20 @@ class ShufflingNonInteractiveFinalizerTest extends BitcoinSJvmTest {
} }
it must "create a shuffled transaction with a ShufflingNonInteractiveFinalizer" in { it must "create a shuffled transaction with a ShufflingNonInteractiveFinalizer" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(), forAll(CreditingTxGen.inputsAndOutputs(),
FeeUnitGen.feeUnit(100), FeeUnitGen.feeUnit(100),
ScriptGenerators.scriptPubKey) { ScriptGenerators.scriptPubKey) {
case ((inputs, outputs), feeRate, (changeSpk, _)) => case ((inputs, outputs), feeRate, (changeSpk, _)) =>
val txsF = val txs =
FutureUtil.foldLeftAsync(Vector.empty[Transaction], 0 to 20) { 0.to(20).foldLeft(Vector.empty[Transaction]) { (accum, _) =>
(accum, _) =>
ShufflingNonInteractiveFinalizer ShufflingNonInteractiveFinalizer
.txFrom(outputs, inputs, feeRate, changeSpk) .txFrom(outputs, inputs, feeRate, changeSpk) +: accum
.map(_ +: accum)
} }
txsF.map { txs =>
assert( assert(
inputs.size <= 1 || txs.exists( inputs.size <= 1 || txs.exists(
_.inputs.map(_.previousOutput) != inputs.map(_.outPoint))) _.inputs.map(_.previousOutput) != inputs.map(_.outPoint)))
assert(outputs.size <= 1 || txs.exists(_.outputs != outputs)) assert(outputs.size <= 1 || txs.exists(_.outputs != outputs))
} }
} }
}
} }

View file

@ -15,9 +15,9 @@ import org.bitcoins.core.wallet.utxo.{
import org.bitcoins.crypto.{DoubleSha256DigestBE, ECPrivateKey, ECPublicKey} import org.bitcoins.crypto.{DoubleSha256DigestBE, ECPrivateKey, ECPublicKey}
import org.bitcoins.testkitcore.Implicits._ import org.bitcoins.testkitcore.Implicits._
import org.bitcoins.testkitcore.gen.ScriptGenerators 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" behavior of "StandardNonInteractiveFinalizer"
private val (spk, privKey) = ScriptGenerators.p2pkhScriptPubKey.sampleSome private val (spk, privKey) = ScriptGenerators.p2pkhScriptPubKey.sampleSome
@ -115,7 +115,7 @@ class StandardNonInteractiveFinalizerTest extends BitcoinSJvmTest {
val utxos = Vector(utxo) val utxos = Vector(utxo)
val feeUnit = SatoshisPerVirtualByte(Satoshis.one) val feeUnit = SatoshisPerVirtualByte(Satoshis.one)
recoverToSucceededIf[IllegalArgumentException] { assertThrows[IllegalArgumentException] {
StandardNonInteractiveFinalizer.txFrom(outputs = destinations, StandardNonInteractiveFinalizer.txFrom(outputs = destinations,
utxos = utxos, utxos = utxos,
feeRate = feeUnit, feeRate = feeUnit,
@ -149,7 +149,7 @@ class StandardNonInteractiveFinalizerTest extends BitcoinSJvmTest {
val utxos = Vector(utxo) val utxos = Vector(utxo)
val feeUnit = SatoshisPerVirtualByte(Satoshis(-1)) val feeUnit = SatoshisPerVirtualByte(Satoshis(-1))
recoverToSucceededIf[IllegalArgumentException] { assertThrows[IllegalArgumentException] {
StandardNonInteractiveFinalizer.txFrom(outputs = destinations, StandardNonInteractiveFinalizer.txFrom(outputs = destinations,
utxos = utxos, utxos = utxos,
feeRate = feeUnit, feeRate = feeUnit,
@ -177,7 +177,7 @@ class StandardNonInteractiveFinalizerTest extends BitcoinSJvmTest {
hashType = HashType.sigHashAll hashType = HashType.sigHashAll
) )
recoverToSucceededIf[UnsupportedOperationException] { assertThrows[UnsupportedOperationException] {
StandardNonInteractiveFinalizer.txFrom( StandardNonInteractiveFinalizer.txFrom(
Vector(TransactionOutput(Bitcoins.one, EmptyScriptPubKey)), Vector(TransactionOutput(Bitcoins.one, EmptyScriptPubKey)),
Vector(spendingInfo), Vector(spendingInfo),

View file

@ -28,14 +28,11 @@ import org.bitcoins.testkitcore.gen.{
ScriptGenerators, ScriptGenerators,
TransactionGenerators TransactionGenerators
} }
import org.bitcoins.testkitcore.util.BitcoinSJvmTest import org.bitcoins.testkitcore.util.BitcoinSUnitTest
import scala.annotation.nowarn import scala.annotation.nowarn
import scala.concurrent.{ExecutionContext, Future}
class SignerTest extends BitcoinSJvmTest { class SignerTest extends BitcoinSUnitTest {
implicit val ec: ExecutionContext = ExecutionContext.global
implicit override val generatorDrivenConfig: PropertyCheckConfiguration = implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode generatorDrivenConfigNewCode
@ -75,7 +72,7 @@ class SignerTest extends BitcoinSJvmTest {
.sample(CreditingTxGen.p2wpkhOutput) .sample(CreditingTxGen.p2wpkhOutput)
.asInstanceOf[ScriptSignatureParams[P2WPKHV0InputInfo]] .asInstanceOf[ScriptSignatureParams[P2WPKHV0InputInfo]]
val tx = GenUtil.sample(TransactionGenerators.baseTransaction) val tx = GenUtil.sample(TransactionGenerators.baseTransaction)
recoverToSucceededIf[IllegalArgumentException] { assertThrows[IllegalArgumentException] {
P2WPKHSigner.sign(dumbSpendingInfo, tx, isDummySignature = false, p2wpkh) P2WPKHSigner.sign(dumbSpendingInfo, tx, isDummySignature = false, p2wpkh)
} }
} }
@ -86,45 +83,40 @@ class SignerTest extends BitcoinSJvmTest {
.sample(CreditingTxGen.p2wshOutput) .sample(CreditingTxGen.p2wshOutput)
.asInstanceOf[ScriptSignatureParams[P2WSHV0InputInfo]] .asInstanceOf[ScriptSignatureParams[P2WSHV0InputInfo]]
val tx = GenUtil.sample(TransactionGenerators.baseTransaction) val tx = GenUtil.sample(TransactionGenerators.baseTransaction)
recoverToSucceededIf[IllegalArgumentException] { assertThrows[IllegalArgumentException] {
P2WSHSigner.sign(dumbSpendingInfo, tx, isDummySignature = false, p2wsh) 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 { it must "sign a mix of spks in a tx and then verify that single signing agrees" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(), forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) {
ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfos, destinations), (changeSPK, _)) => case ((creditingTxsInfos, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(1000)) val fee = SatoshisPerVirtualByte(Satoshis(1000))
for { val unsignedTx =
unsignedTx <-
StandardNonInteractiveFinalizer.txFrom(destinations, StandardNonInteractiveFinalizer.txFrom(destinations,
creditingTxsInfos, creditingTxsInfos,
fee, fee,
changeSPK) changeSPK)
signedTx <-
val signedTx =
RawTxSigner.sign(unsignedTx, creditingTxsInfos.toVector, fee) RawTxSigner.sign(unsignedTx, creditingTxsInfos.toVector, fee)
singleSigs: Vector[Vector[ECDigitalSignature]] <- { val singleSigs: Vector[Vector[ECDigitalSignature]] = {
val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] = val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] =
creditingTxsInfos.toVector.map(_.toSingles) creditingTxsInfos.toVector.map(_.toSingles)
val sigVecFs = singleInfosVec.map { singleInfos => singleInfosVec.map { singleInfos =>
val sigFs = singleInfos.map { singleInfo => singleInfos.map { singleInfo =>
val keyAndSigF = val keyAndSig =
BitcoinSigner.signSingle(singleInfo, BitcoinSigner.signSingle(singleInfo,
unsignedTx, unsignedTx,
isDummySignature = false) isDummySignature = false)
keyAndSigF.map(_.signature) keyAndSig.signature
}
}
} }
Future.sequence(sigFs)
}
Future.sequence(sigVecFs)
}
} yield {
signedTx.inputs.zipWithIndex.foreach { case (input, inputIndex) => signedTx.inputs.zipWithIndex.foreach { case (input, inputIndex) =>
val infoAndIndexOpt = creditingTxsInfos.zipWithIndex val infoAndIndexOpt = creditingTxsInfos.zipWithIndex
.find(_._1.outPoint == input.previousOutput) .find(_._1.outPoint == input.previousOutput)
@ -153,49 +145,42 @@ class SignerTest extends BitcoinSJvmTest {
succeed succeed
} }
} }
}
it should "have old and new doSign functions agree" in { it should "have old and new doSign functions agree" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(), forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) {
ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfo, destinations), (changeSPK, _)) => case ((creditingTxsInfo, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(100)) val fee = SatoshisPerVirtualByte(Satoshis(100))
val unsignedTxF = StandardNonInteractiveFinalizer val spendingTx = StandardNonInteractiveFinalizer
.txFrom(outputs = destinations, .txFrom(outputs = destinations,
utxos = creditingTxsInfo, utxos = creditingTxsInfo,
feeRate = fee, feeRate = fee,
changeSPK = changeSPK) changeSPK = changeSPK)
val correctSigsF = unsignedTxF.flatMap { spendingTx => val correctSigs =
val assertFs = creditingTxsInfo.flatMap { signInfo => creditingTxsInfo.flatMap { signInfo =>
signInfo.signers.map { signer => signInfo.signers.map { signer =>
val txSignatureComponent = val txSignatureComponent =
TxSigComponent(signInfo.inputInfo, spendingTx) TxSigComponent(signInfo.inputInfo, spendingTx)
@nowarn val oldSigF = BitcoinSigner.doSign(txSignatureComponent, @nowarn val oldSig = BitcoinSigner.doSign(txSignatureComponent,
signer.signFunction, signer.sign,
signInfo.hashType, signInfo.hashType,
isDummySignature = isDummySignature =
false) false)
for {
oldSig <- oldSigF val newSig = BitcoinSigner.doSign(spendingTx,
newSig <- BitcoinSigner.doSign(spendingTx,
signInfo, signInfo,
signer.signFunction, signer.sign,
signInfo.hashType, signInfo.hashType,
isDummySignature = false) isDummySignature = false)
} yield {
(oldSig.r == newSig.r) && (oldSig.r == newSig.r) &&
(oldSig.s == newSig.s) && (oldSig.s == newSig.s) &&
(oldSig.hex == newSig.hex) (oldSig.hex == newSig.hex)
} }
} }
}
Future.sequence(assertFs) assert(correctSigs.forall(_ == true))
}
correctSigsF.map(x => assert(x.forall(_ == true)))
} }
} }
@ -270,23 +255,22 @@ class SignerTest extends BitcoinSJvmTest {
} }
it must "sign p2wsh inputs correctly when provided no witness data" in { it must "sign p2wsh inputs correctly when provided no witness data" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(CreditingTxGen.p2wshOutputs), forAll(CreditingTxGen.inputsAndOutputs(CreditingTxGen.p2wshOutputs),
ScriptGenerators.scriptPubKey) { ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfos, destinations), (changeSPK, _)) => case ((creditingTxsInfos, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(100)) val fee = SatoshisPerVirtualByte(Satoshis(100))
for { val unsignedTx =
unsignedTx <-
StandardNonInteractiveFinalizer.txFrom(destinations, StandardNonInteractiveFinalizer.txFrom(destinations,
creditingTxsInfos, creditingTxsInfos,
fee, fee,
changeSPK) changeSPK)
singleSigs: Vector[Vector[PartialSignature]] <- { val singleSigs: Vector[Vector[PartialSignature]] = {
val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] = val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] =
creditingTxsInfos.toVector.map(_.toSingles) creditingTxsInfos.toVector.map(_.toSingles)
val sigVecFs = singleInfosVec.map { singleInfos => singleInfosVec.map { singleInfos =>
val sigFs = singleInfos.map { singleInfo => singleInfos.map { singleInfo =>
val wtx = val wtx =
WitnessTransaction(unsignedTx.version, WitnessTransaction(unsignedTx.version,
unsignedTx.inputs, unsignedTx.inputs,
@ -298,13 +282,8 @@ class SignerTest extends BitcoinSJvmTest {
isDummySignature = false) isDummySignature = false)
} }
Future.sequence(sigFs)
} }
Future.sequence(sigVecFs)
} }
} yield {
val psbt = val psbt =
creditingTxsInfos.foldLeft(PSBT.fromUnsignedTx(unsignedTx)) { creditingTxsInfos.foldLeft(PSBT.fromUnsignedTx(unsignedTx)) {
@ -323,5 +302,4 @@ class SignerTest extends BitcoinSJvmTest {
assert(verifyScripts(signedTx.get, creditingTxsInfos.toVector)) assert(verifyScripts(signedTx.get, creditingTxsInfos.toVector))
} }
} }
}
} }

View file

@ -12,9 +12,9 @@ import org.bitcoins.testkitcore.gen.{
ScriptGenerators, ScriptGenerators,
TransactionGenerators 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 = implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode generatorDrivenConfigNewCode
@ -214,7 +214,7 @@ class InputInfoTest extends BitcoinSJvmTest {
} }
it must "successfully compute maxWitnessLengths" in { it must "successfully compute maxWitnessLengths" in {
forAllAsync(CreditingTxGen.output) { scriptSigParams => forAll(CreditingTxGen.output) { scriptSigParams =>
val dummyTx = BaseTransaction( val dummyTx = BaseTransaction(
TransactionConstants.validLockVersion, TransactionConstants.validLockVersion,
Vector( Vector(
@ -225,22 +225,19 @@ class InputInfoTest extends BitcoinSJvmTest {
UInt32.zero UInt32.zero
) )
val maxWitnessLenF = BitcoinSigner val maxWitnessLen = BitcoinSigner
.sign(scriptSigParams, unsignedTx = dummyTx, isDummySignature = true) .sign(scriptSigParams, unsignedTx = dummyTx, isDummySignature = true)
.map(_.transaction) .transaction match {
.map {
case wtx: WitnessTransaction => wtx.witness.head.byteSize.toInt case wtx: WitnessTransaction => wtx.witness.head.byteSize.toInt
case _: NonWitnessTransaction => 0 case _: NonWitnessTransaction => 0
} }
maxWitnessLenF.map { expectedLen => assert(scriptSigParams.maxWitnessLen == maxWitnessLen)
assert(scriptSigParams.maxWitnessLen == expectedLen)
}
} }
} }
it must "successfully compute maxScriptSigLengths" in { it must "successfully compute maxScriptSigLengths" in {
forAllAsync(CreditingTxGen.output) { scriptSigParams => forAll(CreditingTxGen.output) { scriptSigParams =>
val dummyTx = BaseTransaction( val dummyTx = BaseTransaction(
TransactionConstants.validLockVersion, TransactionConstants.validLockVersion,
Vector( Vector(
@ -251,18 +248,16 @@ class InputInfoTest extends BitcoinSJvmTest {
UInt32.zero UInt32.zero
) )
val maxScriptSigF = BitcoinSigner val maxScriptSig = BitcoinSigner
.sign(scriptSigParams, unsignedTx = dummyTx, isDummySignature = true) .sign(scriptSigParams, unsignedTx = dummyTx, isDummySignature = true)
.map(_.transaction) .transaction
.map { tx => .inputs
tx.inputs.head.scriptSignature .head
} .scriptSignature
maxScriptSigF.map { scriptSig =>
assert(InputInfo.maxScriptSigLen( assert(InputInfo.maxScriptSigLen(
scriptSigParams.inputInfo) == scriptSig.byteSize, scriptSigParams.inputInfo) == maxScriptSig.byteSize,
scriptSig.hex) maxScriptSig.hex)
}
} }
} }
} }

View file

@ -7,7 +7,6 @@ import org.bitcoins.crypto._
import scodec.bits.{ByteVector, HexStringSyntax} import scodec.bits.{ByteVector, HexStringSyntax}
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.Future
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}
/** Represents an extended key as defined by BIP32 /** Represents an extended key as defined by BIP32
@ -225,21 +224,21 @@ sealed abstract class ExtPrivateKey
override def publicKey: ECPublicKey = key.publicKey override def publicKey: ECPublicKey = key.publicKey
override def signFunction: ByteVector => Future[ECDigitalSignature] = { override def sign(bytes: ByteVector): ECDigitalSignature = {
key.signFunction key.sign(bytes)
} }
override def signWithEntropyFunction: ( override def signWithEntropy(
ByteVector, bytes: ByteVector,
ByteVector) => Future[ECDigitalSignature] = { entropy: ByteVector): ECDigitalSignature = {
key.signWithEntropyFunction key.signWithEntropy(bytes, entropy)
} }
/** Signs the given bytes with the given [[BIP32Path path]] */ /** Signs the given bytes with the given [[BIP32Path path]] */
override def deriveAndSignFuture: ( override def deriveAndSign(
ByteVector, bytes: ByteVector,
BIP32Path) => Future[ECDigitalSignature] = { case (bytes, path) => path: BIP32Path): ECDigitalSignature = {
deriveChildPrivKey(path).signFunction(bytes) deriveChildPrivKey(path).sign(bytes)
} }
override def toStringSensitive: String = { override def toStringSensitive: String = {

View file

@ -1,19 +1,38 @@
package org.bitcoins.core.crypto package org.bitcoins.core.crypto
import org.bitcoins.core.hd.BIP32Path import org.bitcoins.core.hd.BIP32Path
import org.bitcoins.crypto.{ECDigitalSignature, Sign} import org.bitcoins.crypto.{AsyncSign, ECDigitalSignature, Sign}
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.concurrent.duration.DurationInt import scala.concurrent.Future
import scala.concurrent.{Await, Future}
/** A signing interface for [[ExtKey]] */ /** 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 */ /** First derives the child key that corresponds to [[BIP32Path path]] and then signs */
def sign(bytes: ByteVector, path: BIP32Path): ECDigitalSignature = { def sign(bytes: ByteVector, path: BIP32Path): ECDigitalSignature = {
Await.result(deriveAndSignFuture(bytes, path), 30.seconds) deriveAndSign(bytes, path)
} }
} }

View file

@ -18,7 +18,6 @@ import org.bitcoins.core.wallet.utxo._
import org.bitcoins.crypto.{DummyECDigitalSignature, Sign} import org.bitcoins.crypto.{DummyECDigitalSignature, Sign}
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.{Await, ExecutionContext, Future}
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}
object TxUtil { object TxUtil {
@ -133,8 +132,7 @@ object TxUtil {
def buildDummyTx( def buildDummyTx(
utxos: Vector[InputInfo], utxos: Vector[InputInfo],
outputs: Vector[TransactionOutput])(implicit outputs: Vector[TransactionOutput]): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val dummySpendingInfos = utxos.map { inputInfo => val dummySpendingInfos = utxos.map { inputInfo =>
val mockSigners = val mockSigners =
inputInfo.pubKeys.take(inputInfo.requiredSigs).map(Sign.dummySign) inputInfo.pubKeys.take(inputInfo.requiredSigs).map(Sign.dummySign)
@ -153,13 +151,12 @@ object TxUtil {
val txBuilder = RawTxBuilder() ++= dummyInputs ++= outputs val txBuilder = RawTxBuilder() ++= dummyInputs ++= outputs
val withFinalizer = txBuilder.setFinalizer(AddWitnessDataFinalizer(utxos)) val withFinalizer = txBuilder.setFinalizer(AddWitnessDataFinalizer(utxos))
for { val utx = withFinalizer.buildTx()
utx <- withFinalizer.buildTx()
signed <- RawTxSigner.sign(utx, RawTxSigner.sign(utx,
dummySpendingInfos, dummySpendingInfos,
RawTxSigner.emptyInvariant, RawTxSigner.emptyInvariant,
dummySign = true) dummySign = true)
} yield signed
} }
/** Inserts script signatures and (potentially) witness data to a given /** 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 * Note that the resulting dummy-signed Transaction will have populated
* (dummy) witness data when applicable. * (dummy) witness data when applicable.
*/ */
def addDummySigs(utx: Transaction, inputInfos: Vector[InputInfo])(implicit def addDummySigs(
ec: ExecutionContext): Future[Transaction] = { utx: Transaction,
val dummyInputAndWitnessFs = inputInfos.zipWithIndex.map { inputInfos: Vector[InputInfo]): Transaction = {
val dummyInputAndWitnesses = inputInfos.zipWithIndex.map {
case (inputInfo, index) => case (inputInfo, index) =>
val mockSigners = val mockSigners =
inputInfo.pubKeys.take(inputInfo.requiredSigs).map { pubKey => inputInfo.pubKeys.take(inputInfo.requiredSigs).map { pubKey =>
Sign(_ => Future.successful(DummyECDigitalSignature), Sign(_ => DummyECDigitalSignature,
(_, _) => Future.successful(DummyECDigitalSignature), (_, _) => DummyECDigitalSignature,
pubKey) pubKey)
} }
@ -186,10 +184,11 @@ object TxUtil {
mockSigners, mockSigners,
HashType.sigHashAll) HashType.sigHashAll)
val tx =
BitcoinSigner BitcoinSigner
.sign(mockSpendingInfo, utx, isDummySignature = true) .sign(mockSpendingInfo, utx, isDummySignature = true)
.map(_.transaction) .transaction
.map { tx =>
val witnessOpt = tx match { val witnessOpt = tx match {
case _: NonWitnessTransaction => None case _: NonWitnessTransaction => None
case wtx: WitnessTransaction => case wtx: WitnessTransaction =>
@ -201,21 +200,14 @@ object TxUtil {
(tx.inputs(index), witnessOpt) (tx.inputs(index), witnessOpt)
} }
}
Future.sequence(dummyInputAndWitnessFs).map { inputsAndWitnesses => val inputs = dummyInputAndWitnesses.map(_._1)
val inputs = inputsAndWitnesses.map(_._1) val txWitnesses = dummyInputAndWitnesses.map(_._2)
val txWitnesses = inputsAndWitnesses.map(_._2)
TransactionWitness.fromWitOpt(txWitnesses) match { TransactionWitness.fromWitOpt(txWitnesses) match {
case _: EmptyWitness => case _: EmptyWitness =>
BaseTransaction(utx.version, inputs, utx.outputs, utx.lockTime) BaseTransaction(utx.version, inputs, utx.outputs, utx.lockTime)
case wit: TransactionWitness => case wit: TransactionWitness =>
WitnessTransaction(utx.version, WitnessTransaction(utx.version, inputs, utx.outputs, utx.lockTime, wit)
inputs,
utx.outputs,
utx.lockTime,
wit)
}
} }
} }
@ -293,10 +285,7 @@ object TxUtil {
val expectedTx = if (isSigned) { val expectedTx = if (isSigned) {
tx tx
} else { } else {
import scala.concurrent.ExecutionContext.Implicits.global TxUtil.addDummySigs(tx, inputInfos)
import scala.concurrent.duration.DurationInt
Await.result(TxUtil.addDummySigs(tx, inputInfos), 20.seconds)
} }
val actualFee = creditingAmount - spentAmount val actualFee = creditingAmount - spentAmount

View file

@ -16,7 +16,6 @@ import org.bitcoins.crypto._
import scodec.bits._ import scodec.bits._
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}
case class PSBT( case class PSBT(
@ -222,8 +221,7 @@ case class PSBT(
inputIndex: Int, inputIndex: Int,
signer: Sign, signer: Sign,
conditionalPath: ConditionalPath = ConditionalPath.NoCondition, conditionalPath: ConditionalPath = ConditionalPath.NoCondition,
isDummySignature: Boolean = false)(implicit isDummySignature: Boolean = false): PSBT = {
ec: ExecutionContext): Future[PSBT] = {
require( require(
inputMaps.size == 1 || !inputMaps(inputIndex).isBIP143Vulnerable, inputMaps.size == 1 || !inputMaps(inputIndex).isBIP143Vulnerable,
"This input map is susceptible to the BIP 143 vulnerability, add the non-witness utxo to be safe" "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( def fromUnsignedTxAndInputs(
unsignedTx: Transaction, unsignedTx: Transaction,
spendingInfoAndNonWitnessTxs: Vector[ScriptSignatureParams[InputInfo]])( spendingInfoAndNonWitnessTxs: Vector[
implicit ec: ExecutionContext): Future[PSBT] = { ScriptSignatureParams[InputInfo]]): PSBT = {
fromUnsignedTxAndInputs(unsignedTx, fromUnsignedTxAndInputs(unsignedTx,
spendingInfoAndNonWitnessTxs, spendingInfoAndNonWitnessTxs,
finalized = false) finalized = false)
@ -954,15 +952,14 @@ object PSBT extends Factory[PSBT] with StringFactory[PSBT] {
*/ */
def finalizedFromUnsignedTxAndInputs( def finalizedFromUnsignedTxAndInputs(
unsignedTx: Transaction, unsignedTx: Transaction,
spendingInfos: Vector[ScriptSignatureParams[InputInfo]])(implicit spendingInfos: Vector[ScriptSignatureParams[InputInfo]]): PSBT = {
ec: ExecutionContext): Future[PSBT] = {
fromUnsignedTxAndInputs(unsignedTx, spendingInfos, finalized = true) fromUnsignedTxAndInputs(unsignedTx, spendingInfos, finalized = true)
} }
private def fromUnsignedTxAndInputs( private def fromUnsignedTxAndInputs(
unsignedTx: Transaction, unsignedTx: Transaction,
spendingInfos: Vector[ScriptSignatureParams[InputInfo]], spendingInfos: Vector[ScriptSignatureParams[InputInfo]],
finalized: Boolean)(implicit ec: ExecutionContext): Future[PSBT] = { finalized: Boolean): PSBT = {
require(spendingInfos.length == unsignedTx.inputs.length, require(spendingInfos.length == unsignedTx.inputs.length,
"Must have a SpendingInfo for every input") "Must have a SpendingInfo for every input")
require( require(
@ -980,7 +977,7 @@ object PSBT extends Factory[PSBT] with StringFactory[PSBT] {
val globalMap = GlobalPSBTMap( val globalMap = GlobalPSBTMap(
Vector(GlobalPSBTRecord.UnsignedTransaction(btx))) Vector(GlobalPSBTRecord.UnsignedTransaction(btx)))
val inputMapFs = spendingInfos.map { info => val inputMaps = spendingInfos.map { info =>
if (finalized) { if (finalized) {
InputPSBTMap.finalizedFromSpendingInfo(info, unsignedTx) InputPSBTMap.finalizedFromSpendingInfo(info, unsignedTx)
} else { } else {
@ -989,8 +986,6 @@ object PSBT extends Factory[PSBT] with StringFactory[PSBT] {
} }
val outputMaps = unsignedTx.outputs.map(_ => OutputPSBTMap.empty).toVector val outputMaps = unsignedTx.outputs.map(_ => OutputPSBTMap.empty).toVector
Future.sequence(inputMapFs).map { inputMaps =>
PSBT(globalMap, inputMaps, outputMaps) PSBT(globalMap, inputMaps, outputMaps)
} }
}
} }

View file

@ -12,7 +12,6 @@ import org.bitcoins.crypto._
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try} import scala.util.{Failure, Success, Try}
sealed trait PSBTMap[+RecordType <: PSBTRecord] extends NetworkElement { sealed trait PSBTMap[+RecordType <: PSBTRecord] extends NetworkElement {
@ -839,12 +838,10 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
*/ */
def finalizedFromSpendingInfo( def finalizedFromSpendingInfo(
spendingInfo: ScriptSignatureParams[InputInfo], spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction)(implicit unsignedTx: Transaction): InputPSBTMap = {
ec: ExecutionContext): Future[InputPSBTMap] = { val sigComponent = BitcoinSigner
val sigComponentF = BitcoinSigner
.sign(spendingInfo, unsignedTx, isDummySignature = false) .sign(spendingInfo, unsignedTx, isDummySignature = false)
sigComponentF.map { sigComponent =>
val utxos = spendingInfo.inputInfo match { val utxos = spendingInfo.inputInfo match {
case _: UnassignedSegwitNativeInputInfo => case _: UnassignedSegwitNativeInputInfo =>
Vector(WitnessUTXO(spendingInfo.output)) Vector(WitnessUTXO(spendingInfo.output))
@ -870,7 +867,6 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
InputPSBTMap(utxos ++ finalizedSigs) InputPSBTMap(utxos ++ finalizedSigs)
} }
} }
}
/** Constructs a full (ready to be finalized) but unfinalized InputPSBTMap /** Constructs a full (ready to be finalized) but unfinalized InputPSBTMap
* from a NewSpendingInfoFull, the corresponding PSBT's unsigned transaction, * from a NewSpendingInfoFull, the corresponding PSBT's unsigned transaction,
@ -878,17 +874,13 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
*/ */
def fromUTXOInfo( def fromUTXOInfo(
spendingInfo: ScriptSignatureParams[InputInfo], spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction)(implicit unsignedTx: Transaction): InputPSBTMap = {
ec: ExecutionContext): Future[InputPSBTMap] = { val sigs = spendingInfo.toSingles.map { spendingInfoSingle =>
val sigsF = spendingInfo.toSingles.map { spendingInfoSingle =>
BitcoinSigner.signSingle(spendingInfoSingle, BitcoinSigner.signSingle(spendingInfoSingle,
unsignedTx, unsignedTx,
isDummySignature = false) isDummySignature = false)
} }
val sigFs = Future.sequence(sigsF)
sigFs.map { sigs =>
val builder = Vector.newBuilder[InputPSBTRecord] val builder = Vector.newBuilder[InputPSBTRecord]
spendingInfo.inputInfo match { spendingInfo.inputInfo match {
@ -925,7 +917,6 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
inputMap inputMap
} }
}
override def constructMap(elements: Vector[InputPSBTRecord]): InputPSBTMap = override def constructMap(elements: Vector[InputPSBTRecord]): InputPSBTMap =
InputPSBTMap(elements) InputPSBTMap(elements)

View file

@ -4,8 +4,6 @@ import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.wallet.fee.FeeUnit import org.bitcoins.core.wallet.fee.FeeUnit
import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams} import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams}
import scala.concurrent.{ExecutionContext, Future}
/** Contains a finalized tx (output from [[RawTxFinalizer.buildTx]]) and the /** Contains a finalized tx (output from [[RawTxFinalizer.buildTx]]) and the
* ScriptSignatureParams needed to sign that transaction. * ScriptSignatureParams needed to sign that transaction.
*/ */
@ -13,8 +11,7 @@ case class FinalizedTxWithSigningInfo(
finalizedTx: Transaction, finalizedTx: Transaction,
infos: Vector[ScriptSignatureParams[InputInfo]]) { infos: Vector[ScriptSignatureParams[InputInfo]]) {
def sign(expectedFeeRate: FeeUnit)(implicit def sign(expectedFeeRate: FeeUnit): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
RawTxSigner.sign(this, expectedFeeRate) RawTxSigner.sign(this, expectedFeeRate)
} }
@ -22,8 +19,7 @@ case class FinalizedTxWithSigningInfo(
expectedFeeRate: FeeUnit, expectedFeeRate: FeeUnit,
invariants: ( invariants: (
Vector[ScriptSignatureParams[InputInfo]], Vector[ScriptSignatureParams[InputInfo]],
Transaction) => Boolean)(implicit Transaction) => Boolean): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
RawTxSigner.sign(this, expectedFeeRate, invariants) RawTxSigner.sign(this, expectedFeeRate, invariants)
} }
} }

View file

@ -4,7 +4,6 @@ import org.bitcoins.core.number.{Int32, UInt32}
import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.protocol.transaction._
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.{ExecutionContext, Future}
/** The mutable transaction builder which collects: /** The mutable transaction builder which collects:
* - Unsigned inputs (script signature will be ignored) * - Unsigned inputs (script signature will be ignored)
@ -149,7 +148,7 @@ case class RawTxBuilderWithFinalizer[F <: RawTxFinalizer](
finalizer: F) { finalizer: F) {
/** Completes the builder and finalizes the result */ /** Completes the builder and finalizes the result */
def buildTx()(implicit ec: ExecutionContext): Future[Transaction] = { def buildTx(): Transaction = {
finalizer.buildTx(builder.result()) finalizer.buildTx(builder.result())
} }

View file

@ -8,7 +8,6 @@ import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.wallet.fee.{FeeUnit, SatoshisPerVirtualByte} import org.bitcoins.core.wallet.fee.{FeeUnit, SatoshisPerVirtualByte}
import org.bitcoins.core.wallet.utxo.{InputInfo, InputSigningInfo} import org.bitcoins.core.wallet.utxo.{InputInfo, InputSigningInfo}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Random, Success, Try} import scala.util.{Failure, Random, Success, Try}
/** This trait is responsible for converting RawTxBuilderResults into /** This trait is responsible for converting RawTxBuilderResults into
@ -33,8 +32,7 @@ import scala.util.{Failure, Random, Success, Try}
trait RawTxFinalizer { trait RawTxFinalizer {
/** Constructs a finalized (unsigned) transaction */ /** Constructs a finalized (unsigned) transaction */
def buildTx(txBuilderResult: RawTxBuilderResult)(implicit def buildTx(txBuilderResult: RawTxBuilderResult): Transaction
ec: ExecutionContext): Future[Transaction]
/** The result of buildTx is converted into a RawTxBuilderResult /** The result of buildTx is converted into a RawTxBuilderResult
* by taking that transactions inputs (in order), outputs (in order), * by taking that transactions inputs (in order), outputs (in order),
@ -43,18 +41,14 @@ trait RawTxFinalizer {
*/ */
def andThen(other: RawTxFinalizer): RawTxFinalizer = { def andThen(other: RawTxFinalizer): RawTxFinalizer = {
// this.buildTx above gets shadowed below, so this allows us to call it // this.buildTx above gets shadowed below, so this allows us to call it
def thisBuildTx(txBuilderResult: RawTxBuilderResult)(implicit def thisBuildTx(txBuilderResult: RawTxBuilderResult): Transaction =
ec: ExecutionContext): Future[Transaction] =
this.buildTx(txBuilderResult) this.buildTx(txBuilderResult)
new RawTxFinalizer { new RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = { val thisResult =
for { RawTxBuilderResult.fromTransaction(thisBuildTx(txBuilderResult))
firstFinalizedTx <- thisBuildTx(txBuilderResult) other.buildTx(thisResult)
composedFinalizedTx <-
other.buildTx(RawTxBuilderResult.fromTransaction(firstFinalizedTx))
} yield composedFinalizedTx
} }
} }
} }
@ -87,20 +81,56 @@ abstract class FinalizerFactory[T <: RawTxFinalizer] {
outputs: Seq[TransactionOutput], outputs: Seq[TransactionOutput],
utxos: Seq[InputSigningInfo[InputInfo]], utxos: Seq[InputSigningInfo[InputInfo]],
feeRate: FeeUnit, feeRate: FeeUnit,
changeSPK: ScriptPubKey)(implicit changeSPK: ScriptPubKey): Transaction = {
ec: ExecutionContext): Future[Transaction] = { val builder = txBuilderFrom(outputs, utxos, feeRate, changeSPK)
val builderF = Future(txBuilderFrom(outputs, utxos, feeRate, changeSPK))
builderF.flatMap(_.buildTx()) builder.buildTx()
} }
} }
/** A trivial finalizer that does no processing */ /** A trivial finalizer that does no processing */
case object RawFinalizer extends RawTxFinalizer { case object RawFinalizer extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = { txBuilderResult.toBaseTransaction
Future.successful(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 { case object FilterDustFinalizer extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val filteredOutputs = val filteredOutputs =
txBuilderResult.outputs.filter(_.value >= Policy.dustThreshold) txBuilderResult.outputs.filter(_.value >= Policy.dustThreshold)
Future.successful(
txBuilderResult.toBaseTransaction.copy(outputs = filteredOutputs)) txBuilderResult.toBaseTransaction.copy(outputs = filteredOutputs)
} }
} }
case object BIP69Finalizer extends RawTxFinalizer { case object BIP69Finalizer extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val sortedInputs = txBuilderResult.inputs.sorted val sortedInputs = txBuilderResult.inputs.sorted
val sortedOutputs = txBuilderResult.outputs.sorted val sortedOutputs = txBuilderResult.outputs.sorted
Future.successful(
txBuilderResult.toBaseTransaction.copy(inputs = sortedInputs, txBuilderResult.toBaseTransaction.copy(inputs = sortedInputs,
outputs = sortedOutputs)) outputs = sortedOutputs)
} }
} }
@ -140,8 +168,7 @@ case class SanityCheckFinalizer(
changeSPKs: Vector[ScriptPubKey] = Vector.empty) changeSPKs: Vector[ScriptPubKey] = Vector.empty)
extends RawTxFinalizer { extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val tx = txBuilderResult.toBaseTransaction val tx = txBuilderResult.toBaseTransaction
val passInOutChecksT = val passInOutChecksT =
@ -154,7 +181,10 @@ case class SanityCheckFinalizer(
TxUtil.sanityChecks(isSigned = false, inputInfos, expectedFeeRate, tx) 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) changeSPK: ScriptPubKey)
extends RawTxFinalizer { extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val outputsWithDummyChange = val outputsWithDummyChange =
txBuilderResult.outputs :+ TransactionOutput(Satoshis.zero, changeSPK) txBuilderResult.outputs :+ TransactionOutput(Satoshis.zero, changeSPK)
@ -220,9 +249,8 @@ case class ChangeFinalizer(
val txDummyChange = val txDummyChange =
txBuilderResult.toBaseTransaction.copy(outputs = outputsWithDummyChange) 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 fee = feeRate.calc(dummyTx)
val change = totalCrediting - totalSpending - fee val change = totalCrediting - totalSpending - fee
@ -236,7 +264,6 @@ case class ChangeFinalizer(
txBuilderResult.toBaseTransaction.copy(outputs = newOutputs) txBuilderResult.toBaseTransaction.copy(outputs = newOutputs)
} }
}
} }
/** A finalizer which adds only the witness data included in the /** A finalizer which adds only the witness data included in the
@ -250,8 +277,7 @@ case class ChangeFinalizer(
case class AddWitnessDataFinalizer(inputInfos: Vector[InputInfo]) case class AddWitnessDataFinalizer(inputInfos: Vector[InputInfo])
extends RawTxFinalizer { extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val result = txBuilderResult.toBaseTransaction val result = txBuilderResult.toBaseTransaction
@ -264,14 +290,11 @@ case class AddWitnessDataFinalizer(inputInfos: Vector[InputInfo])
val witnesses = sortedInputInfos.map(InputInfo.getScriptWitness) val witnesses = sortedInputInfos.map(InputInfo.getScriptWitness)
TransactionWitness.fromWitOpt(witnesses) match { TransactionWitness.fromWitOpt(witnesses) match {
case _: EmptyWitness => case _: EmptyWitness =>
Future.successful(txBuilderResult.toBaseTransaction) txBuilderResult.toBaseTransaction
case wit: TransactionWitness => case wit: TransactionWitness =>
val wtx =
WitnessTransaction WitnessTransaction
.toWitnessTx(txBuilderResult.toBaseTransaction) .toWitnessTx(txBuilderResult.toBaseTransaction)
.copy(witness = wit) .copy(witness = wit)
Future.successful(wtx)
} }
} }
} }
@ -286,8 +309,7 @@ case class StandardNonInteractiveFinalizer(
changeSPK: ScriptPubKey) changeSPK: ScriptPubKey)
extends RawTxFinalizer { extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val addChange = ChangeFinalizer(inputInfos, feeRate, changeSPK) val addChange = ChangeFinalizer(inputInfos, feeRate, changeSPK)
val sanityCheck = SanityCheckFinalizer( val sanityCheck = SanityCheckFinalizer(
@ -326,13 +348,12 @@ case class ShufflingNonInteractiveFinalizer(
changeSPK: ScriptPubKey) changeSPK: ScriptPubKey)
extends RawTxFinalizer { extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val addChange = ChangeFinalizer(inputInfos, feeRate, changeSPK) val addChange = ChangeFinalizer(inputInfos, feeRate, changeSPK)
val shuffled = addChange.andThen(ShuffleFinalizer) val shuffled = addChange.andThen(ShuffleFinalizer)
shuffled.buildTx(txBuilderResult).flatMap { tempTx => val tempTx = shuffled.buildTx(txBuilderResult)
val tempTxBuilderResult = RawTxBuilderResult.fromTransaction(tempTx) val tempTxBuilderResult = RawTxBuilderResult.fromTransaction(tempTx)
val shuffledInputInfos = tempTxBuilderResult.inputs val shuffledInputInfos = tempTxBuilderResult.inputs
@ -349,7 +370,6 @@ case class ShufflingNonInteractiveFinalizer(
.andThen(AddWitnessDataFinalizer(shuffledInputInfos)) .andThen(AddWitnessDataFinalizer(shuffledInputInfos))
.buildTx(tempTxBuilderResult) .buildTx(tempTxBuilderResult)
} }
}
} }
object ShufflingNonInteractiveFinalizer object ShufflingNonInteractiveFinalizer
@ -376,8 +396,7 @@ case class AddFutureFeeFinalizer(
changeSPKs: Vector[ScriptPubKey]) changeSPKs: Vector[ScriptPubKey])
extends RawTxFinalizer { extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val changeOutputs = txBuilderResult.outputs.filter(output => val changeOutputs = txBuilderResult.outputs.filter(output =>
changeSPKs.contains(output.scriptPubKey)) changeSPKs.contains(output.scriptPubKey))
@ -409,7 +428,10 @@ case class AddFutureFeeFinalizer(
txBuilderResult.toBaseTransaction.copy(outputs = outputs) 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) case class SubtractFromOutputFinalizer(spk: ScriptPubKey, subAmt: CurrencyUnit)
extends RawTxFinalizer { extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
txBuilderResult.outputs.zipWithIndex.find(_._1.scriptPubKey == spk) match { txBuilderResult.outputs.zipWithIndex.find(_._1.scriptPubKey == spk) match {
case Some((output, index)) => case Some((output, index)) =>
val newOutput = output.copy(value = output.value - subAmt) val newOutput = output.copy(value = output.value - subAmt)
val newOutputs = txBuilderResult.outputs.updated(index, newOutput) val newOutputs = txBuilderResult.outputs.updated(index, newOutput)
Future.successful(
txBuilderResult.toBaseTransaction.copy(outputs = newOutputs)) txBuilderResult.toBaseTransaction.copy(outputs = newOutputs)
case None => case None =>
Future.failed(new RuntimeException( throw new RuntimeException(
s"Did not find expected SPK $spk in ${txBuilderResult.outputs.map(_.scriptPubKey)}")) s"Did not find expected SPK $spk in ${txBuilderResult.outputs.map(_.scriptPubKey)}")
} }
} }
} }
@ -446,23 +467,19 @@ case class SubtractFeeFromOutputsFinalizer(
spks: Vector[ScriptPubKey]) spks: Vector[ScriptPubKey])
extends RawTxFinalizer { extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = { val dummyTx =
val dummyTxF =
TxUtil.addDummySigs(txBuilderResult.toBaseTransaction, inputInfos) TxUtil.addDummySigs(txBuilderResult.toBaseTransaction, inputInfos)
val outputsAfterFeeF = dummyTxF.map { dummyTx => val outputsAfterFee =
SubtractFeeFromOutputsFinalizer.subtractFees( SubtractFeeFromOutputsFinalizer.subtractFees(
dummyTx, dummyTx,
feeRate, feeRate,
spks spks
) )
}
outputsAfterFeeF.map { outputsAfterFee =>
txBuilderResult.toBaseTransaction.copy(outputs = outputsAfterFee) txBuilderResult.toBaseTransaction.copy(outputs = outputsAfterFee)
} }
}
} }
object SubtractFeeFromOutputsFinalizer { object SubtractFeeFromOutputsFinalizer {
@ -549,8 +566,7 @@ case class DualFundingTxFinalizer(
lazy val (acceptFutureFee, acceptFundingFee) = lazy val (acceptFutureFee, acceptFundingFee) =
computeFees(acceptInputs, acceptPayoutSPK, acceptChangeSPK) computeFees(acceptInputs, acceptPayoutSPK, acceptChangeSPK)
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val addOfferFutureFee = val addOfferFutureFee =
AddFutureFeeFinalizer(fundingSPK, offerFutureFee, Vector(offerChangeSPK)) AddFutureFeeFinalizer(fundingSPK, offerFutureFee, Vector(offerChangeSPK))
val addAcceptFutureFee = AddFutureFeeFinalizer(fundingSPK, val addAcceptFutureFee = AddFutureFeeFinalizer(fundingSPK,
@ -573,8 +589,7 @@ case class DualFundingTxFinalizer(
/** Shuffles in the inputs and outputs of the Transaction into a random order */ /** Shuffles in the inputs and outputs of the Transaction into a random order */
case object ShuffleFinalizer extends RawTxFinalizer { case object ShuffleFinalizer extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
ShuffleInputsFinalizer ShuffleInputsFinalizer
.andThen(ShuffleOutputsFinalizer) .andThen(ShuffleOutputsFinalizer)
.buildTx(txBuilderResult) .buildTx(txBuilderResult)
@ -584,21 +599,19 @@ case object ShuffleFinalizer extends RawTxFinalizer {
/** Shuffles in the inputs of the Transaction into a random order */ /** Shuffles in the inputs of the Transaction into a random order */
case object ShuffleInputsFinalizer extends RawTxFinalizer { case object ShuffleInputsFinalizer extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val shuffledInputs = Random.shuffle(txBuilderResult.inputs) 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 */ /** Shuffles in the outputs of the Transaction into a random order */
case object ShuffleOutputsFinalizer extends RawTxFinalizer { case object ShuffleOutputsFinalizer extends RawTxFinalizer {
override def buildTx(txBuilderResult: RawTxBuilderResult)(implicit override def buildTx(txBuilderResult: RawTxBuilderResult): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val shuffledOutputs = Random.shuffle(txBuilderResult.outputs) val shuffledOutputs = Random.shuffle(txBuilderResult.outputs)
Future.successful(
txBuilderResult.toBaseTransaction.copy(outputs = shuffledOutputs)) txBuilderResult.toBaseTransaction.copy(outputs = shuffledOutputs)
} }
} }

View file

@ -11,9 +11,6 @@ import org.bitcoins.core.wallet.utxo.{
UnassignedSegwitNativeInputInfo UnassignedSegwitNativeInputInfo
} }
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Success, Try}
/** Transactions that have been finalized by a RawTxFinalizer are passed as inputs /** Transactions that have been finalized by a RawTxFinalizer are passed as inputs
* to a sign function here in order to generate fully signed transactions. * to a sign function here in order to generate fully signed transactions.
* *
@ -51,21 +48,20 @@ object RawTxSigner {
def sign( def sign(
utx: Transaction, utx: Transaction,
utxoInfos: Vector[ScriptSignatureParams[InputInfo]])(implicit utxoInfos: Vector[ScriptSignatureParams[InputInfo]]): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
sign(utx, utxoInfos, emptyInvariant, dummySign = false) sign(utx, utxoInfos, emptyInvariant, dummySign = false)
} }
def sign(txWithInfo: FinalizedTxWithSigningInfo, expectedFeeRate: FeeUnit)( def sign(
implicit ec: ExecutionContext): Future[Transaction] = { txWithInfo: FinalizedTxWithSigningInfo,
expectedFeeRate: FeeUnit): Transaction = {
sign(txWithInfo.finalizedTx, txWithInfo.infos, expectedFeeRate) sign(txWithInfo.finalizedTx, txWithInfo.infos, expectedFeeRate)
} }
def sign( def sign(
utx: Transaction, utx: Transaction,
utxoInfos: Vector[ScriptSignatureParams[InputInfo]], utxoInfos: Vector[ScriptSignatureParams[InputInfo]],
expectedFeeRate: FeeUnit)(implicit expectedFeeRate: FeeUnit): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val invariants = feeInvariant(expectedFeeRate) val invariants = feeInvariant(expectedFeeRate)
@ -78,8 +74,7 @@ object RawTxSigner {
expectedFeeRate: FeeUnit, expectedFeeRate: FeeUnit,
userInvariants: ( userInvariants: (
Vector[ScriptSignatureParams[InputInfo]], Vector[ScriptSignatureParams[InputInfo]],
Transaction) => Boolean)(implicit Transaction) => Boolean): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val invariants = addFeeRateInvariant(expectedFeeRate, userInvariants) val invariants = addFeeRateInvariant(expectedFeeRate, userInvariants)
@ -91,8 +86,7 @@ object RawTxSigner {
expectedFeeRate: FeeUnit, expectedFeeRate: FeeUnit,
userInvariants: ( userInvariants: (
Vector[ScriptSignatureParams[InputInfo]], Vector[ScriptSignatureParams[InputInfo]],
Transaction) => Boolean)(implicit Transaction) => Boolean): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
val invariants = addFeeRateInvariant(expectedFeeRate, userInvariants) val invariants = addFeeRateInvariant(expectedFeeRate, userInvariants)
@ -108,8 +102,7 @@ object RawTxSigner {
invariants: ( invariants: (
Vector[ScriptSignatureParams[InputInfo]], Vector[ScriptSignatureParams[InputInfo]],
Transaction) => Boolean, Transaction) => Boolean,
dummySign: Boolean)(implicit dummySign: Boolean): Transaction = {
ec: ExecutionContext): Future[Transaction] = {
require( require(
utxoInfos.length == utx.inputs.length, utxoInfos.length == utx.inputs.length,
s"Must provide exactly one UTXOSatisfyingInfo per input, ${utxoInfos.length} != ${utx.inputs.length}") s"Must provide exactly one UTXOSatisfyingInfo per input, ${utxoInfos.length} != ${utx.inputs.length}")
@ -119,31 +112,27 @@ object RawTxSigner {
utx.inputs.exists(_.previousOutput == utxo.outPoint)), utx.inputs.exists(_.previousOutput == utxo.outPoint)),
"All UTXOSatisfyingInfos must correspond to an input.") "All UTXOSatisfyingInfos must correspond to an input.")
val signedTxF = val signedTx =
if ( if (
utxoInfos.exists( utxoInfos.exists(
_.inputInfo.isInstanceOf[UnassignedSegwitNativeInputInfo]) _.inputInfo.isInstanceOf[UnassignedSegwitNativeInputInfo])
) { ) {
Future.fromTry(TxBuilderError.NoSigner) throw TxBuilderError.NoSigner.exception
} else { } else {
val builder = RawTxBuilder() val builder = RawTxBuilder()
.setVersion(utx.version) .setVersion(utx.version)
.setLockTime(utx.lockTime) ++= utx.outputs .setLockTime(utx.lockTime) ++= utx.outputs
val inputAndWitnessFs = utxoInfos.map { utxo => val inputsAndWitnesses = utxoInfos.map { utxo =>
val txSigCompF = val txSigComp =
BitcoinSigner.sign(utxo, utx, isDummySignature = dummySign) 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 witnessesBuilder = Vector.newBuilder[Option[ScriptWitness]]
val inputsAddedToBuilderF =
Future.sequence(inputAndWitnessFs).map { inputsAndWitnesses =>
utx.inputs.foreach { unsignedInput => utx.inputs.foreach { unsignedInput =>
val (input, witnessOpt) = inputsAndWitnesses val (input, witnessOpt) = inputsAndWitnesses
.find(_._1.previousOutput == unsignedInput.previousOutput) .find(_._1.previousOutput == unsignedInput.previousOutput)
@ -152,12 +141,9 @@ object RawTxSigner {
witnessesBuilder += witnessOpt witnessesBuilder += witnessOpt
builder += input builder += input
} }
}
for { val btx = builder.setFinalizer(RawFinalizer).buildTx()
_ <- inputsAddedToBuilderF
btx <- builder.setFinalizer(RawFinalizer).buildTx()
} yield {
val txWitness = val txWitness =
TransactionWitness.fromWitOpt(witnessesBuilder.result()) TransactionWitness.fromWitOpt(witnessesBuilder.result())
@ -171,18 +157,11 @@ object RawTxSigner {
txWitness) txWitness)
} }
} }
}
signedTxF.flatMap { signedTx =>
val txT: Try[Transaction] = {
if (invariants(utxoInfos, signedTx)) { if (invariants(utxoInfos, signedTx)) {
Success(signedTx) signedTx
} else { } else {
TxBuilderError.FailedUserInvariants throw TxBuilderError.FailedUserInvariants.exception
}
}
Future.fromTry(txT)
} }
} }
} }

View file

@ -14,19 +14,16 @@ import org.bitcoins.core.wallet.utxo._
import org.bitcoins.crypto._ import org.bitcoins.crypto._
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.concurrent.{ExecutionContext, Future}
sealed abstract class SignerUtils { sealed abstract class SignerUtils {
@deprecated("use an InputSigningInfo[InputInfo] instead", since = "6/23/2020") @deprecated("use an InputSigningInfo[InputInfo] instead", since = "6/23/2020")
def doSign( def doSign(
sigComponent: TxSigComponent, sigComponent: TxSigComponent,
sign: ByteVector => Future[ECDigitalSignature], sign: ByteVector => ECDigitalSignature,
hashType: HashType, hashType: HashType,
isDummySignature: Boolean)(implicit isDummySignature: Boolean): ECDigitalSignature = {
ec: ExecutionContext): Future[ECDigitalSignature] = {
if (isDummySignature) { if (isDummySignature) {
Future.successful(DummyECDigitalSignature) DummyECDigitalSignature
} else { } else {
TransactionSignatureCreator.createSig(sigComponent, sign, hashType) TransactionSignatureCreator.createSig(sigComponent, sign, hashType)
} }
@ -35,12 +32,11 @@ sealed abstract class SignerUtils {
def doSign( def doSign(
unsignedTx: Transaction, unsignedTx: Transaction,
signingInfo: InputSigningInfo[InputInfo], signingInfo: InputSigningInfo[InputInfo],
sign: ByteVector => Future[ECDigitalSignature], sign: ByteVector => ECDigitalSignature,
hashType: HashType, hashType: HashType,
isDummySignature: Boolean)(implicit isDummySignature: Boolean): ECDigitalSignature = {
ec: ExecutionContext): Future[ECDigitalSignature] = {
if (isDummySignature) { if (isDummySignature) {
Future.successful(LowRDummyECDigitalSignature) LowRDummyECDigitalSignature
} else { } else {
TransactionSignatureCreator.createSig(unsignedTx, TransactionSignatureCreator.createSig(unsignedTx,
signingInfo, signingInfo,
@ -52,8 +48,7 @@ sealed abstract class SignerUtils {
def signSingle( def signSingle(
spendingInfo: ECSignatureParams[InputInfo], spendingInfo: ECSignatureParams[InputInfo],
unsignedTx: Transaction, unsignedTx: Transaction,
isDummySignature: Boolean)(implicit isDummySignature: Boolean): PartialSignature = {
ec: ExecutionContext): Future[PartialSignature] = {
val tx = spendingInfo.inputInfo match { val tx = spendingInfo.inputInfo match {
case _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo | case _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo |
@ -63,17 +58,15 @@ sealed abstract class SignerUtils {
unsignedTx unsignedTx
} }
val signatureF = doSign( val signature = doSign(
unsignedTx = tx, unsignedTx = tx,
signingInfo = spendingInfo, signingInfo = spendingInfo,
sign = spendingInfo.signer.signLowRFuture, sign = spendingInfo.signer.signLowR,
hashType = spendingInfo.hashType, hashType = spendingInfo.hashType,
isDummySignature = isDummySignature isDummySignature = isDummySignature
) )
signatureF.map { sig => PartialSignature(spendingInfo.signer.publicKey, signature)
PartialSignature(spendingInfo.signer.publicKey, sig)
}
} }
protected val flags: Seq[ScriptFlag] = Policy.standardFlags protected val flags: Seq[ScriptFlag] = Policy.standardFlags
@ -116,8 +109,7 @@ sealed abstract class Signer[-InputType <: InputInfo] extends SignerUtils {
def sign( def sign(
spendingInfo: ScriptSignatureParams[InputType], spendingInfo: ScriptSignatureParams[InputType],
unsignedTx: Transaction, unsignedTx: Transaction,
isDummySignature: Boolean)(implicit isDummySignature: Boolean): TxSigComponent = {
ec: ExecutionContext): Future[TxSigComponent] = {
sign( sign(
spendingInfo, spendingInfo,
unsignedTx, unsignedTx,
@ -137,8 +129,7 @@ sealed abstract class Signer[-InputType <: InputInfo] extends SignerUtils {
spendingInfo: ScriptSignatureParams[InputInfo], spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction, unsignedTx: Transaction,
isDummySignature: Boolean, isDummySignature: Boolean,
spendingInfoToSatisfy: ScriptSignatureParams[InputType])(implicit spendingInfoToSatisfy: ScriptSignatureParams[InputType]): TxSigComponent
ec: ExecutionContext): Future[TxSigComponent]
/** Creates a BaseTxSigComponent by replacing the unsignedTx input at inputIndex /** Creates a BaseTxSigComponent by replacing the unsignedTx input at inputIndex
* with a signed one using the given ScriptSignature * with a signed one using the given ScriptSignature
@ -147,13 +138,11 @@ sealed abstract class Signer[-InputType <: InputInfo] extends SignerUtils {
unsignedTx: Transaction, unsignedTx: Transaction,
inputIndex: Int, inputIndex: Int,
output: TransactionOutput, output: TransactionOutput,
scriptSignatureF: Future[ScriptSignature])(implicit scriptSignature: ScriptSignature): BaseTxSigComponent = {
ec: ExecutionContext): Future[BaseTxSigComponent] = {
val unsignedInput = unsignedTx.inputs(inputIndex) val unsignedInput = unsignedTx.inputs(inputIndex)
scriptSignatureF.map { signature =>
val signedInput = TransactionInput(unsignedInput.previousOutput, val signedInput = TransactionInput(unsignedInput.previousOutput,
signature, scriptSignature,
unsignedInput.sequence) unsignedInput.sequence)
val signedInputs = unsignedTx.inputs.updated(inputIndex, signedInput) val signedInputs = unsignedTx.inputs.updated(inputIndex, signedInput)
val signedTx = unsignedTx match { val signedTx = unsignedTx match {
@ -169,7 +158,6 @@ sealed abstract class Signer[-InputType <: InputInfo] extends SignerUtils {
BaseTxSigComponent(signedTx, UInt32(inputIndex), output, flags) BaseTxSigComponent(signedTx, UInt32(inputIndex), output, flags)
} }
}
} }
object BitcoinSigner extends SignerUtils { object BitcoinSigner extends SignerUtils {
@ -177,8 +165,7 @@ object BitcoinSigner extends SignerUtils {
def sign( def sign(
spendingInfo: ScriptSignatureParams[InputInfo], spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction, unsignedTx: Transaction,
isDummySignature: Boolean)(implicit isDummySignature: Boolean): TxSigComponent = {
ec: ExecutionContext): Future[TxSigComponent] = {
sign(spendingInfo, unsignedTx, isDummySignature, spendingInfo) sign(spendingInfo, unsignedTx, isDummySignature, spendingInfo)
} }
@ -186,8 +173,8 @@ object BitcoinSigner extends SignerUtils {
spendingInfo: ScriptSignatureParams[InputInfo], spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction, unsignedTx: Transaction,
isDummySignature: Boolean, isDummySignature: Boolean,
spendingInfoToSatisfy: ScriptSignatureParams[InputInfo])(implicit spendingInfoToSatisfy: ScriptSignatureParams[
ec: ExecutionContext): Future[TxSigComponent] = { InputInfo]): TxSigComponent = {
def spendingFrom[Info <: InputInfo]( def spendingFrom[Info <: InputInfo](
inputInfo: Info): ScriptSignatureParams[Info] = { inputInfo: Info): ScriptSignatureParams[Info] = {
spendingInfoToSatisfy.copy(inputInfo = inputInfo) spendingInfoToSatisfy.copy(inputInfo = inputInfo)
@ -263,8 +250,7 @@ object BitcoinSigner extends SignerUtils {
inputIndex: Int, inputIndex: Int,
signer: Sign, signer: Sign,
conditionalPath: ConditionalPath = ConditionalPath.NoCondition, conditionalPath: ConditionalPath = ConditionalPath.NoCondition,
isDummySignature: Boolean = false)(implicit isDummySignature: Boolean = false): PSBT = {
ec: ExecutionContext): Future[PSBT] = {
// if already signed by this signer // if already signed by this signer
if ( if (
psbt psbt
@ -272,9 +258,8 @@ object BitcoinSigner extends SignerUtils {
.partialSignatures .partialSignatures
.exists(_.pubKey == signer.publicKey) .exists(_.pubKey == signer.publicKey)
) { ) {
Future.failed( throw new IllegalArgumentException(
new IllegalArgumentException( "Input has already been signed with this key")
"Input has already been signed with this key"))
} }
val tx = psbt.transaction val tx = psbt.transaction
@ -317,13 +302,11 @@ object BitcoinSigner extends SignerUtils {
case _: ScriptPubKey => tx case _: ScriptPubKey => tx
} }
val partialSignatureF = val partialSignature =
signSingle(spendingInfo, txToSign, isDummySignature) signSingle(spendingInfo, txToSign, isDummySignature)
partialSignatureF.map { partialSignature =>
psbt.addSignature(partialSignature, inputIndex) psbt.addSignature(partialSignature, inputIndex)
} }
}
} }
/** Represents a SingleKeyBitcoinSigner which signs a RawScriptPubKey */ /** Represents a SingleKeyBitcoinSigner which signs a RawScriptPubKey */
@ -339,24 +322,23 @@ sealed abstract class RawSingleKeyBitcoinSigner[-InputType <: RawInputInfo]
spendingInfo: ScriptSignatureParams[InputInfo], spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction, unsignedTx: Transaction,
isDummySignature: Boolean, isDummySignature: Boolean,
spendingInfoToSatisfy: ScriptSignatureParams[InputType])(implicit spendingInfoToSatisfy: ScriptSignatureParams[
ec: ExecutionContext): Future[TxSigComponent] = { InputType]): TxSigComponent = {
val (_, output, inputIndex, _) = val (_, output, inputIndex, _) =
relevantInfo(spendingInfo, unsignedTx) relevantInfo(spendingInfo, unsignedTx)
val partialSignatureF = val partialSignature =
signSingle(spendingInfo.toSingle(0), unsignedTx, isDummySignature) signSingle(spendingInfo.toSingle(0), unsignedTx, isDummySignature)
val scriptSigF = partialSignatureF.map { partialSignature => val scriptSig =
keyAndSigToScriptSig(partialSignature.pubKey, keyAndSigToScriptSig(partialSignature.pubKey,
partialSignature.signature, partialSignature.signature,
spendingInfoToSatisfy) spendingInfoToSatisfy)
}
updateScriptSigInSigComponent(unsignedTx, updateScriptSigInSigComponent(unsignedTx,
inputIndex.toInt, inputIndex.toInt,
output, output,
scriptSigF) scriptSig)
} }
} }
@ -367,12 +349,11 @@ sealed abstract class EmptySigner extends Signer[EmptyInputInfo] {
spendingInfo: ScriptSignatureParams[InputInfo], spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction, unsignedTx: Transaction,
isDummySignature: Boolean, isDummySignature: Boolean,
spendingInfoToSatisfy: ScriptSignatureParams[EmptyInputInfo])(implicit spendingInfoToSatisfy: ScriptSignatureParams[
ec: ExecutionContext): Future[TxSigComponent] = { EmptyInputInfo]): TxSigComponent = {
val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx) val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx)
val satisfyEmptyScriptSig = val satisfyEmptyScriptSig = TrivialTrueScriptSignature
Future.successful(TrivialTrueScriptSignature)
updateScriptSigInSigComponent(unsignedTx, updateScriptSigInSigComponent(unsignedTx,
inputIndex.toInt, inputIndex.toInt,
@ -431,25 +412,23 @@ sealed abstract class MultiSigSigner extends Signer[MultiSignatureInputInfo] {
spendingInfo: ScriptSignatureParams[InputInfo], spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction, unsignedTx: Transaction,
isDummySignature: Boolean, isDummySignature: Boolean,
spendingInfoToSatisfy: ScriptSignatureParams[MultiSignatureInputInfo])( spendingInfoToSatisfy: ScriptSignatureParams[
implicit ec: ExecutionContext): Future[TxSigComponent] = { MultiSignatureInputInfo]): TxSigComponent = {
val (_, output, inputIndex, _) = val (_, output, inputIndex, _) =
relevantInfo(spendingInfo, unsignedTx) relevantInfo(spendingInfo, unsignedTx)
val keysAndSigsF = spendingInfo.toSingles.map { spendingInfoSingle => val keysAndSigs = spendingInfo.toSingles.map { spendingInfoSingle =>
signSingle(spendingInfoSingle, unsignedTx, isDummySignature) signSingle(spendingInfoSingle, unsignedTx, isDummySignature)
} }
val signaturesF = Future.sequence(keysAndSigsF).map(_.map(_.signature)) val signatures = keysAndSigs.map(_.signature)
val scriptSigF = signaturesF.map { sigs => val scriptSig = MultiSignatureScriptSignature(signatures)
MultiSignatureScriptSignature(sigs)
}
updateScriptSigInSigComponent(unsignedTx, updateScriptSigInSigComponent(unsignedTx,
inputIndex.toInt, inputIndex.toInt,
output, output,
scriptSigF) scriptSig)
} }
} }
@ -462,10 +441,10 @@ sealed abstract class P2SHSigner extends Signer[P2SHInputInfo] {
spendingInfo: ScriptSignatureParams[InputInfo], spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction, unsignedTx: Transaction,
isDummySignature: Boolean, isDummySignature: Boolean,
spendingInfoToSatisfy: ScriptSignatureParams[P2SHInputInfo])(implicit spendingInfoToSatisfy: ScriptSignatureParams[
ec: ExecutionContext): Future[TxSigComponent] = { P2SHInputInfo]): TxSigComponent = {
if (spendingInfoToSatisfy != spendingInfo) { if (spendingInfoToSatisfy != spendingInfo) {
Future.fromTry(TxBuilderError.WrongSigner) throw TxBuilderError.WrongSigner.exception
} else { } else {
val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx) val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx)
@ -481,12 +460,11 @@ sealed abstract class P2SHSigner extends Signer[P2SHInputInfo] {
val nestedSpendingInfo = spendingInfoToSatisfy.copy( val nestedSpendingInfo = spendingInfoToSatisfy.copy(
inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo) inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo)
val signedTxEither = val signedTx =
BitcoinSigner BitcoinSigner
.sign(nestedSpendingInfo, updatedTx, isDummySignature) .sign(nestedSpendingInfo, updatedTx, isDummySignature)
.map(_.transaction) .transaction
signedTxEither.map { signedTx =>
val i = signedTx.inputs(inputIndex.toInt) val i = signedTx.inputs(inputIndex.toInt)
val p2sh = val p2sh =
@ -518,7 +496,6 @@ sealed abstract class P2SHSigner extends Signer[P2SHInputInfo] {
flags = flags) flags = flags)
} }
} }
}
} }
object P2SHSigner extends P2SHSigner object P2SHSigner extends P2SHSigner
@ -529,10 +506,10 @@ sealed abstract class P2WPKHSigner extends Signer[P2WPKHV0InputInfo] {
spendingInfo: ScriptSignatureParams[InputInfo], spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction, unsignedTx: Transaction,
isDummySignature: Boolean, isDummySignature: Boolean,
spendingInfoToSatisfy: ScriptSignatureParams[P2WPKHV0InputInfo])(implicit spendingInfoToSatisfy: ScriptSignatureParams[
ec: ExecutionContext): Future[TxSigComponent] = { P2WPKHV0InputInfo]): TxSigComponent = {
if (spendingInfoToSatisfy != spendingInfo) { if (spendingInfoToSatisfy != spendingInfo) {
Future.fromTry(TxBuilderError.WrongSigner) throw TxBuilderError.WrongSigner.exception
} else { } else {
val (_, output, inputIndex, hashType) = val (_, output, inputIndex, hashType) =
relevantInfo(spendingInfo, unsignedTx) relevantInfo(spendingInfo, unsignedTx)
@ -554,25 +531,23 @@ sealed abstract class P2WPKHSigner extends Signer[P2WPKHV0InputInfo] {
unsignedTxWitness) unsignedTxWitness)
val witSPK = output.scriptPubKey match { val witSPK = output.scriptPubKey match {
case p2wpkh: P2WPKHWitnessSPKV0 => Future.successful(p2wpkh) case p2wpkh: P2WPKHWitnessSPKV0 => p2wpkh
case _: UnassignedWitnessScriptPubKey | _: P2WSHWitnessSPKV0 => case _: UnassignedWitnessScriptPubKey | _: P2WSHWitnessSPKV0 =>
Future.fromTry(TxBuilderError.WrongSigner) throw TxBuilderError.WrongSigner.exception
case _: NonWitnessScriptPubKey => case _: NonWitnessScriptPubKey =>
Future.fromTry(TxBuilderError.NonWitnessSPK) throw TxBuilderError.NonWitnessSPK.exception
} }
witSPK.flatMap { w => val witOutput = TransactionOutput(output.value, witSPK)
val witOutput = TransactionOutput(output.value, w)
val signature = val signature =
doSign(unsignedTx, doSign(unsignedTx,
spendingInfo, spendingInfo,
signer.signLowRFuture, signer.signLowR,
hashType, hashType,
isDummySignature) isDummySignature)
signature.map { sig => val scriptWitness = P2WPKHWitnessV0(pubKey, signature)
val scriptWitness = P2WPKHWitnessV0(pubKey, sig)
val signedTxWitness = val signedTxWitness =
wtx.witness.updated(inputIndex.toInt, scriptWitness) wtx.witness.updated(inputIndex.toInt, scriptWitness)
val signedTx = WitnessTransaction(unsignedWtx.version, val signedTx = WitnessTransaction(unsignedWtx.version,
@ -581,9 +556,7 @@ sealed abstract class P2WPKHSigner extends Signer[P2WPKHV0InputInfo] {
unsignedWtx.lockTime, unsignedWtx.lockTime,
signedTxWitness) signedTxWitness)
WitnessTxSigComponentRaw(signedTx, inputIndex, witOutput, flags) WitnessTxSigComponentRaw(signedTx, inputIndex, witOutput, flags)
}
}
case btx: NonWitnessTransaction => case btx: NonWitnessTransaction =>
val wtx = WitnessTransaction.toWitnessTx(btx) val wtx = WitnessTransaction.toWitnessTx(btx)
@ -600,10 +573,10 @@ sealed abstract class P2WSHSigner extends Signer[P2WSHV0InputInfo] {
spendingInfo: ScriptSignatureParams[InputInfo], spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction, unsignedTx: Transaction,
isDummySignature: Boolean, isDummySignature: Boolean,
spendingInfoToSatisfy: ScriptSignatureParams[P2WSHV0InputInfo])(implicit spendingInfoToSatisfy: ScriptSignatureParams[
ec: ExecutionContext): Future[TxSigComponent] = { P2WSHV0InputInfo]): TxSigComponent = {
if (spendingInfoToSatisfy != spendingInfo) { if (spendingInfoToSatisfy != spendingInfo) {
Future.fromTry(TxBuilderError.WrongSigner) throw TxBuilderError.WrongSigner.exception
} else { } else {
val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx) val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx)
@ -615,18 +588,16 @@ sealed abstract class P2WSHSigner extends Signer[P2WSHV0InputInfo] {
val nestedSpendingInfo = spendingInfoToSatisfy.copy( val nestedSpendingInfo = spendingInfoToSatisfy.copy(
inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo) inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo)
val signedSigComponentF = BitcoinSigner.sign(spendingInfo, val signedSigComponent = BitcoinSigner.sign(spendingInfo,
wtx, wtx,
isDummySignature, isDummySignature,
nestedSpendingInfo) nestedSpendingInfo)
val scriptWitF = signedSigComponentF.map { signedSigComponent => val scriptWit =
P2WSHWitnessV0( P2WSHWitnessV0(
spendingInfoToSatisfy.inputInfo.scriptWitness.redeemScript, spendingInfoToSatisfy.inputInfo.scriptWitness.redeemScript,
signedSigComponent.scriptSignature) signedSigComponent.scriptSignature)
}
scriptWitF.map { scriptWit =>
val signedWitness = val signedWitness =
wtx.witness.updated(inputIndex.toInt, scriptWit) wtx.witness.updated(inputIndex.toInt, scriptWit)
val signedWTx = WitnessTransaction(wtx.version, val signedWTx = WitnessTransaction(wtx.version,
@ -637,7 +608,6 @@ sealed abstract class P2WSHSigner extends Signer[P2WSHV0InputInfo] {
WitnessTxSigComponentRaw(signedWTx, inputIndex, output, flags) WitnessTxSigComponentRaw(signedWTx, inputIndex, output, flags)
} }
} }
}
} }
object P2WSHSigner extends P2WSHSigner object P2WSHSigner extends P2WSHSigner
@ -647,8 +617,8 @@ sealed abstract class LockTimeSigner extends Signer[LockTimeInputInfo] {
spendingInfo: ScriptSignatureParams[InputInfo], spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction, unsignedTx: Transaction,
isDummySignature: Boolean, isDummySignature: Boolean,
spendingInfoToSatisfy: ScriptSignatureParams[LockTimeInputInfo])(implicit spendingInfoToSatisfy: ScriptSignatureParams[
ec: ExecutionContext): Future[TxSigComponent] = { LockTimeInputInfo]): TxSigComponent = {
val nestedSpendingInfo = spendingInfoToSatisfy.copy( val nestedSpendingInfo = spendingInfoToSatisfy.copy(
inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo) inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo)
@ -669,27 +639,26 @@ sealed abstract class ConditionalSigner extends Signer[ConditionalInputInfo] {
spendingInfo: ScriptSignatureParams[InputInfo], spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction, unsignedTx: Transaction,
isDummySignature: Boolean, isDummySignature: Boolean,
spendingInfoToSatisfy: ScriptSignatureParams[ConditionalInputInfo])( spendingInfoToSatisfy: ScriptSignatureParams[
implicit ec: ExecutionContext): Future[TxSigComponent] = { ConditionalInputInfo]): TxSigComponent = {
val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx) val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx)
val nestedSpendingInfo = spendingInfoToSatisfy.copy( val nestedSpendingInfo = spendingInfoToSatisfy.copy(
inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo) inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo)
val missingOpSigComponentF = BitcoinSigner.sign(spendingInfo, val missingOpSigComponent = BitcoinSigner.sign(spendingInfo,
unsignedTx, unsignedTx,
isDummySignature, isDummySignature,
nestedSpendingInfo) nestedSpendingInfo)
val scriptSigF = missingOpSigComponentF.map { sigComponent => val scriptSig =
ConditionalScriptSignature(sigComponent.scriptSignature, ConditionalScriptSignature(missingOpSigComponent.scriptSignature,
spendingInfoToSatisfy.inputInfo.condition) spendingInfoToSatisfy.inputInfo.condition)
}
updateScriptSigInSigComponent(unsignedTx, updateScriptSigInSigComponent(unsignedTx,
inputIndex.toInt, inputIndex.toInt,
output, output,
scriptSigF) scriptSig)
} }
} }
object ConditionalSigner extends ConditionalSigner object ConditionalSigner extends ConditionalSigner

View file

@ -1,6 +1,6 @@
package org.bitcoins.crypto package org.bitcoins.crypto
class SignWithEntropyTest extends BitcoinSCryptoAsyncTest { class SignWithEntropyTest extends BitcoinSCryptoTest {
implicit override val generatorDrivenConfig: PropertyCheckConfiguration = implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode generatorDrivenConfigNewCode
@ -13,13 +13,11 @@ class SignWithEntropyTest extends BitcoinSCryptoAsyncTest {
behavior of "SignWithEntropy" behavior of "SignWithEntropy"
it must "sign arbitrary data correctly with low R values" in { it must "sign arbitrary data correctly with low R values" in {
forAllAsync(CryptoGenerators.sha256Digest) { hash => forAll(CryptoGenerators.sha256Digest) { hash =>
val bytes = hash.bytes val bytes = hash.bytes
for { val sig1 = privKey.signLowR(bytes)
sig1 <- privKey.signLowRFuture(bytes) val sig2 = privKey.signLowR(bytes) // Check for determinism
sig2 <- privKey.signLowRFuture(bytes) // Check for determinism
} yield {
assert(pubKey.verify(bytes, sig1)) assert(pubKey.verify(bytes, sig1))
assert( assert(
sig1.bytes.length <= 70 sig1.bytes.length <= 70
@ -28,16 +26,13 @@ class SignWithEntropyTest extends BitcoinSCryptoAsyncTest {
assert(sig1 == sig2) assert(sig1 == sig2)
} }
} }
}
it must "sign arbitrary pieces of data with arbitrary entropy correctly" in { 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) => 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))
} }
} }
}
} }

View file

@ -2,7 +2,7 @@ package org.bitcoins.crypto
import scodec.bits.ByteVector import scodec.bits.ByteVector
class SignTest extends BitcoinSCryptoAsyncTest { class SignTest extends BitcoinSCryptoTest {
implicit override val generatorDrivenConfig: PropertyCheckConfiguration = implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode generatorDrivenConfigNewCode
@ -30,12 +30,10 @@ class SignTest extends BitcoinSCryptoAsyncTest {
} }
it must "sign arbitrary pieces of data correctly" in { it must "sign arbitrary pieces of data correctly" in {
forAllAsync(CryptoGenerators.sha256Digest) { hash => forAll(CryptoGenerators.sha256Digest) { hash =>
val sigF = privKey.signFunction(hash.bytes) val sig = privKey.sign(hash.bytes)
sigF.map { sig =>
assert(pubKey.verify(hash.bytes, sig)) assert(pubKey.verify(hash.bytes, sig))
} }
} }
}
} }

View file

@ -4,8 +4,7 @@ import scodec.bits.ByteVector
import java.math.BigInteger import java.math.BigInteger
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.ExecutionContext.Implicits import scala.concurrent.ExecutionContext
import scala.concurrent.{ExecutionContext, Future}
/** Created by chris on 2/16/16. /** Created by chris on 2/16/16.
*/ */
@ -18,11 +17,6 @@ sealed abstract class ECPrivateKey
with Sign with Sign
with MaskedToString { with MaskedToString {
override def signFunction: ByteVector => Future[ECDigitalSignature] = {
bytes =>
Future.successful(sign(bytes))
}
/** Signs a given sequence of bytes with the signingKey /** Signs a given sequence of bytes with the signingKey
* @param dataToSign the bytes to be signed * @param dataToSign the bytes to be signed
* @return the digital signature * @return the digital signature
@ -33,23 +27,12 @@ sealed abstract class ECPrivateKey
def sign(hash: HashDigest): ECDigitalSignature = sign(hash.bytes) def sign(hash: HashDigest): ECDigitalSignature = sign(hash.bytes)
def signFuture(hash: HashDigest)(implicit
ec: ExecutionContext): Future[ECDigitalSignature] =
Future(sign(hash))
override def signWithEntropy( override def signWithEntropy(
bytes: ByteVector, bytes: ByteVector,
entropy: ByteVector): ECDigitalSignature = { entropy: ByteVector): ECDigitalSignature = {
CryptoUtil.signWithEntropy(this, bytes, entropy) 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 = { def schnorrSign(dataToSign: ByteVector): SchnorrDigitalSignature = {
val auxRand = ECPrivateKey.freshPrivateKey.bytes val auxRand = ECPrivateKey.freshPrivateKey.bytes
schnorrSign(dataToSign, auxRand) schnorrSign(dataToSign, auxRand)
@ -156,7 +139,7 @@ object ECPrivateKey extends Factory[ECPrivateKey] {
def fromBytes(bytes: ByteVector, isCompressed: Boolean): ECPrivateKey = { def fromBytes(bytes: ByteVector, isCompressed: Boolean): ECPrivateKey = {
if (bytes.size == 32) if (bytes.size == 32)
ECPrivateKeyImpl(bytes, isCompressed, Implicits.global) ECPrivateKeyImpl(bytes, isCompressed, ExecutionContext.global)
else if (bytes.size < 32) { else if (bytes.size < 32) {
//means we need to pad the private key with 0 bytes so we have 32 bytes //means we need to pad the private key with 0 bytes so we have 32 bytes
ECPrivateKey.fromBytes(bytes.padLeft(32), isCompressed) ECPrivateKey.fromBytes(bytes.padLeft(32), isCompressed)
@ -279,7 +262,7 @@ object ECPublicKey extends Factory[ECPublicKey] {
} }
override def fromBytes(bytes: ByteVector): ECPublicKey = { override def fromBytes(bytes: ByteVector): ECPublicKey = {
ECPublicKeyImpl(bytes, Implicits.global) ECPublicKeyImpl(bytes, ExecutionContext.global)
} }
def apply(): ECPublicKey = freshPublicKey def apply(): ECPublicKey = freshPublicKey

View file

@ -2,8 +2,8 @@ package org.bitcoins.crypto
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.concurrent.duration.DurationInt import scala.annotation.tailrec
import scala.concurrent.{Await, ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
/** This is meant to be an abstraction for a [[org.bitcoins.crypto.ECPrivateKey]], sometimes we will not /** 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 * 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 * 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] * type signature of the function you implement must be scodec.bits.ByteVector => Future[ECDigitalSignature]
*/ */
trait Sign { trait AsyncSign {
def signFunction: ByteVector => Future[ECDigitalSignature] def asyncSign(bytes: ByteVector): Future[ECDigitalSignature]
def signFuture(bytes: ByteVector): Future[ECDigitalSignature] =
signFunction(bytes)
/** Note that using this function to generate digital signatures with specific /** 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 * properties (by trying a bunch of entropy values) can reduce privacy as it will
@ -32,86 +29,68 @@ trait Sign {
* In short, ALL USES OF THIS FUNCTION THAT SIGN THE SAME DATA WITH DIFFERENT ENTROPY * 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! * HAVE THE POTENTIAL TO CAUSE REDUCTIONS IN SECURITY AND PRIVACY, BEWARE!
*/ */
def signWithEntropyFunction: ( def asyncSignWithEntropy(
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(
bytes: ByteVector, bytes: ByteVector,
entropy: ByteVector): Future[ECDigitalSignature] = entropy: ByteVector): Future[ECDigitalSignature]
signWithEntropyFunction(bytes, entropy)
private def signLowRFuture(bytes: ByteVector, startAt: Long)(implicit private def asyncSignLowR(bytes: ByteVector, startAt: Long)(implicit
ec: ExecutionContext): Future[ECDigitalSignature] = { ec: ExecutionContext): Future[ECDigitalSignature] = {
val sigF: Future[ECDigitalSignature] = if (startAt == 0) { val sigF = if (startAt == 0) { // On first try, use normal signing
signFunction(bytes) asyncSign(bytes)
} else { } else { // Subsequently, use additional entropy
val startBytes = ByteVector.fromLong(startAt).padLeft(32).reverse val startBytes = ByteVector.fromLong(startAt).padLeft(32).reverse
signWithEntropyFunction(bytes, startBytes) asyncSignWithEntropy(bytes, startBytes)
} }
sigF.flatMap { sig => sigF.flatMap { sig =>
if (sig.bytes.length <= 70) { if (sig.bytes.length <= 70) {
Future.successful(sig) Future.successful(sig)
} else { } else {
signLowRFuture(bytes, startAt + 1) asyncSignLowR(bytes, startAt + 1)
} }
} }
} }
def signLowRFuture(bytes: ByteVector)(implicit def asyncSignLowR(bytes: ByteVector)(implicit
ec: ExecutionContext): Future[ECDigitalSignature] = { ec: ExecutionContext): Future[ECDigitalSignature] = {
signLowRFuture(bytes, startAt = 0) asyncSignLowR(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)
} }
def publicKey: ECPublicKey def publicKey: ECPublicKey
} }
object Sign { object AsyncSign {
private case class SignImpl( private case class AsyncSignImpl(
signFunction: ByteVector => Future[ECDigitalSignature], asyncSignFunction: ByteVector => Future[ECDigitalSignature],
signWithEntropyFunction: ( asyncSignWithEntropyFunction: (
ByteVector, ByteVector,
ByteVector) => Future[ECDigitalSignature], ByteVector) => Future[ECDigitalSignature],
publicKey: ECPublicKey) override val publicKey: ECPublicKey)
extends Sign extends AsyncSign {
def apply( override def asyncSign(bytes: ByteVector): Future[ECDigitalSignature] = {
signFunction: ByteVector => Future[ECDigitalSignature], asyncSignFunction(bytes)
signWithEntropyFunction: (
ByteVector,
ByteVector) => Future[ECDigitalSignature],
pubKey: ECPublicKey): Sign = {
SignImpl(signFunction, signWithEntropyFunction, pubKey)
} }
def constant(sig: ECDigitalSignature, pubKey: ECPublicKey): Sign = { override def asyncSignWithEntropy(
SignImpl(_ => Future.successful(sig), bytes: ByteVector,
entropy: ByteVector): Future[ECDigitalSignature] = {
asyncSignWithEntropyFunction(bytes, entropy)
}
}
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), (_, _) => Future.successful(sig),
pubKey) pubKey)
} }
@ -123,6 +102,93 @@ object Sign {
* the public key is still useful here though because it can be used to match against * the public key is still useful here though because it can be used to match against
* a specific private key on another server * 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 = { def dummySign(publicKey: ECPublicKey): Sign = {
constant(EmptyDigitalSignature, publicKey) constant(EmptyDigitalSignature, publicKey)
} }

View file

@ -33,7 +33,7 @@ import org.bitcoins.core.wallet.utxo.{
import scodec.bits._ import scodec.bits._
import scala.concurrent.duration.DurationInt import scala.concurrent.duration.DurationInt
import scala.concurrent.{Await, ExecutionContext, ExecutionContextExecutor} import scala.concurrent.{ExecutionContext, ExecutionContextExecutor}
``` ```
```scala mdoc:compile-only ```scala mdoc:compile-only
@ -107,10 +107,10 @@ val privKey0 = ECPrivateKeyUtil.fromWIFToPrivateKey(
val privKey1 = ECPrivateKeyUtil.fromWIFToPrivateKey( val privKey1 = ECPrivateKeyUtil.fromWIFToPrivateKey(
"cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d") "cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d")
val psbtFirstSigF = val psbtFirstSig =
psbtWithSigHashFlags psbtWithSigHashFlags
.sign(inputIndex = 0, signer = privKey0) .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 // Alternatively, you can use produce a signature with a ECSignatureParams
// using the BitcoinSigner will return a PartialSignature that can be added to a PSBT // 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 // Then we can sign the transaction
val signatureF = BitcoinSigner.signSingle( val signature = BitcoinSigner.signSingle(
spendingInfo = spendingInfoSingle, spendingInfo = spendingInfoSingle,
unsignedTx = unsignedTransaction, unsignedTx = unsignedTransaction,
isDummySignature = false) isDummySignature = false)
// We can then add the signature to the PSBT // We can then add the signature to the PSBT
// Note: this signature could be produced by us or another party // 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 // 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
// 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.
// 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.
// The two standard formats for this are in byte form or in base64 you can access these easily. val bytes = psbtFirstSig.bytes
val bytes = psbtFirstSig.bytes val base64 = psbtFirstSig.base64
val base64 = psbtFirstSig.base64
// After the other party has signed their input they can send us back the PSBT with the signatures // 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 // To import we can use any of these functions
val fromBytes = PSBT.fromBytes( val fromBytes = PSBT.fromBytes(
hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8872202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8872202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000")
val fromBase64 = PSBT.fromBase64( val fromBase64 = PSBT.fromBase64(
"cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD") "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD")
// After we've imported the PSBT we can combine it with our own signed PSBT so we can // 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 // have one PSBT with all of the necessary data
val combinedPSBT = fromBase64.combinePSBT(psbtFirstSig) val combinedPSBT = fromBase64.combinePSBT(psbtFirstSig)
// Now that the PSBT has all the necessary data, we can finalize it and extract the transaction // 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 // This will return a Try[PSBT] and will fail if you do not have all the required fields filled
val finalizedPSBT = combinedPSBT.finalizePSBT val finalizedPSBT = combinedPSBT.finalizePSBT
// After it has been finalized we can extract the fully signed transaction that is ready // After it has been finalized we can extract the fully signed transaction that is ready
// to be broadcast to the network. // to be broadcast to the network.
// You can also use extractTransactionAndValidate that will validate if the transaction is valid // You can also use extractTransactionAndValidate that will validate if the transaction is valid
finalizedPSBT.get.extractTransaction finalizedPSBT.get.extractTransaction
}
Await.result(signedTransactionF, 30.seconds)
``` ```

View file

@ -106,7 +106,7 @@ val finalizer = StandardNonInteractiveFinalizer(
changeSPK) changeSPK)
// We can now finalize the tx builder result from earlier with this finalizer // 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 // We now turn to signing the unsigned transaction
// this contains all the information we need to // this contains all the information we need to
@ -129,16 +129,12 @@ val utxoInfos: Vector[ScriptSignatureParams[InputInfo]] = Vector(utxoInfo)
// 1: one input // 1: one input
// 2: outputs (destination and change outputs) // 2: outputs (destination and change outputs)
// 3: a fee rate of 1 satoshi/byte // 3: a fee rate of 1 satoshi/byte
val signedTx: Transaction = { val signedTx: Transaction =
val signedTxF = unsignedTxF.flatMap { unsignedTx =>
RawTxSigner.sign( RawTxSigner.sign(
utx = unsignedTx, utx = unsignedTx,
utxoInfos = utxoInfos, utxoInfos = utxoInfos,
expectedFeeRate = feeRate expectedFeeRate = feeRate
) )
}
Await.result(signedTxF, 30.seconds)
}
``` ```
```scala mdoc:to-string ```scala mdoc:to-string

View file

@ -18,7 +18,6 @@ import org.scalacheck.Gen
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.{ExecutionContext, Future}
object PSBTGenerators { object PSBTGenerators {
@ -107,14 +106,13 @@ object PSBTGenerators {
.map(_.groupBy(_.key).map(_._2.head).toVector) .map(_.groupBy(_.key).map(_._2.head).toVector)
} }
def psbtWithUnknowns(implicit ec: ExecutionContext): Gen[Future[PSBT]] = { def psbtWithUnknowns: Gen[PSBT] = {
for { for {
psbtF <- Gen.frequency((6, fullNonFinalizedPSBT), (1, finalizedPSBT)) psbt <- Gen.frequency((6, fullNonFinalizedPSBT), (1, finalizedPSBT))
globals <- unknownGlobals globals <- unknownGlobals
inputs <- unknownInputs inputs <- unknownInputs
outputs <- unknownOutputs outputs <- unknownOutputs
} yield { } yield {
psbtF.map { psbt =>
val newGlobal = GlobalPSBTMap(psbt.globalMap.elements ++ globals) val newGlobal = GlobalPSBTMap(psbt.globalMap.elements ++ globals)
val newInputMaps = val newInputMaps =
psbt.inputMaps.map(map => InputPSBTMap(map.elements ++ inputs)) psbt.inputMaps.map(map => InputPSBTMap(map.elements ++ inputs))
@ -124,37 +122,30 @@ object PSBTGenerators {
PSBT(newGlobal, newInputMaps, newOutputMaps) PSBT(newGlobal, newInputMaps, newOutputMaps)
} }
} }
}
def psbtWithUnknownVersion(implicit def psbtWithUnknownVersion: Gen[PSBT] = {
ec: ExecutionContext): Gen[Future[PSBT]] = {
for { for {
psbtF <- psbtWithUnknowns psbt <- psbtWithUnknowns
versionNumber <- Gen.choose(min = PSBT.knownVersions.last.toLong, versionNumber <- Gen.choose(min = PSBT.knownVersions.last.toLong,
max = UInt32.max.toLong) max = UInt32.max.toLong)
} yield { } yield {
psbtF.map { psbt =>
val newGlobal = GlobalPSBTMap( val newGlobal = GlobalPSBTMap(
psbt.globalMap.elements :+ Version(UInt32(versionNumber))) psbt.globalMap.elements :+ Version(UInt32(versionNumber)))
PSBT(newGlobal, psbt.inputMaps, psbt.outputMaps) PSBT(newGlobal, psbt.inputMaps, psbt.outputMaps)
} }
} }
}
def psbtToBeSigned(implicit ec: ExecutionContext): Gen[ def psbtToBeSigned: Gen[
Future[(PSBT, Seq[ScriptSignatureParams[InputInfo]], FeeUnit)]] = { (PSBT, Seq[ScriptSignatureParams[InputInfo]], FeeUnit)] = {
psbtWithBuilder(finalized = false).map { psbtAndBuilderF => psbtWithBuilder(finalized = false).map {
psbtAndBuilderF.flatMap {
case (psbt, FinalizedTxWithSigningInfo(_, infos), fee) => case (psbt, FinalizedTxWithSigningInfo(_, infos), fee) =>
val newInputsMaps = psbt.inputMaps.map { map => val newInputsMaps = psbt.inputMaps.map { map =>
InputPSBTMap(map.elements.filterNot(element => InputPSBTMap(map.elements.filterNot(element =>
PSBTInputKeyId.fromBytes(element.key) == PartialSignatureKeyId)) 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]], creditingTxsInfo: Seq[ScriptSignatureParams[InputInfo]],
destinations: Seq[TransactionOutput], destinations: Seq[TransactionOutput],
changeSPK: ScriptPubKey, changeSPK: ScriptPubKey,
fee: FeeUnit)(implicit fee: FeeUnit): (PSBT, FinalizedTxWithSigningInfo, FeeUnit) = {
ec: ExecutionContext): Future[(PSBT, FinalizedTxWithSigningInfo, FeeUnit)] = {
val lockTime = TxUtil.calcLockTime(creditingTxsInfo).get val lockTime = TxUtil.calcLockTime(creditingTxsInfo).get
val inputs = val inputs =
InputUtil.calcSequenceForInputs(creditingTxsInfo) InputUtil.calcSequenceForInputs(creditingTxsInfo)
@ -193,25 +183,25 @@ object PSBTGenerators {
changeSPK) changeSPK)
builder.setFinalizer(finalizer) builder.setFinalizer(finalizer)
for { val unsignedTx = builder.setFinalizer(finalizer).buildTx()
unsignedTx <- builder.setFinalizer(finalizer).buildTx()
orderedTxInfos = orderSpendingInfos(unsignedTx, creditingTxsInfo.toVector) val orderedTxInfos =
orderSpendingInfos(unsignedTx, creditingTxsInfo.toVector)
psbt <- { val psbt =
if (finalized) { if (finalized) {
PSBT.finalizedFromUnsignedTxAndInputs(unsignedTx, orderedTxInfos) PSBT.finalizedFromUnsignedTxAndInputs(unsignedTx, orderedTxInfos)
} else { } else {
PSBT.fromUnsignedTxAndInputs(unsignedTx, orderedTxInfos) PSBT.fromUnsignedTxAndInputs(unsignedTx, orderedTxInfos)
} }
}
} yield (psbt, (psbt,
FinalizedTxWithSigningInfo(unsignedTx, creditingTxsInfo.toVector), FinalizedTxWithSigningInfo(unsignedTx, creditingTxsInfo.toVector),
fee) fee)
} }
def psbtWithBuilder(finalized: Boolean)(implicit ec: ExecutionContext): Gen[ def psbtWithBuilder(
Future[(PSBT, FinalizedTxWithSigningInfo, FeeUnit)]] = { finalized: Boolean): Gen[(PSBT, FinalizedTxWithSigningInfo, FeeUnit)] = {
for { for {
(creditingTxsInfo, destinations) <- CreditingTxGen.inputsAndOutputs() (creditingTxsInfo, destinations) <- CreditingTxGen.inputsAndOutputs()
(changeSPK, _) <- ScriptGenerators.scriptPubKey (changeSPK, _) <- ScriptGenerators.scriptPubKey
@ -234,9 +224,8 @@ object PSBTGenerators {
def psbtWithBuilderAndP2SHOutputs( def psbtWithBuilderAndP2SHOutputs(
finalized: Boolean, finalized: Boolean,
outputGen: CurrencyUnit => Gen[Seq[(TransactionOutput, ScriptPubKey)]] = outputGen: CurrencyUnit => Gen[Seq[(TransactionOutput, ScriptPubKey)]] =
TransactionGenerators.smallP2SHOutputs)(implicit TransactionGenerators.smallP2SHOutputs): Gen[
ec: ExecutionContext): Gen[ (PSBT, FinalizedTxWithSigningInfo, Seq[ScriptPubKey])] = {
Future[(PSBT, FinalizedTxWithSigningInfo, Seq[ScriptPubKey])]] = {
for { for {
(creditingTxsInfo, outputs) <- (creditingTxsInfo, outputs) <-
CreditingTxGen.inputsAndP2SHOutputs(destinationGenerator = outputGen) CreditingTxGen.inputsAndP2SHOutputs(destinationGenerator = outputGen)
@ -250,34 +239,33 @@ object PSBTGenerators {
} }
fee <- FeeUnitGen.feeUnit(maxFee) fee <- FeeUnitGen.feeUnit(maxFee)
} yield { } yield {
val pAndB = psbtAndBuilderFromInputs(finalized = finalized, val p = psbtAndBuilderFromInputs(finalized = finalized,
creditingTxsInfo = creditingTxsInfo, creditingTxsInfo = creditingTxsInfo,
destinations = outputs.map(_._1), destinations = outputs.map(_._1),
changeSPK = changeSPK._1, changeSPK = changeSPK._1,
fee = fee) fee = fee)
pAndB.map(p => (p._1, p._2, outputs.map(_._2))) (p._1, p._2, outputs.map(_._2))
} }
} }
def psbtWithBuilderAndP2WSHOutputs(finalized: Boolean)(implicit def psbtWithBuilderAndP2WSHOutputs(finalized: Boolean): Gen[
ec: ExecutionContext): Gen[ (PSBT, FinalizedTxWithSigningInfo, Seq[ScriptPubKey])] =
Future[(PSBT, FinalizedTxWithSigningInfo, Seq[ScriptPubKey])]] =
psbtWithBuilderAndP2SHOutputs(finalized, psbtWithBuilderAndP2SHOutputs(finalized,
TransactionGenerators.smallP2WSHOutputs) TransactionGenerators.smallP2WSHOutputs)
def finalizedPSBTWithBuilder(implicit ec: ExecutionContext): Gen[ def finalizedPSBTWithBuilder: Gen[
Future[(PSBT, FinalizedTxWithSigningInfo, FeeUnit)]] = { (PSBT, FinalizedTxWithSigningInfo, FeeUnit)] = {
psbtWithBuilder(finalized = true) psbtWithBuilder(finalized = true)
} }
def finalizedPSBT(implicit ec: ExecutionContext): Gen[Future[PSBT]] = { def finalizedPSBT: Gen[PSBT] = {
finalizedPSBTWithBuilder.map(_.map(_._1)) finalizedPSBTWithBuilder.map(_._1)
} }
/** Generates a PSBT that is ready to be finalized but where no input map has been finalized */ /** 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]] = { def fullNonFinalizedPSBT: Gen[PSBT] = {
psbtWithBuilder(finalized = false).map(_.map(_._1)) psbtWithBuilder(finalized = false).map(_._1)
} }
def pruneGlobal(globalMap: GlobalPSBTMap): GlobalPSBTMap = { def pruneGlobal(globalMap: GlobalPSBTMap): GlobalPSBTMap = {
@ -300,9 +288,8 @@ object PSBTGenerators {
/** Generates an arbitrary unfinalized PSBT by generating a full unfinalized PSBT /** Generates an arbitrary unfinalized PSBT by generating a full unfinalized PSBT
* and randomly removing records * and randomly removing records
*/ */
def arbitraryPSBT(implicit ec: ExecutionContext): Gen[Future[PSBT]] = { def arbitraryPSBT: Gen[PSBT] = {
psbtWithUnknowns.map { psbtF => psbtWithUnknowns.map { psbt =>
psbtF.map { psbt =>
val global = psbt.globalMap val global = psbt.globalMap
val inputs = psbt.inputMaps val inputs = psbt.inputMaps
val outputs = psbt.outputMaps val outputs = psbt.outputMaps
@ -316,5 +303,4 @@ object PSBTGenerators {
PSBT(newGlobal, newInputs, newOutputs) PSBT(newGlobal, newInputs, newOutputs)
} }
} }
}
} }

View file

@ -29,8 +29,6 @@ import org.scalacheck.Gen
import scodec.bits.ByteVector import scodec.bits.ByteVector
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.DurationInt import scala.concurrent.duration.DurationInt
//TODO: Need to provide generators for [[NonStandardScriptSignature]] and [[NonStandardScriptPubKey]] //TODO: Need to provide generators for [[NonStandardScriptSignature]] and [[NonStandardScriptPubKey]]
@ -591,8 +589,9 @@ sealed abstract class ScriptGenerators {
privateKey, privateKey,
hashType hashType
) )
txSigComponentFuture = P2PKSigner.sign(spendingInfo, spendingTx, false) txSigComponent = P2PKSigner.sign(spendingInfo,
txSigComponent = Await.result(txSigComponentFuture, timeout) spendingTx,
isDummySignature = false)
//add the signature to the scriptSig instead of having an empty scriptSig //add the signature to the scriptSig instead of having an empty scriptSig
signedScriptSig = signedScriptSig =
txSigComponent.scriptSignature txSigComponent.scriptSignature
@ -628,8 +627,9 @@ sealed abstract class ScriptGenerators {
privateKey, privateKey,
hashType hashType
) )
txSigComponentFuture = P2PKHSigner.sign(spendingInfo, unsignedTx, false) txSigComponent = P2PKHSigner.sign(spendingInfo,
txSigComponent = Await.result(txSigComponentFuture, timeout) unsignedTx,
isDummySignature = false)
signedScriptSig = signedScriptSig =
txSigComponent.scriptSignature txSigComponent.scriptSignature
.asInstanceOf[P2PKHScriptSignature] .asInstanceOf[P2PKHScriptSignature]
@ -658,10 +658,9 @@ sealed abstract class ScriptGenerators {
privKey, privKey,
hashType hashType
) )
val txSigComponentF = P2PKWithTimeoutSigner.sign(spendingInfo, val txSigComponent = P2PKWithTimeoutSigner.sign(spendingInfo,
spendingTx, spendingTx,
isDummySignature = false) isDummySignature = false)
val txSigComponent = Await.result(txSigComponentF, timeout)
val signedScriptSig = val signedScriptSig =
txSigComponent.scriptSignature.asInstanceOf[ConditionalScriptSignature] txSigComponent.scriptSignature.asInstanceOf[ConditionalScriptSignature]
@ -706,9 +705,8 @@ sealed abstract class ScriptGenerators {
privateKeys.toVector, privateKeys.toVector,
hashType hashType
) )
txSigComponentFuture = txSigComponent =
MultiSigSigner.sign(spendingInfo, spendingTx, false) MultiSigSigner.sign(spendingInfo, spendingTx, isDummySignature = false)
txSigComponent = Await.result(txSigComponentFuture, timeout)
signedScriptSig = signedScriptSig =
txSigComponent.scriptSignature txSigComponent.scriptSignature
.asInstanceOf[MultiSignatureScriptSignature] .asInstanceOf[MultiSignatureScriptSignature]

View file

@ -83,8 +83,8 @@ class AddressTagIntegrationTest extends BitcoinSWalletTest {
fromAccount = account, fromAccount = account,
fromTagOpt = Some(exampleTag)) fromTagOpt = Some(exampleTag))
} }
utx <- txBuilder.buildTx() utx = txBuilder.buildTx()
signedTx <- RawTxSigner.sign(utx, utxoInfos, feeRate) signedTx = RawTxSigner.sign(utx, utxoInfos, feeRate)
_ <- wallet.processTransaction(signedTx, None) _ <- wallet.processTransaction(signedTx, None)
utxos <- wallet.listUtxos() utxos <- wallet.listUtxos()

View file

@ -250,9 +250,10 @@ class FundTransactionHandlingTest
fromTagOpt = Some(tag), fromTagOpt = Some(tag),
markAsReserved = true markAsReserved = true
) )
utx <- txBuilder.buildTx()
tx <- RawTxSigner.sign(utx, utxoInfos, feeRate)
} yield { } yield {
val utx = txBuilder.buildTx()
val tx = RawTxSigner.sign(utx, utxoInfos, feeRate)
assert(tx.inputs.forall(input => assert(tx.inputs.forall(input =>
expectedUtxos.exists(_.outPoint == input.previousOutput))) expectedUtxos.exists(_.outPoint == input.previousOutput)))
} }

View file

@ -385,9 +385,10 @@ abstract class Wallet
sentAmount: CurrencyUnit, sentAmount: CurrencyUnit,
feeRate: FeeUnit, feeRate: FeeUnit,
newTags: Vector[AddressTag]): Future[Transaction] = { newTags: Vector[AddressTag]): Future[Transaction] = {
val utx = txBuilder.buildTx()
val signed = RawTxSigner.sign(utx, utxoInfos, feeRate)
for { for {
utx <- txBuilder.buildTx()
signed <- RawTxSigner.sign(utx, utxoInfos, feeRate)
ourOuts <- findOurOuts(signed) ourOuts <- findOurOuts(signed)
creditingAmount = utxoInfos.foldLeft(CurrencyUnits.zero)(_ + _.amount) creditingAmount = utxoInfos.foldLeft(CurrencyUnits.zero)(_ + _.amount)
_ <- processOurTransaction(transaction = signed, _ <- processOurTransaction(transaction = signed,
@ -449,7 +450,7 @@ abstract class Wallet
withFinalizer = txBuilder.setFinalizer(finalizer) withFinalizer = txBuilder.setFinalizer(finalizer)
tmp <- withFinalizer.buildTx() tmp = withFinalizer.buildTx()
_ = require( _ = require(
tmp.outputs.size == 1, tmp.outputs.size == 1,
@ -786,14 +787,14 @@ abstract class Wallet
ourXpubs = accountDbs.map(_.xpub) ourXpubs = accountDbs.map(_.xpub)
utxos <- spendingInfoDAO.findAll() utxos <- spendingInfoDAO.findAll()
txs <- transactionDAO.findByTxIds(inputTxIds.keys.toVector) txs <- transactionDAO.findByTxIds(inputTxIds.keys.toVector)
} yield {
updated = txs.foldLeft(psbt) { (accum, tx) => val updated = txs.foldLeft(psbt) { (accum, tx) =>
val index = inputTxIds(tx.txIdBE) val index = inputTxIds(tx.txIdBE)
accum.addUTXOToInput(tx.transaction, index) accum.addUTXOToInput(tx.transaction, index)
} }
signed <- val signed =
FutureUtil.foldLeftAsync(updated, updated.inputMaps.zipWithIndex) { updated.inputMaps.zipWithIndex.foldLeft(updated) {
case (unsigned, (input, index)) => case (unsigned, (input, index)) =>
val xpubKeyPaths = input.BIP32DerivationPaths val xpubKeyPaths = input.BIP32DerivationPaths
.filter { path => .filter { path =>
@ -822,7 +823,7 @@ abstract class Wallet
val keyPaths = xpubKeyPaths ++ utxoPath val keyPaths = xpubKeyPaths ++ utxoPath
FutureUtil.foldLeftAsync(withData, keyPaths) { (accum, hdPath) => keyPaths.foldLeft(withData) { (accum, hdPath) =>
val sign = keyManager.toSign(hdPath) val sign = keyManager.toSign(hdPath)
// Only sign if that key doesn't have a signature yet // Only sign if that key doesn't have a signature yet
if (!input.partialSignatures.exists(_.pubKey == sign.publicKey)) { if (!input.partialSignatures.exists(_.pubKey == sign.publicKey)) {
@ -830,11 +831,11 @@ abstract class Wallet
s"Signing input $index with key ${sign.publicKey.hex}") s"Signing input $index with key ${sign.publicKey.hex}")
accum.sign(index, sign) accum.sign(index, sign)
} else { } else {
Future.successful(accum) accum
} }
} }
} }
} yield {
if (updated == signed) { if (updated == signed) {
logger.warn("Did not find any keys or utxos that belong to this wallet") logger.warn("Did not find any keys or utxos that belong to this wallet")
} }

View file

@ -40,7 +40,7 @@ trait FundTransactionHandling extends WalletLogger { self: Wallet =>
fromAccount = fromAccount, fromAccount = fromAccount,
fromTagOpt = fromTagOpt, fromTagOpt = fromTagOpt,
markAsReserved = markAsReserved) markAsReserved = markAsReserved)
.flatMap(_._1.buildTx()) .map(_._1.buildTx())
} }
/** This returns a [[RawTxBuilder]] that can be used to generate an unsigned transaction with [[RawTxBuilder.result()]] /** This returns a [[RawTxBuilder]] that can be used to generate an unsigned transaction with [[RawTxBuilder.result()]]