Skip to content

Commit aba09cb

Browse files
Add AArch64 NEON runtime fallback for ML-KEM and ML-DSA (#3353)
On aarch64, the ML-KEM/ML-DSA native primitives called the NEON assembly unconditionally, with no runtime path to the C reference implementation. This was fine since NEON assembly is a mandatory part of the base aarch64 architecture. However, FIPS requires us to include a way to test the C fallback implementation on relevant architectures. This adds a runtime NEON capability gate on every native entry point so that when NEON is unavailable (reachable via `OPENSSL_armcap=0`) execution falls back to the C reference path, mirroring the existing x86-64/AVX2 gate. This was implemented with the shadow-header pattern already used for x86-64 ML-DSA (`mldsa_x86_64_meta.h`). Since upstream mlkem-native does not have this, I've created two similar files `mlkem_aarch64_meta.h` / `mldsa_aarch64_meta.h` which mirror the vendored aarch64 meta.h. This allows no edits to the vendored mlkem/ or mldsa/ trees. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license.
1 parent 970583b commit aba09cb

6 files changed

Lines changed: 527 additions & 6 deletions

File tree

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0 OR ISC
3+
4+
// AWS-LC-owned replacement for the vendored mldsa/native/aarch64/meta.h.
5+
//
6+
// The vendored header calls the NEON assembly unconditionally. This copy is
7+
// identical except that every native entry point is guarded by a runtime NEON
8+
// check (mld_sys_check_capability(MLD_SYS_CAP_NEON)); when NEON is absent it
9+
// returns MLD_NATIVE_FUNC_FALLBACK and the frontend uses its C reference path
10+
// (e.g. under OPENSSL_armcap=0). Kept outside the vendored `mldsa/` tree, which
11+
// importer.sh clobbers on re-import, and selected via mldsa_native_backend.h.
12+
// Keep the toggles/identifier/asm signatures in sync with the vendored header.
13+
14+
// Reuse the vendored include-guard name on purpose: mldsa_native_bcm.c
15+
// #undef's it between per-level rounds to re-emit the per-level native
16+
// functions. A different guard name would break that re-emission.
17+
#ifndef MLD_NATIVE_AARCH64_META_H
18+
#define MLD_NATIVE_AARCH64_META_H
19+
20+
/* Set of primitives that this backend replaces */
21+
#define MLD_USE_NATIVE_NTT
22+
#define MLD_USE_NATIVE_INTT
23+
#define MLD_USE_NATIVE_REJ_UNIFORM
24+
#define MLD_USE_NATIVE_REJ_UNIFORM_ETA2
25+
#define MLD_USE_NATIVE_REJ_UNIFORM_ETA4
26+
#define MLD_USE_NATIVE_POLY_DECOMPOSE_32
27+
#define MLD_USE_NATIVE_POLY_DECOMPOSE_88
28+
#define MLD_USE_NATIVE_POLY_CADDQ
29+
#define MLD_USE_NATIVE_POLY_USE_HINT_32
30+
#define MLD_USE_NATIVE_POLY_USE_HINT_88
31+
#define MLD_USE_NATIVE_POLY_CHKNORM
32+
#define MLD_USE_NATIVE_POLYZ_UNPACK_17
33+
#define MLD_USE_NATIVE_POLYZ_UNPACK_19
34+
#define MLD_USE_NATIVE_POINTWISE_MONTGOMERY
35+
#define MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L4
36+
#define MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L5
37+
#define MLD_USE_NATIVE_POLYVECL_POINTWISE_ACC_MONTGOMERY_L7
38+
39+
/* Identifier for this backend so that source and assembly files
40+
* in the build can be appropriately guarded. */
41+
#define MLD_ARITH_BACKEND_AARCH64
42+
43+
44+
#if !defined(__ASSEMBLER__)
45+
#include "mldsa/native/api.h"
46+
#include "mldsa/native/aarch64/src/arith_native_aarch64.h"
47+
48+
MLD_MUST_CHECK_RETURN_VALUE
49+
static MLD_INLINE int mld_ntt_native(int32_t data[MLDSA_N])
50+
{
51+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON))
52+
{
53+
return MLD_NATIVE_FUNC_FALLBACK;
54+
}
55+
mld_ntt_aarch64_asm(data, mld_aarch64_ntt_zetas_layer123456,
56+
mld_aarch64_ntt_zetas_layer78);
57+
return MLD_NATIVE_FUNC_SUCCESS;
58+
}
59+
60+
MLD_MUST_CHECK_RETURN_VALUE
61+
static MLD_INLINE int mld_intt_native(int32_t data[MLDSA_N])
62+
{
63+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON))
64+
{
65+
return MLD_NATIVE_FUNC_FALLBACK;
66+
}
67+
mld_intt_aarch64_asm(data, mld_aarch64_intt_zetas_layer78,
68+
mld_aarch64_intt_zetas_layer123456);
69+
return MLD_NATIVE_FUNC_SUCCESS;
70+
}
71+
72+
MLD_MUST_CHECK_RETURN_VALUE
73+
static MLD_INLINE int mld_rej_uniform_native(int32_t *r, unsigned len,
74+
const uint8_t *buf,
75+
unsigned buflen)
76+
{
77+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON) || len != MLDSA_N ||
78+
buflen % 24 != 0)
79+
{
80+
return MLD_NATIVE_FUNC_FALLBACK;
81+
}
82+
83+
/* Safety: outlen is at most MLDSA_N, hence, this cast is safe. */
84+
return (int)mld_rej_uniform_aarch64_asm(r, buf, buflen,
85+
mld_rej_uniform_table);
86+
}
87+
88+
#if !defined(MLD_CONFIG_NO_KEYPAIR_API)
89+
#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || MLDSA_ETA == 2
90+
MLD_MUST_CHECK_RETURN_VALUE
91+
static MLD_INLINE int mld_rej_uniform_eta2_native(int32_t *r, unsigned len,
92+
const uint8_t *buf,
93+
unsigned buflen)
94+
{
95+
uint64_t outlen;
96+
/* AArch64 implementation assumes specific buffer lengths */
97+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON) || len != MLDSA_N ||
98+
buflen != MLD_AARCH64_REJ_UNIFORM_ETA2_BUFLEN)
99+
{
100+
return MLD_NATIVE_FUNC_FALLBACK;
101+
}
102+
/* Constant time: Inputs and outputs to this function are secret.
103+
* It is safe to leak which coefficients are accepted/rejected.
104+
* The assembly implementation must not leak any other information about the
105+
* accepted coefficients. Constant-time testing cannot cover this, and we
106+
* hence have to manually verify the assembly.
107+
* We declassify prior the input data and mark the outputs as secret.
108+
*/
109+
MLD_CT_TESTING_DECLASSIFY(buf, buflen);
110+
outlen = mld_rej_uniform_eta2_aarch64_asm(r, buf, buflen,
111+
mld_rej_uniform_eta_table);
112+
MLD_CT_TESTING_SECRET(r, sizeof(int32_t) * outlen);
113+
/* Safety: outlen is at most MLDSA_N and, hence, this cast is safe. */
114+
return (int)outlen;
115+
}
116+
#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLDSA_ETA == 2 */
117+
118+
#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || MLDSA_ETA == 4
119+
MLD_MUST_CHECK_RETURN_VALUE
120+
static MLD_INLINE int mld_rej_uniform_eta4_native(int32_t *r, unsigned len,
121+
const uint8_t *buf,
122+
unsigned buflen)
123+
{
124+
uint64_t outlen;
125+
/* AArch64 implementation assumes specific buffer lengths */
126+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON) || len != MLDSA_N ||
127+
buflen != MLD_AARCH64_REJ_UNIFORM_ETA4_BUFLEN)
128+
{
129+
return MLD_NATIVE_FUNC_FALLBACK;
130+
}
131+
/* Constant time: Inputs and outputs to this function are secret.
132+
* It is safe to leak which coefficients are accepted/rejected.
133+
* The assembly implementation must not leak any other information about the
134+
* accepted coefficients. Constant-time testing cannot cover this, and we
135+
* hence have to manually verify the assembly.
136+
* We declassify prior the input data and mark the outputs as secret.
137+
*/
138+
MLD_CT_TESTING_DECLASSIFY(buf, buflen);
139+
outlen = mld_rej_uniform_eta4_aarch64_asm(r, buf, buflen,
140+
mld_rej_uniform_eta_table);
141+
MLD_CT_TESTING_SECRET(r, sizeof(int32_t) * outlen);
142+
/* Safety: outlen is at most MLDSA_N and, hence, this cast is safe. */
143+
return (int)outlen;
144+
}
145+
#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLDSA_ETA == 4 */
146+
#endif /* !MLD_CONFIG_NO_KEYPAIR_API */
147+
148+
#if !defined(MLD_CONFIG_NO_SIGN_API)
149+
#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || \
150+
(MLD_CONFIG_PARAMETER_SET == 65 || MLD_CONFIG_PARAMETER_SET == 87)
151+
MLD_MUST_CHECK_RETURN_VALUE
152+
static MLD_INLINE int mld_poly_decompose_32_native(int32_t *a1, int32_t *a0)
153+
{
154+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON))
155+
{
156+
return MLD_NATIVE_FUNC_FALLBACK;
157+
}
158+
mld_poly_decompose_32_aarch64_asm(a1, a0);
159+
return MLD_NATIVE_FUNC_SUCCESS;
160+
}
161+
#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLD_CONFIG_PARAMETER_SET == 65 \
162+
|| MLD_CONFIG_PARAMETER_SET == 87 */
163+
164+
#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || MLD_CONFIG_PARAMETER_SET == 44
165+
MLD_MUST_CHECK_RETURN_VALUE
166+
static MLD_INLINE int mld_poly_decompose_88_native(int32_t *a1, int32_t *a0)
167+
{
168+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON))
169+
{
170+
return MLD_NATIVE_FUNC_FALLBACK;
171+
}
172+
mld_poly_decompose_88_aarch64_asm(a1, a0);
173+
return MLD_NATIVE_FUNC_SUCCESS;
174+
}
175+
#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLD_CONFIG_PARAMETER_SET == 44 \
176+
*/
177+
#endif /* !MLD_CONFIG_NO_SIGN_API */
178+
179+
MLD_MUST_CHECK_RETURN_VALUE
180+
static MLD_INLINE int mld_poly_caddq_native(int32_t a[MLDSA_N])
181+
{
182+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON))
183+
{
184+
return MLD_NATIVE_FUNC_FALLBACK;
185+
}
186+
mld_poly_caddq_aarch64_asm(a);
187+
return MLD_NATIVE_FUNC_SUCCESS;
188+
}
189+
190+
#if !defined(MLD_CONFIG_NO_VERIFY_API)
191+
#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || \
192+
(MLD_CONFIG_PARAMETER_SET == 65 || MLD_CONFIG_PARAMETER_SET == 87)
193+
MLD_MUST_CHECK_RETURN_VALUE
194+
static MLD_INLINE int mld_poly_use_hint_32_native(int32_t *a, const int32_t *h)
195+
{
196+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON))
197+
{
198+
return MLD_NATIVE_FUNC_FALLBACK;
199+
}
200+
mld_poly_use_hint_32_aarch64_asm(a, h);
201+
return MLD_NATIVE_FUNC_SUCCESS;
202+
}
203+
#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLD_CONFIG_PARAMETER_SET == 65 \
204+
|| MLD_CONFIG_PARAMETER_SET == 87 */
205+
206+
#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || MLD_CONFIG_PARAMETER_SET == 44
207+
MLD_MUST_CHECK_RETURN_VALUE
208+
static MLD_INLINE int mld_poly_use_hint_88_native(int32_t *a, const int32_t *h)
209+
{
210+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON))
211+
{
212+
return MLD_NATIVE_FUNC_FALLBACK;
213+
}
214+
mld_poly_use_hint_88_aarch64_asm(a, h);
215+
return MLD_NATIVE_FUNC_SUCCESS;
216+
}
217+
#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLD_CONFIG_PARAMETER_SET == 44 \
218+
*/
219+
#endif /* !MLD_CONFIG_NO_VERIFY_API */
220+
221+
MLD_MUST_CHECK_RETURN_VALUE
222+
static MLD_INLINE int mld_poly_chknorm_native(const int32_t *a, int32_t B)
223+
{
224+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON))
225+
{
226+
return MLD_NATIVE_FUNC_FALLBACK;
227+
}
228+
return mld_poly_chknorm_aarch64_asm(a, B);
229+
}
230+
231+
#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API)
232+
#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || MLD_CONFIG_PARAMETER_SET == 44
233+
MLD_MUST_CHECK_RETURN_VALUE
234+
static MLD_INLINE int mld_polyz_unpack_17_native(int32_t *r, const uint8_t *buf)
235+
{
236+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON))
237+
{
238+
return MLD_NATIVE_FUNC_FALLBACK;
239+
}
240+
mld_polyz_unpack_17_aarch64_asm(r, buf, mld_polyz_unpack_17_indices);
241+
return MLD_NATIVE_FUNC_SUCCESS;
242+
}
243+
#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLD_CONFIG_PARAMETER_SET == 44 \
244+
*/
245+
246+
#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || \
247+
(MLD_CONFIG_PARAMETER_SET == 65 || MLD_CONFIG_PARAMETER_SET == 87)
248+
MLD_MUST_CHECK_RETURN_VALUE
249+
static MLD_INLINE int mld_polyz_unpack_19_native(int32_t *r, const uint8_t *buf)
250+
{
251+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON))
252+
{
253+
return MLD_NATIVE_FUNC_FALLBACK;
254+
}
255+
mld_polyz_unpack_19_aarch64_asm(r, buf, mld_polyz_unpack_19_indices);
256+
return MLD_NATIVE_FUNC_SUCCESS;
257+
}
258+
#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLD_CONFIG_PARAMETER_SET == 65 \
259+
|| MLD_CONFIG_PARAMETER_SET == 87 */
260+
#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API */
261+
262+
#if !defined(MLD_CONFIG_NO_SIGN_API) || !defined(MLD_CONFIG_NO_VERIFY_API) || \
263+
defined(MLD_CONFIG_REDUCE_RAM) || defined(MLD_UNIT_TEST)
264+
MLD_MUST_CHECK_RETURN_VALUE
265+
static MLD_INLINE int mld_poly_pointwise_montgomery_native(
266+
int32_t a[MLDSA_N], const int32_t b[MLDSA_N])
267+
{
268+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON))
269+
{
270+
return MLD_NATIVE_FUNC_FALLBACK;
271+
}
272+
mld_poly_pointwise_montgomery_aarch64_asm(a, b);
273+
return MLD_NATIVE_FUNC_SUCCESS;
274+
}
275+
#endif /* !MLD_CONFIG_NO_SIGN_API || !MLD_CONFIG_NO_VERIFY_API || \
276+
MLD_CONFIG_REDUCE_RAM || MLD_UNIT_TEST */
277+
278+
#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || MLDSA_L == 4
279+
MLD_MUST_CHECK_RETURN_VALUE
280+
static MLD_INLINE int mld_polyvecl_pointwise_acc_montgomery_l4_native(
281+
int32_t w[MLDSA_N], const int32_t u[4][MLDSA_N],
282+
const int32_t v[4][MLDSA_N])
283+
{
284+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON))
285+
{
286+
return MLD_NATIVE_FUNC_FALLBACK;
287+
}
288+
mld_polyvecl_pointwise_acc_montgomery_l4_aarch64_asm(w, u, v);
289+
return MLD_NATIVE_FUNC_SUCCESS;
290+
}
291+
#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLDSA_L == 4 */
292+
293+
#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || MLDSA_L == 5
294+
MLD_MUST_CHECK_RETURN_VALUE
295+
static MLD_INLINE int mld_polyvecl_pointwise_acc_montgomery_l5_native(
296+
int32_t w[MLDSA_N], const int32_t u[5][MLDSA_N],
297+
const int32_t v[5][MLDSA_N])
298+
{
299+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON))
300+
{
301+
return MLD_NATIVE_FUNC_FALLBACK;
302+
}
303+
mld_polyvecl_pointwise_acc_montgomery_l5_aarch64_asm(w, u, v);
304+
return MLD_NATIVE_FUNC_SUCCESS;
305+
}
306+
#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLDSA_L == 5 */
307+
308+
#if defined(MLD_CONFIG_MULTILEVEL_WITH_SHARED) || MLDSA_L == 7
309+
MLD_MUST_CHECK_RETURN_VALUE
310+
static MLD_INLINE int mld_polyvecl_pointwise_acc_montgomery_l7_native(
311+
int32_t w[MLDSA_N], const int32_t u[7][MLDSA_N],
312+
const int32_t v[7][MLDSA_N])
313+
{
314+
if (!mld_sys_check_capability(MLD_SYS_CAP_NEON))
315+
{
316+
return MLD_NATIVE_FUNC_FALLBACK;
317+
}
318+
mld_polyvecl_pointwise_acc_montgomery_l7_aarch64_asm(w, u, v);
319+
return MLD_NATIVE_FUNC_SUCCESS;
320+
}
321+
#endif /* MLD_CONFIG_MULTILEVEL_WITH_SHARED || MLDSA_L == 7 */
322+
323+
#endif /* !__ASSEMBLER__ */
324+
#endif /* !MLD_NATIVE_AARCH64_META_H */

