mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-01-19 05:43:51 +01:00
Fixed P2SH(Segwit) bug and brought down DLC-used PSBT functionality (#2140)
* Fixed P2SH(Segwit) bug and allowed redeem scripts when calling addFinalizedScriptWitnessToInput * Add test Co-authored-by: Ben Carman <benthecarman@live.com>
This commit is contained in:
parent
e317fa4a89
commit
3cf6d3cb0a
@ -348,14 +348,14 @@ class PSBTUnitTest extends BitcoinSAsyncTest {
|
||||
}
|
||||
|
||||
def dummyTx(
|
||||
prevTxId: DoubleSha256Digest = DoubleSha256Digest.empty,
|
||||
scriptSig: ScriptSignature = EmptyScriptSignature,
|
||||
spk: ScriptPubKey = EmptyScriptPubKey): Transaction = {
|
||||
BaseTransaction(
|
||||
version = Int32.zero,
|
||||
inputs = Vector(
|
||||
TransactionInput(outPoint =
|
||||
TransactionOutPoint(txId = DoubleSha256Digest.empty,
|
||||
vout = UInt32.zero),
|
||||
TransactionInput(outPoint = TransactionOutPoint(txId = prevTxId,
|
||||
vout = UInt32.zero),
|
||||
scriptSignature = scriptSig,
|
||||
sequenceNumber = UInt32.zero)),
|
||||
outputs = Vector(TransactionOutput(CurrencyUnits.oneBTC, spk)),
|
||||
@ -364,9 +364,10 @@ class PSBTUnitTest extends BitcoinSAsyncTest {
|
||||
}
|
||||
|
||||
def dummyPSBT(
|
||||
prevTxId: DoubleSha256Digest = DoubleSha256Digest.empty,
|
||||
scriptSig: ScriptSignature = EmptyScriptSignature,
|
||||
spk: ScriptPubKey = EmptyScriptPubKey): PSBT = {
|
||||
PSBT.fromUnsignedTx(dummyTx(scriptSig, spk))
|
||||
PSBT.fromUnsignedTx(dummyTx(prevTxId, scriptSig, spk))
|
||||
}
|
||||
|
||||
it must "successfully change a NonWitnessUTXO to a WitnessUTXO when compressing" in {
|
||||
@ -517,4 +518,28 @@ class PSBTUnitTest extends BitcoinSAsyncTest {
|
||||
"70736274ff01005502000000010e43b32dc23232b9c74bb0d4b940e6242c8acc875f41a6e4f6063e99dcbe4eda0000000000000000000180f0fa02000000001976a9149d936768e7338f716548af87b61ad83b80ea422188ac000000000001002a02000000000100e1f5050000000017a914e9b5fdcca093fde8d0424238a517034664c4715a8700000000010717160014cbcdc27013a54990a8e468dbdabf95166ca614e701086b024730440220489caebd034c1c1caf869d1463543248d78b9c8fde4cdd6044220c123ba9489c022007214b4ac24f5b4e278172b8af2fed27990fb10fa1961b3288dd0fc9226ef05d012102b38f6ec85730be3e8fa5a0da221209781de35c2e572e82f2d707e7b67be1b9c20000")
|
||||
assert(psbt.verifyFinalizedInput(0))
|
||||
}
|
||||
|
||||
it must "correctly add a non-witness utxo when there is a witness v0 redeem script" in {
|
||||
|
||||
val witScript = P2PKHScriptPubKey(ECPublicKey.freshPublicKey)
|
||||
val witness = P2WSHWitnessV0(witScript)
|
||||
val redeemScript = P2WSHWitnessSPKV0(witScript)
|
||||
val p2sh = P2SHScriptPubKey(redeemScript)
|
||||
|
||||
val utxoTx = dummyTx(spk = p2sh)
|
||||
|
||||
val psbtTx = dummyTx(prevTxId = utxoTx.txId)
|
||||
|
||||
val psbt = PSBT
|
||||
.fromUnsignedTx(psbtTx)
|
||||
.addScriptWitnessToInput(witness, 0)
|
||||
.addRedeemOrWitnessScriptToInput(redeemScript, 0)
|
||||
|
||||
val withUtxo = psbt.addUTXOToInput(utxoTx, 0)
|
||||
|
||||
assert(
|
||||
withUtxo.inputMaps.head.nonWitnessOrUnknownUTXOOpt
|
||||
.map(_.transactionSpent)
|
||||
.contains(utxoTx))
|
||||
}
|
||||
}
|
||||
|
@ -225,8 +225,13 @@ case class PSBT(
|
||||
inputMap.redeemScriptOpt.isDefined && WitnessScriptPubKey
|
||||
.isWitnessScriptPubKey(
|
||||
inputMap.redeemScriptOpt.get.redeemScript.asm)
|
||||
val notBIP143Vulnerable =
|
||||
!out.scriptPubKey.isInstanceOf[WitnessScriptPubKeyV0]
|
||||
val notBIP143Vulnerable = {
|
||||
!out.scriptPubKey.isInstanceOf[WitnessScriptPubKeyV0] && !(
|
||||
hasWitRedeemScript &&
|
||||
inputMap.redeemScriptOpt.get.redeemScript
|
||||
.isInstanceOf[WitnessScriptPubKeyV0]
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
(outIsWitnessScript || hasWitScript || hasWitRedeemScript) && notBIP143Vulnerable
|
||||
@ -369,6 +374,46 @@ case class PSBT(
|
||||
PSBT(globalMap, newInputMaps, outputMaps)
|
||||
}
|
||||
|
||||
def addFinalizedScriptWitnessToInput(
|
||||
scriptSignature: ScriptSignature,
|
||||
scriptWitness: ScriptWitness,
|
||||
index: Int): PSBT = {
|
||||
require(index >= 0,
|
||||
s"index must be greater than or equal to 0, got: $index")
|
||||
require(
|
||||
index < inputMaps.size,
|
||||
s"index must be less than the number of input maps present in the psbt, $index >= ${inputMaps.size}")
|
||||
require(!inputMaps(index).isFinalized,
|
||||
s"Cannot update an InputPSBTMap that is finalized, index: $index")
|
||||
|
||||
val prevInput = inputMaps(index)
|
||||
|
||||
require(
|
||||
prevInput.elements.forall {
|
||||
case _: NonWitnessOrUnknownUTXO | _: WitnessUTXO | _: Unknown => true
|
||||
case redeemScript: RedeemScript =>
|
||||
// RedeemScript is okay if it matches the script signature given
|
||||
redeemScript.redeemScript match {
|
||||
case _: NonWitnessScriptPubKey => false
|
||||
case wspk: WitnessScriptPubKey =>
|
||||
P2SHScriptSignature(wspk) == scriptSignature
|
||||
}
|
||||
case _: InputPSBTRecord => false
|
||||
},
|
||||
s"Input already contains fields: ${prevInput.elements}"
|
||||
)
|
||||
|
||||
val finalizedScripts = Vector(FinalizedScriptSig(scriptSignature),
|
||||
FinalizedScriptWitness(scriptWitness))
|
||||
|
||||
// Replace RedeemScripts with FinalizedScriptSignatures and add FinalizedScriptWitnesses
|
||||
val records = prevInput.elements.filterNot(
|
||||
_.isInstanceOf[RedeemScript]) ++ finalizedScripts
|
||||
val newMap = InputPSBTMap(records)
|
||||
val newInputMaps = inputMaps.updated(index, newMap)
|
||||
PSBT(globalMap, newInputMaps, outputMaps)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds script to the indexed OutputPSBTMap to either the RedeemScript
|
||||
* or WitnessScript field depending on the script and available information in the PSBT
|
||||
@ -816,6 +861,30 @@ object PSBT extends Factory[PSBT] with StringFactory[PSBT] {
|
||||
PSBT(globalMap, inputMaps, outputMaps)
|
||||
}
|
||||
|
||||
def fromUnsignedTxWithP2SHScript(tx: Transaction): PSBT = {
|
||||
val inputs = tx.inputs.toVector
|
||||
val utxInputs = inputs.map { input =>
|
||||
TransactionInput(input.previousOutput,
|
||||
EmptyScriptSignature,
|
||||
input.sequence)
|
||||
}
|
||||
val utx = BaseTransaction(tx.version, utxInputs, tx.outputs, tx.lockTime)
|
||||
val psbt = fromUnsignedTx(utx)
|
||||
|
||||
val p2shScriptSigs = inputs
|
||||
.map(_.scriptSignature)
|
||||
.zipWithIndex
|
||||
.collect {
|
||||
case (p2sh: P2SHScriptSignature, index) => (p2sh, index)
|
||||
}
|
||||
.filter(_._1.scriptSignatureNoRedeemScript == EmptyScriptSignature)
|
||||
|
||||
p2shScriptSigs.foldLeft(psbt) {
|
||||
case (psbtSoFar, (p2sh, index)) =>
|
||||
psbtSoFar.addRedeemOrWitnessScriptToInput(p2sh.redeemScript, index)
|
||||
}
|
||||
}
|
||||
|
||||
/** Constructs a full (ready to be finalized) but unfinalized PSBT from an
|
||||
* unsigned transaction and a SpendingInfoAndNonWitnessTxs
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user