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

View File

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

View File

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

View File

@ -12,13 +12,13 @@ import org.bitcoins.core.wallet.builder.StandardNonInteractiveFinalizer
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
import org.bitcoins.crypto.DoubleSha256Digest
import org.bitcoins.testkitcore.gen.{CreditingTxGen, ScriptGenerators}
import org.bitcoins.testkitcore.util.BitcoinSJvmTest
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
import scala.util.Try
/** Created by chris on 2/19/16.
*/
class TransactionSignatureSerializerTest extends BitcoinSJvmTest {
class TransactionSignatureSerializerTest extends BitcoinSUnitTest {
"TransactionSignatureSerializer" must "correctly serialize an input that is being checked where another input in the same tx is using SIGHASH_ANYONECANPAY" in {
//this is from a test case inside of tx_valid.json
@ -406,18 +406,17 @@ class TransactionSignatureSerializerTest extends BitcoinSJvmTest {
}
it should "have old and new serializeForSignature functions agree" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(),
ScriptGenerators.scriptPubKey) {
forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfo, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(100))
val unsignedTxF = StandardNonInteractiveFinalizer
val spendingTx = StandardNonInteractiveFinalizer
.txFrom(outputs = destinations,
utxos = creditingTxsInfo,
feeRate = fee,
changeSPK = changeSPK)
val correctScriptsF = unsignedTxF.map { spendingTx =>
val correctScripts =
creditingTxsInfo.flatMap { signInfo =>
signInfo.signers.map { _ =>
val txSigComponent =
@ -437,25 +436,23 @@ class TransactionSignatureSerializerTest extends BitcoinSJvmTest {
oldBytes == newBytes
}
}
}
correctScriptsF.map(x => assert(x.forall(_ == true)))
assert(correctScripts.forall(_ == true))
}
}
it should "have old and new hashForSignature functions agree" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(),
ScriptGenerators.scriptPubKey) {
forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfo, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(100))
val unsignedTxF = StandardNonInteractiveFinalizer
val spendingTx = StandardNonInteractiveFinalizer
.txFrom(outputs = destinations,
utxos = creditingTxsInfo,
feeRate = fee,
changeSPK = changeSPK)
val correctHashesF = unsignedTxF.map { spendingTx =>
val correctHashes =
creditingTxsInfo.flatMap { signInfo =>
signInfo.signers.map { _ =>
val txSigComponent =
@ -480,25 +477,23 @@ class TransactionSignatureSerializerTest extends BitcoinSJvmTest {
oldHash == newHash
}
}
}
correctHashesF.map(x => assert(x.forall(_ == true)))
assert(correctHashes.forall(_ == true))
}
}
it should "have old and new calculateScriptForSigning functions agree" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(),
ScriptGenerators.scriptPubKey) {
forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfo, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(100))
val unsignedTxF = StandardNonInteractiveFinalizer
val spendingTx = StandardNonInteractiveFinalizer
.txFrom(outputs = destinations,
utxos = creditingTxsInfo,
feeRate = fee,
changeSPK = changeSPK)
val correctScriptsF = unsignedTxF.map { spendingTx =>
val correctScripts =
creditingTxsInfo.flatMap { signInfo =>
signInfo.signers.map { _ =>
val txSigComponent =
@ -518,9 +513,8 @@ class TransactionSignatureSerializerTest extends BitcoinSJvmTest {
oldScript == newScript
}
}
}
correctScriptsF.map(x => assert(x.forall(_ == true)))
assert(correctScripts.forall(_ == true))
}
}
}