crypto/fipsmodule/ml_dsa/mldsa_native_backend.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
(defined(OPENSSL_LINUX) || defined(OPENSSL_APPLE))
1111

1212
#if defined(OPENSSL_AARCH64)
13-
#include "mldsa/native/aarch64/meta.h"
13+
// AWS-LC-owned replacement for mldsa/native/aarch64/meta.h that adds a runtime
14+
// NEON capability gate; falls back to the C reference impl when NEON is absent.
15+
#include "mldsa_aarch64_meta.h"
1416
#elif defined(OPENSSL_X86_64) && !defined(MY_ASSEMBLER_IS_TOO_OLD_FOR_AVX)
1517
#include "mldsa_x86_64_meta.h"
1618
#endif

crypto/fipsmodule/ml_dsa/mldsa_native_config.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#if !defined(__ASSEMBLER__)
88
#include "../../internal.h"
9+
#include "../cpucap/internal.h"
910
#endif
1011

1112
// Namespacing: All symbols are of the form mldsa*. Level-specific
@@ -34,18 +35,29 @@
3435
#define MLD_CONFIG_KEYGEN_PCT
3536
#endif
3637

37-
// Map the CPU capability function to the ones used by AWS-LC
38+
// Map the CPU capability function to the ones used by AWS-LC.
39+
// MLD_SYS_CAP_NEON is defined here (not in the vendored mldsa/sys.h enum)
40+
// with a value that does not collide with AVX2 == 0 / SHA3 == 1.
41+
#if !defined(__ASSEMBLER__)
42+
#define MLD_SYS_CAP_NEON 2
43+
#endif
3844
#define MLD_CONFIG_CUSTOM_CAPABILITY_FUNC
3945
#if !defined(__ASSEMBLER__)
4046
#include <stdint.h>
4147
#include "mldsa/sys.h"
42-
static MLD_INLINE int mld_sys_check_capability(mld_sys_cap cap)
48+
static MLD_INLINE int mld_sys_check_capability(int cap)
4349
{
4450
#if defined(MLD_SYS_X86_64)
4551
if (cap == MLD_SYS_CAP_AVX2)
4652
{
4753
return CRYPTO_is_AVX2_capable();
4854
}
55+
#endif
56+
#if defined(MLD_SYS_AARCH64)
57+
if (cap == MLD_SYS_CAP_NEON)
58+
{
59+
return CRYPTO_is_NEON_capable();
60+
}
4961
#endif
5062
return 0;
5163
}

0 commit comments

Comments
 (0)