Skip to content

Commit 78ef84d

Browse files
committed
[EXPERIMENTAL] Add RV32-IM backend
This commit adds an experimental arithmetic backend for RISCV32-IM: The backend includes: - A 2+2+2+2 forward NTT - A 2+2+2+2 inverse NTT - A poly-poly base multiplication (but not vector-vector) The modular arithmetic is implemented via textbook Montgomery multiplication using mul/mulh and subject to the usual bounds.
1 parent 1b47ba6 commit 78ef84d

20 files changed

Lines changed: 1687 additions & 10 deletions

.github/actions/multi-functest/action.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,10 @@ runs:
310310
nix-verbose: ${{ inputs.nix-verbose }}
311311
gh_token: ${{ inputs.gh_token }}
312312
custom_shell: ${{ inputs.custom_shell }}
313-
cflags: "${{ inputs.cflags }} -DMLD_FORCE_RISCV32"
313+
# The RV32-IM arithmetic backend is experimental and not picked
314+
# up by native/meta.h's defaults; select it explicitly here.
315+
# No-op for OPT=0 builds (MLD_CONFIG_ARITH_BACKEND_FILE is unused).
316+
cflags: "${{ inputs.cflags }} -DMLD_FORCE_RISCV32 -DMLD_CONFIG_ARITH_BACKEND_FILE=\\\\\\\"native/rv32im/meta.h\\\\\\\""
314317
ldflags: ${{ inputs.ldflags }}
315318
cross_prefix: riscv32-unknown-linux-gnu-
316319
exec_wrapper: qemu-riscv32
@@ -327,4 +330,3 @@ runs:
327330
rng_fail: ${{ inputs.rng_fail }}
328331
extra_args: ${{ inputs.extra_args }}
329332
extra_env: ${{ inputs.extra_env }}
330-

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,8 @@ jobs:
155155
check_namespace: 'false'
156156
- name: build + test (cross, opt)
157157
uses: ./.github/actions/multi-functest
158-
# There is no native code yet on PPC64LE, riscv32 or AArch64_be, so no point running opt tests
159-
if: ${{ matrix.target.mode != 'native' && (matrix.target.arch != 'ppc64le' && matrix.target.arch != 'riscv32' && matrix.target.arch != 'aarch64_be') }}
158+
# There is no native code yet on PPC64LE or AArch64_be, so no point running opt tests
159+
if: ${{ matrix.target.mode != 'native' && (matrix.target.arch != 'ppc64le' && matrix.target.arch != 'aarch64_be') }}
160160
with:
161161
nix-shell: ${{ matrix.target.nix_shell }}
162162
nix-cache: ${{ matrix.target.mode == 'native' && 'false' || 'true' }}
@@ -165,8 +165,8 @@ jobs:
165165
opt: 'opt'
166166
- name: build + test (cross, opt, +debug)
167167
uses: ./.github/actions/multi-functest
168-
# There is no native code yet on PPC64LE, riscv32 or AArch64_be, so no point running opt tests
169-
if: ${{ matrix.target.mode != 'native' && (matrix.target.arch != 'ppc64le' && matrix.target.arch != 'riscv32' && matrix.target.arch != 'aarch64_be') }}
168+
# There is no native code yet on PPC64LE or AArch64_be, so no point running opt tests
169+
if: ${{ matrix.target.mode != 'native' && (matrix.target.arch != 'ppc64le' && matrix.target.arch != 'aarch64_be') }}
170170
with:
171171
nix-shell: ${{ matrix.target.nix_shell }}
172172
nix-cache: ${{ matrix.target.mode == 'native' && 'false' || 'true' }}

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ mldsa-native allows developers to support ML-DSA with minimal performance and ma
2323

