From 1957b0508c5153fdb0948c2afa7518a8f413bc2e Mon Sep 17 00:00:00 2001 From: Ben Carman Date: Thu, 25 Jun 2020 16:10:36 -0500 Subject: [PATCH] Rework signing logic to take full funding transaction (#1560) --- .../TransactionSignatureCreatorTest.scala | 91 ++++++- .../TransactionSignatureSerializerTest.scala | 122 +++++++++ .../core/psbt/PSBTSerializerTest.scala | 17 +- .../org/bitcoins/core/psbt/PSBTTest.scala | 15 +- .../org/bitcoins/core/psbt/PSBTUnitTest.scala | 78 +++--- .../core/wallet/builder/RawTxSignerTest.scala | 51 ++-- .../StandardNonInteractiveFinalizerTest.scala | 4 + .../core/wallet/signer/SignerTest.scala | 86 ++++--- .../core/wallet/utxo/InputInfoTest.scala | 10 +- .../wallet/utxo/InputSigningInfoTest.scala | 111 +++++++-- .../crypto/TransactionSignatureCreator.scala | 72 ++++++ .../TransactionSignatureSerializer.scala | 231 +++++++++++++++++- .../bitcoins/core/crypto/TxSigComponent.scala | 124 ++++++++++ .../protocol/script/ScriptSignature.scala | 4 +- .../core/protocol/transaction/TxUtil.scala | 77 ++++-- .../scala/org/bitcoins/core/psbt/PSBT.scala | 141 +++-------- .../org/bitcoins/core/psbt/PSBTMap.scala | 181 +++++++++----- .../org/bitcoins/core/psbt/PSBTRecord.scala | 13 +- .../core/util/BitcoinScriptUtil.scala | 88 ++++++- .../core/wallet/builder/RawTxSigner.scala | 8 - .../bitcoins/core/wallet/signer/Signer.scala | 87 +++---- .../core/wallet/utxo/ConditionalPath.scala | 21 ++ .../bitcoins/core/wallet/utxo/InputInfo.scala | 70 +++++- .../core/wallet/utxo/InputSigningInfo.scala | 44 +++- docs/core/psbts.md | 1 + docs/core/txbuilder.md | 1 + .../testkit/core/gen/CreditingTxGen.scala | 57 ++++- .../testkit/core/gen/PSBTGenerators.scala | 34 +-- .../testkit/core/gen/ScriptGenerators.scala | 15 +- .../scala/org/bitcoins/wallet/Wallet.scala | 8 +- .../internal/FundTransactionHandling.scala | 34 +-- .../wallet/models/SpendingInfoDb.scala | 11 +- .../wallet/models/TransactionDAO.scala | 7 +- 33 files changed, 1434 insertions(+), 480 deletions(-) diff --git a/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala b/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala index 7b3780ec32..d47ccff362 100644 --- a/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureCreatorTest.scala @@ -1,6 +1,6 @@ package org.bitcoins.core.crypto -import org.bitcoins.core.currency.CurrencyUnits +import org.bitcoins.core.currency._ import org.bitcoins.core.number.UInt32 import org.bitcoins.core.policy.Policy import org.bitcoins.core.protocol.script._ @@ -9,11 +9,15 @@ import org.bitcoins.core.script.PreExecutionScriptProgram import org.bitcoins.core.script.crypto.HashType import org.bitcoins.core.script.interpreter.ScriptInterpreter import org.bitcoins.core.script.result.ScriptOk +import org.bitcoins.core.wallet.builder.StandardNonInteractiveFinalizer +import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte +import org.bitcoins.core.wallet.utxo.{ECSignatureParams, P2PKHInputInfo} import org.bitcoins.crypto.{ ECDigitalSignature, ECPrivateKey, EmptyDigitalSignature } +import org.bitcoins.testkit.core.gen.{CreditingTxGen, ScriptGenerators} import org.bitcoins.testkit.util.{BitcoinSAsyncTest, TransactionTestUtil} import scodec.bits.ByteVector @@ -33,13 +37,15 @@ class TransactionSignatureCreatorTest extends BitcoinSAsyncTest { val rawTx = "01000000021d50bf7c05b6169ea8d8fb5b79dd2978bbd2ac756a656a777279da43b19fd9d9000000006b4830450221008f2c818a55045a1c9dcda54fcd5b6377f5d09723a9ccd8c71df76ee4bdf7c16802201817cbd71d8148a5d53b11d33c9c58ad1086fe7ddf308da2a7cceb7d85df293e01210381c82dc267a958be06f1c920dc635bcd191d698c167e67a45a882a551c57ce1dfeffffffd4a6a37abfe003a9d10155df215e662f88d5b878b908d1a3772a9fbd195d008d010000006a4730440220357864ae2beba3d6ec34c0ce42262c1c12939502f0f8f4bd338c9d8b307593420220656687c327589dc3e464700fa7b784c7efc2b465c627a60c2f1ce402d05fc39d0121036301d848aec3dfc47789a63ee3c85c6d3bf757162ef77cb1580981b422838ed7feffffff0200e1f505000000001976a9146d39bac171d0bf450698fa0ebd93f51e79dcb6ac88ac35a96d00000000001976a914e11753f499ac7a910148e53156ab273557ed517e88acd6090b00" val transaction = Transaction(rawTx) + val prevTransaction = Transaction( + "01000000018b7dde71a52d40b9f3a03e604eceda72e26e9be2ce17a09ae540d15eddac6d36000000006a473044022069d7be9fcdbc846ff8b24fedf6aeabb1619e3087b4e18b2bb0f8dc174b5a29dc022033fbcb40a9e834ebb051f4a864e00de420ed14b954897280b5f7399971a14b6d012102f81ce897b559c07724a5a52a0e4650f2f43bbf1357f0c1e4c8c238899e9d7523feffffff02dc4c6d0a000000001976a914c23317eae4fcd12cbbae7784fbabbbb6448b4e3d88ac80969800000000001976a914d7b4717a934386601ac3f980d01b48c83b8a0b4b88acf0b60a00") val scriptPubKey = ScriptPubKey("1976a914d7b4717a934386601ac3f980d01b48c83b8a0b4b88ac") val txSignatureComponent = BaseTxSigComponent(transaction = transaction, inputIndex = UInt32.one, output = - TransactionOutput(CurrencyUnits.zero, scriptPubKey), + TransactionOutput(10000000.sats, scriptPubKey), Policy.standardScriptVerifyFlags) val privateKey = ECPrivateKeyUtil.fromWIFToPrivateKey( "cTPg4Zc5Jis2EZXy3NXShgbn487GWBTapbU63BerLDZM3w2hQSjC") @@ -52,20 +58,21 @@ class TransactionSignatureCreatorTest extends BitcoinSAsyncTest { txSignature.hex must be(expectedSig.hex) } - it must "create the correct digital signature for a transaction with 1 input" in { + it must "create the correct digital signature for a transaction with 1 input using a TxSignatureComponent" in { //66f48fa8ef5db20a3b4be6b13f024b6e23480fd83df26ffbe7449110b113a665 on testnet val expectedSig = ECDigitalSignature( "3044022075b4ab08ff34799ee6f8048a5044be98dff493fc5a0b8a36dcaee3bd7a9993ae02207bc532ceab09c10f1d54035d03ff9aad0e1004c3e0325a8b97b6be04b7d6c3a201") val rawTx = "0100000001b8a1278696acfa85f1f576836aa30d335207b69bdaff43d9464cc1db40fe19ae000000006a473044022075b4ab08ff34799ee6f8048a5044be98dff493fc5a0b8a36dcaee3bd7a9993ae02207bc532ceab09c10f1d54035d03ff9aad0e1004c3e0325a8b97b6be04b7d6c3a2012102a01aaa27b468ec3fb2ae0c2a9fa1d5dce9b79b35062178f479156d8daa6c0e50feffffff02a0860100000000001976a914775bd9c79a9e988c0d6177a9205a611a50b7229188acb6342900000000001976a914f23a46f930320ab3cc7ad8c1660325f4c434d11688ac63b70d00" val transaction = Transaction(rawTx) + val prevTransaction = Transaction( + "0100000001e4dbac0d73f4e3a9e99e70596a5f81b35a75f95b0474d051fbfd9dc249a5b67e000000006a4730440220486f112aee12997f6e484754d53d5c2158c18cc6d1d3f13aefcdf0ed19c47b290220136133d934d9e79a57408166c39fbce38e217ea9d417cabc20744134f04f06960121021f8cb5c3d611cf24dd665adff3fd540e4c155a05adaa6b672bfa7897c126d9b6feffffff0293d22a00000000001976a914cd0385f813ec73f8fc340b7069daf566878a0d6b88ac40420f000000000017a91480f7a6c14a8407da3546b4abfc3086876ca9a0668700000000") val scriptPubKey = ScriptPubKey("1976a914cd0385f813ec73f8fc340b7069daf566878a0d6b88ac") val txSignatureComponent = BaseTxSigComponent(transaction = transaction, inputIndex = UInt32.zero, - output = - TransactionOutput(CurrencyUnits.zero, scriptPubKey), + output = TransactionOutput(2806419.sats, scriptPubKey), Policy.standardScriptVerifyFlags) val privateKey = ECPrivateKeyUtil.fromWIFToPrivateKey( "cTTh7jNtZhg3vHTjvYK8zcHkLfsMAS8iqL7pfZ6eVAVHHF8fN1qy") @@ -78,6 +85,80 @@ class TransactionSignatureCreatorTest extends BitcoinSAsyncTest { txSignature.hex must be(expectedSig.hex) } + it must "create the correct digital signature for a transaction with 1 input" in { + //66f48fa8ef5db20a3b4be6b13f024b6e23480fd83df26ffbe7449110b113a665 on testnet + val expectedSig = ECDigitalSignature( + "3044022075b4ab08ff34799ee6f8048a5044be98dff493fc5a0b8a36dcaee3bd7a9993ae02207bc532ceab09c10f1d54035d03ff9aad0e1004c3e0325a8b97b6be04b7d6c3a201") + val rawTx = + "0100000001b8a1278696acfa85f1f576836aa30d335207b69bdaff43d9464cc1db40fe19ae000000006a473044022075b4ab08ff34799ee6f8048a5044be98dff493fc5a0b8a36dcaee3bd7a9993ae02207bc532ceab09c10f1d54035d03ff9aad0e1004c3e0325a8b97b6be04b7d6c3a2012102a01aaa27b468ec3fb2ae0c2a9fa1d5dce9b79b35062178f479156d8daa6c0e50feffffff02a0860100000000001976a914775bd9c79a9e988c0d6177a9205a611a50b7229188acb6342900000000001976a914f23a46f930320ab3cc7ad8c1660325f4c434d11688ac63b70d00" + val transaction = Transaction(rawTx) + val prevTransaction = Transaction( + "0100000001e4dbac0d73f4e3a9e99e70596a5f81b35a75f95b0474d051fbfd9dc249a5b67e000000006a4730440220486f112aee12997f6e484754d53d5c2158c18cc6d1d3f13aefcdf0ed19c47b290220136133d934d9e79a57408166c39fbce38e217ea9d417cabc20744134f04f06960121021f8cb5c3d611cf24dd665adff3fd540e4c155a05adaa6b672bfa7897c126d9b6feffffff0293d22a00000000001976a914cd0385f813ec73f8fc340b7069daf566878a0d6b88ac40420f000000000017a91480f7a6c14a8407da3546b4abfc3086876ca9a0668700000000") + val privateKey = ECPrivateKeyUtil.fromWIFToPrivateKey( + "cTTh7jNtZhg3vHTjvYK8zcHkLfsMAS8iqL7pfZ6eVAVHHF8fN1qy") + + val inputInfo = + P2PKHInputInfo(TransactionOutPoint(prevTransaction.txId, UInt32.zero), + 2806419.sats, + privateKey.publicKey) + val signingInfo = ECSignatureParams(inputInfo, + prevTransaction, + privateKey, + HashType.sigHashAll) + + val txSignature = + TransactionSignatureCreator.createSig(transaction, + signingInfo, + privateKey, + HashType.sigHashAll) + txSignature.r must be(expectedSig.r) + txSignature.s must be(expectedSig.s) + txSignature.hex must be(expectedSig.hex) + } + + it should "have old and new createSig functions agree" in { + forAllAsync(CreditingTxGen.inputsAndOutputs(), + ScriptGenerators.scriptPubKey) { + case ((creditingTxsInfo, destinations), (changeSPK, _)) => + val fee = SatoshisPerVirtualByte(Satoshis(100)) + + val unsignedTxF = StandardNonInteractiveFinalizer + .txFrom(outputs = destinations, + utxos = creditingTxsInfo, + feeRate = fee, + changeSPK = changeSPK) + + val correctSigsF = unsignedTxF.flatMap { spendingTx => + val assertFs = creditingTxsInfo.flatMap { signInfo => + signInfo.signers.map { signer => + val txSignatureComponent = + TxSigComponent(signInfo.inputInfo, spendingTx) + + for { + oldSig <- + TransactionSignatureCreator.createSig(txSignatureComponent, + signer.signFunction, + signInfo.hashType) + newSig <- + TransactionSignatureCreator.createSig(spendingTx, + signInfo, + signer.signFunction, + signInfo.hashType) + } yield { + (oldSig.r == newSig.r) && + (oldSig.s == newSig.s) && + (oldSig.hex == newSig.hex) + } + } + } + + Future.sequence(assertFs) + } + + correctSigsF.map(x => assert(x.forall(_ == true))) + } + } + it must "create a p2pk scriptPubKey, create a crediting tx for scriptPubKey, " + "then create spending tx and make sure it evaluates to true in the interpreter" in { val privateKey = ECPrivateKey() diff --git a/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureSerializerTest.scala index a0a5956b0e..e36011586e 100644 --- a/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/crypto/TransactionSignatureSerializerTest.scala @@ -8,7 +8,10 @@ import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.script.crypto._ import org.bitcoins.core.serializers.script.ScriptParser import org.bitcoins.core.util._ +import org.bitcoins.core.wallet.builder.StandardNonInteractiveFinalizer +import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte import org.bitcoins.crypto.DoubleSha256Digest +import org.bitcoins.testkit.core.gen.{CreditingTxGen, ScriptGenerators} import org.bitcoins.testkit.util.BitcoinSAsyncTest import scala.util.Try @@ -402,4 +405,123 @@ class TransactionSignatureSerializerTest extends BitcoinSAsyncTest { Try(SIGHASH_NONE_ANYONECANPAY(z)).isFailure must be(true) Try(SIGHASH_SINGLE_ANYONECANPAY(z)).isFailure must be(true) } + + it should "have old and new serializeForSignature functions agree" in { + forAllAsync(CreditingTxGen.inputsAndOutputs(), + ScriptGenerators.scriptPubKey) { + case ((creditingTxsInfo, destinations), (changeSPK, _)) => + val fee = SatoshisPerVirtualByte(Satoshis(100)) + + val unsignedTxF = StandardNonInteractiveFinalizer + .txFrom(outputs = destinations, + utxos = creditingTxsInfo, + feeRate = fee, + changeSPK = changeSPK) + + val correctScriptsF = unsignedTxF.map { spendingTx => + creditingTxsInfo.flatMap { signInfo => + signInfo.signers.map { _ => + val txSigComponent = + TxSigComponent(signInfo.inputInfo, spendingTx) + + val oldBytes = + TransactionSignatureSerializer.serializeForSignature( + txSigComponent, + signInfo.hashType) + + val newBytes = + TransactionSignatureSerializer.serializeForSignature( + spendingTx, + signInfo, + signInfo.hashType) + + oldBytes == newBytes + } + } + } + + correctScriptsF.map(x => assert(x.forall(_ == true))) + } + } + + it should "have old and new hashForSignature functions agree" in { + forAllAsync(CreditingTxGen.inputsAndOutputs(), + ScriptGenerators.scriptPubKey) { + case ((creditingTxsInfo, destinations), (changeSPK, _)) => + val fee = SatoshisPerVirtualByte(Satoshis(100)) + + val unsignedTxF = StandardNonInteractiveFinalizer + .txFrom(outputs = destinations, + utxos = creditingTxsInfo, + feeRate = fee, + changeSPK = changeSPK) + + val correctHashesF = unsignedTxF.map { spendingTx => + creditingTxsInfo.flatMap { signInfo => + signInfo.signers.map { _ => + val txSigComponent = + TxSigComponent(signInfo.inputInfo, spendingTx) + + val oldHash = + TransactionSignatureSerializer.hashForSignature( + txSigComponent, + signInfo.hashType) + + val newHash = + TransactionSignatureSerializer.hashForSignature( + spendingTx, + signInfo, + signInfo.hashType) + + if (oldHash != newHash) { + println(oldHash.hex) + println(newHash.hex) + } + + oldHash == newHash + } + } + } + + correctHashesF.map(x => assert(x.forall(_ == true))) + } + } + + it should "have old and new calculateScriptForSigning functions agree" in { + forAllAsync(CreditingTxGen.inputsAndOutputs(), + ScriptGenerators.scriptPubKey) { + case ((creditingTxsInfo, destinations), (changeSPK, _)) => + val fee = SatoshisPerVirtualByte(Satoshis(100)) + + val unsignedTxF = StandardNonInteractiveFinalizer + .txFrom(outputs = destinations, + utxos = creditingTxsInfo, + feeRate = fee, + changeSPK = changeSPK) + + val correctScriptsF = unsignedTxF.map { spendingTx => + creditingTxsInfo.flatMap { signInfo => + signInfo.signers.map { _ => + val txSigComponent = + TxSigComponent(signInfo.inputInfo, spendingTx) + + val oldScript = + BitcoinScriptUtil.calculateScriptForSigning( + txSigComponent, + txSigComponent.output.scriptPubKey.asm) + + val newScript = + BitcoinScriptUtil.calculateScriptForSigning( + spendingTx, + signInfo, + signInfo.output.scriptPubKey.asm) + + oldScript == newScript + } + } + } + + correctScriptsF.map(x => assert(x.forall(_ == true))) + } + } } diff --git a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTSerializerTest.scala b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTSerializerTest.scala index 24f482d611..2bf7b72e5e 100644 --- a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTSerializerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTSerializerTest.scala @@ -1,18 +1,16 @@ package org.bitcoins.core.psbt import org.bitcoins.core.crypto.ExtPublicKey -import org.bitcoins.core.currency.Satoshis import org.bitcoins.core.hd.BIP32Path import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.protocol.script.{ MultiSignatureScriptPubKey, ScriptPubKey } -import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutput} +import org.bitcoins.core.protocol.transaction.Transaction import org.bitcoins.core.psbt.GlobalPSBTRecord.XPubKey import org.bitcoins.core.psbt.InputPSBTRecord.ProofOfReservesCommitment import org.bitcoins.core.script.crypto.HashType -import org.bitcoins.core.serializers.script.RawScriptPubKeyParser import org.bitcoins.crypto.ECPublicKey import org.bitcoins.testkit.core.gen.PSBTGenerators import org.bitcoins.testkit.util.BitcoinSAsyncTest @@ -36,11 +34,11 @@ class PSBTSerializerTest extends BitcoinSAsyncTest { // PSBT with one P2SH-P2WSH input of a 2-of-2 multisig, redeemScript, witnessScript, and keypaths are available. Contains one signature. hex"70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", // PSBT with one P2WSH input of a 2-of-2 multisig. witnessScript, keypaths, and global xpubs are available. Contains no signatures. Outputs filled. - hex"70736274ff01005202000000019dfc6628c26c5899fe1bd3dc338665bfd55d7ada10f6220973df2d386dec12760100000000ffffffff01f03dcd1d000000001600147b3a00bfdc14d27795c2b74901d09da6ef133579000000004f01043587cf02da3fd0088000000097048b1ad0445b1ec8275517727c87b4e4ebc18a203ffa0f94c01566bd38e9000351b743887ee1d40dc32a6043724f2d6459b3b5a4d73daec8fbae0472f3bc43e20cd90c6a4fae000080000000804f01043587cf02da3fd00880000001b90452427139cd78c2cff2444be353cd58605e3e513285e528b407fae3f6173503d30a5e97c8adbc557dac2ad9a7e39c1722ebac69e668b6f2667cc1d671c83cab0cd90c6a4fae000080010000800001012b0065cd1d000000002200202c5486126c4978079a814e13715d65f36459e4d6ccaded266d0508645bafa6320105475221029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c88712103372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b52ae2206029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c887110d90c6a4fae0000800000008000000000220603372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b10d90c6a4fae0000800100008000000000002202039eff1f547a1d5f92dfa2ba7af6ac971a4bd03ba4a734b03156a256b8ad3a1ef910ede45cc500000080000000800100008000", + hex"70736274ff01005202000000019dfc6628c26c5899fe1bd3dc338665bfd55d7ada10f6220973df2d386dec12760100000000ffffffff01f03dcd1d000000001600147b3a00bfdc14d27795c2b74901d09da6ef133579000000004f01043587cf02da3fd0088000000097048b1ad0445b1ec8275517727c87b4e4ebc18a203ffa0f94c01566bd38e9000351b743887ee1d40dc32a6043724f2d6459b3b5a4d73daec8fbae0472f3bc43e20cd90c6a4fae000080000000804f01043587cf02da3fd00880000001b90452427139cd78c2cff2444be353cd58605e3e513285e528b407fae3f6173503d30a5e97c8adbc557dac2ad9a7e39c1722ebac69e668b6f2667cc1d671c83cab0cd90c6a4fae00008001000080000105475221029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c88712103372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b52ae2206029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c887110d90c6a4fae0000800000008000000000220603372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b10d90c6a4fae0000800100008000000000002202039eff1f547a1d5f92dfa2ba7af6ac971a4bd03ba4a734b03156a256b8ad3a1ef910ede45cc500000080000000800100008000", // PSBT with unknown types in the inputs. hex"70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0000", // PSBT with `PSBT_GLOBAL_XPUB`. - hex"70736274ff01009d0100000002710ea76ab45c5cb6438e607e59cc037626981805ae9e0dfd9089012abb0be5350100000000ffffffff190994d6a8b3c8c82ccbcfb2fba4106aa06639b872a8d447465c0d42588d6d670000000000ffffffff0200e1f505000000001976a914b6bc2c0ee5655a843d79afedd0ccc3f7dd64340988ac605af405000000001600141188ef8e4ce0449eaac8fb141cbf5a1176e6a088000000004f010488b21e039e530cac800000003dbc8a5c9769f031b17e77fea1518603221a18fd18f2b9a54c6c8c1ac75cbc3502f230584b155d1c7f1cd45120a653c48d650b431b67c5b2c13f27d7142037c1691027569c503100008000000080000000800001011f00e1f5050000000016001433b982f91b28f160c920b4ab95e58ce50dda3a4a220203309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c47304402202d704ced830c56a909344bd742b6852dccd103e963bae92d38e75254d2bb424502202d86c437195df46c0ceda084f2a291c3da2d64070f76bf9b90b195e7ef28f77201220603309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c1827569c5031000080000000800000008000000000010000000001011f00e1f50500000000160014388fb944307eb77ef45197d0b0b245e079f011de220202c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b11047304402204cb1fb5f869c942e0e26100576125439179ae88dca8a9dc3ba08f7953988faa60220521f49ca791c27d70e273c9b14616985909361e25be274ea200d7e08827e514d01220602c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b1101827569c5031000080000000800000008000000000000000000000220202d20ca502ee289686d21815bd43a80637b0698e1fbcdbe4caed445f6c1a0a90ef1827569c50310000800000008000000080000000000400000000" + hex"70736274ff01009d0100000002710ea76ab45c5cb6438e607e59cc037626981805ae9e0dfd9089012abb0be5350100000000ffffffff190994d6a8b3c8c82ccbcfb2fba4106aa06639b872a8d447465c0d42588d6d670000000000ffffffff0200e1f505000000001976a914b6bc2c0ee5655a843d79afedd0ccc3f7dd64340988ac605af405000000001600141188ef8e4ce0449eaac8fb141cbf5a1176e6a088000000004f010488b21e039e530cac800000003dbc8a5c9769f031b17e77fea1518603221a18fd18f2b9a54c6c8c1ac75cbc3502f230584b155d1c7f1cd45120a653c48d650b431b67c5b2c13f27d7142037c1691027569c5031000080000000800000008000220203309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c47304402202d704ced830c56a909344bd742b6852dccd103e963bae92d38e75254d2bb424502202d86c437195df46c0ceda084f2a291c3da2d64070f76bf9b90b195e7ef28f77201220603309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c1827569c50310000800000008000000080000000000100000000220202c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b11047304402204cb1fb5f869c942e0e26100576125439179ae88dca8a9dc3ba08f7953988faa60220521f49ca791c27d70e273c9b14616985909361e25be274ea200d7e08827e514d01220602c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b1101827569c5031000080000000800000008000000000000000000000220202d20ca502ee289686d21815bd43a80637b0698e1fbcdbe4caed445f6c1a0a90ef1827569c50310000800000008000000080000000000400000000" ) it must "successfully serialize a PSBT's global data" in { @@ -82,14 +80,7 @@ class PSBTSerializerTest extends BitcoinSAsyncTest { val psbt = PSBT.fromBytes(validPsbts(5)) assert(psbt.inputMaps.size == 1) - assert(psbt.inputMaps.head.elements.size == 4) - - val witnessUTXO = psbt.inputMaps.head.witnessUTXOOpt.get - val output = TransactionOutput( - Satoshis(500000000), - RawScriptPubKeyParser.read( - hex"2200202c5486126c4978079a814e13715d65f36459e4d6ccaded266d0508645bafa632")) - assert(witnessUTXO.witnessUTXO == output) + assert(psbt.inputMaps.head.elements.size == 3) val witnessScript = psbt.inputMaps.head.witnessScriptOpt.get val scriptPubKey = MultiSignatureScriptPubKey( diff --git a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTTest.scala b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTTest.scala index dc6a896ae8..a43e696e4b 100644 --- a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTTest.scala @@ -59,14 +59,12 @@ class PSBTTest extends BitcoinSAsyncTest { case (fullPsbt, utxos, _) => val emptyPsbt = PSBT.fromUnsignedTx(fullPsbt.transaction) - val infoAndTxs = PSBTGenerators - .spendingInfoAndNonWitnessTxsFromSpendingInfos(fullPsbt.transaction, - utxos.toVector) - .infoAndTxOpts + val infoAndTxs = PSBTGenerators.orderSpendingInfos(fullPsbt.transaction, + utxos.toVector) val updatedPSBT = infoAndTxs.zipWithIndex.foldLeft(emptyPsbt) { - case (psbt, ((utxo, txOpt), index)) => + case (psbt, (utxo, index)) => val partUpdatedPsbt = psbt - .addUTXOToInput(txOpt.get, index) + .addUTXOToInput(utxo.prevTransaction, index) .addSigHashTypeToInput(utxo.hashType, index) (InputInfo.getRedeemScript(utxo.inputInfo), @@ -114,7 +112,8 @@ class PSBTTest extends BitcoinSAsyncTest { val finalizedPsbtT = signedPSBT.finalizePSBT finalizedPsbtT match { case Success(finalizedPsbt) => - assert(finalizedPsbt.extractTransactionAndValidate.isSuccess) + val txT = finalizedPsbt.extractTransactionAndValidate + assert(txT.isSuccess, txT.failed) case Failure(exception) => fail(exception) } } @@ -169,7 +168,7 @@ class PSBTTest extends BitcoinSAsyncTest { forAllAsync(CreditingTxGen.inputsAndOutputs(), ScriptGenerators.scriptPubKey, ChainParamsGenerator.bitcoinNetworkParams) { - case ((creditingTxsInfo, destinations), (changeSPK, _), network) => + case ((creditingTxsInfo, destinations), (changeSPK, _), _) => val crediting = creditingTxsInfo.foldLeft(0L)(_ + _.amount.satoshis.toLong) val spending = destinations.foldLeft(0L)(_ + _.value.satoshis.toLong) diff --git a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala index 845a3a4278..350b961c45 100644 --- a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala @@ -12,9 +12,15 @@ import org.bitcoins.core.psbt.InputPSBTRecord.{ WitnessUTXO } import org.bitcoins.core.psbt.PSBTGlobalKeyId.XPubKeyKeyId +import org.bitcoins.core.script.constant._ import org.bitcoins.core.script.crypto.HashType import org.bitcoins.core.wallet.utxo.{ConditionalPath, InputInfo} -import org.bitcoins.crypto.{DoubleSha256Digest, ECPublicKey, Sha256Digest, Sign} +import org.bitcoins.crypto.{ + DoubleSha256Digest, + ECPublicKey, + Sha256Hash160Digest, + Sign +} import org.bitcoins.testkit.util.BitcoinSAsyncTest import scodec.bits._ @@ -62,7 +68,7 @@ class PSBTUnitTest extends BitcoinSAsyncTest { hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000") val expected = PSBT( - hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") + hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f000000800000008001000080000100f80200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f796500000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") val extKey = ExtKey .fromString( @@ -108,9 +114,9 @@ class PSBTUnitTest extends BitcoinSAsyncTest { .addSigHashTypeToInput(HashType.sigHashAll, 1) val nextExpected = PSBT( - hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") + hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f000000800000008001000080000100f80200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f79650000000103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") - assert(psbtWithSigHash == nextExpected) + assert(psbtWithSigHash.bytes == nextExpected.bytes) } it must "fail to create a InputPSBTMap with both a NonWitness and Witness UTXO" in { @@ -125,7 +131,7 @@ class PSBTUnitTest extends BitcoinSAsyncTest { it must "correctly filter a GlobalPSBTMap" in { val psbt = PSBT( - "70736274ff01009d0100000002710ea76ab45c5cb6438e607e59cc037626981805ae9e0dfd9089012abb0be5350100000000ffffffff190994d6a8b3c8c82ccbcfb2fba4106aa06639b872a8d447465c0d42588d6d670000000000ffffffff0200e1f505000000001976a914b6bc2c0ee5655a843d79afedd0ccc3f7dd64340988ac605af405000000001600141188ef8e4ce0449eaac8fb141cbf5a1176e6a088000000004f010488b21e039e530cac800000003dbc8a5c9769f031b17e77fea1518603221a18fd18f2b9a54c6c8c1ac75cbc3502f230584b155d1c7f1cd45120a653c48d650b431b67c5b2c13f27d7142037c1691027569c503100008000000080000000800001011f00e1f5050000000016001433b982f91b28f160c920b4ab95e58ce50dda3a4a220203309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c47304402202d704ced830c56a909344bd742b6852dccd103e963bae92d38e75254d2bb424502202d86c437195df46c0ceda084f2a291c3da2d64070f76bf9b90b195e7ef28f77201220603309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c1827569c5031000080000000800000008000000000010000000001011f00e1f50500000000160014388fb944307eb77ef45197d0b0b245e079f011de220202c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b11047304402204cb1fb5f869c942e0e26100576125439179ae88dca8a9dc3ba08f7953988faa60220521f49ca791c27d70e273c9b14616985909361e25be274ea200d7e08827e514d01220602c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b1101827569c5031000080000000800000008000000000000000000000220202d20ca502ee289686d21815bd43a80637b0698e1fbcdbe4caed445f6c1a0a90ef1827569c50310000800000008000000080000000000400000000") + hex"70736274ff01009d0100000002710ea76ab45c5cb6438e607e59cc037626981805ae9e0dfd9089012abb0be5350100000000ffffffff190994d6a8b3c8c82ccbcfb2fba4106aa06639b872a8d447465c0d42588d6d670000000000ffffffff0200e1f505000000001976a914b6bc2c0ee5655a843d79afedd0ccc3f7dd64340988ac605af405000000001600141188ef8e4ce0449eaac8fb141cbf5a1176e6a088000000004f010488b21e039e530cac800000003dbc8a5c9769f031b17e77fea1518603221a18fd18f2b9a54c6c8c1ac75cbc3502f230584b155d1c7f1cd45120a653c48d650b431b67c5b2c13f27d7142037c1691027569c5031000080000000800000008000000000220202d20ca502ee289686d21815bd43a80637b0698e1fbcdbe4caed445f6c1a0a90ef1827569c50310000800000008000000080000000000400000000") val records = psbt.globalMap.filterRecords(XPubKeyKeyId) assert(!records.exists(_.key.head == XPubKeyKeyId.byte)) @@ -193,18 +199,18 @@ class PSBTUnitTest extends BitcoinSAsyncTest { it must "successfully extract a transaction from a finalized PSBT" in { val psbt = PSBT( - "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") + "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae000100f80200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f796500000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f0000008000000080020000800107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") + val expected = Transaction.fromHex( "0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000") + val tx = psbt.extractTransaction + assert(tx == expected) + val txT = psbt.extractTransactionAndValidate - assert(txT.isSuccess) - - val tx = psbt.extractTransaction - + assert(txT.isSuccess, txT.failed) assert(tx == txT.get) - assert(tx == expected) } it must "finalize an already finalized input" in { @@ -231,10 +237,10 @@ class PSBTUnitTest extends BitcoinSAsyncTest { it must "successfully finalize a PSBT" in { val psbt = PSBT( - "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f012202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") + hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f000000800000008001000080000100f80200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f79650000002202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d201220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") val expected = PSBT( - "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") + "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae000100f80200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f79650000000107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") val finalizedPSBT = psbt.finalizePSBT assert(finalizedPSBT.isSuccess) @@ -250,38 +256,48 @@ class PSBTUnitTest extends BitcoinSAsyncTest { it must "create a valid UTXOSpendingInfo" in { // PSBT with one P2WSH input of a 2-of-2 multisig. witnessScript, keypaths, and global xpubs are available. Contains no signatures. Outputs filled. val psbt = PSBT( - "70736274ff01005202000000019dfc6628c26c5899fe1bd3dc338665bfd55d7ada10f6220973df2d386dec12760100000000ffffffff01f03dcd1d000000001600147b3a00bfdc14d27795c2b74901d09da6ef133579000000004f01043587cf02da3fd0088000000097048b1ad0445b1ec8275517727c87b4e4ebc18a203ffa0f94c01566bd38e9000351b743887ee1d40dc32a6043724f2d6459b3b5a4d73daec8fbae0472f3bc43e20cd90c6a4fae000080000000804f01043587cf02da3fd00880000001b90452427139cd78c2cff2444be353cd58605e3e513285e528b407fae3f6173503d30a5e97c8adbc557dac2ad9a7e39c1722ebac69e668b6f2667cc1d671c83cab0cd90c6a4fae000080010000800001012b0065cd1d000000002200202c5486126c4978079a814e13715d65f36459e4d6ccaded266d0508645bafa6320105475221029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c88712103372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b52ae2206029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c887110d90c6a4fae0000800000008000000000220603372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b10d90c6a4fae0000800100008000000000002202039eff1f547a1d5f92dfa2ba7af6ac971a4bd03ba4a734b03156a256b8ad3a1ef910ede45cc500000080000000800100008000") + "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") val dummySigners = Vector( Sign.dummySign(ECPublicKey( - "029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c8871")), + "029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f")), Sign.dummySign( ECPublicKey( - "03372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b")) + "02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7")) ) val spendingInfo = psbt.getSpendingInfoUsingSigners(index = 0, dummySigners) assert(spendingInfo.outPoint == psbt.transaction.inputs.head.previousOutput) - assert(spendingInfo.amount == Satoshis(500000000)) + assert(spendingInfo.amount == Satoshis(50000000)) assert( - spendingInfo.output.scriptPubKey == P2WSHWitnessSPKV0.fromHash( - Sha256Digest( - "2c5486126c4978079a814e13715d65f36459e4d6ccaded266d0508645bafa632"))) + spendingInfo.output.scriptPubKey == P2SHScriptPubKey( + Sha256Hash160Digest("0fb9463421696b82c833af241c78c17ddbde4934"))) assert(spendingInfo.signers == dummySigners) assert(spendingInfo.hashType == HashType.sigHashAll) - assert(InputInfo.getRedeemScript(spendingInfo.inputInfo).isEmpty) + assert(InputInfo.getRedeemScript(spendingInfo.inputInfo).isDefined) + + val expectedRedeemScript = MultiSignatureScriptPubKey( + 2, + Vector( + ECPublicKey( + "029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f"), + ECPublicKey( + "02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7") + ) + ) + assert( InputInfo - .getScriptWitness(spendingInfo.inputInfo) - .contains(P2WSHWitnessV0(RawScriptPubKey.fromAsmHex( - "5221029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c88712103372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b52ae")))) + .getRedeemScript(spendingInfo.inputInfo) + .get == expectedRedeemScript) + assert(InputInfo.getScriptWitness(spendingInfo.inputInfo).isEmpty) assert(spendingInfo.conditionalPath == ConditionalPath.NoCondition) } it must "fail to create a valid UTXOSpendingInfo from a PSBTInputMap with insufficient data" in { val psbt1 = PSBT( "70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0000") - assertThrows[UnsupportedOperationException]( + assertThrows[RuntimeException]( psbt1.getSpendingInfoUsingSigners(index = 0, getDummySigners(size = 1))) } @@ -298,7 +314,7 @@ class PSBTUnitTest extends BitcoinSAsyncTest { // Test case given in https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#test-vectors it must "sign a PSBT" in { val unsignedPsbt = PSBT( - "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") + hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f000000800000008001000080000100f80200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f79650000000103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") val privKey0 = ECPrivateKeyUtil.fromWIFToPrivateKey( "cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr") @@ -306,10 +322,10 @@ class PSBTUnitTest extends BitcoinSAsyncTest { "cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d") val expectedPsbt0 = PSBT( - "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") + hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f000000800000008001000080000100f80200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") val expectedPsbt1 = PSBT( - "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8872202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") + hex"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f000000800000008001000080000100f80200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f79650000002202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000") val privKey2 = ECPrivateKeyUtil.fromWIFToPrivateKey( "cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au") @@ -352,7 +368,11 @@ class PSBTUnitTest extends BitcoinSAsyncTest { } it must "successfully change a NonWitnessUTXO to a WitnessUTXO when compressing" in { - val wspk = P2WSHWitnessSPKV0(EmptyScriptPubKey) + // Create non BIP-143 vulnerable witness utxo + val dummyData = ECPublicKey.freshPublicKey.bytes + val asm = + Vector(OP_1, ScriptNumber(dummyData.size), ScriptConstant(dummyData)) + val wspk = WitnessScriptPubKey.fromAsm(asm) val output = TransactionOutput(value = CurrencyUnits.oneBTC, scriptPubKey = wspk) diff --git a/core-test/src/test/scala/org/bitcoins/core/wallet/builder/RawTxSignerTest.scala b/core-test/src/test/scala/org/bitcoins/core/wallet/builder/RawTxSignerTest.scala index 55d1c71f1c..f085991a5f 100644 --- a/core-test/src/test/scala/org/bitcoins/core/wallet/builder/RawTxSignerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/wallet/builder/RawTxSignerTest.scala @@ -47,8 +47,9 @@ class RawTxSignerTest extends BitcoinSAsyncTest { conditionalPath = ConditionalPath.NoCondition, hashPreImages = Vector(privKey.publicKey) ), - privKey, - HashType.sigHashAll + prevTransaction = creditingTx, + signer = privKey, + hashType = HashType.sigHashAll ) val utxos = Vector(utxo) val feeUnit = SatoshisPerVirtualByte(currencyUnit = Satoshis(1)) @@ -84,8 +85,9 @@ class RawTxSignerTest extends BitcoinSAsyncTest { conditionalPath = ConditionalPath.NoCondition, hashPreImages = Vector(privKey.publicKey) ), - privKey, - HashType.sigHashAll + prevTransaction = creditingTx, + signer = privKey, + hashType = HashType.sigHashAll ) val utxos = Vector(utxo) @@ -122,6 +124,7 @@ class RawTxSignerTest extends BitcoinSAsyncTest { conditionalPath = ConditionalPath.NoCondition, hashPreImages = Vector(privKey.publicKey) ), + prevTransaction = creditingTx, signer = privKey, hashType = HashType.sigHashAll ) @@ -148,14 +151,21 @@ class RawTxSignerTest extends BitcoinSAsyncTest { CLTVScriptPubKey(ScriptNumber(lockTime), P2PKScriptPubKey(fundingPrivKey.publicKey)) + val creditingTx = BaseTransaction( + version = TransactionConstants.validLockVersion, + inputs = Nil, + outputs = Vector(TransactionOutput(Bitcoins.one, cltvSPK)), + lockTime = TransactionConstants.lockTime + ) + val cltvSpendingInfo = ScriptSignatureParams( - LockTimeInputInfo(TransactionOutPoint(DoubleSha256DigestBE.empty, - UInt32.zero), + LockTimeInputInfo(TransactionOutPoint(creditingTx.txId, UInt32.zero), Bitcoins.one, cltvSPK, ConditionalPath.NoCondition), - Vector(fundingPrivKey), - HashType.sigHashAll + prevTransaction = creditingTx, + signers = Vector(fundingPrivKey), + hashType = HashType.sigHashAll ) val utxos = Vector(cltvSpendingInfo) @@ -185,14 +195,21 @@ class RawTxSignerTest extends BitcoinSAsyncTest { CLTVScriptPubKey(ScriptNumber(lockTime), P2PKScriptPubKey(fundingPrivKey.publicKey)) + val creditingTx = BaseTransaction( + version = TransactionConstants.validLockVersion, + inputs = Nil, + outputs = Vector(TransactionOutput(Bitcoins.one, cltvSPK)), + lockTime = TransactionConstants.lockTime + ) + val cltvSpendingInfo = ScriptSignatureParams( - LockTimeInputInfo(TransactionOutPoint(DoubleSha256DigestBE.empty, - UInt32.zero), + LockTimeInputInfo(TransactionOutPoint(creditingTx.txId, UInt32.zero), Bitcoins.one, cltvSPK, ConditionalPath.NoCondition), - Vector(fundingPrivKey), - HashType.sigHashAll + prevTransaction = creditingTx, + signers = Vector(fundingPrivKey), + hashType = HashType.sigHashAll ) val utxos = Vector(cltvSpendingInfo) @@ -233,8 +250,9 @@ class RawTxSignerTest extends BitcoinSAsyncTest { Bitcoins.one, cltvSPK1, ConditionalPath.NoCondition), - Vector(fundingPrivKey1), - HashType.sigHashAll + prevTransaction = EmptyTransaction, + signers = Vector(fundingPrivKey1), + hashType = HashType.sigHashAll ) val cltvSpendingInfo2 = ScriptSignatureParams( @@ -243,8 +261,9 @@ class RawTxSignerTest extends BitcoinSAsyncTest { Bitcoins.one, cltvSPK2, ConditionalPath.NoCondition), - Vector(fundingPrivKey2), - HashType.sigHashAll + prevTransaction = EmptyTransaction, + signers = Vector(fundingPrivKey2), + hashType = HashType.sigHashAll ) val utxos = Vector(cltvSpendingInfo1, cltvSpendingInfo2) diff --git a/core-test/src/test/scala/org/bitcoins/core/wallet/builder/StandardNonInteractiveFinalizerTest.scala b/core-test/src/test/scala/org/bitcoins/core/wallet/builder/StandardNonInteractiveFinalizerTest.scala index 2fc09c88b6..e706322688 100644 --- a/core-test/src/test/scala/org/bitcoins/core/wallet/builder/StandardNonInteractiveFinalizerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/wallet/builder/StandardNonInteractiveFinalizerTest.scala @@ -11,6 +11,7 @@ import org.bitcoins.core.protocol.script.{ } import org.bitcoins.core.protocol.transaction.{ BaseTransaction, + EmptyTransaction, TransactionConstants, TransactionInput, TransactionOutPoint, @@ -120,6 +121,7 @@ class StandardNonInteractiveFinalizerTest extends BitcoinSAsyncTest { conditionalPath = ConditionalPath.NoCondition, hashPreImages = Vector(privKey.publicKey) ), + prevTransaction = creditingTx, signer = privKey, hashType = HashType.sigHashAll ) @@ -153,6 +155,7 @@ class StandardNonInteractiveFinalizerTest extends BitcoinSAsyncTest { conditionalPath = ConditionalPath.NoCondition, hashPreImages = Vector(privKey.publicKey) ), + prevTransaction = creditingTx, signer = privKey, hashType = HashType.sigHashAll ) @@ -182,6 +185,7 @@ class StandardNonInteractiveFinalizerTest extends BitcoinSAsyncTest { conditionalPath = ConditionalPath.NoCondition, Vector(pubKey) ), + prevTransaction = EmptyTransaction, signers = Vector(privKey), hashType = HashType.sigHashAll ) diff --git a/core-test/src/test/scala/org/bitcoins/core/wallet/signer/SignerTest.scala b/core-test/src/test/scala/org/bitcoins/core/wallet/signer/SignerTest.scala index 1a50225f1f..1a7a2897d9 100644 --- a/core-test/src/test/scala/org/bitcoins/core/wallet/signer/SignerTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/wallet/signer/SignerTest.scala @@ -2,39 +2,15 @@ package org.bitcoins.core.wallet.signer import org.bitcoins.core.crypto.{ BaseTxSigComponent, + TxSigComponent, WitnessTxSigComponentP2SH, WitnessTxSigComponentRaw } import org.bitcoins.core.currency.{CurrencyUnits, Satoshis} import org.bitcoins.core.number.UInt32 import org.bitcoins.core.policy.Policy -import org.bitcoins.core.protocol.script.{ - CLTVScriptPubKey, - CSVScriptPubKey, - ConditionalScriptPubKey, - EmptyScriptPubKey, - EmptyScriptWitness, - MultiSignatureScriptPubKey, - NonStandardScriptPubKey, - P2PKHScriptPubKey, - P2PKScriptPubKey, - P2PKWithTimeoutScriptPubKey, - P2SHScriptPubKey, - P2SHScriptSignature, - P2WPKHWitnessV0, - P2WSHWitnessV0, - UnassignedWitnessScriptPubKey, - WitnessCommitment, - WitnessScriptPubKey, - WitnessScriptPubKeyV0 -} -import org.bitcoins.core.protocol.transaction.{ - EmptyWitness, - Transaction, - TransactionInput, - TransactionOutput, - WitnessTransaction -} +import org.bitcoins.core.protocol.script._ +import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.psbt.InputPSBTRecord.PartialSignature import org.bitcoins.core.psbt.PSBT import org.bitcoins.core.script.PreExecutionScriptProgram @@ -44,15 +20,7 @@ import org.bitcoins.core.wallet.builder.{ StandardNonInteractiveFinalizer } import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte -import org.bitcoins.core.wallet.utxo.{ - ECSignatureParams, - InputInfo, - InputSigningInfo, - P2WPKHV0InputInfo, - P2WSHV0InputInfo, - ScriptSignatureParams, - UnassignedSegwitNativeInputInfo -} +import org.bitcoins.core.wallet.utxo._ import org.bitcoins.crypto.ECDigitalSignature import org.bitcoins.testkit.core.gen.{ CreditingTxGen, @@ -85,6 +53,7 @@ class SignerTest extends BitcoinSAsyncTest { p2wpkh.conditionalPath, p2wpkh.signers.map(_.publicKey) ), + p2wpkh.prevTransaction, p2wpkh.signers, p2wpkh.hashType ) @@ -186,6 +155,49 @@ class SignerTest extends BitcoinSAsyncTest { } } + it should "have old and new doSign functions agree" in { + forAllAsync(CreditingTxGen.inputsAndOutputs(), + ScriptGenerators.scriptPubKey) { + case ((creditingTxsInfo, destinations), (changeSPK, _)) => + val fee = SatoshisPerVirtualByte(Satoshis(100)) + + val unsignedTxF = StandardNonInteractiveFinalizer + .txFrom(outputs = destinations, + utxos = creditingTxsInfo, + feeRate = fee, + changeSPK = changeSPK) + + val correctSigsF = unsignedTxF.flatMap { spendingTx => + val assertFs = creditingTxsInfo.flatMap { signInfo => + signInfo.signers.map { signer => + val txSignatureComponent = + TxSigComponent(signInfo.inputInfo, spendingTx) + + for { + oldSig <- BitcoinSigner.doSign(txSignatureComponent, + signer.signFunction, + signInfo.hashType, + isDummySignature = false) + newSig <- BitcoinSigner.doSign(spendingTx, + signInfo, + signer.signFunction, + signInfo.hashType, + isDummySignature = false) + } yield { + (oldSig.r == newSig.r) && + (oldSig.s == newSig.s) && + (oldSig.hex == newSig.hex) + } + } + } + + Future.sequence(assertFs) + } + + correctSigsF.map(x => assert(x.forall(_ == true))) + } + } + def inputIndex( spendingInfo: InputSigningInfo[InputInfo], tx: Transaction): Int = { @@ -298,7 +310,7 @@ class SignerTest extends BitcoinSAsyncTest { (psbt, spendInfo) => val idx = inputIndex(spendInfo, unsignedTx) psbt - .addWitnessUTXOToInput(spendInfo.output, idx) + .addUTXOToInput(spendInfo.prevTransaction, idx) .addScriptWitnessToInput( InputInfo.getScriptWitness(spendInfo.inputInfo).get, idx) diff --git a/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputInfoTest.scala b/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputInfoTest.scala index c4172e043c..6ce13a6995 100644 --- a/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputInfoTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputInfoTest.scala @@ -3,13 +3,7 @@ package org.bitcoins.core.wallet.utxo import org.bitcoins.core.currency.CurrencyUnits import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.script._ -import org.bitcoins.core.protocol.transaction.{ - BaseTransaction, - TransactionConstants, - TransactionOutPoint, - TransactionOutput -} -import org.bitcoins.core.script.crypto.HashType +import org.bitcoins.core.protocol.transaction._ import org.bitcoins.crypto.{ECPrivateKey, ECPublicKey} import org.bitcoins.testkit.core.gen.{ GenUtil, @@ -161,7 +155,7 @@ class InputInfoTest extends BitcoinSAsyncTest { } } - it should "successfully return UnassingedSegwitNativeUTXOSpendingInfoFull" in { + it should "successfully return UnassignedSegwitNativeUTXOSpendingInfoFull" in { val unassingedWitnessSPK = UnassignedWitnessScriptPubKey.fromAsm( P2WPKHWitnessSPKV0(ECPublicKey.freshPublicKey).asm) diff --git a/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputSigningInfoTest.scala b/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputSigningInfoTest.scala index e659a69645..695a0bddd0 100644 --- a/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputSigningInfoTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/wallet/utxo/InputSigningInfoTest.scala @@ -2,20 +2,10 @@ package org.bitcoins.core.wallet.utxo import org.bitcoins.core.currency.CurrencyUnits import org.bitcoins.core.number.UInt32 -import org.bitcoins.core.protocol.script.{ - EmptyScriptPubKey, - P2SHScriptPubKey, - P2WPKHWitnessSPKV0, - P2WSHWitnessSPKV0, - P2WSHWitnessV0 -} -import org.bitcoins.core.protocol.transaction.{ - BaseTransaction, - TransactionConstants, - TransactionOutPoint, - TransactionOutput -} +import org.bitcoins.core.protocol.script._ +import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.script.crypto.HashType +import org.bitcoins.crypto.DoubleSha256DigestBE import org.bitcoins.testkit.Implicits._ import org.bitcoins.testkit.core.gen.ScriptGenerators import org.bitcoins.testkit.util.BitcoinSUnitTest @@ -25,7 +15,7 @@ class InputSigningInfoTest extends BitcoinSUnitTest { private val (spk, privKey) = ScriptGenerators.p2pkhScriptPubKey.sampleSome - it should "fail to build a tx if you have the wrong redeemscript" in { + it should "fail to build a tx if you have the wrong redeem script" in { val p2sh = P2SHScriptPubKey(spk) val creditingOutput = TransactionOutput(CurrencyUnits.zero, p2sh) val creditingTx = BaseTransaction(version = @@ -45,6 +35,7 @@ class InputSigningInfoTest extends BitcoinSUnitTest { assertThrows[RuntimeException] { ScriptSignatureParams( inputInfo = inputInfo, + prevTransaction = creditingTx, signer = privKey, hashType = HashType.sigHashAll ) @@ -53,6 +44,7 @@ class InputSigningInfoTest extends BitcoinSUnitTest { assertThrows[RuntimeException] { ECSignatureParams( inputInfo = inputInfo, + prevTransaction = creditingTx, signer = privKey, hashType = HashType.sigHashAll ) @@ -77,6 +69,7 @@ class InputSigningInfoTest extends BitcoinSUnitTest { conditionalPath = ConditionalPath.NoCondition, hashPreImages = Vector(privKey.publicKey) ), + prevTransaction = creditingTx, privKey, HashType.sigHashAll ) @@ -92,6 +85,7 @@ class InputSigningInfoTest extends BitcoinSUnitTest { conditionalPath = ConditionalPath.NoCondition, hashPreImages = Vector(privKey.publicKey) ), + prevTransaction = creditingTx, privKey, HashType.sigHashAll ) @@ -120,6 +114,7 @@ class InputSigningInfoTest extends BitcoinSUnitTest { assertThrows[IllegalArgumentException] { ScriptSignatureParams( inputInfo = inputInfo, + prevTransaction = creditingTx, signer = privKey, hashType = HashType.sigHashAll ) @@ -128,6 +123,7 @@ class InputSigningInfoTest extends BitcoinSUnitTest { assertThrows[IllegalArgumentException] { ECSignatureParams( inputInfo = inputInfo, + prevTransaction = creditingTx, signer = privKey, hashType = HashType.sigHashAll ) @@ -152,6 +148,7 @@ class InputSigningInfoTest extends BitcoinSUnitTest { conditionalPath = ConditionalPath.NoCondition, hashPreImages = Vector(privKey.publicKey) ), + prevTransaction = creditingTx, privKey, HashType.sigHashAll ) @@ -167,6 +164,92 @@ class InputSigningInfoTest extends BitcoinSUnitTest { conditionalPath = ConditionalPath.NoCondition, hashPreImages = Vector(privKey.publicKey) ), + prevTransaction = creditingTx, + privKey, + HashType.sigHashAll + ) + } + } + + it should "fail to sign if the prevTransaction does not match the outPoint" in { + val p2wpkh = P2WPKHWitnessSPKV0(privKey.publicKey) + val creditingOutput = TransactionOutput(CurrencyUnits.zero, p2wpkh) + val creditingTx = BaseTransaction(TransactionConstants.validLockVersion, + Nil, + Vector(creditingOutput), + TransactionConstants.lockTime) + val outPoint = TransactionOutPoint(DoubleSha256DigestBE.empty, UInt32.zero) + + assertThrows[IllegalArgumentException] { + ScriptSignatureParams( + InputInfo( + outPoint = outPoint, + output = creditingOutput, + redeemScriptOpt = None, + scriptWitnessOpt = Some(P2WPKHWitnessV0(privKey.publicKey)), + conditionalPath = ConditionalPath.NoCondition, + hashPreImages = Vector(privKey.publicKey) + ), + prevTransaction = creditingTx, + privKey, + HashType.sigHashAll + ) + } + + assertThrows[IllegalArgumentException] { + ECSignatureParams( + InputInfo( + outPoint = outPoint, + output = creditingOutput, + redeemScriptOpt = None, + scriptWitnessOpt = Some(P2WPKHWitnessV0(privKey.publicKey)), + conditionalPath = ConditionalPath.NoCondition, + hashPreImages = Vector(privKey.publicKey) + ), + prevTransaction = creditingTx, + privKey, + HashType.sigHashAll + ) + } + } + + it should "fail to sign if the prevTransaction's output does not match the amount" in { + val p2wpkh = P2WPKHWitnessSPKV0(privKey.publicKey) + val creditingOutput = TransactionOutput(CurrencyUnits.zero, p2wpkh) + val creditingTx = + BaseTransaction(TransactionConstants.validLockVersion, + Nil, + Vector(TransactionOutput(CurrencyUnits.oneBTC, p2wpkh)), + TransactionConstants.lockTime) + val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero) + + assertThrows[IllegalArgumentException] { + ScriptSignatureParams( + InputInfo( + outPoint = outPoint, + output = creditingOutput, + redeemScriptOpt = None, + scriptWitnessOpt = Some(P2WPKHWitnessV0(privKey.publicKey)), + conditionalPath = ConditionalPath.NoCondition, + hashPreImages = Vector(privKey.publicKey) + ), + prevTransaction = creditingTx, + privKey, + HashType.sigHashAll + ) + } + + assertThrows[IllegalArgumentException] { + ECSignatureParams( + InputInfo( + outPoint = outPoint, + output = creditingOutput, + redeemScriptOpt = None, + scriptWitnessOpt = Some(P2WPKHWitnessV0(privKey.publicKey)), + conditionalPath = ConditionalPath.NoCondition, + hashPreImages = Vector(privKey.publicKey) + ), + prevTransaction = creditingTx, privKey, HashType.sigHashAll ) diff --git a/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureCreator.scala b/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureCreator.scala index 8a91b4a958..ad08288d2f 100644 --- a/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureCreator.scala +++ b/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureCreator.scala @@ -1,6 +1,8 @@ package org.bitcoins.core.crypto +import org.bitcoins.core.protocol.transaction.Transaction import org.bitcoins.core.script.crypto.HashType +import org.bitcoins.core.wallet.utxo.{InputInfo, InputSigningInfo} import org.bitcoins.crypto.{DERSignatureUtil, ECDigitalSignature, ECPrivateKey} import scodec.bits.ByteVector @@ -54,6 +56,7 @@ sealed abstract class TransactionSignatureCreator { } /** This is the same as createSig above, except the 'sign' function returns a Future[ECDigitalSignature] */ + @deprecated("use an InputSigningInfo[InputInfo] instead", since = "6/23/2020") def createSig( component: TxSigComponent, sign: ByteVector => Future[ECDigitalSignature], @@ -62,7 +65,76 @@ sealed abstract class TransactionSignatureCreator { val hash = TransactionSignatureSerializer.hashForSignature(component, hashType) val signature = sign(hash.bytes) + // append 1 byte hash type onto the end + val sig = signature.map(s => + ECDigitalSignature(s.bytes ++ ByteVector.fromByte(hashType.byte))) + sig.map { s => + require( + s.isStrictEncoded, + "We did not create a signature that is strictly encoded, got: " + sig) + require(DERSignatureUtil.isLowS(s), "Sig does not have a low s value") + s + } + } + + /** + * Creates a signature from a tx signature component + * + * @param privateKey the private key which we are signing the hash with + * @param hashType the procedure to use for hashing to transaction + * @return + */ + def createSig( + spendingTransaction: Transaction, + signingInfo: InputSigningInfo[InputInfo], + privateKey: ECPrivateKey, + hashType: HashType): ECDigitalSignature = { + val sign: ByteVector => ECDigitalSignature = privateKey.sign(_: ByteVector) + createSig(spendingTransaction, signingInfo, sign, hashType) + } + + /** + * This is intended to be a low level hardware wallet API. + * At a fundamental level, a hardware wallet expects a scodec.bits.ByteVector as input, and returns an [[ECDigitalSignature]] + * if it is able to sign the scodec.bits.ByteVector's correctly. + * @param sign - the implementation of the hardware wallet protocol to sign the scodec.bits.ByteVector w/ the given public key + * @param hashType - the hash type to be appended on the digital signature when the hardware wallet is done being signed + * @return the digital signature returned by the hardware wallet + */ + def createSig( + spendingTransaction: Transaction, + signingInfo: InputSigningInfo[InputInfo], + sign: ByteVector => ECDigitalSignature, + hashType: HashType): ECDigitalSignature = { + val hash = TransactionSignatureSerializer.hashForSignature( + spendingTransaction, + signingInfo, + hashType) + + val signature = sign(hash.bytes) //append 1 byte hash type onto the end + val sig = ECDigitalSignature( + signature.bytes ++ ByteVector.fromByte(hashType.byte)) + require( + sig.isStrictEncoded, + "We did not create a signature that is strictly encoded, got: " + sig) + require(DERSignatureUtil.isLowS(sig), "Sig does not have a low s value") + sig + } + + /** This is the same as createSig above, except the 'sign' function returns a Future[ECDigitalSignature] */ + def createSig( + spendingTransaction: Transaction, + signingInfo: InputSigningInfo[InputInfo], + sign: ByteVector => Future[ECDigitalSignature], + hashType: HashType)(implicit + ec: ExecutionContext): Future[ECDigitalSignature] = { + val hash = + TransactionSignatureSerializer.hashForSignature(spendingTransaction, + signingInfo, + hashType) + val signature = sign(hash.bytes) + // append 1 byte hash type onto the end val sig = signature.map(s => ECDigitalSignature(s.bytes ++ ByteVector.fromByte(hashType.byte))) sig.map { s => diff --git a/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala b/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala index 7aa4ce65d9..714677c443 100644 --- a/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala +++ b/core/src/main/scala/org/bitcoins/core/crypto/TransactionSignatureSerializer.scala @@ -8,6 +8,7 @@ import org.bitcoins.core.script.constant.ScriptToken import org.bitcoins.core.script.crypto._ import org.bitcoins.core.serializers.transaction.RawTransactionOutputParser import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil, BytesUtil} +import org.bitcoins.core.wallet.utxo.{InputInfo, InputSigningInfo} import org.bitcoins.crypto.{CryptoUtil, DoubleSha256Digest} import scodec.bits.ByteVector @@ -64,7 +65,7 @@ sealed abstract class TransactionSignatureSerializer { input.sequence) //make sure all scriptSigs have empty asm - inputSigsRemoved.map(input => + inputSigsRemoved.foreach(input => require(input.scriptSignature.asm.isEmpty, "Input asm was not empty " + input.scriptSignature.asm)) @@ -253,6 +254,234 @@ sealed abstract class TransactionSignatureSerializer { } } + /** + * Implements the signature serialization algorithm that Satoshi Nakamoto originally created + * and the new signature serialization algorithm as specified by + * [[https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki BIP143]]. + * [[https://github.com/bitcoin/bitcoin/blob/f8528134fc188abc5c7175a19680206964a8fade/src/script/interpreter.cpp#L1113]] + */ + def serializeForSignature( + spendingTransaction: Transaction, + signingInfo: InputSigningInfo[InputInfo], + hashType: HashType): ByteVector = { + val idx = TxUtil.inputIndex(signingInfo.inputInfo, spendingTransaction) + + require( + signingInfo.prevTransaction != EmptyTransaction, + "prevTransaction can only be an EmptyTransaction when dummy signing") + + val inputIndex = UInt32(idx) + val output = signingInfo.output + val script = BitcoinScriptUtil.calculateScriptForSigning( + spendingTransaction, + signingInfo, + output.scriptPubKey.asm) + logger.trace(s"scriptForSigning: $script") + val amount = output.value + signingInfo.sigVersion match { + case SigVersionBase => + logger.trace("Serializing for signature") + logger.trace("Script: " + script) + // Clear input scripts in preparation for signing. If we're signing a fresh + // CScript's inside the Bitcoin Core codebase retain their compactSizeUInt + // while clearing out all of the actual asm operations in the CScript + val inputSigsRemoved = for { + input <- spendingTransaction.inputs + s = input.scriptSignature + } yield TransactionInput( + input.previousOutput, + NonStandardScriptSignature(s.compactSizeUInt.hex), + input.sequence) + + //make sure all scriptSigs have empty asm + inputSigsRemoved.foreach(input => + require(input.scriptSignature.asm.isEmpty, + "Input asm was not empty " + input.scriptSignature.asm)) + + // This step has no purpose beyond being synchronized with Bitcoin Core's bugs. OP_CODESEPARATOR + // is a legacy holdover from a previous, broken design of executing scripts that shipped in Bitcoin 0.1. + // It was seriously flawed and would have let anyone take anyone else's money. Later versions switched to + // the design we use today where scripts are executed independently but share a stack. This left the + // OP_CODESEPARATOR instruction having no purpose as it was only meant to be used internally, not actually + // ever put into scripts. Deleting OP_CODESEPARATOR is a step that should never be required but if we don't + // do it, we could split off the main chain. + logger.trace("Before Bitcoin-S Script to be connected: " + script) + val scriptWithOpCodeSeparatorsRemoved: Seq[ScriptToken] = + removeOpCodeSeparators(script) + + logger.trace( + "After Bitcoin-S Script to be connected: " + scriptWithOpCodeSeparatorsRemoved) + + val inputToSign = inputSigsRemoved(idx) + + // Set the input to the script of its output. Bitcoin Core does this but the step has no obvious purpose as + // the signature covers the hash of the prevout transaction which obviously includes the output script + // already. Perhaps it felt safer to him in some way, or is another leftover from how the code was written. + val scriptSig = + ScriptSignature.fromAsm(scriptWithOpCodeSeparatorsRemoved) + logger.trace(s"scriptSig $scriptSig") + val inputWithConnectedScript = TransactionInput( + inputToSign.previousOutput, + scriptSig, + inputToSign.sequence) + + //update the input at index i with inputWithConnectScript + val updatedInputs = for { + (input, index) <- inputSigsRemoved.zipWithIndex + } yield { + if (index == idx) { + inputWithConnectedScript + } else input + } + + val txWithInputSigsRemoved = BaseTransaction( + spendingTransaction.version, + updatedInputs, + spendingTransaction.outputs, + spendingTransaction.lockTime) + val sigHashBytes = hashType.num.bytes.reverse + + hashType match { + case _: SIGHASH_NONE => + val sigHashNoneTx: Transaction = + sigHashNone(txWithInputSigsRemoved, inputIndex) + sigHashNoneTx.bytes ++ sigHashBytes + + case _: SIGHASH_SINGLE => + if (idx >= spendingTransaction.outputs.size) { + // comment copied from bitcoinj + // The input index is beyond the number of outputs, it's a buggy signature made by a broken + // Bitcoin implementation. Bitcoin Core also contains a bug in handling this case: + // any transaction output that is signed in this case will result in both the signed output + // and any future outputs to this public key being steal-able by anyone who has + // the resulting signature and the public key (both of which are part of the signed tx input). + + // Bitcoin Core's bug is that SignatureHash was supposed to return a hash and on this codepath it + // actually returns the constant "1" to indicate an error, which is never checked for. Oops. + errorHash.bytes + } else { + val sigHashSingleTx = + sigHashSingle(txWithInputSigsRemoved, inputIndex) + sigHashSingleTx.bytes ++ sigHashBytes + } + + case _: SIGHASH_ALL => + val sigHashAllTx: Transaction = sigHashAll(txWithInputSigsRemoved) + sigHashAllTx.bytes ++ sigHashBytes + + case _: SIGHASH_ANYONECANPAY => + val txWithInputsRemoved = sigHashAnyoneCanPay( + txWithInputSigsRemoved, + inputWithConnectedScript) + txWithInputsRemoved.bytes ++ sigHashBytes + + case _: SIGHASH_ALL_ANYONECANPAY => + val sigHashAllTx = sigHashAll(txWithInputSigsRemoved) + val sigHashAllAnyoneCanPayTx = + sigHashAnyoneCanPay(sigHashAllTx, inputWithConnectedScript) + sigHashAllAnyoneCanPayTx.bytes ++ sigHashBytes + + case _: SIGHASH_NONE_ANYONECANPAY => + val sigHashNoneTx = sigHashNone(txWithInputSigsRemoved, inputIndex) + val sigHashNoneAnyoneCanPay = + sigHashAnyoneCanPay(sigHashNoneTx, inputWithConnectedScript) + sigHashNoneAnyoneCanPay.bytes ++ sigHashBytes + + case _: SIGHASH_SINGLE_ANYONECANPAY => + val sigHashSingleTx = + sigHashSingle(txWithInputSigsRemoved, inputIndex) + val sigHashSingleAnyoneCanPay = + sigHashAnyoneCanPay(sigHashSingleTx, inputWithConnectedScript) + sigHashSingleAnyoneCanPay.bytes ++ sigHashBytes + } + case SigVersionWitnessV0 => + val isNotAnyoneCanPay = !HashType.isAnyoneCanPay(hashType) + val isNotSigHashSingle = !(HashType.isSigHashSingle(hashType.num)) + val isNotSigHashNone = !(HashType.isSigHashNone(hashType.num)) + val inputIndexInt = idx + val emptyHash = DoubleSha256Digest.empty + + val outPointHash: ByteVector = if (isNotAnyoneCanPay) { + val prevOuts = spendingTransaction.inputs.map(_.previousOutput) + val bytes: ByteVector = BytesUtil.toByteVector(prevOuts) + CryptoUtil.doubleSHA256(bytes).bytes + } else emptyHash.bytes + + val sequenceHash: ByteVector = + if (isNotAnyoneCanPay && isNotSigHashNone && isNotSigHashSingle) { + val sequences = spendingTransaction.inputs.map(_.sequence) + val littleEndianSeq = + sequences.foldLeft(ByteVector.empty)(_ ++ _.bytes.reverse) + CryptoUtil.doubleSHA256(littleEndianSeq).bytes + } else emptyHash.bytes + + val outputHash: ByteVector = + if (isNotSigHashSingle && isNotSigHashNone) { + val outputs = spendingTransaction.outputs + val bytes = BytesUtil.toByteVector(outputs) + CryptoUtil.doubleSHA256(bytes).bytes + } else if ( + HashType.isSigHashSingle(hashType.num) && + idx < spendingTransaction.outputs.size + ) { + val output = spendingTransaction.outputs(inputIndexInt) + val bytes = CryptoUtil + .doubleSHA256(RawTransactionOutputParser.write(output)) + .bytes + bytes + } else emptyHash.bytes + + val scriptBytes = BytesUtil.toByteVector(script) + + val i = spendingTransaction.inputs(inputIndexInt) + + val serializationForSig: ByteVector = + spendingTransaction.version.bytes.reverse ++ outPointHash ++ sequenceHash ++ + i.previousOutput.bytes ++ CompactSizeUInt.calc(scriptBytes).bytes ++ + scriptBytes ++ amount.bytes ++ i.sequence.bytes.reverse ++ + outputHash ++ spendingTransaction.lockTime.bytes.reverse ++ hashType.num.bytes.reverse + logger.debug( + "Serialization for signature for WitnessV0Sig: " + BytesUtil + .encodeHex(serializationForSig)) + serializationForSig + } + } + + /** + * Hashes a [[org.bitcoins.core.wallet.utxo.InputSigningInfo InputSigningInfo]] to give the value that needs to be signed + * by a [[org.bitcoins.crypto.Sign Sign]] to + * produce a valid [[org.bitcoins.crypto.ECDigitalSignature ECDigitalSignature]] for a transaction + */ + def hashForSignature( + spendingTransaction: Transaction, + signingInfo: InputSigningInfo[InputInfo], + hashType: HashType): DoubleSha256Digest = { + val inputIndexOpt = + TxUtil.inputIndexOpt(signingInfo.inputInfo, spendingTransaction) + + if (inputIndexOpt.isEmpty) { + logger.warn("Our input is not contained in the spending transaction") + errorHash + } else if ( + (hashType.isInstanceOf[SIGHASH_SINGLE] || hashType + .isInstanceOf[SIGHASH_SINGLE_ANYONECANPAY]) && + inputIndexOpt.get >= spendingTransaction.outputs.size && + signingInfo.sigVersion != SigVersionWitnessV0 + ) { + logger.warn( + "When we have a SIGHASH_SINGLE we cannot have more inputs than outputs") + errorHash + } else { + val serializedTxForSignature = + serializeForSignature(spendingTransaction, signingInfo, hashType) + logger.trace( + "Serialized tx for signature: " + BytesUtil.encodeHex( + serializedTxForSignature)) + logger.trace("HashType: " + hashType.num) + CryptoUtil.doubleSHA256(serializedTxForSignature) + } + } + /** Sets the input's sequence number to zero EXCEPT for the input at inputIndex. */ private def setSequenceNumbersZero( inputs: Seq[TransactionInput], diff --git a/core/src/main/scala/org/bitcoins/core/crypto/TxSigComponent.scala b/core/src/main/scala/org/bitcoins/core/crypto/TxSigComponent.scala index 2eea82159f..c204d1640c 100644 --- a/core/src/main/scala/org/bitcoins/core/crypto/TxSigComponent.scala +++ b/core/src/main/scala/org/bitcoins/core/crypto/TxSigComponent.scala @@ -2,9 +2,11 @@ package org.bitcoins.core.crypto import org.bitcoins.core.currency.CurrencyUnit import org.bitcoins.core.number.UInt32 +import org.bitcoins.core.policy.Policy import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.script.flag.ScriptFlag +import org.bitcoins.core.wallet.utxo._ import scala.util.{Failure, Success, Try} @@ -44,6 +46,128 @@ sealed abstract class TxSigComponent { object TxSigComponent { + def apply( + inputInfo: InputInfo, + unsignedTx: Transaction, + flags: Seq[ScriptFlag] = Policy.standardFlags): TxSigComponent = { + inputInfo match { + case segwit: SegwitV0NativeInputInfo => + fromWitnessInput(segwit, unsignedTx, flags) + case unassigned: UnassignedSegwitNativeInputInfo => + fromWitnessInput(unassigned, unsignedTx, flags) + case p2sh: P2SHInputInfo => + fromP2SHInput(p2sh, unsignedTx, flags) + case raw: RawInputInfo => + fromRawInput(raw, unsignedTx, flags) + } + } + + private def setTransactionWitness( + inputInfo: InputInfo, + unsignedTx: Transaction): WitnessTransaction = { + val idx = TxUtil.inputIndex(inputInfo, unsignedTx) + val unsignedWtx = WitnessTransaction.toWitnessTx(unsignedTx) + + unsignedWtx.witness.witnesses(idx) match { + // Only set the witness if we don't already have one + case EmptyScriptWitness => + InputInfo.getScriptWitness(inputInfo) match { + case None => + unsignedWtx + case Some(scriptWitness) => + unsignedWtx.updateWitness(idx, scriptWitness) + } + case _: ScriptWitnessV0 => + unsignedWtx + } + } + + def fromWitnessInput( + inputInfo: SegwitV0NativeInputInfo, + unsignedTx: Transaction, + flags: Seq[ScriptFlag]): TxSigComponent = { + val idx = TxUtil.inputIndex(inputInfo, unsignedTx) + val wtx = setTransactionWitness(inputInfo, unsignedTx) + + WitnessTxSigComponent(wtx, UInt32(idx), inputInfo.output, flags) + } + + def fromWitnessInput( + inputInfo: UnassignedSegwitNativeInputInfo, + unsignedTx: Transaction, + flags: Seq[ScriptFlag]): TxSigComponent = { + val idx = TxUtil.inputIndex(inputInfo, unsignedTx) + val wtx = setTransactionWitness(inputInfo, unsignedTx) + + WitnessTxSigComponent(wtx, UInt32(idx), inputInfo.output, flags) + } + + def fromWitnessInput( + inputInfo: P2SHNestedSegwitV0InputInfo, + unsignedTx: Transaction, + flags: Seq[ScriptFlag] = Policy.standardFlags): TxSigComponent = { + val idx = TxUtil.inputIndex(inputInfo, unsignedTx) + val emptyInput = unsignedTx.inputs(idx) + val newInput = TransactionInput( + emptyInput.previousOutput, + P2SHScriptSignature(EmptyScriptSignature, inputInfo.redeemScript), + emptyInput.sequence) + val updatedTx = unsignedTx.updateInput(idx, newInput) + + val wtx = WitnessTransaction.toWitnessTx(updatedTx) + val updatedWtx = + wtx.updateWitness(idx, InputInfo.getScriptWitness(inputInfo).get) + + WitnessTxSigComponentP2SH(updatedWtx, UInt32(idx), inputInfo.output, flags) + } + + def fromP2SHInput( + inputInfo: P2SHInputInfo, + unsignedTx: Transaction, + flags: Seq[ScriptFlag]): TxSigComponent = { + inputInfo match { + case nonSegwit: P2SHNonSegwitInputInfo => + fromP2SHInput(nonSegwit, unsignedTx, flags) + case segwit: P2SHNestedSegwitV0InputInfo => + fromWitnessInput(segwit, unsignedTx, flags) + } + } + + def fromP2SHInput( + inputInfo: P2SHNonSegwitInputInfo, + unsignedTx: Transaction, + flags: Seq[ScriptFlag]): TxSigComponent = { + val idx = TxUtil.inputIndex(inputInfo, unsignedTx) + + val updatedTx = unsignedTx.inputs(idx).scriptSignature match { + case EmptyScriptSignature => + val emptyInput = unsignedTx.inputs(idx) + val newInput = TransactionInput( + emptyInput.previousOutput, + P2SHScriptSignature(EmptyScriptSignature, inputInfo.redeemScript), + emptyInput.sequence) + unsignedTx.updateInput(idx, newInput) + case _: P2SHScriptSignature => + unsignedTx + case invalid @ (_: CLTVScriptSignature | _: CSVScriptSignature | + _: ConditionalScriptSignature | _: MultiSignatureScriptSignature | + _: NonStandardScriptSignature | _: P2PKHScriptSignature | + _: P2PKScriptSignature | TrivialTrueScriptSignature) => + throw new IllegalArgumentException( + s"Unexpected script sig with P2SHNonSegwitInputInfo, got $invalid") + } + + P2SHTxSigComponent(updatedTx, UInt32(idx), inputInfo.output, flags) + } + + def fromRawInput( + inputInfo: RawInputInfo, + unsignedTx: Transaction, + flags: Seq[ScriptFlag] = Policy.standardFlags): TxSigComponent = { + val idx = TxUtil.inputIndex(inputInfo, unsignedTx) + BaseTxSigComponent(unsignedTx, UInt32(idx), inputInfo.output, flags) + } + def apply( transaction: Transaction, inputIndex: UInt32, diff --git a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala index 9d70644993..eb99e1b709 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/script/ScriptSignature.scala @@ -149,7 +149,9 @@ sealed trait P2SHScriptSignature extends ScriptSignature { /** The redeemScript represents the conditions that must be satisfied to spend the output */ def redeemScript: ScriptPubKey = { val scriptSig = scriptSignatureNoRedeemScript - if ( + if (asm.isEmpty) { + EmptyScriptPubKey + } else if ( scriptSig == EmptyScriptSignature && WitnessScriptPubKey.isWitnessScriptPubKey(asm.tail) ) { diff --git a/core/src/main/scala/org/bitcoins/core/protocol/transaction/TxUtil.scala b/core/src/main/scala/org/bitcoins/core/protocol/transaction/TxUtil.scala index c62b34c4b6..e7030a2f57 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/transaction/TxUtil.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/transaction/TxUtil.scala @@ -3,34 +3,14 @@ package org.bitcoins.core.protocol.transaction import org.bitcoins.core.currency.{CurrencyUnit, CurrencyUnits, Satoshis} import org.bitcoins.core.number.UInt32 import org.bitcoins.core.policy.Policy -import org.bitcoins.core.protocol.script.{ - CLTVScriptPubKey, - CSVScriptPubKey, - EmptyScriptSignature, - EmptyScriptWitness, - ScriptWitnessV0 -} +import org.bitcoins.core.protocol.script._ import org.bitcoins.core.script.control.OP_RETURN import org.bitcoins.core.script.crypto.HashType import org.bitcoins.core.wallet.builder.RawTxSigner.logger import org.bitcoins.core.wallet.builder.TxBuilderError import org.bitcoins.core.wallet.fee.FeeUnit import org.bitcoins.core.wallet.signer.BitcoinSigner -import org.bitcoins.core.wallet.utxo.{ - ConditionalInputInfo, - EmptyInputInfo, - InputInfo, - InputSigningInfo, - LockTimeInputInfo, - MultiSignatureInputInfo, - P2PKHInputInfo, - P2PKInputInfo, - P2PKWithTimeoutInputInfo, - P2SHInputInfo, - P2WPKHV0InputInfo, - P2WSHV0InputInfo, - UnassignedSegwitNativeInputInfo -} +import org.bitcoins.core.wallet.utxo._ import org.bitcoins.crypto.{DummyECDigitalSignature, Sign} import scala.annotation.tailrec @@ -149,7 +129,7 @@ object TxUtil { */ def addDummySigs(utx: Transaction, inputInfos: Vector[InputInfo])(implicit ec: ExecutionContext): Future[Transaction] = { - val dummyInputAndWitnnessFs = inputInfos.zipWithIndex.map { + val dummyInputAndWitnessFs = inputInfos.zipWithIndex.map { case (inputInfo, index) => val mockSigners = inputInfo.pubKeys.take(inputInfo.requiredSigs).map { pubKey => @@ -157,7 +137,9 @@ object TxUtil { } val mockSpendingInfo = - inputInfo.toSpendingInfo(mockSigners, HashType.sigHashAll) + inputInfo.toSpendingInfo(EmptyTransaction, + mockSigners, + HashType.sigHashAll) BitcoinSigner .sign(mockSpendingInfo, utx, isDummySignature = true) @@ -176,7 +158,7 @@ object TxUtil { } } - Future.sequence(dummyInputAndWitnnessFs).map { inputsAndWitnesses => + Future.sequence(dummyInputAndWitnessFs).map { inputsAndWitnesses => val inputs = inputsAndWitnesses.map(_._1) val txWitnesses = inputsAndWitnesses.map(_._2) TransactionWitness.fromWitOpt(txWitnesses) match { @@ -282,9 +264,10 @@ object TxUtil { /** * Checks if the fee is within a 'valid' range + * * @param estimatedFee the estimated amount of fee we should pay - * @param actualFee the actual amount of fee the transaction pays - * @param feeRate the fee rate in satoshis/vbyte we paid per byte on this tx + * @param actualFee the actual amount of fee the transaction pays + * @param feeRate the fee rate in satoshis/vbyte we paid per byte on this tx * @return */ def isValidFeeRange( @@ -322,4 +305,44 @@ object TxUtil { Success(()) } } + + /** Adds the signingInfo's scriptWitness from the transaction, if it has one */ + def addWitnessData( + tx: Transaction, + signingInfo: InputSigningInfo[InputInfo]): WitnessTransaction = { + val noWitnessWtx = WitnessTransaction.toWitnessTx(tx) + + val indexOpt = tx.inputs.zipWithIndex + .find(_._1.previousOutput == signingInfo.outPoint) + .map(_._2) + + val scriptWitnessOpt = InputInfo.getScriptWitness(signingInfo.inputInfo) + + (scriptWitnessOpt, indexOpt) match { + case (_, None) => + throw new IllegalArgumentException( + s"Input is not contained in tx, got $signingInfo") + case (None, Some(_)) => + noWitnessWtx + case (Some(scriptWitness), Some(index)) => + noWitnessWtx.updateWitness(index, scriptWitness) + } + } + + /** Returns the index of the InputInfo in the transaction */ + def inputIndex(inputInfo: InputInfo, tx: Transaction): Int = { + inputIndexOpt(inputInfo, tx) match { + case Some(index) => index + case None => + throw new IllegalArgumentException( + s"The transaction did not contain the expected outPoint (${inputInfo.outPoint}), got $tx") + } + } + + /** Returns the index of the InputInfo in the transaction */ + def inputIndexOpt(inputInfo: InputInfo, tx: Transaction): Option[Int] = { + tx.inputs.zipWithIndex + .find(_._1.previousOutput == inputInfo.outPoint) + .map(_._2) + } } diff --git a/core/src/main/scala/org/bitcoins/core/psbt/PSBT.scala b/core/src/main/scala/org/bitcoins/core/psbt/PSBT.scala index 60b6048505..4f8fa99a34 100644 --- a/core/src/main/scala/org/bitcoins/core/psbt/PSBT.scala +++ b/core/src/main/scala/org/bitcoins/core/psbt/PSBT.scala @@ -3,23 +3,14 @@ package org.bitcoins.core.psbt import org.bitcoins.core.crypto._ import org.bitcoins.core.hd.BIP32Path import org.bitcoins.core.number.UInt32 -import org.bitcoins.core.policy.Policy import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.transaction._ -import org.bitcoins.core.script.PreExecutionScriptProgram import org.bitcoins.core.script.crypto.HashType import org.bitcoins.core.script.interpreter.ScriptInterpreter -import org.bitcoins.core.script.result.ScriptOk -import org.bitcoins.core.util.BitcoinSLogger +import org.bitcoins.core.util.{BitcoinSLogger, BitcoinScriptUtil} import org.bitcoins.core.wallet.signer.BitcoinSigner import org.bitcoins.core.wallet.utxo._ -import org.bitcoins.crypto.{ - ECDigitalSignature, - ECPublicKey, - Factory, - NetworkElement, - Sign -} +import org.bitcoins.crypto._ import scodec.bits._ import scala.annotation.tailrec @@ -39,6 +30,16 @@ case class PSBT( outputMaps.size == transaction.outputs.size, s"There must be an output map for every output in the global transaction, outputs: ${transaction.outputs}") + require( + inputMaps.zip(transaction.inputs).forall { + case (inputMap, txIn) => + val prevTxOpt = inputMap.nonWitnessOrUnknownUTXOOpt + prevTxOpt.isEmpty || prevTxOpt.get.transactionSpent.txId == txIn.previousOutput.txId + }, + "Some of the inputMaps' nonWitnessOrUnknownUTXO txId does not match the unsigned transaction's txId" + + s"got $inputMaps, ${transaction.inputs}" + ) + import org.bitcoins.core.psbt.InputPSBTRecord._ import org.bitcoins.core.psbt.PSBTInputKeyId._ @@ -203,8 +204,12 @@ case class PSBT( inputMap.redeemScriptOpt.isDefined && WitnessScriptPubKey .isWitnessScriptPubKey( inputMap.redeemScriptOpt.get.redeemScript.asm) + val notBIP143Vulnerable = + !out.scriptPubKey.isInstanceOf[WitnessScriptPubKeyV0] - if (outIsWitnessScript || hasWitScript || hasWitRedeemScript) { + if ( + (outIsWitnessScript || hasWitScript || hasWitRedeemScript) && notBIP143Vulnerable + ) { inputMap.filterRecords(WitnessUTXOKeyId) :+ WitnessUTXO(out) } else { inputMap.filterRecords( @@ -605,54 +610,7 @@ case class PSBT( inputMaps.zipWithIndex.foldLeft(Try(extractTransaction)) { case (txT, (inputMap, index)) => txT.flatMap { tx => - val wUtxoOpt = inputMap.witnessUTXOOpt - val utxoOpt = inputMap.nonWitnessOrUnknownUTXOOpt - - (wUtxoOpt, tx) match { - case (Some(wUtxo), wtx: WitnessTransaction) => - val output = wUtxo.witnessUTXO - val txSigComponent = WitnessTxSigComponent(wtx, - UInt32(index), - output, - Policy.standardFlags) - val inputResult = - ScriptInterpreter.run(PreExecutionScriptProgram(txSigComponent)) - if (inputResult == ScriptOk) { - Success(tx) - } else { - Failure( - new RuntimeException( - s"Input $index was invalid: $inputResult")) - } - case (Some(_), _: NonWitnessTransaction) => - Failure(new RuntimeException( - s"Extracted program is not witness transaction, but input $index has WitnessUTXO record")) - case (None, _) => - utxoOpt match { - case Some(utxo) => - val input = tx.inputs(index) - val output = utxo.transactionSpent.outputs( - input.previousOutput.vout.toInt) - val txSigComponent = BaseTxSigComponent(tx, - UInt32(index), - output, - Policy.standardFlags) - val inputResult = ScriptInterpreter.run( - PreExecutionScriptProgram(txSigComponent)) - if (inputResult == ScriptOk) { - Success(tx) - } else { - Failure( - new RuntimeException( - s"Input $index was invalid: $inputResult")) - } - case None => - logger.info( - s"No UTXO record was provided for input $index, hence no validation was done for this input") - - Success(tx) - } - } + BitcoinScriptUtil.verifyPSBTInputScript(tx, inputMap, index) } } } @@ -680,8 +638,7 @@ case class PSBT( case None => witness case Some( InputPSBTRecord.FinalizedScriptWitness(scriptWitness)) => - TransactionWitness( - witness.updated(index, scriptWitness).toVector) + witness.updated(index, scriptWitness) } } WitnessTransaction(transaction.version, @@ -815,38 +772,13 @@ object PSBT extends Factory[PSBT] { PSBT(globalMap, inputMaps, outputMaps) } - /** - * Wraps a Vector of pairs of NewSpendingInfos and the Transactions whose outputs are spent. - * Note that this Transaction is only necessary when the output is non-segwit. - */ - case class SpendingInfoAndNonWitnessTxs( - infoAndTxOpts: Vector[ - (ScriptSignatureParams[InputInfo], Option[BaseTransaction])]) { - val length: Int = infoAndTxOpts.length - - def matchesInputs(inputs: Seq[TransactionInput]): Boolean = { - infoAndTxOpts - .zip(inputs) - .forall { - case ((info, _), input) => info.outPoint == input.previousOutput - } - } - - def map[T]( - func: ( - ScriptSignatureParams[InputInfo], - Option[BaseTransaction]) => T): Vector[T] = { - infoAndTxOpts.map { case (info, txOpt) => func(info, txOpt) } - } - } - /** Constructs a full (ready to be finalized) but unfinalized PSBT from an * unsigned transaction and a SpendingInfoAndNonWitnessTxs */ def fromUnsignedTxAndInputs( unsignedTx: Transaction, - spendingInfoAndNonWitnessTxs: SpendingInfoAndNonWitnessTxs)(implicit - ec: ExecutionContext): Future[PSBT] = { + spendingInfoAndNonWitnessTxs: Vector[ScriptSignatureParams[InputInfo]])( + implicit ec: ExecutionContext): Future[PSBT] = { fromUnsignedTxAndInputs(unsignedTx, spendingInfoAndNonWitnessTxs, finalized = false) @@ -857,22 +789,22 @@ object PSBT extends Factory[PSBT] { */ def finalizedFromUnsignedTxAndInputs( unsignedTx: Transaction, - spendingInfoAndNonWitnessTxs: SpendingInfoAndNonWitnessTxs)(implicit + spendingInfos: Vector[ScriptSignatureParams[InputInfo]])(implicit ec: ExecutionContext): Future[PSBT] = { - fromUnsignedTxAndInputs(unsignedTx, - spendingInfoAndNonWitnessTxs, - finalized = true) + fromUnsignedTxAndInputs(unsignedTx, spendingInfos, finalized = true) } private def fromUnsignedTxAndInputs( unsignedTx: Transaction, - spendingInfoAndNonWitnessTxs: SpendingInfoAndNonWitnessTxs, + spendingInfos: Vector[ScriptSignatureParams[InputInfo]], finalized: Boolean)(implicit ec: ExecutionContext): Future[PSBT] = { - require(spendingInfoAndNonWitnessTxs.length == unsignedTx.inputs.length, - "Must have a NewSpendingInfo for every input") + require(spendingInfos.length == unsignedTx.inputs.length, + "Must have a SpendingInfo for every input") require( - spendingInfoAndNonWitnessTxs.matchesInputs(unsignedTx.inputs), - "NewSpendingInfos must correspond to transaction inputs" + spendingInfos.zip(unsignedTx.inputs).forall { + case (info, input) => info.outPoint == input.previousOutput + }, + "SpendingInfos must correspond to transaction inputs" ) val emptySigTx = TxUtil.emptyAllScriptSigs(unsignedTx) val btx = emptySigTx match { @@ -883,13 +815,12 @@ object PSBT extends Factory[PSBT] { val globalMap = GlobalPSBTMap( Vector(GlobalPSBTRecord.UnsignedTransaction(btx))) - val inputMapFs = spendingInfoAndNonWitnessTxs.map { - case (info, txOpt) => - if (finalized) { - InputPSBTMap.finalizedFromNewSpendingInfo(info, unsignedTx, txOpt) - } else { - InputPSBTMap.fromUTXOInfo(info, unsignedTx, txOpt) - } + val inputMapFs = spendingInfos.map { info => + if (finalized) { + InputPSBTMap.finalizedFromSpendingInfo(info, unsignedTx) + } else { + InputPSBTMap.fromUTXOInfo(info, unsignedTx) + } } val outputMaps = unsignedTx.outputs.map(_ => OutputPSBTMap.empty).toVector diff --git a/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala b/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala index 95d4d305fb..68214bf948 100644 --- a/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala +++ b/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala @@ -3,24 +3,12 @@ package org.bitcoins.core.psbt import org.bitcoins.core.byteVectorOrdering import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.script._ -import org.bitcoins.core.protocol.transaction.{ - NonWitnessTransaction, - Transaction, - TransactionInput, - TransactionOutput, - WitnessTransaction -} +import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.script.crypto.HashType import org.bitcoins.core.util.SeqWrapper import org.bitcoins.core.wallet.signer.BitcoinSigner import org.bitcoins.core.wallet.utxo._ -import org.bitcoins.crypto.{ - CryptoUtil, - Factory, - NetworkElement, - Sha256Hash160Digest, - Sign -} +import org.bitcoins.crypto._ import scodec.bits.ByteVector import scala.annotation.tailrec @@ -516,17 +504,25 @@ case class InputPSBTMap(elements: Vector[InputPSBTRecord]) ScriptSignatureParams( infoSingle.inputInfo, + infoSingle.prevTransaction, signers, infoSingle.hashType ) } - def toUTXOSigningInfo( + def toInputInfo( txIn: TransactionInput, - signer: Sign, - conditionalPath: ConditionalPath = - ConditionalPath.NoCondition): ECSignatureParams[InputInfo] = { - require(!isFinalized, s"Cannot update an InputPSBTMap that is finalized") + conditionalPath: ConditionalPath = ConditionalPath.NoCondition, + preImages: Vector[NetworkElement] = Vector.empty): InputInfo = { + if (isFinalized) + toInputInfoFinalized(txIn, conditionalPath, preImages) + else toInputInfoNonFinalized(txIn, conditionalPath, preImages) + } + + def toInputInfoFinalized( + txIn: TransactionInput, + conditionalPath: ConditionalPath, + preImages: Vector[NetworkElement]): InputInfo = { val outPoint = txIn.previousOutput val witVec = getRecords(WitnessUTXOKeyId) @@ -539,13 +535,54 @@ case class InputPSBTMap(elements: Vector[InputPSBTRecord]) tx.outputs(txIn.previousOutput.vout.toInt) } else { throw new UnsupportedOperationException( - "Not enough information in the InputPSBTMap to get a valid NewSpendingInfo") + "Not enough information in the InputPSBTMap to get a valid InputInfo") } - val redeemScriptVec = getRecords(RedeemScriptKeyId) - val redeemScriptOpt = - if (redeemScriptVec.size == 1) Some(redeemScriptVec.head.redeemScript) - else None + val redeemScriptOpt = finalizedScriptSigOpt match { + case Some(scriptSig) => + scriptSig.scriptSig match { + case p2sh: P2SHScriptSignature => + Some(p2sh.redeemScript) + case TrivialTrueScriptSignature | EmptyScriptSignature | + _: CLTVScriptSignature | _: CSVScriptSignature | + _: ConditionalScriptSignature | _: MultiSignatureScriptSignature | + _: NonStandardScriptSignature | _: P2PKHScriptSignature | + _: P2PKScriptSignature => + None + } + case None => None + } + + val scriptWitnessOpt = finalizedScriptWitnessOpt.map(_.scriptWitness) + + InputInfo(outPoint, + output, + redeemScriptOpt, + scriptWitnessOpt, + conditionalPath, + preImages) + } + + private def toInputInfoNonFinalized( + txIn: TransactionInput, + conditionalPath: ConditionalPath, + preImages: Vector[NetworkElement]): InputInfo = { + val outPoint = txIn.previousOutput + + val witVec = getRecords(WitnessUTXOKeyId) + val txVec = getRecords(NonWitnessUTXOKeyId) + + val output = if (witVec.size == 1) { + witVec.head.witnessUTXO + } else if (txVec.size == 1) { + val tx = txVec.head.transactionSpent + tx.outputs(txIn.previousOutput.vout.toInt) + } else { + throw new RuntimeException( + "Not enough information in the InputPSBTMap to get a valid InputInfo") + } + + val redeemScriptOpt = this.redeemScriptOpt.map(_.redeemScript) val scriptWitnessVec = getRecords(WitnessScriptKeyId) val scriptWitnessOpt = @@ -556,52 +593,80 @@ case class InputPSBTMap(elements: Vector[InputPSBTRecord]) .isInstanceOf[P2WPKHWitnessSPKV0] || redeemScriptOpt.exists( _.isInstanceOf[P2WPKHWitnessSPKV0]) ) { - Some(P2WPKHWitnessV0(signer.publicKey)) + require(preImages.size == 1, + "P2WPKHWitnessV0 must have it's public key as a pre-image") + + preImages.head match { + case pubKey: ECPublicKey => + Some(P2WPKHWitnessV0(pubKey)) + case _: NetworkElement => + throw new IllegalArgumentException( + "P2WPKHWitnessV0 must have it's public key as a pre-image") + } } else { None } + InputInfo(outPoint, + output, + redeemScriptOpt, + scriptWitnessOpt, + conditionalPath, + preImages) + } + + def toUTXOSigningInfo( + txIn: TransactionInput, + signer: Sign, + conditionalPath: ConditionalPath = + ConditionalPath.NoCondition): ECSignatureParams[InputInfo] = { + require(!isFinalized, s"Cannot update an InputPSBTMap that is finalized") + val txVec = getRecords(NonWitnessUTXOKeyId) + val hashTypeVec = getRecords(SigHashTypeKeyId) val hashType = if (hashTypeVec.size == 1) hashTypeVec.head.hashType else HashType.sigHashAll - val inputInfo = InputInfo(outPoint, - output, - redeemScriptOpt, - scriptWitnessOpt, - conditionalPath, - Vector(signer.publicKey)) + val inputInfo = toInputInfo(txIn, conditionalPath, Vector(signer.publicKey)) - ECSignatureParams(inputInfo, signer, hashType) + ECSignatureParams(inputInfo, txVec.head.transactionSpent, signer, hashType) } private def changeToWitnessUTXO( transactionOutput: TransactionOutput): InputPSBTMap = { val newElements = transactionOutput.scriptPubKey match { - case _: WitnessScriptPubKey => - filterRecords(NonWitnessUTXOKeyId) :+ WitnessUTXO(transactionOutput) case _: P2SHScriptPubKey => - if ( - redeemScriptOpt.isDefined && redeemScriptOpt.get.redeemScript - .isInstanceOf[WitnessScriptPubKey] - ) { - filterRecords(NonWitnessUTXOKeyId) :+ WitnessUTXO(transactionOutput) + if (redeemScriptOpt.isDefined) { + val redeemScript = redeemScriptOpt.get.redeemScript + // SegwitV0 has a vulnerability where we need the full funding tx to be safe + // future versions of segwit should be considered safe however + if ( + redeemScript.isInstanceOf[WitnessScriptPubKey] && !redeemScript + .isInstanceOf[WitnessScriptPubKeyV0] + ) { + filterRecords(NonWitnessUTXOKeyId) :+ WitnessUTXO(transactionOutput) + } else { + elements + } } else { elements } - case _: P2PKHScriptPubKey | _: P2PKScriptPubKey | - _: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey | - EmptyScriptPubKey | _: LockTimeScriptPubKey | - _: NonStandardScriptPubKey | _: WitnessCommitment | - _: ConditionalScriptPubKey => + case _: WitnessScriptPubKeyV0 | _: P2PKHScriptPubKey | + _: P2PKScriptPubKey | _: P2PKWithTimeoutScriptPubKey | + _: MultiSignatureScriptPubKey | EmptyScriptPubKey | + _: LockTimeScriptPubKey | _: NonStandardScriptPubKey | + _: WitnessCommitment | _: ConditionalScriptPubKey => elements + case _: WitnessScriptPubKey => + filterRecords(NonWitnessUTXOKeyId) :+ WitnessUTXO(transactionOutput) } InputPSBTMap(newElements) } /** + * After a discovered vulnerability in BIP-143, this is no longer safe for SegwitV0 * Check if this satisfies criteria for witness. If it does, delete the NonWitnessOrUnknownUTXO field * This is useful for following reasons. * 1. Compresses the size of the data @@ -639,25 +704,20 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] { * the corresponding PSBT's unsigned transaction, and if this is * a non-witness spend, the transaction being spent */ - def finalizedFromNewSpendingInfo( + def finalizedFromSpendingInfo( spendingInfo: ScriptSignatureParams[InputInfo], - unsignedTx: Transaction, - nonWitnessTxOpt: Option[Transaction])(implicit + unsignedTx: Transaction)(implicit ec: ExecutionContext): Future[InputPSBTMap] = { val sigComponentF = BitcoinSigner .sign(spendingInfo, unsignedTx, isDummySignature = false) sigComponentF.map { sigComponent => val utxos = spendingInfo.inputInfo match { - case _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo => + case _: UnassignedSegwitNativeInputInfo => Vector(WitnessUTXO(spendingInfo.output)) case _: RawInputInfo | _: P2SHNonSegwitInputInfo | - _: UnassignedSegwitNativeInputInfo => - nonWitnessTxOpt match { - case None => Vector.empty - case Some(nonWitnessTx) => - Vector(NonWitnessOrUnknownUTXO(nonWitnessTx)) - } + _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo => + Vector(NonWitnessOrUnknownUTXO(spendingInfo.prevTransaction)) } val scriptSig = @@ -685,8 +745,7 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] { */ def fromUTXOInfo( spendingInfo: ScriptSignatureParams[InputInfo], - unsignedTx: Transaction, - nonWitnessTxOpt: Option[Transaction])(implicit + unsignedTx: Transaction)(implicit ec: ExecutionContext): Future[InputPSBTMap] = { val sigsF = spendingInfo.toSingles.map { spendingInfoSingle => BitcoinSigner.signSingle(spendingInfoSingle, @@ -700,15 +759,11 @@ object InputPSBTMap extends PSBTMapFactory[InputPSBTRecord, InputPSBTMap] { val builder = Vector.newBuilder[InputPSBTRecord] spendingInfo.inputInfo match { - case _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo => + case _: UnassignedSegwitNativeInputInfo => builder.+=(WitnessUTXO(spendingInfo.output)) case _: RawInputInfo | _: P2SHNonSegwitInputInfo | - _: UnassignedSegwitNativeInputInfo => - nonWitnessTxOpt match { - case None => () - case Some(nonWitnessTx) => - builder.+=(NonWitnessOrUnknownUTXO(nonWitnessTx)) - } + _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo => + builder.+=(NonWitnessOrUnknownUTXO(spendingInfo.prevTransaction)) } builder.++=(sigs) diff --git a/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala b/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala index c2d3116152..72aef0ad13 100644 --- a/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala +++ b/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala @@ -14,13 +14,7 @@ import org.bitcoins.core.protocol.transaction.{ import org.bitcoins.core.script.crypto.HashType import org.bitcoins.core.serializers.script.RawScriptWitnessParser import org.bitcoins.core.util.BytesUtil -import org.bitcoins.crypto.{ - DummyECDigitalSignature, - ECDigitalSignature, - ECPublicKey, - Factory, - NetworkElement -} +import org.bitcoins.crypto._ import scodec.bits.ByteVector sealed trait PSBTRecord extends NetworkElement { @@ -157,6 +151,11 @@ object InputPSBTRecord extends Factory[InputPSBTRecord] { case class WitnessUTXO(witnessUTXO: TransactionOutput) extends InputPSBTRecord { + require( + !witnessUTXO.scriptPubKey.isInstanceOf[WitnessScriptPubKeyV0], + "This UTXO is vulnerable to the BIP143 vulnerability, use NonWitnessOrUnknownUTXO instead" + ) + override type KeyId = WitnessUTXOKeyId.type override val key: ByteVector = ByteVector(WitnessUTXOKeyId.byte) override val value: ByteVector = witnessUTXO.bytes diff --git a/core/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala b/core/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala index b0daf87be3..8e88b94725 100644 --- a/core/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala +++ b/core/src/main/scala/org/bitcoins/core/util/BitcoinScriptUtil.scala @@ -12,12 +12,8 @@ import org.bitcoins.core.protocol.script.{ EmptyScriptPubKey, _ } -import org.bitcoins.core.protocol.transaction.{ - Transaction, - TransactionInput, - TransactionOutput, - WitnessTransaction -} +import org.bitcoins.core.protocol.transaction._ +import org.bitcoins.core.psbt.InputPSBTMap import org.bitcoins.core.script.constant._ import org.bitcoins.core.script.crypto.{ OP_CHECKMULTISIG, @@ -30,19 +26,20 @@ import org.bitcoins.core.script.interpreter.ScriptInterpreter import org.bitcoins.core.script.result.{ ScriptError, ScriptErrorPubKeyType, - ScriptErrorWitnessPubKeyType + ScriptErrorWitnessPubKeyType, + ScriptOk } import org.bitcoins.core.script.{ ExecutionInProgressScriptProgram, PreExecutionScriptProgram } import org.bitcoins.core.serializers.script.ScriptParser -import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams} +import org.bitcoins.core.wallet.utxo._ import org.bitcoins.crypto.{ECDigitalSignature, ECPublicKey} import scodec.bits.ByteVector import scala.annotation.tailrec -import scala.util.Try +import scala.util.{Failure, Success, Try} /** * Created by chris on 3/2/16. @@ -482,6 +479,52 @@ trait BitcoinScriptUtil extends BitcoinSLogger { script } + def calculateScriptForSigning( + spendingTransaction: Transaction, + signingInfo: InputSigningInfo[InputInfo], + script: Seq[ScriptToken]): Seq[ScriptToken] = { + + val idx = TxUtil.inputIndex(signingInfo.inputInfo, spendingTransaction) + + signingInfo.output.scriptPubKey match { + case _: P2SHScriptPubKey => + val p2sh = signingInfo.inputInfo.asInstanceOf[P2SHInputInfo] + + p2sh.redeemScript match { + case p2wpkh: P2WPKHWitnessSPKV0 => + //we treat p2sh(p2wpkh) differently for script signing than other spks + //Please note that for a P2SH-P2WPKH, the scriptCode is always 26 bytes including the leading size byte, + // as 0x1976a914{20-byte keyhash}88ac, NOT the redeemScript nor scriptPubKey + //https://bitcoincore.org/en/segwit_wallet_dev/#signature-generation-and-verification-for-p2sh-p2wpkh + + P2PKHScriptPubKey(p2wpkh.pubKeyHash).asm + + case _: P2WSHWitnessSPKV0 => + val wtx = + spendingTransaction.asInstanceOf[WitnessTransaction] + + val p2wshRedeem = + ScriptPubKey.fromAsmBytes(wtx.witness.witnesses(idx).stack.head) + p2wshRedeem.asm + case script: ScriptPubKey => + script.asm + } + + case w: WitnessScriptPubKey => + val wtx = spendingTransaction.asInstanceOf[WitnessTransaction] + val scriptEither = + w.witnessVersion.rebuild(wtx.witness.witnesses(idx), w.witnessProgram) + parseScriptEither(scriptEither) + + case _: P2PKHScriptPubKey | _: P2PKScriptPubKey | + _: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey | + _: ConditionalScriptPubKey | _: NonStandardScriptPubKey | + _: CLTVScriptPubKey | _: CSVScriptPubKey | _: WitnessCommitment | + EmptyScriptPubKey => + script + } + } + /** Removes the given [[ECDigitalSignature ECDigitalSignature]] from the list of * [[org.bitcoins.core.script.constant.ScriptToken ScriptToken]] if it exists. */ def removeSignatureFromScript( @@ -671,6 +714,33 @@ trait BitcoinScriptUtil extends BitcoinSLogger { } ScriptInterpreter.runAllVerify(programs) } + + def verifyPSBTInputScript( + tx: Transaction, + inputMap: InputPSBTMap, + index: Int, + flags: Seq[ScriptFlag] = Policy.standardFlags): Try[Transaction] = { + + val txIn = tx.inputs(index) + + val (preImages, condPath) = + InputInfo.getHashPreImagesAndConditionalPath(tx, index) + + val inputInfo = inputMap.toInputInfo(txIn, + conditionalPath = condPath, + preImages = preImages) + + val txSigComponent = TxSigComponent(inputInfo, tx, flags) + + val inputResult = + ScriptInterpreter.run(PreExecutionScriptProgram(txSigComponent)) + + if (inputResult == ScriptOk) { + Success(tx) + } else { + Failure(new RuntimeException(s"Input $index was invalid: $inputResult")) + } + } } object BitcoinScriptUtil extends BitcoinScriptUtil diff --git a/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxSigner.scala b/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxSigner.scala index 2feae9f94c..aff6529bab 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxSigner.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/builder/RawTxSigner.scala @@ -85,14 +85,6 @@ object RawTxSigner extends BitcoinSLogger { txSigCompF.map { txSigComp => val scriptWitnessOpt = TxSigComponent.getScriptWitness(txSigComp) - if ( - scriptWitnessOpt.isEmpty && InputInfo - .getScriptWitness(utxo.inputInfo) - .isDefined - ) { - println(utxo.inputInfo) - } - (txSigComp.input, scriptWitnessOpt) } } diff --git a/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala b/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala index 5b9d001144..c0d02ceb00 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/signer/Signer.scala @@ -23,6 +23,7 @@ import scala.concurrent.{ExecutionContext, Future} sealed abstract class SignerUtils { + @deprecated("use an InputSigningInfo[InputInfo] instead", since = "6/23/2020") def doSign( sigComponent: TxSigComponent, sign: ByteVector => Future[ECDigitalSignature], @@ -36,13 +37,40 @@ sealed abstract class SignerUtils { } } + def doSign( + unsignedTx: Transaction, + signingInfo: InputSigningInfo[InputInfo], + sign: ByteVector => Future[ECDigitalSignature], + hashType: HashType, + isDummySignature: Boolean)(implicit + ec: ExecutionContext): Future[ECDigitalSignature] = { + if (isDummySignature) { + Future.successful(DummyECDigitalSignature) + } else { + TransactionSignatureCreator.createSig(unsignedTx, + signingInfo, + sign, + hashType) + } + } + def signSingle( spendingInfo: ECSignatureParams[InputInfo], unsignedTx: Transaction, isDummySignature: Boolean)(implicit ec: ExecutionContext): Future[PartialSignature] = { + + val tx = spendingInfo.inputInfo match { + case _: SegwitV0NativeInputInfo | _: P2SHNestedSegwitV0InputInfo | + _: UnassignedSegwitNativeInputInfo => + TxUtil.addWitnessData(unsignedTx, spendingInfo) + case _: RawInputInfo | _: P2SHNonSegwitInputInfo => + unsignedTx + } + val signatureF = doSign( - sigComponent = sigComponent(spendingInfo, unsignedTx), + unsignedTx = tx, + signingInfo = spendingInfo, sign = spendingInfo.signer.signFunction, hashType = spendingInfo.hashType, isDummySignature = isDummySignature @@ -79,52 +107,6 @@ sealed abstract class SignerUtils { "Transaction did not contain expected input.") } } - - protected def sigComponent( - spendingInfo: InputSigningInfo[InputInfo], - unsignedTx: Transaction): TxSigComponent = { - val index = inputIndex(spendingInfo, unsignedTx) - - spendingInfo.output.scriptPubKey match { - case _: WitnessScriptPubKey => - val wtx = { - val noWitnessWtx = WitnessTransaction.toWitnessTx(unsignedTx) - InputInfo.getScriptWitness(spendingInfo.inputInfo) match { - case None => - noWitnessWtx - case Some(scriptWitness) => - noWitnessWtx.updateWitness(index.toInt, scriptWitness) - } - } - - WitnessTxSigComponent(wtx, index, spendingInfo.output, flags) - case _: P2SHScriptPubKey => - val emptyInput = unsignedTx.inputs(index.toInt) - val redeemScript = InputInfo.getRedeemScript(spendingInfo.inputInfo).get - val newInput = TransactionInput( - emptyInput.previousOutput, - P2SHScriptSignature(EmptyScriptSignature, redeemScript), - emptyInput.sequence) - val updatedTx = unsignedTx.updateInput(index.toInt, newInput) - redeemScript match { - case _: WitnessScriptPubKey => - val wtx = WitnessTransaction.toWitnessTx(updatedTx) - val updatedWtx = - wtx.updateWitness( - index.toInt, - InputInfo.getScriptWitness(spendingInfo.inputInfo).get) - - WitnessTxSigComponentP2SH(updatedWtx, - index, - spendingInfo.output, - flags) - case _: ScriptPubKey => - P2SHTxSigComponent(updatedTx, index, spendingInfo.output, flags) - } - case _: ScriptPubKey => - BaseTxSigComponent(unsignedTx, index, spendingInfo.output, flags) - } - } } /** The class used to represent a signing process for a specific [[org.bitcoins.core.protocol.script.ScriptPubKey]] type */ @@ -590,13 +572,12 @@ sealed abstract class P2WPKHSigner extends Signer[P2WPKHV0InputInfo] { witSPK.flatMap { w => val witOutput = TransactionOutput(output.value, w) - val wtxComp = WitnessTxSigComponentRaw(unsignedWtx, - inputIndex, - witOutput, - flags) - val signature = - doSign(wtxComp, signer.signFunction, hashType, isDummySignature) + doSign(unsignedTx, + spendingInfo, + signer.signFunction, + hashType, + isDummySignature) signature.map { sig => val scriptWitness = P2WPKHWitnessV0(pubKey, sig) diff --git a/core/src/main/scala/org/bitcoins/core/wallet/utxo/ConditionalPath.scala b/core/src/main/scala/org/bitcoins/core/wallet/utxo/ConditionalPath.scala index 2c422ca2e5..d2d76ba99d 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/utxo/ConditionalPath.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/utxo/ConditionalPath.scala @@ -1,5 +1,7 @@ package org.bitcoins.core.wallet.utxo +import scala.annotation.tailrec + /** Represents the spending branch being taken in a ScriptPubKey's execution * * If you over-specify a path, such as giving a condition where none is needed, @@ -38,6 +40,25 @@ object ConditionalPath { val nonNestedTrue: ConditionalPath = ConditionTrue(NoCondition) val nonNestedFalse: ConditionalPath = ConditionFalse(NoCondition) + def toVector(conditionalPath: ConditionalPath): Vector[Boolean] = { + + @tailrec + def loop( + current: ConditionalPath, + accum: Vector[Boolean]): Vector[Boolean] = { + current match { + case cond: ConditionTrue => + loop(cond.nextCondition, accum :+ true) + case cond: ConditionFalse => + loop(cond.nextCondition, accum :+ false) + case NoCondition => + accum + } + } + + loop(conditionalPath, Vector.empty) + } + def fromBranch(branch: Vector[Boolean]): ConditionalPath = { if (branch.isEmpty) { NoCondition diff --git a/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputInfo.scala b/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputInfo.scala index 2171066f46..e88c894fc9 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputInfo.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputInfo.scala @@ -2,14 +2,12 @@ package org.bitcoins.core.wallet.utxo import org.bitcoins.core.currency.CurrencyUnit import org.bitcoins.core.protocol.script._ -import org.bitcoins.core.protocol.transaction.{ - OutputReference, - TransactionOutPoint, - TransactionOutput -} +import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.script.crypto.HashType import org.bitcoins.crypto.{ECPublicKey, NetworkElement, Sign} +import scala.annotation.tailrec + /** An InputInfo contains all information other than private keys about * a particular spending condition in a UTXO. * @@ -39,15 +37,17 @@ sealed trait InputInfo { def requiredSigs: Int def toSpendingInfo( + prevTransaction: Transaction, signers: Vector[Sign], hashType: HashType): ScriptSignatureParams[InputInfo] = { - ScriptSignatureParams(this, signers, hashType) + ScriptSignatureParams(this, prevTransaction, signers, hashType) } def toSpendingInfo( + prevTransaction: Transaction, signer: Sign, hashType: HashType): ECSignatureParams[InputInfo] = { - ECSignatureParams(this, signer, hashType) + ECSignatureParams(this, prevTransaction, signer, hashType) } def genericWithSignFrom( @@ -112,6 +112,57 @@ object InputInfo { } } + /** + * Returns the needed hash pre-images and conditional path that was used to spend the input + * at inputIndex, this is calculated through the ScriptSignature and ScriptWitness + */ + def getHashPreImagesAndConditionalPath( + signedTransaction: Transaction, + inputIndex: Int): (Vector[NetworkElement], ConditionalPath) = { + + val txIn = signedTransaction.inputs(inputIndex) + + @tailrec + def getPreImagesAndCondPath( + scriptSignature: ScriptSignature, + conditionalPath: Vector[Boolean] = Vector.empty): ( + Vector[NetworkElement], + Vector[Boolean]) = { + scriptSignature match { + case p2pkh: P2PKHScriptSignature => + (Vector(p2pkh.publicKey), conditionalPath) + case cond: ConditionalScriptSignature => + val path = conditionalPath :+ cond.isTrue + getPreImagesAndCondPath(cond.nestedScriptSig, path) + case p2sh: P2SHScriptSignature => + getPreImagesAndCondPath(p2sh.scriptSignatureNoRedeemScript, + conditionalPath) + case _: ScriptSignature => + (Vector.empty, conditionalPath) + } + } + + val (preImages, conditionsVec) = signedTransaction match { + case EmptyTransaction => + (Vector.empty, Vector.empty) + case _: BaseTransaction => + getPreImagesAndCondPath(txIn.scriptSignature) + case wtx: WitnessTransaction => + wtx.witness.witnesses(inputIndex) match { + case p2wpkh: P2WPKHWitnessV0 => + (Vector(p2wpkh.pubKey), Vector.empty) + case p2wsh: P2WSHWitnessV0 => + getPreImagesAndCondPath(p2wsh.scriptSignature) + case EmptyScriptWitness => + getPreImagesAndCondPath(txIn.scriptSignature) + } + } + + val conditionalPath = ConditionalPath.fromBranch(conditionsVec) + + (preImages, conditionalPath) + } + def apply( outPoint: TransactionOutPoint, output: TransactionOutput, @@ -201,14 +252,15 @@ object RawInputInfo { conditionalPath: ConditionalPath, hashPreImages: Vector[NetworkElement] = Vector.empty): RawInputInfo = { scriptPubKey match { - case p2pk: P2PKScriptPubKey => P2PKInputInfo(outPoint, amount, p2pk) + case p2pk: P2PKScriptPubKey => + P2PKInputInfo(outPoint, amount, p2pk) case p2pkh: P2PKHScriptPubKey => hashPreImages.collectFirst { case pubKey: ECPublicKey => pubKey } match { case None => throw new IllegalArgumentException( - "P2PKH pre-image must be specified for P2PKH ScriptPubKey") + s"P2PKH pre-image must be specified for P2PKH ScriptPubKey, got $hashPreImages") case Some(p2pkhPreImage) => require( P2PKHScriptPubKey(p2pkhPreImage) == p2pkh, diff --git a/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputSigningInfo.scala b/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputSigningInfo.scala index fe0f7f67a8..18840bd777 100644 --- a/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputSigningInfo.scala +++ b/core/src/main/scala/org/bitcoins/core/wallet/utxo/InputSigningInfo.scala @@ -1,11 +1,12 @@ package org.bitcoins.core.wallet.utxo import org.bitcoins.core.currency.CurrencyUnit -import org.bitcoins.core.protocol.transaction.{ - OutputReference, - TransactionOutPoint, - TransactionOutput +import org.bitcoins.core.protocol.script.{ + SigVersionBase, + SigVersionWitnessV0, + SignatureVersion } +import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.script.crypto.HashType import org.bitcoins.crypto.Sign @@ -15,9 +16,24 @@ import org.bitcoins.crypto.Sign */ sealed trait InputSigningInfo[+InputType <: InputInfo] { def inputInfo: InputType + def prevTransaction: Transaction def hashType: HashType def signers: Vector[Sign] + // If using EmptyTransaction we are testing or dummy signing + require( + prevTransaction == EmptyTransaction || outPoint.txId == prevTransaction.txId, + s"prevTransaction txId (${prevTransaction.txId.hex}) does not match the outPoint's (${outPoint.txId.hex})" + ) + + require( + prevTransaction == EmptyTransaction || prevTransaction + .outputs(outPoint.vout.toInt) + .value == amount, + s"prevTransaction output at index ${outPoint.vout.toInt} (${prevTransaction + .outputs(outPoint.vout.toInt)}) does match the corresponding value $amount" + ) + private val keysToSignFor = inputInfo.pubKeys require(signers.map(_.publicKey).forall(keysToSignFor.contains), s"Cannot have signers that do not sign for one of $keysToSignFor") @@ -27,6 +43,15 @@ sealed trait InputSigningInfo[+InputType <: InputInfo] { def output: TransactionOutput = inputInfo.output def outPoint: TransactionOutPoint = inputInfo.outPoint def conditionalPath: ConditionalPath = inputInfo.conditionalPath + + def sigVersion: SignatureVersion = + inputInfo match { + case _: SegwitV0NativeInputInfo | _: UnassignedSegwitNativeInputInfo | + _: P2SHNestedSegwitV0InputInfo => + SigVersionWitnessV0 + case _: P2SHNonSegwitInputInfo | _: RawInputInfo => + SigVersionBase + } } /** Stores the information needed to generate a ScriptSignature for a specific @@ -34,6 +59,7 @@ sealed trait InputSigningInfo[+InputType <: InputInfo] { */ case class ScriptSignatureParams[+InputType <: InputInfo]( inputInfo: InputType, + prevTransaction: Transaction, signers: Vector[Sign], hashType: HashType) extends InputSigningInfo[InputType] { @@ -47,12 +73,12 @@ case class ScriptSignatureParams[+InputType <: InputInfo]( } def toSingle(index: Int): ECSignatureParams[InputType] = { - ECSignatureParams(inputInfo, signers(index), hashType) + ECSignatureParams(inputInfo, prevTransaction, signers(index), hashType) } def toSingles: Vector[ECSignatureParams[InputType]] = { signers.map { signer => - ECSignatureParams(inputInfo, signer, hashType) + ECSignatureParams(inputInfo, prevTransaction, signer, hashType) } } @@ -66,9 +92,10 @@ object ScriptSignatureParams { def apply[InputType <: InputInfo]( inputInfo: InputType, + prevTransaction: Transaction, signer: Sign, hashType: HashType): ScriptSignatureParams[InputType] = - ScriptSignatureParams(inputInfo, Vector(signer), hashType) + ScriptSignatureParams(inputInfo, prevTransaction, Vector(signer), hashType) } /** Stores the information needed to generate an ECDigitalSignature for @@ -76,13 +103,14 @@ object ScriptSignatureParams { */ case class ECSignatureParams[+InputType <: InputInfo]( inputInfo: InputType, + prevTransaction: Transaction, signer: Sign, hashType: HashType) extends InputSigningInfo[InputType] { override def signers: Vector[Sign] = Vector(signer) def toScriptSignatureParams: ScriptSignatureParams[InputType] = { - ScriptSignatureParams(inputInfo, signer, hashType) + ScriptSignatureParams(inputInfo, prevTransaction, signer, hashType) } def mapInfo[T <: InputInfo](func: InputType => T): ECSignatureParams[T] = { diff --git a/docs/core/psbts.md b/docs/core/psbts.md index 560f2d3d3d..ed6cb379f0 100644 --- a/docs/core/psbts.md +++ b/docs/core/psbts.md @@ -125,6 +125,7 @@ val spendingInfoSingle = ECSignatureParams( redeemScriptOpt = Some(redeemScript0), scriptWitnessOpt = None, conditionalPath = ConditionalPath.NoCondition), + prevTransaction = utxo0, signer = privKey0, hashType = HashType.sigHashAll ) diff --git a/docs/core/txbuilder.md b/docs/core/txbuilder.md index 42b9eeda0b..30f847a974 100644 --- a/docs/core/txbuilder.md +++ b/docs/core/txbuilder.md @@ -112,6 +112,7 @@ val unsignedTxF: Future[Transaction] = finalizer.buildTx(builderResult) // this contains all the information we need to // validly sign the UTXO above val utxoInfo = ScriptSignatureParams(inputInfo = inputInfo, + prevTransaction = creditingTx, signers = Vector(privKey), hashType = HashType.sigHashAll) diff --git a/testkit/src/main/scala/org/bitcoins/testkit/core/gen/CreditingTxGen.scala b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/CreditingTxGen.scala index e739d32a80..105b9040c0 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/core/gen/CreditingTxGen.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/CreditingTxGen.scala @@ -59,6 +59,30 @@ sealed abstract class CreditingTxGen { conditionalOutput) } + def nonP2WSHOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] = + Gen.choose(min, max).flatMap(n => Gen.listOfN(n, nonP2WSHOutput)) + + def rawOutput: Gen[ScriptSignatureParams[InputInfo]] = { + Gen.oneOf(p2pkOutput, + p2pkhOutput, + p2pkWithTimeoutOutput, + multiSigOutput, + cltvOutput, + csvOutput, + multiSignatureWithTimeoutOutput, + conditionalOutput) + } + + def rawOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] = + Gen.choose(min, max).flatMap(n => Gen.listOfN(n, rawOutput)) + + def witOutput: Gen[ScriptSignatureParams[InputInfo]] = { + Gen.oneOf(p2wpkhOutput, p2wshOutput) + } + + def witOutputs: Gen[Seq[ScriptSignatureParams[InputInfo]]] = + Gen.choose(min, max).flatMap(n => Gen.listOfN(n, witOutput)) + /** Only for use in constructing P2SH outputs */ private def nonP2SHOutput: Gen[ScriptSignatureParams[InputInfo]] = { Gen @@ -78,6 +102,7 @@ sealed abstract class CreditingTxGen { case ScriptSignatureParams( P2SHNestedSegwitV0InputInfo(_, _, witness, _, _), _, + _, _) => witness.stack.exists(_.length > ScriptInterpreter.MAX_PUSH_SIZE) case _ => true @@ -199,16 +224,22 @@ sealed abstract class CreditingTxGen { val p2sh = P2SHScriptPubKey(redeemScript) val updatedOutput = TransactionOutput(oldOutput.value, p2sh) val scriptWitnessOpt = InputInfo.getScriptWitness(o.inputInfo) + val tc = TransactionConstants + val oldOutputs = o.prevTransaction.outputs + val updated = oldOutputs.updated(o.outPoint.vout.toInt, updatedOutput) + val creditingTx = + BaseTransaction(tc.validLockVersion, Nil, updated, tc.lockTime) ScriptSignatureParams( InputInfo( - TransactionOutPoint(o.outPoint.txId, o.outPoint.vout), + TransactionOutPoint(creditingTx.txId, o.outPoint.vout), updatedOutput, Some(redeemScript), scriptWitnessOpt, computeAllTrueConditionalPath(redeemScript, None, scriptWitnessOpt), o.signers.map(_.publicKey) ), + creditingTx, o.signers, hashType ) @@ -227,15 +258,23 @@ sealed abstract class CreditingTxGen { val oldOutput = o.output val csvSPK = CLTVScriptPubKey(scriptNum, oldOutput.scriptPubKey) val updatedOutput = TransactionOutput(oldOutput.value, csvSPK) + val tc = TransactionConstants + val oldOutputs = o.prevTransaction.outputs + val updated = + oldOutputs.updated(o.outPoint.vout.toInt, updatedOutput) + val creditingTx = + BaseTransaction(tc.validLockVersion, Nil, updated, tc.lockTime) + ScriptSignatureParams( InputInfo( - TransactionOutPoint(o.outPoint.txId, o.outPoint.vout), + TransactionOutPoint(creditingTx.txId, o.outPoint.vout), updatedOutput, InputInfo.getRedeemScript(o.inputInfo), InputInfo.getScriptWitness(o.inputInfo), ConditionalPath.NoCondition, o.signers.map(_.publicKey) ), + creditingTx, o.signers, hashType ) @@ -254,15 +293,23 @@ sealed abstract class CreditingTxGen { val oldOutput = o.output val csvSPK = CSVScriptPubKey(scriptNum, oldOutput.scriptPubKey) val updatedOutput = TransactionOutput(oldOutput.value, csvSPK) + val tc = TransactionConstants + val oldOutputs = o.prevTransaction.outputs + val updated = + oldOutputs.updated(o.outPoint.vout.toInt, updatedOutput) + val creditingTx = + BaseTransaction(tc.validLockVersion, Nil, updated, tc.lockTime) + ScriptSignatureParams( InputInfo( - TransactionOutPoint(o.outPoint.txId, o.outPoint.vout), + TransactionOutPoint(creditingTx.txId, o.outPoint.vout), updatedOutput, InputInfo.getRedeemScript(o.inputInfo), InputInfo.getScriptWitness(o.inputInfo), ConditionalPath.NoCondition, o.signers.map(_.publicKey) ), + creditingTx, o.signers, hashType ) @@ -287,7 +334,7 @@ sealed abstract class CreditingTxGen { .suchThat(output => !ScriptGenerators.redeemScriptTooBig(output.output.scriptPubKey)) .flatMap { - case ScriptSignatureParams(info, signers, _) => + case ScriptSignatureParams(info, _, signers, _) => val spk = info.scriptPubKey spk match { case rspk: RawScriptPubKey => @@ -337,6 +384,7 @@ sealed abstract class CreditingTxGen { ConditionalPath.NoCondition, signers.map(_.publicKey) ), + creditingTx, signers, hashType ) @@ -408,6 +456,7 @@ sealed abstract class CreditingTxGen { computeAllTrueConditionalPath(spk, redeemScript, scriptWitness), signers.toVector.map(_.publicKey) ), + btx, signers.toVector, hashType ) diff --git a/testkit/src/main/scala/org/bitcoins/testkit/core/gen/PSBTGenerators.scala b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/PSBTGenerators.scala index 08c8448936..5f9c43d3f2 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/core/gen/PSBTGenerators.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/PSBTGenerators.scala @@ -1,17 +1,10 @@ package org.bitcoins.testkit.core.gen import org.bitcoins.core.currency.CurrencyUnit -import org.bitcoins.core.number.{Int32, UInt32} +import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.script.ScriptPubKey -import org.bitcoins.core.protocol.transaction.{ - BaseTransaction, - InputUtil, - Transaction, - TransactionOutput, - TxUtil -} +import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.psbt.GlobalPSBTRecord.Version -import org.bitcoins.core.psbt.PSBT.SpendingInfoAndNonWitnessTxs import org.bitcoins.core.psbt.PSBTInputKeyId.PartialSignatureKeyId import org.bitcoins.core.psbt._ import org.bitcoins.core.wallet.builder.{ @@ -20,7 +13,7 @@ import org.bitcoins.core.wallet.builder.{ StandardNonInteractiveFinalizer } import org.bitcoins.core.wallet.fee.FeeUnit -import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams} +import org.bitcoins.core.wallet.utxo._ import org.scalacheck.Gen import scodec.bits.ByteVector @@ -170,27 +163,20 @@ object PSBTGenerators { } } - def spendingInfoAndNonWitnessTxsFromSpendingInfos( + def orderSpendingInfos( unsignedTx: Transaction, - creditingTxsInfo: Vector[ - ScriptSignatureParams[InputInfo]]): SpendingInfoAndNonWitnessTxs = { - val elements = unsignedTx.inputs.toVector.map { input => + creditingTxsInfo: Vector[ScriptSignatureParams[InputInfo]]): Vector[ + ScriptSignatureParams[InputInfo]] = { + unsignedTx.inputs.toVector.map { input => val infoOpt = creditingTxsInfo.find(_.outPoint == input.previousOutput) infoOpt match { - case Some(info) => - val tx = BaseTransaction(Int32.zero, - Vector.empty, - Vector.fill(5)(info.output), - UInt32.zero) - (info, Some(tx)) + case Some(info) => info case None => throw new RuntimeException( "CreditingTxGen.inputsAndOutputs is being inconsistent") } } - - SpendingInfoAndNonWitnessTxs(elements) } def psbtAndBuilderFromInputs( @@ -215,9 +201,7 @@ object PSBTGenerators { for { unsignedTx <- builder.setFinalizer(finalizer).buildTx() - orderedTxInfos = spendingInfoAndNonWitnessTxsFromSpendingInfos( - unsignedTx, - creditingTxsInfo.toVector) + orderedTxInfos = orderSpendingInfos(unsignedTx, creditingTxsInfo.toVector) psbt <- { if (finalized) { diff --git a/testkit/src/main/scala/org/bitcoins/testkit/core/gen/ScriptGenerators.scala b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/ScriptGenerators.scala index be4c836e50..ca495a13b6 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/core/gen/ScriptGenerators.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/core/gen/ScriptGenerators.scala @@ -19,13 +19,7 @@ import org.bitcoins.core.wallet.signer.{ P2PKSigner, P2PKWithTimeoutSigner } -import org.bitcoins.core.wallet.utxo.{ - MultiSignatureInputInfo, - P2PKHInputInfo, - P2PKInputInfo, - P2PKWithTimeoutInputInfo, - ScriptSignatureParams -} +import org.bitcoins.core.wallet.utxo._ import org.bitcoins.crypto.{ ECDigitalSignature, ECPrivateKey, @@ -603,8 +597,10 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger { P2PKInputInfo(TransactionOutPoint(creditingTx.txIdBE, inputIndex), creditingTx.outputs(outputIndex.toInt).value, scriptPubKey), + creditingTx, privateKey, - hashType) + hashType + ) txSigComponentFuture = P2PKSigner.sign(spendingInfo, spendingTx, false) txSigComponent = Await.result(txSigComponentFuture, timeout) //add the signature to the scriptSig instead of having an empty scriptSig @@ -639,6 +635,7 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger { P2PKHInputInfo(TransactionOutPoint(creditingTx.txIdBE, inputIndex), creditingTx.outputs(outputIndex.toInt).value, privateKey.publicKey), + creditingTx, privateKey, hashType ) @@ -667,6 +664,7 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger { creditingTx.outputs(outputIndex.toInt).value, spk, isBeforeTimeout = true), + creditingTx, privKey, hashType ) @@ -715,6 +713,7 @@ sealed abstract class ScriptGenerators extends BitcoinSLogger { TransactionOutPoint(creditingTx.txIdBE, inputIndex), creditingTx.outputs(outputIndex.toInt).value, multiSigScriptPubKey), + creditingTx, privateKeys.toVector, hashType ) diff --git a/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala b/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala index e224dd7bdc..1c7311e06b 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala @@ -305,7 +305,13 @@ abstract class Wallet _ = require(diff.isEmpty, s"Not all OutPoints belong to this wallet, diff $diff") - utxos = utxoDbs.map(_.toUTXOInfo(keyManager)) + prevTxFs = utxoDbs.map(utxo => + transactionDAO.findByOutPoint(utxo.outPoint).map(_.get.transaction)) + prevTxs <- Future.sequence(prevTxFs) + utxos = + utxoDbs + .zip(prevTxs) + .map(info => info._1.toUTXOInfo(keyManager, info._2)) changeAddr <- getNewChangeAddress(fromAccount.hdAccount) diff --git a/wallet/src/main/scala/org/bitcoins/wallet/internal/FundTransactionHandling.scala b/wallet/src/main/scala/org/bitcoins/wallet/internal/FundTransactionHandling.scala index 51c6ebbcd4..8a0dbad637 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/internal/FundTransactionHandling.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/internal/FundTransactionHandling.scala @@ -2,13 +2,7 @@ package org.bitcoins.wallet.internal import org.bitcoins.commons.jsonmodels.wallet.CoinSelectionAlgo import org.bitcoins.core.consensus.Consensus -import org.bitcoins.core.protocol.transaction.{ - EmptyTransactionOutPoint, - InputUtil, - Transaction, - TransactionOutput, - TxUtil -} +import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.wallet.builder.{ RawTxBuilder, RawTxBuilderWithFinalizer, @@ -18,13 +12,13 @@ import org.bitcoins.core.wallet.fee.FeeUnit import org.bitcoins.core.wallet.utxo.{InputInfo, ScriptSignatureParams} import org.bitcoins.crypto.Sign import org.bitcoins.keymanager.bip39.BIP39KeyManager -import org.bitcoins.wallet.WalletLogger -import org.bitcoins.wallet.api.{AddressInfo, CoinSelector, WalletApi} +import org.bitcoins.wallet.api.{AddressInfo, CoinSelector} import org.bitcoins.wallet.models.{AccountDb, SpendingInfoDb} +import org.bitcoins.wallet.{Wallet, WalletLogger} import scala.concurrent.Future -trait FundTransactionHandling extends WalletLogger { self: WalletApi => +trait FundTransactionHandling extends WalletLogger { self: Wallet => def fundRawTransaction( destinations: Vector[TransactionOutput], @@ -104,15 +98,21 @@ trait FundTransactionHandling extends WalletLogger { self: WalletApi => else Future.successful(utxos) } yield selectedUtxos - val addrInfosWithUtxoF: Future[Vector[(SpendingInfoDb, AddressInfo)]] = + val addrInfosWithUtxoF: Future[ + Vector[(SpendingInfoDb, Transaction, AddressInfo)]] = for { selectedUtxos <- selectedUtxosF _ = selectedUtxosF.failed.foreach(err => logger.error("Error selecting utxos to fund transaction ", err)) addrInfoOptF = selectedUtxos.map { utxo => - val addrInfoOptF = getAddressInfo(utxo) - //.get should be safe here because of foreign key at the database level - addrInfoOptF.map(addrInfoOpt => (utxo, addrInfoOpt.get)) + // .gets should be safe here because of foreign key at the database level + for { + addrInfo <- getAddressInfo(utxo).map(_.get) + prevTx <- + transactionDAO + .findByOutPoint(utxo.outPoint) + .map(_.get.transaction) + } yield (utxo, prevTx, addrInfo) } vec <- Future.sequence(addrInfoOptF) } yield vec @@ -122,12 +122,12 @@ trait FundTransactionHandling extends WalletLogger { self: WalletApi => change <- getNewChangeAddress(fromAccount) utxoSpendingInfos = { addrInfosWithUtxo.map { - case (utxo, addrInfo) => + case (utxo, prevTx, addrInfo) => keyManagerOpt match { case Some(km) => - utxo.toUTXOInfo(keyManager = km) + utxo.toUTXOInfo(keyManager = km, prevTx) case None => - utxo.toUTXOInfo(sign = Sign.dummySign(addrInfo.pubkey)) + utxo.toUTXOInfo(sign = Sign.dummySign(addrInfo.pubkey), prevTx) } } diff --git a/wallet/src/main/scala/org/bitcoins/wallet/models/SpendingInfoDb.scala b/wallet/src/main/scala/org/bitcoins/wallet/models/SpendingInfoDb.scala index 8fc97fa01a..071fa90b51 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/models/SpendingInfoDb.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/models/SpendingInfoDb.scala @@ -8,6 +8,7 @@ import org.bitcoins.core.hd.{ } import org.bitcoins.core.protocol.script.{ScriptPubKey, ScriptWitness} import org.bitcoins.core.protocol.transaction.{ + Transaction, TransactionOutPoint, TransactionOutput } @@ -174,14 +175,17 @@ sealed trait SpendingInfoDb extends DbRowAutoInc[SpendingInfoDb] { * a signable (and sensitive) real-world UTXO */ def toUTXOInfo( - keyManager: BIP39KeyManager): ScriptSignatureParams[InputInfo] = { + keyManager: BIP39KeyManager, + prevTransaction: Transaction): ScriptSignatureParams[InputInfo] = { val sign: Sign = keyManager.toSign(privKeyPath = privKeyPath) - toUTXOInfo(sign = sign) + toUTXOInfo(sign = sign, prevTransaction) } - def toUTXOInfo(sign: Sign): ScriptSignatureParams[InputInfo] = { + def toUTXOInfo( + sign: Sign, + prevTransaction: Transaction): ScriptSignatureParams[InputInfo] = { ScriptSignatureParams( InputInfo( outPoint, @@ -191,6 +195,7 @@ sealed trait SpendingInfoDb extends DbRowAutoInc[SpendingInfoDb] { ConditionalPath.NoCondition, // TODO: Migrate to add the Column for this (default: NoConditionsLeft) Vector(sign.publicKey) ), + prevTransaction, Vector(sign), hashType ) diff --git a/wallet/src/main/scala/org/bitcoins/wallet/models/TransactionDAO.scala b/wallet/src/main/scala/org/bitcoins/wallet/models/TransactionDAO.scala index 93bfd9d633..5dedb6f1cc 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/models/TransactionDAO.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/models/TransactionDAO.scala @@ -2,7 +2,7 @@ package org.bitcoins.wallet.models import org.bitcoins.core.currency.CurrencyUnit import org.bitcoins.core.number.UInt32 -import org.bitcoins.core.protocol.transaction.Transaction +import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutPoint} import org.bitcoins.crypto.{DoubleSha256Digest, DoubleSha256DigestBE} import org.bitcoins.db.{CRUD, SlickUtil} import org.bitcoins.wallet.config._ @@ -51,6 +51,11 @@ trait TxDAO[DbEntryType <: TxDB] txs: Vector[DbEntryType]): Query[DbTable, DbEntryType, Seq] = findByPrimaryKeys(txs.map(_.txIdBE)) + def findByOutPoint( + outPoint: TransactionOutPoint): Future[Option[DbEntryType]] = { + findByTxId(outPoint.txId) + } + def findByTxId(txIdBE: DoubleSha256DigestBE): Future[Option[DbEntryType]] = { val q = table .filter(_.txIdBE === txIdBE)