3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-14 11:15:51 +00:00

ECKey/DeterministicKey: replace ECPoint with a LazyECPoint wrapper that doesn't delays parsing of key bytes into a key structure until it's needed. The process of decoding keys from the wallet previously involved decompressing/recompressing them which was taking ~seconds for hundreds of keys on Dalvik/2012 era Androids. After this patch loading such a wallet takes a few hundred milliseconds, most of which is spent inside RIPEMD160.

This commit is contained in:
Mike Hearn 2014-11-19 15:50:22 +01:00
parent 34017e16f8
commit 69de1f01ac
5 changed files with 227 additions and 19 deletions

View File

@ -141,7 +141,7 @@ public class ECKey implements EncryptableItem, Serializable {
// The two parts of the key. If "priv" is set, "pub" can always be calculated. If "pub" is set but not "priv", we
// can only verify signatures not make them.
protected final BigInteger priv; // A field element.
protected final ECPoint pub;
protected final LazyECPoint pub;
// Creation time of the key in seconds since the epoch, or zero if the key was deserialized from a version that did
// not have this field.
@ -173,11 +173,16 @@ public class ECKey implements EncryptableItem, Serializable {
ECPrivateKeyParameters privParams = (ECPrivateKeyParameters) keypair.getPrivate();
ECPublicKeyParameters pubParams = (ECPublicKeyParameters) keypair.getPublic();
priv = privParams.getD();
pub = CURVE.getCurve().decodePoint(pubParams.getQ().getEncoded(true));
pub = new LazyECPoint(CURVE.getCurve(), pubParams.getQ().getEncoded(true));
creationTimeSeconds = Utils.currentTimeSeconds();
}
protected ECKey(@Nullable BigInteger priv, ECPoint pub) {
this.priv = priv;
this.pub = new LazyECPoint(checkNotNull(pub));
}
protected ECKey(@Nullable BigInteger priv, LazyECPoint pub) {
this.priv = priv;
this.pub = checkNotNull(pub);
}
@ -186,16 +191,24 @@ public class ECKey implements EncryptableItem, Serializable {
* Utility for compressing an elliptic curve point. Returns the same point if it's already compressed.
* See the ECKey class docs for a discussion of point compression.
*/
public static ECPoint compressPoint(ECPoint uncompressed) {
return CURVE.getCurve().decodePoint(uncompressed.getEncoded(true));
public static ECPoint compressPoint(ECPoint point) {
return point.isCompressed() ? point : CURVE.getCurve().decodePoint(point.getEncoded(true));
}
public static LazyECPoint compressPoint(LazyECPoint point) {
return point.isCompressed() ? point : new LazyECPoint(compressPoint(point.get()));
}
/**
* Utility for decompressing an elliptic curve point. Returns the same point if it's already compressed.
* See the ECKey class docs for a discussion of point compression.
*/
public static ECPoint decompressPoint(ECPoint compressed) {
return CURVE.getCurve().decodePoint(compressed.getEncoded(false));
public static ECPoint decompressPoint(ECPoint point) {
return !point.isCompressed() ? point : CURVE.getCurve().decodePoint(point.getEncoded(false));
}
public static LazyECPoint decompressPoint(LazyECPoint point) {
return !point.isCompressed() ? point : new LazyECPoint(decompressPoint(point.get()));
}
/**
@ -283,7 +296,7 @@ public class ECKey implements EncryptableItem, Serializable {
if (!pub.isCompressed())
return this;
else
return new ECKey(priv, decompressPoint(pub));
return new ECKey(priv, decompressPoint(pub.get()));
}
/**
@ -340,12 +353,12 @@ public class ECKey implements EncryptableItem, Serializable {
ECPoint point = CURVE.getG().multiply(privKey);
if (compressed)
point = compressPoint(point);
this.pub = point;
this.pub = new LazyECPoint(point);
} else {
// We expect the pubkey to be in regular encoded form, just as a BigInteger. Therefore the first byte is
// a special marker byte.
// TODO: This is probably not a useful API and may be confusing.
this.pub = CURVE.getCurve().decodePoint(pubKey);
this.pub = new LazyECPoint(CURVE.getCurve().decodePoint(pubKey));
}
}
@ -431,7 +444,7 @@ public class ECKey implements EncryptableItem, Serializable {
/** Gets the public key in the form of an elliptic curve point object from Bouncy Castle. */
public ECPoint getPubKeyPoint() {
return pub;
return pub.get();
}
/**

View File

@ -49,7 +49,7 @@ public class DeterministicKey extends ECKey {
/** Constructs a key from its components. This is not normally something you should use. */
public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,
byte[] chainCode,
ECPoint publicAsPoint,
LazyECPoint publicAsPoint,
@Nullable BigInteger priv,
@Nullable DeterministicKey parent) {
super(priv, compressPoint(checkNotNull(publicAsPoint)));
@ -59,6 +59,14 @@ public class DeterministicKey extends ECKey {
this.chainCode = Arrays.copyOf(chainCode, chainCode.length);
}
public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,
byte[] chainCode,
ECPoint publicAsPoint,
@Nullable BigInteger priv,
@Nullable DeterministicKey parent) {
this(childNumberPath, chainCode, new LazyECPoint(publicAsPoint), priv, parent);
}
/** Constructs a key from its components. This is not normally something you should use. */
public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,
byte[] chainCode,
@ -74,7 +82,10 @@ public class DeterministicKey extends ECKey {
/** Constructs a key from its components. This is not normally something you should use. */
public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,
byte[] chainCode,
KeyCrypter crypter, ECPoint pub, EncryptedData priv, @Nullable DeterministicKey parent) {
KeyCrypter crypter,
LazyECPoint pub,
EncryptedData priv,
@Nullable DeterministicKey parent) {
this(childNumberPath, chainCode, pub, null, parent);
this.encryptedPrivateKey = checkNotNull(priv);
this.keyCrypter = checkNotNull(crypter);
@ -82,7 +93,7 @@ public class DeterministicKey extends ECKey {
/** Clones the key */
public DeterministicKey(DeterministicKey keyToClone, DeterministicKey newParent) {
super(keyToClone.priv, keyToClone.pub);
super(keyToClone.priv, keyToClone.pub.get());
this.parent = newParent;
this.childNumberPath = keyToClone.childNumberPath;
this.chainCode = keyToClone.chainCode;
@ -155,8 +166,7 @@ public class DeterministicKey extends ECKey {
*/
public DeterministicKey getPubOnly() {
if (isPubKeyOnly()) return this;
//final DeterministicKey parentPub = getParent() == null ? null : getParent().getPubOnly();
return new DeterministicKey(getPath(), getChainCode(), getPubKeyPoint(), null, parent);
return new DeterministicKey(getPath(), getChainCode(), pub, null, parent);
}
@ -413,7 +423,7 @@ public class DeterministicKey extends ECKey {
checkArgument(!buffer.hasRemaining(), "Found unexpected data in key");
if (pub) {
ECPoint point = ECKey.CURVE.getCurve().decodePoint(data);
return new DeterministicKey(path, chainCode, point, null, parent);
return new DeterministicKey(path, chainCode, new LazyECPoint(point), null, parent);
} else {
return new DeterministicKey(path, chainCode, new BigInteger(1, data), parent);
}

View File

@ -86,7 +86,7 @@ public final class HDKeyDerivation {
}
public static DeterministicKey createMasterPubKeyFromBytes(byte[] pubKeyBytes, byte[] chainCode) {
return new DeterministicKey(ImmutableList.<ChildNumber>of(), chainCode, ECKey.CURVE.getCurve().decodePoint(pubKeyBytes), null, null);
return new DeterministicKey(ImmutableList.<ChildNumber>of(), chainCode, new LazyECPoint(ECKey.CURVE.getCurve(), pubKeyBytes), null, null);
}
/**
@ -130,7 +130,7 @@ public final class HDKeyDerivation {
return new DeterministicKey(
HDUtils.append(parent.getPath(), childNumber),
rawKey.chainCode,
ECKey.CURVE.getCurve().decodePoint(rawKey.keyBytes), // c'tor will compress
new LazyECPoint(ECKey.CURVE.getCurve(), rawKey.keyBytes),
null,
parent);
} else {

View File

@ -0,0 +1,185 @@
package org.bitcoinj.crypto;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECFieldElement;
import org.spongycastle.math.ec.ECPoint;
import javax.annotation.Nullable;
import java.math.BigInteger;
import java.util.Arrays;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A wrapper around ECPoint that delays decoding of the point for as long as possible. This is useful because point
* encode/decode in Bouncy Castle is quite slow especially on Dalvik, as it often involves decompression/recompression.
*/
public class LazyECPoint {
// If curve is set, bits is also set. If curve is unset, point is set and bits is unset. Point can be set along
// with curve and bits when the cached form has been accessed and thus must have been converted.
// These fields are all effectively final - once set they won't change again. However they can be set after
// construction.
private ECCurve curve;
private byte[] bits;
@Nullable
private ECPoint point;
public LazyECPoint(ECCurve curve, byte[] bits) {
this.curve = curve;
this.bits = bits;
}
public LazyECPoint(ECPoint point) {
this.point = checkNotNull(point);
}
public ECPoint get() {
if (point == null)
point = curve.decodePoint(bits);
return point;
}
// Delegated methods.
public ECPoint getDetachedPoint() {
return get().getDetachedPoint();
}
public byte[] getEncoded() {
if (bits != null)
return Arrays.copyOf(bits, bits.length);
else
return get().getEncoded();
}
public boolean isInfinity() {
return get().isInfinity();
}
public ECPoint timesPow2(int e) {
return get().timesPow2(e);
}
public ECFieldElement getYCoord() {
return get().getYCoord();
}
public ECFieldElement[] getZCoords() {
return get().getZCoords();
}
public boolean isNormalized() {
return get().isNormalized();
}
public boolean isCompressed() {
if (bits != null)
return bits[0] == 2 || bits[0] == 3;
else
return get().isCompressed();
}
public ECPoint multiply(BigInteger k) {
return get().multiply(k);
}
public ECPoint subtract(ECPoint b) {
return get().subtract(b);
}
public boolean isValid() {
return get().isValid();
}
public ECPoint scaleY(ECFieldElement scale) {
return get().scaleY(scale);
}
public ECFieldElement getXCoord() {
return get().getXCoord();
}
public ECPoint scaleX(ECFieldElement scale) {
return get().scaleX(scale);
}
public boolean equals(ECPoint other) {
return get().equals(other);
}
public ECPoint negate() {
return get().negate();
}
public ECPoint threeTimes() {
return get().threeTimes();
}
public ECFieldElement getZCoord(int index) {
return get().getZCoord(index);
}
public byte[] getEncoded(boolean compressed) {
if (compressed == isCompressed() && bits != null)
return Arrays.copyOf(bits, bits.length);
else
return get().getEncoded(compressed);
}
public ECPoint add(ECPoint b) {
return get().add(b);
}
public ECPoint twicePlus(ECPoint b) {
return get().twicePlus(b);
}
public ECCurve getCurve() {
return get().getCurve();
}
public ECPoint normalize() {
return get().normalize();
}
public ECFieldElement getY() {
return get().getY();
}
public ECPoint twice() {
return get().twice();
}
public ECFieldElement getAffineYCoord() {
return get().getAffineYCoord();
}
public ECFieldElement getAffineXCoord() {
return get().getAffineXCoord();
}
public ECFieldElement getX() {
return get().getX();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LazyECPoint point1 = (LazyECPoint) o;
if (bits != null && point1.bits != null)
return Arrays.equals(bits, point1.bits);
else
return get().equals(point1.get());
}
@Override
public int hashCode() {
if (bits != null)
return Arrays.hashCode(bits);
else
return get().hashCode();
}
}

View File

@ -797,7 +797,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
for (int i : key.getDeterministicKey().getPathList())
path.add(new ChildNumber(i));
// Deserialize the public key and path.
ECPoint pubkey = ECKey.CURVE.getCurve().decodePoint(key.getPublicKey().toByteArray());
LazyECPoint pubkey = new LazyECPoint(ECKey.CURVE.getCurve(), key.getPublicKey().toByteArray());
final ImmutableList<ChildNumber> immutablePath = ImmutableList.copyOf(path);
// Possibly create the chain, if we didn't already do so yet.
boolean isWatchingAccountKey = false;