/*
 * Copyright (c) 2025-2026 Roumen Petrov.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "includes.h"

#define SSHKEY_INTERNAL
#include "sshxkey.h"

static const char *pkey_mldsa44_typename[] = { "ML-DSA-44", NULL };
static const char *pkey_mldsa65_typename[] = { "ML-DSA-65", NULL };
static const char *pkey_mldsa87_typename[] = { "ML-DSA-87", NULL };

#ifdef ENABLE_KEY_PROVIDERS

#include "openssl/core_names.h"
#include "openssl/err.h"

#include "log.h"
#include "ssherr.h"

/* see sshkey-crypto.c */
extern OSSL_LIB_CTX *pkixssh_libctx;
extern char *pkixssh_propq;


static const char*
ssh_mldsa_algorithm(int subtype) {
	switch (subtype) {
	case 44: return pkey_mldsa44_typename[0];
	case 65: return pkey_mldsa65_typename[0];
	case 87: return pkey_mldsa87_typename[0];
	}
	return NULL;
}

/*
Parameter	Security Level	Public Key	Private Key	Signature Size
				(bytes)		(bytes)		(bytes)
ML-DSA-44	2 (128-bit)	1,312		2,560		2,420
ML-DSA-65	3 (192-bit)	1,952		4,032		3,309
ML-DSA-87	5 (256-bit)	2,592		4,896		4,627
*/

static inline size_t
ssh_mldsa_pub_len(int subtype) {
	switch (subtype) {
	case 44: return 1312;
	case 65: return 1952;
	case 87: return 2592;
	}
	return 0;
}

static inline size_t
ssh_mldsa_priv_len(int subtype) {
	switch (subtype) {
	case 44: return 2560;
	case 65: return 4032;
	case 87: return 4896;
	}
	return 0;
}

static inline size_t
ssh_mldsa_sig_len(int subtype) {
	switch (subtype) {
	case 44: return 2420;
	case 65: return 3309;
	case 87: return 4627;
	}
	return 0;
}


static int
ssh_pkey_get_raw_pub(EVP_PKEY *pk, size_t pub_len, unsigned char **pubp) {
	u_char *pub;

	*pubp = NULL;

	pub = malloc(pub_len);
	if (pub == NULL) return SSH_ERR_ALLOC_FAIL;

{	size_t len = pub_len;
	if (EVP_PKEY_get_raw_public_key(pk, pub, &len) != 1 &&
	    len != pub_len) {
		free(pub);
		return SSH_ERR_LIBCRYPTO_ERROR;
	}
}

	*pubp = pub;
	return 0;
}

static int
sshkey_get_mldsa_raw_pub(const struct sshkey *key, unsigned char **pubp, size_t *lenp) {
	size_t pub_len;
	int r;

	*pubp = NULL;
	*lenp = 0;

	pub_len = ssh_mldsa_pub_len(key->ecdsa_nid);
	if (pub_len == 0)
		return SSH_ERR_KEY_BITS_MISMATCH;

	r = ssh_pkey_get_raw_pub(key->pk, pub_len, pubp);
	if (r != 0) return r;

	*lenp = pub_len;
	return 0;
}


static int
sshbuf_read_pub_mldsa(struct sshbuf *buf, struct sshkey *key) {
	EVP_PKEY *pk = NULL;
	size_t pub_len;
	u_char *pub;
	int r;

	r = sshbuf_get_string(buf, &pub, &pub_len);
	if (r != 0) return r;

{	size_t need = ssh_mldsa_pub_len(key->ecdsa_nid);
	/* NOTE need > 0 */
	if (pub_len != need) {
		r = SSH_ERR_INVALID_FORMAT;
		goto err;
	}
}

{	const char *alg = ssh_mldsa_algorithm(key->ecdsa_nid);
	if (alg == NULL) {
		r = SSH_ERR_INVALID_ARGUMENT;
		goto err;
	}
	pk = EVP_PKEY_new_raw_public_key_ex(pkixssh_libctx,
	    alg, pkixssh_propq, pub, pub_len);
}
	if (pk == NULL) {
		r = SSH_ERR_INVALID_FORMAT;
		goto err;
	}

	/* success */
	key->pk = pk;
	pk = NULL; /* transferred */

err:
	free(pub);
	EVP_PKEY_free(pk);
	return r;
}

