diff --git a/core/src/main/java/org/bitcoinj/core/Transaction.java b/core/src/main/java/org/bitcoinj/core/Transaction.java index 00bbf41b1..39987924a 100644 --- a/core/src/main/java/org/bitcoinj/core/Transaction.java +++ b/core/src/main/java/org/bitcoinj/core/Transaction.java @@ -47,6 +47,7 @@ import java.util.*; import static org.bitcoinj.core.NetworkParameters.ProtocolVersion.WITNESS_VERSION; import static org.bitcoinj.core.Utils.*; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import java.math.BigInteger; @@ -984,13 +985,23 @@ public class Transaction extends ChildMessage { * to understand the values of sigHash and anyoneCanPay: otherwise you can use the other form of this method * that sets them to typical defaults. * - * @throws ScriptException if the scriptPubKey is not a pay to address or P2PK script. + * @param prevOut A reference to the output being spent + * @param scriptPubKey The scriptPubKey of the output + * @param amount The amount of the output (which is part of the signature hash for segwit) + * @param sigKey The signing key + * @param sigHash enum specifying how the transaction hash is calculated + * @param anyoneCanPay anyone-can-pay hashing + * @return The newly created input + * @throws ScriptException if the scriptPubKey is something we don't know how to sign. */ - public TransactionInput addSignedInput(TransactionOutPoint prevOut, Script scriptPubKey, ECKey sigKey, + public TransactionInput addSignedInput(TransactionOutPoint prevOut, Script scriptPubKey, Coin amount, ECKey sigKey, SigHash sigHash, boolean anyoneCanPay) throws ScriptException { // Verify the API user didn't try to do operations out of order. checkState(!outputs.isEmpty(), "Attempting to sign tx without outputs."); - TransactionInput input = new TransactionInput(params, this, new byte[] {}, prevOut); + if (amount == null || amount.value <= 0) { + log.warn("Illegal amount value. Amount is required for SegWit transactions."); + } + TransactionInput input = new TransactionInput(params, this, new byte[] {}, prevOut, amount); addInput(input); int inputIndex = inputs.size() - 1; if (ScriptPattern.isP2PK(scriptPubKey)) { @@ -1016,27 +1027,74 @@ public class Transaction extends ChildMessage { } /** - * Same as {@link #addSignedInput(TransactionOutPoint, Script, ECKey, Transaction.SigHash, boolean)} - * but defaults to {@link SigHash#ALL} and "false" for the anyoneCanPay flag. This is normally what you want. + * @param prevOut A reference to the output being spent + * @param scriptPubKey The scriptPubKey of the output + * @param sigKey The signing key + * @param sigHash enum specifying how the transaction hash is calculated + * @param anyoneCanPay anyone-can-pay hashing + * @return The newly created input + * @throws ScriptException if the scriptPubKey is something we don't know how to sign. + * @deprecated Use {@link Transaction#addSignedInput(TransactionOutPoint, Script, Coin, ECKey, SigHash, boolean)} */ + @Deprecated + public TransactionInput addSignedInput(TransactionOutPoint prevOut, Script scriptPubKey, ECKey sigKey, + SigHash sigHash, boolean anyoneCanPay) throws ScriptException { + return addSignedInput(prevOut, scriptPubKey, null, sigKey, sigHash, anyoneCanPay); + } + + /** + * Adds a new and fully signed input for the given parameters. Note that this method is not thread safe + * and requires external synchronization. + * Defaults to {@link SigHash#ALL} and "false" for the anyoneCanPay flag. This is normally what you want. + * @param prevOut A reference to the output being spent + * @param scriptPubKey The scriptPubKey of the output + * @param amount The amount of the output (which is part of the signature hash for segwit) + * @param sigKey The signing key + * @return The newly created input + * @throws ScriptException if the scriptPubKey is something we don't know how to sign. + */ + public TransactionInput addSignedInput(TransactionOutPoint prevOut, Script scriptPubKey, Coin amount, ECKey sigKey) throws ScriptException { + return addSignedInput(prevOut, scriptPubKey, amount, sigKey, SigHash.ALL, false); + } + + /** + * @param prevOut A reference to the output being spent + * @param scriptPubKey The scriptPubKey of the output + * @param sigKey The signing key + * @return The newly created input + * @throws ScriptException if the scriptPubKey is something we don't know how to sign. + * @deprecated Use {@link Transaction#addSignedInput(TransactionOutPoint, Script, Coin, ECKey)} + */ + @Deprecated public TransactionInput addSignedInput(TransactionOutPoint prevOut, Script scriptPubKey, ECKey sigKey) throws ScriptException { - return addSignedInput(prevOut, scriptPubKey, sigKey, SigHash.ALL, false); + return addSignedInput(prevOut, scriptPubKey, null, sigKey); + } + + /** + * Adds an input that points to the given output and contains a valid signature for it, calculated using the + * signing key. Defaults to {@link SigHash#ALL} and "false" for the anyoneCanPay flag. This is normally what you want. + * @param output output to sign and use as input + * @param sigKey The signing key + * @return The newly created input + */ + public TransactionInput addSignedInput(TransactionOutput output, ECKey sigKey) { + return addSignedInput(output, sigKey, SigHash.ALL, false); } /** * Adds an input that points to the given output and contains a valid signature for it, calculated using the * signing key. + * @see Transaction#addSignedInput(TransactionOutPoint, Script, Coin, ECKey, SigHash, boolean) + * @param output output to sign and use as input + * @param sigKey The signing key + * @param sigHash enum specifying how the transaction hash is calculated + * @param anyoneCanPay anyone-can-pay hashing + * @return The newly created input */ - public TransactionInput addSignedInput(TransactionOutput output, ECKey signingKey) { - return addSignedInput(output.getOutPointFor(), output.getScriptPubKey(), signingKey); - } - - /** - * Adds an input that points to the given output and contains a valid signature for it, calculated using the - * signing key. - */ - public TransactionInput addSignedInput(TransactionOutput output, ECKey signingKey, SigHash sigHash, boolean anyoneCanPay) { - return addSignedInput(output.getOutPointFor(), output.getScriptPubKey(), signingKey, sigHash, anyoneCanPay); + public TransactionInput addSignedInput(TransactionOutput output, ECKey sigKey, SigHash sigHash, boolean anyoneCanPay) { + checkNotNull(output.getValue(), "TransactionOutput.getValue() must not be null"); + checkState(output.getValue().value > 0, "TransactionOutput.getValue() must not be greater than zero"); + return addSignedInput(output.getOutPointFor(), output.getScriptPubKey(), output.getValue(), sigKey, sigHash, anyoneCanPay); } /** diff --git a/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java b/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java index 2a25b6032..1973719d2 100644 --- a/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java +++ b/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java @@ -183,21 +183,22 @@ public abstract class AbstractFullPrunedBlockChainTest { // Build some blocks on genesis block to create a spendable output Block rollingBlock = PARAMS.getGenesisBlock().createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); - TransactionOutPoint spendableOutput = new TransactionOutPoint(PARAMS, 0, rollingBlock.getTransactions().get(0).getTxId()); - byte[] spendableOutputScriptPubKey = rollingBlock.getTransactions().get(0).getOutputs().get(0).getScriptBytes(); + TransactionOutput spendableOutput = rollingBlock.getTransactions().get(0).getOutput(0); + TransactionOutPoint transactionOutPoint = spendableOutput.getOutPointFor(); + Script spendableOutputScriptPubKey = spendableOutput.getScriptPubKey(); for (int i = 1; i < PARAMS.getSpendableCoinbaseDepth(); i++) { rollingBlock = rollingBlock.createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); } WeakReference out = new WeakReference<> - (store.getTransactionOutput(spendableOutput.getHash(), spendableOutput.getIndex())); + (store.getTransactionOutput(transactionOutPoint.getHash(), transactionOutPoint.getIndex())); rollingBlock = rollingBlock.createNextBlock(null); Transaction t = new Transaction(PARAMS); // Entirely invalid scriptPubKey t.addOutput(new TransactionOutput(PARAMS, t, FIFTY_COINS, new byte[]{})); - t.addSignedInput(spendableOutput, new Script(spendableOutputScriptPubKey), outKey); + t.addSignedInput(transactionOutPoint, spendableOutputScriptPubKey, spendableOutput.getValue(), outKey); rollingBlock.addTransaction(t); rollingBlock.solve(); @@ -257,8 +258,9 @@ public abstract class AbstractFullPrunedBlockChainTest { Block rollingBlock = PARAMS.getGenesisBlock().createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); Transaction transaction = rollingBlock.getTransactions().get(0); - TransactionOutPoint spendableOutput = new TransactionOutPoint(PARAMS, 0, transaction.getTxId()); - byte[] spendableOutputScriptPubKey = transaction.getOutputs().get(0).getScriptBytes(); + TransactionOutput spendableOutput = transaction.getOutput(0); + TransactionOutPoint spendableOutputPoint = spendableOutput.getOutPointFor(); + Script spendableOutputScriptPubKey = spendableOutput.getScriptPubKey(); for (int i = 1; i < PARAMS.getSpendableCoinbaseDepth(); i++) { rollingBlock = rollingBlock.createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); @@ -273,7 +275,7 @@ public abstract class AbstractFullPrunedBlockChainTest { Transaction t = new Transaction(PARAMS); t.addOutput(new TransactionOutput(PARAMS, t, amount, toKey)); - t.addSignedInput(spendableOutput, new Script(spendableOutputScriptPubKey), outKey); + t.addSignedInput(spendableOutputPoint, spendableOutputScriptPubKey, spendableOutput.getValue(), outKey); rollingBlock.addTransaction(t); rollingBlock.solve(); chain.add(rollingBlock); @@ -308,8 +310,9 @@ public abstract class AbstractFullPrunedBlockChainTest { Block rollingBlock = PARAMS.getGenesisBlock().createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); Transaction transaction = rollingBlock.getTransactions().get(0); - TransactionOutPoint spendableOutput = new TransactionOutPoint(PARAMS, 0, transaction.getTxId()); - byte[] spendableOutputScriptPubKey = transaction.getOutputs().get(0).getScriptBytes(); + TransactionOutput spendableOutput = transaction.getOutput(0); + TransactionOutPoint spendableOutPoint = new TransactionOutPoint(PARAMS, 0, transaction.getTxId()); + Script spendableOutputScriptPubKey = spendableOutput.getScriptPubKey(); for (int i = 1; i < PARAMS.getSpendableCoinbaseDepth(); i++) { rollingBlock = rollingBlock.createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); @@ -327,7 +330,7 @@ public abstract class AbstractFullPrunedBlockChainTest { Transaction t = new Transaction(PARAMS); t.addOutput(new TransactionOutput(PARAMS, t, amount, toKey)); - t.addSignedInput(spendableOutput, new Script(spendableOutputScriptPubKey), outKey); + t.addSignedInput(spendableOutPoint, spendableOutputScriptPubKey, spendableOutput.getValue(), outKey); rollingBlock.addTransaction(t); rollingBlock.solve(); chain.add(rollingBlock); diff --git a/core/src/test/java/org/bitcoinj/core/TransactionTest.java b/core/src/test/java/org/bitcoinj/core/TransactionTest.java index 167058389..36a5e57c8 100644 --- a/core/src/test/java/org/bitcoinj/core/TransactionTest.java +++ b/core/src/test/java/org/bitcoinj/core/TransactionTest.java @@ -168,6 +168,45 @@ public class TransactionTest { assertEquals(tx.isMature(), false); } + @Test + public void testBuildingSimpleP2PKH() { + final Address toAddr = Address.fromKey(TESTNET, new ECKey(), Script.ScriptType.P2PKH); + final Sha256Hash utxo_id = Sha256Hash.wrap("81b4c832d70cb56ff957589752eb4125a4cab78a25a8fc52d6a09e5bd4404d48"); + final Coin inAmount = Coin.ofSat(91234); + final Coin outAmount = Coin.ofSat(91234); + + ECKey fromKey = new ECKey(); + Address fromAddress = Address.fromKey(TESTNET, fromKey, Script.ScriptType.P2PKH); + Transaction tx = new Transaction(TESTNET); + TransactionOutPoint outPoint = new TransactionOutPoint(TESTNET, 0, utxo_id); + TransactionOutput output = new TransactionOutput(TESTNET, null, inAmount, fromAddress); + tx.addOutput(outAmount, toAddr); + tx.addSignedInput(outPoint, ScriptBuilder.createOutputScript(fromAddress), inAmount, fromKey); + + byte[] rawTx = tx.bitcoinSerialize(); + + assertNotNull(rawTx); + } + + @Test + public void testBuildingSimpleP2WPKH() { + final Address toAddr = Address.fromKey(TESTNET, new ECKey(), Script.ScriptType.P2WPKH); + final Sha256Hash utxo_id = Sha256Hash.wrap("81b4c832d70cb56ff957589752eb4125a4cab78a25a8fc52d6a09e5bd4404d48"); + final Coin inAmount = Coin.ofSat(91234); + final Coin outAmount = Coin.ofSat(91234); + + ECKey fromKey = new ECKey(); + Address fromAddress = Address.fromKey(TESTNET, fromKey, Script.ScriptType.P2WPKH); + Transaction tx = new Transaction(TESTNET); + TransactionOutPoint outPoint = new TransactionOutPoint(TESTNET, 0, utxo_id); + tx.addOutput(outAmount, toAddr); + tx.addSignedInput(outPoint, ScriptBuilder.createOutputScript(fromAddress), inAmount, fromKey); + + byte[] rawTx = tx.bitcoinSerialize(); + + assertNotNull(rawTx); + } + @Test public void witnessTransaction() { String hex; @@ -464,14 +503,14 @@ public class TransactionTest { public void testAddSignedInputThrowsExceptionWhenScriptIsNotToRawPubKeyAndIsNotToAddress() { ECKey key = new ECKey(); Address addr = LegacyAddress.fromKey(UNITTEST, key); - Transaction fakeTx = FakeTxBuilder.createFakeTx(UNITTEST, Coin.COIN, addr); + TransactionOutput fakeOutput = FakeTxBuilder.createFakeTx(UNITTEST, Coin.COIN, addr).getOutput(0); Transaction tx = new Transaction(UNITTEST); - tx.addOutput(fakeTx.getOutput(0)); + tx.addOutput(fakeOutput); Script script = ScriptBuilder.createOpReturnScript(new byte[0]); - tx.addSignedInput(fakeTx.getOutput(0).getOutPointFor(), script, key); + tx.addSignedInput(fakeOutput.getOutPointFor(), script, fakeOutput.getValue(), key); } @Test