From ae6b2fbd4d961b46b6a283fe7aa744601a6c946d Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Sun, 6 Jan 2013 22:07:55 +0100 Subject: [PATCH] Improvements to the ECKey class: - Implement ECDSA key recovery. - Use it to implement sign/verifymessage functionality that's compatible with Bitcoin-Qt. - Redesign the signing APIs a bit. --- .../java/com/google/bitcoin/core/ECKey.java | 258 ++++++++++++++++-- .../com/google/bitcoin/core/Transaction.java | 2 +- .../java/com/google/bitcoin/core/Utils.java | 28 +- .../com/google/bitcoin/core/ECKeyTest.java | 66 ++++- .../bitcoin/core/FullBlockTestGenerator.java | 6 +- .../core/FullPrunedBlockChainTest.java | 16 +- 6 files changed, 333 insertions(+), 43 deletions(-) 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 dab865b9..fdf538f7 100644 --- a/core/src/main/java/com/google/bitcoin/core/ECKey.java +++ b/core/src/main/java/com/google/bitcoin/core/ECKey.java @@ -16,6 +16,7 @@ package com.google.bitcoin.core; +import com.google.common.base.Preconditions; import org.spongycastle.asn1.*; import org.spongycastle.asn1.sec.SECNamedCurves; import org.spongycastle.asn1.x9.X9ECParameters; @@ -26,23 +27,30 @@ import org.spongycastle.crypto.params.ECKeyGenerationParameters; import org.spongycastle.crypto.params.ECPrivateKeyParameters; import org.spongycastle.crypto.params.ECPublicKeyParameters; import org.spongycastle.crypto.signers.ECDSASigner; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECFieldElement; +import org.spongycastle.math.ec.ECPoint; +import org.spongycastle.util.encoders.Base64; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.Serializable; import java.math.BigInteger; +import java.nio.charset.Charset; import java.security.SecureRandom; +import java.security.SignatureException; import java.util.Arrays; import static com.google.common.base.Preconditions.checkArgument; // TODO: This class is quite a mess by now. Once users are migrated away from Java serialization for the wallets, // refactor this to have better internal layout and a more consistent API. +// TODO: Properly/completely support compressed pubkeys and use them by default. /** * Represents an elliptic curve keypair that we own and can use for signing transactions. Currently, * Bouncy Castle is used. In future this may become an interface with multiple implementations using different crypto - * libraries. The class also provides a static method that can verify a signature with just the public key.