static int
sshbuf_write_pub_mldsa(struct sshbuf *buf, const struct sshkey *key) {
	size_t pub_len;
	u_char *pub;
	int r;

	r = sshkey_get_mldsa_raw_pub(key, &pub, &pub_len);
	if (r != 0) return r;

	r = sshbuf_put_string(buf, pub, pub_len);

	free(pub);
	return r;
}

static int
sshbuf_read_mldsa(struct sshbuf *buf, int with_public, struct sshkey *key) {
	size_t pub_len, priv_len;
	u_char *pub, *priv = NULL;
	int r;

	if (with_public) {
		if (key->pk != NULL)
			return SSH_ERR_INVALID_ARGUMENT;
		r = sshbuf_get_string(buf, &pub, &pub_len);
		if (r != 0) return r;
	{	size_t need = ssh_mldsa_pub_len(key->ecdsa_nid);
		/* NOTE need > 0 */
		if (pub_len != need) {
			r = SSH_ERR_INVALID_FORMAT;
			goto err;
		}
	}
	} else {
		/* transfer as private versus public match verification
		 * is not reliable and usually fail
		 */
		if (key->pk == NULL)
			return SSH_ERR_INVALID_ARGUMENT;
		r = sshkey_get_mldsa_raw_pub(key, &pub, &pub_len);
		if (r != 0) return r;
		EVP_PKEY_free(key->pk);
		key->pk = NULL;
	}

	r = sshbuf_get_string(buf, &priv, &priv_len);
	if (r != 0) return r;

{	size_t need = ssh_mldsa_priv_len(key->ecdsa_nid);
	/*NOTE need > 0*/
	if (priv_len != need) {
		r = SSH_ERR_INVALID_FORMAT;
		goto err;
	}
}

{	const char *alg = ssh_mldsa_algorithm(key->ecdsa_nid);
	if (alg == NULL) {
		r = SSH_ERR_INVALID_ARGUMENT;
		goto err;
	}
{	OSSL_PARAM params[] = {
		OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, priv, priv_len),
		OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, pub, pub_len),
		OSSL_PARAM_END
	};
	int selection = OSSL_KEYMGMT_SELECT_KEYPAIR;
	r = ssh_pkey_fromdata(alg, selection, params, &key->pk);
}
}

err:
	free(pub);
	if (priv != NULL)
		freezero(priv, priv_len);
	return r;
}

static int
sshbuf_write_mldsa(struct sshbuf *buf, const struct sshkey *key, int with_public) {
	size_t priv_len;
	u_char *priv = NULL;
	int r;

	if (!with_public) goto skip_public;

	r = sshbuf_write_pub_mldsa(buf, key);
	if (r != 0) return r;

skip_public:
	priv_len = ssh_mldsa_priv_len(key->ecdsa_nid);
	if (priv_len == 0)
		return SSH_ERR_KEY_BITS_MISMATCH;

	priv = malloc(priv_len);
	if (priv == NULL) {
		r = SSH_ERR_ALLOC_FAIL;
		goto err;
	}

{	size_t len = priv_len;
	if ((EVP_PKEY_get_raw_private_key(key->pk, priv, &len) != 1) &&
	    (len != priv_len)) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}
}

	r = sshbuf_put_string(buf, priv, priv_len);

err:
	freezero(priv, priv_len);
	return r;
}


/* pkey implementation */

