]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Flexible encoders for ML-DSA
authorViktor Dukhovni <openssl-users@dukhovni.org>
Mon, 3 Feb 2025 01:39:29 +0000 (12:39 +1100)
committerTomas Mraz <tomas@openssl.org>
Fri, 14 Feb 2025 09:46:04 +0000 (10:46 +0100)
- Same UX as ML-KEM.  The main ASN.1 private key syntax is the one from
  Russ Housley's post on the LAMPS list, subsequently amended to tag the
  seed instead of the key (each of the three parameter sets will have a
  fixed size for the `expandedKey`):

    ML-DSA-PrivateKey ::= CHOICE {
      seed [0] IMPLICIT OCTET STRING SIZE (32),
      expandedKey OCTET STRING SIZE (2560 | 4032 | 4896)
      both SEQUENCE {
        seed OCTET STRING SIZE (32),
        expandedKey OCTET STRING SIZE (2560 | 4032 | 4896) } }

Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
Reviewed-by: Tim Hudson <tjh@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/26638)

61 files changed:
.gitattributes
apps/pkeyutl.c
crypto/err/openssl.txt
crypto/ml_dsa/ml_dsa_encoders.c
crypto/ml_dsa/ml_dsa_key.c
crypto/ml_dsa/ml_dsa_key.h
crypto/ml_dsa/ml_dsa_local.h
crypto/ml_dsa/ml_dsa_params.c
crypto/ml_dsa/ml_dsa_params.h [deleted file]
crypto/ml_dsa/ml_dsa_sign.c
doc/man7/EVP_PKEY-ML-DSA.pod
include/crypto/ml_dsa.h
include/crypto/types.h
include/openssl/core_dispatch.h
include/openssl/proverr.h
providers/common/provider_err.c
providers/implementations/encode_decode/build.info
providers/implementations/encode_decode/decode_der2key.c
providers/implementations/encode_decode/encode_key2any.c
providers/implementations/encode_decode/encode_key2text.c
providers/implementations/encode_decode/ml_dsa_codecs.c [new file with mode: 0644]
providers/implementations/encode_decode/ml_dsa_codecs.h [new file with mode: 0644]
providers/implementations/keymgmt/ml_dsa_kmgmt.c
providers/implementations/signature/ml_dsa_sig.c
test/ml_dsa_test.c
test/recipes/15-test_ml_dsa_codecs.t [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/ml-dsa.cnf [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-44-bare-priv.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-44-bare-seed.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-44-oqskeypair.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-44-priv-only.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-44-priv.txt [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-44-seed-only.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-44-seed-priv.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-44-seed.txt [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-65-bare-priv.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-65-bare-seed.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-65-oqskeypair.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-65-priv-only.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-65-priv.txt [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-65-seed-only.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-65-seed-priv.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-65-seed.txt [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-87-bare-priv.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-87-bare-seed.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-87-oqskeypair.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-87-priv-only.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-87-priv.txt [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-87-seed-only.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-87-seed-priv.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/prv-87-seed.txt [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/pub-44.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/pub-44.txt [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/pub-65.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/pub-65.txt [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/pub-87.pem [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/pub-87.txt [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/sig-44.dat [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/sig-65.dat [new file with mode: 0644]
test/recipes/15-test_ml_dsa_codecs_data/sig-87.dat [new file with mode: 0644]
util/perl/OpenSSL/paramnames.pm

index acecef0e19e4e33c611970292a438bdef8f6e8a3..efd52c7e5e25b505b7b98cd6eea7f113bad9f0d1 100644 (file)
@@ -2,6 +2,7 @@
 *.der binary
 /fuzz/corpora/** binary
 *.pfx binary
+test/recipes/15-test_ml_dsa_codecs_data/*.dat   binary
 
 # For git archive
 fuzz/corpora/**                         export-ignore
index 7762f251be1d9a1fc6d9778e63714bba5155a2af..1d97b3cc5e61dc3fb8b7d54eb3724dfb8ba12fe8 100644 (file)
@@ -43,17 +43,15 @@ static int do_raw_keyop(int pkey_op, EVP_MD_CTX *mctx,
                         int filesize, unsigned char *sig, int siglen,
                         unsigned char **out, size_t *poutlen);
 
-static int is_EdDSA(const EVP_PKEY *pkey)
+static int only_nomd(EVP_PKEY *pkey)
 {
-    if (pkey == NULL)
-        return 0;
-    return EVP_PKEY_is_a(pkey, "ED25519")
-        || EVP_PKEY_is_a(pkey, "ED448");
-}
+#define MADE_UP_MAX_MD_NAME_LEN 100
+    char defname[MADE_UP_MAX_MD_NAME_LEN];
+    int deftype;
 
-static int only_rawin(const EVP_PKEY *pkey)
-{
-    return is_EdDSA(pkey);
+    deftype = EVP_PKEY_get_default_digest_name(pkey, defname, sizeof(defname));
+    return deftype == 2 /* Mandatory */
+        && strcmp(defname, "UNDEF") == 0;
 }
 
 typedef enum OPTION_choice {
@@ -326,13 +324,16 @@ int pkeyutl_main(int argc, char **argv)
     }
 
     if (pkey_op == EVP_PKEY_OP_SIGN || pkey_op == EVP_PKEY_OP_VERIFY) {
-        if (only_rawin(pkey)) {
-            if (is_EdDSA(pkey) && digestname != NULL) {
+        if (only_nomd(pkey)) {
+            if (digestname != NULL) {
+                const char *alg = EVP_PKEY_get0_type_name(pkey);
+
                 BIO_printf(bio_err,
-                           "%s: -digest (prehash) is not supported with EdDSA\n", prog);
+                           "%s: -digest (prehash) is not supported with %s\n",
+                           prog, alg != NULL ? alg : "(unknown key type)");
                 goto end;
             }
-            rawin = 1; /* implied for Ed25519(ph) and Ed448(ph) and maybe others in the future */
+            rawin = 1;
         }
     } else if (digestname != NULL || rawin) {
         BIO_printf(bio_err,
@@ -821,7 +822,7 @@ static int do_raw_keyop(int pkey_op, EVP_MD_CTX *mctx,
     int buf_len = 0;
 
     /* Some algorithms only support oneshot digests */
-    if (only_rawin(pkey)) {
+    if (only_nomd(pkey)) {
         if (filesize < 0) {
             BIO_printf(bio_err,
                        "Error: unable to determine file size for oneshot operation\n");
index 3b8183da4f5e8c0549860e50194ecb8f5cfd13d0..7f2bb931196cacb7517a7d947e6ba1340de71f75 100644 (file)
@@ -1131,6 +1131,7 @@ PROV_R_MISSING_SEED:140:missing seed
 PROV_R_MISSING_SESSION_ID:133:missing session id
 PROV_R_MISSING_TYPE:134:missing type
 PROV_R_MISSING_XCGHASH:135:missing xcghash
+PROV_R_ML_DSA_NO_FORMAT:247:ml dsa no format
 PROV_R_MODULE_INTEGRITY_FAILURE:214:module integrity failure
 PROV_R_NOT_A_PRIVATE_KEY:221:not a private key
 PROV_R_NOT_A_PUBLIC_KEY:220:not a public key
index e669c511a7a678fcef8f0b6a2782bdf1a602bfc0..67b750fbaf4dac305a14fa615d680b78043d7862 100644 (file)
@@ -9,10 +9,8 @@
 
 #include <openssl/byteorder.h>
 #include <openssl/evp.h>
-#include "ml_dsa_local.h"
 #include "ml_dsa_hash.h"
 #include "ml_dsa_key.h"
-#include "ml_dsa_params.h"
 #include "ml_dsa_sign.h"
 #include "internal/packet.h"
 
@@ -768,7 +766,13 @@ int ossl_ml_dsa_sk_decode(ML_DSA_KEY *key, const uint8_t *in, size_t in_len)
     size_t i, k = params->k, l = params->l;
     PACKET pkt;
 
-    if (key->priv_encoding != NULL || key->pub_encoding != NULL)
+    /* When loading from an explicit key, drop the seed. */
+    OPENSSL_free(key->seed);
+    key->seed = NULL;
+
+    /* Allow the key encoding to be already set to the provided pointer */
+    if ((key->priv_encoding != NULL && key->priv_encoding != in)
+        || key->pub_encoding != NULL)
         return 0; /* Do not allow key mutation */
     if (in_len != key->params->sk_len)
         return 0;
@@ -785,23 +789,23 @@ int ossl_ml_dsa_sk_decode(ML_DSA_KEY *key, const uint8_t *in, size_t in_len)
             || !PACKET_copy_bytes(&pkt, key->rho, sizeof(key->rho))
             || !PACKET_copy_bytes(&pkt, key->K, sizeof(key->K))
             || !PACKET_copy_bytes(&pkt, key->tr, sizeof(key->tr)))
-        goto err;
+        return 0;
 
     for (i = 0; i < l; ++i)
         if (!decode_fn(key->s1.poly + i, &pkt))
-            goto err;
+            return 0;
     for (i = 0; i < k; ++i)
         if (!decode_fn(key->s2.poly + i, &pkt))
-            goto err;
+            return 0;
     for (i = 0; i < k; ++i)
         if (!poly_decode_signed_two_to_power_12(key->t0.poly + i, &pkt))
-            goto err;
+            return 0;
     if (PACKET_remaining(&pkt) != 0)
-        goto err;
-    key->priv_encoding = OPENSSL_memdup(in, in_len);
-    return key->priv_encoding != NULL;
-err:
-    return 0;
+        return 0;
+    if (key->priv_encoding == NULL
+        && (key->priv_encoding = OPENSSL_memdup(in, in_len)) == NULL)
+        return 0;
+    return ossl_ml_dsa_key_public_from_private(key);
 }
 
 /*
index e34f22cef75f9c0b6d912831e09b2bb62c33c872..12b28b28f5a2f5929fc465db097c7a3cea690c0c 100644 (file)
 #include <openssl/params.h>
 #include <openssl/proverr.h>
 #include <openssl/rand.h>
-#include "ml_dsa_local.h"
 #include "ml_dsa_key.h"
-#include "ml_dsa_params.h"
 #include "ml_dsa_matrix.h"
 #include "ml_dsa_hash.h"
 #include "internal/encoder.h"
 
+const ML_DSA_PARAMS *ossl_ml_dsa_key_params(const ML_DSA_KEY *key)
+{
+    return key->params;
+}
+
+/* Returns the seed data or NULL if there is no seed */
+const uint8_t *ossl_ml_dsa_key_get_seed(const ML_DSA_KEY *key)
+{
+    return key->seed;
+}
+
+int ossl_ml_dsa_key_prefer_seed(const ML_DSA_KEY *key)
+{
+    return key->prefer_seed;
+}
+
+int ossl_ml_dsa_key_retain_seed(const ML_DSA_KEY *key)
+{
+    return key->retain_seed;
+}
+
+int ossl_ml_dsa_set_prekey(ML_DSA_KEY *key, int prefer_seed, int retain_seed,
+                           const uint8_t *seed, size_t seed_len,
+                           const uint8_t *sk, size_t sk_len)
+{
+    int ret = 0;
+
+    if (key == NULL
+        || key->pub_encoding != NULL
+        || key->priv_encoding != NULL
+        || (sk != NULL && sk_len != key->params->sk_len)
+        || (seed != NULL && seed_len != ML_DSA_SEED_BYTES)
+        || key->seed != NULL)
+        return 0;
+
+    if (sk != NULL
+        && (key->priv_encoding = OPENSSL_memdup(sk, sk_len)) == NULL)
+        goto end;
+    if (seed != NULL
+        && (key->seed = OPENSSL_memdup(seed, seed_len)) == NULL)
+        goto end;
+    if (prefer_seed >= 0)
+        key->prefer_seed = prefer_seed;
+    if (retain_seed >= 0)
+        key->retain_seed = retain_seed;
+    ret = 1;
+
+  end:
+    if (!ret) {
+        OPENSSL_free(key->priv_encoding);
+        OPENSSL_free(key->seed);
+        key->priv_encoding = key->seed = NULL;
+    }
+    return ret;
+}
+
 /**
  * @brief Create a new ML_DSA_KEY object
  *
  * @returns The new ML_DSA_KEY object on success, or NULL on malloc failure
  */
 ML_DSA_KEY *ossl_ml_dsa_key_new(OSSL_LIB_CTX *libctx, const char *propq,
-                                const char *alg)
+                                int evp_type)
 {
     ML_DSA_KEY *ret;
-    const ML_DSA_PARAMS *params = ossl_ml_dsa_params_get(alg);
+    const ML_DSA_PARAMS *params = ossl_ml_dsa_params_get(evp_type);
 
     if (params == NULL)
         return NULL;
@@ -40,6 +94,8 @@ ML_DSA_KEY *ossl_ml_dsa_key_new(OSSL_LIB_CTX *libctx, const char *propq,
     if (ret != NULL) {
         ret->libctx = libctx;
         ret->params = params;
+        ret->prefer_seed = 1;
+        ret->retain_seed = 1;
         ret->shake128_md = EVP_MD_fetch(libctx, "SHAKE-128", propq);
         ret->shake256_md = EVP_MD_fetch(libctx, "SHAKE-256", propq);
         if (ret->shake128_md == NULL || ret->shake256_md == NULL)
@@ -92,7 +148,10 @@ void ossl_ml_dsa_key_free(ML_DSA_KEY *key)
     vector_free(&key->t1);
     OPENSSL_cleanse(key->K, sizeof(key->K));
     OPENSSL_free(key->pub_encoding);
-    OPENSSL_free(key->priv_encoding);
+    if (key->priv_encoding != NULL)
+        OPENSSL_clear_free(key->priv_encoding, key->params->sk_len);
+    if (key->seed != NULL)
+        OPENSSL_clear_free(key->seed, ML_DSA_SEED_BYTES);
     OPENSSL_free(key);
 }
 
@@ -111,10 +170,17 @@ ML_DSA_KEY *ossl_ml_dsa_key_dup(const ML_DSA_KEY *src, int selection)
     if (src == NULL)
         return NULL;
 
+    /* Prekeys with just a seed or private key are not dupable */
+    if (src->pub_encoding == NULL
+        && (src->priv_encoding != NULL || src->seed != NULL))
+        return NULL;
+
     ret = OPENSSL_zalloc(sizeof(*ret));
     if (ret != NULL) {
         ret->libctx = src->libctx;
         ret->params = src->params;
+        ret->retain_seed = src->retain_seed;
+        ret->prefer_seed = src->prefer_seed;
         if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) != 0) {
             if (src->pub_encoding != NULL) {
                 /* The public components are present if the private key is present */
@@ -139,13 +205,15 @@ ML_DSA_KEY *ossl_ml_dsa_key_dup(const ML_DSA_KEY *src, int selection)
                         vector_copy(&ret->s2, &src->s2);
                         vector_copy(&ret->t0, &src->t0);
                     }
-                    if (src->priv_encoding != NULL) {
-                        if ((ret->priv_encoding =
-                                OPENSSL_memdup(src->priv_encoding,
-                                               src->params->sk_len)) == NULL)
-                            goto err;
-                    }
+                    if ((ret->priv_encoding =
+                            OPENSSL_memdup(src->priv_encoding,
+                                           src->params->sk_len)) == NULL)
+                        goto err;
                 }
+                if (src->seed != NULL
+                    && (ret->seed = OPENSSL_memdup(src->seed,
+                                                   ML_DSA_SEED_BYTES)) == NULL)
+                    goto err;
             }
         }
         EVP_MD_up_ref(src->shake128_md);
@@ -294,57 +362,6 @@ err:
     return ret;
 }
 
-
-/**
- * @brief Load a ML_DSA key from raw data.
- *
- * @param key An ML_DSA key to load into
- * @param params An array of parameters containing key data.
- * @param include_private Set to 1 to optionally include the private key data
- *                        if it exists.
- * @returns 1 on success, or 0 on failure.
- */
-int ossl_ml_dsa_key_fromdata(ML_DSA_KEY *key, const OSSL_PARAM params[],
-                             int include_private)
-{
-    const OSSL_PARAM *pub = NULL, *priv = NULL;
-    const uint8_t *pub_data = NULL, *priv_data = NULL;
-    size_t pub_data_len = 0, priv_data_len = 0;
-
-    pub = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PUB_KEY);
-    if (pub != NULL
-            && !OSSL_PARAM_get_octet_string_ptr(pub, (const void **)&pub_data,
-                                                &pub_data_len))
-        return 0;
-
-    /* Private key is optional */
-    if (include_private) {
-        priv = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PRIV_KEY);
-        if (priv != NULL) {
-            if (!OSSL_PARAM_get_octet_string_ptr(priv, (const void **)&priv_data,
-                                                 &priv_data_len)
-                    || !ossl_ml_dsa_sk_decode(key, priv_data, priv_data_len))
-                return 0;
-            /* Always generate the public key from the private key */
-            if (!ossl_ml_dsa_key_public_from_private(key))
-                return 0;
-            /* Error if the supplied public key does not match the generated key */
-            if (pub != NULL
-                    && (pub_data_len != ossl_ml_dsa_key_get_pub_len(key)
-                        || memcmp(ossl_ml_dsa_key_get_pub(key), pub_data,
-                                  pub_data_len) != 0))
-                return 0;
-        }
-    }
-
-    /* If we only have the public key component then just decode it */
-    if (priv == NULL && pub != NULL) {
-        if (!ossl_ml_dsa_pk_decode(key, pub_data, pub_data_len))
-            return 0;
-    }
-    return 1;
-}
-
 int ossl_ml_dsa_key_pairwise_check(const ML_DSA_KEY *key)
 {
     int ret = 0;
@@ -379,14 +396,11 @@ err:
  * @brief Generate a public-private key pair from a seed.
  * See FIPS 204, Algorithm 6 ML-DSA.KeyGen_internal().
  *
- * @param seed The input seed
- * @param seed_len The size of entropy (Must be 32 bytes)
  * @param out The generated key (which contains params on input)
  *
  * @returns 1 on success or 0 on failure.
  */
-static int keygen_internal(const uint8_t *seed, size_t seed_len,
-                           ML_DSA_KEY *out)
+static int keygen_internal(ML_DSA_KEY *out)
 {
     int ret = 0;
     uint8_t augmented_seed[ML_DSA_SEED_BYTES + 2];
@@ -397,14 +411,14 @@ static int keygen_internal(const uint8_t *seed, size_t seed_len,
     const ML_DSA_PARAMS *params = out->params;
     EVP_MD_CTX *md_ctx = NULL;
 
-    md_ctx = EVP_MD_CTX_new();
-    if (md_ctx == NULL
-            || !ossl_ml_dsa_key_pub_alloc(out)
-            || !ossl_ml_dsa_key_priv_alloc(out))
+    if (out->seed == NULL
+        || (md_ctx = EVP_MD_CTX_new()) == NULL
+        || !ossl_ml_dsa_key_pub_alloc(out)
+        || !ossl_ml_dsa_key_priv_alloc(out))
         goto err;
 
     /* augmented_seed = seed || k || l */
-    memcpy(augmented_seed, seed, seed_len);
+    memcpy(augmented_seed, out->seed, ML_DSA_SEED_BYTES);
     augmented_seed[ML_DSA_SEED_BYTES] = (uint8_t)params->k;
     augmented_seed[ML_DSA_SEED_BYTES + 1] = (uint8_t)params->l;
     /* Expand the seed into p[32], p'[64], K[32] */
@@ -421,35 +435,35 @@ static int keygen_internal(const uint8_t *seed, size_t seed_len,
         && shake_xof(md_ctx, out->shake256_md, out->pub_encoding, out->params->pk_len,
                      out->tr, sizeof(out->tr))
         && ossl_ml_dsa_sk_encode(out);
+
 err:
+    if (out->seed != NULL && !out->retain_seed) {
+        OPENSSL_clear_free(out->seed, ML_DSA_SEED_BYTES);
+        out->seed = NULL;
+    }
     EVP_MD_CTX_free(md_ctx);
     OPENSSL_cleanse(augmented_seed, sizeof(augmented_seed));
     OPENSSL_cleanse(expanded_seed, sizeof(expanded_seed));
     return ret;
 }
 
-int ossl_ml_dsa_generate_key(OSSL_LIB_CTX *lib_ctx,
-                             const uint8_t *entropy, size_t entropy_len,
-                             ML_DSA_KEY *out)
+int ossl_ml_dsa_generate_key(ML_DSA_KEY *out)
 {
-    int ret = 0;
-    uint8_t seed[ML_DSA_SEED_BYTES];
-    size_t seed_len = sizeof(seed);
+    size_t seed_len = ML_DSA_SEED_BYTES;
 
-    if (entropy != NULL && entropy_len != 0) {
-        if (entropy_len != seed_len) {
-            ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_SEED_LENGTH);
+    if (out->seed == NULL) {
+        if ((out->seed = OPENSSL_malloc(seed_len)) == NULL)
+            return 0;
+        if (RAND_priv_bytes_ex(out->libctx, out->seed, seed_len, 0) <= 0) {
+            OPENSSL_free(out->seed);
+            out->seed = NULL;
             return 0;
         }
-        memcpy(seed, entropy, seed_len);
-    } else {
-        if (RAND_priv_bytes_ex(lib_ctx, seed, seed_len, 0) <= 0)
-            goto err;
     }
-    ret = keygen_internal(seed, seed_len, out);
-err:
-    OPENSSL_cleanse(seed, seed_len);
-    return ret;
+    /* We're generating from a seed, drop private prekey encoding */
+    OPENSSL_free(out->priv_encoding);
+    out->priv_encoding = NULL;
+    return keygen_internal(out);
 }
 
 /**
@@ -457,14 +471,14 @@ err:
  * This checks that the algorithm is the same (i.e. uses the same parameters)
  *
  * @param key A ML_DSA key to use for an operation.
- * @param alg The algorithm name associated with an operation
+ * @param evp_type The algorithm nid associated with an operation
  *
  * @returns 1 if the algorithm matches, or 0 otherwise.
  */
 
-int ossl_ml_dsa_key_matches(const ML_DSA_KEY *key, const char *alg)
+int ossl_ml_dsa_key_matches(const ML_DSA_KEY *key, int evp_type)
 {
-    return (key->params == ossl_ml_dsa_params_get(alg));
+    return (key->params->evp_type == evp_type);
 }
 
 /* Returns the public key data or NULL if there is no public key */
@@ -509,47 +523,3 @@ const char *ossl_ml_dsa_key_get_name(const ML_DSA_KEY *key)
 {
     return key->params->alg;
 }
-
-#ifndef FIPS_MODULE
-int ossl_ml_dsa_to_text(BIO *out, ML_DSA_KEY *key, int selection)
-{
-    const char *name;
-
-    if (out == NULL || key == NULL) {
-        ERR_raise(ERR_LIB_PROV, ERR_R_PASSED_NULL_PARAMETER);
-        return 0;
-    }
-    name = ossl_ml_dsa_key_get_name(key);
-    if (ossl_ml_dsa_key_get_pub(key) == NULL) {
-        /* Regardless of the |selection|, there must be a public key */
-        ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_KEY,
-                       "no %s key material available", name);
-        return 0;
-    }
-
-    name = ossl_ml_dsa_key_get_name(key);
-    if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0) {
-        if (ossl_ml_dsa_key_get_priv(key) == NULL) {
-            ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_KEY,
-                           "no %s key material available", name);
-            return 0;
-        }
-        if (BIO_printf(out, "%s Private-Key:\n", name) <= 0)
-            return 0;
-        if (!ossl_bio_print_labeled_buf(out, "priv:",
-                                        ossl_ml_dsa_key_get_priv(key),
-                                        ossl_ml_dsa_key_get_priv_len(key)))
-            return 0;
-    } else if ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0) {
-        if (BIO_printf(out, "%s Public-Key:\n", name) <= 0)
-            return 0;
-    }
-
-    if (!ossl_bio_print_labeled_buf(out, "pub:",
-                                    ossl_ml_dsa_key_get_pub(key),
-                                    ossl_ml_dsa_key_get_pub_len(key)))
-        return 0;
-
-    return 1;
-}
-#endif /* FIPS_MODULE */
index 0bc7ca0458b5d1ba9cd2c8946a907337decff9da..10d61cb54518ffd2ed9a0d060751fd5765c998d5 100644 (file)
@@ -8,8 +8,7 @@
  */
 
 #include <openssl/e_os2.h>
-#include "crypto/types.h"
-#include "internal/refcount.h"
+#include "ml_dsa_local.h"
 #include "ml_dsa_vector.h"
 
 /* NOTE - any changes to this struct may require updates to ossl_ml_dsa_dup() */
@@ -27,9 +26,16 @@ struct ml_dsa_key_st {
     /*
      * The encoded public and private keys, these are non NULL if the key
      * components are generated or loaded.
+     *
+     * For keys that are decoded, but not yet loaded or imported into the
+     * provider, the pub_encoding is NULL, while the seed or priv_encoding
+     * is not NULL.
      */
     uint8_t *pub_encoding;
     uint8_t *priv_encoding;
+    uint8_t *seed;
+    int retain_seed;
+    int prefer_seed;
 
     /*
      * t1 is the Polynomial encoding of the 10 MSB of each coefficient of the
index 9bc56f562bd4c376303cb367a8ddf40c1a122e0c..fc8deb0a3073bbe0e9bcdd3e9dbb2c6c2ab267b8 100644 (file)
@@ -15,7 +15,6 @@
 # include "internal/packet.h"
 
 /* The following constants are shared by ML-DSA-44, ML-DSA-65 & ML-DSA-87 */
-# define ML_DSA_SEED_BYTES 32
 # define ML_DSA_Q 8380417   /* The modulus is 23 bits (2^23 - 2^13 + 1) */
 # define ML_DSA_Q_MINUS1_DIV2 ((ML_DSA_Q - 1) / 2)
 
 # define ML_DSA_GAMMA2_Q_MINUS1_DIV32 ((ML_DSA_Q - 1) / 32)
 # define ML_DSA_GAMMA2_Q_MINUS1_DIV88 ((ML_DSA_Q - 1) / 88)
 
-typedef struct ml_dsa_params_st ML_DSA_PARAMS;
 typedef struct poly_st POLY;
 typedef struct vector_st VECTOR;
 typedef struct matrix_st MATRIX;
-
 typedef struct ml_dsa_sig_st ML_DSA_SIG;
 
 int ossl_ml_dsa_matrix_expand_A(EVP_MD_CTX *g_ctx, const EVP_MD *md,
index 3057f491d48b8cdf055e5a4faeb21e4a20028406..aa3f0cb3735c4107ebd501d2558648584dba1fd9 100644 (file)
@@ -8,8 +8,8 @@
  */
 #include <stddef.h>
 #include <string.h>
+#include <openssl/evp.h>
 #include "ml_dsa_local.h"
-#include "ml_dsa_params.h"
 
 /* See FIPS 204 Section 4 Table 1 & Table 2 */
 #define ML_DSA_44_TAU 39
@@ -43,6 +43,7 @@
 
 static const ML_DSA_PARAMS ml_dsa_params[] = {
     { "ML-DSA-44",
+      EVP_PKEY_ML_DSA_44,
       ML_DSA_44_TAU,
       ML_DSA_44_LAMBDA,
       ML_DSA_GAMMA1_TWO_POWER_17,
@@ -58,6 +59,7 @@ static const ML_DSA_PARAMS ml_dsa_params[] = {
       ML_DSA_44_SIG_LEN
     },
     { "ML-DSA-65",
+      EVP_PKEY_ML_DSA_65,
       ML_DSA_65_TAU,
       ML_DSA_65_LAMBDA,
       ML_DSA_GAMMA1_TWO_POWER_19,
@@ -73,6 +75,7 @@ static const ML_DSA_PARAMS ml_dsa_params[] = {
       ML_DSA_65_SIG_LEN
     },
     { "ML-DSA-87",
+      EVP_PKEY_ML_DSA_87,
       ML_DSA_87_TAU,
       ML_DSA_87_LAMBDA,
       ML_DSA_GAMMA1_TWO_POWER_19,
@@ -93,14 +96,12 @@ static const ML_DSA_PARAMS ml_dsa_params[] = {
 /**
  * @brief A getter to convert an algorithm name into a ML_DSA_PARAMS object
  */
-const ML_DSA_PARAMS *ossl_ml_dsa_params_get(const char *alg)
+const ML_DSA_PARAMS *ossl_ml_dsa_params_get(int evp_type)
 {
     const ML_DSA_PARAMS *p;
 
-    if (alg == NULL)
-        return NULL;
     for (p = ml_dsa_params; p->alg != NULL; ++p) {
-        if (strcmp(p->alg, alg) == 0)
+        if (p->evp_type == evp_type)
             return p;
     }
     return NULL;
diff --git a/crypto/ml_dsa/ml_dsa_params.h b/crypto/ml_dsa/ml_dsa_params.h
deleted file mode 100644 (file)
index a8cdac4..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
- *
- * Licensed under the Apache License 2.0 (the "License").  You may not use
- * this file except in compliance with the License.  You can obtain a copy
- * in the file LICENSE in the source distribution or at
- * https://www.openssl.org/source/license.html
- */
-
-#include <openssl/e_os2.h>
-
-/*
- * Refer to FIPS 204 Section 4 Parameter sets.
- * Fields that are shared between all algorithms (such as q & d) have been omitted.
- */
-struct ml_dsa_params_st {
-    const char *alg;
-    int tau;    /* Number of +/-1's in polynomial c */
-    int bit_strength; /* The collision strength (lambda) */
-    int gamma1; /* coefficient range of y */
-    int gamma2; /* low-order rounding range */
-    size_t k, l; /* matrix dimensions of 'A' */
-    int eta;    /* Private key range */
-    int beta;   /* tau * eta */
-    int omega;  /* Number of 1's in the hint 'h' */
-    int security_category; /* Category is related to Security strength */
-    size_t sk_len; /* private key size */
-    size_t pk_len; /* public key size */
-    size_t sig_len; /* signature size */
-};
-
-const struct ml_dsa_params_st *ossl_ml_dsa_params_get(const char *alg);
index 4d0e068c8dbd78078b48f317697c3e093fcbec91..5b997f6a9f36051efc3bfec8bee0d1c7813ccf4e 100644 (file)
@@ -13,7 +13,6 @@
 #include <openssl/rand.h>
 #include "ml_dsa_local.h"
 #include "ml_dsa_key.h"
-#include "ml_dsa_params.h"
 #include "ml_dsa_matrix.h"
 #include "ml_dsa_sign.h"
 #include "ml_dsa_hash.h"
index a6850e0e39798a79479e878f81ce4ce14cee4fe8..4cd072f67d5281d14e4d70d9511c1b110115e8c9 100644 (file)
@@ -1,5 +1,7 @@
 =pod
 
+=encoding utf-8
+
 =head1 NAME
 
 EVP_PKEY-ML-DSA, EVP_KEYMGMT-ML-DSA,
@@ -25,32 +27,40 @@ and B<ML-DSA-87> respectively, which correspond to security strengths of
 
 =item "seed" (B<OSSL_PKEY_PARAM_ML_DSA_SEED>) <octet string>
 
-The seed used to generate the private and public key components in a
-deterministic manner. This should only be used for testing purposes.
+The seed can be used to generate the private and public key components in a
+deterministic manner.
 The length of the value supplied must be 32 bytes.
 When this value is not supplied the seed is generated randomly using a DRBG. 
 
-=item "properties" (B<OSSL_PKEY_PARAM_PROPERTIES>) <utf8_string>
+Generated keys default to retaining the seed used.
+The seed is also by default retained when keys are loaded from B<PKCS#8> files
+in the seed format.
+When available, the seed parameter is also used during key export and import,
+with keys (by default) regenerated from the seed even when also provided on import.
+See L</Provider configuration parameters> below for related controls.
+
+When the seed is retained, it is also available as a B<gettable> parameter,
+and private key output to B<PKCS#8> files will by default include the seed.
+When the seed was not initially known, or was not retained, B<PKCS#8> private
+key files will contain only the private key in FIPS 204 C<sk> format.
+
+=item "properties" (B<OSSL_PKEY_PARAM_PROPERTIES>) <UTF8 string>
 
 Sets properties to be used when fetching algorithm implementations used for
 ML-DSA hashing operations.
 
 =back
 
-Use EVP_PKEY_CTX_set_params() after calling EVP_PKEY_keygen_init().
+Use L<EVP_PKEY_CTX_set_params(3)> after calling L<EVP_PKEY_keygen_init(3)>.
 
 =head2 Common ML-DSA parameters
 
 In addition to the common parameters that all keytypes should support (see
 L<provider-keymgmt(7)/Common Information Parameters>, the implementation of
-these key types support the following.
-
-The following parameters are gettable using EVP_PKEY_get_octet_string_param(),
-and settable when using EVP_PKEY_fromdata().
-
-The following octet string parameters are gettable using
+these key types support the parameters listed below.
+These are gettable using
 L<EVP_PKEY_get_octet_string_param(3)> or L<EVP_PKEY_get_params(3)>.
-They can be set into L<EVP_PKEY_fromdata(3)>, and are returned by
+They can be initialised via L<EVP_PKEY_fromdata(3)>, and are returned by
 L<EVP_PKEY_todata(3)> given a suitable I<selection>.
 Once a public or private key is configured, it can no longer be modified,
 nor can another key component be added.
@@ -69,6 +79,128 @@ respective key type of B<ML-DSA-44>, B<ML-DSA-65> or B<ML-DSA-87>.
 
 =back
 
+=head2 Provider configuration parameters
+
+See the description of the B<-provparam> option in L<openssl(1)> to learn
+how to set provider configuration parameters in the command line tools.
+See L<OSSL_PROVIDER_add_conf_parameter(3)> to learn how to set provider
+configuration options programmatically.
+
+=over 4
+
+=item C<ml-dsa.retain_seed> (B<OSSL_PKEY_PARAM_ML_DSA_RETAIN_SEED>) <UTF8 string>
+
+When set to a string representing a false boolean value (see
+L<OSSL_PROVIDER_conf_get_bool(3)>), the seed will not be retained after key
+generation or key import from a seed value.
+If the resulting key is then written to a PKCS#8 object, it will contain
+only the FIPS 204 C<sk> key.
+
+=item C<ml-dsa.prefer_seed> (B<OSSL_PKEY_PARAM_ML_DSA_PREFER_SEED>) <UTF8 string>
+
+When decoding PKCS#8 objects that contain both a seed and the FIPS 204 C<sk>
+private key, the seed is by default used to regenerate the key, and the
+companion private key is ignored.
+When this configuration parameter is set to a string representing a false
+boolean value (see L<OSSL_PROVIDER_conf_get_bool(3)>), the seed is ignored
+(neither used to regenerate the key, nor retained), and the companion key is
+used instead.
+
+=item C<ml-dsa.input_formats> (B<OSSL_PKEY_PARAM_ML_DSA_INPUT_FORMATS>) <UTF8 string>
+
+List of enabled private key input formats when parsing PKCS#8 objects.
+List elements are separated by commas, spaces or tabs.
+The list of enabled formats can be specified in the configuration file, as seen
+in the L</EXAMPLES> section below, or the via the B<-provparam> command-line
+option (see also L<OSSL_PROVIDER_add_conf_parameter(3)>).
+
+Values specified on the command-line override any configuration file settings.
+By default all the supported formats are enabled.
+The supported formats are:
+
+=over 4
+
+=item C<seed-priv>:
+
+This format represents B<PKCS#8> objects in which both the FIPS 204 32-byte
+B<ξ> seed and the secret key B<sk> are present in the private key as part of
+the DER encoding of the ASN.1 sequence:
+
+    ML-DSA-PrivateKey ::= CHOICE {
+      seed [0] IMPLICIT OCTET STRING SIZE (32),
+      expandedKey OCTET STRING SIZE (2560 | 4032 | 4896)
+      both SEQUENCE {
+        seed OCTET STRING SIZE (32),
+        expandedKey OCTET STRING SIZE (2560 | 4032 | 4896) } }
+
+If the C<seed-priv> format is not included in the list, this format will not be
+recognised on input.
+
+=item C<seed-only>:
+
+This format represents B<PKCS#8> objects in which only the 32-byte FIPS 204
+B<ξ> seed is present in the above sequence.
+If the C<seed-only> format is not included in the list, this format will not be
+recognised on input.
+
+=item C<priv-only>:
+
+This format represents B<PKCS#8> objects in which only the FIPS 204
+private key B<sk> is present in the above sequence.
+If the C<priv-only> format is not included in the list, this format will not be
+recognised on input.
+
+=item C<oqskeypair>:
+
+This format represents B<PKCS#8> objects in which the private key is a DER
+encoding of an octet string containing the concatenaton of the FIPS 204 private
+key B<sk> and the public key B<pk>.
+This encoding is used in some builds of the C<oqsprovider>.
+If the C<oqskeypair> format is not included in the list, this format will not be
+recognised on input.
+
+=item C<bare-seed>:
+
+This format represents B<PKCS#8> objects in which the private key contains
+the 32-byte FIPS 204 seed B<ξ> without any ASN.1 encapsulation.
+If the C<bare-seed> format is not included in the list, this format will not be
+recognised on input.
+
+=item C<bare-priv>:
+
+This format represents B<PKCS#8> objects in which the private key contains
+the FIPS 204 secret key B<sk> without any ASN.1 encapsulation.
+If the C<bare-priv> format is not included in the list, this format will not be
+recognised on input.
+
+=back
+
+=item C<ml-dsa.output_formats> (B<OSSL_PKEY_PARAM_ML_DSA_OUTPUT_FORMATS>) <UTF8 string>
+
+Ordered list of enabled private key output formats when writing B<PKCS#8> files.
+List elements are separated by commas, spaces or tabs.
+The list of enabled formats can be specified in the configuration file, as seen
+in the L</EXAMPLES> section below, or the via the B<-provparam> command-line
+option.
+
+This supports the same set of formats as described under C<ml-dsa.input_formats>
+above.
+The order in which elements are listed is important, the selected format will be
+the first one that is possible to output.
+If the key seed is known, the first listed format will be selected.
+If the key seed is not known, the first format that omits the seed will be selected.
+The default order is equivalent to C<seed-priv> first and C<priv-only> second, with
+both seed and key output when the seed is available, and just the
+key otherwise.
+If C<seed-only> is listed first, then the seed will be output without the key
+when available, otherwise the output will have just the key.
+If C<priv-only> is listed first, then just the key is output regardless of
+whether the seed is present.
+The legacy C<oqskeypair>, C<bare-seed> and C<bare-priv> formats can also be
+output, by listing those first.
+
+=back
+
 =head1 CONFORMING TO
 
 =over 4
@@ -84,25 +216,77 @@ An B<EVP_PKEY> context can be obtained by calling:
     EVP_PKEY_CTX *pctx =
         EVP_PKEY_CTX_new_from_name(NULL, "ML-DSA-44", NULL);
 
-An B<ML-DSA> key can be generated like this:
+An B<ML-DSA-44> key can be generated like this:
 
     pkey = EVP_PKEY_Q_keygen(NULL, NULL, "ML-DSA-44");
 
 The key pair components can be extracted from a key by calling:
 
-    uint8_t priv[3 * 1024], pub[5 * 1024];
-    size_t priv_len, pub_len;
+    /* Sizes large enough for ML-DSA-87 */
+    uint8_t pub[2592], priv[4896], seed[32]:
+    size_t priv_len, pub_len, seed_len;
 
+    EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_ML_DSA_SEED,
+                                    seed, sizeof(seed), &seed_len);
     EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY,
                                     priv, sizeof(priv), &priv_len);
     EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY,
                                     pub, sizeof(pub), &pub_len));
 
-Similar code can be used for the other key types such as "ML-DSA-87".
+An B<ML-DSA> private key in seed format can be converted to a key in the FIPS
+204 B<sk> format by running:
+
+    $ openssl pkey -provparam ml-dsa.retain_seed=no \
+        -in seed-only.pem -out priv-only.pem
+
+To generate an, e.g., B<ML-DSA-65> key, in FIPS 204 B<sk> format, you can run:
+
+    $ openssl genpkey -provparam ml-dsa.retain_seed=no \
+        -algorithm ml-dsa-65 -out priv-only.pem
+
+If you have a B<PKCS#8> file with both a seed and a key, and prefer to import the
+companion key rather than the seed, you can run:
+
+    $ openssl pkey -provparam ml-dsa.prefer_seed=no \
+        -in seed-priv.pem -out priv-only.pem
+
+In the B<openssl.cnf> file, this looks like:
+
+    openssl_conf = openssl_init
+
+    [openssl_init]
+    providers = providers_sect
+
+    # Can be referenced in one or more provider sections
+    [ml_dsa_sect]
+    prefer_seed = yes
+    retain_seed = yes
+    # OQS legacy formats disabled
+    input_formats = seed-priv, seed-only, priv-only
+    # Output either the seed alone, or else the key alone
+    output_formats = seed-only, priv-only
+
+    [providers_sect]
+    default = default_sect
+    # Or perhaps just: base = default_sect
+    base = base_sect
+
+    [default_sect]
+    ml-dsa = ml_dsa_sect
+
+    [base_sect]
+    ml-dsa = ml_dsa_sect
 
 =head1 SEE ALSO
 
-L<EVP_KEYMGMT(3)>, L<EVP_PKEY(3)>, L<provider-keymgmt(7)>,
+L<EVP_KEYMGMT(3)>,
+L<EVP_PKEY(3)>,
+L<provider-keymgmt(7)>,
+L<EVP_PKEY_get_raw_private_key(3)>,
+L<EVP_PKEY_get_raw_public_key(3)>,
+L<EVP_PKEY_get1_encoded_public_key(3)>,
+LOSSL_PROVIDER_add_conf_parameter(3)>,
+L<provider-keymgmt(7)>,
 L<EVP_SIGNATURE-ML-DSA(7)>
 
 =head1 HISTORY
index 709dad9d70b48c439f6a4ab1a8c6adef066faf0a..2999fe488f14388bd6ff138f7a446e36e4d3db20 100644 (file)
@@ -18,6 +18,7 @@
 # include "crypto/types.h"
 
 # define ML_DSA_MAX_CONTEXT_STRING_LEN 255
+# define ML_DSA_SEED_BYTES 32
 
 # define ML_DSA_ENTROPY_LEN 32
 
 # define ML_DSA_87_PUB_LEN 2592
 # define ML_DSA_87_SIG_LEN 4627
 
-/* Key and signature size maximums taken from values above */
+/* Key and signature size maxima taken from values above */
 # define MAX_ML_DSA_PRIV_LEN ML_DSA_87_PRIV_LEN
 # define MAX_ML_DSA_PUB_LEN ML_DSA_87_PUB_LEN
 # define MAX_ML_DSA_SIG_LEN ML_DSA_87_SIG_LEN
 
+/*
+ * Refer to FIPS 204 Section 4 Parameter sets.
+ * Fields that are shared between all algorithms (such as q & d) have been omitted.
+ */
+typedef struct ml_dsa_params_st {
+    const char *alg;
+    int evp_type;
+    int tau;    /* Number of +/-1's in polynomial c */
+    int bit_strength; /* The collision strength (lambda) */
+    int gamma1; /* coefficient range of y */
+    int gamma2; /* low-order rounding range */
+    size_t k, l; /* matrix dimensions of 'A' */
+    int eta;    /* Private key range */
+    int beta;   /* tau * eta */
+    int omega;  /* Number of 1's in the hint 'h' */
+    int security_category; /* Category is related to Security strength */
+    size_t sk_len; /* private key size */
+    size_t pk_len; /* public key size */
+    size_t sig_len; /* signature size */
+} ML_DSA_PARAMS;
+
+/* NOTE - any changes to this struct may require updates to ossl_ml_dsa_dup() */
+typedef struct ml_dsa_key_st ML_DSA_KEY;
+
+const ML_DSA_PARAMS *ossl_ml_dsa_params_get(int evp_type);
+const ML_DSA_PARAMS *ossl_ml_dsa_key_params(const ML_DSA_KEY *key);
 __owur ML_DSA_KEY *ossl_ml_dsa_key_new(OSSL_LIB_CTX *libctx, const char *propq,
-                                       const char *alg);
+                                       int evp_type);
 __owur int ossl_ml_dsa_key_pub_alloc(ML_DSA_KEY *key);
 __owur int ossl_ml_dsa_key_priv_alloc(ML_DSA_KEY *key);
 void ossl_ml_dsa_key_free(ML_DSA_KEY *key);
 __owur ML_DSA_KEY *ossl_ml_dsa_key_dup(const ML_DSA_KEY *src, int selection);
 __owur int ossl_ml_dsa_key_equal(const ML_DSA_KEY *key1, const ML_DSA_KEY *key2,
                                  int selection);
-__owur int ossl_ml_dsa_to_text(BIO *out, ML_DSA_KEY *key, int selection);
 __owur int ossl_ml_dsa_key_has(const ML_DSA_KEY *key, int selection);
 __owur int ossl_ml_dsa_key_pairwise_check(const ML_DSA_KEY *key);
-__owur int ossl_ml_dsa_key_fromdata(ML_DSA_KEY *key, const OSSL_PARAM *params,
-                                    int include_private);
-__owur int ossl_ml_dsa_generate_key(OSSL_LIB_CTX *libctx,
-                                    const uint8_t *entropy, size_t entropy_len,
-                                    ML_DSA_KEY *out);
+__owur int ossl_ml_dsa_generate_key(ML_DSA_KEY *out);
 __owur const uint8_t *ossl_ml_dsa_key_get_pub(const ML_DSA_KEY *key);
-__owur const uint8_t *ossl_ml_dsa_key_get_priv(const ML_DSA_KEY *key);
 __owur size_t ossl_ml_dsa_key_get_pub_len(const ML_DSA_KEY *key);
-__owur size_t ossl_ml_dsa_key_get_collision_strength_bits(const ML_DSA_KEY *key);
+__owur const uint8_t *ossl_ml_dsa_key_get_priv(const ML_DSA_KEY *key);
 __owur size_t ossl_ml_dsa_key_get_priv_len(const ML_DSA_KEY *key);
+__owur const uint8_t *ossl_ml_dsa_key_get_seed(const ML_DSA_KEY *key);
+__owur int ossl_ml_dsa_key_prefer_seed(const ML_DSA_KEY *key);
+__owur int ossl_ml_dsa_key_retain_seed(const ML_DSA_KEY *key);
+int ossl_ml_dsa_set_prekey(ML_DSA_KEY *key, int prefer_seed, int retain_seed,
+                           const uint8_t *seed, size_t seed_len,
+                           const uint8_t *sk, size_t sk_len);
+__owur size_t ossl_ml_dsa_key_get_collision_strength_bits(const ML_DSA_KEY *key);
 __owur size_t ossl_ml_dsa_key_get_sig_len(const ML_DSA_KEY *key);
-__owur int ossl_ml_dsa_key_matches(const ML_DSA_KEY *key, const char *alg);
+__owur int ossl_ml_dsa_key_matches(const ML_DSA_KEY *key, int evp_type);
 __owur const char *ossl_ml_dsa_key_get_name(const ML_DSA_KEY *key);
-__owur int ossl_ml_dsa_key_to_text(BIO *out, const ML_DSA_KEY *key, int selection);
 OSSL_LIB_CTX *ossl_ml_dsa_key_get0_libctx(const ML_DSA_KEY *key);
 
 __owur int ossl_ml_dsa_key_public_from_private(ML_DSA_KEY *key);
index 8f60ed81e00a0454f7de2785b653c23c79792a09..ad17f052e45f5047ad498fa1c55538ee33bc93c4 100644 (file)
@@ -28,8 +28,5 @@ typedef struct dsa_st DSA;
 # ifndef OPENSSL_NO_EC
 typedef struct ecx_key_st ECX_KEY;
 # endif
-# ifndef OPENSSL_NO_ML_DSA
-typedef struct ml_dsa_key_st ML_DSA_KEY;
-# endif
 
 #endif
index fa2e7f33035ddac2d6e2c3c6dd3daedccfe18f30..c3de07ff09cb7e132944b435814d831095092b8a 100644 (file)
 extern "C" {
 # endif
 
+/*
+ * Generic function pointer for provider method arrays, or other contexts where
+ * functions of various signatures must occupy a common slot in an array of
+ * structures.
+ */
+typedef void (*OSSL_FUNC)(void);
+
 /*-
  * Identities
  * ----------
index 091549ddd8c3161a8789938389795c06c20931b3..4afd008c4ba2c097d6ae566dc3250600eb08c18a 100644 (file)
 # define PROV_R_MISSING_SESSION_ID                        133
 # define PROV_R_MISSING_TYPE                              134
 # define PROV_R_MISSING_XCGHASH                           135
+# define PROV_R_ML_DSA_NO_FORMAT                          247
 # define PROV_R_MODULE_INTEGRITY_FAILURE                  214
 # define PROV_R_NOT_A_PRIVATE_KEY                         221
 # define PROV_R_NOT_A_PUBLIC_KEY                          220
index 03375d6bbd23fcc758e409b82658948906da8e71..adb13a4120989b314ccf2a56598d67cb3cb5a503 100644 (file)
@@ -153,6 +153,7 @@ static const ERR_STRING_DATA PROV_str_reasons[] = {
      "missing session id"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_TYPE), "missing type"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_XCGHASH), "missing xcghash"},
+    {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_ML_DSA_NO_FORMAT), "ml dsa no format"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MODULE_INTEGRITY_FAILURE),
      "module integrity failure"},
     {ERR_PACK(ERR_LIB_PROV, 0, PROV_R_NOT_A_PRIVATE_KEY), "not a private key"},
index d3f6ca4abdef7e6ba50cbe81112837bc0c3309db..a70d4d1fba2a0cf6fc9c7d64bb580e1f02e393eb 100644 (file)
@@ -18,3 +18,7 @@ IF[{- !$disabled{ec} -}]
   SOURCE[$ENCODER_GOAL]=encode_key2blob.c
 ENDIF
 DEPEND[encode_key2any.o]=../../common/include/prov/der_rsa.h
+
+IF[{- !$disabled{'ml-dsa'} -}]
+  SOURCE[$DECODER_GOAL]=ml_dsa_codecs.c
+ENDIF
index 271cd02b41f0c25421f14fee3ad9583c8e63a5e6..b77690a77347cc872ecde1003855f88b787cc544 100644 (file)
@@ -23,9 +23,7 @@
 #include <openssl/pkcs12.h>
 #include <openssl/x509.h>
 #include <openssl/proverr.h>
-#include <openssl/asn1t.h>
 #include "internal/cryptlib.h"   /* ossl_assert() */
-#include "internal/asn1.h"
 #include "crypto/dh.h"
 #include "crypto/dsa.h"
 #include "crypto/ec.h"
 #include "crypto/rsa.h"
 #include "crypto/ml_dsa.h"
 #include "crypto/x509.h"
-#include "openssl/obj_mac.h"
 #include "prov/bio.h"
 #include "prov/implementations.h"
 #include "endecoder_local.h"
 #include "internal/nelem.h"
-
-#ifndef OPENSSL_NO_ML_DSA
-typedef struct {
-    ASN1_OBJECT *oid;
-} BARE_ALGOR;
-
-typedef struct {
-    BARE_ALGOR algor;
-    ASN1_BIT_STRING *pubkey;
-} BARE_PUBKEY;
-
-ASN1_SEQUENCE(BARE_ALGOR) = {
-    ASN1_SIMPLE(BARE_ALGOR, oid, ASN1_OBJECT),
-} static_ASN1_SEQUENCE_END(BARE_ALGOR)
-
-ASN1_SEQUENCE(BARE_PUBKEY) = {
-    ASN1_EMBED(BARE_PUBKEY, algor, BARE_ALGOR),
-    ASN1_SIMPLE(BARE_PUBKEY, pubkey, ASN1_BIT_STRING)
-} static_ASN1_SEQUENCE_END(BARE_PUBKEY)
-#endif /* OPENSSL_NO_ML_DSA */
+#include "ml_dsa_codecs.h"
 
 struct der2key_ctx_st;           /* Forward declaration */
 typedef int check_key_fn(void *, struct der2key_ctx_st *ctx);
@@ -640,118 +618,25 @@ static void rsa_adjust(void *key, struct der2key_ctx_st *ctx)
 static void *
 ml_dsa_d2i_PKCS8(const uint8_t **der, long der_len, struct der2key_ctx_st *ctx)
 {
-    ML_DSA_KEY *key = NULL, *ret = NULL;
-    OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(ctx->provctx);
-    PKCS8_PRIV_KEY_INFO *p8inf = NULL;
-    const unsigned char *p;
-    const X509_ALGOR *alg = NULL;
-    int plen, ptype;
-
-    if ((p8inf = d2i_PKCS8_PRIV_KEY_INFO(NULL, der, der_len)) == NULL
-        || !PKCS8_pkey_get0(NULL, &p, &plen, &alg, p8inf))
-        goto end;
-
-    /* Algorithm parameters must be absent */
-    if ((X509_ALGOR_get0(NULL, &ptype, NULL, alg), ptype != V_ASN1_UNDEF))
-        goto end;
-    if (OBJ_obj2nid(alg->algorithm) != ctx->desc->evp_type)
-        goto end;
-    if ((key = ossl_ml_dsa_key_new(libctx, ctx->propq,
-                                   ctx->desc->keytype_name)) == NULL)
-        goto end;
-
-    if (!ossl_ml_dsa_sk_decode(key, p, plen)
-            || !ossl_ml_dsa_key_public_from_private(key))
-        goto end;
-    ret = key;
- end:
-    PKCS8_PRIV_KEY_INFO_free(p8inf);
-    if (ret == NULL)
-        ossl_ml_dsa_key_free(key);
-    return ret;
+    ML_DSA_KEY *key;
+
+    key = ossl_ml_dsa_d2i_PKCS8(*der, der_len, ctx->desc->evp_type,
+                                ctx->provctx, ctx->propq);
+    if (key != NULL)
+        *der += der_len;
+    return key;
 }
 
 static ossl_inline void * ml_dsa_d2i_PUBKEY(const uint8_t **der, long der_len,
                                             struct der2key_ctx_st *ctx)
 {
-    int ok = 0;
-    OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(ctx->provctx);
-    ML_DSA_KEY *ret = NULL;
-    BARE_PUBKEY *spki = NULL;
-    const uint8_t *end = *der;
-    size_t len;
-
-    ret = ossl_ml_dsa_key_new(libctx, ctx->propq, ctx->desc->keytype_name);
-    if (ret == NULL)
-        return NULL;
-    len = ossl_ml_dsa_key_get_pub_len(ret);
-
-    /*-
-     * The DER ASN.1 encoding of ML-DSA public keys prepends 22 bytes to the
-     * encoded public key:
-     *
-     * - 4 byte outer sequence tag and length
-     * -  2 byte algorithm sequence tag and length
-     * -    2 byte algorithm OID tag and length
-     * -      9 byte algorithm OID
-     * -  4 byte bit string tag and length
-     * -    1 bitstring lead byte
-     *
-     * Check that we have the right OID, the bit string has no "bits left" and
-     * that we consume all the input exactly.
-     */
-    if (der_len != 22 + (long)len) {
-        ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING,
-                       "unexpected %s public key length: %ld != %ld",
-                       ctx->desc->keytype_name, der_len,
-                       22 + (long)len);
-        goto err;
-    }
-
-    if ((spki = OPENSSL_zalloc(sizeof(*spki))) == NULL)
-        goto err;
-
-    /* The spki storage is freed on error */
-    if (ASN1_item_d2i_ex((ASN1_VALUE **)&spki, &end, der_len,
-                         ASN1_ITEM_rptr(BARE_PUBKEY), NULL, NULL) == NULL) {
-        ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING,
-                       "malformed %s public key ASN.1 encoding",
-                       ossl_ml_dsa_key_get_name(ret));
-        goto err;
-    }
-
-    /* The spki structure now owns some memory */
-    if ((spki->pubkey->flags & 0x7) != 0 || end != *der + der_len) {
-        ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING,
-                       "malformed %s public key ASN.1 encoding",
-                       ossl_ml_dsa_key_get_name(ret));
-        goto err;
-    }
-    if (OBJ_cmp(OBJ_nid2obj(ctx->desc->evp_type), spki->algor.oid) != 0) {
-        ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING,
-                       "unexpected algorithm OID for an %s public key",
-                       ossl_ml_dsa_key_get_name(ret));
-        goto err;
-    }
+    ML_DSA_KEY *key;
 
-    if (!ossl_ml_dsa_pk_decode(ret, spki->pubkey->data, spki->pubkey->length)) {
-        ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING,
-                       "failed to parse %s public key from the input data",
-                       ossl_ml_dsa_key_get_name(ret));
-        goto err;
-    }
-    ok = 1;
- err:
-    if (spki != NULL) {
-        ASN1_OBJECT_free(spki->algor.oid);
-        ASN1_BIT_STRING_free(spki->pubkey);
-        OPENSSL_free(spki);
-    }
-    if (!ok) {
-        ossl_ml_dsa_key_free(ret);
-        ret = NULL;
-    }
-    return ret;
+    key = ossl_ml_dsa_d2i_PUBKEY(*der, der_len, ctx->desc->evp_type,
+                                 ctx->provctx, ctx->propq);
+    if (key != NULL)
+        *der += der_len;
+    return key;
 }
 
 # define ml_dsa_44_evp_type                EVP_PKEY_ML_DSA_44
index 0ca75b9af7e2b40fdc75ab7985a0b5ea88624a2c..9f43b17f840bc40bb74dced4ac6120d0ef099672 100644 (file)
@@ -36,6 +36,7 @@
 #include "prov/provider_ctx.h"
 #include "prov/der_rsa.h"
 #include "endecoder_local.h"
+#include "ml_dsa_codecs.h"
 
 #if defined(OPENSSL_NO_DH) && defined(OPENSSL_NO_DSA) && defined(OPENSSL_NO_EC)
 # define OPENSSL_NO_KEYPARAMS
@@ -851,40 +852,15 @@ static int ecx_pki_priv_to_der(const void *vecxkey, unsigned char **pder,
 static int ml_dsa_spki_pub_to_der(const void *vkey, unsigned char **pder,
                                   ossl_unused void *ctx)
 {
-    const ML_DSA_KEY *key = vkey;
-    size_t publen;
-
-    if (ossl_ml_dsa_key_get_pub(key) == NULL) {
-        ERR_raise(ERR_LIB_PROV, ERR_R_PASSED_NULL_PARAMETER);
-        return 0;
-    }
-    publen = ossl_ml_dsa_key_get_pub_len(key);
-
-    if (pder != NULL
-            && ((*pder = OPENSSL_memdup(ossl_ml_dsa_key_get_pub(key),
-                                        publen)) == NULL))
-        return 0;
-
-    return publen;
+    return ossl_ml_dsa_i2d_pubkey(vkey, pder);
 }
 
 static int ml_dsa_pki_priv_to_der(const void *vkey, unsigned char **pder,
-                                  ossl_unused void *ctx)
+                                  void *vctx)
 {
-    const ML_DSA_KEY *key = vkey;
-    size_t len;
-
-    if (ossl_ml_dsa_key_get_priv(key) == NULL) {
-        ERR_raise(ERR_LIB_PROV, ERR_R_PASSED_NULL_PARAMETER);
-        return 0;
-    }
-    len = ossl_ml_dsa_key_get_priv_len(key);
-
-    if (pder != NULL
-            && ((*pder = OPENSSL_memdup(ossl_ml_dsa_key_get_priv(key), len)) == NULL))
-        return 0;
+    KEY2ANY_CTX *ctx = vctx;
 
-    return len;
+    return ossl_ml_dsa_i2d_prvkey(vkey, pder, ctx->provctx);
 }
 
 # define ml_dsa_epki_priv_to_der ml_dsa_pki_priv_to_der
index 9db121f551bd27ed11735369876ff64564f226f3..e50eff4c205dc90aa8c153d1d94266b8556bfead 100644 (file)
@@ -29,6 +29,7 @@
 #include "prov/implementations.h"
 #include "internal/encoder.h"
 #include "endecoder_local.h"
+#include "ml_dsa_codecs.h"
 
 DEFINE_SPECIAL_STACK_OF_CONST(BIGNUM_const, BIGNUM)
 
@@ -591,7 +592,7 @@ static int rsa_to_text(BIO *out, const void *key, int selection)
 #ifndef OPENSSL_NO_ML_DSA
 static int ml_dsa_to_text(BIO *out, const void *key, int selection)
 {
-    return ossl_ml_dsa_to_text(out, (ML_DSA_KEY *)key, selection);
+    return ossl_ml_dsa_key_to_text(out, (ML_DSA_KEY *)key, selection);
 }
 #endif /* OPENSSL_NO_ML_DSA */
 /* ---------------------------------------------------------------------- */
diff --git a/providers/implementations/encode_decode/ml_dsa_codecs.c b/providers/implementations/encode_decode/ml_dsa_codecs.c
new file mode 100644 (file)
index 0000000..365133d
--- /dev/null
@@ -0,0 +1,575 @@
+/*
+ * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <string.h>
+#include <openssl/byteorder.h>
+#include <openssl/err.h>
+#include <openssl/proverr.h>
+#include <openssl/x509.h>
+#include <openssl/core_names.h>
+#include "internal/encoder.h"
+#include "ml_dsa_codecs.h"
+
+/*-
+ * Tables describing supported ASN.1 input/output formats.
+ * For each parameter set we support a few PKCS#8 input formats, three
+ * corresponding to the "either or both" variants of:
+ *
+ *  ML-DSA-PrivateKey ::= CHOICE {
+ *    seed [0] IMPLICIT OCTET STRING SIZE (32),
+ *    expandedKey OCTET STRING SIZE (2560 | 4032 | 4896)
+ *    both SEQUENCE {
+ *      seed OCTET STRING SIZE (32),
+ *      expandedKey OCTET STRING SIZE (2560 | 4032 | 4896) } }
+ *
+ * one more for a historical OQS encoding:
+ *
+ * - OQS private + public key: OCTET STRING
+ *   (The public key is ignored, just as with PKCS#8 v2.)
+ *
+ * and two more that are the minimal IETF non-ASN.1 seed encoding:
+ *
+ * - Bare seed (just the 32 bytes)
+ * - Bare priv (just the key bytes)
+ *
+ * A length of zero means that particular field is absent.
+ *
+ * The p8_shift is 0 when the top-level tag+length occupy four bytes, 2 when
+ * they occupy two by†es, and 4 when no tag is used at all.
+ *
+ * On output the PKCS8 info table order is important:
+ * - When we have a seed we'll use the first entry with a non-zero seed offset.
+ * - Otherwise, the first entry with a zero seed offset.
+ *
+ * As written, when possible, we prefer to output both the seed and private
+ * key, otherwise, just the private key ([1] IMPLICIT OCTET STRING form).
+ *
+ * The various lengths in the PKCS#8 tag/len fields could have been left
+ * zeroed, and filled in on the fly from the algorithm parameters, but that
+ * makes the code more complex, so a choice was made to embed them directly
+ * into the tables.  Had they been zeroed, one table could cover all three
+ * ML-DSA parameter sets.
+ */
+#define NUM_PKCS8_FORMATS   6
+
+/*-
+ * ML-DSA-44:
+ * Public key bytes:  1312 (0x0520)
+ * Private key bytes: 2560 (0x0a00)
+ */
+static const ML_DSA_SPKI_FMT ml_dsa_44_spkifmt = {
+    { 0x30, 0x82, 0x05, 0x32, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48,
+      0x01, 0x65, 0x03, 0x04, 0x03, 0x11, 0x03, 0x82, 0x05, 0x21, 0x00, }
+};
+static const ML_DSA_PKCS8_FMT ml_dsa_44_p8fmt[NUM_PKCS8_FORMATS] = {
+    { "seed-priv",  0x0a2a, 0, 0x30820a26, 0x0420, 6, 0x20, 0x04820a00, 0x2a, 0x0a00, 0,      0,     },
+    { "priv-only",  0x0a04, 0, 0x04820a00, 0,      0, 0,    0,          0x04, 0x0a00, 0,      0,     },
+    { "oqskeypair", 0x0f24, 0, 0x04820f20, 0,      0, 0,    0,          0x04, 0x0a00, 0x0a04, 0x0520 },
+    { "seed-only",  0x0022, 2, 0x8020,     0,      2, 0x20, 0,          0,    0,      0,      0,     },
+    { "bare-priv",  0x0a00, 4, 0,          0,      0, 0,    0,          0,    0x0a00, 0,      0,     },
+    { "bare-seed",  0x0020, 4, 0,          0,      0, 0x20, 0,          0,    0,      0,      0,     },
+};
+
+/*
+ * ML-DSA-65:
+ * Public key bytes:  1952 (0x07a0)
+ * Private key bytes: 4032 (0x0fc0)
+ */
+static const ML_DSA_SPKI_FMT ml_dsa_65_spkifmt = {
+    { 0x30, 0x82, 0x07, 0xb2, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48,
+      0x01, 0x65, 0x03, 0x04, 0x03, 0x12, 0x03, 0x82, 0x07, 0xa1, 0x00, }
+};
+static const ML_DSA_PKCS8_FMT ml_dsa_65_p8fmt[NUM_PKCS8_FORMATS] = {
+    { "seed-priv",  0x0fea, 0, 0x30820fe6, 0x0420, 6, 0x20, 0x04820fc0, 0x2a, 0x0fc0, 0,      0,     },
+    { "priv-only",  0x0fc4, 0, 0x04820fc0, 0,      0, 0,    0,          0x04, 0x0fc0, 0,      0,     },
+    { "oqskeypair", 0x1764, 0, 0x04821760, 0,      0, 0,    0,          0x04, 0x0fc0, 0x0fc4, 0x07a0 },
+    { "seed-only",  0x0022, 2, 0x8020,     0,      2, 0x20, 0,          0,    0,      0,      0,     },
+    { "bare-priv",  0x0fc0, 4, 0,          0,      0, 0,    0,          0,    0x0fc0, 0,      0,     },
+    { "bare-seed",  0x0020, 4, 0,          0,      0, 0x20, 0,          0,    0,      0,      0,     },
+};
+
+/*-
+ * ML-DSA-87:
+ * Public key bytes:  2592 (0x0a20)
+ * Private key bytes: 4896 (0x1320)
+ */
+static const ML_DSA_SPKI_FMT ml_dsa_87_spkifmt = {
+    { 0x30, 0x82, 0x0a, 0x32, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48,
+      0x01, 0x65, 0x03, 0x04, 0x03, 0x13, 0x03, 0x82, 0x0a, 0x21, 0x00, }
+};
+static const ML_DSA_PKCS8_FMT ml_dsa_87_p8fmt[NUM_PKCS8_FORMATS] = {
+    { "seed-priv",  0x134a, 0, 0x30821346, 0x0420, 6, 0x20, 0x04821320, 0x2a, 0x1320, 0,      0,     },
+    { "priv-only",  0x1324, 0, 0x04821320, 0,      0, 0,    0,          0x04, 0x1320, 0,      0,     },
+    { "oqskeypair", 0x1d44, 0, 0x04821d40, 0,      0, 0,    0,          0x04, 0x1320, 0x1324, 0x0a20 },
+    { "seed-only",  0x0022, 2, 0x8020,     0,      2, 0x20, 0,          0,    0,      0,      0,     },
+    { "bare-priv",  0x1320, 4, 0,          0,      0, 0,    0,          0,    0x1320, 0,      0,     },
+    { "bare-seed",  0x0020, 4, 0,          0,      0, 0x20, 0,          0,    0,      0,      0,     },
+};
+
+/* Indices of slots in the codec table below */
+#define ML_DSA_44_CODEC    0
+#define ML_DSA_65_CODEC    1
+#define ML_DSA_87_CODEC    2
+
+/*
+ * Per-variant fixed parameters
+ */
+static const ML_DSA_CODEC codecs[3] = {
+    { &ml_dsa_44_spkifmt, ml_dsa_44_p8fmt },
+    { &ml_dsa_65_spkifmt, ml_dsa_65_p8fmt },
+    { &ml_dsa_87_spkifmt, ml_dsa_87_p8fmt }
+};
+
+/* Retrieve the parameters of one of the ML-DSA variants */
+static const ML_DSA_CODEC *ml_dsa_get_codec(int evp_type)
+{
+    switch (evp_type) {
+    case EVP_PKEY_ML_DSA_44:
+        return &codecs[ML_DSA_44_CODEC];
+    case EVP_PKEY_ML_DSA_65:
+        return &codecs[ML_DSA_65_CODEC];
+    case EVP_PKEY_ML_DSA_87:
+        return &codecs[ML_DSA_87_CODEC];
+    }
+    return NULL;
+}
+
+static int pref_cmp(const void *va, const void *vb)
+{
+    const ML_DSA_PKCS8_FMT_PREF *a = va;
+    const ML_DSA_PKCS8_FMT_PREF *b = vb;
+
+    /*
+     * Zeros sort last, otherwise the sort is in increasing order.
+     *
+     * The preferences are small enough to ensure the comparison is transitive
+     * as required by qsort(3).  When overflow or underflow is possible, the
+     * correct transitive comparison would be: (b < a) - (a < b).
+     */
+    if (a->pref > 0 && b->pref > 0)
+        return a->pref - b->pref;
+    /* A preference of 0 is "larger" than (sorts after) any nonzero value. */
+    return b->pref - a->pref;
+}
+
+static
+ML_DSA_PKCS8_FMT_PREF *vp8_order(const char *algorithm_name,
+                                 const ML_DSA_PKCS8_FMT *p8fmt,
+                                 const char *direction, const char *formats)
+{
+    ML_DSA_PKCS8_FMT_PREF *ret;
+    int i, count = 0;
+    const char *fmt = formats, *end;
+    const char *sep = "\t ,";
+
+    /* Reserve an extra terminal slot with fmt == NULL */
+    if ((ret = OPENSSL_zalloc((NUM_PKCS8_FORMATS + 1) * sizeof(*ret))) == NULL)
+        return NULL;
+
+    /* Entries that match a format will get a non-zero preference. */
+    for (i = 0; i < NUM_PKCS8_FORMATS; ++i) {
+        ret[i].fmt = &p8fmt[i];
+        ret[i].pref = 0;
+    }
+
+    /* Default to compile-time table order when none specified. */
+    if (formats == NULL)
+        return ret;
+
+    /*
+     * Formats are case-insensitive, separated by spaces, tabs or commas.
+     * Duplicate formats are allowed, the first occurence determines the order.
+     */
+    do {
+        if (*(fmt += strspn(fmt, sep)) == '\0')
+            break;
+        end = fmt + strcspn(fmt, sep);
+        for (i = 0; i < NUM_PKCS8_FORMATS; ++i) {
+            /* Skip slots already selected or with a different name. */
+            if (ret[i].pref > 0
+                || OPENSSL_strncasecmp(ret[i].fmt->p8_name,
+                                       fmt, (end - fmt)) != 0)
+                continue;
+            /* First time match */
+            ret[i].pref = ++count;
+            break;
+        }
+        fmt = end;
+    } while (count < NUM_PKCS8_FORMATS);
+
+    /* No formats matched, raise an error */
+    if (count == 0) {
+        OPENSSL_free(ret);
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_DSA_NO_FORMAT,
+                       "no %s private key %s formats are enabled",
+                       algorithm_name, direction);
+        return NULL;
+    }
+    /* Sort by preference, with 0's last */
+    qsort(ret, NUM_PKCS8_FORMATS, sizeof(*ret), pref_cmp);
+    /* Terminate the list at first unselected entry, perhaps reserved slot. */
+    ret[count].fmt = NULL;
+    return ret;
+}
+
+ML_DSA_KEY *
+ossl_ml_dsa_d2i_PUBKEY(const uint8_t *pk, int pk_len, int evp_type,
+                       PROV_CTX *provctx, const char *propq)
+{
+    OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx);
+    const ML_DSA_CODEC *codec;
+    const ML_DSA_PARAMS *params;
+    ML_DSA_KEY *ret;
+
+    if ((params = ossl_ml_dsa_params_get(evp_type)) == NULL
+        || (codec = ml_dsa_get_codec(evp_type)) == NULL)
+        return NULL;
+    if (pk_len != ML_DSA_SPKI_OVERHEAD + (ossl_ssize_t) params->pk_len
+        || memcmp(pk, codec->spkifmt->asn1_prefix, ML_DSA_SPKI_OVERHEAD) != 0)
+        return NULL;
+    pk_len -= ML_DSA_SPKI_OVERHEAD;
+    pk += ML_DSA_SPKI_OVERHEAD;
+
+    if ((ret = ossl_ml_dsa_key_new(libctx, propq, evp_type)) == NULL)
+        return NULL;
+
+    if (!ossl_ml_dsa_pk_decode(ret, pk, (size_t) pk_len)) {
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_BAD_ENCODING,
+                       "errror parsing %s public key from input SPKI",
+                       params->alg);
+        ossl_ml_dsa_key_free(ret);
+        return NULL;
+    }
+
+    return ret;
+}
+
+ML_DSA_KEY *
+ossl_ml_dsa_d2i_PKCS8(const uint8_t *prvenc, int prvlen,
+                      int evp_type, PROV_CTX *provctx,
+                      const char *propq)
+{
+    OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx);
+    const ML_DSA_PARAMS *v;
+    const ML_DSA_CODEC *codec;
+    ML_DSA_PKCS8_FMT_PREF *vp8_alloc = NULL, *slot;
+    const ML_DSA_PKCS8_FMT *p8fmt;
+    ML_DSA_KEY *key = NULL, *ret = NULL;
+    PKCS8_PRIV_KEY_INFO *p8inf = NULL;
+    const uint8_t *buf, *pos;
+    const X509_ALGOR *alg = NULL;
+    const char *formats;
+    int len, ptype, retain, prefer;
+    uint32_t magic;
+    uint16_t seed_magic;
+    const uint8_t *seed = NULL;
+    const uint8_t *priv = NULL;
+
+    /* Which ML-DSA variant? */
+    if ((v = ossl_ml_dsa_params_get(evp_type)) == NULL
+        || (codec = ml_dsa_get_codec(evp_type)) == NULL)
+        return 0;
+
+    /* Extract the key OID and any parameters. */
+    if ((p8inf = d2i_PKCS8_PRIV_KEY_INFO(NULL, &prvenc, prvlen)) == NULL)
+        return 0;
+    /* Shortest prefix is 4 bytes: seq tag/len  + octet string tag/len */
+    if (!PKCS8_pkey_get0(NULL, &buf, &len, &alg, p8inf))
+        goto end;
+    /* Bail out early if this is some other key type. */
+    if (OBJ_obj2nid(alg->algorithm) != evp_type)
+        goto end;
+
+    /* Get the list of enabled decoders. Their order is not important here. */
+    formats = ossl_prov_ctx_get_param(
+        provctx, OSSL_PKEY_PARAM_ML_DSA_INPUT_FORMATS, NULL);
+    vp8_alloc = vp8_order(v->alg, codec->p8fmt, "input", formats);
+    if (vp8_alloc == NULL)
+        goto end;
+
+    /* Parameters must be absent. */
+    X509_ALGOR_get0(NULL, &ptype, NULL, alg);
+    if (ptype != V_ASN1_UNDEF) {
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_UNEXPECTED_KEY_PARAMETERS,
+                       "unexpected parameters with a PKCS#8 %s private key",
+                       v->alg);
+        goto end;
+    }
+    if ((ossl_ssize_t)len < (ossl_ssize_t)sizeof(magic))
+        goto end;
+
+    /* Find the matching p8 info slot, that also has the expected length. */
+    pos = OPENSSL_load_u32_be(&magic, buf);
+    for (slot = vp8_alloc; (p8fmt = slot->fmt) != NULL; ++slot) {
+        if (len != (ossl_ssize_t)p8fmt->p8_bytes)
+            continue;
+        if (p8fmt->p8_shift == sizeof(magic)
+            || (magic >> (p8fmt->p8_shift * 8)) == p8fmt->p8_magic) {
+            pos -= p8fmt->p8_shift;
+            break;
+        }
+    }
+    if (p8fmt == NULL
+        || (p8fmt->seed_length > 0 && p8fmt->seed_length != ML_DSA_SEED_BYTES)
+        || (p8fmt->priv_length > 0 && p8fmt->priv_length != v->sk_len)
+        || (p8fmt->pub_length > 0 && p8fmt->pub_length != v->pk_len)) {
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_DSA_NO_FORMAT,
+                       "no matching enabled %s private key input formats",
+                       v->alg);
+        goto end;
+    }
+
+    if (p8fmt->seed_length > 0) {
+        /* Check |seed| tag/len, if not subsumed by |magic|. */
+        if (pos + sizeof(uint16_t) == buf + p8fmt->seed_offset) {
+            pos = OPENSSL_load_u16_be(&seed_magic, pos);
+            if (seed_magic != p8fmt->seed_magic)
+                goto end;
+        } else if (pos != buf + p8fmt->seed_offset) {
+            goto end;
+        }
+        pos += ML_DSA_SEED_BYTES;
+    }
+    if (p8fmt->priv_length > 0) {
+        /* Check |priv| tag/len */
+        if (pos + sizeof(uint32_t) == buf + p8fmt->priv_offset) {
+            pos = OPENSSL_load_u32_be(&magic, pos);
+            if (magic != p8fmt->priv_magic)
+                goto end;
+        } else if (pos != buf + p8fmt->priv_offset) {
+            goto end;
+        }
+        pos += v->sk_len;
+    }
+    if (p8fmt->pub_length > 0) {
+        if (pos != buf + p8fmt->pub_offset)
+            goto end;
+        pos += v->pk_len;
+    }
+    if (pos != buf + len)
+        goto end;
+
+    /*
+     * Collect the seed and/or key into a "decoded" private key object,
+     * to be turned into a real key on provider "load" or "import".
+     */
+    if ((key = ossl_ml_dsa_key_new(libctx, propq, evp_type)) == NULL)
+        goto end;
+    if (p8fmt->seed_length > 0)
+        seed = buf + p8fmt->seed_offset;
+    if (p8fmt->priv_length > 0)
+        priv = buf + p8fmt->priv_offset;
+    /* Any OQS public key content is ignored */
+
+    /*
+     * If the key ends up "loaded" into the same provider, these are the
+     * correct config settings, otherwise, new values will be assigned on
+     * import into a different provider.  The "load" API does not pass along
+     * the provider context.
+     */
+    retain =
+        ossl_prov_ctx_get_bool_param(
+            provctx, OSSL_PKEY_PARAM_ML_DSA_RETAIN_SEED, 1);
+    prefer =
+        ossl_prov_ctx_get_bool_param(
+            provctx, OSSL_PKEY_PARAM_ML_DSA_PREFER_SEED, 1);
+    if (ossl_ml_dsa_set_prekey(key, prefer, retain,
+                               seed, ML_DSA_SEED_BYTES, priv, v->sk_len))
+        ret = key;
+
+  end:
+    OPENSSL_free(vp8_alloc);
+    PKCS8_PRIV_KEY_INFO_free(p8inf);
+    if (ret == NULL)
+        ossl_ml_dsa_key_free(key);
+    return ret;
+}
+
+/* Same as ossl_ml_dsa_encode_pubkey, but allocates the output buffer. */
+int ossl_ml_dsa_i2d_pubkey(const ML_DSA_KEY *key, unsigned char **out)
+{
+    const ML_DSA_PARAMS *params = ossl_ml_dsa_key_params(key);
+    const uint8_t *pk = ossl_ml_dsa_key_get_pub(key);
+
+    if (pk == NULL) {
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PUBLIC_KEY,
+                       "no %s public key data available", params->alg);
+        return 0;
+    }
+    if (out != NULL
+        && (*out = OPENSSL_memdup(pk, params->pk_len)) == NULL)
+        return 0;
+    return (int)params->pk_len;
+}
+
+/* Allocate and encode PKCS#8 private key payload. */
+int ossl_ml_dsa_i2d_prvkey(const ML_DSA_KEY *key, uint8_t **out,
+                           PROV_CTX *provctx)
+{
+    const ML_DSA_PARAMS *params = ossl_ml_dsa_key_params(key);
+    const ML_DSA_CODEC *codec;
+    ML_DSA_PKCS8_FMT_PREF *vp8_alloc, *slot;
+    const ML_DSA_PKCS8_FMT *p8fmt;
+    uint8_t *buf = NULL, *pos;
+    const char *formats;
+    int len = ML_DSA_SEED_BYTES;
+    int ret = 0;
+    const uint8_t *seed = ossl_ml_dsa_key_get_seed(key);
+    const uint8_t *sk = ossl_ml_dsa_key_get_priv(key);
+
+    /* Not ours to handle */
+    if ((codec = ml_dsa_get_codec(params->evp_type)) == NULL)
+        return 0;
+
+    if (sk == NULL) {
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_NOT_A_PRIVATE_KEY,
+                       "no %s private key data available",
+                       params->alg);
+        return 0;
+    }
+
+    formats = ossl_prov_ctx_get_param(
+        provctx, OSSL_PKEY_PARAM_ML_DSA_OUTPUT_FORMATS, NULL);
+    vp8_alloc = vp8_order(params->alg, codec->p8fmt, "output", formats);
+    if (vp8_alloc == NULL)
+        return 0;
+
+    /* If we don't have a seed, skip seedful entries */
+    for (slot = vp8_alloc; (p8fmt = slot->fmt) != NULL; ++slot)
+        if (seed != NULL || p8fmt->seed_length == 0)
+            break;
+    /* No matching table entries, give up */
+    if (p8fmt == NULL
+        || (p8fmt->seed_length > 0 && p8fmt->seed_length != ML_DSA_SEED_BYTES)
+        || (p8fmt->priv_length > 0 && p8fmt->priv_length != params->sk_len)
+        || (p8fmt->pub_length > 0 && p8fmt->pub_length != params->pk_len)) {
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_ML_DSA_NO_FORMAT,
+                       "no matching enabled %s private key output formats",
+                       params->alg);
+        goto end;
+    }
+    len = p8fmt->p8_bytes;
+
+    if (out == NULL) {
+        ret = len;
+        goto end;
+    }
+
+    if ((pos = buf = OPENSSL_malloc((size_t) len)) == NULL)
+        goto end;
+
+    switch (p8fmt->p8_shift) {
+    case 0:
+        pos = OPENSSL_store_u32_be(pos, p8fmt->p8_magic);
+        break;
+    case 2:
+        pos = OPENSSL_store_u16_be(pos, (uint16_t)p8fmt->p8_magic);
+        break;
+    case 4:
+        break;
+    default:
+        ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+                       "error encoding %s private key", params->alg);
+        goto end;
+    }
+
+    if (p8fmt->seed_length != 0) {
+        /*
+         * Either the tag/len were already included in |magic| or they require
+         * us to write two bytes now.
+         */
+        if (pos + sizeof(uint16_t) == buf + p8fmt->seed_offset)
+            pos = OPENSSL_store_u16_be(pos, p8fmt->seed_magic);
+        if (pos != buf + p8fmt->seed_offset) {
+            ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+                           "error encoding %s private key", params->alg);
+            goto end;
+        }
+        memcpy(pos, seed, ML_DSA_SEED_BYTES);
+        pos += ML_DSA_SEED_BYTES;
+    }
+    if (p8fmt->priv_length != 0) {
+        if (pos + sizeof(uint32_t) == buf + p8fmt->priv_offset)
+            pos = OPENSSL_store_u32_be(pos, p8fmt->priv_magic);
+        if (pos != buf + p8fmt->priv_offset) {
+            ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+                           "error encoding %s private key", params->alg);
+            goto end;
+        }
+        memcpy(pos, sk, params->sk_len);
+        pos += params->sk_len;
+    }
+    /* OQS form output with tacked-on public key */
+    if (p8fmt->pub_length != 0) {
+        /* The OQS pubkey is never separately DER-wrapped */
+        if (pos != buf + p8fmt->pub_offset) {
+            ERR_raise_data(ERR_LIB_PROV, ERR_R_INTERNAL_ERROR,
+                           "error encoding %s private key", params->alg);
+            goto end;
+        }
+        memcpy(pos, ossl_ml_dsa_key_get_pub(key), params->pk_len);
+        pos += params->pk_len;
+    }
+
+    if (pos == buf + len) {
+        *out = buf;
+        ret = len;
+    }
+
+  end:
+    OPENSSL_free(vp8_alloc);
+    if (ret == 0)
+        OPENSSL_free(buf);
+    return ret;
+}
+
+int ossl_ml_dsa_key_to_text(BIO *out, const ML_DSA_KEY *key, int selection)
+{
+    const ML_DSA_PARAMS *params;
+    const uint8_t *seed, *sk, *pk;
+
+    if (out == NULL || key == NULL) {
+        ERR_raise(ERR_LIB_PROV, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+    params = ossl_ml_dsa_key_params(key);
+    pk = ossl_ml_dsa_key_get_pub(key);
+    sk = ossl_ml_dsa_key_get_priv(key);
+    seed = ossl_ml_dsa_key_get_seed(key);
+
+    if (pk == NULL) {
+        /* Regardless of the |selection|, there must be a public key */
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_KEY,
+                       "no %s key material available", params->alg);
+        return 0;
+    }
+
+    if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0) {
+        if (sk == NULL) {
+            ERR_raise_data(ERR_LIB_PROV, PROV_R_MISSING_KEY,
+                           "no %s key material available", params->alg);
+            return 0;
+        }
+        if (BIO_printf(out, "%s Private-Key:\n", params->alg) <= 0)
+            return 0;
+        if (seed != NULL && !ossl_bio_print_labeled_buf(out, "seed:", seed,
+                                                        ML_DSA_SEED_BYTES))
+            return 0;
+        if (!ossl_bio_print_labeled_buf(out, "priv:", sk, params->sk_len))
+            return 0;
+    } else if ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0) {
+        if (BIO_printf(out, "%s Public-Key:\n", params->alg) <= 0)
+            return 0;
+    }
+
+    if (!ossl_bio_print_labeled_buf(out, "pub:", pk, params->pk_len))
+        return 0;
+
+    return 1;
+}
diff --git a/providers/implementations/encode_decode/ml_dsa_codecs.h b/providers/implementations/encode_decode/ml_dsa_codecs.h
new file mode 100644 (file)
index 0000000..4bbcaae
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#ifndef PROV_ML_DSA_CODECS_H
+# define PROV_ML_DSA_CODECS_H
+# pragma once
+
+# include <openssl/e_os2.h>
+# include "crypto/ml_dsa.h"
+# include "prov/provider_ctx.h"
+
+ /*-
+  * The DER ASN.1 encoding of ML-KEM (and ML-DSA) public keys prepends 22 bytes
+  * to the encoded public key:
+  *
+  * - 4 byte outer sequence tag and length
+  * -  2 byte algorithm sequence tag and length
+  * -    2 byte algorithm OID tag and length
+  * -      9 byte algorithm OID (from NIST CSOR OID arc)
+  * -  4 byte bit string tag and length
+  * -    1 bitstring lead byte
+  */
+# define ML_DSA_SPKI_OVERHEAD   22
+typedef struct {
+    const uint8_t asn1_prefix[ML_DSA_SPKI_OVERHEAD];
+} ML_DSA_SPKI_FMT;
+
+/*-
+* For each parameter set we support a few PKCS#8 input formats, three
+ * corresponding to the "either or both" variants of:
+ *
+ *  ML-DSA-PrivateKey ::= CHOICE {
+ *    seed [0] IMPLICIT OCTET STRING SIZE (32),
+ *    expandedKey OCTET STRING SIZE (2560 | 4032 | 4896)
+ *    both SEQUENCE {
+ *      seed OCTET STRING SIZE (32),
+ *      expandedKey OCTET STRING SIZE (2560 | 4032 | 4896) } }
+ *
+ * one more for a historical OQS encoding:
+ *
+ * - OQS private + public key: OCTET STRING
+ *   (The public key is ignored, just as with PKCS#8 v2.)
+ *
+ * and two more that are the minimal IETF non-ASN.1 seed encoding:
+ *
+ * - Bare seed (just the 32 bytes)
+ * - Bare priv (just the key bytes)
+ *
+ * A length of zero means that particular field is absent.
+ *
+ * The p8_shift is 0 when the top-level tag+length occupy four bytes, 2 when
+ * they occupy two by†es, and 4 when no tag is used at all.
+ */
+typedef struct {
+    const char *p8_name;    /* Format name */
+    size_t p8_bytes;        /* Total P8 encoding length */
+    int    p8_shift;        /* 4 - (top-level tag + len) */
+    uint32_t p8_magic;      /* The tag + len value */
+    uint16_t seed_magic;    /* Interior tag + len for the seed */
+    size_t seed_offset;     /* Seed offset from start */
+    size_t seed_length;     /* Seed bytes */
+    uint32_t priv_magic;    /* Interior tag + len for the key */
+    size_t priv_offset;     /* Key offset from start */
+    size_t priv_length;     /* Key bytes */
+    size_t pub_offset;      /* Pubkey offset */
+    size_t pub_length;      /* Pubkey bytes */
+} ML_DSA_PKCS8_FMT;
+
+typedef struct {
+    const ML_DSA_SPKI_FMT *spkifmt;
+    const ML_DSA_PKCS8_FMT *p8fmt;
+} ML_DSA_CODEC;
+
+typedef struct {
+    const ML_DSA_PKCS8_FMT *fmt;
+    int pref;
+} ML_DSA_PKCS8_FMT_PREF;
+
+__owur
+ML_DSA_KEY *ossl_ml_dsa_d2i_PUBKEY(const uint8_t *pubenc, int publen,
+                                   int evp_type, PROV_CTX *provctx,
+                                   const char *propq);
+__owur
+ML_DSA_KEY *ossl_ml_dsa_d2i_PKCS8(const uint8_t *prvenc, int prvlen,
+                                  int evp_type, PROV_CTX *provctx,
+                                  const char *propq);
+__owur
+int ossl_ml_dsa_key_to_text(BIO *out, const ML_DSA_KEY *key, int selection);
+__owur
+__owur
+int ossl_ml_dsa_i2d_pubkey(const ML_DSA_KEY *key, unsigned char **out);
+__owur
+__owur
+int ossl_ml_dsa_i2d_prvkey(const ML_DSA_KEY *key, unsigned char **out,
+                           PROV_CTX *provctx);
+
+#endif  /* PROV_ML_DSA_CODECS_H */
index 12395bb0896283d6fb782701a0159ae9732102cd..ac837f3f96883522f2c89268b63217ec5c0a3398 100644 (file)
@@ -9,6 +9,7 @@
 
 #include <openssl/core_dispatch.h>
 #include <openssl/core_names.h>
+#include <openssl/evp.h>
 #include <openssl/param_build.h>
 #include <openssl/proverr.h>
 #include <openssl/self_test.h>
@@ -25,7 +26,6 @@ static OSSL_FUNC_keymgmt_import_fn ml_dsa_import;
 static OSSL_FUNC_keymgmt_export_fn ml_dsa_export;
 static OSSL_FUNC_keymgmt_import_types_fn ml_dsa_imexport_types;
 static OSSL_FUNC_keymgmt_export_types_fn ml_dsa_imexport_types;
-static OSSL_FUNC_keymgmt_load_fn ml_dsa_load;
 static OSSL_FUNC_keymgmt_dup_fn ml_dsa_dup_key;
 static OSSL_FUNC_keymgmt_get_params_fn ml_dsa_get_params;
 static OSSL_FUNC_keymgmt_gettable_params_fn ml_dsa_gettable_params;
@@ -34,9 +34,12 @@ static OSSL_FUNC_keymgmt_gen_init_fn ml_dsa_gen_init;
 static OSSL_FUNC_keymgmt_gen_cleanup_fn ml_dsa_gen_cleanup;
 static OSSL_FUNC_keymgmt_gen_set_params_fn ml_dsa_gen_set_params;
 static OSSL_FUNC_keymgmt_gen_settable_params_fn ml_dsa_gen_settable_params;
+#ifndef FIPS_MODULE
+static OSSL_FUNC_keymgmt_load_fn ml_dsa_load;
+#endif
 
 struct ml_dsa_gen_ctx {
-    OSSL_LIB_CTX *libctx;
+    PROV_CTX *provctx;
     char *propq;
     uint8_t entropy[32];
     size_t entropy_len;
@@ -90,12 +93,24 @@ static int ml_dsa_pairwise_test(const ML_DSA_KEY *key)
 }
 #endif
 
-static void *ml_dsa_new_key(void *provctx, const char *alg)
+static void *ml_dsa_new_key(void *provctx, const char *propq,
+                            int evp_type)
 {
+    ML_DSA_KEY *key;
+    int prefer, retain;
+
     if (!ossl_prov_is_running())
         return 0;
 
-    return ossl_ml_dsa_key_new(PROV_LIBCTX_OF(provctx), NULL, alg);
+    key = ossl_ml_dsa_key_new(PROV_LIBCTX_OF(provctx), propq, evp_type);
+    if (key != NULL) {
+        prefer = ossl_prov_ctx_get_bool_param(
+            provctx, OSSL_PKEY_PARAM_ML_DSA_PREFER_SEED, 1);
+        retain = ossl_prov_ctx_get_bool_param(
+            provctx, OSSL_PKEY_PARAM_ML_DSA_RETAIN_SEED, 1);
+        ossl_ml_dsa_set_prekey(key, prefer, retain, NULL, 0, NULL, 0);
+    }
+    return key;
 }
 
 static void ml_dsa_free_key(void *keydata)
@@ -146,6 +161,83 @@ static int ml_dsa_validate(const void *key_data, int selection, int check_type)
     return 1;
 }
 
+/**
+ * @brief Load a ML_DSA key from raw data.
+ *
+ * @param key An ML_DSA key to load into
+ * @param params An array of parameters containing key data.
+ * @param include_private Set to 1 to optionally include the private key data
+ *                        if it exists.
+ * @returns 1 on success, or 0 on failure.
+ */
+static int ml_dsa_key_fromdata(ML_DSA_KEY *key, const OSSL_PARAM params[],
+                               int include_private)
+{
+    const OSSL_PARAM *p = NULL;
+    const ML_DSA_PARAMS *key_params = ossl_ml_dsa_key_params(key);
+    const uint8_t *pk = NULL, *sk = NULL, *seed = NULL;
+    size_t pk_len = 0, sk_len = 0, seed_len = 0;
+
+    p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PUB_KEY);
+    if (p != NULL
+        && !OSSL_PARAM_get_octet_string_ptr(p, (const void **)&pk, &pk_len))
+        return 0;
+    if (pk != NULL && pk_len != key_params->pk_len) {
+        ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY_LENGTH,
+                       "Invalid %s public key length", key_params->alg);
+        return 0;
+    }
+
+    /* Private key is optional */
+    if (include_private) {
+        p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_ML_DSA_SEED);
+        if (p != NULL
+            && !OSSL_PARAM_get_octet_string_ptr(p, (const void **)&seed,
+                                                &seed_len))
+            return 0;
+        if (seed != NULL && seed_len != ML_DSA_SEED_BYTES) {
+            ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_SEED_LENGTH);
+            return 0;
+        }
+        p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PRIV_KEY);
+        if (p != NULL
+            && !OSSL_PARAM_get_octet_string_ptr(p, (const void **)&sk, &sk_len))
+            return 0;
+        if (sk != NULL && sk_len != key_params->sk_len) {
+            ERR_raise_data(ERR_LIB_PROV, PROV_R_INVALID_KEY_LENGTH,
+                           "Invalid %s private key length", key_params->alg);
+            return 0;
+        }
+    }
+
+    /* The caller MUST specify at least one of seed, private or public keys. */
+    if (seed_len == 0 && pk_len == 0 && sk_len == 0) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_KEY);
+        return 0;
+    }
+
+    if (seed_len != 0
+        && (sk_len == 0 || ossl_ml_dsa_key_prefer_seed(key))) {
+        if (!ossl_ml_dsa_set_prekey(key, -1, -1, seed, seed_len, NULL, 0))
+            return 0;
+        if (!ossl_ml_dsa_generate_key(key)) {
+            ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GENERATE_KEY);
+            return 0;
+        }
+    } else if (sk_len > 0) {
+        if (!ossl_ml_dsa_sk_decode(key, sk, sk_len))
+            return 0;
+    } else if (pk_len > 0) {
+        if (!ossl_ml_dsa_pk_decode(key, pk, pk_len))
+            return 0;
+    }
+
+    /* Error if the supplied public key does not match the generated key */
+    return pk_len == 0
+        || seed_len + sk_len == 0
+        || memcmp(ossl_ml_dsa_key_get_pub(key), pk, pk_len) == 0;
+}
+
 static int ml_dsa_import(void *keydata, int selection, const OSSL_PARAM params[])
 {
     ML_DSA_KEY *key = keydata;
@@ -159,7 +251,7 @@ static int ml_dsa_import(void *keydata, int selection, const OSSL_PARAM params[]
         return 0;
 
     include_priv = ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0);
-    res = ossl_ml_dsa_key_fromdata(key, params, include_priv);
+    res = ml_dsa_key_fromdata(key, params, include_priv);
 #ifdef FIPS_MODULE
     if (res > 0) {
         res = ml_dsa_pairwise_test(key);
@@ -247,8 +339,9 @@ static int ml_dsa_export(void *keydata, int selection,
                          OSSL_CALLBACK *param_cb, void *cbarg)
 {
     ML_DSA_KEY *key = keydata;
-    OSSL_PARAM params[2];
-    int include_private;
+    OSSL_PARAM params[3];
+    const uint8_t *buf;
+    int include_private, pnum = 0;
 
     if (!ossl_prov_is_running() || key == NULL)
         return 0;
@@ -258,52 +351,76 @@ static int ml_dsa_export(void *keydata, int selection,
 
     include_private = ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0);
 
-    /* Error if there is no public key */
-    if (ossl_ml_dsa_key_get_pub(key) == NULL)
-        return 0;
-
     /*
-     * Note that the private key always contains the public key elements so we
-     * just save the one blob and return.
+     * Note that the public key can be recovered from the private key, so we
+     * just export one or the other.  If the seed is present, both the seed and
+     * the private key are exported.  The recipient will have a choice.
      */
-    if (include_private && ossl_ml_dsa_key_get_priv(key) != NULL)
-        params[0] = OSSL_PARAM_construct_octet_string
-            (OSSL_PKEY_PARAM_PRIV_KEY, (void *)ossl_ml_dsa_key_get_priv(key),
-             ossl_ml_dsa_key_get_priv_len(key));
-    else
-        params[0] = OSSL_PARAM_construct_octet_string
-            (OSSL_PKEY_PARAM_PUB_KEY, (void *)ossl_ml_dsa_key_get_pub(key),
+    if (include_private) {
+        if ((buf = ossl_ml_dsa_key_get_seed(key)) != NULL) {
+            params[pnum++] = OSSL_PARAM_construct_octet_string
+                (OSSL_PKEY_PARAM_ML_DSA_SEED, (void *)buf, ML_DSA_SEED_BYTES);
+        }
+        if ((buf = ossl_ml_dsa_key_get_priv(key)) != NULL) {
+            params[pnum++] = OSSL_PARAM_construct_octet_string
+                (OSSL_PKEY_PARAM_PRIV_KEY, (void *)buf,
+                 ossl_ml_dsa_key_get_priv_len(key));
+        }
+    }
+    if (pnum == 0 && (buf = ossl_ml_dsa_key_get_pub(key)) != NULL) {
+        params[pnum++] = OSSL_PARAM_construct_octet_string
+            (OSSL_PKEY_PARAM_PUB_KEY, (void *)buf,
              ossl_ml_dsa_key_get_pub_len(key));
-    params[1] = OSSL_PARAM_construct_end();
-
+    }
+    if (pnum == 0)
+        return 0;
+    params[pnum] = OSSL_PARAM_construct_end();
     return param_cb(params, cbarg);
 }
 
+#ifndef FIPS_MODULE
 static void *ml_dsa_load(const void *reference, size_t reference_sz)
 {
-    ML_DSA_KEY *key = NULL;
+    ML_DSA_KEY *ret = NULL, *key = NULL;
+    const uint8_t *sk, *seed;
 
     if (ossl_prov_is_running() && reference_sz == sizeof(key)) {
         /* The contents of the reference is the address to our object */
         key = *(ML_DSA_KEY **)reference;
         /* We grabbed, so we detach it */
         *(ML_DSA_KEY **)reference = NULL;
-        return key;
+        /* All done, if the pubkey is present. */
+        if (key == NULL || ossl_ml_dsa_key_get_pub(key) != NULL)
+            return key;
+        /* Handle private prekey inputs. */
+        sk = ossl_ml_dsa_key_get_priv(key);
+        seed = ossl_ml_dsa_key_get_seed(key);
+        if (seed != NULL
+            && (sk == NULL || ossl_ml_dsa_key_prefer_seed(key))) {
+            if (ossl_ml_dsa_generate_key(key))
+                ret = key;
+        } else if (sk != NULL) {
+            if (ossl_ml_dsa_sk_decode(key, sk,
+                                      ossl_ml_dsa_key_get_priv_len(key)))
+                ret = key;
+        }
     }
-    return NULL;
+    if (ret == NULL)
+        ossl_ml_dsa_key_free(key);
+    return ret;
 }
+#endif
 
 static void *ml_dsa_gen_init(void *provctx, int selection,
                              const OSSL_PARAM params[])
 {
-    OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx);
     struct ml_dsa_gen_ctx *gctx = NULL;
 
     if (!ossl_prov_is_running())
         return NULL;
 
     if ((gctx = OPENSSL_zalloc(sizeof(*gctx))) != NULL) {
-        gctx->libctx = libctx;
+        gctx->provctx = provctx;
         if (!ml_dsa_gen_set_params(gctx, params)) {
             OPENSSL_free(gctx);
             gctx = NULL;
@@ -312,18 +429,21 @@ static void *ml_dsa_gen_init(void *provctx, int selection,
     return gctx;
 }
 
-static void *ml_dsa_gen(void *genctx, const char *alg)
+static void *ml_dsa_gen(void *genctx, int evp_type)
 {
     struct ml_dsa_gen_ctx *gctx = genctx;
     ML_DSA_KEY *key = NULL;
 
     if (!ossl_prov_is_running())
         return NULL;
-    key = ossl_ml_dsa_key_new(gctx->libctx, gctx->propq, alg);
+    key = ml_dsa_new_key(gctx->provctx, gctx->propq, evp_type);
     if (key == NULL)
         return NULL;
-    if (!ossl_ml_dsa_generate_key(gctx->libctx, gctx->entropy, gctx->entropy_len,
-                                  key)) {
+    if (gctx->entropy_len != 0
+        && !ossl_ml_dsa_set_prekey(key, -1, -1,
+                                   gctx->entropy, gctx->entropy_len, NULL, 0))
+        goto err;
+    if (!ossl_ml_dsa_generate_key(key)) {
         ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GENERATE_KEY);
         goto err;
     }
@@ -386,19 +506,26 @@ static void ml_dsa_gen_cleanup(void *genctx)
     OPENSSL_free(gctx);
 }
 
-#define MAKE_KEYMGMT_FUNCTIONS(alg, fn)                                        \
-    static OSSL_FUNC_keymgmt_new_fn ml_dsa_##fn##_new_key;                     \
-    static OSSL_FUNC_keymgmt_gen_fn ml_dsa_##fn##_gen;                         \
-    static void *ml_dsa_##fn##_new_key(void *provctx)                          \
+#ifndef FIPS_MODULE
+# define DISPATCH_LOAD_FN \
+        { OSSL_FUNC_KEYMGMT_LOAD, (OSSL_FUNC) ml_dsa_load },
+#else
+# define DISPATCH_LOAD_FN   /* Non-FIPS only */
+#endif
+
+#define MAKE_KEYMGMT_FUNCTIONS(alg)                                            \
+    static OSSL_FUNC_keymgmt_new_fn ml_dsa_##alg##_new_key;                    \
+    static OSSL_FUNC_keymgmt_gen_fn ml_dsa_##alg##_gen;                        \
+    static void *ml_dsa_##alg##_new_key(void *provctx)                         \
     {                                                                          \
-        return ml_dsa_new_key(provctx, alg);                                   \
+        return ml_dsa_new_key(provctx, NULL, EVP_PKEY_ML_DSA_##alg);           \
     }                                                                          \
-    static void *ml_dsa_##fn##_gen(void *genctx, OSSL_CALLBACK *osslcb, void *cbarg)\
+    static void *ml_dsa_##alg##_gen(void *genctx, OSSL_CALLBACK *osslcb, void *cbarg)\
     {                                                                          \
-        return ml_dsa_gen(genctx, alg);                                        \
+        return ml_dsa_gen(genctx, EVP_PKEY_ML_DSA_##alg);                      \
     }                                                                          \
-    const OSSL_DISPATCH ossl_ml_dsa_##fn##_keymgmt_functions[] = {             \
-        { OSSL_FUNC_KEYMGMT_NEW, (void (*)(void))ml_dsa_##fn##_new_key },      \
+    const OSSL_DISPATCH ossl_ml_dsa_##alg##_keymgmt_functions[] = {            \
+        { OSSL_FUNC_KEYMGMT_NEW, (void (*)(void))ml_dsa_##alg##_new_key },     \
         { OSSL_FUNC_KEYMGMT_FREE, (void (*)(void))ml_dsa_free_key },           \
         { OSSL_FUNC_KEYMGMT_HAS, (void (*)(void))ml_dsa_has },                 \
         { OSSL_FUNC_KEYMGMT_MATCH, (void (*)(void))ml_dsa_match },             \
@@ -406,12 +533,12 @@ static void ml_dsa_gen_cleanup(void *genctx)
         { OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (void (*)(void))ml_dsa_imexport_types },\
         { OSSL_FUNC_KEYMGMT_EXPORT, (void (*)(void))ml_dsa_export },           \
         { OSSL_FUNC_KEYMGMT_EXPORT_TYPES, (void (*)(void))ml_dsa_imexport_types },\
-        { OSSL_FUNC_KEYMGMT_LOAD, (void (*)(void))ml_dsa_load },               \
+        DISPATCH_LOAD_FN                                                       \
         { OSSL_FUNC_KEYMGMT_GET_PARAMS, (void (*) (void))ml_dsa_get_params },  \
         { OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, (void (*) (void))ml_dsa_gettable_params },\
         { OSSL_FUNC_KEYMGMT_VALIDATE, (void (*)(void))ml_dsa_validate },       \
         { OSSL_FUNC_KEYMGMT_GEN_INIT, (void (*)(void))ml_dsa_gen_init },       \
-        { OSSL_FUNC_KEYMGMT_GEN, (void (*)(void))ml_dsa_##fn##_gen },          \
+        { OSSL_FUNC_KEYMGMT_GEN, (void (*)(void))ml_dsa_##alg##_gen },         \
         { OSSL_FUNC_KEYMGMT_GEN_CLEANUP, (void (*)(void))ml_dsa_gen_cleanup }, \
         { OSSL_FUNC_KEYMGMT_GEN_SET_PARAMS,                                    \
           (void (*)(void))ml_dsa_gen_set_params },                             \
@@ -421,6 +548,6 @@ static void ml_dsa_gen_cleanup(void *genctx)
         OSSL_DISPATCH_END                                                      \
     }
 
-MAKE_KEYMGMT_FUNCTIONS("ML-DSA-44", 44);
-MAKE_KEYMGMT_FUNCTIONS("ML-DSA-65", 65);
-MAKE_KEYMGMT_FUNCTIONS("ML-DSA-87", 87);
+MAKE_KEYMGMT_FUNCTIONS(44);
+MAKE_KEYMGMT_FUNCTIONS(65);
+MAKE_KEYMGMT_FUNCTIONS(87);
index 1f1e9ea2e765cdcf1a6ae9b7db485b0cce475ea4..36d28d75dea1090f9e3d38c1c72a9fd9031285b3 100644 (file)
@@ -49,7 +49,7 @@ typedef struct {
     size_t test_entropy_len;
     int msg_encode;
     int deterministic;
-    const char *alg;
+    int evp_type;
     /* The Algorithm Identifier of the signature algorithm */
     uint8_t aid_buf[OSSL_MAX_ALGORITHM_ID_SIZE];
     size_t  aid_len;
@@ -63,7 +63,7 @@ static void ml_dsa_freectx(void *vctx)
     OPENSSL_free(ctx);
 }
 
-static void *ml_dsa_newctx(void *provctx, const char *alg, const char *propq)
+static void *ml_dsa_newctx(void *provctx, int evp_type, const char *propq)
 {
     PROV_ML_DSA_CTX *ctx;
 
@@ -76,7 +76,7 @@ static void *ml_dsa_newctx(void *provctx, const char *alg, const char *propq)
 
     ctx->libctx = PROV_LIBCTX_OF(provctx);
     ctx->msg_encode = ML_DSA_MESSAGE_ENCODE_PURE;
-    ctx->alg = alg;
+    ctx->evp_type = evp_type;
 
     return ctx;
 }
@@ -139,7 +139,7 @@ static int ml_dsa_signverify_msg_init(void *vctx, void *vkey,
 
     if (key != NULL)
         ctx->key = vkey;
-    if (!ossl_ml_dsa_key_matches(ctx->key, ctx->alg))
+    if (!ossl_ml_dsa_key_matches(ctx->key, ctx->evp_type))
         return 0;
 
     set_alg_id_buffer(ctx);
@@ -319,14 +319,14 @@ static int ml_dsa_get_ctx_params(void *vctx, OSSL_PARAM *params)
     return 1;
 }
 
-#define MAKE_SIGNATURE_FUNCTIONS(alg, fn)                                      \
-    static OSSL_FUNC_signature_newctx_fn ml_dsa_##fn##_newctx;                 \
-    static void *ml_dsa_##fn##_newctx(void *provctx, const char *propq)        \
+#define MAKE_SIGNATURE_FUNCTIONS(alg)                                          \
+    static OSSL_FUNC_signature_newctx_fn ml_dsa_##alg##_newctx;                \
+    static void *ml_dsa_##alg##_newctx(void *provctx, const char *propq)       \
     {                                                                          \
-        return ml_dsa_newctx(provctx, alg, propq);                             \
+        return ml_dsa_newctx(provctx, EVP_PKEY_ML_DSA_##alg, propq);           \
     }                                                                          \
-    const OSSL_DISPATCH ossl_ml_dsa_##fn##_signature_functions[] = {           \
-        { OSSL_FUNC_SIGNATURE_NEWCTX, (void (*)(void))ml_dsa_##fn##_newctx },  \
+    const OSSL_DISPATCH ossl_ml_dsa_##alg##_signature_functions[] = {          \
+        { OSSL_FUNC_SIGNATURE_NEWCTX, (void (*)(void))ml_dsa_##alg##_newctx }, \
         { OSSL_FUNC_SIGNATURE_SIGN_MESSAGE_INIT,                               \
           (void (*)(void))ml_dsa_sign_msg_init },                              \
         { OSSL_FUNC_SIGNATURE_SIGN, (void (*)(void))ml_dsa_sign },             \
@@ -354,6 +354,6 @@ static int ml_dsa_get_ctx_params(void *vctx, OSSL_PARAM *params)
         OSSL_DISPATCH_END                                                      \
     }
 
-MAKE_SIGNATURE_FUNCTIONS("ML-DSA-44", 44);
-MAKE_SIGNATURE_FUNCTIONS("ML-DSA-65", 65);
-MAKE_SIGNATURE_FUNCTIONS("ML-DSA-87", 87);
+MAKE_SIGNATURE_FUNCTIONS(44);
+MAKE_SIGNATURE_FUNCTIONS(65);
+MAKE_SIGNATURE_FUNCTIONS(87);
index 6205c34ef3751f73905829c77221637927789992..24d49a91b6bb62d6e3a14ae95793017e47e41cb8 100644 (file)
@@ -219,12 +219,13 @@ static int ml_dsa_key_internal_test(void)
     ossl_ml_dsa_key_free(NULL);
 
     /* We should fail to fetch and fail here if the libctx is not set */
-    if (!TEST_ptr_null(key = ossl_ml_dsa_key_new(NULL, NULL, "ML-DSA-44"))
+    if (!TEST_ptr_null(key = ossl_ml_dsa_key_new(NULL, NULL, EVP_PKEY_ML_DSA_44))
             /* fail if the algorithm is invalid */
-            || !TEST_ptr_null(key = ossl_ml_dsa_key_new(lib_ctx, "", "ML-DSA-666"))
+            || !TEST_ptr_null(key = ossl_ml_dsa_key_new(lib_ctx, "", NID_undef))
             /* Dup should fail if the src is NULL */
             || !TEST_ptr_null(key1 = ossl_ml_dsa_key_dup(NULL, OSSL_KEYMGMT_SELECT_KEYPAIR))
-            || !TEST_ptr(key = ossl_ml_dsa_key_new(lib_ctx, "?fips=yes", "ML-DSA-44"))
+            || !TEST_ptr(key = ossl_ml_dsa_key_new(lib_ctx, "?fips=yes",
+                                                   EVP_PKEY_ML_DSA_44))
             || !TEST_ptr(key1 = ossl_ml_dsa_key_dup(key, OSSL_KEYMGMT_SELECT_KEYPAIR))
             || !TEST_true(ossl_ml_dsa_key_pub_alloc(key1))
             || !TEST_false(ossl_ml_dsa_key_pub_alloc(key1))
diff --git a/test/recipes/15-test_ml_dsa_codecs.t b/test/recipes/15-test_ml_dsa_codecs.t
new file mode 100644 (file)
index 0000000..274c830
--- /dev/null
@@ -0,0 +1,155 @@
+#! /usr/bin/env perl
+# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License").  You may not use
+# this file except in compliance with the License.  You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+
+use strict;
+use warnings;
+
+use File::Spec;
+use File::Copy;
+use File::Compare qw/compare_text compare/;
+use OpenSSL::Glob;
+use OpenSSL::Test qw/:DEFAULT data_file srctop_file bldtop_dir/;
+use OpenSSL::Test::Utils;
+
+setup("test_ml_dsa_codecs");
+
+my @algs = qw(44 65 87);
+my @formats = qw(seed-priv priv-only seed-only oqskeypair bare-seed bare-priv);
+
+plan skip_all => "ML-DSA isn't supported in this build"
+    if disabled("ml-dsa");
+
+plan tests => @algs * (16 + 10 * @formats);
+my $seed = join ("", map {sprintf "%02x", $_} (0..31));
+my $ikme = join ("", map {sprintf "%02x", $_} (0..31));
+
+foreach my $alg (@algs) {
+    my $pub = sprintf("pub-%s.pem", $alg);
+    my %formats = map { ($_, sprintf("prv-%s-%s.pem", $alg, $_)) } @formats;
+
+    # (1 + 6 * @formats) tests
+    my $i = 0;
+    my $in0 = data_file($pub);
+    my $der0 = sprintf("pub-%s.%d.der", $alg, $i++);
+    ok(run(app(['openssl', 'pkey', '-pubin', '-in', $in0,
+                '-outform', 'DER', '-out', $der0])));
+    foreach my $f (keys %formats) {
+        my $k = $formats{$f};
+        my %pruned = %formats;
+        delete $pruned{$f};
+        my $rest = join(", ", keys %pruned);
+        my $in = data_file($k);
+        my $der = sprintf("pub-%s.%d.der", $alg, $i);
+        #
+        # Compare expected DER public key with DER public key of private
+        ok(run(app(['openssl', 'pkey', '-in', $in, '-pubout',
+                    '-outform', 'DER', '-out', $der])));
+        ok(!compare($der0, $der),
+            sprintf("pubkey DER match: %s, %s", $alg, $f));
+        #
+        # Compare expected PEM private key with regenerated key
+        my $pem = sprintf("prv-%s-%s.%d.pem", $alg, $f, $i++);
+        ok(run(app(['openssl', 'genpkey', '-out', $pem,
+                    '-pkeyopt', "hexseed:$seed", '-algorithm', "ml-dsa-$alg",
+                    '-provparam', "ml-dsa.output_formats=$f"])));
+        ok(!compare($in, $pem),
+            sprintf("prvkey PEM match: %s, %s", $alg, $f));
+
+        ok(run(app(['openssl', 'pkey', '-in', $in, '-noout',
+                     '-provparam', "ml-dsa.input_formats=$f"])));
+        ok(!run(app(['openssl', 'pkey', '-in', $in, '-noout',
+                     '-provparam', "ml-dsa.input_formats=$rest"])));
+    }
+
+    # (1 + 2 * @formats) tests
+    # Perform sign/verify PCT
+    $i = 0;
+    my $refsig = data_file(sprintf("sig-%s.dat", $alg));
+    my $sig = sprintf("sig-%s.%d.dat", $alg, $i);
+    ok(run(app([qw(openssl pkeyutl -verify -rawin -pubin -inkey),
+                $in0, '-in', $der0, '-sigfile', $refsig],
+               sprintf("Signature verify with pubkey: %s", $alg))));
+    while (my ($f, $k) = each %formats) {
+        my $sk = data_file($k);
+        my $s = sprintf("sig-%s.%d.dat", $alg, $i++);
+        ok(run(app([qw(openssl pkeyutl -sign -rawin -inkey), $sk, '-in', $der0,
+                    qw(-pkeyopt deterministic:1 -out), $s])));
+        ok(!compare($s, $refsig),
+            sprintf("Signature blob match %s with %s", $alg, $f));
+    }
+
+    # 6 tests
+    # Test keygen seed suppression via the command-line and config file.
+    my $seedless = sprintf("seedless-%s.gen.cli.pem", $alg);
+    ok(run(app([qw(openssl genpkey -provparam ml-dsa.retain_seed=no),
+                '-algorithm', "ml-dsa-$alg", '-pkeyopt', "hexseed:$seed",
+                '-out', $seedless])));
+    ok(!compare(data_file($formats{'priv-only'}), $seedless),
+        sprintf("seedless via cli key match: %s", $alg));
+    {
+        local $ENV{'OPENSSL_CONF'} = data_file("ml-dsa.cnf");
+        local $ENV{'RETAIN_SEED'} = "no";
+        $seedless = sprintf("seedless-%s.gen.cnf.pem", $alg);
+        ok(run(app(['openssl', 'genpkey',
+                    '-algorithm', "ml-dsa-$alg", '-pkeyopt', "hexseed:$seed",
+                    '-out', $seedless])));
+        ok(!compare(data_file($formats{'priv-only'}), $seedless),
+            sprintf("seedless via config match: %s", $alg));
+
+        my $seedfull = sprintf("seedfull-%s.gen.conf+cli.pem", $alg);
+        ok(run(app(['openssl', 'genpkey', '-provparam', 'ml-dsa.retain_seed=yes',
+                    '-algorithm', "ml-dsa-$alg", '-pkeyopt', "hexseed:$seed",
+                    '-out', $seedfull])));
+        ok(!compare(data_file($formats{'seed-priv'}), $seedfull),
+            sprintf("seedfull via cli vs. conf key match: %s", $alg));
+    }
+
+    # 6 tests
+    # Test decoder seed suppression via the config file and command-line.
+    $seedless = sprintf("seedless-%s.dec.cli.pem", $alg);
+    ok(run(app(['openssl', 'pkey', '-provparam', 'ml-dsa.retain_seed=no',
+                '-in', data_file($formats{'seed-only'}), '-out', $seedless])));
+    ok(!compare(data_file($formats{'priv-only'}), $seedless),
+        sprintf("seedless via provparam key match: %s", $alg));
+    {
+        local $ENV{'OPENSSL_CONF'} = data_file("ml-dsa.cnf");
+        local $ENV{'RETAIN_SEED'} = "no";
+        $seedless = sprintf("seedless-%s.dec.cnf.pem", $alg);
+        ok(run(app(['openssl', 'pkey',
+                    '-in', data_file($formats{'seed-only'}), '-out', $seedless])));
+        ok(!compare(data_file($formats{'priv-only'}), $seedless),
+            sprintf("seedless via config match: %s", $alg));
+
+        my $seedfull = sprintf("seedfull-%s.dec.conf+cli.pem", $alg);
+        ok(run(app(['openssl', 'pkey', '-provparam', 'ml-dsa.retain_seed=yes',
+                    '-in', data_file($formats{'seed-only'}), '-out', $seedfull])));
+        ok(!compare(data_file($formats{'seed-priv'}), $seedfull),
+            sprintf("seedfull via cli vs. conf key match: %s", $alg));
+    }
+
+    # 2 tests
+    # Test decoder seed non-preference via the command-line.
+    my $privpref = sprintf("privpref-%s.dec.cli.pem", $alg);
+    ok(run(app(['openssl', 'pkey', '-provparam', 'ml-dsa.prefer_seed=no',
+                '-in', data_file($formats{'seed-priv'}), '-out', $privpref])));
+    ok(!compare(data_file($formats{'priv-only'}), $privpref),
+        sprintf("seed non-preference via provparam key match: %s", $alg));
+
+    # (2 * @formats) tests
+    # Check text encoding
+    while (my ($f, $k) = each %formats) {
+        my $txt =  sprintf("prv-%s-%s.txt", $alg,
+                            ($f =~ m{seed}) ? 'seed' : 'priv');
+        my $out = sprintf("prv-%s-%s.txt", $alg, $f);
+        ok(run(app(['openssl', 'pkey', '-in', data_file($k),
+                    '-noout', '-text', '-out', $out])));
+        ok(!compare(data_file($txt), $out),
+            sprintf("text form private key: %s with %s", $alg, $f));
+    }
+}
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/ml-dsa.cnf b/test/recipes/15-test_ml_dsa_codecs_data/ml-dsa.cnf
new file mode 100644 (file)
index 0000000..6e52d59
--- /dev/null
@@ -0,0 +1,18 @@
+openssl_conf = openssl_init
+
+[openssl_init]
+providers = providers_sect
+
+# Can be referenced in one or more provider sections
+[ml_dsa_sect]
+retain_seed = $ENV::RETAIN_SEED
+
+[providers_sect]
+default = default_sect
+base = base_sect
+
+[default_sect]
+ml-dsa = ml_dsa_sect
+
+[base_sect]
+ml-dsa = ml_dsa_sect
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-44-bare-priv.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-44-bare-priv.pem
new file mode 100644 (file)
index 0000000..b056593
--- /dev/null
@@ -0,0 +1,56 @@
+-----BEGIN PRIVATE KEY-----
+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
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-44-bare-seed.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-44-bare-seed.pem
new file mode 100644 (file)
index 0000000..1d1e878
--- /dev/null
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MDICAQAwCwYJYIZIAWUDBAMRBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob
+HB0eHw==
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-44-oqskeypair.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-44-oqskeypair.pem
new file mode 100644 (file)
index 0000000..e372db6
--- /dev/null
@@ -0,0 +1,84 @@
+-----BEGIN PRIVATE KEY-----
+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
+25eEzwXIMLMkLIMS
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-44-priv-only.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-44-priv-only.pem
new file mode 100644 (file)
index 0000000..6a62d43
--- /dev/null
@@ -0,0 +1,56 @@
+-----BEGIN PRIVATE KEY-----
+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
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-44-priv.txt b/test/recipes/15-test_ml_dsa_codecs_data/prv-44-priv.txt
new file mode 100644 (file)
index 0000000..45f9ab1
--- /dev/null
@@ -0,0 +1,262 @@
+ML-DSA-44 Private-Key:
+priv:
+    d7:b2:b4:72:54:aa:e0:db:45:e7:93:0d:4a:98:d2:
+    c9:7d:8f:13:97:d1:78:9d:af:a1:70:24:b3:16:e9:
+    be:c9:39:ce:0f:7f:77:f8:db:56:44:dc:da:36:6b:
+    fe:47:34:bd:95:f4:35:ff:9a:61:3a:a5:4a:a4:1c:
+    2c:69:4c:04:32:9a:07:b1:fa:bb:48:f5:2a:30:9f:
+    11:a1:89:8f:84:8e:23:22:ff:e6:23:ec:81:0d:b3:
+    be:e3:36:85:85:4a:88:26:9d:a3:20:d5:12:0b:fc:
+    fe:89:a1:8e:30:f7:11:4d:83:aa:40:4a:64:6b:6c:
+    99:73:89:86:0d:12:52:2e:e0:00:6e:23:84:81:91:
+    86:61:9b:26:0d:11:86:64:d4:a6:28:22:18:44:82:
+    40:28:98:14:61:48:a6:61:4c:42:48:a1:92:08:c2:
+    38:29:51:24:48:08:a1:25:c2:08:31:08:c4:71:20:
+    14:09:14:83:6c:18:a7:80:84:10:6e:c9:c0:70:22:
+    b5:64:08:b0:61:0c:07:04:98:12:44:51:88:69:59:
+    00:46:22:93:20:41:06:2e:42:b6:4c:01:16:49:14:
+    28:4c:41:a8:51:80:46:0a:51:16:51:5a:08:20:02:
+    22:44:dc:98:49:d1:32:51:e1:30:65:d3:c0:85:92:
+    a8:51:12:a1:64:00:39:22:09:46:62:1c:c7:0c:d9:
+    08:6d:d0:06:26:52:40:85:80:44:30:91:06:2c:50:
+    c8:09:24:c5:84:1a:96:6d:4a:98:2c:99:06:6d:a4:
+    44:32:20:a7:64:5a:32:6e:11:b5:70:20:92:61:24:
+    13:8e:04:85:2c:0a:48:72:c8:a0:51:d3:08:2a:99:
+    20:80:58:24:20:24:07:4e:59:14:88:10:a4:64:60:
+    c0:6d:e0:b2:8d:1b:19:09:20:34:22:c0:24:41:09:
+    43:71:0a:21:20:61:a2:01:52:22:52:1b:80:80:9a:
+    34:00:13:93:4d:d3:32:29:22:17:0a:98:92:69:1a:
+    14:51:20:27:21:9c:c0:20:62:a2:81:48:18:69:1a:
+    85:4d:83:44:69:5b:20:41:03:12:42:cb:18:46:01:
+    a9:0d:0c:02:31:83:b0:21:5a:22:4a:c8:92:05:d9:
+    90:69:04:30:6a:4b:06:4a:d2:b2:01:1c:40:40:81:
+    42:32:52:32:72:54:a6:40:5a:18:10:0c:32:12:92:
+    c2:80:52:12:62:5c:82:28:0b:b4:6c:03:42:8d:53:
+    10:0c:14:01:0e:e1:36:52:88:84:24:91:02:0a:63:
+    46:26:20:06:29:11:c2:28:d0:20:48:02:b3:6c:a2:
+    36:09:5a:86:48:cb:b4:61:8b:46:62:c4:40:82:1a:
+    89:09:10:02:4d:24:b2:45:20:12:25:24:c9:05:88:
+    28:8c:c9:c0:4d:59:48:22:0a:27:6e:c1:34:64:4c:
+    90:60:5b:44:50:82:86:49:43:88:04:43:b2:8c:60:
+    30:80:a2:88:2d:84:a4:6d:8c:a6:29:d0:c6:84:42:
+    06:46:89:88:51:00:a9:8d:01:49:8d:e4:38:0d:a4:
+    06:8d:d3:94:71:42:b2:6c:1a:84:61:1b:a3:28:42:
+    b4:28:08:a0:71:1a:c5:31:e0:a0:4c:01:37:65:24:
+    28:62:14:28:90:09:10:61:d9:40:22:1b:33:60:09:
+    02:92:d0:24:81:20:04:08:49:18:44:a3:22:2d:5c:
+    88:44:14:98:08:a4:46:61:01:95:64:0b:39:0a:0c:
+    94:50:ca:40:6a:d2:b2:20:c0:38:01:82:30:8e:13:
+    b9:08:91:80:84:14:88:29:c0:18:91:12:35:0d:a0:
+    24:22:e2:04:06:d9:c2:85:04:28:12:1c:c9:89:18:
+    02:72:d2:40:29:c2:08:12:d8:06:2a:99:94:71:9b:
+    b8:68:23:84:29:1a:22:89:14:45:11:dc:82:44:50:
+    96:45:0c:44:84:c0:b2:04:9a:a6:05:43:86:2c:44:
+    32:6e:88:44:21:20:a8:4c:9a:30:70:e3:b8:2d:63:
+    26:88:03:25:49:03:43:8c:48:a8:09:ca:14:72:53:
+    34:4e:12:43:08:1b:a7:04:59:30:22:d9:94:80:e2:
+    34:22:81:42:12:9c:30:2a:94:34:26:61:04:45:24:
+    26:28:13:46:09:4a:32:6d:11:28:09:18:b8:25:62:
+    28:11:13:41:0d:41:b2:11:90:84:4c:8b:12:12:a2:
+    c6:88:c9:c0:30:22:06:06:d2:18:8e:84:86:30:90:
+    44:52:12:88:31:d9:20:71:13:c5:28:43:06:0e:03:
+    30:60:cc:a6:84:58:26:52:4c:88:01:1e:f7:25:62:
+    c8:5f:fa:43:ac:fa:49:21:7f:2b:17:2d:7b:bc:14:
+    62:0e:6d:98:0a:71:aa:bb:df:0c:45:e9:a2:06:ec:
+    b1:42:3f:ee:15:de:cc:17:60:13:00:14:9d:92:23:
+    cd:6e:6c:6e:1f:a8:e4:1f:c7:c6:49:38:ab:68:90:
+    5f:d3:dc:da:50:d8:70:82:e7:d0:d7:1d:1b:c9:b2:
+    b8:4c:85:52:3c:a8:fe:6c:ad:29:4a:df:83:be:15:
+    b1:08:ff:72:1d:0c:c8:7b:c3:dd:3a:75:90:18:4b:
+    0e:84:56:63:a9:1f:c9:e1:c3:c5:3a:61:d8:67:42:
+    0b:04:f0:92:35:57:53:bc:65:a0:63:68:fd:41:29:
+    5f:d0:99:24:13:2c:6f:91:f6:79:64:c1:42:67:4a:
+    72:5c:34:39:14:c4:ce:cf:58:c0:74:bc:af:45:58:
+    c9:7b:f7:91:1e:07:aa:6d:09:38:f2:ee:2b:b3:c1:
+    a8:c5:95:d6:35:e8:43:42:fd:ea:01:dc:24:b2:11:
+    ad:2f:c2:81:cf:77:e5:91:10:c7:ab:c5:4b:f0:c8:
+    6d:48:0b:9b:e2:76:47:1d:c9:d6:03:ce:e9:8c:fd:
+    ab:3e:9f:cf:b7:03:79:35:60:54:9e:a4:45:0f:a7:
+    b3:3f:b9:16:9c:44:b4:d2:5f:b9:c4:57:f4:97:91:
+    cd:3d:a0:3e:ac:96:09:58:13:c1:05:13:2c:cd:a4:
+    e6:3e:49:22:8c:d2:3d:8a:1f:37:85:6f:14:2d:93:
+    b9:0d:b0:9f:82:af:89:25:8c:63:aa:b8:04:7a:80:
+    c0:36:c9:35:7e:a2:04:6f:8d:c6:35:4f:0c:52:95:
+    f3:42:bb:41:7d:3c:fe:b0:b1:fd:33:62:2c:29:e1:
+    4c:bb:d9:2e:13:63:c6:5e:bd:45:04:b7:51:23:29:
+    b9:67:0e:32:e1:b2:c6:7a:54:e7:f1:a5:5f:8b:9f:
+    9e:a0:4e:8c:a3:a7:05:e6:2a:3c:5e:63:73:74:af:
+    b7:ae:b6:dd:ea:61:2c:de:28:f0:1a:20:2d:7a:a4:
+    e3:47:22:d2:7d:d3:f9:b8:98:94:d0:19:fd:5d:4d:
+    71:19:ef:e3:72:3b:ba:10:4c:b8:bb:09:81:e0:74:
+    de:3a:fe:20:0d:aa:ae:ad:82:6c:c4:5f:24:4d:bf:
+    43:1a:fa:b3:4e:fb:df:78:24:74:d2:fd:57:11:8f:
+    64:62:14:93:4e:d9:9c:ba:3b:00:3e:8d:67:a3:83:
+    6f:6f:19:fc:41:91:0c:e5:16:3e:e3:ae:99:eb:84:
+    d5:14:eb:76:1e:63:68:4e:a5:6f:97:91:d2:dd:4a:
+    ac:6e:61:68:b9:48:c8:17:f7:5a:22:2a:cb:0e:8c:
+    dc:03:cc:4a:fe:8f:67:15:7e:1a:36:3b:7f:ae:ff:
+    9f:17:2b:98:91:36:77:c5:a1:dd:08:5e:9e:e4:c2:
+    20:52:c1:af:58:19:31:16:67:3d:cd:3b:fc:5f:34:
+    b8:55:dc:c6:c7:78:85:64:9e:9e:71:f4:3d:4a:ea:
+    0f:4b:72:ca:7e:da:05:78:ba:13:d3:1a:65:8d:2d:
+    06:0a:9a:66:ff:69:ed:1b:e7:99:7a:2f:b1:d2:72:
+    3d:38:f9:bf:ab:e1:8f:8e:7b:3c:da:90:6e:4e:9b:
+    5e:94:2c:8e:ae:b2:96:07:0e:bf:d3:64:94:7a:94:
+    0c:c9:78:be:d6:6b:37:74:9e:6d:5d:cd:7b:e8:c4:
+    94:44:0e:2b:84:ce:cf:ef:b9:8c:0b:ed:fb:3c:41:
+    e3:35:9d:2c:d7:19:7f:be:72:0c:48:aa:6c:6b:64:
+    65:c1:ee:63:e3:56:9c:2a:dc:74:44:91:37:0b:7f:
+    78:26:fe:0b:77:a1:d1:9d:64:10:1d:03:2b:91:81:
+    06:b4:2d:2e:f7:37:47:e5:60:1f:e4:ba:50:f2:3e:
+    de:52:1f:03:1a:81:7d:15:29:4a:43:72:2e:83:78:
+    78:4b:6d:b0:cf:1b:a9:e8:ae:91:1d:92:01:b9:ce:
+    9c:c3:01:9c:6f:5c:27:cb:98:da:26:14:4b:64:22:
+    5a:7c:93:2b:30:f7:61:e7:8a:2d:59:a1:d8:b8:3e:
+    c6:34:4a:2f:6d:d4:7e:76:57:06:d0:0b:f4:a7:9a:
+    6a:92:6c:3b:a9:1d:81:2c:8f:2c:79:7a:b1:79:67:
+    09:e5:d1:68:56:77:82:93:52:9f:02:86:d0:15:c3:
+    b5:39:96:19:64:2a:33:3e:9e:59:3d:6e:3f:53:53:
+    99:42:08:e9:e6:a3:32:85:1d:7f:65:25:22:a9:28:
+    b9:17:e2:7e:2d:6d:42:13:7d:fe:2e:bf:a6:fb:1c:
+    67:b2:6c:02:54:52:86:85:f7:eb:db:e3:15:a6:8e:
+    aa:2d:a7:69:e8:a9:f4:2d:3e:60:00:7c:71:33:09:
+    26:b2:c0:01:2d:83:ea:d4:e4:fd:1e:d8:72:cc:d1:
+    97:22:01:d2:b0:27:f3:54:5a:c2:d3:0c:d7:8b:c1:
+    d7:40:fe:cc:bc:6f:c2:a0:44:6c:6e:30:ea:c5:1f:
+    5a:69:09:8a:a2:d4:47:f2:08:5b:4e:4e:4b:92:cc:
+    c2:69:21:d2:de:47:85:18:cd:09:0c:e2:67:ae:a2:
+    d2:7a:da:57:fd:88:b4:97:6d:89:fb:84:3c:dc:cf:
+    49:a7:6c:a2:67:9e:68:01:bf:a7:fb:03:18:96:fb:
+    50:62:97:04:b9:92:39:36:bb:5d:d3:85:31:11:21:
+    ca:df:b1:19:95:e5:9b:73:03:4c:f6:7e:d0:3a:b8:
+    13:86:76:48:d0:25:82:80:87:e9:49:a9:af:d1:6b:
+    95:d7:2d:99:b1:ed:ca:25:7a:ac:13:2f:fb:7a:07:
+    09:ae:d5:a9:c0:ff:05:fb:0f:2b:bf:28:40:9e:ed:
+    7b:5f:58:01:be:96:4c:ed:01:9e:1c:b7:85:1d:38:
+    51:f1:02:90:67:4e:19:ff:b0:08:b3:01:c4:ac:f6:
+    41:a2:bb:14:21:6e:1d:69:ca:bf:52:b5:ef:22:74:
+    96:b0:f3:07:99:a8:55:d1:17:fa:d3:74:4a:6f:a3:
+    35:03:ea:79:8b:52:dd:d7:ee:54:26:60:9d:bf:cd:
+    3f:0c:13:b1:64:d6:c0:51:f7:ed:4a:11:97:19:a7:
+    12:e3:88:d3:28:40:20:81:ff:13:54:b5:54:d2:c2:
+    37:af:ed:3b:15:1c:4b:a8:e9:f4:bd:eb:84:99:a3:
+    06:6e:26:bb:c6:9e:8a:f0:89:de:c7:17:31:d1:dc:
+    52:9e:ab:17:ef:73:74:73:4c:0f:e4:75:49:4c:83:
+    83:6b:dd:34:a0:3b:9b:c8:99:14:71:60:61:bf:b9:
+    8e:c6:e6:1c:3e:d4:43:8e:dc:af:25:24:3c:64:70:
+    86:b9:ea:70:18:b0:d9:a8:a0:b0:0c:ec:b0:0a:bd:
+    e2:49:8d:69:c2:33:61:01:a7:72:cb:e4:f5:71:52:
+    3f:51:bd:05:88:2c:df:35:8b:84:9c:c1:40:aa:1f:
+    af:22:42:3a:12:85:1c:e0:e3:3f:d4:89:75:a4:95:
+    9f:a5:c5:fe:41:8c:93:90:81:91:ab:6e:74:1b:77:
+    bf:e0:2c:bd:69:8e:e7:95:c4:66:d6:15:61:9e:64:
+    41:38:2c:6e:ac:01:83:4e:e9:ab:73:ce:a8:0b:be:
+    23:5c:78:da:91:bd:79:b6:f8:2f:89:97:85:d6:87:
+    00:d3:93:e6:75:c2:22:4d:6b:7a:1a:d2:13:20:49:
+    56:79:ad:ae:d7:01:67:b5:08:66:71:3a:53:10:9d:
+    b7:b6:f7:d8:13:04:ec:df:d8:3b:31:9b:1e:f2:48:
+    30:6b:45:ad:29:e7:dd:cc:86:3d:ac:56:04:8b:5d:
+    69:ea:17:50:11:f7:61:4c:00:a8:6a:86:3c:de:18:
+    72:a8:93:28:78:b9:ac:7e:1a:c5:bd:a4:99:7b:72:
+    06:4f:0c:d7:5f:4c:81:4e:03:4d:e1:1a:cb:90:13:
+    cf:7e:a9:26:b4:e7:ea:ac:e0:70:c7:ba:21:88:ef:
+    ad:2e:43:1e:12:23:d4:5d:d0:5c:4d:84:03:c2:e4:
+    5c:ee:64:13:ec:be:75:27:e8:73:e4:55:c4:e6:10:
+    a6:18:39:aa:cc:0b:d5:6d:24:83:e7:8f:29:8b:66:
+    a4:78:eb:2f:55:8c:ba:fc:a8:6b:e8:47:ba:eb:02:
+    c5:b2:16:c8:cd:88:fe:a4:df:24:9b:09:e6:70:a2:
+    07:03:ab:ac:24:b0:a9:1a:bc:4a:56:46:60:14:42:
+    ba:10:be:cf:d3:09:93:88:00:51:d0:7f:56:a0:5a:
+    93:79:e7:a8:e6:be:fe:e3:f2:2f:aa:10:63:98:f7:
+    70:60:06:e4:2e:9b:e1:ef:89:d2:5c:27:2f:11:a9:
+    50:95:c5:87:d7:13:73:22:84:de:9d:bd:3c:72:17:
+    b0:68:9e:21:d8:eb:0f:f6:96:68
+pub:
+    d7:b2:b4:72:54:aa:e0:db:45:e7:93:0d:4a:98:d2:
+    c9:7d:8f:13:97:d1:78:9d:af:a1:70:24:b3:16:e9:
+    be:c9:4f:c9:94:6d:42:f1:9b:79:a7:41:3b:ba:a3:
+    3e:71:49:cb:42:ed:51:15:69:3a:c0:41:fa:cb:98:
+    8a:de:b5:fe:0e:1d:86:31:18:49:95:b5:92:c3:97:
+    d2:29:4e:2e:14:f9:0a:a4:14:ba:38:26:89:9a:c4:
+    3f:4c:cc:ac:bc:26:e9:a8:32:b9:51:18:d5:cb:43:
+    3c:be:f9:66:0b:00:13:8e:08:17:f6:1e:76:2c:a2:
+    74:c3:6a:d5:54:eb:22:aa:c1:16:2e:4a:b0:1a:cb:
+    a1:e3:8c:4e:fd:8f:80:b6:5b:33:3d:0f:72:e5:5d:
+    fe:71:ce:9c:1e:bb:98:89:e7:c5:61:06:c0:fd:73:
+    80:3a:2a:ec:fe:af:de:d7:aa:3c:b2:ce:da:54:d1:
+    2b:d8:cd:36:a7:8c:f9:75:94:3b:47:ab:d2:5e:88:
+    0a:c4:52:e5:74:2e:d1:e8:d1:a8:2a:fa:86:e5:90:
+    c7:58:c1:5a:e4:d2:84:0d:92:bc:a1:a5:09:0f:40:
+    49:65:97:fc:a7:d8:b9:51:3f:1a:1b:da:6e:95:0a:
+    aa:98:de:46:75:07:d4:a4:f5:a4:f0:59:92:16:58:
+    2c:35:72:f6:2e:da:89:05:ab:35:81:67:0c:4a:02:
+    77:7a:33:e0:ca:72:95:fd:8f:4f:f6:d1:a0:a3:a7:
+    68:3d:65:f5:f5:f7:fc:60:da:02:3e:82:6c:5f:92:
+    14:4c:02:f7:d1:ba:10:75:98:75:53:ea:93:67:fc:
+    d7:6d:99:0b:7f:a9:9c:d4:5a:fd:b8:83:6d:43:e4:
+    59:f5:18:7d:f0:58:47:97:09:a0:1e:a6:83:59:35:
+    fa:70:46:09:90:cd:3d:c1:ba:40:1b:a9:4b:ab:1d:
+    de:41:ac:67:ab:33:19:dc:ac:a0:60:48:d4:c4:ee:
+    f2:7e:e1:3a:9c:17:d0:53:8f:43:0f:2d:64:2d:c2:
+    41:56:60:de:78:87:7d:8d:8a:bc:72:52:39:78:c0:
+    42:e4:28:5f:43:19:84:6c:44:12:62:42:97:68:44:
+    c1:0e:55:6b:a2:15:b5:a7:19:e5:9d:0c:6b:2a:96:
+    d3:98:59:07:1f:dc:c2:cd:e7:52:4a:7b:ed:ae:54:
+    e8:5b:31:8e:85:4e:8f:e2:b2:f3:ed:fa:c9:71:91:
+    28:27:0a:af:d1:e5:04:4c:3a:4f:da:fd:9f:f3:1f:
+    90:78:4b:8e:8e:45:96:14:4a:0d:af:58:65:11:d3:
+    d9:96:2b:9e:a9:5a:f1:97:b4:e5:fc:60:f2:b1:ed:
+    15:de:3a:5b:ef:5f:89:bd:c7:9d:91:05:1d:9b:28:
+    16:e7:4f:a5:45:31:ef:dc:1c:be:74:d4:48:85:7f:
+    47:6b:cd:58:f2:1c:0b:65:3b:3b:76:a4:e0:76:a6:
+    55:9a:30:27:18:55:5c:c6:3f:74:85:9a:ab:ab:92:
+    5f:02:38:61:ca:8c:d0:f7:ba:db:28:71:f6:7d:55:
+    32:6d:74:51:13:5a:d4:5f:4a:1b:a6:91:18:fb:b2:
+    c8:a3:0e:ec:93:92:ef:3f:97:70:66:c9:ad:d5:c7:
+    10:cc:64:7b:15:14:d2:17:d9:58:c7:01:7c:3e:90:
+    fd:20:c0:4e:67:4b:90:48:6e:93:70:a3:1a:00:1d:
+    32:f4:73:97:9e:49:06:74:9e:7e:47:7f:a0:b7:45:
+    08:f8:a5:f2:37:83:12:b8:3c:25:bd:38:8c:a0:b0:
+    ff:f7:47:8b:af:42:b7:16:67:ed:aa:c9:7c:46:b1:
+    29:64:3e:58:6e:5b:05:5a:0c:21:19:46:d4:f3:6e:
+    67:5b:ed:58:60:fa:04:2a:31:5d:98:26:16:4d:6a:
+    92:37:c3:5a:5f:bf:49:54:90:a5:bd:4d:f2:48:b9:
+    5c:4a:ae:77:84:b6:05:67:31:66:ac:42:45:b5:b4:
+    b0:82:a0:9e:93:23:e6:2f:20:78:c5:b7:67:83:44:
+    6d:ef:d7:36:ad:3a:37:02:d4:9b:08:98:44:90:0a:
+    61:83:33:97:bc:44:19:b3:0d:7a:97:a0:b3:87:c1:
+    91:14:74:c4:d4:1b:53:e3:2a:97:7a:cb:6f:0e:a7:
+    5d:b6:5b:b3:9e:59:e7:01:e7:69:57:de:f6:f2:d4:
+    45:59:c3:1a:77:12:2b:52:04:e3:b5:c2:19:f1:68:
+    8b:14:ed:0b:c0:b8:01:b3:e6:e8:2d:cd:43:e9:c0:
+    e9:f4:17:44:cd:98:15:bd:1b:c8:82:0d:8b:b1:23:
+    f0:4f:ac:d1:b1:b6:85:dd:5a:2b:1b:8d:bb:f3:ed:
+    93:36:70:f0:95:a1:80:b4:f1:92:d0:8b:10:b8:fa:
+    bb:df:cc:2b:24:51:8e:32:ee:a0:a5:e0:c9:04:ca:
+    84:47:80:08:3f:3b:0c:d2:d0:b8:b6:af:67:bc:35:
+    5b:94:94:02:5d:c7:b0:a7:8f:a8:0e:3a:2d:bf:eb:
+    51:32:88:51:d6:07:81:98:e9:49:36:51:ae:78:7e:
+    c0:25:1f:92:2b:a3:0e:9f:51:df:62:a6:d7:27:84:
+    cf:3d:d2:05:39:31:76:df:a3:24:a5:12:bd:94:97:
+    0a:36:dd:34:a5:14:a8:67:91:f0:eb:36:f0:14:5b:
+    09:ab:64:65:1b:4a:03:13:b2:99:61:1a:2a:1c:48:
+    89:16:27:59:87:68:a3:11:40:60:ba:44:43:48:6d:
+    f5:15:22:a1:ce:88:b3:09:85:c2:16:f8:e6:ed:17:
+    8d:d5:67:b3:04:a0:d4:ca:fb:a8:82:a2:83:42:f1:
+    7a:9a:a2:6a:e5:8d:b6:30:08:3d:2c:35:8f:df:56:
+    6c:3f:5d:62:a4:28:56:7b:c9:ea:8c:e9:5c:aa:0f:
+    35:47:4b:0b:fa:8f:33:9a:25:0a:b4:df:cf:20:83:
+    be:8e:ef:bc:10:55:e1:8f:e1:53:70:ee:cb:26:05:
+    66:d8:3f:f0:6b:21:1a:ae:c4:3c:a2:9b:54:cc:d0:
+    0f:88:15:a2:46:5e:f0:b4:65:15:cc:7e:41:f3:12:
+    4f:09:ef:ff:73:93:09:ab:58:b2:9a:14:59:a0:0b:
+    ce:50:38:e9:38:c9:67:8f:72:eb:0e:4e:e5:fd:aa:
+    e6:6d:9f:85:73:fc:97:fc:42:b4:95:9f:4b:f8:b6:
+    1d:78:43:3e:86:b0:33:5d:6e:91:91:c4:d8:bf:48:
+    7b:39:05:c1:08:cf:d6:ac:24:b0:ce:b7:dc:b7:cf:
+    51:f8:4d:0e:d6:87:b9:5e:ae:b1:c5:33:c0:6f:0d:
+    97:02:3d:92:a7:08:25:83:7b:59:ba:6c:b7:d4:e5:
+    6b:0a:87:c2:03:86:2a:e8:f3:15:ba:59:25:e8:ed:
+    ef:a6:79:36:9a:22:02:76:61:51:f1:6a:96:5f:9f:
+    81:ec:e7:6c:c0:70:b5:58:69:e4:db:97:84:cf:05:
+    c8:30:b3:24:2c:83:12
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-44-seed-only.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-44-seed-only.pem
new file mode 100644 (file)
index 0000000..93febbe
--- /dev/null
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MDQCAQAwCwYJYIZIAWUDBAMRBCKAIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ
+GhscHR4f
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-44-seed-priv.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-44-seed-priv.pem
new file mode 100644 (file)
index 0000000..08d8f4c
--- /dev/null
@@ -0,0 +1,57 @@
+-----BEGIN PRIVATE KEY-----
+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
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-44-seed.txt b/test/recipes/15-test_ml_dsa_codecs_data/prv-44-seed.txt
new file mode 100644 (file)
index 0000000..be8b819
--- /dev/null
@@ -0,0 +1,266 @@
+ML-DSA-44 Private-Key:
+seed:
+    00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:
+    0f:10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:
+    1e:1f
+priv:
+    d7:b2:b4:72:54:aa:e0:db:45:e7:93:0d:4a:98:d2:
+    c9:7d:8f:13:97:d1:78:9d:af:a1:70:24:b3:16:e9:
+    be:c9:39:ce:0f:7f:77:f8:db:56:44:dc:da:36:6b:
+    fe:47:34:bd:95:f4:35:ff:9a:61:3a:a5:4a:a4:1c:
+    2c:69:4c:04:32:9a:07:b1:fa:bb:48:f5:2a:30:9f:
+    11:a1:89:8f:84:8e:23:22:ff:e6:23:ec:81:0d:b3:
+    be:e3:36:85:85:4a:88:26:9d:a3:20:d5:12:0b:fc:
+    fe:89:a1:8e:30:f7:11:4d:83:aa:40:4a:64:6b:6c:
+    99:73:89:86:0d:12:52:2e:e0:00:6e:23:84:81:91:
+    86:61:9b:26:0d:11:86:64:d4:a6:28:22:18:44:82:
+    40:28:98:14:61:48:a6:61:4c:42:48:a1:92:08:c2:
+    38:29:51:24:48:08:a1:25:c2:08:31:08:c4:71:20:
+    14:09:14:83:6c:18:a7:80:84:10:6e:c9:c0:70:22:
+    b5:64:08:b0:61:0c:07:04:98:12:44:51:88:69:59:
+    00:46:22:93:20:41:06:2e:42:b6:4c:01:16:49:14:
+    28:4c:41:a8:51:80:46:0a:51:16:51:5a:08:20:02:
+    22:44:dc:98:49:d1:32:51:e1:30:65:d3:c0:85:92:
+    a8:51:12:a1:64:00:39:22:09:46:62:1c:c7:0c:d9:
+    08:6d:d0:06:26:52:40:85:80:44:30:91:06:2c:50:
+    c8:09:24:c5:84:1a:96:6d:4a:98:2c:99:06:6d:a4:
+    44:32:20:a7:64:5a:32:6e:11:b5:70:20:92:61:24:
+    13:8e:04:85:2c:0a:48:72:c8:a0:51:d3:08:2a:99:
+    20:80:58:24:20:24:07:4e:59:14:88:10:a4:64:60:
+    c0:6d:e0:b2:8d:1b:19:09:20:34:22:c0:24:41:09:
+    43:71:0a:21:20:61:a2:01:52:22:52:1b:80:80:9a:
+    34:00:13:93:4d:d3:32:29:22:17:0a:98:92:69:1a:
+    14:51:20:27:21:9c:c0:20:62:a2:81:48:18:69:1a:
+    85:4d:83:44:69:5b:20:41:03:12:42:cb:18:46:01:
+    a9:0d:0c:02:31:83:b0:21:5a:22:4a:c8:92:05:d9:
+    90:69:04:30:6a:4b:06:4a:d2:b2:01:1c:40:40:81:
+    42:32:52:32:72:54:a6:40:5a:18:10:0c:32:12:92:
+    c2:80:52:12:62:5c:82:28:0b:b4:6c:03:42:8d:53:
+    10:0c:14:01:0e:e1:36:52:88:84:24:91:02:0a:63:
+    46:26:20:06:29:11:c2:28:d0:20:48:02:b3:6c:a2:
+    36:09:5a:86:48:cb:b4:61:8b:46:62:c4:40:82:1a:
+    89:09:10:02:4d:24:b2:45:20:12:25:24:c9:05:88:
+    28:8c:c9:c0:4d:59:48:22:0a:27:6e:c1:34:64:4c:
+    90:60:5b:44:50:82:86:49:43:88:04:43:b2:8c:60:
+    30:80:a2:88:2d:84:a4:6d:8c:a6:29:d0:c6:84:42:
+    06:46:89:88:51:00:a9:8d:01:49:8d:e4:38:0d:a4:
+    06:8d:d3:94:71:42:b2:6c:1a:84:61:1b:a3:28:42:
+    b4:28:08:a0:71:1a:c5:31:e0:a0:4c:01:37:65:24:
+    28:62:14:28:90:09:10:61:d9:40:22:1b:33:60:09:
+    02:92:d0:24:81:20:04:08:49:18:44:a3:22:2d:5c:
+    88:44:14:98:08:a4:46:61:01:95:64:0b:39:0a:0c:
+    94:50:ca:40:6a:d2:b2:20:c0:38:01:82:30:8e:13:
+    b9:08:91:80:84:14:88:29:c0:18:91:12:35:0d:a0:
+    24:22:e2:04:06:d9:c2:85:04:28:12:1c:c9:89:18:
+    02:72:d2:40:29:c2:08:12:d8:06:2a:99:94:71:9b:
+    b8:68:23:84:29:1a:22:89:14:45:11:dc:82:44:50:
+    96:45:0c:44:84:c0:b2:04:9a:a6:05:43:86:2c:44:
+    32:6e:88:44:21:20:a8:4c:9a:30:70:e3:b8:2d:63:
+    26:88:03:25:49:03:43:8c:48:a8:09:ca:14:72:53:
+    34:4e:12:43:08:1b:a7:04:59:30:22:d9:94:80:e2:
+    34:22:81:42:12:9c:30:2a:94:34:26:61:04:45:24:
+    26:28:13:46:09:4a:32:6d:11:28:09:18:b8:25:62:
+    28:11:13:41:0d:41:b2:11:90:84:4c:8b:12:12:a2:
+    c6:88:c9:c0:30:22:06:06:d2:18:8e:84:86:30:90:
+    44:52:12:88:31:d9:20:71:13:c5:28:43:06:0e:03:
+    30:60:cc:a6:84:58:26:52:4c:88:01:1e:f7:25:62:
+    c8:5f:fa:43:ac:fa:49:21:7f:2b:17:2d:7b:bc:14:
+    62:0e:6d:98:0a:71:aa:bb:df:0c:45:e9:a2:06:ec:
+    b1:42:3f:ee:15:de:cc:17:60:13:00:14:9d:92:23:
+    cd:6e:6c:6e:1f:a8:e4:1f:c7:c6:49:38:ab:68:90:
+    5f:d3:dc:da:50:d8:70:82:e7:d0:d7:1d:1b:c9:b2:
+    b8:4c:85:52:3c:a8:fe:6c:ad:29:4a:df:83:be:15:
+    b1:08:ff:72:1d:0c:c8:7b:c3:dd:3a:75:90:18:4b:
+    0e:84:56:63:a9:1f:c9:e1:c3:c5:3a:61:d8:67:42:
+    0b:04:f0:92:35:57:53:bc:65:a0:63:68:fd:41:29:
+    5f:d0:99:24:13:2c:6f:91:f6:79:64:c1:42:67:4a:
+    72:5c:34:39:14:c4:ce:cf:58:c0:74:bc:af:45:58:
+    c9:7b:f7:91:1e:07:aa:6d:09:38:f2:ee:2b:b3:c1:
+    a8:c5:95:d6:35:e8:43:42:fd:ea:01:dc:24:b2:11:
+    ad:2f:c2:81:cf:77:e5:91:10:c7:ab:c5:4b:f0:c8:
+    6d:48:0b:9b:e2:76:47:1d:c9:d6:03:ce:e9:8c:fd:
+    ab:3e:9f:cf:b7:03:79:35:60:54:9e:a4:45:0f:a7:
+    b3:3f:b9:16:9c:44:b4:d2:5f:b9:c4:57:f4:97:91:
+    cd:3d:a0:3e:ac:96:09:58:13:c1:05:13:2c:cd:a4:
+    e6:3e:49:22:8c:d2:3d:8a:1f:37:85:6f:14:2d:93:
+    b9:0d:b0:9f:82:af:89:25:8c:63:aa:b8:04:7a:80:
+    c0:36:c9:35:7e:a2:04:6f:8d:c6:35:4f:0c:52:95:
+    f3:42:bb:41:7d:3c:fe:b0:b1:fd:33:62:2c:29:e1:
+    4c:bb:d9:2e:13:63:c6:5e:bd:45:04:b7:51:23:29:
+    b9:67:0e:32:e1:b2:c6:7a:54:e7:f1:a5:5f:8b:9f:
+    9e:a0:4e:8c:a3:a7:05:e6:2a:3c:5e:63:73:74:af:
+    b7:ae:b6:dd:ea:61:2c:de:28:f0:1a:20:2d:7a:a4:
+    e3:47:22:d2:7d:d3:f9:b8:98:94:d0:19:fd:5d:4d:
+    71:19:ef:e3:72:3b:ba:10:4c:b8:bb:09:81:e0:74:
+    de:3a:fe:20:0d:aa:ae:ad:82:6c:c4:5f:24:4d:bf:
+    43:1a:fa:b3:4e:fb:df:78:24:74:d2:fd:57:11:8f:
+    64:62:14:93:4e:d9:9c:ba:3b:00:3e:8d:67:a3:83:
+    6f:6f:19:fc:41:91:0c:e5:16:3e:e3:ae:99:eb:84:
+    d5:14:eb:76:1e:63:68:4e:a5:6f:97:91:d2:dd:4a:
+    ac:6e:61:68:b9:48:c8:17:f7:5a:22:2a:cb:0e:8c:
+    dc:03:cc:4a:fe:8f:67:15:7e:1a:36:3b:7f:ae:ff:
+    9f:17:2b:98:91:36:77:c5:a1:dd:08:5e:9e:e4:c2:
+    20:52:c1:af:58:19:31:16:67:3d:cd:3b:fc:5f:34:
+    b8:55:dc:c6:c7:78:85:64:9e:9e:71:f4:3d:4a:ea:
+    0f:4b:72:ca:7e:da:05:78:ba:13:d3:1a:65:8d:2d:
+    06:0a:9a:66:ff:69:ed:1b:e7:99:7a:2f:b1:d2:72:
+    3d:38:f9:bf:ab:e1:8f:8e:7b:3c:da:90:6e:4e:9b:
+    5e:94:2c:8e:ae:b2:96:07:0e:bf:d3:64:94:7a:94:
+    0c:c9:78:be:d6:6b:37:74:9e:6d:5d:cd:7b:e8:c4:
+    94:44:0e:2b:84:ce:cf:ef:b9:8c:0b:ed:fb:3c:41:
+    e3:35:9d:2c:d7:19:7f:be:72:0c:48:aa:6c:6b:64:
+    65:c1:ee:63:e3:56:9c:2a:dc:74:44:91:37:0b:7f:
+    78:26:fe:0b:77:a1:d1:9d:64:10:1d:03:2b:91:81:
+    06:b4:2d:2e:f7:37:47:e5:60:1f:e4:ba:50:f2:3e:
+    de:52:1f:03:1a:81:7d:15:29:4a:43:72:2e:83:78:
+    78:4b:6d:b0:cf:1b:a9:e8:ae:91:1d:92:01:b9:ce:
+    9c:c3:01:9c:6f:5c:27:cb:98:da:26:14:4b:64:22:
+    5a:7c:93:2b:30:f7:61:e7:8a:2d:59:a1:d8:b8:3e:
+    c6:34:4a:2f:6d:d4:7e:76:57:06:d0:0b:f4:a7:9a:
+    6a:92:6c:3b:a9:1d:81:2c:8f:2c:79:7a:b1:79:67:
+    09:e5:d1:68:56:77:82:93:52:9f:02:86:d0:15:c3:
+    b5:39:96:19:64:2a:33:3e:9e:59:3d:6e:3f:53:53:
+    99:42:08:e9:e6:a3:32:85:1d:7f:65:25:22:a9:28:
+    b9:17:e2:7e:2d:6d:42:13:7d:fe:2e:bf:a6:fb:1c:
+    67:b2:6c:02:54:52:86:85:f7:eb:db:e3:15:a6:8e:
+    aa:2d:a7:69:e8:a9:f4:2d:3e:60:00:7c:71:33:09:
+    26:b2:c0:01:2d:83:ea:d4:e4:fd:1e:d8:72:cc:d1:
+    97:22:01:d2:b0:27:f3:54:5a:c2:d3:0c:d7:8b:c1:
+    d7:40:fe:cc:bc:6f:c2:a0:44:6c:6e:30:ea:c5:1f:
+    5a:69:09:8a:a2:d4:47:f2:08:5b:4e:4e:4b:92:cc:
+    c2:69:21:d2:de:47:85:18:cd:09:0c:e2:67:ae:a2:
+    d2:7a:da:57:fd:88:b4:97:6d:89:fb:84:3c:dc:cf:
+    49:a7:6c:a2:67:9e:68:01:bf:a7:fb:03:18:96:fb:
+    50:62:97:04:b9:92:39:36:bb:5d:d3:85:31:11:21:
+    ca:df:b1:19:95:e5:9b:73:03:4c:f6:7e:d0:3a:b8:
+    13:86:76:48:d0:25:82:80:87:e9:49:a9:af:d1:6b:
+    95:d7:2d:99:b1:ed:ca:25:7a:ac:13:2f:fb:7a:07:
+    09:ae:d5:a9:c0:ff:05:fb:0f:2b:bf:28:40:9e:ed:
+    7b:5f:58:01:be:96:4c:ed:01:9e:1c:b7:85:1d:38:
+    51:f1:02:90:67:4e:19:ff:b0:08:b3:01:c4:ac:f6:
+    41:a2:bb:14:21:6e:1d:69:ca:bf:52:b5:ef:22:74:
+    96:b0:f3:07:99:a8:55:d1:17:fa:d3:74:4a:6f:a3:
+    35:03:ea:79:8b:52:dd:d7:ee:54:26:60:9d:bf:cd:
+    3f:0c:13:b1:64:d6:c0:51:f7:ed:4a:11:97:19:a7:
+    12:e3:88:d3:28:40:20:81:ff:13:54:b5:54:d2:c2:
+    37:af:ed:3b:15:1c:4b:a8:e9:f4:bd:eb:84:99:a3:
+    06:6e:26:bb:c6:9e:8a:f0:89:de:c7:17:31:d1:dc:
+    52:9e:ab:17:ef:73:74:73:4c:0f:e4:75:49:4c:83:
+    83:6b:dd:34:a0:3b:9b:c8:99:14:71:60:61:bf:b9:
+    8e:c6:e6:1c:3e:d4:43:8e:dc:af:25:24:3c:64:70:
+    86:b9:ea:70:18:b0:d9:a8:a0:b0:0c:ec:b0:0a:bd:
+    e2:49:8d:69:c2:33:61:01:a7:72:cb:e4:f5:71:52:
+    3f:51:bd:05:88:2c:df:35:8b:84:9c:c1:40:aa:1f:
+    af:22:42:3a:12:85:1c:e0:e3:3f:d4:89:75:a4:95:
+    9f:a5:c5:fe:41:8c:93:90:81:91:ab:6e:74:1b:77:
+    bf:e0:2c:bd:69:8e:e7:95:c4:66:d6:15:61:9e:64:
+    41:38:2c:6e:ac:01:83:4e:e9:ab:73:ce:a8:0b:be:
+    23:5c:78:da:91:bd:79:b6:f8:2f:89:97:85:d6:87:
+    00:d3:93:e6:75:c2:22:4d:6b:7a:1a:d2:13:20:49:
+    56:79:ad:ae:d7:01:67:b5:08:66:71:3a:53:10:9d:
+    b7:b6:f7:d8:13:04:ec:df:d8:3b:31:9b:1e:f2:48:
+    30:6b:45:ad:29:e7:dd:cc:86:3d:ac:56:04:8b:5d:
+    69:ea:17:50:11:f7:61:4c:00:a8:6a:86:3c:de:18:
+    72:a8:93:28:78:b9:ac:7e:1a:c5:bd:a4:99:7b:72:
+    06:4f:0c:d7:5f:4c:81:4e:03:4d:e1:1a:cb:90:13:
+    cf:7e:a9:26:b4:e7:ea:ac:e0:70:c7:ba:21:88:ef:
+    ad:2e:43:1e:12:23:d4:5d:d0:5c:4d:84:03:c2:e4:
+    5c:ee:64:13:ec:be:75:27:e8:73:e4:55:c4:e6:10:
+    a6:18:39:aa:cc:0b:d5:6d:24:83:e7:8f:29:8b:66:
+    a4:78:eb:2f:55:8c:ba:fc:a8:6b:e8:47:ba:eb:02:
+    c5:b2:16:c8:cd:88:fe:a4:df:24:9b:09:e6:70:a2:
+    07:03:ab:ac:24:b0:a9:1a:bc:4a:56:46:60:14:42:
+    ba:10:be:cf:d3:09:93:88:00:51:d0:7f:56:a0:5a:
+    93:79:e7:a8:e6:be:fe:e3:f2:2f:aa:10:63:98:f7:
+    70:60:06:e4:2e:9b:e1:ef:89:d2:5c:27:2f:11:a9:
+    50:95:c5:87:d7:13:73:22:84:de:9d:bd:3c:72:17:
+    b0:68:9e:21:d8:eb:0f:f6:96:68
+pub:
+    d7:b2:b4:72:54:aa:e0:db:45:e7:93:0d:4a:98:d2:
+    c9:7d:8f:13:97:d1:78:9d:af:a1:70:24:b3:16:e9:
+    be:c9:4f:c9:94:6d:42:f1:9b:79:a7:41:3b:ba:a3:
+    3e:71:49:cb:42:ed:51:15:69:3a:c0:41:fa:cb:98:
+    8a:de:b5:fe:0e:1d:86:31:18:49:95:b5:92:c3:97:
+    d2:29:4e:2e:14:f9:0a:a4:14:ba:38:26:89:9a:c4:
+    3f:4c:cc:ac:bc:26:e9:a8:32:b9:51:18:d5:cb:43:
+    3c:be:f9:66:0b:00:13:8e:08:17:f6:1e:76:2c:a2:
+    74:c3:6a:d5:54:eb:22:aa:c1:16:2e:4a:b0:1a:cb:
+    a1:e3:8c:4e:fd:8f:80:b6:5b:33:3d:0f:72:e5:5d:
+    fe:71:ce:9c:1e:bb:98:89:e7:c5:61:06:c0:fd:73:
+    80:3a:2a:ec:fe:af:de:d7:aa:3c:b2:ce:da:54:d1:
+    2b:d8:cd:36:a7:8c:f9:75:94:3b:47:ab:d2:5e:88:
+    0a:c4:52:e5:74:2e:d1:e8:d1:a8:2a:fa:86:e5:90:
+    c7:58:c1:5a:e4:d2:84:0d:92:bc:a1:a5:09:0f:40:
+    49:65:97:fc:a7:d8:b9:51:3f:1a:1b:da:6e:95:0a:
+    aa:98:de:46:75:07:d4:a4:f5:a4:f0:59:92:16:58:
+    2c:35:72:f6:2e:da:89:05:ab:35:81:67:0c:4a:02:
+    77:7a:33:e0:ca:72:95:fd:8f:4f:f6:d1:a0:a3:a7:
+    68:3d:65:f5:f5:f7:fc:60:da:02:3e:82:6c:5f:92:
+    14:4c:02:f7:d1:ba:10:75:98:75:53:ea:93:67:fc:
+    d7:6d:99:0b:7f:a9:9c:d4:5a:fd:b8:83:6d:43:e4:
+    59:f5:18:7d:f0:58:47:97:09:a0:1e:a6:83:59:35:
+    fa:70:46:09:90:cd:3d:c1:ba:40:1b:a9:4b:ab:1d:
+    de:41:ac:67:ab:33:19:dc:ac:a0:60:48:d4:c4:ee:
+    f2:7e:e1:3a:9c:17:d0:53:8f:43:0f:2d:64:2d:c2:
+    41:56:60:de:78:87:7d:8d:8a:bc:72:52:39:78:c0:
+    42:e4:28:5f:43:19:84:6c:44:12:62:42:97:68:44:
+    c1:0e:55:6b:a2:15:b5:a7:19:e5:9d:0c:6b:2a:96:
+    d3:98:59:07:1f:dc:c2:cd:e7:52:4a:7b:ed:ae:54:
+    e8:5b:31:8e:85:4e:8f:e2:b2:f3:ed:fa:c9:71:91:
+    28:27:0a:af:d1:e5:04:4c:3a:4f:da:fd:9f:f3:1f:
+    90:78:4b:8e:8e:45:96:14:4a:0d:af:58:65:11:d3:
+    d9:96:2b:9e:a9:5a:f1:97:b4:e5:fc:60:f2:b1:ed:
+    15:de:3a:5b:ef:5f:89:bd:c7:9d:91:05:1d:9b:28:
+    16:e7:4f:a5:45:31:ef:dc:1c:be:74:d4:48:85:7f:
+    47:6b:cd:58:f2:1c:0b:65:3b:3b:76:a4:e0:76:a6:
+    55:9a:30:27:18:55:5c:c6:3f:74:85:9a:ab:ab:92:
+    5f:02:38:61:ca:8c:d0:f7:ba:db:28:71:f6:7d:55:
+    32:6d:74:51:13:5a:d4:5f:4a:1b:a6:91:18:fb:b2:
+    c8:a3:0e:ec:93:92:ef:3f:97:70:66:c9:ad:d5:c7:
+    10:cc:64:7b:15:14:d2:17:d9:58:c7:01:7c:3e:90:
+    fd:20:c0:4e:67:4b:90:48:6e:93:70:a3:1a:00:1d:
+    32:f4:73:97:9e:49:06:74:9e:7e:47:7f:a0:b7:45:
+    08:f8:a5:f2:37:83:12:b8:3c:25:bd:38:8c:a0:b0:
+    ff:f7:47:8b:af:42:b7:16:67:ed:aa:c9:7c:46:b1:
+    29:64:3e:58:6e:5b:05:5a:0c:21:19:46:d4:f3:6e:
+    67:5b:ed:58:60:fa:04:2a:31:5d:98:26:16:4d:6a:
+    92:37:c3:5a:5f:bf:49:54:90:a5:bd:4d:f2:48:b9:
+    5c:4a:ae:77:84:b6:05:67:31:66:ac:42:45:b5:b4:
+    b0:82:a0:9e:93:23:e6:2f:20:78:c5:b7:67:83:44:
+    6d:ef:d7:36:ad:3a:37:02:d4:9b:08:98:44:90:0a:
+    61:83:33:97:bc:44:19:b3:0d:7a:97:a0:b3:87:c1:
+    91:14:74:c4:d4:1b:53:e3:2a:97:7a:cb:6f:0e:a7:
+    5d:b6:5b:b3:9e:59:e7:01:e7:69:57:de:f6:f2:d4:
+    45:59:c3:1a:77:12:2b:52:04:e3:b5:c2:19:f1:68:
+    8b:14:ed:0b:c0:b8:01:b3:e6:e8:2d:cd:43:e9:c0:
+    e9:f4:17:44:cd:98:15:bd:1b:c8:82:0d:8b:b1:23:
+    f0:4f:ac:d1:b1:b6:85:dd:5a:2b:1b:8d:bb:f3:ed:
+    93:36:70:f0:95:a1:80:b4:f1:92:d0:8b:10:b8:fa:
+    bb:df:cc:2b:24:51:8e:32:ee:a0:a5:e0:c9:04:ca:
+    84:47:80:08:3f:3b:0c:d2:d0:b8:b6:af:67:bc:35:
+    5b:94:94:02:5d:c7:b0:a7:8f:a8:0e:3a:2d:bf:eb:
+    51:32:88:51:d6:07:81:98:e9:49:36:51:ae:78:7e:
+    c0:25:1f:92:2b:a3:0e:9f:51:df:62:a6:d7:27:84:
+    cf:3d:d2:05:39:31:76:df:a3:24:a5:12:bd:94:97:
+    0a:36:dd:34:a5:14:a8:67:91:f0:eb:36:f0:14:5b:
+    09:ab:64:65:1b:4a:03:13:b2:99:61:1a:2a:1c:48:
+    89:16:27:59:87:68:a3:11:40:60:ba:44:43:48:6d:
+    f5:15:22:a1:ce:88:b3:09:85:c2:16:f8:e6:ed:17:
+    8d:d5:67:b3:04:a0:d4:ca:fb:a8:82:a2:83:42:f1:
+    7a:9a:a2:6a:e5:8d:b6:30:08:3d:2c:35:8f:df:56:
+    6c:3f:5d:62:a4:28:56:7b:c9:ea:8c:e9:5c:aa:0f:
+    35:47:4b:0b:fa:8f:33:9a:25:0a:b4:df:cf:20:83:
+    be:8e:ef:bc:10:55:e1:8f:e1:53:70:ee:cb:26:05:
+    66:d8:3f:f0:6b:21:1a:ae:c4:3c:a2:9b:54:cc:d0:
+    0f:88:15:a2:46:5e:f0:b4:65:15:cc:7e:41:f3:12:
+    4f:09:ef:ff:73:93:09:ab:58:b2:9a:14:59:a0:0b:
+    ce:50:38:e9:38:c9:67:8f:72:eb:0e:4e:e5:fd:aa:
+    e6:6d:9f:85:73:fc:97:fc:42:b4:95:9f:4b:f8:b6:
+    1d:78:43:3e:86:b0:33:5d:6e:91:91:c4:d8:bf:48:
+    7b:39:05:c1:08:cf:d6:ac:24:b0:ce:b7:dc:b7:cf:
+    51:f8:4d:0e:d6:87:b9:5e:ae:b1:c5:33:c0:6f:0d:
+    97:02:3d:92:a7:08:25:83:7b:59:ba:6c:b7:d4:e5:
+    6b:0a:87:c2:03:86:2a:e8:f3:15:ba:59:25:e8:ed:
+    ef:a6:79:36:9a:22:02:76:61:51:f1:6a:96:5f:9f:
+    81:ec:e7:6c:c0:70:b5:58:69:e4:db:97:84:cf:05:
+    c8:30:b3:24:2c:83:12
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-65-bare-priv.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-65-bare-priv.pem
new file mode 100644 (file)
index 0000000..89ae4d5
--- /dev/null
@@ -0,0 +1,87 @@
+-----BEGIN PRIVATE KEY-----
+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
+bKFHsTJE1siS/I7yI6s/Q/kkwvRmCX7o
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-65-bare-seed.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-65-bare-seed.pem
new file mode 100644 (file)
index 0000000..a9f7c41
--- /dev/null
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MDICAQAwCwYJYIZIAWUDBAMSBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob
+HB0eHw==
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-65-oqskeypair.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-65-oqskeypair.pem
new file mode 100644 (file)
index 0000000..79fdf51
--- /dev/null
@@ -0,0 +1,128 @@
+-----BEGIN PRIVATE KEY-----
+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
+IQS8RgoFHgLy6ERP
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-65-priv-only.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-65-priv-only.pem
new file mode 100644 (file)
index 0000000..4f5f4ba
--- /dev/null
@@ -0,0 +1,87 @@
+-----BEGIN PRIVATE KEY-----
+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
+v++Sr2yhR7EyRNbIkvyO8iOrP0P5JML0Zgl+6A==
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-65-priv.txt b/test/recipes/15-test_ml_dsa_codecs_data/prv-65-priv.txt
new file mode 100644 (file)
index 0000000..2602240
--- /dev/null
@@ -0,0 +1,403 @@
+ML-DSA-65 Private-Key:
+priv:
+    48:68:3d:91:97:8e:31:eb:3d:dd:b8:b0:47:34:82:
+    d2:b8:8a:5f:62:59:49:fd:8f:58:a5:61:e6:96:bd:
+    4c:27:d8:53:fa:69:b8:19:90:23:e8:cd:67:8d:d9:
+    fa:bf:90:47:64:6f:fd:0c:b3:cc:7f:79:58:05:a7:
+    1e:70:d2:37:1b:05:63:e3:cd:33:46:14:9c:8c:9e:
+    bc:f2:3b:0a:4e:5a:90:0e:ea:9c:65:62:79:0a:7c:
+    63:e3:86:63:da:a2:dd:db:6e:48:0d:c4:05:a1:e7:
+    01:94:8b:74:84:1e:f5:cc:1c:3f:2b:f3:27:97:2e:
+    95:10:51:0c:d5:37:5e:cc:08:55:71:77:11:87:22:
+    21:86:23:81:00:04:24:77:80:61:47:50:07:50:17:
+    17:03:55:04:51:51:25:47:18:38:04:61:75:72:22:
+    44:10:88:68:60:86:46:01:27:47:56:71:80:87:06:
+    66:86:43:32:44:41:22:04:36:38:66:75:02:82:36:
+    34:24:43:22:05:73:64:10:64:55:54:77:22:75:56:
+    81:43:36:14:62:55:08:20:64:37:68:54:68:75:43:
+    53:75:10:68:71:83:33:80:54:75:05:25:80:75:28:
+    18:84:38:11:08:72:60:20:20:08:58:83:01:83:61:
+    13:82:82:12:06:17:11:57:87:68:78:88:78:64:37:
+    54:60:16:57:15:50:84:71:88:66:07:27:32:88:06:
+    64:74:18:56:76:21:80:31:82:76:64:15:78:24:50:
+    25:64:66:43:11:35:04:36:47:80:12:66:73:14:30:
+    11:66:06:55:86:47:18:36:88:63:50:38:47:86:11:
+    01:20:23:56:11:61:37:86:07:85:32:12:40:07:54:
+    78:82:30:43:66:61:16:60:42:55:41:82:85:60:53:
+    67:78:56:38:43:44:30:63:26:10:77:07:31:78:42:
+    72:14:11:16:53:03:85:27:68:67:46:01:50:82:37:
+    35:32:07:66:10:75:04:68:12:48:06:66:03:03:26:
+    52:31:24:45:40:88:00:31:80:88:76:72:17:30:71:
+    82:47:21:51:27:80:11:65:44:74:86:61:72:23:33:
+    80:86:60:64:46:83:52:15:84:20:36:80:11:80:21:
+    18:18:33:17:73:54:53:48:81:00:44:86:53:67:43:
+    70:57:72:58:83:34:60:38:42:32:85:68:10:06:04:
+    26:04:25:84:56:02:35:68:20:51:83:86:38:43:24:
+    21:22:42:45:64:58:58:67:71:45:72:85:04:78:87:
+    17:18:06:18:83:60:86:86:41:56:50:81:16:50:26:
+    46:70:06:08:26:62:27:38:31:72:40:72:57:30:07:
+    27:28:86:20:66:75:88:68:26:07:06:40:20:33:03:
+    43:66:31:55:46:42:45:34:56:67:18:73:45:65:83:
+    70:22:50:84:68:56:28:80:70:36:70:84:62:37:17:
+    10:06:57:17:58:47:78:70:86:55:53:78:22:35:14:
+    46:77:28:56:73:03:22:87:00:14:33:20:61:71:58:
+    45:52:66:32:50:26:51:33:47:77:38:03:55:16:43:
+    13:47:35:10:66:27:51:75:74:02:46:88:81:70:67:
+    43:46:81:86:01:76:52:45:33:30:87:21:04:34:34:
+    01:03:22:87:63:51:55:26:50:81:30:77:45:44:41:
+    68:15:41:83:63:64:11:20:40:26:87:30:43:67:77:
+    12:80:88:46:35:54:53:00:62:45:81:04:58:36:51:
+    24:84:27:80:34:51:66:63:58:43:78:56:01:46:51:
+    15:74:23:21:43:66:85:22:47:77:31:34:50:17:83:
+    62:42:05:50:00:64:84:47:12:34:40:88:00:60:47:
+    35:40:57:83:33:63:08:21:06:15:22:52:07:24:88:
+    51:34:86:37:06:76:22:58:85:71:26:56:73:47:68:
+    16:46:46:84:25:87:08:12:27:05:50:08:38:32:00:
+    23:20:80:66:34:53:36:00:33:46:85:72:47:06:35:
+    54:00:35:77:12:27:52:30:71:42:53:68:74:37:45:
+    70:05:66:43:22:44:82:85:20:72:18:33:30:20:53:
+    37:33:40:77:27:80:55:25:30:63:52:50:40:67:33:
+    46:13:18:07:28:07:17:24:83:77:63:45:73:18:58:
+    51:60:23:33:44:36:25:16:43:38:16:08:58:77:34:
+    62:42:88:30:07:03:65:85:37:55:00:75:52:31:50:
+    37:02:13:24:63:04:37:08:68:06:36:15:03:03:00:
+    43:58:63:57:08:02:11:06:64:73:46:35:22:62:03:
+    30:43:80:21:08:52:87:57:83:21:07:88:67:48:08:
+    56:34:74:36:73:42:84:05:84:66:84:14:37:00:55:
+    10:87:34:26:44:77:21:12:73:84:73:65:26:47:25:
+    77:14:47:04:17:86:44:26:02:47:11:87:40:81:22:
+    16:60:58:47:17:81:37:06:76:80:81:70:58:18:55:
+    85:47:13:63:42:10:75:58:01:63:58:35:85:18:44:
+    03:84:71:10:33:87:42:62:82:47:74:13:65:54:42:
+    70:73:46:35:77:75:00:66:25:62:68:42:02:12:46:
+    83:86:46:16:64:60:31:22:53:88:84:54:00:84:57:
+    34:46:47:54:47:25:60:54:61:66:84:66:30:88:06:
+    38:27:15:63:28:71:83:84:06:52:24:76:81:16:06:
+    62:13:03:30:18:68:02:80:13:84:63:05:05:65:72:
+    38:75:83:65:72:32:30:68:80:46:12:26:06:65:16:
+    75:57:05:32:41:32:27:67:35:17:08:01:53:00:16:
+    28:46:01:34:88:77:01:11:88:15:57:13:15:46:43:
+    11:70:47:32:88:28:56:36:82:34:55:50:41:86:27:
+    65:63:11:11:68:75:05:10:42:54:41:44:27:85:22:
+    11:17:17:88:15:36:85:15:74:47:16:62:55:36:55:
+    83:63:02:50:28:55:76:87:53:27:13:71:03:72:37:
+    05:71:47:61:71:36:51:84:12:42:36:64:44:66:41:
+    43:52:05:21:08:51:57:03:33:63:86:02:58:42:66:
+    28:14:81:10:54:62:68:17:30:38:75:64:33:21:65:
+    88:56:86:63:63:28:13:40:62:54:01:20:40:88:65:
+    47:88:61:71:65:76:23:72:62:34:86:70:30:11:51:
+    15:63:20:50:75:35:02:12:21:08:42:65:31:43:55:
+    67:11:15:25:72:01:06:85:36:30:15:05:57:58:60:
+    58:78:43:14:31:32:78:78:80:87:38:47:88:63:78:
+    81:81:38:73:42:61:78:38:85:24:66:77:33:50:60:
+    21:15:14:64:23:82:32:68:01:35:44:07:83:47:53:
+    85:53:57:52:83:23:35:18:76:01:15:21:34:32:57:
+    73:33:36:55:18:86:15:81:61:68:24:18:42:21:22:
+    30:84:14:48:15:12:01:10:30:24:77:72:42:54:43:
+    66:06:77:17:70:76:03:01:45:25:40:35:00:18:38:
+    73:23:77:35:26:50:86:35:71:13:73:44:81:60:52:
+    77:45:65:53:73:00:85:83:77:85:03:51:21:11:54:
+    80:62:88:50:18:02:68:13:86:52:05:34:68:01:32:
+    07:24:18:03:21:30:05:72:38:64:07:64:27:11:41:
+    01:83:85:25:51:06:32:60:71:04:86:51:76:83:38:
+    28:57:27:62:35:45:18:73:50:83:13:28:86:37:66:
+    61:42:63:11:67:50:33:11:25:53:76:41:76:03:14:
+    33:17:72:12:23:44:18:a8:2e:4f:5c:9e:a0:fa:f9:
+    9e:b0:4d:78:a7:33:27:11:11:7c:33:f1:8e:ca:21:
+    f8:74:33:76:ad:a5:21:98:04:a7:ed:9a:55:57:fc:
+    d6:7a:35:50:b3:a4:b8:c5:88:62:9c:02:14:75:fa:
+    3d:56:d5:d6:cf:bb:1a:09:bd:a8:d1:4d:e6:22:dd:
+    ff:16:d8:bc:99:b1:42:78:a8:af:1d:76:be:d1:57:
+    67:2d:d9:c3:23:16:f9:7e:8d:aa:de:f8:d9:da:69:
+    58:67:25:56:7f:b9:6b:59:99:0d:4b:f0:bc:9c:19:
+    5b:90:b7:42:95:f5:67:5b:24:25:7c:27:10:c1:75:
+    b0:15:3f:29:11:32:8c:2e:b7:ab:b9:ad:46:e7:0a:
+    8b:53:c3:9e:a6:42:ce:e4:b3:cb:42:62:0e:86:3c:
+    e8:b6:50:ce:8a:dc:d9:23:72:1a:16:87:02:3c:67:
+    3a:8c:bb:6b:03:d5:1c:d1:97:e8:c3:46:eb:ad:ce:
+    93:95:0f:88:ce:e2:01:db:9e:32:08:43:e2:9f:30:
+    0d:9a:19:50:0d:70:a4:ca:f2:72:c6:9e:4e:ef:69:
+    fb:b8:a5:5e:fd:7c:a2:be:d9:90:d2:d3:b5:82:84:
+    8f:9c:45:c2:ab:c5:4c:fc:47:d3:4f:06:c0:ff:a5:
+    6f:cd:76:2a:b9:cb:a9:14:6d:77:25:21:89:63:b2:
+    40:d7:2b:6d:22:c9:31:71:fb:d4:77:88:b7:6e:72:
+    04:2d:ef:08:78:d2:3d:f6:31:a1:a1:e5:a6:02:76:
+    86:de:5b:4a:10:e9:10:69:c8:f2:ba:02:59:b0:4d:
+    64:09:da:96:56:7c:a5:2d:a4:97:02:6e:58:3a:0e:
+    ce:fc:1f:01:e6:b9:88:e2:1f:97:67:a2:b7:e1:67:
+    2d:eb:9a:1e:2a:3f:cc:86:3a:a9:15:17:c3:34:62:
+    06:01:b4:fe:79:73:0e:93:49:35:f4:b6:fb:c4:e3:
+    26:95:14:5c:2b:5f:6a:12:7f:ec:c0:a2:77:45:1e:
+    bc:3f:d5:23:44:4f:9e:e7:c9:c3:45:34:f3:56:db:
+    54:4f:c3:1c:1b:fd:e5:f6:5c:77:ea:2f:7c:2e:ae:
+    4c:55:eb:af:10:42:71:c5:66:fd:4e:ba:c7:1c:7a:
+    62:c7:49:52:81:7a:e6:75:50:4d:95:99:b1:b7:62:
+    b6:ac:a1:68:a8:32:48:c9:d9:ad:b0:ce:b1:55:6e:
+    57:59:49:0b:bc:0c:79:00:79:5a:d7:21:23:03:8b:
+    66:2f:64:f1:06:a9:99:36:81:a2:5d:59:af:7b:c9:
+    7a:23:5b:e9:28:4c:5b:c4:5a:6c:90:cb:1c:29:99:
+    c6:63:d9:6b:47:8e:23:07:f8:55:48:95:7d:65:74:
+    0e:26:73:e9:eb:d1:35:28:29:03:8f:46:2b:8f:d3:
+    b5:68:1d:a5:5c:02:52:52:38:53:52:5e:a0:ad:64:
+    7e:71:ac:2c:5a:88:93:e6:03:ac:97:e5:6c:04:ce:
+    b2:f2:6f:5c:5b:4b:6d:94:ab:81:13:80:fd:00:f2:
+    20:8f:e8:65:35:08:6a:eb:fd:35:c2:91:20:62:4c:
+    04:fb:b6:11:39:29:d9:c5:56:35:02:53:76:6c:20:
+    9f:db:a8:3c:95:fc:cd:34:2a:28:09:93:55:d0:0b:
+    c8:63:f4:ee:f5:96:eb:0b:42:eb:cc:7c:79:49:1c:
+    ce:ae:20:5e:a0:b8:05:9f:bb:8a:57:26:c5:94:9d:
+    2b:15:e7:e2:9c:51:fc:9b:02:ee:1a:4f:c3:57:b5:
+    f1:be:f9:c4:ad:d4:6a:2a:92:0c:2f:bf:08:a3:7e:
+    b1:51:4b:fa:15:11:0a:43:92:a7:4c:6f:13:c5:0c:
+    5c:ff:d9:75:31:09:8d:7c:d2:3b:60:eb:35:c4:a4:
+    28:b4:6c:55:38:6e:10:10:c4:ba:7f:70:e4:c7:ec:
+    b7:57:5f:30:63:a7:1e:84:df:dc:f0:9a:58:b2:cd:
+    b0:f9:9f:27:ed:37:86:10:d2:5c:ba:d7:bf:a6:ba:
+    0d:59:18:9c:fe:88:ea:b9:b4:6d:7e:6d:b0:30:7e:
+    ab:e4:19:8e:99:bd:71:f7:79:ab:66:58:1e:09:12:
+    fc:7b:1d:25:85:24:5e:9a:12:68:7a:97:5c:d5:e8:
+    e1:dc:c0:45:d5:f8:91:c4:c6:85:db:07:cf:81:e7:
+    73:89:b3:63:eb:6b:df:e3:9b:27:ff:84:c9:7e:ef:
+    ee:16:2e:3b:45:1f:e6:91:47:19:cb:64:36:d8:55:
+    96:0f:f9:15:d7:ce:a6:ad:ea:fd:fc:1c:05:78:6c:
+    49:f9:23:a4:74:ff:df:c3:15:3a:06:e6:ed:0b:0a:
+    d2:20:d7:25:24:43:4d:52:73:c0:aa:b6:dd:e4:e9:
+    14:76:d5:81:a2:69:5a:60:de:6d:9f:44:d7:7a:a0:
+    82:66:e9:38:ee:b4:a9:59:7c:9b:64:98:60:59:e4:
+    92:62:a4:ea:b2:45:4e:14:01:5a:d0:53:6c:42:73:
+    3a:5d:77:d7:99:5c:2a:20:44:60:09:eb:fe:56:32:
+    c8:0c:08:ed:2b:97:af:35:06:64:89:f5:97:eb:1b:
+    1f:11:f0:4f:60:e0:c9:04:01:59:c4:4a:b3:e6:0e:
+    0a:15:22:9d:19:12:28:be:d1:7b:bc:3a:c9:39:b3:
+    c6:7c:ee:13:5f:35:2c:27:21:6c:9c:31:f7:2a:3e:
+    87:04:0c:5f:61:93:06:eb:0b:6c:ca:2a:9c:e7:b2:
+    2a:16:94:d0:0c:a9:c0:5e:31:51:26:45:7f:26:ce:
+    84:f9:61:72:41:86:07:82:f8:64:b4:73:d8:40:17:
+    49:19:02:b1:bd:c8:cd:c5:80:0d:d4:61:27:fb:80:
+    a7:1c:09:5b:47:3a:56:25:29:b3:b1:e7:e4:37:e1:
+    58:a5:f6:66:6e:99:74:d0:05:b0:62:c2:30:9e:6d:
+    ce:98:f9:b6:58:c6:e3:f9:a2:16:d5:8c:8c:91:42:
+    bd:1c:8c:85:a9:da:87:2e:bb:fa:d3:fe:a9:d9:ab:
+    a2:b6:8c:0e:8f:19:c6:ff:5f:00:58:4d:45:da:f9:
+    d6:c9:d6:9e:d0:4b:8d:a8:d6:87:25:8b:77:80:79:
+    27:61:2c:53:04:46:fe:a7:69:7a:e3:f9:26:69:89:
+    29:bc:6a:5a:8c:f3:e2:02:4c:0f:0c:5e:e5:7b:58:
+    69:bf:98:18:81:ca:f9:e3:66:5f:c7:f7:ef:c6:78:
+    92:9f:87:a5:6e:aa:42:ea:4d:1f:f6:69:18:22:dd:
+    79:a4:70:96:b7:76:d1:d8:f0:14:56:e5:87:3b:07:
+    38:40:6c:38:2c:57:3a:e9:cd:e2:d9:e7:f2:31:b6:
+    cc:5c:67:6e:7c:f4:39:63:37:30:13:a5:80:75:38:
+    1f:f0:94:9b:e0:84:54:6d:72:e4:f8:a3:e5:fe:4a:
+    a5:09:1a:dd:23:4e:2a:fe:00:30:b1:b6:63:ae:9d:
+    2d:32:41:09:86:b9:40:2a:aa:f2:46:5b:74:a5:e2:
+    d0:bc:38:e3:a9:2b:bd:dd:8a:1f:ed:7b:94:8c:23:
+    cc:e6:f8:c0:8f:e3:56:83:5b:a6:5b:0f:98:40:68:
+    61:6e:f4:81:38:ef:d8:9b:f3:57:a5:4d:2e:bb:f3:
+    76:cb:dc:c6:9c:5f:1f:61:c6:4d:27:94:bc:06:cc:
+    b9:ab:df:66:e2:50:85:d8:c8:30:e2:ae:3b:0f:e0:
+    f0:7a:7a:f8:b9:32:0b:f3:42:97:09:97:d6:7d:7c:
+    12:59:3a:8f:bf:ad:e6:35:aa:c5:30:83:a7:02:2c:
+    47:d5:f7:7a:52:b5:7b:59:8d:a9:39:2a:e6:d8:6a:
+    fc:46:fc:06:45:51:81:b9:c7:5a:64:6d:c2:1f:81:
+    e4:bf:21:37:53:de:73:7f:d2:a1:40:02:79:20:ad:
+    d3:5a:22:3f:9f:5f:44:65:ce:b6:0c:03:ed:04:55:
+    a3:33:a5:cc:83:ad:bf:43:f1:f4:2c:2c:cb:83:28:
+    c2:1c:7a:b7:fa:ed:2b:21:cf:ad:e2:da:55:22:3a:
+    aa:b2:af:9b:41:c7:33:23:41:74:63:41:b3:9a:a2:
+    f4:38:15:65:0f:54:80:51:14:24:cf:a6:90:17:79:
+    c4:d1:8b:63:8c:c0:28:7a:aa:f3:16:80:33:8d:20:
+    b1:7c:74:49:fd:c6:a2:78:a8:d9:6a:82:ee:4c:4e:
+    ca:40:12:5e:2d:65:29:00:71:c7:ae:f1:be:6a:99:
+    15:98:fb:9d:59:51:25:23:bc:d4:b3:8c:56:6b:8e:
+    80:a7:3a:e3:33:e1:34:41:43:27:ef:1d:83:c4:7c:
+    49:df:e7:93:6d:f1:33:8a:5e:24:77:87:86:8f:c8:
+    4f:dc:b9:5a:c8:9c:18:5c:4b:b5:fd:57:b2:33:8a:
+    c4:2b:41:c1:0a:82:3d:f3:96:24:f3:6b:15:a2:f0:
+    67:58:4e:06:ca:2e:08:cc:af:f1:61:8f:e0:1d:d0:
+    6d:f3:51:2e:0b:72:4d:ec:85:06:da:24:21:5a:ca:
+    cc:2c:51:b8:2a:d8:d3:02:00:2f:b4:10:68:b1:da:
+    4f:8b:b1:47:98:7b:35:16:ba:d5:db:dd:f0:13:18:
+    fd:3f:a9:bc:43:70:2a:c4:98:c7:19:d9:5f:2e:84:
+    1b:62:2a:5e:48:48:a3:c5:c2:62:95:99:92:ea:7a:
+    7d:72:ca:8a:36:80:28:f4:97:df:ad:93:35:5c:bb:
+    1b:b9:78:6d:14:ff:2c:f5:90:31:78:48:f9:58:56:
+    42:71:10:dd:a3:6f:51:92:a8:16:ce:9c:88:16:cc:
+    7b:bf:c8:04:ef:c4:00:85:a3:85:0b:89:f1:e7:fe:
+    56:56:db:a4:10:f9:06:a9:7c:32:33:6c:1a:e7:e8:
+    17:37:a8:3e:08:73:54:e4:28:da:85:38:d9:48:db:
+    f5:df:ac:b5:9d:d2:b5:fd:3b:c8:03:f4:ba:43:2c:
+    9a:73:9d:f2:cf:a9:ed:94:84:32:0f:97:ed:ff:1a:
+    48:c6:b8:6b:30:02:cf:b7:72:dd:5e:56:2b:c4:c3:
+    d6:83:ed:96:4b:61:99:fa:05:14:b0:79:0d:95:80:
+    95:b7:b8:5c:6b:e8:75:fb:b5:59:e1:93:01:46:cc:
+    ea:63:a3:88:a1:94:fe:09:c3:de:a0:3b:e5:2d:e2:
+    7e:90:10:17:af:e8:09:af:63:0a:73:82:bf:5c:4c:
+    d4:d1:b8:f4:15:79:fb:43:48:ed:e4:ca:05:f4:cd:
+    3f:13:9a:31:b2:54:4e:51:6d:be:40:86:b9:bb:4b:
+    2b:ed:47:e2:d2:30:98:2d:d5:19:24:29:d3:77:b7:
+    c0:74:5c:c0:68:e2:f5:a4:aa:04:c7:ff:87:20:9e:
+    d1:25:99:76:a0:fc:9b:25:e9:e8:51:d4:e3:50:2c:
+    02:c8:5d:6d:ff:02:9e:21:1d:01:eb:f0:e9:e7:18:
+    8d:56:8f:84:37:d8:13:b0:f1:22:f2:fb:17:60:3b:
+    69:3e:d9:c3:8f:17:cf:d5:0b:81:5e:6d:9d:fc:0e:
+    d2:cc:f1:9f:63:99:27:4a:14:20:f2:35:a5:9d:8b:
+    f7:24:34:5e:14:e4:5d:9e:4b:e8:93:4d:fc:3f:a9:
+    26:78:db:61:d7:11:8b:f5:3c:b8:a2:22:5b:33:5f:
+    7e:ae:50:e3:f9:41:23:76:28:db:76:d8:ea:38:f7:
+    7a:72:af:3a:26:c8:1f:e4:35:23:b3:35:53:5a:5d:
+    1d:b7:c3:8f:34:10:82:bb:57:34:d0:89:e8:ae:30:
+    9c:fd:a3:a0:bc:b5:cd:5b:09:71:13:c8:ed:f9:61:
+    6a:a4:f6:e6:63:1b:91:25:27:6f:b3:f6:80:a3:43:
+    41:c3:db:66:8d:c6:ca:d4:5f:c9:3b:27:08:ca:2a:
+    f7:5c:cc:e7:34:fd:19:1c:50:08:9d:ad:53:98:2f:
+    dd:ae:02:53:1f:f9:3e:1f:21:ff:39:5f:c0:a1:28:
+    74:ed:f0:6b:6f:96:47:e9:5a:73:24:58:6c:71:df:
+    d9:1d:90:1d:62:18:58:19:0f:ec:d0:0c:cd:11:0b:
+    ba:c5:9f:96:cb:88:4c:3c:93:99:47:48:a5:6f:41:
+    28:3b:fc:41:fb:89:05:21:53:a8:94:58:8c:3c:b9:
+    01:7f:3d:66:32:6c:98:56:37:e5:75:ac:b8:12:34:
+    63:42:65:40:25:d6:02:de:3b:a9:40:c1:9a:c1:a6:
+    33:df:fd:a9:77:b5:29:b8:01:3e:19:c1:d6:d0:68:
+    0f:4d:ae:62:c9:24:45:0a:e6:6a:ab:82:f2:14:73:
+    06:1d:ab:3d:62:b2:47:f9:07:e3:55:19:39:ad:3f:
+    54:65:e9:d0:8a:82:bf:ea:17:ee:a1:b6:b2:b9:23:
+    75:74:77:f9:93:00:0b:2f:43:b7:0f:28:aa:ab:1f:
+    e9:a2:6a:d1:fd:33:61:61:6c:0b:0e:24:2f:e7:66:
+    04:b7:03:3a:1f:30:e9:7e:28:f5:26:ca:3c:88:0f:
+    e2:b8:d9:d1:b0:c9:ff:18:8b:31:cb:9d:97:42:5a:
+    ca:b9:b2:16:d9:8a:6a:e3:55:e5:83:da:71:e8:86:
+    4e:e3:d1:6b:07:59:79:61:90:ef:54:5c:1e:62:bf:
+    ef:92:af:6c:a1:47:b1:32:44:d6:c8:92:fc:8e:f2:
+    23:ab:3f:43:f9:24:c2:f4:66:09:7e:e8
+pub:
+    48:68:3d:91:97:8e:31:eb:3d:dd:b8:b0:47:34:82:
+    d2:b8:8a:5f:62:59:49:fd:8f:58:a5:61:e6:96:bd:
+    4c:27:d0:5b:38:db:b2:ed:f0:1e:66:4e:fd:81:be:
+    1e:a8:93:68:8c:e6:8a:a2:d5:1c:59:58:f8:bb:c6:
+    eb:4e:89:ee:67:d2:c0:32:09:54:d5:72:12:ca:c7:
+    22:9f:f1:d6:ea:f0:39:28:bd:51:51:1f:8d:88:d8:
+    47:73:6c:7d:e2:73:0d:59:78:e5:41:07:13:16:09:
+    78:86:77:11:bf:55:39:a0:bf:c4:c3:50:c2:be:57:
+    2b:af:0e:e2:e2:fb:16:cc:fe:a0:80:28:d9:9a:c4:
+    9a:eb:b7:59:37:dd:ce:11:1c:da:b6:2f:ff:3c:ea:
+    8b:a2:23:3d:1e:56:fb:c5:c5:a1:e7:26:de:63:fa:
+    dd:2a:f0:16:b1:19:17:7f:a3:d9:71:a2:d9:27:71:
+    73:fc:e5:5b:67:74:5a:f0:b7:c2:1d:59:7d:be:b9:
+    3e:6a:32:f3:41:c4:9a:5a:8b:e9:e8:25:08:8d:1f:
+    2a:a4:51:55:d6:c8:ae:15:36:7e:4e:b0:03:b8:fd:
+    f7:85:10:71:94:97:39:f9:ff:f0:90:23:ea:f4:51:
+    04:d2:a8:4a:45:90:6e:ed:46:71:a4:4d:c2:8d:27:
+    98:7b:b5:5d:f6:9e:9e:85:61:f6:1a:80:a7:26:99:
+    50:38:65:fe:d9:b7:ee:72:a8:e1:7a:19:c4:08:14:
+    4f:4b:29:af:ef:70:31:c3:a6:d8:57:16:10:b4:2c:
+    9f:42:12:45:a8:8f:19:7e:16:81:2b:03:11:59:b6:
+    5b:96:87:e5:b3:e9:34:c5:22:5a:e9:8a:79:ba:73:
+    d2:b3:99:d7:35:10:ef:fa:d1:9e:53:b8:45:0f:0b:
+    a8:fc:e1:01:2f:d9:8d:26:0a:74:aa:aa:13:fa:e2:
+    49:a0:06:b1:c3:4f:5b:a0:b8:82:f2:63:78:22:2f:
+    b3:6f:22:83:c2:43:f0:ff:eb:5f:1b:b4:14:a0:a7:
+    0d:55:e3:d4:0a:56:b6:cb:c8:8a:e1:f0:3b:7b:28:
+    82:d9:8d:ee:a2:8e:14:5c:9d:ed:fd:8e:af:1c:ef:
+    2e:d9:4a:8b:05:0f:89:64:f4:6d:1e:a0:d0:c2:a4:
+    3e:0d:da:61:82:ad:bf:4f:6e:d1:75:b6:74:22:57:
+    85:9b:f2:2f:3a:41:7e:cf:1f:9d:89:31:7b:5e:53:
+    9d:58:7a:f1:6b:9e:13:13:e0:45:14:ff:a6:4b:a8:
+    b3:ff:2b:83:21:f8:81:1c:b3:fb:02:2c:8f:64:4e:
+    70:a4:b8:0a:2f:bf:ee:60:4a:bb:73:79:09:1e:a8:
+    e6:c5:c7:4d:fc:02:83:66:6b:40:c0:79:38:70:02:
+    82:04:a1:36:bf:5d:a9:56:8e:b7:98:d3:49:03:8b:
+    db:0c:11:e0:34:45:e7:84:7c:b5:06:9c:75:cf:28:
+    ac:60:1c:77:99:d9:58:21:0d:db:cb:22:6e:51:af:
+    ef:9f:1d:e4:7b:07:38:73:d6:d3:f9:74:56:be:de:
+    08:50:82:e7:4a:29:8b:2c:d4:8f:4b:30:93:15:5f:
+    36:6c:8f:a6:01:c6:af:85:8d:fa:32:c0:84:91:b2:
+    a2:98:87:f9:03:35:94:9a:5d:6e:da:a6:79:88:2a:
+    3a:95:d6:bf:6d:97:0a:22:1f:4b:9d:3d:8c:bf:38:
+    4a:f8:1a:ac:95:e2:b3:29:4e:04:78:9a:c8:37:27:
+    a5:dc:04:55:9f:96:af:41:d8:a0:53:51:6f:ee:ee:
+    bc:52:74:6e:b6:ab:28:19:e0:91:08:71:0d:83:5f:
+    01:1f:a6:30:65:87:2a:d3:34:d5:cd:ff:b2:b2:31:
+    05:07:e9:2f:c9:93:ae:31:7d:a9:7f:4f:30:9c:da:
+    f0:f6:7e:d9:9d:90:21:55:76:08:38:49:f9:53:b2:
+    46:d7:fe:db:3f:db:67:67:98:50:a5:ad:40:4e:64:
+    14:7f:b7:cf:4f:6a:ed:dd:05:af:b4:b8:34:96:8d:
+    1f:e8:80:14:96:0d:ce:5d:94:22:36:52:6e:12:a4:
+    78:d6:9e:5f:be:69:70:31:0b:30:8c:06:84:50:18:
+    cf:c7:b2:ab:43:0a:13:a6:b1:ac:7b:b0:2c:cc:bb:
+    3d:91:1a:c2:f1:10:68:61:3f:be:02:9b:fd:ce:02:
+    cf:5c:d3:89:50:ed:72:c8:39:44:ed:fb:c7:56:15:
+    af:87:f8:64:c0:51:f3:c5:54:56:c5:41:28:63:a4:
+    0c:06:d1:da:b5:62:bd:ff:05:71:b8:d3:c3:91:7b:
+    bd:30:08:80:bb:a5:e9:98:23:9b:95:fa:91:b7:d6:
+    41:6d:4f:39:8b:3a:db:cd:30:98:3e:d3:59:2b:4d:
+    9e:f7:d4:23:6f:d0:0f:50:d9:8a:a5:3a:23:5a:c4:
+    17:27:20:f7:7d:96:17:26:72:98:0c:fe:8f:f7:a5:
+    a7:02:78:3e:dc:2b:a3:1b:22:59:01:5a:11:2f:c7:
+    f4:68:a9:c2:f9:46:40:39:00:2d:30:ef:67:8b:4c:
+    b7:98:bc:11:62:16:bf:7a:9a:7c:18:ba:03:b7:b5:
+    8f:d0:75:15:d3:11:50:49:d3:61:4b:e7:a0:7e:74:
+    43:00:75:0d:f1:d2:c5:87:53:38:90:59:ea:fc:3d:
+    78:5c:cd:d3:1c:07:64:8b:ed:c0:3a:5c:3b:8a:d4:
+    6d:06:4d:59:c1:3d:57:37:47:29:fc:4e:29:53:62:
+    e2:a5:19:12:04:53:04:28:bc:15:22:af:a2:8f:f5:
+    fe:16:55:e3:04:ca:5b:c8:c2:7a:d0:e0:c6:a3:9d:
+    d4:df:28:95:6c:14:b3:8c:c9:36:82:ce:fe:40:2b:
+    bd:5e:82:d2:9c:46:4e:44:eb:5d:37:b4:8f:c5:68:
+    df:e0:cc:6e:8e:16:ba:ea:05:e5:13:55:90:f1:92:
+    94:e7:3e:83:67:b0:21:6d:bb:81:50:30:b9:de:55:
+    91:3f:08:03:9c:42:35:1c:59:e5:51:5d:d5:af:8e:
+    08:9a:15:e6:25:e8:f6:de:e6:39:38:6c:46:49:7d:
+    7a:26:32:88:77:4d:e5:81:a7:de:96:29:b4:1b:44:
+    24:14:1f:97:8f:b8:33:12:08:ef:de:c3:c6:e0:de:
+    39:bc:57:06:3f:3d:cd:6c:47:03:73:c0:88:91:ea:
+    29:cb:c7:cc:6d:64:83:b8:88:90:83:ac:e8:6a:a7:
+    b5:1b:1c:2c:fe:6e:2a:d1:8d:97:ce:36:fb:c5:6e:
+    a4:2f:ae:97:e6:a7:ac:11:48:64:47:8c:36:6d:f1:
+    eb:b1:e7:b1:1a:90:98:50:4f:d5:97:5b:df:1f:49:
+    dc:70:00:2b:63:c1:73:9a:9d:26:3f:ba:d4:07:3f:
+    6a:9f:6c:2b:8a:f4:b4:c3:32:a1:03:a0:cf:fa:5d:
+    ee:b2:d0:62:ca:3c:21:5f:d3:60:02:6b:e7:c5:16:
+    4f:4a:44:24:ef:74:94:88:04:d6:6f:46:48:77:32:
+    c8:20:2c:79:54:78:64:7b:4e:a7:1d:62:7c:08:60:
+    24:cc:a3:54:a4:1f:08:77:b3:8f:19:b3:77:4a:d2:
+    09:5c:8d:a5:3b:06:9e:21:c7:6a:e2:d2:00:7e:16:
+    71:9e:d4:00:80:d3:34:f7:da:52:e9:f5:a5:99:04:
+    39:ca:f0:83:a9:5b:83:3f:02:ad:10:a0:8c:1a:6d:
+    0f:26:0c:00:72:85:bd:4a:2f:47:70:3a:5a:ef:46:
+    52:87:d2:53:b1:8a:c2:25:14:31:62:10:ff:56:68:
+    14:b1:0f:87:a2:93:d6:f1:99:d3:c3:95:99:90:d0:
+    c1:26:8b:4f:50:d5:f9:fc:ef:bb:f2:37:bd:0c:28:
+    b8:01:82:d6:65:97:41:f1:4f:10:bf:bb:21:bb:a1:
+    2a:b6:20:aa:23:96:f5:6c:06:86:b4:ea:90:17:99:
+    02:24:21:6b:2f:e8:ad:76:c4:a9:14:8e:ef:9a:86:
+    a3:63:5a:6a:a7:7b:c1:dc:fb:6f:ba:59:a7:7d:fd:
+    a9:b7:53:0d:c0:ca:86:48:c8:d9:73:73:8e:01:ba:
+    b8:f0:8b:49:05:e8:4a:a4:64:1b:d6:02:41:0c:d9:
+    75:20:26:5f:2f:23:1f:2b:35:e1:5e:b2:fa:04:d2:
+    bd:94:d5:a7:7a:ba:f1:e0:e1:61:01:0a:99:00:87:
+    f5:b4:6e:a9:88:b2:bc:05:12:fd:a0:fa:92:3d:ad:
+    d6:c4:5c:53:01:d0:94:83:67:32:65:b5:ab:2e:10:
+    f4:ba:52:0f:6b:ba:d5:64:a5:c3:d5:e2:7b:db:08:
+    0f:7d:20:e1:32:96:a3:18:19:54:c3:9c:64:9c:94:
+    3e:be:17:df:5c:1f:7a:ae:0a:8f:e1:26:c4:77:58:
+    5a:5d:4d:64:8a:0d:00:8b:6a:f5:e8:cd:31:be:69:
+    a9:29:6d:4f:3f:d2:5e:d8:6f:22:1e:4b:93:f6:5f:
+    59:29:96:75:33:62:4b:92:35:75:0c:30:70:75:50:
+    b5:85:36:d1:09:a7:13:1c:5a:5b:be:4a:57:15:56:
+    7c:12:53:4a:ec:76:60:76:1e:eb:b9:fa:e2:89:1c:
+    77:45:89:b8:0e:56:6a:d5:57:dd:ef:73:67:19:6b:
+    72:27:ea:98:70:ef:09:dd:fe:c7:9d:6b:93:19:a6:
+    87:9b:52:05:d7:6b:f7:ab:a5:ac:f3:3a:fb:59:d1:
+    7f:c5:4e:68:38:3d:6b:e5:a0:8e:9b:66:da:53:dc:
+    de:00:8b:b2:94:b8:58:2b:d1:32:cd:cc:49:95:9f:
+    db:c2:1e:52:72:18:80:c8:ad:03:52:c7:9f:03:a4:
+    3b:bd:84:c4:cd:fd:c6:c5:29:00:5e:1e:7c:d9:a3:
+    49:a7:16:8a:35:56:9b:a5:de:a8:18:96:8d:5a:91:
+    46:6b:d6:e6:4e:20:bf:62:41:71:98:af:c4:e8:1c:
+    28:dd:77:ed:40:28:23:23:98:b5:2f:bd:e8:6b:c8:
+    4f:47:5b:90:16:71:0c:e2:aa:bc:11:a0:6b:4d:ba:
+    c9:01:ec:16:cf:36:5c:a3:f2:d5:38:13:94:8a:69:
+    3a:0f:93:e7:9c:46:ca:5d:5a:6d:ca:3d:28:ca:50:
+    ad:18:bd:13:fc:a5:50:59:dd:9b:18:5f:79:f9:c4:
+    71:96:a4:e8:1b:21:04:bc:46:0a:05:1e:02:f2:e8:
+    44:4f
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-65-seed-only.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-65-seed-only.pem
new file mode 100644 (file)
index 0000000..733f324
--- /dev/null
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MDQCAQAwCwYJYIZIAWUDBAMSBCKAIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ
+GhscHR4f
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-65-seed-priv.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-65-seed-priv.pem
new file mode 100644 (file)
index 0000000..ec3f331
--- /dev/null
@@ -0,0 +1,88 @@
+-----BEGIN PRIVATE KEY-----
+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
+1siS/I7yI6s/Q/kkwvRmCX7o
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-65-seed.txt b/test/recipes/15-test_ml_dsa_codecs_data/prv-65-seed.txt
new file mode 100644 (file)
index 0000000..b78c1ae
--- /dev/null
@@ -0,0 +1,407 @@
+ML-DSA-65 Private-Key:
+seed:
+    00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:
+    0f:10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:
+    1e:1f
+priv:
+    48:68:3d:91:97:8e:31:eb:3d:dd:b8:b0:47:34:82:
+    d2:b8:8a:5f:62:59:49:fd:8f:58:a5:61:e6:96:bd:
+    4c:27:d8:53:fa:69:b8:19:90:23:e8:cd:67:8d:d9:
+    fa:bf:90:47:64:6f:fd:0c:b3:cc:7f:79:58:05:a7:
+    1e:70:d2:37:1b:05:63:e3:cd:33:46:14:9c:8c:9e:
+    bc:f2:3b:0a:4e:5a:90:0e:ea:9c:65:62:79:0a:7c:
+    63:e3:86:63:da:a2:dd:db:6e:48:0d:c4:05:a1:e7:
+    01:94:8b:74:84:1e:f5:cc:1c:3f:2b:f3:27:97:2e:
+    95:10:51:0c:d5:37:5e:cc:08:55:71:77:11:87:22:
+    21:86:23:81:00:04:24:77:80:61:47:50:07:50:17:
+    17:03:55:04:51:51:25:47:18:38:04:61:75:72:22:
+    44:10:88:68:60:86:46:01:27:47:56:71:80:87:06:
+    66:86:43:32:44:41:22:04:36:38:66:75:02:82:36:
+    34:24:43:22:05:73:64:10:64:55:54:77:22:75:56:
+    81:43:36:14:62:55:08:20:64:37:68:54:68:75:43:
+    53:75:10:68:71:83:33:80:54:75:05:25:80:75:28:
+    18:84:38:11:08:72:60:20:20:08:58:83:01:83:61:
+    13:82:82:12:06:17:11:57:87:68:78:88:78:64:37:
+    54:60:16:57:15:50:84:71:88:66:07:27:32:88:06:
+    64:74:18:56:76:21:80:31:82:76:64:15:78:24:50:
+    25:64:66:43:11:35:04:36:47:80:12:66:73:14:30:
+    11:66:06:55:86:47:18:36:88:63:50:38:47:86:11:
+    01:20:23:56:11:61:37:86:07:85:32:12:40:07:54:
+    78:82:30:43:66:61:16:60:42:55:41:82:85:60:53:
+    67:78:56:38:43:44:30:63:26:10:77:07:31:78:42:
+    72:14:11:16:53:03:85:27:68:67:46:01:50:82:37:
+    35:32:07:66:10:75:04:68:12:48:06:66:03:03:26:
+    52:31:24:45:40:88:00:31:80:88:76:72:17:30:71:
+    82:47:21:51:27:80:11:65:44:74:86:61:72:23:33:
+    80:86:60:64:46:83:52:15:84:20:36:80:11:80:21:
+    18:18:33:17:73:54:53:48:81:00:44:86:53:67:43:
+    70:57:72:58:83:34:60:38:42:32:85:68:10:06:04:
+    26:04:25:84:56:02:35:68:20:51:83:86:38:43:24:
+    21:22:42:45:64:58:58:67:71:45:72:85:04:78:87:
+    17:18:06:18:83:60:86:86:41:56:50:81:16:50:26:
+    46:70:06:08:26:62:27:38:31:72:40:72:57:30:07:
+    27:28:86:20:66:75:88:68:26:07:06:40:20:33:03:
+    43:66:31:55:46:42:45:34:56:67:18:73:45:65:83:
+    70:22:50:84:68:56:28:80:70:36:70:84:62:37:17:
+    10:06:57:17:58:47:78:70:86:55:53:78:22:35:14:
+    46:77:28:56:73:03:22:87:00:14:33:20:61:71:58:
+    45:52:66:32:50:26:51:33:47:77:38:03:55:16:43:
+    13:47:35:10:66:27:51:75:74:02:46:88:81:70:67:
+    43:46:81:86:01:76:52:45:33:30:87:21:04:34:34:
+    01:03:22:87:63:51:55:26:50:81:30:77:45:44:41:
+    68:15:41:83:63:64:11:20:40:26:87:30:43:67:77:
+    12:80:88:46:35:54:53:00:62:45:81:04:58:36:51:
+    24:84:27:80:34:51:66:63:58:43:78:56:01:46:51:
+    15:74:23:21:43:66:85:22:47:77:31:34:50:17:83:
+    62:42:05:50:00:64:84:47:12:34:40:88:00:60:47:
+    35:40:57:83:33:63:08:21:06:15:22:52:07:24:88:
+    51:34:86:37:06:76:22:58:85:71:26:56:73:47:68:
+    16:46:46:84:25:87:08:12:27:05:50:08:38:32:00:
+    23:20:80:66:34:53:36:00:33:46:85:72:47:06:35:
+    54:00:35:77:12:27:52:30:71:42:53:68:74:37:45:
+    70:05:66:43:22:44:82:85:20:72:18:33:30:20:53:
+    37:33:40:77:27:80:55:25:30:63:52:50:40:67:33:
+    46:13:18:07:28:07:17:24:83:77:63:45:73:18:58:
+    51:60:23:33:44:36:25:16:43:38:16:08:58:77:34:
+    62:42:88:30:07:03:65:85:37:55:00:75:52:31:50:
+    37:02:13:24:63:04:37:08:68:06:36:15:03:03:00:
+    43:58:63:57:08:02:11:06:64:73:46:35:22:62:03:
+    30:43:80:21:08:52:87:57:83:21:07:88:67:48:08:
+    56:34:74:36:73:42:84:05:84:66:84:14:37:00:55:
+    10:87:34:26:44:77:21:12:73:84:73:65:26:47:25:
+    77:14:47:04:17:86:44:26:02:47:11:87:40:81:22:
+    16:60:58:47:17:81:37:06:76:80:81:70:58:18:55:
+    85:47:13:63:42:10:75:58:01:63:58:35:85:18:44:
+    03:84:71:10:33:87:42:62:82:47:74:13:65:54:42:
+    70:73:46:35:77:75:00:66:25:62:68:42:02:12:46:
+    83:86:46:16:64:60:31:22:53:88:84:54:00:84:57:
+    34:46:47:54:47:25:60:54:61:66:84:66:30:88:06:
+    38:27:15:63:28:71:83:84:06:52:24:76:81:16:06:
+    62:13:03:30:18:68:02:80:13:84:63:05:05:65:72:
+    38:75:83:65:72:32:30:68:80:46:12:26:06:65:16:
+    75:57:05:32:41:32:27:67:35:17:08:01:53:00:16:
+    28:46:01:34:88:77:01:11:88:15:57:13:15:46:43:
+    11:70:47:32:88:28:56:36:82:34:55:50:41:86:27:
+    65:63:11:11:68:75:05:10:42:54:41:44:27:85:22:
+    11:17:17:88:15:36:85:15:74:47:16:62:55:36:55:
+    83:63:02:50:28:55:76:87:53:27:13:71:03:72:37:
+    05:71:47:61:71:36:51:84:12:42:36:64:44:66:41:
+    43:52:05:21:08:51:57:03:33:63:86:02:58:42:66:
+    28:14:81:10:54:62:68:17:30:38:75:64:33:21:65:
+    88:56:86:63:63:28:13:40:62:54:01:20:40:88:65:
+    47:88:61:71:65:76:23:72:62:34:86:70:30:11:51:
+    15:63:20:50:75:35:02:12:21:08:42:65:31:43:55:
+    67:11:15:25:72:01:06:85:36:30:15:05:57:58:60:
+    58:78:43:14:31:32:78:78:80:87:38:47:88:63:78:
+    81:81:38:73:42:61:78:38:85:24:66:77:33:50:60:
+    21:15:14:64:23:82:32:68:01:35:44:07:83:47:53:
+    85:53:57:52:83:23:35:18:76:01:15:21:34:32:57:
+    73:33:36:55:18:86:15:81:61:68:24:18:42:21:22:
+    30:84:14:48:15:12:01:10:30:24:77:72:42:54:43:
+    66:06:77:17:70:76:03:01:45:25:40:35:00:18:38:
+    73:23:77:35:26:50:86:35:71:13:73:44:81:60:52:
+    77:45:65:53:73:00:85:83:77:85:03:51:21:11:54:
+    80:62:88:50:18:02:68:13:86:52:05:34:68:01:32:
+    07:24:18:03:21:30:05:72:38:64:07:64:27:11:41:
+    01:83:85:25:51:06:32:60:71:04:86:51:76:83:38:
+    28:57:27:62:35:45:18:73:50:83:13:28:86:37:66:
+    61:42:63:11:67:50:33:11:25:53:76:41:76:03:14:
+    33:17:72:12:23:44:18:a8:2e:4f:5c:9e:a0:fa:f9:
+    9e:b0:4d:78:a7:33:27:11:11:7c:33:f1:8e:ca:21:
+    f8:74:33:76:ad:a5:21:98:04:a7:ed:9a:55:57:fc:
+    d6:7a:35:50:b3:a4:b8:c5:88:62:9c:02:14:75:fa:
+    3d:56:d5:d6:cf:bb:1a:09:bd:a8:d1:4d:e6:22:dd:
+    ff:16:d8:bc:99:b1:42:78:a8:af:1d:76:be:d1:57:
+    67:2d:d9:c3:23:16:f9:7e:8d:aa:de:f8:d9:da:69:
+    58:67:25:56:7f:b9:6b:59:99:0d:4b:f0:bc:9c:19:
+    5b:90:b7:42:95:f5:67:5b:24:25:7c:27:10:c1:75:
+    b0:15:3f:29:11:32:8c:2e:b7:ab:b9:ad:46:e7:0a:
+    8b:53:c3:9e:a6:42:ce:e4:b3:cb:42:62:0e:86:3c:
+    e8:b6:50:ce:8a:dc:d9:23:72:1a:16:87:02:3c:67:
+    3a:8c:bb:6b:03:d5:1c:d1:97:e8:c3:46:eb:ad:ce:
+    93:95:0f:88:ce:e2:01:db:9e:32:08:43:e2:9f:30:
+    0d:9a:19:50:0d:70:a4:ca:f2:72:c6:9e:4e:ef:69:
+    fb:b8:a5:5e:fd:7c:a2:be:d9:90:d2:d3:b5:82:84:
+    8f:9c:45:c2:ab:c5:4c:fc:47:d3:4f:06:c0:ff:a5:
+    6f:cd:76:2a:b9:cb:a9:14:6d:77:25:21:89:63:b2:
+    40:d7:2b:6d:22:c9:31:71:fb:d4:77:88:b7:6e:72:
+    04:2d:ef:08:78:d2:3d:f6:31:a1:a1:e5:a6:02:76:
+    86:de:5b:4a:10:e9:10:69:c8:f2:ba:02:59:b0:4d:
+    64:09:da:96:56:7c:a5:2d:a4:97:02:6e:58:3a:0e:
+    ce:fc:1f:01:e6:b9:88:e2:1f:97:67:a2:b7:e1:67:
+    2d:eb:9a:1e:2a:3f:cc:86:3a:a9:15:17:c3:34:62:
+    06:01:b4:fe:79:73:0e:93:49:35:f4:b6:fb:c4:e3:
+    26:95:14:5c:2b:5f:6a:12:7f:ec:c0:a2:77:45:1e:
+    bc:3f:d5:23:44:4f:9e:e7:c9:c3:45:34:f3:56:db:
+    54:4f:c3:1c:1b:fd:e5:f6:5c:77:ea:2f:7c:2e:ae:
+    4c:55:eb:af:10:42:71:c5:66:fd:4e:ba:c7:1c:7a:
+    62:c7:49:52:81:7a:e6:75:50:4d:95:99:b1:b7:62:
+    b6:ac:a1:68:a8:32:48:c9:d9:ad:b0:ce:b1:55:6e:
+    57:59:49:0b:bc:0c:79:00:79:5a:d7:21:23:03:8b:
+    66:2f:64:f1:06:a9:99:36:81:a2:5d:59:af:7b:c9:
+    7a:23:5b:e9:28:4c:5b:c4:5a:6c:90:cb:1c:29:99:
+    c6:63:d9:6b:47:8e:23:07:f8:55:48:95:7d:65:74:
+    0e:26:73:e9:eb:d1:35:28:29:03:8f:46:2b:8f:d3:
+    b5:68:1d:a5:5c:02:52:52:38:53:52:5e:a0:ad:64:
+    7e:71:ac:2c:5a:88:93:e6:03:ac:97:e5:6c:04:ce:
+    b2:f2:6f:5c:5b:4b:6d:94:ab:81:13:80:fd:00:f2:
+    20:8f:e8:65:35:08:6a:eb:fd:35:c2:91:20:62:4c:
+    04:fb:b6:11:39:29:d9:c5:56:35:02:53:76:6c:20:
+    9f:db:a8:3c:95:fc:cd:34:2a:28:09:93:55:d0:0b:
+    c8:63:f4:ee:f5:96:eb:0b:42:eb:cc:7c:79:49:1c:
+    ce:ae:20:5e:a0:b8:05:9f:bb:8a:57:26:c5:94:9d:
+    2b:15:e7:e2:9c:51:fc:9b:02:ee:1a:4f:c3:57:b5:
+    f1:be:f9:c4:ad:d4:6a:2a:92:0c:2f:bf:08:a3:7e:
+    b1:51:4b:fa:15:11:0a:43:92:a7:4c:6f:13:c5:0c:
+    5c:ff:d9:75:31:09:8d:7c:d2:3b:60:eb:35:c4:a4:
+    28:b4:6c:55:38:6e:10:10:c4:ba:7f:70:e4:c7:ec:
+    b7:57:5f:30:63:a7:1e:84:df:dc:f0:9a:58:b2:cd:
+    b0:f9:9f:27:ed:37:86:10:d2:5c:ba:d7:bf:a6:ba:
+    0d:59:18:9c:fe:88:ea:b9:b4:6d:7e:6d:b0:30:7e:
+    ab:e4:19:8e:99:bd:71:f7:79:ab:66:58:1e:09:12:
+    fc:7b:1d:25:85:24:5e:9a:12:68:7a:97:5c:d5:e8:
+    e1:dc:c0:45:d5:f8:91:c4:c6:85:db:07:cf:81:e7:
+    73:89:b3:63:eb:6b:df:e3:9b:27:ff:84:c9:7e:ef:
+    ee:16:2e:3b:45:1f:e6:91:47:19:cb:64:36:d8:55:
+    96:0f:f9:15:d7:ce:a6:ad:ea:fd:fc:1c:05:78:6c:
+    49:f9:23:a4:74:ff:df:c3:15:3a:06:e6:ed:0b:0a:
+    d2:20:d7:25:24:43:4d:52:73:c0:aa:b6:dd:e4:e9:
+    14:76:d5:81:a2:69:5a:60:de:6d:9f:44:d7:7a:a0:
+    82:66:e9:38:ee:b4:a9:59:7c:9b:64:98:60:59:e4:
+    92:62:a4:ea:b2:45:4e:14:01:5a:d0:53:6c:42:73:
+    3a:5d:77:d7:99:5c:2a:20:44:60:09:eb:fe:56:32:
+    c8:0c:08:ed:2b:97:af:35:06:64:89:f5:97:eb:1b:
+    1f:11:f0:4f:60:e0:c9:04:01:59:c4:4a:b3:e6:0e:
+    0a:15:22:9d:19:12:28:be:d1:7b:bc:3a:c9:39:b3:
+    c6:7c:ee:13:5f:35:2c:27:21:6c:9c:31:f7:2a:3e:
+    87:04:0c:5f:61:93:06:eb:0b:6c:ca:2a:9c:e7:b2:
+    2a:16:94:d0:0c:a9:c0:5e:31:51:26:45:7f:26:ce:
+    84:f9:61:72:41:86:07:82:f8:64:b4:73:d8:40:17:
+    49:19:02:b1:bd:c8:cd:c5:80:0d:d4:61:27:fb:80:
+    a7:1c:09:5b:47:3a:56:25:29:b3:b1:e7:e4:37:e1:
+    58:a5:f6:66:6e:99:74:d0:05:b0:62:c2:30:9e:6d:
+    ce:98:f9:b6:58:c6:e3:f9:a2:16:d5:8c:8c:91:42:
+    bd:1c:8c:85:a9:da:87:2e:bb:fa:d3:fe:a9:d9:ab:
+    a2:b6:8c:0e:8f:19:c6:ff:5f:00:58:4d:45:da:f9:
+    d6:c9:d6:9e:d0:4b:8d:a8:d6:87:25:8b:77:80:79:
+    27:61:2c:53:04:46:fe:a7:69:7a:e3:f9:26:69:89:
+    29:bc:6a:5a:8c:f3:e2:02:4c:0f:0c:5e:e5:7b:58:
+    69:bf:98:18:81:ca:f9:e3:66:5f:c7:f7:ef:c6:78:
+    92:9f:87:a5:6e:aa:42:ea:4d:1f:f6:69:18:22:dd:
+    79:a4:70:96:b7:76:d1:d8:f0:14:56:e5:87:3b:07:
+    38:40:6c:38:2c:57:3a:e9:cd:e2:d9:e7:f2:31:b6:
+    cc:5c:67:6e:7c:f4:39:63:37:30:13:a5:80:75:38:
+    1f:f0:94:9b:e0:84:54:6d:72:e4:f8:a3:e5:fe:4a:
+    a5:09:1a:dd:23:4e:2a:fe:00:30:b1:b6:63:ae:9d:
+    2d:32:41:09:86:b9:40:2a:aa:f2:46:5b:74:a5:e2:
+    d0:bc:38:e3:a9:2b:bd:dd:8a:1f:ed:7b:94:8c:23:
+    cc:e6:f8:c0:8f:e3:56:83:5b:a6:5b:0f:98:40:68:
+    61:6e:f4:81:38:ef:d8:9b:f3:57:a5:4d:2e:bb:f3:
+    76:cb:dc:c6:9c:5f:1f:61:c6:4d:27:94:bc:06:cc:
+    b9:ab:df:66:e2:50:85:d8:c8:30:e2:ae:3b:0f:e0:
+    f0:7a:7a:f8:b9:32:0b:f3:42:97:09:97:d6:7d:7c:
+    12:59:3a:8f:bf:ad:e6:35:aa:c5:30:83:a7:02:2c:
+    47:d5:f7:7a:52:b5:7b:59:8d:a9:39:2a:e6:d8:6a:
+    fc:46:fc:06:45:51:81:b9:c7:5a:64:6d:c2:1f:81:
+    e4:bf:21:37:53:de:73:7f:d2:a1:40:02:79:20:ad:
+    d3:5a:22:3f:9f:5f:44:65:ce:b6:0c:03:ed:04:55:
+    a3:33:a5:cc:83:ad:bf:43:f1:f4:2c:2c:cb:83:28:
+    c2:1c:7a:b7:fa:ed:2b:21:cf:ad:e2:da:55:22:3a:
+    aa:b2:af:9b:41:c7:33:23:41:74:63:41:b3:9a:a2:
+    f4:38:15:65:0f:54:80:51:14:24:cf:a6:90:17:79:
+    c4:d1:8b:63:8c:c0:28:7a:aa:f3:16:80:33:8d:20:
+    b1:7c:74:49:fd:c6:a2:78:a8:d9:6a:82:ee:4c:4e:
+    ca:40:12:5e:2d:65:29:00:71:c7:ae:f1:be:6a:99:
+    15:98:fb:9d:59:51:25:23:bc:d4:b3:8c:56:6b:8e:
+    80:a7:3a:e3:33:e1:34:41:43:27:ef:1d:83:c4:7c:
+    49:df:e7:93:6d:f1:33:8a:5e:24:77:87:86:8f:c8:
+    4f:dc:b9:5a:c8:9c:18:5c:4b:b5:fd:57:b2:33:8a:
+    c4:2b:41:c1:0a:82:3d:f3:96:24:f3:6b:15:a2:f0:
+    67:58:4e:06:ca:2e:08:cc:af:f1:61:8f:e0:1d:d0:
+    6d:f3:51:2e:0b:72:4d:ec:85:06:da:24:21:5a:ca:
+    cc:2c:51:b8:2a:d8:d3:02:00:2f:b4:10:68:b1:da:
+    4f:8b:b1:47:98:7b:35:16:ba:d5:db:dd:f0:13:18:
+    fd:3f:a9:bc:43:70:2a:c4:98:c7:19:d9:5f:2e:84:
+    1b:62:2a:5e:48:48:a3:c5:c2:62:95:99:92:ea:7a:
+    7d:72:ca:8a:36:80:28:f4:97:df:ad:93:35:5c:bb:
+    1b:b9:78:6d:14:ff:2c:f5:90:31:78:48:f9:58:56:
+    42:71:10:dd:a3:6f:51:92:a8:16:ce:9c:88:16:cc:
+    7b:bf:c8:04:ef:c4:00:85:a3:85:0b:89:f1:e7:fe:
+    56:56:db:a4:10:f9:06:a9:7c:32:33:6c:1a:e7:e8:
+    17:37:a8:3e:08:73:54:e4:28:da:85:38:d9:48:db:
+    f5:df:ac:b5:9d:d2:b5:fd:3b:c8:03:f4:ba:43:2c:
+    9a:73:9d:f2:cf:a9:ed:94:84:32:0f:97:ed:ff:1a:
+    48:c6:b8:6b:30:02:cf:b7:72:dd:5e:56:2b:c4:c3:
+    d6:83:ed:96:4b:61:99:fa:05:14:b0:79:0d:95:80:
+    95:b7:b8:5c:6b:e8:75:fb:b5:59:e1:93:01:46:cc:
+    ea:63:a3:88:a1:94:fe:09:c3:de:a0:3b:e5:2d:e2:
+    7e:90:10:17:af:e8:09:af:63:0a:73:82:bf:5c:4c:
+    d4:d1:b8:f4:15:79:fb:43:48:ed:e4:ca:05:f4:cd:
+    3f:13:9a:31:b2:54:4e:51:6d:be:40:86:b9:bb:4b:
+    2b:ed:47:e2:d2:30:98:2d:d5:19:24:29:d3:77:b7:
+    c0:74:5c:c0:68:e2:f5:a4:aa:04:c7:ff:87:20:9e:
+    d1:25:99:76:a0:fc:9b:25:e9:e8:51:d4:e3:50:2c:
+    02:c8:5d:6d:ff:02:9e:21:1d:01:eb:f0:e9:e7:18:
+    8d:56:8f:84:37:d8:13:b0:f1:22:f2:fb:17:60:3b:
+    69:3e:d9:c3:8f:17:cf:d5:0b:81:5e:6d:9d:fc:0e:
+    d2:cc:f1:9f:63:99:27:4a:14:20:f2:35:a5:9d:8b:
+    f7:24:34:5e:14:e4:5d:9e:4b:e8:93:4d:fc:3f:a9:
+    26:78:db:61:d7:11:8b:f5:3c:b8:a2:22:5b:33:5f:
+    7e:ae:50:e3:f9:41:23:76:28:db:76:d8:ea:38:f7:
+    7a:72:af:3a:26:c8:1f:e4:35:23:b3:35:53:5a:5d:
+    1d:b7:c3:8f:34:10:82:bb:57:34:d0:89:e8:ae:30:
+    9c:fd:a3:a0:bc:b5:cd:5b:09:71:13:c8:ed:f9:61:
+    6a:a4:f6:e6:63:1b:91:25:27:6f:b3:f6:80:a3:43:
+    41:c3:db:66:8d:c6:ca:d4:5f:c9:3b:27:08:ca:2a:
+    f7:5c:cc:e7:34:fd:19:1c:50:08:9d:ad:53:98:2f:
+    dd:ae:02:53:1f:f9:3e:1f:21:ff:39:5f:c0:a1:28:
+    74:ed:f0:6b:6f:96:47:e9:5a:73:24:58:6c:71:df:
+    d9:1d:90:1d:62:18:58:19:0f:ec:d0:0c:cd:11:0b:
+    ba:c5:9f:96:cb:88:4c:3c:93:99:47:48:a5:6f:41:
+    28:3b:fc:41:fb:89:05:21:53:a8:94:58:8c:3c:b9:
+    01:7f:3d:66:32:6c:98:56:37:e5:75:ac:b8:12:34:
+    63:42:65:40:25:d6:02:de:3b:a9:40:c1:9a:c1:a6:
+    33:df:fd:a9:77:b5:29:b8:01:3e:19:c1:d6:d0:68:
+    0f:4d:ae:62:c9:24:45:0a:e6:6a:ab:82:f2:14:73:
+    06:1d:ab:3d:62:b2:47:f9:07:e3:55:19:39:ad:3f:
+    54:65:e9:d0:8a:82:bf:ea:17:ee:a1:b6:b2:b9:23:
+    75:74:77:f9:93:00:0b:2f:43:b7:0f:28:aa:ab:1f:
+    e9:a2:6a:d1:fd:33:61:61:6c:0b:0e:24:2f:e7:66:
+    04:b7:03:3a:1f:30:e9:7e:28:f5:26:ca:3c:88:0f:
+    e2:b8:d9:d1:b0:c9:ff:18:8b:31:cb:9d:97:42:5a:
+    ca:b9:b2:16:d9:8a:6a:e3:55:e5:83:da:71:e8:86:
+    4e:e3:d1:6b:07:59:79:61:90:ef:54:5c:1e:62:bf:
+    ef:92:af:6c:a1:47:b1:32:44:d6:c8:92:fc:8e:f2:
+    23:ab:3f:43:f9:24:c2:f4:66:09:7e:e8
+pub:
+    48:68:3d:91:97:8e:31:eb:3d:dd:b8:b0:47:34:82:
+    d2:b8:8a:5f:62:59:49:fd:8f:58:a5:61:e6:96:bd:
+    4c:27:d0:5b:38:db:b2:ed:f0:1e:66:4e:fd:81:be:
+    1e:a8:93:68:8c:e6:8a:a2:d5:1c:59:58:f8:bb:c6:
+    eb:4e:89:ee:67:d2:c0:32:09:54:d5:72:12:ca:c7:
+    22:9f:f1:d6:ea:f0:39:28:bd:51:51:1f:8d:88:d8:
+    47:73:6c:7d:e2:73:0d:59:78:e5:41:07:13:16:09:
+    78:86:77:11:bf:55:39:a0:bf:c4:c3:50:c2:be:57:
+    2b:af:0e:e2:e2:fb:16:cc:fe:a0:80:28:d9:9a:c4:
+    9a:eb:b7:59:37:dd:ce:11:1c:da:b6:2f:ff:3c:ea:
+    8b:a2:23:3d:1e:56:fb:c5:c5:a1:e7:26:de:63:fa:
+    dd:2a:f0:16:b1:19:17:7f:a3:d9:71:a2:d9:27:71:
+    73:fc:e5:5b:67:74:5a:f0:b7:c2:1d:59:7d:be:b9:
+    3e:6a:32:f3:41:c4:9a:5a:8b:e9:e8:25:08:8d:1f:
+    2a:a4:51:55:d6:c8:ae:15:36:7e:4e:b0:03:b8:fd:
+    f7:85:10:71:94:97:39:f9:ff:f0:90:23:ea:f4:51:
+    04:d2:a8:4a:45:90:6e:ed:46:71:a4:4d:c2:8d:27:
+    98:7b:b5:5d:f6:9e:9e:85:61:f6:1a:80:a7:26:99:
+    50:38:65:fe:d9:b7:ee:72:a8:e1:7a:19:c4:08:14:
+    4f:4b:29:af:ef:70:31:c3:a6:d8:57:16:10:b4:2c:
+    9f:42:12:45:a8:8f:19:7e:16:81:2b:03:11:59:b6:
+    5b:96:87:e5:b3:e9:34:c5:22:5a:e9:8a:79:ba:73:
+    d2:b3:99:d7:35:10:ef:fa:d1:9e:53:b8:45:0f:0b:
+    a8:fc:e1:01:2f:d9:8d:26:0a:74:aa:aa:13:fa:e2:
+    49:a0:06:b1:c3:4f:5b:a0:b8:82:f2:63:78:22:2f:
+    b3:6f:22:83:c2:43:f0:ff:eb:5f:1b:b4:14:a0:a7:
+    0d:55:e3:d4:0a:56:b6:cb:c8:8a:e1:f0:3b:7b:28:
+    82:d9:8d:ee:a2:8e:14:5c:9d:ed:fd:8e:af:1c:ef:
+    2e:d9:4a:8b:05:0f:89:64:f4:6d:1e:a0:d0:c2:a4:
+    3e:0d:da:61:82:ad:bf:4f:6e:d1:75:b6:74:22:57:
+    85:9b:f2:2f:3a:41:7e:cf:1f:9d:89:31:7b:5e:53:
+    9d:58:7a:f1:6b:9e:13:13:e0:45:14:ff:a6:4b:a8:
+    b3:ff:2b:83:21:f8:81:1c:b3:fb:02:2c:8f:64:4e:
+    70:a4:b8:0a:2f:bf:ee:60:4a:bb:73:79:09:1e:a8:
+    e6:c5:c7:4d:fc:02:83:66:6b:40:c0:79:38:70:02:
+    82:04:a1:36:bf:5d:a9:56:8e:b7:98:d3:49:03:8b:
+    db:0c:11:e0:34:45:e7:84:7c:b5:06:9c:75:cf:28:
+    ac:60:1c:77:99:d9:58:21:0d:db:cb:22:6e:51:af:
+    ef:9f:1d:e4:7b:07:38:73:d6:d3:f9:74:56:be:de:
+    08:50:82:e7:4a:29:8b:2c:d4:8f:4b:30:93:15:5f:
+    36:6c:8f:a6:01:c6:af:85:8d:fa:32:c0:84:91:b2:
+    a2:98:87:f9:03:35:94:9a:5d:6e:da:a6:79:88:2a:
+    3a:95:d6:bf:6d:97:0a:22:1f:4b:9d:3d:8c:bf:38:
+    4a:f8:1a:ac:95:e2:b3:29:4e:04:78:9a:c8:37:27:
+    a5:dc:04:55:9f:96:af:41:d8:a0:53:51:6f:ee:ee:
+    bc:52:74:6e:b6:ab:28:19:e0:91:08:71:0d:83:5f:
+    01:1f:a6:30:65:87:2a:d3:34:d5:cd:ff:b2:b2:31:
+    05:07:e9:2f:c9:93:ae:31:7d:a9:7f:4f:30:9c:da:
+    f0:f6:7e:d9:9d:90:21:55:76:08:38:49:f9:53:b2:
+    46:d7:fe:db:3f:db:67:67:98:50:a5:ad:40:4e:64:
+    14:7f:b7:cf:4f:6a:ed:dd:05:af:b4:b8:34:96:8d:
+    1f:e8:80:14:96:0d:ce:5d:94:22:36:52:6e:12:a4:
+    78:d6:9e:5f:be:69:70:31:0b:30:8c:06:84:50:18:
+    cf:c7:b2:ab:43:0a:13:a6:b1:ac:7b:b0:2c:cc:bb:
+    3d:91:1a:c2:f1:10:68:61:3f:be:02:9b:fd:ce:02:
+    cf:5c:d3:89:50:ed:72:c8:39:44:ed:fb:c7:56:15:
+    af:87:f8:64:c0:51:f3:c5:54:56:c5:41:28:63:a4:
+    0c:06:d1:da:b5:62:bd:ff:05:71:b8:d3:c3:91:7b:
+    bd:30:08:80:bb:a5:e9:98:23:9b:95:fa:91:b7:d6:
+    41:6d:4f:39:8b:3a:db:cd:30:98:3e:d3:59:2b:4d:
+    9e:f7:d4:23:6f:d0:0f:50:d9:8a:a5:3a:23:5a:c4:
+    17:27:20:f7:7d:96:17:26:72:98:0c:fe:8f:f7:a5:
+    a7:02:78:3e:dc:2b:a3:1b:22:59:01:5a:11:2f:c7:
+    f4:68:a9:c2:f9:46:40:39:00:2d:30:ef:67:8b:4c:
+    b7:98:bc:11:62:16:bf:7a:9a:7c:18:ba:03:b7:b5:
+    8f:d0:75:15:d3:11:50:49:d3:61:4b:e7:a0:7e:74:
+    43:00:75:0d:f1:d2:c5:87:53:38:90:59:ea:fc:3d:
+    78:5c:cd:d3:1c:07:64:8b:ed:c0:3a:5c:3b:8a:d4:
+    6d:06:4d:59:c1:3d:57:37:47:29:fc:4e:29:53:62:
+    e2:a5:19:12:04:53:04:28:bc:15:22:af:a2:8f:f5:
+    fe:16:55:e3:04:ca:5b:c8:c2:7a:d0:e0:c6:a3:9d:
+    d4:df:28:95:6c:14:b3:8c:c9:36:82:ce:fe:40:2b:
+    bd:5e:82:d2:9c:46:4e:44:eb:5d:37:b4:8f:c5:68:
+    df:e0:cc:6e:8e:16:ba:ea:05:e5:13:55:90:f1:92:
+    94:e7:3e:83:67:b0:21:6d:bb:81:50:30:b9:de:55:
+    91:3f:08:03:9c:42:35:1c:59:e5:51:5d:d5:af:8e:
+    08:9a:15:e6:25:e8:f6:de:e6:39:38:6c:46:49:7d:
+    7a:26:32:88:77:4d:e5:81:a7:de:96:29:b4:1b:44:
+    24:14:1f:97:8f:b8:33:12:08:ef:de:c3:c6:e0:de:
+    39:bc:57:06:3f:3d:cd:6c:47:03:73:c0:88:91:ea:
+    29:cb:c7:cc:6d:64:83:b8:88:90:83:ac:e8:6a:a7:
+    b5:1b:1c:2c:fe:6e:2a:d1:8d:97:ce:36:fb:c5:6e:
+    a4:2f:ae:97:e6:a7:ac:11:48:64:47:8c:36:6d:f1:
+    eb:b1:e7:b1:1a:90:98:50:4f:d5:97:5b:df:1f:49:
+    dc:70:00:2b:63:c1:73:9a:9d:26:3f:ba:d4:07:3f:
+    6a:9f:6c:2b:8a:f4:b4:c3:32:a1:03:a0:cf:fa:5d:
+    ee:b2:d0:62:ca:3c:21:5f:d3:60:02:6b:e7:c5:16:
+    4f:4a:44:24:ef:74:94:88:04:d6:6f:46:48:77:32:
+    c8:20:2c:79:54:78:64:7b:4e:a7:1d:62:7c:08:60:
+    24:cc:a3:54:a4:1f:08:77:b3:8f:19:b3:77:4a:d2:
+    09:5c:8d:a5:3b:06:9e:21:c7:6a:e2:d2:00:7e:16:
+    71:9e:d4:00:80:d3:34:f7:da:52:e9:f5:a5:99:04:
+    39:ca:f0:83:a9:5b:83:3f:02:ad:10:a0:8c:1a:6d:
+    0f:26:0c:00:72:85:bd:4a:2f:47:70:3a:5a:ef:46:
+    52:87:d2:53:b1:8a:c2:25:14:31:62:10:ff:56:68:
+    14:b1:0f:87:a2:93:d6:f1:99:d3:c3:95:99:90:d0:
+    c1:26:8b:4f:50:d5:f9:fc:ef:bb:f2:37:bd:0c:28:
+    b8:01:82:d6:65:97:41:f1:4f:10:bf:bb:21:bb:a1:
+    2a:b6:20:aa:23:96:f5:6c:06:86:b4:ea:90:17:99:
+    02:24:21:6b:2f:e8:ad:76:c4:a9:14:8e:ef:9a:86:
+    a3:63:5a:6a:a7:7b:c1:dc:fb:6f:ba:59:a7:7d:fd:
+    a9:b7:53:0d:c0:ca:86:48:c8:d9:73:73:8e:01:ba:
+    b8:f0:8b:49:05:e8:4a:a4:64:1b:d6:02:41:0c:d9:
+    75:20:26:5f:2f:23:1f:2b:35:e1:5e:b2:fa:04:d2:
+    bd:94:d5:a7:7a:ba:f1:e0:e1:61:01:0a:99:00:87:
+    f5:b4:6e:a9:88:b2:bc:05:12:fd:a0:fa:92:3d:ad:
+    d6:c4:5c:53:01:d0:94:83:67:32:65:b5:ab:2e:10:
+    f4:ba:52:0f:6b:ba:d5:64:a5:c3:d5:e2:7b:db:08:
+    0f:7d:20:e1:32:96:a3:18:19:54:c3:9c:64:9c:94:
+    3e:be:17:df:5c:1f:7a:ae:0a:8f:e1:26:c4:77:58:
+    5a:5d:4d:64:8a:0d:00:8b:6a:f5:e8:cd:31:be:69:
+    a9:29:6d:4f:3f:d2:5e:d8:6f:22:1e:4b:93:f6:5f:
+    59:29:96:75:33:62:4b:92:35:75:0c:30:70:75:50:
+    b5:85:36:d1:09:a7:13:1c:5a:5b:be:4a:57:15:56:
+    7c:12:53:4a:ec:76:60:76:1e:eb:b9:fa:e2:89:1c:
+    77:45:89:b8:0e:56:6a:d5:57:dd:ef:73:67:19:6b:
+    72:27:ea:98:70:ef:09:dd:fe:c7:9d:6b:93:19:a6:
+    87:9b:52:05:d7:6b:f7:ab:a5:ac:f3:3a:fb:59:d1:
+    7f:c5:4e:68:38:3d:6b:e5:a0:8e:9b:66:da:53:dc:
+    de:00:8b:b2:94:b8:58:2b:d1:32:cd:cc:49:95:9f:
+    db:c2:1e:52:72:18:80:c8:ad:03:52:c7:9f:03:a4:
+    3b:bd:84:c4:cd:fd:c6:c5:29:00:5e:1e:7c:d9:a3:
+    49:a7:16:8a:35:56:9b:a5:de:a8:18:96:8d:5a:91:
+    46:6b:d6:e6:4e:20:bf:62:41:71:98:af:c4:e8:1c:
+    28:dd:77:ed:40:28:23:23:98:b5:2f:bd:e8:6b:c8:
+    4f:47:5b:90:16:71:0c:e2:aa:bc:11:a0:6b:4d:ba:
+    c9:01:ec:16:cf:36:5c:a3:f2:d5:38:13:94:8a:69:
+    3a:0f:93:e7:9c:46:ca:5d:5a:6d:ca:3d:28:ca:50:
+    ad:18:bd:13:fc:a5:50:59:dd:9b:18:5f:79:f9:c4:
+    71:96:a4:e8:1b:21:04:bc:46:0a:05:1e:02:f2:e8:
+    44:4f
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-87-bare-priv.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-87-bare-priv.pem
new file mode 100644 (file)
index 0000000..955ee10
--- /dev/null
@@ -0,0 +1,105 @@
+-----BEGIN PRIVATE KEY-----
+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
+lf/C6W/FWWVn6UNk36ptnVpuuZrk3fQk
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-87-bare-seed.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-87-bare-seed.pem
new file mode 100644 (file)
index 0000000..6a54733
--- /dev/null
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MDICAQAwCwYJYIZIAWUDBAMTBCAAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRob
+HB0eHw==
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-87-oqskeypair.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-87-oqskeypair.pem
new file mode 100644 (file)
index 0000000..55290ca
--- /dev/null
@@ -0,0 +1,159 @@
+-----BEGIN PRIVATE KEY-----
+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
+6IX3hvqG5VvgYiIvi6kKl0BzMmsxISrs4KNKYA==
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-87-priv-only.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-87-priv-only.pem
new file mode 100644 (file)
index 0000000..150b365
--- /dev/null
@@ -0,0 +1,105 @@
+-----BEGIN PRIVATE KEY-----
+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
+v+h/H5X/wulvxVllZ+lDZN+qbZ1abrma5N30JA==
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-87-priv.txt b/test/recipes/15-test_ml_dsa_codecs_data/prv-87-priv.txt
new file mode 100644 (file)
index 0000000..13e7612
--- /dev/null
@@ -0,0 +1,503 @@
+ML-DSA-87 Private-Key:
+priv:
+    97:92:bc:ec:2f:24:30:68:6a:82:fc:cf:3c:2f:5f:
+    f6:65:e7:71:d7:ab:41:b9:02:58:cf:a7:e9:0e:c9:
+    71:24:d8:e9:ee:4e:90:a1:6c:60:2f:5e:c9:bc:38:
+    51:7d:c3:0e:32:9d:5a:b2:76:73:bd:85:f4:c9:b0:
+    30:0f:77:63:89:88:67:50:b5:7c:24:db:3f:c0:12:
+    e6:1e:de:59:75:33:37:37:4f:a7:12:49:91:54:9a:
+    f2:43:49:6d:06:37:cb:3b:e0:5a:59:48:23:5b:f7:
+    98:75:f8:96:d8:fe:0c:ab:30:c8:49:48:db:4d:63:
+    15:aa:af:16:0a:c6:24:36:64:22:01:48:16:11:09:
+    11:2c:94:02:89:22:45:2c:62:b8:45:00:45:2a:08:
+    96:70:90:12:6e:14:93:70:d4:46:10:84:44:51:58:
+    96:91:0c:a9:29:82:b2:41:c9:08:71:c4:28:68:04:
+    96:89:48:40:85:9b:22:6d:1c:28:64:59:12:41:9c:
+    b8:91:84:04:89:44:90:05:cb:34:62:a0:86:90:40:
+    26:92:20:99:29:13:05:69:5c:34:68:a4:32:8e:19:
+    26:92:59:46:10:09:a4:49:23:42:4d:12:36:61:58:
+    10:65:01:28:90:1a:33:4c:99:86:31:d3:a2:49:09:
+    82:25:43:14:28:c0:38:81:03:15:4d:5b:28:86:08:
+    87:48:23:31:52:94:22:25:c3:c0:4d:a4:98:21:98:
+    40:20:d1:42:86:cb:40:70:5b:b0:71:9c:96:2c:c1:
+    12:06:53:46:09:0c:45:02:14:46:6e:91:b4:21:54:
+    b0:8c:e4:46:42:9a:20:8c:01:21:25:13:41:05:5a:
+    40:22:13:c9:0c:a0:18:40:52:c2:30:cb:34:2c:4b:
+    c8:68:1b:a4:60:49:84:84:63:30:29:4a:a0:69:5b:
+    80:04:d2:38:0a:14:26:4c:e2:b2:44:8b:a2:11:24:
+    46:49:c4:14:52:0b:42:71:03:b2:10:92:28:80:01:
+    24:88:e3:08:11:0a:05:28:19:c4:81:00:20:22:dc:
+    44:68:42:12:22:44:00:2a:c9:26:6a:0c:87:31:e0:
+    c0:44:99:14:84:18:36:0d:11:37:42:22:18:8c:63:
+    b2:91:0c:98:08:a1:a0:01:08:92:44:04:13:24:5c:
+    98:71:82:84:71:84:32:52:51:b0:31:9c:42:2e:1a:
+    a8:28:02:08:91:01:c0:89:0b:c7:05:8a:24:65:22:
+    c2:64:4c:88:91:5c:82:68:13:a5:64:50:20:86:21:
+    04:02:91:94:44:48:22:30:22:39:4a:02:98:84:09:
+    a2:88:19:19:29:44:48:8c:22:95:0c:a1:04:72:04:
+    87:70:12:21:10:42:06:84:1b:49:85:89:06:4a:d3:
+    36:08:db:c0:40:58:b6:51:0c:a7:09:8c:24:61:99:
+    90:64:8c:c2:90:5b:24:90:10:a3:49:03:25:61:43:
+    32:8a:11:98:44:c8:b2:20:04:38:41:10:47:2c:19:
+    c6:44:1c:25:2c:04:88:30:d9:46:69:9b:20:00:1b:
+    46:82:5a:a4:80:5b:a0:49:18:90:25:00:26:80:0b:
+    c2:31:5a:40:72:54:c6:20:c1:b0:31:24:b1:4d:10:
+    95:28:14:00:0a:a0:c8:4d:54:a2:88:23:98:81:60:
+    90:04:02:16:2c:13:21:40:91:86:8d:08:c2:91:91:
+    14:26:d0:b4:0c:09:c6:60:51:44:2e:04:11:26:00:
+    29:11:93:c2:08:63:a4:31:22:00:28:c1:14:08:0c:
+    40:2c:41:14:06:9c:20:72:54:22:06:8b:08:4d:a1:
+    48:69:10:30:41:1c:28:49:44:18:8e:cc:96:48:d9:
+    42:50:1b:06:49:0c:45:88:23:04:0d:20:30:2e:23:
+    85:2c:14:07:30:40:b6:85:4c:c0:20:44:86:20:19:
+    00:6c:54:02:48:cc:88:6c:59:06:32:49:14:84:04:
+    c7:50:13:49:28:e4:06:09:d3:c6:10:c8:28:4c:23:
+    39:44:52:a4:64:cc:a8:49:44:38:32:0a:89:84:00:
+    34:2c:22:85:8d:10:31:09:09:32:65:1c:89:8c:40:
+    40:29:21:85:00:09:a1:6d:84:c0:64:e2:02:2d:48:
+    04:40:12:09:8e:e0:42:2e:93:44:08:12:10:6a:01:
+    84:05:92:30:8a:cb:34:8e:a2:26:2e:5c:86:11:0b:
+    35:08:18:10:00:02:34:26:24:23:89:d1:84:00:24:
+    46:60:13:b2:49:24:28:46:18:02:71:a0:38:90:89:
+    14:44:d3:96:2d:a3:18:40:23:27:21:c0:18:50:43:
+    c8:04:41:42:8d:5c:26:41:44:a2:6d:48:12:0e:40:
+    32:25:0b:14:82:0a:48:2e:cb:82:88:03:a3:60:1b:
+    25:26:8c:b8:20:24:b0:85:98:04:21:08:a7:2c:83:
+    38:64:54:32:89:01:04:01:23:49:84:02:95:69:d1:
+    a4:4d:13:a4:0c:91:46:0d:61:94:80:90:38:45:5c:
+    c6:50:01:17:20:53:c6:28:9b:18:10:41:12:68:90:
+    12:21:c0:10:84:42:16:92:53:82:29:81:26:49:d8:
+    a4:05:9a:26:24:24:03:29:e0:40:26:d2:02:48:11:
+    24:68:11:99:89:98:14:85:c9:20:0d:50:12:8c:1c:
+    08:10:02:10:00:00:95:28:c1:28:90:59:b4:85:d3:
+    14:65:0a:40:6e:11:29:65:18:c2:4c:21:34:65:d8:
+    30:69:10:30:52:19:31:66:8c:18:8a:c8:c0:08:4c:
+    98:30:a3:a6:20:41:16:22:18:05:2e:22:25:2a:64:
+    b8:25:0a:b3:01:63:20:84:09:80:09:19:28:0e:02:
+    11:01:a3:94:11:e3:98:6c:58:20:21:09:41:10:60:
+    36:22:08:06:71:12:c2:85:5a:a0:85:c0:c6:84:5c:
+    38:06:cb:b6:69:14:84:84:53:22:82:a1:a6:40:cc:
+    86:00:c4:26:22:a0:a8:08:98:34:72:d4:20:41:43:
+    c4:90:48:16:68:1b:16:52:1a:37:02:50:20:42:48:
+    48:8a:20:11:41:e2:00:6c:c0:c2:0c:14:06:49:11:
+    31:4d:19:06:0a:89:46:09:1b:81:65:44:c8:00:82:
+    06:70:00:16:72:cc:24:50:8a:42:89:9c:96:90:64:
+    28:70:92:b2:68:98:26:62:61:94:40:c1:16:89:d8:
+    42:64:1a:21:4e:62:90:64:21:c8:24:8b:28:6d:5c:
+    42:92:a0:c6:4d:0c:85:80:cc:88:4d:d4:42:8d:42:
+    34:8a:0b:04:51:c3:26:86:24:25:81:12:35:06:a0:
+    44:04:c8:94:81:5b:b4:31:1c:08:06:5c:24:08:03:
+    27:6a:20:c2:25:e1:80:90:19:b4:6d:a3:46:0c:4b:
+    18:60:50:c6:2c:1b:92:2d:11:15:04:a2:00:04:21:
+    48:2e:d8:16:06:d2:10:8a:83:a2:25:08:31:0d:09:
+    38:51:d9:48:49:0b:16:4c:23:32:25:19:19:02:4a:
+    44:09:d1:b2:21:0b:83:2c:23:25:85:93:16:85:44:
+    a0:44:1b:83:50:02:22:72:4b:04:80:9b:14:65:21:
+    93:60:18:13:0a:d9:46:0d:22:45:61:c8:b4:40:a1:
+    42:2d:02:b8:09:00:14:44:9b:b6:11:0b:97:8c:40:
+    10:4a:82:14:6a:da:90:05:1c:02:8e:0c:19:72:a3:
+    b4:8d:24:30:50:11:87:09:64:c6:28:e4:18:92:98:
+    b4:6c:61:16:51:40:46:0e:1c:32:48:da:20:51:88:
+    36:8a:23:b1:21:82:90:28:1a:15:32:e2:18:61:92:
+    04:8e:13:b6:90:13:13:68:c9:84:68:4c:40:6d:0b:
+    33:00:81:46:4d:d2:38:0c:04:96:81:a4:88:50:02:
+    90:85:22:b0:04:d3:a4:71:d2:80:10:ca:96:40:51:
+    a6:41:a4:84:28:e0:08:52:0b:30:8c:d2:38:0a:0c:
+    29:51:c3:82:09:ca:20:91:d8:36:92:a3:a6:28:92:
+    42:22:a2:16:01:1a:34:86:37:d9:a6:59:16:98:81:
+    ec:21:cf:48:11:86:9d:1d:7f:13:9f:05:37:e9:6f:
+    11:84:58:54:05:fd:17:80:8a:f1:e0:62:39:d3:b3:
+    4e:5a:ca:8b:f1:36:96:77:b4:47:ac:71:8a:c4:7d:
+    85:0c:4d:77:b0:be:31:dc:9f:50:8e:39:78:f2:42:
+    74:ab:01:85:f7:27:ab:df:f5:9f:44:90:37:1b:f0:
+    46:10:e3:64:e6:4e:c8:75:ef:9d:20:dc:94:07:7e:
+    1e:16:63:27:a8:79:b8:ab:51:61:60:b2:a3:f7:74:
+    37:b9:b3:cc:7d:17:ae:ad:dc:84:db:62:74:6a:35:
+    ac:09:6f:78:2f:62:a7:f0:1a:a6:d6:69:3d:ee:c9:
+    0b:23:c6:69:85:a0:23:07:e0:a1:ca:e5:98:a6:73:
+    24:db:a0:f5:2f:22:43:22:75:e9:32:57:06:5c:3b:
+    7e:5e:1c:fe:1d:fd:4d:0d:f0:86:df:21:24:34:14:
+    a2:d2:7e:20:23:0a:82:9b:e4:eb:4c:82:c1:6d:35:
+    f7:8b:0e:5e:19:83:32:e0:00:74:bb:64:61:2f:ab:
+    17:d4:c8:97:1c:b6:8e:5e:da:b0:36:9f:11:57:b3:
+    46:9a:bd:83:84:e2:d9:55:3f:1b:78:e7:86:e1:ee:
+    9d:0b:98:d3:9f:83:cc:ec:f3:7d:1e:bd:3a:9d:63:
+    ae:c7:66:16:4a:10:17:1a:4f:d8:c6:3d:af:18:2c:
+    42:12:58:c5:f5:29:aa:55:cb:7e:ba:e2:e1:65:23:
+    15:e1:f7:1e:8a:74:13:14:10:d0:32:47:ed:e1:1d:
+    34:db:91:f6:f0:8a:a2:47:8f:d7:89:67:9c:04:94:
+    9f:71:bc:01:71:e0:7e:3a:8b:b5:75:3d:bb:da:a4:
+    11:a6:35:0a:b4:6e:ef:bf:86:fc:55:1c:29:ef:e4:
+    cd:d7:66:1d:5c:f6:c3:db:22:d0:ce:dd:e5:99:85:
+    44:59:d9:7f:20:df:74:55:bd:f3:56:a1:98:d0:f7:
+    eb:6d:34:11:1f:c9:40:b2:5c:05:43:b7:88:ed:da:
+    9d:26:81:0e:ac:3d:6c:c9:c5:13:27:c2:cf:83:e8:
+    87:d4:08:9e:19:69:5e:11:ad:d8:37:f6:f4:40:cc:
+    36:0f:93:f3:2f:ee:8a:96:63:71:2c:6b:bd:38:c8:
+    4a:b7:b5:48:23:ec:36:3e:b7:e4:2e:b5:9f:c1:fc:
+    e6:0f:bd:55:30:7b:3e:c8:5f:d9:da:f3:20:6d:7b:
+    4b:39:17:f1:c8:b7:a9:2e:3c:67:d8:98:80:fd:f2:
+    e4:7f:5a:0c:99:45:95:db:17:0a:f4:1b:ab:f5:a2:
+    5b:4d:c1:c4:2d:d6:a9:db:27:1e:76:4d:e2:fb:01:
+    5a:49:a8:50:c7:91:9b:e4:70:06:a3:36:e2:e3:25:
+    fd:e5:3a:c5:99:55:4d:0a:7d:e4:ef:45:ec:40:c3:
+    9d:6b:af:f3:11:be:ee:75:d8:9e:02:ad:31:f4:be:
+    4b:d2:0a:e9:19:4f:5e:dd:da:a6:65:07:76:11:6e:
+    9f:27:0f:77:71:4a:d7:a8:e8:9a:ce:f7:4b:7f:f7:
+    d8:db:ec:27:f8:02:0a:98:52:47:e2:cd:ac:ef:48:
+    94:a4:d6:8b:a3:7c:a9:12:d6:be:73:50:1c:99:51:
+    81:e5:b7:77:23:35:0b:36:31:da:37:00:e1:3f:d3:
+    66:e1:31:bf:06:b3:6e:b6:b0:34:50:93:20:9f:0a:
+    7b:ef:fa:e1:fd:d8:75:b0:06:87:c1:16:3c:35:3d:
+    7d:2a:c9:09:37:b3:4e:97:8e:92:f8:21:ad:c9:66:
+    22:02:ec:e8:9a:17:e7:bb:65:ae:17:d8:3b:90:db:
+    be:6a:50:1a:4e:13:45:be:e4:e5:a5:b5:3a:f2:e5:
+    ba:3d:1e:f3:f4:e0:5a:df:0b:3a:4c:f2:e5:30:36:
+    0f:ee:64:92:99:02:b5:71:f6:fd:2e:30:56:52:a4:
+    cb:01:0f:79:f8:15:e1:8f:2b:bb:8c:c8:9f:a6:fc:
+    76:f7:7c:89:e2:93:cf:17:5a:0b:19:58:00:fe:72:
+    d2:cc:dd:7d:75:e5:bd:90:bc:6a:c4:35:d6:a4:40:
+    ef:85:2e:9a:1c:8c:53:de:03:bf:19:33:65:d7:35:
+    aa:f2:9c:51:62:a6:17:e3:64:e7:f9:44:16:8d:0f:
+    b4:8f:ef:40:55:8f:45:42:97:cc:3d:d5:08:66:2c:
+    f2:3f:b8:8e:19:54:aa:45:d1:c5:e1:15:bc:c3:6f:
+    05:b3:e0:98:d5:55:22:0f:40:be:26:29:b3:45:07:
+    b8:46:4c:54:c2:7b:5d:ec:78:da:8f:22:65:05:14:
+    79:7a:f8:6a:25:12:bc:b7:e2:92:33:79:ef:6d:73:
+    c1:37:00:6c:1b:38:f5:1e:37:f9:35:85:e2:90:41:
+    a3:e4:e3:af:46:00:7c:e1:3b:8b:5f:7b:17:d5:d6:
+    5d:7d:56:68:e4:27:bc:be:7e:c1:d7:c4:08:c0:54:
+    a4:8c:1a:e7:97:bf:99:ac:bc:8d:26:07:52:29:35:
+    fd:66:5e:a7:82:2d:93:0f:23:ea:bf:f7:83:bb:23:
+    69:75:69:e2:04:b9:43:14:1e:00:c0:88:10:95:6b:
+    e0:52:53:65:db:ab:54:ed:48:cb:76:96:4c:cd:f5:
+    cb:d3:ae:e7:28:2d:4a:00:00:d2:78:4d:7b:8f:ab:
+    16:b2:f7:f0:d5:22:57:32:b1:ef:bc:4e:b1:cf:ed:
+    eb:43:fd:e7:9b:69:ec:c0:fb:ea:a1:e6:b4:07:28:
+    67:3b:d4:b2:e9:8a:0d:4a:8f:02:f8:53:95:07:30:
+    f2:8d:35:eb:12:fc:c7:97:68:b8:e1:8e:4b:da:0e:
+    58:a3:31:a2:f7:1d:7c:cc:2d:45:1b:32:b1:c6:5c:
+    31:2a:cf:47:ee:51:3b:21:95:4c:41:c0:0c:87:38:
+    72:ee:94:cf:14:f4:60:37:42:53:61:f4:bd:b5:48:
+    21:f7:11:46:0c:eb:ae:8c:07:50:8a:92:19:f8:8f:
+    a6:be:da:a6:78:ee:d5:01:94:4a:16:ae:6f:7b:5b:
+    b7:a2:e1:e3:57:e7:0d:7b:98:46:1a:2c:71:cb:0f:
+    a7:62:d6:ad:98:24:08:1d:37:f2:92:fd:4b:e8:b8:
+    4c:36:11:0d:c7:44:36:02:01:be:eb:e0:bd:6c:9d:
+    05:e8:69:25:6d:2f:f3:f9:95:17:b7:ef:d2:a3:37:
+    74:05:6c:b5:67:16:75:a8:b4:92:e9:f5:f2:62:0e:
+    b8:ef:93:81:d3:d1:df:19:93:8b:7b:5f:fa:ac:59:
+    bc:81:10:fa:87:ba:8d:7a:3d:01:65:f8:e4:1d:d0:
+    f8:04:f1:1b:9d:ed:0f:35:2a:59:78:35:d0:63:07:
+    a8:e0:c6:ef:4d:21:90:43:39:e1:cf:45:89:23:a3:
+    e8:9e:02:5d:94:53:47:36:6c:02:f3:dd:63:68:d4:
+    e4:7e:85:d3:d2:a9:70:5b:d5:79:61:85:2e:5a:57:
+    9f:93:b1:c5:14:c5:39:f4:9e:a1:16:3a:2a:49:3b:
+    0e:fc:b4:7f:47:48:f6:a9:9e:10:bf:70:78:28:2e:
+    4a:ce:18:13:6e:2a:8b:3e:e0:a3:80:dc:d3:b3:ef:
+    3e:65:e1:b8:15:72:89:d6:24:67:ad:48:8b:a0:39:
+    2b:2e:90:a1:ed:ed:cb:dc:93:1d:c1:72:98:cc:ef:
+    76:64:5c:7d:33:0a:05:c2:ce:40:f8:9b:85:46:8f:
+    35:7a:21:77:51:e1:54:63:13:04:ec:4e:04:bb:45:
+    b3:67:89:09:c7:4a:f5:1c:e3:70:36:4d:8f:4f:7e:
+    b1:e6:1e:00:28:74:29:c9:96:1d:e8:32:2c:a9:a2:
+    62:9b:13:09:d8:00:e9:2b:c1:dc:50:55:dc:c7:97:
+    f3:38:66:eb:0c:fd:8d:49:02:50:d4:8f:fc:a8:02:
+    2f:49:29:0e:2d:53:76:16:2f:ba:a9:82:d1:64:53:
+    c8:25:b3:5f:65:15:63:5e:a9:2b:ea:72:36:7b:aa:
+    54:de:3f:9e:ae:a6:95:42:a8:1a:41:27:f7:1c:ba:
+    a2:57:f3:24:fe:fe:f1:4f:08:fb:d6:5a:04:9c:d2:
+    fb:36:25:94:a8:e2:3f:f1:a2:61:7d:b5:b1:58:f6:
+    f0:1c:f5:0a:b0:ed:95:c6:e7:09:84:11:64:10:8b:
+    06:e1:b4:0a:b0:ab:11:c4:08:30:1d:3d:9d:8e:a6:
+    9e:96:8a:96:00:b3:d1:7f:38:01:1c:e2:80:74:e2:
+    c2:e1:0b:f6:19:7c:60:2d:8d:0c:e7:d3:a3:ef:2d:
+    89:62:3b:c9:f1:2e:a3:38:79:1e:92:66:bb:8c:e0:
+    2b:12:4c:6c:79:29:ba:ea:69:32:44:09:84:54:a0:
+    80:eb:75:23:e1:3b:b1:b7:c5:b6:77:5f:ab:ab:ab:
+    be:90:75:fe:56:87:aa:45:13:97:bb:9c:fc:cd:05:
+    12:43:e9:bf:5a:ef:24:06:2d:33:5d:e5:fc:e2:4e:
+    9d:db:de:11:91:05:2d:80:c3:6d:f9:f8:43:48:72:
+    f2:77:ed:4f:5a:1c:e8:eb:d3:b9:60:82:4a:4e:4f:
+    10:01:b0:4c:b6:85:f9:be:e4:d0:dd:b0:c5:71:59:
+    8a:c2:02:1a:66:06:fd:23:34:5c:6f:bb:84:f0:ce:
+    05:fe:52:73:45:21:b7:b0:7c:63:88:d3:a3:b9:93:
+    18:bf:01:31:50:4a:a9:df:ba:f5:48:f9:d3:2a:9c:
+    d4:c6:89:35:24:b1:13:30:a2:d3:aa:d3:ed:2a:58:
+    96:6e:bb:01:34:46:5d:54:3f:d7:79:7a:f5:49:f5:
+    68:ea:eb:e9:57:f6:4f:ec:85:46:74:90:2b:97:55:
+    87:56:98:69:46:ea:3a:b7:a2:51:cb:be:a1:1a:68:
+    7b:d4:3f:5d:0b:d8:9c:d2:ca:ba:61:d5:21:83:74:
+    99:0e:e8:b9:22:19:ed:25:dc:a0:11:c6:8a:97:57:
+    c0:13:bd:83:7b:2d:d7:34:e3:75:1f:64:fc:b4:b2:
+    3d:cd:6b:c5:7e:a5:67:f5:71:6e:17:36:72:44:75:
+    1e:23:03:b2:2a:95:3e:77:27:56:95:6c:dc:c0:13:
+    ff:d2:c3:24:90:75:44:22:a5:72:52:9d:4c:92:f1:
+    eb:b1:9f:1d:ad:4d:03:6f:2f:df:31:ca:91:01:bd:
+    f8:1a:ea:94:8a:ed:cf:21:7a:a8:fc:cd:7a:07:71:
+    aa:27:53:e1:a8:23:bf:41:c9:53:77:a2:ff:a6:1b:
+    22:65:13:81:53:ce:86:d2:c8:7d:d0:7a:4b:32:d2:
+    7f:5f:28:72:64:14:31:ce:9a:18:a5:02:aa:ef:d9:
+    af:c5:b0:d1:3c:d4:6c:35:7e:38:e6:9e:1e:e9:45:
+    ad:d1:99:29:32:a5:b1:e5:c5:62:9c:9f:48:f7:66:
+    18:53:da:00:78:7c:9d:78:fb:92:55:53:bf:07:a5:
+    0d:d5:b9:d9:35:85:34:20:e4:d1:a7:1a:e6:2f:f9:
+    0c:a1:93:cd:d6:c2:f4:be:d2:63:41:5a:af:9a:35:
+    09:4b:c2:a2:2e:2a:66:3c:76:45:00:1c:d1:90:b7:
+    bc:17:c7:5f:ea:df:8e:87:ce:5c:24:b7:63:b6:58:
+    4e:d3:2e:71:b0:26:81:42:ea:3e:d6:89:81:57:bf:
+    92:3b:eb:f0:19:2d:1b:f5:ee:30:a7:d3:51:63:4a:
+    60:b5:04:dd:e3:8a:2e:11:4f:7a:e9:bf:17:6d:4a:
+    18:ba:28:95:a7:bb:4b:47:44:4a:9b:a8:db:b4:c1:
+    24:cd:41:bb:b3:2f:4b:cb:1d:e4:8c:4a:bb:51:06:
+    07:a0:01:b5:a0:00:bb:a4:36:18:b6:c1:9e:43:51:
+    7b:45:b4:24:05:92:8b:67:c7:13:88:18:58:ba:d3:
+    a4:25:11:c2:71:6f:f9:cd:33:20:34:b6:72:b5:2f:
+    f1:66:10:80:5c:db:e7:54:4a:8a:84:b6:6e:1c:74:
+    5a:73:c1:b6:bc:da:5b:77:b9:51:f3:6c:0f:7a:53:
+    72:de:9e:5d:1f:9b:bc:de:88:43:c6:90:90:02:dd:
+    a4:87:5e:67:57:1a:f0:be:c5:81:85:6c:32:c0:9c:
+    24:0e:66:4e:76:1e:57:cd:0d:8d:c8:a7:1c:b9:18:
+    a5:76:2d:11:12:85:cd:8b:56:13:dd:bd:0c:a0:8a:
+    c0:34:2b:2b:de:e3:8f:96:fa:75:4b:b2:b0:87:17:
+    9c:11:3c:93:98:6a:81:03:56:eb:94:54:0b:93:cb:
+    9d:ec:4a:a9:29:0f:f1:2e:c1:aa:2e:65:6c:9b:e3:
+    d5:90:75:3c:36:6c:60:14:06:c0:61:bc:22:03:3a:
+    1f:d1:f4:e1:11:1d:03:9b:88:13:b9:83:cb:50:6c:
+    3e:a7:ff:30:57:98:3e:8b:f0:16:82:fb:b0:0f:43:
+    00:53:13:c8:2c:13:92:91:8a:61:65:a1:33:38:ff:
+    e1:1a:99:2c:1f:b3:d1:03:2a:a6:79:a4:18:c8:ba:
+    4f:8a:0b:c1:99:e1:0c:f6:bd:77:a1:4f:dd:6a:06:
+    09:35:14:34:8e:3a:89:74:43:4a:e8:a3:67:63:69:
+    c6:be:2c:f9:0e:67:2b:34:3f:ce:04:ac:6b:22:e0:
+    cf:47:56:8b:c4:5d:70:a6:8e:68:c6:49:a4:83:0a:
+    e2:18:59:0c:1a:43:7e:7a:23:a5:4e:fe:44:f6:70:
+    86:eb:69:7b:9f:a5:78:35:f0:b8:f7:0f:0a:92:92:
+    26:ef:b3:36:c0:e2:18:33:a0:28:21:8c:d6:37:32:
+    c8:0a:a4:77:e6:2d:14:1d:ba:81:85:4f:70:da:68:
+    da:ff:4a:84:cb:6d:e7:79:25:4e:8a:97:e7:35:65:
+    37:4a:f4:09:2a:f0:5c:bd:66:54:af:c3:fd:72:f0:
+    ae:23:26:95:cb:66:68:ea:fe:cc:40:69:bd:90:bb:
+    52:8b:83:ef:a2:fb:cd:bd:93:b2:89:92:96:21:ed:
+    74:d8:08:73:8f:c1:03:ee:b1:05:51:08:51:fc:93:
+    19:f1:71:ea:0c:ed:0b:97:b5:b9:fb:5e:f9:85:18:
+    6b:c5:20:98:f9:eb:47:6f:67:b7:cc:76:65:d4:75:
+    87:97:5c:b4:5a:50:fc:64:10:07:19:bf:76:34:5f:
+    0f:df:1e:09:ef:e9:fb:80:0d:c1:14:e4:6b:e0:87:
+    9a:19:5c:c0:68:70:e2:3d:26:31:da:e7:1c:39:94:
+    48:1c:87:61:c4:0d:07:c5:bf:ca:95:e7:18:b7:b2:
+    25:85:af:03:ed:34:17:5a:46:d5:7a:f3:51:8e:32:
+    a7:fc:1a:a4:48:27:32:a8:1a:87:f7:24:f8:d2:e7:
+    80:b3:a3:9d:45:1a:38:0f:75:c2:d6:80:cc:72:13:
+    ea:b1:d4:a5:9d:39:4a:e3:81:0a:1c:90:81:8d:52:
+    f9:3f:b2:03:e2:d8:b1:b5:fa:8f:60:b2:d5:85:d9:
+    13:5d:64:88:46:f1:38:b8:69:53:24:2d:2b:b1:f2:
+    ec:df:38:9b:4d:e7:65:18:17:b8:e4:e6:4b:33:3f:
+    1a:ac:52:3a:93:f2:74:8a:9c:38:ff:bc:29:ce:d4:
+    57:b6:f9:78:1b:08:a6:7a:19:75:d0:31:cc:d7:15:
+    45:c0:03:74:34:05:6c:24:34:d1:3e:6c:4b:ee:bf:
+    46:fc:12:22:2c:0b:2e:cc:d6:15:9d:5a:ea:8e:55:
+    4d:7a:09:65:2b:06:bf:7c:a6:99:a7:19:9e:71:6d:
+    05:dd:55:30:41:a8:f2:b3:03:d2:36:a9:ba:ba:af:
+    b9:fa:52:8f:28:a2:ca:2a:a7:80:b9:40:38:3c:09:
+    9a:a6:5a:00:74:b8:3f:d1:f0:bc:5b:7b:5e:46:c2:
+    5e:54:83:8b:3c:bc:fc:95:f8:7f:1d:47:1b:3b:a8:
+    94:43:4f:a5:89:52:fd:cb:77:f1:61:37:26:93:30:
+    6d:ba:4e:8f:21:6d:1c:8e:5c:af:f0:fe:83:60:a5:
+    1c:60:76:36:44:16:9f:dc:6a:82:67:f2:e3:f9:09:
+    a6:1b:2a:67:8b:ce:6a:e9:04:03:a8:36:b1:a7:b7:
+    e8:cd:8b:54:c3:70:87:a9:e1:44:46:d9:5e:69:08:
+    d2:ee:db:fc:c6:53:e0:2f:df:77:1f:70:1a:79:b9:
+    e5:a2:6e:d0:a9:47:84:20:70:f3:b5:70:17:42:21:
+    12:19:e7:61:76:2c:37:f0:d0:a1:d1:b9:75:0f:ee:
+    57:7e:12:08:11:5c:66:ac:07:ec:09:1e:6a:3f:c4:
+    aa:6a:25:3b:cb:a8:68:ed:d3:15:4d:ca:f5:16:2f:
+    61:5e:85:49:0a:6c:a3:42:f3:4c:43:ac:61:a3:ea:
+    6b:fe:ef:d8:50:e1:90:eb:1d:8d:a4:d2:8b:5e:ce:
+    eb:16:78:c0:24:33:ec:d5:d4:8b:25:36:40:42:57:
+    e8:ca:7b:ef:58:55:f2:b8:13:ed:2f:4c:40:94:45:
+    a3:31:7c:9b:e1:a3:5a:e2:fb:4d:2b:87:92:1b:90:
+    4b:f2:c1:4d:b5:14:ce:e0:45:25:1c:fc:27:63:74:
+    db:15:c9:9d:ea:15:ac:de:19:7c:6e:b5:24:98:8e:
+    39:b6:32:87:be:b8:67:68:65:aa:a3:ba:d1:b4:3b:
+    8c:ab:15:cb:f2:7a:49:87:59:e3:20:3a:bf:36:9e:
+    97:24:2f:0b:01:54:14:9f:14:ac:23:3c:db:73:a2:
+    2b:7f:b8:f0:93:25:bf:2a:ce:83:bb:6b:5d:b8:a1:
+    21:a2:b6:82:14:9a:69:13:1c:cc:e5:22:29:84:0b:
+    11:3f:c7:b0:bc:c5:84:05:bf:e8:7f:1f:95:ff:c2:
+    e9:6f:c5:59:65:67:e9:43:64:df:aa:6d:9d:5a:6e:
+    b9:9a:e4:dd:f4:24
+pub:
+    97:92:bc:ec:2f:24:30:68:6a:82:fc:cf:3c:2f:5f:
+    f6:65:e7:71:d7:ab:41:b9:02:58:cf:a7:e9:0e:c9:
+    71:24:a7:3b:32:3b:9b:a2:1a:b6:4d:76:7c:43:3f:
+    5a:52:1e:ff:e1:8f:86:e4:6a:18:89:52:c4:46:7e:
+    04:8b:72:9e:7f:c4:d1:15:e7:e4:8d:a1:89:6d:5f:
+    e1:19:b1:0d:cd:de:f6:2c:b3:07:95:40:74:b4:23:
+    36:e5:28:36:de:61:da:94:1f:8d:37:ea:68:ac:81:
+    06:fa:be:19:07:06:79:af:60:08:53:71:20:f7:07:
+    93:b8:ea:9c:c0:e6:e7:b7:b4:c9:a5:c7:42:1c:60:
+    f2:44:51:ba:1e:93:3d:b1:a2:ee:16:c7:95:59:f2:
+    1b:3d:1b:83:05:85:0a:a4:2a:fb:b1:3f:1f:4d:5b:
+    9f:48:35:f9:d8:7d:fc:eb:16:2d:0e:f4:a7:fd:c4:
+    cb:a1:74:3c:d1:c8:7b:b4:96:7d:a1:6c:c8:76:4b:
+    65:69:df:8e:e5:bd:cb:ff:e9:a4:e0:57:48:e6:fd:
+    f2:25:af:9e:4e:eb:77:73:b6:2e:8f:85:f9:b5:6b:
+    54:89:45:55:18:44:fb:d8:98:06:a4:ac:36:9b:ed:
+    2d:25:61:00:f6:88:a6:ad:5e:0a:70:98:26:dc:44:
+    49:e9:1e:23:c5:50:6e:64:23:61:ef:5a:31:37:12:
+    f7:9b:c4:b3:18:68:61:ca:85:a4:ba:b1:7e:7f:94:
+    3d:1b:8a:33:3a:a3:ae:7c:e1:6b:44:0d:60:18:f9:
+    e0:4d:af:57:25:c7:f1:a9:3f:ad:1a:5a:27:b6:78:
+    95:bd:24:9a:a9:16:85:de:20:af:32:c8:b7:e2:68:
+    c7:f9:68:77:d0:c8:50:01:13:5a:4f:0a:8f:1b:82:
+    64:fa:6e:be:5a:34:9d:8a:ec:ad:1a:16:29:9c:cf:
+    2f:d9:c7:b8:5b:ac:e2:ce:d3:aa:12:76:ba:61:ee:
+    78:ed:7e:5c:a5:b6:7c:dd:45:8a:93:54:03:0e:6a:
+    bb:ba:bf:56:a0:a2:31:6f:ec:9d:ba:83:b5:1d:42:
+    fd:31:67:f1:e0:f9:08:55:d5:c6:65:09:b2:10:26:
+    5d:c1:e5:4e:c4:4b:43:ba:7c:f9:ae:f1:18:b4:4d:
+    80:91:2c:e7:51:66:a6:65:1e:11:6c:eb:e4:92:29:
+    a7:06:2c:09:93:1f:71:ab:d2:29:3f:76:f7:ef:c3:
+    21:5b:a9:78:00:03:7e:58:e4:70:bd:bb:b4:3c:1b:
+    04:39:ea:f7:9c:54:d9:3b:44:aa:c9:ef:e9:fb:e1:
+    51:87:4c:fb:2a:64:cb:ee:28:cc:4c:0f:e7:77:5e:
+    5d:87:0f:1c:02:e5:b2:e3:c5:00:4c:99:5f:24:c9:
+    b7:79:cb:75:3a:27:7d:0e:71:fd:42:5e:b6:bc:2c:
+    a5:6c:e1:29:db:51:f7:07:40:f3:1e:63:97:6b:50:
+    c7:31:2e:97:97:d7:8c:5b:1a:c2:4a:5f:a3:47:cc:
+    91:6e:0a:83:f5:c3:b6:75:cd:30:b8:1e:3f:a1:0b:
+    93:44:4e:07:39:75:71:cc:e9:8b:28:da:51:db:90:
+    56:bc:72:8c:5b:0b:11:81:e2:fb:d3:87:b4:c7:9a:
+    b1:a5:fe:fe:ce:37:16:7a:f7:72:dd:ad:14:eb:4c:
+    39:82:da:5a:59:d0:e9:eb:17:3e:c6:31:50:91:17:
+    00:27:a3:ab:5e:f6:aa:12:9c:b8:58:57:27:b9:35:
+    8a:28:50:1d:71:3a:72:f3:f1:db:31:71:42:86:f9:
+    b6:40:80:13:af:06:04:5d:75:59:2f:c0:b7:dd:47:
+    c7:3e:d9:c7:5b:11:e9:d7:c6:9f:7c:ad:fc:32:80:
+    a9:06:2c:52:73:c4:3b:e1:c3:4f:87:44:88:64:ce:
+    a7:b5:c9:7d:6d:32:f5:9b:d5:f2:53:84:65:3b:b5:
+    c4:fa:a4:5b:ea:8b:89:40:28:43:e6:45:b6:b9:26:
+    9e:2b:d9:88:dd:ac:b0:33:32:8f:fb:06:04:50:f7:
+    df:08:00:53:e6:96:9b:25:1e:87:5e:ce:c3:2c:fc:
+    59:28:40:d6:9a:b6:9a:75:e0:6b:37:9c:53:5d:95:
+    26:6b:08:2f:4f:09:c9:31:62:b3:3b:0d:9f:73:07:
+    a4:ea:aa:52:10:44:37:fe:d6:6f:8e:e3:ea:bb:d4:
+    5d:67:b2:5a:81:33:f4:96:46:8b:52:ba:ff:db:fa:
+    d9:3e:ef:1a:98:18:b5:e4:2e:c7:22:78:8a:3d:8d:
+    35:29:fc:77:7d:2b:a5:70:80:1d:fa:e0:1e:c8:83:
+    02:83:7c:1f:b9:e0:35:57:27:64:5e:e1:04:6c:3f:
+    91:5f:6a:e8:2d:ad:4f:b6:b0:35:6a:46:51:8f:fc:
+    83:41:55:c3:b4:fe:6d:af:a6:cc:8a:5c:cf:53:c7:
+    3a:08:49:d8:d4:4f:7d:cf:72:75:4e:70:e1:b7:df:
+    b4:47:bb:4e:f4:9d:1a:71:8f:61:71:bb:ce:20:09:
+    50:e0:ce:92:61:06:b1:51:a3:e8:71:d5:ce:49:73:
+    1b:d6:65:0a:9b:0c:a9:72:da:1c:5f:13:6d:44:82:
+    0e:a6:38:3c:08:f3:b3:84:cf:23:38:e7:89:c5:13:
+    f6:18:cc:56:94:a6:f0:ce:e1:04:51:1e:1e:d7:c5:
+    f2:3a:1e:bf:d8:a0:db:84:24:55:32:40:15:6d:bf:
+    62:28:31:b0:c6:43:d1:c5:51:b6:f3:f7:a9:8d:29:
+    b8:5c:2d:e0:5a:65:fa:61:5e:ee:16:49:5b:d9:07:
+    37:67:21:15:b5:3e:91:c5:d9:00:28:cf:3f:1a:93:
+    95:3a:15:3d:e5:3b:44:08:4e:9c:cf:f6:b7:36:69:
+    39:26:da:ef:eb:b2:d7:7a:a5:ad:68:9b:92:f3:16:
+    86:66:9d:f1:6d:17:15:cc:58:f7:a2:cf:b7:2d:d1:
+    a5:1e:92:f8:25:99:3a:74:02:2b:e7:e9:eb:60:54:
+    65:44:57:09:4d:14:92:8f:20:21:5e:7b:22:2a:c5:
+    6b:51:ad:be:c8:d8:bd:b6:98:39:79:a7:e3:a2:1b:
+    44:b5:d1:51:8c:a9:7d:0b:51:95:f5:1e:d6:a2:43:
+    50:c8:97:47:e1:ed:ea:51:b4:48:e3:e9:14:70:54:
+    ce:92:78:73:c9:0d:b3:94:d8:68:88:e0:7d:ff:17:
+    75:93:d6:f7:9e:15:23:02:20:4a:eb:03:be:23:86:
+    af:3e:24:07:8b:d0:28:b1:68:9f:5e:14:7c:9f:45:
+    2c:8c:eb:02:ec:59:cc:9d:b6:3a:03:57:6c:ee:af:
+    e9:82:39:02:38:97:da:02:36:63:0a:53:c0:de:7f:
+    43:5a:19:86:97:92:fa:b3:6e:7b:9e:63:57:60:f0:
+    90:69:e6:43:2e:70:00:35:ac:2a:02:87:9f:ff:0a:
+    1e:1b:ec:52:20:47:19:3d:94:eb:5d:f1:ef:d5:3e:
+    ea:11:44:ca:78:94:08:52:f5:ec:97:27:90:4b:36:
+    6e:de:4f:5e:2d:33:1f:ad:5f:c2:82:ea:2c:47:e9:
+    23:14:27:71:c3:dd:75:a8:73:57:48:7d:ef:99:e5:
+    f1:8e:9d:9e:d6:23:c1:75:d0:28:88:c5:1f:82:c0:
+    7a:80:d5:47:16:b3:c3:c2:bd:be:2e:9f:0a:9b:ba:
+    ae:be:b4:d5:29:36:87:64:06:f5:c0:0e:8e:4b:bd:
+    0a:5e:c0:57:97:e6:20:7c:5a:b6:c8:8f:1a:68:84:
+    21:bd:05:a1:14:f4:d7:de:2a:c2:41:fa:0e:8b:ed:
+    ff:47:f7:62:dd:cb:ea:a9:10:04:f8:d3:1e:85:09:
+    5c:81:05:49:94:ad:38:26:e3:44:ba:96:04:08:10:
+    fc:0b:2a:d1:de:48:cf:ad:e0:02:c6:2e:5a:49:a0:
+    73:1a:b3:83:44:bc:16:36:df:16:bf:60:7d:56:85:
+    5e:56:d6:84:00:3c:71:8e:4b:ad:9e:5a:09:99:79:
+    fc:dd:ee:b1:c4:a7:77:6c:d3:7a:34:17:cb:0e:18:
+    4e:29:ef:9b:c0:e8:74:75:ba:66:3b:e0:9e:00:ab:
+    56:2e:b7:c0:f7:16:5f:96:9a:9b:42:41:41:98:cc:
+    f1:bf:f2:a2:c8:d6:89:a4:14:ec:e7:66:29:27:66:
+    56:89:e9:4d:b9:61:eb:ae:c5:61:5c:bc:1a:78:95:
+    c6:85:1a:c9:61:43:2f:f1:11:8d:46:07:d3:2e:f9:
+    dc:73:2d:51:33:3b:e4:b4:d0:e3:0d:de:a7:84:ec:
+    a8:be:47:e7:41:be:9c:19:63:1d:c4:70:a5:2e:f4:
+    dc:13:a4:f3:63:3f:d4:34:d7:87:c1:70:97:7b:41:
+    7d:f5:98:e1:d0:dd:e5:06:bb:71:d6:f0:bc:17:ec:
+    70:e3:b0:3c:dc:19:65:cb:36:99:3f:63:3b:04:72:
+    e5:0d:09:23:ac:6c:66:fd:f1:d3:e6:45:9c:c1:21:
+    f0:f5:f9:4d:09:e9:db:cf:5d:69:0e:23:23:38:38:
+    a0:ba:cb:7c:63:8d:1b:26:50:a4:30:8c:d1:71:b6:
+    85:51:26:d1:da:67:2a:6e:d8:5a:8d:78:c2:86:fb:
+    56:f4:ab:3d:21:49:75:28:04:5c:63:26:2c:8a:42:
+    af:2f:98:02:c5:3b:7b:b8:be:28:e7:8f:e0:b5:ce:
+    45:fb:b7:a1:af:1a:3b:28:a8:d9:4b:78:90:e3:c8:
+    82:e3:9b:c9:8e:9f:0a:d7:60:25:bf:0d:d2:f0:02:
+    98:e7:14:1a:22:6b:3d:7c:ee:41:4f:60:4d:1e:0b:
+    a5:4d:11:d5:fe:58:bc:ce:a6:ad:77:ad:2e:8c:1c:
+    aa:cf:32:45:90:14:b7:b9:10:01:b1:ef:a8:ad:17:
+    2a:52:3f:b8:e3:65:b5:77:12:1b:f9:fd:88:a2:c6:
+    0c:21:e8:21:d7:b6:ac:b4:7a:5a:99:5e:40:ca:ce:
+    d5:c2:23:b8:fe:6d:e5:e1:8e:9d:2e:58:93:ae:fe:
+    bb:7a:ae:7f:f1:a1:46:26:0e:2f:11:0e:93:95:28:
+    21:3a:00:25:a3:8e:c7:9a:ab:c8:61:b2:5e:bc:50:
+    9a:46:74:c1:32:aa:ac:b7:e0:14:6f:14:ef:d1:1c:
+    fc:af:4c:aa:4f:77:5a:71:6c:e3:25:e0:a4:35:a4:
+    d3:49:d7:20:bc:f1:37:45:0a:fc:45:04:6f:c1:a1:
+    f8:3a:9d:32:97:77:a7:08:4e:4a:ad:ae:71:22:ce:
+    97:00:59:30:52:8e:b3:c7:f7:f1:12:9b:37:28:87:
+    a3:71:15:5a:3b:a2:01:a2:5c:bf:1d:cb:64:e7:cd:
+    ee:09:2c:31:41:fb:55:50:fe:3d:0d:d8:2e:87:0e:
+    57:8b:2b:46:50:08:18:11:3b:8f:65:69:77:3c:67:
+    73:85:b6:9a:42:b7:7d:cb:a7:ac:ff:d9:5f:d4:45:
+    2e:23:aa:a1:d3:7e:1d:a2:15:1e:a6:58:d4:0a:35:
+    96:b2:7a:c9:f8:12:9d:c6:cf:06:43:77:26:24:b5:
+    9f:4f:46:12:30:df:47:1c:a2:60:87:c3:94:2d:5c:
+    66:87:df:60:82:83:59:35:a3:f8:7c:b7:62:b0:c3:
+    b1:d0:dd:a4:a6:53:39:65:be:f1:b7:b8:29:2e:25:
+    4c:01:4d:09:0f:ed:85:7c:44:c1:83:9c:69:4c:0a:
+    64:e3:fa:d9:0a:11:f5:34:72:2b:6e:e1:57:4f:2e:
+    14:9d:55:d7:44:de:48:87:02:4e:08:51:14:31:c0:
+    62:75:0e:16:c7:4a:b9:f3:24:2f:2d:b3:ff:b1:2a:
+    8d:61:07:fa:a2:29:d6:f6:37:3b:07:f3:6d:39:32:
+    b3:bd:b0:4c:19:dd:64:ea:dd:7f:93:c3:c5:64:c3:
+    58:a1:c8:1d:cf:1c:9c:31:e5:b0:65:68:f9:75:44:
+    c1:7d:c1:56:98:c5:cb:38:98:3a:9a:fc:42:78:3f:
+    aa:77:3a:52:c9:d8:26:06:90:be:9e:31:56:aa:5b:
+    c1:50:9d:ea:3f:69:58:76:95:cd:6f:f1:72:ba:83:
+    e6:a6:d8:a7:d6:bb:eb:bb:cd:a3:67:27:31:98:3f:
+    89:bc:58:31:dc:37:c3:f3:c5:c5:6f:ac:c6:97:f3:
+    cb:20:bd:5d:ba:db:d7:02:e5:48:44:ac:2f:62:69:
+    01:fe:15:9d:b9:3d:fd:47:73:d8:fe:73:56:2b:84:
+    6c:1f:c8:56:d1:80:27:62:84:0e:bc:72:d7:98:8b:
+    de:75:cb:ca:70:d3:19:d3:2c:e0:cc:02:53:bb:2a:
+    d4:55:72:3e:e0:c7:f4:73:6c:e6:e6:66:5c:5a:ca:
+    32:a4:81:c5:38:39:bc:25:91:67:b0:13:d0:42:33:
+    95:ee:b9:aa:ae:e3:20:61:49:a7:d5:50:d6:7f:c5:
+    fd:fe:4a:8a:5c:35:d2:51:0b:66:43:79:ab:8f:72:
+    85:5a:2a:f4:7a:bc:e2:a6:32:04:8e:af:89:e5:cb:
+    4a:88:de:bc:53:a5:95:10:3a:cc:e4:f1:cf:f1:8a:
+    cf:f0:7a:fe:1e:b5:71:6a:a1:e4:0b:63:13:4c:3a:
+    3a:e9:57:9f:a8:7f:51:5b:e0:93:c2:d2:9d:b6:d6:
+    b6:5c:93:66:1e:00:63:6b:59:27:04:d0:93:cc:67:
+    16:c2:34:2e:b1:85:3d:48:c8:5c:63:ac:8a:28:54:
+    46:2c:7b:77:e7:e3:bd:1e:ac:5b:ca:28:ff:aa:00:
+    b5:d3:49:f8:a5:47:ad:87:5b:96:a8:c2:b2:91:0c:
+    93:01:30:9a:3f:91:38:a5:69:31:11:f5:5b:3c:00:
+    9c:a9:47:c3:9d:fc:82:d9:8e:b1:ca:a4:a9:cb:e8:
+    85:f7:86:fa:86:e5:5b:e0:62:22:2f:8b:a9:0a:97:
+    40:73:32:6b:31:21:2a:ec:e0:a3:4a:60
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-87-seed-only.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-87-seed-only.pem
new file mode 100644 (file)
index 0000000..87b2f0f
--- /dev/null
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MDQCAQAwCwYJYIZIAWUDBAMTBCKAIAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ
+GhscHR4f
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-87-seed-priv.pem b/test/recipes/15-test_ml_dsa_codecs_data/prv-87-seed-priv.pem
new file mode 100644 (file)
index 0000000..4ffd108
--- /dev/null
@@ -0,0 +1,106 @@
+-----BEGIN PRIVATE KEY-----
+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
+WWVn6UNk36ptnVpuuZrk3fQk
+-----END PRIVATE KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/prv-87-seed.txt b/test/recipes/15-test_ml_dsa_codecs_data/prv-87-seed.txt
new file mode 100644 (file)
index 0000000..129821d
--- /dev/null
@@ -0,0 +1,507 @@
+ML-DSA-87 Private-Key:
+seed:
+    00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:
+    0f:10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:
+    1e:1f
+priv:
+    97:92:bc:ec:2f:24:30:68:6a:82:fc:cf:3c:2f:5f:
+    f6:65:e7:71:d7:ab:41:b9:02:58:cf:a7:e9:0e:c9:
+    71:24:d8:e9:ee:4e:90:a1:6c:60:2f:5e:c9:bc:38:
+    51:7d:c3:0e:32:9d:5a:b2:76:73:bd:85:f4:c9:b0:
+    30:0f:77:63:89:88:67:50:b5:7c:24:db:3f:c0:12:
+    e6:1e:de:59:75:33:37:37:4f:a7:12:49:91:54:9a:
+    f2:43:49:6d:06:37:cb:3b:e0:5a:59:48:23:5b:f7:
+    98:75:f8:96:d8:fe:0c:ab:30:c8:49:48:db:4d:63:
+    15:aa:af:16:0a:c6:24:36:64:22:01:48:16:11:09:
+    11:2c:94:02:89:22:45:2c:62:b8:45:00:45:2a:08:
+    96:70:90:12:6e:14:93:70:d4:46:10:84:44:51:58:
+    96:91:0c:a9:29:82:b2:41:c9:08:71:c4:28:68:04:
+    96:89:48:40:85:9b:22:6d:1c:28:64:59:12:41:9c:
+    b8:91:84:04:89:44:90:05:cb:34:62:a0:86:90:40:
+    26:92:20:99:29:13:05:69:5c:34:68:a4:32:8e:19:
+    26:92:59:46:10:09:a4:49:23:42:4d:12:36:61:58:
+    10:65:01:28:90:1a:33:4c:99:86:31:d3:a2:49:09:
+    82:25:43:14:28:c0:38:81:03:15:4d:5b:28:86:08:
+    87:48:23:31:52:94:22:25:c3:c0:4d:a4:98:21:98:
+    40:20:d1:42:86:cb:40:70:5b:b0:71:9c:96:2c:c1:
+    12:06:53:46:09:0c:45:02:14:46:6e:91:b4:21:54:
+    b0:8c:e4:46:42:9a:20:8c:01:21:25:13:41:05:5a:
+    40:22:13:c9:0c:a0:18:40:52:c2:30:cb:34:2c:4b:
+    c8:68:1b:a4:60:49:84:84:63:30:29:4a:a0:69:5b:
+    80:04:d2:38:0a:14:26:4c:e2:b2:44:8b:a2:11:24:
+    46:49:c4:14:52:0b:42:71:03:b2:10:92:28:80:01:
+    24:88:e3:08:11:0a:05:28:19:c4:81:00:20:22:dc:
+    44:68:42:12:22:44:00:2a:c9:26:6a:0c:87:31:e0:
+    c0:44:99:14:84:18:36:0d:11:37:42:22:18:8c:63:
+    b2:91:0c:98:08:a1:a0:01:08:92:44:04:13:24:5c:
+    98:71:82:84:71:84:32:52:51:b0:31:9c:42:2e:1a:
+    a8:28:02:08:91:01:c0:89:0b:c7:05:8a:24:65:22:
+    c2:64:4c:88:91:5c:82:68:13:a5:64:50:20:86:21:
+    04:02:91:94:44:48:22:30:22:39:4a:02:98:84:09:
+    a2:88:19:19:29:44:48:8c:22:95:0c:a1:04:72:04:
+    87:70:12:21:10:42:06:84:1b:49:85:89:06:4a:d3:
+    36:08:db:c0:40:58:b6:51:0c:a7:09:8c:24:61:99:
+    90:64:8c:c2:90:5b:24:90:10:a3:49:03:25:61:43:
+    32:8a:11:98:44:c8:b2:20:04:38:41:10:47:2c:19:
+    c6:44:1c:25:2c:04:88:30:d9:46:69:9b:20:00:1b:
+    46:82:5a:a4:80:5b:a0:49:18:90:25:00:26:80:0b:
+    c2:31:5a:40:72:54:c6:20:c1:b0:31:24:b1:4d:10:
+    95:28:14:00:0a:a0:c8:4d:54:a2:88:23:98:81:60:
+    90:04:02:16:2c:13:21:40:91:86:8d:08:c2:91:91:
+    14:26:d0:b4:0c:09:c6:60:51:44:2e:04:11:26:00:
+    29:11:93:c2:08:63:a4:31:22:00:28:c1:14:08:0c:
+    40:2c:41:14:06:9c:20:72:54:22:06:8b:08:4d:a1:
+    48:69:10:30:41:1c:28:49:44:18:8e:cc:96:48:d9:
+    42:50:1b:06:49:0c:45:88:23:04:0d:20:30:2e:23:
+    85:2c:14:07:30:40:b6:85:4c:c0:20:44:86:20:19:
+    00:6c:54:02:48:cc:88:6c:59:06:32:49:14:84:04:
+    c7:50:13:49:28:e4:06:09:d3:c6:10:c8:28:4c:23:
+    39:44:52:a4:64:cc:a8:49:44:38:32:0a:89:84:00:
+    34:2c:22:85:8d:10:31:09:09:32:65:1c:89:8c:40:
+    40:29:21:85:00:09:a1:6d:84:c0:64:e2:02:2d:48:
+    04:40:12:09:8e:e0:42:2e:93:44:08:12:10:6a:01:
+    84:05:92:30:8a:cb:34:8e:a2:26:2e:5c:86:11:0b:
+    35:08:18:10:00:02:34:26:24:23:89:d1:84:00:24:
+    46:60:13:b2:49:24:28:46:18:02:71:a0:38:90:89:
+    14:44:d3:96:2d:a3:18:40:23:27:21:c0:18:50:43:
+    c8:04:41:42:8d:5c:26:41:44:a2:6d:48:12:0e:40:
+    32:25:0b:14:82:0a:48:2e:cb:82:88:03:a3:60:1b:
+    25:26:8c:b8:20:24:b0:85:98:04:21:08:a7:2c:83:
+    38:64:54:32:89:01:04:01:23:49:84:02:95:69:d1:
+    a4:4d:13:a4:0c:91:46:0d:61:94:80:90:38:45:5c:
+    c6:50:01:17:20:53:c6:28:9b:18:10:41:12:68:90:
+    12:21:c0:10:84:42:16:92:53:82:29:81:26:49:d8:
+    a4:05:9a:26:24:24:03:29:e0:40:26:d2:02:48:11:
+    24:68:11:99:89:98:14:85:c9:20:0d:50:12:8c:1c:
+    08:10:02:10:00:00:95:28:c1:28:90:59:b4:85:d3:
+    14:65:0a:40:6e:11:29:65:18:c2:4c:21:34:65:d8:
+    30:69:10:30:52:19:31:66:8c:18:8a:c8:c0:08:4c:
+    98:30:a3:a6:20:41:16:22:18:05:2e:22:25:2a:64:
+    b8:25:0a:b3:01:63:20:84:09:80:09:19:28:0e:02:
+    11:01:a3:94:11:e3:98:6c:58:20:21:09:41:10:60:
+    36:22:08:06:71:12:c2:85:5a:a0:85:c0:c6:84:5c:
+    38:06:cb:b6:69:14:84:84:53:22:82:a1:a6:40:cc:
+    86:00:c4:26:22:a0:a8:08:98:34:72:d4:20:41:43:
+    c4:90:48:16:68:1b:16:52:1a:37:02:50:20:42:48:
+    48:8a:20:11:41:e2:00:6c:c0:c2:0c:14:06:49:11:
+    31:4d:19:06:0a:89:46:09:1b:81:65:44:c8:00:82:
+    06:70:00:16:72:cc:24:50:8a:42:89:9c:96:90:64:
+    28:70:92:b2:68:98:26:62:61:94:40:c1:16:89:d8:
+    42:64:1a:21:4e:62:90:64:21:c8:24:8b:28:6d:5c:
+    42:92:a0:c6:4d:0c:85:80:cc:88:4d:d4:42:8d:42:
+    34:8a:0b:04:51:c3:26:86:24:25:81:12:35:06:a0:
+    44:04:c8:94:81:5b:b4:31:1c:08:06:5c:24:08:03:
+    27:6a:20:c2:25:e1:80:90:19:b4:6d:a3:46:0c:4b:
+    18:60:50:c6:2c:1b:92:2d:11:15:04:a2:00:04:21:
+    48:2e:d8:16:06:d2:10:8a:83:a2:25:08:31:0d:09:
+    38:51:d9:48:49:0b:16:4c:23:32:25:19:19:02:4a:
+    44:09:d1:b2:21:0b:83:2c:23:25:85:93:16:85:44:
+    a0:44:1b:83:50:02:22:72:4b:04:80:9b:14:65:21:
+    93:60:18:13:0a:d9:46:0d:22:45:61:c8:b4:40:a1:
+    42:2d:02:b8:09:00:14:44:9b:b6:11:0b:97:8c:40:
+    10:4a:82:14:6a:da:90:05:1c:02:8e:0c:19:72:a3:
+    b4:8d:24:30:50:11:87:09:64:c6:28:e4:18:92:98:
+    b4:6c:61:16:51:40:46:0e:1c:32:48:da:20:51:88:
+    36:8a:23:b1:21:82:90:28:1a:15:32:e2:18:61:92:
+    04:8e:13:b6:90:13:13:68:c9:84:68:4c:40:6d:0b:
+    33:00:81:46:4d:d2:38:0c:04:96:81:a4:88:50:02:
+    90:85:22:b0:04:d3:a4:71:d2:80:10:ca:96:40:51:
+    a6:41:a4:84:28:e0:08:52:0b:30:8c:d2:38:0a:0c:
+    29:51:c3:82:09:ca:20:91:d8:36:92:a3:a6:28:92:
+    42:22:a2:16:01:1a:34:86:37:d9:a6:59:16:98:81:
+    ec:21:cf:48:11:86:9d:1d:7f:13:9f:05:37:e9:6f:
+    11:84:58:54:05:fd:17:80:8a:f1:e0:62:39:d3:b3:
+    4e:5a:ca:8b:f1:36:96:77:b4:47:ac:71:8a:c4:7d:
+    85:0c:4d:77:b0:be:31:dc:9f:50:8e:39:78:f2:42:
+    74:ab:01:85:f7:27:ab:df:f5:9f:44:90:37:1b:f0:
+    46:10:e3:64:e6:4e:c8:75:ef:9d:20:dc:94:07:7e:
+    1e:16:63:27:a8:79:b8:ab:51:61:60:b2:a3:f7:74:
+    37:b9:b3:cc:7d:17:ae:ad:dc:84:db:62:74:6a:35:
+    ac:09:6f:78:2f:62:a7:f0:1a:a6:d6:69:3d:ee:c9:
+    0b:23:c6:69:85:a0:23:07:e0:a1:ca:e5:98:a6:73:
+    24:db:a0:f5:2f:22:43:22:75:e9:32:57:06:5c:3b:
+    7e:5e:1c:fe:1d:fd:4d:0d:f0:86:df:21:24:34:14:
+    a2:d2:7e:20:23:0a:82:9b:e4:eb:4c:82:c1:6d:35:
+    f7:8b:0e:5e:19:83:32:e0:00:74:bb:64:61:2f:ab:
+    17:d4:c8:97:1c:b6:8e:5e:da:b0:36:9f:11:57:b3:
+    46:9a:bd:83:84:e2:d9:55:3f:1b:78:e7:86:e1:ee:
+    9d:0b:98:d3:9f:83:cc:ec:f3:7d:1e:bd:3a:9d:63:
+    ae:c7:66:16:4a:10:17:1a:4f:d8:c6:3d:af:18:2c:
+    42:12:58:c5:f5:29:aa:55:cb:7e:ba:e2:e1:65:23:
+    15:e1:f7:1e:8a:74:13:14:10:d0:32:47:ed:e1:1d:
+    34:db:91:f6:f0:8a:a2:47:8f:d7:89:67:9c:04:94:
+    9f:71:bc:01:71:e0:7e:3a:8b:b5:75:3d:bb:da:a4:
+    11:a6:35:0a:b4:6e:ef:bf:86:fc:55:1c:29:ef:e4:
+    cd:d7:66:1d:5c:f6:c3:db:22:d0:ce:dd:e5:99:85:
+    44:59:d9:7f:20:df:74:55:bd:f3:56:a1:98:d0:f7:
+    eb:6d:34:11:1f:c9:40:b2:5c:05:43:b7:88:ed:da:
+    9d:26:81:0e:ac:3d:6c:c9:c5:13:27:c2:cf:83:e8:
+    87:d4:08:9e:19:69:5e:11:ad:d8:37:f6:f4:40:cc:
+    36:0f:93:f3:2f:ee:8a:96:63:71:2c:6b:bd:38:c8:
+    4a:b7:b5:48:23:ec:36:3e:b7:e4:2e:b5:9f:c1:fc:
+    e6:0f:bd:55:30:7b:3e:c8:5f:d9:da:f3:20:6d:7b:
+    4b:39:17:f1:c8:b7:a9:2e:3c:67:d8:98:80:fd:f2:
+    e4:7f:5a:0c:99:45:95:db:17:0a:f4:1b:ab:f5:a2:
+    5b:4d:c1:c4:2d:d6:a9:db:27:1e:76:4d:e2:fb:01:
+    5a:49:a8:50:c7:91:9b:e4:70:06:a3:36:e2:e3:25:
+    fd:e5:3a:c5:99:55:4d:0a:7d:e4:ef:45:ec:40:c3:
+    9d:6b:af:f3:11:be:ee:75:d8:9e:02:ad:31:f4:be:
+    4b:d2:0a:e9:19:4f:5e:dd:da:a6:65:07:76:11:6e:
+    9f:27:0f:77:71:4a:d7:a8:e8:9a:ce:f7:4b:7f:f7:
+    d8:db:ec:27:f8:02:0a:98:52:47:e2:cd:ac:ef:48:
+    94:a4:d6:8b:a3:7c:a9:12:d6:be:73:50:1c:99:51:
+    81:e5:b7:77:23:35:0b:36:31:da:37:00:e1:3f:d3:
+    66:e1:31:bf:06:b3:6e:b6:b0:34:50:93:20:9f:0a:
+    7b:ef:fa:e1:fd:d8:75:b0:06:87:c1:16:3c:35:3d:
+    7d:2a:c9:09:37:b3:4e:97:8e:92:f8:21:ad:c9:66:
+    22:02:ec:e8:9a:17:e7:bb:65:ae:17:d8:3b:90:db:
+    be:6a:50:1a:4e:13:45:be:e4:e5:a5:b5:3a:f2:e5:
+    ba:3d:1e:f3:f4:e0:5a:df:0b:3a:4c:f2:e5:30:36:
+    0f:ee:64:92:99:02:b5:71:f6:fd:2e:30:56:52:a4:
+    cb:01:0f:79:f8:15:e1:8f:2b:bb:8c:c8:9f:a6:fc:
+    76:f7:7c:89:e2:93:cf:17:5a:0b:19:58:00:fe:72:
+    d2:cc:dd:7d:75:e5:bd:90:bc:6a:c4:35:d6:a4:40:
+    ef:85:2e:9a:1c:8c:53:de:03:bf:19:33:65:d7:35:
+    aa:f2:9c:51:62:a6:17:e3:64:e7:f9:44:16:8d:0f:
+    b4:8f:ef:40:55:8f:45:42:97:cc:3d:d5:08:66:2c:
+    f2:3f:b8:8e:19:54:aa:45:d1:c5:e1:15:bc:c3:6f:
+    05:b3:e0:98:d5:55:22:0f:40:be:26:29:b3:45:07:
+    b8:46:4c:54:c2:7b:5d:ec:78:da:8f:22:65:05:14:
+    79:7a:f8:6a:25:12:bc:b7:e2:92:33:79:ef:6d:73:
+    c1:37:00:6c:1b:38:f5:1e:37:f9:35:85:e2:90:41:
+    a3:e4:e3:af:46:00:7c:e1:3b:8b:5f:7b:17:d5:d6:
+    5d:7d:56:68:e4:27:bc:be:7e:c1:d7:c4:08:c0:54:
+    a4:8c:1a:e7:97:bf:99:ac:bc:8d:26:07:52:29:35:
+    fd:66:5e:a7:82:2d:93:0f:23:ea:bf:f7:83:bb:23:
+    69:75:69:e2:04:b9:43:14:1e:00:c0:88:10:95:6b:
+    e0:52:53:65:db:ab:54:ed:48:cb:76:96:4c:cd:f5:
+    cb:d3:ae:e7:28:2d:4a:00:00:d2:78:4d:7b:8f:ab:
+    16:b2:f7:f0:d5:22:57:32:b1:ef:bc:4e:b1:cf:ed:
+    eb:43:fd:e7:9b:69:ec:c0:fb:ea:a1:e6:b4:07:28:
+    67:3b:d4:b2:e9:8a:0d:4a:8f:02:f8:53:95:07:30:
+    f2:8d:35:eb:12:fc:c7:97:68:b8:e1:8e:4b:da:0e:
+    58:a3:31:a2:f7:1d:7c:cc:2d:45:1b:32:b1:c6:5c:
+    31:2a:cf:47:ee:51:3b:21:95:4c:41:c0:0c:87:38:
+    72:ee:94:cf:14:f4:60:37:42:53:61:f4:bd:b5:48:
+    21:f7:11:46:0c:eb:ae:8c:07:50:8a:92:19:f8:8f:
+    a6:be:da:a6:78:ee:d5:01:94:4a:16:ae:6f:7b:5b:
+    b7:a2:e1:e3:57:e7:0d:7b:98:46:1a:2c:71:cb:0f:
+    a7:62:d6:ad:98:24:08:1d:37:f2:92:fd:4b:e8:b8:
+    4c:36:11:0d:c7:44:36:02:01:be:eb:e0:bd:6c:9d:
+    05:e8:69:25:6d:2f:f3:f9:95:17:b7:ef:d2:a3:37:
+    74:05:6c:b5:67:16:75:a8:b4:92:e9:f5:f2:62:0e:
+    b8:ef:93:81:d3:d1:df:19:93:8b:7b:5f:fa:ac:59:
+    bc:81:10:fa:87:ba:8d:7a:3d:01:65:f8:e4:1d:d0:
+    f8:04:f1:1b:9d:ed:0f:35:2a:59:78:35:d0:63:07:
+    a8:e0:c6:ef:4d:21:90:43:39:e1:cf:45:89:23:a3:
+    e8:9e:02:5d:94:53:47:36:6c:02:f3:dd:63:68:d4:
+    e4:7e:85:d3:d2:a9:70:5b:d5:79:61:85:2e:5a:57:
+    9f:93:b1:c5:14:c5:39:f4:9e:a1:16:3a:2a:49:3b:
+    0e:fc:b4:7f:47:48:f6:a9:9e:10:bf:70:78:28:2e:
+    4a:ce:18:13:6e:2a:8b:3e:e0:a3:80:dc:d3:b3:ef:
+    3e:65:e1:b8:15:72:89:d6:24:67:ad:48:8b:a0:39:
+    2b:2e:90:a1:ed:ed:cb:dc:93:1d:c1:72:98:cc:ef:
+    76:64:5c:7d:33:0a:05:c2:ce:40:f8:9b:85:46:8f:
+    35:7a:21:77:51:e1:54:63:13:04:ec:4e:04:bb:45:
+    b3:67:89:09:c7:4a:f5:1c:e3:70:36:4d:8f:4f:7e:
+    b1:e6:1e:00:28:74:29:c9:96:1d:e8:32:2c:a9:a2:
+    62:9b:13:09:d8:00:e9:2b:c1:dc:50:55:dc:c7:97:
+    f3:38:66:eb:0c:fd:8d:49:02:50:d4:8f:fc:a8:02:
+    2f:49:29:0e:2d:53:76:16:2f:ba:a9:82:d1:64:53:
+    c8:25:b3:5f:65:15:63:5e:a9:2b:ea:72:36:7b:aa:
+    54:de:3f:9e:ae:a6:95:42:a8:1a:41:27:f7:1c:ba:
+    a2:57:f3:24:fe:fe:f1:4f:08:fb:d6:5a:04:9c:d2:
+    fb:36:25:94:a8:e2:3f:f1:a2:61:7d:b5:b1:58:f6:
+    f0:1c:f5:0a:b0:ed:95:c6:e7:09:84:11:64:10:8b:
+    06:e1:b4:0a:b0:ab:11:c4:08:30:1d:3d:9d:8e:a6:
+    9e:96:8a:96:00:b3:d1:7f:38:01:1c:e2:80:74:e2:
+    c2:e1:0b:f6:19:7c:60:2d:8d:0c:e7:d3:a3:ef:2d:
+    89:62:3b:c9:f1:2e:a3:38:79:1e:92:66:bb:8c:e0:
+    2b:12:4c:6c:79:29:ba:ea:69:32:44:09:84:54:a0:
+    80:eb:75:23:e1:3b:b1:b7:c5:b6:77:5f:ab:ab:ab:
+    be:90:75:fe:56:87:aa:45:13:97:bb:9c:fc:cd:05:
+    12:43:e9:bf:5a:ef:24:06:2d:33:5d:e5:fc:e2:4e:
+    9d:db:de:11:91:05:2d:80:c3:6d:f9:f8:43:48:72:
+    f2:77:ed:4f:5a:1c:e8:eb:d3:b9:60:82:4a:4e:4f:
+    10:01:b0:4c:b6:85:f9:be:e4:d0:dd:b0:c5:71:59:
+    8a:c2:02:1a:66:06:fd:23:34:5c:6f:bb:84:f0:ce:
+    05:fe:52:73:45:21:b7:b0:7c:63:88:d3:a3:b9:93:
+    18:bf:01:31:50:4a:a9:df:ba:f5:48:f9:d3:2a:9c:
+    d4:c6:89:35:24:b1:13:30:a2:d3:aa:d3:ed:2a:58:
+    96:6e:bb:01:34:46:5d:54:3f:d7:79:7a:f5:49:f5:
+    68:ea:eb:e9:57:f6:4f:ec:85:46:74:90:2b:97:55:
+    87:56:98:69:46:ea:3a:b7:a2:51:cb:be:a1:1a:68:
+    7b:d4:3f:5d:0b:d8:9c:d2:ca:ba:61:d5:21:83:74:
+    99:0e:e8:b9:22:19:ed:25:dc:a0:11:c6:8a:97:57:
+    c0:13:bd:83:7b:2d:d7:34:e3:75:1f:64:fc:b4:b2:
+    3d:cd:6b:c5:7e:a5:67:f5:71:6e:17:36:72:44:75:
+    1e:23:03:b2:2a:95:3e:77:27:56:95:6c:dc:c0:13:
+    ff:d2:c3:24:90:75:44:22:a5:72:52:9d:4c:92:f1:
+    eb:b1:9f:1d:ad:4d:03:6f:2f:df:31:ca:91:01:bd:
+    f8:1a:ea:94:8a:ed:cf:21:7a:a8:fc:cd:7a:07:71:
+    aa:27:53:e1:a8:23:bf:41:c9:53:77:a2:ff:a6:1b:
+    22:65:13:81:53:ce:86:d2:c8:7d:d0:7a:4b:32:d2:
+    7f:5f:28:72:64:14:31:ce:9a:18:a5:02:aa:ef:d9:
+    af:c5:b0:d1:3c:d4:6c:35:7e:38:e6:9e:1e:e9:45:
+    ad:d1:99:29:32:a5:b1:e5:c5:62:9c:9f:48:f7:66:
+    18:53:da:00:78:7c:9d:78:fb:92:55:53:bf:07:a5:
+    0d:d5:b9:d9:35:85:34:20:e4:d1:a7:1a:e6:2f:f9:
+    0c:a1:93:cd:d6:c2:f4:be:d2:63:41:5a:af:9a:35:
+    09:4b:c2:a2:2e:2a:66:3c:76:45:00:1c:d1:90:b7:
+    bc:17:c7:5f:ea:df:8e:87:ce:5c:24:b7:63:b6:58:
+    4e:d3:2e:71:b0:26:81:42:ea:3e:d6:89:81:57:bf:
+    92:3b:eb:f0:19:2d:1b:f5:ee:30:a7:d3:51:63:4a:
+    60:b5:04:dd:e3:8a:2e:11:4f:7a:e9:bf:17:6d:4a:
+    18:ba:28:95:a7:bb:4b:47:44:4a:9b:a8:db:b4:c1:
+    24:cd:41:bb:b3:2f:4b:cb:1d:e4:8c:4a:bb:51:06:
+    07:a0:01:b5:a0:00:bb:a4:36:18:b6:c1:9e:43:51:
+    7b:45:b4:24:05:92:8b:67:c7:13:88:18:58:ba:d3:
+    a4:25:11:c2:71:6f:f9:cd:33:20:34:b6:72:b5:2f:
+    f1:66:10:80:5c:db:e7:54:4a:8a:84:b6:6e:1c:74:
+    5a:73:c1:b6:bc:da:5b:77:b9:51:f3:6c:0f:7a:53:
+    72:de:9e:5d:1f:9b:bc:de:88:43:c6:90:90:02:dd:
+    a4:87:5e:67:57:1a:f0:be:c5:81:85:6c:32:c0:9c:
+    24:0e:66:4e:76:1e:57:cd:0d:8d:c8:a7:1c:b9:18:
+    a5:76:2d:11:12:85:cd:8b:56:13:dd:bd:0c:a0:8a:
+    c0:34:2b:2b:de:e3:8f:96:fa:75:4b:b2:b0:87:17:
+    9c:11:3c:93:98:6a:81:03:56:eb:94:54:0b:93:cb:
+    9d:ec:4a:a9:29:0f:f1:2e:c1:aa:2e:65:6c:9b:e3:
+    d5:90:75:3c:36:6c:60:14:06:c0:61:bc:22:03:3a:
+    1f:d1:f4:e1:11:1d:03:9b:88:13:b9:83:cb:50:6c:
+    3e:a7:ff:30:57:98:3e:8b:f0:16:82:fb:b0:0f:43:
+    00:53:13:c8:2c:13:92:91:8a:61:65:a1:33:38:ff:
+    e1:1a:99:2c:1f:b3:d1:03:2a:a6:79:a4:18:c8:ba:
+    4f:8a:0b:c1:99:e1:0c:f6:bd:77:a1:4f:dd:6a:06:
+    09:35:14:34:8e:3a:89:74:43:4a:e8:a3:67:63:69:
+    c6:be:2c:f9:0e:67:2b:34:3f:ce:04:ac:6b:22:e0:
+    cf:47:56:8b:c4:5d:70:a6:8e:68:c6:49:a4:83:0a:
+    e2:18:59:0c:1a:43:7e:7a:23:a5:4e:fe:44:f6:70:
+    86:eb:69:7b:9f:a5:78:35:f0:b8:f7:0f:0a:92:92:
+    26:ef:b3:36:c0:e2:18:33:a0:28:21:8c:d6:37:32:
+    c8:0a:a4:77:e6:2d:14:1d:ba:81:85:4f:70:da:68:
+    da:ff:4a:84:cb:6d:e7:79:25:4e:8a:97:e7:35:65:
+    37:4a:f4:09:2a:f0:5c:bd:66:54:af:c3:fd:72:f0:
+    ae:23:26:95:cb:66:68:ea:fe:cc:40:69:bd:90:bb:
+    52:8b:83:ef:a2:fb:cd:bd:93:b2:89:92:96:21:ed:
+    74:d8:08:73:8f:c1:03:ee:b1:05:51:08:51:fc:93:
+    19:f1:71:ea:0c:ed:0b:97:b5:b9:fb:5e:f9:85:18:
+    6b:c5:20:98:f9:eb:47:6f:67:b7:cc:76:65:d4:75:
+    87:97:5c:b4:5a:50:fc:64:10:07:19:bf:76:34:5f:
+    0f:df:1e:09:ef:e9:fb:80:0d:c1:14:e4:6b:e0:87:
+    9a:19:5c:c0:68:70:e2:3d:26:31:da:e7:1c:39:94:
+    48:1c:87:61:c4:0d:07:c5:bf:ca:95:e7:18:b7:b2:
+    25:85:af:03:ed:34:17:5a:46:d5:7a:f3:51:8e:32:
+    a7:fc:1a:a4:48:27:32:a8:1a:87:f7:24:f8:d2:e7:
+    80:b3:a3:9d:45:1a:38:0f:75:c2:d6:80:cc:72:13:
+    ea:b1:d4:a5:9d:39:4a:e3:81:0a:1c:90:81:8d:52:
+    f9:3f:b2:03:e2:d8:b1:b5:fa:8f:60:b2:d5:85:d9:
+    13:5d:64:88:46:f1:38:b8:69:53:24:2d:2b:b1:f2:
+    ec:df:38:9b:4d:e7:65:18:17:b8:e4:e6:4b:33:3f:
+    1a:ac:52:3a:93:f2:74:8a:9c:38:ff:bc:29:ce:d4:
+    57:b6:f9:78:1b:08:a6:7a:19:75:d0:31:cc:d7:15:
+    45:c0:03:74:34:05:6c:24:34:d1:3e:6c:4b:ee:bf:
+    46:fc:12:22:2c:0b:2e:cc:d6:15:9d:5a:ea:8e:55:
+    4d:7a:09:65:2b:06:bf:7c:a6:99:a7:19:9e:71:6d:
+    05:dd:55:30:41:a8:f2:b3:03:d2:36:a9:ba:ba:af:
+    b9:fa:52:8f:28:a2:ca:2a:a7:80:b9:40:38:3c:09:
+    9a:a6:5a:00:74:b8:3f:d1:f0:bc:5b:7b:5e:46:c2:
+    5e:54:83:8b:3c:bc:fc:95:f8:7f:1d:47:1b:3b:a8:
+    94:43:4f:a5:89:52:fd:cb:77:f1:61:37:26:93:30:
+    6d:ba:4e:8f:21:6d:1c:8e:5c:af:f0:fe:83:60:a5:
+    1c:60:76:36:44:16:9f:dc:6a:82:67:f2:e3:f9:09:
+    a6:1b:2a:67:8b:ce:6a:e9:04:03:a8:36:b1:a7:b7:
+    e8:cd:8b:54:c3:70:87:a9:e1:44:46:d9:5e:69:08:
+    d2:ee:db:fc:c6:53:e0:2f:df:77:1f:70:1a:79:b9:
+    e5:a2:6e:d0:a9:47:84:20:70:f3:b5:70:17:42:21:
+    12:19:e7:61:76:2c:37:f0:d0:a1:d1:b9:75:0f:ee:
+    57:7e:12:08:11:5c:66:ac:07:ec:09:1e:6a:3f:c4:
+    aa:6a:25:3b:cb:a8:68:ed:d3:15:4d:ca:f5:16:2f:
+    61:5e:85:49:0a:6c:a3:42:f3:4c:43:ac:61:a3:ea:
+    6b:fe:ef:d8:50:e1:90:eb:1d:8d:a4:d2:8b:5e:ce:
+    eb:16:78:c0:24:33:ec:d5:d4:8b:25:36:40:42:57:
+    e8:ca:7b:ef:58:55:f2:b8:13:ed:2f:4c:40:94:45:
+    a3:31:7c:9b:e1:a3:5a:e2:fb:4d:2b:87:92:1b:90:
+    4b:f2:c1:4d:b5:14:ce:e0:45:25:1c:fc:27:63:74:
+    db:15:c9:9d:ea:15:ac:de:19:7c:6e:b5:24:98:8e:
+    39:b6:32:87:be:b8:67:68:65:aa:a3:ba:d1:b4:3b:
+    8c:ab:15:cb:f2:7a:49:87:59:e3:20:3a:bf:36:9e:
+    97:24:2f:0b:01:54:14:9f:14:ac:23:3c:db:73:a2:
+    2b:7f:b8:f0:93:25:bf:2a:ce:83:bb:6b:5d:b8:a1:
+    21:a2:b6:82:14:9a:69:13:1c:cc:e5:22:29:84:0b:
+    11:3f:c7:b0:bc:c5:84:05:bf:e8:7f:1f:95:ff:c2:
+    e9:6f:c5:59:65:67:e9:43:64:df:aa:6d:9d:5a:6e:
+    b9:9a:e4:dd:f4:24
+pub:
+    97:92:bc:ec:2f:24:30:68:6a:82:fc:cf:3c:2f:5f:
+    f6:65:e7:71:d7:ab:41:b9:02:58:cf:a7:e9:0e:c9:
+    71:24:a7:3b:32:3b:9b:a2:1a:b6:4d:76:7c:43:3f:
+    5a:52:1e:ff:e1:8f:86:e4:6a:18:89:52:c4:46:7e:
+    04:8b:72:9e:7f:c4:d1:15:e7:e4:8d:a1:89:6d:5f:
+    e1:19:b1:0d:cd:de:f6:2c:b3:07:95:40:74:b4:23:
+    36:e5:28:36:de:61:da:94:1f:8d:37:ea:68:ac:81:
+    06:fa:be:19:07:06:79:af:60:08:53:71:20:f7:07:
+    93:b8:ea:9c:c0:e6:e7:b7:b4:c9:a5:c7:42:1c:60:
+    f2:44:51:ba:1e:93:3d:b1:a2:ee:16:c7:95:59:f2:
+    1b:3d:1b:83:05:85:0a:a4:2a:fb:b1:3f:1f:4d:5b:
+    9f:48:35:f9:d8:7d:fc:eb:16:2d:0e:f4:a7:fd:c4:
+    cb:a1:74:3c:d1:c8:7b:b4:96:7d:a1:6c:c8:76:4b:
+    65:69:df:8e:e5:bd:cb:ff:e9:a4:e0:57:48:e6:fd:
+    f2:25:af:9e:4e:eb:77:73:b6:2e:8f:85:f9:b5:6b:
+    54:89:45:55:18:44:fb:d8:98:06:a4:ac:36:9b:ed:
+    2d:25:61:00:f6:88:a6:ad:5e:0a:70:98:26:dc:44:
+    49:e9:1e:23:c5:50:6e:64:23:61:ef:5a:31:37:12:
+    f7:9b:c4:b3:18:68:61:ca:85:a4:ba:b1:7e:7f:94:
+    3d:1b:8a:33:3a:a3:ae:7c:e1:6b:44:0d:60:18:f9:
+    e0:4d:af:57:25:c7:f1:a9:3f:ad:1a:5a:27:b6:78:
+    95:bd:24:9a:a9:16:85:de:20:af:32:c8:b7:e2:68:
+    c7:f9:68:77:d0:c8:50:01:13:5a:4f:0a:8f:1b:82:
+    64:fa:6e:be:5a:34:9d:8a:ec:ad:1a:16:29:9c:cf:
+    2f:d9:c7:b8:5b:ac:e2:ce:d3:aa:12:76:ba:61:ee:
+    78:ed:7e:5c:a5:b6:7c:dd:45:8a:93:54:03:0e:6a:
+    bb:ba:bf:56:a0:a2:31:6f:ec:9d:ba:83:b5:1d:42:
+    fd:31:67:f1:e0:f9:08:55:d5:c6:65:09:b2:10:26:
+    5d:c1:e5:4e:c4:4b:43:ba:7c:f9:ae:f1:18:b4:4d:
+    80:91:2c:e7:51:66:a6:65:1e:11:6c:eb:e4:92:29:
+    a7:06:2c:09:93:1f:71:ab:d2:29:3f:76:f7:ef:c3:
+    21:5b:a9:78:00:03:7e:58:e4:70:bd:bb:b4:3c:1b:
+    04:39:ea:f7:9c:54:d9:3b:44:aa:c9:ef:e9:fb:e1:
+    51:87:4c:fb:2a:64:cb:ee:28:cc:4c:0f:e7:77:5e:
+    5d:87:0f:1c:02:e5:b2:e3:c5:00:4c:99:5f:24:c9:
+    b7:79:cb:75:3a:27:7d:0e:71:fd:42:5e:b6:bc:2c:
+    a5:6c:e1:29:db:51:f7:07:40:f3:1e:63:97:6b:50:
+    c7:31:2e:97:97:d7:8c:5b:1a:c2:4a:5f:a3:47:cc:
+    91:6e:0a:83:f5:c3:b6:75:cd:30:b8:1e:3f:a1:0b:
+    93:44:4e:07:39:75:71:cc:e9:8b:28:da:51:db:90:
+    56:bc:72:8c:5b:0b:11:81:e2:fb:d3:87:b4:c7:9a:
+    b1:a5:fe:fe:ce:37:16:7a:f7:72:dd:ad:14:eb:4c:
+    39:82:da:5a:59:d0:e9:eb:17:3e:c6:31:50:91:17:
+    00:27:a3:ab:5e:f6:aa:12:9c:b8:58:57:27:b9:35:
+    8a:28:50:1d:71:3a:72:f3:f1:db:31:71:42:86:f9:
+    b6:40:80:13:af:06:04:5d:75:59:2f:c0:b7:dd:47:
+    c7:3e:d9:c7:5b:11:e9:d7:c6:9f:7c:ad:fc:32:80:
+    a9:06:2c:52:73:c4:3b:e1:c3:4f:87:44:88:64:ce:
+    a7:b5:c9:7d:6d:32:f5:9b:d5:f2:53:84:65:3b:b5:
+    c4:fa:a4:5b:ea:8b:89:40:28:43:e6:45:b6:b9:26:
+    9e:2b:d9:88:dd:ac:b0:33:32:8f:fb:06:04:50:f7:
+    df:08:00:53:e6:96:9b:25:1e:87:5e:ce:c3:2c:fc:
+    59:28:40:d6:9a:b6:9a:75:e0:6b:37:9c:53:5d:95:
+    26:6b:08:2f:4f:09:c9:31:62:b3:3b:0d:9f:73:07:
+    a4:ea:aa:52:10:44:37:fe:d6:6f:8e:e3:ea:bb:d4:
+    5d:67:b2:5a:81:33:f4:96:46:8b:52:ba:ff:db:fa:
+    d9:3e:ef:1a:98:18:b5:e4:2e:c7:22:78:8a:3d:8d:
+    35:29:fc:77:7d:2b:a5:70:80:1d:fa:e0:1e:c8:83:
+    02:83:7c:1f:b9:e0:35:57:27:64:5e:e1:04:6c:3f:
+    91:5f:6a:e8:2d:ad:4f:b6:b0:35:6a:46:51:8f:fc:
+    83:41:55:c3:b4:fe:6d:af:a6:cc:8a:5c:cf:53:c7:
+    3a:08:49:d8:d4:4f:7d:cf:72:75:4e:70:e1:b7:df:
+    b4:47:bb:4e:f4:9d:1a:71:8f:61:71:bb:ce:20:09:
+    50:e0:ce:92:61:06:b1:51:a3:e8:71:d5:ce:49:73:
+    1b:d6:65:0a:9b:0c:a9:72:da:1c:5f:13:6d:44:82:
+    0e:a6:38:3c:08:f3:b3:84:cf:23:38:e7:89:c5:13:
+    f6:18:cc:56:94:a6:f0:ce:e1:04:51:1e:1e:d7:c5:
+    f2:3a:1e:bf:d8:a0:db:84:24:55:32:40:15:6d:bf:
+    62:28:31:b0:c6:43:d1:c5:51:b6:f3:f7:a9:8d:29:
+    b8:5c:2d:e0:5a:65:fa:61:5e:ee:16:49:5b:d9:07:
+    37:67:21:15:b5:3e:91:c5:d9:00:28:cf:3f:1a:93:
+    95:3a:15:3d:e5:3b:44:08:4e:9c:cf:f6:b7:36:69:
+    39:26:da:ef:eb:b2:d7:7a:a5:ad:68:9b:92:f3:16:
+    86:66:9d:f1:6d:17:15:cc:58:f7:a2:cf:b7:2d:d1:
+    a5:1e:92:f8:25:99:3a:74:02:2b:e7:e9:eb:60:54:
+    65:44:57:09:4d:14:92:8f:20:21:5e:7b:22:2a:c5:
+    6b:51:ad:be:c8:d8:bd:b6:98:39:79:a7:e3:a2:1b:
+    44:b5:d1:51:8c:a9:7d:0b:51:95:f5:1e:d6:a2:43:
+    50:c8:97:47:e1:ed:ea:51:b4:48:e3:e9:14:70:54:
+    ce:92:78:73:c9:0d:b3:94:d8:68:88:e0:7d:ff:17:
+    75:93:d6:f7:9e:15:23:02:20:4a:eb:03:be:23:86:
+    af:3e:24:07:8b:d0:28:b1:68:9f:5e:14:7c:9f:45:
+    2c:8c:eb:02:ec:59:cc:9d:b6:3a:03:57:6c:ee:af:
+    e9:82:39:02:38:97:da:02:36:63:0a:53:c0:de:7f:
+    43:5a:19:86:97:92:fa:b3:6e:7b:9e:63:57:60:f0:
+    90:69:e6:43:2e:70:00:35:ac:2a:02:87:9f:ff:0a:
+    1e:1b:ec:52:20:47:19:3d:94:eb:5d:f1:ef:d5:3e:
+    ea:11:44:ca:78:94:08:52:f5:ec:97:27:90:4b:36:
+    6e:de:4f:5e:2d:33:1f:ad:5f:c2:82:ea:2c:47:e9:
+    23:14:27:71:c3:dd:75:a8:73:57:48:7d:ef:99:e5:
+    f1:8e:9d:9e:d6:23:c1:75:d0:28:88:c5:1f:82:c0:
+    7a:80:d5:47:16:b3:c3:c2:bd:be:2e:9f:0a:9b:ba:
+    ae:be:b4:d5:29:36:87:64:06:f5:c0:0e:8e:4b:bd:
+    0a:5e:c0:57:97:e6:20:7c:5a:b6:c8:8f:1a:68:84:
+    21:bd:05:a1:14:f4:d7:de:2a:c2:41:fa:0e:8b:ed:
+    ff:47:f7:62:dd:cb:ea:a9:10:04:f8:d3:1e:85:09:
+    5c:81:05:49:94:ad:38:26:e3:44:ba:96:04:08:10:
+    fc:0b:2a:d1:de:48:cf:ad:e0:02:c6:2e:5a:49:a0:
+    73:1a:b3:83:44:bc:16:36:df:16:bf:60:7d:56:85:
+    5e:56:d6:84:00:3c:71:8e:4b:ad:9e:5a:09:99:79:
+    fc:dd:ee:b1:c4:a7:77:6c:d3:7a:34:17:cb:0e:18:
+    4e:29:ef:9b:c0:e8:74:75:ba:66:3b:e0:9e:00:ab:
+    56:2e:b7:c0:f7:16:5f:96:9a:9b:42:41:41:98:cc:
+    f1:bf:f2:a2:c8:d6:89:a4:14:ec:e7:66:29:27:66:
+    56:89:e9:4d:b9:61:eb:ae:c5:61:5c:bc:1a:78:95:
+    c6:85:1a:c9:61:43:2f:f1:11:8d:46:07:d3:2e:f9:
+    dc:73:2d:51:33:3b:e4:b4:d0:e3:0d:de:a7:84:ec:
+    a8:be:47:e7:41:be:9c:19:63:1d:c4:70:a5:2e:f4:
+    dc:13:a4:f3:63:3f:d4:34:d7:87:c1:70:97:7b:41:
+    7d:f5:98:e1:d0:dd:e5:06:bb:71:d6:f0:bc:17:ec:
+    70:e3:b0:3c:dc:19:65:cb:36:99:3f:63:3b:04:72:
+    e5:0d:09:23:ac:6c:66:fd:f1:d3:e6:45:9c:c1:21:
+    f0:f5:f9:4d:09:e9:db:cf:5d:69:0e:23:23:38:38:
+    a0:ba:cb:7c:63:8d:1b:26:50:a4:30:8c:d1:71:b6:
+    85:51:26:d1:da:67:2a:6e:d8:5a:8d:78:c2:86:fb:
+    56:f4:ab:3d:21:49:75:28:04:5c:63:26:2c:8a:42:
+    af:2f:98:02:c5:3b:7b:b8:be:28:e7:8f:e0:b5:ce:
+    45:fb:b7:a1:af:1a:3b:28:a8:d9:4b:78:90:e3:c8:
+    82:e3:9b:c9:8e:9f:0a:d7:60:25:bf:0d:d2:f0:02:
+    98:e7:14:1a:22:6b:3d:7c:ee:41:4f:60:4d:1e:0b:
+    a5:4d:11:d5:fe:58:bc:ce:a6:ad:77:ad:2e:8c:1c:
+    aa:cf:32:45:90:14:b7:b9:10:01:b1:ef:a8:ad:17:
+    2a:52:3f:b8:e3:65:b5:77:12:1b:f9:fd:88:a2:c6:
+    0c:21:e8:21:d7:b6:ac:b4:7a:5a:99:5e:40:ca:ce:
+    d5:c2:23:b8:fe:6d:e5:e1:8e:9d:2e:58:93:ae:fe:
+    bb:7a:ae:7f:f1:a1:46:26:0e:2f:11:0e:93:95:28:
+    21:3a:00:25:a3:8e:c7:9a:ab:c8:61:b2:5e:bc:50:
+    9a:46:74:c1:32:aa:ac:b7:e0:14:6f:14:ef:d1:1c:
+    fc:af:4c:aa:4f:77:5a:71:6c:e3:25:e0:a4:35:a4:
+    d3:49:d7:20:bc:f1:37:45:0a:fc:45:04:6f:c1:a1:
+    f8:3a:9d:32:97:77:a7:08:4e:4a:ad:ae:71:22:ce:
+    97:00:59:30:52:8e:b3:c7:f7:f1:12:9b:37:28:87:
+    a3:71:15:5a:3b:a2:01:a2:5c:bf:1d:cb:64:e7:cd:
+    ee:09:2c:31:41:fb:55:50:fe:3d:0d:d8:2e:87:0e:
+    57:8b:2b:46:50:08:18:11:3b:8f:65:69:77:3c:67:
+    73:85:b6:9a:42:b7:7d:cb:a7:ac:ff:d9:5f:d4:45:
+    2e:23:aa:a1:d3:7e:1d:a2:15:1e:a6:58:d4:0a:35:
+    96:b2:7a:c9:f8:12:9d:c6:cf:06:43:77:26:24:b5:
+    9f:4f:46:12:30:df:47:1c:a2:60:87:c3:94:2d:5c:
+    66:87:df:60:82:83:59:35:a3:f8:7c:b7:62:b0:c3:
+    b1:d0:dd:a4:a6:53:39:65:be:f1:b7:b8:29:2e:25:
+    4c:01:4d:09:0f:ed:85:7c:44:c1:83:9c:69:4c:0a:
+    64:e3:fa:d9:0a:11:f5:34:72:2b:6e:e1:57:4f:2e:
+    14:9d:55:d7:44:de:48:87:02:4e:08:51:14:31:c0:
+    62:75:0e:16:c7:4a:b9:f3:24:2f:2d:b3:ff:b1:2a:
+    8d:61:07:fa:a2:29:d6:f6:37:3b:07:f3:6d:39:32:
+    b3:bd:b0:4c:19:dd:64:ea:dd:7f:93:c3:c5:64:c3:
+    58:a1:c8:1d:cf:1c:9c:31:e5:b0:65:68:f9:75:44:
+    c1:7d:c1:56:98:c5:cb:38:98:3a:9a:fc:42:78:3f:
+    aa:77:3a:52:c9:d8:26:06:90:be:9e:31:56:aa:5b:
+    c1:50:9d:ea:3f:69:58:76:95:cd:6f:f1:72:ba:83:
+    e6:a6:d8:a7:d6:bb:eb:bb:cd:a3:67:27:31:98:3f:
+    89:bc:58:31:dc:37:c3:f3:c5:c5:6f:ac:c6:97:f3:
+    cb:20:bd:5d:ba:db:d7:02:e5:48:44:ac:2f:62:69:
+    01:fe:15:9d:b9:3d:fd:47:73:d8:fe:73:56:2b:84:
+    6c:1f:c8:56:d1:80:27:62:84:0e:bc:72:d7:98:8b:
+    de:75:cb:ca:70:d3:19:d3:2c:e0:cc:02:53:bb:2a:
+    d4:55:72:3e:e0:c7:f4:73:6c:e6:e6:66:5c:5a:ca:
+    32:a4:81:c5:38:39:bc:25:91:67:b0:13:d0:42:33:
+    95:ee:b9:aa:ae:e3:20:61:49:a7:d5:50:d6:7f:c5:
+    fd:fe:4a:8a:5c:35:d2:51:0b:66:43:79:ab:8f:72:
+    85:5a:2a:f4:7a:bc:e2:a6:32:04:8e:af:89:e5:cb:
+    4a:88:de:bc:53:a5:95:10:3a:cc:e4:f1:cf:f1:8a:
+    cf:f0:7a:fe:1e:b5:71:6a:a1:e4:0b:63:13:4c:3a:
+    3a:e9:57:9f:a8:7f:51:5b:e0:93:c2:d2:9d:b6:d6:
+    b6:5c:93:66:1e:00:63:6b:59:27:04:d0:93:cc:67:
+    16:c2:34:2e:b1:85:3d:48:c8:5c:63:ac:8a:28:54:
+    46:2c:7b:77:e7:e3:bd:1e:ac:5b:ca:28:ff:aa:00:
+    b5:d3:49:f8:a5:47:ad:87:5b:96:a8:c2:b2:91:0c:
+    93:01:30:9a:3f:91:38:a5:69:31:11:f5:5b:3c:00:
+    9c:a9:47:c3:9d:fc:82:d9:8e:b1:ca:a4:a9:cb:e8:
+    85:f7:86:fa:86:e5:5b:e0:62:22:2f:8b:a9:0a:97:
+    40:73:32:6b:31:21:2a:ec:e0:a3:4a:60
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/pub-44.pem b/test/recipes/15-test_ml_dsa_codecs_data/pub-44.pem
new file mode 100644 (file)
index 0000000..106f158
--- /dev/null
@@ -0,0 +1,30 @@
+-----BEGIN PUBLIC KEY-----
+MIIFMjALBglghkgBZQMEAxEDggUhANeytHJUquDbReeTDUqY0sl9jxOX0Xidr6Fw
+JLMW6b7JT8mUbULxm3mnQTu6oz5xSctC7VEVaTrAQfrLmIretf4OHYYxGEmVtZLD
+l9IpTi4U+QqkFLo4JomaxD9MzKy8JumoMrlRGNXLQzy++WYLABOOCBf2HnYsonTD
+atVU6yKqwRYuSrAay6HjjE79j4C2WzM9D3LlXf5xzpweu5iJ58VhBsD9c4A6Kuz+
+r97XqjyyztpU0SvYzTanjPl1lDtHq9JeiArEUuV0LtHo0agq+oblkMdYwVrk0oQN
+kryhpQkPQElll/yn2LlRPxob2m6VCqqY3kZ1B9Sk9aTwWZIWWCw1cvYu2okFqzWB
+ZwxKAnd6M+DKcpX9j0/20aCjp2g9ZfX19/xg2gI+gmxfkhRMAvfRuhB1mHVT6pNn
+/NdtmQt/qZzUWv24g21D5Fn1GH3wWEeXCaAepoNZNfpwRgmQzT3BukAbqUurHd5B
+rGerMxncrKBgSNTE7vJ+4TqcF9BTj0MPLWQtwkFWYN54h32NirxyUjl4wELkKF9D
+GYRsRBJiQpdoRMEOVWuiFbWnGeWdDGsqltOYWQcf3MLN51JKe+2uVOhbMY6FTo/i
+svPt+slxkSgnCq/R5QRMOk/a/Z/zH5B4S46ORZYUSg2vWGUR09mWK56pWvGXtOX8
+YPKx7RXeOlvvX4m9x52RBR2bKBbnT6VFMe/cHL501EiFf0drzVjyHAtlOzt2pOB2
+plWaMCcYVVzGP3SFmqurkl8COGHKjND3utsocfZ9VTJtdFETWtRfShumkRj7ssij
+DuyTku8/l3Bmya3VxxDMZHsVFNIX2VjHAXw+kP0gwE5nS5BIbpNwoxoAHTL0c5ee
+SQZ0nn5Hf6C3RQj4pfI3gxK4PCW9OIygsP/3R4uvQrcWZ+2qyXxGsSlkPlhuWwVa
+DCEZRtTzbmdb7Vhg+gQqMV2YJhZNapI3w1pfv0lUkKW9TfJIuVxKrneEtgVnMWas
+QkW1tLCCoJ6TI+YvIHjFt2eDRG3v1zatOjcC1JsImESQCmGDM5e8RBmzDXqXoLOH
+wZEUdMTUG1PjKpd6y28Op122W7OeWecB52lX3vby1EVZwxp3EitSBOO1whnxaIsU
+7QvAuAGz5ugtzUPpwOn0F0TNmBW9G8iCDYuxI/BPrNGxtoXdWisbjbvz7ZM2cPCV
+oYC08ZLQixC4+rvfzCskUY4y7qCl4MkEyoRHgAg/OwzS0Li2r2e8NVuUlAJdx7Cn
+j6gOOi2/61EyiFHWB4GY6Uk2Ua54fsAlH5Irow6fUd9iptcnhM890gU5MXbfoySl
+Er2Ulwo23TSlFKhnkfDrNvAUWwmrZGUbSgMTsplhGiocSIkWJ1mHaKMRQGC6RENI
+bfUVIqHOiLMJhcIW+ObtF43VZ7MEoNTK+6iCooNC8XqaomrljbYwCD0sNY/fVmw/
+XWKkKFZ7yeqM6VyqDzVHSwv6jzOaJQq0388gg76O77wQVeGP4VNw7ssmBWbYP/Br
+IRquxDyim1TM0A+IFaJGXvC0ZRXMfkHzEk8J7/9zkwmrWLKaFFmgC85QOOk4yWeP
+cusOTuX9quZtn4Vz/Jf8QrSVn0v4th14Qz6GsDNdbpGRxNi/SHs5BcEIz9asJLDO
+t9y3z1H4TQ7Wh7lerrHFM8BvDZcCPZKnCCWDe1m6bLfU5WsKh8IDhiro8xW6WSXo
+7e+meTaaIgJ2YVHxapZfn4Hs52zAcLVYaeTbl4TPBcgwsyQsgxI=
+-----END PUBLIC KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/pub-44.txt b/test/recipes/15-test_ml_dsa_codecs_data/pub-44.txt
new file mode 100644 (file)
index 0000000..a3f43de
--- /dev/null
@@ -0,0 +1,90 @@
+ML-DSA-44 Public-Key:
+pub:
+    d7:b2:b4:72:54:aa:e0:db:45:e7:93:0d:4a:98:d2:
+    c9:7d:8f:13:97:d1:78:9d:af:a1:70:24:b3:16:e9:
+    be:c9:4f:c9:94:6d:42:f1:9b:79:a7:41:3b:ba:a3:
+    3e:71:49:cb:42:ed:51:15:69:3a:c0:41:fa:cb:98:
+    8a:de:b5:fe:0e:1d:86:31:18:49:95:b5:92:c3:97:
+    d2:29:4e:2e:14:f9:0a:a4:14:ba:38:26:89:9a:c4:
+    3f:4c:cc:ac:bc:26:e9:a8:32:b9:51:18:d5:cb:43:
+    3c:be:f9:66:0b:00:13:8e:08:17:f6:1e:76:2c:a2:
+    74:c3:6a:d5:54:eb:22:aa:c1:16:2e:4a:b0:1a:cb:
+    a1:e3:8c:4e:fd:8f:80:b6:5b:33:3d:0f:72:e5:5d:
+    fe:71:ce:9c:1e:bb:98:89:e7:c5:61:06:c0:fd:73:
+    80:3a:2a:ec:fe:af:de:d7:aa:3c:b2:ce:da:54:d1:
+    2b:d8:cd:36:a7:8c:f9:75:94:3b:47:ab:d2:5e:88:
+    0a:c4:52:e5:74:2e:d1:e8:d1:a8:2a:fa:86:e5:90:
+    c7:58:c1:5a:e4:d2:84:0d:92:bc:a1:a5:09:0f:40:
+    49:65:97:fc:a7:d8:b9:51:3f:1a:1b:da:6e:95:0a:
+    aa:98:de:46:75:07:d4:a4:f5:a4:f0:59:92:16:58:
+    2c:35:72:f6:2e:da:89:05:ab:35:81:67:0c:4a:02:
+    77:7a:33:e0:ca:72:95:fd:8f:4f:f6:d1:a0:a3:a7:
+    68:3d:65:f5:f5:f7:fc:60:da:02:3e:82:6c:5f:92:
+    14:4c:02:f7:d1:ba:10:75:98:75:53:ea:93:67:fc:
+    d7:6d:99:0b:7f:a9:9c:d4:5a:fd:b8:83:6d:43:e4:
+    59:f5:18:7d:f0:58:47:97:09:a0:1e:a6:83:59:35:
+    fa:70:46:09:90:cd:3d:c1:ba:40:1b:a9:4b:ab:1d:
+    de:41:ac:67:ab:33:19:dc:ac:a0:60:48:d4:c4:ee:
+    f2:7e:e1:3a:9c:17:d0:53:8f:43:0f:2d:64:2d:c2:
+    41:56:60:de:78:87:7d:8d:8a:bc:72:52:39:78:c0:
+    42:e4:28:5f:43:19:84:6c:44:12:62:42:97:68:44:
+    c1:0e:55:6b:a2:15:b5:a7:19:e5:9d:0c:6b:2a:96:
+    d3:98:59:07:1f:dc:c2:cd:e7:52:4a:7b:ed:ae:54:
+    e8:5b:31:8e:85:4e:8f:e2:b2:f3:ed:fa:c9:71:91:
+    28:27:0a:af:d1:e5:04:4c:3a:4f:da:fd:9f:f3:1f:
+    90:78:4b:8e:8e:45:96:14:4a:0d:af:58:65:11:d3:
+    d9:96:2b:9e:a9:5a:f1:97:b4:e5:fc:60:f2:b1:ed:
+    15:de:3a:5b:ef:5f:89:bd:c7:9d:91:05:1d:9b:28:
+    16:e7:4f:a5:45:31:ef:dc:1c:be:74:d4:48:85:7f:
+    47:6b:cd:58:f2:1c:0b:65:3b:3b:76:a4:e0:76:a6:
+    55:9a:30:27:18:55:5c:c6:3f:74:85:9a:ab:ab:92:
+    5f:02:38:61:ca:8c:d0:f7:ba:db:28:71:f6:7d:55:
+    32:6d:74:51:13:5a:d4:5f:4a:1b:a6:91:18:fb:b2:
+    c8:a3:0e:ec:93:92:ef:3f:97:70:66:c9:ad:d5:c7:
+    10:cc:64:7b:15:14:d2:17:d9:58:c7:01:7c:3e:90:
+    fd:20:c0:4e:67:4b:90:48:6e:93:70:a3:1a:00:1d:
+    32:f4:73:97:9e:49:06:74:9e:7e:47:7f:a0:b7:45:
+    08:f8:a5:f2:37:83:12:b8:3c:25:bd:38:8c:a0:b0:
+    ff:f7:47:8b:af:42:b7:16:67:ed:aa:c9:7c:46:b1:
+    29:64:3e:58:6e:5b:05:5a:0c:21:19:46:d4:f3:6e:
+    67:5b:ed:58:60:fa:04:2a:31:5d:98:26:16:4d:6a:
+    92:37:c3:5a:5f:bf:49:54:90:a5:bd:4d:f2:48:b9:
+    5c:4a:ae:77:84:b6:05:67:31:66:ac:42:45:b5:b4:
+    b0:82:a0:9e:93:23:e6:2f:20:78:c5:b7:67:83:44:
+    6d:ef:d7:36:ad:3a:37:02:d4:9b:08:98:44:90:0a:
+    61:83:33:97:bc:44:19:b3:0d:7a:97:a0:b3:87:c1:
+    91:14:74:c4:d4:1b:53:e3:2a:97:7a:cb:6f:0e:a7:
+    5d:b6:5b:b3:9e:59:e7:01:e7:69:57:de:f6:f2:d4:
+    45:59:c3:1a:77:12:2b:52:04:e3:b5:c2:19:f1:68:
+    8b:14:ed:0b:c0:b8:01:b3:e6:e8:2d:cd:43:e9:c0:
+    e9:f4:17:44:cd:98:15:bd:1b:c8:82:0d:8b:b1:23:
+    f0:4f:ac:d1:b1:b6:85:dd:5a:2b:1b:8d:bb:f3:ed:
+    93:36:70:f0:95:a1:80:b4:f1:92:d0:8b:10:b8:fa:
+    bb:df:cc:2b:24:51:8e:32:ee:a0:a5:e0:c9:04:ca:
+    84:47:80:08:3f:3b:0c:d2:d0:b8:b6:af:67:bc:35:
+    5b:94:94:02:5d:c7:b0:a7:8f:a8:0e:3a:2d:bf:eb:
+    51:32:88:51:d6:07:81:98:e9:49:36:51:ae:78:7e:
+    c0:25:1f:92:2b:a3:0e:9f:51:df:62:a6:d7:27:84:
+    cf:3d:d2:05:39:31:76:df:a3:24:a5:12:bd:94:97:
+    0a:36:dd:34:a5:14:a8:67:91:f0:eb:36:f0:14:5b:
+    09:ab:64:65:1b:4a:03:13:b2:99:61:1a:2a:1c:48:
+    89:16:27:59:87:68:a3:11:40:60:ba:44:43:48:6d:
+    f5:15:22:a1:ce:88:b3:09:85:c2:16:f8:e6:ed:17:
+    8d:d5:67:b3:04:a0:d4:ca:fb:a8:82:a2:83:42:f1:
+    7a:9a:a2:6a:e5:8d:b6:30:08:3d:2c:35:8f:df:56:
+    6c:3f:5d:62:a4:28:56:7b:c9:ea:8c:e9:5c:aa:0f:
+    35:47:4b:0b:fa:8f:33:9a:25:0a:b4:df:cf:20:83:
+    be:8e:ef:bc:10:55:e1:8f:e1:53:70:ee:cb:26:05:
+    66:d8:3f:f0:6b:21:1a:ae:c4:3c:a2:9b:54:cc:d0:
+    0f:88:15:a2:46:5e:f0:b4:65:15:cc:7e:41:f3:12:
+    4f:09:ef:ff:73:93:09:ab:58:b2:9a:14:59:a0:0b:
+    ce:50:38:e9:38:c9:67:8f:72:eb:0e:4e:e5:fd:aa:
+    e6:6d:9f:85:73:fc:97:fc:42:b4:95:9f:4b:f8:b6:
+    1d:78:43:3e:86:b0:33:5d:6e:91:91:c4:d8:bf:48:
+    7b:39:05:c1:08:cf:d6:ac:24:b0:ce:b7:dc:b7:cf:
+    51:f8:4d:0e:d6:87:b9:5e:ae:b1:c5:33:c0:6f:0d:
+    97:02:3d:92:a7:08:25:83:7b:59:ba:6c:b7:d4:e5:
+    6b:0a:87:c2:03:86:2a:e8:f3:15:ba:59:25:e8:ed:
+    ef:a6:79:36:9a:22:02:76:61:51:f1:6a:96:5f:9f:
+    81:ec:e7:6c:c0:70:b5:58:69:e4:db:97:84:cf:05:
+    c8:30:b3:24:2c:83:12
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/pub-65.pem b/test/recipes/15-test_ml_dsa_codecs_data/pub-65.pem
new file mode 100644 (file)
index 0000000..3f2512b
--- /dev/null
@@ -0,0 +1,44 @@
+-----BEGIN PUBLIC KEY-----
+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
+HgLy6ERP
+-----END PUBLIC KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/pub-65.txt b/test/recipes/15-test_ml_dsa_codecs_data/pub-65.txt
new file mode 100644 (file)
index 0000000..49ff603
--- /dev/null
@@ -0,0 +1,133 @@
+ML-DSA-65 Public-Key:
+pub:
+    48:68:3d:91:97:8e:31:eb:3d:dd:b8:b0:47:34:82:
+    d2:b8:8a:5f:62:59:49:fd:8f:58:a5:61:e6:96:bd:
+    4c:27:d0:5b:38:db:b2:ed:f0:1e:66:4e:fd:81:be:
+    1e:a8:93:68:8c:e6:8a:a2:d5:1c:59:58:f8:bb:c6:
+    eb:4e:89:ee:67:d2:c0:32:09:54:d5:72:12:ca:c7:
+    22:9f:f1:d6:ea:f0:39:28:bd:51:51:1f:8d:88:d8:
+    47:73:6c:7d:e2:73:0d:59:78:e5:41:07:13:16:09:
+    78:86:77:11:bf:55:39:a0:bf:c4:c3:50:c2:be:57:
+    2b:af:0e:e2:e2:fb:16:cc:fe:a0:80:28:d9:9a:c4:
+    9a:eb:b7:59:37:dd:ce:11:1c:da:b6:2f:ff:3c:ea:
+    8b:a2:23:3d:1e:56:fb:c5:c5:a1:e7:26:de:63:fa:
+    dd:2a:f0:16:b1:19:17:7f:a3:d9:71:a2:d9:27:71:
+    73:fc:e5:5b:67:74:5a:f0:b7:c2:1d:59:7d:be:b9:
+    3e:6a:32:f3:41:c4:9a:5a:8b:e9:e8:25:08:8d:1f:
+    2a:a4:51:55:d6:c8:ae:15:36:7e:4e:b0:03:b8:fd:
+    f7:85:10:71:94:97:39:f9:ff:f0:90:23:ea:f4:51:
+    04:d2:a8:4a:45:90:6e:ed:46:71:a4:4d:c2:8d:27:
+    98:7b:b5:5d:f6:9e:9e:85:61:f6:1a:80:a7:26:99:
+    50:38:65:fe:d9:b7:ee:72:a8:e1:7a:19:c4:08:14:
+    4f:4b:29:af:ef:70:31:c3:a6:d8:57:16:10:b4:2c:
+    9f:42:12:45:a8:8f:19:7e:16:81:2b:03:11:59:b6:
+    5b:96:87:e5:b3:e9:34:c5:22:5a:e9:8a:79:ba:73:
+    d2:b3:99:d7:35:10:ef:fa:d1:9e:53:b8:45:0f:0b:
+    a8:fc:e1:01:2f:d9:8d:26:0a:74:aa:aa:13:fa:e2:
+    49:a0:06:b1:c3:4f:5b:a0:b8:82:f2:63:78:22:2f:
+    b3:6f:22:83:c2:43:f0:ff:eb:5f:1b:b4:14:a0:a7:
+    0d:55:e3:d4:0a:56:b6:cb:c8:8a:e1:f0:3b:7b:28:
+    82:d9:8d:ee:a2:8e:14:5c:9d:ed:fd:8e:af:1c:ef:
+    2e:d9:4a:8b:05:0f:89:64:f4:6d:1e:a0:d0:c2:a4:
+    3e:0d:da:61:82:ad:bf:4f:6e:d1:75:b6:74:22:57:
+    85:9b:f2:2f:3a:41:7e:cf:1f:9d:89:31:7b:5e:53:
+    9d:58:7a:f1:6b:9e:13:13:e0:45:14:ff:a6:4b:a8:
+    b3:ff:2b:83:21:f8:81:1c:b3:fb:02:2c:8f:64:4e:
+    70:a4:b8:0a:2f:bf:ee:60:4a:bb:73:79:09:1e:a8:
+    e6:c5:c7:4d:fc:02:83:66:6b:40:c0:79:38:70:02:
+    82:04:a1:36:bf:5d:a9:56:8e:b7:98:d3:49:03:8b:
+    db:0c:11:e0:34:45:e7:84:7c:b5:06:9c:75:cf:28:
+    ac:60:1c:77:99:d9:58:21:0d:db:cb:22:6e:51:af:
+    ef:9f:1d:e4:7b:07:38:73:d6:d3:f9:74:56:be:de:
+    08:50:82:e7:4a:29:8b:2c:d4:8f:4b:30:93:15:5f:
+    36:6c:8f:a6:01:c6:af:85:8d:fa:32:c0:84:91:b2:
+    a2:98:87:f9:03:35:94:9a:5d:6e:da:a6:79:88:2a:
+    3a:95:d6:bf:6d:97:0a:22:1f:4b:9d:3d:8c:bf:38:
+    4a:f8:1a:ac:95:e2:b3:29:4e:04:78:9a:c8:37:27:
+    a5:dc:04:55:9f:96:af:41:d8:a0:53:51:6f:ee:ee:
+    bc:52:74:6e:b6:ab:28:19:e0:91:08:71:0d:83:5f:
+    01:1f:a6:30:65:87:2a:d3:34:d5:cd:ff:b2:b2:31:
+    05:07:e9:2f:c9:93:ae:31:7d:a9:7f:4f:30:9c:da:
+    f0:f6:7e:d9:9d:90:21:55:76:08:38:49:f9:53:b2:
+    46:d7:fe:db:3f:db:67:67:98:50:a5:ad:40:4e:64:
+    14:7f:b7:cf:4f:6a:ed:dd:05:af:b4:b8:34:96:8d:
+    1f:e8:80:14:96:0d:ce:5d:94:22:36:52:6e:12:a4:
+    78:d6:9e:5f:be:69:70:31:0b:30:8c:06:84:50:18:
+    cf:c7:b2:ab:43:0a:13:a6:b1:ac:7b:b0:2c:cc:bb:
+    3d:91:1a:c2:f1:10:68:61:3f:be:02:9b:fd:ce:02:
+    cf:5c:d3:89:50:ed:72:c8:39:44:ed:fb:c7:56:15:
+    af:87:f8:64:c0:51:f3:c5:54:56:c5:41:28:63:a4:
+    0c:06:d1:da:b5:62:bd:ff:05:71:b8:d3:c3:91:7b:
+    bd:30:08:80:bb:a5:e9:98:23:9b:95:fa:91:b7:d6:
+    41:6d:4f:39:8b:3a:db:cd:30:98:3e:d3:59:2b:4d:
+    9e:f7:d4:23:6f:d0:0f:50:d9:8a:a5:3a:23:5a:c4:
+    17:27:20:f7:7d:96:17:26:72:98:0c:fe:8f:f7:a5:
+    a7:02:78:3e:dc:2b:a3:1b:22:59:01:5a:11:2f:c7:
+    f4:68:a9:c2:f9:46:40:39:00:2d:30:ef:67:8b:4c:
+    b7:98:bc:11:62:16:bf:7a:9a:7c:18:ba:03:b7:b5:
+    8f:d0:75:15:d3:11:50:49:d3:61:4b:e7:a0:7e:74:
+    43:00:75:0d:f1:d2:c5:87:53:38:90:59:ea:fc:3d:
+    78:5c:cd:d3:1c:07:64:8b:ed:c0:3a:5c:3b:8a:d4:
+    6d:06:4d:59:c1:3d:57:37:47:29:fc:4e:29:53:62:
+    e2:a5:19:12:04:53:04:28:bc:15:22:af:a2:8f:f5:
+    fe:16:55:e3:04:ca:5b:c8:c2:7a:d0:e0:c6:a3:9d:
+    d4:df:28:95:6c:14:b3:8c:c9:36:82:ce:fe:40:2b:
+    bd:5e:82:d2:9c:46:4e:44:eb:5d:37:b4:8f:c5:68:
+    df:e0:cc:6e:8e:16:ba:ea:05:e5:13:55:90:f1:92:
+    94:e7:3e:83:67:b0:21:6d:bb:81:50:30:b9:de:55:
+    91:3f:08:03:9c:42:35:1c:59:e5:51:5d:d5:af:8e:
+    08:9a:15:e6:25:e8:f6:de:e6:39:38:6c:46:49:7d:
+    7a:26:32:88:77:4d:e5:81:a7:de:96:29:b4:1b:44:
+    24:14:1f:97:8f:b8:33:12:08:ef:de:c3:c6:e0:de:
+    39:bc:57:06:3f:3d:cd:6c:47:03:73:c0:88:91:ea:
+    29:cb:c7:cc:6d:64:83:b8:88:90:83:ac:e8:6a:a7:
+    b5:1b:1c:2c:fe:6e:2a:d1:8d:97:ce:36:fb:c5:6e:
+    a4:2f:ae:97:e6:a7:ac:11:48:64:47:8c:36:6d:f1:
+    eb:b1:e7:b1:1a:90:98:50:4f:d5:97:5b:df:1f:49:
+    dc:70:00:2b:63:c1:73:9a:9d:26:3f:ba:d4:07:3f:
+    6a:9f:6c:2b:8a:f4:b4:c3:32:a1:03:a0:cf:fa:5d:
+    ee:b2:d0:62:ca:3c:21:5f:d3:60:02:6b:e7:c5:16:
+    4f:4a:44:24:ef:74:94:88:04:d6:6f:46:48:77:32:
+    c8:20:2c:79:54:78:64:7b:4e:a7:1d:62:7c:08:60:
+    24:cc:a3:54:a4:1f:08:77:b3:8f:19:b3:77:4a:d2:
+    09:5c:8d:a5:3b:06:9e:21:c7:6a:e2:d2:00:7e:16:
+    71:9e:d4:00:80:d3:34:f7:da:52:e9:f5:a5:99:04:
+    39:ca:f0:83:a9:5b:83:3f:02:ad:10:a0:8c:1a:6d:
+    0f:26:0c:00:72:85:bd:4a:2f:47:70:3a:5a:ef:46:
+    52:87:d2:53:b1:8a:c2:25:14:31:62:10:ff:56:68:
+    14:b1:0f:87:a2:93:d6:f1:99:d3:c3:95:99:90:d0:
+    c1:26:8b:4f:50:d5:f9:fc:ef:bb:f2:37:bd:0c:28:
+    b8:01:82:d6:65:97:41:f1:4f:10:bf:bb:21:bb:a1:
+    2a:b6:20:aa:23:96:f5:6c:06:86:b4:ea:90:17:99:
+    02:24:21:6b:2f:e8:ad:76:c4:a9:14:8e:ef:9a:86:
+    a3:63:5a:6a:a7:7b:c1:dc:fb:6f:ba:59:a7:7d:fd:
+    a9:b7:53:0d:c0:ca:86:48:c8:d9:73:73:8e:01:ba:
+    b8:f0:8b:49:05:e8:4a:a4:64:1b:d6:02:41:0c:d9:
+    75:20:26:5f:2f:23:1f:2b:35:e1:5e:b2:fa:04:d2:
+    bd:94:d5:a7:7a:ba:f1:e0:e1:61:01:0a:99:00:87:
+    f5:b4:6e:a9:88:b2:bc:05:12:fd:a0:fa:92:3d:ad:
+    d6:c4:5c:53:01:d0:94:83:67:32:65:b5:ab:2e:10:
+    f4:ba:52:0f:6b:ba:d5:64:a5:c3:d5:e2:7b:db:08:
+    0f:7d:20:e1:32:96:a3:18:19:54:c3:9c:64:9c:94:
+    3e:be:17:df:5c:1f:7a:ae:0a:8f:e1:26:c4:77:58:
+    5a:5d:4d:64:8a:0d:00:8b:6a:f5:e8:cd:31:be:69:
+    a9:29:6d:4f:3f:d2:5e:d8:6f:22:1e:4b:93:f6:5f:
+    59:29:96:75:33:62:4b:92:35:75:0c:30:70:75:50:
+    b5:85:36:d1:09:a7:13:1c:5a:5b:be:4a:57:15:56:
+    7c:12:53:4a:ec:76:60:76:1e:eb:b9:fa:e2:89:1c:
+    77:45:89:b8:0e:56:6a:d5:57:dd:ef:73:67:19:6b:
+    72:27:ea:98:70:ef:09:dd:fe:c7:9d:6b:93:19:a6:
+    87:9b:52:05:d7:6b:f7:ab:a5:ac:f3:3a:fb:59:d1:
+    7f:c5:4e:68:38:3d:6b:e5:a0:8e:9b:66:da:53:dc:
+    de:00:8b:b2:94:b8:58:2b:d1:32:cd:cc:49:95:9f:
+    db:c2:1e:52:72:18:80:c8:ad:03:52:c7:9f:03:a4:
+    3b:bd:84:c4:cd:fd:c6:c5:29:00:5e:1e:7c:d9:a3:
+    49:a7:16:8a:35:56:9b:a5:de:a8:18:96:8d:5a:91:
+    46:6b:d6:e6:4e:20:bf:62:41:71:98:af:c4:e8:1c:
+    28:dd:77:ed:40:28:23:23:98:b5:2f:bd:e8:6b:c8:
+    4f:47:5b:90:16:71:0c:e2:aa:bc:11:a0:6b:4d:ba:
+    c9:01:ec:16:cf:36:5c:a3:f2:d5:38:13:94:8a:69:
+    3a:0f:93:e7:9c:46:ca:5d:5a:6d:ca:3d:28:ca:50:
+    ad:18:bd:13:fc:a5:50:59:dd:9b:18:5f:79:f9:c4:
+    71:96:a4:e8:1b:21:04:bc:46:0a:05:1e:02:f2:e8:
+    44:4f
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/pub-87.pem b/test/recipes/15-test_ml_dsa_codecs_data/pub-87.pem
new file mode 100644 (file)
index 0000000..b627e0d
--- /dev/null
@@ -0,0 +1,57 @@
+-----BEGIN PUBLIC KEY-----
+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
+5VvgYiIvi6kKl0BzMmsxISrs4KNKYA==
+-----END PUBLIC KEY-----
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/pub-87.txt b/test/recipes/15-test_ml_dsa_codecs_data/pub-87.txt
new file mode 100644 (file)
index 0000000..1425474
--- /dev/null
@@ -0,0 +1,175 @@
+ML-DSA-87 Public-Key:
+pub:
+    97:92:bc:ec:2f:24:30:68:6a:82:fc:cf:3c:2f:5f:
+    f6:65:e7:71:d7:ab:41:b9:02:58:cf:a7:e9:0e:c9:
+    71:24:a7:3b:32:3b:9b:a2:1a:b6:4d:76:7c:43:3f:
+    5a:52:1e:ff:e1:8f:86:e4:6a:18:89:52:c4:46:7e:
+    04:8b:72:9e:7f:c4:d1:15:e7:e4:8d:a1:89:6d:5f:
+    e1:19:b1:0d:cd:de:f6:2c:b3:07:95:40:74:b4:23:
+    36:e5:28:36:de:61:da:94:1f:8d:37:ea:68:ac:81:
+    06:fa:be:19:07:06:79:af:60:08:53:71:20:f7:07:
+    93:b8:ea:9c:c0:e6:e7:b7:b4:c9:a5:c7:42:1c:60:
+    f2:44:51:ba:1e:93:3d:b1:a2:ee:16:c7:95:59:f2:
+    1b:3d:1b:83:05:85:0a:a4:2a:fb:b1:3f:1f:4d:5b:
+    9f:48:35:f9:d8:7d:fc:eb:16:2d:0e:f4:a7:fd:c4:
+    cb:a1:74:3c:d1:c8:7b:b4:96:7d:a1:6c:c8:76:4b:
+    65:69:df:8e:e5:bd:cb:ff:e9:a4:e0:57:48:e6:fd:
+    f2:25:af:9e:4e:eb:77:73:b6:2e:8f:85:f9:b5:6b:
+    54:89:45:55:18:44:fb:d8:98:06:a4:ac:36:9b:ed:
+    2d:25:61:00:f6:88:a6:ad:5e:0a:70:98:26:dc:44:
+    49:e9:1e:23:c5:50:6e:64:23:61:ef:5a:31:37:12:
+    f7:9b:c4:b3:18:68:61:ca:85:a4:ba:b1:7e:7f:94:
+    3d:1b:8a:33:3a:a3:ae:7c:e1:6b:44:0d:60:18:f9:
+    e0:4d:af:57:25:c7:f1:a9:3f:ad:1a:5a:27:b6:78:
+    95:bd:24:9a:a9:16:85:de:20:af:32:c8:b7:e2:68:
+    c7:f9:68:77:d0:c8:50:01:13:5a:4f:0a:8f:1b:82:
+    64:fa:6e:be:5a:34:9d:8a:ec:ad:1a:16:29:9c:cf:
+    2f:d9:c7:b8:5b:ac:e2:ce:d3:aa:12:76:ba:61:ee:
+    78:ed:7e:5c:a5:b6:7c:dd:45:8a:93:54:03:0e:6a:
+    bb:ba:bf:56:a0:a2:31:6f:ec:9d:ba:83:b5:1d:42:
+    fd:31:67:f1:e0:f9:08:55:d5:c6:65:09:b2:10:26:
+    5d:c1:e5:4e:c4:4b:43:ba:7c:f9:ae:f1:18:b4:4d:
+    80:91:2c:e7:51:66:a6:65:1e:11:6c:eb:e4:92:29:
+    a7:06:2c:09:93:1f:71:ab:d2:29:3f:76:f7:ef:c3:
+    21:5b:a9:78:00:03:7e:58:e4:70:bd:bb:b4:3c:1b:
+    04:39:ea:f7:9c:54:d9:3b:44:aa:c9:ef:e9:fb:e1:
+    51:87:4c:fb:2a:64:cb:ee:28:cc:4c:0f:e7:77:5e:
+    5d:87:0f:1c:02:e5:b2:e3:c5:00:4c:99:5f:24:c9:
+    b7:79:cb:75:3a:27:7d:0e:71:fd:42:5e:b6:bc:2c:
+    a5:6c:e1:29:db:51:f7:07:40:f3:1e:63:97:6b:50:
+    c7:31:2e:97:97:d7:8c:5b:1a:c2:4a:5f:a3:47:cc:
+    91:6e:0a:83:f5:c3:b6:75:cd:30:b8:1e:3f:a1:0b:
+    93:44:4e:07:39:75:71:cc:e9:8b:28:da:51:db:90:
+    56:bc:72:8c:5b:0b:11:81:e2:fb:d3:87:b4:c7:9a:
+    b1:a5:fe:fe:ce:37:16:7a:f7:72:dd:ad:14:eb:4c:
+    39:82:da:5a:59:d0:e9:eb:17:3e:c6:31:50:91:17:
+    00:27:a3:ab:5e:f6:aa:12:9c:b8:58:57:27:b9:35:
+    8a:28:50:1d:71:3a:72:f3:f1:db:31:71:42:86:f9:
+    b6:40:80:13:af:06:04:5d:75:59:2f:c0:b7:dd:47:
+    c7:3e:d9:c7:5b:11:e9:d7:c6:9f:7c:ad:fc:32:80:
+    a9:06:2c:52:73:c4:3b:e1:c3:4f:87:44:88:64:ce:
+    a7:b5:c9:7d:6d:32:f5:9b:d5:f2:53:84:65:3b:b5:
+    c4:fa:a4:5b:ea:8b:89:40:28:43:e6:45:b6:b9:26:
+    9e:2b:d9:88:dd:ac:b0:33:32:8f:fb:06:04:50:f7:
+    df:08:00:53:e6:96:9b:25:1e:87:5e:ce:c3:2c:fc:
+    59:28:40:d6:9a:b6:9a:75:e0:6b:37:9c:53:5d:95:
+    26:6b:08:2f:4f:09:c9:31:62:b3:3b:0d:9f:73:07:
+    a4:ea:aa:52:10:44:37:fe:d6:6f:8e:e3:ea:bb:d4:
+    5d:67:b2:5a:81:33:f4:96:46:8b:52:ba:ff:db:fa:
+    d9:3e:ef:1a:98:18:b5:e4:2e:c7:22:78:8a:3d:8d:
+    35:29:fc:77:7d:2b:a5:70:80:1d:fa:e0:1e:c8:83:
+    02:83:7c:1f:b9:e0:35:57:27:64:5e:e1:04:6c:3f:
+    91:5f:6a:e8:2d:ad:4f:b6:b0:35:6a:46:51:8f:fc:
+    83:41:55:c3:b4:fe:6d:af:a6:cc:8a:5c:cf:53:c7:
+    3a:08:49:d8:d4:4f:7d:cf:72:75:4e:70:e1:b7:df:
+    b4:47:bb:4e:f4:9d:1a:71:8f:61:71:bb:ce:20:09:
+    50:e0:ce:92:61:06:b1:51:a3:e8:71:d5:ce:49:73:
+    1b:d6:65:0a:9b:0c:a9:72:da:1c:5f:13:6d:44:82:
+    0e:a6:38:3c:08:f3:b3:84:cf:23:38:e7:89:c5:13:
+    f6:18:cc:56:94:a6:f0:ce:e1:04:51:1e:1e:d7:c5:
+    f2:3a:1e:bf:d8:a0:db:84:24:55:32:40:15:6d:bf:
+    62:28:31:b0:c6:43:d1:c5:51:b6:f3:f7:a9:8d:29:
+    b8:5c:2d:e0:5a:65:fa:61:5e:ee:16:49:5b:d9:07:
+    37:67:21:15:b5:3e:91:c5:d9:00:28:cf:3f:1a:93:
+    95:3a:15:3d:e5:3b:44:08:4e:9c:cf:f6:b7:36:69:
+    39:26:da:ef:eb:b2:d7:7a:a5:ad:68:9b:92:f3:16:
+    86:66:9d:f1:6d:17:15:cc:58:f7:a2:cf:b7:2d:d1:
+    a5:1e:92:f8:25:99:3a:74:02:2b:e7:e9:eb:60:54:
+    65:44:57:09:4d:14:92:8f:20:21:5e:7b:22:2a:c5:
+    6b:51:ad:be:c8:d8:bd:b6:98:39:79:a7:e3:a2:1b:
+    44:b5:d1:51:8c:a9:7d:0b:51:95:f5:1e:d6:a2:43:
+    50:c8:97:47:e1:ed:ea:51:b4:48:e3:e9:14:70:54:
+    ce:92:78:73:c9:0d:b3:94:d8:68:88:e0:7d:ff:17:
+    75:93:d6:f7:9e:15:23:02:20:4a:eb:03:be:23:86:
+    af:3e:24:07:8b:d0:28:b1:68:9f:5e:14:7c:9f:45:
+    2c:8c:eb:02:ec:59:cc:9d:b6:3a:03:57:6c:ee:af:
+    e9:82:39:02:38:97:da:02:36:63:0a:53:c0:de:7f:
+    43:5a:19:86:97:92:fa:b3:6e:7b:9e:63:57:60:f0:
+    90:69:e6:43:2e:70:00:35:ac:2a:02:87:9f:ff:0a:
+    1e:1b:ec:52:20:47:19:3d:94:eb:5d:f1:ef:d5:3e:
+    ea:11:44:ca:78:94:08:52:f5:ec:97:27:90:4b:36:
+    6e:de:4f:5e:2d:33:1f:ad:5f:c2:82:ea:2c:47:e9:
+    23:14:27:71:c3:dd:75:a8:73:57:48:7d:ef:99:e5:
+    f1:8e:9d:9e:d6:23:c1:75:d0:28:88:c5:1f:82:c0:
+    7a:80:d5:47:16:b3:c3:c2:bd:be:2e:9f:0a:9b:ba:
+    ae:be:b4:d5:29:36:87:64:06:f5:c0:0e:8e:4b:bd:
+    0a:5e:c0:57:97:e6:20:7c:5a:b6:c8:8f:1a:68:84:
+    21:bd:05:a1:14:f4:d7:de:2a:c2:41:fa:0e:8b:ed:
+    ff:47:f7:62:dd:cb:ea:a9:10:04:f8:d3:1e:85:09:
+    5c:81:05:49:94:ad:38:26:e3:44:ba:96:04:08:10:
+    fc:0b:2a:d1:de:48:cf:ad:e0:02:c6:2e:5a:49:a0:
+    73:1a:b3:83:44:bc:16:36:df:16:bf:60:7d:56:85:
+    5e:56:d6:84:00:3c:71:8e:4b:ad:9e:5a:09:99:79:
+    fc:dd:ee:b1:c4:a7:77:6c:d3:7a:34:17:cb:0e:18:
+    4e:29:ef:9b:c0:e8:74:75:ba:66:3b:e0:9e:00:ab:
+    56:2e:b7:c0:f7:16:5f:96:9a:9b:42:41:41:98:cc:
+    f1:bf:f2:a2:c8:d6:89:a4:14:ec:e7:66:29:27:66:
+    56:89:e9:4d:b9:61:eb:ae:c5:61:5c:bc:1a:78:95:
+    c6:85:1a:c9:61:43:2f:f1:11:8d:46:07:d3:2e:f9:
+    dc:73:2d:51:33:3b:e4:b4:d0:e3:0d:de:a7:84:ec:
+    a8:be:47:e7:41:be:9c:19:63:1d:c4:70:a5:2e:f4:
+    dc:13:a4:f3:63:3f:d4:34:d7:87:c1:70:97:7b:41:
+    7d:f5:98:e1:d0:dd:e5:06:bb:71:d6:f0:bc:17:ec:
+    70:e3:b0:3c:dc:19:65:cb:36:99:3f:63:3b:04:72:
+    e5:0d:09:23:ac:6c:66:fd:f1:d3:e6:45:9c:c1:21:
+    f0:f5:f9:4d:09:e9:db:cf:5d:69:0e:23:23:38:38:
+    a0:ba:cb:7c:63:8d:1b:26:50:a4:30:8c:d1:71:b6:
+    85:51:26:d1:da:67:2a:6e:d8:5a:8d:78:c2:86:fb:
+    56:f4:ab:3d:21:49:75:28:04:5c:63:26:2c:8a:42:
+    af:2f:98:02:c5:3b:7b:b8:be:28:e7:8f:e0:b5:ce:
+    45:fb:b7:a1:af:1a:3b:28:a8:d9:4b:78:90:e3:c8:
+    82:e3:9b:c9:8e:9f:0a:d7:60:25:bf:0d:d2:f0:02:
+    98:e7:14:1a:22:6b:3d:7c:ee:41:4f:60:4d:1e:0b:
+    a5:4d:11:d5:fe:58:bc:ce:a6:ad:77:ad:2e:8c:1c:
+    aa:cf:32:45:90:14:b7:b9:10:01:b1:ef:a8:ad:17:
+    2a:52:3f:b8:e3:65:b5:77:12:1b:f9:fd:88:a2:c6:
+    0c:21:e8:21:d7:b6:ac:b4:7a:5a:99:5e:40:ca:ce:
+    d5:c2:23:b8:fe:6d:e5:e1:8e:9d:2e:58:93:ae:fe:
+    bb:7a:ae:7f:f1:a1:46:26:0e:2f:11:0e:93:95:28:
+    21:3a:00:25:a3:8e:c7:9a:ab:c8:61:b2:5e:bc:50:
+    9a:46:74:c1:32:aa:ac:b7:e0:14:6f:14:ef:d1:1c:
+    fc:af:4c:aa:4f:77:5a:71:6c:e3:25:e0:a4:35:a4:
+    d3:49:d7:20:bc:f1:37:45:0a:fc:45:04:6f:c1:a1:
+    f8:3a:9d:32:97:77:a7:08:4e:4a:ad:ae:71:22:ce:
+    97:00:59:30:52:8e:b3:c7:f7:f1:12:9b:37:28:87:
+    a3:71:15:5a:3b:a2:01:a2:5c:bf:1d:cb:64:e7:cd:
+    ee:09:2c:31:41:fb:55:50:fe:3d:0d:d8:2e:87:0e:
+    57:8b:2b:46:50:08:18:11:3b:8f:65:69:77:3c:67:
+    73:85:b6:9a:42:b7:7d:cb:a7:ac:ff:d9:5f:d4:45:
+    2e:23:aa:a1:d3:7e:1d:a2:15:1e:a6:58:d4:0a:35:
+    96:b2:7a:c9:f8:12:9d:c6:cf:06:43:77:26:24:b5:
+    9f:4f:46:12:30:df:47:1c:a2:60:87:c3:94:2d:5c:
+    66:87:df:60:82:83:59:35:a3:f8:7c:b7:62:b0:c3:
+    b1:d0:dd:a4:a6:53:39:65:be:f1:b7:b8:29:2e:25:
+    4c:01:4d:09:0f:ed:85:7c:44:c1:83:9c:69:4c:0a:
+    64:e3:fa:d9:0a:11:f5:34:72:2b:6e:e1:57:4f:2e:
+    14:9d:55:d7:44:de:48:87:02:4e:08:51:14:31:c0:
+    62:75:0e:16:c7:4a:b9:f3:24:2f:2d:b3:ff:b1:2a:
+    8d:61:07:fa:a2:29:d6:f6:37:3b:07:f3:6d:39:32:
+    b3:bd:b0:4c:19:dd:64:ea:dd:7f:93:c3:c5:64:c3:
+    58:a1:c8:1d:cf:1c:9c:31:e5:b0:65:68:f9:75:44:
+    c1:7d:c1:56:98:c5:cb:38:98:3a:9a:fc:42:78:3f:
+    aa:77:3a:52:c9:d8:26:06:90:be:9e:31:56:aa:5b:
+    c1:50:9d:ea:3f:69:58:76:95:cd:6f:f1:72:ba:83:
+    e6:a6:d8:a7:d6:bb:eb:bb:cd:a3:67:27:31:98:3f:
+    89:bc:58:31:dc:37:c3:f3:c5:c5:6f:ac:c6:97:f3:
+    cb:20:bd:5d:ba:db:d7:02:e5:48:44:ac:2f:62:69:
+    01:fe:15:9d:b9:3d:fd:47:73:d8:fe:73:56:2b:84:
+    6c:1f:c8:56:d1:80:27:62:84:0e:bc:72:d7:98:8b:
+    de:75:cb:ca:70:d3:19:d3:2c:e0:cc:02:53:bb:2a:
+    d4:55:72:3e:e0:c7:f4:73:6c:e6:e6:66:5c:5a:ca:
+    32:a4:81:c5:38:39:bc:25:91:67:b0:13:d0:42:33:
+    95:ee:b9:aa:ae:e3:20:61:49:a7:d5:50:d6:7f:c5:
+    fd:fe:4a:8a:5c:35:d2:51:0b:66:43:79:ab:8f:72:
+    85:5a:2a:f4:7a:bc:e2:a6:32:04:8e:af:89:e5:cb:
+    4a:88:de:bc:53:a5:95:10:3a:cc:e4:f1:cf:f1:8a:
+    cf:f0:7a:fe:1e:b5:71:6a:a1:e4:0b:63:13:4c:3a:
+    3a:e9:57:9f:a8:7f:51:5b:e0:93:c2:d2:9d:b6:d6:
+    b6:5c:93:66:1e:00:63:6b:59:27:04:d0:93:cc:67:
+    16:c2:34:2e:b1:85:3d:48:c8:5c:63:ac:8a:28:54:
+    46:2c:7b:77:e7:e3:bd:1e:ac:5b:ca:28:ff:aa:00:
+    b5:d3:49:f8:a5:47:ad:87:5b:96:a8:c2:b2:91:0c:
+    93:01:30:9a:3f:91:38:a5:69:31:11:f5:5b:3c:00:
+    9c:a9:47:c3:9d:fc:82:d9:8e:b1:ca:a4:a9:cb:e8:
+    85:f7:86:fa:86:e5:5b:e0:62:22:2f:8b:a9:0a:97:
+    40:73:32:6b:31:21:2a:ec:e0:a3:4a:60
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/sig-44.dat b/test/recipes/15-test_ml_dsa_codecs_data/sig-44.dat
new file mode 100644 (file)
index 0000000..564f82a
Binary files /dev/null and b/test/recipes/15-test_ml_dsa_codecs_data/sig-44.dat differ
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/sig-65.dat b/test/recipes/15-test_ml_dsa_codecs_data/sig-65.dat
new file mode 100644 (file)
index 0000000..b99f4ca
Binary files /dev/null and b/test/recipes/15-test_ml_dsa_codecs_data/sig-65.dat differ
diff --git a/test/recipes/15-test_ml_dsa_codecs_data/sig-87.dat b/test/recipes/15-test_ml_dsa_codecs_data/sig-87.dat
new file mode 100644 (file)
index 0000000..9a972b9
Binary files /dev/null and b/test/recipes/15-test_ml_dsa_codecs_data/sig-87.dat differ
index c2eb63ee2b7f317dd12f4d64016dccdc960191aa..4d74b69f01f6de91b2636d566f596e7f90a1e25f 100644 (file)
@@ -433,6 +433,10 @@ my %params = (
 
 # ML_DSA Key generation parameter
     'PKEY_PARAM_ML_DSA_SEED' =>             "seed",
+    'PKEY_PARAM_ML_DSA_RETAIN_SEED' =>      "ml-dsa.retain_seed",
+    'PKEY_PARAM_ML_DSA_PREFER_SEED' =>      "ml-dsa.prefer_seed",
+    'PKEY_PARAM_ML_DSA_INPUT_FORMATS' =>    "ml-dsa.input_formats",
+    'PKEY_PARAM_ML_DSA_OUTPUT_FORMATS' =>   "ml-dsa.output_formats",
 
 # Key Exchange parameters
     'EXCHANGE_PARAM_PAD' =>                   "pad",# uint