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