static int
ssh_mldsa_sshkey_from_pkey(EVP_PKEY *pk, struct sshkey **keyp) {
	const char *type_name;
	int r;

	type_name = EVP_PKEY_get0_type_name(pk);
	if (type_name == NULL) {
		error_crypto("EVP_PKEY_get0_type_name");
		return SSH_ERR_LIBCRYPTO_ERROR;
	}

	r = sshkey_new_typename(type_name, keyp);
	if (r != 0) {
		error_fr(r, "creation fail for type name: '%s'", type_name);
		return r;
	}

	(*keyp)->pk = pk;
	return 0;
}

static int
ssh_mldsa_validate_public(EVP_PKEY *pk) {
	UNUSED(pk);
	/* TODO? */
	return 0;
}

static const struct ssh_pkey_impl_funcs
ssh_pkey_mldsa_funcs = {
	ssh_mldsa_sshkey_from_pkey,
	ssh_mldsa_validate_public
};

#define SSH_PKEY_MLDSA_FUNCS	&ssh_pkey_mldsa_funcs


/* key implementation */

static u_int
ssh_mldsa_size(const struct sshkey *key)
{
	return (key->pk != NULL) ? EVP_PKEY_bits(key->pk) : 0;
}

static void
ssh_mldsa_cleanup(struct sshkey *k)
{
	sshkey_clear_pkey(k);
}

static int
ssh_mldsa_equal(const struct sshkey *a, const struct sshkey *b)
{
	return sshkey_equal_public_pkey(a, b);
}

static int
ssh_mldsa_serialize_public(const struct sshkey_impl *impl,
    const struct sshkey *key, struct sshbuf *buf)
{
	UNUSED(impl);
	return sshbuf_write_pub_mldsa(buf, key);
}

static int
ssh_mldsa_serialize_private(const struct sshkey_impl *impl,
    const struct sshkey *key, struct sshbuf *buf)
{
	int with_public = !impl->cert;

	return sshbuf_write_mldsa(buf, key, with_public);
}

static int
ssh_mldsa_deserialize_public(const struct sshkey_impl *impl,
    struct sshbuf *buf, struct sshkey *key)
{
	int r;

	key->ecdsa_nid = impl->id.subtype;

	r = sshbuf_read_pub_mldsa(buf, key);
	if (r != 0) return r;

	SSHKEY_DUMP(key);

	return ssh_pkey_public_check(key->pk);
}

static int
ssh_mldsa_deserialize_private(const struct sshkey_impl *impl,
    struct sshbuf *buf, struct sshkey *key)
{
	int r, with_public = !impl->cert;

	if (with_public)
		key->ecdsa_nid = impl->id.subtype;
	/*else already set*/

	r = sshbuf_read_mldsa(buf, with_public, key);
	if (r != 0) return r;

	SSHKEY_DUMP(key);

	return ssh_pkey_check(key->pk);
}