+ * libraries. The class also provides various methods that can be used to sign and verify different kinds of messages. */ public class ECKey implements Serializable { private static final ECDomainParameters ecParams; @@ -182,6 +190,13 @@ public class ECKey implements Serializable { return pub; } + /** + * Returns whether this key is using the compressed form or not. Compressed pubkeys are only 35 bytes, not 64. + */ + public boolean isCompressed() { + return pub.length == 35; + } + public String toString() { StringBuffer b = new StringBuffer(); b.append("pub:").append(Utils.bytesToHexString(pub)); @@ -210,31 +225,52 @@ public class ECKey implements Serializable { } /** - * Calcuates an ECDSA signature in DER format for the given input hash. Note that the input is expected to be - * 32 bytes long. - * @throws IllegalStateException if this ECKey has only a public key. + * Just groups the two components that make up a signature, and provides a way to encode to DER form, which is + * how ECDSA signatures are represented when embedded in other data structures in the Bitcoin protocol. The raw + * components can be useful for doing further EC maths on them. */ - public byte[] sign(byte[] input) { + public static class ECDSASignature { + public BigInteger r, s; + + 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. + */ + 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(); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + } + + /** + * Signs the given hash and returns the R and S components as BigIntegers. In the Bitcoin protocol, they are + * usually encoded using DER format, so you want {@link ECKey#signToDER(Sha256Hash)} instead. However sometimes + * the independent components can be useful, for instance, if you're doing to do further EC maths on them. + * @throws IllegalStateException if this ECKey doesn't have a private part. + */ + public ECDSASignature sign(Sha256Hash input) { if (priv == null) throw new IllegalStateException("This ECKey does not have the private key necessary for signing."); ECDSASigner signer = new ECDSASigner(); ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(priv, ecParams); signer.init(true, privKey); - BigInteger[] sigs = signer.generateSignature(input); - // 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. - try { - // Usually 70-72 bytes. - ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(72); - DERSequenceGenerator seq = new DERSequenceGenerator(bos); - seq.addObject(new DERInteger(sigs[0])); - seq.addObject(new DERInteger(sigs[1])); - seq.close(); - return bos.toByteArray(); - } catch (IOException e) { - throw new RuntimeException(e); // Cannot happen. - } + BigInteger[] sigs = signer.generateSignature(input.getBytes()); + return new ECDSASignature(sigs[0], sigs[1]); } /** @@ -300,6 +336,188 @@ public class ECKey implements Serializable { } } + /** + * Signs a text message using the standard Bitcoin messaging signing format and returns the signature as a base64 + * encoded string. + * + * @throws IllegalStateException if this ECKey does not have the private part. + */ + public String signMessage(String message) { + if (priv == null) + throw new IllegalStateException("This ECKey does not have the private key necessary for signing."); + byte[] data = Utils.formatMessageForSigning(message); + Sha256Hash hash = Sha256Hash.createDouble(data); + ECDSASignature sig = sign(hash); + // Now we have to work backwards to figure out the recId needed to recover the signature. + int recId = -1; + for (int i = 0; i < 4; i++) { + ECKey k = ECKey.recoverFromSignature(i, sig, hash, isCompressed()); + if (k != null && Arrays.equals(k.pub, pub)) { + recId = i; + break; + } + } + if (recId == -1) + throw new RuntimeException("Could not construct a recoverable key. This should never happen."); + int headerByte = recId + 27 + (isCompressed() ? 4 : 0); + byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S + sigData[0] = (byte)headerByte; + System.arraycopy(Utils.bigIntegerToBytes(sig.r, 32), 0, sigData, 1, 32); + System.arraycopy(Utils.bigIntegerToBytes(sig.s, 32), 0, sigData, 33, 32); + return new String(Base64.encode(sigData), Charset.forName("UTF-8")); + } + + /** + * Given an arbitrary piece of text and a Bitcoin-format message signature encoded in base64, returns an ECKey + * containing the public key that was used to sign it. This can then be compared to the expected public key to + * determine if the signature was correct. These sorts of signatures are compatible with the Bitcoin-Qt/bitcoind + * format generated by signmessage/verifymessage RPCs and GUI menu options. They are intended for humans to verify + * their communications with each other, hence the base64 format and the fact that the input is text. + * + * @param message Some piece of human readable text. + * @param signatureBase64 The Bitcoin-format message signature in base64 + * @throws SignatureException If the public key could not be recovered or if there was a signature format error. + */ + public static ECKey signedMessageToKey(String message, String signatureBase64) throws SignatureException { + byte[] signatureEncoded; + try { + signatureEncoded = Base64.decode(signatureBase64); + } catch (RuntimeException e) { + // This is what you get back from Bouncy Castle if base64 doesn't decode :( + throw new SignatureException("Could not decode base64", e); + } + // Parse the signature bytes into r/s and the selector value. + if (signatureEncoded.length < 65) + throw new SignatureException("Signature truncated, expected 65 bytes and got " + signatureEncoded.length); + int header = signatureEncoded[0] & 0xFF; + // The header byte: 0x1B = first key with even y, 0x1C = first key with odd y, + // 0x1D = second key with even y, 0x1E = second key with odd y + if (header < 27 || header > 34) + throw new SignatureException("Header byte out of range: " + header); + BigInteger r = new BigInteger(1, Arrays.copyOfRange(signatureEncoded, 1, 33)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(signatureEncoded, 33, 65)); + ECDSASignature sig = new ECDSASignature(r, s); + byte[] messageBytes = Utils.formatMessageForSigning(message); + // Note that the C++ code doesn't actually seem to specify any character encoding. Presumably it's whatever + // JSON-SPIRIT hands back. Assume UTF-8 for now. + Sha256Hash messageHash = Sha256Hash.createDouble(messageBytes); + boolean compressed = false; + if (header >= 31) { + compressed = true; + header -= 4; + } + int recId = header - 27; + ECKey key = ECKey.recoverFromSignature(recId, sig, messageHash, compressed); + if (key == null) + throw new SignatureException("Could not recover public key from signature"); + return key; + } + + /** + * Convenience wrapper around {@link ECKey#signedMessageToKey(String, String)}. If the key derived from the + * signature is not the same as this one, throws a SignatureException. + */ + public void verifyMessage(String message, String signatureBase64) throws SignatureException { + ECKey key = ECKey.signedMessageToKey(message, signatureBase64); + if (!Arrays.equals(key.getPubKey(), pub)) + throw new SignatureException("Signature did not match for message"); + } + + /** + *

Given the components of a signature and a selector value, recover and return the public key + * that generated the signature according to the algorithm in SEC1v2 section 4.1.6.

+ * + *

The recId is an index from 0 to 3 which indicates which of the 4 possible keys is the correct one. Because + * the key recovery operation yields multiple potential keys, the correct key must either be stored alongside the + * signature, or you must be willing to try each recId in turn until you find one that outputs the key you are + * expecting.

+ * + *

If this method returns null it means recovery was not possible and recId should be iterated.

+ * + *

Given the above two points, a correct usage of this method is inside a for loop from 0 to 3, and if the + * output is null OR a key that is not the one you expect, you try again with the next recId.

+ * + * @param recId Which possible key to recover. + * @param r The R component of the signature. + * @param s The S component of the signature. + * @param message Hash of the data that was signed. + * @param compressed Whether or not the original pubkey was compressed. + * @return An ECKey containing only the public part, or null if recovery wasn't possible. + */ + public static ECKey recoverFromSignature(int recId, ECDSASignature sig, Sha256Hash message, boolean compressed) { + Preconditions.checkArgument(recId >= 0, "recId must be positive"); + Preconditions.checkArgument(sig.r.compareTo(BigInteger.ZERO) >= 0, "r must be positive"); + Preconditions.checkArgument(sig.s.compareTo(BigInteger.ZERO) >= 0, "s must be positive"); + Preconditions.checkNotNull(message); + // 1.0 For j from 0 to h (h == recId here and the loop is outside this function) + // 1.1 Let x = r + jn + BigInteger n = ecParams.getN(); // Curve order. + BigInteger i = BigInteger.valueOf((long) recId / 2); + BigInteger x = sig.r.add(i.multiply(n)); + // 1.2. Convert the integer x to an octet string X of length mlen using the conversion routine + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉. + // 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R using the + // conversion routine specified in Section 2.3.4. If this conversion routine outputs “invalid”, then + // do another iteration of Step 1. + // + // More concisely, what these points mean is to use X as a compressed public key. + ECCurve.Fp curve = (ECCurve.Fp) ecParams.getCurve(); + BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent about the letter it uses for the prime. + if (x.compareTo(prime) >= 0) { + // Cannot have point co-ordinates larger than this as everything takes place modulo Q. + return null; + } + // Compressed keys require you to know an extra bit of data about the y-coord as there are two possibilities. + // So it's encoded in the recId. + ECPoint R = decompressKey(x, recId % 2 == 1); + // 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers responsibility). + if (!R.multiply(n).isInfinity()) + return null; + // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification. + BigInteger e = message.toBigInteger(); + // 1.6. For k from 1 to 2 do the following. (loop is outside this function via iterating recId) + // 1.6.1. Compute a candidate public key as: + // Q = mi(r) * (sR - eG) + // + // Where mi(x) is the modular multiplicative inverse. We transform this into the following: + // Q = (mi(r) * s * R) + (mi(r) * -e * G) + // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n), and + is the EC + // group operator. + // + // We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive + // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8. + BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); + BigInteger rInv = sig.r.modInverse(n); + BigInteger srInv = rInv.multiply(sig.s).mod(n); + BigInteger eInvrInv = rInv.multiply(eInv).mod(n); + ECPoint p1 = ecParams.getG().multiply(eInvrInv); + ECPoint p2 = R.multiply(srInv); + ECPoint.Fp q = (ECPoint.Fp) p2.add(p1); + if (compressed) { + // We have to manually recompress the point as the compressed-ness gets lost when multiply() is used. + q = new ECPoint.Fp(curve, q.getX(), q.getY(), true); + } + return new ECKey((byte[])null, q.getEncoded()); + } + + /** Decompress a compressed public key (x co-ord and low-bit of y-coord). */ + private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { + // This code is adapted from Bouncy Castle ECCurve.Fp.decodePoint(), but it wasn't easily re-used. + ECCurve.Fp curve = (ECCurve.Fp) ecParams.getCurve(); + ECFieldElement x = new ECFieldElement.Fp(curve.getQ(), xBN); + ECFieldElement alpha = x.multiply(x.square().add(curve.getA())).add(curve.getB()); + ECFieldElement beta = alpha.sqrt(); + // If we can't find a sqrt we haven't got a point on the curve - invalid inputs. + if (beta == null) + throw new IllegalArgumentException("Invalid point compression"); + if (beta.toBigInteger().testBit(0) == yBit) { + return new ECPoint.Fp(curve, x, beta, true); + } else { + ECFieldElement.Fp y = new ECFieldElement.Fp(curve.getQ(), curve.getQ().subtract(beta.toBigInteger())); + return new ECPoint.Fp(curve, x, y, true); + } + } + /** * Returns a 32 byte array containing the private key. */ 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 5662b300..a13c6b9d 100644 --- a/core/src/main/java/com/google/bitcoin/core/Transaction.java +++ b/core/src/main/java/com/google/bitcoin/core/Transaction.java @@ -711,7 +711,7 @@ public class Transaction extends ChildMessage implements Serializable { try { // Usually 71-73 bytes. ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73); - bos.write(key.sign(hash.getBytes())); + bos.write(key.sign(hash).encodeToDER()); bos.write((hashType.ordinal() + 1) | (anyoneCanPay ? 0x80 : 0)); signatures[i] = bos.toByteArray(); bos.close(); diff --git a/core/src/main/java/com/google/bitcoin/core/Utils.java b/core/src/main/java/com/google/bitcoin/core/Utils.java index 0ac6274b..dffb199c 100644 --- a/core/src/main/java/com/google/bitcoin/core/Utils.java +++ b/core/src/main/java/com/google/bitcoin/core/Utils.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.OutputStream; import java.math.BigDecimal; import java.math.BigInteger; +import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Date; @@ -33,6 +34,9 @@ import static com.google.common.base.Preconditions.checkArgument; * To enable debug logging from the library, run with -Dbitcoinj.logging=true on your command line. */ public class Utils { + /** The string that prefixes all text messages signed using Bitcoin keys. */ + public static final String BITCOIN_SIGNED_MESSAGE_HEADER = "Bitcoin Signed Message:\n"; + // TODO: Replace this nanocoins business with something better. /** @@ -149,7 +153,7 @@ public class Utils { /** * Calculates the SHA-256 hash of the given byte range, and then hashes the resulting hash again. This is - * standard procedure in BitCoin. The resulting hash is in big endian form. + * standard procedure in Bitcoin. The resulting hash is in big endian form. */ public static byte[] doubleDigest(byte[] input, int offset, int length) { try { @@ -448,4 +452,26 @@ public class Utils { public static boolean isWindows() { return System.getProperty("os.name").toLowerCase().indexOf("win") >= 0; } + + /** + *

Given a textual message, returns a byte buffer formatted as follows:

+ * + *

[24] "Bitcoin Signed Message:\n" [message.length as a varint] message

+ */ + public static byte[] formatMessageForSigning(String message) { + VarInt size = new VarInt(message.length()); + int totalSize = 1 + BITCOIN_SIGNED_MESSAGE_HEADER.length() + size.getSizeInBytes() + message.length(); + byte[] result = new byte[totalSize]; + int cursor = 0; + result[cursor++] = (byte) BITCOIN_SIGNED_MESSAGE_HEADER.length(); + byte[] bytes = BITCOIN_SIGNED_MESSAGE_HEADER.getBytes(Charset.forName("UTF-8")); + System.arraycopy(bytes, 0, result, cursor, bytes.length); + cursor += bytes.length; + bytes = size.encode(); + System.arraycopy(bytes, 0, result, cursor, bytes.length); + cursor += bytes.length; + bytes = message.getBytes(Charset.forName("UTF-8")); + System.arraycopy(bytes, 0, result, cursor, bytes.length); + return result; + } } diff --git a/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java b/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java index 1a9fbbef..f4a6078c 100644 --- a/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java +++ b/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java @@ -20,6 +20,7 @@ import org.junit.Test; import org.spongycastle.util.encoders.Hex; import java.math.BigInteger; +import java.security.SignatureException; import static com.google.bitcoin.core.Utils.reverseBytes; import static org.junit.Assert.*; @@ -31,14 +32,13 @@ public class ECKeyTest { // a message with it. BigInteger privkey = new BigInteger(1, Hex.decode("180cb41c7c600be951b5d3d0a7334acc7506173875834f7a6c4c786a28fcbb19")); ECKey key = new ECKey(privkey); - byte[] message = new byte[32]; // All zeroes. - byte[] output = key.sign(message); - assertTrue(key.verify(message, output)); + byte[] output = key.sign(Sha256Hash.ZERO_HASH).encodeToDER(); + assertTrue(key.verify(Sha256Hash.ZERO_HASH.getBytes(), output)); // Test interop with a signature from elsewhere. byte[] sig = Hex.decode( "3046022100dffbc26774fc841bbe1c1362fd643609c6e42dcb274763476d87af2c0597e89e022100c59e3c13b96b316cae9fa0ab0260612c7a133a6fe2b3445b6bf80b3123bf274d"); - assertTrue(key.verify(message, sig)); + assertTrue(key.verify(Sha256Hash.ZERO_HASH.getBytes(), sig)); } @Test @@ -56,7 +56,7 @@ public class ECKeyTest { for (ECKey key : new ECKey[] {decodedKey, roundtripKey}) { byte[] message = reverseBytes(Hex.decode( "11da3761e86431e4a54c176789e41f1651b324d240d599a7067bee23d328ec2a")); - byte[] output = key.sign(message); + byte[] output = key.sign(new Sha256Hash(message)).encodeToDER(); assertTrue(key.verify(message, output)); output = Hex.decode( @@ -67,8 +67,8 @@ public class ECKeyTest { // Try to sign with one key and verify with the other. byte[] message = reverseBytes(Hex.decode( "11da3761e86431e4a54c176789e41f1651b324d240d599a7067bee23d328ec2a")); - assertTrue(roundtripKey.verify(message, decodedKey.sign(message))); - assertTrue(decodedKey.verify(message, roundtripKey.sign(message))); + assertTrue(roundtripKey.verify(message, decodedKey.sign(new Sha256Hash(message)).encodeToDER())); + assertTrue(decodedKey.verify(message, roundtripKey.sign(new Sha256Hash(message)).encodeToDER())); } @Test @@ -85,7 +85,7 @@ public class ECKeyTest { for (ECKey key : new ECKey[] {decodedKey, roundtripKey}) { byte[] message = reverseBytes(Hex.decode( "11da3761e86431e4a54c176789e41f1651b324d240d599a7067bee23d328ec2a")); - byte[] output = key.sign(message); + byte[] output = key.sign(new Sha256Hash(message)).encodeToDER(); assertTrue(key.verify(message, output)); output = Hex.decode( @@ -96,8 +96,8 @@ public class ECKeyTest { // Try to sign with one key and verify with the other. byte[] message = reverseBytes(Hex.decode( "11da3761e86431e4a54c176789e41f1651b324d240d599a7067bee23d328ec2a")); - assertTrue(roundtripKey.verify(message, decodedKey.sign(message))); - assertTrue(decodedKey.verify(message, roundtripKey.sign(message))); + assertTrue(roundtripKey.verify(message, decodedKey.sign(new Sha256Hash(message)).encodeToDER())); + assertTrue(decodedKey.verify(message, roundtripKey.sign(new Sha256Hash(message)).encodeToDER())); } @Test @@ -128,4 +128,50 @@ public class ECKeyTest { Utils.bytesToHexString(key1.getPrivKeyBytes())); } } + + @Test + public void signTextMessage() throws Exception { + ECKey key = new ECKey(); + String message = "Hello World!"; + String signatureBase64 = key.signMessage(message); + System.out.println("Message signed with " + key.toAddress(NetworkParameters.prodNet()) + ": " + signatureBase64); + // Should verify correctly. + key.verifyMessage(message, signatureBase64); + try { + key.verifyMessage("Evil attacker says hello!", signatureBase64); + fail(); + } catch (SignatureException e) { + // OK. + } + } + + @Test + public void verifyMessage() throws Exception { + // Test vector generated by Bitcoin-Qt. + String message = "hello"; + String sigBase64 = "HxNZdo6ggZ41hd3mM3gfJRqOQPZYcO8z8qdX2BwmpbF11CaOQV+QiZGGQxaYOncKoNW61oRuSMMF8udfK54XqI8="; + Address expectedAddress = new Address(NetworkParameters.prodNet(), "14YPSNPi6NSXnUxtPAsyJSuw3pv7AU3Cag"); + ECKey key = ECKey.signedMessageToKey(message, sigBase64); + Address gotAddress = key.toAddress(NetworkParameters.prodNet()); + assertEquals(expectedAddress, gotAddress); + } + + @Test + public void keyRecovery() throws Exception { + ECKey key = new ECKey(); + String message = "Hello World!"; + Sha256Hash hash = Sha256Hash.create(message.getBytes()); + ECKey.ECDSASignature sig = key.sign(hash); + key = new ECKey(null, key.getPubKey()); + boolean found = false; + for (int i = 0; i < 4; i++) { + ECKey key2 = ECKey.recoverFromSignature(i, sig, hash, false); + assertNotNull("Key recovery did not work", key2); + if (key.equals(key2)) { + found = true; + break; + } + } + assertTrue(found); + } } diff --git a/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java b/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java index 0096bedb..cf866f40 100644 --- a/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java +++ b/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java @@ -669,7 +669,7 @@ public class FullBlockTestGenerator { // Sign input try { ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73); - bos.write(coinbaseOutKey.sign(hash.getBytes())); + bos.write(coinbaseOutKey.sign(hash).encodeToDER()); bos.write(SigHash.SINGLE.ordinal() + 1); byte[] signature = bos.toByteArray(); @@ -740,7 +740,7 @@ public class FullBlockTestGenerator { try { ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream( 73); - bos.write(coinbaseOutKey.sign(hash.getBytes())); + bos.write(coinbaseOutKey.sign(hash).encodeToDER()); bos.write(SigHash.SINGLE.ordinal() + 1); byte[] signature = bos.toByteArray(); @@ -1274,7 +1274,7 @@ public class FullBlockTestGenerator { // Sign input try { ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73); - bos.write(coinbaseOutKey.sign(hash.getBytes())); + bos.write(coinbaseOutKey.sign(hash).encodeToDER()); bos.write(SigHash.ALL.ordinal() + 1); byte[] signature = bos.toByteArray(); diff --git a/core/src/test/java/com/google/bitcoin/core/FullPrunedBlockChainTest.java b/core/src/test/java/com/google/bitcoin/core/FullPrunedBlockChainTest.java index 79a5abc5..2e0ea7ed 100644 --- a/core/src/test/java/com/google/bitcoin/core/FullPrunedBlockChainTest.java +++ b/core/src/test/java/com/google/bitcoin/core/FullPrunedBlockChainTest.java @@ -17,22 +17,22 @@ package com.google.bitcoin.core; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.List; - import com.google.bitcoin.core.Transaction.SigHash; import com.google.bitcoin.store.FullPrunedBlockStore; import com.google.bitcoin.store.MemoryFullPrunedBlockStore; import com.google.bitcoin.utils.BriefLogFormatter; - import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.junit.Assert.*; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.List; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * We don't do any wallet tests here, we leave that to {@link ChainSplitTest} @@ -148,7 +148,7 @@ public class FullPrunedBlockChainTest { // Sign input try { ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73); - bos.write(sigKey.sign(hash.getBytes())); + bos.write(sigKey.sign(hash).encodeToDER()); bos.write(SigHash.ALL.ordinal() + 1); byte[] signature = bos.toByteArray();