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..76a568ca799 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 @@ -7,20 +7,19 @@ import java.io.IOException; import java.io.Serializable; 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; import javax.annotation.Nullable; 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,72 +40,71 @@ 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; /** - * Implement Chinese Commercial Cryptographic Standard of SM2 + * Blockchain-adapted SM2 signature implementation (GB/T 32918 / GM/T 0003). + * + *
Non-standard usage: The SM2 standard computes signatures over + * {@code e = SM3(Z_A || M)}, where {@code Z_A} binds the signer's identity and public key to the + * message. This implementation omits the {@code Z_A} step and signs the 32-byte transaction hash + * directly, consistent with how ECDSA is used on TRON. This keeps both cryptographic engines + * interchangeable at the {@link org.tron.common.crypto.SignInterface} level. + * + *
Note: Signatures produced here are not interoperable with standard SM2 + * implementations that apply the {@code Z_A} pre-hash. */ @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 - */ - /** - * Generate a new keypair using the given Java Security Provider. - * - *
All private key operations will use the provider. + * Generates a new keypair using the given SecureRandom source. */ public SM2(SecureRandom secureRandom) { - ECKeyGenerationParameters ecKeyGenerationParameters = new ECKeyGenerationParameters(ecc_param, + ECKeyGenerationParameters ecKeyGenerationParameters = new ECKeyGenerationParameters(eccParam, secureRandom); ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); keyPairGenerator.init(ecKeyGenerationParameters); @@ -121,40 +119,51 @@ 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; + try { + point = eccParam.getCurve().decodePoint(key); + } catch (RuntimeException e) { + throw new IllegalArgumentException("Public key is not a valid point on SM2 curve.", e); + } + 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. + * Pairs a private key with a public EC point. */ - 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"); - } else { - this.pub = pub; + if (pub == null || pub.isInfinity() || !pub.isValid()) { + throw new IllegalArgumentException("Public key is not a valid point on SM2 curve."); } + this.pub = pub; } /** @@ -162,168 +171,120 @@ 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 - * - * it is not sufficient to check that privKey is a subtype of ECPrivateKey - * as the SunPKCS11 Provider will return a generic PrivateKey instance - * a fallback that covers this case is to check the key algorithm + /** + * Tests if a generic private key is an EC private key (checks both type and algorithm + * to handle SunPKCS11 providers that return a generic PrivateKey). */ private static boolean isECPrivateKey(PrivateKey privKey) { return privKey instanceof ECPrivateKey || privKey.getAlgorithm() .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 - * compressed. See the ECKey class docs for a discussion of point compression. + * Compresses an elliptic curve point. Returns the same point if already compressed. * - * @param uncompressed - - * @return - * @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 - * compressed. See the ECKey class docs for a discussion of point compression. + * Decompresses an elliptic curve point. Returns the same point if already decompressed. * - * @param compressed - - * @return - * @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)); } /** * Creates an SM2 given the private key only. - * - * @param privKey - - * @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)); } /** - * Creates an SM2 given the private key only. - * - * @param privKeyBytes - - * @return - + * Creates an SM2 given the private key bytes. */ 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; + } + // Accept a 33-byte array only when the leading byte is 0x00 (BigInteger sign-byte padding); + // reject anything longer or any non-canonical 33-byte encoding. + if (keyBytes.length > 33 || (keyBytes.length == 33 && keyBytes[0] != 0x00)) { + 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 - * encoded point. The compression state of pub will be preserved. - * - * @param pub - - * @return - + * Creates a public-key-only SM2 from the given encoded point (cannot sign, only verify). */ public static SM2 fromPublicOnly(byte[] pub) { - return new SM2((PrivateKey) null, ecc_param.getCurve().decodePoint(pub)); + return new SM2(pub, false); } /** - * 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 compressed - - * @return - + * Returns the encoded public key bytes derived from the given private key. */ - public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean - compressed) { - ECPoint point = ecc_param.getG().multiply(privKey); + public static byte[] publicKeyFromPrivate(BigInteger privKey, boolean compressed) { + if (!isValidPrivateKey(privKey)) { + throw new IllegalArgumentException("Invalid private key in SM2."); + } + 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 - * 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 @@ -339,6 +300,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 +310,11 @@ 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); @@ -357,9 +324,9 @@ public static byte[] signatureToKeyBytes(byte[] messageHash, String throw new SignatureException("Could not decode base64", e); } // Parse the signature bytes into r/s and the selector value. - if (signatureEncoded.length < 65) { - throw new SignatureException("Signature truncated, expected 65 " + - "bytes and got " + signatureEncoded.length); + if (signatureEncoded.length != 65) { + throw new SignatureException("Invalid signature length, expected 65 bytes and got " + + signatureEncoded.length); } return signatureToKeyBytes( @@ -371,14 +338,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,12 +365,6 @@ public static byte[] signatureToKeyBytes(byte[] messageHash, return key; } - - public byte[] hash(byte[] message) { - SM2Signer signer = this.getSM2SignerForHash(); - return signer.generateSM3Hash(message); - } - @Override public byte[] getPrivateKey() { return getPrivKeyBytes(); @@ -427,19 +390,18 @@ 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 +410,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 the R and S components of the signature * @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,57 +436,34 @@ public static SM2 signatureToKey(byte[] messageHash, String * Compute the key that signed the given signature. * * @param messageHash 32-byte hash of message - * @param sig - - * @return ECKey + * @param sig the R and S components of the signature + * @return SM2 */ - 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 - * - * @param messageHash - - * @return - - * @throws IllegalStateException if this ECKey does not have the private part. + * Signs the given 32-byte hash with recovery ID. */ 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 - * - * @param input to sign - * @return SM2Signature signature that contains the R and S components + * Signs the given 32-byte hash and returns the Base64-encoded signature. */ public String signHash(byte[] input) { return sign(input).toBase64(); @@ -538,111 +476,77 @@ public byte[] Base64toBytes(String signature) { return ByteUtil.appendByte(temp, first); } - /** - * Takes the message of data and returns the SM2 signature - * - * @param message - - * @return - - * @throws IllegalStateException if this ECKey does not have the private part. - */ - 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; + private int findRecId(SM2Signature sig, byte[] messageHash) { 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; + return i; } } - if (recId == -1) { - throw new RuntimeException("Could not construct a recoverable key" + - ". This should never happen."); - } - sig.v = (byte) (recId + 27); - return sig; - } - - /** - * 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) { - throw new IllegalArgumentException("Expected signature message of " + - "SM2 is null"); - } - // No decryption of private key required. - SM2Signer signer = getSigner(); - BigInteger[] componets = signer.generateSignature(msg); - return new SM2Signature(componets[0], componets[1]); + 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); - signer.init(true, privateKeyParameters); + ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(d, eccParam); + signer.init(privateKeyParameters); return signer; } /** - * used to generate the SM3 hash for SM2 signature generation or verification + * Returns an {@link SM2Signer} initialized with this key's public key, ready to call + * {@link SM2Signer#verifyHashSignature}. */ - public SM2Signer getSM2SignerForHash() { + public SM2Signer getVerifier() { SM2Signer signer = new SM2Signer(); - ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pub, ecc_param); - signer.init(false, publicKeyParameters); + ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pub, eccParam); + signer.init(publicKeyParameters); return signer; } - /** - *
Given the components of a signature and a selector value, recover and return the public key - * that generated the signature + * Recovers the public key from a signature, per SM2 recovery algorithm. + * + *
recId (0-3) selects which of the candidate keys to return. */ @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,36 +556,35 @@ 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); } /** - * Decompress a compressed public key (x co-ord and low-bit of y-coord). - * - * @param xBN - - * @param yBit - - * @return - + * Decompresses a public key from x-coordinate and y-parity bit. */ - 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,23 +594,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 32-byte 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 hash 32-byte hash of the data to verify + * @param signature signature + * @param pub public key bytes to use + * @return true if the signature is valid */ - public static boolean verify(byte[] data, SM2Signature signature, + public static boolean verify(byte[] hash, SM2Signature signature, byte[] pub) { + if (ByteArray.isEmpty(hash) || signature == null || ByteArray.isEmpty(pub)) { + throw new IllegalArgumentException("Hash, signature, or public key cannot be null"); + } SM2Signer signer = new SM2Signer(); - ECPublicKeyParameters params = new ECPublicKeyParameters(ecc_param - .getCurve().decodePoint(pub), ecc_param); - signer.init(false, params); + ECPoint pubPoint; try { - return signer.verifyHashSignature(data, signature.r, signature.s); + pubPoint = eccParam.getCurve().decodePoint(pub); + } catch (RuntimeException e) { + throw new IllegalArgumentException("Public key is not a valid point on SM2 curve.", e); + } + ECPublicKeyParameters params = new ECPublicKeyParameters(pubPoint, eccParam); + signer.init(params); + try { + return signer.verifyHashSignature(hash, signature.r, signature.s); } catch (NullPointerException npe) { // Bouncy Castle contains a bug that can cause NPEs given // specially crafted signatures. @@ -719,63 +628,28 @@ 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 hash 32-byte 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); - } - - /** - *Verifies the given 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 - - */ - 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); + public static boolean verify(byte[] hash, byte[] signature, byte[] pub) { + if (hash == null || signature == null || pub == null) { + return false; + } 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 verify(hash, SM2Signature.decodeFromDER(signature), pub); + } catch (IllegalArgumentException | SignatureException e) { return false; } } - /** - * Verifies the given ASN.1 encoded SM2 signature against a hash using the public key. - * - * @param msg the message data to verify. - * @param signature signature. - * @param pub The public key bytes to use. - * @return - - */ - public static boolean verifyMessage(byte[] msg, byte[] signature, byte[] pub, - @Nullable String userID) { - return verifyMessage(msg, SM2Signature.decodeFromDER(signature), pub, userID); - } - /** * Returns true if the given pubkey is canonical, i.e. the correct length taking into account * compression. - * - * @param pubkey - - * @return - */ public static boolean isPubKeyCanonical(byte[] pubkey) { if (pubkey[0] == 0x04) { @@ -790,15 +664,14 @@ 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 */ @Nullable public static byte[] recoverAddressFromSignature(int recId, - SM2Signature sig, - byte[] messageHash) { + SM2Signature sig, byte[] messageHash) { final byte[] pubBytes = recoverPubBytesFromSignature(recId, sig, messageHash); if (pubBytes == null) { @@ -809,8 +682,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,26 +700,19 @@ 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). - * - * @return - + * Returns true if this is a public-key-only (watching) key with no private key access. */ public boolean isPubKeyOnly() { return privKey == null; } /** - * Returns true if this key has access to private key bytes. Does the opposite of {@link - * #isPubKeyOnly()}. - * - * @return - + * Returns true if this key has access to private key bytes. */ public boolean hasPrivKey() { return privKey != null; } - /** * Generates the NodeID based on this key, that is the public key without first format byte */ @@ -854,25 +720,18 @@ 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. - * - * @return - */ public ECPoint getPubKeyPoint() { return pub; } /** - * 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. + * Returns the private key as a BigInteger. */ public BigInteger getPrivKey() { if (privKey == null) { @@ -885,64 +744,25 @@ 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 signature - - * @return - + * Verifies the given signature against a hash using this key's public key. */ public boolean verify(byte[] sigHash, SM2Signature signature) { return SM2.verify(sigHash, signature, getPubKey()); } /** - * Returns true if this pubkey is canonical, i.e. the correct length taking into account - * compression. - * - * @return - + * Returns true if this key's public encoding has canonical length. */ public boolean isPubKeyCanonical() { return isPubKeyCanonical(pub.getEncoded(/* uncompressed */ false)); } /** - * Returns a 32 byte array containing the private key, or null if the key is encrypted or public - * only - * - * @return - + * Returns 32-byte private key array, or null if unavailable. */ @Nullable public byte[] getPrivKeyBytes() { @@ -967,10 +787,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 +798,6 @@ public int hashCode() { return Arrays.hashCode(getPubKey()); } - public static class SM2Signature implements SignatureInterface { /** @@ -988,11 +807,7 @@ public static class SM2Signature implements SignatureInterface { public byte v; /** - * Constructs a signature with the given components. Does NOT automatically canonicalise the - * signature. - * - * @param r - - * @param s - + * Constructs a signature with the given components. Does NOT automatically canonicalise. */ public SM2Signature(BigInteger r, BigInteger s) { this.r = r; @@ -1005,29 +820,41 @@ 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)); } - /** - * @param r - - * @param 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 { + ASN1Primitive primitive = ASN1Primitive.fromByteArray(signature); + 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 +875,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 - + * Encodes this signature as a 65-byte Base64 string (v || r || s). */ 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) @@ -1140,5 +930,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..969a7f07199 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 @@ -1,110 +1,51 @@ package org.tron.common.crypto.sm2; import java.math.BigInteger; -import java.security.SecureRandom; -import javax.annotation.Nullable; import org.bouncycastle.crypto.CipherParameters; -import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.digests.SM3Digest; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECKeyParameters; 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; + +/** + * Low-level SM2 signer used by {@link org.tron.common.crypto.sm2.SM2}. + * + *
Exposes two operations: {@link #generateHashSignature} (sign a pre-computed 32-byte hash) + * and {@link #verifyHashSignature} (verify against a pre-computed hash). The standard SM2 + * {@code Z_A} pre-hash step is intentionally absent; see {@link org.tron.common.crypto.sm2.SM2} + * for the rationale. + */ public class SM2Signer implements ECConstants { - private final DSAKCalculator kCalculator = new RandomDSAKCalculator(); - - private byte[] userID; + private final DSAKCalculator kCalculator = new HMacDSAKCalculator(new SM3Digest()); - private int curveLength; private ECDomainParameters ecParams; - private ECPoint pubPoint; private ECKeyParameters ecKey; - private SecureRandom random; - - public void init(boolean forSigning, CipherParameters param) { - CipherParameters baseParam; - - if (param instanceof ParametersWithID) { - baseParam = ((ParametersWithID) param).getParameters(); - userID = ((ParametersWithID) param).getID(); - } else { - baseParam = param; - userID = new byte[0]; - } - - 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(); + public void init(CipherParameters param) { + if (param == null) { + throw new IllegalArgumentException("CipherParameters cannot be null"); } - - curveLength = (ecParams.getCurve().getFieldSize() + 7) / 8; - } - - - /** - * generate the signature for the message - * - * @param message plaintext - */ - public BigInteger[] generateSignature(byte[] message) { - byte[] eHash = generateSM3Hash(message); - return generateHashSignature(eHash); - } - - /** - * generate the signature for the message - */ - - public byte[] generateSM3Hash(byte[] message) { - //byte[] msg = message.getBytes(); - - SM3Digest digest = new SM3Digest(); - byte[] z = getZ(digest); - - digest.update(z, 0, z.length); - digest.update(message, 0, message.length); - - byte[] eHash = new byte[digest.getDigestSize()]; - - digest.doFinal(eHash, 0); - return eHash; + ecKey = (ECKeyParameters) param; + ecParams = ecKey.getParameters(); } /** * 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 +55,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 { @@ -142,52 +86,17 @@ public BigInteger[] generateHashSignature(byte[] hash) { return new BigInteger[]{r, s}; } - /** - * verify the message signature - */ - public boolean verifySignature(byte[] message, BigInteger r, BigInteger s, - @Nullable String userID) { - BigInteger n = ecParams.getN(); - - // 5.3.1 Draft RFC: SM2 Public Key Algorithms - // B1 - if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) { - return false; - } - - // B2 - if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) { - return false; - } - - ECPoint q = ((ECPublicKeyParameters) ecKey).getQ(); - - if (userID != null) { - this.userID = userID.getBytes(); - } - byte[] eHash = generateSM3Hash(message); - - // B4 - BigInteger e = calculateE(eHash); - - // B5 - BigInteger t = r.add(s).mod(n); - if (t.equals(ZERO)) { - return false; - } else { - // B6 - ECPoint x1y1 = ecParams.getG().multiply(s); - x1y1 = x1y1.add(q.multiply(t)).normalize(); - - // B7 - return r.equals(e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n)); - } - } - /** * verify the hash signature */ public boolean verifyHashSignature(byte[] hash, BigInteger r, BigInteger s) { + if (ByteArray.isEmpty(hash) || hash.length != 32) { + throw new IllegalArgumentException("Expected 32 byte input to " + + "SM2 signature, not " + (hash == null ? "null" : hash.length)); + } + 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 @@ -220,43 +129,15 @@ public boolean verifyHashSignature(byte[] hash, BigInteger r, BigInteger s) { } } - private byte[] getZ(Digest digest) { - - //addUserID(digest, userID); - - addFieldElement(digest, ecParams.getCurve().getA()); - addFieldElement(digest, ecParams.getCurve().getB()); - addFieldElement(digest, ecParams.getG().getAffineXCoord()); - addFieldElement(digest, ecParams.getG().getAffineYCoord()); - addFieldElement(digest, pubPoint.getAffineXCoord()); - addFieldElement(digest, pubPoint.getAffineYCoord()); - - byte[] rv = new byte[digest.getDigestSize()]; - - digest.doFinal(rv, 0); - - return rv; - } - - private void addUserID(Digest digest, byte[] userID) { - int len = userID.length * 8; - digest.update((byte) (len >> 8 & 0xFF)); - digest.update((byte) (len & 0xFF)); - digest.update(userID, 0, userID.length); - } - - private void addFieldElement(Digest digest, ECFieldElement v) { - byte[] p = BigIntegers.asUnsignedByteArray(curveLength, v.toBigInteger()); - digest.update(p, 0, p.length); - } - protected ECMultiplier createBasePointMultiplier() { return new FixedPointCombMultiplier(); } 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/test/java/org/tron/common/crypto/SM2KeyTest.java b/framework/src/test/java/org/tron/common/crypto/SM2KeyTest.java index 87e4e14698c..b4edb1b9788 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,11 +14,16 @@ 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; import org.tron.common.crypto.sm2.SM2; import org.tron.common.crypto.sm2.SM2Signer; +import org.tron.common.utils.ByteUtil; +import org.tron.common.utils.Sha256Hash; import org.tron.core.Wallet; /** @@ -66,10 +71,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) @@ -116,17 +119,6 @@ public void testInvalidSignatureLength() throws SignatureException { fail("Expecting a SignatureException for invalid signature length"); } - @Test - public void testSM3Hash() { - SM2 key = SM2.fromPublicOnly(pubKey); - SM2Signer signer = key.getSM2SignerForHash(); - String message = "message digest"; - byte[] hash = signer.generateSM3Hash(message.getBytes()); - assertEquals("2A723761EAE35429DF643648FD69FB7787E7FC32F321BFAF7E294390F529BAF4", - Hex.toHexString(hash).toUpperCase()); - } - - @Test public void testSignatureToKeyBytes() throws SignatureException { SM2 key = SM2.fromPrivate(privateKey); @@ -281,4 +273,109 @@ 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 = Sha256Hash.hash(false, message.getBytes()); + + SM2.SM2Signature signature = key.sign(hash); + + // Verify the signature + SM2Signer verifier = key.getVerifier(); + 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()); + } + + @Test + public void testSM2IsValidPrivateKey() { + assertFalse(SM2.isValidPrivateKey((byte[]) null)); + assertFalse(SM2.isValidPrivateKey(new byte[0])); + assertFalse(SM2.isValidPrivateKey(new byte[32])); + BigInteger sm2N = new BigInteger( + "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", 16); + assertFalse(SM2.isValidPrivateKey(ByteUtil.bigIntegerToBytes(sm2N, 32))); + assertFalse(SM2.isValidPrivateKey(new byte[34])); + byte[] minKey = new byte[32]; + minKey[31] = 1; + assertTrue(SM2.isValidPrivateKey(minKey)); + } + + 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/common/utils/PublicMethod.java b/framework/src/test/java/org/tron/common/utils/PublicMethod.java index 90a2aae3f76..a34ef5e4d49 100644 --- a/framework/src/test/java/org/tron/common/utils/PublicMethod.java +++ b/framework/src/test/java/org/tron/common/utils/PublicMethod.java @@ -80,12 +80,6 @@ public static byte[] getSM2PublicKeyFromPrivate(String privateKey) { return SM2.publicKeyFromPrivate(tmpKey, true); } - public static byte[] getSM2HashByPubKey(byte[] pubKey, String message) { - SM2 key = SM2.fromPublicOnly(pubKey); - SM2Signer signer = key.getSM2SignerForHash(); - return signer.generateSM3Hash(message.getBytes()); - } - /** constructor. */ public static SmartContractOuterClass.SmartContract.ABI jsonStr2Abi(String jsonStr) { if (jsonStr == null) {