static int
ssh_mldsa_generate(struct sshkey *key, int bits) {
	EVP_PKEY_CTX *genctx = NULL;
	EVP_PKEY *pk = NULL;
	int r;

{	const char *alg = ssh_mldsa_algorithm(bits);
	if (alg == NULL) return SSH_ERR_KEY_LENGTH;

	genctx = EVP_PKEY_CTX_new_from_name(pkixssh_libctx,
	    alg, pkixssh_propq);
}
	if (genctx == NULL) {
		error_crypto("EVP_PKEY_CTX_new_from_name");
		return SSH_ERR_LIBCRYPTO_ERROR;
	}

	if (EVP_PKEY_keygen_init(genctx) <= 0) {
		error_crypto("EVP_PKEY_keygen_init");
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

	if (EVP_PKEY_generate(genctx, &pk) <= 0) {
		error_crypto("EVP_PKEY_generate");
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

	key->pk = pk;
	key->ecdsa_nid = bits;
	r = 0;

err:
	EVP_PKEY_CTX_free(genctx);
	return r;
}

static u_int
ssh_mldsa_default_bits(const char *name) {
	const struct sshkey_impl *impl;

	impl = (name != NULL)
		? sshkey_impl_from_name(name)
		: NULL;
	/* NOTE: sinc with keygen manual page */
	return (impl != NULL)
		? impl->id.subtype
		: 65;
}

static void
ssh_mldsa_valid_bits(int bits) {
	if (ssh_mldsa_algorithm(bits) != NULL)
		return;

	/* NOTE assume availability on all ML-DSA algorithms */
	fatal("Invalid MLDSA key length: valid lengths are "
	    "44, 65 or 87");
}

static void
ssh_mldsa_move_public(struct sshkey *from, struct sshkey *to) {
	sshkey_move_pkey(from, to);
}

static int
ssh_mldsa_copy_public(const struct sshkey *from, struct sshkey *to) {
	return sshkey_copy_public_pkey(from, to);
}

static int
ssh_mldsa_sign(const ssh_sign_ctx *ctx, u_char **sigp, size_t *lenp,
    const u_char *data, size_t datalen)
{
#ifdef HAVE_EVP_PKEY_SIGN_MESSAGE_INIT /* OpenSSL >= 3.4 */
	const struct sshkey *key = ctx->key;
	EVP_SIGNATURE *sig_alg;
	EVP_PKEY_CTX *sctx = NULL;
	unsigned char *sig = NULL;
	size_t slen;
	int r;

{	const char *alg = ssh_mldsa_algorithm(key->ecdsa_nid);
	if (alg == NULL) return SSH_ERR_SIGN_ALG_UNSUPPORTED;

	ERR_set_mark();
	sig_alg = EVP_SIGNATURE_fetch(pkixssh_libctx, alg, pkixssh_propq);
	/* clear needless errors for unsupported algorithms */
	ERR_pop_to_mark();
	if (sig_alg == NULL) return SSH_ERR_SIGN_ALG_UNSUPPORTED;
}

	sctx = EVP_PKEY_CTX_new_from_pkey(pkixssh_libctx,
	    key->pk, pkixssh_propq);
	if (sctx == NULL) {
		error_crypto("EVP_PKEY_CTX_new_from_pkey");
		return SSH_ERR_LIBCRYPTO_ERROR;
	}
	/* note by default "context-string" is an empty string. */
	if (EVP_PKEY_sign_message_init(sctx, sig_alg, NULL) != 1) {
		error_crypto("EVP_PKEY_sign_message_init");
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

{	size_t len = ssh_mldsa_sig_len(key->ecdsa_nid);
	slen = len;
	sig = OPENSSL_zalloc(slen);
	if (sig == NULL) {
		r = SSH_ERR_ALLOC_FAIL;
		goto err;
	}
	if (EVP_PKEY_sign(sctx, sig, &slen, data, datalen) != 1) {
		error_crypto("EVP_PKEY_sign_message_init");
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}
	if (len != slen) {
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}
}

	r = ssh_encode_signature(sigp, lenp,
	    sshkey_ssh_name_plain(key), sig, slen);

err:
	EVP_SIGNATURE_free(sig_alg);
	EVP_PKEY_CTX_free(sctx);
	OPENSSL_free(sig);
	return r;
#else /*ndef HAVE_EVP_PKEY_SIGN_MESSAGE_INIT*/
	UNUSED(ctx);
	UNUSED(sigp); UNUSED(lenp);
	UNUSED(data); UNUSED(datalen);
	return SSH_ERR_FEATURE_UNSUPPORTED;
#endif
}

static int
ssh_mldsa_verify(const ssh_verify_ctx *ctx,
    const u_char *sig, size_t siglen,
    const u_char *data, size_t datalen)
{
#ifdef HAVE_EVP_PKEY_SIGN_MESSAGE_INIT /* OpenSSL >= 3.4 */
	const struct sshkey *key = ctx->key;
	char *sigtype = NULL;
	const u_char *sigblob;
	size_t lenblob;
	EVP_SIGNATURE *sig_alg = NULL;
	EVP_PKEY_CTX *sctx = NULL;
	int r;

{	/* fetch signature */
	struct sshbuf *b = sshbuf_from(sig, siglen);
	if (b == NULL)
		return SSH_ERR_ALLOC_FAIL;

	if (sshbuf_get_cstring(b, &sigtype, NULL) != 0 ||
	    sshbuf_get_string_direct(b, &sigblob, &lenblob) != 0) {
		sshbuf_free(b);
		r = SSH_ERR_INVALID_FORMAT;
		goto err;
	}
	if (sshbuf_len(b) != 0) {
		r = SSH_ERR_UNEXPECTED_TRAILING_DATA;
		goto err;
	}
	sshbuf_free(b);
}

	/* TODO use ssh-key implementation to find signature name.
	 * Note public key and signature format match for ML-DSA.
	 */
	if (strcmp(sshkey_ssh_name_plain(key), sigtype) != 0) {
		r = SSH_ERR_KEY_TYPE_MISMATCH;
		goto err;
	}

{	const char *alg = ssh_mldsa_algorithm(key->ecdsa_nid);
	if (alg == NULL) {
		/* unexpected error */
		return SSH_ERR_SIGN_ALG_UNSUPPORTED;
	}
{	size_t need = ssh_mldsa_sig_len(key->ecdsa_nid);
	if (lenblob != need) {
		r = SSH_ERR_SIGNATURE_INVALID;
		goto err;
	}
}
	ERR_set_mark();
	sig_alg = EVP_SIGNATURE_fetch(pkixssh_libctx, alg, pkixssh_propq);
	/* clear needless errors for unsupported algorithms */
	ERR_pop_to_mark();
}
	if (sig_alg == NULL) {
		/* fatal mismatch? */
		r = SSH_ERR_SIGN_ALG_UNSUPPORTED;
		goto err;
	}

	sctx = EVP_PKEY_CTX_new_from_pkey(pkixssh_libctx,
	    key->pk, pkixssh_propq);
	if (sctx == NULL) {
		error_crypto("EVP_PKEY_CTX_new_from_pkey");
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

	/* note by default "context-string" is an empty string. */
	if (EVP_PKEY_verify_message_init(sctx, sig_alg, NULL) != 1) {
		error_crypto("EVP_PKEY_verify_message_init");
		r = SSH_ERR_LIBCRYPTO_ERROR;
		goto err;
	}

{	int ret = EVP_PKEY_verify(sctx, sigblob, lenblob, data, datalen);
	if (ret != 1) {
		if (ret == 0)
			r = SSH_ERR_SIGNATURE_INVALID;
		else {
			error_crypto("EVP_PKEY_verify");
			r = SSH_ERR_LIBCRYPTO_ERROR;
		}
		goto err;
	}
}

	r = 0;

err:
	EVP_SIGNATURE_free(sig_alg);
	EVP_PKEY_CTX_free(sctx);
	free(sigtype);
	return r;
#else /*ndef HAVE_EVP_PKEY_SIGN_MESSAGE_INIT*/
	UNUSED(ctx);
	UNUSED(sig); UNUSED(siglen);
	UNUSED(data); UNUSED(datalen);
	return SSH_ERR_FEATURE_UNSUPPORTED;
#endif
}

static const struct sshkey_impl_funcs sshkey_mldsa_funcs = {
	/* .size = */		ssh_mldsa_size,
	/* .alloc =		NULL, */
	/* .cleanup = */	ssh_mldsa_cleanup,
	/* .equal = */		ssh_mldsa_equal,
	/* .serialize_public = */	ssh_mldsa_serialize_public,
	/* .deserialize_public = */	ssh_mldsa_deserialize_public,
	/* .serialize_private = */	ssh_mldsa_serialize_private,
	/* .deserialize_private = */	ssh_mldsa_deserialize_private,
	/* .generate = */	ssh_mldsa_generate,
	/* .default_bits = */	ssh_mldsa_default_bits,
	/* .valid_bits = */	ssh_mldsa_valid_bits,
	/* .move_public = */	ssh_mldsa_move_public,
	/* .copy_public = */	ssh_mldsa_copy_public,
	/* .sign = */		ssh_mldsa_sign,
	/* .verify = */		ssh_mldsa_verify
};

static inline int
sshkey_mldsa_enabled(const char *algorithm, int *flag) {
#ifdef HAVE_EVP_PKEY_SIGN_MESSAGE_INIT
	if (*flag >= 0) return *flag;
	*flag = ssh_keytype_provided(algorithm, NULL);
	return *flag;
#else
	/* TODO ML-DSA signature for OpenSSL < 3.4? */
	UNUSED(algorithm); UNUSED(flag);
	return 0;
#endif
}

static int sshkey_mldsa44_enabled(void) {
	static int flag = -1;
	return sshkey_mldsa_enabled(pkey_mldsa44_typename[0], &flag);
}

static int sshkey_mldsa65_enabled(void) {
	static int flag = -1;
	return sshkey_mldsa_enabled(pkey_mldsa65_typename[0], &flag);
}

static int sshkey_mldsa87_enabled(void) {
	static int flag = -1;
	return sshkey_mldsa_enabled(pkey_mldsa87_typename[0], &flag);
}
#define SSHKEY_MLDSA_FUNCS	&sshkey_mldsa_funcs

#else /*ndef ENABLE_KEY_PROVIDERS*/

static int sshkey_mldsa44_enabled(void) { return 0; }
static int sshkey_mldsa65_enabled(void) { return 0; }
static int sshkey_mldsa87_enabled(void) { return 0; }
#define SSHKEY_MLDSA_FUNCS	NULL
#define SSH_PKEY_MLDSA_FUNCS	NULL

#endif /*ndef ENABLE_KEY_PROVIDERS*/

const struct sshkey_impl sshkey_mldsa44_impl = {
	/* .id = */		{ KEY_MLDSA, 44,
				  EVP_PKEY_NONE, pkey_mldsa44_typename },
	/* .name = */		"mldsa-44",
	/* .shortname = */	"MLDSA",
	/* .sigalg = */		NULL,
	/* .cert = */		0,
	/* .sigonly = */	0,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_mldsa44_enabled,
	/* .funcs = */		SSHKEY_MLDSA_FUNCS
};

const struct sshkey_impl sshkey_mldsa65_impl = {
	/* .id = */		{ KEY_MLDSA, 65,
				  EVP_PKEY_NONE, pkey_mldsa65_typename },
	/* .name = */		"mldsa-65",
	/* .shortname = */	"MLDSA",
	/* .sigalg = */		NULL,
	/* .cert = */		0,
	/* .sigonly = */	0,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_mldsa65_enabled,
	/* .funcs = */		SSHKEY_MLDSA_FUNCS
};

const struct sshkey_impl sshkey_mldsa87_impl = {
	/* .id = */		{ KEY_MLDSA, 87,
				  EVP_PKEY_NONE, pkey_mldsa87_typename },
	/* .name = */		"mldsa-87",
	/* .shortname = */	"MLDSA",
	/* .sigalg = */		NULL,
	/* .cert = */		0,
	/* .sigonly = */	0,
	/* .keybits = */	0,
	/* .enabled = */	sshkey_mldsa87_enabled,
	/* .funcs = */		SSHKEY_MLDSA_FUNCS
};

const struct ssh_pkey_impl ssh_pkey_mldsa44_impl = {
	pkey_mldsa44_typename,
	EVP_PKEY_NONE,
	SSH_PKEY_MLDSA_FUNCS
};

const struct ssh_pkey_impl ssh_pkey_mldsa65_impl = {
	pkey_mldsa65_typename,
	EVP_PKEY_NONE,
	SSH_PKEY_MLDSA_FUNCS
};

const struct ssh_pkey_impl ssh_pkey_mldsa87_impl = {
	pkey_mldsa87_typename,
	EVP_PKEY_NONE,
	SSH_PKEY_MLDSA_FUNCS
};
