3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-07 14:54:15 +00:00

Rename TransactionInput get/setSequence -> get/setSequenceNumber. The previous name could be confusing and was inconsistent. Add lots of comments to Transaction.hashForSignature.

This commit is contained in:
Mike Hearn 2012-11-01 14:06:59 +01:00
parent 40237f0549
commit 8d1591183f
5 changed files with 63 additions and 35 deletions

View File

@ -759,61 +759,85 @@ public class Transaction extends ChildMessage implements Serializable {
*/ */
synchronized Sha256Hash hashTransactionForSignature(int inputIndex, byte[] connectedScript, synchronized Sha256Hash hashTransactionForSignature(int inputIndex, byte[] connectedScript,
byte sigHashType) throws ScriptException { byte sigHashType) throws ScriptException {
// TODO: This whole separate method should be un-necessary if we fix how we deserialize sighash flags.
// The SIGHASH flags are used in the design of contracts, please see this page for a further understanding of
// the purposes of the code in this method:
//
// https://en.bitcoin.it/wiki/Contracts
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.
// Also store the input sequences in case we are clearing them with SigHash.NONE/SINGLE //
// Also store the input sequence numbers in case we are clearing them with SigHash.NONE/SINGLE
byte[][] inputScripts = new byte[inputs.size()][]; byte[][] inputScripts = new byte[inputs.size()][];
long[] inputSequences = new long[inputs.size()]; long[] inputSequenceNumbers = new long[inputs.size()];
for (int i = 0; i < inputs.size(); i++) { for (int i = 0; i < inputs.size(); i++) {
inputScripts[i] = inputs.get(i).getScriptBytes(); inputScripts[i] = inputs.get(i).getScriptBytes();
inputSequences[i] = inputs.get(i).getSequence(); inputSequenceNumbers[i] = inputs.get(i).getSequenceNumber();
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 // This step has no purpose beyond being synchronized with the reference clients bugs. OP_CODESEPARATOR
// one at the end, this prevents all those possible incompatibilities." - reference client // is a legacy holdover from a previous, broken design of executing scripts that shipped in Bitcoin 0.1.
// It was seriously flawed and would have let anyone take anyone elses money. Later versions switched to
// the design we use today where scripts are executed independently but share a stack. This left the
// OP_CODESEPARATOR instruction having no purpose as it was only meant to be used internally, not actually
// ever put into scripts. Deleting OP_CODESEPARATOR is a step that should never be required but if we don't
// do it, we could split off the main chain.
connectedScript = Script.removeAllInstancesOfOp(connectedScript, Script.OP_CODESEPARATOR); 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. Satoshi does this but the step has no obvious purpose as
// the signature covers the hash of the prevout transaction which obviously includes the output script
// already. Perhaps it felt safer to him in some way, or is another leftover from how the code was written.
TransactionInput input = inputs.get(inputIndex); TransactionInput input = inputs.get(inputIndex);
input.setScriptBytes(connectedScript); input.setScriptBytes(connectedScript);
ArrayList<TransactionOutput> outputs = this.outputs; ArrayList<TransactionOutput> outputs = this.outputs;
if ((sigHashType & 0x1f) == (SigHash.NONE.ordinal() + 1)) { if ((sigHashType & 0x1f) == (SigHash.NONE.ordinal() + 1)) {
// SIGHASH_NONE means no outputs are signed at all - the signature is effectively for a "blank cheque".
this.outputs = new ArrayList<TransactionOutput>(0); this.outputs = new ArrayList<TransactionOutput>(0);
// The signature isn't broken by new versions of the transaction issued by other parties.
for (int i = 0; i < inputs.size(); i++) for (int i = 0; i < inputs.size(); i++)
if (i != inputIndex) if (i != inputIndex)
inputs.get(i).setSequence(0); inputs.get(i).setSequenceNumber(0);
} } else if ((sigHashType & 0x1f) == (SigHash.SINGLE.ordinal() + 1)) {
else if ((sigHashType & 0x1f) == (SigHash.SINGLE.ordinal() + 1)) { // SIGHASH_SINGLE means only sign the output at the same index as the input (ie, my output).
if (inputIndex >= this.outputs.size()) { if (inputIndex >= this.outputs.size()) {
// TODO: Only allow this to happen if we are checking a signature, not signing a transactions // The input index is beyond the number of outputs, it's a buggy signature made by a broken
// Any transaction ouptut that is signed in this case will result in both the signed output // Bitcoin implementation. The reference client also contains a bug in handling this case:
// any transaction output 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 // 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) // 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. // Put the transaction back to how we found it.
//
// TODO: Only allow this to happen if we are checking a signature, not signing a transactions
for (int i = 0; i < inputs.size(); i++) { for (int i = 0; i < inputs.size(); i++) {
inputs.get(i).setScriptBytes(inputScripts[i]); inputs.get(i).setScriptBytes(inputScripts[i]);
inputs.get(i).setSequence(inputSequences[i]); inputs.get(i).setSequenceNumber(inputSequenceNumbers[i]);
} }
this.outputs = outputs; this.outputs = outputs;
// Satoshis bug is that SignatureHash was supposed to return a hash and on this codepath it
// actually returns the constant "1" to indicate an error, which is never checked for. Oops.
return new Sha256Hash("0100000000000000000000000000000000000000000000000000000000000000"); return new Sha256Hash("0100000000000000000000000000000000000000000000000000000000000000");
} }
// In SIGHASH_SINGLE the outputs after the matching input index are deleted, and the outputs before
// that position are "nulled out". Unintuitively, the value in a "null" transaction is set to -1.
this.outputs = new ArrayList<TransactionOutput>(this.outputs.subList(0, inputIndex)); this.outputs = new ArrayList<TransactionOutput>(this.outputs.subList(0, inputIndex));
for (int i = 0; i < inputIndex; i++) for (int i = 0; i < inputIndex; i++)
this.outputs.set(i, new TransactionOutput(params, this, BigInteger.valueOf(-1), new byte[] {})); this.outputs.set(i, new TransactionOutput(params, this, BigInteger.valueOf(-1), new byte[] {}));
// The signature isn't broken by new versions of the transaction issued by other parties.
for (int i = 0; i < inputs.size(); i++) for (int i = 0; i < inputs.size(); i++)
if (i != inputIndex) if (i != inputIndex)
inputs.get(i).setSequence(0); inputs.get(i).setSequenceNumber(0);
} }
ArrayList<TransactionInput> inputs = this.inputs; ArrayList<TransactionInput> inputs = this.inputs;
if ((sigHashType & 0x80) == 0x80) { if ((sigHashType & 0x80) == 0x80) {
// SIGHASH_ANYONECANPAY means the signature in the input is not broken by changes/additions/removals
// of other inputs. For example, this is useful for building assurance contracts.
this.inputs = new ArrayList<TransactionInput>(); this.inputs = new ArrayList<TransactionInput>();
this.inputs.add(input); this.inputs.add(input);
} }
@ -831,7 +855,7 @@ public class Transaction extends ChildMessage implements Serializable {
this.inputs = inputs; this.inputs = inputs;
for (int i = 0; i < inputs.size(); i++) { for (int i = 0; i < inputs.size(); i++) {
inputs.get(i).setScriptBytes(inputScripts[i]); inputs.get(i).setScriptBytes(inputScripts[i]);
inputs.get(i).setSequence(inputSequences[i]); inputs.get(i).setSequenceNumber(inputSequenceNumbers[i]);
} }
this.outputs = outputs; this.outputs = outputs;
return hash; return hash;

View File

@ -179,17 +179,25 @@ public class TransactionInput extends ChildMessage implements Serializable {
} }
/** /**
* @return Transaction version as defined by the sender. Intended for "replacement" of transactions when information is updated before inclusion into a block. * Sequence numbers allow participants in a multi-party transaction signing protocol to create new versions of the
* transaction independently of each other. Newer versions of a transaction can replace an existing version that's
* in nodes memory pools if the existing version is time locked. See the Contracts page on the Bitcoin wiki for
* examples of how you can use this feature to build contract protocols. Note that as of 2012 the tx replacement
* feature is disabled so sequence numbers are unusable.
*/ */
public long getSequence() { public long getSequenceNumber() {
maybeParse(); maybeParse();
return sequence; return sequence;
} }
/** /**
* @param sequence Transaction version as defined by the sender. Intended for "replacement" of transactions when information is updated before inclusion into a block. * Sequence numbers allow participants in a multi-party transaction signing protocol to create new versions of the
* transaction independently of each other. Newer versions of a transaction can replace an existing version that's
* in nodes memory pools if the existing version is time locked. See the Contracts page on the Bitcoin wiki for
* examples of how you can use this feature to build contract protocols. Note that as of 2012 the tx replacement
* feature is disabled so sequence numbers are unusable.
*/ */
public void setSequence(long sequence) { public void setSequenceNumber(long sequence) {
unCache(); unCache();
this.sequence = sequence; this.sequence = sequence;
} }

View File

@ -164,7 +164,7 @@ public class WalletProtobufSerializer {
.setTransactionOutPointHash(hashToByteString(input.getOutpoint().getHash())) .setTransactionOutPointHash(hashToByteString(input.getOutpoint().getHash()))
.setTransactionOutPointIndex((int) input.getOutpoint().getIndex()); .setTransactionOutPointIndex((int) input.getOutpoint().getIndex());
if (input.hasSequence()) { if (input.hasSequence()) {
inputBuilder.setSequence((int)input.getSequence()); inputBuilder.setSequence((int)input.getSequenceNumber());
} }
txBuilder.addTransactionInput(inputBuilder); txBuilder.addTransactionInput(inputBuilder);
} }
@ -332,7 +332,7 @@ public class WalletProtobufSerializer {
); );
TransactionInput input = new TransactionInput(params, tx, scriptBytes, outpoint); TransactionInput input = new TransactionInput(params, tx, scriptBytes, outpoint);
if (transactionInput.hasSequence()) { if (transactionInput.hasSequence()) {
input.setSequence(transactionInput.getSequence()); input.setSequenceNumber(transactionInput.getSequence());
} }
tx.addInput(input); tx.addInput(input);
} }

View File

@ -150,7 +150,7 @@ public class BitcoinSerializerTest {
assertEquals(!lazy, tx.isParsed()); assertEquals(!lazy, tx.isParsed());
assertEquals(true, tx.isCached()); assertEquals(true, tx.isCached());
tx.getInputs().get(0).setSequence(1); tx.getInputs().get(0).setSequenceNumber(1);
//parent should have been uncached //parent should have been uncached
assertEquals(false, tx.isCached()); assertEquals(false, tx.isCached());
//so should child //so should child
@ -177,7 +177,7 @@ public class BitcoinSerializerTest {
assertEquals(!lazy, tx.isParsed()); assertEquals(!lazy, tx.isParsed());
assertEquals(true, tx.isCached()); assertEquals(true, tx.isCached());
tx.getInputs().get(0).setSequence(tx.getInputs().get(0).getSequence()); tx.getInputs().get(0).setSequenceNumber(tx.getInputs().get(0).getSequenceNumber());
bos = new ByteArrayOutputStream(); bos = new ByteArrayOutputStream();
bs.serialize(tx, bos); bs.serialize(tx, bos);

View File

@ -1,16 +1,12 @@
package com.google.bitcoin.core; package com.google.bitcoin.core;
import com.google.bitcoin.core.Transaction.SigHash;
import com.google.common.base.Preconditions;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import com.google.bitcoin.core.Transaction.SigHash;
import com.google.common.base.Preconditions;
class BlockAndValidity { class BlockAndValidity {
Block block; Block block;
@ -1102,7 +1098,7 @@ public class FullBlockTestGenerator {
private void addOnlyInputToTransaction(Transaction t, TransactionOutPointWithValue prevOut, long sequence) throws ScriptException { 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.setSequenceNumber(sequence);
t.addInput(input); t.addInput(input);
byte[] connectedPubKeyScript = prevOut.scriptPubKey.program; byte[] connectedPubKeyScript = prevOut.scriptPubKey.program;