mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2024-11-20 10:12:19 +01:00
Transaction: support segwit in addSignedInput() methods
Add addSignedInput() variants to Transaction that provide input value, deprecate non-value methods, add checks and logs. Add simple transaction building tests to TransactionTest. This also updates some tests to not use the deprecated methods.
This commit is contained in:
parent
f3a314e2d9
commit
1959dab5e4
@ -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 <b>not</b> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<UTXO> 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);
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user