mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 18:47:38 +01:00
2024 11 20 prevoutmap ordering (#5776)
* Add test case and add invariant to RawTxSigner.sign() * Add InputInfo.sortPreviousOutputMap() * Fix bug where we were sorting prevoutputmap when it didn't need to be sorted
This commit is contained in:
parent
d8ad023254
commit
b6cc97a663
4 changed files with 83 additions and 10 deletions
|
@ -1,8 +1,14 @@
|
||||||
package org.bitcoins.core.wallet.builder
|
package org.bitcoins.core.wallet.builder
|
||||||
|
|
||||||
import org.bitcoins.core.crypto.TxSigComponent
|
import org.bitcoins.core.crypto.TxSigComponent
|
||||||
import org.bitcoins.core.protocol.script.ScriptWitness
|
import org.bitcoins.core.protocol.script.{
|
||||||
import org.bitcoins.core.protocol.transaction._
|
NonWitnessScriptPubKey,
|
||||||
|
ScriptWitness,
|
||||||
|
TaprootScriptPubKey,
|
||||||
|
UnassignedWitnessScriptPubKey,
|
||||||
|
WitnessScriptPubKeyV0
|
||||||
|
}
|
||||||
|
import org.bitcoins.core.protocol.transaction.*
|
||||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||||
import org.bitcoins.core.wallet.signer.BitcoinSigner
|
import org.bitcoins.core.wallet.signer.BitcoinSigner
|
||||||
import org.bitcoins.core.wallet.utxo.{
|
import org.bitcoins.core.wallet.utxo.{
|
||||||
|
@ -105,9 +111,18 @@ object RawTxSigner {
|
||||||
s"Must provide exactly one UTXOSatisfyingInfo per input, ${utxoInfos.length} != ${utx.inputs.length}")
|
s"Must provide exactly one UTXOSatisfyingInfo per input, ${utxoInfos.length} != ${utx.inputs.length}")
|
||||||
require(utxoInfos.distinct.length == utxoInfos.length,
|
require(utxoInfos.distinct.length == utxoInfos.length,
|
||||||
"All UTXOSatisfyingInfos must be unique. ")
|
"All UTXOSatisfyingInfos must be unique. ")
|
||||||
require(utxoInfos.forall(utxo =>
|
val utxOutPoints = utx.inputs.map(_.previousOutput)
|
||||||
utx.inputs.exists(_.previousOutput == utxo.outPoint)),
|
val sortedUtxoInfos = utxoInfos.map { u =>
|
||||||
"All UTXOSatisfyingInfos must correspond to an input.")
|
u.output.scriptPubKey match {
|
||||||
|
case _: NonWitnessScriptPubKey | _: WitnessScriptPubKeyV0 |
|
||||||
|
_: UnassignedWitnessScriptPubKey =>
|
||||||
|
// no sorting needed for these spk types as the sighash algorithm
|
||||||
|
// doesn't include all outputs
|
||||||
|
u
|
||||||
|
case _: TaprootScriptPubKey =>
|
||||||
|
u.copy(inputInfo = u.inputInfo.sortPreviousOutputMap(utxOutPoints))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val signedTx =
|
val signedTx =
|
||||||
if (
|
if (
|
||||||
|
@ -121,7 +136,7 @@ object RawTxSigner {
|
||||||
.setLockTime(utx.lockTime)
|
.setLockTime(utx.lockTime)
|
||||||
.++=(utx.outputs)
|
.++=(utx.outputs)
|
||||||
|
|
||||||
val inputsAndWitnesses = utxoInfos.map { utxo =>
|
val inputsAndWitnesses = sortedUtxoInfos.map { utxo =>
|
||||||
val txSigComp =
|
val txSigComp =
|
||||||
BitcoinSigner.sign(utxo, utx)
|
BitcoinSigner.sign(utxo, utx)
|
||||||
val scriptWitnessOpt = TxSigComponent.getScriptWitness(txSigComp)
|
val scriptWitnessOpt = TxSigComponent.getScriptWitness(txSigComp)
|
||||||
|
|
|
@ -8,6 +8,11 @@ import org.bitcoins.core.protocol.transaction.*
|
||||||
import org.bitcoins.core.script.constant.{OP_TRUE, ScriptConstant}
|
import org.bitcoins.core.script.constant.{OP_TRUE, ScriptConstant}
|
||||||
import org.bitcoins.core.script.util.PreviousOutputMap
|
import org.bitcoins.core.script.util.PreviousOutputMap
|
||||||
import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil}
|
import org.bitcoins.core.util.{BitcoinScriptUtil, BytesUtil}
|
||||||
|
import org.bitcoins.core.wallet.utxo.InputInfo.{
|
||||||
|
getHashPreImages,
|
||||||
|
getRedeemScript,
|
||||||
|
getScriptWitness
|
||||||
|
}
|
||||||
import org.bitcoins.crypto.{
|
import org.bitcoins.crypto.{
|
||||||
ECDigitalSignature,
|
ECDigitalSignature,
|
||||||
ECPublicKey,
|
ECPublicKey,
|
||||||
|
@ -83,6 +88,30 @@ sealed trait InputInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
def previousOutputMap: PreviousOutputMap
|
def previousOutputMap: PreviousOutputMap
|
||||||
|
|
||||||
|
/** Sorts our [[previousOutputMap]] to be in the same ordering as the given
|
||||||
|
* outPoints This is necessary as the outpoints must be signed in the exact
|
||||||
|
* order they appear in the inputs of our [[Transaction]] according to BIP341
|
||||||
|
* @return
|
||||||
|
* InputInfo with the [[previousOutputMap]] sorted correctly
|
||||||
|
*/
|
||||||
|
def sortPreviousOutputMap(
|
||||||
|
outPoints: Vector[TransactionOutPoint]): InputInfo = {
|
||||||
|
require(
|
||||||
|
outPoints.forall(o => previousOutputMap.get(o).isDefined),
|
||||||
|
s"Could not find all outPoints in map, outPoints=$outPoints previousOutputMap=${previousOutputMap.outputMap.keys}"
|
||||||
|
)
|
||||||
|
val sorted = outPoints.map(o => o -> previousOutputMap(o)).toMap
|
||||||
|
InputInfo(
|
||||||
|
outPoint = outPoint,
|
||||||
|
output = output,
|
||||||
|
redeemScriptOpt = getRedeemScript(this),
|
||||||
|
scriptWitnessOpt = getScriptWitness(this),
|
||||||
|
conditionalPath = conditionalPath,
|
||||||
|
previousOutputMap = PreviousOutputMap(sorted),
|
||||||
|
hashPreImages = getHashPreImages(this)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object InputInfo {
|
object InputInfo {
|
||||||
|
|
|
@ -411,4 +411,30 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
||||||
newBalance <- bitcoind.getBalance
|
newBalance <- bitcoind.getBalance
|
||||||
} yield assert(newBalance == oldBalance + amountToSend + Bitcoins(50))
|
} yield assert(newBalance == oldBalance + amountToSend + Bitcoins(50))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it must "sweep the wallet with multiple utxos" in { walletWithBitcoind =>
|
||||||
|
val wallet = walletWithBitcoind.wallet
|
||||||
|
val bitcoind = walletWithBitcoind.bitcoind
|
||||||
|
val addr1F = wallet.getNewAddress()
|
||||||
|
val addr2F = wallet.getNewAddress()
|
||||||
|
val bitcoindAddr1F = bitcoind.getNewAddress
|
||||||
|
for {
|
||||||
|
addr1 <- addr1F
|
||||||
|
addr2 <- addr2F
|
||||||
|
txId1 <- bitcoind.sendToAddress(addr1, valueFromBitcoind)
|
||||||
|
txId2 <- bitcoind.sendToAddress(addr2, valueFromBitcoind)
|
||||||
|
tx1 <- bitcoind.getRawTransactionRaw(txId1)
|
||||||
|
tx2 <- bitcoind.getRawTransactionRaw(txId2)
|
||||||
|
_ <- wallet.transactionProcessing.processTransaction(tx1, None)
|
||||||
|
_ <- wallet.transactionProcessing.processTransaction(tx2, None)
|
||||||
|
balance1 <- wallet.getBalance()
|
||||||
|
_ = assert(balance1 == valueFromBitcoind * 2)
|
||||||
|
bitcoindAddr1 <- bitcoindAddr1F
|
||||||
|
sweepTx <- wallet.sendFundsHandling.sweepWallet(bitcoindAddr1, None)
|
||||||
|
_ <- bitcoind.sendRawTransaction(sweepTx)
|
||||||
|
balance2 <- wallet.getBalance()
|
||||||
|
} yield {
|
||||||
|
assert(balance2 == Satoshis.zero)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -488,9 +488,9 @@ case class SendFundsHandlingHandling(
|
||||||
|
|
||||||
txBuilder = RawTxBuilder() ++= inputs += dummyOutput
|
txBuilder = RawTxBuilder() ++= inputs += dummyOutput
|
||||||
finalizer = SubtractFeeFromOutputsFinalizer(
|
finalizer = SubtractFeeFromOutputsFinalizer(
|
||||||
inputInfos,
|
inputInfos = inputInfos,
|
||||||
feeRate,
|
feeRate = feeRate,
|
||||||
Vector(address.scriptPubKey)
|
spks = Vector(address.scriptPubKey)
|
||||||
)
|
)
|
||||||
.andThen(ShuffleFinalizer)
|
.andThen(ShuffleFinalizer)
|
||||||
.andThen(AddWitnessDataFinalizer(inputInfos))
|
.andThen(AddWitnessDataFinalizer(inputInfos))
|
||||||
|
@ -503,7 +503,10 @@ case class SendFundsHandlingHandling(
|
||||||
tmp.outputs.size == 1,
|
tmp.outputs.size == 1,
|
||||||
s"Created tx is not as expected, does not have 1 output, got $tmp"
|
s"Created tx is not as expected, does not have 1 output, got $tmp"
|
||||||
)
|
)
|
||||||
rawTxHelper = FundRawTxHelper(withFinalizer, utxos, feeRate, Future.unit)
|
rawTxHelper = FundRawTxHelper(txBuilderWithFinalizer = withFinalizer,
|
||||||
|
scriptSigParams = utxos,
|
||||||
|
feeRate = feeRate,
|
||||||
|
reservedUTXOsCallbackF = Future.unit)
|
||||||
tx <- finishSend(
|
tx <- finishSend(
|
||||||
rawTxHelper,
|
rawTxHelper,
|
||||||
tmp.outputs.head.value,
|
tmp.outputs.head.value,
|
||||||
|
|
Loading…
Add table
Reference in a new issue