mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 01:40:55 +01:00
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:
parent
7aa3ccd974
commit
e6899b20b1
@ -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(
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 = {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
```
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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()
|
||||
|
@ -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)))
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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()]]
|
||||
|
Loading…
Reference in New Issue
Block a user