diff --git a/chainbase/src/main/java/org/tron/common/utils/LocalWitnesses.java b/chainbase/src/main/java/org/tron/common/utils/LocalWitnesses.java index 7d42907e254..9cbdc05c4ab 100644 --- a/chainbase/src/main/java/org/tron/common/utils/LocalWitnesses.java +++ b/chainbase/src/main/java/org/tron/common/utils/LocalWitnesses.java @@ -86,6 +86,9 @@ private void validate(String privateKey) { throw new TronError("private key must be hex string", TronError.ErrCode.WITNESS_INIT); } + if (!ECKey.isValidPrivateKey(ByteArray.fromHexString(privateKey))) { + throw new TronError("Invalid ECDSA witness private key", TronError.ErrCode.WITNESS_INIT); + } } public void addPrivateKeys(String privateKey) { diff --git a/crypto/src/main/java/org/tron/common/crypto/ECKey.java b/crypto/src/main/java/org/tron/common/crypto/ECKey.java index a23933a3a90..f2a59854bc3 100644 --- a/crypto/src/main/java/org/tron/common/crypto/ECKey.java +++ b/crypto/src/main/java/org/tron/common/crypto/ECKey.java @@ -23,15 +23,11 @@ import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; -import java.security.Provider; import java.security.PublicKey; import java.security.SecureRandom; import java.security.SignatureException; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; -import java.util.Objects; import javax.annotation.Nullable; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.asn1.sec.SECNamedCurves; @@ -58,6 +54,14 @@ import org.tron.common.utils.ByteArray; import org.tron.common.utils.ByteUtil; +/** + * A secp256k1 key pair and ECDSA signing utility. + * + *
ECDSA signatures are malleable: for a valid {@code (r, s)} pair, {@code (r, n - s)} is + * valid as well. New signatures are canonicalized to low-S form, but verification accepts both + * forms because transaction identifiers do not include signatures. See BIP-62 for background on + * low-S signatures. + */ @Slf4j(topic = "crypto") public class ECKey implements Serializable, SignInterface { @@ -77,6 +81,9 @@ public class ECKey implements Serializable, SignInterface { */ public static final BigInteger HALF_CURVE_ORDER; + private static final int MAX_PRIVATE_KEY_LENGTH = 32; + private static final int COMPRESSED_PUBLIC_KEY_LENGTH = 33; + private static final int UNCOMPRESSED_PUBLIC_KEY_LENGTH = 65; private static final BigInteger SECP256K1N = new BigInteger("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16); private static final SecureRandom secureRandom; @@ -100,16 +107,10 @@ public class ECKey implements Serializable, SignInterface { // TODO: Redesign this class to use consistent internals and more // efficient serialization. private final PrivateKey privKey; - // the Java Cryptographic Architecture provider to use for Signature - // this is set along with the PrivateKey privKey and must be compatible - // this provider will be used when selecting a Signature instance - // https://docs.oracle.com/javase/8/docs/technotes/guides/security - // /SunProviders.html - private final Provider provider; // Transient because it's calculated on demand. - private transient byte[] pubKeyHash; - private transient byte[] nodeId; + private transient volatile byte[] pubKeyHash; + private transient volatile byte[] nodeId; /** * Generates an entirely new keypair. @@ -121,78 +122,58 @@ public ECKey() { } /** - * Generate a new keypair using the given Java Security Provider. + * Generates an entirely new keypair with the given {@link SecureRandom} object.
BouncyCastle + * will be used as the Java Security Provider * - *
All private key operations will use the provider. + * @param secureRandom - */ - public ECKey(Provider provider, SecureRandom secureRandom) { - this.provider = provider; - - final KeyPairGenerator keyPairGen = ECKeyPairGenerator.getInstance(provider, secureRandom); + public ECKey(SecureRandom secureRandom) { + final KeyPairGenerator keyPairGen = ECKeyPairGenerator.getInstance( + TronCastleProvider.getInstance(), secureRandom); final KeyPair keyPair = keyPairGen.generateKeyPair(); this.privKey = keyPair.getPrivate(); final PublicKey pubKey = keyPair.getPublic(); - if (pubKey instanceof BCECPublicKey) { - pub = ((BCECPublicKey) pubKey).getQ(); - } else if (pubKey instanceof ECPublicKey) { - pub = extractPublicKey((ECPublicKey) pubKey); - } else { + if (!(pubKey instanceof BCECPublicKey)) { throw new AssertionError( - "Expected Provider " + provider.getName() - + " to produce a subtype of ECPublicKey, found " - + pubKey.getClass()); + "Expected Bouncy Castle EC public key, found " + pubKey.getClass()); } + this.pub = ((BCECPublicKey) pubKey).getQ(); } - /** - * Generates an entirely new keypair with the given {@link SecureRandom} object.
BouncyCastle - * will be used as the Java Security Provider - * - * @param secureRandom - - */ - public ECKey(SecureRandom secureRandom) { - this(TronCastleProvider.getInstance(), secureRandom); - } - - /** - * Pair a private key with a public EC point. - * - *
All private key operations will use the provider.
- */
-
public ECKey(byte[] key, boolean isPrivateKey) {
if (isPrivateKey) {
+ check(isValidPrivateKey(key), "Invalid private key");
BigInteger pk = new BigInteger(1, key);
this.privKey = privateKeyFromBigInteger(pk);
this.pub = CURVE.getG().multiply(pk);
} else {
this.privKey = null;
- this.pub = CURVE.getCurve().decodePoint(key);
+ this.pub = decodePublicKey(key);
+ check(isValidPublicPoint(this.pub), "Invalid public key");
}
- this.provider = TronCastleProvider.getInstance();
}
- public ECKey(Provider provider, @Nullable PrivateKey privKey, ECPoint pub) {
- this.provider = provider;
+ private ECKey(BigInteger privateKey) {
+ this.privKey = privateKeyFromBigInteger(privateKey);
+ this.pub = CURVE.getG().multiply(privateKey);
+ }
- if (privKey == null || isECPrivateKey(privKey)) {
+ private ECKey(@Nullable PrivateKey privKey, ECPoint pub) {
+ if (privKey == null || privKey instanceof BCECPrivateKey) {
this.privKey = privKey;
} else {
throw new IllegalArgumentException(
- "Expected EC private key, given a private key object with" +
- " class "
+ "Expected Bouncy Castle EC private key, given a private key object with class "
+ privKey.getClass().toString() +
" and algorithm "
+ privKey.getAlgorithm());
}
- if (pub == null) {
- throw new IllegalArgumentException("Public key may not be null");
- } else {
- this.pub = pub;
- }
+ check(isValidPublicPoint(pub), "Invalid public key");
+ checkPrivateKeyMatchesPublic(privKey, pub);
+ this.pub = pub;
}
/**
@@ -200,32 +181,17 @@ public ECKey(Provider provider, @Nullable PrivateKey privKey, ECPoint pub) {
* Security Provider
*/
public ECKey(@Nullable BigInteger priv, ECPoint pub) {
- this(
- TronCastleProvider.getInstance(),
- privateKeyFromBigInteger(priv),
- pub
- );
+ this(privateKeyFromBigInteger(priv), pub);
}
- /* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint
- */
- private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) {
- final java.security.spec.ECPoint publicPointW = ecPublicKey.getW();
- final BigInteger xCoord = publicPointW.getAffineX();
- final BigInteger yCoord = publicPointW.getAffineY();
-
- return CURVE.getCurve().createPoint(xCoord, yCoord);
- }
-
- /* Test if a generic private key is an EC private key
- *
- * it is not sufficient to check that privKey is a subtype of ECPrivateKey
- * as the SunPKCS11 Provider will return a generic PrivateKey instance
- * a fallback that covers this case is to check the key algorithm
- */
- private static boolean isECPrivateKey(PrivateKey privKey) {
- return privKey instanceof ECPrivateKey || privKey.getAlgorithm()
- .equals("EC");
+ private static void checkPrivateKeyMatchesPublic(
+ @Nullable PrivateKey privateKey, ECPoint publicPoint) {
+ if (privateKey instanceof BCECPrivateKey) {
+ BigInteger privateScalar = ((BCECPrivateKey) privateKey).getD();
+ check(isValidPrivateKey(privateScalar), "Invalid private key");
+ check(CURVE.getG().multiply(privateScalar).equals(publicPoint),
+ "Private key does not match public key");
+ }
}
/* Convert a BigInteger into a PrivateKey object
@@ -234,6 +200,7 @@ private static PrivateKey privateKeyFromBigInteger(BigInteger priv) {
if (priv == null) {
return null;
} else {
+ check(isValidPrivateKey(priv), "Invalid private key");
try {
return ECKeyFactory
.getInstance(TronCastleProvider.getInstance())
@@ -245,6 +212,57 @@ private static PrivateKey privateKeyFromBigInteger(BigInteger priv) {
}
}
+ /**
+ * Returns whether the supplied key bytes represent a valid secp256k1 private scalar.
+ * Accepts unsigned encodings up to 32 bytes and Java {@link BigInteger} encodings with one
+ * leading zero sign byte.
+ */
+ public static boolean isValidPrivateKey(byte[] privateKey) {
+ // BigInteger.toByteArray() prepends a zero sign byte only when the magnitude's high bit is set.
+ return !ByteArray.isEmpty(privateKey)
+ && (privateKey.length <= MAX_PRIVATE_KEY_LENGTH
+ || (privateKey.length == MAX_PRIVATE_KEY_LENGTH + 1
+ && privateKey[0] == 0
+ && (privateKey[1] & 0x80) != 0))
+ && isValidPrivateKey(new BigInteger(1, privateKey));
+ }
+
+ /**
+ * Returns whether the supplied value is in the secp256k1 private-key range {@code [1, n)}.
+ */
+ public static boolean isValidPrivateKey(BigInteger privateKey) {
+ return privateKey != null
+ && privateKey.signum() > 0
+ && privateKey.compareTo(SECP256K1N) < 0;
+ }
+
+ /**
+ * Returns whether the encoded key is a non-infinity point on its elliptic curve.
+ */
+ public static boolean isValidPublicKey(byte[] publicKey) {
+ try {
+ return isValidPublicPoint(decodePublicKey(publicKey));
+ } catch (RuntimeException e) {
+ return false;
+ }
+ }
+
+ private static boolean isValidPublicPoint(ECPoint publicPoint) {
+ return publicPoint != null
+ && CURVE.getCurve().equals(publicPoint.getCurve())
+ && !publicPoint.isInfinity()
+ && publicPoint.isValid();
+ }
+
+ private static ECPoint decodePublicKey(byte[] publicKey) {
+ check(isPubKeyCanonical(publicKey), "Invalid public key");
+ try {
+ return CURVE.getCurve().decodePoint(publicKey);
+ } catch (RuntimeException e) {
+ throw new IllegalArgumentException("Invalid public key", e);
+ }
+ }
+
/**
* 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.
@@ -276,7 +294,8 @@ public static ECPoint decompressPoint(ECPoint compressed) {
* @return -
*/
public static ECKey fromPrivate(BigInteger privKey) {
- return new ECKey(privKey, CURVE.getG().multiply(privKey));
+ check(isValidPrivateKey(privKey), "Invalid private key");
+ return new ECKey(privKey);
}
/**
@@ -286,41 +305,8 @@ public static ECKey fromPrivate(BigInteger privKey) {
* @return -
*/
public static ECKey fromPrivate(byte[] privKeyBytes) {
- if (ByteArray.isEmpty(privKeyBytes)) {
- return null;
- }
- return fromPrivate(new BigInteger(1, privKeyBytes));
- }
-
- /**
- * 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.
- *
- * @param priv -
- * @param pub -
- * @return -
- */
- public static ECKey fromPrivateAndPrecalculatedPublic(BigInteger priv,
- ECPoint pub) {
- return new ECKey(priv, pub);
- }
-
- /**
- * 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 the point will be preserved.
- *
- * @param priv -
- * @param pub -
- * @return -
- */
- public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[]
- pub) {
- check(priv != null, "Private key must not be null");
- check(pub != null, "Public key must not be null");
- return new ECKey(new BigInteger(1, priv), CURVE.getCurve()
- .decodePoint(pub));
+ check(isValidPrivateKey(privKeyBytes), "Invalid private key");
+ return new ECKey(new BigInteger(1, privKeyBytes));
}
/**
@@ -331,7 +317,7 @@ public static ECKey fromPrivateAndPrecalculatedPublic(byte[] priv, byte[]
* @return -
*/
public static ECKey fromPublicOnly(ECPoint pub) {
- return new ECKey(null, pub);
+ return new ECKey((PrivateKey) null, pub);
}
/**
@@ -342,7 +328,7 @@ public static ECKey fromPublicOnly(ECPoint pub) {
* @return -
*/
public static ECKey fromPublicOnly(byte[] pub) {
- return new ECKey(null, CURVE.getCurve().decodePoint(pub));
+ return new ECKey((PrivateKey) null, decodePublicKey(pub));
}
/**
@@ -355,6 +341,7 @@ public static ECKey fromPublicOnly(byte[] pub) {
*/
public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean
compressed) {
+ check(isValidPrivateKey(privKey), "Invalid private key");
ECPoint point = CURVE.getG().multiply(privKey);
return point.getEncoded(compressed);
}
@@ -481,12 +468,15 @@ public static ECKey signatureToKey(byte[] messageHash, String
* @return -
*/
public static boolean isPubKeyCanonical(byte[] pubkey) {
+ if (ByteArray.isEmpty(pubkey)) {
+ return false;
+ }
if (pubkey[0] == 0x04) {
// Uncompressed pubkey
- return pubkey.length == 65;
+ return pubkey.length == UNCOMPRESSED_PUBLIC_KEY_LENGTH;
} else if (pubkey[0] == 0x02 || pubkey[0] == 0x03) {
// Compressed pubkey
- return pubkey.length == 33;
+ return pubkey.length == COMPRESSED_PUBLIC_KEY_LENGTH;
} else {
return false;
}
@@ -669,10 +659,12 @@ public boolean hasPrivKey() {
* @return 21-byte address
*/
public byte[] getAddress() {
- if (pubKeyHash == null) {
- pubKeyHash = Hash.computeAddress(this.pub);
+ byte[] address = pubKeyHash;
+ if (address == null) {
+ address = Hash.computeAddress(this.pub);
+ pubKeyHash = address;
}
- return pubKeyHash;
+ return Arrays.copyOf(address, address.length);
}
@Override
@@ -691,10 +683,12 @@ public byte[] Base64toBytes(String signature) {
* Generates the NodeID based on this key, that is the public key without first format byte
*/
public byte[] getNodeId() {
- if (nodeId == null) {
- nodeId = pubBytesWithoutFormat(this.pub);
+ byte[] id = nodeId;
+ if (id == null) {
+ id = pubBytesWithoutFormat(this.pub);
+ nodeId = id;
}
- return nodeId;
+ return Arrays.copyOf(id, id.length);
}
@@ -744,22 +738,6 @@ public String toString() {
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().
- *
- * @return -
- */
- public String toStringWithPrivate() {
- StringBuilder b = new StringBuilder();
- b.append(toString());
- if (privKey != null && privKey instanceof BCECPrivateKey) {
- b.append(" priv:").append(Hex.toHexString(((BCECPrivateKey)
- privKey).getD().toByteArray()));
- }
- return b.toString();
- }
-
/**
* Signs the given hash and returns the R and S components as BigIntegers and putData them in
* ECDSASignature
@@ -841,8 +819,6 @@ public byte[] getPrivKeyBytes() {
return null;
} else if (privKey instanceof BCECPrivateKey) {
return ByteUtil.bigIntegerToBytes(((BCECPrivateKey) privKey).getD(), 32);
- } else if (privKey instanceof ECPrivateKey) {
- return ByteUtil.bigIntegerToBytes(((ECPrivateKey) privKey).getS(), 32);
} else {
return null;
}
@@ -860,10 +836,7 @@ public boolean equals(Object o) {
ECKey ecKey = (ECKey) o;
- if (privKey != null && !privKey.equals(ecKey.privKey)) {
- return false;
- }
- return pub == null || pub.equals(ecKey.pub);
+ return pub.equals(ecKey.pub);
}
@Override
diff --git a/crypto/src/main/java/org/tron/common/crypto/Hash.java b/crypto/src/main/java/org/tron/common/crypto/Hash.java
index eca769a8dad..7af0f2c06fb 100644
--- a/crypto/src/main/java/org/tron/common/crypto/Hash.java
+++ b/crypto/src/main/java/org/tron/common/crypto/Hash.java
@@ -68,8 +68,8 @@ public class Hash {
private static final int SIZE_THRESHOLD = 56;
static {
- Security.addProvider(TronCastleProvider.getInstance());
- CRYPTO_PROVIDER = Security.getProvider("BC");
+ CRYPTO_PROVIDER = TronCastleProvider.getInstance();
+ Security.addProvider(CRYPTO_PROVIDER);
HASH_256_ALGORITHM_NAME = "TRON-KECCAK-256";
HASH_512_ALGORITHM_NAME = "TRON-KECCAK-512";
EMPTY_TRIE_HASH = sha3(encodeElement(EMPTY_BYTE_ARRAY));
diff --git a/crypto/src/main/java/org/tron/common/crypto/jce/TronCastleProvider.java b/crypto/src/main/java/org/tron/common/crypto/jce/TronCastleProvider.java
index ad8e51e65ac..b65317deda9 100644
--- a/crypto/src/main/java/org/tron/common/crypto/jce/TronCastleProvider.java
+++ b/crypto/src/main/java/org/tron/common/crypto/jce/TronCastleProvider.java
@@ -19,7 +19,6 @@
package org.tron.common.crypto.jce;
import java.security.Provider;
-import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.tron.common.crypto.cryptohash.Keccak256;
import org.tron.common.crypto.cryptohash.Keccak512;
@@ -35,9 +34,8 @@ private static class Holder {
private static final Provider INSTANCE;
static {
- Provider p = Security.getProvider("BC");
-
- INSTANCE = (p != null) ? p : new BouncyCastleProvider();
+ // Use our own instance instead of trusting a globally registered provider named BC.
+ INSTANCE = new BouncyCastleProvider();
INSTANCE.put("MessageDigest.TRON-KECCAK-256", Keccak256.class.getName());
INSTANCE.put("MessageDigest.TRON-KECCAK-512", Keccak512.class.getName());
}
diff --git a/crypto/src/main/java/org/tron/keystore/Wallet.java b/crypto/src/main/java/org/tron/keystore/Wallet.java
index a1f74c7891b..d0051764490 100644
--- a/crypto/src/main/java/org/tron/keystore/Wallet.java
+++ b/crypto/src/main/java/org/tron/keystore/Wallet.java
@@ -207,31 +207,48 @@ public static SignInterface decrypt(String password, WalletFile walletFile)
throw new CipherException("Unable to deserialize params: " + crypto.getKdf());
}
- byte[] derivedMac = generateMac(derivedKey, cipherText);
+ byte[] encryptKey = null;
+ byte[] privateKey = null;
+ try {
+ byte[] derivedMac = generateMac(derivedKey, cipherText);
- if (!java.security.MessageDigest.isEqual(derivedMac, mac)) {
- throw new CipherException("Invalid password provided");
- }
+ if (!java.security.MessageDigest.isEqual(derivedMac, mac)) {
+ throw new CipherException("Invalid password provided");
+ }
- byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16);
- byte[] privateKey = performCipherOperation(Cipher.DECRYPT_MODE, iv, encryptKey, cipherText);
-
- SignInterface keyPair = SignUtils.fromPrivate(privateKey);
-
- // Enforce address consistency: if the keystore declares an address, it MUST match
- // the address derived from the decrypted private key. Prevents address spoofing
- // where a crafted keystore displays one address but encrypts a different key.
- String declared = walletFile.getAddress();
- if (declared != null && !declared.isEmpty()) {
- String derived = StringUtil.encode58Check(keyPair.getAddress());
- if (!declared.equals(derived)) {
- throw new CipherException(
- "Keystore address mismatch: file declares " + declared
- + " but private key derives " + derived);
+ encryptKey = Arrays.copyOfRange(derivedKey, 0, 16);
+ privateKey = performCipherOperation(Cipher.DECRYPT_MODE, iv, encryptKey, cipherText);
+
+ SignInterface keyPair;
+ try {
+ keyPair = SignUtils.fromPrivate(privateKey);
+ } catch (IllegalArgumentException e) {
+ throw new CipherException("Invalid private key in keystore", e);
}
- }
- return keyPair;
+ // Enforce address consistency: if the keystore declares an address, it MUST match
+ // the address derived from the decrypted private key. Prevents address spoofing
+ // where a crafted keystore displays one address but encrypts a different key.
+ String declared = walletFile.getAddress();
+ if (declared != null && !declared.isEmpty()) {
+ String derived = StringUtil.encode58Check(keyPair.getAddress());
+ if (!declared.equals(derived)) {
+ throw new CipherException(
+ "Keystore address mismatch: file declares " + declared
+ + " but private key derives " + derived);
+ }
+ }
+
+ return keyPair;
+ } finally {
+ Arrays.fill(derivedKey, (byte) 0);
+ if (encryptKey != null) {
+ Arrays.fill(encryptKey, (byte) 0);
+ }
+ if (privateKey != null) {
+ Arrays.fill(privateKey, (byte) 0);
+ }
+ }
}
/**
diff --git a/framework/src/test/java/org/tron/common/crypto/ECKeyTest.java b/framework/src/test/java/org/tron/common/crypto/ECKeyTest.java
index 273672e8342..f847ef30bd8 100644
--- a/framework/src/test/java/org/tron/common/crypto/ECKeyTest.java
+++ b/framework/src/test/java/org/tron/common/crypto/ECKeyTest.java
@@ -5,17 +5,16 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.tron.common.utils.client.utils.AbiUtil.generateOccupationConstantPrivateKey;
import java.math.BigInteger;
-import java.security.KeyPairGenerator;
-import java.security.Security;
import java.security.SignatureException;
import java.util.Arrays;
import lombok.extern.slf4j.Slf4j;
+import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.util.encoders.Hex;
import org.junit.Test;
import org.tron.common.crypto.ECKey.ECDSASignature;
@@ -69,10 +68,76 @@ public void testFromPrivateKey() {
assertTrue(key.hasPrivKey());
assertArrayEquals(pubKey, key.getPubKey());
- key = ECKey.fromPrivate((byte[]) null);
- assertNull(key);
- key = ECKey.fromPrivate(new byte[0]);
- assertNull(key);
+ assertThrows(IllegalArgumentException.class,
+ () -> ECKey.fromPrivate((byte[]) null));
+ assertThrows(IllegalArgumentException.class,
+ () -> ECKey.fromPrivate(new byte[0]));
+ }
+
+ @Test
+ public void testValidatePrivateKey() {
+ assertTrue(ECKey.isValidPrivateKey(privateKey));
+ assertTrue(ECKey.isValidPrivateKey(Hex.decode(privString)));
+ assertFalse(ECKey.isValidPrivateKey((BigInteger) null));
+ assertFalse(ECKey.isValidPrivateKey((byte[]) null));
+ assertFalse(ECKey.isValidPrivateKey(new byte[0]));
+ assertFalse(ECKey.isValidPrivateKey(new byte[33]));
+ assertFalse(ECKey.isValidPrivateKey(BigInteger.ZERO));
+ assertFalse(ECKey.isValidPrivateKey(ECKey.CURVE.getN()));
+
+ BigInteger highBitPrivateKey = ECKey.CURVE.getN().subtract(BigInteger.ONE);
+ byte[] signPaddedPrivateKey = highBitPrivateKey.toByteArray();
+ assertEquals(33, signPaddedPrivateKey.length);
+ assertEquals(0, signPaddedPrivateKey[0]);
+ assertTrue(ECKey.isValidPrivateKey(signPaddedPrivateKey));
+ assertEquals(highBitPrivateKey, ECKey.fromPrivate(signPaddedPrivateKey).getPrivKey());
+
+ byte[] redundantSignPaddedPrivateKey = new byte[33];
+ redundantSignPaddedPrivateKey[32] = 1;
+ assertFalse(ECKey.isValidPrivateKey(redundantSignPaddedPrivateKey));
+
+ byte[] nonZeroLeadingPrivateKey = Arrays.copyOf(signPaddedPrivateKey,
+ signPaddedPrivateKey.length);
+ nonZeroLeadingPrivateKey[0] = 1;
+ assertFalse(ECKey.isValidPrivateKey(nonZeroLeadingPrivateKey));
+
+ byte[] doubleSignPaddedPrivateKey = new byte[34];
+ System.arraycopy(signPaddedPrivateKey, 0, doubleSignPaddedPrivateKey, 1,
+ signPaddedPrivateKey.length);
+ assertFalse(ECKey.isValidPrivateKey(doubleSignPaddedPrivateKey));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> ECKey.fromPrivate(BigInteger.ZERO));
+ assertThrows(IllegalArgumentException.class,
+ () -> ECKey.fromPrivate(ECKey.CURVE.getN()));
+ assertThrows(IllegalArgumentException.class,
+ () -> ECKey.fromPrivate(new byte[1024 * 1024]));
+ assertThrows(IllegalArgumentException.class,
+ () -> ECKey.publicKeyFromPrivate(null, false));
+ }
+
+ @Test
+ public void testRejectInvalidPublicKey() {
+ assertTrue(ECKey.isValidPublicKey(pubKey));
+ assertTrue(ECKey.isValidPublicKey(compressedPubKey));
+ assertFalse(ECKey.isValidPublicKey(null));
+ assertFalse(ECKey.isValidPublicKey(new byte[0]));
+ assertFalse(ECKey.isValidPublicKey(new byte[]{0}));
+ assertFalse(ECKey.isValidPublicKey(new byte[66]));
+
+ byte[] oversizedPublicKey = new byte[1024 * 1024];
+ oversizedPublicKey[0] = 0x04;
+ assertFalse(ECKey.isValidPublicKey(oversizedPublicKey));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> ECKey.fromPublicOnly((byte[]) null));
+ assertThrows(IllegalArgumentException.class,
+ () -> ECKey.fromPublicOnly(ECKey.CURVE.getCurve().getInfinity()));
+ assertThrows(IllegalArgumentException.class,
+ () -> ECKey.fromPublicOnly(
+ ECKey.CURVE.getCurve().createPoint(BigInteger.ZERO, BigInteger.ZERO)));
+ assertThrows(IllegalArgumentException.class,
+ () -> ECKey.fromPublicOnly(SECNamedCurves.getByName("secp256r1").getG()));
}
@Test(expected = IllegalArgumentException.class)
@@ -81,12 +146,21 @@ public void testPrivatePublicKeyBytesNoArg() {
fail("Expecting an IllegalArgumentException for using only null-parameters");
}
- @Test(expected = IllegalArgumentException.class)
- public void testInvalidPrivateKey() throws Exception {
- new ECKey(Security.getProvider("SunEC"),
- KeyPairGenerator.getInstance("RSA").generateKeyPair().getPrivate(),
- ECKey.fromPublicOnly(pubKey).getPubKeyPoint());
- fail("Expecting an IllegalArgumentException for using an non EC private key");
+ @Test
+ public void testRejectMismatchedKeyPair() {
+ BigInteger otherPrivateKey = privateKey.add(BigInteger.ONE);
+ ECKey otherKey = ECKey.fromPrivate(otherPrivateKey);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new ECKey(privateKey, otherKey.getPubKeyPoint()));
+ }
+
+ @Test
+ public void testAcceptMatchingKeyPair() {
+ ECKey key = new ECKey(privateKey, ECKey.CURVE.getG().multiply(privateKey));
+
+ assertArrayEquals(pubKey, key.getPubKey());
+ assertTrue(key.hasPrivKey());
}
@Test
@@ -201,6 +275,8 @@ public void testIsPubKeyCanonicalWrongPrefix() {
// Test wrong prefix 3, right length 33
byte[] nonCanonicalPubkey6 = new byte[33];
assertFalse(ECKey.isPubKeyCanonical(nonCanonicalPubkey6));
+ assertFalse(ECKey.isPubKeyCanonical(null));
+ assertFalse(ECKey.isPubKeyCanonical(new byte[0]));
}
@Test
@@ -215,10 +291,28 @@ public void testEqualsObject() {
ECKey key0 = new ECKey();
ECKey key1 = ECKey.fromPrivate(privateKey);
ECKey key2 = ECKey.fromPrivate(privateKey);
+ ECKey publicOnlyKey = ECKey.fromPublicOnly(key1.getPubKey());
assertFalse(key0.equals(key1));
assertTrue(key1.equals(key1));
assertTrue(key1.equals(key2));
+ assertTrue(key1.equals(publicOnlyKey));
+ assertTrue(publicOnlyKey.equals(key1));
+ assertEquals(key1.hashCode(), publicOnlyKey.hashCode());
+ }
+
+ @Test
+ public void testDefensiveCopy() {
+ ECKey key = ECKey.fromPrivate(privateKey);
+ byte[] expectedAddress = Arrays.copyOf(key.getAddress(), key.getAddress().length);
+ byte[] returnedAddress = key.getAddress();
+ returnedAddress[0] ^= 1;
+ assertArrayEquals(expectedAddress, key.getAddress());
+
+ byte[] expectedNodeId = Arrays.copyOf(key.getNodeId(), key.getNodeId().length);
+ byte[] returnedNodeId = key.getNodeId();
+ returnedNodeId[0] ^= 1;
+ assertArrayEquals(expectedNodeId, key.getNodeId());
}
diff --git a/framework/src/test/java/org/tron/common/crypto/jce/TronCastleProviderTest.java b/framework/src/test/java/org/tron/common/crypto/jce/TronCastleProviderTest.java
new file mode 100644
index 00000000000..81fd0e3b952
--- /dev/null
+++ b/framework/src/test/java/org/tron/common/crypto/jce/TronCastleProviderTest.java
@@ -0,0 +1,98 @@
+package org.tron.common.crypto.jce;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.MessageDigest;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.util.Arrays;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.util.encoders.Hex;
+import org.junit.Test;
+import org.tron.common.crypto.Hash;
+import org.tron.common.crypto.cryptohash.Keccak512;
+
+public class TronCastleProviderTest {
+
+ @Test
+ public void testExistingProvider() throws Exception {
+ assertProviderIsolation(new Provider("BC", 1.0, "Unrelated test provider") { });
+ }
+
+ @Test
+ public void testNoRegisteredProvider() throws Exception {
+ assertProviderIsolation(null);
+ }
+
+ private void assertProviderIsolation(Provider existing) throws Exception {
+ Provider original = Security.getProvider("BC");
+ int position = Arrays.asList(Security.getProviders()).indexOf(original) + 1;
+ Security.removeProvider("BC");
+ try (URLClassLoader loader = newCryptoClassLoader()) {
+ if (existing != null) {
+ Security.addProvider(existing);
+ }
+ Class> providerClass = loader.loadClass(TronCastleProvider.class.getName());
+ Provider provider = (Provider) providerClass.getMethod("getInstance").invoke(null);
+ assertEquals(BouncyCastleProvider.class, provider.getClass());
+ assertNotSame(existing, provider);
+ assertSame(provider, providerClass.getMethod("getInstance").invoke(null));
+ assertSame(existing, Security.getProvider("BC"));
+
+ byte[] empty = new byte[0];
+ byte[] expected = Hex.decode(
+ "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");
+ assertArrayEquals(expected,
+ MessageDigest.getInstance("TRON-KECCAK-256", provider).digest(empty));
+ assertArrayEquals(new Keccak512().digest(empty),
+ MessageDigest.getInstance("TRON-KECCAK-512", provider).digest(empty));
+ assertNotNull(ECKeyPairGenerator.getInstance(provider, new SecureRandom()).generateKeyPair());
+
+ Class> hashClass = loader.loadClass(Hash.class.getName());
+ assertArrayEquals(expected,
+ (byte[]) hashClass.getMethod("sha3", byte[].class).invoke(null, empty));
+ if (existing != null) {
+ assertSame(existing, Security.getProvider("BC"));
+ assertNull(existing.get("MessageDigest.TRON-KECCAK-256"));
+ assertNull(existing.get("MessageDigest.TRON-KECCAK-512"));
+ }
+ } finally {
+ Security.removeProvider("BC");
+ if (original != null) {
+ Security.insertProviderAt(original, position);
+ }
+ }
+ }
+
+ private URLClassLoader newCryptoClassLoader() {
+ URL location = TronCastleProvider.class.getProtectionDomain().getCodeSource().getLocation();
+ // Reload static initializers regardless of which crypto tests ran earlier in this JVM.
+ return new URLClassLoader(new URL[]{location}, getClass().getClassLoader()) {
+ @Override
+ protected synchronized Class> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException {
+ if (name.equals(Hash.class.getName())
+ || name.equals(TronCastleProvider.class.getName())
+ || name.startsWith(TronCastleProvider.class.getName() + "$")) {
+ Class> loaded = findLoadedClass(name);
+ if (loaded == null) {
+ loaded = findClass(name);
+ }
+ if (resolve) {
+ resolveClass(loaded);
+ }
+ return loaded;
+ }
+ return super.loadClass(name, resolve);
+ }
+ };
+ }
+}
diff --git a/framework/src/test/java/org/tron/core/config/args/LocalWitnessTest.java b/framework/src/test/java/org/tron/core/config/args/LocalWitnessTest.java
index 28feadbd1db..1b613dc3320 100644
--- a/framework/src/test/java/org/tron/core/config/args/LocalWitnessTest.java
+++ b/framework/src/test/java/org/tron/core/config/args/LocalWitnessTest.java
@@ -22,7 +22,9 @@
import com.google.common.collect.Lists;
import java.io.IOException;
+import java.math.BigInteger;
import java.security.SecureRandom;
+import java.util.Arrays;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
@@ -30,6 +32,7 @@
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.tron.common.TestConstants;
+import org.tron.common.crypto.ECKey;
import org.tron.common.utils.ByteArray;
import org.tron.common.utils.LocalWitnesses;
import org.tron.common.utils.PublicMethod;
@@ -132,6 +135,22 @@ public void testInvalidPrivateKey() {
assertTronError(localWitnesses, privateKey2, expectedMessage2);
}
+ @Test
+ public void testPrivateKeyRange() {
+ for (BigInteger scalar : Arrays.asList(BigInteger.ZERO, ECKey.CURVE.getN())) {
+ String key = String.format("%064x", scalar);
+ assertTronError(localWitness, key, "Invalid ECDSA witness private key");
+ TronError error = assertThrows(TronError.class,
+ () -> localWitness.setPrivateKeys(Arrays.asList(PRIVATE_KEY, key)));
+ assertEquals(ErrCode.WITNESS_INIT, error.getErrCode());
+ }
+ for (BigInteger scalar : Arrays.asList(BigInteger.ONE,
+ ECKey.CURVE.getN().subtract(BigInteger.ONE))) {
+ String key = String.format("%064x", scalar);
+ assertEquals(key, new LocalWitnesses(key).getPrivateKey());
+ }
+ }
+
private void assertTronError(LocalWitnesses localWitnesses, String privateKey,
String expectedMessage) {
TronError thrown = assertThrows(TronError.class,
diff --git a/framework/src/test/java/org/tron/core/config/args/WitnessInitializerKeystoreTest.java b/framework/src/test/java/org/tron/core/config/args/WitnessInitializerKeystoreTest.java
index 2ee4331b99b..988f20c7acb 100644
--- a/framework/src/test/java/org/tron/core/config/args/WitnessInitializerKeystoreTest.java
+++ b/framework/src/test/java/org/tron/core/config/args/WitnessInitializerKeystoreTest.java
@@ -5,6 +5,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
@@ -12,19 +14,25 @@
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
+import java.math.BigInteger;
import java.security.SecureRandom;
+import java.util.Collections;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import org.mockito.MockedStatic;
import org.slf4j.LoggerFactory;
+import org.tron.common.crypto.ECKey;
import org.tron.common.crypto.SignInterface;
import org.tron.common.crypto.SignUtils;
import org.tron.common.utils.ByteArray;
import org.tron.common.utils.LocalWitnesses;
+import org.tron.core.exception.CipherException;
import org.tron.core.exception.TronError;
import org.tron.keystore.Credentials;
+import org.tron.keystore.Wallet;
import org.tron.keystore.WalletFile;
import org.tron.keystore.WalletUtils;
@@ -183,6 +191,25 @@ public void testTamperedKeystoreRejectedAtSrLoading() throws Exception {
}
}
+ @Test
+ public void testInvalidPrivateKeyLoadError() throws Exception {
+ // Only mock the source of bytes for encryption. Decryption and key validation are real.
+ SignInterface invalidKey = mock(SignInterface.class);
+ when(invalidKey.getPrivateKey()).thenReturn(new byte[32]);
+ when(invalidKey.getAddress()).thenReturn(ECKey.fromPrivate(BigInteger.ONE).getAddress());
+ WalletFile walletFile = Wallet.createLight(PASSWORD, invalidKey);
+ String fileName = DIR_NAME + "/invalid-key.json";
+ new ObjectMapper().writeValue(new File(System.getProperty("user.dir"), fileName), walletFile);
+
+ TronError err = assertThrows(TronError.class,
+ () -> WitnessInitializer.initFromKeystore(
+ Collections.singletonList(fileName), PASSWORD, null));
+ assertEquals(TronError.ErrCode.WITNESS_KEYSTORE_LOAD, err.getErrCode());
+ assertTrue(err.getCause() instanceof CipherException);
+ assertEquals("Invalid private key in keystore", err.getCause().getMessage());
+ assertTrue(err.getCause().getCause() instanceof IllegalArgumentException);
+ }
+
private static ListAppender