From d22ee01f8a9d7301479a0a65aa6401fea17e43a0 Mon Sep 17 00:00:00 2001 From: Andreas Schildbach Date: Thu, 3 Jul 2014 16:31:07 +0200 Subject: [PATCH] Fix ECKey.equals() and toString() to also include encryptedPrivateKey. Convert to Guava and consolidate at bottom of class. --- .../java/com/google/bitcoin/core/ECKey.java | 100 +++++++++--------- .../bitcoin/crypto/DeterministicKey.java | 45 ++++---- .../google/bitcoin/wallet/KeyChainGroup.java | 2 +- .../com/google/bitcoin/core/ECKeyTest.java | 4 +- 4 files changed, 79 insertions(+), 72 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 b7b264f5..586ee0da 100644 --- a/core/src/main/java/com/google/bitcoin/core/ECKey.java +++ b/core/src/main/java/com/google/bitcoin/core/ECKey.java @@ -19,6 +19,8 @@ package com.google.bitcoin.core; import com.google.bitcoin.crypto.*; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; import com.google.common.base.Preconditions; import org.bitcoin.NativeSecp256k1; import org.bitcoinj.wallet.Protos; @@ -405,32 +407,6 @@ public class ECKey implements EncryptableItem, Serializable { return pub.isCompressed(); } - @Override - public String toString() { - StringBuilder b = new StringBuilder(); - b.append("pub:").append(Utils.HEX.encode(pub.getEncoded())); - if (creationTimeSeconds != 0) { - b.append(" timestamp:").append(creationTimeSeconds); - } - if (isEncrypted()) { - b.append(" encrypted"); - } - return b.toString(); - } - - /** - * Produce a string rendering of the ECKey INCLUDING the private key. - * Unless you absolutely need the private key it is better for security reasons to just use toString(). - */ - public String toStringWithPrivate() { - StringBuilder b = new StringBuilder(); - b.append(toString()); - if (priv != null) { - b.append(" priv:").append(Utils.HEX.encode(priv.toByteArray())); - } - return b.toString(); - } - /** * Returns the address that corresponds to the public part of this ECKey. Note that an address is derived from * the RIPEMD-160 hash of the public key and is not the public key itself (which is too large to be convenient). @@ -921,29 +897,6 @@ public class ECKey implements EncryptableItem, Serializable { creationTimeSeconds = newCreationTimeSeconds; } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || !(o instanceof ECKey)) return false; - - ECKey ecKey = (ECKey) o; - - if (creationTimeSeconds != ecKey.creationTimeSeconds) return false; - if (keyCrypter != null ? !keyCrypter.equals(ecKey.keyCrypter) : ecKey.keyCrypter != null) return false; - if (priv != null && !priv.equals(ecKey.priv)) return false; - if (pub != null && !pub.equals(ecKey.pub)) return false; - - return true; - } - - @Override - public int hashCode() { - // Public keys are random already so we can just use a part of them as the hashcode. Read from the start to - // avoid picking up the type code (compressed vs uncompressed) which is tacked on the end. - byte[] bits = getPubKey(); - return (bits[0] & 0xFF) | ((bits[1] & 0xFF) << 8) | ((bits[2] & 0xFF) << 16) | ((bits[3] & 0xFF) << 24); - } - /** * Create an encrypted private key with the keyCrypter and the AES key supplied. * This method returns a new encrypted key and leaves the original unchanged. @@ -1083,4 +1036,53 @@ public class ECKey implements EncryptableItem, Serializable { public static class KeyIsEncryptedException extends MissingPrivateKeyException { } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || !(o instanceof ECKey)) return false; + + ECKey other = (ECKey) o; + + return Objects.equal(this.priv, other.priv) + && Objects.equal(this.pub, other.pub) + && Objects.equal(this.creationTimeSeconds, other.creationTimeSeconds) + && Objects.equal(this.keyCrypter, other.keyCrypter) + && Objects.equal(this.encryptedPrivateKey, other.encryptedPrivateKey); + } + + @Override + public int hashCode() { + // Public keys are random already so we can just use a part of them as the hashcode. Read from the start to + // avoid picking up the type code (compressed vs uncompressed) which is tacked on the end. + byte[] bits = getPubKey(); + return (bits[0] & 0xFF) | ((bits[1] & 0xFF) << 8) | ((bits[2] & 0xFF) << 16) | ((bits[3] & 0xFF) << 24); + } + + @Override + public String toString() { + return toString(false); + } + + /** + * Produce a string rendering of the ECKey INCLUDING the private key. + * Unless you absolutely need the private key it is better for security reasons to just use {@link #toString()}. + */ + public String toStringWithPrivate() { + return toString(true); + } + + private String toString(boolean includePrivate) { + final ToStringHelper helper = Objects.toStringHelper(this).omitNullValues(); + helper.add("pub", Utils.HEX.encode(pub.getEncoded())); + if (includePrivate) + helper.add("priv", Utils.HEX.encode(priv.toByteArray())); + if (creationTimeSeconds > 0) + helper.add("creationTimeSeconds", creationTimeSeconds); + helper.add("keyCrypter", keyCrypter); + if (includePrivate) + helper.add("encryptedPrivateKey", encryptedPrivateKey); + helper.add("isEncrypted", isEncrypted()); + return helper.toString(); + } } diff --git a/core/src/main/java/com/google/bitcoin/crypto/DeterministicKey.java b/core/src/main/java/com/google/bitcoin/crypto/DeterministicKey.java index 420e0538..e738bbc4 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/DeterministicKey.java +++ b/core/src/main/java/com/google/bitcoin/crypto/DeterministicKey.java @@ -17,6 +17,8 @@ package com.google.bitcoin.crypto; import com.google.bitcoin.core.*; +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; import com.google.common.collect.ImmutableList; import org.spongycastle.crypto.params.KeyParameter; import org.spongycastle.math.ec.ECPoint; @@ -404,6 +406,18 @@ public class DeterministicKey extends ECKey { } } + /** + * The creation time of a deterministic key is equal to that of its parent, unless this key is the root of a tree + * in which case the time is stored alongside the key as per normal, see {@link com.google.bitcoin.core.ECKey#getCreationTimeSeconds()}. + */ + @Override + public long getCreationTimeSeconds() { + if (parent != null) + return parent.getCreationTimeSeconds(); + else + return super.getCreationTimeSeconds(); + } + /** * Verifies equality of all fields but NOT the parent pointer (thus the same key derived in two separate heirarchy * objects will equal each other. @@ -412,14 +426,12 @@ public class DeterministicKey extends ECKey { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - DeterministicKey key = (DeterministicKey) o; + DeterministicKey other = (DeterministicKey) o; - if (!Arrays.equals(chainCode, key.chainCode)) return false; - if (!childNumberPath.equals(key.childNumberPath)) return false; - - return true; + return super.equals(other) + && Arrays.equals(this.chainCode, other.chainCode) + && Objects.equal(this.childNumberPath, other.childNumberPath); } @Override @@ -432,19 +444,12 @@ public class DeterministicKey extends ECKey { @Override public String toString() { - return String.format("pub:%s chaincode:%s path:%s time:%d", HEX.encode(getPubKey()), - HEX.encode(getChainCode()), getPathAsString(), getCreationTimeSeconds()); - } - - /** - * The creation time of a deterministic key is equal to that of its parent, unless this key is the root of a tree - * in which case the time is stored alongside the key as per normal, see {@link com.google.bitcoin.core.ECKey#getCreationTimeSeconds()}. - */ - @Override - public long getCreationTimeSeconds() { - if (parent != null) - return parent.getCreationTimeSeconds(); - else - return super.getCreationTimeSeconds(); + final ToStringHelper helper = Objects.toStringHelper(this).omitNullValues(); + helper.add("pub", Utils.HEX.encode(pub.getEncoded())); + helper.add("chainCode", HEX.encode(chainCode)); + helper.add("path", getPathAsString()); + if (creationTimeSeconds > 0) + helper.add("creationTimeSeconds", creationTimeSeconds); + return helper.toString(); } } diff --git a/core/src/main/java/com/google/bitcoin/wallet/KeyChainGroup.java b/core/src/main/java/com/google/bitcoin/wallet/KeyChainGroup.java index af5f8eaa..d20be030 100644 --- a/core/src/main/java/com/google/bitcoin/wallet/KeyChainGroup.java +++ b/core/src/main/java/com/google/bitcoin/wallet/KeyChainGroup.java @@ -857,7 +857,7 @@ public class KeyChainGroup { builder.append(address.toString()); builder.append(" hash160:"); builder.append(Utils.HEX.encode(key.getPubKeyHash())); - builder.append(" "); + builder.append("\n "); builder.append(includePrivateKeys ? key.toStringWithPrivate() : key.toString()); builder.append("\n"); } 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 07795ed1..cb95882c 100644 --- a/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java +++ b/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java @@ -318,8 +318,8 @@ public class ECKeyTest { public void testToString() throws Exception { ECKey key = ECKey.fromPrivate(BigInteger.TEN).decompress(); // An example private key. - assertEquals("pub:04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7", key.toString()); - assertEquals("pub:04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7 priv:0a", key.toStringWithPrivate()); + assertEquals("ECKey{pub=04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7, isEncrypted=false}", key.toString()); + assertEquals("ECKey{pub=04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7, priv=0a, isEncrypted=false}", key.toStringWithPrivate()); } @Test