mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-12 10:15:52 +00:00
Refactor some code in ECKey into ECKey.ECDSASignature, and introduce a new TransactionSignature class.
It extends ECDSASignature and adds support for holding and working with sighash flags. Make some code use it instead of raw byte handling.
This commit is contained in:
parent
2149fea7e6
commit
57c1ecbd01
@ -309,31 +309,57 @@ public class ECKey implements Serializable {
|
||||
* components can be useful for doing further EC maths on them.
|
||||
*/
|
||||
public static class ECDSASignature {
|
||||
/** The two components of the signature. */
|
||||
public BigInteger r, s;
|
||||
|
||||
/** Constructs a signature with the given components. */
|
||||
public ECDSASignature(BigInteger r, BigInteger s) {
|
||||
this.r = r;
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
/**
|
||||
* What we get back from the signer are the two components of a signature, r and s. To get a flat byte stream
|
||||
* of the type used by Bitcoin we have to encode them using DER encoding, which is just a way to pack the two
|
||||
* components into a structure.
|
||||
* DER is an international standard for serializing data structures which is widely used in cryptography.
|
||||
* It's somewhat like protocol buffers but less convenient. This method returns a standard DER encoding
|
||||
* of the signature, as recognized by OpenSSL and other libraries.
|
||||
*/
|
||||
public byte[] encodeToDER() {
|
||||
try {
|
||||
// Usually 70-72 bytes.
|
||||
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(72);
|
||||
DERSequenceGenerator seq = new DERSequenceGenerator(bos);
|
||||
seq.addObject(new DERInteger(r));
|
||||
seq.addObject(new DERInteger(s));
|
||||
seq.close();
|
||||
return bos.toByteArray();
|
||||
return derByteStream().toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // Cannot happen.
|
||||
}
|
||||
}
|
||||
|
||||
public static ECDSASignature decodeFromDER(byte[] bytes) {
|
||||
try {
|
||||
ASN1InputStream decoder = new ASN1InputStream(bytes);
|
||||
DLSequence seq = (DLSequence) decoder.readObject();
|
||||
DERInteger r, s;
|
||||
try {
|
||||
r = (DERInteger) seq.getObjectAt(0);
|
||||
s = (DERInteger) seq.getObjectAt(1);
|
||||
} catch (ClassCastException e) {
|
||||
return null;
|
||||
}
|
||||
decoder.close();
|
||||
// OpenSSL deviates from the DER spec by interpreting these values as unsigned, though they should not be
|
||||
// Thus, we always use the positive versions. See: http://r6.ca/blog/20111119T211504Z.html
|
||||
return new ECDSASignature(r.getPositiveValue(), s.getPositiveValue());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected ByteArrayOutputStream derByteStream() throws IOException {
|
||||
// Usually 70-72 bytes.
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(72);
|
||||
DERSequenceGenerator seq = new DERSequenceGenerator(bos);
|
||||
seq.addObject(new DERInteger(r));
|
||||
seq.addObject(new DERInteger(s));
|
||||
seq.close();
|
||||
return bos;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -428,25 +454,7 @@ public class ECKey implements Serializable {
|
||||
public static boolean verify(byte[] data, byte[] signature, byte[] pub) {
|
||||
if (NativeSecp256k1.enabled)
|
||||
return NativeSecp256k1.verify(data, signature, pub);
|
||||
|
||||
try {
|
||||
ASN1InputStream decoder = new ASN1InputStream(signature);
|
||||
DLSequence seq = (DLSequence) decoder.readObject();
|
||||
DERInteger r, s;
|
||||
try {
|
||||
r = (DERInteger) seq.getObjectAt(0);
|
||||
s = (DERInteger) seq.getObjectAt(1);
|
||||
} catch (ClassCastException e) {
|
||||
return false; // An invalid signature can cause this
|
||||
}
|
||||
decoder.close();
|
||||
// OpenSSL deviates from the DER spec by interpreting these values as unsigned, though they should not be
|
||||
// Thus, we always use the positive versions.
|
||||
// See: http://r6.ca/blog/20111119T211504Z.html
|
||||
return verify(data, new ECDSASignature(r.getPositiveValue(), s.getPositiveValue()), pub);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return verify(data, ECDSASignature.decodeFromDER(signature), pub);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,6 +17,7 @@
|
||||
package com.google.bitcoin.core;
|
||||
|
||||
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
|
||||
import com.google.bitcoin.crypto.TransactionSignature;
|
||||
import com.google.bitcoin.script.Script;
|
||||
import com.google.bitcoin.script.ScriptBuilder;
|
||||
import com.google.bitcoin.script.ScriptOpCodes;
|
||||
@ -767,8 +768,7 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
// Note that each input may be claiming an output sent to a different key. So we have to look at the outputs
|
||||
// to figure out which key to sign with.
|
||||
|
||||
int[] sigHashFlags = new int[inputs.size()];
|
||||
ECKey.ECDSASignature[] signatures = new ECKey.ECDSASignature[inputs.size()];
|
||||
TransactionSignature[] signatures = new TransactionSignature[inputs.size()];
|
||||
ECKey[] signingKeys = new ECKey[inputs.size()];
|
||||
for (int i = 0; i < inputs.size(); i++) {
|
||||
TransactionInput input = inputs.get(i);
|
||||
@ -801,8 +801,7 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
|
||||
// Now calculate the signatures we need to prove we own this transaction and are authorized to claim the
|
||||
// associated money.
|
||||
signatures[i] = key.sign(hash, aesKey);
|
||||
sigHashFlags[i] = (hashType.ordinal() + 1) | (anyoneCanPay ? SIGHASH_ANYONECANPAY_VALUE : 0);
|
||||
signatures[i] = new TransactionSignature(key.sign(hash, aesKey), hashType, anyoneCanPay);
|
||||
}
|
||||
|
||||
// Now we have calculated each signature, go through and create the scripts. Reminder: the script consists:
|
||||
@ -816,9 +815,9 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
TransactionInput input = inputs.get(i);
|
||||
Script scriptPubKey = input.getOutpoint().getConnectedOutput().getScriptPubKey();
|
||||
if (scriptPubKey.isSentToAddress()) {
|
||||
input.setScriptSig(ScriptBuilder.createInputScript(signatures[i], signingKeys[i], sigHashFlags[i]));
|
||||
input.setScriptSig(ScriptBuilder.createInputScript(signatures[i], signingKeys[i]));
|
||||
} else if (scriptPubKey.isSentToRawPubKey()) {
|
||||
input.setScriptSig(ScriptBuilder.createInputScript(signatures[i], sigHashFlags[i]));
|
||||
input.setScriptSig(ScriptBuilder.createInputScript(signatures[i]));
|
||||
} else {
|
||||
// Should be unreachable - if we don't recognize the type of script we're trying to sign for, we should
|
||||
// have failed above when fetching the key to sign with.
|
||||
@ -843,7 +842,8 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
*/
|
||||
public synchronized Sha256Hash hashTransactionForSignature(int inputIndex, byte[] connectedScript,
|
||||
SigHash type, boolean anyoneCanPay) {
|
||||
return hashTransactionForSignature(inputIndex, connectedScript, (byte)((type.ordinal() + 1) | (anyoneCanPay ? SIGHASH_ANYONECANPAY_VALUE : 0x00)));
|
||||
byte sigHashType = (byte) TransactionSignature.calcSigHashValue(type, anyoneCanPay);
|
||||
return hashTransactionForSignature(inputIndex, connectedScript, sigHashType);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -860,18 +860,15 @@ public class Transaction extends ChildMessage implements Serializable {
|
||||
*/
|
||||
public synchronized Sha256Hash hashTransactionForSignature(int inputIndex, Script connectedScript,
|
||||
SigHash type, boolean anyoneCanPay) {
|
||||
return hashTransactionForSignature(inputIndex, connectedScript.getProgram(),
|
||||
(byte)((type.ordinal() + 1) | (anyoneCanPay ? SIGHASH_ANYONECANPAY_VALUE : 0x00)));
|
||||
int sigHash = TransactionSignature.calcSigHashValue(type, anyoneCanPay);
|
||||
return hashTransactionForSignature(inputIndex, connectedScript.getProgram(), (byte) sigHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is required for signatures which use a sigHashType which cannot be represented using SigHash and anyoneCanPay
|
||||
* See transaction c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73, which has sigHashType 0
|
||||
*/
|
||||
public synchronized Sha256Hash hashTransactionForSignature(int inputIndex, byte[] connectedScript,
|
||||
byte sigHashType) {
|
||||
// TODO: This whole separate method should be un-necessary if we fix how we deserialize sighash flags.
|
||||
|
||||
public synchronized Sha256Hash hashTransactionForSignature(int inputIndex, byte[] connectedScript, byte sigHashType) {
|
||||
// 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:
|
||||
//
|
||||
|
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.bitcoin.crypto;
|
||||
|
||||
import com.google.bitcoin.core.ECKey;
|
||||
import com.google.bitcoin.core.Transaction;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* A TransactionSignature wraps an {@link com.google.bitcoin.core.ECKey.ECDSASignature} and adds methods for handling
|
||||
* the additional SIGHASH mode byte that is used.
|
||||
*/
|
||||
public class TransactionSignature extends ECKey.ECDSASignature {
|
||||
/**
|
||||
* A byte that controls which parts of a transaction are signed. This is exposed because signatures
|
||||
* parsed off the wire may have sighash flags that aren't "normal" serializations of the enum values.
|
||||
* Because Satoshi's code works via bit testing, we must not lose the exact value when round-tripping
|
||||
* otherwise we'll fail to verify signature hashes.
|
||||
*/
|
||||
public int sighashFlags = Transaction.SigHash.ALL.ordinal() + 1;
|
||||
|
||||
/** Constructs a signature with the given components and SIGHASH_ALL. */
|
||||
public TransactionSignature(BigInteger r, BigInteger s) {
|
||||
super(r, s);
|
||||
}
|
||||
|
||||
/** Constructs a transaction signature based on the ECDSA signature. */
|
||||
public TransactionSignature(ECKey.ECDSASignature signature, Transaction.SigHash mode, boolean anyoneCanPay) {
|
||||
super(signature.r, signature.s);
|
||||
setSigHash(mode, anyoneCanPay);
|
||||
}
|
||||
|
||||
/** 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;
|
||||
if (anyoneCanPay)
|
||||
sighashFlags |= Transaction.SIGHASH_ANYONECANPAY_VALUE;
|
||||
return sighashFlags;
|
||||
}
|
||||
|
||||
/** Configures the sighashFlags field as appropriate. */
|
||||
public void setSigHash(Transaction.SigHash mode, boolean anyoneCanPay) {
|
||||
sighashFlags = calcSigHashValue(mode, anyoneCanPay);
|
||||
}
|
||||
|
||||
public boolean anyoneCanPay() {
|
||||
return (sighashFlags & Transaction.SIGHASH_ANYONECANPAY_VALUE) != 0;
|
||||
}
|
||||
|
||||
public Transaction.SigHash sigHashMode() {
|
||||
final int mode = sighashFlags & 0x1f;
|
||||
if (mode == Transaction.SigHash.NONE.ordinal() + 1)
|
||||
return Transaction.SigHash.NONE;
|
||||
else if (mode == Transaction.SigHash.SINGLE.ordinal() + 1)
|
||||
return Transaction.SigHash.SINGLE;
|
||||
else
|
||||
return Transaction.SigHash.ALL;
|
||||
}
|
||||
|
||||
/**
|
||||
* What we get back from the signer are the two components of a signature, r and s. To get a flat byte stream
|
||||
* of the type used by Bitcoin we have to encode them using DER encoding, which is just a way to pack the two
|
||||
* components into a structure, and then we append a byte to the end for the sighash flags.
|
||||
*/
|
||||
public byte[] encodeToBitcoin() {
|
||||
try {
|
||||
ByteArrayOutputStream bos = derByteStream();
|
||||
bos.write(sighashFlags);
|
||||
return bos.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // Cannot happen.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a decoded signature.
|
||||
* @throws RuntimeException if the signature is invalid or unparseable in some way.
|
||||
*/
|
||||
public static TransactionSignature decodeFromBitcoin(byte[] bytes) {
|
||||
// Bitcoin encoding is DER signature + sighash byte.
|
||||
ECKey.ECDSASignature sig = ECKey.ECDSASignature.decodeFromDER(bytes);
|
||||
if (sig == null)
|
||||
throw new RuntimeException("Could not DER decode signature.");
|
||||
TransactionSignature tsig = new TransactionSignature(sig.r, sig.s);
|
||||
// In Bitcoin, any value of the final byte is valid unfortunately. However it may not be "canonical".
|
||||
tsig.sighashFlags = bytes[bytes.length - 1];
|
||||
return tsig;
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
package com.google.bitcoin.script;
|
||||
|
||||
import com.google.bitcoin.core.*;
|
||||
import com.google.bitcoin.crypto.TransactionSignature;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -1042,32 +1043,31 @@ public class Script {
|
||||
if (stack.size() < 2)
|
||||
throw new ScriptException("Attempted OP_CHECKSIG(VERIFY) on a stack with size < 2");
|
||||
byte[] pubKey = stack.pollLast();
|
||||
byte[] sig = stack.pollLast();
|
||||
if (sig.length == 0 || pubKey.length == 0)
|
||||
byte[] sigBytes = stack.pollLast();
|
||||
if (sigBytes.length == 0 || pubKey.length == 0)
|
||||
throw new ScriptException("Attempted OP_CHECKSIG(VERIFY) with a sig or pubkey of length 0");
|
||||
|
||||
byte[] prog = script.getProgram();
|
||||
byte[] connectedScript = Arrays.copyOfRange(prog, lastCodeSepLocation, prog.length);
|
||||
|
||||
UnsafeByteArrayOutputStream outStream = new UnsafeByteArrayOutputStream(sig.length + 1);
|
||||
UnsafeByteArrayOutputStream outStream = new UnsafeByteArrayOutputStream(sigBytes.length + 1);
|
||||
try {
|
||||
writeBytes(outStream, sig);
|
||||
writeBytes(outStream, sigBytes);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // Cannot happen
|
||||
}
|
||||
connectedScript = removeAllInstancesOf(connectedScript, outStream.toByteArray());
|
||||
|
||||
// TODO: Use int for indexes everywhere, we can't have that many inputs/outputs
|
||||
Sha256Hash hash = txContainingThis.hashTransactionForSignature(index, connectedScript, sig[sig.length - 1]);
|
||||
|
||||
boolean sigValid;
|
||||
boolean sigValid = false;
|
||||
try {
|
||||
sigValid = ECKey.verify(hash.getBytes(), Arrays.copyOf(sig, sig.length - 1), pubKey);
|
||||
TransactionSignature sig = TransactionSignature.decodeFromBitcoin(sigBytes);
|
||||
Sha256Hash hash = txContainingThis.hashTransactionForSignature(index, connectedScript, (byte)sig.sighashFlags);
|
||||
sigValid = ECKey.verify(hash.getBytes(), sig, pubKey);
|
||||
} catch (Exception e1) {
|
||||
// There is (at least) one exception that could be hit here (EOFException, if the sig is too short)
|
||||
// Because I can't verify there aren't more, we use a very generic Exception catch
|
||||
log.warn(e1.toString());
|
||||
sigValid = false;
|
||||
}
|
||||
|
||||
if (opcode == OP_CHECKSIG)
|
||||
@ -1127,14 +1127,13 @@ public class Script {
|
||||
|
||||
boolean valid = true;
|
||||
while (sigs.size() > 0) {
|
||||
byte[] sig = sigs.getFirst();
|
||||
byte[] pubKey = pubkeys.pollFirst();
|
||||
|
||||
// We could reasonably move this out of the loop, but because signature verification is significantly
|
||||
// more expensive than hashing, its not a big deal.
|
||||
Sha256Hash hash = txContainingThis.hashTransactionForSignature(index, connectedScript, sig[sig.length - 1]);
|
||||
try {
|
||||
if (ECKey.verify(hash.getBytes(), Arrays.copyOf(sig, sig.length - 1), pubKey))
|
||||
TransactionSignature sig = TransactionSignature.decodeFromBitcoin(sigs.getFirst());
|
||||
Sha256Hash hash = txContainingThis.hashTransactionForSignature(index, connectedScript, (byte)sig.sighashFlags);
|
||||
if (ECKey.verify(hash.getBytes(), sig, pubKey))
|
||||
sigs.pollFirst();
|
||||
} catch (Exception e) {
|
||||
// There is (at least) one exception that could be hit here (EOFException, if the sig is too short)
|
||||
|
@ -18,10 +18,10 @@ package com.google.bitcoin.script;
|
||||
|
||||
import com.google.bitcoin.core.Address;
|
||||
import com.google.bitcoin.core.ECKey;
|
||||
import com.google.bitcoin.core.Transaction;
|
||||
import com.google.bitcoin.crypto.TransactionSignature;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.bitcoin.core.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@ -80,16 +80,14 @@ public class ScriptBuilder {
|
||||
}
|
||||
|
||||
/** Creates a scriptSig that can redeem a pay-to-address output. */
|
||||
public static Script createInputScript(ECKey.ECDSASignature signature, ECKey pubKey, int sigHashFlags) {
|
||||
public static Script createInputScript(TransactionSignature signature, ECKey pubKey) {
|
||||
byte[] pubkeyBytes = pubKey.getPubKey();
|
||||
byte[] sigBytes = Utils.appendByte(signature.encodeToDER(), (byte) sigHashFlags);
|
||||
return new ScriptBuilder().data(sigBytes).data(pubkeyBytes).build();
|
||||
return new ScriptBuilder().data(signature.encodeToBitcoin()).data(pubkeyBytes).build();
|
||||
}
|
||||
|
||||
/** Creates a scriptSig that can redeem a pay-to-pubkey output. */
|
||||
public static Script createInputScript(ECKey.ECDSASignature signature, int sigHashFlags) {
|
||||
byte[] sigBytes = Utils.appendByte(signature.encodeToDER(), (byte) sigHashFlags);
|
||||
return new ScriptBuilder().data(sigBytes).build();
|
||||
public static Script createInputScript(TransactionSignature signature) {
|
||||
return new ScriptBuilder().data(signature.encodeToBitcoin()).build();
|
||||
}
|
||||
|
||||
/** Creates a program that requires at least N of the given keys to sign, using OP_CHECKMULTISIG. */
|
||||
@ -98,28 +96,25 @@ public class ScriptBuilder {
|
||||
checkArgument(threshold <= pubkeys.size());
|
||||
checkArgument(pubkeys.size() <= 16); // That's the max we can represent with a single opcode.
|
||||
ScriptBuilder builder = new ScriptBuilder();
|
||||
builder.smallNum((byte) threshold);
|
||||
builder.smallNum(threshold);
|
||||
for (ECKey key : pubkeys) {
|
||||
builder.data(key.getPubKey());
|
||||
}
|
||||
builder.smallNum((byte)pubkeys.size());
|
||||
builder.smallNum(pubkeys.size());
|
||||
builder.op(OP_CHECKMULTISIG);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** Create a program that satisfies an OP_CHECKMULTISIG program. */
|
||||
public static Script createMultiSigInputScript(List<ECKey.ECDSASignature> signatures,
|
||||
Transaction.SigHash sigHash, boolean anyoneCanPay) {
|
||||
checkArgument(signatures.size() <= 16); // Max allowable.
|
||||
ScriptBuilder builder = new ScriptBuilder();
|
||||
builder.smallNum(0); // Work around a bug in CHECKMULTISIG that is now a required part of the protocol.
|
||||
for (ECKey.ECDSASignature signature : signatures)
|
||||
builder.data(Utils.appendByte(signature.encodeToDER(),(byte) ((sigHash.ordinal() + 1) | (anyoneCanPay ? 0x80 : 0))));
|
||||
return builder.build();
|
||||
public static Script createMultiSigInputScript(List<TransactionSignature> signatures) {
|
||||
List<byte[]> sigs = new ArrayList<byte[]>(signatures.size());
|
||||
for (TransactionSignature signature : signatures)
|
||||
sigs.add(signature.encodeToBitcoin());
|
||||
return createMultiSigInputScriptBytes(sigs);
|
||||
}
|
||||
|
||||
/** Create a program that satisfies an OP_CHECKMULTISIG program, for when the signatures may not have the same SIGHASH/anyoneCanPay flags */
|
||||
public static Script createMultiSigInputScript(List<byte[]> signatures) {
|
||||
/** Create a program that satisfies an OP_CHECKMULTISIG program, using pre-encoded signatures. */
|
||||
public static Script createMultiSigInputScriptBytes(List<byte[]> signatures) {
|
||||
checkArgument(signatures.size() <= 16);
|
||||
ScriptBuilder builder = new ScriptBuilder();
|
||||
builder.smallNum(0); // Work around a bug in CHECKMULTISIG that is now a required part of the protocol.
|
||||
|
Loading…
x
Reference in New Issue
Block a user