Rework signing logic to take full funding transaction (#1560)

This commit is contained in:
Ben Carman 2020-06-25 16:10:36 -05:00 committed by GitHub
parent 9016f0bbca
commit 1957b0508c
33 changed files with 1434 additions and 480 deletions

View file

@ -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()

View file

@ -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)))
}
}
}

View file

@ -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(

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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
)

View file

@ -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)

View file

@ -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)

View file

@ -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
)

View file

@ -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 =>

View file

@ -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],

View file

@ -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,

View file

@ -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)
) {

View file

@ -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)
}
}

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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

View file

@ -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,

View file

@ -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] = {

View file

@ -125,6 +125,7 @@ val spendingInfoSingle = ECSignatureParams(
redeemScriptOpt = Some(redeemScript0),
scriptWitnessOpt = None,
conditionalPath = ConditionalPath.NoCondition),
prevTransaction = utxo0,
signer = privKey0,
hashType = HashType.sigHashAll
)

View file

@ -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)

View file

@ -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
)

View file

@ -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) {

View file

@ -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
)

View file

@ -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)

View file

@ -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)
}
}

View file

@ -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
)

View file

@ -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)