Skip to content

ML-DSA AArch64/32: assembly implementation - #11188

Open
SparkiDev wants to merge 1 commit into
wolfSSL:masterfrom
SparkiDev:mldsa_aarch64
Open

SparkiDev wants to merge 1 commit into
wolfSSL:masterfrom
SparkiDev:mldsa_aarch64

Conversation

@SparkiDev

Copy link
Copy Markdown
Contributor

Description

Add optimized AArch64 NEON assembly implementation
Add optimized AArch32 NEON assembly implementation

Testing

Regression tested for C/x64/AArch64/AArch32 assembly code.

@SparkiDev SparkiDev self-assigned this Aug 17, 2026
@github-actions

github-actions Bot commented Aug 17, 2026 •

Copy link
Copy Markdown

@SparkiDev

Copy link
Copy Markdown
Contributor Author

Code generated by PR:
https://github.com/wolfSSL/scripts/pull/658

@SparkiDev

Copy link
Copy Markdown
Contributor Author

Jenkins: retest this please

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #11188

Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src
Findings: 5

High (3)

AArch64 inline asm writes the return value to raw x0 instead of an operand in mldsa_make_hint_neon

File: wolfcrypt/src/port/arm/armv8-mldsa-asm_c.c:20543
Function: mldsa_make_hint_neon
Category: Copy-paste errors across platforms

The block copies the .S epilogue verbatim: it deposits the result in x0, which is neither an operand nor a clobber, while the function returns [s] — a "+r" operand the asm only advances by 1024 bytes. Correct only if the compiler happens to allocate s to x0; otherwise mldsa_make_hint() gets a pointer as the hint count and x0 is corrupted behind the compiler's back.

Recommendation: Write the result into the returned operand (mov %x[s], %x[idx] / movn %x[s], #0) as armv8-mlkem-asm_c.c does.

Referenced code: wolfcrypt/src/port/arm/armv8-mldsa-asm_c.c:20543-20548 (6 lines)


AArch64 inline asm writes the return value to raw x0 instead of an operand in mldsa_vec_check_low_neon

File: wolfcrypt/src/port/arm/armv8-mldsa-asm_c.c:26961
Function: mldsa_vec_check_low_neon
Category: Copy-paste errors across platforms

mov x0, x4 targets a register that is neither an operand nor a clobber, while the function returns [a], a "+r" operand the asm only advances past the input. Unless the compiler happens to place a in x0, the caller receives a non-zero pointer and the range check in mldsa_vec_check_low() always reports "low".

Recommendation: Emit the result into the returned operand, e.g. cset %w[a], eq, and drop the mov x0 copy.

Referenced code: wolfcrypt/src/port/arm/armv8-mldsa-asm_c.c:26961-26964 (4 lines)


AArch64 inline asm writes the return value to raw x0 instead of an operand in mldsa_rej_uniform_neon

File: wolfcrypt/src/port/arm/armv8-mldsa-asm_c.c:27645
Function: mldsa_rej_uniform_neon
Category: Copy-paste errors across platforms

mov x0, x14 writes a register that is neither an operand nor a clobber, while the function returns [p], a "+r" operand the asm advances as it stores samples. Unless the compiler places p in x0, the sample count returned to mldsa_rej_ntt_poly() is a pointer value.

Recommendation: Replace with mov %x[p], x14 to match the idiom used by mlkem_rej_uniform_neon.

Referenced code: wolfcrypt/src/port/arm/armv8-mldsa-asm_c.c:27645-27648 (4 lines)


Medium (1)

New MLDSA_SHAKE_DIRECT hash256 padding overwrites the 0x1f domain byte when the input exactly fills rate-1

File: wolfcrypt/src/wc_mldsa.c:917
Function: mldsa_hash256
Category: Logic errors

The short-input branch of the new MLDSA_SHAKE_DIRECT path assigns rather than ORs the final padding bit. When data1Len + data2Len == 135 (e.g. tr of 64 bytes plus a 71-byte message), state8[135] is set to 0x1f at line 915 and then clobbered to 0x80, dropping the SHAKE domain separator and producing a non-FIPS-204 mu. The sibling helpers mldsa_shake256 (line 695) and mldsa_squeeze256 (line 1231) use ^= here.

Related known finding #10014 (similar but distinct): Both are ML-DSA hash-input/FIPS-204 conformance defects in wc_mldsa.c, but this fault is SHAKE padding-byte clobbering in the MLDSA_SHAKE_DIRECT hash operation, while #10014 omits external message-prefix formatting in selectable no-context sign/verify paths. Their root causes and required patches differ.

Recommendation: Change the assignment to state8[WC_SHA3_256_COUNT * 8 - 1] ^= 0x80; to match the sibling SHAKE helpers.

Referenced code: wolfcrypt/src/wc_mldsa.c:917-921 (5 lines)


Low (1)

Batched-Keccak declarations moved outside the extern "C" block in sha3.h

File: wolfssl/wolfcrypt/sha3.h:465
Function: sha3_blocksx3_neon
Category: API contract violations

The relocated sha3_blocksx3_* / sha3_*_blocksx3_seed_* declarations sit after the closing } /* extern "C" */ at line 455, so a C++ translation unit including sha3.h declares them with C++ linkage while the assembly defines them with C linkage.

