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:
parent
34017e16f8
commit
69de1f01ac
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
185
core/src/main/java/org/bitcoinj/crypto/LazyECPoint.java
Normal file
185
core/src/main/java/org/bitcoinj/crypto/LazyECPoint.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user