View File

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

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.crypto.CryptoUtil
import org.bitcoins.testkitcore.gen._
import org.bitcoins.testkitcore.util.BitcoinSJvmTest
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
import scodec.bits._
import scala.concurrent.Future
import scala.util.{Failure, Success}
class PSBTTest extends BitcoinSJvmTest {
class PSBTTest extends BitcoinSUnitTest {
behavior of "PSBT"
@ -24,198 +23,178 @@ class PSBTTest extends BitcoinSJvmTest {
generatorDrivenConfigNewCode
it must "correctly combine PSBTs" in {
forAllAsync(PSBTGenerators.arbitraryPSBT) { psbtF =>
psbtF.map { psbt =>
val global = psbt.globalMap
val inputs = psbt.inputMaps
val outputs = psbt.outputMaps
forAll(PSBTGenerators.arbitraryPSBT) { psbt =>
val global = psbt.globalMap
val inputs = psbt.inputMaps
val outputs = psbt.outputMaps
val newGlobal = PSBTGenerators.pruneGlobal(global)
val newInputs =
inputs.map(input =>
InputPSBTMap(PSBTGenerators.pruneVec(input.elements)))
val newOutputs =
outputs.map(output =>
OutputPSBTMap(PSBTGenerators.pruneVec(output.elements)))
val newGlobal = PSBTGenerators.pruneGlobal(global)
val newInputs =
inputs.map(input =>
InputPSBTMap(PSBTGenerators.pruneVec(input.elements)))
val newOutputs =
outputs.map(output =>
OutputPSBTMap(PSBTGenerators.pruneVec(output.elements)))
val psbt1 = PSBT(newGlobal, newInputs, newOutputs)
val psbt1 = PSBT(newGlobal, newInputs, newOutputs)
val oppositeGlobalElements = global.elements.filterNot(e =>
newGlobal.elements.contains(e)) :+ global.unsignedTransaction
val oppositeGlobal = GlobalPSBTMap(oppositeGlobalElements.distinct)
val oppositeInputs = inputs.zip(newInputs).map { case (map, pruned) =>
InputPSBTMap(map.elements.filterNot(e => pruned.elements.contains(e)))
}
val oppositeOutputs =
outputs.zip(newOutputs).map { case (map, pruned) =>
OutputPSBTMap(
map.elements.filterNot(e => pruned.elements.contains(e)))
}
val psbt2 = PSBT(oppositeGlobal, oppositeInputs, oppositeOutputs)
assert(psbt1.combinePSBT(psbt2) == psbt)
assert(psbt2.combinePSBT(psbt1) == psbt)
val oppositeGlobalElements = global.elements.filterNot(e =>
newGlobal.elements.contains(e)) :+ global.unsignedTransaction
val oppositeGlobal = GlobalPSBTMap(oppositeGlobalElements.distinct)
val oppositeInputs = inputs.zip(newInputs).map { case (map, pruned) =>
InputPSBTMap(map.elements.filterNot(e => pruned.elements.contains(e)))
}
val oppositeOutputs =
outputs.zip(newOutputs).map { case (map, pruned) =>
OutputPSBTMap(
map.elements.filterNot(e => pruned.elements.contains(e)))
}
val psbt2 = PSBT(oppositeGlobal, oppositeInputs, oppositeOutputs)
assert(psbt1.combinePSBT(psbt2) == psbt)
assert(psbt2.combinePSBT(psbt1) == psbt)
}
}
it must "correctly update PSBTs' inputs" in {
forAllAsync(PSBTGenerators.psbtToBeSigned)(_.flatMap {
case (fullPsbt, utxos, _) =>
val emptyPsbt = PSBT.fromUnsignedTx(fullPsbt.transaction)
forAll(PSBTGenerators.psbtToBeSigned) { case (fullPsbt, utxos, _) =>
val emptyPsbt = PSBT.fromUnsignedTx(fullPsbt.transaction)
val infoAndTxs = PSBTGenerators.orderSpendingInfos(fullPsbt.transaction,
utxos.toVector)
val updatedPSBT =
infoAndTxs.zipWithIndex
.foldLeft(emptyPsbt) {
case (psbt, (utxo, index)) =>
val partUpdatedPsbt = psbt
.addUTXOToInput(utxo.prevTransaction, index)
.addSigHashTypeToInput(utxo.hashType, index)
(InputInfo.getRedeemScript(utxo.inputInfo),
InputInfo.getScriptWitness(utxo.inputInfo)) match {
case (Some(redeemScript), Some(scriptWitness)) =>
partUpdatedPsbt
.addRedeemOrWitnessScriptToInput(redeemScript, index)
.addScriptWitnessToInput(scriptWitness, index)
case (Some(redeemScript), None) =>
partUpdatedPsbt
.addRedeemOrWitnessScriptToInput(redeemScript, index)
case (None, Some(scriptWitness)) =>
partUpdatedPsbt
.addScriptWitnessToInput(scriptWitness, index)
case (None, None) =>
partUpdatedPsbt
}
val infoAndTxs =
PSBTGenerators.orderSpendingInfos(fullPsbt.transaction, utxos.toVector)
val updatedPSBT =
infoAndTxs.zipWithIndex
.foldLeft(emptyPsbt) { case (psbt, (utxo, index)) =>
val partUpdatedPsbt = psbt
.addUTXOToInput(utxo.prevTransaction, index)
.addSigHashTypeToInput(utxo.hashType, index)
(InputInfo.getRedeemScript(utxo.inputInfo),
InputInfo.getScriptWitness(utxo.inputInfo)) match {
case (Some(redeemScript), Some(scriptWitness)) =>
partUpdatedPsbt
.addRedeemOrWitnessScriptToInput(redeemScript, index)
.addScriptWitnessToInput(scriptWitness, index)
case (Some(redeemScript), None) =>
partUpdatedPsbt
.addRedeemOrWitnessScriptToInput(redeemScript, index)
case (None, Some(scriptWitness)) =>
partUpdatedPsbt
.addScriptWitnessToInput(scriptWitness, index)
case (None, None) =>
partUpdatedPsbt
}
assert(updatedPSBT == fullPsbt)
})
}
assert(updatedPSBT == fullPsbt)
}
}
it must "correctly construct and sign a PSBT" in {
forAllAsync(PSBTGenerators.psbtToBeSigned) { psbtWithBuilderF =>
psbtWithBuilderF.flatMap { case (psbtNoSigs, utxos, _) =>
val infos = utxos.toVector.zipWithIndex.map {
case (utxo: ScriptSignatureParams[InputInfo], index) =>
(index, utxo)
}
val signedPSBTF = infos.foldLeft(Future.successful(psbtNoSigs)) {
case (unsignedPSBTF, (index, info)) =>
unsignedPSBTF.flatMap { unsignedPSBT =>
info.toSingles.foldLeft(Future.successful(unsignedPSBT)) {
(psbtToSignF, singleInfo) =>
psbtToSignF.flatMap(
_.sign(index,
singleInfo.signer,
singleInfo.conditionalPath))
}
}
}
signedPSBTF.map { signedPSBT =>
val finalizedPsbtT = signedPSBT.finalizePSBT
finalizedPsbtT match {
case Success(finalizedPsbt) =>
val txT = finalizedPsbt.extractTransactionAndValidate
assert(txT.isSuccess, txT.failed)
case Failure(exception) => fail(exception)
forAll(PSBTGenerators.psbtToBeSigned) { case (psbtNoSigs, utxos, _) =>
val infos = utxos.toVector.zipWithIndex.map {
case (utxo: ScriptSignatureParams[InputInfo], index) =>
(index, utxo)
}
val signedPSBT = infos.foldLeft(psbtNoSigs) {
case (unsignedPSBT, (index, info)) =>
info.toSingles.foldLeft(unsignedPSBT) { (psbtToSign, singleInfo) =>
psbtToSign.sign(index,
singleInfo.signer,
singleInfo.conditionalPath)
}
}
}
val finalizedPsbtT = signedPSBT.finalizePSBT
finalizedPsbtT match {
case Success(finalizedPsbt) =>
val txT = finalizedPsbt.extractTransactionAndValidate
assert(txT.isSuccess, txT.failed)
case Failure(exception) => fail(exception)
}
}
}
it must "add Redeem Scripts to outputs" in {
forAllAsync(
PSBTGenerators.psbtWithBuilderAndP2SHOutputs(finalized = false)) {
psbtWithBuilderF =>
psbtWithBuilderF.flatMap { case (psbtEmptyOutputs, _, redeemScripts) =>
val psbtWithOutputs =
redeemScripts.zipWithIndex.foldLeft(psbtEmptyOutputs)((psbt, spk) =>
psbt.addRedeemOrWitnessScriptToOutput(spk._1, spk._2))
forAll(PSBTGenerators.psbtWithBuilderAndP2SHOutputs(finalized = false)) {
case (psbtEmptyOutputs, _, redeemScripts) =>
val psbtWithOutputs =
redeemScripts.zipWithIndex.foldLeft(psbtEmptyOutputs)((psbt, spk) =>
psbt.addRedeemOrWitnessScriptToOutput(spk._1, spk._2))
val allOutputsValid =
psbtWithOutputs.outputMaps.zip(redeemScripts).forall {
case (map, spk) =>
map.redeemScriptOpt.contains(RedeemScript(spk))
}
assert(allOutputsValid)
}
val allOutputsValid =
psbtWithOutputs.outputMaps.zip(redeemScripts).forall {
case (map, spk) =>
map.redeemScriptOpt.contains(RedeemScript(spk))
}
assert(allOutputsValid)
}
}
it must "add Witness Scripts to outputs" in {
forAllAsync(
PSBTGenerators.psbtWithBuilderAndP2WSHOutputs(finalized = false)) {
psbtWithBuilderF =>
psbtWithBuilderF.flatMap { case (psbtEmptyOutputs, _, redeemScripts) =>
val psbtWithOutputs =
redeemScripts.zipWithIndex.foldLeft(psbtEmptyOutputs)((psbt, spk) =>
psbt.addRedeemOrWitnessScriptToOutput(spk._1, spk._2))
forAll(PSBTGenerators.psbtWithBuilderAndP2WSHOutputs(finalized = false)) {
case (psbtEmptyOutputs, _, redeemScripts) =>
val psbtWithOutputs =
redeemScripts.zipWithIndex.foldLeft(psbtEmptyOutputs)((psbt, spk) =>
psbt.addRedeemOrWitnessScriptToOutput(spk._1, spk._2))
val allOutputsValid =
psbtWithOutputs.outputMaps.zip(redeemScripts).forall {
case (map, spk) =>
map.witnessScriptOpt.contains(WitnessScript(spk))
val allOutputsValid =
psbtWithOutputs.outputMaps.zip(redeemScripts).forall {
case (map, spk) =>
map.witnessScriptOpt.contains(WitnessScript(spk))
}
assert(allOutputsValid)
}
}
assert(allOutputsValid)
}
}
it must "correctly construct and finalize PSBTs from UTXOSpendingInfo" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(),
ScriptGenerators.scriptPubKey,
ChainParamsGenerator.bitcoinNetworkParams) {
forAll(CreditingTxGen.inputsAndOutputs(),
ScriptGenerators.scriptPubKey,
ChainParamsGenerator.bitcoinNetworkParams) {
case ((creditingTxsInfo, destinations), (changeSPK, _), _) =>
val crediting =
creditingTxsInfo.foldLeft(0L)(_ + _.amount.satoshis.toLong)
val spending = destinations.foldLeft(0L)(_ + _.value.satoshis.toLong)
val maxFee = crediting - spending
val fee = GenUtil.sample(FeeUnitGen.feeUnit(maxFee))
for {
(psbt, _, _) <-
PSBTGenerators.psbtAndBuilderFromInputs(finalized = false,
creditingTxsInfo =
creditingTxsInfo,
destinations = destinations,
changeSPK = changeSPK,
fee = fee)
(expected, _, _) <-
PSBTGenerators.psbtAndBuilderFromInputs(finalized = true,
creditingTxsInfo =
creditingTxsInfo,
destinations = destinations,
changeSPK = changeSPK,
fee = fee)
} yield {
val finalizedPsbtOpt = psbt.finalizePSBT
assert(finalizedPsbtOpt.isSuccess, psbt.hex)
assert(finalizedPsbtOpt.get == expected)
}
val (psbt, _, _) =
PSBTGenerators.psbtAndBuilderFromInputs(finalized = false,
creditingTxsInfo =
creditingTxsInfo,
destinations = destinations,
changeSPK = changeSPK,
fee = fee)
val (expected, _, _) =
PSBTGenerators.psbtAndBuilderFromInputs(finalized = true,
creditingTxsInfo =
creditingTxsInfo,
destinations = destinations,
changeSPK = changeSPK,
fee = fee)
val finalizedPsbtOpt = psbt.finalizePSBT
assert(finalizedPsbtOpt.isSuccess, psbt.hex)
assert(finalizedPsbtOpt.get == expected)
}
}
it must "agree with TxBuilder.sign given UTXOSpendingInfos" in {
forAllAsync(PSBTGenerators.finalizedPSBTWithBuilder) { psbtAndBuilderF =>
for {
(psbt, builder, fee) <- psbtAndBuilderF
signedTx <- builder.sign(fee)
} yield {
forAll(PSBTGenerators.finalizedPSBTWithBuilder) {
case (psbt, builder, fee) =>
val signedTx = builder.sign(fee)
val txT = psbt.extractTransactionAndValidate
assert(txT.isSuccess, txT.failed)
assert(txT.get == signedTx)
}
}
}
it must "correctly serialize RIPEMD160 hash preimages" in {
forAllAsync(StringGenerators.hexString) { hex =>
forAll(StringGenerators.hexString) { hex =>
val preimage = ByteVector.fromValidHex(hex)
val ripeMd160 = RIPEMD160PreImage(preimage)
@ -227,7 +206,7 @@ class PSBTTest extends BitcoinSJvmTest {
}
it must "correctly serialize SHA256 hash preimages" in {
forAllAsync(StringGenerators.hexString) { hex =>
forAll(StringGenerators.hexString) { hex =>
val preimage = ByteVector.fromValidHex(hex)
val sha256 = SHA256PreImage(preimage)
@ -239,7 +218,7 @@ class PSBTTest extends BitcoinSJvmTest {
}
it must "correctly serialize HASH160 hash preimages" in {
forAllAsync(StringGenerators.hexString) { hex =>
forAll(StringGenerators.hexString) { hex =>
val preimage = ByteVector.fromValidHex(hex)
val hash160 = HASH160PreImage(preimage)
@ -251,7 +230,7 @@ class PSBTTest extends BitcoinSJvmTest {
}
it must "correctly serialize HASH256 hash preimages" in {
forAllAsync(StringGenerators.hexString) { hex =>
forAll(StringGenerators.hexString) { hex =>
val preimage = ByteVector.fromValidHex(hex)
val hash256 = HASH256PreImage(preimage)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,14 +28,11 @@ import org.bitcoins.testkitcore.gen.{
ScriptGenerators,
TransactionGenerators
}
import org.bitcoins.testkitcore.util.BitcoinSJvmTest
import org.bitcoins.testkitcore.util.BitcoinSUnitTest
import scala.annotation.nowarn
import scala.concurrent.{ExecutionContext, Future}
class SignerTest extends BitcoinSJvmTest {
implicit val ec: ExecutionContext = ExecutionContext.global
class SignerTest extends BitcoinSUnitTest {
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode
@ -75,7 +72,7 @@ class SignerTest extends BitcoinSJvmTest {
.sample(CreditingTxGen.p2wpkhOutput)
.asInstanceOf[ScriptSignatureParams[P2WPKHV0InputInfo]]
val tx = GenUtil.sample(TransactionGenerators.baseTransaction)
recoverToSucceededIf[IllegalArgumentException] {
assertThrows[IllegalArgumentException] {
P2WPKHSigner.sign(dumbSpendingInfo, tx, isDummySignature = false, p2wpkh)
}
}
@ -86,116 +83,104 @@ class SignerTest extends BitcoinSJvmTest {
.sample(CreditingTxGen.p2wshOutput)
.asInstanceOf[ScriptSignatureParams[P2WSHV0InputInfo]]
val tx = GenUtil.sample(TransactionGenerators.baseTransaction)
recoverToSucceededIf[IllegalArgumentException] {
assertThrows[IllegalArgumentException] {
P2WSHSigner.sign(dumbSpendingInfo, tx, isDummySignature = false, p2wsh)
}
}
it must "sign a mix of spks in a tx and then verify that single signing agrees" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(),
ScriptGenerators.scriptPubKey) {
forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfos, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(1000))
for {
unsignedTx <-
StandardNonInteractiveFinalizer.txFrom(destinations,
creditingTxsInfos,
fee,
changeSPK)
signedTx <-
RawTxSigner.sign(unsignedTx, creditingTxsInfos.toVector, fee)
val unsignedTx =
StandardNonInteractiveFinalizer.txFrom(destinations,
creditingTxsInfos,
fee,
changeSPK)
singleSigs: Vector[Vector[ECDigitalSignature]] <- {
val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] =
creditingTxsInfos.toVector.map(_.toSingles)
val sigVecFs = singleInfosVec.map { singleInfos =>
val sigFs = singleInfos.map { singleInfo =>
val keyAndSigF =
BitcoinSigner.signSingle(singleInfo,
unsignedTx,
isDummySignature = false)
val signedTx =
RawTxSigner.sign(unsignedTx, creditingTxsInfos.toVector, fee)
keyAndSigF.map(_.signature)
val singleSigs: Vector[Vector[ECDigitalSignature]] = {
val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] =
creditingTxsInfos.toVector.map(_.toSingles)
singleInfosVec.map { singleInfos =>
singleInfos.map { singleInfo =>
val keyAndSig =
BitcoinSigner.signSingle(singleInfo,
unsignedTx,
isDummySignature = false)
keyAndSig.signature
}
}
}
signedTx.inputs.zipWithIndex.foreach { case (input, inputIndex) =>
val infoAndIndexOpt = creditingTxsInfos.zipWithIndex
.find(_._1.outPoint == input.previousOutput)
assert(infoAndIndexOpt.isDefined)
val (info, index) = infoAndIndexOpt.get
val sigs = singleSigs(index)
val expectedSigs =
if (InputInfo.getScriptWitness(info.inputInfo).isEmpty) {
input.scriptSignature.signatures
} else {
signedTx
.asInstanceOf[WitnessTransaction]
.witness
.witnesses(inputIndex) match {
case p2wpkh: P2WPKHWitnessV0 => Vector(p2wpkh.signature)
case p2wsh: P2WSHWitnessV0 => p2wsh.signatures
case EmptyScriptWitness => Vector.empty
}
Future.sequence(sigFs)
}
Future.sequence(sigVecFs)
}
} yield {
signedTx.inputs.zipWithIndex.foreach { case (input, inputIndex) =>
val infoAndIndexOpt = creditingTxsInfos.zipWithIndex
.find(_._1.outPoint == input.previousOutput)
assert(infoAndIndexOpt.isDefined)
val (info, index) = infoAndIndexOpt.get
val sigs = singleSigs(index)
val expectedSigs =
if (InputInfo.getScriptWitness(info.inputInfo).isEmpty) {
input.scriptSignature.signatures
} else {
signedTx
.asInstanceOf[WitnessTransaction]
.witness
.witnesses(inputIndex) match {
case p2wpkh: P2WPKHWitnessV0 => Vector(p2wpkh.signature)
case p2wsh: P2WSHWitnessV0 => p2wsh.signatures
case EmptyScriptWitness => Vector.empty
}
}
assert(sigs.length == expectedSigs.length)
assert(sigs.forall(expectedSigs.contains))
}
succeed
assert(sigs.length == expectedSigs.length)
assert(sigs.forall(expectedSigs.contains))
}
succeed
}
}
it should "have old and new doSign functions agree" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(),
ScriptGenerators.scriptPubKey) {
forAll(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfo, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(100))
val unsignedTxF = StandardNonInteractiveFinalizer
val spendingTx = StandardNonInteractiveFinalizer
.txFrom(outputs = destinations,
utxos = creditingTxsInfo,
feeRate = fee,
changeSPK = changeSPK)
val correctSigsF = unsignedTxF.flatMap { spendingTx =>
val assertFs = creditingTxsInfo.flatMap { signInfo =>
val correctSigs =
creditingTxsInfo.flatMap { signInfo =>
signInfo.signers.map { signer =>
val txSignatureComponent =
TxSigComponent(signInfo.inputInfo, spendingTx)
@nowarn val oldSigF = BitcoinSigner.doSign(txSignatureComponent,
signer.signFunction,
signInfo.hashType,
isDummySignature =
false)
for {
oldSig <- oldSigF
newSig <- BitcoinSigner.doSign(spendingTx,
signInfo,
signer.signFunction,
signInfo.hashType,
isDummySignature = false)
} yield {
(oldSig.r == newSig.r) &&
(oldSig.s == newSig.s) &&
(oldSig.hex == newSig.hex)
}
@nowarn val oldSig = BitcoinSigner.doSign(txSignatureComponent,
signer.sign,
signInfo.hashType,
isDummySignature =
false)
val newSig = BitcoinSigner.doSign(spendingTx,
signInfo,
signer.sign,
signInfo.hashType,
isDummySignature = false)
(oldSig.r == newSig.r) &&
(oldSig.s == newSig.s) &&
(oldSig.hex == newSig.hex)
}
}
Future.sequence(assertFs)
}
correctSigsF.map(x => assert(x.forall(_ == true)))
assert(correctSigs.forall(_ == true))
}
}
@ -270,58 +255,51 @@ class SignerTest extends BitcoinSJvmTest {
}
it must "sign p2wsh inputs correctly when provided no witness data" in {
forAllAsync(CreditingTxGen.inputsAndOutputs(CreditingTxGen.p2wshOutputs),
ScriptGenerators.scriptPubKey) {
forAll(CreditingTxGen.inputsAndOutputs(CreditingTxGen.p2wshOutputs),
ScriptGenerators.scriptPubKey) {
case ((creditingTxsInfos, destinations), (changeSPK, _)) =>
val fee = SatoshisPerVirtualByte(Satoshis(100))
for {
unsignedTx <-
StandardNonInteractiveFinalizer.txFrom(destinations,
creditingTxsInfos,
fee,
changeSPK)
val unsignedTx =
StandardNonInteractiveFinalizer.txFrom(destinations,
creditingTxsInfos,
fee,
changeSPK)
singleSigs: Vector[Vector[PartialSignature]] <- {
val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] =
creditingTxsInfos.toVector.map(_.toSingles)
val sigVecFs = singleInfosVec.map { singleInfos =>
val sigFs = singleInfos.map { singleInfo =>
val wtx =
WitnessTransaction(unsignedTx.version,
unsignedTx.inputs,
unsignedTx.outputs,
unsignedTx.lockTime,
EmptyWitness.fromInputs(unsignedTx.inputs))
BitcoinSigner.signSingle(singleInfo,
wtx,
isDummySignature = false)
val singleSigs: Vector[Vector[PartialSignature]] = {
val singleInfosVec: Vector[Vector[ECSignatureParams[InputInfo]]] =
creditingTxsInfos.toVector.map(_.toSingles)
singleInfosVec.map { singleInfos =>
singleInfos.map { singleInfo =>
val wtx =
WitnessTransaction(unsignedTx.version,
unsignedTx.inputs,
unsignedTx.outputs,
unsignedTx.lockTime,
EmptyWitness.fromInputs(unsignedTx.inputs))
BitcoinSigner.signSingle(singleInfo,
wtx,
isDummySignature = false)
}
Future.sequence(sigFs)
}
Future.sequence(sigVecFs)
}
} yield {
val psbt =
creditingTxsInfos.foldLeft(PSBT.fromUnsignedTx(unsignedTx)) {
(psbt, spendInfo) =>
val idx = inputIndex(spendInfo, unsignedTx)
psbt
.addUTXOToInput(spendInfo.prevTransaction, idx)
.addScriptWitnessToInput(
InputInfo.getScriptWitness(spendInfo.inputInfo).get,
idx)
.addSignatures(singleSigs(idx), idx)
}
val signedTx = psbt.finalizePSBT.get.extractTransactionAndValidate
assert(verifyScripts(signedTx.get, creditingTxsInfos.toVector))
}
val psbt =
creditingTxsInfos.foldLeft(PSBT.fromUnsignedTx(unsignedTx)) {
(psbt, spendInfo) =>
val idx = inputIndex(spendInfo, unsignedTx)
psbt
.addUTXOToInput(spendInfo.prevTransaction, idx)
.addScriptWitnessToInput(
InputInfo.getScriptWitness(spendInfo.inputInfo).get,
idx)
.addSignatures(singleSigs(idx), idx)
}
val signedTx = psbt.finalizePSBT.get.extractTransactionAndValidate
assert(verifyScripts(signedTx.get, creditingTxsInfos.toVector))
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,6 @@ import org.bitcoins.crypto._
import scodec.bits.ByteVector
import scala.annotation.tailrec
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try}
sealed trait PSBTMap[+RecordType <: PSBTRecord] extends NetworkElement {
@ -839,36 +838,33 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
*/
def finalizedFromSpendingInfo(
spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction)(implicit
ec: ExecutionContext): Future[InputPSBTMap] = {
val sigComponentF = BitcoinSigner
unsignedTx: Transaction): InputPSBTMap = {
val sigComponent = BitcoinSigner
.sign(spendingInfo, unsignedTx, isDummySignature = false)
sigComponentF.map { sigComponent =>
val utxos = spendingInfo.inputInfo match {
case _: UnassignedSegwitNativeInputInfo =>
Vector(WitnessUTXO(spendingInfo.output))
case _: RawInputInfo | _: P2SHNonSegwitInputInfo |
_: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo =>
Vector(NonWitnessOrUnknownUTXO(spendingInfo.prevTransaction))
}
val utxos = spendingInfo.inputInfo match {
case _: UnassignedSegwitNativeInputInfo =>
Vector(WitnessUTXO(spendingInfo.output))
case _: RawInputInfo | _: P2SHNonSegwitInputInfo |
_: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo =>
Vector(NonWitnessOrUnknownUTXO(spendingInfo.prevTransaction))
}
val scriptSig =
FinalizedScriptSig(sigComponent.scriptSignature)
sigComponent.transaction match {
case _: NonWitnessTransaction =>
InputPSBTMap(utxos ++ Vector(scriptSig))
case wtx: WitnessTransaction =>
val witness = wtx.witness(sigComponent.inputIndex.toInt)
val scriptWitness = FinalizedScriptWitness(witness)
val finalizedSigs =
if (witness != EmptyScriptWitness) {
Vector(scriptSig, scriptWitness)
} else {
Vector(scriptSig)
}
InputPSBTMap(utxos ++ finalizedSigs)
}
val scriptSig =
FinalizedScriptSig(sigComponent.scriptSignature)
sigComponent.transaction match {
case _: NonWitnessTransaction =>
InputPSBTMap(utxos ++ Vector(scriptSig))
case wtx: WitnessTransaction =>
val witness = wtx.witness(sigComponent.inputIndex.toInt)
val scriptWitness = FinalizedScriptWitness(witness)
val finalizedSigs =
if (witness != EmptyScriptWitness) {
Vector(scriptSig, scriptWitness)
} else {
Vector(scriptSig)
}
InputPSBTMap(utxos ++ finalizedSigs)
}
}
@ -878,53 +874,48 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] {
*/
def fromUTXOInfo(
spendingInfo: ScriptSignatureParams[InputInfo],
unsignedTx: Transaction)(implicit
ec: ExecutionContext): Future[InputPSBTMap] = {
val sigsF = spendingInfo.toSingles.map { spendingInfoSingle =>
unsignedTx: Transaction): InputPSBTMap = {
val sigs = spendingInfo.toSingles.map { spendingInfoSingle =>
BitcoinSigner.signSingle(spendingInfoSingle,
unsignedTx,
isDummySignature = false)
}
val sigFs = Future.sequence(sigsF)
val builder = Vector.newBuilder[InputPSBTRecord]
sigFs.map { sigs =>
val builder = Vector.newBuilder[InputPSBTRecord]
spendingInfo.inputInfo match {
case _: UnassignedSegwitNativeInputInfo =>
builder.+=(WitnessUTXO(spendingInfo.output))
case _: RawInputInfo | _: P2SHNonSegwitInputInfo |
_: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo =>
builder.+=(NonWitnessOrUnknownUTXO(spendingInfo.prevTransaction))
}
builder.++=(sigs)
val sigHashType = SigHashType(spendingInfo.hashType)
builder.+=(sigHashType)
spendingInfo.inputInfo match {
case p2sh: P2SHNonSegwitInputInfo =>
builder.+=(RedeemScript(p2sh.redeemScript))
case p2sh: P2SHNestedSegwitV0InputInfo =>
builder.+=(RedeemScript(p2sh.redeemScript))
p2sh.scriptWitness match {
case p2wsh: P2WSHWitnessV0 =>
builder.+=(WitnessScript(p2wsh.redeemScript))
case _: P2WPKHWitnessV0 => ()
}
case p2wsh: P2WSHV0InputInfo =>
builder.+=(WitnessScript(p2wsh.scriptWitness.redeemScript))
case _: RawInputInfo | _: P2WPKHV0InputInfo |
_: UnassignedSegwitNativeInputInfo =>
()
}
val inputMap = InputPSBTMap(builder.result())
inputMap
spendingInfo.inputInfo match {
case _: UnassignedSegwitNativeInputInfo =>
builder.+=(WitnessUTXO(spendingInfo.output))
case _: RawInputInfo | _: P2SHNonSegwitInputInfo |
_: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo =>
builder.+=(NonWitnessOrUnknownUTXO(spendingInfo.prevTransaction))
}
builder.++=(sigs)
val sigHashType = SigHashType(spendingInfo.hashType)
builder.+=(sigHashType)
spendingInfo.inputInfo match {
case p2sh: P2SHNonSegwitInputInfo =>
builder.+=(RedeemScript(p2sh.redeemScript))
case p2sh: P2SHNestedSegwitV0InputInfo =>
builder.+=(RedeemScript(p2sh.redeemScript))
p2sh.scriptWitness match {
case p2wsh: P2WSHWitnessV0 =>
builder.+=(WitnessScript(p2wsh.redeemScript))
case _: P2WPKHWitnessV0 => ()
}
case p2wsh: P2WSHV0InputInfo =>
builder.+=(WitnessScript(p2wsh.scriptWitness.redeemScript))
case _: RawInputInfo | _: P2WPKHV0InputInfo |
_: UnassignedSegwitNativeInputInfo =>
()
}
val inputMap = InputPSBTMap(builder.result())
inputMap
}
override def constructMap(elements: Vector[InputPSBTRecord]): InputPSBTMap =

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

View File

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

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
package org.bitcoins.crypto
class SignWithEntropyTest extends BitcoinSCryptoAsyncTest {
class SignWithEntropyTest extends BitcoinSCryptoTest {
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
generatorDrivenConfigNewCode
@ -13,31 +13,26 @@ class SignWithEntropyTest extends BitcoinSCryptoAsyncTest {
behavior of "SignWithEntropy"
it must "sign arbitrary data correctly with low R values" in {
forAllAsync(CryptoGenerators.sha256Digest) { hash =>
forAll(CryptoGenerators.sha256Digest) { hash =>
val bytes = hash.bytes
for {
sig1 <- privKey.signLowRFuture(bytes)
sig2 <- privKey.signLowRFuture(bytes) // Check for determinism
} yield {
assert(pubKey.verify(bytes, sig1))
assert(
sig1.bytes.length <= 70
) // This assertion fails if Low R is not used
assert(sig1.bytes == sig2.bytes)
assert(sig1 == sig2)
}
val sig1 = privKey.signLowR(bytes)
val sig2 = privKey.signLowR(bytes) // Check for determinism
assert(pubKey.verify(bytes, sig1))
assert(
sig1.bytes.length <= 70
) // This assertion fails if Low R is not used
assert(sig1.bytes == sig2.bytes)
assert(sig1 == sig2)
}
}
it must "sign arbitrary pieces of data with arbitrary entropy correctly" in {
forAllAsync(CryptoGenerators.sha256Digest, CryptoGenerators.sha256Digest) {
forAll(CryptoGenerators.sha256Digest, CryptoGenerators.sha256Digest) {
case (hash, entropy) =>
val sigF = privKey.signWithEntropyFunction(hash.bytes, entropy.bytes)
val sig = privKey.signWithEntropy(hash.bytes, entropy.bytes)
sigF.map { sig =>
assert(pubKey.verify(hash.bytes, sig))
}
assert(pubKey.verify(hash.bytes, sig))
}
}
}

View File

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

View File

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

View File

@ -2,8 +2,8 @@ package org.bitcoins.crypto
import scodec.bits.ByteVector
import scala.concurrent.duration.DurationInt
import scala.concurrent.{Await, ExecutionContext, Future}
import scala.annotation.tailrec
import scala.concurrent.{ExecutionContext, Future}
/** This is meant to be an abstraction for a [[org.bitcoins.crypto.ECPrivateKey]], sometimes we will not
* have direct access to a private key in memory -- for instance if that key is on a hardware device -- so we need to create an
@ -17,11 +17,8 @@ import scala.concurrent.{Await, ExecutionContext, Future}
* If you have a hardware wallet, you will need to implement the protocol to send a message to the hardware device. The
* type signature of the function you implement must be scodec.bits.ByteVector => Future[ECDigitalSignature]
*/
trait Sign {
def signFunction: ByteVector => Future[ECDigitalSignature]
def signFuture(bytes: ByteVector): Future[ECDigitalSignature] =
signFunction(bytes)
trait AsyncSign {
def asyncSign(bytes: ByteVector): Future[ECDigitalSignature]
/** Note that using this function to generate digital signatures with specific
* properties (by trying a bunch of entropy values) can reduce privacy as it will
@ -32,88 +29,70 @@ trait Sign {
* In short, ALL USES OF THIS FUNCTION THAT SIGN THE SAME DATA WITH DIFFERENT ENTROPY
* HAVE THE POTENTIAL TO CAUSE REDUCTIONS IN SECURITY AND PRIVACY, BEWARE!
*/
def signWithEntropyFunction: (
ByteVector,
ByteVector) => Future[ECDigitalSignature]
/** Note that using this function to generate digital signatures with specific
* properties (by trying a bunch of entropy values) can reduce privacy as it will
* fingerprint your wallet. Additionally it could lead to a loss of entropy in
* the resulting nonce should the property you are interested in cause a constraint
* on the input space.
*
* In short, ALL USES OF THIS FUNCTION THAT SIGN THE SAME DATA WITH DIFFERENT ENTROPY
* HAVE THE POTENTIAL TO CAUSE REDUCTIONS IN SECURITY AND PRIVACY, BEWARE!
*/
def signWithEntropyFuture(
def asyncSignWithEntropy(
bytes: ByteVector,
entropy: ByteVector): Future[ECDigitalSignature] =
signWithEntropyFunction(bytes, entropy)
entropy: ByteVector): Future[ECDigitalSignature]
private def signLowRFuture(bytes: ByteVector, startAt: Long)(implicit
private def asyncSignLowR(bytes: ByteVector, startAt: Long)(implicit
ec: ExecutionContext): Future[ECDigitalSignature] = {
val sigF: Future[ECDigitalSignature] = if (startAt == 0) {
signFunction(bytes)
} else {
val sigF = if (startAt == 0) { // On first try, use normal signing
asyncSign(bytes)
} else { // Subsequently, use additional entropy
val startBytes = ByteVector.fromLong(startAt).padLeft(32).reverse
signWithEntropyFunction(bytes, startBytes)
asyncSignWithEntropy(bytes, startBytes)
}
sigF.flatMap { sig =>
if (sig.bytes.length <= 70) {
Future.successful(sig)
} else {
signLowRFuture(bytes, startAt + 1)
asyncSignLowR(bytes, startAt + 1)
}
}
}
def signLowRFuture(bytes: ByteVector)(implicit
def asyncSignLowR(bytes: ByteVector)(implicit
ec: ExecutionContext): Future[ECDigitalSignature] = {
signLowRFuture(bytes, startAt = 0)
}
def sign(bytes: ByteVector): ECDigitalSignature = {
Await.result(signFuture(bytes), 30.seconds)
}
def signLowR(bytes: ByteVector)(implicit
ec: ExecutionContext): ECDigitalSignature = {
Await.result(signLowRFuture(bytes), 30.seconds)
}
def signWithEntropy(
bytes: ByteVector,
entropy: ByteVector): ECDigitalSignature = {
Await.result(signWithEntropyFuture(bytes, entropy), 30.seconds)
asyncSignLowR(bytes, startAt = 0)
}
def publicKey: ECPublicKey
}
object Sign {
object AsyncSign {
private case class SignImpl(
signFunction: ByteVector => Future[ECDigitalSignature],
signWithEntropyFunction: (
private case class AsyncSignImpl(
asyncSignFunction: ByteVector => Future[ECDigitalSignature],
asyncSignWithEntropyFunction: (
ByteVector,
ByteVector) => Future[ECDigitalSignature],
publicKey: ECPublicKey)
extends Sign
override val publicKey: ECPublicKey)
extends AsyncSign {
def apply(
signFunction: ByteVector => Future[ECDigitalSignature],
signWithEntropyFunction: (
ByteVector,
ByteVector) => Future[ECDigitalSignature],
pubKey: ECPublicKey): Sign = {
SignImpl(signFunction, signWithEntropyFunction, pubKey)
override def asyncSign(bytes: ByteVector): Future[ECDigitalSignature] = {
asyncSignFunction(bytes)
}
override def asyncSignWithEntropy(
bytes: ByteVector,
entropy: ByteVector): Future[ECDigitalSignature] = {
asyncSignWithEntropyFunction(bytes, entropy)
}
}
def constant(sig: ECDigitalSignature, pubKey: ECPublicKey): Sign = {
SignImpl(_ => Future.successful(sig),
(_, _) => Future.successful(sig),
pubKey)
def apply(
asyncSign: ByteVector => Future[ECDigitalSignature],
asyncSignWithEntropy: (
ByteVector,
ByteVector) => Future[ECDigitalSignature],
pubKey: ECPublicKey): AsyncSign = {
AsyncSignImpl(asyncSign, asyncSignWithEntropy, pubKey)
}
def constant(sig: ECDigitalSignature, pubKey: ECPublicKey): AsyncSign = {
AsyncSignImpl(_ => Future.successful(sig),
(_, _) => Future.successful(sig),
pubKey)
}
/** This dummySign function is useful for the case where we do not have the
@ -123,6 +102,93 @@ object Sign {
* the public key is still useful here though because it can be used to match against
* a specific private key on another server
*/
def dummySign(publicKey: ECPublicKey): AsyncSign = {
constant(EmptyDigitalSignature, publicKey)
}
}
trait Sign extends AsyncSign {
def sign(bytes: ByteVector): ECDigitalSignature
override def asyncSign(bytes: ByteVector): Future[ECDigitalSignature] = {
Future.successful(sign(bytes))
}
/** Note that using this function to generate digital signatures with specific
* properties (by trying a bunch of entropy values) can reduce privacy as it will
* fingerprint your wallet. Additionally it could lead to a loss of entropy in
* the resulting nonce should the property you are interested in cause a constraint
* on the input space.
*
* In short, ALL USES OF THIS FUNCTION THAT SIGN THE SAME DATA WITH DIFFERENT ENTROPY
* HAVE THE POTENTIAL TO CAUSE REDUCTIONS IN SECURITY AND PRIVACY, BEWARE!
*/
def signWithEntropy(
bytes: ByteVector,
entropy: ByteVector): ECDigitalSignature
override def asyncSignWithEntropy(
bytes: ByteVector,
entropy: ByteVector): Future[ECDigitalSignature] = {
Future.successful(signWithEntropy(bytes, entropy))
}
@tailrec
private def signLowR(bytes: ByteVector, startAt: Long): ECDigitalSignature = {
val sig = if (startAt == 0) { // On first try, use normal signing
sign(bytes)
} else { // Subsequently, use additional entropy
val startBytes = ByteVector.fromLong(startAt).padLeft(32).reverse
signWithEntropy(bytes, startBytes)
}
if (sig.bytes.length <= 70) {
sig
} else {
signLowR(bytes, startAt + 1)
}
}
def signLowR(bytes: ByteVector): ECDigitalSignature = {
signLowR(bytes, startAt = 0)
}
override def asyncSignLowR(bytes: ByteVector)(implicit
ec: ExecutionContext): Future[ECDigitalSignature] = {
Future.successful(signLowR(bytes))
}
}
object Sign {
private case class SignImpl(
signFunction: ByteVector => ECDigitalSignature,
signWithEntropyFunction: (ByteVector, ByteVector) => ECDigitalSignature,
override val publicKey: ECPublicKey)
extends Sign {
override def sign(bytes: ByteVector): ECDigitalSignature = {
signFunction(bytes)
}
override def signWithEntropy(
bytes: ByteVector,
entropy: ByteVector): ECDigitalSignature = {
signWithEntropyFunction(bytes, entropy)
}
}
def apply(
sign: ByteVector => ECDigitalSignature,
signWithEntropy: (ByteVector, ByteVector) => ECDigitalSignature,
pubKey: ECPublicKey): Sign = {
SignImpl(sign, signWithEntropy, pubKey)
}
def constant(sig: ECDigitalSignature, pubKey: ECPublicKey): Sign = {
SignImpl(_ => sig, (_, _) => sig, pubKey)
}
def dummySign(publicKey: ECPublicKey): Sign = {
constant(EmptyDigitalSignature, publicKey)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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