3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-30 23:02:15 +00:00

Implement all SigHash types in hashTransactionForSignature.

This commit is contained in:
Matt Corallo 2012-08-01 20:40:53 +02:00 committed by Mike Hearn
parent 2f2850610e
commit 40237f0549
3 changed files with 68 additions and 13 deletions

View File

@ -746,39 +746,94 @@ public class Transaction extends ChildMessage implements Serializable {
* @param connectedScript the bytes that should be in the given input during signing.
* @param type Should be SigHash.ALL
* @param anyoneCanPay should be false.
* @throws ScriptException if connectedScript is invalid
*/
public synchronized Sha256Hash hashTransactionForSignature(int inputIndex, byte[] connectedScript,
SigHash type, boolean anyoneCanPay) {
SigHash type, boolean anyoneCanPay) throws ScriptException {
return hashTransactionForSignature(inputIndex, connectedScript, (byte)((type.ordinal() + 1) | (anyoneCanPay ? 0x80 : 0x00)));
}
/**
* This is required for signatures which use a sigHashType which cannot be represented using SigHash and anyoneCanPay
* See transaction c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73, which has sigHashType 0
*/
synchronized Sha256Hash hashTransactionForSignature(int inputIndex, byte[] connectedScript,
byte sigHashType) throws ScriptException {
try {
// Store all the input scripts and clear them in preparation for signing. If we're signing a fresh
// transaction that step isn't very helpful, but it doesn't add much cost relative to the actual
// EC math so we'll do it anyway.
byte[][] scripts = new byte[inputs.size()][];
// Also store the input sequences in case we are clearing them with SigHash.NONE/SINGLE
byte[][] inputScripts = new byte[inputs.size()][];
long[] inputSequences = new long[inputs.size()];
for (int i = 0; i < inputs.size(); i++) {
scripts[i] = inputs.get(i).getScriptBytes();
inputScripts[i] = inputs.get(i).getScriptBytes();
inputSequences[i] = inputs.get(i).getSequence();
inputs.get(i).setScriptBytes(TransactionInput.EMPTY_ARRAY);
}
// "In case concatenating two scripts ends up with two codeseparators, or an extra
// one at the end, this prevents all those possible incompatibilities." - reference client
connectedScript = Script.removeAllInstancesOfOp(connectedScript, Script.OP_CODESEPARATOR);
// Set the input to the script of its output.
TransactionInput input = inputs.get(inputIndex);
input.setScriptBytes(connectedScript);
ArrayList<TransactionOutput> outputs = this.outputs;
if ((sigHashType & 0x1f) == (SigHash.NONE.ordinal() + 1)) {
this.outputs = new ArrayList<TransactionOutput>(0);
for (int i = 0; i < inputs.size(); i++)
if (i != inputIndex)
inputs.get(i).setSequence(0);
}
else if ((sigHashType & 0x1f) == (SigHash.SINGLE.ordinal() + 1)) {
if (inputIndex >= this.outputs.size()) {
// TODO: Only allow this to happen if we are checking a signature, not signing a transactions
// Any transaction ouptut that is signed in this case will result in both the signed output
// and any future outputs to this public key being steal-able by anyone who has
// the resulting signature and the public key (both of which are part of the signed tx input)
// Put the transaction back to how we found it.
for (int i = 0; i < inputs.size(); i++) {
inputs.get(i).setScriptBytes(inputScripts[i]);
inputs.get(i).setSequence(inputSequences[i]);
}
this.outputs = outputs;
return new Sha256Hash("0100000000000000000000000000000000000000000000000000000000000000");
}
this.outputs = new ArrayList<TransactionOutput>(this.outputs.subList(0, inputIndex));
for (int i = 0; i < inputIndex; i++)
this.outputs.set(i, new TransactionOutput(params, this, BigInteger.valueOf(-1), new byte[] {}));
for (int i = 0; i < inputs.size(); i++)
if (i != inputIndex)
inputs.get(i).setSequence(0);
}
ArrayList<TransactionInput> inputs = this.inputs;
if ((sigHashType & 0x80) == 0x80) {
this.inputs = new ArrayList<TransactionInput>();
this.inputs.add(input);
}
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(length == UNKNOWN_LENGTH ? 256 : length + 4);
bitcoinSerialize(bos);
// We also have to write a hash type.
int hashType = type.ordinal() + 1;
if (anyoneCanPay)
hashType |= 0x80;
uint32ToByteStreamLE(hashType, bos);
uint32ToByteStreamLE(sigHashType, bos);
// Note that this is NOT reversed to ensure it will be signed correctly. If it were to be printed out
// however then we would expect that it is IS reversed.
Sha256Hash hash = new Sha256Hash(doubleDigest(bos.toByteArray()));
bos.close();
// Put the transaction back to how we found it.
this.inputs = inputs;
for (int i = 0; i < inputs.size(); i++) {
inputs.get(i).setScriptBytes(scripts[i]);
inputs.get(i).setScriptBytes(inputScripts[i]);
inputs.get(i).setSequence(inputSequences[i]);
}
this.outputs = outputs;
return hash;
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.

View File

@ -1077,7 +1077,7 @@ public class FullBlockTestGenerator {
}
private Block createNextBlock(Block baseBlock, int nextBlockHeight, TransactionOutPointWithValue prevOut,
BigInteger additionalCoinbaseValue) {
BigInteger additionalCoinbaseValue) throws ScriptException {
BigInteger coinbaseValue = Utils.toNanoCoins(50, 0).shiftRight(nextBlockHeight / params.getSubsidyDecreaseBlockCount())
.add((prevOut != null ? prevOut.value : BigInteger.valueOf(0))).subtract(BigInteger.valueOf(1))
.add(additionalCoinbaseValue == null ? BigInteger.valueOf(0) : additionalCoinbaseValue);
@ -1096,11 +1096,11 @@ public class FullBlockTestGenerator {
return block;
}
private void addOnlyInputToTransaction(Transaction t, TransactionOutPointWithValue prevOut) {
private void addOnlyInputToTransaction(Transaction t, TransactionOutPointWithValue prevOut) throws ScriptException {
addOnlyInputToTransaction(t, prevOut, TransactionInput.NO_SEQUENCE);
}
private void addOnlyInputToTransaction(Transaction t, TransactionOutPointWithValue prevOut, long sequence) {
private void addOnlyInputToTransaction(Transaction t, TransactionOutPointWithValue prevOut, long sequence) throws ScriptException {
TransactionInput input = new TransactionInput(params, t, new byte[]{}, prevOut.outpoint);
input.setSequence(sequence);
t.addInput(input);

View File

@ -139,7 +139,7 @@ public class FullPrunedBlockChainTest {
assertTrue(out.get() == null);
}
private void addInputToTransaction(Transaction t, TransactionOutPoint prevOut, byte[] prevOutScriptPubKey, ECKey sigKey) {
private void addInputToTransaction(Transaction t, TransactionOutPoint prevOut, byte[] prevOutScriptPubKey, ECKey sigKey) throws ScriptException {
TransactionInput input = new TransactionInput(unitTestParams, t, new byte[]{}, prevOut);
t.addInput(input);
@ -157,4 +157,4 @@ public class FullPrunedBlockChainTest {
throw new RuntimeException(e); // Cannot happen.
}
}
}
}