diff --git a/common/src/main/java/org/tron/common/parameter/CommonParameter.java b/common/src/main/java/org/tron/common/parameter/CommonParameter.java index a73158a718a..f853dfa648f 100644 --- a/common/src/main/java/org/tron/common/parameter/CommonParameter.java +++ b/common/src/main/java/org/tron/common/parameter/CommonParameter.java @@ -335,7 +335,7 @@ public class CommonParameter { @Getter @Setter - public boolean allowShieldedTransactionApi; // clearParam: true + public boolean allowShieldedTransactionApi; // clearParam: false @Getter @Setter public long blockNumForEnergyLimit; 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 d0a6048aca1..5117df337dc 100644 --- a/crypto/src/main/java/org/tron/common/crypto/ECKey.java +++ b/crypto/src/main/java/org/tron/common/crypto/ECKey.java @@ -31,7 +31,6 @@ 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; @@ -68,17 +67,21 @@ public class ECKey implements Serializable, SignInterface { public static final ECParameterSpec CURVE_SPEC; /** - * Equal to CURVE.getN().shiftRight(1), used for canonicalising the S value of a signature. ECDSA - * signatures are mutable in the sense that for a given (R, S) pair, then both (R, S) and (R, N - - * S mod N) are valid signatures. Canonical signatures are those where 1 <= S <= N/2 - * - *

See https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki - * #Low_S_values_in_signatures + * ECDSA signatures are mutable: for a given (R, S) pair, both (R, S) and (R, N - S mod N) are + * valid. Canonical signatures satisfy 1 <= S <= N/2, where N is the curve order (SECP256K1N). + *

+ * Reference: + * https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#Low_S_values_in_signatures + *

+ * For the TRON network, since the transaction ID does not include the signature and can still + * guarantee the transaction uniqueness, it is not necessary to strictly enforce signature + * canonicalization. Signature verification accepts both low-S and high-S forms. + *

+ * Note: While not enforced by the protocol, using low-S signatures is recommended to prevent + * signature malleability. */ - public static final BigInteger HALF_CURVE_ORDER; - private static final BigInteger SECP256K1N = - new BigInteger("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16); + private static final BigInteger SECP256K1N; private static final SecureRandom secureRandom; private static final long serialVersionUID = -728224901792295832L; @@ -89,6 +92,7 @@ public class ECKey implements Serializable, SignInterface { params.getN(), params.getH()); CURVE_SPEC = new ECParameterSpec(params.getCurve(), params.getG(), params.getN(), params.getH()); + SECP256K1N = params.getN(); HALF_CURVE_ORDER = params.getN().shiftRight(1); secureRandom = new SecureRandom(); } @@ -108,13 +112,14 @@ public class ECKey implements Serializable, SignInterface { 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. * - *

BouncyCastle will be used as the Java Security Provider + *

+ * BouncyCastle will be used as the Java Security Provider */ public ECKey() { this(secureRandom); @@ -123,7 +128,8 @@ public ECKey() { /** * Generate a new keypair using the given Java Security Provider. * - *

All private key operations will use the provider. + *

+ * All private key operations will use the provider. */ public ECKey(Provider provider, SecureRandom secureRandom) { this.provider = provider; @@ -147,8 +153,9 @@ public ECKey(Provider provider, SecureRandom secureRandom) { } /** - * Generates an entirely new keypair with the given {@link SecureRandom} object.

BouncyCastle - * will be used as the Java Security Provider + * Generates an entirely new keypair with the given {@link SecureRandom} object. + *

+ * BouncyCastle will be used as the Java Security Provider * * @param secureRandom - */ @@ -159,15 +166,22 @@ public ECKey(SecureRandom secureRandom) { /** * Pair a private key with a public EC point. * - *

All private key operations will use the provider. + *

+ * All private key operations will use the provider. */ public ECKey(byte[] key, boolean isPrivateKey) { if (isPrivateKey) { + if (!isValidPrivateKey(key)) { + throw new IllegalArgumentException("Invalid private key."); + } BigInteger pk = new BigInteger(1, key); this.privKey = privateKeyFromBigInteger(pk); this.pub = CURVE.getG().multiply(pk); } else { + if (!isValidPublicKey(key)) { + throw new IllegalArgumentException("Invalid public key."); + } this.privKey = null; this.pub = CURVE.getCurve().decodePoint(key); } @@ -183,31 +197,32 @@ public ECKey(Provider provider, @Nullable PrivateKey privKey, ECPoint pub) { throw new IllegalArgumentException( "Expected EC private key, given a private key object with" + " class " - + privKey.getClass().toString() + + + privKey.getClass() + " and algorithm " + privKey.getAlgorithm()); } if (pub == null) { - throw new IllegalArgumentException("Public key may not be null"); + throw new IllegalArgumentException("Public key should not be null"); } else { this.pub = pub; } } /** - * Pair a private key integer with a public EC point

BouncyCastle will be used as the Java - * Security Provider + * Pair a private key integer with a public EC point + *

+ * BouncyCastle will be used as the Java Security Provider */ public ECKey(@Nullable BigInteger priv, ECPoint pub) { this( TronCastleProvider.getInstance(), - privateKeyFromBigInteger(priv), - pub - ); + priv == null ? null : privateKeyFromBigInteger(priv), + pub); } - /* Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint + /* + * Convert a Java JCE ECPublicKey into a BouncyCastle ECPoint */ private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { final java.security.spec.ECPoint publicPointW = ecPublicKey.getW(); @@ -217,7 +232,8 @@ private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { return CURVE.getCurve().createPoint(xCoord, yCoord); } - /* Test if a generic private key is an EC private key + /* + * 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 @@ -228,20 +244,50 @@ private static boolean isECPrivateKey(PrivateKey privKey) { .equals("EC"); } - /* Convert a BigInteger into a PrivateKey object + /* + * Convert a BigInteger into a PrivateKey object */ private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { - if (priv == null) { - return null; - } else { - try { - return ECKeyFactory - .getInstance(TronCastleProvider.getInstance()) - .generatePrivate(new ECPrivateKeySpec(priv, - CURVE_SPEC)); - } catch (InvalidKeySpecException ex) { - throw new AssertionError("Assumed correct key spec statically"); - } + if (!isValidPrivateKey(priv)) { + throw new IllegalArgumentException("Invalid private key."); + } + + try { + return ECKeyFactory + .getInstance(TronCastleProvider.getInstance()) + .generatePrivate(new ECPrivateKeySpec(priv, + CURVE_SPEC)); + } catch (InvalidKeySpecException ex) { + throw new AssertionError("Assumed correct key spec statically"); + } + } + + public static boolean isValidPrivateKey(byte[] keyBytes) { + if (ByteArray.isEmpty(keyBytes)) { + return false; + } + + BigInteger key = new BigInteger(1, keyBytes); + return key.compareTo(BigInteger.ONE) >= 0 && key.compareTo(CURVE.getN()) < 0; + } + + public static boolean isValidPrivateKey(BigInteger privateKey) { + if (privateKey == null) { + return false; + } + return privateKey.compareTo(BigInteger.ONE) >= 0 && privateKey.compareTo(CURVE.getN()) < 0; + } + + public static boolean isValidPublicKey(byte[] keyBytes) { + if (ByteArray.isEmpty(keyBytes)) { + return false; + } + + try { + ECPoint point = CURVE.getCurve().decodePoint(keyBytes); + return !point.isInfinity() && point.isValid(); + } catch (RuntimeException e) { + return false; } } @@ -276,6 +322,10 @@ public static ECPoint decompressPoint(ECPoint compressed) { * @return - */ public static ECKey fromPrivate(BigInteger privKey) { + if (!isValidPrivateKey(privKey)) { + throw new IllegalArgumentException("Invalid private key."); + } + return new ECKey(privKey, CURVE.getG().multiply(privKey)); } @@ -286,54 +336,12 @@ public static ECKey fromPrivate(BigInteger privKey) { * @return - */ public static ECKey fromPrivate(byte[] privKeyBytes) { - if (ByteArray.isEmpty(privKeyBytes)) { - return null; + if (!isValidPrivateKey(privKeyBytes)) { + throw new IllegalArgumentException("Invalid private key."); } 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)); - } - - /** - * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given - * point. The compression state of pub will be preserved. - * - * @param pub - - * @return - - */ - public static ECKey fromPublicOnly(ECPoint pub) { - return new ECKey(null, pub); - } - /** * Creates an ECKey that cannot be used for signing, only verifying signatures, from the given * encoded point. The compression state of pub will be preserved. @@ -349,19 +357,19 @@ public static ECKey fromPublicOnly(byte[] pub) { * Returns public key bytes from the given private key. To convert a byte array into a BigInteger, * use new BigInteger(1, bytes); * - * @param privKey - + * @param privKey - * @param compressed - * @return - */ - public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean - compressed) { + public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean compressed) { ECPoint point = CURVE.getG().multiply(privKey); return point.getEncoded(compressed); } /** - * Compute the encoded X, Y coordinates of a public point.

This is the encoded public key - * without the leading byte. + * Compute the encoded X, Y coordinates of a public point. + *

+ * This is the encoded public key without the leading byte. * * @param pubPoint a public point * @return 64-byte X,Y point pair @@ -384,14 +392,13 @@ public static ECKey fromNodeId(byte[] nodeId) { return ECKey.fromPublicOnly(pubBytes); } - public static byte[] signatureToKeyBytes(byte[] messageHash, String - signatureBase64) throws SignatureException { + public static byte[] signatureToKeyBytes(byte[] messageHash, String signatureBase64) + throws SignatureException { byte[] signatureEncoded; try { signatureEncoded = Base64.decode(signatureBase64); } catch (RuntimeException e) { - // This is what you getData back from Bouncy Castle if base64 doesn't - // decode :( + // This is what you getData back from Bouncy Castle if base64 doesn't decode throw new SignatureException("Could not decode base64", e); } // Parse the signature bytes into r/s and the selector value. @@ -415,7 +422,7 @@ public static byte[] signatureToKeyBytes(byte[] messageHash, int header = sig.v; // The header byte: 0x1B = first key with even y, 0x1C = first key // with odd y, - // 0x1D = second key with even y, 0x1E = second key + // 0x1D = second key with even y, 0x1E = second key // with odd y if (header < 27 || header > 34) { throw new SignatureException("Header byte out of range: " + header); @@ -436,12 +443,12 @@ public static byte[] signatureToKeyBytes(byte[] messageHash, /** * Compute the address of the key that signed the given signature. * - * @param messageHash 32-byte hash of message + * @param messageHash 32-byte hash of message * @param signatureBase64 Base-64 encoded signature * @return 20-byte address */ - public static byte[] signatureToAddress(byte[] messageHash, String - signatureBase64) throws SignatureException { + public static byte[] signatureToAddress(byte[] messageHash, String signatureBase64) + throws SignatureException { return Hash.computeAddress(signatureToKeyBytes(messageHash, signatureBase64)); } @@ -450,24 +457,23 @@ public static byte[] signatureToAddress(byte[] messageHash, String * Compute the address of the key that signed the given signature. * * @param messageHash 32-byte hash of message - * @param sig - + * @param sig - * @return 20-byte address */ public static byte[] signatureToAddress(byte[] messageHash, - ECDSASignature sig) throws - SignatureException { + ECDSASignature sig) throws SignatureException { return Hash.computeAddress(signatureToKeyBytes(messageHash, sig)); } /** * Compute the key that signed the given signature. * - * @param messageHash 32-byte hash of message + * @param messageHash 32-byte hash of message * @param signatureBase64 Base-64 encoded signature * @return ECKey */ - public static ECKey signatureToKey(byte[] messageHash, String - signatureBase64) throws SignatureException { + public static ECKey signatureToKey(byte[] messageHash, String signatureBase64) + throws SignatureException { final byte[] keyBytes = signatureToKeyBytes(messageHash, signatureBase64); return ECKey.fromPublicOnly(keyBytes); @@ -493,25 +499,14 @@ public static boolean isPubKeyCanonical(byte[] pubkey) { } /** - *

Given the components of a signature and a selector value, recover and return the public key - * that generated the signature according to the algorithm in SEC1v2 section 4.1.6.

- * - *

The recId is an index from 0 to 3 which indicates which of the 4 possible allKeys is - * the - * correct one. Because the key recovery operation yields multiple potential allKeys, the correct - * key must either be stored alongside the signature, or you must be willing to try each recId in - * turn until you find one that outputs the key you are expecting.

- * - *

If this method returns null it means recovery was not possible and recId should be - * iterated.

+ * Recover the public key from a signature, per SEC1v2 section 4.1.6. * - *

Given the above two points, a correct usage of this method is inside a for loop from 0 - * to 3, and if the output is null OR a key that is not the one you expect, you try again with the - * next recId.

+ *

recId (0–3) selects which of the candidate keys to return. Iterate recId in a loop and + * retry if the result is null or not the expected key. * - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. - * @param messageHash Hash of the data that was signed. + * @param recId which possible key to recover + * @param sig the R and S components of the signature, wrapped + * @param messageHash hash of the signed data * @return 65-byte encoded public key */ @Nullable @@ -521,26 +516,26 @@ public static byte[] recoverPubBytesFromSignature(int recId, check(sig.r.signum() >= 0, "r must be positive"); check(sig.s.signum() >= 0, "s must be positive"); check(messageHash != null, "messageHash must not be null"); - // 1.0 For j from 0 to h (h == recId here and the loop is outside + // 1.0 For j from 0 to h (h == recId here and the loop is outside // this function) - // 1.1 Let x = r + jn - BigInteger n = CURVE.getN(); // Curve order. + // 1.1 Let x = r + jn + BigInteger n = CURVE.getN(); // Curve order. BigInteger i = BigInteger.valueOf((long) recId / 2); BigInteger x = sig.r.add(i.multiply(n)); - // 1.2. Convert the integer x to an octet string X of length mlen + // 1.2. Convert the integer x to an octet string X of length mlen // using the conversion routine - // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or // mlen = ⌈m/8⌉. - // 1.3. Convert the octet string (16 set binary digits)||X to an + // 1.3. Convert the octet string (16 set binary digits)||X to an // elliptic curve point R using the - // conversion routine specified in Section 2.3.4. If this + // conversion routine specified in Section 2.3.4. If this // conversion routine outputs “invalid”, then - // do another iteration of Step 1. + // do another iteration of Step 1. // // More concisely, what these points mean is to use X as a compressed // public key. ECCurve.Fp curve = (ECCurve.Fp) CURVE.getCurve(); - BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent + BigInteger prime = curve.getQ(); // Bouncy Castle is not consistent // about the letter it uses for the prime. if (x.compareTo(prime) >= 0) { // Cannot have point co-ordinates larger than this as everything @@ -551,22 +546,22 @@ public static byte[] recoverPubBytesFromSignature(int recId, // y-coord as there are two possibilities. // So it's encoded in the recId. ECPoint R = decompressKey(x, (recId & 1) == 1); - // 1.4. If nR != point at infinity, then do another iteration of + // 1.4. If nR != point at infinity, then do another iteration of // Step 1 (callers responsibility). if (!R.multiply(n).isInfinity()) { return null; } - // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature + // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature // verification. BigInteger e = new BigInteger(1, messageHash); - // 1.6. For k from 1 to 2 do the following. (loop is outside this + // 1.6. For k from 1 to 2 do the following. (loop is outside this // function via iterating recId) - // 1.6.1. Compute a candidate public key as: - // Q = mi(r) * (sR - eG) + // 1.6.1. Compute a candidate public key as: + // Q = mi(r) * (sR - eG) // // Where mi(x) is the modular multiplicative inverse. We transform // this into the following: - // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) + // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) // Where -e is the modular additive inverse of e, that is z such that // z + e = 0 (mod n). In the above equation // ** is point multiplication and + is point addition (the EC group @@ -586,8 +581,8 @@ public static byte[] recoverPubBytesFromSignature(int recId, } /** - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. * @param messageHash Hash of the data that was signed. * @return 20-byte address */ @@ -604,8 +599,8 @@ public static byte[] recoverAddressFromSignature(int recId, } /** - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. * @param messageHash Hash of the data that was signed. * @return ECKey */ @@ -624,7 +619,7 @@ public static ECKey recoverFromSignature(int recId, ECDSASignature sig, /** * Decompress a compressed public key (x co-ord and low-bit of y-coord). * - * @param xBN - + * @param xBN - * @param yBit - * @return - */ @@ -654,8 +649,8 @@ public boolean isPubKeyOnly() { } /** - * Returns true if this key has access to private key bytes. Does the opposite of {@link - * #isPubKeyOnly()}. + * Returns true if this key has access to private key bytes. Does the opposite of + * {@link #isPubKeyOnly()}. * * @return - */ @@ -672,7 +667,7 @@ public byte[] getAddress() { if (pubKeyHash == null) { pubKeyHash = Hash.computeAddress(this.pub); } - return pubKeyHash; + return Arrays.copyOf(pubKeyHash, pubKeyHash.length); } @Override @@ -694,10 +689,9 @@ public byte[] getNodeId() { if (nodeId == null) { nodeId = pubBytesWithoutFormat(this.pub); } - return nodeId; + return Arrays.copyOf(nodeId, nodeId.length); } - @Override public byte[] getPrivateKey() { return getPrivKeyBytes(); @@ -739,25 +733,7 @@ public BigInteger getPrivKey() { } public String toString() { - StringBuilder b = new StringBuilder(); - b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); - 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(); + return "pub:" + Hex.toHexString(pub.getEncoded(false)); } /** @@ -777,10 +753,9 @@ public ECDSASignature doSign(byte[] input) { throw new MissingPrivateKeyException(); } if (privKey instanceof BCECPrivateKey) { - ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new - SHA256Digest())); - ECPrivateKeyParameters privKeyParams = new ECPrivateKeyParameters - (((BCECPrivateKey) privKey).getD(), CURVE); + ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); + ECPrivateKeyParameters privKeyParams = new ECPrivateKeyParameters( + ((BCECPrivateKey) privKey).getD(), CURVE); signer.init(true, privKeyParams); BigInteger[] components = signer.generateSignature(input); return new ECDSASignature(components[0], components[1]) @@ -818,7 +793,6 @@ public ECDSASignature sign(byte[] messageHash) { return sig; } - /** * Returns true if this pubkey is canonical, i.e. the correct length taking into account * compression. @@ -913,8 +887,7 @@ private static ECDSASignature fromComponents(byte[] r, byte[] s) { * @param v - * @return - */ - public static ECDSASignature fromComponents(byte[] r, byte[] s, byte - v) { + public static ECDSASignature fromComponents(byte[] r, byte[] s, byte v) { ECDSASignature signature = fromComponents(r, s); signature.v = v; return signature; @@ -940,7 +913,6 @@ public static boolean validateComponents(BigInteger r, BigInteger s, return BIUtil.isLessThan(s, SECP256K1N); } - public boolean validateComponents() { return validateComponents(r, s, v); } @@ -951,10 +923,10 @@ public ECDSASignature toCanonicalised() { // exist on that curve. If S is in the upper // half of the number of valid points, then bring it back to // the lower half. Otherwise, imagine that - // N = 10 - // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) + // N = 10 + // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) // are valid solutions. - // 10 - 8 == 2, giving us always the latter solution, + // 10 - 8 == 2, giving us always the latter solution, // which is canonical. return new ECDSASignature(r, CURVE.getN().subtract(s)); } else { @@ -966,7 +938,7 @@ public ECDSASignature toCanonicalised() { * @return - */ public String toBase64() { - byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 + byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 // bytes for S sigData[0] = v; System.arraycopy(ByteUtil.bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); @@ -974,7 +946,6 @@ public String toBase64() { return new String(Base64.encode(sigData), Charset.forName("UTF-8")); } - public byte[] toByteArray() { final byte fixedV = this.v >= 27 ? (byte) (this.v - 27) diff --git a/crypto/src/main/java/org/tron/common/crypto/Rsv.java b/crypto/src/main/java/org/tron/common/crypto/Rsv.java index 15c8498e836..e37be7d8be5 100644 --- a/crypto/src/main/java/org/tron/common/crypto/Rsv.java +++ b/crypto/src/main/java/org/tron/common/crypto/Rsv.java @@ -15,6 +15,10 @@ public class Rsv { public static Rsv fromSignature(byte[] sign) { + if (sign == null || sign.length < 65) { + throw new IllegalArgumentException( + "Invalid signature length: " + (sign == null ? "null" : sign.length)); + } byte[] r = Arrays.copyOfRange(sign, 0, 32); byte[] s = Arrays.copyOfRange(sign, 32, 64); byte v = sign[64]; diff --git a/crypto/src/main/java/org/tron/common/crypto/SignUtils.java b/crypto/src/main/java/org/tron/common/crypto/SignUtils.java index b921d548e8b..2c2519dd931 100644 --- a/crypto/src/main/java/org/tron/common/crypto/SignUtils.java +++ b/crypto/src/main/java/org/tron/common/crypto/SignUtils.java @@ -23,6 +23,13 @@ public static SignInterface fromPrivate(byte[] privKeyBytes, boolean isECKeyCryp return SM2.fromPrivate(privKeyBytes); } + public static boolean isValidPrivateKey(byte[] privKeyBytes, boolean isECKeyCryptoEngine) { + if (isECKeyCryptoEngine) { + return ECKey.isValidPrivateKey(privKeyBytes); + } + return SM2.isValidPrivateKey(privKeyBytes); + } + public static byte[] signatureToAddress( byte[] messageHash, String signatureBase64, boolean isECKeyCryptoEngine) throws SignatureException { diff --git a/crypto/src/main/java/org/tron/common/crypto/sm2/SM2.java b/crypto/src/main/java/org/tron/common/crypto/sm2/SM2.java index b1d349efad3..f3a0254f075 100644 --- a/crypto/src/main/java/org/tron/common/crypto/sm2/SM2.java +++ b/crypto/src/main/java/org/tron/common/crypto/sm2/SM2.java @@ -4,15 +4,14 @@ import static org.tron.common.utils.BIUtil.isLessThan; import static org.tron.common.utils.ByteUtil.bigIntegerToBytes; -import java.io.IOException; import java.io.Serializable; +import java.io.IOException; import java.math.BigInteger; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.PrivateKey; 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; @@ -20,7 +19,8 @@ import lombok.extern.slf4j.Slf4j; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Integer; -import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.ASN1Primitive; +import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.x9.X9IntegerConverter; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.generators.ECKeyPairGenerator; @@ -41,6 +41,7 @@ import org.tron.common.crypto.SignatureInterface; import org.tron.common.crypto.jce.ECKeyFactory; import org.tron.common.crypto.jce.TronCastleProvider; +import org.tron.common.utils.BIUtil; import org.tron.common.utils.ByteArray; import org.tron.common.utils.ByteUtil; @@ -50,63 +51,62 @@ @Slf4j(topic = "crypto") public class SM2 implements Serializable, SignInterface { - private static BigInteger SM2_N = new BigInteger( + private static final BigInteger SM2_N = new BigInteger( "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16); - private static BigInteger SM2_P = new BigInteger( + private static final BigInteger SM2_P = new BigInteger( "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", 16); - private static BigInteger SM2_A = new BigInteger( + private static final BigInteger SM2_A = new BigInteger( "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", 16); - private static BigInteger SM2_B = new BigInteger( + private static final BigInteger SM2_B = new BigInteger( "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", 16); - private static BigInteger SM2_GX = new BigInteger( + private static final BigInteger SM2_GX = new BigInteger( "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16); - private static BigInteger SM2_GY = new BigInteger( + private static final BigInteger SM2_GY = new BigInteger( "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16); - private static ECDomainParameters ecc_param; - private static ECParameterSpec ecc_spec; - private static ECCurve.Fp curve; - private static ECPoint ecc_point_g; + private static final ECDomainParameters eccParam; + private static final ECParameterSpec eccSpec; + private static final ECCurve.Fp curve; + private static final ECPoint eccPointG; private static final SecureRandom secureRandom; - static { secureRandom = new SecureRandom(); curve = new ECCurve.Fp(SM2_P, SM2_A, SM2_B, null, null); - ecc_point_g = curve.createPoint(SM2_GX, SM2_GY); - ecc_param = new ECDomainParameters(curve, ecc_point_g, SM2_N); - ecc_spec = new ECParameterSpec(curve, ecc_point_g, SM2_N); + eccPointG = curve.createPoint(SM2_GX, SM2_GY); + eccParam = new ECDomainParameters(curve, eccPointG, SM2_N); + eccSpec = new ECParameterSpec(curve, eccPointG, SM2_N); } protected final ECPoint pub; private final PrivateKey privKey; - // 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; public SM2() { this(secureRandom); } + /** * Generates an entirely new keypair. * - *

BouncyCastle will be used as the Java Security Provider + *

+ * BouncyCastle will be used as the Java Security Provider */ - /** * Generate a new keypair using the given Java Security Provider. * - *

All private key operations will use the provider. + *

+ * All private key operations will use the provider. */ public SM2(SecureRandom secureRandom) { - ECKeyGenerationParameters ecKeyGenerationParameters = new ECKeyGenerationParameters(ecc_param, + ECKeyGenerationParameters ecKeyGenerationParameters = new ECKeyGenerationParameters(eccParam, secureRandom); ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); keyPairGenerator.init(ecKeyGenerationParameters); @@ -121,37 +121,48 @@ public SM2(SecureRandom secureRandom) { public SM2(byte[] key, boolean isPrivateKey) { if (isPrivateKey) { + if (!isValidPrivateKey(key)) { + throw new IllegalArgumentException("Invalid private key in SM2."); + } + BigInteger pk = new BigInteger(1, key); this.privKey = privateKeyFromBigInteger(pk); - this.pub = ecc_param.getG().multiply(pk); + this.pub = eccParam.getG().multiply(pk); } else { + if (ByteArray.isEmpty(key)) { + throw new IllegalArgumentException("Empty public key in SM2."); + } + + ECPoint point = eccParam.getCurve().decodePoint(key); + if (point.isInfinity() || !point.isValid()) { + throw new IllegalArgumentException("Public key is not a valid point on SM2 curve."); + } this.privKey = null; - this.pub = ecc_param.getCurve().decodePoint(key); + this.pub = point; } } - /** * Pair a private key with a public EC point. * - *

All private key operations will use the provider. + *

+ * All private key operations will use the provider. */ public SM2(@Nullable PrivateKey privKey, ECPoint pub) { - if (privKey == null || isECPrivateKey(privKey)) { this.privKey = privKey; } else { throw new IllegalArgumentException( "Expected EC private key, given a private key object with" + " class " - + privKey.getClass().toString() + + + privKey.getClass() + " and algorithm " + privKey.getAlgorithm()); } if (pub == null) { - throw new IllegalArgumentException("Public key may not be null"); + throw new IllegalArgumentException("Public key should not be null"); } else { this.pub = pub; } @@ -162,30 +173,30 @@ public SM2(@Nullable PrivateKey privKey, ECPoint pub) { */ public SM2(@Nullable BigInteger priv, ECPoint pub) { this( - privateKeyFromBigInteger(priv), - pub - ); + priv == null ? null : privateKeyFromBigInteger(priv), + pub); } /** * Convert a BigInteger into a PrivateKey object */ private static PrivateKey privateKeyFromBigInteger(BigInteger priv) { - if (priv == null) { - return null; - } else { - try { - return ECKeyFactory - .getInstance(TronCastleProvider.getInstance()) - .generatePrivate(new ECPrivateKeySpec(priv, - ecc_spec)); - } catch (InvalidKeySpecException ex) { - throw new AssertionError("Assumed correct key spec statically"); - } + if (!isValidPrivateKey(priv)) { + throw new IllegalArgumentException("Invalid private key in SM2."); + } + + try { + return ECKeyFactory + .getInstance(TronCastleProvider.getInstance()) + .generatePrivate(new ECPrivateKeySpec(priv, + eccSpec)); + } catch (InvalidKeySpecException ex) { + throw new AssertionError("Assumed correct key spec statically"); } } - /* Test if a generic private key is an EC private key + /* + * 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 @@ -196,19 +207,9 @@ private static boolean isECPrivateKey(PrivateKey privKey) { .equals("EC"); } - /* 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 ecc_param.getCurve().createPoint(xCoord, yCoord); - } - - /** - * Utility for compressing an elliptic curve point. Returns the same point if it's already + * 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. * * @param uncompressed - @@ -216,11 +217,12 @@ private static ECPoint extractPublicKey(final ECPublicKey ecPublicKey) { * @deprecated per-point compression property will be removed in Bouncy Castle */ public static ECPoint compressPoint(ECPoint uncompressed) { - return ecc_param.getCurve().decodePoint(uncompressed.getEncoded(true)); + return eccParam.getCurve().decodePoint(uncompressed.getEncoded(true)); } /** - * Utility for decompressing an elliptic curve point. Returns the same point if it's already + * 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. * * @param compressed - @@ -228,7 +230,7 @@ public static ECPoint compressPoint(ECPoint uncompressed) { * @deprecated per-point compression property will be removed in Bouncy Castle */ public static ECPoint decompressPoint(ECPoint compressed) { - return ecc_param.getCurve().decodePoint(compressed.getEncoded(false)); + return eccParam.getCurve().decodePoint(compressed.getEncoded(false)); } /** @@ -238,7 +240,11 @@ public static ECPoint decompressPoint(ECPoint compressed) { * @return - */ public static SM2 fromPrivate(BigInteger privKey) { - return new SM2(privKey, ecc_param.getG().multiply(privKey)); + if (!isValidPrivateKey(privKey)) { + throw new IllegalArgumentException("Invalid private key in SM2."); + } + + return new SM2(privKey, eccParam.getG().multiply(privKey)); } /** @@ -248,81 +254,59 @@ public static SM2 fromPrivate(BigInteger privKey) { * @return - */ public static SM2 fromPrivate(byte[] privKeyBytes) { - if (ByteArray.isEmpty(privKeyBytes)) { - return null; + if (!isValidPrivateKey(privKeyBytes)) { + throw new IllegalArgumentException("Invalid private key in SM2."); } + return fromPrivate(new BigInteger(1, privKeyBytes)); } - /** - * Creates an SM2 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 SM2 fromPrivateAndPrecalculatedPublic(BigInteger priv, - ECPoint pub) { - return new SM2(priv, pub); - } + public static boolean isValidPrivateKey(byte[] keyBytes) { + if (ByteArray.isEmpty(keyBytes)) { + return false; + } - /** - * Creates an SM2 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 SM2 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 SM2(new BigInteger(1, priv), ecc_param.getCurve() - .decodePoint(pub)); + BigInteger key = new BigInteger(1, keyBytes); + return key.compareTo(BigInteger.ONE) >= 0 && key.compareTo(SM2_N) < 0; } - /** - * Creates an SM2 that cannot be used for signing, only verifying signatures, from the given - * point. The compression state of pub will be preserved. - * - * @param pub - - * @return - - */ - public static SM2 fromPublicOnly(ECPoint pub) { - return new SM2((PrivateKey) null, pub); + public static boolean isValidPrivateKey(BigInteger privateKey) { + if (privateKey == null) { + return false; + } + return privateKey.compareTo(BigInteger.ONE) >= 0 && privateKey.compareTo(SM2_N) < 0; } /** - * Creates an SM2 that cannot be used for signing, only verifying signatures, from the given + * Creates an SM2 that cannot be used for signing, only verifying signatures, + * from the given * encoded point. The compression state of pub will be preserved. * * @param pub - * @return - */ public static SM2 fromPublicOnly(byte[] pub) { - return new SM2((PrivateKey) null, ecc_param.getCurve().decodePoint(pub)); + return new SM2((PrivateKey) null, eccParam.getCurve().decodePoint(pub)); } /** - * Returns public key bytes from the given private key. To convert a byte array into a BigInteger, + * Returns public key bytes from the given private key. To convert a byte array + * into a BigInteger, * use new BigInteger(1, bytes); * - * @param privKey - + * @param privKey - * @param compressed - * @return - */ - public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean - compressed) { - ECPoint point = ecc_param.getG().multiply(privKey); + public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean compressed) { + ECPoint point = eccParam.getG().multiply(privKey); return point.getEncoded(compressed); } /** - * Compute the encoded X, Y coordinates of a public point.

This is the encoded public key + * Compute the encoded X, Y coordinates of a public point. + *

+ * This is the encoded public key * without the leading byte. * * @param pubPoint a public point @@ -339,6 +323,9 @@ public static byte[] pubBytesWithoutFormat(ECPoint pubPoint) { * @param nodeId a 64-byte X,Y point pair */ public static SM2 fromNodeId(byte[] nodeId) { + if (nodeId == null) { + throw new IllegalArgumentException("Node ID cannot be null"); + } check(nodeId.length == 64, "Expected a 64 byte node id"); byte[] pubBytes = new byte[65]; System.arraycopy(nodeId, 0, pubBytes, 1, nodeId.length); @@ -346,8 +333,10 @@ public static SM2 fromNodeId(byte[] nodeId) { return SM2.fromPublicOnly(pubBytes); } - public static byte[] signatureToKeyBytes(byte[] messageHash, String - signatureBase64) throws SignatureException { + public static byte[] signatureToKeyBytes(byte[] messageHash, String signatureBase64) throws SignatureException { + if (messageHash == null || signatureBase64 == null) { + throw new SignatureException("Message hash or signature cannot be null"); + } byte[] signatureEncoded; try { signatureEncoded = Base64.decode(signatureBase64); @@ -371,14 +360,16 @@ public static byte[] signatureToKeyBytes(byte[] messageHash, String } public static byte[] signatureToKeyBytes(byte[] messageHash, - SM2Signature sig) throws - SignatureException { + SM2Signature sig) throws SignatureException { + if (messageHash == null || sig == null) { + throw new SignatureException("Message hash or signature cannot be null"); + } check(messageHash.length == 32, "messageHash argument has length " + messageHash.length); int header = sig.v; // The header byte: 0x1B = first key with even y, 0x1C = first key // with odd y, - // 0x1D = second key with even y, 0x1E = second key + // 0x1D = second key with even y, 0x1E = second key // with odd y if (header < 27 || header > 34) { throw new SignatureException("Header byte out of range: " + header); @@ -396,7 +387,6 @@ public static byte[] signatureToKeyBytes(byte[] messageHash, return key; } - public byte[] hash(byte[] message) { SM2Signer signer = this.getSM2SignerForHash(); return signer.generateSM3Hash(message); @@ -427,19 +417,17 @@ public byte[] getAddress() { if (pubKeyHash == null) { pubKeyHash = computeAddress(this.pub); } - return pubKeyHash; + return Arrays.copyOf(pubKeyHash, pubKeyHash.length); } - /** * Compute the address of the key that signed the given signature. * - * @param messageHash 32-byte hash of message + * @param messageHash 32-byte hash of message * @param signatureBase64 Base-64 encoded signature * @return 20-byte address */ - public static byte[] signatureToAddress(byte[] messageHash, String - signatureBase64) throws SignatureException { + public static byte[] signatureToAddress(byte[] messageHash, String signatureBase64) throws SignatureException { return computeAddress(signatureToKeyBytes(messageHash, signatureBase64)); } @@ -448,24 +436,22 @@ public static byte[] signatureToAddress(byte[] messageHash, String * Compute the address of the key that signed the given signature. * * @param messageHash 32-byte hash of message - * @param sig - + * @param sig - * @return 20-byte address */ public static byte[] signatureToAddress(byte[] messageHash, - SM2Signature sig) throws - SignatureException { + SM2Signature sig) throws SignatureException { return computeAddress(signatureToKeyBytes(messageHash, sig)); } /** * Compute the key that signed the given signature. * - * @param messageHash 32-byte hash of message + * @param messageHash 32-byte hash of message * @param signatureBase64 Base-64 encoded signature * @return ECKey */ - public static SM2 signatureToKey(byte[] messageHash, String - signatureBase64) throws SignatureException { + public static SM2 signatureToKey(byte[] messageHash, String signatureBase64) throws SignatureException { final byte[] keyBytes = signatureToKeyBytes(messageHash, signatureBase64); return fromPublicOnly(keyBytes); @@ -475,54 +461,39 @@ public static SM2 signatureToKey(byte[] messageHash, String * Compute the key that signed the given signature. * * @param messageHash 32-byte hash of message - * @param sig - + * @param sig - * @return ECKey */ - public static SM2 signatureToKey(byte[] messageHash, SM2Signature - sig) throws SignatureException { + public static SM2 signatureToKey(byte[] messageHash, SM2Signature sig) throws SignatureException { final byte[] keyBytes = signatureToKeyBytes(messageHash, sig); return fromPublicOnly(keyBytes); } /** - * Takes the SM3 hash (32 bytes) of data and returns the SM2 signature which including the v + * Takes the SM3 hash (32 bytes) of data and returns the SM2 signature which + * including the v * * @param messageHash - * @return - * @throws IllegalStateException if this ECKey does not have the private part. */ public SM2Signature sign(byte[] messageHash) { - if (messageHash.length != 32) { + if (ByteArray.isEmpty(messageHash) || messageHash.length != 32) { throw new IllegalArgumentException("Expected 32 byte input to " + - "SM2 signature, not " + messageHash.length); + "SM2 signature, not " + (messageHash == null ? "null" : messageHash.length)); } // No decryption of private key required. SM2Signer signer = getSigner(); BigInteger[] componets = signer.generateHashSignature(messageHash); SM2Signature sig = new SM2Signature(componets[0], componets[1]); - // Now we have to work backwards to figure out the recId needed to - // recover the signature. - int recId = -1; - byte[] thisKey = this.pub.getEncoded(/* compressed */ false); - for (int i = 0; i < 4; i++) { - byte[] k = recoverPubBytesFromSignature(i, sig, messageHash); - if (k != null && Arrays.equals(k, thisKey)) { - recId = i; - break; - } - } - if (recId == -1) { - throw new RuntimeException("Could not construct a recoverable key" + - ". This should never happen."); - } - sig.v = (byte) (recId + 27); + sig.v = (byte) (findRecId(sig, messageHash) + 27); return sig; } /** - * Signs the given hash and returns the R and S components as BigIntegers and putData them in - * SM2Signature + * Signs the given hash and returns the R and S components as BigIntegers and + * putData them in SM2Signature * * @param input to sign * @return SM2Signature signature that contains the R and S components @@ -547,37 +518,22 @@ public byte[] Base64toBytes(String signature) { */ public SM2Signature signMessage(byte[] message, @Nullable String userID) { SM2Signature sig = signMsg(message, userID); - // Now we have to work backwards to figure out the recId needed to - // recover the signature. - int recId = -1; - byte[] thisKey = this.pub.getEncoded(/* compressed */ false); SM2Signer signer = getSigner(); byte[] messageHash = signer.generateSM3Hash(message); - for (int i = 0; i < 4; i++) { - byte[] k = recoverPubBytesFromSignature(i, sig, messageHash); - if (k != null && Arrays.equals(k, thisKey)) { - recId = i; - break; - } - } - if (recId == -1) { - throw new RuntimeException("Could not construct a recoverable key" + - ". This should never happen."); - } - sig.v = (byte) (recId + 27); + sig.v = (byte) (findRecId(sig, messageHash) + 27); return sig; } /** - * Signs the given hash and returns the R and S components as BigIntegers and putData them in - * SM2Signature + * Signs the given hash and returns the R and S components as BigIntegers and + * putData them in SM2Signature * * @param msg to sign * @return SM2Signature signature that contains the R and S components */ public SM2Signature signMsg(byte[] msg, @Nullable String userID) { - if (null == msg) { + if (msg == null) { throw new IllegalArgumentException("Expected signature message of " + "SM2 is null"); } @@ -587,10 +543,22 @@ public SM2Signature signMsg(byte[] msg, @Nullable String userID) { return new SM2Signature(componets[0], componets[1]); } + private int findRecId(SM2Signature sig, byte[] messageHash) { + byte[] thisKey = this.pub.getEncoded(/* compressed */ false); + for (int i = 0; i < 4; i++) { + byte[] k = recoverPubBytesFromSignature(i, sig, messageHash); + if (k != null && Arrays.equals(k, thisKey)) { + return i; + } + } + throw new RuntimeException("Could not construct a recoverable key" + + ". This should never happen."); + } + private SM2Signer getSigner() { SM2Signer signer = new SM2Signer(); BigInteger d = getPrivKey(); - ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(d, ecc_param); + ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(d, eccParam); signer.init(true, privateKeyParameters); return signer; } @@ -600,49 +568,51 @@ private SM2Signer getSigner() { */ public SM2Signer getSM2SignerForHash() { SM2Signer signer = new SM2Signer(); - ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pub, ecc_param); + ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pub, eccParam); signer.init(false, publicKeyParameters); return signer; } - /** - *

Given the components of a signature and a selector value, recover and return the public key - * that generated the signature + *

+ * Given the components of a signature and a selector value, recover and return + * the public key that generated the signature */ @Nullable public static byte[] recoverPubBytesFromSignature(int recId, SM2Signature sig, byte[] messageHash) { - check(recId >= 0, "recId must be positive"); - check(sig.r.signum() >= 0, "r must be positive"); - check(sig.s.signum() >= 0, "s must be positive"); - check(messageHash != null, "messageHash must not be null"); - // 1.0 For j from 0 to h (h == recId here and the loop is outside + if (sig == null || messageHash == null) { + throw new IllegalArgumentException("Signature or message hash cannot be null"); + } + check(recId >= 0 && recId <= 3, "recId must be in range [0, 3]"); + check(sig.r != null && sig.r.signum() > 0 && BIUtil.isLessThan(sig.r, SM2_N), + "r must be in range (0, n)"); + check(sig.s != null && sig.s.signum() > 0 && BIUtil.isLessThan(sig.s, SM2_N), + "s must be in range (0, n)"); + check(messageHash.length == 32, "messageHash must be 32 bytes"); + // 1.0 For j from 0 to h (h == recId here and the loop is outside // this function) - // 1.1 Let x = r + jn - BigInteger n = ecc_param.getN(); // Curve order. + // 1.1 Let x = r + jn + BigInteger n = eccParam.getN(); // Curve order. BigInteger prime = curve.getQ(); BigInteger i = BigInteger.valueOf((long) recId / 2); BigInteger e = new BigInteger(1, messageHash); - BigInteger x = sig.r.subtract(e).mod(n); // r = (x + e) mod n + BigInteger x = sig.r.subtract(e).mod(n); // r = (x + e) mod n x = x.add(i.multiply(n)); - // 1.2. Convert the integer x to an octet string X of length mlen + // 1.2. Convert the integer x to an octet string X of length mlen // using the conversion routine - // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or // mlen = ⌈m/8⌉. - // 1.3. Convert the octet string (16 set binary digits)||X to an + // 1.3. Convert the octet string (16 set binary digits)||X to an // elliptic curve point R using the - // conversion routine specified in Section 2.3.4. If this + // conversion routine specified in Section 2.3.4. If this // conversion routine outputs “invalid”, then - // do another iteration of Step 1. + // do another iteration of Step 1. // // More concisely, what these points mean is to use X as a compressed // public key. - ECCurve.Fp curve = (ECCurve.Fp) ecc_param.getCurve(); - // Bouncy Castle is not consistent - // about the letter it uses for the prime. if (x.compareTo(prime) >= 0) { // Cannot have point co-ordinates larger than this as everything // takes place modulo Q. @@ -652,18 +622,22 @@ public static byte[] recoverPubBytesFromSignature(int recId, // y-coord as there are two possibilities. // So it's encoded in the recId. ECPoint R = decompressKey(x, (recId & 1) == 1); - // 1.4. If nR != point at infinity, then do another iteration of + // 1.4. If nR != point at infinity, then do another iteration of // Step 1 (callers responsibility). if (!R.multiply(n).isInfinity()) { return null; } - // recover Q from the formula: s*G + (s+r)*Q = R => Q = (s+r)^(-1) (R-s*G) - BigInteger srInv = sig.s.add(sig.r).modInverse(n); + // recover Q from the formula: s*G + (s+r)*Q = R => Q = (s+r)^(-1) (R-s*G) + BigInteger sAddR = sig.s.add(sig.r).mod(n); + if (sAddR.equals(BigInteger.ZERO)) { + return null; + } + BigInteger srInv = sAddR.modInverse(n); BigInteger sNeg = BigInteger.ZERO.subtract(sig.s).mod(n); BigInteger coeff = srInv.multiply(sNeg).mod(n); - ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(ecc_param + ECPoint.Fp q = (ECPoint.Fp) ECAlgorithms.sumOfTwoMultiplies(eccParam .getG(), coeff, R, srInv); return q.getEncoded(/* compressed */ false); } @@ -671,17 +645,17 @@ public static byte[] recoverPubBytesFromSignature(int recId, /** * Decompress a compressed public key (x co-ord and low-bit of y-coord). * - * @param xBN - + * @param xBN - * @param yBit - * @return - */ private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { X9IntegerConverter x9 = new X9IntegerConverter(); - byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(ecc_param + byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(eccParam .getCurve())); compEnc[0] = (byte) (yBit ? 0x03 : 0x02); - return ecc_param.getCurve().decodePoint(compEnc); + return eccParam.getCurve().decodePoint(compEnc); } private static void check(boolean test, String message) { @@ -691,20 +665,29 @@ private static void check(boolean test, String message) { } /** - *

Verifies the given SM2 signature against the message bytes using the public key bytes.

- *

When using native SM2 verification, data must be 32 bytes, and no element may be - * larger than 520 bytes.

+ *

+ * Verifies the given SM2 signature against the message bytes using the public + * key bytes. + *

+ *

+ *

+ * When using native SM2 verification, data must be 32 bytes, and no element may + * be larger than 520 bytes. + *

* - * @param data Hash of the data to verify. + * @param data Hash of the data to verify. * @param signature signature. - * @param pub The public key bytes to use. + * @param pub The public key bytes to use. * @return - */ public static boolean verify(byte[] data, SM2Signature signature, byte[] pub) { + if (ByteArray.isEmpty(data) || signature == null || ByteArray.isEmpty(pub)) { + throw new IllegalArgumentException("Data, signature, or public key cannot be null"); + } SM2Signer signer = new SM2Signer(); - ECPublicKeyParameters params = new ECPublicKeyParameters(ecc_param - .getCurve().decodePoint(pub), ecc_param); + ECPublicKeyParameters params = new ECPublicKeyParameters(eccParam + .getCurve().decodePoint(pub), eccParam); signer.init(false, params); try { return signer.verifyHashSignature(data, signature.r, signature.s); @@ -719,60 +702,65 @@ public static boolean verify(byte[] data, SM2Signature signature, } /** - * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. + * Verifies a DER-encoded SM2 signature against the provided hash using the + * public key bytes. * - * @param data Hash of the data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - + * @param data hash of the data to verify + * @param signature DER-encoded signature + * @param pub public key bytes to use + * @return false when the signature is malformed or invalid */ public static boolean verify(byte[] data, byte[] signature, byte[] pub) { - return verify(data, SM2Signature.decodeFromDER(signature), pub); + if (data == null || signature == null || pub == null) { + return false; + } + try { + return verify(data, SM2Signature.decodeFromDER(signature), pub); + } catch (IllegalArgumentException | SignatureException e) { + return false; + } } /** - *

Verifies the given SM2 signature against the message bytes using the public key bytes. + * Verifies a DER-encoded SM2 signature against the message bytes using the + * public key bytes. * - * @param msg the message data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - + * @param message message bytes to verify + * @param signature DER-encoded signature + * @param pub public key bytes to use + * @return false when the signature is malformed or invalid */ - public static boolean verifyMessage(byte[] msg, SM2Signature signature, - byte[] pub, @Nullable String userID) { - SM2Signer signer = new SM2Signer(); - ECPublicKeyParameters params = new ECPublicKeyParameters(ecc_param - .getCurve().decodePoint(pub), ecc_param); - signer.init(false, params); - try { - return signer.verifySignature(msg, signature.r, signature.s, userID); - } catch (NullPointerException npe) { - // Bouncy Castle contains a bug that can cause NPEs given - // specially crafted signatures. - // Those signatures are inherently invalid/attack sigs so we just - // fail them here rather than crash the thread. - logger.error("Caught NPE inside bouncy castle", npe); - return false; - } + public static boolean verifyMessage(byte[] message, byte[] signature, byte[] pub) { + return verifyMessage(message, signature, pub, null); } /** - * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. + * Verifies a DER-encoded SM2 signature against the message bytes using the + * public key bytes. * - * @param msg the message data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - + * @param message message bytes to verify + * @param signature DER-encoded signature + * @param pub public key bytes to use + * @param userID optional user identifier + * @return false when the signature is malformed or invalid */ - public static boolean verifyMessage(byte[] msg, byte[] signature, byte[] pub, + public static boolean verifyMessage(byte[] message, byte[] signature, byte[] pub, @Nullable String userID) { - return verifyMessage(msg, SM2Signature.decodeFromDER(signature), pub, userID); + if (message == null || signature == null || pub == null) { + return false; + } + try { + SM2 key = SM2.fromPublicOnly(pub); + byte[] messageHash = key.getSM2SignerForHash().generateSM3Hash(message); + return verify(messageHash, SM2Signature.decodeFromDER(signature), pub); + } catch (IllegalArgumentException | SignatureException e) { + return false; + } } - /** - * Returns true if the given pubkey is canonical, i.e. the correct length taking into account - * compression. + * Returns true if the given pubkey is canonical, i.e. the correct length taking + * into account compression. * * @param pubkey - * @return - @@ -790,8 +778,8 @@ public static boolean isPubKeyCanonical(byte[] pubkey) { } /** - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. * @param messageHash Hash of the data that was signed. * @return 20-byte address */ @@ -809,8 +797,8 @@ public static byte[] recoverAddressFromSignature(int recId, } /** - * @param recId Which possible key to recover. - * @param sig the R and S components of the signature, wrapped. + * @param recId Which possible key to recover. + * @param sig the R and S components of the signature, wrapped. * @param messageHash Hash of the data that was signed. * @return ECKey */ @@ -827,8 +815,9 @@ public static SM2 recoverFromSignature(int recId, SM2Signature sig, } /** - * Returns true if this key doesn't have access to private key bytes. This may be because it was - * never given any private key bytes to begin with (a watching key). + * Returns true if this key doesn't have access to private key bytes. This may + * be because it was never given any private key bytes to begin with (a watching + * key). * * @return - */ @@ -837,7 +826,8 @@ public boolean isPubKeyOnly() { } /** - * Returns true if this key has access to private key bytes. Does the opposite of {@link + * Returns true if this key has access to private key bytes. Does the opposite + * of {@link * #isPubKeyOnly()}. * * @return - @@ -846,20 +836,20 @@ public boolean hasPrivKey() { return privKey != null; } - /** - * Generates the NodeID based on this key, that is the public key without first format byte + * 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); } - return nodeId; + return Arrays.copyOf(nodeId, nodeId.length); } - /** - * Gets the public key in the form of an elliptic curve point object from Bouncy Castle. + * Gets the public key in the form of an elliptic curve point object from Bouncy + * Castle. * * @return - */ @@ -868,8 +858,9 @@ public ECPoint getPubKeyPoint() { } /** - * Gets the private key in the form of an integer field element. The public key is derived by - * performing EC point addition this number of times (i.e. point multiplying). + * Gets the private key in the form of an integer field element. The public key + * is derived by performing EC point addition this number of times (i.e. point + * multiplying). * * @return - * @throws IllegalStateException if the private key bytes are not available. @@ -885,42 +876,13 @@ public BigInteger getPrivKey() { } public String toString() { - StringBuilder b = new StringBuilder(); - b.append("pub:").append(Hex.toHexString(pub.getEncoded(false))); - 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(); - } - - /** - * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. - * - * @param data Hash of the data to verify. - * @param signature signature. - * @return - - */ - public boolean verify(byte[] data, byte[] signature) { - return SM2.verify(data, signature, getPubKey()); + return "pub:" + Hex.toHexString(pub.getEncoded(false)); } /** * Verifies the given R/S pair (signature) against a hash using the public key. * - * @param sigHash - + * @param sigHash - * @param signature - * @return - */ @@ -929,8 +891,8 @@ public boolean verify(byte[] sigHash, SM2Signature signature) { } /** - * Returns true if this pubkey is canonical, i.e. the correct length taking into account - * compression. + * Returns true if this pubkey is canonical, i.e. the correct length taking into + * account compression. * * @return - */ @@ -939,8 +901,8 @@ public boolean isPubKeyCanonical() { } /** - * Returns a 32 byte array containing the private key, or null if the key is encrypted or public - * only + * Returns a 32 byte array containing the private key, or null if the key is + * encrypted or public only * * @return - */ @@ -967,10 +929,10 @@ public boolean equals(Object o) { SM2 ecKey = (SM2) o; - if (privKey != null && !privKey.equals(ecKey.privKey)) { + if (!Objects.equals(privKey, ecKey.privKey)) { return false; } - return pub == null || pub.equals(ecKey.pub); + return Objects.equals(pub, ecKey.pub); } @Override @@ -978,7 +940,6 @@ public int hashCode() { return Arrays.hashCode(getPubKey()); } - public static class SM2Signature implements SignatureInterface { /** @@ -988,8 +949,8 @@ public static class SM2Signature implements SignatureInterface { public byte v; /** - * Constructs a signature with the given components. Does NOT automatically canonicalise the - * signature. + * Constructs a signature with the given components. Does NOT automatically + * canonicalise the signature. * * @param r - * @param s - @@ -1005,11 +966,6 @@ public SM2Signature(byte[] r, byte[] s, byte v) { this.v = v; } - /** - * t - * - * @return - - */ private static SM2Signature fromComponents(byte[] r, byte[] s) { return new SM2Signature(new BigInteger(1, r), new BigInteger(1, s)); @@ -1021,13 +977,36 @@ private static SM2Signature fromComponents(byte[] r, byte[] s) { * @param v - * @return - */ - public static SM2Signature fromComponents(byte[] r, byte[] s, byte - v) { + public static SM2Signature fromComponents(byte[] r, byte[] s, byte v) { SM2Signature signature = fromComponents(r, s); signature.v = v; return signature; } + public static SM2Signature decodeFromDER(byte[] signature) throws SignatureException { + if (ByteArray.isEmpty(signature) || signature.length > 128) { + throw new SignatureException("Invalid DER signature length"); + } + + try (ASN1InputStream inputStream = new ASN1InputStream(signature)) { + ASN1Primitive primitive = inputStream.readObject(); + if (!(primitive instanceof ASN1Sequence)) { + throw new SignatureException("Invalid DER signature format"); + } + + ASN1Sequence sequence = (ASN1Sequence) primitive; + if (sequence.size() != 2) { + throw new SignatureException("Invalid DER signature component count"); + } + + BigInteger r = ASN1Integer.getInstance(sequence.getObjectAt(0)).getPositiveValue(); + BigInteger s = ASN1Integer.getInstance(sequence.getObjectAt(1)).getPositiveValue(); + return new SM2Signature(r, s); + } catch (IOException | IllegalArgumentException e) { + throw new SignatureException("Could not decode DER signature", e); + } + } + public static boolean validateComponents(BigInteger r, BigInteger s, byte v) { @@ -1048,59 +1027,22 @@ public static boolean validateComponents(BigInteger r, BigInteger s, return isLessThan(s, SM2.SM2_N); } - public static SM2Signature decodeFromDER(byte[] bytes) { - ASN1InputStream decoder = null; - try { - decoder = new ASN1InputStream(bytes); - DLSequence seq = (DLSequence) decoder.readObject(); - if (seq == null) { - throw new RuntimeException("Reached past end of ASN.1 " - + "stream."); - } - ASN1Integer r, s; - try { - r = (ASN1Integer) seq.getObjectAt(0); - s = (ASN1Integer) seq.getObjectAt(1); - } catch (ClassCastException e) { - throw new IllegalArgumentException(e); - } - // OpenSSL deviates from the DER spec by interpreting these - // values as unsigned, though they should not be - // Thus, we always use the positive versions. See: - // http://r6.ca/blog/20111119T211504Z.html - return new SM2Signature(r.getPositiveValue(), s - .getPositiveValue()); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (decoder != null) { - try { - decoder.close(); - } catch (IOException x) { - - } - } - } - } - public boolean validateComponents() { return validateComponents(r, s, v); } - /** * @return - */ public String toBase64() { - byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 + byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 // bytes for S sigData[0] = v; System.arraycopy(bigIntegerToBytes(this.r, 32), 0, sigData, 1, 32); System.arraycopy(bigIntegerToBytes(this.s, 32), 0, sigData, 33, 32); - return new String(Base64.encode(sigData), Charset.forName("UTF-8")); + return new String(Base64.encode(sigData), StandardCharsets.UTF_8); } - public byte[] toByteArray() { final byte fixedV = this.v >= 27 ? (byte) (this.v - 27) @@ -1109,7 +1051,7 @@ public byte[] toByteArray() { return ByteUtil.merge( ByteUtil.bigIntegerToBytes(this.r, 32), ByteUtil.bigIntegerToBytes(this.s, 32), - new byte[]{fixedV}); + new byte[] { fixedV }); } public String toHex() { @@ -1140,5 +1082,4 @@ public int hashCode() { return result; } } - } diff --git a/crypto/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java b/crypto/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java index 817b909de58..a0170b9c2d6 100644 --- a/crypto/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java +++ b/crypto/src/main/java/org/tron/common/crypto/sm2/SM2Signer.java @@ -11,20 +11,20 @@ import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.params.ParametersWithID; -import org.bouncycastle.crypto.params.ParametersWithRandom; import org.bouncycastle.crypto.signers.DSAKCalculator; -import org.bouncycastle.crypto.signers.RandomDSAKCalculator; +import org.bouncycastle.crypto.signers.HMacDSAKCalculator; import org.bouncycastle.math.ec.ECConstants; import org.bouncycastle.math.ec.ECFieldElement; import org.bouncycastle.math.ec.ECMultiplier; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.math.ec.FixedPointCombMultiplier; import org.bouncycastle.util.BigIntegers; +import org.tron.common.utils.ByteArray; public class SM2Signer implements ECConstants { - private final DSAKCalculator kCalculator = new RandomDSAKCalculator(); + private final DSAKCalculator kCalculator = new HMacDSAKCalculator(new SM3Digest()); private byte[] userID; @@ -33,9 +33,10 @@ public class SM2Signer private ECPoint pubPoint; private ECKeyParameters ecKey; - private SecureRandom random; - public void init(boolean forSigning, CipherParameters param) { + if (param == null) { + throw new IllegalArgumentException("CipherParameters cannot be null"); + } CipherParameters baseParam; if (param instanceof ParametersWithID) { @@ -46,22 +47,12 @@ public void init(boolean forSigning, CipherParameters param) { userID = new byte[0]; } + ecKey = (ECKeyParameters) baseParam; + ecParams = ecKey.getParameters(); + if (forSigning) { - if (baseParam instanceof ParametersWithRandom) { - ParametersWithRandom rParam = (ParametersWithRandom) baseParam; - - ecKey = (ECKeyParameters) rParam.getParameters(); - ecParams = ecKey.getParameters(); - kCalculator.init(ecParams.getN(), rParam.getRandom()); - } else { - ecKey = (ECKeyParameters) baseParam; - ecParams = ecKey.getParameters(); - kCalculator.init(ecParams.getN(), new SecureRandom()); - } pubPoint = ecParams.getG().multiply(((ECPrivateKeyParameters) ecKey).getD()).normalize(); } else { - ecKey = (ECKeyParameters) baseParam; - ecParams = ecKey.getParameters(); pubPoint = ((ECPublicKeyParameters) ecKey).getQ(); } @@ -84,8 +75,9 @@ public BigInteger[] generateSignature(byte[] message) { */ public byte[] generateSM3Hash(byte[] message) { - //byte[] msg = message.getBytes(); - + if (message == null) { + throw new IllegalArgumentException("Message cannot be null"); + } SM3Digest digest = new SM3Digest(); byte[] z = getZ(digest); @@ -102,9 +94,9 @@ public byte[] generateSM3Hash(byte[] message) { * generate the signature from the 32 byte hash */ public BigInteger[] generateHashSignature(byte[] hash) { - if (hash.length != 32) { + if (ByteArray.isEmpty(hash) || hash.length != 32) { throw new IllegalArgumentException("Expected 32 byte input to " + - "ECDSA signature, not " + hash.length); + "SM2 signature, not " + (hash == null ? "null" : hash.length)); } BigInteger n = ecParams.getN(); BigInteger e = calculateE(hash); @@ -114,6 +106,9 @@ public BigInteger[] generateHashSignature(byte[] hash) { ECMultiplier basePointMultiplier = createBasePointMultiplier(); + // Initialize the deterministic K calculator with the private key and message hash + kCalculator.init(n, d, hash); + // 5.2.1 Draft RFC: SM2 Public Key Algorithms do // generate s { @@ -147,6 +142,9 @@ public BigInteger[] generateHashSignature(byte[] hash) { */ public boolean verifySignature(byte[] message, BigInteger r, BigInteger s, @Nullable String userID) { + if (message == null || r == null || s == null) { + throw new IllegalArgumentException("Message, R, or S cannot be null"); + } BigInteger n = ecParams.getN(); // 5.3.1 Draft RFC: SM2 Public Key Algorithms @@ -188,6 +186,12 @@ public boolean verifySignature(byte[] message, BigInteger r, BigInteger s, * verify the hash signature */ public boolean verifyHashSignature(byte[] hash, BigInteger r, BigInteger s) { + if (ByteArray.isEmpty(hash)) { + throw new IllegalArgumentException("Hash cannot be empty"); + } + if (r == null || s == null) { + throw new IllegalArgumentException("R or S cannot be null"); + } BigInteger n = ecParams.getN(); // 5.3.1 Draft RFC: SM2 Public Key Algorithms @@ -255,8 +259,10 @@ protected ECMultiplier createBasePointMultiplier() { } protected BigInteger calculateE(byte[] message) { + if (message == null) { + throw new IllegalArgumentException("Message cannot be null"); + } return new BigInteger(1, message); } } - diff --git a/framework/src/main/java/org/tron/core/Wallet.java b/framework/src/main/java/org/tron/core/Wallet.java index 39e8f06c281..665ffb5f77a 100755 --- a/framework/src/main/java/org/tron/core/Wallet.java +++ b/framework/src/main/java/org/tron/core/Wallet.java @@ -780,7 +780,7 @@ public WitnessList getPaginatedNowWitnessList(long offset, long limit) throws if (limit > WITNESS_COUNT_LIMIT_MAX) { limit = WITNESS_COUNT_LIMIT_MAX; } - + /* In the maintenance period, the VoteStores will be cleared. To avoid the race condition of VoteStores deleted but Witness vote counts not updated, @@ -1502,8 +1502,8 @@ public Protocol.ChainParameters getChainParameters() { builder.addChainParameter(Protocol.ChainParameters.ChainParameter.newBuilder() .setKey("getAllowTvmSelfdestructRestriction") .setValue(dbManager.getDynamicPropertiesStore().getAllowTvmSelfdestructRestriction()) - .build()); - + .build()); + builder.addChainParameter(Protocol.ChainParameters.ChainParameter.newBuilder() .setKey("getProposalExpireTime") .setValue(dbManager.getDynamicPropertiesStore().getProposalExpireTime()) @@ -2320,57 +2320,57 @@ public TransactionCapsule createShieldedTransaction(PrivateParameters request) checkCmValid(shieldedSpends, shieldedReceives); - // add - if (!ArrayUtils.isEmpty(transparentFromAddress)) { - builder.setTransparentInput(transparentFromAddress, fromAmount); - } + try { + // add + if (!ArrayUtils.isEmpty(transparentFromAddress)) { + builder.setTransparentInput(transparentFromAddress, fromAmount); + } - if (!ArrayUtils.isEmpty(transparentToAddress)) { - builder.setTransparentOutput(transparentToAddress, toAmount); - } - - // from shielded to public, without shielded receive, will create a random shielded address - if (!shieldedSpends.isEmpty() - && !ArrayUtils.isEmpty(transparentToAddress) - && shieldedReceives.isEmpty()) { - shieldedReceives = new ArrayList<>(); - ReceiveNote receiveNote = createReceiveNoteRandom(0); - shieldedReceives.add(receiveNote); - } - - // input - if (!(ArrayUtils.isEmpty(ask) || ArrayUtils.isEmpty(nsk) || ArrayUtils.isEmpty(ovk))) { - ExpandedSpendingKey expsk = new ExpandedSpendingKey(ask, nsk, ovk); - for (SpendNote spendNote : shieldedSpends) { - GrpcAPI.Note note = spendNote.getNote(); - PaymentAddress paymentAddress = KeyIo.decodePaymentAddress(note.getPaymentAddress()); - if (paymentAddress == null) { - throw new ZksnarkException(PAYMENT_ADDRESS_FORMAT_WRONG); + if (!ArrayUtils.isEmpty(transparentToAddress)) { + builder.setTransparentOutput(transparentToAddress, toAmount); + } + + // from shielded to public, without shielded receive, will create a random shielded address + if (!shieldedSpends.isEmpty() + && !ArrayUtils.isEmpty(transparentToAddress) + && shieldedReceives.isEmpty()) { + shieldedReceives = new ArrayList<>(); + ReceiveNote receiveNote = createReceiveNoteRandom(0); + shieldedReceives.add(receiveNote); + } + + // input + if (!(ArrayUtils.isEmpty(ask) || ArrayUtils.isEmpty(nsk) || ArrayUtils.isEmpty(ovk))) { + ExpandedSpendingKey expsk = new ExpandedSpendingKey(ask, nsk, ovk); + for (SpendNote spendNote : shieldedSpends) { + GrpcAPI.Note note = spendNote.getNote(); + PaymentAddress paymentAddress = KeyIo.decodePaymentAddress(note.getPaymentAddress()); + if (paymentAddress == null) { + throw new ZksnarkException(PAYMENT_ADDRESS_FORMAT_WRONG); + } + Note baseNote = new Note(paymentAddress.getD(), + paymentAddress.getPkD(), note.getValue(), note.getRcm().toByteArray()); + + IncrementalMerkleVoucherContainer voucherContainer = new IncrementalMerkleVoucherCapsule( + spendNote.getVoucher()).toMerkleVoucherContainer(); + builder.addSpend(expsk, + baseNote, + spendNote.getAlpha().toByteArray(), + spendNote.getVoucher().getRt().toByteArray(), + voucherContainer); } - Note baseNote = new Note(paymentAddress.getD(), - paymentAddress.getPkD(), note.getValue(), note.getRcm().toByteArray()); - - IncrementalMerkleVoucherContainer voucherContainer = new IncrementalMerkleVoucherCapsule( - spendNote.getVoucher()).toMerkleVoucherContainer(); - builder.addSpend(expsk, - baseNote, - spendNote.getAlpha().toByteArray(), - spendNote.getVoucher().getRt().toByteArray(), - voucherContainer); } - } - // output - shieldedOutput(shieldedReceives, builder, ovk); + // output + shieldedOutput(shieldedReceives, builder, ovk); - TransactionCapsule transactionCapsule = null; - try { - transactionCapsule = builder.build(); + return builder.build(); + } catch (ArithmeticException e) { + throw new ZksnarkException("shielded amount overflow"); } catch (ZksnarkException e) { logger.error("createShieldedTransaction except, error is " + e.toString()); throw new ZksnarkException(e.toString()); } - return transactionCapsule; } @@ -2422,58 +2422,58 @@ public TransactionCapsule createShieldedTransactionWithoutSpendAuthSig( checkCmValid(shieldedSpends, shieldedReceives); - // add - if (!ArrayUtils.isEmpty(transparentFromAddress)) { - builder.setTransparentInput(transparentFromAddress, fromAmount); - } + try { + // add + if (!ArrayUtils.isEmpty(transparentFromAddress)) { + builder.setTransparentInput(transparentFromAddress, fromAmount); + } - if (!ArrayUtils.isEmpty(transparentToAddress)) { - builder.setTransparentOutput(transparentToAddress, toAmount); - } + if (!ArrayUtils.isEmpty(transparentToAddress)) { + builder.setTransparentOutput(transparentToAddress, toAmount); + } - // from shielded to public, without shielded receive, will create a random shielded address - if (!shieldedSpends.isEmpty() - && !ArrayUtils.isEmpty(transparentToAddress) - && shieldedReceives.isEmpty()) { - shieldedReceives = new ArrayList<>(); - ReceiveNote receiveNote = createReceiveNoteRandom(0); - shieldedReceives.add(receiveNote); - } + // from shielded to public, without shielded receive, will create a random shielded address + if (!shieldedSpends.isEmpty() + && !ArrayUtils.isEmpty(transparentToAddress) + && shieldedReceives.isEmpty()) { + shieldedReceives = new ArrayList<>(); + ReceiveNote receiveNote = createReceiveNoteRandom(0); + shieldedReceives.add(receiveNote); + } - // input - if (!(ArrayUtils.isEmpty(ak) || ArrayUtils.isEmpty(nsk) || ArrayUtils.isEmpty(ovk))) { - for (SpendNote spendNote : shieldedSpends) { - GrpcAPI.Note note = spendNote.getNote(); - PaymentAddress paymentAddress = KeyIo.decodePaymentAddress(note.getPaymentAddress()); - if (paymentAddress == null) { - throw new ZksnarkException(PAYMENT_ADDRESS_FORMAT_WRONG); + // input + if (!(ArrayUtils.isEmpty(ak) || ArrayUtils.isEmpty(nsk) || ArrayUtils.isEmpty(ovk))) { + for (SpendNote spendNote : shieldedSpends) { + GrpcAPI.Note note = spendNote.getNote(); + PaymentAddress paymentAddress = KeyIo.decodePaymentAddress(note.getPaymentAddress()); + if (paymentAddress == null) { + throw new ZksnarkException(PAYMENT_ADDRESS_FORMAT_WRONG); + } + Note baseNote = new Note(paymentAddress.getD(), + paymentAddress.getPkD(), note.getValue(), note.getRcm().toByteArray()); + + IncrementalMerkleVoucherContainer voucherContainer = new IncrementalMerkleVoucherCapsule( + spendNote.getVoucher()).toMerkleVoucherContainer(); + builder.addSpend(ak, + nsk, + ovk, + baseNote, + spendNote.getAlpha().toByteArray(), + spendNote.getVoucher().getRt().toByteArray(), + voucherContainer); } - Note baseNote = new Note(paymentAddress.getD(), - paymentAddress.getPkD(), note.getValue(), note.getRcm().toByteArray()); - - IncrementalMerkleVoucherContainer voucherContainer = new IncrementalMerkleVoucherCapsule( - spendNote.getVoucher()).toMerkleVoucherContainer(); - builder.addSpend(ak, - nsk, - ovk, - baseNote, - spendNote.getAlpha().toByteArray(), - spendNote.getVoucher().getRt().toByteArray(), - voucherContainer); } - } - // output - shieldedOutput(shieldedReceives, builder, ovk); + // output + shieldedOutput(shieldedReceives, builder, ovk); - TransactionCapsule transactionCapsule = null; - try { - transactionCapsule = builder.buildWithoutAsk(); + return builder.buildWithoutAsk(); + } catch (ArithmeticException e) { + throw new ZksnarkException("shielded amount overflow"); } catch (ZksnarkException e) { logger.error("createShieldedTransaction exception, error is " + e.toString()); throw new ZksnarkException(e.toString()); } - return transactionCapsule; } @@ -2492,7 +2492,6 @@ private void shieldedOutput(List shieldedReceives, } } - public ShieldedAddressInfo getNewShieldedAddress() throws BadItemException, ZksnarkException { checkAllowShieldedTransactionApi(); @@ -3658,77 +3657,82 @@ public ShieldedTRC20Parameters createShieldedContractParameters( scaledToAmount, shieldedReceives.get(0).getNote().getValue(), dbManager.getDynamicPropertiesStore().disableJavaLangMath())); } catch (ArithmeticException e) { - throw new ZksnarkException("Unbalanced burn!"); + throw new ZksnarkException("shielded amount overflow"); } } - if (scaledFromAmount > 0 && spendSize == 0 && receiveSize == 1 - && scaledFromAmount == shieldedReceives.get(0).getNote().getValue() - && scaledToAmount == 0) { - builder.setShieldedTRC20ParametersType(ShieldedTRC20ParametersType.MINT); - - byte[] ovk = request.getOvk().toByteArray(); - if (ArrayUtils.isEmpty(ovk)) { - ovk = SpendingKey.random().fullViewingKey().getOvk(); - } + try { + if (scaledFromAmount > 0 && spendSize == 0 && receiveSize == 1 + && scaledFromAmount == shieldedReceives.get(0).getNote().getValue() + && scaledToAmount == 0) { + builder.setShieldedTRC20ParametersType(ShieldedTRC20ParametersType.MINT); + + byte[] ovk = request.getOvk().toByteArray(); + if (ArrayUtils.isEmpty(ovk)) { + ovk = SpendingKey.random().fullViewingKey().getOvk(); + } - builder.setTransparentFromAmount(fromAmount); - buildShieldedTRC20Output(builder, shieldedReceives.get(0), ovk); - } else if (scaledFromAmount == 0 && spendSize > 0 && spendSize < 3 - && receiveSize > 0 && receiveSize < 3 && scaledToAmount == 0) { - builder.setShieldedTRC20ParametersType(ShieldedTRC20ParametersType.TRANSFER); - - byte[] ask = request.getAsk().toByteArray(); - byte[] nsk = request.getNsk().toByteArray(); - byte[] ovk = request.getOvk().toByteArray(); - if ((ArrayUtils.isEmpty(ask) || ArrayUtils.isEmpty(nsk) || ArrayUtils.isEmpty(ovk))) { - throw new ContractValidateException("No shielded TRC-20 ask, nsk or ovk"); - } + builder.setTransparentFromAmount(fromAmount); + buildShieldedTRC20Output(builder, shieldedReceives.get(0), ovk); + } else if (scaledFromAmount == 0 && spendSize > 0 && spendSize < 3 + && receiveSize > 0 && receiveSize < 3 && scaledToAmount == 0) { + builder.setShieldedTRC20ParametersType(ShieldedTRC20ParametersType.TRANSFER); + + byte[] ask = request.getAsk().toByteArray(); + byte[] nsk = request.getNsk().toByteArray(); + byte[] ovk = request.getOvk().toByteArray(); + if ((ArrayUtils.isEmpty(ask) || ArrayUtils.isEmpty(nsk) || ArrayUtils.isEmpty(ovk))) { + throw new ContractValidateException("No shielded TRC-20 ask, nsk or ovk"); + } - ExpandedSpendingKey expsk = new ExpandedSpendingKey(ask, nsk, ovk); - for (GrpcAPI.SpendNoteTRC20 spendNote : shieldedSpends) { - buildShieldedTRC20Input(builder, spendNote, expsk); - } + ExpandedSpendingKey expsk = new ExpandedSpendingKey(ask, nsk, ovk); + for (GrpcAPI.SpendNoteTRC20 spendNote : shieldedSpends) { + buildShieldedTRC20Input(builder, spendNote, expsk); + } - for (ReceiveNote receiveNote : shieldedReceives) { - buildShieldedTRC20Output(builder, receiveNote, ovk); - } - } else if (scaledFromAmount == 0 && spendSize == 1 && receiveSize >= 0 && receiveSize <= 1 - && scaledToAmount > 0 && totalToAmount == shieldedSpends.get(0).getNote().getValue()) { - builder.setShieldedTRC20ParametersType(ShieldedTRC20ParametersType.BURN); - - byte[] ask = request.getAsk().toByteArray(); - byte[] nsk = request.getNsk().toByteArray(); - byte[] ovk = request.getOvk().toByteArray(); - if ((ArrayUtils.isEmpty(ask) || ArrayUtils.isEmpty(nsk) || ArrayUtils.isEmpty(ovk))) { - throw new ContractValidateException("No shielded TRC-20 ask, nsk or ovk"); - } + for (ReceiveNote receiveNote : shieldedReceives) { + buildShieldedTRC20Output(builder, receiveNote, ovk); + } + } else if (scaledFromAmount == 0 && spendSize == 1 && receiveSize >= 0 + && receiveSize <= 1 + && scaledToAmount > 0 && totalToAmount == shieldedSpends.get(0).getNote().getValue()) { + builder.setShieldedTRC20ParametersType(ShieldedTRC20ParametersType.BURN); + + byte[] ask = request.getAsk().toByteArray(); + byte[] nsk = request.getNsk().toByteArray(); + byte[] ovk = request.getOvk().toByteArray(); + if ((ArrayUtils.isEmpty(ask) || ArrayUtils.isEmpty(nsk) || ArrayUtils.isEmpty(ovk))) { + throw new ContractValidateException("No shielded TRC-20 ask, nsk or ovk"); + } - byte[] transparentToAddress = request.getTransparentToAddress().toByteArray(); - if (ArrayUtils.isEmpty(transparentToAddress) || transparentToAddress.length != 21) { - throw new ContractValidateException("No valid transparent TRC-20 output address"); - } + byte[] transparentToAddress = request.getTransparentToAddress().toByteArray(); + if (ArrayUtils.isEmpty(transparentToAddress) || transparentToAddress.length != 21) { + throw new ContractValidateException("No valid transparent TRC-20 output address"); + } - byte[] transparentToAddressTvm = new byte[20]; - System.arraycopy(transparentToAddress, 1, transparentToAddressTvm, 0, 20); - builder.setTransparentToAddress(transparentToAddressTvm); - builder.setTransparentToAmount(toAmount); + byte[] transparentToAddressTvm = new byte[20]; + System.arraycopy(transparentToAddress, 1, transparentToAddressTvm, 0, 20); + builder.setTransparentToAddress(transparentToAddressTvm); + builder.setTransparentToAmount(toAmount); - Optional cipher = NoteEncryption.Encryption - .encryptBurnMessageByOvk(ovk, toAmount, transparentToAddress); - cipher.ifPresent(builder::setBurnCiphertext); + Optional cipher = NoteEncryption.Encryption + .encryptBurnMessageByOvk(ovk, toAmount, transparentToAddress); + cipher.ifPresent(builder::setBurnCiphertext); - ExpandedSpendingKey expsk = new ExpandedSpendingKey(ask, nsk, ovk); - GrpcAPI.SpendNoteTRC20 spendNote = shieldedSpends.get(0); - buildShieldedTRC20Input(builder, spendNote, expsk); - if (receiveSize == 1) { - buildShieldedTRC20Output(builder, shieldedReceives.get(0), ovk); + ExpandedSpendingKey expsk = new ExpandedSpendingKey(ask, nsk, ovk); + GrpcAPI.SpendNoteTRC20 spendNote = shieldedSpends.get(0); + buildShieldedTRC20Input(builder, spendNote, expsk); + if (receiveSize == 1) { + buildShieldedTRC20Output(builder, shieldedReceives.get(0), ovk); + } + } else { + throw new ContractValidateException("invalid shielded TRC-20 parameters"); } - } else { - throw new ContractValidateException("invalid shielded TRC-20 parameters"); - } - return builder.build(true); + return builder.build(true); + } catch (ArithmeticException e) { + throw new ZksnarkException("shielded amount overflow"); + } } private void buildShieldedTRC20InputWithAK( @@ -3791,69 +3795,74 @@ public ShieldedTRC20Parameters createShieldedContractParametersWithoutAsk( scaledToAmount, shieldedReceives.get(0).getNote().getValue(), chainBaseManager.getDynamicPropertiesStore().disableJavaLangMath()); } catch (ArithmeticException e) { - throw new ZksnarkException("Unbalanced burn!"); + throw new ZksnarkException("shielded amount overflow"); } } - if (scaledFromAmount > 0 && spendSize == 0 && receiveSize == 1 - && scaledFromAmount == shieldedReceives.get(0).getNote().getValue() - && scaledToAmount == 0) { - byte[] ovk = request.getOvk().toByteArray(); - if (ArrayUtils.isEmpty(ovk)) { - ovk = SpendingKey.random().fullViewingKey().getOvk(); - } - builder.setShieldedTRC20ParametersType(ShieldedTRC20ParametersType.MINT); - builder.setTransparentFromAmount(fromAmount); - ReceiveNote receiveNote = shieldedReceives.get(0); - buildShieldedTRC20Output(builder, receiveNote, ovk); - } else if (scaledFromAmount == 0 && spendSize > 0 && spendSize < 3 - && receiveSize > 0 && receiveSize < 3 && scaledToAmount == 0) { - builder.setShieldedTRC20ParametersType(ShieldedTRC20ParametersType.TRANSFER); - byte[] ak = request.getAk().toByteArray(); - byte[] nsk = request.getNsk().toByteArray(); - byte[] ovk = request.getOvk().toByteArray(); - if ((ArrayUtils.isEmpty(ak) || ArrayUtils.isEmpty(nsk) || ArrayUtils.isEmpty(ovk))) { - throw new ContractValidateException("No shielded TRC-20 ak, nsk or ovk"); - } - for (GrpcAPI.SpendNoteTRC20 spendNote : shieldedSpends) { - buildShieldedTRC20InputWithAK(builder, spendNote, ak, nsk); - } - for (ReceiveNote receiveNote : shieldedReceives) { + try { + if (scaledFromAmount > 0 && spendSize == 0 && receiveSize == 1 + && scaledFromAmount == shieldedReceives.get(0).getNote().getValue() + && scaledToAmount == 0) { + byte[] ovk = request.getOvk().toByteArray(); + if (ArrayUtils.isEmpty(ovk)) { + ovk = SpendingKey.random().fullViewingKey().getOvk(); + } + builder.setShieldedTRC20ParametersType(ShieldedTRC20ParametersType.MINT); + builder.setTransparentFromAmount(fromAmount); + ReceiveNote receiveNote = shieldedReceives.get(0); buildShieldedTRC20Output(builder, receiveNote, ovk); + } else if (scaledFromAmount == 0 && spendSize > 0 && spendSize < 3 + && receiveSize > 0 && receiveSize < 3 && scaledToAmount == 0) { + builder.setShieldedTRC20ParametersType(ShieldedTRC20ParametersType.TRANSFER); + byte[] ak = request.getAk().toByteArray(); + byte[] nsk = request.getNsk().toByteArray(); + byte[] ovk = request.getOvk().toByteArray(); + if ((ArrayUtils.isEmpty(ak) || ArrayUtils.isEmpty(nsk) || ArrayUtils.isEmpty(ovk))) { + throw new ContractValidateException("No shielded TRC-20 ak, nsk or ovk"); + } + for (GrpcAPI.SpendNoteTRC20 spendNote : shieldedSpends) { + buildShieldedTRC20InputWithAK(builder, spendNote, ak, nsk); + } + for (ReceiveNote receiveNote : shieldedReceives) { + buildShieldedTRC20Output(builder, receiveNote, ovk); + } + } else if (scaledFromAmount == 0 && spendSize == 1 && receiveSize >= 0 + && receiveSize <= 1 + && scaledToAmount > 0 && totalToAmount == shieldedSpends.get(0).getNote().getValue()) { + builder.setShieldedTRC20ParametersType(ShieldedTRC20ParametersType.BURN); + byte[] ak = request.getAk().toByteArray(); + byte[] nsk = request.getNsk().toByteArray(); + byte[] ovk = request.getOvk().toByteArray(); + if ((ArrayUtils.isEmpty(ak) || ArrayUtils.isEmpty(nsk) || ArrayUtils.isEmpty(ovk))) { + throw new ContractValidateException("No shielded TRC-20 ak, nsk or ovk"); + } + byte[] transparentToAddress = request.getTransparentToAddress().toByteArray(); + if (ArrayUtils.isEmpty(transparentToAddress) || transparentToAddress.length != 21) { + throw new ContractValidateException("No transparent TRC-20 output address"); + } + byte[] transparentToAddressTvm = new byte[20]; + System.arraycopy(transparentToAddress, 1, transparentToAddressTvm, 0, 20); + builder.setTransparentToAddress(transparentToAddressTvm); + builder.setTransparentToAmount(toAmount); + Optional cipher = NoteEncryption.Encryption + .encryptBurnMessageByOvk(ovk, toAmount, transparentToAddress); + cipher.ifPresent(builder::setBurnCiphertext); + GrpcAPI.SpendNoteTRC20 spendNote = shieldedSpends.get(0); + buildShieldedTRC20InputWithAK(builder, spendNote, ak, nsk); + if (receiveSize == 1) { + buildShieldedTRC20Output(builder, shieldedReceives.get(0), ovk); + } + } else { + throw new ContractValidateException("invalid shielded TRC-20 parameters"); } - } else if (scaledFromAmount == 0 && spendSize == 1 && receiveSize >= 0 && receiveSize <= 1 - && scaledToAmount > 0 && totalToAmount == shieldedSpends.get(0).getNote().getValue()) { - builder.setShieldedTRC20ParametersType(ShieldedTRC20ParametersType.BURN); - byte[] ak = request.getAk().toByteArray(); - byte[] nsk = request.getNsk().toByteArray(); - byte[] ovk = request.getOvk().toByteArray(); - if ((ArrayUtils.isEmpty(ak) || ArrayUtils.isEmpty(nsk) || ArrayUtils.isEmpty(ovk))) { - throw new ContractValidateException("No shielded TRC-20 ak, nsk or ovk"); - } - byte[] transparentToAddress = request.getTransparentToAddress().toByteArray(); - if (ArrayUtils.isEmpty(transparentToAddress) || transparentToAddress.length != 21) { - throw new ContractValidateException("No transparent TRC-20 output address"); - } - byte[] transparentToAddressTvm = new byte[20]; - System.arraycopy(transparentToAddress, 1, transparentToAddressTvm, 0, 20); - builder.setTransparentToAddress(transparentToAddressTvm); - builder.setTransparentToAmount(toAmount); - Optional cipher = NoteEncryption.Encryption - .encryptBurnMessageByOvk(ovk, toAmount, transparentToAddress); - cipher.ifPresent(builder::setBurnCiphertext); - GrpcAPI.SpendNoteTRC20 spendNote = shieldedSpends.get(0); - buildShieldedTRC20InputWithAK(builder, spendNote, ak, nsk); - if (receiveSize == 1) { - buildShieldedTRC20Output(builder, shieldedReceives.get(0), ovk); - } - } else { - throw new ContractValidateException("invalid shielded TRC-20 parameters"); + return builder.build(false); + } catch (ArithmeticException e) { + throw new ZksnarkException("shielded amount overflow"); } - return builder.build(false); } - private int getShieldedTRC20LogType(TransactionInfo.Log log, byte[] contractAddress, - ProtocolStringList topicsList) throws ZksnarkException { + private int getShieldedTRC20LogType(TransactionInfo.Log log, byte[] contractAddress) + throws ZksnarkException { byte[] logAddress = log.getAddress().toByteArray(); byte[] addressWithoutPrefix = new byte[20]; if (ArrayUtils.isEmpty(contractAddress) || contractAddress.length != 21) { @@ -3866,33 +3875,14 @@ private int getShieldedTRC20LogType(TransactionInfo.Log log, byte[] contractAddr for (ByteString bs : logTopicsList) { topicsBytes = ByteUtil.merge(topicsBytes, bs.toByteArray()); } - if (Objects.isNull(topicsList) || topicsList.isEmpty()) { - if (Arrays.equals(topicsBytes, SHIELDED_TRC20_LOG_TOPICS_MINT)) { - return 1; - } else if (Arrays.equals(topicsBytes, SHIELDED_TRC20_LOG_TOPICS_TRANSFER)) { - return 2; - } else if (Arrays.equals(topicsBytes, SHIELDED_TRC20_LOG_TOPICS_BURN_LEAF)) { - return 3; - } else if (Arrays.equals(topicsBytes, SHIELDED_TRC20_LOG_TOPICS_BURN_TOKEN)) { - return 4; - } - } else { - for (String topic : topicsList) { - byte[] topicHash = Hash.sha3(ByteArray.fromString(topic)); - if (Arrays.equals(topicsBytes, topicHash)) { - if (topic.toLowerCase().contains("mint")) { - return 1; - } else if (topic.toLowerCase().contains("transfer")) { - return 2; - } else if (topic.toLowerCase().contains("burn")) { - if (topic.toLowerCase().contains("leaf")) { - return 3; - } else if (topic.toLowerCase().contains("token")) { - return 4; - } - } - } - } + if (Arrays.equals(topicsBytes, SHIELDED_TRC20_LOG_TOPICS_MINT)) { + return 1; + } else if (Arrays.equals(topicsBytes, SHIELDED_TRC20_LOG_TOPICS_TRANSFER)) { + return 2; + } else if (Arrays.equals(topicsBytes, SHIELDED_TRC20_LOG_TOPICS_BURN_LEAF)) { + return 3; + } else if (Arrays.equals(topicsBytes, SHIELDED_TRC20_LOG_TOPICS_BURN_TOKEN)) { + return 4; } } return 0; @@ -3942,8 +3932,7 @@ private Optional getNoteTxFromLogListByIvk( } private DecryptNotesTRC20 queryTRC20NoteByIvk(long startNum, long endNum, - byte[] shieldedTRC20ContractAddress, byte[] ivk, byte[] ak, byte[] nk, - ProtocolStringList topicsList) + byte[] shieldedTRC20ContractAddress, byte[] ivk, byte[] ak, byte[] nk) throws BadItemException, ZksnarkException, ContractExeException { if (!(startNum >= 0 && endNum > startNum && endNum - startNum <= 1000)) { throw new BadItemException( @@ -3964,7 +3953,7 @@ private DecryptNotesTRC20 queryTRC20NoteByIvk(long startNum, long endNum, Optional noteTx; int index = 0; for (TransactionInfo.Log log : logList) { - int logType = getShieldedTRC20LogType(log, shieldedTRC20ContractAddress, topicsList); + int logType = getShieldedTRC20LogType(log, shieldedTRC20ContractAddress); if (logType > 0) { noteBuilder = DecryptNotesTRC20.NoteTx.newBuilder(); noteBuilder.setTxid(ByteString.copyFrom(txId)); @@ -4048,12 +4037,11 @@ private boolean isShieldedTRC20NoteSpent(GrpcAPI.Note note, long pos, byte[] ak, public DecryptNotesTRC20 scanShieldedTRC20NotesByIvk( long startNum, long endNum, byte[] shieldedTRC20ContractAddress, - byte[] ivk, byte[] ak, byte[] nk, ProtocolStringList topicsList) + byte[] ivk, byte[] ak, byte[] nk) throws BadItemException, ZksnarkException, ContractExeException { checkAllowShieldedTransactionApi(); - return queryTRC20NoteByIvk(startNum, endNum, - shieldedTRC20ContractAddress, ivk, ak, nk, topicsList); + return queryTRC20NoteByIvk(startNum, endNum, shieldedTRC20ContractAddress, ivk, ak, nk); } private Optional getNoteTxFromLogListByOvk( @@ -4127,7 +4115,7 @@ private Optional getNoteTxFromLogListByOvk( } public DecryptNotesTRC20 scanShieldedTRC20NotesByOvk(long startNum, long endNum, - byte[] ovk, byte[] shieldedTRC20ContractAddress, ProtocolStringList topicsList) + byte[] ovk, byte[] shieldedTRC20ContractAddress) throws ZksnarkException, BadItemException { checkAllowShieldedTransactionApi(); @@ -4149,7 +4137,7 @@ public DecryptNotesTRC20 scanShieldedTRC20NotesByOvk(long startNum, long endNum, Optional noteTx; int index = 0; for (TransactionInfo.Log log : logList) { - int logType = getShieldedTRC20LogType(log, shieldedTRC20ContractAddress, topicsList); + int logType = getShieldedTRC20LogType(log, shieldedTRC20ContractAddress); if (logType > 0) { noteBuilder = DecryptNotesTRC20.NoteTx.newBuilder(); noteBuilder.setTxid(ByteString.copyFrom(txid)); @@ -4596,4 +4584,3 @@ public PricesResponseMessage getMemoFeePrices() { return null; } } - diff --git a/framework/src/main/java/org/tron/core/config/args/Args.java b/framework/src/main/java/org/tron/core/config/args/Args.java index 83d7fd2c63d..39631e2a3f1 100644 --- a/framework/src/main/java/org/tron/core/config/args/Args.java +++ b/framework/src/main/java/org/tron/core/config/args/Args.java @@ -726,7 +726,7 @@ public static void applyConfigParams( logger.warn("Configuring [node.fullNodeAllowShieldedTransaction] will be deprecated. " + "Please use [node.allowShieldedTransactionApi] instead."); } else { - PARAMETER.allowShieldedTransactionApi = true; + PARAMETER.allowShieldedTransactionApi = false; } PARAMETER.zenTokenId = config.hasPath(ConfigKey.NODE_ZEN_TOKENID) diff --git a/framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java b/framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java index 30711eb6190..1a90d3b8b06 100644 --- a/framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java +++ b/framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java @@ -3,10 +3,12 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.tron.common.crypto.SignInterface; +import org.tron.common.crypto.SignUtils; import org.tron.common.utils.ByteArray; import org.tron.common.utils.Commons; import org.tron.common.utils.LocalWitnesses; @@ -81,8 +83,15 @@ public static LocalWitnesses initFromKeystore( try { Credentials credentials = WalletUtils.loadCredentials(pwd, new File(fileName)); SignInterface sign = credentials.getSignInterface(); - String prikey = ByteArray.toHexString(sign.getPrivateKey()); - privateKeys.add(prikey); + byte[] privKeyBytes = sign.getPrivateKey(); + if (!SignUtils.isValidPrivateKey(privKeyBytes, Args.getInstance().isECKeyCryptoEngine())) { + Arrays.fill(privKeyBytes, (byte) 0); + throw new TronError( + "Keystore contains an invalid private key", + TronError.ErrCode.WITNESS_KEYSTORE_LOAD); + } + privateKeys.add(ByteArray.toHexString(privKeyBytes)); + Arrays.fill(privKeyBytes, (byte) 0); } catch (IOException | CipherException e) { logger.error("Witness node start failed!"); throw new TronError(e, TronError.ErrCode.WITNESS_KEYSTORE_LOAD); diff --git a/framework/src/main/java/org/tron/core/services/RpcApiService.java b/framework/src/main/java/org/tron/core/services/RpcApiService.java index 63e7ba03fc7..2e6da5d9255 100755 --- a/framework/src/main/java/org/tron/core/services/RpcApiService.java +++ b/framework/src/main/java/org/tron/core/services/RpcApiService.java @@ -4,7 +4,6 @@ import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; -import com.google.protobuf.ProtocolStringList; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.netty.NettyServerBuilder; @@ -722,18 +721,21 @@ public void isSpend(NoteParameters request, StreamObserver response @Override public void scanShieldedTRC20NotesByIvk(IvkDecryptTRC20Parameters request, StreamObserver responseObserver) { + if (!request.getEventsList().isEmpty()) { + responseObserver.onError(getRunTimeException( + new IllegalArgumentException("'events' field is deprecated and no longer supported"))); + return; + } long startNum = request.getStartBlockIndex(); long endNum = request.getEndBlockIndex(); byte[] contractAddress = request.getShieldedTRC20ContractAddress().toByteArray(); byte[] ivk = request.getIvk().toByteArray(); byte[] ak = request.getAk().toByteArray(); byte[] nk = request.getNk().toByteArray(); - ProtocolStringList topicsList = request.getEventsList(); try { responseObserver.onNext( - wallet.scanShieldedTRC20NotesByIvk(startNum, endNum, contractAddress, ivk, ak, nk, - topicsList)); + wallet.scanShieldedTRC20NotesByIvk(startNum, endNum, contractAddress, ivk, ak, nk)); } catch (Exception e) { responseObserver.onError(getRunTimeException(e)); @@ -744,15 +746,18 @@ public void scanShieldedTRC20NotesByIvk(IvkDecryptTRC20Parameters request, @Override public void scanShieldedTRC20NotesByOvk(OvkDecryptTRC20Parameters request, StreamObserver responseObserver) { + if (!request.getEventsList().isEmpty()) { + responseObserver.onError(getRunTimeException( + new IllegalArgumentException("'events' field is deprecated and no longer supported"))); + return; + } long startNum = request.getStartBlockIndex(); long endNum = request.getEndBlockIndex(); byte[] contractAddress = request.getShieldedTRC20ContractAddress().toByteArray(); byte[] ovk = request.getOvk().toByteArray(); - ProtocolStringList topicList = request.getEventsList(); try { responseObserver - .onNext(wallet - .scanShieldedTRC20NotesByOvk(startNum, endNum, ovk, contractAddress, topicList)); + .onNext(wallet.scanShieldedTRC20NotesByOvk(startNum, endNum, ovk, contractAddress)); } catch (Exception e) { responseObserver.onError(getRunTimeException(e)); } @@ -2412,6 +2417,11 @@ public void createShieldedContractParametersWithoutAsk( public void scanShieldedTRC20NotesByIvk( IvkDecryptTRC20Parameters request, StreamObserver responseObserver) { + if (!request.getEventsList().isEmpty()) { + responseObserver.onError(getRunTimeException( + new IllegalArgumentException("'events' field is deprecated and no longer supported"))); + return; + } long startNum = request.getStartBlockIndex(); long endNum = request.getEndBlockIndex(); try { @@ -2419,8 +2429,7 @@ public void scanShieldedTRC20NotesByIvk( request.getShieldedTRC20ContractAddress().toByteArray(), request.getIvk().toByteArray(), request.getAk().toByteArray(), - request.getNk().toByteArray(), - request.getEventsList()); + request.getNk().toByteArray()); responseObserver.onNext(decryptNotes); } catch (BadItemException | ZksnarkException e) { responseObserver.onError(getRunTimeException(e)); @@ -2438,13 +2447,17 @@ public void scanShieldedTRC20NotesByIvk( public void scanShieldedTRC20NotesByOvk( OvkDecryptTRC20Parameters request, StreamObserver responseObserver) { + if (!request.getEventsList().isEmpty()) { + responseObserver.onError(getRunTimeException( + new IllegalArgumentException("'events' field is deprecated and no longer supported"))); + return; + } long startNum = request.getStartBlockIndex(); long endNum = request.getEndBlockIndex(); try { DecryptNotesTRC20 decryptNotes = wallet.scanShieldedTRC20NotesByOvk(startNum, endNum, request.getOvk().toByteArray(), - request.getShieldedTRC20ContractAddress().toByteArray(), - request.getEventsList()); + request.getShieldedTRC20ContractAddress().toByteArray()); responseObserver.onNext(decryptNotes); } catch (Exception e) { responseObserver.onError(getRunTimeException(e)); diff --git a/framework/src/main/java/org/tron/core/services/http/ScanShieldedTRC20NotesByIvkServlet.java b/framework/src/main/java/org/tron/core/services/http/ScanShieldedTRC20NotesByIvkServlet.java index 2b52883f490..047fc5c26c9 100644 --- a/framework/src/main/java/org/tron/core/services/http/ScanShieldedTRC20NotesByIvkServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/ScanShieldedTRC20NotesByIvkServlet.java @@ -2,7 +2,6 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; -import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @@ -41,6 +40,10 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) IvkDecryptTRC20Parameters.Builder ivkDecryptTRC20Parameters = IvkDecryptTRC20Parameters .newBuilder(); JsonFormat.merge(params.getParams(), ivkDecryptTRC20Parameters, params.isVisible()); + if (!ivkDecryptTRC20Parameters.getEventsList().isEmpty()) { + throw new IllegalArgumentException( + "'events' field is deprecated and no longer supported"); + } GrpcAPI.DecryptNotesTRC20 notes = wallet .scanShieldedTRC20NotesByIvk(ivkDecryptTRC20Parameters.getStartBlockIndex(), @@ -48,8 +51,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) ivkDecryptTRC20Parameters.getShieldedTRC20ContractAddress().toByteArray(), ivkDecryptTRC20Parameters.getIvk().toByteArray(), ivkDecryptTRC20Parameters.getAk().toByteArray(), - ivkDecryptTRC20Parameters.getNk().toByteArray(), - ivkDecryptTRC20Parameters.getEventsList()); + ivkDecryptTRC20Parameters.getNk().toByteArray()); response.getWriter().println(convertOutput(notes, params.isVisible())); } catch (Exception e) { Util.processError(e, response); @@ -58,6 +60,10 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { + if (request.getParameter("events") != null) { + throw new IllegalArgumentException( + "'events' field is deprecated and no longer supported"); + } boolean visible = Util.getVisible(request); long startNum = Long.parseLong(request.getParameter("start_block_index")); long endNum = Long.parseLong(request.getParameter("end_block_index")); @@ -74,7 +80,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { GrpcAPI.DecryptNotesTRC20 notes = wallet .scanShieldedTRC20NotesByIvk(startNum, endNum, ByteArray.fromHexString(contractAddress), ByteArray.fromHexString(ivk), - ByteArray.fromHexString(ak), ByteArray.fromHexString(nk), null); + ByteArray.fromHexString(ak), ByteArray.fromHexString(nk)); response.getWriter().println(convertOutput(notes, visible)); } catch (Exception e) { Util.processError(e, response); diff --git a/framework/src/main/java/org/tron/core/services/http/ScanShieldedTRC20NotesByOvkServlet.java b/framework/src/main/java/org/tron/core/services/http/ScanShieldedTRC20NotesByOvkServlet.java index b5fbbab1625..b6d3669a79c 100644 --- a/framework/src/main/java/org/tron/core/services/http/ScanShieldedTRC20NotesByOvkServlet.java +++ b/framework/src/main/java/org/tron/core/services/http/ScanShieldedTRC20NotesByOvkServlet.java @@ -1,6 +1,5 @@ package org.tron.core.services.http; -import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @@ -24,14 +23,16 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) OvkDecryptTRC20Parameters.Builder ovkDecryptTRC20Parameters = OvkDecryptTRC20Parameters .newBuilder(); JsonFormat.merge(params.getParams(), ovkDecryptTRC20Parameters, params.isVisible()); + if (!ovkDecryptTRC20Parameters.getEventsList().isEmpty()) { + throw new IllegalArgumentException( + "'events' field is deprecated and no longer supported"); + } GrpcAPI.DecryptNotesTRC20 notes = wallet .scanShieldedTRC20NotesByOvk(ovkDecryptTRC20Parameters.getStartBlockIndex(), ovkDecryptTRC20Parameters.getEndBlockIndex(), ovkDecryptTRC20Parameters.getOvk().toByteArray(), - ovkDecryptTRC20Parameters.getShieldedTRC20ContractAddress().toByteArray(), - ovkDecryptTRC20Parameters.getEventsList() - ); + ovkDecryptTRC20Parameters.getShieldedTRC20ContractAddress().toByteArray()); response.getWriter() .println(ScanShieldedTRC20NotesByIvkServlet.convertOutput(notes, params.isVisible())); } catch (Exception e) { @@ -41,6 +42,10 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) protected void doGet(HttpServletRequest request, HttpServletResponse response) { try { + if (request.getParameter("events") != null) { + throw new IllegalArgumentException( + "'events' field is deprecated and no longer supported"); + } boolean visible = Util.getVisible(request); long startBlockIndex = Long.parseLong(request.getParameter("start_block_index")); long endBlockIndex = Long.parseLong(request.getParameter("end_block_index")); @@ -51,7 +56,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) { } GrpcAPI.DecryptNotesTRC20 notes = wallet .scanShieldedTRC20NotesByOvk(startBlockIndex, endBlockIndex, - ByteArray.fromHexString(ovk), ByteArray.fromHexString(contractAddress), null); + ByteArray.fromHexString(ovk), ByteArray.fromHexString(contractAddress)); response.getWriter() .println(ScanShieldedTRC20NotesByIvkServlet.convertOutput(notes, visible)); diff --git a/framework/src/main/java/org/tron/core/zen/ShieldedTRC20ParametersBuilder.java b/framework/src/main/java/org/tron/core/zen/ShieldedTRC20ParametersBuilder.java index 95e4eeb0ccd..60855491c13 100644 --- a/framework/src/main/java/org/tron/core/zen/ShieldedTRC20ParametersBuilder.java +++ b/framework/src/main/java/org/tron/core/zen/ShieldedTRC20ParametersBuilder.java @@ -14,6 +14,7 @@ import org.tron.api.GrpcAPI; import org.tron.api.GrpcAPI.BytesMessage; import org.tron.api.GrpcAPI.ShieldedTRC20Parameters; +import org.tron.common.math.StrictMathWrapper; import org.tron.common.utils.ByteArray; import org.tron.common.utils.ByteUtil; import org.tron.common.utils.Sha256Hash; @@ -548,7 +549,7 @@ public void addSpend( byte[] path, long position) throws ZksnarkException { spends.add(new SpendDescriptionInfo(expsk, note, anchor, path, position)); - valueBalance += note.getValue(); + valueBalance = StrictMathWrapper.addExact(valueBalance, note.getValue()); } public void addSpend( @@ -559,7 +560,7 @@ public void addSpend( byte[] path, long position) { spends.add(new SpendDescriptionInfo(expsk, note, alpha, anchor, path, position)); - valueBalance += note.getValue(); + valueBalance = StrictMathWrapper.addExact(valueBalance, note.getValue()); } public void addSpend( @@ -571,7 +572,7 @@ public void addSpend( byte[] path, long position) { spends.add(new SpendDescriptionInfo(ak, nsk, note, alpha, anchor, path, position)); - valueBalance += note.getValue(); + valueBalance = StrictMathWrapper.addExact(valueBalance, note.getValue()); } public void addOutput(byte[] ovk, PaymentAddress to, long value, byte[] memo) @@ -579,14 +580,14 @@ public void addOutput(byte[] ovk, PaymentAddress to, long value, byte[] memo) Note note = new Note(to, value); note.setMemo(memo); receives.add(new ReceiveDescriptionInfo(ovk, note)); - valueBalance -= value; + valueBalance = StrictMathWrapper.subtractExact(valueBalance, value); } public void addOutput(byte[] ovk, DiversifierT d, byte[] pkD, long value, byte[] r, byte[] memo) { Note note = new Note(d, pkD, value, r); note.setMemo(memo); receives.add(new ReceiveDescriptionInfo(ovk, note)); - valueBalance -= value; + valueBalance = StrictMathWrapper.subtractExact(valueBalance, value); } public static class SpendDescriptionInfo { diff --git a/framework/src/main/java/org/tron/core/zen/ZenTransactionBuilder.java b/framework/src/main/java/org/tron/core/zen/ZenTransactionBuilder.java index 2e531e44d44..f41ed666b57 100644 --- a/framework/src/main/java/org/tron/core/zen/ZenTransactionBuilder.java +++ b/framework/src/main/java/org/tron/core/zen/ZenTransactionBuilder.java @@ -10,6 +10,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; +import org.tron.common.math.StrictMathWrapper; import org.tron.common.utils.ByteArray; import org.tron.common.zksnark.IncrementalMerkleVoucherContainer; import org.tron.common.zksnark.JLibrustzcash; @@ -67,7 +68,7 @@ public ZenTransactionBuilder() { public void addSpend(SpendDescriptionInfo spendDescriptionInfo) { spends.add(spendDescriptionInfo); - valueBalance += spendDescriptionInfo.note.getValue(); + valueBalance = StrictMathWrapper.addExact(valueBalance, spendDescriptionInfo.note.getValue()); } public void addSpend( @@ -76,7 +77,7 @@ public void addSpend( byte[] anchor, IncrementalMerkleVoucherContainer voucher) throws ZksnarkException { spends.add(new SpendDescriptionInfo(expsk, note, anchor, voucher)); - valueBalance += note.getValue(); + valueBalance = StrictMathWrapper.addExact(valueBalance, note.getValue()); } public void addSpend( @@ -86,7 +87,7 @@ public void addSpend( byte[] anchor, IncrementalMerkleVoucherContainer voucher) { spends.add(new SpendDescriptionInfo(expsk, note, alpha, anchor, voucher)); - valueBalance += note.getValue(); + valueBalance = StrictMathWrapper.addExact(valueBalance, note.getValue()); } public void addSpend( @@ -98,7 +99,7 @@ public void addSpend( byte[] anchor, IncrementalMerkleVoucherContainer voucher) { spends.add(new SpendDescriptionInfo(ak, nsk, ovk, note, alpha, anchor, voucher)); - valueBalance += note.getValue(); + valueBalance = StrictMathWrapper.addExact(valueBalance, note.getValue()); } public void addOutput(byte[] ovk, PaymentAddress to, long value, byte[] memo) @@ -106,14 +107,14 @@ public void addOutput(byte[] ovk, PaymentAddress to, long value, byte[] memo) Note note = new Note(to, value); note.setMemo(memo); receives.add(new ReceiveDescriptionInfo(ovk, note)); - valueBalance -= value; + valueBalance = StrictMathWrapper.subtractExact(valueBalance, value); } public void addOutput(byte[] ovk, DiversifierT d, byte[] pkD, long value, byte[] r, byte[] memo) { Note note = new Note(d, pkD, value, r); note.setMemo(memo); receives.add(new ReceiveDescriptionInfo(ovk, note)); - valueBalance -= value; + valueBalance = StrictMathWrapper.subtractExact(valueBalance, value); } public void setTransparentInput(byte[] address, long value) { diff --git a/framework/src/main/resources/config.conf b/framework/src/main/resources/config.conf index 369924074bc..05c7ab56378 100644 --- a/framework/src/main/resources/config.conf +++ b/framework/src/main/resources/config.conf @@ -178,7 +178,13 @@ node { minParticipationRate = 15 - # allowShieldedTransactionApi = true + # WARNING: Some shielded transaction APIs require sending private keys as parameters. + # Calling these APIs on untrusted or remote nodes may leak your private keys. + # It is recommended to invoke them locally for development and testing. + # To opt in, set: allowShieldedTransactionApi = true + # Migration: the legacy key node.fullNodeAllowShieldedTransaction is still supported + # but deprecated; please migrate to node.allowShieldedTransactionApi. + # allowShieldedTransactionApi = false # openPrintLog = true 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..237aa9d0789 100644 --- a/framework/src/test/java/org/tron/common/crypto/ECKeyTest.java +++ b/framework/src/test/java/org/tron/common/crypto/ECKeyTest.java @@ -5,7 +5,7 @@ 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; @@ -19,12 +19,12 @@ import org.bouncycastle.util.encoders.Hex; import org.junit.Test; import org.tron.common.crypto.ECKey.ECDSASignature; +import org.tron.common.utils.ByteUtil; import org.tron.core.Wallet; /** - * The reason the test case uses the private key plaintext is to ensure that, - * after the ECkey tool or algorithm is upgraded, - * the upgraded differences can be verified. + * The reason the test case uses the private key plaintext is to ensure that, after the ECkey tool + * or algorithm is upgraded, the upgraded differences can be verified. */ @Slf4j public class ECKeyTest { @@ -69,10 +69,36 @@ 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 testFromPrivateKeyRejectsZeroValue() { + byte[] zeroPrivateKey = new byte[32]; + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> ECKey.fromPrivate(zeroPrivateKey)); + assertEquals("Invalid private key.", exception.getMessage()); + } + + @Test + public void testFromPrivateKeyRejectsCurveOrder() { + byte[] curveOrder = ByteUtil.bigIntegerToBytes(ECKey.CURVE.getN(), 32); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> ECKey.fromPrivate(curveOrder)); + assertEquals("Invalid private key.", exception.getMessage()); + } + + @Test + public void testInvalidPublicKeyEncoding() { + byte[] invalidPublicKey = new byte[33]; + invalidPublicKey[0] = 0x02; + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> new ECKey(invalidPublicKey, false)); + assertEquals("Invalid public key.", exception.getMessage()); } @Test(expected = IllegalArgumentException.class) diff --git a/framework/src/test/java/org/tron/common/crypto/SM2KeyTest.java b/framework/src/test/java/org/tron/common/crypto/SM2KeyTest.java index 87e4e14698c..5ed228680e7 100644 --- a/framework/src/test/java/org/tron/common/crypto/SM2KeyTest.java +++ b/framework/src/test/java/org/tron/common/crypto/SM2KeyTest.java @@ -4,7 +4,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; 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; @@ -14,6 +14,9 @@ import java.security.SignatureException; import java.util.Arrays; import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.crypto.digests.SM3Digest; import org.bouncycastle.util.encoders.Hex; import org.junit.Test; @@ -66,10 +69,8 @@ public void testFromPrivateKey() { assertTrue(key.hasPrivKey()); assertArrayEquals(pubKey, key.getPubKey()); - key = SM2.fromPrivate((byte[]) null); - assertNull(key); - key = SM2.fromPrivate(new byte[0]); - assertNull(key); + assertThrows(IllegalArgumentException.class, () -> SM2.fromPrivate((byte[]) null)); + assertThrows(IllegalArgumentException.class, () -> SM2.fromPrivate(new byte[0])); } @Test(expected = IllegalArgumentException.class) @@ -281,4 +282,95 @@ public void testSM3() { assertEquals("b524f552cd82b8b028476e005c377fb19a87e6fc682d48bb5d42e3d9b9effe76", Hex.toHexString(eHash)); } + + /** + * Verifies SM2 signatures are deterministic with HMacDSAKCalculator (RFC 6979). + */ + @Test + public void testDeterministicSignature() { + SM2 key = SM2.fromPrivate(privateKey); + byte[] hash = Hex.decode("B524F552CD82B8B028476E005C377FB" + + "19A87E6FC682D48BB5D42E3D9B9EFFE76"); + + SM2.SM2Signature signature1 = key.sign(hash); + SM2.SM2Signature signature2 = key.sign(hash); + SM2.SM2Signature signature3 = key.sign(hash); + + assertEquals(signature1.r, signature2.r); + assertEquals(signature1.s, signature2.s); + assertEquals(signature2.r, signature3.r); + assertEquals(signature2.s, signature3.s); + } + + /** + * Test that different messages produce different signatures. + */ + @Test + public void testDifferentMessagesSignatures() { + SM2 key = SM2.fromPrivate(privateKey); + byte[] hash1 = Hex.decode("B524F552CD82B8B028476E005C377FB" + + "19A87E6FC682D48BB5D42E3D9B9EFFE76"); + byte[] hash2 = Hex.decode("C524F552CD82B8B028476E005C377FB" + + "19A87E6FC682D48BB5D42E3D9B9EFFE77"); + + SM2.SM2Signature signature1 = key.sign(hash1); + SM2.SM2Signature signature2 = key.sign(hash2); + + // Different messages should produce different signatures + // (with overwhelming probability) + assertFalse("Different messages should produce different signatures", + signature1.r.equals(signature2.r) && signature1.s.equals(signature2.s)); + } + + /** + * Test that signature verification still works correctly with deterministic signatures. + */ + @Test + public void testSignatureVerification() { + SM2 key = SM2.fromPrivate(privateKey); + String message = "Hello, SM2 deterministic signature test!"; + byte[] hash = key.getSM2SignerForHash().generateSM3Hash(message.getBytes()); + + SM2.SM2Signature signature = key.sign(hash); + + // Verify the signature + SM2Signer verifier = key.getSM2SignerForHash(); + boolean isValid = verifier.verifyHashSignature(hash, signature.r, signature.s); + + assertTrue("Signature should be valid", isValid); + } + + @Test + public void testVerifyDerEncodedSignatureCompatibility() throws Exception { + SM2 key = SM2.fromPrivate(privateKey); + byte[] hash = Hex.decode("B524F552CD82B8B028476E005C377FB" + + "19A87E6FC682D48BB5D42E3D9B9EFFE76"); + SM2.SM2Signature signature = key.sign(hash); + + assertTrue(SM2.verify(hash, toDer(signature), key.getPubKey())); + } + + @Test + public void testVerifyDerEncodedMalformedSignatureReturnsFalse() { + SM2 key = SM2.fromPrivate(privateKey); + byte[] hash = Hex.decode("B524F552CD82B8B028476E005C377FB" + + "19A87E6FC682D48BB5D42E3D9B9EFFE76"); + + assertFalse(SM2.verify(hash, new byte[] {0x01, 0x02, 0x03}, key.getPubKey())); + } + + @Test + public void testDecodeFromDerRejectsOversizedSignature() { + SignatureException exception = assertThrows(SignatureException.class, + () -> SM2.SM2Signature.decodeFromDER(new byte[129])); + + assertEquals("Invalid DER signature length", exception.getMessage()); + } + + private byte[] toDer(SM2.SM2Signature signature) throws Exception { + ASN1EncodableVector vector = new ASN1EncodableVector(); + vector.add(new ASN1Integer(signature.r)); + vector.add(new ASN1Integer(signature.s)); + return new DERSequence(vector).getEncoded(); + } } diff --git a/framework/src/test/java/org/tron/core/ShieldedTRC20BuilderTest.java b/framework/src/test/java/org/tron/core/ShieldedTRC20BuilderTest.java index c2c4bfe3006..6498a223df1 100644 --- a/framework/src/test/java/org/tron/core/ShieldedTRC20BuilderTest.java +++ b/framework/src/test/java/org/tron/core/ShieldedTRC20BuilderTest.java @@ -76,6 +76,7 @@ public class ShieldedTRC20BuilderTest extends BaseTest { @BeforeClass public static void initZksnarkParams() { ZksnarkInitService.librustzcashInitZksnarkParams(); + Args.getInstance().allowShieldedTransactionApi = true; } @Ignore @@ -2243,7 +2244,7 @@ public void testScanShieldedTRC20NotesByIvk() throws Exception { byte[] ivk = fvk.inViewingKey().value; GrpcAPI.DecryptNotesTRC20 scannedNotes = wallet.scanShieldedTRC20NotesByIvk( - statNum, endNum, SHIELDED_CONTRACT_ADDRESS, ivk, fvk.getAk(), fvk.getNk(), null); + statNum, endNum, SHIELDED_CONTRACT_ADDRESS, ivk, fvk.getAk(), fvk.getNk()); for (GrpcAPI.DecryptNotesTRC20.NoteTx noteTx : scannedNotes.getNoteTxsList()) { logger.info(noteTx.toString()); @@ -2258,7 +2259,7 @@ public void testscanShieldedTRC20NotesByOvk() throws Exception { FullViewingKey fvk = sk.fullViewingKey(); GrpcAPI.DecryptNotesTRC20 scannedNotes = wallet.scanShieldedTRC20NotesByOvk( - statNum, endNum, fvk.getOvk(), SHIELDED_CONTRACT_ADDRESS, null); + statNum, endNum, fvk.getOvk(), SHIELDED_CONTRACT_ADDRESS); for (GrpcAPI.DecryptNotesTRC20.NoteTx noteTx : scannedNotes.getNoteTxsList()) { logger.info(noteTx.toString()); @@ -2274,7 +2275,7 @@ public void isShieldedTRC20ContractNoteSpent() throws Exception { byte[] ivk = fvk.inViewingKey().value; GrpcAPI.DecryptNotesTRC20 scannedNotes = wallet.scanShieldedTRC20NotesByIvk( - statNum, endNum, SHIELDED_CONTRACT_ADDRESS, ivk, fvk.getAk(), fvk.getNk(), null); + statNum, endNum, SHIELDED_CONTRACT_ADDRESS, ivk, fvk.getAk(), fvk.getNk()); for (GrpcAPI.DecryptNotesTRC20.NoteTx noteTx : scannedNotes.getNoteTxsList()) { logger.info(noteTx.toString()); @@ -2461,6 +2462,4 @@ private byte[] longTo32Bytes(long value) { byte[] zeroBytes = new byte[24]; return ByteUtil.merge(zeroBytes, longBytes); } - - } diff --git a/framework/src/test/java/org/tron/core/WalletMockTest.java b/framework/src/test/java/org/tron/core/WalletMockTest.java index efcf0da8d0d..cf381f83e48 100644 --- a/framework/src/test/java/org/tron/core/WalletMockTest.java +++ b/framework/src/test/java/org/tron/core/WalletMockTest.java @@ -17,9 +17,6 @@ import com.google.common.cache.CacheBuilder; import com.google.protobuf.Any; import com.google.protobuf.ByteString; -import com.google.protobuf.LazyStringArrayList; -import com.google.protobuf.ProtocolStringList; - import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -1058,19 +1055,16 @@ public void testGetShieldedTRC20LogType() { Wallet wallet = new Wallet(); Protocol.TransactionInfo.Log log = Protocol.TransactionInfo.Log.newBuilder().build(); byte[] contractAddress = "contractAddress".getBytes(StandardCharsets.UTF_8); - LazyStringArrayList topicsList = new LazyStringArrayList(); Throwable thrown = assertThrows(InvocationTargetException.class, () -> { Method privateMethod = Wallet.class.getDeclaredMethod( "getShieldedTRC20LogType", Protocol.TransactionInfo.Log.class, - byte[].class, - ProtocolStringList.class); + byte[].class); privateMethod.setAccessible(true); privateMethod.invoke(wallet, log, - contractAddress, - topicsList); + contractAddress); }); Throwable cause = thrown.getCause(); assertTrue(cause instanceof ZksnarkException); @@ -1088,18 +1082,15 @@ public void testGetShieldedTRC20LogType1() { .setAddress(ByteString.copyFrom(addressWithoutPrefix)) .build(); - LazyStringArrayList topicsList = new LazyStringArrayList(); try { Method privateMethod = Wallet.class.getDeclaredMethod( "getShieldedTRC20LogType", Protocol.TransactionInfo.Log.class, - byte[].class, - ProtocolStringList.class); + byte[].class); privateMethod.setAccessible(true); privateMethod.invoke(wallet, log, - contractAddress, - topicsList); + contractAddress); } catch (Exception e) { assertTrue(false); } @@ -1119,19 +1110,15 @@ public void testGetShieldedTRC20LogType2() { .addTopics(ByteString.copyFrom("topic".getBytes())) .build(); - LazyStringArrayList topicsList = new LazyStringArrayList(); - topicsList.add("topic"); try { Method privateMethod = Wallet.class.getDeclaredMethod( "getShieldedTRC20LogType", Protocol.TransactionInfo.Log.class, - byte[].class, - ProtocolStringList.class); + byte[].class); privateMethod.setAccessible(true); privateMethod.invoke(wallet, log, - contractAddress, - topicsList); + contractAddress); } catch (Exception e) { assertTrue(false); } diff --git a/framework/src/test/java/org/tron/core/actuator/ShieldedTransferActuatorTest.java b/framework/src/test/java/org/tron/core/actuator/ShieldedTransferActuatorTest.java index faec4c74039..919f63f6f28 100755 --- a/framework/src/test/java/org/tron/core/actuator/ShieldedTransferActuatorTest.java +++ b/framework/src/test/java/org/tron/core/actuator/ShieldedTransferActuatorTest.java @@ -1157,6 +1157,9 @@ public void publicAddressToShieldNoteValueFailure() { actuator.validate(); actuator.execute(ret); Assert.assertTrue(false); + } catch (ArithmeticException e) { + // StrictMathWrapper.subtractExact throws ArithmeticException on overflow + Assert.assertTrue(true); } catch (ContractValidateException e) { Assert.assertTrue(e instanceof ContractValidateException); Assert.assertEquals("librustzcashSaplingFinalCheck error", e.getMessage()); @@ -1346,5 +1349,58 @@ public void shieldAddressToPublic() { Assert.assertTrue(false); } } + + /** + * Test that shielded transfer transaction validation works even when + * allowShieldedTransactionApi is disabled. This verifies that the API flag + * only gates wallet/helper APIs, not the core transaction validation logic. + */ + @Test + public void shieldedTransferValidationWorksWhenApiDisabled() { + // Disable the shielded API (this should NOT affect transaction validation) + Args.getInstance().setAllowShieldedTransactionApi(false); + + dbManager.getDynamicPropertiesStore().saveAllowShieldedTransaction(1); + dbManager.getDynamicPropertiesStore().saveTotalShieldedPoolValue(AMOUNT); + + try { + ZenTransactionBuilder builder = new ZenTransactionBuilder(wallet); + SpendingKey sk = SpendingKey.random(); + ExpandedSpendingKey expsk = sk.expandedSpendingKey(); + PaymentAddress address = sk.defaultAddress(); + Note note = new Note(address, AMOUNT); + IncrementalMerkleVoucherContainer voucher = createSimpleMerkleVoucherContainer(note.cm()); + byte[] anchor = voucher.root().getContent().toByteArray(); + dbManager.getMerkleContainer() + .putMerkleTreeIntoStore(anchor, voucher.getVoucherCapsule().getTree()); + builder.addSpend(expsk, note, anchor, voucher); + + addZeroValueOutputNote(builder); + + long fee = dbManager.getDynamicPropertiesStore().getShieldedTransactionCreateAccountFee(); + String addressNotExist = + Wallet.getAddressPreFixString() + "8ba2aaae540c642e44e3bed5522c63bbc21f0000"; + + builder.setTransparentOutput(ByteArray.fromHexString(addressNotExist), AMOUNT - fee); + + TransactionCapsule transactionCap = builder.build(); + Contract contract = + transactionCap.getInstance().toBuilder().getRawDataBuilder().getContract(0); + ShieldedTransferActuator actuator = new ShieldedTransferActuator(); + actuator.setChainBaseManager(dbManager.getChainBaseManager()).setContract(contract) + .setTx(transactionCap); + + // Validation should succeed even when API is disabled + actuator.validate(); + } catch (ContractValidateException e) { + Assert.fail("Shielded transfer validation should not throw ContractValidateException: " + + e.getMessage()); + } catch (Exception e) { + Assert.fail("Shielded transfer should not throw Exception: " + e.getMessage()); + } finally { + // Restore default for other tests + Args.getInstance().setAllowShieldedTransactionApi(true); + } + } } diff --git a/framework/src/test/java/org/tron/core/config/args/ArgsTest.java b/framework/src/test/java/org/tron/core/config/args/ArgsTest.java index 88d893bcf97..e8b5d1e80dc 100644 --- a/framework/src/test/java/org/tron/core/config/args/ArgsTest.java +++ b/framework/src/test/java/org/tron/core/config/args/ArgsTest.java @@ -360,5 +360,31 @@ public void testConfigStorageDefaults() { Args.clearParam(); } + + /** + * Test that the default value of allowShieldedTransactionApi is false. + * This verifies the security enhancement is applied by default. + */ + @Test + public void testDefaultAllowShieldedTransactionApiIsFalse() { + // After Args.setParam() without explicit allowShieldedTransactionApi, + // the default should be false + Args.setParam(new String[] {}, TestConstants.TEST_CONF); + boolean defaultValue = Args.getInstance().isAllowShieldedTransactionApi(); + Assert.assertFalse("Default value of allowShieldedTransactionApi should be false", + defaultValue); + } + + /** + * Test that setting allowShieldedTransactionApi to true works correctly. + */ + @Test + public void testExplicitEnableShieldedTransactionApi() { + Args.setParam(new String[] {}, TestConstants.TEST_CONF); + // Enable the flag + Args.getInstance().setAllowShieldedTransactionApi(true); + Assert.assertTrue("Should be able to enable allowShieldedTransactionApi", + Args.getInstance().isAllowShieldedTransactionApi()); + } } diff --git a/framework/src/test/java/org/tron/core/config/args/WitnessInitializerTest.java b/framework/src/test/java/org/tron/core/config/args/WitnessInitializerTest.java index 3ecef5b10c9..8950d093ce0 100644 --- a/framework/src/test/java/org/tron/core/config/args/WitnessInitializerTest.java +++ b/framework/src/test/java/org/tron/core/config/args/WitnessInitializerTest.java @@ -110,7 +110,7 @@ public void testInitFromKeystore() { mockedByteArray.when(() -> ByteArray.toHexString(any())) .thenReturn(privateKey); mockedByteArray.when(() -> ByteArray.fromHexString(anyString())) - .thenReturn(keyBytes); + .thenAnswer(inv -> Hex.decode(privateKey)); LocalWitnesses result = WitnessInitializer.initFromKeystore( keystores, "password", null); diff --git a/framework/src/test/java/org/tron/core/services/RpcApiServicesTest.java b/framework/src/test/java/org/tron/core/services/RpcApiServicesTest.java index f40ec48e035..c93de1ce38b 100644 --- a/framework/src/test/java/org/tron/core/services/RpcApiServicesTest.java +++ b/framework/src/test/java/org/tron/core/services/RpcApiServicesTest.java @@ -1,6 +1,7 @@ package org.tron.core.services; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; import static org.tron.common.parameter.CommonParameter.getInstance; import static org.tron.common.utils.client.WalletClient.decodeFromBase58Check; import static org.tron.protos.Protocol.Transaction.Contract.ContractType.TransferContract; @@ -9,6 +10,7 @@ import com.google.protobuf.ByteString; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; +import io.grpc.StatusRuntimeException; import java.io.IOException; import java.util.Objects; import java.util.concurrent.ExecutorService; @@ -148,8 +150,9 @@ public class RpcApiServicesTest { @BeforeClass public static void init() throws IOException { - Args.setParam(new String[] {"-d", temporaryFolder.newFolder().toString()}, + Args.setParam(new String[]{"-d", temporaryFolder.newFolder().toString()}, TestConstants.TEST_CONF); + getInstance().allowShieldedTransactionApi = true; Assert.assertEquals(5, getInstance().getRpcMaxRstStream()); Assert.assertEquals(10, getInstance().getRpcSecondsPerWindow()); String OWNER_ADDRESS = Wallet.getAddressPreFixString() @@ -556,6 +559,54 @@ public void testScanShieldedTRC20NotesByOvk() { assertNotNull(blockingStubPBFT.scanShieldedTRC20NotesByOvk(message)); } + @Test + public void testScanShieldedTRC20NotesByIvkRejectsDeprecatedEvents() { + IvkDecryptTRC20Parameters message = IvkDecryptTRC20Parameters.newBuilder() + .setStartBlockIndex(1) + .setEndBlockIndex(10) + .addEvents("mint") + .build(); + + StatusRuntimeException fullException = assertThrows(StatusRuntimeException.class, + () -> blockingStubFull.scanShieldedTRC20NotesByIvk(message)); + Assert.assertTrue(fullException.getStatus().getDescription() + .contains("'events' field is deprecated and no longer supported")); + + StatusRuntimeException solidityException = assertThrows(StatusRuntimeException.class, + () -> blockingStubSolidity.scanShieldedTRC20NotesByIvk(message)); + Assert.assertTrue(solidityException.getStatus().getDescription() + .contains("'events' field is deprecated and no longer supported")); + + StatusRuntimeException pbftException = assertThrows(StatusRuntimeException.class, + () -> blockingStubPBFT.scanShieldedTRC20NotesByIvk(message)); + Assert.assertTrue(pbftException.getStatus().getDescription() + .contains("'events' field is deprecated and no longer supported")); + } + + @Test + public void testScanShieldedTRC20NotesByOvkRejectsDeprecatedEvents() { + OvkDecryptTRC20Parameters message = OvkDecryptTRC20Parameters.newBuilder() + .setStartBlockIndex(1) + .setEndBlockIndex(10) + .addEvents("burn") + .build(); + + StatusRuntimeException fullException = assertThrows(StatusRuntimeException.class, + () -> blockingStubFull.scanShieldedTRC20NotesByOvk(message)); + Assert.assertTrue(fullException.getStatus().getDescription() + .contains("'events' field is deprecated and no longer supported")); + + StatusRuntimeException solidityException = assertThrows(StatusRuntimeException.class, + () -> blockingStubSolidity.scanShieldedTRC20NotesByOvk(message)); + Assert.assertTrue(solidityException.getStatus().getDescription() + .contains("'events' field is deprecated and no longer supported")); + + StatusRuntimeException pbftException = assertThrows(StatusRuntimeException.class, + () -> blockingStubPBFT.scanShieldedTRC20NotesByOvk(message)); + Assert.assertTrue(pbftException.getStatus().getDescription() + .contains("'events' field is deprecated and no longer supported")); + } + // @Test // public void testIsShieldedTRC20ContractNoteSpent() { // NfTRC20Parameters message = NfTRC20Parameters.newBuilder().build(); diff --git a/framework/src/test/java/org/tron/core/services/http/CreateSpendAuthSigServletTest.java b/framework/src/test/java/org/tron/core/services/http/CreateSpendAuthSigServletTest.java index 85d6764132b..8a54aa265a3 100644 --- a/framework/src/test/java/org/tron/core/services/http/CreateSpendAuthSigServletTest.java +++ b/framework/src/test/java/org/tron/core/services/http/CreateSpendAuthSigServletTest.java @@ -24,6 +24,7 @@ public class CreateSpendAuthSigServletTest extends BaseTest { "--output-directory", dbPath(), }, TestConstants.TEST_CONF ); + Args.getInstance().allowShieldedTransactionApi = true; } @Resource diff --git a/framework/src/test/java/org/tron/core/zksnark/MerkleContainerTest.java b/framework/src/test/java/org/tron/core/zksnark/MerkleContainerTest.java index ed52e014a7b..360ca076be6 100644 --- a/framework/src/test/java/org/tron/core/zksnark/MerkleContainerTest.java +++ b/framework/src/test/java/org/tron/core/zksnark/MerkleContainerTest.java @@ -40,6 +40,7 @@ public class MerkleContainerTest extends BaseTest { static { Args.setParam(new String[]{"-d", dbPath()}, TestConstants.TEST_CONF); + Args.getInstance().allowShieldedTransactionApi = true; } /*@Before diff --git a/protocol/src/main/protos/api/api.proto b/protocol/src/main/protos/api/api.proto index d66d8aa6263..9d5a144f6af 100644 --- a/protocol/src/main/protos/api/api.proto +++ b/protocol/src/main/protos/api/api.proto @@ -1498,7 +1498,7 @@ message IvkDecryptTRC20Parameters { bytes ivk = 4; bytes ak = 5; bytes nk = 6; - repeated string events = 7; + repeated string events = 7 [deprecated = true]; } message OvkDecryptTRC20Parameters { @@ -1506,7 +1506,7 @@ message OvkDecryptTRC20Parameters { int64 end_block_index = 2; bytes ovk = 3; bytes shielded_TRC20_contract_address = 4; - repeated string events = 5; + repeated string events = 5 [deprecated = true]; } message DecryptNotesTRC20 { diff --git a/sprout-verifying.key b/sprout-verifying.key deleted file mode 100644 index 16655abb5d7..00000000000 Binary files a/sprout-verifying.key and /dev/null differ