LazyECPoint: make private curve member static

We do not need to specify the curve for each instance as
bitcoinj uses SECP256K1 curve exclusively.

This change reduces per instance memory usage, simplifies the API,
and reduces public API dependence on Bouncy Castle.

One two-arg constructor is deprecated and replaced with a single-arg
constructor that no longer requires the curve parameter.

Just to be extra safe, in the deprecated method we validate the curve
argument and make sure it is the P256K1 curve.

Also correctly mark private `bits` field as @Nullable, and improve
JavaDoc and other comments.
This commit is contained in:
Sean Gilligan 2024-03-10 13:10:09 -07:00 committed by Andreas Schildbach
parent 155b404063
commit 3a28fb0274
5 changed files with 28 additions and 15 deletions

View File

@ -685,7 +685,7 @@ public class DeterministicKey extends ECKey {
checkArgument(!buffer.hasRemaining(), () ->
"found unexpected data in key");
if (pub) {
return new DeterministicKey(path, chainCode, new LazyECPoint(ECKey.CURVE.getCurve(), data), parent, depth, parentFingerprint);
return new DeterministicKey(path, chainCode, new LazyECPoint(data), parent, depth, parentFingerprint);
} else {
return new DeterministicKey(path, chainCode, ByteUtils.bytesToBigInteger(data), parent, depth, parentFingerprint);
}

View File

@ -259,7 +259,7 @@ public class ECKey implements EncryptableItem {
public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] pub) {
Objects.requireNonNull(priv);
Objects.requireNonNull(pub);
return new ECKey(ByteUtils.bytesToBigInteger(priv), new LazyECPoint(CURVE.getCurve(), pub));
return new ECKey(ByteUtils.bytesToBigInteger(priv), new LazyECPoint(pub));
}
/**
@ -275,7 +275,7 @@ public class ECKey implements EncryptableItem {
* The compression state of pub will be preserved.
*/
public static ECKey fromPublicOnly(byte[] pub) {
return new ECKey(null, new LazyECPoint(CURVE.getCurve(), pub));
return new ECKey(null, new LazyECPoint(pub));
}
public static ECKey fromPublicOnly(ECKey key) {

View File

@ -90,7 +90,7 @@ public final class HDKeyDerivation {
}
public static DeterministicKey createMasterPubKeyFromBytes(byte[] pubKeyBytes, byte[] chainCode) {
return new DeterministicKey(HDPath.M(), chainCode, new LazyECPoint(ECKey.CURVE.getCurve(), pubKeyBytes), null, null);
return new DeterministicKey(HDPath.M(), chainCode, new LazyECPoint(pubKeyBytes), null, null);
}
/**
@ -190,7 +190,7 @@ public final class HDKeyDerivation {
PublicDeriveMode mode) throws HDDerivationException {
RawKeyBytes rawKey = deriveChildKeyBytesFromPublic(parent, childNumber, PublicDeriveMode.NORMAL);
return new DeterministicKey(parent.getPath().extend(childNumber), rawKey.chainCode,
new LazyECPoint(ECKey.CURVE.getCurve(), rawKey.keyBytes), null, parent);
new LazyECPoint(rawKey.keyBytes), null, parent);
}
public static RawKeyBytes deriveChildKeyBytesFromPublic(DeterministicKey parent, ChildNumber childNumber, PublicDeriveMode mode) throws HDDerivationException {

View File

@ -25,20 +25,21 @@ import java.math.BigInteger;
import java.util.Arrays;
import java.util.Objects;
import static org.bitcoinj.base.internal.Preconditions.checkArgument;
/**
* A wrapper around ECPoint that delays decoding of the point for as long as possible. This is useful because point
* A wrapper around a SECP256K1 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.
private static final ECCurve curve = ECKey.CURVE.getCurve();
private final ECCurve curve;
// bits will be null if LazyECPoint is constructed from an (already decoded) point
@Nullable
private final byte[] bits;
private final boolean compressed;
// This field is effectively final - once set it won't change again. However it can be set after
// construction.
// This field is lazy - once set it won't change again. However, it can be set after construction.
@Nullable
private ECPoint point;
@ -46,11 +47,24 @@ public class LazyECPoint {
* Construct a LazyECPoint from a public key. Due to the delayed decoding of the point the validation of the
* public key is delayed too, e.g. until a getter is called.
*
* @param curve a curve the point is on
* @param bits public key bytes
*/
public LazyECPoint(byte[] bits) {
this.bits = bits;
this.compressed = ECKey.isPubKeyCompressed(bits);
}
/**
* Construct a LazyECPoint from a public key. Due to the delayed decoding of the point the validation of the
* public key is delayed too, e.g. until a getter is called.
*
* @param curve a curve the point is on
* @param bits public key bytes
* @deprecated Use {@link LazyECPoint#LazyECPoint(byte[])}
*/
@Deprecated
public LazyECPoint(ECCurve curve, byte[] bits) {
this.curve = curve;
checkArgument(LazyECPoint.curve.equals(curve), () -> "Curve must be SECP256K1");
this.bits = bits;
this.compressed = ECKey.isPubKeyCompressed(bits);
}
@ -64,7 +78,6 @@ public class LazyECPoint {
public LazyECPoint(ECPoint point, boolean compressed) {
this.point = Objects.requireNonNull(point).normalize();
this.compressed = compressed;
this.curve = null;
this.bits = null;
}

View File

@ -893,7 +893,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
throw new UnreadableWalletException("Deterministic key missing extra data: " + key);
byte[] chainCode = key.getDeterministicKey().getChainCode().toByteArray();
// Deserialize the public key and path.
LazyECPoint pubkey = new LazyECPoint(ECKey.CURVE.getCurve(), key.getPublicKey().toByteArray());
LazyECPoint pubkey = new LazyECPoint(key.getPublicKey().toByteArray());
// Deserialize the path through the tree.
final HDPath path = HDPath.deserialize(key.getDeterministicKey().getPathList());
if (key.hasOutputScriptType())