From 7efdd39bc1a4b38a45ae6aa8a973d094a511d685 Mon Sep 17 00:00:00 2001 From: Andreas Schildbach Date: Wed, 5 Feb 2025 20:07:09 +0100 Subject: [PATCH] TransactionInput: make field `scriptBytes` immutable Because tweaking is necessary for transaction signing, these usages have been changed to produce new inputs instead and replace them in transactions as needed. --- .../java/org/bitcoinj/core/Transaction.java | 13 +++--- .../org/bitcoinj/core/TransactionInput.java | 46 ++++++++++++------- .../signers/CustomTransactionSigner.java | 3 +- .../signers/LocalTransactionSigner.java | 4 +- .../signers/MissingSigResolutionSigner.java | 4 +- .../org/bitcoinj/testing/FakeTxBuilder.java | 12 +++-- .../main/java/org/bitcoinj/wallet/Wallet.java | 3 +- .../AbstractFullPrunedBlockChainTest.java | 3 +- .../bitcoinj/core/FullBlockTestGenerator.java | 23 ++++++---- .../org/bitcoinj/core/TransactionTest.java | 15 +++--- .../java/org/bitcoinj/wallet/WalletTest.java | 10 ++-- .../bitcoinj/examples/GenerateLowSTests.java | 6 +-- 12 files changed, 86 insertions(+), 56 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/core/Transaction.java b/core/src/main/java/org/bitcoinj/core/Transaction.java index 2f77dace2..b1586f68c 100644 --- a/core/src/main/java/org/bitcoinj/core/Transaction.java +++ b/core/src/main/java/org/bitcoinj/core/Transaction.java @@ -902,20 +902,20 @@ public class Transaction extends BaseMessage { if (ScriptPattern.isP2PK(scriptPubKey)) { TransactionSignature signature = calculateSignature(inputIndex, sigKey, scriptPubKey, sigHash, anyoneCanPay); - input.setScriptSig(ScriptBuilder.createInputScript(signature)); + input = input.withScriptSig(ScriptBuilder.createInputScript(signature)); input = input.withoutWitness(); replaceInput(inputIndex, input); } else if (ScriptPattern.isP2PKH(scriptPubKey)) { TransactionSignature signature = calculateSignature(inputIndex, sigKey, scriptPubKey, sigHash, anyoneCanPay); - input.setScriptSig(ScriptBuilder.createInputScript(signature, sigKey)); + input = input.withScriptSig(ScriptBuilder.createInputScript(signature, sigKey)); input = input.withoutWitness(); replaceInput(inputIndex, input); } else if (ScriptPattern.isP2WPKH(scriptPubKey)) { Script scriptCode = ScriptBuilder.createP2PKHOutputScript(sigKey); TransactionSignature signature = calculateWitnessSignature(inputIndex, sigKey, scriptCode, input.getValue(), sigHash, anyoneCanPay); - input.setScriptSig(ScriptBuilder.createEmpty()); + input = input.withScriptSig(ScriptBuilder.createEmpty()); input = input.withWitness(TransactionWitness.redeemP2WPKH(signature, sigKey)); replaceInput(inputIndex, input); } else { @@ -1210,7 +1210,7 @@ public class Transaction extends BaseMessage { // EC math so we'll do it anyway. for (int i = 0; i < tx.inputs.size(); i++) { TransactionInput input = tx.getInput(i); - input.clearScriptBytes(); + input = input.withoutScriptBytes(); input = input.withoutWitness(); tx.replaceInput(i, input); } @@ -1227,8 +1227,9 @@ public class Transaction extends BaseMessage { // 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. - TransactionInput input = tx.inputs.get(inputIndex); - input.setScriptBytes(connectedScript); + TransactionInput input = tx.getInput(inputIndex); + input = input.withScriptBytes(connectedScript); + tx.replaceInput(inputIndex, input); if ((sigHashType & 0x1f) == SigHash.NONE.value) { // SIGHASH_NONE means no outputs are signed at all - the signature is effectively for a "blank cheque". diff --git a/core/src/main/java/org/bitcoinj/core/TransactionInput.java b/core/src/main/java/org/bitcoinj/core/TransactionInput.java index 62d8a7a3f..57ac1cd8d 100644 --- a/core/src/main/java/org/bitcoinj/core/TransactionInput.java +++ b/core/src/main/java/org/bitcoinj/core/TransactionInput.java @@ -79,7 +79,7 @@ public class TransactionInput { // The "script bytes" might not actually be a script. In coinbase transactions where new coins are minted there // is no input transaction, so instead the scriptBytes contains some extra stuff (like a rollover nonce) that we // don't care about much. The bytes are turned into a Script object (cached below) on demand via a getter. - private byte[] scriptBytes; + private final byte[] scriptBytes; // The Script object obtained from parsing scriptBytes. Only filled in on demand and if the transaction is not // coinbase. private WeakReference