diff --git a/core/src/main/java/com/google/bitcoin/core/ECKey.java b/core/src/main/java/com/google/bitcoin/core/ECKey.java index 5bab0b4f..8fe17944 100644 --- a/core/src/main/java/com/google/bitcoin/core/ECKey.java +++ b/core/src/main/java/com/google/bitcoin/core/ECKey.java @@ -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); } /** diff --git a/core/src/main/java/com/google/bitcoin/core/Transaction.java b/core/src/main/java/com/google/bitcoin/core/Transaction.java index 71ab1f20..1b22a711 100644 --- a/core/src/main/java/com/google/bitcoin/core/Transaction.java +++ b/core/src/main/java/com/google/bitcoin/core/Transaction.java @@ -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: // diff --git a/core/src/main/java/com/google/bitcoin/crypto/TransactionSignature.java b/core/src/main/java/com/google/bitcoin/crypto/TransactionSignature.java new file mode 100644 index 00000000..5a45c0ba --- /dev/null +++ b/core/src/main/java/com/google/bitcoin/crypto/TransactionSignature.java @@ -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; + } +} diff --git a/core/src/main/java/com/google/bitcoin/script/Script.java b/core/src/main/java/com/google/bitcoin/script/Script.java index 7f82c693..ca508446 100644 --- a/core/src/main/java/com/google/bitcoin/script/Script.java +++ b/core/src/main/java/com/google/bitcoin/script/Script.java @@ -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) diff --git a/core/src/main/java/com/google/bitcoin/script/ScriptBuilder.java b/core/src/main/java/com/google/bitcoin/script/ScriptBuilder.java index 6b6fe836..84350c76 100644 --- a/core/src/main/java/com/google/bitcoin/script/ScriptBuilder.java +++ b/core/src/main/java/com/google/bitcoin/script/ScriptBuilder.java @@ -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 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 signatures) { + List sigs = new ArrayList(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 signatures) { + /** Create a program that satisfies an OP_CHECKMULTISIG program, using pre-encoded signatures. */ + public static Script createMultiSigInputScriptBytes(List 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.