1 /* $OpenBSD: test_mldsa_eddsa.c,v 1.1 2026/06/14 04:08:06 djm Exp $ */
2 /*
3 * Regress test for MLDSA44-Ed25519 composite signature
4 *
5 * Placed in the public domain
6 */
7
8 #include "includes.h"
9
10 #ifdef USE_MLDSA
11
12 #include <sys/types.h>
13 #include <stdio.h>
14 #include <stdint.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <ctype.h>
18 #include <fcntl.h>
19 #include <unistd.h>
20
21 #include "../test_helper/test_helper.h"
22 #include "crypto_api.h"
23 #include "ssherr.h"
24 #include "sshkey.h"
25 #include "sshbuf.h"
26 #include "log.h"
27
28 /* in tests.c */
29 struct sshbuf *load_text_file(const char *name);
30 char *get_json_string(struct sshbuf *content, const char *key, int consume);
31
32 void mldsa_eddsa_tests(void);
33
34 /*
35 * Simple JSON-ish parser for tvec.json.
36 * Extracts the base64 value for a given key and decodes it into a new sshbuf.
37 * Errors cause ASSERT_* failures.
38 */
39 static struct sshbuf *
get_json_b64(struct sshbuf * content,const char * key)40 get_json_b64(struct sshbuf *content, const char *key)
41 {
42 char *b64;
43 struct sshbuf *ret;
44
45 b64 = get_json_string(content, key, 0);
46 ASSERT_PTR_NE(ret = sshbuf_new(), NULL);
47 ASSERT_INT_EQ(sshbuf_b64tod(ret, b64), 0);
48 free(b64);
49 return ret;
50 }
51
52 static void
onerror(void * fuzz)53 onerror(void *fuzz)
54 {
55 fprintf(stderr, "Failed during fuzz:\n");
56 fuzz_dump((struct fuzz *)fuzz);
57 }
58
59 static void
sig_fuzz(const uint8_t * m,size_t m_len,const uint8_t * ctx,size_t ctx_len,const uint8_t * pk,const uint8_t * s)60 sig_fuzz(const uint8_t *m, size_t m_len, const uint8_t *ctx, size_t ctx_len,
61 const uint8_t *pk, const uint8_t *s)
62 {
63 struct fuzz *fuzz;
64 u_int fuzzers = FUZZ_1_BIT_FLIP | FUZZ_1_BYTE_FLIP;
65
66 if (test_is_fast())
67 fuzzers &= ~FUZZ_1_BIT_FLIP;
68 if (test_is_slow())
69 fuzzers |= FUZZ_2_BYTE_FLIP; /* FUZZ_2_BIT_FLIP much too slow */
70
71 ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(s, m, m_len,
72 ctx, ctx_len, pk), 0);
73 fuzz = fuzz_begin(fuzzers, s, MLDSA44_ED25519_SIG_SZ);
74 TEST_ONERROR(onerror, fuzz);
75 for(; !fuzz_done(fuzz); fuzz_next(fuzz)) {
76 /* Ensure 1-bit difference at least */
77 if (fuzz_matches_original(fuzz))
78 continue;
79 ASSERT_INT_NE(crypto_sign_mldsa44_ed25519_verify(fuzz_ptr(fuzz),
80 m, m_len, ctx, ctx_len, pk), 0);
81 }
82 fuzz_cleanup(fuzz);
83 }
84
85
86 void
mldsa_eddsa_tests(void)87 mldsa_eddsa_tests(void)
88 {
89 uint8_t pk[MLDSA44_ED25519_PK_SZ];
90 uint8_t sk[MLDSA44_ED25519_SK_SZ];
91 uint8_t sig[MLDSA44_ED25519_SIG_SZ];
92 struct sshbuf *b_m = NULL, *b_ctx = NULL, *b_pk = NULL;
93 struct sshbuf *b_sk = NULL, *b_s = NULL, *b_sWithContext = NULL;
94 struct sshbuf *json_buf = NULL;
95 const uint8_t *tvec_m;
96 size_t tvec_m_len;
97 const uint8_t *tvec_ctx;
98 size_t tvec_ctx_len;
99 const uint8_t *tvec_pk;
100 const uint8_t *tvec_sk;
101 const uint8_t *tvec_s;
102 const uint8_t *tvec_sWithContext;
103
104 TEST_START("MLDSA44-Ed25519-SHA512 load test vectors");
105 json_buf = load_text_file("draft-ietf-lamps-pq-composite-sigs.json");
106 b_m = get_json_b64(json_buf, "m");
107 b_ctx = get_json_b64(json_buf, "ctx");
108 b_pk = get_json_b64(json_buf, "pk");
109 b_sk = get_json_b64(json_buf, "sk");
110 b_s = get_json_b64(json_buf, "s");
111 b_sWithContext = get_json_b64(json_buf, "sWithContext");
112 ASSERT_INT_EQ(sshbuf_len(b_pk), MLDSA44_ED25519_PK_SZ);
113 ASSERT_INT_EQ(sshbuf_len(b_sk), MLDSA44_ED25519_SK_SZ);
114 ASSERT_INT_EQ(sshbuf_len(b_s), MLDSA44_ED25519_SIG_SZ);
115 ASSERT_INT_EQ(sshbuf_len(b_sWithContext), MLDSA44_ED25519_SIG_SZ);
116 TEST_DONE();
117
118 tvec_m = sshbuf_ptr(b_m);
119 tvec_m_len = sshbuf_len(b_m);
120 tvec_ctx = sshbuf_ptr(b_ctx);
121 tvec_ctx_len = sshbuf_len(b_ctx);
122 tvec_pk = sshbuf_ptr(b_pk);
123 tvec_sk = sshbuf_ptr(b_sk);
124 tvec_s = sshbuf_ptr(b_s);
125 tvec_sWithContext = sshbuf_ptr(b_sWithContext);
126
127 TEST_START("MLDSA44-Ed25519-SHA512 raw self-consistency");
128 ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_keygen(pk, sk), 0);
129 ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_sign(sig,
130 tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len, sk), 0);
131 ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(sig,
132 tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len, pk), 0);
133 TEST_DONE();
134
135 TEST_START("MLDSA44-Ed25519-SHA512 raw KAT key expansion");
136 uint8_t pk_expanded[MLDSA44_ED25519_PK_SZ];
137 uint8_t sk_expanded[MLDSA44_ED25519_SK_SZ];
138
139 /* Expansion: mldsa_seed (sk[0:32]) and ed25519_seed (sk[32:64]) */
140 ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_keygen_seeded(pk_expanded,
141 sk_expanded, tvec_sk, tvec_sk + 32), 0);
142 ASSERT_MEM_EQ(pk_expanded, tvec_pk, MLDSA44_ED25519_PK_SZ);
143 /* sk_expanded should also match tvec_sk */
144 ASSERT_MEM_EQ(sk_expanded, tvec_sk, MLDSA44_ED25519_SK_SZ);
145 TEST_DONE();
146
147 TEST_START("MLDSA44-Ed25519-SHA512 raw KAT verify (no context)");
148 ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(tvec_s, tvec_m,
149 tvec_m_len, NULL, 0, tvec_pk), 0);
150 TEST_DONE();
151
152 TEST_START("MLDSA44-Ed25519-SHA512 raw KAT verify (with context)");
153 ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(tvec_sWithContext,
154 tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len, tvec_pk), 0);
155 TEST_DONE();
156
157 TEST_START("MLDSA44-Ed25519-SHA512 raw round-trip (no context)");
158 ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_sign(sig, tvec_m, tvec_m_len,
159 NULL, 0, tvec_sk), 0);
160 ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(sig,
161 tvec_m, tvec_m_len, NULL, 0, tvec_pk), 0);
162 TEST_DONE();
163
164 TEST_START("MLDSA44-Ed25519-SHA512 raw round-trip (with context)");
165 ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_sign(sig,
166 tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len, tvec_sk), 0);
167 ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(sig,
168 tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len, tvec_pk), 0);
169 TEST_DONE();
170
171 TEST_START("MLDSA44-Ed25519-SHA512 raw KAT verify (no context)");
172 ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(tvec_s, tvec_m,
173 tvec_m_len, NULL, 0, tvec_pk), 0);
174 TEST_DONE();
175
176 TEST_START("MLDSA44-Ed25519-SHA512 fuzz raw verify (with context)");
177 ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(tvec_sWithContext,
178 tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len, tvec_pk), 0);
179 sig_fuzz(tvec_m, tvec_m_len, NULL, 0, tvec_pk, tvec_s);
180 TEST_DONE();
181
182 TEST_START("MLDSA44-Ed25519-SHA512 fuzz raw verify (with context)");
183 ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(tvec_sWithContext,
184 tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len, tvec_pk), 0);
185 sig_fuzz(tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len,
186 tvec_pk, tvec_sWithContext);
187 TEST_DONE();
188
189 sshbuf_free(json_buf);
190 sshbuf_free(b_m);
191 sshbuf_free(b_ctx);
192 sshbuf_free(b_pk);
193 sshbuf_free(b_sk);
194 sshbuf_free(b_s);
195 sshbuf_free(b_sWithContext);
196 }
197 #endif /* USE_MLDSA */
198