Recommendation: Move the #ifdef __cplusplus } #endif closer to the end of the file so the new block sits inside extern "C".

Referenced code: wolfssl/wolfcrypt/sha3.h:465-469 (5 lines)


This review was generated automatically by Fenrir. Blocking findings require changes before merge.

@SparkiDev

Copy link
Copy Markdown
Contributor Author

All Fenrir issues fixed

@SparkiDev

Copy link
Copy Markdown
Contributor Author

Jenkins: retest this please

FIPS

@SparkiDev SparkiDev assigned wolfSSL-Bot and unassigned SparkiDev Aug 20, 2026
@philljj
philljj self-requested a review August 27, 2026 14:24
Copilot AI balanced review requested due to automatic review settings October 5, 2026 23:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The new AArch32 Keccak loops access scratch memory outside their allocated stack frames.

Review effort: Balanced
Findings: 2 High severity

Open (2)
What changed in this PR

Adds optimized AArch64 and AArch32 NEON assembly support for ML-DSA, including shared ML-KEM/ML-DSA Keccak routines and architecture-specific CI coverage.

Changes:

  • Adds ARM ML-DSA declarations, dispatch, and build integration.
  • Shares batched SHA-3 assembly between ML-DSA and ML-KEM.
  • Adds QEMU-based AArch32/AArch64 regression jobs.
File Description
wolfssl/​wolfcrypt/​wc_mlkem.h Removes superseded ML-KEM SHA-3 declarations.
wolfssl/​wolfcrypt/​wc_mldsa.h Declares ARM ML-DSA assembly entry points.
wolfssl/​wolfcrypt/​sha3.h Declares shared batched Keccak routines.
wolfssl.vcxproj Adds ARM64 ML-DSA assembly build support.
wolfssl-VS2022.vcxproj Adds VS2022 ARM64 ML-DSA assembly support.
wolfcrypt/​src/​wc_mlkem_poly.c Reuses shared ARM Keccak paths.
wolfcrypt/​src/​port/​arm/​armv8-aes-asm.S Adjusts Apple SHA-3 assembler directives.
wolfcrypt/​src/​port/​arm/​armv8-aes-asm.asm Synchronizes generated armasm output.
wolfcrypt/​src/​port/​arm/​armv8-aes-asm_c.c Synchronizes inline assembly output.
wolfcrypt/​src/​port/​arm/​armv8-32-sha3-asm.S Adds AArch32 batched Keccak assembly.
wolfcrypt/​src/​port/​arm/​armv8-32-sha3-asm_c.c Adds its inline-assembly counterpart.
wolfcrypt/​src/​include.am Packages the ARM64 ML-DSA assembly source.
src/​include.am Selects ARM ML-DSA sources for builds.
.github/​workflows/​pq-all.yml Installs QEMU for cross-architecture tests.
.github/​configs/​pq-all.json Adds ARM assembly runtime configurations.
.github/​ci-deps/​packages-ubuntu-24.04-cross.txt Adds QEMU to the CI dependency bundle.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread wolfcrypt/src/port/arm/armv8-32-sha3-asm.S Outdated
Comment thread wolfcrypt/src/port/arm/armv8-32-sha3-asm_c.c Outdated
@SparkiDev
SparkiDev force-pushed the mldsa_aarch64 branch 2 times, most recently from faa4c9a to 1a4d297 Compare October 6, 2026 00:19
@SparkiDev

Copy link
Copy Markdown
Contributor Author

Jenkins retest this please

@philljj philljj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge conflict in sha3-asm* and wc_mldsa.c

@philljj philljj assigned SparkiDev and unassigned wolfSSL-Bot Oct 6, 2026
@SparkiDev
SparkiDev force-pushed the mldsa_aarch64 branch 3 times, most recently from 6393951 to c64ddca Compare October 7, 2026 08:11
@SparkiDev

