mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 10:46:42 +01:00
Rework signing logic to take full funding transaction (#1560)
This commit is contained in:
parent
9016f0bbca
commit
1957b0508c
33 changed files with 1434 additions and 480 deletions
|
@ -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()
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -59,14 +59,12 @@ class PSBTTest extends BitcoinSAsyncTest {
|
|||
case (fullPsbt, utxos, _) =>
|
||||
val emptyPsbt = PSBT.fromUnsignedTx(fullPsbt.transaction)
|
||||
|
||||
val infoAndTxs = PSBTGenerators
|
||||
.spendingInfoAndNonWitnessTxsFromSpendingInfos(fullPsbt.transaction,
|
||||
val infoAndTxs = PSBTGenerators.orderSpendingInfos(fullPsbt.transaction,
|
||||
utxos.toVector)
|
||||
.infoAndTxOpts
|
||||
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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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],
|
||||
|
@ -73,6 +76,75 @@ sealed abstract class TransactionSignatureCreator {
|
|||
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 =>
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object TransactionSignatureCreator extends TransactionSignatureCreator
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
) {
|
||||
|
|
|
@ -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,6 +264,7 @@ 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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,12 +815,11 @@ object PSBT extends Factory[PSBT] {
|
|||
|
||||
val globalMap = GlobalPSBTMap(
|
||||
Vector(GlobalPSBTRecord.UnsignedTransaction(btx)))
|
||||
val inputMapFs = spendingInfoAndNonWitnessTxs.map {
|
||||
case (info, txOpt) =>
|
||||
val inputMapFs = spendingInfos.map { info =>
|
||||
if (finalized) {
|
||||
InputPSBTMap.finalizedFromNewSpendingInfo(info, unsignedTx, txOpt)
|
||||
InputPSBTMap.finalizedFromSpendingInfo(info, unsignedTx)
|
||||
} else {
|
||||
InputPSBTMap.fromUTXOInfo(info, unsignedTx, txOpt)
|
||||
InputPSBTMap.fromUTXOInfo(info, unsignedTx)
|
||||
}
|
||||
}
|
||||
val outputMaps = unsignedTx.outputs.map(_ => OutputPSBTMap.empty).toVector
|
||||
|
|
|
@ -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) {
|
||||
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 (
|
||||
redeemScriptOpt.isDefined && redeemScriptOpt.get.redeemScript
|
||||
.isInstanceOf[WitnessScriptPubKey]
|
||||
redeemScript.isInstanceOf[WitnessScriptPubKey] && !redeemScript
|
||||
.isInstanceOf[WitnessScriptPubKeyV0]
|
||||
) {
|
||||
filterRecords(NonWitnessUTXOKeyId) :+ WitnessUTXO(transactionOutput)
|
||||
} else {
|
||||
elements
|
||||
}
|
||||
case _: P2PKHScriptPubKey | _: P2PKScriptPubKey |
|
||||
_: P2PKWithTimeoutScriptPubKey | _: MultiSignatureScriptPubKey |
|
||||
EmptyScriptPubKey | _: LockTimeScriptPubKey |
|
||||
_: NonStandardScriptPubKey | _: WitnessCommitment |
|
||||
_: ConditionalScriptPubKey =>
|
||||
} else {
|
||||
elements
|
||||
}
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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] = {
|
||||
|
|
|
@ -125,6 +125,7 @@ val spendingInfoSingle = ECSignatureParams(
|
|||
redeemScriptOpt = Some(redeemScript0),
|
||||
scriptWitnessOpt = None,
|
||||
conditionalPath = ConditionalPath.NoCondition),
|
||||
prevTransaction = utxo0,
|
||||
signer = privKey0,
|
||||
hashType = HashType.sigHashAll
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue