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