From a78f02b78f05aa23d97d8e51a875958f782d0458 Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 2 Jun 2025 15:58:13 +0000 Subject: [PATCH 01/24] Add ML-DSA-87 to NativeCrypto. Also, add MlDsaAlgorithm enum, which we will need later. --- .../jni/main/cpp/conscrypt/native_crypto.cc | 158 ++++++++++++++++++ .../java/org/conscrypt/MlDsaAlgorithm.java | 35 ++++ .../main/java/org/conscrypt/NativeCrypto.java | 8 + .../java/org/conscrypt/NativeCryptoTest.java | 16 ++ 4 files changed, 217 insertions(+) create mode 100644 common/src/main/java/org/conscrypt/MlDsaAlgorithm.java diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index f271ec757..6ba896c74 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -2661,6 +2661,161 @@ static jint NativeCrypto_MLDSA65_verify(JNIEnv* env, jclass, jbyteArray data, ji return static_cast(result); } +static jbyteArray NativeCrypto_MLDSA87_public_key_from_seed( + JNIEnv* env, jclass, jbyteArray privateKeySeed) { + CHECK_ERROR_QUEUE_ON_RETURN; + + ScopedByteArrayRO seedArray(env, privateKeySeed); + if (seedArray.get() == nullptr) { + JNI_TRACE("NativeCrypto_MLDSA87_sign => privateKeySeed == null"); + return nullptr; + } + + MLDSA87_private_key privateKey; + if (!MLDSA87_private_key_from_seed( + &privateKey, reinterpret_cast(seedArray.get()), + seedArray.size())) { + JNI_TRACE("MLDSA87_private_key_from_seed failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "MLDSA87_private_key_from_seed"); + return nullptr; + } + + MLDSA87_public_key publicKey; + if (!MLDSA87_public_from_private(&publicKey, &privateKey)) { + JNI_TRACE("MLDSA87_public_from_private failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "MLDSA87_public_from_private"); + return nullptr; + } + + CBB cbb; + size_t size; + uint8_t public_key_bytes[MLDSA87_SIGNATURE_BYTES]; + if (!CBB_init_fixed(&cbb, public_key_bytes, MLDSA87_PUBLIC_KEY_BYTES) || + !MLDSA87_marshal_public_key(&cbb, &publicKey) || + !CBB_finish(&cbb, nullptr, &size) || size != MLDSA87_PUBLIC_KEY_BYTES) { + JNI_TRACE("Failed to serialize ML-DSA public key."); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "MLDSA87_marshal_public_key"); + return nullptr; + } + + ScopedLocalRef publicKeyRef( + env, env->NewByteArray(static_cast(MLDSA87_PUBLIC_KEY_BYTES))); + if (publicKeyRef.get() == nullptr) { + return nullptr; + } + + ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); + if (publicKeyArray.get() == nullptr) { + return nullptr; + } + memcpy(publicKeyArray.get(), public_key_bytes, MLDSA87_PUBLIC_KEY_BYTES); + return publicKeyRef.release(); +} + +static jbyteArray NativeCrypto_MLDSA87_sign(JNIEnv* env, jclass, + jbyteArray data, jint dataLen, + jbyteArray privateKeySeed) { + CHECK_ERROR_QUEUE_ON_RETURN; + + ScopedByteArrayRO seedArray(env, privateKeySeed); + if (seedArray.get() == nullptr) { + JNI_TRACE("NativeCrypto_MLDSA87_sign => privateKeySeed == null"); + return nullptr; + } + + MLDSA87_private_key privateKey; + if (!MLDSA87_private_key_from_seed( + &privateKey, reinterpret_cast(seedArray.get()), + seedArray.size())) { + JNI_TRACE("MLDSA87_private_key_from_seed failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "MLDSA87_private_key_from_seed"); + return nullptr; + } + + ScopedByteArrayRO dataArray(env, data); + if (dataArray.get() == nullptr) { + return nullptr; + } + + if (ARRAY_OFFSET_LENGTH_INVALID(dataArray, 0, dataLen)) { + conscrypt::jniutil::throwException( + env, "java/lang/ArrayIndexOutOfBoundsException", "dataLen"); + return nullptr; + } + + uint8_t result[MLDSA87_SIGNATURE_BYTES]; + if (!MLDSA87_sign(result, &privateKey, + reinterpret_cast(dataArray.get()), + dataLen, /* context */ NULL, /* context_len */ 0)) { + JNI_TRACE("MLDSA87_sign failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_sign"); + return nullptr; + } + + ScopedLocalRef resultRef( + env, env->NewByteArray(static_cast(MLDSA87_SIGNATURE_BYTES))); + if (resultRef.get() == nullptr) { + return nullptr; + } + + ScopedByteArrayRW resultArray(env, resultRef.get()); + if (resultArray.get() == nullptr) { + return nullptr; + } + memcpy(resultArray.get(), result, MLDSA87_SIGNATURE_BYTES); + return resultRef.release(); +} + +static jint NativeCrypto_MLDSA87_verify(JNIEnv* env, jclass, jbyteArray data, + jint dataLen, jbyteArray sig, + jbyteArray publicKey) { + CHECK_ERROR_QUEUE_ON_RETURN; + + ScopedByteArrayRO publicKeyArray(env, publicKey); + if (publicKeyArray.get() == nullptr) { + JNI_TRACE("NativeCrypto_MLDSA87_verify => publicKey == null"); + return -1; + } + + CBS cbs; + CBS_init(&cbs, reinterpret_cast(publicKeyArray.get()), + publicKeyArray.size()); + MLDSA87_public_key pubkey; + if (!MLDSA87_parse_public_key(&pubkey, &cbs)) { + JNI_TRACE("MLDSA87_parse_public_key failed"); + return -1; + } + + ScopedByteArrayRO dataArray(env, data); + if (dataArray.get() == nullptr) { + return -1; + } + + if (ARRAY_OFFSET_LENGTH_INVALID(dataArray, 0, dataLen)) { + conscrypt::jniutil::throwException( + env, "java/lang/ArrayIndexOutOfBoundsException", "dataLen"); + return -1; + } + + ScopedByteArrayRO sigArray(env, sig); + if (sigArray.get() == nullptr) { + return -1; + } + + int result = MLDSA87_verify( + &pubkey, reinterpret_cast(sigArray.get()), + sigArray.size(), reinterpret_cast(dataArray.get()), + dataLen, /*context=*/NULL, /*context_len=*/0); + + JNI_TRACE("MLDSA87_verify(%p, %p, %p, %d) => %d", publicKey, sig, data, + dataLen, result); + return static_cast(result); +} + static void NativeCrypto_SLHDSA_SHA2_128S_generate_key(JNIEnv* env, jclass, jbyteArray outPublicArray, jbyteArray outPrivateArray) { @@ -11702,6 +11857,9 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(MLDSA65_public_key_from_seed, "([B)[B"), CONSCRYPT_NATIVE_METHOD(MLDSA65_sign, "([BI[B)[B"), CONSCRYPT_NATIVE_METHOD(MLDSA65_verify, "([BI[B[B)I"), + CONSCRYPT_NATIVE_METHOD(MLDSA87_public_key_from_seed, "([B)[B"), + CONSCRYPT_NATIVE_METHOD(MLDSA87_sign, "([BI[B)[B"), + CONSCRYPT_NATIVE_METHOD(MLDSA87_verify, "([BI[B[B)I"), CONSCRYPT_NATIVE_METHOD(SLHDSA_SHA2_128S_generate_key, "([B[B)V"), CONSCRYPT_NATIVE_METHOD(SLHDSA_SHA2_128S_sign, "([BI[B)[B"), CONSCRYPT_NATIVE_METHOD(SLHDSA_SHA2_128S_verify, "([BI[B[B)I"), diff --git a/common/src/main/java/org/conscrypt/MlDsaAlgorithm.java b/common/src/main/java/org/conscrypt/MlDsaAlgorithm.java new file mode 100644 index 000000000..c25197a9b --- /dev/null +++ b/common/src/main/java/org/conscrypt/MlDsaAlgorithm.java @@ -0,0 +1,35 @@ +package org.conscrypt; + +/** ML-DSA algorithm. */ +public enum MlDsaAlgorithm { + ML_DSA_65("ML-DSA-65", 1952), + ML_DSA_87("ML-DSA-87", 2592); + + private final String name; + private final int publicKeySize; + + private MlDsaAlgorithm(String name, int publicKeySize) { + this.name = name; + this.publicKeySize = publicKeySize; + } + + @Override + public String toString() { + return name; + } + + public int publicKeySize() { + return publicKeySize; + } + + public static MlDsaAlgorithm parse(String name) { + switch (name) { + case "ML-DSA-65": + return ML_DSA_65; + case "ML-DSA-87": + return ML_DSA_87; + default: + throw new IllegalArgumentException("Unsupported algorithm: " + name); + } + } +} diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java index 1918cd14f..667aa7188 100644 --- a/common/src/main/java/org/conscrypt/NativeCrypto.java +++ b/common/src/main/java/org/conscrypt/NativeCrypto.java @@ -215,6 +215,14 @@ static native int ECDH_compute_key(byte[] out, int outOffset, NativeRef.EVP_PKEY static native int MLDSA65_verify(byte[] data, int dataLen, byte[] sig, byte[] publicKey); + // --- MLDSA87 -------------------------------------------------------------- + + static native byte[] MLDSA87_public_key_from_seed(byte[] privateKeySeed); + + static native byte[] MLDSA87_sign(byte[] data, int dataLen, byte[] privateKeySeed); + + static native int MLDSA87_verify(byte[] data, int dataLen, byte[] sig, byte[] publicKey); + // --- SLHDSA_SHA2_128S -------------------------------------------------------------- static native void SLHDSA_SHA2_128S_generate_key(byte[] outPublicKey, byte[] outPrivateKey); diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java index 98f4c675c..3820708eb 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -3386,6 +3386,22 @@ public void test_mldsa65_works() throws Exception { -1, NativeCrypto.MLDSA65_verify(data, data.length, signature, publicKeyTooLong)); } + @Test + public void test_mldsa87_works() throws Exception { + byte[] privateKeySeed = + decodeHex("7C9935A0B07694AA0C6D10E4DB6B1ADD2FD81A25CCB148032DCD739936737F2D"); + byte[] data = decodeHex("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8"); + + byte[] publicKey = NativeCrypto.MLDSA87_public_key_from_seed(privateKeySeed); + assertEquals(2592, publicKey.length); + + byte[] signature = NativeCrypto.MLDSA87_sign(data, data.length, privateKeySeed); + assertEquals(4627, signature.length); + + int result = NativeCrypto.MLDSA87_verify(data, data.length, signature, publicKey); + assertEquals(1, result); + } + @Test public void test_slhdsa_sha2_128s_works() throws Exception { byte[] publicKey = new byte[32]; From 8d7d1458ac499da03e40b32e0743cc8b6ce56d3d Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 2 Jun 2025 16:08:47 +0000 Subject: [PATCH 02/24] Add more tests for ML-DSA-87. --- .../java/org/conscrypt/MlDsaAlgorithm.java | 48 ++++++++-------- .../java/org/conscrypt/NativeCryptoTest.java | 56 ++++++++++++++++++- 2 files changed, 77 insertions(+), 27 deletions(-) diff --git a/common/src/main/java/org/conscrypt/MlDsaAlgorithm.java b/common/src/main/java/org/conscrypt/MlDsaAlgorithm.java index c25197a9b..be8a017e6 100644 --- a/common/src/main/java/org/conscrypt/MlDsaAlgorithm.java +++ b/common/src/main/java/org/conscrypt/MlDsaAlgorithm.java @@ -2,34 +2,34 @@ /** ML-DSA algorithm. */ public enum MlDsaAlgorithm { - ML_DSA_65("ML-DSA-65", 1952), - ML_DSA_87("ML-DSA-87", 2592); + ML_DSA_65("ML-DSA-65", 1952), + ML_DSA_87("ML-DSA-87", 2592); - private final String name; - private final int publicKeySize; + private final String name; + private final int publicKeySize; - private MlDsaAlgorithm(String name, int publicKeySize) { - this.name = name; - this.publicKeySize = publicKeySize; - } + private MlDsaAlgorithm(String name, int publicKeySize) { + this.name = name; + this.publicKeySize = publicKeySize; + } - @Override - public String toString() { - return name; - } + @Override + public String toString() { + return name; + } - public int publicKeySize() { - return publicKeySize; - } + public int publicKeySize() { + return publicKeySize; + } - public static MlDsaAlgorithm parse(String name) { - switch (name) { - case "ML-DSA-65": - return ML_DSA_65; - case "ML-DSA-87": - return ML_DSA_87; - default: - throw new IllegalArgumentException("Unsupported algorithm: " + name); + public static MlDsaAlgorithm parse(String name) { + switch (name) { + case "ML-DSA-65": + return ML_DSA_65; + case "ML-DSA-87": + return ML_DSA_87; + default: + throw new IllegalArgumentException("Unsupported algorithm: " + name); + } } - } } diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java index 3820708eb..b9d82a4bd 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -3388,8 +3388,7 @@ public void test_mldsa65_works() throws Exception { @Test public void test_mldsa87_works() throws Exception { - byte[] privateKeySeed = - decodeHex("7C9935A0B07694AA0C6D10E4DB6B1ADD2FD81A25CCB148032DCD739936737F2D"); + byte[] privateKeySeed = decodeHex("7C9935A0B07694AA0C6D10E4DB6B1ADD2FD81A25CCB148032DCD739936737F2D"); byte[] data = decodeHex("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8"); byte[] publicKey = NativeCrypto.MLDSA87_public_key_from_seed(privateKeySeed); @@ -3400,7 +3399,58 @@ public void test_mldsa87_works() throws Exception { int result = NativeCrypto.MLDSA87_verify(data, data.length, signature, publicKey); assertEquals(1, result); - } + + // data buffer is larger than data + byte[] dataBuffer = Arrays.copyOf(data, data.length + 42); + assertEquals(1, NativeCrypto.MLDSA87_verify(dataBuffer, data.length, signature, publicKey)); + + // data too short + assertEquals(0, NativeCrypto.MLDSA87_verify(data, data.length - 1, signature, publicKey)); + + byte[] signatureTooShort = Arrays.copyOf(signature, signature.length - 1); + assertEquals( + 0, NativeCrypto.MLDSA87_verify(data, data.length, signatureTooShort, publicKey)); + + byte[] signatureTooLong = Arrays.copyOf(signature, signature.length + 1); + assertEquals( + 0, NativeCrypto.MLDSA87_verify(data, data.length, signatureTooLong, publicKey)); + + byte[] modifiedSignature = signature.clone(); + modifiedSignature[0] = (byte) (modifiedSignature[0] ^ 0x01); + assertEquals( + 0, NativeCrypto.MLDSA87_verify(data, data.length, modifiedSignature, publicKey)); + + byte[] modifiedData = data.clone(); + modifiedData[0] = (byte) (modifiedData[0] ^ 0x01); + assertEquals( + 0, NativeCrypto.MLDSA87_verify(modifiedData, data.length, signature, publicKey)); + + int invalidDataLen = data.length + 1; + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLDSA87_sign(data, invalidDataLen, privateKeySeed)); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLDSA87_verify(data, invalidDataLen, signature, publicKey)); + + byte[] privateKeySeedTooShort = Arrays.copyOf(privateKeySeed, privateKeySeed.length - 1); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLDSA87_public_key_from_seed(privateKeySeedTooShort)); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLDSA87_sign(data, data.length, privateKeySeedTooShort)); + + byte[] privateKeySeedTooLong = Arrays.copyOf(privateKeySeed, privateKeySeed.length + 1); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLDSA87_public_key_from_seed(privateKeySeedTooLong)); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLDSA87_sign(data, data.length, privateKeySeedTooLong)); + + byte[] publicKeyTooShort = Arrays.copyOf(publicKey, publicKey.length - 1); + assertEquals( + -1, NativeCrypto.MLDSA87_verify(data, data.length, signature, publicKeyTooShort)); + + byte[] publicKeyTooLong = Arrays.copyOf(publicKey, publicKey.length + 1); + assertEquals( + -1, NativeCrypto.MLDSA87_verify(data, data.length, signature, publicKeyTooLong)); + } @Test public void test_slhdsa_sha2_128s_works() throws Exception { From a4daa54fd0b825a32516ef33fe2d6f0985b59611 Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 2 Jun 2025 16:25:15 +0000 Subject: [PATCH 03/24] Add ML-DSA-87. --- .../java/org/conscrypt/OpenSSLProvider.java | 15 +-- .../org/conscrypt/OpenSslMlDsaKeyFactory.java | 54 ++++++++++- .../OpenSslMlDsaKeyPairGenerator.java | 62 +++++++++--- .../org/conscrypt/OpenSslMlDsaPrivateKey.java | 32 +++++-- .../org/conscrypt/OpenSslMlDsaPublicKey.java | 33 +++++-- .../org/conscrypt/OpenSslSignatureMlDsa.java | 84 ++++++++++++++-- .../test/java/org/conscrypt/MlDsaTest.java | 95 +++++++++++++++++-- common/src/test/resources/crypto/mldsa.txt | 8 ++ 8 files changed, 328 insertions(+), 55 deletions(-) diff --git a/common/src/main/java/org/conscrypt/OpenSSLProvider.java b/common/src/main/java/org/conscrypt/OpenSSLProvider.java index 319750e84..333bddb70 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLProvider.java +++ b/common/src/main/java/org/conscrypt/OpenSSLProvider.java @@ -216,8 +216,9 @@ public OpenSSLProvider(String providerName) { put("Alg.Alias.KeyPairGenerator.1.3.101.112", "EdDSA"); put("Alg.Alias.KeyPairGenerator.Ed25519", "EdDSA"); - put("KeyPairGenerator.ML-DSA", PREFIX + "OpenSslMlDsaKeyPairGenerator"); - put("Alg.Alias.KeyPairGenerator.ML-DSA-65", "ML-DSA"); + put("KeyPairGenerator.ML-DSA", PREFIX + "OpenSslMlDsaKeyPairGenerator$MlDsa"); + put("KeyPairGenerator.ML-DSA-65", PREFIX + "OpenSslMlDsaKeyPairGenerator$MlDsa65"); + put("KeyPairGenerator.ML-DSA-87", PREFIX + "OpenSslMlDsaKeyPairGenerator$MlDsa87"); // We don't support SLH-DSA, because it's not clear which algorithm to use. put("KeyPairGenerator.SLH-DSA-SHA2-128S", PREFIX + "OpenSslSlhDsaKeyPairGenerator"); @@ -240,8 +241,9 @@ public OpenSSLProvider(String providerName) { put("Alg.Alias.KeyFactory.1.3.101.112", "EdDSA"); put("Alg.Alias.KeyFactory.Ed25519", "EdDSA"); - put("KeyFactory.ML-DSA", PREFIX + "OpenSslMlDsaKeyFactory"); - put("Alg.Alias.KeyFactory.ML-DSA-65", "ML-DSA"); + put("KeyFactory.ML-DSA", PREFIX + "OpenSslMlDsaKeyFactory$MlDsa"); + put("KeyFactory.ML-DSA-65", PREFIX + "OpenSslMlDsaKeyFactory$MlDsa65"); + put("KeyFactory.ML-DSA-87", PREFIX + "OpenSslMlDsaKeyFactory$MlDsa87"); // We don't support SLH-DSA, because it's not clear which algorithm to use. put("KeyFactory.SLH-DSA-SHA2-128S", PREFIX + "OpenSslSlhDsaKeyFactory"); @@ -372,8 +374,9 @@ public OpenSSLProvider(String providerName) { put("Alg.Alias.Signature.1.3.101.112", "EdDSA"); put("Alg.Alias.Signature.Ed25519", "EdDSA"); - putSignatureImplClass("ML-DSA", "OpenSslSignatureMlDsa"); - put("Alg.Alias.Signature.ML-DSA-65", "ML-DSA"); + putSignatureImplClass("ML-DSA", "OpenSslSignatureMlDsa$MlDsa"); + putSignatureImplClass("ML-DSA-65", "OpenSslSignatureMlDsa$MlDsa65"); + putSignatureImplClass("ML-DSA-87", "OpenSslSignatureMlDsa$MlDsa87"); // We don't support SLH-DSA, because it's not clear which algorithm to use. putSignatureImplClass("SLH-DSA-SHA2-128S", "OpenSslSignatureSlhDsa"); diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java index d90186729..f36f1bd9b 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java @@ -31,8 +31,48 @@ /** An implementation of a {@link KeyFactorySpi} for MLDSA keys based on BoringSSL. */ @Internal -public final class OpenSslMlDsaKeyFactory extends KeyFactorySpi { - public OpenSslMlDsaKeyFactory() {} +public abstract class OpenSslMlDsaKeyFactory extends KeyFactorySpi { + private final MlDsaAlgorithm algorithm; + + private OpenSslMlDsaKeyFactory(MlDsaAlgorithm algorithm) { + this.algorithm = algorithm; + } + + abstract boolean supportsAlgorithm(MlDsaAlgorithm algorithm); + + /** ML-DSA */ + public static class MlDsa extends OpenSslMlDsaKeyFactory { + public MlDsa() { + super(MlDsaAlgorithm.ML_DSA_65); + } + @Override + boolean supportsAlgorithm(MlDsaAlgorithm algorithm) { + return algorithm.equals(MlDsaAlgorithm.ML_DSA_65) + || algorithm.equals(MlDsaAlgorithm.ML_DSA_87); + } + } + + /** ML-DSA-65 */ + public static class MlDsa65 extends OpenSslMlDsaKeyFactory { + public MlDsa65() { + super(MlDsaAlgorithm.ML_DSA_65); + } + @Override + boolean supportsAlgorithm(MlDsaAlgorithm algorithm) { + return algorithm.equals(MlDsaAlgorithm.ML_DSA_65); + } + } + + /** ML-DSA-87 */ + public static class MlDsa87 extends OpenSslMlDsaKeyFactory { + public MlDsa87() { + super(MlDsaAlgorithm.ML_DSA_87); + } + @Override + boolean supportsAlgorithm(MlDsaAlgorithm algorithm) { + return algorithm.equals(MlDsaAlgorithm.ML_DSA_87); + } + } @Override protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException { @@ -40,7 +80,7 @@ protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecE throw new InvalidKeySpecException("keySpec == null"); } if (keySpec instanceof EncodedKeySpec) { - return new OpenSslMlDsaPublicKey((EncodedKeySpec) keySpec); + return new OpenSslMlDsaPublicKey((EncodedKeySpec) keySpec, algorithm); } throw new InvalidKeySpecException( "Currently only EncodedKeySpec is supported; was " + keySpec.getClass().getName()); @@ -52,7 +92,7 @@ protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpe throw new InvalidKeySpecException("keySpec == null"); } if (keySpec instanceof EncodedKeySpec) { - return new OpenSslMlDsaPrivateKey((EncodedKeySpec) keySpec); + return new OpenSslMlDsaPrivateKey((EncodedKeySpec) keySpec, algorithm); } throw new InvalidKeySpecException( "Currently only EncodedKeySpec is supported; was " + keySpec.getClass().getName()); @@ -72,6 +112,9 @@ protected T engineGetKeySpec(Key key, Class keySpec) } if (key instanceof OpenSslMlDsaPublicKey) { OpenSslMlDsaPublicKey conscryptKey = (OpenSslMlDsaPublicKey) key; + if (!supportsAlgorithm(conscryptKey.getMlDsaAlgorithm())) { + throw new InvalidKeySpecException("Key algorithm mismatch"); + } if (X509EncodedKeySpec.class.isAssignableFrom(keySpec)) { throw new UnsupportedOperationException( "X509EncodedKeySpec is currently not supported"); @@ -80,6 +123,9 @@ protected T engineGetKeySpec(Key key, Class keySpec) } } else if (key instanceof OpenSslMlDsaPrivateKey) { OpenSslMlDsaPrivateKey conscryptKey = (OpenSslMlDsaPrivateKey) key; + if (!supportsAlgorithm(conscryptKey.getMlDsaAlgorithm())) { + throw new InvalidKeySpecException("Key algorithm mismatch"); + } if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec)) { throw new UnsupportedOperationException( "PKCS8EncodedKeySpec is currently not supported"); diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java index 2db7c460f..c43dc0aec 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyPairGenerator.java @@ -22,14 +22,56 @@ /** * An implementation of {@link KeyPairGenerator} for ML-DSA keys which uses BoringSSL to perform all - * the operations. + * the operations. It supports algorithms "ML-DSA", "ML-DSA-65" and "ML-DSA-87". "ML-DSA" uses + * ML-DSA-65. */ @Internal -public final class OpenSslMlDsaKeyPairGenerator extends KeyPairGenerator { - private static final String ALGORITHM = "ML-DSA"; +public class OpenSslMlDsaKeyPairGenerator extends KeyPairGenerator { + private OpenSslMlDsaKeyPairGenerator(String algorithm) { + super(algorithm); + } + + /** ML-DSA-65 */ + public static class MlDsa65 extends OpenSslMlDsaKeyPairGenerator { + public MlDsa65() { + super("ML-DSA-65"); + } + + MlDsa65(String algorithm) { + super(algorithm); + } + + @Override + public KeyPair generateKeyPair() { + byte[] privateKeyBytes = new byte[32]; + NativeCrypto.RAND_bytes(privateKeyBytes); + byte[] publicKeyBytes = NativeCrypto.MLDSA65_public_key_from_seed(privateKeyBytes); + return new KeyPair(new OpenSslMlDsaPublicKey(publicKeyBytes, MlDsaAlgorithm.ML_DSA_65), + new OpenSslMlDsaPrivateKey(privateKeyBytes, MlDsaAlgorithm.ML_DSA_65)); + } + } + + /** ML-DSA uses ML-DSA-65. */ + public static class MlDsa extends MlDsa65 { + public MlDsa() { + super("ML-DSA"); + } + } - public OpenSslMlDsaKeyPairGenerator() { - super(ALGORITHM); + /** ML-DSA-87 */ + public static final class MlDsa87 extends OpenSslMlDsaKeyPairGenerator { + public MlDsa87() { + super("ML-DSA-87"); + } + + @Override + public KeyPair generateKeyPair() { + byte[] privateKeyBytes = new byte[32]; + NativeCrypto.RAND_bytes(privateKeyBytes); + byte[] publicKeyBytes = NativeCrypto.MLDSA87_public_key_from_seed(privateKeyBytes); + return new KeyPair(new OpenSslMlDsaPublicKey(publicKeyBytes, MlDsaAlgorithm.ML_DSA_87), + new OpenSslMlDsaPrivateKey(privateKeyBytes, MlDsaAlgorithm.ML_DSA_87)); + } } @Override @@ -38,14 +80,4 @@ public void initialize(int bits) throws InvalidParameterException { throw new InvalidParameterException("ML-DSA only supports -1 for bits"); } } - - @Override - public KeyPair generateKeyPair() { - byte[] privateKeyBytes = new byte[32]; - NativeCrypto.RAND_bytes(privateKeyBytes); - byte[] publicKeyBytes = NativeCrypto.MLDSA65_public_key_from_seed(privateKeyBytes); - - return new KeyPair(new OpenSslMlDsaPublicKey(publicKeyBytes), - new OpenSslMlDsaPrivateKey(privateKeyBytes)); - } } diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java index 95535ec25..c0d7e3e31 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java @@ -20,22 +20,36 @@ import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; +import java.util.Objects; /** An OpenSSL ML-DSA private key. */ public class OpenSslMlDsaPrivateKey implements PrivateKey { private byte[] seed; + private final MlDsaAlgorithm algorithm; - public OpenSslMlDsaPrivateKey(EncodedKeySpec keySpec) throws InvalidKeySpecException { + private boolean isValidSeed(byte[] seed) { + return seed.length == 32; + } + + public OpenSslMlDsaPrivateKey(EncodedKeySpec keySpec, MlDsaAlgorithm algorithm) + throws InvalidKeySpecException { byte[] encoded = keySpec.getEncoded(); - if ("raw".equalsIgnoreCase(keySpec.getFormat())) { - seed = encoded; - } else { + if (!"raw".equalsIgnoreCase(keySpec.getFormat())) { throw new InvalidKeySpecException("Encoding must be in raw format"); } + if (!isValidSeed(encoded)) { + throw new InvalidKeySpecException("Invalid seed"); + } + this.seed = encoded; + this.algorithm = algorithm; } - public OpenSslMlDsaPrivateKey(byte[] seed) { + public OpenSslMlDsaPrivateKey(byte[] seed, MlDsaAlgorithm algorithm) { + if (!isValidSeed(seed)) { + throw new IllegalArgumentException("Invalid seed"); + } this.seed = seed.clone(); + this.algorithm = algorithm; } @Override @@ -43,6 +57,10 @@ public String getAlgorithm() { return "ML-DSA"; } + public MlDsaAlgorithm getMlDsaAlgorithm() { + return algorithm; + } + @Override public String getFormat() { throw new UnsupportedOperationException("getFormat() not yet supported"); @@ -82,11 +100,11 @@ public boolean equals(Object o) { return false; } OpenSslMlDsaPrivateKey that = (OpenSslMlDsaPrivateKey) o; - return Arrays.equals(seed, that.seed); + return algorithm.equals(that.algorithm) && Arrays.equals(seed, that.seed); } @Override public int hashCode() { - return Arrays.hashCode(seed); + return Objects.hash(Arrays.hashCode(seed), algorithm); } } diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java index 31c2c2261..865cedb1a 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java @@ -20,24 +20,38 @@ import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; +import java.util.Objects; /** An OpenSSL ML-DSA public key. */ public class OpenSslMlDsaPublicKey implements PublicKey { private static final long serialVersionUID = 453861992373478445L; private final byte[] raw; + private final MlDsaAlgorithm algorithm; - public OpenSslMlDsaPublicKey(EncodedKeySpec keySpec) throws InvalidKeySpecException { + private boolean isValidRawKey(byte[] rawKey, MlDsaAlgorithm algorithm) { + return rawKey.length == algorithm.publicKeySize(); + } + + public OpenSslMlDsaPublicKey(EncodedKeySpec keySpec, MlDsaAlgorithm algorithm) + throws InvalidKeySpecException { byte[] encoded = keySpec.getEncoded(); - if ("raw".equalsIgnoreCase(keySpec.getFormat())) { - raw = encoded; - } else { + if (!"raw".equalsIgnoreCase(keySpec.getFormat())) { throw new InvalidKeySpecException("Encoding must be in raw format"); } + if (!isValidRawKey(encoded, algorithm)) { + throw new InvalidKeySpecException("Invalid raw key"); + } + this.raw = encoded; + this.algorithm = algorithm; } - public OpenSslMlDsaPublicKey(byte[] raw) { + public OpenSslMlDsaPublicKey(byte[] raw, MlDsaAlgorithm algorithm) { + if (!isValidRawKey(raw, algorithm)) { + throw new IllegalArgumentException("Invalid raw key"); + } this.raw = raw.clone(); + this.algorithm = algorithm; } @Override @@ -45,6 +59,10 @@ public String getAlgorithm() { return "ML-DSA"; } + public MlDsaAlgorithm getMlDsaAlgorithm() { + return algorithm; + } + @Override public String getFormat() { throw new UnsupportedOperationException("getFormat() not yet supported"); @@ -75,7 +93,8 @@ public boolean equals(Object o) { return false; } OpenSslMlDsaPublicKey that = (OpenSslMlDsaPublicKey) o; - return Arrays.equals(raw, that.raw); + + return algorithm.equals(that.algorithm) && Arrays.equals(raw, that.raw); } @Override @@ -83,6 +102,6 @@ public int hashCode() { if (raw == null) { throw new IllegalStateException("key is destroyed"); } - return Arrays.hashCode(raw); + return Objects.hash(Arrays.hashCode(raw), algorithm); } } diff --git a/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java b/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java index 8855e1072..3410a3462 100644 --- a/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java +++ b/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java @@ -15,6 +15,7 @@ */ package org.conscrypt; +import java.io.ByteArrayOutputStream; import java.security.InvalidKeyException; import java.security.InvalidParameterException; import java.security.PrivateKey; @@ -27,7 +28,7 @@ * using BoringSSL. */ @Internal -public class OpenSslSignatureMlDsa extends SignatureSpi { +public abstract class OpenSslSignatureMlDsa extends SignatureSpi { /** * The current OpenSSL key we're operating on. */ @@ -37,9 +38,43 @@ public class OpenSslSignatureMlDsa extends SignatureSpi { /** * Buffer to hold value to be signed or verified. */ - private ExposedByteArrayOutputStream buffer = new ExposedByteArrayOutputStream(); + private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - public OpenSslSignatureMlDsa() {} + abstract boolean supportsAlgorithm(MlDsaAlgorithm algorithm); + + /** ML-DSA */ + public static class MlDsa extends OpenSslSignatureMlDsa { + public MlDsa() { + super(); + } + @Override + boolean supportsAlgorithm(MlDsaAlgorithm algorithm) { + return algorithm.equals(MlDsaAlgorithm.ML_DSA_65) + || algorithm.equals(MlDsaAlgorithm.ML_DSA_87); + } + } + + /** ML-DSA-65 */ + public static class MlDsa65 extends OpenSslSignatureMlDsa { + public MlDsa65() { + super(); + } + @Override + boolean supportsAlgorithm(MlDsaAlgorithm algorithm) { + return algorithm.equals(MlDsaAlgorithm.ML_DSA_65); + } + } + + /** ML-DSA-87 */ + public static class MlDsa87 extends OpenSslSignatureMlDsa { + public MlDsa87() { + super(); + } + @Override + boolean supportsAlgorithm(MlDsaAlgorithm algorithm) { + return algorithm.equals(MlDsaAlgorithm.ML_DSA_87); + } + } @Override protected void engineUpdate(byte input) { @@ -59,14 +94,29 @@ protected Object engineGetParameter(String param) throws InvalidParameterExcepti @Override protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { - this.privateKey = (OpenSslMlDsaPrivateKey) privateKey; + if (!(privateKey instanceof OpenSslMlDsaPrivateKey)) { + throw new InvalidKeyException("PrivateKey must be OpenSslMlDsaPrivateKey"); + } + OpenSslMlDsaPrivateKey conscryptPrivateKey = (OpenSslMlDsaPrivateKey) privateKey; + if (!supportsAlgorithm(conscryptPrivateKey.getMlDsaAlgorithm())) { + throw new InvalidKeyException( + "Key version mismatch: " + conscryptPrivateKey.getMlDsaAlgorithm()); + } + this.privateKey = conscryptPrivateKey; this.publicKey = null; buffer.reset(); } @Override protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { - this.publicKey = (OpenSslMlDsaPublicKey) publicKey; + if (!(publicKey instanceof OpenSslMlDsaPublicKey)) { + throw new InvalidKeyException("PublicKey must be OpenSslMlDsaPublicKey"); + } + OpenSslMlDsaPublicKey conscryptPublicKey = (OpenSslMlDsaPublicKey) publicKey; + if (!supportsAlgorithm(conscryptPublicKey.getMlDsaAlgorithm())) { + throw new InvalidKeyException("Key algorithm mismatch"); + } + this.publicKey = conscryptPublicKey; this.privateKey = null; buffer.reset(); } @@ -82,9 +132,15 @@ protected byte[] engineSign() throws SignatureException { // This can't actually happen, but you never know... throw new SignatureException("No privateKey provided"); } - byte[] sig = NativeCrypto.MLDSA65_sign(buffer.array(), buffer.size(), privateKey.getSeed()); + byte[] data = buffer.toByteArray(); buffer.reset(); - return sig; + switch (privateKey.getMlDsaAlgorithm()) { + case ML_DSA_65: + return NativeCrypto.MLDSA65_sign(data, privateKey.getSeed()); + case ML_DSA_87: + return NativeCrypto.MLDSA87_sign(data, data.length, privateKey.getSeed()); + } + throw new SignatureException("Unsupported algorithm: " + privateKey.getMlDsaAlgorithm()); } @Override @@ -93,9 +149,17 @@ protected boolean engineVerify(byte[] sigBytes) throws SignatureException { // This can't actually happen, but you never know... throw new SignatureException("No publicKey provided"); } - int result = NativeCrypto.MLDSA65_verify( - buffer.array(), buffer.size(), sigBytes, publicKey.getRaw()); + byte[] data = buffer.toByteArray(); buffer.reset(); - return result == 1; + switch (publicKey.getMlDsaAlgorithm()) { + case ML_DSA_65: + int result = NativeCrypto.MLDSA65_verify(data, sigBytes, publicKey.getRaw()); + return result == 1; + case ML_DSA_87: + int result2 = NativeCrypto.MLDSA87_verify( + data, data.length, sigBytes, publicKey.getRaw()); + return result2 == 1; + } + throw new SignatureException("Unsupported algorithm: " + publicKey.getMlDsaAlgorithm()); } } diff --git a/common/src/test/java/org/conscrypt/MlDsaTest.java b/common/src/test/java/org/conscrypt/MlDsaTest.java index fb9ad3bfa..c7d8d29e1 100644 --- a/common/src/test/java/org/conscrypt/MlDsaTest.java +++ b/common/src/test/java/org/conscrypt/MlDsaTest.java @@ -122,6 +122,7 @@ public void mldsa65_works() throws Exception { ss.initSign(privateKey); ss.update(msg); byte[] sig = ss.sign(); + assertEquals(3309, sig.length); Signature sv = Signature.getInstance("ML-DSA-65", conscryptProvider); sv.initVerify(publicKey); @@ -130,6 +131,30 @@ public void mldsa65_works() throws Exception { assertTrue(verified); } + @Test + public void mldsa87_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA-87", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + PrivateKey privateKey = keyPair.getPrivate(); + PublicKey publicKey = keyPair.getPublic(); + + assertEquals("ML-DSA", privateKey.getAlgorithm()); + assertEquals("ML-DSA", publicKey.getAlgorithm()); + + byte[] msg = new byte[123]; + Signature ss = Signature.getInstance("ML-DSA-87", conscryptProvider); + ss.initSign(privateKey); + ss.update(msg); + byte[] sig = ss.sign(); + assertEquals(4627, sig.length); + + Signature sv = Signature.getInstance("ML-DSA-87", conscryptProvider); + sv.initVerify(publicKey); + sv.update(msg); + boolean verified = sv.verify(sig); + assertTrue(verified); + } + @Test public void getRawKey_works() throws Exception { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA", conscryptProvider); @@ -145,6 +170,58 @@ public void getRawKey_works() throws Exception { EncodedKeySpec publicKeySpec = keyFactory.getKeySpec(keyPair.getPublic(), RawKeySpec.class); assertEquals("raw", publicKeySpec.getFormat()); assertEquals(1952, publicKeySpec.getEncoded().length); + + PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + assertEquals(privateKey, keyPair.getPrivate()); + assertEquals(publicKey, keyPair.getPublic()); + } + + @Test + public void mldsa65_getRawKey_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA-65", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + + KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA-65", conscryptProvider); + + EncodedKeySpec privateKeySpec = + keyFactory.getKeySpec(keyPair.getPrivate(), RawKeySpec.class); + assertEquals("raw", privateKeySpec.getFormat()); + assertEquals(32, privateKeySpec.getEncoded().length); + + EncodedKeySpec publicKeySpec = keyFactory.getKeySpec(keyPair.getPublic(), RawKeySpec.class); + assertEquals("raw", publicKeySpec.getFormat()); + assertEquals(1952, publicKeySpec.getEncoded().length); + + PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + assertEquals(privateKey, keyPair.getPrivate()); + assertEquals(publicKey, keyPair.getPublic()); + } + + @Test + public void mldsa87_getRawKey_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA-87", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + + KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA-87", conscryptProvider); + + EncodedKeySpec privateKeySpec = + keyFactory.getKeySpec(keyPair.getPrivate(), RawKeySpec.class); + assertEquals("raw", privateKeySpec.getFormat()); + assertEquals(32, privateKeySpec.getEncoded().length); + + EncodedKeySpec publicKeySpec = keyFactory.getKeySpec(keyPair.getPublic(), RawKeySpec.class); + assertEquals("raw", publicKeySpec.getFormat()); + assertEquals(2592, publicKeySpec.getEncoded().length); + + PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + assertEquals(privateKey, keyPair.getPrivate()); + assertEquals(publicKey, keyPair.getPublic()); } @Test @@ -172,23 +249,29 @@ public void testVectors() throws Exception { byte[] message = vector.getBytes("message"); byte[] signature = vector.getBytes("signature"); - assertEquals(errMsg + ", algorithm:", "ML-DSA-65", algorithm); + if (!algorithm.equals("ML-DSA-65") && !algorithm.equals("ML-DSA-87")) { + assertTrue(errMsg + ", algorithm must be ML-DSA-65 or ML-DSA-87", false); + } - KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA", conscryptProvider); + KeyFactory keyFactory = KeyFactory.getInstance(algorithm, conscryptProvider); - Signature signer = Signature.getInstance("ML-DSA", conscryptProvider); + Signature signer = Signature.getInstance(algorithm, conscryptProvider); signer.initSign(keyFactory.generatePrivate(new RawKeySpec(seed))); signer.update(message); byte[] sig = signer.sign(); - Signature verifier = Signature.getInstance("ML-DSA", conscryptProvider); + assertEquals(errMsg + ", signature length mismatch", signature.length, sig.length); + + Signature verifier = Signature.getInstance(algorithm, conscryptProvider); verifier.initVerify(keyFactory.generatePublic(new RawKeySpec(publicKey))); verifier.update(message); - assertTrue(verifier.verify(sig)); + assertTrue(errMsg + ", new signature verification failed", verifier.verify(sig)); verifier.initVerify(keyFactory.generatePublic(new RawKeySpec(publicKey))); verifier.update(message); - assertTrue(verifier.verify(signature)); + assertTrue(errMsg + ", testvector signature verification failed. how about: [" + + TestUtils.encodeHex(sig) + "]", + verifier.verify(signature)); } } } diff --git a/common/src/test/resources/crypto/mldsa.txt b/common/src/test/resources/crypto/mldsa.txt index 8e4ae7e64..cfa7059b0 100644 --- a/common/src/test/resources/crypto/mldsa.txt +++ b/common/src/test/resources/crypto/mldsa.txt @@ -15,3 +15,11 @@ seed = 7C9935A0B07694AA0C6D10E4DB6B1ADD2FD81A25CCB148032DCD739936737F2D public_key = 1483236FC9F943D98417809E95405384530ED83E151E8465D34E4638F1F8D7058D62E19AB806490883A823176D4DC8A3C10C9960D0E948A9F7B62CA8E118DE5D7A05BB18E8018B6CACB4FE7885490599939D90D004BD480B116F5D6627B6C4C1B2A1496CC3525EF9F19953EC63CDD6EBDB21D65B27C644194916AAD07CC559B08CFC1282D25D7276C9E5062E0B1C4CF111C0A9DCC49BF40F5ED3C27CB4E78E39C1F068736A788E2ED4A02E9EF23EACE802CD295B6EB97D533091B3293D9BAD2938DFDECF2C4F9F6387B38A7FD22738A010B85949688650B6F063B6BC6350A1E84C869FB3BBCDC4BF6C0D0674D7C07F7AE78E4BBB302B6DB8488B5F9164E5E264682E45E71B58FC19ADF5EA892439EB352AFDDB63D22177AEF17261909E3F87BCC7E1B1A58CD5DE8F8A886A12D7137CE5BFBD2C53ECEBFD1B9F2298583D767E0DB5178B952F4D069D66FDEDCA1FBDCF8720AAAA5313C0500ECF95B9B70E7E3D58DD2B57433D3A0637DF36E964B21F44F791B3AF9074D6DBC9A2FC041D9E22D5E387C4081E6D4CCE6AB11FC8B4F2C718EB2A19924E3F17EA1F44D0084B5D5296A97A3624E4E1F6CA05229F2888557AAB577FD72F8DC328F0E4F45DD13A191920F671ACE3BC29DC3195E951D0F5EEAA095A3D5F20E4E4EA1AC157261C1C514AEB6940E63053AD68383F14E923602E6B241E9813246B47F009DB446FBF61246BAD7ED386647D020A854CCA39ECAE5FA6D667CB6D433F02BC2FAB9F37096F3C127741EC02A46C81022E070AE1DF54623DF44C5C744EDD0D3BC66581B8E1348E75B5C52D0E41BC71EDAD5B12DDA2280724B7D704BFF2AF04505F65AE496DA86701D36BC9AFB0B199442A9C5C743D97880E89C8CCB34C51890602627924316E79D4415CC1C2ED490A7A6EBB4B507181CFF18BB53A6B8F816C15A2EA8667CE59EDBE8F42376001E31981310CA403E08328AA97828DC3A86C260819BC8DF72A3E29657CA65B7763A54067958CCD6FD73DF789B306A37185C8117F0C86CF9D1C48D102ECA8343F41F86F6084E2E72E6952357D7DC076A02A7CEF64724AE634E35712E291A24704D2939717246371B42C11A672FE8FD31DA83FC3D5DE650FB2136A13A0D6229A115EA3758E3AD0810A99944275FA8FECFD2BF1D130B40473F4ABF886485A1E36290DB437B331DB303539F98D298183509D934F1A747AF29BC36BD7CA79E5D40D098EBFE61F400620B5B1AFB81327342AADEC634F1A77DAE793D55A252D391AD155A6150AB049CBA0270F07936AC21575BE6FAD53A0DC23F462E377F2C882391BAC1C17C11D18A677C3EFFACC4C6A920596F8654BB4955750BCBC18744375656F0B594D825872BB161A1B7FDFE7D01E7A19E02F41AB9D02D1FED47161716172B8D68DB04E57C74053DAC785E9245BCC8DCA48C736457EDEB8A075C1C42254E87110CBE4A909421AE6AECECE5D65834739BE6CAC51D1023CA25C322B7B3461EC65168CCCF483A2668FB4527BCB312564C4097224DBC38AB397C3A7FD693B29992B9A773C43C0E9E94479F1762C91C367D9A079B13FDC38BD74F209E4D543ABF8C9B14CED015599DFAE94723361ACBF6C1C0434DC0EFAF22C61057775F17F36D76FD75D6BFCE7DCE922DCD7585AA33CAE7A6916C4E4AC5F86E4753F8CC798C20205C8C47656FBAD7799B6A53DAE5DCB74CDB677FFFA66CBF2873A219413714578D6DA3B61AA29C494C2F084BE1FA1C1CC40D1E4A424A4CEC73E455062B6E28C333839570D6FC6C08402A8D39F145B97C3AACC6F24702E80F66F5D2FA1530CFF2A07486B3D38D8C9994EE633C2E527AF49FBE26F634C6663CF95520E04A76F33E8876826B88887C4FE8FDEB1C50F55C7E7FBC2A5077FA029DB53B7CD8FA3576BBC219AE7D7B21518FD94FA187D39D63187BF9F2BF2592F1A7A35628137D82E50477FF3406DABFE558A3FD30D4E72D1F523EBF51DF6C7BFD9C85325897A7949113F30C9570F3A9FBAF73658430C3B2AFA43BF9D37D5410B5E416C5CF375CF9ADDCECF560E7D636C2D58B89D3E5A446201990EFFC467FFBA1009EE90D0F46BD2D7018AE92CABECF62130BD7B4A077AF31882A713C73572387533EA249C9A18F0599C06EE216CFC60F7498B2A75F3F8143D90A4ABF8651DEFAD600FD332AB09E3D8FAEFA2EC9152EAF6F2BE6B78629022C0231849BE4C13FA08B827EC301150FA380663F737418C8BF0700F4327F58C2256F8BA8B61176DFD1ACE6A81C19033E3D678A9CB234F85A5B6372EAF1A1883F5ACED3ADF58B7FABFE44D986DBEDA351EA9DE5A841CD523336F986AB8FBBECF1F52B1E87DBB3AC457A743FAE899A5BB3D10EAFC4D0808B7FA98C8068093CAE7A0BC2074BAA701273734C28E97CD1102FFBCEBB83EBB17C9200BE6DBE58BC87C522E4D24254204FD2EC52C60C1225649C3DEE17012C1CC0D5CDA0B2F0FC4F27274E04ACEDE68BACE92E294B589BE45D74C5377AFEAC7182F4B702B5A50B49F1B32BD476483957C664676A819FE6851F07768DA82261C75D53F8F04A64291A56E008B11AE09EE73923257EC195020D958F7B6D43ABA268978CB33B150A9C0DECAFBB36291257512CC7F2CB0B5564A0F81EF4686838CDBFE10475520E6EF69047CCA864E50C86E9D91FC4EAE741D4BE8AD7B12952B76C3429548169C370A7A5E2DB3FC809B9930952EF5AF9CDCCAF74FC13D0DB8D55862858E47E4C6F66FDA9DA423B884DB6ED79D012587F757F0BD974680AD8E message = D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8 signature = BD0D51DB2F225AC6D3DA8F0C2439B0BCDA26EFF7EFA67CFD3C2B98EFA08477A74088DC638126865E493697B6FE360FF9C55B304D15A7474C983C3D8A4E1AB28FF9925CC9073AD986D4B53C28B4CC909DC36B9334CC4510AFFDEA9548620923ED2158224AC5CA8FEF19228DBBBF12956F5422176E8A474AFBE6EC6551F1FFDE71E86C48B39BE6CA540DBD78B985E89A2F7576325E79DCF801585D30DCB3F971C827F4489745D450DF7AE34496C42C7A8778AAC7FDDB9740CD3F07A8AFAD1C1471FB9591BBCF37BEAEA10C465ADB4BD7303ED6CA41AD4848CE8A5659F7E3D4894AB0E79A0E7206C9FE278AC9CF1F6A3DA6B9FA8E03AFEEE717739CBFEB5C26EF3B1C9130C8DD46F9C8E8149DA9B0FE5AA8FD03600F87824A6F2EE8BBCA0EF6D8C38EC526E982100BB8A8974EA91129BF827FE4CCA13D7203D38AC51B2A14025948E5AC0F71394EB804C885521EE65EEA303CE30D0FA9626A914F36246A8F55EB2D866B215FC191CB734CC6B4724C8C1562F81E3678D39097871249B86833C6981FF45CEC71339E1C6F38ED1D04B6C70C21642D268B5E058F8095101C2339EE5619280F2553308DBCFEF74537DD02722E42608FFCA2E8EA8B8A2FECF46948C952D003071792845A07DBCFCC483B594CA9E0A69664498835DA427761E19F9FDF29E5319AA0FBAA7150DE0B1F951D9CC0E1B62DFB0857DB7C2129A896D65DCE0ECD3A87FABCC2A4A6FA5811CF6312DC9E3ABFD5ACC116A8A25F45AD3736FDB541276732DCD997B1B687BDAC9827A4582B8D3F0877595830E2079DCE9104E1FCFEFD0F8225BA9739C30CA7671A05688B55BCA1F9ED968E6F3F2831E3D54E596707BF63FD6AA809FE410EC38A17E3F8DE2E050A9E6B81CC386CC229041A7BE15FFC912FC4066A4D2D7FB98AF7022840E593C4E599D0309F37B65B85F10541683300779FA41124B19D4032CF8D7AF5726D3A08331D7A712DA910903C0A381F616CE5B1085F779486172EA4D7B127692557DD156B63B0E445ED8888E446397542E50C9BFE7B728E31388F7743D0F51151D4B4CB7642431ED0BAEAE264F4B2D9BAC2D5618338EE092228A251A4F99D4F95D263CAE16FB9A45A51D45BEF0F6CAD30547AB4BAA1C6F28E6FF35B195D938514F58FC2B47BEB8C895D213F11035E5FAEF85C917D7AA551FDF8D316CC4DE5A159CD4F39E3C118673984147C82BB41089CF0D9B6712E899A99CBA5DE33BF33E2C0DA03745031A48A37F7E6A7288790839461F2C58BB5ED93477834B572DCE2DD00DD31B866C2387076037053872D8CF8EB57AE81FDD84823DC69FE0A33F599846620AB74E86912759E245332EECFEFAAB9726F8A59256200BE72BC47DC3E0A4E28868842935D216334191F32E0630920D8DB05EE62813218A1E1FC5DE96719D08A00FE7D5072C8D51B3ED0AB0F9D5B45BBC2D5DD2CC7E6ECCB080D617565119C4B2A4E408A0B18EC969DCDB2BB7D8DE2EEEF3A76A0A5E437C6681AE7A00D54868E0F51EE39616AA29FEB7ABF4A3E17865003B781497BA572EDE6EA7A9479FD15C295B79C0384D4D8451043C6F67F2E10D8442F0C4E72684D6576FD41BC3756B1A8834082144760C7F609B3665C03F001073CCFEC1EB18FB9A61D82A8462D0A86FF80520053C55F2D79502F95EEE9B50F1B95179BEAB6EB1ADC4F582A9CA12C31E6F165E064AA9F289DD2A5E12F45E71C98CBC87DBF218926250D1A78DFD2B46B1DB4844AC63C5A6960F67A6BF0B270337E629AC04BA47883E52C33246863EB9F54BF2DFA5905F057490FE14F993D81EAC50E0D16DD0EB2098D0D1170FBF30892A7BFB45F6C6B7E349865CF4313D1572CA41A06C0D5561B0704AF4BCD4CBFF4045C5F76A9A760751F7B1432F8049CC9C0496F3E80026E2078CDC7BF54132C84200A4C27B23AAF69E97B25D8CBADA6F5C82748D73F8CEE44980B909EB0C11EB49FCEA972552BF5BE540DD9467EC81D70990562DC558C00CFF68DB80F3D2BBE61D7E154A2D5A4166E86546D8A82886E1CFA28CE2D8BF57D67D9B6CE32D451F9B2B4D73474C299C64FDD8D2AE15EAFC3F88179B8B364FE16B51E7B6C4DB47D796E159546BD409DD72879234578875C7940E057FB9508DDD9754D130F5CC3E32D82104DBCE1BA883FBC0C9AB9072A1A2771B0EA1152682D182D537EEEABE3F79C531A26E236AEF6479D5A7817D00723D0183E4A1A671C3285BAE7793D7FF982A6B90F7D38E40F763EDC401F2BD0618D3E305257CFADD3CCFED8DD3FD03CDBB533976FA353ABE73503EF8360964C2CA78888B4E67B0EEA68D35E64A840D136A7F0CA41CBBC52543BE45CA846F0213EEA90D932AB3A6902795B0B4FAC28C838224309E94782FA315BFBB9A535F3763FA9C3C95FFA3FFDA9C486678F7905A3637605A6929F234B9B04BDC729E14581888848930DF0D77FB1DB65D75F292E0EC78FFF3352ECF99D87E0B6FFC78F5B9CB423FCCE606D74D35D115A418EEEAE012026691B82D5B0262A1DD137ABF192683173A5615A3298A2224280C405EEE6094ADD0E1ACEE74204BC0F8170221621A71743084A072FDF03293D8FD7778E8E3282DC49A1A950404CE827C281E1F57E9DFA1F1156726DFCA3560F5C909987D6D79E831166155D5AAEE8F1ED382863195ED48EA6924D7A119EA99756434092F08E217804EB4943E56A42CC7AC5CDFA7CACE562FAC86AAF3BB5C3CF6F6DC35036B388E9EC8BE2272C2D6CA425FF23E6EF7878332042B120246271B93F87C463434921D0BF6A105A2C7E473B3C5E4BC5828403C130005B2EEDB7C161010A7A782AF3EA91700A7610DDA532DAC61DCA768B51541D2F6213B9C5047CA2AC0E1DDA275EFB58359B5AE203706BBCB1B2DB3ED8896C3721B51865A6F9B4B8949FAB4F3301AE7CBDC540F0B04FD6E27BE48748DA228DAE22353DA7CA1C464E70FB78960491279E827128BEF241C764061A5AD103EE62B26AE08066C5F20B807883C8E8A3144B7968F232627440154FED536DCC09DC9E33BB7BCDAED850F0435E1B9D943F79640BA06F21F99A1D89997BC5529D1E69095DE36958B8F186C12007DAF19115B0F971DFACB126280E1C4B956C458F9AD2EDF2226A696685A3DEACE620DBAD643B4B2E31911F53BBCC1E712B83DE8687D4956EBE1A30CF4D7E86DBE8B6E28DD6AF59BF6E83E25D9B67458ABE922181C4BFA5E5D047A7799D8F117411DA633096CE2ABF19C5317C545835B06A54759497605A0265A0396C4F069F7AAF9E677140679A265893780B0F4ACA2E48010346CDA16356E6D69F48FBD6E9763E1EAF576008BD2EDCCA2DF8808989D801F687EFC97EBD1C0FAA8555664BDD49E39B38565480D7DE0BB51E1CC5341DBF12DA73B5AA7DF954B5569272A7A3EA3AD45D8F65F718007A0C35AE3C7206E14AE7033E4DCE999F232BBB488AEFF090A1D160B10847B134FA82867114C4EFB7CC83DF601108E61457F7242FB159B0840D7711C0C50DEDBDDF346BFBA7C7EFCA4068B35B93FF81054115AE59DE3C55BBA020AD66893B88AE491F8F6BD45BDB0D506D15E050B26BDD0242F0EEC3092830E3F35D59A4B94B7A41A993F44DF9199EE6B084681D554AFD3970DD410E748F4A95F3F5A3B2827F1C587B563FF7F0D7C47AF3B9F72B8AD6A46C2CB178929F80C1852AD8247769BD4FEE274A0A07B20137CA67674E91779D9C6424F06E78A8BAC807C31CBB4677E9CC7D8755997BD19DBF053F1EB7DD6DC3875E667088B0501FDDBAB90C6A4C215E28B17DB87B0F4423C6108813AC993F69CD20953E0C6B85E308F20F1855F5993FB269159F2EE5D87316A0B744CD6530BFAF581C7FBAFD20689B702BDD4F907CD9D5ED768FAB06CD625B171D7159112E2446F8B6B2FD3B89F43D6C42B5120CFC98AE2762D241C41D32DFF80F7147119FBA9900689E1919EAD74C77F27C046B513FE143884A439F1E8399CF97C7E83F3BA585C5A0117251EFB5AFF33974D5B0FDBD61B62CA5692983643788AC31010E70E6909BE8757F6BD2E721BAC6790F8DCA7D1AFCDA291F1DA1669E8906F4880E0E1BDC2608A0DF671BA401C178A53AA6E1B2D6C90D2769E4230B60E9FF10EE38A1532090B3D5076D1D320697F4AC06FC8574136373FDF90D6872190E26F5311BAF686A95F47EF7A31F8A6AAF0196D3CCED25D5A549FE618D02F3C531FECF1C6770BE5B43FFC299519B7AA701BED350A09AF45B9268D8D5D81E8B962303C1F8E4BF15F5DE14A85312EB1C9511DF3E687CA14081754A2958324B4E5BAC035C91240F01D7719DAAE546ED56885F1F393DF95690C20618AAE3229C6488AF7820C3E8B421957CCF4F31A5173B7282FB972F7981AE53F73F2AE5747B608FB05F01888E80C1C6CA031D52E573FBCDF986471D038EE3C6E0814E24E8DF75BDBAE63F2909B47D9401107439A6B022C897763194687110D50779A9ACA6231B04D587A87CAADE5E4E91B7BCF43B2E469F52DBF19AB1D180F477D5DF2E45ED2609638E22E4F5143BB0E733F16AD183153C8460E9D0A821C9AE4AD7DB358B18E91A9022A26283F553D722F4D37B3B9EA7E5F684A1395C72EAF26150960A318B8901630E1A657479A2B1F7181A1C215678F3626BB7E2FD0F36498497A20F2D3C467E803F697DA800000000000000000000000000000000000000080F141A2024 + +name = Test case 2 +algorithm = ML-DSA-87 +seed = 000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E03 +public_key = 467079ba853507ac38bd883bffbd9100dd43a2b755c31d59f2459f09907450525e781539f5b1ac3dcb67bbceaaa08107e92524084199be4ca89b15b1e66c9c3eecf51a885a9559ef458ff47710e12b82e083bf05a7ccb91e4ec28c9bea51cc8c093ca88306b30adef37e3af8ebd73fa730d9470e4cda5125ceb91cef4959898f4da7768d774a9a32907112220f9e44b8bfbd40e3262e3c409483a325d5bdb3d9bb998c6b2bee793947fc733f93e75b2fd2b44624e75bd0d861427147d3f82779cfdf414842d5e3645a05c05157a8097c60116d2d33090ba6f72237df5e6cd6a99b34cf75fe3c72195444165f875de88dd24cf90bd91d61d49e8319e4214e6d321b4943b988fcea283a307d46144d4da2c3dcea3158a181141fb38528214245a159c836abc600fdd870826362c2988be73ceada985f5de1efa5f27e6f42da75d57ece95447e4734efdd7577a6792ae4e166b2ecba0bd4bf5d428581d4ba12b44d2d7e44006b7471e538af7bb83b0bc588a999568542be276dad6416c71bb3129ea0cb81eeab734d5692c2db0786017c3c583987fc1d207d9bade74c288d4efe08b169907702cb10698a0e04223da082e90c68b6d1e45e86f2585ae4f4b0f70b96c6b79900fe34fdeaee21125271b090e43ac7d5219f0baa95dd71f9435f5e7acfce0eaa6c0b948308553238fc5063b9f1cdd2a4fd938cd000edd91f3ea7b13fc8a795ee2066c6f963d8a90ec0ab323e5d22ddccfa63261ee0bd51ef06bf7eef0fc84d35289f717463887717d2bf4030e55a6fc086ac4adf07d946e77bea275cb94cecd869ef162972d0489237d1be2cbfac5b7e0c1a6b1420aeb114fa83bd7fdbfa4f44dae7cf893e54aeff340845d85072cfa7fe0eff63d4a1fb0817fe6a9aea8326e6e30b7a1ad4a7ebde2bbea83986a4a39fe531eec7d6186ca44a8a8481f2832f74c4089b4338ca0236e46c7c08b00225e00217f19d24083e3b65694451e97995dffab3254f39ec1fa61164f0ac8b40c2b9d05db2e1d618a4d45b589dd742a5d2964ecb6a00ab13794ec615b9ecdf2cf33cdc3074d50138e8e5cef99542e6586b854075452630767056d4ce60d27e9ba603ed10676a891729a9fe02e5341da78668276284ed2aa7dd002080b907c36c2e68f51b47b625513cc0ebb6de26d2bd9bdaa91faa504080b9d7bfa4c42b0ef1d7966f40e82fcbc0f1790b6a1ba240f061c5b2dc63e1339e960f1999a498b7e3e2ed9cc048f389dbf2a5d4b2841b2aa7ada38118dfbca25ec124eea44a7155b3e2812b6993b91bb6558b7fafefecbd4637a411cc363f098b674dbac2236825fedb722832ac6d7825cab66d9e27384a1d99f2c4ba87ed124a4df127fee2ae6e44e7b134ccdffcc83c43ceb6971541beacd798868d1606f289b823325f841627fa3425133332633ff1e96b0d61eee4a756cddee52faf8587f96df0036fd5ed4bfafa21acef1112874efce3dc3cf385cd39d0a5dc7fa5998d850136e4639505d7b53f6d7c1076f5677b731849d9e54a5f3edc51014dcd9e171d28355a4555e643752cc6ac459e570384215d14d9f18b2a3a9a43b6e60d098bd16e546922b3a8fb743940a15af7220d07ab41b376f78b24845b8f5d17d2ef95b2ea0c66731c11f2e6a5663d57225394cf7e274fdbec0cb10e29ed42b09dccb97cf7e4974b2b07222ac77d51b94fdc3850c220e61c1b2816e85725053f0973d04f02e58993ba4ca46bb40fe8f0609516c145a4b5668ef893735d02bc0e86572ae8027f4be99e024ab922bb310411eb3f90286c8de75a1def0faf118ff322d87a348efda4f0a6cca47a059d897219453913cfbabce4badf2396cbd0b08d2e2d9a6cdd00de2caccab4f9fa28ea099bd253bcef35bd9d45e20589219b86647afc6a58ac4e0c3d10aca039d455f27fca07e4393cc4fb5cbdfc71ed8c433501a8cb9a86faf81080b17bea367c84cb4ed076e1ce037faabff6d752127c6fde5f9f388369d69200b49453e3862c607551bbf17e8d729ebf231a058d0c13ca97a426d2f9e0b7b573e5f9702ae3c673c2495b188870073995d2cd7d7f2f59672ce97cc24a08b2ec0b1671330faf986720ee5146fe45198d6b9cb291dd4b11b0741db975694244b9d746f1e95e78cac2dfd20eb4bf34b67371fb7f4619dc2e5c687d1a519903a31bbf7de02cb6dc6e9337011f1520b992e65ebcfbbf1d843a8e41f9f1080a148764a86d893b34bd70902e04fc5bff58a4dc007655cfb641e424b7ff50b587981f1225df7808cfdd7d9d67d01a67dfd9f5f264c7c5731463cb215d7ec5ada0c79411fcbcfe7932b25a668f415bda6738dff5b35195ba3ab4d233075e3d14ecdc3b695c502dd2bc7644a59ee0dffda587345e9d917b5bc7014d44097ad326f409582af76c17ad066cf62f1cf3fb0d04da94918972661b34dda287d7d2b29fc901fac30dd789f2680d7d5d5734c8cb562916b86e5e626dda2158932299985e78b7c64114c25367e69514e856ecba8e2d631bae701870110b77f1fce7f34a7cbd94475557844d2201e40caf27dd47716361e3795880f388babed1049fc83e125d3f570ce32c11f309ad24407b5d8066e94c6580d8b59a33408333d6431cf19ee5364bbb9c155faff06bdf23e32543e27913984213f9be63ed8d929b1a743d7b760d9c8ce98299dd4fe6d8bb1313b5a2cef487bd032e1b82c5740ab2c4a380aa04237947e6ffa6e8f6d2cde7ea3eedbf4d2c4e94a17a57739166b3b22e19eeb21d812775d26416f7a52bed28631ba24fb05c271b4bbc08f7725bedd2018de9b521faf8808b3c3ea26c17a50bfb6eb3abc438d3abf7f214e42ec7bca0ffe935111e4761a2fcf7bf41895be662f90d258a879b85f4393cf41d94037c96eb9e8188ddc03e6904a97361971747b48bf9a1315894f75a2f77953f63dce7741b815bf56f6205776a42d97ea7de99748d8991346dbc18b0b61bb77267df6c42c17d5afa9f3d5f49eca689d1f962eb5635165c307090f9ceeb19c96fec4ac27c0fead4e986c89d805a6318cba05d0ae727a017b813bf6f1fed8e30fc5fa5278b88919e9e20e0631d40e338b7326b464ceb154a641f208162e2ba8dca3253a80c7ff651274bcbbf6412049a91213cb0108b889d1e0b1b59edc2d730c0e36c35e395375333f48155cf2a5020587aba95fc325c633bb46d511f9717d8a9b2f272988a9ec0c7c30978030775a6d33d0816c3de1bd7fa03f01de7a1a58e1f5cd7b9fdcb1bf54520fec8547cf50ab33565b7eb605e355beeda9306341f658cb97f0d6d43e6f502f4e3bd253738a3841868e954f5859f0d26eee0141300c2001e6dbbb8c598ebe72f0f58276ed92682b95c777e2054392b1cb399a9dae4e658e3af757aa80897e549e3d47c99daf602d4523cfb994ff5b102c5555ea31145e6c6ffff8bb72c4d7a91dc9d06f20a7ba19fdd93a4628aaa9c665b0cae98720d9681d1421ab0af82758efaa4b5489ec1d22900efe84b3bdb7b64d8268036273a21afa618d8ef46feca0a8468719c4ceb2ee0159a1599ccd1bc074bcca27c0b228428f7ce1e4b310ed753cfcf9de625cbe325e07a846d2a73bdc487f36b3b5c538f6b22f0d4db5c003732e12599989071f58522702dc1c902d8df391e19f17882c1c6 +message = D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8 +signature = d3be591816ce4bc363796f443a379217541db749ffc6582ed94a70818931a7e62968992768a13fff51528d74478a4e2a3390250aa530a27a6ae3543614042d2d5b452e2a733263a113e0025fe0b2b6dd2e89414e528c9bd8604a8fbb1a1a6cd9d9d0c7500ea0ad8396940a307221b92e62dcf96b288ba154a8da5995cd960c7a800cddbb469a617bbf68285a9cbb57a15739491784409a6cad5f232017dd4841f8476031e1d6fa79cdfc20ba209b2d274b717a59f79843953e2dbaca722f9b799b2d048effa210668cb2858d5adb65e21d75578382753b942f3517d3a8d069bb20ffb397a76a8cf53a41e0f041d94ca1b67dd93169f86874498505af3f3764acae5eec67aaa358545e5f0a1e5c87ee7f4349cef10f19338f278cb6f832952b121cbe1142497ea56c31cf97cbc16a00776e08f21d64ce8d857ba69262c9ec98bbae7f05d922bd2433779e649d78fec6f50cf41000da74065ced36bb3d2b51f2b1fd508a35281d8461aa5c3ec3e17ec0f270de804a884d52f6b9c57cf4c0b33d843d518c71c7c43b46ddae4f40c5706a87adda711df7e66462ab28915163819aa25cd4926bc0899b260729cb6fc6f0165315fc8229fbb022cfd479a5c087236f35603f8c3aa8e8efc0b8c8afee93c0ac1decbf32652c0777df532c8c2fd116929c07662b97e6b5e371ecd366ea7709b52b5c37faeecb373e82ebc93a8e403b4e201e39a7543481ad30dfa49665dd26cf8515cfbc5af2a47d65bffb8f029ebd7bc06d43937a935d5e62f3fea4eee3cdc8430375aad72f1ea8db224512fa259bfdd4c36abd7dcdb8f4b4d959ed9371ff58abb92e2811ed393993cec4fec9b8c41909573bec8a08030dd8460111879afab83386fbdc3d4954db19d78a3a37b1959b15b2bd09d45557df6d0158e6ef3a1443c038277aedca7d41d80e2ed50334564aab6d125e059770f66e050524f16889949e13fb318ba24c469ce15959731acc2ec5856aba01466ef1782a36c0a93654930c92cc568ac7dc878f3b36eff507b184b632f4cd2be35fef048c9bd5ff80fafbcdd8b3b960fe00d6472ccaace23f947ab8c0f782039e6ec48668baf54b1c2f2ffd5faf7020bcda3c7ba171b3efaa87df961e08d5f64b1960b40c4d5c1e11bb6da005b1ef2194f17b51ecc2ec921c975c7694e58527976682f5bc25f4be70a7706d908fa933086b0a1a0dc49a841b302af47ab049b63d4ac525adb454a399067188fb49adbaae486ac3fb87472df0f39e4c2507b232ee9bb553e39b993ee8737441c22b5d2678a83724bde9420c421d68ba8fb82d660c4b0641a723b93a1742e6bd8f90f38353fa5f930c574134c37e09bce39ab264da956b414b0c4c60005c67b5ea5287e47f2d63bb4c0424190c1b665a045127f48ea474559577ba0beb4d3ec6252b40b8011f24e8b3af0e8d95708827dbb12bdb1f435daee02fdfe6ef81f9013b354bff3c535222589a378b897eee85fa2eed12faa11e173278a32a7adeecd9c16a076e7afcdb550af0847a91086d9c71e5cd2326a8b5d7832f319e44c31f78c433f4ca10e1a5845e617986051992d3fb2a0875e806cfa3de686fb09fecd579b085a7c51aa01c61d49b0ec582e9c8dd3c9b055ca1900237fe4de997044ce5847a0205e55aeebfba2610be6e5039afc067ad571f6affda0085f107c7814de6e4103193f56bffd6079d60b9a385f1ab0bd651be8b4469cd091d6dcafa0f81a003fc3c63815ac8fa1b56094ce8af51b9a9f345af54938f63c37d6dc1df637aacd88f713cb752c38a53171ae38e74619b68137326a5e62aa328a81e27e9ed22603dd1270a68892b045b345e1f8e705619d89ae44ccc0e9ae4eb61d0ba45514015d42d857d84261235f0198db95f6b779013695a5317783962fb5ab14cc2725c9f85c235602240770bc6962718d8eac1b841bb4fa30c0dc61e2b9ca8f5fa6424f480e509df854f8d16bcbcd08ccf77d2e2556fe56397ddb822b0a91e30a126be925286aee40fc9901c3c61d72fab8fc09a3b0995e5cf571d1e1b2d0df3cf32ccda0cc252775efcf52dbbdd9dc4c380f6fd058bec108edaa9acba1e005c2c93c456ba26b260d773d69d4f66771173b20b85a4c804849a1a3c16a15a5f651029e7f185fc4090c3b21df6158820b943fdc07aac19721ef04f014563126374c7fcc2505d89e2939f27e98f065c833094781ab4b2942ab360e43124f2f38ae493913d36487bb639fc0feeec199b31ddf404c070dbe51b2f411b20887bd06678b7378f14460a112c0416dd39da5049f0e619ead7e9cf1de6946b6c4c36a1ed2532995abae1ae79fba228075bca677022a577265743fbf34ae61054c3493d2d4f29bea0ed71807452d554cd8aa3f7ea8c8f41810e55d99cbf4c2d5b5e78f3fe1bb2616e67d42dd4a6fd9b72160572d65c937f370cd826589c8ef25c6e5037887d3010fcfd120e7941342df15120f5f3a521bb92f5da31d288348ec430b470100598d0f6c8a32392aa0bd85ffaca5718530d108d9d664460263273554e24ec8d4066f74e43736219b3636a202dd1b3a798066c0047c57fbe4a71f02233dbd8300dacc759ad6df3b79d2ba8921abd8f14c046837c91106fa56931814395ae2ea6f3a828b54a4ad95869f6cdf93c551059866d6b57104b0a6d9f973b6538899e366838816f7c501b6f7a6e0413d86b1fca71bc58a9a43719ab935758dc2387b0de85d0d07e9eb7c946b7b8eb3003f92c6d8efcd7fec6154ecaae449c6a71282a64f7450a8b1b33f398e661d9ee72f86c473f0ea2aac7ead66438354eee88d54ef3cd8ea4417d0a1f616b13ed90a3f6d66ba5820e2d435323d9e3520e454ce587d6363bcfcef8588380b5b052961d9b4a061475697e569fbdc969dac86d5eee2151edc80be3da45bb879e2de9e0b9b8e5ebab89c0d8546f5b3a1576b3e50f688a157bd251707a69c56645448dbacfcd60bb42f924d1b73b95811b47e88322237c73131c2653dea669b93c41c99a0f7a4f721a1752be9b695cdd74758b45348ccdf5a20b3a34b5a4b24881d1ddb723649f1f298d85682b66dbdf7c7849314e49365e59218ad6f1d94033946c3d7cf814c1eacbb62523cb4e0f6351cad02caaa31f8d9327f52341ec461ac1bd1b4fa1fb340b4bc48c37c9107b838635ee2639a932e45080a7c449b421149f7c838dd6457564fc41278e4be657e82649916e93191d72ae345721d8a146de8a9662b9ce1f1848a57469255a1748a44978514c08ce0bf96d6f20b79e728a32ee71a9256f1809320218d3df2a6d22ae1956dcc5e0f4e8ed933c61d05d5ee9bc44938efe78c5d6065111074e5171f24bd92d375c697c32da2d1e1c597bb3fa66baa950d6521ce5db353aaf8b544207c698882bf21a0760661066f3e186a3932d1481a1c178e448e497e7f30891f6c67667d8d72f4fa9a329002501fee18b4dd0106d5e2b5d7af3adb4738b71d86cd2ec96a1576a6c0cab3da2481e24f65a34d475600581677bc0220f52ecfd734b987f40920281b1683301f08905a8ddecafc13919cbc163951c780b93c0331239a941e96031873c31dbb1379803ebb31569b1a3fb5a90db1d1e7f7b92ff59598e4d5a7bf700eee9ceef0446542818213988239adb6d4e0b4359b9c06e866469070813ebf8f6527ff65efe77a3171d43ead68f581f7b890c7242bd5d4fe53bbd86d56a094c842386a7f5f66498a54ba9ee24ffd0847780175aa6f1c427b35d861fed7c8eed4122020e73069940f5d441c6d0da2da194d0924390ea7e541de93d47853e511c3256ce9e741e25a1d2b536db4924ecc09006fd31951c5dfac5c78ea2d86ae120b61bf6893c1773cb2552cb491dcb6b610bd089536d055d915be096ab1c2dc7c877bba20a9726e0958c10abaade14f7c001a671da6ef7056ff97eb9d1b5983384604909cd41480ea07173131e9a200e67ad442d9f0d7f653a3169529b0a2a286bcb63e5e7a0839c96011c2fd1adc95442465e1436a9f504a728d5b4d8ef5b996da169235930fc298603d65f3e06dd8ce42ee918402ee36e1953233dc5d4b7d941fae462ec9872032241f4a1a133ff6cfb071e9139c43968abab669f7cf6e425f601561f1cf458e735c042a5bbb2dfbb5a78a5cf691e317bc18083be6dd2fa28b7d37d198dfb3b14bb7f2c614ac852bdbf3f2b8a05a0a216403a1b27b5179a884f780450068742f455e8aab981e0e2298f330bbf99ca1b6ee9dce4a5c9f2d9bfd52dee5407d5d61f40683a26c0dbde0f851c15b20ae5710f79530217eee83e100bcd3dcc81debbe2916352e0ab6de9a1e61e2acb2442ca568ae2299819ef63a35051338227741e3a4a73f5a6c604c7c8ea08baa8db88c0dbd5ae99e0086e86c82dc367cc897e480de4521afc34ddf78a526b729d97b2b1cf484f3f43d6f90a804e8af912f25128dfc4496add301c86d222188edf88899559ff0a3f71025ab919db828473982823dad969067a45554b2035da52dfd282cd41b80590930147aed1ad27cfb6b0c9d4a5152da59b3816ff928e78083620e32b5c98022f68f3dd65dbcd473a3683c02dd8a3ff98ae0505ad8cae12820769787893930303c5e919a3bc9f219a21baacb894a77cdbdfb297a29eaa6470704d53a25d683d3c1f65211025dcb320f9d1aa876f0283b6bb409242568147d0c2d92310030c006b16b71f6501b58c818e4dcc20ba72d57f086a22383fd1d2c7c2a03c2a3f0d38ec3bb2526a19ab852226860a60ba7c32b1794d0b9019ab49170d694dd9b6b93180c95f4c20d4bae3bcc2c25b35c01fc68c8cdf5e58701782fe185d966e1a5a7da6960ef8b80add96b0d7008b6cc5a355e818f13e95fd31957466d5a1872edf9cd47efeced6fb246a0d01c0f13c18a9fcf7100d0fb3bf430c97b4a66e66e1e24bb42dd05a41641c2bc5eb25cc65c8c48ef4ef7d0dd3de0f7d602920cc45edda2d637f7e65fcc45a82647dbc17de18b75c51c46178b9666b88815f6e11bfeccce1f3c95dca8efa9476af7fa2907f7f0afcefb8d3dd7a01e1d6240770a779773b093e7ade2611b89713b4dd4f1c4850a2767dd75ce9b080ba6dfa42d76ceb7e0cbceb53b5bba1fd83459e0682132acffd929b73de2d5b5350d88b94ac4c0ed06f7e949d745b9102df24a2b65266c9de4c5fea09d17e984ec5529ed852e5896c60aedd265b45f91ca007654868c1584c5e453e943b7abfb25c1d5b33dc7d163f20405a1023ec306383f00d345c8e899e3e3f1ad1db418eaeba4559d11a38e1f67843f023ff57df9fd53ee9dd00e929d7b31584d4419e1c00b033ab491ad26e79ce7ac2260db8b8b11d66fd6477c81d707bc68e50c1eb2e795465d6c4405d5594f4d7310d35e80d49dccb0878c11bfe26a70d0d7768c175503f1dde1669e7415cf2470cbff44b9dee9a74f2724e4449202831fa76c8efe6643c59ea21db3ece43bb35fa20e6733f455041f241dd461ec18d06faa4ba28c4754866f7f16dd0e2b2bbd8fd9e7d74ea7844dc83b9e7f64d63a7c5f20e5d41d41db95ea4d1c087d6be384a1ec512a2cdd6d0adac61cf50bf083c87c630dbaa0d4f53b419c8b21229d7b1df94e5f5968c18f91de1c45f90d3e50616857ae3dcdc4d7e4d5a569ea1643bf6084a48fd1b63af39123a629f5b406babe4724aaee5e81b734895e78df11bb3c47cfe2194633f843edf09ad9e6b20b3bb1c970f52621561fc214873863b58bacad039459f07a9ee6fadb969201859cd3367807f80e7ecf2f7219356bb46ebb42859056e679e460bfa9e361a5ee3e329af1ccded0bf33d4fd4c4e891ff31da9f891165c6c628c6fbe00edce79eb94db153c69a6c58f20d7035ca349f18e1bb3960ae8be636e6b0b66eef5fb5386b85a360ef67d4397c261ba3f81c1ed5bebaef83226f9f7cc749b452ab7685abd26989b450c192a02447548edaaf5c02a594ce3b1e92413422daeb01cf2907d316832d4edf055fcb5ef4310b1f7faa95fba22e0e3e3dc9834ad3947d3b571f904221496e0c3aa095ab2201decbc075af4fae03fc3f19fc65da67e492c7db2eab9a52e8594db71a1fb403d6643cd6cc942133d9f4d3a108609749e35afe50025c5012545db4f357587fb626517d7b749fd868bf6a09200cfa27e48869632d1fad2f055e3e3d6d8b061a57c9c5780c2f5daa682acd348209a696819a60b19d11d99f8e1864a1cc504150312a3960fab82af738e98ed49a257f08e815ac622721e96cf477f65f387e15f185cb3cd483d1b541936e3d9da05d7d5ba10331f2887dc156125b95f79e20d747014a87c66a440c40e89307b15e07d4fa3c99997ca60ab7a66fcd2dab29c13ea152f889498c34e724dd5db98cd7a242da838cdd652b4b65c35953678999fad9cfce7b48a370cee13039879c4a6af3db5d6b707e939db4ceecf5ff25d0ea0d4c676ebfe808509dbafafb1f2d535ba1dafd010609456caad5ecf51a494a506269888ba7f30f29488ca5bac1e40000000000000000000000000000000b0e141a212a343c + From 2fa371486ca014df2e67790136775cf27744ab86 Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 2 Jun 2025 17:23:20 +0000 Subject: [PATCH 04/24] Fix formatting. --- .../jni/main/cpp/conscrypt/native_crypto.cc | 296 ++++++++++-------- 1 file changed, 163 insertions(+), 133 deletions(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 6ba896c74..7ff253174 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -2661,159 +2661,189 @@ static jint NativeCrypto_MLDSA65_verify(JNIEnv* env, jclass, jbyteArray data, ji return static_cast(result); } -static jbyteArray NativeCrypto_MLDSA87_public_key_from_seed( - JNIEnv* env, jclass, jbyteArray privateKeySeed) { - CHECK_ERROR_QUEUE_ON_RETURN; +static jbyteArray NativeCrypto_MLDSA87_public_key_from_seed(JNIEnv* env, jclass, + jbyteArray privateKeySeed) { + CHECK_ERROR_QUEUE_ON_RETURN; - ScopedByteArrayRO seedArray(env, privateKeySeed); - if (seedArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_MLDSA87_sign => privateKeySeed == null"); - return nullptr; - } - - MLDSA87_private_key privateKey; - if (!MLDSA87_private_key_from_seed( - &privateKey, reinterpret_cast(seedArray.get()), - seedArray.size())) { - JNI_TRACE("MLDSA87_private_key_from_seed failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "MLDSA87_private_key_from_seed"); - return nullptr; - } + ScopedByteArrayRO seedArray(env, privateKeySeed); + if (seedArray.get() == nullptr) { + JNI_TRACE("NativeCrypto_MLDSA87_sign => privateKeySeed == null"); + return nullptr; + } - MLDSA87_public_key publicKey; - if (!MLDSA87_public_from_private(&publicKey, &privateKey)) { - JNI_TRACE("MLDSA87_public_from_private failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "MLDSA87_public_from_private"); - return nullptr; - } - - CBB cbb; - size_t size; - uint8_t public_key_bytes[MLDSA87_SIGNATURE_BYTES]; - if (!CBB_init_fixed(&cbb, public_key_bytes, MLDSA87_PUBLIC_KEY_BYTES) || - !MLDSA87_marshal_public_key(&cbb, &publicKey) || - !CBB_finish(&cbb, nullptr, &size) || size != MLDSA87_PUBLIC_KEY_BYTES) { - JNI_TRACE("Failed to serialize ML-DSA public key."); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "MLDSA87_marshal_public_key"); - return nullptr; - } + MLDSA87_private_key privateKey; + if (!MLDSA87_private_key_from_seed( + &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { + JNI_TRACE("MLDSA87_private_key_from_seed failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_private_key_from_seed"); + return nullptr; + } - ScopedLocalRef publicKeyRef( - env, env->NewByteArray(static_cast(MLDSA87_PUBLIC_KEY_BYTES))); - if (publicKeyRef.get() == nullptr) { - return nullptr; - } + MLDSA87_public_key publicKey; + if (!MLDSA87_public_from_private(&publicKey, &privateKey)) { + JNI_TRACE("MLDSA87_public_from_private failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_public_from_private"); + return nullptr; + } - ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); - if (publicKeyArray.get() == nullptr) { - return nullptr; - } - memcpy(publicKeyArray.get(), public_key_bytes, MLDSA87_PUBLIC_KEY_BYTES); - return publicKeyRef.release(); + CBB cbb; + size_t size; + uint8_t public_key_bytes[MLDSA87_SIGNATURE_BYTES]; + if (!CBB_init_fixed(&cbb, public_key_bytes, MLDSA87_PUBLIC_KEY_BYTES) || + !MLDSA87_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || + size != MLDSA87_PUBLIC_KEY_BYTES) { + JNI_TRACE("Failed to serialize ML-DSA public key."); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_marshal_public_key"); + return nullptr; + } + + ScopedLocalRef publicKeyRef( + env, env->NewByteArray(static_cast(MLDSA87_PUBLIC_KEY_BYTES))); + if (publicKeyRef.get() == nullptr) { + return nullptr; + } + + ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); + if (publicKeyArray.get() == nullptr) { + return nullptr; + } + memcpy(publicKeyArray.get(), public_key_bytes, MLDSA87_PUBLIC_KEY_BYTES); + return publicKeyRef.release(); } -static jbyteArray NativeCrypto_MLDSA87_sign(JNIEnv* env, jclass, - jbyteArray data, jint dataLen, +static jbyteArray NativeCrypto_MLDSA87_sign(JNIEnv* env, jclass, jbyteArray data, jint dataLen, jbyteArray privateKeySeed) { - CHECK_ERROR_QUEUE_ON_RETURN; + CHECK_ERROR_QUEUE_ON_RETURN; - ScopedByteArrayRO seedArray(env, privateKeySeed); - if (seedArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_MLDSA87_sign => privateKeySeed == null"); - return nullptr; - } - - MLDSA87_private_key privateKey; - if (!MLDSA87_private_key_from_seed( - &privateKey, reinterpret_cast(seedArray.get()), - seedArray.size())) { - JNI_TRACE("MLDSA87_private_key_from_seed failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "MLDSA87_private_key_from_seed"); - return nullptr; - } + ScopedByteArrayRO seedArray(env, privateKeySeed); + if (seedArray.get() == nullptr) { + JNI_TRACE("NativeCrypto_MLDSA87_sign => privateKeySeed == null"); + return nullptr; + } - ScopedByteArrayRO dataArray(env, data); - if (dataArray.get() == nullptr) { - return nullptr; - } + MLDSA87_private_key privateKey; + if (!MLDSA87_private_key_from_seed( + &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { + JNI_TRACE("MLDSA87_private_key_from_seed failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_private_key_from_seed"); + return nullptr; + } - if (ARRAY_OFFSET_LENGTH_INVALID(dataArray, 0, dataLen)) { - conscrypt::jniutil::throwException( - env, "java/lang/ArrayIndexOutOfBoundsException", "dataLen"); - return nullptr; - } - - uint8_t result[MLDSA87_SIGNATURE_BYTES]; - if (!MLDSA87_sign(result, &privateKey, - reinterpret_cast(dataArray.get()), - dataLen, /* context */ NULL, /* context_len */ 0)) { - JNI_TRACE("MLDSA87_sign failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_sign"); - return nullptr; - } + ScopedByteArrayRO dataArray(env, data); + if (dataArray.get() == nullptr) { + return nullptr; + } - ScopedLocalRef resultRef( - env, env->NewByteArray(static_cast(MLDSA87_SIGNATURE_BYTES))); - if (resultRef.get() == nullptr) { - return nullptr; - } + if (ARRAY_OFFSET_LENGTH_INVALID(dataArray, 0, dataLen)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "dataLen"); + return nullptr; + } - ScopedByteArrayRW resultArray(env, resultRef.get()); - if (resultArray.get() == nullptr) { - return nullptr; - } - memcpy(resultArray.get(), result, MLDSA87_SIGNATURE_BYTES); - return resultRef.release(); + uint8_t result[MLDSA87_SIGNATURE_BYTES]; + if (!MLDSA87_sign(result, &privateKey, reinterpret_cast(dataArray.get()), + dataLen, /* context */ NULL, /* context_len */ 0)) { + JNI_TRACE("MLDSA87_sign failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_sign"); + return nullptr; + } + + ScopedLocalRef resultRef( + env, env->NewByteArray(static_cast(MLDSA87_SIGNATURE_BYTES))); + if (resultRef.get() == nullptr) { + return nullptr; + } + + ScopedByteArrayRW resultArray(env, resultRef.get()); + if (resultArray.get() == nullptr) { + return nullptr; + } + memcpy(resultArray.get(), result, MLDSA87_SIGNATURE_BYTES); + return resultRef.release(); } -static jint NativeCrypto_MLDSA87_verify(JNIEnv* env, jclass, jbyteArray data, - jint dataLen, jbyteArray sig, - jbyteArray publicKey) { - CHECK_ERROR_QUEUE_ON_RETURN; +static jint NativeCrypto_MLDSA87_verify(JNIEnv* env, jclass, jbyteArray data, jint dataLen, + jbyteArray sig, jbyteArray publicKey) { + CHECK_ERROR_QUEUE_ON_RETURN; - ScopedByteArrayRO publicKeyArray(env, publicKey); - if (publicKeyArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_MLDSA87_verify => publicKey == null"); - return -1; - } - - CBS cbs; - CBS_init(&cbs, reinterpret_cast(publicKeyArray.get()), - publicKeyArray.size()); - MLDSA87_public_key pubkey; - if (!MLDSA87_parse_public_key(&pubkey, &cbs)) { - JNI_TRACE("MLDSA87_parse_public_key failed"); - return -1; - } + ScopedByteArrayRO publicKeyArray(env, publicKey); + if (publicKeyArray.get() == nullptr) { + JNI_TRACE("NativeCrypto_MLDSA87_verify => publicKey == null"); + return -1; + } - ScopedByteArrayRO dataArray(env, data); - if (dataArray.get() == nullptr) { - return -1; - } + CBS cbs; + CBS_init(&cbs, reinterpret_cast(publicKeyArray.get()), publicKeyArray.size()); + MLDSA87_public_key pubkey; + if (!MLDSA87_parse_public_key(&pubkey, &cbs)) { + JNI_TRACE("MLDSA87_parse_public_key failed"); + return -1; + } - if (ARRAY_OFFSET_LENGTH_INVALID(dataArray, 0, dataLen)) { - conscrypt::jniutil::throwException( - env, "java/lang/ArrayIndexOutOfBoundsException", "dataLen"); - return -1; - } + ScopedByteArrayRO dataArray(env, data); + if (dataArray.get() == nullptr) { + return -1; + } - ScopedByteArrayRO sigArray(env, sig); - if (sigArray.get() == nullptr) { - return -1; - } + if (ARRAY_OFFSET_LENGTH_INVALID(dataArray, 0, dataLen)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "dataLen"); + return -1; + } + + ScopedByteArrayRO sigArray(env, sig); + if (sigArray.get() == nullptr) { + return -1; + } + + int result = + MLDSA87_verify(&pubkey, reinterpret_cast(sigArray.get()), + sigArray.size(), reinterpret_cast(dataArray.get()), + dataLen, /*context=*/NULL, /*context_len=*/0); + + JNI_TRACE("MLDSA87_verify(%p, %p, %p, %d) => %d", publicKey, sig, data, dataLen, result); + return static_cast(result); +} + +static void NativeCrypto_SLHDSA_SHA2_128S_generate_key(JNIEnv* env, jclass, + jbyteArray outPublicArray, + jbyteArray outPrivateArray) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("SLHDSA_SHA2_128S_generate_key(%p, %p)", outPublicArray, outPrivateArray); + + ScopedByteArrayRW outPublic(env, outPublicArray); + if (outPublic.get() == nullptr) { + JNI_TRACE( + "SLHDSA_SHA2_128S_generate_key(%p, %p) can't get output public key " + "buffer", + outPublicArray, outPrivateArray); + return; + } + + ScopedByteArrayRW outPrivate(env, outPrivateArray); + if (outPrivate.get() == nullptr) { + JNI_TRACE( + "SLHDSA_SHA2_128S_generate_key(%p, %p) can't get output private key " + "buffer", + outPublicArray, outPrivateArray); + return; + } + + if (outPublic.size() != SLHDSA_SHA2_128S_PUBLIC_KEY_BYTES) { + conscrypt::jniutil::throwIllegalArgumentException(env, + "Output public key array length != 32"); + return; + } - int result = MLDSA87_verify( - &pubkey, reinterpret_cast(sigArray.get()), - sigArray.size(), reinterpret_cast(dataArray.get()), - dataLen, /*context=*/NULL, /*context_len=*/0); + if (outPrivate.size() != SLHDSA_SHA2_128S_PRIVATE_KEY_BYTES) { + conscrypt::jniutil::throwIllegalArgumentException(env, + "Output private key array length != 64"); + return; + } - JNI_TRACE("MLDSA87_verify(%p, %p, %p, %d) => %d", publicKey, sig, data, - dataLen, result); - return static_cast(result); + SLHDSA_SHA2_128S_generate_key(reinterpret_cast(outPublic.get()), + reinterpret_cast(outPrivate.get())); + JNI_TRACE("SLHDSA_SHA2_128S_generate_key(%p, %p) => success", outPublicArray, outPrivateArray); } static void NativeCrypto_SLHDSA_SHA2_128S_generate_key(JNIEnv* env, jclass, From 6ec1445b56ab0cd70e8c829da9eac4e433612b40 Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 2 Jun 2025 17:26:29 +0000 Subject: [PATCH 05/24] Fix formating. --- openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java index b9d82a4bd..b0f85b9ce 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -3388,8 +3388,10 @@ public void test_mldsa65_works() throws Exception { @Test public void test_mldsa87_works() throws Exception { - byte[] privateKeySeed = decodeHex("7C9935A0B07694AA0C6D10E4DB6B1ADD2FD81A25CCB148032DCD739936737F2D"); - byte[] data = decodeHex("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8"); + byte[] privateKeySeed = + decodeHex("7C9935A0B07694AA0C6D10E4DB6B1ADD2FD81A25CCB148032DCD739936737F2D"); + byte[] data = + decodeHex("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8"); byte[] publicKey = NativeCrypto.MLDSA87_public_key_from_seed(privateKeySeed); assertEquals(2592, publicKey.length); @@ -3450,7 +3452,7 @@ public void test_mldsa87_works() throws Exception { byte[] publicKeyTooLong = Arrays.copyOf(publicKey, publicKey.length + 1); assertEquals( -1, NativeCrypto.MLDSA87_verify(data, data.length, signature, publicKeyTooLong)); - } + } @Test public void test_slhdsa_sha2_128s_works() throws Exception { From a0231229069f4b2685c61e7f75b919da70884e26 Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 2 Jun 2025 18:00:54 +0000 Subject: [PATCH 06/24] Fix ML-DSA-65 calls. --- .../src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java b/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java index 3410a3462..f7a57bfe0 100644 --- a/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java +++ b/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java @@ -136,7 +136,7 @@ protected byte[] engineSign() throws SignatureException { buffer.reset(); switch (privateKey.getMlDsaAlgorithm()) { case ML_DSA_65: - return NativeCrypto.MLDSA65_sign(data, privateKey.getSeed()); + return NativeCrypto.MLDSA65_sign(data, data.length, privateKey.getSeed()); case ML_DSA_87: return NativeCrypto.MLDSA87_sign(data, data.length, privateKey.getSeed()); } @@ -153,7 +153,8 @@ protected boolean engineVerify(byte[] sigBytes) throws SignatureException { buffer.reset(); switch (publicKey.getMlDsaAlgorithm()) { case ML_DSA_65: - int result = NativeCrypto.MLDSA65_verify(data, sigBytes, publicKey.getRaw()); + int result = NativeCrypto.MLDSA65_verify( + data, data.length, sigBytes, publicKey.getRaw()); return result == 1; case ML_DSA_87: int result2 = NativeCrypto.MLDSA87_verify( From aef6a2a5b7eb652510ad8087868b50a7345a344b Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 2 Jun 2025 18:10:40 +0000 Subject: [PATCH 07/24] Use ExposedByteArrayOutputStream. --- .../org/conscrypt/OpenSslSignatureMlDsa.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java b/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java index f7a57bfe0..31c2baf3f 100644 --- a/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java +++ b/common/src/main/java/org/conscrypt/OpenSslSignatureMlDsa.java @@ -38,7 +38,7 @@ public abstract class OpenSslSignatureMlDsa extends SignatureSpi { /** * Buffer to hold value to be signed or verified. */ - private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + private ExposedByteArrayOutputStream buffer = new ExposedByteArrayOutputStream(); abstract boolean supportsAlgorithm(MlDsaAlgorithm algorithm); @@ -132,13 +132,18 @@ protected byte[] engineSign() throws SignatureException { // This can't actually happen, but you never know... throw new SignatureException("No privateKey provided"); } - byte[] data = buffer.toByteArray(); - buffer.reset(); + byte[] sig; switch (privateKey.getMlDsaAlgorithm()) { case ML_DSA_65: - return NativeCrypto.MLDSA65_sign(data, data.length, privateKey.getSeed()); + sig = NativeCrypto.MLDSA65_sign( + buffer.array(), buffer.size(), privateKey.getSeed()); + buffer.reset(); + return sig; case ML_DSA_87: - return NativeCrypto.MLDSA87_sign(data, data.length, privateKey.getSeed()); + sig = NativeCrypto.MLDSA87_sign( + buffer.array(), buffer.size(), privateKey.getSeed()); + buffer.reset(); + return sig; } throw new SignatureException("Unsupported algorithm: " + privateKey.getMlDsaAlgorithm()); } @@ -149,17 +154,18 @@ protected boolean engineVerify(byte[] sigBytes) throws SignatureException { // This can't actually happen, but you never know... throw new SignatureException("No publicKey provided"); } - byte[] data = buffer.toByteArray(); - buffer.reset(); + int result; switch (publicKey.getMlDsaAlgorithm()) { case ML_DSA_65: - int result = NativeCrypto.MLDSA65_verify( - data, data.length, sigBytes, publicKey.getRaw()); + result = NativeCrypto.MLDSA65_verify( + buffer.array(), buffer.size(), sigBytes, publicKey.getRaw()); + buffer.reset(); return result == 1; case ML_DSA_87: - int result2 = NativeCrypto.MLDSA87_verify( - data, data.length, sigBytes, publicKey.getRaw()); - return result2 == 1; + result = NativeCrypto.MLDSA87_verify( + buffer.array(), buffer.size(), sigBytes, publicKey.getRaw()); + buffer.reset(); + return result == 1; } throw new SignatureException("Unsupported algorithm: " + publicKey.getMlDsaAlgorithm()); } From 34c12db8c22fe80575de1534b9be5e054443580f Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 2 Jun 2025 19:25:09 +0000 Subject: [PATCH 08/24] Fix copy-paste error. --- .../jni/main/cpp/conscrypt/native_crypto.cc | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 7ff253174..81757a8d0 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -2846,47 +2846,6 @@ static void NativeCrypto_SLHDSA_SHA2_128S_generate_key(JNIEnv* env, jclass, JNI_TRACE("SLHDSA_SHA2_128S_generate_key(%p, %p) => success", outPublicArray, outPrivateArray); } -static void NativeCrypto_SLHDSA_SHA2_128S_generate_key(JNIEnv* env, jclass, - jbyteArray outPublicArray, - jbyteArray outPrivateArray) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("SLHDSA_SHA2_128S_generate_key(%p, %p)", outPublicArray, outPrivateArray); - - ScopedByteArrayRW outPublic(env, outPublicArray); - if (outPublic.get() == nullptr) { - JNI_TRACE( - "SLHDSA_SHA2_128S_generate_key(%p, %p) can't get output public key " - "buffer", - outPublicArray, outPrivateArray); - return; - } - - ScopedByteArrayRW outPrivate(env, outPrivateArray); - if (outPrivate.get() == nullptr) { - JNI_TRACE( - "SLHDSA_SHA2_128S_generate_key(%p, %p) can't get output private key " - "buffer", - outPublicArray, outPrivateArray); - return; - } - - if (outPublic.size() != SLHDSA_SHA2_128S_PUBLIC_KEY_BYTES) { - conscrypt::jniutil::throwIllegalArgumentException(env, - "Output public key array length != 32"); - return; - } - - if (outPrivate.size() != SLHDSA_SHA2_128S_PRIVATE_KEY_BYTES) { - conscrypt::jniutil::throwIllegalArgumentException(env, - "Output private key array length != 64"); - return; - } - - SLHDSA_SHA2_128S_generate_key(reinterpret_cast(outPublic.get()), - reinterpret_cast(outPrivate.get())); - JNI_TRACE("SLHDSA_SHA2_128S_generate_key(%p, %p) => success", outPublicArray, outPrivateArray); -} - static jbyteArray NativeCrypto_SLHDSA_SHA2_128S_sign(JNIEnv* env, jclass, jbyteArray data, jint dataLen, jbyteArray privateKey) { CHECK_ERROR_QUEUE_ON_RETURN; From 3c9f0770bb14be32295345352e28ac8825410a79 Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 2 Jun 2025 19:54:22 +0000 Subject: [PATCH 09/24] Fix KeyPairGeneratorTest. --- .../conscrypt/java/security/KeyPairGeneratorTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java index 518e98aa0..3ce2116f3 100644 --- a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java +++ b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java @@ -151,6 +151,9 @@ private static List getKeySizes(String algorithm) throws Exception { putKeySize("XDH", 255); putKeySize("EdDSA", 255); putKeySize("ML-DSA", -1); + putKeySize("ML-DSA-44", -1); + putKeySize("ML-DSA-65", -1); + putKeySize("ML-DSA-87", -1); putKeySize("SLH-DSA-SHA2-128S", -1); } @@ -234,6 +237,12 @@ private void test_Key(KeyPairGenerator kpg, Key k) throws Exception { if (StandardNames.IS_RI && expectedAlgorithm.equals("DIFFIEHELLMAN")) { expectedAlgorithm = "DH"; } + if (expectedAlgorithm.startsWith("ML-DSA")) { + // In OpenJDK, KeyPairGenerator.getInstance("ML-DSA-65") returns a + // KeyPairGenerator with algorithm "ML-DSA-65". But the key it generates + // have algorithm "ML-DSA". + expectedAlgorithm = "ML-DSA"; + } assertEquals(expectedAlgorithm, k.getAlgorithm().toUpperCase(Locale.ROOT)); if (expectedAlgorithm.equals("DH")) { if (k instanceof DHPublicKey) { From ca7402b57dbaf9588c30ee085f3235f9b1732ba3 Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 2 Jun 2025 19:57:05 +0000 Subject: [PATCH 10/24] Fix SignatureTest. --- .../test/java/org/conscrypt/java/security/SignatureTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/src/test/java/org/conscrypt/java/security/SignatureTest.java b/common/src/test/java/org/conscrypt/java/security/SignatureTest.java index b932ddb10..fa83122c8 100644 --- a/common/src/test/java/org/conscrypt/java/security/SignatureTest.java +++ b/common/src/test/java/org/conscrypt/java/security/SignatureTest.java @@ -98,6 +98,9 @@ public void test_getInstance() throws Exception { .skipAlgorithm("EdDSA") // ML-DSA is skipped because it doesn't yet support getFormat() and getEncoded(). .skipAlgorithm("ML-DSA") + .skipAlgorithm("ML-DSA-44") + .skipAlgorithm("ML-DSA-65") + .skipAlgorithm("ML-DSA-87") // SLH-DSA-SHA2-128S is skipped because it doesn't yet support getFormat() and // getEncoded(). .skipAlgorithm("SLH-DSA-SHA2-128S") @@ -147,7 +150,7 @@ private KeyPair keyPair(String sigAlgorithm) throws Exception { kpAlgorithm = "RSA"; } else if (sigAlgorithmUpperCase.equals("ED25519")) { kpAlgorithm = "ED25519"; - } else if (sigAlgorithmUpperCase.equals("ML-DSA")) { + } else if (sigAlgorithmUpperCase.startsWith("ML-DSA")) { kpAlgorithm = "ML-DSA"; } else { throw new Exception("Unknown KeyPair algorithm for Signature algorithm " From e48bf07d9d9cd5c9bc4d91b46c2eb28901ccede3 Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 2 Jun 2025 19:59:00 +0000 Subject: [PATCH 11/24] Fix formatting. --- .../org/conscrypt/java/security/KeyPairGeneratorTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java index 3ce2116f3..db4672180 100644 --- a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java +++ b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java @@ -238,10 +238,10 @@ private void test_Key(KeyPairGenerator kpg, Key k) throws Exception { expectedAlgorithm = "DH"; } if (expectedAlgorithm.startsWith("ML-DSA")) { - // In OpenJDK, KeyPairGenerator.getInstance("ML-DSA-65") returns a - // KeyPairGenerator with algorithm "ML-DSA-65". But the key it generates - // have algorithm "ML-DSA". - expectedAlgorithm = "ML-DSA"; + // In OpenJDK, KeyPairGenerator.getInstance("ML-DSA-65") returns a + // KeyPairGenerator with algorithm "ML-DSA-65". But the key it generates + // have algorithm "ML-DSA"s. + expectedAlgorithm = "ML-DSA"; } assertEquals(expectedAlgorithm, k.getAlgorithm().toUpperCase(Locale.ROOT)); if (expectedAlgorithm.equals("DH")) { From 91e3e4c0d25ad401338369539e48b47417470e78 Mon Sep 17 00:00:00 2001 From: juergw Date: Thu, 5 Jun 2025 15:58:44 +0000 Subject: [PATCH 12/24] Add missing license. --- .../main/java/org/conscrypt/MlDsaAlgorithm.java | 16 ++++++++++++++++ .../java/security/KeyPairGeneratorTest.java | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/org/conscrypt/MlDsaAlgorithm.java b/common/src/main/java/org/conscrypt/MlDsaAlgorithm.java index be8a017e6..b44d8db96 100644 --- a/common/src/main/java/org/conscrypt/MlDsaAlgorithm.java +++ b/common/src/main/java/org/conscrypt/MlDsaAlgorithm.java @@ -1,3 +1,19 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.conscrypt; /** ML-DSA algorithm. */ diff --git a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java index db4672180..c628d339f 100644 --- a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java +++ b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java @@ -240,7 +240,7 @@ private void test_Key(KeyPairGenerator kpg, Key k) throws Exception { if (expectedAlgorithm.startsWith("ML-DSA")) { // In OpenJDK, KeyPairGenerator.getInstance("ML-DSA-65") returns a // KeyPairGenerator with algorithm "ML-DSA-65". But the key it generates - // have algorithm "ML-DSA"s. + // have algorithm "ML-DSA". expectedAlgorithm = "ML-DSA"; } assertEquals(expectedAlgorithm, k.getAlgorithm().toUpperCase(Locale.ROOT)); From cfaee198734e063157d6897b010f73059a3b7c29 Mon Sep 17 00:00:00 2001 From: juergw Date: Tue, 10 Jun 2025 14:44:11 +0000 Subject: [PATCH 13/24] Throw an exception when MLDSA87_parse_public_key fails. And also do this for MLDSA65. --- .../src/jni/main/cpp/conscrypt/native_crypto.cc | 4 ++++ .../java/org/conscrypt/NativeCryptoTest.java | 16 ++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 81757a8d0..d6f1c5824 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -2633,6 +2633,8 @@ static jint NativeCrypto_MLDSA65_verify(JNIEnv* env, jclass, jbyteArray data, ji MLDSA65_public_key pubkey; if (!MLDSA65_parse_public_key(&pubkey, &cbs)) { JNI_TRACE("MLDSA65_parse_public_key failed"); + conscrypt::jniutil::throwIllegalArgumentException(env, + "MLDSA87_parse_public_key failed"); return -1; } @@ -2777,6 +2779,8 @@ static jint NativeCrypto_MLDSA87_verify(JNIEnv* env, jclass, jbyteArray data, ji MLDSA87_public_key pubkey; if (!MLDSA87_parse_public_key(&pubkey, &cbs)) { JNI_TRACE("MLDSA87_parse_public_key failed"); + conscrypt::jniutil::throwIllegalArgumentException(env, + "MLDSA87_parse_public_key failed"); return -1; } diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java index b0f85b9ce..9816cb9b3 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -3378,12 +3378,12 @@ public void test_mldsa65_works() throws Exception { () -> NativeCrypto.MLDSA65_sign(data, data.length, privateKeySeedTooLong)); byte[] publicKeyTooShort = Arrays.copyOf(publicKey, publicKey.length - 1); - assertEquals( - -1, NativeCrypto.MLDSA65_verify(data, data.length, signature, publicKeyTooShort)); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLDSA65_verify(data, data.length, signature, publicKeyTooShort)); byte[] publicKeyTooLong = Arrays.copyOf(publicKey, publicKey.length + 1); - assertEquals( - -1, NativeCrypto.MLDSA65_verify(data, data.length, signature, publicKeyTooLong)); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLDSA65_verify(data, data.length, signature, publicKeyTooLong)); } @Test @@ -3446,12 +3446,12 @@ public void test_mldsa87_works() throws Exception { () -> NativeCrypto.MLDSA87_sign(data, data.length, privateKeySeedTooLong)); byte[] publicKeyTooShort = Arrays.copyOf(publicKey, publicKey.length - 1); - assertEquals( - -1, NativeCrypto.MLDSA87_verify(data, data.length, signature, publicKeyTooShort)); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLDSA87_verify(data, data.length, signature, publicKeyTooShort)); byte[] publicKeyTooLong = Arrays.copyOf(publicKey, publicKey.length + 1); - assertEquals( - -1, NativeCrypto.MLDSA87_verify(data, data.length, signature, publicKeyTooLong)); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLDSA87_verify(data, data.length, signature, publicKeyTooLong)); } @Test From a2cecfa3f2370880f47a2b064491140022b185e4 Mon Sep 17 00:00:00 2001 From: juergw Date: Tue, 10 Jun 2025 15:17:16 +0000 Subject: [PATCH 14/24] Add JNI_TRACE call when MLDSA_sign couldn't allocate output. --- common/src/jni/main/cpp/conscrypt/native_crypto.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index d6f1c5824..81787197d 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -2607,6 +2607,7 @@ static jbyteArray NativeCrypto_MLDSA65_sign(JNIEnv* env, jclass, jbyteArray data ScopedLocalRef resultRef( env, env->NewByteArray(static_cast(MLDSA65_SIGNATURE_BYTES))); if (resultRef.get() == nullptr) { + JNI_TRACE("NativeCrypto_MLDSA65_sign: byte array creation failed"); return nullptr; } @@ -2633,8 +2634,7 @@ static jint NativeCrypto_MLDSA65_verify(JNIEnv* env, jclass, jbyteArray data, ji MLDSA65_public_key pubkey; if (!MLDSA65_parse_public_key(&pubkey, &cbs)) { JNI_TRACE("MLDSA65_parse_public_key failed"); - conscrypt::jniutil::throwIllegalArgumentException(env, - "MLDSA87_parse_public_key failed"); + conscrypt::jniutil::throwIllegalArgumentException(env, "MLDSA65_parse_public_key failed"); return -1; } @@ -2753,6 +2753,7 @@ static jbyteArray NativeCrypto_MLDSA87_sign(JNIEnv* env, jclass, jbyteArray data ScopedLocalRef resultRef( env, env->NewByteArray(static_cast(MLDSA87_SIGNATURE_BYTES))); if (resultRef.get() == nullptr) { + JNI_TRACE("NativeCrypto_MLDSA87_sign: byte array creation failed"); return nullptr; } @@ -2779,8 +2780,7 @@ static jint NativeCrypto_MLDSA87_verify(JNIEnv* env, jclass, jbyteArray data, ji MLDSA87_public_key pubkey; if (!MLDSA87_parse_public_key(&pubkey, &cbs)) { JNI_TRACE("MLDSA87_parse_public_key failed"); - conscrypt::jniutil::throwIllegalArgumentException(env, - "MLDSA87_parse_public_key failed"); + conscrypt::jniutil::throwIllegalArgumentException(env, "MLDSA87_parse_public_key failed"); return -1; } From 53d44ba4ca26259d6f5d9e787a24b9d80c888dc0 Mon Sep 17 00:00:00 2001 From: juergw Date: Tue, 10 Jun 2025 15:42:39 +0000 Subject: [PATCH 15/24] Fix whitespace. --- common/src/jni/main/cpp/conscrypt/native_crypto.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 3035322a6..6d2bf10b0 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -2609,7 +2609,7 @@ static jbyteArray NativeCrypto_MLDSA65_sign(JNIEnv* env, jclass, jbyteArray data ScopedLocalRef resultRef( env, env->NewByteArray(static_cast(MLDSA65_SIGNATURE_BYTES))); if (resultRef.get() == nullptr) { - JNI_TRACE("NativeCrypto_MLDSA65_sign: byte array creation failed"); + JNI_TRACE("NativeCrypto_MLDSA65_sign: byte array creation failed"); return nullptr; } @@ -2755,7 +2755,7 @@ static jbyteArray NativeCrypto_MLDSA87_sign(JNIEnv* env, jclass, jbyteArray data ScopedLocalRef resultRef( env, env->NewByteArray(static_cast(MLDSA87_SIGNATURE_BYTES))); if (resultRef.get() == nullptr) { - JNI_TRACE("NativeCrypto_MLDSA87_sign: byte array creation failed"); + JNI_TRACE("NativeCrypto_MLDSA87_sign: byte array creation failed"); return nullptr; } From 73124189657448f559213d3220c4846729f8675c Mon Sep 17 00:00:00 2001 From: juergw Date: Tue, 17 Jun 2025 13:45:01 +0000 Subject: [PATCH 16/24] Avoid some memcopy. --- .../jni/main/cpp/conscrypt/native_crypto.cc | 86 ++++++++++--------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 6d2bf10b0..b266f1a44 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -2544,17 +2544,6 @@ static jbyteArray NativeCrypto_MLDSA65_public_key_from_seed(JNIEnv* env, jclass, return nullptr; } - CBB cbb; - size_t size; - uint8_t public_key_bytes[MLDSA65_PUBLIC_KEY_BYTES]; - if (!CBB_init_fixed(&cbb, public_key_bytes, MLDSA65_PUBLIC_KEY_BYTES) || - !MLDSA65_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || - size != MLDSA65_PUBLIC_KEY_BYTES) { - JNI_TRACE("Failed to serialize ML-DSA public key."); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_marshal_public_key"); - return nullptr; - } - ScopedLocalRef publicKeyRef( env, env->NewByteArray(static_cast(MLDSA65_PUBLIC_KEY_BYTES))); if (publicKeyRef.get() == nullptr) { @@ -2565,7 +2554,19 @@ static jbyteArray NativeCrypto_MLDSA65_public_key_from_seed(JNIEnv* env, jclass, if (publicKeyArray.get() == nullptr) { return nullptr; } - memcpy(publicKeyArray.get(), public_key_bytes, MLDSA65_PUBLIC_KEY_BYTES); + + CBB cbb; + size_t size; + uint8_t public_key_bytes[MLDSA65_PUBLIC_KEY_BYTES]; + if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), + MLDSA65_PUBLIC_KEY_BYTES) || + !MLDSA65_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || + size != MLDSA65_PUBLIC_KEY_BYTES) { + JNI_TRACE("Failed to serialize ML-DSA public key."); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_marshal_public_key"); + return nullptr; + } + return publicKeyRef.release(); } @@ -2598,14 +2599,6 @@ static jbyteArray NativeCrypto_MLDSA65_sign(JNIEnv* env, jclass, jbyteArray data return nullptr; } - uint8_t result[MLDSA65_SIGNATURE_BYTES]; - if (!MLDSA65_sign(result, &privateKey, reinterpret_cast(dataArray.get()), - dataLen, /* context */ NULL, /* context_len */ 0)) { - JNI_TRACE("MLDSA65_sign failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_sign"); - return nullptr; - } - ScopedLocalRef resultRef( env, env->NewByteArray(static_cast(MLDSA65_SIGNATURE_BYTES))); if (resultRef.get() == nullptr) { @@ -2617,7 +2610,16 @@ static jbyteArray NativeCrypto_MLDSA65_sign(JNIEnv* env, jclass, jbyteArray data if (resultArray.get() == nullptr) { return nullptr; } - memcpy(resultArray.get(), result, MLDSA65_SIGNATURE_BYTES); + + if (!MLDSA65_sign(reinterpret_cast(resultArray.get()), + &privateKey, + reinterpret_cast(dataArray.get()), + dataLen, /* context */ NULL, /* context_len */ 0)) { + JNI_TRACE("MLDSA65_sign failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_sign"); + return nullptr; + } + return resultRef.release(); } @@ -2690,17 +2692,6 @@ static jbyteArray NativeCrypto_MLDSA87_public_key_from_seed(JNIEnv* env, jclass, return nullptr; } - CBB cbb; - size_t size; - uint8_t public_key_bytes[MLDSA87_SIGNATURE_BYTES]; - if (!CBB_init_fixed(&cbb, public_key_bytes, MLDSA87_PUBLIC_KEY_BYTES) || - !MLDSA87_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || - size != MLDSA87_PUBLIC_KEY_BYTES) { - JNI_TRACE("Failed to serialize ML-DSA public key."); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_marshal_public_key"); - return nullptr; - } - ScopedLocalRef publicKeyRef( env, env->NewByteArray(static_cast(MLDSA87_PUBLIC_KEY_BYTES))); if (publicKeyRef.get() == nullptr) { @@ -2711,7 +2702,17 @@ static jbyteArray NativeCrypto_MLDSA87_public_key_from_seed(JNIEnv* env, jclass, if (publicKeyArray.get() == nullptr) { return nullptr; } - memcpy(publicKeyArray.get(), public_key_bytes, MLDSA87_PUBLIC_KEY_BYTES); + + CBB cbb; + size_t size; + if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), + MLDSA87_PUBLIC_KEY_BYTES) || + !MLDSA87_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || + size != MLDSA87_PUBLIC_KEY_BYTES) { + JNI_TRACE("Failed to serialize ML-DSA public key."); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_marshal_public_key"); + return nullptr; + } return publicKeyRef.release(); } @@ -2744,14 +2745,6 @@ static jbyteArray NativeCrypto_MLDSA87_sign(JNIEnv* env, jclass, jbyteArray data return nullptr; } - uint8_t result[MLDSA87_SIGNATURE_BYTES]; - if (!MLDSA87_sign(result, &privateKey, reinterpret_cast(dataArray.get()), - dataLen, /* context */ NULL, /* context_len */ 0)) { - JNI_TRACE("MLDSA87_sign failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_sign"); - return nullptr; - } - ScopedLocalRef resultRef( env, env->NewByteArray(static_cast(MLDSA87_SIGNATURE_BYTES))); if (resultRef.get() == nullptr) { @@ -2763,7 +2756,16 @@ static jbyteArray NativeCrypto_MLDSA87_sign(JNIEnv* env, jclass, jbyteArray data if (resultArray.get() == nullptr) { return nullptr; } - memcpy(resultArray.get(), result, MLDSA87_SIGNATURE_BYTES); + + if (!MLDSA87_sign(reinterpret_cast(resultArray.get()), + &privateKey, + reinterpret_cast(dataArray.get()), + dataLen, /* context */ NULL, /* context_len */ 0)) { + JNI_TRACE("MLDSA87_sign failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_sign"); + return nullptr; + } + return resultRef.release(); } From a292bdf5d8b116368db483b27ccbcde4696a8b1e Mon Sep 17 00:00:00 2001 From: juergw Date: Tue, 17 Jun 2025 13:50:21 +0000 Subject: [PATCH 17/24] Fix formatting, and one error message. --- common/src/jni/main/cpp/conscrypt/native_crypto.cc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index b266f1a44..315d065f3 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -2611,8 +2611,7 @@ static jbyteArray NativeCrypto_MLDSA65_sign(JNIEnv* env, jclass, jbyteArray data return nullptr; } - if (!MLDSA65_sign(reinterpret_cast(resultArray.get()), - &privateKey, + if (!MLDSA65_sign(reinterpret_cast(resultArray.get()), &privateKey, reinterpret_cast(dataArray.get()), dataLen, /* context */ NULL, /* context_len */ 0)) { JNI_TRACE("MLDSA65_sign failed"); @@ -2673,7 +2672,7 @@ static jbyteArray NativeCrypto_MLDSA87_public_key_from_seed(JNIEnv* env, jclass, ScopedByteArrayRO seedArray(env, privateKeySeed); if (seedArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_MLDSA87_sign => privateKeySeed == null"); + JNI_TRACE("NativeCrypto_MLDSA87_public_key_from_seed => privateKeySeed == null"); return nullptr; } @@ -2757,8 +2756,7 @@ static jbyteArray NativeCrypto_MLDSA87_sign(JNIEnv* env, jclass, jbyteArray data return nullptr; } - if (!MLDSA87_sign(reinterpret_cast(resultArray.get()), - &privateKey, + if (!MLDSA87_sign(reinterpret_cast(resultArray.get()), &privateKey, reinterpret_cast(dataArray.get()), dataLen, /* context */ NULL, /* context_len */ 0)) { JNI_TRACE("MLDSA87_sign failed"); From 2015f8ddc26a579c7780dae64d6597252f0a82e7 Mon Sep 17 00:00:00 2001 From: juergw Date: Tue, 17 Jun 2025 13:55:14 +0000 Subject: [PATCH 18/24] Change the serialVersionUID of the private and the public key. Because we need to add a new field to these keys, it would be cumbersome to keep the serialization exactly the same. And because the serialization changes, we need to change the serialVersionUID. --- common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java | 1 + common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java index c0d7e3e31..91b60444b 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java @@ -24,6 +24,7 @@ /** An OpenSSL ML-DSA private key. */ public class OpenSslMlDsaPrivateKey implements PrivateKey { + private static final long serialVersionUID = 2L; private byte[] seed; private final MlDsaAlgorithm algorithm; diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java index 865cedb1a..44bd96186 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java @@ -24,7 +24,7 @@ /** An OpenSSL ML-DSA public key. */ public class OpenSslMlDsaPublicKey implements PublicKey { - private static final long serialVersionUID = 453861992373478445L; + private static final long serialVersionUID = 2L; private final byte[] raw; private final MlDsaAlgorithm algorithm; From d3567a3d6a6d425d1170ed42502e2b38844bdf1c Mon Sep 17 00:00:00 2001 From: juergw Date: Tue, 17 Jun 2025 14:06:36 +0000 Subject: [PATCH 19/24] Fix formatting. --- common/src/jni/main/cpp/conscrypt/native_crypto.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 315d065f3..59dbe2b64 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -2612,8 +2612,8 @@ static jbyteArray NativeCrypto_MLDSA65_sign(JNIEnv* env, jclass, jbyteArray data } if (!MLDSA65_sign(reinterpret_cast(resultArray.get()), &privateKey, - reinterpret_cast(dataArray.get()), - dataLen, /* context */ NULL, /* context_len */ 0)) { + reinterpret_cast(dataArray.get()), dataLen, + /* context */ NULL, /* context_len */ 0)) { JNI_TRACE("MLDSA65_sign failed"); conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_sign"); return nullptr; @@ -2757,8 +2757,8 @@ static jbyteArray NativeCrypto_MLDSA87_sign(JNIEnv* env, jclass, jbyteArray data } if (!MLDSA87_sign(reinterpret_cast(resultArray.get()), &privateKey, - reinterpret_cast(dataArray.get()), - dataLen, /* context */ NULL, /* context_len */ 0)) { + reinterpret_cast(dataArray.get()), dataLen, + /* context */ NULL, /* context_len */ 0)) { JNI_TRACE("MLDSA87_sign failed"); conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_sign"); return nullptr; From 0b364864004b418a8551f0b30010965348c12b3f Mon Sep 17 00:00:00 2001 From: juergw Date: Tue, 17 Jun 2025 14:17:45 +0000 Subject: [PATCH 20/24] Remove unused variable. --- common/src/jni/main/cpp/conscrypt/native_crypto.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 59dbe2b64..faae9bf9b 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -2557,7 +2557,6 @@ static jbyteArray NativeCrypto_MLDSA65_public_key_from_seed(JNIEnv* env, jclass, CBB cbb; size_t size; - uint8_t public_key_bytes[MLDSA65_PUBLIC_KEY_BYTES]; if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), MLDSA65_PUBLIC_KEY_BYTES) || !MLDSA65_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || From 596895a72ce4160d7bd8c12831285531b3d86257 Mon Sep 17 00:00:00 2001 From: juergw Date: Tue, 17 Jun 2025 14:41:52 +0000 Subject: [PATCH 21/24] Add test vector for ML-DSA. --- common/src/test/java/org/conscrypt/MlDsaTest.java | 4 ++-- common/src/test/resources/crypto/mldsa.txt | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/common/src/test/java/org/conscrypt/MlDsaTest.java b/common/src/test/java/org/conscrypt/MlDsaTest.java index c7d8d29e1..80bbf3e1c 100644 --- a/common/src/test/java/org/conscrypt/MlDsaTest.java +++ b/common/src/test/java/org/conscrypt/MlDsaTest.java @@ -249,8 +249,8 @@ public void testVectors() throws Exception { byte[] message = vector.getBytes("message"); byte[] signature = vector.getBytes("signature"); - if (!algorithm.equals("ML-DSA-65") && !algorithm.equals("ML-DSA-87")) { - assertTrue(errMsg + ", algorithm must be ML-DSA-65 or ML-DSA-87", false); + if (!algorithm.startsWith("ML-DSA")) { + assertTrue(errMsg + ", algorithm must start with ML-DSA", false); } KeyFactory keyFactory = KeyFactory.getInstance(algorithm, conscryptProvider); diff --git a/common/src/test/resources/crypto/mldsa.txt b/common/src/test/resources/crypto/mldsa.txt index cfa7059b0..d72479b89 100644 --- a/common/src/test/resources/crypto/mldsa.txt +++ b/common/src/test/resources/crypto/mldsa.txt @@ -16,6 +16,13 @@ public_key = 1483236FC9F943D98417809E95405384530ED83E151E8465D34E4638F1F8D7058D6 message = D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8 signature = BD0D51DB2F225AC6D3DA8F0C2439B0BCDA26EFF7EFA67CFD3C2B98EFA08477A74088DC638126865E493697B6FE360FF9C55B304D15A7474C983C3D8A4E1AB28FF9925CC9073AD986D4B53C28B4CC909DC36B9334CC4510AFFDEA9548620923ED2158224AC5CA8FEF19228DBBBF12956F5422176E8A474AFBE6EC6551F1FFDE71E86C48B39BE6CA540DBD78B985E89A2F7576325E79DCF801585D30DCB3F971C827F4489745D450DF7AE34496C42C7A8778AAC7FDDB9740CD3F07A8AFAD1C1471FB9591BBCF37BEAEA10C465ADB4BD7303ED6CA41AD4848CE8A5659F7E3D4894AB0E79A0E7206C9FE278AC9CF1F6A3DA6B9FA8E03AFEEE717739CBFEB5C26EF3B1C9130C8DD46F9C8E8149DA9B0FE5AA8FD03600F87824A6F2EE8BBCA0EF6D8C38EC526E982100BB8A8974EA91129BF827FE4CCA13D7203D38AC51B2A14025948E5AC0F71394EB804C885521EE65EEA303CE30D0FA9626A914F36246A8F55EB2D866B215FC191CB734CC6B4724C8C1562F81E3678D39097871249B86833C6981FF45CEC71339E1C6F38ED1D04B6C70C21642D268B5E058F8095101C2339EE5619280F2553308DBCFEF74537DD02722E42608FFCA2E8EA8B8A2FECF46948C952D003071792845A07DBCFCC483B594CA9E0A69664498835DA427761E19F9FDF29E5319AA0FBAA7150DE0B1F951D9CC0E1B62DFB0857DB7C2129A896D65DCE0ECD3A87FABCC2A4A6FA5811CF6312DC9E3ABFD5ACC116A8A25F45AD3736FDB541276732DCD997B1B687BDAC9827A4582B8D3F0877595830E2079DCE9104E1FCFEFD0F8225BA9739C30CA7671A05688B55BCA1F9ED968E6F3F2831E3D54E596707BF63FD6AA809FE410EC38A17E3F8DE2E050A9E6B81CC386CC229041A7BE15FFC912FC4066A4D2D7FB98AF7022840E593C4E599D0309F37B65B85F10541683300779FA41124B19D4032CF8D7AF5726D3A08331D7A712DA910903C0A381F616CE5B1085F779486172EA4D7B127692557DD156B63B0E445ED8888E446397542E50C9BFE7B728E31388F7743D0F51151D4B4CB7642431ED0BAEAE264F4B2D9BAC2D5618338EE092228A251A4F99D4F95D263CAE16FB9A45A51D45BEF0F6CAD30547AB4BAA1C6F28E6FF35B195D938514F58FC2B47BEB8C895D213F11035E5FAEF85C917D7AA551FDF8D316CC4DE5A159CD4F39E3C118673984147C82BB41089CF0D9B6712E899A99CBA5DE33BF33E2C0DA03745031A48A37F7E6A7288790839461F2C58BB5ED93477834B572DCE2DD00DD31B866C2387076037053872D8CF8EB57AE81FDD84823DC69FE0A33F599846620AB74E86912759E245332EECFEFAAB9726F8A59256200BE72BC47DC3E0A4E28868842935D216334191F32E0630920D8DB05EE62813218A1E1FC5DE96719D08A00FE7D5072C8D51B3ED0AB0F9D5B45BBC2D5DD2CC7E6ECCB080D617565119C4B2A4E408A0B18EC969DCDB2BB7D8DE2EEEF3A76A0A5E437C6681AE7A00D54868E0F51EE39616AA29FEB7ABF4A3E17865003B781497BA572EDE6EA7A9479FD15C295B79C0384D4D8451043C6F67F2E10D8442F0C4E72684D6576FD41BC3756B1A8834082144760C7F609B3665C03F001073CCFEC1EB18FB9A61D82A8462D0A86FF80520053C55F2D79502F95EEE9B50F1B95179BEAB6EB1ADC4F582A9CA12C31E6F165E064AA9F289DD2A5E12F45E71C98CBC87DBF218926250D1A78DFD2B46B1DB4844AC63C5A6960F67A6BF0B270337E629AC04BA47883E52C33246863EB9F54BF2DFA5905F057490FE14F993D81EAC50E0D16DD0EB2098D0D1170FBF30892A7BFB45F6C6B7E349865CF4313D1572CA41A06C0D5561B0704AF4BCD4CBFF4045C5F76A9A760751F7B1432F8049CC9C0496F3E80026E2078CDC7BF54132C84200A4C27B23AAF69E97B25D8CBADA6F5C82748D73F8CEE44980B909EB0C11EB49FCEA972552BF5BE540DD9467EC81D70990562DC558C00CFF68DB80F3D2BBE61D7E154A2D5A4166E86546D8A82886E1CFA28CE2D8BF57D67D9B6CE32D451F9B2B4D73474C299C64FDD8D2AE15EAFC3F88179B8B364FE16B51E7B6C4DB47D796E159546BD409DD72879234578875C7940E057FB9508DDD9754D130F5CC3E32D82104DBCE1BA883FBC0C9AB9072A1A2771B0EA1152682D182D537EEEABE3F79C531A26E236AEF6479D5A7817D00723D0183E4A1A671C3285BAE7793D7FF982A6B90F7D38E40F763EDC401F2BD0618D3E305257CFADD3CCFED8DD3FD03CDBB533976FA353ABE73503EF8360964C2CA78888B4E67B0EEA68D35E64A840D136A7F0CA41CBBC52543BE45CA846F0213EEA90D932AB3A6902795B0B4FAC28C838224309E94782FA315BFBB9A535F3763FA9C3C95FFA3FFDA9C486678F7905A3637605A6929F234B9B04BDC729E14581888848930DF0D77FB1DB65D75F292E0EC78FFF3352ECF99D87E0B6FFC78F5B9CB423FCCE606D74D35D115A418EEEAE012026691B82D5B0262A1DD137ABF192683173A5615A3298A2224280C405EEE6094ADD0E1ACEE74204BC0F8170221621A71743084A072FDF03293D8FD7778E8E3282DC49A1A950404CE827C281E1F57E9DFA1F1156726DFCA3560F5C909987D6D79E831166155D5AAEE8F1ED382863195ED48EA6924D7A119EA99756434092F08E217804EB4943E56A42CC7AC5CDFA7CACE562FAC86AAF3BB5C3CF6F6DC35036B388E9EC8BE2272C2D6CA425FF23E6EF7878332042B120246271B93F87C463434921D0BF6A105A2C7E473B3C5E4BC5828403C130005B2EEDB7C161010A7A782AF3EA91700A7610DDA532DAC61DCA768B51541D2F6213B9C5047CA2AC0E1DDA275EFB58359B5AE203706BBCB1B2DB3ED8896C3721B51865A6F9B4B8949FAB4F3301AE7CBDC540F0B04FD6E27BE48748DA228DAE22353DA7CA1C464E70FB78960491279E827128BEF241C764061A5AD103EE62B26AE08066C5F20B807883C8E8A3144B7968F232627440154FED536DCC09DC9E33BB7BCDAED850F0435E1B9D943F79640BA06F21F99A1D89997BC5529D1E69095DE36958B8F186C12007DAF19115B0F971DFACB126280E1C4B956C458F9AD2EDF2226A696685A3DEACE620DBAD643B4B2E31911F53BBCC1E712B83DE8687D4956EBE1A30CF4D7E86DBE8B6E28DD6AF59BF6E83E25D9B67458ABE922181C4BFA5E5D047A7799D8F117411DA633096CE2ABF19C5317C545835B06A54759497605A0265A0396C4F069F7AAF9E677140679A265893780B0F4ACA2E48010346CDA16356E6D69F48FBD6E9763E1EAF576008BD2EDCCA2DF8808989D801F687EFC97EBD1C0FAA8555664BDD49E39B38565480D7DE0BB51E1CC5341DBF12DA73B5AA7DF954B5569272A7A3EA3AD45D8F65F718007A0C35AE3C7206E14AE7033E4DCE999F232BBB488AEFF090A1D160B10847B134FA82867114C4EFB7CC83DF601108E61457F7242FB159B0840D7711C0C50DEDBDDF346BFBA7C7EFCA4068B35B93FF81054115AE59DE3C55BBA020AD66893B88AE491F8F6BD45BDB0D506D15E050B26BDD0242F0EEC3092830E3F35D59A4B94B7A41A993F44DF9199EE6B084681D554AFD3970DD410E748F4A95F3F5A3B2827F1C587B563FF7F0D7C47AF3B9F72B8AD6A46C2CB178929F80C1852AD8247769BD4FEE274A0A07B20137CA67674E91779D9C6424F06E78A8BAC807C31CBB4677E9CC7D8755997BD19DBF053F1EB7DD6DC3875E667088B0501FDDBAB90C6A4C215E28B17DB87B0F4423C6108813AC993F69CD20953E0C6B85E308F20F1855F5993FB269159F2EE5D87316A0B744CD6530BFAF581C7FBAFD20689B702BDD4F907CD9D5ED768FAB06CD625B171D7159112E2446F8B6B2FD3B89F43D6C42B5120CFC98AE2762D241C41D32DFF80F7147119FBA9900689E1919EAD74C77F27C046B513FE143884A439F1E8399CF97C7E83F3BA585C5A0117251EFB5AFF33974D5B0FDBD61B62CA5692983643788AC31010E70E6909BE8757F6BD2E721BAC6790F8DCA7D1AFCDA291F1DA1669E8906F4880E0E1BDC2608A0DF671BA401C178A53AA6E1B2D6C90D2769E4230B60E9FF10EE38A1532090B3D5076D1D320697F4AC06FC8574136373FDF90D6872190E26F5311BAF686A95F47EF7A31F8A6AAF0196D3CCED25D5A549FE618D02F3C531FECF1C6770BE5B43FFC299519B7AA701BED350A09AF45B9268D8D5D81E8B962303C1F8E4BF15F5DE14A85312EB1C9511DF3E687CA14081754A2958324B4E5BAC035C91240F01D7719DAAE546ED56885F1F393DF95690C20618AAE3229C6488AF7820C3E8B421957CCF4F31A5173B7282FB972F7981AE53F73F2AE5747B608FB05F01888E80C1C6CA031D52E573FBCDF986471D038EE3C6E0814E24E8DF75BDBAE63F2909B47D9401107439A6B022C897763194687110D50779A9ACA6231B04D587A87CAADE5E4E91B7BCF43B2E469F52DBF19AB1D180F477D5DF2E45ED2609638E22E4F5143BB0E733F16AD183153C8460E9D0A821C9AE4AD7DB358B18E91A9022A26283F553D722F4D37B3B9EA7E5F684A1395C72EAF26150960A318B8901630E1A657479A2B1F7181A1C215678F3626BB7E2FD0F36498497A20F2D3C467E803F697DA800000000000000000000000000000000000000080F141A2024 +name = Same as Test case 1. Tests that ML-DSA implements the same as ML-DSA-65. +algorithm = ML-DSA +seed = 7C9935A0B07694AA0C6D10E4DB6B1ADD2FD81A25CCB148032DCD739936737F2D +public_key = 1483236FC9F943D98417809E95405384530ED83E151E8465D34E4638F1F8D7058D62E19AB806490883A823176D4DC8A3C10C9960D0E948A9F7B62CA8E118DE5D7A05BB18E8018B6CACB4FE7885490599939D90D004BD480B116F5D6627B6C4C1B2A1496CC3525EF9F19953EC63CDD6EBDB21D65B27C644194916AAD07CC559B08CFC1282D25D7276C9E5062E0B1C4CF111C0A9DCC49BF40F5ED3C27CB4E78E39C1F068736A788E2ED4A02E9EF23EACE802CD295B6EB97D533091B3293D9BAD2938DFDECF2C4F9F6387B38A7FD22738A010B85949688650B6F063B6BC6350A1E84C869FB3BBCDC4BF6C0D0674D7C07F7AE78E4BBB302B6DB8488B5F9164E5E264682E45E71B58FC19ADF5EA892439EB352AFDDB63D22177AEF17261909E3F87BCC7E1B1A58CD5DE8F8A886A12D7137CE5BFBD2C53ECEBFD1B9F2298583D767E0DB5178B952F4D069D66FDEDCA1FBDCF8720AAAA5313C0500ECF95B9B70E7E3D58DD2B57433D3A0637DF36E964B21F44F791B3AF9074D6DBC9A2FC041D9E22D5E387C4081E6D4CCE6AB11FC8B4F2C718EB2A19924E3F17EA1F44D0084B5D5296A97A3624E4E1F6CA05229F2888557AAB577FD72F8DC328F0E4F45DD13A191920F671ACE3BC29DC3195E951D0F5EEAA095A3D5F20E4E4EA1AC157261C1C514AEB6940E63053AD68383F14E923602E6B241E9813246B47F009DB446FBF61246BAD7ED386647D020A854CCA39ECAE5FA6D667CB6D433F02BC2FAB9F37096F3C127741EC02A46C81022E070AE1DF54623DF44C5C744EDD0D3BC66581B8E1348E75B5C52D0E41BC71EDAD5B12DDA2280724B7D704BFF2AF04505F65AE496DA86701D36BC9AFB0B199442A9C5C743D97880E89C8CCB34C51890602627924316E79D4415CC1C2ED490A7A6EBB4B507181CFF18BB53A6B8F816C15A2EA8667CE59EDBE8F42376001E31981310CA403E08328AA97828DC3A86C260819BC8DF72A3E29657CA65B7763A54067958CCD6FD73DF789B306A37185C8117F0C86CF9D1C48D102ECA8343F41F86F6084E2E72E6952357D7DC076A02A7CEF64724AE634E35712E291A24704D2939717246371B42C11A672FE8FD31DA83FC3D5DE650FB2136A13A0D6229A115EA3758E3AD0810A99944275FA8FECFD2BF1D130B40473F4ABF886485A1E36290DB437B331DB303539F98D298183509D934F1A747AF29BC36BD7CA79E5D40D098EBFE61F400620B5B1AFB81327342AADEC634F1A77DAE793D55A252D391AD155A6150AB049CBA0270F07936AC21575BE6FAD53A0DC23F462E377F2C882391BAC1C17C11D18A677C3EFFACC4C6A920596F8654BB4955750BCBC18744375656F0B594D825872BB161A1B7FDFE7D01E7A19E02F41AB9D02D1FED47161716172B8D68DB04E57C74053DAC785E9245BCC8DCA48C736457EDEB8A075C1C42254E87110CBE4A909421AE6AECECE5D65834739BE6CAC51D1023CA25C322B7B3461EC65168CCCF483A2668FB4527BCB312564C4097224DBC38AB397C3A7FD693B29992B9A773C43C0E9E94479F1762C91C367D9A079B13FDC38BD74F209E4D543ABF8C9B14CED015599DFAE94723361ACBF6C1C0434DC0EFAF22C61057775F17F36D76FD75D6BFCE7DCE922DCD7585AA33CAE7A6916C4E4AC5F86E4753F8CC798C20205C8C47656FBAD7799B6A53DAE5DCB74CDB677FFFA66CBF2873A219413714578D6DA3B61AA29C494C2F084BE1FA1C1CC40D1E4A424A4CEC73E455062B6E28C333839570D6FC6C08402A8D39F145B97C3AACC6F24702E80F66F5D2FA1530CFF2A07486B3D38D8C9994EE633C2E527AF49FBE26F634C6663CF95520E04A76F33E8876826B88887C4FE8FDEB1C50F55C7E7FBC2A5077FA029DB53B7CD8FA3576BBC219AE7D7B21518FD94FA187D39D63187BF9F2BF2592F1A7A35628137D82E50477FF3406DABFE558A3FD30D4E72D1F523EBF51DF6C7BFD9C85325897A7949113F30C9570F3A9FBAF73658430C3B2AFA43BF9D37D5410B5E416C5CF375CF9ADDCECF560E7D636C2D58B89D3E5A446201990EFFC467FFBA1009EE90D0F46BD2D7018AE92CABECF62130BD7B4A077AF31882A713C73572387533EA249C9A18F0599C06EE216CFC60F7498B2A75F3F8143D90A4ABF8651DEFAD600FD332AB09E3D8FAEFA2EC9152EAF6F2BE6B78629022C0231849BE4C13FA08B827EC301150FA380663F737418C8BF0700F4327F58C2256F8BA8B61176DFD1ACE6A81C19033E3D678A9CB234F85A5B6372EAF1A1883F5ACED3ADF58B7FABFE44D986DBEDA351EA9DE5A841CD523336F986AB8FBBECF1F52B1E87DBB3AC457A743FAE899A5BB3D10EAFC4D0808B7FA98C8068093CAE7A0BC2074BAA701273734C28E97CD1102FFBCEBB83EBB17C9200BE6DBE58BC87C522E4D24254204FD2EC52C60C1225649C3DEE17012C1CC0D5CDA0B2F0FC4F27274E04ACEDE68BACE92E294B589BE45D74C5377AFEAC7182F4B702B5A50B49F1B32BD476483957C664676A819FE6851F07768DA82261C75D53F8F04A64291A56E008B11AE09EE73923257EC195020D958F7B6D43ABA268978CB33B150A9C0DECAFBB36291257512CC7F2CB0B5564A0F81EF4686838CDBFE10475520E6EF69047CCA864E50C86E9D91FC4EAE741D4BE8AD7B12952B76C3429548169C370A7A5E2DB3FC809B9930952EF5AF9CDCCAF74FC13D0DB8D55862858E47E4C6F66FDA9DA423B884DB6ED79D012587F757F0BD974680AD8E +message = D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8 +signature = BD0D51DB2F225AC6D3DA8F0C2439B0BCDA26EFF7EFA67CFD3C2B98EFA08477A74088DC638126865E493697B6FE360FF9C55B304D15A7474C983C3D8A4E1AB28FF9925CC9073AD986D4B53C28B4CC909DC36B9334CC4510AFFDEA9548620923ED2158224AC5CA8FEF19228DBBBF12956F5422176E8A474AFBE6EC6551F1FFDE71E86C48B39BE6CA540DBD78B985E89A2F7576325E79DCF801585D30DCB3F971C827F4489745D450DF7AE34496C42C7A8778AAC7FDDB9740CD3F07A8AFAD1C1471FB9591BBCF37BEAEA10C465ADB4BD7303ED6CA41AD4848CE8A5659F7E3D4894AB0E79A0E7206C9FE278AC9CF1F6A3DA6B9FA8E03AFEEE717739CBFEB5C26EF3B1C9130C8DD46F9C8E8149DA9B0FE5AA8FD03600F87824A6F2EE8BBCA0EF6D8C38EC526E982100BB8A8974EA91129BF827FE4CCA13D7203D38AC51B2A14025948E5AC0F71394EB804C885521EE65EEA303CE30D0FA9626A914F36246A8F55EB2D866B215FC191CB734CC6B4724C8C1562F81E3678D39097871249B86833C6981FF45CEC71339E1C6F38ED1D04B6C70C21642D268B5E058F8095101C2339EE5619280F2553308DBCFEF74537DD02722E42608FFCA2E8EA8B8A2FECF46948C952D003071792845A07DBCFCC483B594CA9E0A69664498835DA427761E19F9FDF29E5319AA0FBAA7150DE0B1F951D9CC0E1B62DFB0857DB7C2129A896D65DCE0ECD3A87FABCC2A4A6FA5811CF6312DC9E3ABFD5ACC116A8A25F45AD3736FDB541276732DCD997B1B687BDAC9827A4582B8D3F0877595830E2079DCE9104E1FCFEFD0F8225BA9739C30CA7671A05688B55BCA1F9ED968E6F3F2831E3D54E596707BF63FD6AA809FE410EC38A17E3F8DE2E050A9E6B81CC386CC229041A7BE15FFC912FC4066A4D2D7FB98AF7022840E593C4E599D0309F37B65B85F10541683300779FA41124B19D4032CF8D7AF5726D3A08331D7A712DA910903C0A381F616CE5B1085F779486172EA4D7B127692557DD156B63B0E445ED8888E446397542E50C9BFE7B728E31388F7743D0F51151D4B4CB7642431ED0BAEAE264F4B2D9BAC2D5618338EE092228A251A4F99D4F95D263CAE16FB9A45A51D45BEF0F6CAD30547AB4BAA1C6F28E6FF35B195D938514F58FC2B47BEB8C895D213F11035E5FAEF85C917D7AA551FDF8D316CC4DE5A159CD4F39E3C118673984147C82BB41089CF0D9B6712E899A99CBA5DE33BF33E2C0DA03745031A48A37F7E6A7288790839461F2C58BB5ED93477834B572DCE2DD00DD31B866C2387076037053872D8CF8EB57AE81FDD84823DC69FE0A33F599846620AB74E86912759E245332EECFEFAAB9726F8A59256200BE72BC47DC3E0A4E28868842935D216334191F32E0630920D8DB05EE62813218A1E1FC5DE96719D08A00FE7D5072C8D51B3ED0AB0F9D5B45BBC2D5DD2CC7E6ECCB080D617565119C4B2A4E408A0B18EC969DCDB2BB7D8DE2EEEF3A76A0A5E437C6681AE7A00D54868E0F51EE39616AA29FEB7ABF4A3E17865003B781497BA572EDE6EA7A9479FD15C295B79C0384D4D8451043C6F67F2E10D8442F0C4E72684D6576FD41BC3756B1A8834082144760C7F609B3665C03F001073CCFEC1EB18FB9A61D82A8462D0A86FF80520053C55F2D79502F95EEE9B50F1B95179BEAB6EB1ADC4F582A9CA12C31E6F165E064AA9F289DD2A5E12F45E71C98CBC87DBF218926250D1A78DFD2B46B1DB4844AC63C5A6960F67A6BF0B270337E629AC04BA47883E52C33246863EB9F54BF2DFA5905F057490FE14F993D81EAC50E0D16DD0EB2098D0D1170FBF30892A7BFB45F6C6B7E349865CF4313D1572CA41A06C0D5561B0704AF4BCD4CBFF4045C5F76A9A760751F7B1432F8049CC9C0496F3E80026E2078CDC7BF54132C84200A4C27B23AAF69E97B25D8CBADA6F5C82748D73F8CEE44980B909EB0C11EB49FCEA972552BF5BE540DD9467EC81D70990562DC558C00CFF68DB80F3D2BBE61D7E154A2D5A4166E86546D8A82886E1CFA28CE2D8BF57D67D9B6CE32D451F9B2B4D73474C299C64FDD8D2AE15EAFC3F88179B8B364FE16B51E7B6C4DB47D796E159546BD409DD72879234578875C7940E057FB9508DDD9754D130F5CC3E32D82104DBCE1BA883FBC0C9AB9072A1A2771B0EA1152682D182D537EEEABE3F79C531A26E236AEF6479D5A7817D00723D0183E4A1A671C3285BAE7793D7FF982A6B90F7D38E40F763EDC401F2BD0618D3E305257CFADD3CCFED8DD3FD03CDBB533976FA353ABE73503EF8360964C2CA78888B4E67B0EEA68D35E64A840D136A7F0CA41CBBC52543BE45CA846F0213EEA90D932AB3A6902795B0B4FAC28C838224309E94782FA315BFBB9A535F3763FA9C3C95FFA3FFDA9C486678F7905A3637605A6929F234B9B04BDC729E14581888848930DF0D77FB1DB65D75F292E0EC78FFF3352ECF99D87E0B6FFC78F5B9CB423FCCE606D74D35D115A418EEEAE012026691B82D5B0262A1DD137ABF192683173A5615A3298A2224280C405EEE6094ADD0E1ACEE74204BC0F8170221621A71743084A072FDF03293D8FD7778E8E3282DC49A1A950404CE827C281E1F57E9DFA1F1156726DFCA3560F5C909987D6D79E831166155D5AAEE8F1ED382863195ED48EA6924D7A119EA99756434092F08E217804EB4943E56A42CC7AC5CDFA7CACE562FAC86AAF3BB5C3CF6F6DC35036B388E9EC8BE2272C2D6CA425FF23E6EF7878332042B120246271B93F87C463434921D0BF6A105A2C7E473B3C5E4BC5828403C130005B2EEDB7C161010A7A782AF3EA91700A7610DDA532DAC61DCA768B51541D2F6213B9C5047CA2AC0E1DDA275EFB58359B5AE203706BBCB1B2DB3ED8896C3721B51865A6F9B4B8949FAB4F3301AE7CBDC540F0B04FD6E27BE48748DA228DAE22353DA7CA1C464E70FB78960491279E827128BEF241C764061A5AD103EE62B26AE08066C5F20B807883C8E8A3144B7968F232627440154FED536DCC09DC9E33BB7BCDAED850F0435E1B9D943F79640BA06F21F99A1D89997BC5529D1E69095DE36958B8F186C12007DAF19115B0F971DFACB126280E1C4B956C458F9AD2EDF2226A696685A3DEACE620DBAD643B4B2E31911F53BBCC1E712B83DE8687D4956EBE1A30CF4D7E86DBE8B6E28DD6AF59BF6E83E25D9B67458ABE922181C4BFA5E5D047A7799D8F117411DA633096CE2ABF19C5317C545835B06A54759497605A0265A0396C4F069F7AAF9E677140679A265893780B0F4ACA2E48010346CDA16356E6D69F48FBD6E9763E1EAF576008BD2EDCCA2DF8808989D801F687EFC97EBD1C0FAA8555664BDD49E39B38565480D7DE0BB51E1CC5341DBF12DA73B5AA7DF954B5569272A7A3EA3AD45D8F65F718007A0C35AE3C7206E14AE7033E4DCE999F232BBB488AEFF090A1D160B10847B134FA82867114C4EFB7CC83DF601108E61457F7242FB159B0840D7711C0C50DEDBDDF346BFBA7C7EFCA4068B35B93FF81054115AE59DE3C55BBA020AD66893B88AE491F8F6BD45BDB0D506D15E050B26BDD0242F0EEC3092830E3F35D59A4B94B7A41A993F44DF9199EE6B084681D554AFD3970DD410E748F4A95F3F5A3B2827F1C587B563FF7F0D7C47AF3B9F72B8AD6A46C2CB178929F80C1852AD8247769BD4FEE274A0A07B20137CA67674E91779D9C6424F06E78A8BAC807C31CBB4677E9CC7D8755997BD19DBF053F1EB7DD6DC3875E667088B0501FDDBAB90C6A4C215E28B17DB87B0F4423C6108813AC993F69CD20953E0C6B85E308F20F1855F5993FB269159F2EE5D87316A0B744CD6530BFAF581C7FBAFD20689B702BDD4F907CD9D5ED768FAB06CD625B171D7159112E2446F8B6B2FD3B89F43D6C42B5120CFC98AE2762D241C41D32DFF80F7147119FBA9900689E1919EAD74C77F27C046B513FE143884A439F1E8399CF97C7E83F3BA585C5A0117251EFB5AFF33974D5B0FDBD61B62CA5692983643788AC31010E70E6909BE8757F6BD2E721BAC6790F8DCA7D1AFCDA291F1DA1669E8906F4880E0E1BDC2608A0DF671BA401C178A53AA6E1B2D6C90D2769E4230B60E9FF10EE38A1532090B3D5076D1D320697F4AC06FC8574136373FDF90D6872190E26F5311BAF686A95F47EF7A31F8A6AAF0196D3CCED25D5A549FE618D02F3C531FECF1C6770BE5B43FFC299519B7AA701BED350A09AF45B9268D8D5D81E8B962303C1F8E4BF15F5DE14A85312EB1C9511DF3E687CA14081754A2958324B4E5BAC035C91240F01D7719DAAE546ED56885F1F393DF95690C20618AAE3229C6488AF7820C3E8B421957CCF4F31A5173B7282FB972F7981AE53F73F2AE5747B608FB05F01888E80C1C6CA031D52E573FBCDF986471D038EE3C6E0814E24E8DF75BDBAE63F2909B47D9401107439A6B022C897763194687110D50779A9ACA6231B04D587A87CAADE5E4E91B7BCF43B2E469F52DBF19AB1D180F477D5DF2E45ED2609638E22E4F5143BB0E733F16AD183153C8460E9D0A821C9AE4AD7DB358B18E91A9022A26283F553D722F4D37B3B9EA7E5F684A1395C72EAF26150960A318B8901630E1A657479A2B1F7181A1C215678F3626BB7E2FD0F36498497A20F2D3C467E803F697DA800000000000000000000000000000000000000080F141A2024 + name = Test case 2 algorithm = ML-DSA-87 seed = 000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E03 From 865981c20a152a6b4598e8e37b792af6eb00f615 Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 23 Jun 2025 11:49:50 +0000 Subject: [PATCH 22/24] Keep serialization of ML-DSA private and public key compatible. --- .../org/conscrypt/OpenSslMlDsaPrivateKey.java | 82 +++++-- .../org/conscrypt/OpenSslMlDsaPublicKey.java | 45 ++-- .../test/java/org/conscrypt/MlDsaTest.java | 200 ++++++++++++++++++ 3 files changed, 301 insertions(+), 26 deletions(-) diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java index 91b60444b..cd08b2dde 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java @@ -16,6 +16,8 @@ package org.conscrypt; +import java.io.IOException; +import java.io.ObjectInputStream; import java.security.PrivateKey; import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; @@ -24,33 +26,75 @@ /** An OpenSSL ML-DSA private key. */ public class OpenSslMlDsaPrivateKey implements PrivateKey { - private static final long serialVersionUID = 2L; + private static final long serialVersionUID = 0x3bacc385e8e106a3L; + + // To preserve the serialization format, "seed" is the only variable that gets + // serialized. To be able to distinguish between ML-DSA-65 and ML-DSA-87, we add + // and additional byte to the end of the seed if the algorithm is ML-DSA-87. So: + // - for ML-DSA-65, "seed" has length 32 and is equal to the seed. + // - for ML-DSA-87, "seed" has length 33, where the first 32 bytes are the seed, and + // the last byte has value 87 = 0x57. private byte[] seed; - private final MlDsaAlgorithm algorithm; + private transient MlDsaAlgorithm algorithm; - private boolean isValidSeed(byte[] seed) { - return seed.length == 32; + private static boolean isValid(byte[] encodedSeed, MlDsaAlgorithm algorithm) { + if (algorithm == MlDsaAlgorithm.ML_DSA_65) { + return encodedSeed.length == 32; + } + if (algorithm == MlDsaAlgorithm.ML_DSA_87) { + return encodedSeed.length == 33 && encodedSeed[32] == 87; + } + return false; + } + + private static MlDsaAlgorithm getAlgorithmFromEncodedSeed(byte[] encodedSeed) { + if (encodedSeed.length == 32) { + return MlDsaAlgorithm.ML_DSA_65; + } + if (encodedSeed.length == 33 && encodedSeed[32] == 87) { + return MlDsaAlgorithm.ML_DSA_87; + } + throw new IllegalArgumentException("Invalid encoded seed"); + } + + private static byte[] encodeSeed(byte[] unencodedSeed, MlDsaAlgorithm algorithm) { + if (unencodedSeed.length != 32) { + throw new IllegalArgumentException("Invalid seed"); + } + if (algorithm == MlDsaAlgorithm.ML_DSA_65) { + return unencodedSeed.clone(); + } else { + // add the suffix 87 to the end of the seed. + byte[] encodedSeed = Arrays.copyOf(unencodedSeed, 33); + encodedSeed[32] = 87; + return encodedSeed; + } } public OpenSslMlDsaPrivateKey(EncodedKeySpec keySpec, MlDsaAlgorithm algorithm) throws InvalidKeySpecException { - byte[] encoded = keySpec.getEncoded(); + byte[] rawKey = keySpec.getEncoded(); if (!"raw".equalsIgnoreCase(keySpec.getFormat())) { throw new InvalidKeySpecException("Encoding must be in raw format"); } - if (!isValidSeed(encoded)) { - throw new InvalidKeySpecException("Invalid seed"); + if (rawKey.length != 32) { + throw new InvalidKeySpecException("Invalid key"); + } + byte[] encodedSeed = encodeSeed(rawKey, algorithm); + if (!isValid(encodedSeed, algorithm)) { + throw new IllegalArgumentException("Invalid key"); } - this.seed = encoded; this.algorithm = algorithm; + this.seed = encodedSeed; } public OpenSslMlDsaPrivateKey(byte[] seed, MlDsaAlgorithm algorithm) { - if (!isValidSeed(seed)) { - throw new IllegalArgumentException("Invalid seed"); + byte[] encodedSeed = encodeSeed(seed, algorithm); + if (!isValid(encodedSeed, algorithm)) { + throw new IllegalArgumentException("Invalid key"); } - this.seed = seed.clone(); this.algorithm = algorithm; + this.seed = encodedSeed; } @Override @@ -76,7 +120,8 @@ byte[] getSeed() { if (seed == null) { throw new IllegalStateException("key is destroyed"); } - return seed.clone(); + // The unencoded seed is always the first 32 bytes of the encoded seed. + return Arrays.copyOf(seed, 32); } @Override @@ -101,11 +146,20 @@ public boolean equals(Object o) { return false; } OpenSslMlDsaPrivateKey that = (OpenSslMlDsaPrivateKey) o; - return algorithm.equals(that.algorithm) && Arrays.equals(seed, that.seed); + // algorithm is encoded in the seed, so we only need to compare the seed. + return Arrays.equals(seed, that.seed); } @Override public int hashCode() { - return Objects.hash(Arrays.hashCode(seed), algorithm); + return Arrays.hashCode(seed); + } + + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); // reads "seed" + this.algorithm = getAlgorithmFromEncodedSeed(this.seed); + if (!isValid(this.seed, this.algorithm)) { + throw new IOException("Invalid key"); + } } } diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java index 44bd96186..cf5d252ca 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java @@ -16,21 +16,32 @@ package org.conscrypt; +import java.io.IOException; +import java.io.ObjectInputStream; import java.security.PublicKey; import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; -import java.util.Objects; /** An OpenSSL ML-DSA public key. */ public class OpenSslMlDsaPublicKey implements PublicKey { - private static final long serialVersionUID = 2L; + private static final long serialVersionUID = 453861992373478445L; - private final byte[] raw; - private final MlDsaAlgorithm algorithm; + private byte[] raw; + private transient MlDsaAlgorithm algorithm; - private boolean isValidRawKey(byte[] rawKey, MlDsaAlgorithm algorithm) { - return rawKey.length == algorithm.publicKeySize(); + private static boolean isValid(byte[] raw, MlDsaAlgorithm algorithm) { + return raw.length == algorithm.publicKeySize(); + } + + private static MlDsaAlgorithm getAlgorithmFromRaw(byte[] raw) { + if (raw.length == MlDsaAlgorithm.ML_DSA_65.publicKeySize()) { + return MlDsaAlgorithm.ML_DSA_65; + } + if (raw.length == MlDsaAlgorithm.ML_DSA_87.publicKeySize()) { + return MlDsaAlgorithm.ML_DSA_87; + } + throw new IllegalArgumentException("Invalid raw key of length " + raw.length); } public OpenSslMlDsaPublicKey(EncodedKeySpec keySpec, MlDsaAlgorithm algorithm) @@ -39,16 +50,16 @@ public OpenSslMlDsaPublicKey(EncodedKeySpec keySpec, MlDsaAlgorithm algorithm) if (!"raw".equalsIgnoreCase(keySpec.getFormat())) { throw new InvalidKeySpecException("Encoding must be in raw format"); } - if (!isValidRawKey(encoded, algorithm)) { - throw new InvalidKeySpecException("Invalid raw key"); + if (!isValid(encoded, algorithm)) { + throw new InvalidKeySpecException("Invalid key of length " + encoded.length); } this.raw = encoded; this.algorithm = algorithm; } public OpenSslMlDsaPublicKey(byte[] raw, MlDsaAlgorithm algorithm) { - if (!isValidRawKey(raw, algorithm)) { - throw new IllegalArgumentException("Invalid raw key"); + if (!isValid(raw, algorithm)) { + throw new IllegalArgumentException("Invalid key of length " + raw.length); } this.raw = raw.clone(); this.algorithm = algorithm; @@ -94,7 +105,9 @@ public boolean equals(Object o) { } OpenSslMlDsaPublicKey that = (OpenSslMlDsaPublicKey) o; - return algorithm.equals(that.algorithm) && Arrays.equals(raw, that.raw); + // different algorithms have different raw key lengths, so we only need to compare the raw + // key. + return Arrays.equals(raw, that.raw); } @Override @@ -102,6 +115,14 @@ public int hashCode() { if (raw == null) { throw new IllegalStateException("key is destroyed"); } - return Objects.hash(Arrays.hashCode(raw), algorithm); + return Arrays.hashCode(raw); + } + + private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { + stream.defaultReadObject(); // reads "this.raw" + this.algorithm = getAlgorithmFromRaw(this.raw); + if (!isValid(this.raw, this.algorithm)) { + throw new IOException("Invalid key"); + } } } diff --git a/common/src/test/java/org/conscrypt/MlDsaTest.java b/common/src/test/java/org/conscrypt/MlDsaTest.java index 80bbf3e1c..1d85c5d00 100644 --- a/common/src/test/java/org/conscrypt/MlDsaTest.java +++ b/common/src/test/java/org/conscrypt/MlDsaTest.java @@ -274,4 +274,204 @@ public void testVectors() throws Exception { verifier.verify(signature)); } } + + @Test + public void serializeAndDeserialize_65_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA-65", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(16384); + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(keyPair.getPrivate()); + oos.writeObject(keyPair.getPublic()); + } + + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bais); + PrivateKey inflatedPrivateKey = (PrivateKey) ois.readObject(); + PublicKey inflatedPublicKey = (PublicKey) ois.readObject(); + + assertEquals(inflatedPrivateKey, keyPair.getPrivate()); + assertEquals(inflatedPublicKey, keyPair.getPublic()); + } + + @Test + public void serializeAndDeserialize_87_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-DSA-87", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(16384); + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(keyPair.getPrivate()); + oos.writeObject(keyPair.getPublic()); + } + + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bais); + PrivateKey inflatedPrivateKey = (PrivateKey) ois.readObject(); + PublicKey inflatedPublicKey = (PublicKey) ois.readObject(); + + assertEquals(inflatedPrivateKey, keyPair.getPrivate()); + assertEquals(inflatedPublicKey, keyPair.getPublic()); + } + + @Test + public void serializePrivateKey_65_isEqualToTestVector() throws Exception { + byte[] rawPrivateKey = TestUtils.decodeHex( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + + KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA-65", conscryptProvider); + PrivateKey privateKey = keyFactory.generatePrivate(new RawKeySpec(rawPrivateKey)); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(16384); + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(privateKey); + } + + String expectedHexEncoding = "aced000573720024" + + "6f72672e636f6e7363727970742e" // hex("org.conscrypt.") + + "4f70656e53736c4d6c447361507269766174654b6579" // hex("OpenSslMldsaPrivateKey") + + "3bacc385e8e106a3" // serialVersionUID + + "0200015b0004" + + "73656564" // hex("seed") + + "7400025b427870757200025b42acf317f8060854e00200007870000000" + + "20" // hex(32), size of the raw private key + + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; // rawPrivateKey + assertEquals(expectedHexEncoding, TestUtils.encodeHex(baos.toByteArray())); + } + + @Test + public void serializePrivateKey_87_isEqualToTestVector() throws Exception { + byte[] rawPrivateKey = TestUtils.decodeHex( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + + KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA-87", conscryptProvider); + PrivateKey privateKey = keyFactory.generatePrivate(new RawKeySpec(rawPrivateKey)); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(16384); + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(privateKey); + } + + String expectedHexEncoding = "aced000573720024" + + "6f72672e636f6e7363727970742e" // hex("org.conscrypt.") + + "4f70656e53736c4d6c447361507269766174654b6579" // hex("OpenSslMldsaPrivateKey") + + "3bacc385e8e106a3" // serialVersionUID + + "0200015b0004" + + "73656564" // hex("seed") + + "7400025b427870757200025b42acf317f8060854e00200007870000000" + + "21" // hex(33), size of "seed", which is 32 + 1 + + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" // rawPrivateKey + + "57"; // hex(87) + assertEquals(expectedHexEncoding, TestUtils.encodeHex(baos.toByteArray())); + } + + @Test + public void serializePublicKey_65_isEqualToTestVector() throws Exception { + byte[] rawPublicKey = new byte[1952]; + + KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA-65", conscryptProvider); + PublicKey publicKey = keyFactory.generatePublic(new RawKeySpec(rawPublicKey)); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(16384); + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(publicKey); + } + + String expectedHexEncoding = "aced000573720023" + + "6f72672e636f6e7363727970742e" // hex("org.conscrypt.") + + "4f70656e53736c4d6c4473615075626c69634b6579" // hex("OpenSslMldsaPublicKey") + + "064c7113d078e42d" // serialVersionUID + + "0200015b0003" + + "726177" // hex("raw") + + "7400025b427870757200025b42acf317f8060854e002000078700000" + + "07a0" + TestUtils.encodeHex(rawPublicKey); + assertEquals(expectedHexEncoding, TestUtils.encodeHex(baos.toByteArray())); + } + + @Test + public void serializePublicKey_87_isEqualToTestVector() throws Exception { + byte[] rawPublicKey = new byte[2592]; + + KeyFactory keyFactory = KeyFactory.getInstance("ML-DSA-87", conscryptProvider); + PublicKey publicKey = keyFactory.generatePublic(new RawKeySpec(rawPublicKey)); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(16384); + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(publicKey); + } + + String expectedHexEncoding = "aced000573720023" + + "6f72672e636f6e7363727970742e" // hex("org.conscrypt.") + + "4f70656e53736c4d6c4473615075626c69634b6579" // hex("OpenSslMldsaPublicKey") + + "064c7113d078e42d" // serialVersionUID + + "0200015b0003" + + "726177" // hex("raw") + + "7400025b427870757200025b42acf317f8060854e002000078700000" + + "0a20" // hex(2592), size of the raw public key + + TestUtils.encodeHex(rawPublicKey); + assertEquals(expectedHexEncoding, TestUtils.encodeHex(baos.toByteArray())); + } + + @Test + public void deserializePrivateKeyWithWrongSuffix_fails() throws Exception { + String invalidPrivateKey = "aced000573720024" + + "6f72672e636f6e7363727970742e" // hex("org.conscrypt.") + + "4f70656e53736c4d6c447361507269766174654b6579" // hex("OpenSslMldsaPrivateKey") + + "3bacc385e8e106a3" // serialVersionUID + + "0200015b0004" + + "73656564" // hex("seed") + + "7400025b427870757200025b42acf317f8060854e00200007870000000" + + "21" // hex(33), size of encoded seed, which is 1 + 32 + // encoded seed. + + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" + + "41"; // wrong suffix. + + ByteArrayInputStream bais = + new ByteArrayInputStream(TestUtils.decodeHex(invalidPrivateKey)); + ObjectInputStream ois = new ObjectInputStream(bais); + + assertThrows(IllegalArgumentException.class, () -> ois.readObject()); + } + + @Test + public void deserializePublicKeyWithWrongSize_fails() throws Exception { + String invalidPrivateKey = "aced000573720024" + + "6f72672e636f6e7363727970742e" // hex("org.conscrypt.") + + "4f70656e53736c4d6c447361507269766174654b6579" // hex("OpenSslMldsaPrivateKey") + + "3bacc385e8e106a3" // serialVersionUID + + "0200015b0004" + + "73656564" // hex("seed") + + "7400025b427870757200025b42acf317f8060854e00200007870000000" + + "22" // hex(34), illegal size of encoded seed, only 32 or 33 are allowed. + // encoded seed. + + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" + + "5757"; + + ByteArrayInputStream bais = + new ByteArrayInputStream(TestUtils.decodeHex(invalidPrivateKey)); + ObjectInputStream ois = new ObjectInputStream(bais); + + assertThrows(IllegalArgumentException.class, () -> ois.readObject()); + } + + @Test + public void deserializeInvalidPublicKey_fails() throws Exception { + byte[] invalidRawPublicKey = new byte[2593]; // one byte too long. + + String hexPublicKey = "aced000573720023" + + "6f72672e636f6e7363727970742e" // hex("org.conscrypt.") + + "4f70656e53736c4d6c4473615075626c69634b6579" // hex("OpenSslMldsaPublicKey") + + "064c7113d078e42d" // serialVersionUID + + "0200015b0003" + + "726177" // hex("raw") + + "7400025b427870757200025b42acf317f8060854e002000078700000" + + "0a21" // hex(2593), size of the invalid raw public key + + TestUtils.encodeHex(invalidRawPublicKey); + + ByteArrayInputStream bais = new ByteArrayInputStream(TestUtils.decodeHex(hexPublicKey)); + ObjectInputStream ois = new ObjectInputStream(bais); + + assertThrows(IllegalArgumentException.class, () -> ois.readObject()); + } } From 1dab3184ee0d373f432122882ce2becad4a57e23 Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 23 Jun 2025 12:04:39 +0000 Subject: [PATCH 23/24] Add missing imports. --- common/src/test/java/org/conscrypt/MlDsaTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/src/test/java/org/conscrypt/MlDsaTest.java b/common/src/test/java/org/conscrypt/MlDsaTest.java index 1d85c5d00..f4e59a145 100644 --- a/common/src/test/java/org/conscrypt/MlDsaTest.java +++ b/common/src/test/java/org/conscrypt/MlDsaTest.java @@ -25,6 +25,10 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; From 6d34ac485f04b3cfc468b7fa42be047d325f4c52 Mon Sep 17 00:00:00 2001 From: juergw Date: Thu, 26 Jun 2025 06:08:39 +0000 Subject: [PATCH 24/24] Remove remaining conflict marks. --- common/src/test/java/org/conscrypt/MlDsaTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/common/src/test/java/org/conscrypt/MlDsaTest.java b/common/src/test/java/org/conscrypt/MlDsaTest.java index de68a101f..05b1c6b2b 100644 --- a/common/src/test/java/org/conscrypt/MlDsaTest.java +++ b/common/src/test/java/org/conscrypt/MlDsaTest.java @@ -203,7 +203,6 @@ public void mldsa65_getRawKey_works() throws Exception { assertEquals(privateKey, keyPair.getPrivate()); assertEquals(publicKey, keyPair.getPublic()); -<<<<<<< HEAD } @Test @@ -227,8 +226,6 @@ public void mldsa87_getRawKey_works() throws Exception { assertEquals(privateKey, keyPair.getPrivate()); assertEquals(publicKey, keyPair.getPublic()); -======= ->>>>>>> upstream/master } @Test