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 connectedScript the bytes that should be in the given input during signing.
* @param type Should be SigHash.ALL * @param type Should be SigHash.ALL
* @param anyoneCanPay should be false. * @param anyoneCanPay should be false.
* @throws ScriptException if connectedScript is invalid
*/ */
public synchronized Sha256Hash hashTransactionForSignature(int inputIndex, byte[] connectedScript, 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 { try {
// Store all the input scripts and clear them in preparation for signing. If we're signing a fresh // 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 // 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. // 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++) { 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); 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. // Set the input to the script of its output.
TransactionInput input = inputs.get(inputIndex); TransactionInput input = inputs.get(inputIndex);
input.setScriptBytes(connectedScript); 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); ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(length == UNKNOWN_LENGTH ? 256 : length + 4);
bitcoinSerialize(bos); bitcoinSerialize(bos);
// We also have to write a hash type. // We also have to write a hash type.
int hashType = type.ordinal() + 1; uint32ToByteStreamLE(sigHashType, bos);
if (anyoneCanPay)
hashType |= 0x80;
uint32ToByteStreamLE(hashType, bos);
// Note that this is NOT reversed to ensure it will be signed correctly. If it were to be printed out // 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. // however then we would expect that it is IS reversed.
Sha256Hash hash = new Sha256Hash(doubleDigest(bos.toByteArray())); Sha256Hash hash = new Sha256Hash(doubleDigest(bos.toByteArray()));
bos.close(); bos.close();
// Put the transaction back to how we found it. // Put the transaction back to how we found it.
this.inputs = inputs;
for (int i = 0; i < inputs.size(); i++) { 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; return hash;
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); // Cannot happen. throw new RuntimeException(e); // Cannot happen.

View File

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

View File

@ -139,7 +139,7 @@ public class FullPrunedBlockChainTest {
assertTrue(out.get() == null); 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); TransactionInput input = new TransactionInput(unitTestParams, t, new byte[]{}, prevOut);
t.addInput(input); t.addInput(input);
@ -157,4 +157,4 @@ public class FullPrunedBlockChainTest {
throw new RuntimeException(e); // Cannot happen. throw new RuntimeException(e); // Cannot happen.
} }
} }
} }