From e397928ec3fdba977dea9a4b02e9e6b14d757f84 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 17 Mar 2014 17:42:56 +0100 Subject: [PATCH] ECKey: preserve compression state when deserializing from ASN.1. Resolves issue 528. --- .../java/com/google/bitcoin/core/ECKey.java | 17 +++++++++++++---- .../java/com/google/bitcoin/core/ECKeyTest.java | 10 +++++++--- 2 files changed, 20 insertions(+), 7 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 d339d99e..196cb313 100644 --- a/core/src/main/java/com/google/bitcoin/core/ECKey.java +++ b/core/src/main/java/com/google/bitcoin/core/ECKey.java @@ -143,7 +143,7 @@ public class ECKey implements Serializable { * reference implementation in its wallet. Note that this is slow because it requires an EC point multiply. */ public static ECKey fromASN1(byte[] asn1privkey) { - return new ECKey(extractPrivateKeyFromASN1(asn1privkey)); + return extractKeyFromASN1(asn1privkey); } /** Creates an ECKey given the private key only. The public key is calculated from it (this is slow) */ @@ -559,7 +559,7 @@ public class ECKey implements Serializable { return true; } - private static BigInteger extractPrivateKeyFromASN1(byte[] asn1privkey) { + private static ECKey extractKeyFromASN1(byte[] asn1privkey) { // To understand this code, see the definition of the ASN.1 format for EC private keys in the OpenSSL source // code in ec_asn1.c: // @@ -577,9 +577,18 @@ public class ECKey implements Serializable { checkArgument(((DERInteger) seq.getObjectAt(0)).getValue().equals(BigInteger.ONE), "Input is of wrong version"); Object obj = seq.getObjectAt(1); - byte[] bits = ((ASN1OctetString) obj).getOctets(); + byte[] privbits = ((ASN1OctetString) obj).getOctets(); decoder.close(); - return new BigInteger(1, bits); + BigInteger privkey = new BigInteger(1, privbits); + byte[] pubbits = ((DERBitString)((ASN1TaggedObject)seq.getObjectAt(3)).getObject()).getBytes(); + // Now sanity check to ensure the pubkey bytes match the privkey. + byte[] compressed = publicKeyFromPrivate(privkey, true); + if (Arrays.equals(pubbits, compressed)) + return new ECKey(privkey, compressed); + byte[] uncompressed = publicKeyFromPrivate(privkey, false); + if (Arrays.equals(pubbits, uncompressed)) + return new ECKey(privkey, uncompressed); + throw new IllegalArgumentException("Public key in ASN.1 structure does not match private key."); } catch (IOException e) { throw new RuntimeException(e); // Cannot happen, reading from memory stream. } 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 fcd80f79..a9fc5cdb 100644 --- a/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java +++ b/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java @@ -39,15 +39,14 @@ import org.slf4j.LoggerFactory; import org.spongycastle.crypto.params.KeyParameter; import org.spongycastle.util.encoders.Hex; +import java.io.InputStream; import java.math.BigInteger; import java.security.SecureRandom; import java.security.SignatureException; import java.util.Arrays; import java.util.List; import java.util.Random; -import java.io.InputStream; import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static com.google.bitcoin.core.Utils.reverseBytes; @@ -171,6 +170,11 @@ public class ECKeyTest { "11da3761e86431e4a54c176789e41f1651b324d240d599a7067bee23d328ec2a")); assertTrue(roundtripKey.verify(message, decodedKey.sign(new Sha256Hash(message)).encodeToDER())); assertTrue(decodedKey.verify(message, roundtripKey.sign(new Sha256Hash(message)).encodeToDER())); + + // Verify bytewise equivalence of public keys (i.e. compression state is preserved) + ECKey key = new ECKey(); + ECKey key2 = ECKey.fromASN1(key.toASN1()); + assertArrayEquals(key.getPubKey(), key2.getPubKey()); } @Test @@ -367,7 +371,7 @@ public class ECKeyTest { public void keyRecoveryWithEncryptedKey() throws Exception { ECKey unencryptedKey = new ECKey(); KeyParameter aesKey = keyCrypter.deriveKey(PASSWORD1); - ECKey encryptedKey = unencryptedKey.encrypt(keyCrypter,aesKey); + ECKey encryptedKey = unencryptedKey.encrypt(keyCrypter, aesKey); String message = "Goodbye Jupiter!"; Sha256Hash hash = Sha256Hash.create(message.getBytes());