2424
**Maintainability and Safety:** Memory safety, type safety and absence of various classes of timing leakage are automatically checked on every change, using a combination of static model checking (using CBMC) and dynamic instrumentation (using valgrind). This reduces review and maintenance burden and accelerates safe code delivery. See [Formal Verification](#formal-verification) and [Security](#security).
2525

26-
**Architecture Support:** Native backends are added under a unified interface, minimizing duplicated code and reasoning. mldsa-native comes with backends for AArch64 and x86-64. See [Design](#design).
26+
**Architecture Support:** Native backends are added under a unified interface, minimizing duplicated code and reasoning. mldsa-native comes with backends for AArch64 and x86-64, and experimental backends for Armv8.1-M and RV32-IM. See [Design](#design).
2727

2828
## Quickstart for Ubuntu
2929

@@ -92,6 +92,7 @@ mldsa-native currently offers the following backends:
9292
* 64-bit Arm backend (using Neon)
9393
* 64-bit Intel/AMD backend (using AVX2)
9494
* 32-bit Armv8.1-M backend (using Helium/MVE). This is still experimental and disabled by default.
95+
* 32-bit RISC-V backend (RV32-IM, base integer + M-extension only). This is still experimental and disabled by default.
9596

9697
If you'd like contribute new backends, please reach out!
9798

dev/riscv32/meta.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) The mlkem-native project authors
3+
* Copyright (c) The mldsa-native project authors
4+
* SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT
5+
*/
6+
7+
#ifndef MLD_NATIVE_RV32IM_META_H
8+
#define MLD_NATIVE_RV32IM_META_H
9+
10+
/* Set of primitives that this backend replaces */
11+
#define MLD_USE_NATIVE_NTT
12+
#define MLD_USE_NATIVE_INTT
13+
#define MLD_USE_NATIVE_POINTWISE_MONTGOMERY
14+
15+
/* Identifier for this backend so that source and assembly files
16+
* in the build can be appropriately guarded. */
17+
#define MLD_ARITH_BACKEND_RV32IM
18+
19+
20+
#if !defined(__ASSEMBLER__)
21+
#include "../api.h"
22+
#include "src/arith_native_rv32im.h"
23+
24+
MLD_MUST_CHECK_RETURN_VALUE
25+
static MLD_INLINE int mld_ntt_native(int32_t data[MLDSA_N])
26+
{
27+
mld_ntt_rv32im_asm(data, mld_rv32im_ntt_zetas);
28+
return MLD_NATIVE_FUNC_SUCCESS;
29+
}
30+
31+
MLD_MUST_CHECK_RETURN_VALUE
32+
static MLD_INLINE int mld_intt_native(int32_t data[MLDSA_N])
33+
{
34+
mld_intt_rv32im_asm(data, mld_rv32im_ntt_zetas);
35+
return MLD_NATIVE_FUNC_SUCCESS;
36+
}
37+
38+
MLD_MUST_CHECK_RETURN_VALUE
39+
static MLD_INLINE int mld_poly_pointwise_montgomery_native(
40+
int32_t a[MLDSA_N], const int32_t b[MLDSA_N])
41+
{
42+
mld_poly_pointwise_montgomery_rv32im_asm(a, b);
43+
return MLD_NATIVE_FUNC_SUCCESS;
44+
}
45+
46+
#endif /* !__ASSEMBLER__ */
47+
#endif /* !MLD_NATIVE_RV32IM_META_H */
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (c) The mldsa-native project authors
3+
* SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT
4+
*/
5+
6+
#ifndef MLD_NATIVE_RV32IM_SRC_ARITH_NATIVE_RV32IM_H
7+
#define MLD_NATIVE_RV32IM_SRC_ARITH_NATIVE_RV32IM_H
8+
9+
#include "../../../cbmc.h"
10+
#include "../../../common.h"
11+
12+
#define mld_rv32im_ntt_zetas MLD_NAMESPACE(rv32im_ntt_zetas)
13+
14+
/*
15+
* Forward NTT zeta table for the RV32-IM backend.
16+
*
17+
* 255 logical entries, each a (zeta, zeta * QINV mod 2^32) pair, with
18+
* zeta in Montgomery form (i.e. R * w^{bitrev_8(k)} mod q where R = 2^32).
19+
* The order matches the consumption order of the 2+2+2+2 forward NTT.
20+
*/
21+
MLD_INTERNAL_DATA_DECLARATION const int32_t mld_rv32im_ntt_zetas[510];
22+
23+
#define mld_ntt_rv32im_asm MLD_NAMESPACE(ntt_rv32im_asm)
24+
void mld_ntt_rv32im_asm(int32_t *r, const int32_t *zetas)
25+
__contract__(
26+
requires(memory_no_alias(r, sizeof(int32_t) * MLDSA_N))
27+
requires(array_abs_bound(r, 0, MLDSA_N, MLDSA_Q))
28+
requires(zetas == mld_rv32im_ntt_zetas)
29+
assigns(memory_slice(r, sizeof(int32_t) * MLDSA_N))
30+
ensures(array_abs_bound(r, 0, MLDSA_N, 9 * MLDSA_Q))
31+
);
32+
33+
#define mld_intt_rv32im_asm MLD_NAMESPACE(intt_rv32im_asm)
34+
void mld_intt_rv32im_asm(int32_t *r, const int32_t *zetas)
35+
__contract__(
36+
requires(memory_no_alias(r, sizeof(int32_t) * MLDSA_N))
37+
requires(array_abs_bound(r, 0, MLDSA_N, MLDSA_Q))
38+
requires(zetas == mld_rv32im_ntt_zetas)
39+
assigns(memory_slice(r, sizeof(int32_t) * MLDSA_N))
40+
ensures(array_abs_bound(r, 0, MLDSA_N, MLDSA_Q))
41+
);
42+
43+
#define mld_poly_pointwise_montgomery_rv32im_asm \
44+
MLD_NAMESPACE(poly_pointwise_montgomery_rv32im_asm)
45+
void mld_poly_pointwise_montgomery_rv32im_asm(int32_t *a, const int32_t *b)
46+
__contract__(
47+
requires(memory_no_alias(a, sizeof(int32_t) * MLDSA_N))
48+
requires(memory_no_alias(b, sizeof(int32_t) * MLDSA_N))
49+
/* check-magic: off */
50+
requires(array_abs_bound(a, 0, MLDSA_N, 75423753)) /* MLD_NTT_BOUND */
51+
requires(array_abs_bound(b, 0, MLDSA_N, 75423753))
52+
/* check-magic: on */
53+
assigns(memory_slice(a, sizeof(int32_t) * MLDSA_N))
54+
ensures(array_abs_bound(a, 0, MLDSA_N, MLDSA_Q))
55+
);
56+
57+
#endif /* !MLD_NATIVE_RV32IM_SRC_ARITH_NATIVE_RV32IM_H */

0 commit comments

Comments
 (0)