Complete the SigHash enum and make updates to stop using the ordinal while preserving the ordinal for any existing code that might use it.

This commit is contained in:
mruddy
2016-03-24 22:40:00 +00:00
committed by Andreas Schildbach
parent c02c5ff249
commit 2748b35181
6 changed files with 50 additions and 27 deletions

View File

@@ -460,16 +460,37 @@ public class Transaction extends ChildMessage {
/**
* These constants are a part of a scriptSig signature on the inputs. They define the details of how a
* transaction can be redeemed, specifically, they control how the hash of the transaction is calculated.
* <p/>
* In Bitcoin Core, this enum also has another flag, SIGHASH_ANYONECANPAY. In this implementation,
* that's kept separate. Only SIGHASH_ALL is actually used in Bitcoin Core today. The other flags
* exist to allow for distributed contracts.
*/
public enum SigHash {
ALL, // 1
NONE, // 2
SINGLE, // 3
ALL(1),
NONE(2),
SINGLE(3),
ANYONECANPAY(0x80), // Caution: Using this type in isolation is non-standard. Treated similar to ANYONECANPAY_ALL.
ANYONECANPAY_ALL(0x81),
ANYONECANPAY_NONE(0x82),
ANYONECANPAY_SINGLE(0x83),
UNSET(0); // Caution: Using this type in isolation is non-standard. Treated similar to ALL.
public final int value;
/**
* @param value
*/
private SigHash(final int value) {
this.value = value;
}
/**
* @return the value as a byte
*/
public byte byteValue() {
return (byte) this.value;
}
}
/**
* @deprecated Instead use SigHash.ANYONECANPAY.value or SigHash.ANYONECANPAY.byteValue() as appropriate.
*/
public static final byte SIGHASH_ANYONECANPAY_VALUE = (byte) 0x80;
@Override
@@ -958,14 +979,14 @@ public class Transaction extends ChildMessage {
TransactionInput input = tx.inputs.get(inputIndex);
input.setScriptBytes(connectedScript);
if ((sigHashType & 0x1f) == (SigHash.NONE.ordinal() + 1)) {
if ((sigHashType & 0x1f) == SigHash.NONE.value) {
// SIGHASH_NONE means no outputs are signed at all - the signature is effectively for a "blank cheque".
tx.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 < tx.inputs.size(); i++)
if (i != inputIndex)
tx.inputs.get(i).setSequenceNumber(0);
} else if ((sigHashType & 0x1f) == (SigHash.SINGLE.ordinal() + 1)) {
} else if ((sigHashType & 0x1f) == SigHash.SINGLE.value) {
// SIGHASH_SINGLE means only sign the output at the same index as the input (ie, my output).
if (inputIndex >= tx.outputs.size()) {
// The input index is beyond the number of outputs, it's a buggy signature made by a broken
@@ -989,7 +1010,7 @@ public class Transaction extends ChildMessage {
tx.inputs.get(i).setSequenceNumber(0);
}
if ((sigHashType & SIGHASH_ANYONECANPAY_VALUE) == SIGHASH_ANYONECANPAY_VALUE) {
if ((sigHashType & SigHash.ANYONECANPAY.value) == SigHash.ANYONECANPAY.value) {
// 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.
tx.inputs = new ArrayList<TransactionInput>();

View File

@@ -19,7 +19,8 @@ package org.bitcoinj.crypto;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.core.Transaction.SigHash;
import com.google.common.base.Preconditions;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
@@ -39,7 +40,7 @@ public class TransactionSignature extends ECKey.ECDSASignature {
/** Constructs a signature with the given components and SIGHASH_ALL. */
public TransactionSignature(BigInteger r, BigInteger s) {
this(r, s, Transaction.SigHash.ALL.ordinal() + 1);
this(r, s, Transaction.SigHash.ALL.value);
}
/** Constructs a signature with the given components and raw sighash flag bytes (needed for rule compatibility). */
@@ -67,9 +68,10 @@ public class TransactionSignature extends ECKey.ECDSASignature {
/** Calculates the byte used in the protocol to represent the combination of mode and anyoneCanPay. */
public static int calcSigHashValue(Transaction.SigHash mode, boolean anyoneCanPay) {
int sighashFlags = mode.ordinal() + 1;
Preconditions.checkArgument(SigHash.ALL == mode || SigHash.NONE == mode || SigHash.SINGLE == mode); // enforce compatibility since this code was made before the SigHash enum was updated
int sighashFlags = mode.value;
if (anyoneCanPay)
sighashFlags |= Transaction.SIGHASH_ANYONECANPAY_VALUE;
sighashFlags |= Transaction.SigHash.ANYONECANPAY.value;
return sighashFlags;
}
@@ -90,8 +92,8 @@ public class TransactionSignature extends ECKey.ECDSASignature {
if (signature.length < 9 || signature.length > 73)
return false;
int hashType = signature[signature.length-1] & ~Transaction.SIGHASH_ANYONECANPAY_VALUE;
if (hashType < (Transaction.SigHash.ALL.ordinal() + 1) || hashType > (Transaction.SigHash.SINGLE.ordinal() + 1))
int hashType = (signature[signature.length-1] & 0xff) & ~Transaction.SigHash.ANYONECANPAY.value; // mask the byte to prevent sign-extension hurting us
if (hashType < Transaction.SigHash.ALL.value || hashType > Transaction.SigHash.SINGLE.value)
return false;
// "wrong type" "wrong length marker"
@@ -121,14 +123,14 @@ public class TransactionSignature extends ECKey.ECDSASignature {
}
public boolean anyoneCanPay() {
return (sighashFlags & Transaction.SIGHASH_ANYONECANPAY_VALUE) != 0;
return (sighashFlags & Transaction.SigHash.ANYONECANPAY.value) != 0;
}
public Transaction.SigHash sigHashMode() {
final int mode = sighashFlags & 0x1f;
if (mode == Transaction.SigHash.NONE.ordinal() + 1)
if (mode == Transaction.SigHash.NONE.value)
return Transaction.SigHash.NONE;
else if (mode == Transaction.SigHash.SINGLE.ordinal() + 1)
else if (mode == Transaction.SigHash.SINGLE.value)
return Transaction.SigHash.SINGLE;
else
return Transaction.SigHash.ALL;

View File

@@ -447,7 +447,7 @@ public class ECKeyTest {
new Random().nextBytes(hash);
byte[] sigBytes = key.sign(Sha256Hash.wrap(hash)).encodeToDER();
byte[] encodedSig = Arrays.copyOf(sigBytes, sigBytes.length + 1);
encodedSig[sigBytes.length] = (byte) (Transaction.SigHash.ALL.ordinal() + 1);
encodedSig[sigBytes.length] = Transaction.SigHash.ALL.byteValue();
if (!TransactionSignature.isEncodingCanonical(encodedSig)) {
log.error(Utils.HEX.encode(sigBytes));
fail();

View File

@@ -759,7 +759,7 @@ public class FullBlockTestGenerator {
try {
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73);
bos.write(coinbaseOutKey.sign(hash).encodeToDER());
bos.write(SigHash.SINGLE.ordinal() + 1);
bos.write(SigHash.SINGLE.value);
byte[] signature = bos.toByteArray();
ByteArrayOutputStream scriptSigBos = new UnsafeByteArrayOutputStream(signature.length + b39p2shScriptPubKey.length + 3);
@@ -830,7 +830,7 @@ public class FullBlockTestGenerator {
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(
73);
bos.write(coinbaseOutKey.sign(hash).encodeToDER());
bos.write(SigHash.SINGLE.ordinal() + 1);
bos.write(SigHash.SINGLE.value);
byte[] signature = bos.toByteArray();
ByteArrayOutputStream scriptSigBos = new UnsafeByteArrayOutputStream(

View File

@@ -392,14 +392,14 @@ public class TransactionTest {
final Transaction tx = block1.getTransactions().get(1);
final String txHash = tx.getHashAsString();
final String txNormalizedHash = tx.hashForSignature(0, new byte[0], (byte) (Transaction.SigHash.ALL.ordinal() + 1)).toString();
final String txNormalizedHash = tx.hashForSignature(0, new byte[0], Transaction.SigHash.ALL.byteValue()).toString();
for (int i = 0; i < 100; i++) {
// ensure the transaction object itself was not modified; if it was, the hash will change
assertEquals(txHash, tx.getHashAsString());
new Thread(){
public void run() {
assertEquals(txNormalizedHash, tx.hashForSignature(0, new byte[0], (byte) (Transaction.SigHash.ALL.ordinal() + 1)).toString());
assertEquals(txNormalizedHash, tx.hashForSignature(0, new byte[0], Transaction.SigHash.ALL.byteValue()).toString());
}
};
}

View File

@@ -513,7 +513,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
byte[] refundSigCopy = Arrays.copyOf(refundSig, refundSig.length);
refundSigCopy[refundSigCopy.length - 1] = (byte) (Transaction.SigHash.NONE.ordinal() + 1);
refundSigCopy[refundSigCopy.length - 1] = Transaction.SigHash.NONE.byteValue();
try {
clientV1State().provideRefundSignature(refundSigCopy, null);
fail();
@@ -627,7 +627,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
totalPayment = totalPayment.add(size);
byte[] signatureCopy = Arrays.copyOf(signature, signature.length);
signatureCopy[signatureCopy.length - 1] = (byte) ((Transaction.SigHash.NONE.ordinal() + 1) | 0x80);
signatureCopy[signatureCopy.length - 1] = Transaction.SigHash.ANYONECANPAY_NONE.byteValue();
try {
serverState.incrementPayment(halfCoin.subtract(totalPayment), signatureCopy);
fail();
@@ -659,7 +659,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
assertEquals(totalPayment, halfCoin);
signatureCopy = Arrays.copyOf(signature, signature.length);
signatureCopy[signatureCopy.length - 1] = (byte) ((Transaction.SigHash.SINGLE.ordinal() + 1) | 0x80);
signatureCopy[signatureCopy.length - 1] = Transaction.SigHash.ANYONECANPAY_SINGLE.byteValue();
try {
serverState.incrementPayment(halfCoin.subtract(totalPayment), signatureCopy);
fail();