SparkiDev commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Jenkins: retest this please

@SparkiDev

Copy link
Copy Markdown
Contributor Author

Jenkins: retest this please

MAC out of disk.

@SparkiDev

Copy link
Copy Markdown
Contributor Author

rebased to latest master

@philljj

philljj commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

benchmark results with aarch64

./configure --enable-cryptonly --enable-all-asm --enable-armasm --enable-mldsa && make

master:

ML-DSA    44  key gen     31100 ops took 1.001 sec, avg 0.032 ms, 31059.590 ops/sec, 24031224 cycles      772.7 Cycles/op
ML-DSA    44     sign      8400 ops took 1.003 sec, avg 0.119 ms,  8377.932 ops/sec, 24063186 cycles     2864.7 Cycles/op
ML-DSA    44   verify     28200 ops took 1.002 sec, avg 0.036 ms, 28155.963 ops/sec, 24037511 cycles      852.4 Cycles/op
ML-DSA    65  key gen     15700 ops took 1.006 sec, avg 0.064 ms, 15601.322 ops/sec, 24151803 cycles     1538.3 Cycles/op
ML-DSA    65     sign      5500 ops took 1.017 sec, avg 0.185 ms,  5406.627 ops/sec, 24414445 cycles     4439.0 Cycles/op
ML-DSA    65   verify     17800 ops took 1.004 sec, avg 0.056 ms, 17721.547 ops/sec, 24106238 cycles     1354.3 Cycles/op
ML-DSA    87  key gen     11900 ops took 1.006 sec, avg 0.085 ms, 11829.272 ops/sec, 24143483 cycles     2028.9 Cycles/op
ML-DSA    87     sign      4600 ops took 1.020 sec, avg 0.222 ms,  4509.614 ops/sec, 24481016 cycles     5322.0 Cycles/op
ML-DSA    87   verify     11200 ops took 1.003 sec, avg 0.090 ms, 11161.059 ops/sec, 24083705 cycles     2150.3 Cycles/op

this pr branch:

ML-DSA    44  key gen     66000 ops took 1.001 sec, avg 0.015 ms, 65951.193 ops/sec, 24017734 cycles      363.9 Cycles/op
ML-DSA    44     sign     23900 ops took 1.001 sec, avg 0.042 ms, 23887.004 ops/sec, 24013030 cycles     1004.7 Cycles/op
ML-DSA    44   verify     66300 ops took 1.001 sec, avg 0.015 ms, 66259.053 ops/sec, 24014793 cycles      362.2 Cycles/op
ML-DSA    65  key gen     40500 ops took 1.000 sec, avg 0.025 ms, 40489.352 ops/sec, 24006277 cycles      592.7 Cycles/op
ML-DSA    65     sign     16400 ops took 1.002 sec, avg 0.061 ms, 16370.778 ops/sec, 24042785 cycles     1466.0 Cycles/op
ML-DSA    65   verify     43500 ops took 1.000 sec, avg 0.023 ms, 43484.428 ops/sec, 24008539 cycles      551.9 Cycles/op
ML-DSA    87  key gen     26000 ops took 1.004 sec, avg 0.039 ms, 25908.856 ops/sec, 24084394 cycles      926.3 Cycles/op
ML-DSA    87     sign     13000 ops took 1.006 sec, avg 0.077 ms, 12918.791 ops/sec, 24150813 cycles     1857.8 Cycles/op
ML-DSA    87   verify     26500 ops took 1.003 sec, avg 0.038 ms, 26419.632 ops/sec, 24072982 cycles      908.4 Cycles/op

looks like substantial improvements for all categories. thank you @SparkiDev

@philljj philljj left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a verify-only aarch64 build error with this config:

./configure --enable-cryptonly --enable-all-asm --enable-armasm --enable-mldsa=verify-only && make

gives

wolfcrypt/src/wc_mldsa.c:361:13: error: unused function 'mlkem_shake256_blocksx3_seed_64' [-Werror,-Wunused-function]
static void mlkem_shake256_blocksx3_seed_64(word64* state, byte* seed)
            ^
1 error generated.

Builds fine on intel/amd with equivalent config there.

@philljj philljj assigned SparkiDev and unassigned wolfSSL-Bot Oct 29, 2026
Add optimized AArch64 NEON assembly implementation
Add optimized AArch32 NEON assembly implementation
@SparkiDev

Copy link
Copy Markdown
Contributor Author

Fixed the configurations.

@SparkiDev SparkiDev removed their assignment Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants