mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-02-23 22:46:56 +01:00
Track point compression in LazyECPoint, rather than ECPoint.
The reason is BouncyCastle 1.64 removed point compression tracking.
This commit is contained in:
parent
7eb9c73655
commit
7629677103
9 changed files with 62 additions and 75 deletions
|
@ -63,9 +63,6 @@ import java.util.Objects;
|
|||
|
||||
import static com.google.common.base.Preconditions.*;
|
||||
|
||||
// TODO: Move this class to tracking compression state itself.
|
||||
// The Bouncy Castle developers are deprecating their own tracking of the compression state.
|
||||
|
||||
/**
|
||||
* <p>Represents an elliptic curve public and (optionally) private key, usable for digital signatures but not encryption.
|
||||
* Creating a new ECKey with the empty constructor will generate a new random keypair. Other static methods can be used
|
||||
|
@ -179,12 +176,12 @@ public class ECKey implements EncryptableItem {
|
|||
ECPrivateKeyParameters privParams = (ECPrivateKeyParameters) keypair.getPrivate();
|
||||
ECPublicKeyParameters pubParams = (ECPublicKeyParameters) keypair.getPublic();
|
||||
priv = privParams.getD();
|
||||
pub = new LazyECPoint(CURVE.getCurve(), pubParams.getQ().getEncoded(true));
|
||||
pub = getPointWithCompression(pubParams.getQ(), true);
|
||||
creationTimeSeconds = Utils.currentTimeSeconds();
|
||||
}
|
||||
|
||||
protected ECKey(@Nullable BigInteger priv, ECPoint pub) {
|
||||
this(priv, new LazyECPoint(checkNotNull(pub)));
|
||||
protected ECKey(@Nullable BigInteger priv, ECPoint pub, boolean compressed) {
|
||||
this(priv, getPointWithCompression(checkNotNull(pub), compressed));
|
||||
}
|
||||
|
||||
protected ECKey(@Nullable BigInteger priv, LazyECPoint pub) {
|
||||
|
@ -204,33 +201,20 @@ public class ECKey implements EncryptableItem {
|
|||
* 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 point) {
|
||||
return getPointWithCompression(point, true);
|
||||
}
|
||||
|
||||
public static LazyECPoint compressPoint(LazyECPoint point) {
|
||||
return point.isCompressed() ? point : new LazyECPoint(compressPoint(point.get()));
|
||||
return point.isCompressed() ? point : getPointWithCompression(point.get(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for decompressing an elliptic curve point. Returns the same point if it's already compressed.
|
||||
* Utility for decompressing an elliptic curve point. Returns the same point if it's already uncompressed.
|
||||
* See the ECKey class docs for a discussion of point compression.
|
||||
*/
|
||||
public static ECPoint decompressPoint(ECPoint point) {
|
||||
return getPointWithCompression(point, false);
|
||||
}
|
||||
|
||||
public static LazyECPoint decompressPoint(LazyECPoint point) {
|
||||
return !point.isCompressed() ? point : new LazyECPoint(decompressPoint(point.get()));
|
||||
return !point.isCompressed() ? point : getPointWithCompression(point.get(), false);
|
||||
}
|
||||
|
||||
private static ECPoint getPointWithCompression(ECPoint point, boolean compressed) {
|
||||
if (point.isCompressed() == compressed)
|
||||
return point;
|
||||
point = point.normalize();
|
||||
BigInteger x = point.getAffineXCoord().toBigInteger();
|
||||
BigInteger y = point.getAffineYCoord().toBigInteger();
|
||||
return CURVE.getCurve().createPoint(x, y, compressed);
|
||||
private static LazyECPoint getPointWithCompression(ECPoint point, boolean compressed) {
|
||||
return new LazyECPoint(point, compressed);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -250,8 +234,8 @@ public class ECKey implements EncryptableItem {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates an ECKey given the private key only. The public key is calculated from it (this is slow), either
|
||||
* compressed or not.
|
||||
* Creates an ECKey given the private key only. The public key is calculated from it (this is slow).
|
||||
* @param compressed Determines whether the resulting ECKey will use a compressed encoding for the public key.
|
||||
*/
|
||||
public static ECKey fromPrivate(BigInteger privKey, boolean compressed) {
|
||||
ECPoint point = publicPointFromPrivate(privKey);
|
||||
|
@ -267,8 +251,8 @@ public class ECKey implements EncryptableItem {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates an ECKey given the private key only. The public key is calculated from it (this is slow), either
|
||||
* compressed or not.
|
||||
* Creates an ECKey given the private key only. The public key is calculated from it (this is slow).
|
||||
* @param compressed Determines whether the resulting ECKey will use a compressed encoding for the public key.
|
||||
*/
|
||||
public static ECKey fromPrivate(byte[] privKeyBytes, boolean compressed) {
|
||||
return fromPrivate(new BigInteger(1, privKeyBytes), compressed);
|
||||
|
@ -277,10 +261,11 @@ public class ECKey implements EncryptableItem {
|
|||
/**
|
||||
* Creates an ECKey that simply trusts the caller to ensure that point is really the result of multiplying the
|
||||
* generator point by the private key. This is used to speed things up when you know you have the right values
|
||||
* already. The compression state of pub will be preserved.
|
||||
* already.
|
||||
* @param compressed Determines whether the resulting ECKey will use a compressed encoding for the public key.
|
||||
*/
|
||||
public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, ECPoint pub) {
|
||||
return new ECKey(priv, pub);
|
||||
public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv, ECPoint pub, boolean compressed) {
|
||||
return new ECKey(priv, pub, compressed);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -291,15 +276,15 @@ public class ECKey implements EncryptableItem {
|
|||
public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[] pub) {
|
||||
checkNotNull(priv);
|
||||
checkNotNull(pub);
|
||||
return new ECKey(new BigInteger(1, priv), CURVE.getCurve().decodePoint(pub));
|
||||
return new ECKey(new BigInteger(1, priv), new LazyECPoint(CURVE.getCurve(), pub));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ECKey that cannot be used for signing, only verifying signatures, from the given point. The
|
||||
* compression state of pub will be preserved.
|
||||
* Creates an ECKey that cannot be used for signing, only verifying signatures, from the given point.
|
||||
* @param compressed Determines whether the resulting ECKey will use a compressed encoding for the public key.
|
||||
*/
|
||||
public static ECKey fromPublicOnly(ECPoint pub) {
|
||||
return new ECKey(null, pub);
|
||||
public static ECKey fromPublicOnly(ECPoint pub, boolean compressed) {
|
||||
return new ECKey(null, pub, compressed);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -307,7 +292,11 @@ public class ECKey implements EncryptableItem {
|
|||
* The compression state of pub will be preserved.
|
||||
*/
|
||||
public static ECKey fromPublicOnly(byte[] pub) {
|
||||
return new ECKey(null, CURVE.getCurve().decodePoint(pub));
|
||||
return new ECKey(null, new LazyECPoint(CURVE.getCurve(), pub));
|
||||
}
|
||||
|
||||
public static ECKey fromPublicOnly(ECKey key) {
|
||||
return fromPublicOnly(key.getPubKeyPoint(), key.isCompressed());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -318,7 +307,7 @@ public class ECKey implements EncryptableItem {
|
|||
if (!pub.isCompressed())
|
||||
return this;
|
||||
else
|
||||
return new ECKey(priv, decompressPoint(pub.get()));
|
||||
return new ECKey(priv, getPointWithCompression(pub.get(), false));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -373,8 +362,7 @@ public class ECKey implements EncryptableItem {
|
|||
if (pubKey == null) {
|
||||
// Derive public from private.
|
||||
ECPoint point = publicPointFromPrivate(privKey);
|
||||
point = getPointWithCompression(point, compressed);
|
||||
this.pub = new LazyECPoint(point);
|
||||
this.pub = getPointWithCompression(point, compressed);
|
||||
} else {
|
||||
// We expect the pubkey to be in regular encoded form, just as a BigInteger. Therefore the first byte is
|
||||
// a special marker byte.
|
||||
|
@ -853,7 +841,7 @@ public class ECKey implements EncryptableItem {
|
|||
|
||||
// Now sanity check to ensure the pubkey bytes match the privkey.
|
||||
boolean compressed = isPubKeyCompressed(pubbits);
|
||||
ECKey key = new ECKey(privkey, null, compressed);
|
||||
ECKey key = new ECKey(privkey, (byte[]) null, compressed);
|
||||
if (!Arrays.equals(key.getPubKey(), pubbits))
|
||||
throw new IllegalArgumentException("Public key in ASN.1 structure does not match private key.");
|
||||
return key;
|
||||
|
@ -1036,7 +1024,7 @@ public class ECKey implements EncryptableItem {
|
|||
BigInteger srInv = rInv.multiply(sig.s).mod(n);
|
||||
BigInteger eInvrInv = rInv.multiply(eInv).mod(n);
|
||||
ECPoint q = ECAlgorithms.sumOfTwoMultiplies(CURVE.getG(), eInvrInv, R, srInv);
|
||||
return ECKey.fromPublicOnly(q.getEncoded(compressed));
|
||||
return ECKey.fromPublicOnly(q, compressed);
|
||||
}
|
||||
|
||||
/** Decompress a compressed public key (x co-ord and low-bit of y-coord). */
|
||||
|
@ -1121,9 +1109,7 @@ public class ECKey implements EncryptableItem {
|
|||
if (unencryptedPrivateKey.length != 32)
|
||||
throw new KeyCrypterException.InvalidCipherText(
|
||||
"Decrypted key must be 32 bytes long, but is " + unencryptedPrivateKey.length);
|
||||
ECKey key = ECKey.fromPrivate(unencryptedPrivateKey);
|
||||
if (!isCompressed())
|
||||
key = key.decompress();
|
||||
ECKey key = ECKey.fromPrivate(unencryptedPrivateKey, isCompressed());
|
||||
if (!Arrays.equals(key.getPubKey(), getPubKey()))
|
||||
throw new KeyCrypterException("Provided AES key is wrong");
|
||||
key.setCreationTimeSeconds(creationTimeSeconds);
|
||||
|
|
|
@ -79,9 +79,10 @@ public class DeterministicKey extends ECKey {
|
|||
public DeterministicKey(List<ChildNumber> childNumberPath,
|
||||
byte[] chainCode,
|
||||
ECPoint publicAsPoint,
|
||||
boolean compressed,
|
||||
@Nullable BigInteger priv,
|
||||
@Nullable DeterministicKey parent) {
|
||||
this(childNumberPath, chainCode, new LazyECPoint(publicAsPoint), priv, parent);
|
||||
this(childNumberPath, chainCode, new LazyECPoint(publicAsPoint, compressed), priv, parent);
|
||||
}
|
||||
|
||||
/** Constructs a key from its components. This is not normally something you should use. */
|
||||
|
@ -89,7 +90,7 @@ public class DeterministicKey extends ECKey {
|
|||
byte[] chainCode,
|
||||
BigInteger priv,
|
||||
@Nullable DeterministicKey parent) {
|
||||
super(priv, compressPoint(ECKey.publicPointFromPrivate(priv)));
|
||||
super(priv, ECKey.publicPointFromPrivate(priv), true);
|
||||
checkArgument(chainCode.length == 32);
|
||||
this.parent = parent;
|
||||
this.childNumberPath = HDPath.M(checkNotNull(childNumberPath));
|
||||
|
@ -157,7 +158,7 @@ public class DeterministicKey extends ECKey {
|
|||
@Nullable DeterministicKey parent,
|
||||
int depth,
|
||||
int parentFingerprint) {
|
||||
super(priv, compressPoint(ECKey.publicPointFromPrivate(priv)));
|
||||
super(priv, ECKey.publicPointFromPrivate(priv), true);
|
||||
checkArgument(chainCode.length == 32);
|
||||
this.parent = parent;
|
||||
this.childNumberPath = HDPath.M(checkNotNull(childNumberPath));
|
||||
|
@ -169,7 +170,7 @@ public class DeterministicKey extends ECKey {
|
|||
|
||||
/** Clones the key */
|
||||
public DeterministicKey(DeterministicKey keyToClone, DeterministicKey newParent) {
|
||||
super(keyToClone.priv, keyToClone.pub.get());
|
||||
super(keyToClone.priv, keyToClone.pub.get(), keyToClone.pub.isCompressed());
|
||||
this.parent = newParent;
|
||||
this.childNumberPath = keyToClone.childNumberPath;
|
||||
this.chainCode = keyToClone.chainCode;
|
||||
|
|
|
@ -37,6 +37,7 @@ public class LazyECPoint {
|
|||
|
||||
private final ECCurve curve;
|
||||
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.
|
||||
|
@ -46,10 +47,12 @@ public class LazyECPoint {
|
|||
public LazyECPoint(ECCurve curve, byte[] bits) {
|
||||
this.curve = curve;
|
||||
this.bits = bits;
|
||||
this.compressed = ECKey.isPubKeyCompressed(bits);
|
||||
}
|
||||
|
||||
public LazyECPoint(ECPoint point) {
|
||||
this.point = checkNotNull(point);
|
||||
public LazyECPoint(ECPoint point, boolean compressed) {
|
||||
this.point = checkNotNull(point).normalize();
|
||||
this.compressed = compressed;
|
||||
this.curve = null;
|
||||
this.bits = null;
|
||||
}
|
||||
|
@ -60,17 +63,17 @@ public class LazyECPoint {
|
|||
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();
|
||||
return get().getEncoded(compressed);
|
||||
}
|
||||
|
||||
// Delegated methods.
|
||||
|
||||
public ECPoint getDetachedPoint() {
|
||||
return get().getDetachedPoint();
|
||||
}
|
||||
|
||||
public boolean isInfinity() {
|
||||
|
@ -94,10 +97,7 @@ public class LazyECPoint {
|
|||
}
|
||||
|
||||
public boolean isCompressed() {
|
||||
if (bits != null)
|
||||
return ECKey.isPubKeyCompressed(bits);
|
||||
else
|
||||
return get().isCompressed();
|
||||
return compressed;
|
||||
}
|
||||
|
||||
public ECPoint multiply(BigInteger k) {
|
||||
|
|
|
@ -79,7 +79,7 @@ public class BloomFilterTest {
|
|||
|
||||
KeyChainGroup group = KeyChainGroup.builder(MAINNET).build();
|
||||
// Add a random key which happens to have been used in a recent generation
|
||||
group.importKeys(ECKey.fromPublicOnly(privKey.getKey().getPubKeyPoint()), ECKey.fromPublicOnly(HEX.decode("03cb219f69f1b49468bd563239a86667e74a06fcba69ac50a08a5cbc42a5808e99")));
|
||||
group.importKeys(ECKey.fromPublicOnly(privKey.getKey()), ECKey.fromPublicOnly(HEX.decode("03cb219f69f1b49468bd563239a86667e74a06fcba69ac50a08a5cbc42a5808e99")));
|
||||
Wallet wallet = new Wallet(MAINNET, group);
|
||||
wallet.commitTx(new Transaction(MAINNET, HEX.decode("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d038754030114062f503253482fffffffff01c05e559500000000232103cb219f69f1b49468bd563239a86667e74a06fcba69ac50a08a5cbc42a5808e99ac00000000")));
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ public class ECKeyTest {
|
|||
// Now re-encode and decode the ASN.1 to see if it is equivalent (it does not produce the exact same byte
|
||||
// sequence, some integers are padded now).
|
||||
ECKey roundtripKey =
|
||||
ECKey.fromPrivateAndPrecalculatedPublic(decodedKey.getPrivKey(), decodedKey.getPubKeyPoint());
|
||||
ECKey.fromPrivateAndPrecalculatedPublic(decodedKey.getPrivKey(), decodedKey.getPubKeyPoint(), decodedKey.isCompressed());
|
||||
|
||||
for (ECKey key : new ECKey[] {decodedKey, roundtripKey}) {
|
||||
byte[] message = reverseBytes(HEX.decode(
|
||||
|
@ -239,7 +239,7 @@ public class ECKeyTest {
|
|||
String message = "Hello World!";
|
||||
Sha256Hash hash = Sha256Hash.of(message.getBytes());
|
||||
ECKey.ECDSASignature sig = key.sign(hash);
|
||||
key = ECKey.fromPublicOnly(key.getPubKeyPoint());
|
||||
key = ECKey.fromPublicOnly(key);
|
||||
|
||||
List<Byte> possibleRecIds = Lists.newArrayList((byte) 0, (byte) 1, (byte) 2, (byte) 3);
|
||||
byte recId = key.findRecoveryId(hash, sig);
|
||||
|
@ -255,13 +255,13 @@ public class ECKeyTest {
|
|||
String message = "Maarten Bodewes generated this test vector on 2016-11-08";
|
||||
Sha256Hash hash = Sha256Hash.of(message.getBytes());
|
||||
ECKey.ECDSASignature sig = key.sign(hash);
|
||||
key = ECKey.fromPublicOnly(key.getPubKeyPoint());
|
||||
key = ECKey.fromPublicOnly(key);
|
||||
|
||||
byte recId = key.findRecoveryId(hash, sig);
|
||||
byte expectedRecId = 0;
|
||||
assertEquals(recId, expectedRecId);
|
||||
|
||||
ECKey pubKey = ECKey.fromPublicOnly(key.getPubKeyPoint());
|
||||
ECKey pubKey = ECKey.fromPublicOnly(key);
|
||||
ECKey recoveredKey = ECKey.recoverFromSignature(recId, sig, hash, true);
|
||||
assertEquals(recoveredKey, pubKey);
|
||||
}
|
||||
|
@ -274,7 +274,7 @@ public class ECKeyTest {
|
|||
ECKey.ECDSASignature sig = key.sign(hash);
|
||||
|
||||
byte recId = key.findRecoveryId(hash, sig);
|
||||
ECKey pubKey = ECKey.fromPublicOnly(key.getPubKeyPoint());
|
||||
ECKey pubKey = ECKey.fromPublicOnly(key);
|
||||
ECKey recoveredKey = ECKey.recoverFromSignature(recId, sig, hash, true);
|
||||
assertEquals(recoveredKey, pubKey);
|
||||
}
|
||||
|
@ -285,7 +285,7 @@ public class ECKeyTest {
|
|||
String message = "Hello World!";
|
||||
Sha256Hash hash = Sha256Hash.of(message.getBytes());
|
||||
ECKey.ECDSASignature sig = key.sign(hash);
|
||||
key = ECKey.fromPublicOnly(key.getPubKeyPoint());
|
||||
key = ECKey.fromPublicOnly(key);
|
||||
boolean found = false;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ECKey key2 = ECKey.recoverFromSignature(i, sig, hash, true);
|
||||
|
@ -384,7 +384,7 @@ public class ECKeyTest {
|
|||
String message = "Goodbye Jupiter!";
|
||||
Sha256Hash hash = Sha256Hash.of(message.getBytes());
|
||||
ECKey.ECDSASignature sig = encryptedKey.sign(hash, aesKey);
|
||||
unencryptedKey = ECKey.fromPublicOnly(unencryptedKey.getPubKeyPoint());
|
||||
unencryptedKey = ECKey.fromPublicOnly(unencryptedKey);
|
||||
boolean found = false;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ECKey key2 = ECKey.recoverFromSignature(i, sig, hash, true);
|
||||
|
@ -520,7 +520,7 @@ public class ECKeyTest {
|
|||
@Test
|
||||
public void testPublicKeysAreEqual() {
|
||||
ECKey key = new ECKey();
|
||||
ECKey pubKey1 = ECKey.fromPublicOnly(key.getPubKeyPoint());
|
||||
ECKey pubKey1 = ECKey.fromPublicOnly(key);
|
||||
assertTrue(pubKey1.isCompressed());
|
||||
ECKey pubKey2 = pubKey1.decompress();
|
||||
assertEquals(pubKey1, pubKey2);
|
||||
|
|
|
@ -91,7 +91,7 @@ public class ScriptTest {
|
|||
Script script = ScriptBuilder.createMultiSigOutputScript(3, keys);
|
||||
assertTrue(ScriptPattern.isSentToMultisig(script));
|
||||
List<ECKey> pubkeys = new ArrayList<>(3);
|
||||
for (ECKey key : keys) pubkeys.add(ECKey.fromPublicOnly(key.getPubKeyPoint()));
|
||||
for (ECKey key : keys) pubkeys.add(ECKey.fromPublicOnly(key));
|
||||
assertEquals(script.getPubKeys(), pubkeys);
|
||||
assertFalse(ScriptPattern.isSentToMultisig(ScriptBuilder.createP2PKOutputScript(new ECKey())));
|
||||
try {
|
||||
|
|
|
@ -233,7 +233,7 @@ public class BasicKeyChainTest {
|
|||
@Test
|
||||
public void watching() throws UnreadableWalletException {
|
||||
ECKey key1 = new ECKey();
|
||||
ECKey pub = ECKey.fromPublicOnly(key1.getPubKeyPoint());
|
||||
ECKey pub = ECKey.fromPublicOnly(key1);
|
||||
chain.importKeys(pub);
|
||||
assertEquals(1, chain.numKeys());
|
||||
List<Protos.Key> keys = chain.serializeToProtobuf();
|
||||
|
|
|
@ -649,7 +649,7 @@ public class KeyChainGroupTest {
|
|||
"xpub69bjfJ91ikC5ghsqsVDHNq2dRGaV2HHVx7Y9LXi27LN9BWWAXPTQr4u8U3wAtap8bLdHdkqPpAcZmhMS5SnrMQC4ccaoBccFhh315P4UYzo",
|
||||
MAINNET)).outputScriptType(Script.ScriptType.P2PKH).build())
|
||||
.build();
|
||||
final ECKey watchingKey = ECKey.fromPublicOnly(new ECKey().getPubKeyPoint());
|
||||
final ECKey watchingKey = ECKey.fromPublicOnly(new ECKey());
|
||||
group.importKeys(watchingKey);
|
||||
assertTrue(group.isWatching());
|
||||
}
|
||||
|
|
|
@ -3197,7 +3197,7 @@ public class WalletTest extends TestWithWallet {
|
|||
public void completeTxPartiallySigned(Wallet.MissingSigsMode missSigMode, byte[] expectedSig) throws Exception {
|
||||
// Check the wallet will write dummy scriptSigs for inputs that we have only pubkeys for without the privkey.
|
||||
ECKey priv = new ECKey();
|
||||
ECKey pub = ECKey.fromPublicOnly(priv.getPubKeyPoint());
|
||||
ECKey pub = ECKey.fromPublicOnly(priv);
|
||||
wallet.importKey(pub);
|
||||
ECKey priv2 = wallet.freshReceiveKey();
|
||||
// Send three transactions, with one being an address type and the other being a raw CHECKSIG type pubkey only,
|
||||
|
|
Loading…
Add table
Reference in a new issue