wolfCrypt on TI C2000 C28x (LAUNCHXL-F28P55X) - #10724
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds and CI-guards a bare-metal wolfCrypt port for TI C2000 C28x targets where CHAR_BIT == 16, introducing gated fixes so hashing, DRBG, ML-DSA verify, and SP-math ECC work correctly when a C “byte” is wider than 8 bits.
Changes:
- Introduces
WOLFSSL_NO_OCTET_BYTEdetection and uses octet-wise load/store paths to avoid invalid byte/word aliasing onCHAR_BIT != 8targets (SHA-256/512 family, SHA-3/SHAKE, Base64 CT decode, DRBG helpers, rotate helpers). - Adds “smallest memory” ML-DSA verify mode that streams
zper polynomial to reduce pinned RAM inwc_MlDsaKey. - Adds TI C2000 compile-only guard scripts plus a GitHub Actions workflow that downloads the TI CGT and compiles a scoped subset.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| wolfssl/wolfcrypt/wc_port.h | Makes atomic arg type selection robust for 16-bit int by also checking UINT_MAX. |
| wolfssl/wolfcrypt/wc_mldsa.h | Adds WOLFSSL_MLDSA_VERIFY_SMALLEST_MEM struct layout variant for reduced verify RAM. |
| wolfssl/wolfcrypt/types.h | Adds WOLFSSL_NO_OCTET_BYTE auto-detection; adjusts WC_16BIT_CPU 64-bit availability behavior. |
| wolfssl/wolfcrypt/sp_int.h | Adds support for unsigned char being 16-bit (no native 8-bit type). |
| wolfssl/wolfcrypt/settings.h | Requires explicit opt-in for SP math on 16-bit-int CPUs via WOLFSSL_SP_ALLOW_16BIT_CPU. |
| wolfssl/wolfcrypt/dilithium.h | Adds smallest-mem verify gating and defaults slow Montgomery reduction macros on WC_16BIT_CPU. |
| wolfcrypt/test/test.c | Switches large-digest constants from C strings to byte[] to avoid CHAR_BIT!=8 pitfalls. |
| wolfcrypt/src/wc_port.c | Fixes init-state static assert to use CHAR_BIT instead of hardcoded 8. |
| wolfcrypt/src/wc_mldsa.c | Adds octet-masking for packed bytes and fixes integer-promotion/sign issues on 16-bit int; adds streaming z verify path. |
| wolfcrypt/src/sha512.c | Adds octet-wise word load/store and corrects length carry/length placement for CHAR_BIT!=8. |
| wolfcrypt/src/sha3.c | Forces bytewise Keccak absorb/squeeze for WOLFSSL_NO_OCTET_BYTE and adds squeeze helper. |
| wolfcrypt/src/sha256.c | Adds octet-wise word load/store and corrects length carry/length placement for CHAR_BIT!=8. |
| wolfcrypt/src/random.c | Fixes DRBG serialization/addition helpers for non-8-bit “byte” targets. |
| wolfcrypt/src/misc.c | Fixes rotate helpers to use CHAR_BIT-based bit width when needed. |
| wolfcrypt/src/coding.c | Ensures Base64 CT decode returns 0xFF for invalid chars even when byte is wider than 8 bits. |
| wolfcrypt/benchmark/benchmark.c | Adds static buffers for WOLFSSL_NO_MALLOC benchmarking and adjusts frees/allocations accordingly. |
| scripts/ti-c2000/user_settings.h | Adds minimal CI-only config for cl2000 compile-guard. |
| scripts/ti-c2000/compile.sh | Adds compile-only script to build a scoped source set with TI cl2000. |
| .github/workflows/ti-c2000-compile.yml | Adds CI workflow to download/cache TI CGT and run the compile-only guard. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
39c343a to
afaf660
Compare
0f8a445 to
b5dfdc2
Compare
|
retest this please |
|
|
Jenkins retest this please |
…T != 8 (fixes C28x ECC/RSA/DH)
|
Jenkins retest this please |
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #10724
Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src
Findings: 1
Medium (1)
Missing WC_OCTET masking in WOLFSSL_MLKEM_SMALL path of mlkem_vec_compress_10_c
File: wolfcrypt/src/wc_mlkem_poly.c:5031
Function: mlkem_vec_compress_10_c
Category: Copy-paste errors
The PR adds || defined(WOLFSSL_WIDE_BYTE) to the outer #if at line 5018 and correctly wraps the #else path (lines 5043–5047) with WC_OCTET, but the #ifdef WOLFSSL_MLKEM_SMALL sub-path (lines 5030–5034) is left unmasked. On CHAR_BIT=16, expressions like (t[0] >> 8) | (t[1] << 2) can be up to 12 bits wide; storing them in a 16-bit byte cell without WC_OCTET corrupts the ciphertext.
Recommendation: Wrap each assignment with WC_OCTET(...) to match the #else path immediately below.
Referenced code: wolfcrypt/src/wc_mlkem_poly.c:5031-5035 (5 lines)
This review was generated automatically by Fenrir. Findings are non-blocking.
|
Jenkins retest this please |
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #10724
Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src
Findings: 3
2 finding(s) posted as inline comments (see file-level comments below)
Medium (1)
Second SqueezeBlocks call overwrites instead of appending in new SHAKE incremental-squeeze test
File: wolfcrypt/test/test.c:8099
Function: shake128_absorb_test
Category: Weak or missing assertions
wc_Shake128_SqueezeBlocks writes starting at the given out pointer each call; the second call passes hash again instead of hash + WC_SHA3_128_BLOCK_SIZE, so it overwrites the first squeezed block instead of appending the second. The tail of hash past one block width is never written by this test and compared against large_digest as if it were valid, so the test does not actually validate incremental squeezing despite the comment's stated intent. The identical bug exists in shake256_absorb_test at test.c:8474.
Recommendation: Advance the destination pointer between calls, e.g. pass hash + WC_SHA3_128_BLOCK_SIZE (and the SHAKE-256 equivalent) for the second SqueezeBlocks call.
Referenced code: wolfcrypt/test/test.c:8099-8104 (6 lines)
This review was generated automatically by Fenrir. Findings are non-blocking.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #10724
Scan targets checked: wolfcrypt-bugs, wolfcrypt-port-bugs, wolfcrypt-rs-bugs, wolfcrypt-src, wolfssl-bugs, wolfssl-src
Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Findings are non-blocking.
| #if defined(WOLFSSL_WIDE_BYTE) | ||
| /* CHAR_BIT != 8: write the digest words as big-endian octets. */ | ||
| BytesFromWordsBE32(hash, sha->digest, WC_SHA_DIGEST_SIZE); | ||
| (void)digest; |
There was a problem hiding this comment.
🔵 [Low] (void)digest; references undeclared variable when WIDE_BYTE + BIG_ENDIAN_ORDER · Incorrect macro expansion
digest is only declared under #ifdef LITTLE_ENDIAN_ORDER, but the new WOLFSSL_WIDE_BYTE branch unconditionally does (void)digest;. On a big-endian WOLFSSL_WIDE_BYTE target (a config the codebase otherwise treats as valid, e.g. sha3.c's split-64 guard checks !BIG_ENDIAN_ORDER), this references an undeclared identifier and fails to compile.
Fix: Guard the (void)digest; with #ifdef LITTLE_ENDIAN_ORDER or declare digest independent of endianness.
| #if defined(WOLFSSL_WIDE_BYTE) | ||
| /* CHAR_BIT != 8: store digest words as big-endian octets. */ | ||
| BytesFromWordsBE32(hash, sha256->digest, WC_SHA256_DIGEST_SIZE); | ||
| (void)digest; |
There was a problem hiding this comment.
🔵 [Low] (void)digest; references undeclared variable when WIDE_BYTE + BIG_ENDIAN_ORDER · Incorrect macro expansion
Same pattern as wc_ShaFinalRaw: digest is declared only under #ifdef LITTLE_ENDIAN_ORDER, but the WOLFSSL_WIDE_BYTE branch unconditionally references it via (void)digest;, breaking compilation on a hypothetical big-endian WOLFSSL_WIDE_BYTE target.
Fix: Guard the (void)digest; with #ifdef LITTLE_ENDIAN_ORDER or declare digest independent of endianness.
Companion example PR (wolfssl-examples): wolfSSL/wolfssl-examples#576
Summary
Adds
WOLFSSL_WIDE_BYTEsupport so wolfCrypt builds and runs correctly on word-addressed targets whereCHAR_BIT != 8- specifically the TI C2000 C28x DSP family, where a Cchar/unsigned char(wolfSSL'sbyte) is 16 bits and is the smallest addressable unit. All changes are gated and are a no-op on normal 8-bit-byte targets.The work was validated end-to-end on a TI LAUNCHXL-F28P55X (TMS320F28P550SJ, C28x, 150 MHz) using the bare-metal example added in the companion wolfssl-examples PR. Every algorithm below passes known-answer tests on hardware, and the standard host
wolfcrypt_testcontinues to pass (no 8-bit regression).Validated algorithms (on C28x hardware)
WC_16BIT_CPUthat emits native instructions instead of compiler 64-bit helper calls - ~53% faster SHAKE/SHA3 on this target)What the
CHAR_BIT != 8fixes addressAll behind
WOLFSSL_WIDE_BYTE(auto-enabled forCHAR_BIT != 8and known 16-bit-char TI toolchain macros), each a no-op on 8-bit targets:word32/word64by casting tobyte*moves addressable cells, not octets. Replaced with explicit shift-based octet I/O via shared helpers inmisc.c(WordsFromBytesBE32/BytesFromWordsBE32,BytesFromWordsLE32, the 64-bit variants, octet-correctreadUnalignedWord32/readUnalignedWord64).sp_int.csp_read_unsigned_binuses an endian-/CHAR_BIT-agnostic shift loop for its leftover bytes (a 3-byte RSA exponent previously loaded as 1 instead of 65537).(byte)xnot truncating to an octet (it keeps 16 bits). Masked withWC_OCTET(x)=(byte)((x) & 0xFF). Used across the ML-KEM/ML-DSA encoders, the SP*_to_binserializers, AESGETBYTE, base64, the DRBG, and the Curve448/Ed448CURVE448_SMALLbyte-array field backend (whose carry-store(word8)casts must mask before the next limb re-reads them).1U << nis 16-bit on C28x (use1UL); a bit width writtensizeof(t) * 8is wrong whenCHAR_BIT != 8(useCHAR_BIT * sizeof(t));byteoperands promote to a 16-bitint.sizeofcounting cells, not octets. e.g.CHACHA_CHUNK_BYTESmust be16 * 4, not16 * sizeof(word32)(= 32 on C28x, which halves the ChaCha block and desyncs the counter).xorbufword stride.WOLFSSL_WORD_SIZE_LOG2vssizeof(word)mismatch left half of each buffer un-XORed on a 16-bit-cell target; corrected for theWC_16BIT_CPUword16path.-O2,mldsa_red()'s Barrett reduction emits a stray store that corrupts memory a later allocation trips on, while the reduced value stays correct). Both are worked around indilithium.h, defaulted on underWC_16BIT_CPU, so ML-DSA builds at full-O2with no per-file overrides.New build options (added by this PR)
WOLFSSL_WIDE_BYTE- master gate for theCHAR_BIT != 8support. Auto-enabled bywolfssl/wolfcrypt/types.hwhenCHAR_BIT != 8or a known 16-bit-char TI toolchain macro is seen (__TMS320C28XX__,__TMS320C2000__, ...); can be forced. No effect on 8-bit-byte targets.WC_OCTET(x)(types.h) -(byte)((x) & 0xFF), the octet-truncation helper used wherever a value must be reduced to 8 bits regardless of type width.WOLFSSL_MLDSA_VERIFY_SMALLEST_MEM- streams the ML-DSA signature response vector one polynomial at a time instead of buffering all of it. Combined withWOLFSSL_MLDSA_ASSIGN_KEY(public key referenced in flash) andWOLFSSL_DILITHIUM_VERIFY_NO_MALLOC, brings ML-DSA-87 verify to ~10.8 KB RAM with zero heap.MLDSA_MUL_QINV_WIDE64andMLDSA_MUL_Q_SLOW(dilithium.h) - the two cl2000 workarounds above, defaulted on underWC_16BIT_CPU(no-ops elsewhere); each can be disabled with an override.WC_SHA3_SPLIT64- the 32-bit split Keccak permutation, auto-enabled forWOLFSSL_WIDE_BYTE(~53% faster SHAKE/SHA3 on this target; portable C, no assembly).Recommended configuration (C28x / CHAR_BIT != 8)
WOLFSSL_SP_MATH,SP_WORD_SIZE 32,WOLFSSL_SP_ALLOW_16BIT_CPU(addWOLFSSL_SP_NO_MALLOCfor a no-heap build).WOLFSSL_MLDSA_VERIFY_SMALLEST_MEM+WOLFSSL_MLDSA_ASSIGN_KEY+WOLFSSL_DILITHIUM_VERIFY_NO_MALLOC(~10.8 KB RAM, zero heap).WOLFSSL_MLDSA_SIGN_SMALL_MEM(min RAM) ->WOLFSSL_MLDSA_SIGN_SMALL_MEM_PRECALC_A(faster) -> full memory (fastest). ML-DSA keygen:WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM. Cross-op caches:WC_DILITHIUM_CACHE_MATRIX_A/CACHE_PUB_VECTORS/CACHE_PRIV_VECTORSfor repeated ops with one key.WOLFSSL_MLKEM_SMALL,WOLFSSL_MLKEM_MAKEKEY_SMALL_MEM,WOLFSSL_MLKEM_ENCAPSULATE_SMALL_MEM,WOLFSSL_MLKEM_CACHE_A.wc_mldsa.cbuild at-O2.See
IDE/C2000/user_settings.hfor a minimal reference config andIDE/C2000/README.mdfor the full write-up.Footprint (measured on F28P55X, cl2000 25.11.0; octets, KB = 1024 octets)
Code size is per-object
.textfrom the linker map (16-bit words x2). Builds are single-parameter: ML-DSA-87 only, ML-KEM-1024 only.wc_mldsa.obj)wc_mlkem+wc_mlkem_poly)WOLFSSL_SHA3_SMALL/ split-64 fast path)RAM per operation, measured on hardware (heap high-water via
wolfSSL_SetAllocators, stack via paint/scan):WOLFSSL_MLDSA_ASSIGN_KEY(zero heap); ~15.9 KB copying the public key into the key structPerformance (F28P55X @ 150 MHz, generic portable C - no assembly or accelerator)
Hashing / RNG throughput:
Post-quantum, measured on hardware (ms/op):
ML-KEM-768 is bracketed by the measured 512/1024 figures (the exact line drops over the slow JTAG-CIO console). ML-DSA sign reflects the RAM-optimized
WOLFSSL_MLDSA_SIGN_SMALL_MEMbuild and varies run-to-run (rejection sampling); the higher-RAM configurations above trade RAM for faster signing.Testing
./configure --enable-dilithium --enable-experimental --enable-shake256 --enable-shake128 && make && ./wolfcrypt/test/testwolfcrypt- passes (RSA, ECC, ML-DSA, ML-KEM, SHA-2/3, all crypto). No behavior change on 8-bit-byte targets.wolfcrypt_testcrypto passes.IDE/C2000/compile.shrunscl2000 --compile_onlyover theCHAR_BIT != 8wolfCrypt subset (SHA-1/2/3, AES + modes, ChaCha/Poly1305, X25519/Ed25519, X448/Ed448, ML-DSA verify, SP-ECC);.github/workflows/ti-c2000-compile.ymlruns it on PRs (fetches/caches the TI C2000 code generation tools, with optional SHA-256 pinning of the installer).Notes
wolfcrypt/src/sp_c32.cis generated. The& 0xFFoctet masks added to itssp_*_to_bin_*serializers are also applied in the SP generator templates (kept in sync so a regeneration preserves them).IDE/C2000/README.mddescribes the support, the build options, and the benchmark/footprint results; the full bare-metal example (with KATs, benchmark, linker scripts, and per-algorithmmaketoggles) is in wolfssl-examples atembedded/ti-c2000-f28p55x/.