diff --git a/.github/workflows/ti-c2000-compile.yml b/.github/workflows/ti-c2000-compile.yml index 611dc2f7d5e..a5b87cf9e00 100644 --- a/.github/workflows/ti-c2000-compile.yml +++ b/.github/workflows/ti-c2000-compile.yml @@ -157,6 +157,51 @@ jobs: # even though the C28x itself is little-endian. Those arms are # otherwise unreachable in CI, which is how a WIDE_BYTE arm # referencing a LITTLE_ENDIAN_ORDER-only local once went unnoticed. + sp_c32_octet_masks: + # sp_c32.c is generated. Two CHAR_BIT != 8 fixes live in it and have been + # dropped by a regeneration before, which breaks ECDSA/ECDH/RSA/DH on + # 16-bit-byte targets while every 8-bit build stays green. Fail loudly. + name: sp_c32.c CHAR_BIT != 8 masks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Octet masks present in every sp_*_to_bin_* + # Counting occurrences alone would pass a partial regression: if one + # serializer lost both masks the counts would simply fall together. + # Tie them to the number of generated functions instead, so a dropped + # mask or a newly added unmasked serializer both fail. + run: | + defs=$(grep -cE '^static (void|int) sp_[0-9]+_to_bin_[0-9]+\(' \ + wolfcrypt/src/sp_c32.c || true) + n=$(grep -c 'a\[j--\] |= (byte)(((sp_uint32)r\[i\] << s) & 0xFF)' \ + wolfcrypt/src/sp_c32.c || true) + m=$(grep -c 'a\[j--\] = (byte)((r\[i\] >> b) & 0xFF)' \ + wolfcrypt/src/sp_c32.c || true) + echo "to_bin functions: $defs shift-left: $n shift-right: $m" + if [ "$defs" -eq 0 ] || [ "$n" -ne "$defs" ] || [ "$m" -ne "$defs" ]; then + echo "::error::sp_c32.c: every sp_*_to_bin_* must carry both & 0xFF" + echo "octet masks - expected $defs of each, found $n and $m." + echo "Regenerate with the fixed sp/conv.rb templates" + echo "(wolfSSL/scripts)." + exit 1 + fi + - name: CHAR_BIT used in every sp_*_from_mp constant-time mask + run: | + defs=$(grep -cE '^static (void|int) sp_[0-9]+_from_mp\(' \ + wolfcrypt/src/sp_c32.c || true) + n=$(grep -c 'sizeof(mp_digit) \* CHAR_BIT - 1' \ + wolfcrypt/src/sp_c32.c || true) + echo "from_mp functions: $defs CHAR_BIT masks: $n" + if grep -q 'sizeof(mp_digit) \* 8 - 1' wolfcrypt/src/sp_c32.c; then + echo "::error::sp_c32.c uses sizeof(mp_digit) * 8; must be CHAR_BIT" + exit 1 + fi + if [ "$defs" -eq 0 ] || [ "$n" -ne "$defs" ]; then + echo "::error::sp_c32.c: every sp_*_from_mp must use" + echo "sizeof(mp_digit) * CHAR_BIT - expected $defs, found $n." + exit 1 + fi + wide_byte_be_compile: name: WIDE_BYTE + big-endian compile guard if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} @@ -169,7 +214,7 @@ jobs: - name: Syntax-check the octet paths with WIDE_BYTE + BIG_ENDIAN_ORDER run: | set -e - for f in sha sha256 sha512 sha3 misc aes chacha random; do + for f in sha sha256 sha512 sha3 misc aes chacha random wc_port; do echo "== wolfcrypt/src/$f.c ==" gcc -fsyntax-only -Werror -I. \ -DWOLFSSL_WIDE_BYTE -DBIG_ENDIAN_ORDER \ diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 4d52b845037..3ec83a69f21 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -947,6 +947,7 @@ WOLFSSL_MAKE_SYSTEM_NAME_WSL WOLFSSL_MANUALLY_SELECT_DEVICE_CONFIG WOLFSSL_MDK5 WOLFSSL_MICROCHIP_AESGCM +WOLFSSL_MLDSA_VERIFY_PRECOMP_A WOLFSSL_MLKEM_ASM_TEST WOLFSSL_MLKEM_INVNTT_UNROLL WOLFSSL_MLKEM_NO_MALLOC diff --git a/IDE/C2000/README.md b/IDE/C2000/README.md index f92a62bb501..4d0efe5e269 100644 --- a/IDE/C2000/README.md +++ b/IDE/C2000/README.md @@ -12,7 +12,9 @@ etc.). On normal 8-bit-byte targets none of this code changes behavior. - SHA-1; SHA-224/256, SHA-384/512, SHA-512/224, SHA-512/256 - SHA3-224/256/384/512, SHAKE128/256 (split-64 Keccak permutation auto-enabled for `WOLFSSL_WIDE_BYTE`, ~53% faster than the generic C path) -- ML-DSA-44/65/87 (Dilithium) verify and full keygen/sign/verify; +- ML-DSA-44/65/87 (Dilithium) verify, including the HashML-DSA (pre-hash) + `wc_MlDsaKey_VerifyCtxHash()` path over SHA-256 and SHA-512 with a non-empty + context, and full ML-DSA-87 keygen/sign/verify; ML-KEM-512/768/1024 (FIPS 203) - AES-128/192/256 CBC/CTR/CFB/OFB/GCM/XTS; AES-CMAC, AES-CCM, AES-GMAC, AES-SIV, AES-EAX @@ -27,6 +29,48 @@ split-64 Keccak path is additionally validated on a host build with `-DWC_SHA3_SPLIT64` forced, and the compile-only CI below guards every `WOLFSSL_WIDE_BYTE` source against build breakage. +## Octet representation at the API boundary + +**Every wolfCrypt `byte*` buffer holds exactly one octet per `byte` cell** - +keys, signatures, digests, ciphertext alike. On a normal target a cell is 8 +bits, so that is a packed octet stream. On the C28x a cell is 16 bits, so the +same buffer costs twice the RAM and each cell reads as `0x00nn`. The octet +*values* are unchanged, so this is a footprint property, not an integrity one: +an ML-DSA-65 signature is 3309 octets = 3309 cells = 6618 bytes of RAM, +`sizeof(sig)` is 3309, and length arguments such as `sigLen` are octet counts +throughout. + +Data arriving from outside the CPU - flash, SCI, CAN, a host tool - is +different: it is **packed**, `CHAR_BIT / 8` octets per cell, low octet first +(the order TI's `__byte()` uses). Convert at the boundary with +`wc_UnpackOctets()`, and `wc_PackOctets()` on the way back out: + + +```c +static byte sig[WC_MLDSA_65_SIG_SIZE]; /* 3309 cells, one octet each */ + +/* sigPacked: WC_PACKED_CELLS(3309) cells as stored in flash. */ +if (wc_UnpackOctets(sig, (word32)sizeof(sig), sigPacked, + (word32)sizeof(sigPacked), WC_MLDSA_65_SIG_SIZE) == 0) { + ret = wc_MlDsaKey_VerifyCtxHash(key, sig, WC_MLDSA_65_SIG_SIZE, + ctx, ctxLen, hash, hashLen, + WC_HASH_TYPE_SHA256, &res); +} +``` + + +Both are declared only under `WOLFSSL_WIDE_BYTE`, since on an 8-bit-byte target +the packed and unpacked layouts are the same buffer and there is nothing to +convert. `WC_OCTETS_PER_BYTE` and `WC_PACKED_CELLS()` are always available for +sizing. Source and destination must not overlap. Note that a C array literal +needs no unpacking - `static const byte sig[] = { 0xaa, ... }` is already one +octet per cell - and no API needs a "wide" variant; only the transport layout +differs. + +The reference example's `make MLDSA=1` image exercises this on hardware: +ML-DSA-44/65/87 verify, `wc_MlDsaKey_VerifyCtxHash()` over SHA-256 and SHA-512, +and a verify from a packed key and signature. + ## What `WOLFSSL_WIDE_BYTE` fixes The `CHAR_BIT != 8` work falls into a few recurring classes, each a no-op on @@ -47,9 +91,14 @@ The `CHAR_BIT != 8` work falls into a few recurring classes, each a no-op on - `sizeof` counting cells, not octets. e.g. `CHACHA_CHUNK_BYTES` is `16 * 4`, not `16 * sizeof(word32)` (= 32 on C28x, which halves the ChaCha block). -The SP backend file `wolfcrypt/src/sp_c32.c` is generated; the `& 0xFF` octet -masks added to its `sp_*_to_bin_*` serializers are also applied in the SP -generator templates so a regeneration preserves them (tracked separately). +The SP backend file `wolfcrypt/src/sp_c32.c` is generated and carries two +`CHAR_BIT != 8` fixes: the `& 0xFF` octet masks in `sp_*_to_bin_*`, and +`sizeof(mp_digit) * CHAR_BIT` (not `* 8`) in the `sp_*_from_mp` constant-time +mask. Both now come from the SP generator templates (`sp/conv.rb` in the +wolfSSL scripts repo). A regeneration with older templates drops them, which +breaks ECDSA, ECDH, RSA and DH on the C28x while leaving every 8-bit-byte build +and all hardware-free CI green - this has happened once already. After any SP +regeneration, re-check with `./regress.sh ecc rsa dh`. ## cl2000 compiler workarounds @@ -81,8 +130,8 @@ math backend on a 16-bit-int target also set `WOLFSSL_SP_MATH`, A complete bare-metal example with KATs, benchmark, linker scripts, and per- algorithm build toggles is in wolfSSL Examples: `embedded/ti-c2000-f28p55x/` (see its `README.md` for the `make` options: -`ECC`, `MLKEM`, `AES`, `AESEXTRA`, `X25519`, `HKDF`, `CHACHA`, `RSA`, `SIGN`, -`BENCH`). +`ECC`, `MLKEM`, `MLDSA`, `AES`, `AESEXTRA`, `X25519`, `HKDF`, `CHACHA`, +`RSA`, `SIGN`, `BENCH`). Representative throughput on the F28P55X at 150 MHz: SHA-256 ~284 KiB/s; SHA3-256 ~264 KiB/s; SHAKE128 ~319 KiB/s; RNG Hash-DRBG ~122 KiB/s. ML-DSA-87 verify diff --git a/IDE/C2000/compile.sh b/IDE/C2000/compile.sh index 13de96b92f7..4bfc4c31fe5 100755 --- a/IDE/C2000/compile.sh +++ b/IDE/C2000/compile.sh @@ -36,11 +36,9 @@ CFLAGS="-v28 --abi=eabi --float_support=fpu32 --tmu_support=tmu1 -O2 \ # wolfCrypt sources to compile-guard under CHAR_BIT==16. This is the set that # carries the CHAR_BIT != 8 gated fixes (plus their direct deps) - the -# regression surface for this port. hash.c (an unmodified dispatch wrapper) is -# intentionally omitted: its wc_OidGetHash() OID switch needs the fuller ASN/OID -# config of a real build to avoid a 16-bit-int case-label fold, and it is -# covered by the on-target example build, not by this minimal guard. -SRCS="error wc_port memory logging misc coding \ +# regression surface for this port, plus hash.c, which HashML-DSA +# (wc_MlDsaKey_VerifyCtxHash) calls for the digest size and OID. +SRCS="error wc_port memory logging misc coding hash \ sha sha256 sha512 sha3 wc_mldsa random ecc sp_int sp_c32 \ aes cmac chacha poly1305 \ curve25519 ed25519 fe_operations ge_operations \ diff --git a/IDE/C2000/user_settings.h b/IDE/C2000/user_settings.h index 01978fcbeda..1e56003796a 100644 --- a/IDE/C2000/user_settings.h +++ b/IDE/C2000/user_settings.h @@ -12,6 +12,15 @@ #define TI_C2000_CI_USER_SETTINGS_H #define WOLFCRYPT_ONLY /* crypto only - no TLS (no MD5/SHA1 dep) */ +/* The C28x has a 16-bit int as well as a 16-bit char, so this models the real + * target rather than a half-configured one. It is a broad switch, not just an + * OID knob: settings.h and types.h key several decisions off it - the old + * 16-bit-safe OID sums (without which the 32-bit sums in wc_OidGetHash() + * collide once truncated to int and hash.c will not compile), MP_16BIT for the + * big-int backends, the small GCM tables, WORD64_AVAILABLE handling, and the + * ML-DSA cl2000 codegen workarounds in dilithium.h. The reference example + * defines it too; keep the two in step. */ +#define WC_16BIT_CPU #define WOLFSSL_GENERAL_ALIGNMENT 2 #define HAVE_LIMITS_H #define WOLFSSL_NO_ASM @@ -61,10 +70,11 @@ #define HAVE_ED448 #define ED448_SMALL -/* ML-DSA-87 verify (smallest-mem streaming verifier) */ +/* ML-DSA verify, all three parameter sets (smallest-mem streaming) */ #define WOLFSSL_HAVE_MLDSA -#define WOLFSSL_NO_ML_DSA_44 -#define WOLFSSL_NO_ML_DSA_65 +/* All three parameter sets: level 44 is the only one whose w1 commitment + * encoder packs 6-bit values (mldsa_encode_w1_88_c), so without it this guard + * would not compile the very code the CHAR_BIT != 8 masking protects. */ #define WOLFSSL_MLDSA_NO_ASN1 #define WOLFSSL_MLDSA_VERIFY_ONLY #define WOLFSSL_MLDSA_VERIFY_SMALL_MEM diff --git a/doc/ALGORITHM_DEFINES.md b/doc/ALGORITHM_DEFINES.md index 6763f2e0803..738c00fc7e0 100644 --- a/doc/ALGORITHM_DEFINES.md +++ b/doc/ALGORITHM_DEFINES.md @@ -469,6 +469,7 @@ Operations and size: | `WOLFSSL_MLDSA_NO_MAKE_KEY` / `_NO_SIGN` / `_NO_VERIFY` | Drop an operation | | `WOLFSSL_MLDSA_VERIFY_ONLY` | Verify only — the firmware-check case | | `WOLFSSL_MLDSA_VERIFY_SMALL_MEM` | Stream the verify instead of expanding the key at once | +| `WOLFSSL_MLDSA_VERIFY_PRECOMP_A` | Allow a host-expanded matrix A to be attached with `wc_MlDsaKey_SetPrecompA()`, so verify skips the SHAKE128 expansion. Needs a verification key fixed at build time; works with both the default and small-memory verifiers. The stored matrix must be integrity-protected exactly as the public key is | | `WOLFSSL_MLKEM_SMALL`, `WOLFSSL_MLKEM_NO_LARGE_CODE` | Loop rather than unroll | | `WOLFSSL_MLDSA_SMALL`, `WOLFSSL_MLDSA_NO_LARGE_CODE` | As above for ML-DSA | | `WOLFSSL_MLKEM_DYNAMIC_KEYS` | Allocate key buffers to the size actually needed, rather than carrying the largest in the key structure. Reduces handshake memory on constrained systems. **Cannot be used with `WOLFSSL_NO_MALLOC`** | diff --git a/doc/dox_comments/header_files/types.h b/doc/dox_comments/header_files/types.h index e55748d6ba0..b17bb4306b1 100644 --- a/doc/dox_comments/header_files/types.h +++ b/doc/dox_comments/header_files/types.h @@ -232,6 +232,68 @@ char* wc_strsep(char **stringp, const char *delim); */ size_t wc_strlcpy(char *dst, const char *src, size_t dstSize); +/*! + \ingroup wolfCrypt + \brief Expands a packed octet stream into the one-octet-per-byte-cell form + every wolfCrypt byte* API expects. Declared only when WOLFSSL_WIDE_BYTE is + set (CHAR_BIT != 8); elsewhere the two layouts coincide and no conversion + is needed. + + \return 0 on success + \return BAD_FUNC_ARG when out or in is NULL + \return BUFFER_E when out is smaller than octetSz, or in smaller than + WC_PACKED_CELLS(octetSz) + + \param out Destination, one octet per cell + \param outSz Size of out in byte cells + \param in Packed source, WC_OCTETS_PER_BYTE octets per cell, low octet + first, and must not overlap out + \param inSz Size of in in byte cells; must be at least + WC_PACKED_CELLS(octetSz) + \param octetSz Number of octets to expand + + _Example_ + \code + byte sig[WC_MLDSA_65_SIG_SIZE]; + int ret = wc_UnpackOctets(sig, (word32)sizeof(sig), sigPacked, + (word32)sizeof(sigPacked), + WC_MLDSA_65_SIG_SIZE); + \endcode + + \sa wc_PackOctets +*/ +int wc_UnpackOctets(byte* out, word32 outSz, const byte* in, word32 inSz, + word32 octetSz); + +/*! + \ingroup wolfCrypt + \brief Packs a one-octet-per-byte-cell buffer into an octet stream, for + storing to flash or handing to a byte-oriented peripheral. Inverse of + wc_UnpackOctets(); requires WOLFSSL_WIDE_BYTE. + + \return 0 on success + \return BAD_FUNC_ARG when out or in is NULL + \return BUFFER_E when out is smaller than WC_PACKED_CELLS(octetSz), or + in smaller than octetSz + + \param out Destination for the packed stream + \param outSz Size of out in byte cells + \param in Source, one octet per cell, and must not overlap out + \param inSz Size of in in byte cells; must be at least octetSz + \param octetSz Number of octets to pack + + _Example_ + \code + byte packed[WC_PACKED_CELLS(sizeof(sig))]; + int ret = wc_PackOctets(packed, (word32)sizeof(packed), sig, + (word32)sizeof(sig), (word32)sizeof(sig)); + \endcode + + \sa wc_UnpackOctets +*/ +int wc_PackOctets(byte* out, word32 outSz, const byte* in, word32 inSz, + word32 octetSz); + /*! \ingroup String \brief Safely concatenates strings with size limit. diff --git a/doc/dox_comments/header_files/wc_mldsa.h b/doc/dox_comments/header_files/wc_mldsa.h index 8aea9086deb..9011e73d8cc 100644 --- a/doc/dox_comments/header_files/wc_mldsa.h +++ b/doc/dox_comments/header_files/wc_mldsa.h @@ -521,6 +521,65 @@ int wc_MlDsaKey_VerifyCtxHash(wc_MlDsaKey* key, const byte* sig, word32 sigLen, int wc_MlDsaKey_VerifyMu(wc_MlDsaKey* key, const byte* sig, word32 sigLen, const byte* mu, word32 muLen, int* res); +/*! + \ingroup ML_DSA + + \brief Attaches a matrix A that was expanded off target, so verification + can skip the SHAKE128 rejection sampling that otherwise dominates it. + A is a function of the public seed rho alone, so wherever the verification + key is fixed at build time - secure boot being the usual case - A can be + computed once on a host and stored in flash. Available only when + WOLFSSL_MLDSA_VERIFY_PRECOMP_A is defined, and honoured by both the + default and the small-memory verifiers. + + The matrix is borrowed, never copied and never freed: it must stay valid + and unchanged for as long as the key uses it. It holds + k * l * MLDSA_N sword32 elements (not bytes) in row-major (r, s) order and + in the NTT domain, exactly as ExpandA produces them. + + Import the public key first: the matrix is bound to it by comparing rho. + The binding is re-checked on every verify, so replacing the key or the + parameters afterwards simply discards the matrix and falls back to + expanding A rather than using a stale one. + + \note A is derived from the public key and must be integrity-protected + exactly as the public key is. An attacker able to substitute it can + influence verification. In a secure-boot design the key already lives in + protected flash, so store the two together. + + \return 0 on success. + \return BAD_FUNC_ARG if a pointer is NULL, rhoLen is not + MLDSA_PUB_SEED_SZ, aLen is not k * l * MLDSA_N, or no public key has been + imported yet. + \return PUBLIC_KEY_E if rho does not match the imported public key. + + \param [in,out] key Pointer to a wc_MlDsaKey holding the public key. + \param [in] a Expanded matrix A, k * l * MLDSA_N elements, caller owned. + \param [in] aLen Number of sword32 elements in a. + \param [in] rho Public seed the matrix was expanded from. + \param [in] rhoLen Length of rho; must be MLDSA_PUB_SEED_SZ. + + _Example_ + \code + wc_MlDsaKey key; + int res = 0; + + wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID); + wc_MlDsaKey_SetParams(&key, WC_ML_DSA_87); + wc_MlDsaKey_ImportPubRaw(&key, pub, pubLen); + if (wc_MlDsaKey_SetPrecompA(&key, matrixA, matrixALen, pub, + MLDSA_PUB_SEED_SZ) != 0) { + // handle error + } + wc_MlDsaKey_VerifyMu(&key, sig, sigLen, mu, muLen, &res); + \endcode + + \sa wc_MlDsaKey_VerifyMu + \sa wc_MlDsaKey_ImportPubRaw +*/ +int wc_MlDsaKey_SetPrecompA(wc_MlDsaKey* key, const sword32* a, word32 aLen, + const byte* rho, word32 rhoLen); + /*! \ingroup ML_DSA diff --git a/tests/api/test_mldsa.c b/tests/api/test_mldsa.c index b0d4b8c18dd..05be5e71fb5 100644 --- a/tests/api/test_mldsa.c +++ b/tests/api/test_mldsa.c @@ -31504,3 +31504,228 @@ int test_mldsa_cb_free(void) #endif return EXPECT_RESULT(); } + +/* Independent ExpandA for the precomputed-matrix test: FIPS 204 Algorithm 32 + * over the public SHAKE-128 API only, so it does not borrow the very code it + * is checking. Coefficients come out in the NTT domain, row-major (r, s). */ +#if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_VERIFY_PRECOMP_A) && \ + !defined(WOLFSSL_MLDSA_NO_VERIFY) +static int mldsa_test_expand_a(const byte* rho, byte k, byte l, sword32* a) +{ + wc_Shake shake; + byte seed[MLDSA_PUB_SEED_SZ + 2]; + byte h[168]; /* SHAKE-128 rate */ + int r; + int s; + int c; + int j; + int ret = 0; + sword32 t; + + XMEMCPY(seed, rho, MLDSA_PUB_SEED_SZ); + + for (r = 0; (ret == 0) && (r < (int)k); r++) { + seed[MLDSA_PUB_SEED_SZ + 1] = (byte)r; + for (s = 0; (ret == 0) && (s < (int)l); s++) { + seed[MLDSA_PUB_SEED_SZ + 0] = (byte)s; + + ret = wc_InitShake128(&shake, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_Shake128_Absorb(&shake, seed, (word32)sizeof(seed)); + } + for (j = 0; (ret == 0) && (j < MLDSA_N); ) { + ret = wc_Shake128_SqueezeBlocks(&shake, h, 1); + for (c = 0; (ret == 0) && (c < (int)sizeof(h)) && + (j < MLDSA_N); c += 3) { + t = (sword32)h[c] + ((sword32)h[c + 1] << 8) + + ((sword32)h[c + 2] << 16); + t &= 0x7fffff; + if (t < MLDSA_Q) { + a[(((r * (int)l) + s) * MLDSA_N) + j] = t; + j++; + } + } + } + wc_Shake128_Free(&shake); + } + } + + return ret; +} +#endif + +int test_wc_MlDsaKey_SetPrecompA(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_VERIFY_PRECOMP_A) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && !defined(WOLFSSL_MLDSA_NO_SIGN) && \ + !defined(WOLFSSL_MLDSA_NO_VERIFY) + wc_MlDsaKey key; + WC_RNG rng; + sword32* a = NULL; + byte* pub = NULL; + byte* sig = NULL; + byte* pub2 = NULL; + byte* sig2 = NULL; + word32 pubLen = 0; + word32 sigLen = 0; + word32 aLen = 0; + int pubLenI = 0; + int sigLenI = 0; + const byte msg[] = "precomputed matrix A"; + int res = 0; + byte level; + byte k; + byte l; + +#ifndef WOLFSSL_NO_ML_DSA_44 + level = WC_ML_DSA_44; k = 4; l = 4; +#elif !defined(WOLFSSL_NO_ML_DSA_65) + level = WC_ML_DSA_65; k = 6; l = 5; +#else + level = WC_ML_DSA_87; k = 8; l = 7; +#endif + aLen = (word32)k * (word32)l * MLDSA_N; + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&rng, 0, sizeof(rng)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(&key, level), 0); + ExpectIntEQ(wc_MlDsaKey_MakeKey(&key, &rng), 0); + + ExpectIntEQ(wc_MlDsaKey_GetPubLen(&key, &pubLenI), 0); + ExpectIntEQ(wc_MlDsaKey_GetSigLen(&key, &sigLenI), 0); + pubLen = (word32)pubLenI; + sigLen = (word32)sigLenI; + ExpectNotNull(pub = (byte*)XMALLOC(pubLen, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(sig = (byte*)XMALLOC(sigLen, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(a = (sword32*)XMALLOC(aLen * sizeof(sword32), NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectIntEQ(wc_MlDsaKey_ExportPubRaw(&key, pub, &pubLen), 0); + ExpectIntEQ(wc_MlDsaKey_SignCtx(&key, NULL, 0, sig, &sigLen, msg, + (word32)sizeof(msg), &rng), 0); + if (a != NULL) { + XMEMSET(a, 0, aLen * sizeof(sword32)); + } + ExpectIntEQ(mldsa_test_expand_a(pub, k, l, a), 0); + + /* A second, unrelated key at the SAME level. Re-importing it must not + * leave the first key's matrix in use - that is the case a level change + * would not catch. */ + ExpectNotNull(pub2 = (byte*)XMALLOC(pubLen, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(sig2 = (byte*)XMALLOC(sigLen, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + wc_MlDsaKey_Free(&key); + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(&key, level), 0); + ExpectIntEQ(wc_MlDsaKey_MakeKey(&key, &rng), 0); + pubLenI = (int)pubLen; + sigLenI = (int)sigLen; + ExpectIntEQ(wc_MlDsaKey_ExportPubRaw(&key, pub2, &pubLen), 0); + ExpectIntEQ(wc_MlDsaKey_SignCtx(&key, NULL, 0, sig2, &sigLen, msg, + (word32)sizeof(msg), &rng), 0); + + /* Verify-only key holding just the public half. */ + wc_MlDsaKey_Free(&key); + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(&key, level), 0); + + /* Rejected before a public key exists to bind against. */ + ExpectIntEQ(wc_MlDsaKey_SetPrecompA(&key, a, aLen, pub, + MLDSA_PUB_SEED_SZ), BAD_FUNC_ARG); + + ExpectIntEQ(wc_MlDsaKey_ImportPubRaw(&key, pub, pubLen), 0); + + /* Baseline: ordinary expansion verifies. */ + res = 0; + ExpectIntEQ(wc_MlDsaKey_VerifyCtx(&key, sig, sigLen, NULL, 0, msg, + (word32)sizeof(msg), &res), 0); + ExpectIntEQ(res, 1); + + /* Argument validation. */ + ExpectIntEQ(wc_MlDsaKey_SetPrecompA(NULL, a, aLen, pub, + MLDSA_PUB_SEED_SZ), BAD_FUNC_ARG); + ExpectIntEQ(wc_MlDsaKey_SetPrecompA(&key, NULL, aLen, pub, + MLDSA_PUB_SEED_SZ), BAD_FUNC_ARG); + ExpectIntEQ(wc_MlDsaKey_SetPrecompA(&key, a, aLen, NULL, + MLDSA_PUB_SEED_SZ), BAD_FUNC_ARG); + ExpectIntEQ(wc_MlDsaKey_SetPrecompA(&key, a, aLen - 1, pub, + MLDSA_PUB_SEED_SZ), BAD_FUNC_ARG); + ExpectIntEQ(wc_MlDsaKey_SetPrecompA(&key, a, aLen, pub, + MLDSA_PUB_SEED_SZ - 1), BAD_FUNC_ARG); + + /* rho that does not belong to this key is refused, not used. */ + if (pub != NULL) { + pub[0] ^= 0xFF; + ExpectIntEQ(wc_MlDsaKey_SetPrecompA(&key, a, aLen, pub, + MLDSA_PUB_SEED_SZ), PUBLIC_KEY_E); + pub[0] ^= 0xFF; + } + + /* Attached: must agree with ordinary expansion. */ + ExpectIntEQ(wc_MlDsaKey_SetPrecompA(&key, a, aLen, pub, + MLDSA_PUB_SEED_SZ), 0); + res = 0; + ExpectIntEQ(wc_MlDsaKey_VerifyCtx(&key, sig, sigLen, NULL, 0, msg, + (word32)sizeof(msg), &res), 0); + ExpectIntEQ(res, 1); + + /* A wrong matrix must not verify - proves the attached one is really used + * rather than quietly re-expanded. */ + if (a != NULL) { + a[0] ^= 0x01; + res = 1; + ExpectIntEQ(wc_MlDsaKey_VerifyCtx(&key, sig, sigLen, NULL, 0, msg, + (word32)sizeof(msg), &res), 0); + ExpectIntEQ(res, 0); + a[0] ^= 0x01; + } + + /* Tampered signature still rejected with the matrix attached. */ + if (sig != NULL) { + sig[0] ^= 0x01; + res = 1; + ExpectIntEQ(wc_MlDsaKey_VerifyCtx(&key, sig, sigLen, NULL, 0, msg, + (word32)sizeof(msg), &res), 0); + ExpectIntEQ(res, 0); + sig[0] ^= 0x01; + } + + /* Same-level re-import: the matrix bound to the first key must not be + * applied to the second. If it were, this verify would fail. */ + ExpectIntEQ(wc_MlDsaKey_ImportPubRaw(&key, pub2, pubLen), 0); + res = 0; + ExpectIntEQ(wc_MlDsaKey_VerifyCtx(&key, sig2, sigLen, NULL, 0, msg, + (word32)sizeof(msg), &res), 0); + ExpectIntEQ(res, 1); + + /* Back to the first key and matrix for the remaining checks. */ + ExpectIntEQ(wc_MlDsaKey_ImportPubRaw(&key, pub, pubLen), 0); + ExpectIntEQ(wc_MlDsaKey_SetPrecompA(&key, a, aLen, pub, + MLDSA_PUB_SEED_SZ), 0); + + /* Lifecycle: changing level drops the binding, and verification falls + * back to expanding rather than indexing a wrongly sized matrix. */ +#if !defined(WOLFSSL_NO_ML_DSA_44) && !defined(WOLFSSL_NO_ML_DSA_87) + ExpectIntEQ(wc_MlDsaKey_SetParams(&key, WC_ML_DSA_87), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(&key, level), 0); + ExpectIntEQ(wc_MlDsaKey_ImportPubRaw(&key, pub, pubLen), 0); + res = 0; + ExpectIntEQ(wc_MlDsaKey_VerifyCtx(&key, sig, sigLen, NULL, 0, msg, + (word32)sizeof(msg), &res), 0); + ExpectIntEQ(res, 1); +#endif + + wc_MlDsaKey_Free(&key); + wc_FreeRng(&rng); + XFREE(pub, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(pub2, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(sig2, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(a, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_mldsa.h b/tests/api/test_mldsa.h index df6c6bb1f00..d3cd09b78a7 100644 --- a/tests/api/test_mldsa.h +++ b/tests/api/test_mldsa.h @@ -70,6 +70,7 @@ int test_wc_MldsaDecisionCoverage2(void); int test_wc_MldsaDerDecisionCoverage(void); int test_mldsa_cb_free(void); +int test_wc_MlDsaKey_SetPrecompA(void); #define TEST_MLDSA_DECLS \ TEST_DECL_GROUP("mldsa", test_mldsa), \ TEST_DECL_GROUP("mldsa", test_mldsa_sign_pubonly_fails), \ @@ -101,6 +102,7 @@ int test_mldsa_cb_free(void); TEST_DECL_GROUP("mldsa", test_mldsa_legacy_shim), \ TEST_DECL_GROUP("mldsa", test_wc_MldsaDecisionCoverage2), \ TEST_DECL_GROUP("mldsa", test_wc_MldsaDerDecisionCoverage), \ - TEST_DECL_GROUP("mldsa", test_mldsa_cb_free) + TEST_DECL_GROUP("mldsa", test_mldsa_cb_free), \ + TEST_DECL_GROUP("mldsa", test_wc_MlDsaKey_SetPrecompA) #endif /* WOLFCRYPT_TEST_MLDSA_H */ diff --git a/wolfcrypt/src/sha3.c b/wolfcrypt/src/sha3.c index 817839c0164..15f6ba2a2ed 100644 --- a/wolfcrypt/src/sha3.c +++ b/wolfcrypt/src/sha3.c @@ -831,16 +831,19 @@ void BlockSha3(word64* s) #ifdef WC_SHA3_SW_KECCAK #if defined(BIG_ENDIAN_ORDER) || defined(WOLFSSL_WIDE_BYTE) +/* Mask each cell to an octet: where CHAR_BIT != 8 a cell can hold more than an + * octet and would bleed into the neighbouring lane bits. Matches + * readUnalignedWord32/64() in misc.c. No-op where a byte is an octet. */ static WC_INLINE word64 Load64Unaligned(const unsigned char *a) { - return ((word64)a[0] << 0) | - ((word64)a[1] << 8) | - ((word64)a[2] << 16) | - ((word64)a[3] << 24) | - ((word64)a[4] << 32) | - ((word64)a[5] << 40) | - ((word64)a[6] << 48) | - ((word64)a[7] << 56); + return ((word64)(a[0] & 0xFF) << 0) | + ((word64)(a[1] & 0xFF) << 8) | + ((word64)(a[2] & 0xFF) << 16) | + ((word64)(a[3] & 0xFF) << 24) | + ((word64)(a[4] & 0xFF) << 32) | + ((word64)(a[5] & 0xFF) << 40) | + ((word64)(a[6] & 0xFF) << 48) | + ((word64)(a[7] & 0xFF) << 56); } /* Convert the array of bytes, in little-endian order, to a 64-bit integer. @@ -853,8 +856,9 @@ static word64 Load64BitLittleEndian(const byte* a) word64 n = 0; int i; + /* Masked as in Load64Unaligned() above. */ for (i = 0; i < 8; i++) - n |= (word64)a[i] << (8 * i); + n |= (word64)(a[i] & 0xFF) << (8 * i); return n; } diff --git a/wolfcrypt/src/sp_arm32.c b/wolfcrypt/src/sp_arm32.c index a2f5943f664..1b85225729a 100644 --- a/wolfcrypt/src/sp_arm32.c +++ b/wolfcrypt/src/sp_arm32.c @@ -277,7 +277,7 @@ static void sp_2048_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -19260,7 +19260,7 @@ static void sp_3072_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -47510,7 +47510,7 @@ static void sp_4096_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -68387,7 +68387,7 @@ static void sp_256_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -91534,7 +91534,7 @@ static void sp_384_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -118408,7 +118408,7 @@ static void sp_521_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -148229,7 +148229,7 @@ static void sp_1024_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } diff --git a/wolfcrypt/src/sp_arm64.c b/wolfcrypt/src/sp_arm64.c index eac5958a961..3b31645af79 100644 --- a/wolfcrypt/src/sp_arm64.c +++ b/wolfcrypt/src/sp_arm64.c @@ -343,7 +343,7 @@ static void sp_2048_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -7380,7 +7380,7 @@ static void sp_3072_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -17128,7 +17128,7 @@ static void sp_4096_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -22680,7 +22680,7 @@ static void sp_256_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -43760,7 +43760,7 @@ static void sp_384_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -71034,7 +71034,7 @@ static void sp_521_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -115626,7 +115626,7 @@ static void sp_1024_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } diff --git a/wolfcrypt/src/sp_armthumb.c b/wolfcrypt/src/sp_armthumb.c index 304a1c44b4f..0a77bddf1c4 100644 --- a/wolfcrypt/src/sp_armthumb.c +++ b/wolfcrypt/src/sp_armthumb.c @@ -277,7 +277,7 @@ static void sp_2048_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -30683,7 +30683,7 @@ static void sp_3072_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -83992,7 +83992,7 @@ static void sp_4096_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -98335,7 +98335,7 @@ static void sp_256_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -109837,7 +109837,7 @@ static void sp_384_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -120907,7 +120907,7 @@ static void sp_521_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -202867,7 +202867,7 @@ static void sp_1024_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } diff --git a/wolfcrypt/src/sp_c32.c b/wolfcrypt/src/sp_c32.c index 3dbf45846fc..b920f7c57d5 100644 --- a/wolfcrypt/src/sp_c32.c +++ b/wolfcrypt/src/sp_c32.c @@ -372,7 +372,7 @@ static void sp_2048_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -434,14 +434,17 @@ static void sp_2048_to_bin_72(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<71 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint32)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint32)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 29) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -5451,7 +5454,7 @@ static void sp_3072_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -5513,14 +5516,17 @@ static void sp_3072_to_bin_106(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<106 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint32)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint32)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 29) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -9072,7 +9078,7 @@ static void sp_3072_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -9134,14 +9140,17 @@ static void sp_3072_to_bin_112(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<110 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint32)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint32)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 28) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -13322,7 +13331,7 @@ static void sp_4096_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -13384,14 +13393,17 @@ static void sp_4096_to_bin_142(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<142 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint32)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint32)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 29) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -16850,7 +16862,7 @@ static void sp_4096_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -16912,14 +16924,17 @@ static void sp_4096_to_bin_162(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<158 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint32)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint32)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 26) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -21517,7 +21532,7 @@ static void sp_256_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -26071,14 +26086,17 @@ static void sp_256_to_bin_9(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<9 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint32)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint32)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 29) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -28727,7 +28745,7 @@ static void sp_384_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -33983,14 +34001,17 @@ static void sp_384_to_bin_15(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<15 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint32)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint32)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 26) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -36314,7 +36335,7 @@ static void sp_521_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -41959,14 +41980,17 @@ static void sp_521_to_bin_21(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<21 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint32)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint32)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 25) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -45109,7 +45133,7 @@ static void sp_1024_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } diff --git a/wolfcrypt/src/sp_c64.c b/wolfcrypt/src/sp_c64.c index 3ef4549c470..37643067838 100644 --- a/wolfcrypt/src/sp_c64.c +++ b/wolfcrypt/src/sp_c64.c @@ -299,7 +299,7 @@ static void sp_2048_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -361,14 +361,17 @@ static void sp_2048_to_bin_34(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<34 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint64)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint64)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 61) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -3894,7 +3897,7 @@ static void sp_2048_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -3956,14 +3959,17 @@ static void sp_2048_to_bin_36(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<36 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint64)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint64)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 57) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -7474,7 +7480,7 @@ static void sp_3072_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -7536,14 +7542,17 @@ static void sp_3072_to_bin_52(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<52 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint64)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint64)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 60) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -11000,7 +11009,7 @@ static void sp_3072_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -11062,14 +11071,17 @@ static void sp_3072_to_bin_54(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<54 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint64)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint64)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 57) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -14765,7 +14777,7 @@ static void sp_4096_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -14827,14 +14839,17 @@ static void sp_4096_to_bin_70(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<70 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint64)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint64)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 59) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -18189,7 +18204,7 @@ static void sp_4096_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -18251,14 +18266,17 @@ static void sp_4096_to_bin_78(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<78 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint64)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint64)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 53) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -22391,7 +22409,7 @@ static void sp_256_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -26813,14 +26831,17 @@ static void sp_256_to_bin_5(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<5 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint64)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint64)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 52) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -29118,7 +29139,7 @@ static void sp_384_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -34148,14 +34169,17 @@ static void sp_384_to_bin_7(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<7 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint64)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint64)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 55) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -36544,7 +36568,7 @@ static void sp_521_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -41452,14 +41476,17 @@ static void sp_521_to_bin_9(sp_digit* r, byte* a) a[j] = 0; for (i=0; i<9 && j>=0; i++) { b = 0; + /* Mask to an octet: a (byte) cast does not truncate where CHAR_BIT is + * not 8 (e.g. TI C2000 C28x), which would leave high bits in the + * output cell. No-op on 8-bit-byte targets. */ /* lint allow cast of mismatch sp_digit and int */ - a[j--] |= (byte)((sp_uint64)r[i] << s); /*lint !e9033*/ + a[j--] |= (byte)(((sp_uint64)r[i] << s) & 0xFF); /*lint !e9033*/ b += 8 - s; if (j < 0) { break; } while (b < 58) { - a[j--] = (byte)(r[i] >> b); + a[j--] = (byte)((r[i] >> b) & 0xFF); b += 8; if (j < 0) { break; @@ -44407,7 +44434,7 @@ static void sp_1024_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } diff --git a/wolfcrypt/src/sp_cortexm.c b/wolfcrypt/src/sp_cortexm.c index 86f3570eeaa..8f8ca66052a 100644 --- a/wolfcrypt/src/sp_cortexm.c +++ b/wolfcrypt/src/sp_cortexm.c @@ -277,7 +277,7 @@ static void sp_2048_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -10680,7 +10680,7 @@ static void sp_3072_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -24049,7 +24049,7 @@ static void sp_4096_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -34427,7 +34427,7 @@ static void sp_256_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -46079,7 +46079,7 @@ static void sp_384_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -58040,7 +58040,7 @@ static void sp_521_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -72919,7 +72919,7 @@ static void sp_1024_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } diff --git a/wolfcrypt/src/sp_riscv64.c b/wolfcrypt/src/sp_riscv64.c index f58b2edf27c..1f9309b0791 100644 --- a/wolfcrypt/src/sp_riscv64.c +++ b/wolfcrypt/src/sp_riscv64.c @@ -289,7 +289,7 @@ static void sp_2048_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -16707,7 +16707,7 @@ static void sp_3072_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -29093,7 +29093,7 @@ static void sp_4096_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -37505,7 +37505,7 @@ static void sp_256_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -46068,7 +46068,7 @@ static void sp_384_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -57944,7 +57944,7 @@ static void sp_521_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -72990,7 +72990,7 @@ static void sp_1024_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } diff --git a/wolfcrypt/src/sp_x86_64.c b/wolfcrypt/src/sp_x86_64.c index 220550e4e6c..ca0abb541e7 100644 --- a/wolfcrypt/src/sp_x86_64.c +++ b/wolfcrypt/src/sp_x86_64.c @@ -266,7 +266,7 @@ static void sp_2048_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -3115,7 +3115,7 @@ static void sp_3072_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -5899,7 +5899,7 @@ static void sp_4096_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -8012,7 +8012,7 @@ static void sp_256_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -26942,7 +26942,7 @@ static void sp_384_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -51745,7 +51745,7 @@ static void sp_521_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } @@ -92935,7 +92935,7 @@ static void sp_1024_from_mp(sp_digit* r, int size, const mp_int* a) /* Recompute mask for the next read index, then advance o by -mask * (0 or 1) so it only moves while another digit remains. */ mask = (mp_digit)0 - (((mp_digit)(i + 1U) - (mp_digit)(unsigned int)a->used) >> - (sizeof(mp_digit) * 8 - 1)); + (sizeof(mp_digit) * CHAR_BIT - 1)); o += (int)((mp_digit)0 - mask); } diff --git a/wolfcrypt/src/wc_mldsa.c b/wolfcrypt/src/wc_mldsa.c index 65477170254..dc41bd67b58 100644 --- a/wolfcrypt/src/wc_mldsa.c +++ b/wolfcrypt/src/wc_mldsa.c @@ -2492,18 +2492,21 @@ static void mldsa_encode_w1_88_c(const sword32* w1, byte* w1e) ((word32)w1[j+14] << 20) | ((word32)w1[j+15] << 26))); #else - w1e[ 0] = (byte)( w1[j+ 0] | (w1[j+ 1] << 6)); - w1e[ 1] = (byte)((w1[j+ 1] >> 2) | (w1[j+ 2] << 4)); - w1e[ 2] = (byte)((w1[j+ 2] >> 4) | (w1[j+ 3] << 2)); - w1e[ 3] = (byte)( w1[j+ 4] | (w1[j+ 5] << 6)); - w1e[ 4] = (byte)((w1[j+ 5] >> 2) | (w1[j+ 6] << 4)); - w1e[ 5] = (byte)((w1[j+ 6] >> 4) | (w1[j+ 7] << 2)); - w1e[ 6] = (byte)( w1[j+ 8] | (w1[j+ 9] << 6)); - w1e[ 7] = (byte)((w1[j+ 9] >> 2) | (w1[j+10] << 4)); - w1e[ 8] = (byte)((w1[j+10] >> 4) | (w1[j+11] << 2)); - w1e[ 9] = (byte)( w1[j+12] | (w1[j+13] << 6)); - w1e[10] = (byte)((w1[j+13] >> 2) | (w1[j+14] << 4)); - w1e[11] = (byte)((w1[j+14] >> 4) | (w1[j+15] << 2)); + /* 6-bit values: a shifted operand reaches 43 << 6 = 2752, which a + * (byte) cast does not reduce to an octet where CHAR_BIT != 8. The + * 4-bit packer below cannot exceed an octet and needs no mask. */ + w1e[ 0] = WC_OCTET( w1[j+ 0] | (w1[j+ 1] << 6)); + w1e[ 1] = WC_OCTET((w1[j+ 1] >> 2) | (w1[j+ 2] << 4)); + w1e[ 2] = WC_OCTET((w1[j+ 2] >> 4) | (w1[j+ 3] << 2)); + w1e[ 3] = WC_OCTET( w1[j+ 4] | (w1[j+ 5] << 6)); + w1e[ 4] = WC_OCTET((w1[j+ 5] >> 2) | (w1[j+ 6] << 4)); + w1e[ 5] = WC_OCTET((w1[j+ 6] >> 4) | (w1[j+ 7] << 2)); + w1e[ 6] = WC_OCTET( w1[j+ 8] | (w1[j+ 9] << 6)); + w1e[ 7] = WC_OCTET((w1[j+ 9] >> 2) | (w1[j+10] << 4)); + w1e[ 8] = WC_OCTET((w1[j+10] >> 4) | (w1[j+11] << 2)); + w1e[ 9] = WC_OCTET( w1[j+12] | (w1[j+13] << 6)); + w1e[10] = WC_OCTET((w1[j+13] >> 2) | (w1[j+14] << 4)); + w1e[11] = WC_OCTET((w1[j+14] >> 4) | (w1[j+15] << 2)); #endif /* Move to next place to encode to. */ w1e += MLDSA_Q_HI_88_ENC_BITS * 2; @@ -9302,7 +9305,11 @@ static int mldsa_sign_with_seed_mu(wc_MlDsaKey* key, /* Allocate memory for large intermediates. */ #ifdef WC_MLDSA_CACHE_MATRIX_A #ifndef WC_MLDSA_FIXED_ARRAY - if ((ret == 0) && (key->a == NULL)) { + if ((ret == 0) && (key->a == NULL) +#ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A + && (aPre == NULL) +#endif + ) { key->a = (sword32*)XMALLOC((size_t)params->aSz, key->heap, DYNAMIC_TYPE_MLDSA); if (key->a == NULL) { @@ -10588,6 +10595,34 @@ static void mldsa_make_pub_vec(wc_MlDsaKey* key, sword32* t1) * @return MEMORY_E when memory allocation fails. * @return Other negative when an error occurs. */ +#ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A +/* Return the attached matrix A only while it still matches the key. + * + * The setter checks rho once, but SetParams(), a public-key import or key + * generation can replace the key underneath a matrix that is already + * attached. Indexing a level-44 matrix with level-87 k and l would read off + * the end of the caller's array, so re-derive validity here rather than trust + * the one-time check. A mismatch simply falls back to expanding A, which is + * always correct - only slower. */ +static const sword32* mldsa_precomp_a(const wc_MlDsaKey* key) +{ + const wc_MlDsaParams* params = key->params; + + if ((key->aPre == NULL) || (params == NULL) || (!key->pubKeySet)) { + return NULL; + } + if (key->aPreLen != + (word32)params->k * (word32)params->l * MLDSA_N) { + return NULL; + } + if (XMEMCMP(key->aPreRho, key->p, MLDSA_PUB_SEED_SZ) != 0) { + return NULL; + } + + return key->aPre; +} +#endif /* WOLFSSL_MLDSA_VERIFY_PRECOMP_A */ + static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, const byte* sig, word32 sigLen, int* res) { @@ -10604,6 +10639,12 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, sword32* z = NULL; sword32* w = NULL; sword32* t1c = NULL; +#ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A + /* aPre is the caller's flash-resident matrix when usable; aRead is what + * the multiply consumes, so the expansion and its buffer drop out. */ + const sword32* aPre = mldsa_precomp_a(key); +#endif + const sword32* aRead = NULL; byte commit_calc[MLDSA_TR_SZ]; byte* w1e = NULL; int valid = 0; @@ -10660,8 +10701,13 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, allocSz = (unsigned int)MLDSA_POLY_SIZE + params->s1Sz + params->s2Sz + params->s2Sz; #ifndef WC_MLDSA_CACHE_MATRIX_A - /* a */ - allocSz += params->aSz; + /* a - not needed when the caller supplied the matrix. */ +#ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A + if (aPre == NULL) +#endif + { + allocSz += params->aSz; + } #endif z = (sword32*)XMALLOC(allocSz, key->heap, DYNAMIC_TYPE_MLDSA); @@ -10702,18 +10748,30 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, mldsa_make_pub_vec(key, t1); } -#ifdef WC_MLDSA_CACHE_MATRIX_A - /* Check that we haven't already cached the matrix A. */ - if (!key->aSet) +#ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A + if (aPre != NULL) { + /* A was expanded off target and lives in flash: no allocation and + * no SHAKE128 rejection sampling. */ + aRead = aPre; + } + else #endif { - /* Step 5: Expand pub seed to compute matrix A. */ - ret = mldsa_expand_a(&key->shake, pub_seed, params->k, - params->l, a, key->heap); #ifdef WC_MLDSA_CACHE_MATRIX_A - /* Whether we have cached A is dependent on success of operation. */ - key->aSet = (ret == 0); + /* Check that we haven't already cached the matrix A. */ + if (!key->aSet) #endif + { + /* Step 5: Expand pub seed to compute matrix A. */ + ret = mldsa_expand_a(&key->shake, pub_seed, params->k, + params->l, a, key->heap); +#ifdef WC_MLDSA_CACHE_MATRIX_A + /* Whether we have cached A is dependent on success of + * operation. */ + key->aSet = (ret == 0); +#endif + } + aRead = a; } } if ((ret == 0) && valid) { @@ -10724,7 +10782,7 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, if ((ret == 0) && valid) { /* Step 10: w = NTT-1(A o NTT(z) - NTT(c) o NTT(t1)) */ mldsa_vec_ntt_full(z, params->l); - mldsa_matrix_mul(w, a, z, params->k, params->l); + mldsa_matrix_mul(w, aRead, z, params->k, params->l); #ifdef WOLFSSL_MLDSA_SMALL mldsa_vec_red(w, params->k); #endif @@ -10757,7 +10815,14 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, const byte* ze = sig + params->lambda / 4; const byte* h = ze + params->zEncSz; sword32* t1 = NULL; - sword32* a = NULL; + /* aBuf is the scratch the rejection sampler writes; a is what the pointwise + * loops read. With a precomputed matrix they differ - a points straight at + * the caller's flash-resident A and aBuf is unused. */ + sword32* aBuf = NULL; + const sword32* a = NULL; +#ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A + const sword32* aPre = mldsa_precomp_a(key); +#endif sword32* c = NULL; sword32* z = NULL; sword32* w = NULL; @@ -10812,7 +10877,7 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, t1 = w + MLDSA_N; block = (byte*)(t1 + MLDSA_N); w1e = block + MLDSA_REJ_NTT_POLY_H_SIZE; - a = t1; + aBuf = t1; #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 t64 = (sword64*)(w1e + params->w1EncSz); #endif @@ -10825,7 +10890,7 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, w = key->w; t1 = key->t1; w1e = key->w1e; - a = t1; + aBuf = t1; #ifdef WOLFSSL_MLDSA_SMALL_MEM_POLY64 t64 = key->t64; #endif @@ -10942,11 +11007,25 @@ static int mldsa_verify_with_mu(wc_MlDsaKey* key, const byte* mu, zt = z; #endif /* Step 3: Create polynomial from hashing seed. */ + #ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A + if (aPre != NULL) { + /* A was expanded off-target and lives in flash: skip the + * SHAKE128 rejection sampling entirely. */ + a = aPre + + ((word32)r * (word32)params->l + (word32)s) * MLDSA_N; + } + else + #endif + { #ifdef WOLFSSL_MLDSA_VERIFY_NO_MALLOC - ret = mldsa_rej_ntt_poly_ex(&key->shake, seed, a, key->h); + ret = mldsa_rej_ntt_poly_ex(&key->shake, seed, aBuf, + key->h); #else - ret = mldsa_rej_ntt_poly_ex(&key->shake, seed, a, block); + ret = mldsa_rej_ntt_poly_ex(&key->shake, seed, aBuf, + block); #endif + a = aBuf; + } /* Step 10: w = A o NTT(z) - NTT(c) o NTT(t1) */ #ifndef WOLFSSL_MLDSA_SMALL_MEM_POLY64 @@ -11809,6 +11888,42 @@ int wc_MlDsaKey_VerifyCtxHash(wc_MlDsaKey* key, const byte* sig, word32 sigLen, return ret; } +#ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A +/* Attach a precomputed matrix A. See wc_mldsa.h for the contract and the + * security requirement. */ +int wc_MlDsaKey_SetPrecompA(wc_MlDsaKey* key, const sword32* a, word32 aLen, + const byte* rho, word32 rhoLen) +{ + if ((key == NULL) || (a == NULL) || (rho == NULL) || + (key->params == NULL)) { + return BAD_FUNC_ARG; + } + if (rhoLen != MLDSA_PUB_SEED_SZ) { + return BAD_FUNC_ARG; + } + /* Row-major k x l polynomials of MLDSA_N coefficients. */ + if (aLen != (word32)key->params->k * (word32)key->params->l * MLDSA_N) { + return BAD_FUNC_ARG; + } + /* A is a function of rho alone. Reject a matrix built for a different + * key rather than silently verifying against the wrong one. key->p only + * holds rho once a public key has been imported, so require that first - + * BAD_FUNC_ARG, as documented in wc_mldsa.h. */ + if (!key->pubKeySet) { + return BAD_FUNC_ARG; + } + if (XMEMCMP(rho, key->p, MLDSA_PUB_SEED_SZ) != 0) { + return PUBLIC_KEY_E; + } + + key->aPre = a; + key->aPreLen = aLen; + XMEMCPY(key->aPreRho, rho, MLDSA_PUB_SEED_SZ); + + return 0; +} +#endif /* WOLFSSL_MLDSA_VERIFY_PRECOMP_A */ + /* Verify using the ML-DSA internal interface with a pre-computed mu value. * * This implements ML-DSA.Verify_internal from FIPS 204 Section 6.3. @@ -12061,6 +12176,14 @@ int wc_MlDsaKey_SetParams(wc_MlDsaKey* key, byte level) } if (ret == 0) { /* Clear any cached items. */ +#ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A + /* The attached matrix was sized for the old level. mldsa_precomp_a() + * would reject it anyway, but drop it here so the borrowed pointer + * does not outlive the key it was bound to. */ + key->aPre = NULL; + key->aPreLen = 0; + XMEMSET(key->aPreRho, 0, sizeof(key->aPreRho)); +#endif #ifndef WC_MLDSA_FIXED_ARRAY #ifdef WC_MLDSA_CACHE_MATRIX_A XFREE(key->a, key->heap, DYNAMIC_TYPE_MLDSA); diff --git a/wolfcrypt/src/wc_port.c b/wolfcrypt/src/wc_port.c index 8b0f7bed3b8..92f1ef7c81c 100644 --- a/wolfcrypt/src/wc_port.c +++ b/wolfcrypt/src/wc_port.c @@ -1651,6 +1651,57 @@ char* wc_strdup_ex(const char *src, int memType) { } #endif +#ifdef WOLFSSL_WIDE_BYTE + +/* Packed octet stream -> one octet per byte cell. See WC_OCTETS_PER_BYTE in + * types.h. in holds the packed stream, low octet of a cell first, and must not + * overlap out. Returns 0, BAD_FUNC_ARG on NULL, BUFFER_E if either buffer is + * short. */ +int wc_UnpackOctets(byte* out, word32 outSz, const byte* in, word32 inSz, + word32 octetSz) +{ + word32 i; + + if ((out == NULL) || (in == NULL)) { + return BAD_FUNC_ARG; + } + if ((outSz < octetSz) || (inSz < WC_PACKED_CELLS(octetSz))) { + return BUFFER_E; + } + + for (i = 0; i < octetSz; i++) { + out[i] = WC_OCTET((word32)in[i / WC_OCTETS_PER_BYTE] >> + ((i % WC_OCTETS_PER_BYTE) * 8)); + } + + return 0; +} + +/* Inverse of wc_UnpackOctets(), for flash or a byte-oriented peripheral. in + * holds one octet per cell and must not overlap out. */ +int wc_PackOctets(byte* out, word32 outSz, const byte* in, word32 inSz, + word32 octetSz) +{ + word32 i; + + if ((out == NULL) || (in == NULL)) { + return BAD_FUNC_ARG; + } + if ((outSz < WC_PACKED_CELLS(octetSz)) || (inSz < octetSz)) { + return BUFFER_E; + } + + XMEMSET(out, 0, WC_PACKED_CELLS(octetSz)); /* zero-fill partial tail */ + for (i = 0; i < octetSz; i++) { + out[i / WC_OCTETS_PER_BYTE] |= (byte)((word32)WC_OCTET(in[i]) << + ((i % WC_OCTETS_PER_BYTE) * 8)); + } + + return 0; +} + +#endif /* WOLFSSL_WIDE_BYTE */ + #ifdef WOLFSSL_ATOMIC_OPS #if defined(WOLFSSL_USER_DEFINED_ATOMICS) diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index efdfc5b68df..5602966c7ed 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -780,6 +780,7 @@ typedef struct testVector { WOLFSSL_TEST_SUBROUTINE wc_test_ret_t macro_test(void); WOLFSSL_TEST_SUBROUTINE wc_test_ret_t error_test(void); +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t octets_test(void); WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base64_test(void); WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base16_test(void); WOLFSSL_TEST_SUBROUTINE wc_test_ret_t asn_test(void); @@ -2449,6 +2450,11 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\ else TEST_PASS("MEMORY test passed!\n"); + if ( (ret = octets_test()) != 0) + TEST_FAIL("octets test failed!\n", ret); + else + TEST_PASS("octets test passed!\n"); + #ifndef NO_CODING if ( (ret = base64_test()) != 0) TEST_FAIL("base64 test failed!\n", ret); @@ -4287,6 +4293,101 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t error_test(void) return 0; } +/* Octet-layout helpers. WC_PACKED_CELLS() is checked everywhere; the + * pack/unpack calls exist only where a byte cell is wider than an octet, and + * are gated to match. */ +WOLFSSL_TEST_SUBROUTINE wc_test_ret_t octets_test(void) +{ +#ifdef WOLFSSL_WIDE_BYTE + byte src[65]; + byte packed[65]; + byte back[65]; + word32 i; + word32 expect; + wc_test_ret_t ret; +#endif + + WOLFSSL_ENTER("octets_test"); + + /* Rounds up without wrapping near WORD32_MAX. */ + if (WC_PACKED_CELLS(0) != 0) + return WC_TEST_RET_ENC_NC; + if (WC_PACKED_CELLS(1) != 1) + return WC_TEST_RET_ENC_NC; + if (WC_PACKED_CELLS(0xFFFFFFFFU) < (0xFFFFFFFFU / WC_OCTETS_PER_BYTE)) + return WC_TEST_RET_ENC_NC; + +#ifdef WOLFSSL_WIDE_BYTE + for (i = 0; i < (word32)sizeof(src); i++) { + src[i] = (byte)((i * 7 + 1) & 0xFF); + } + + /* Odd octet count so a partial trailing cell is covered. */ + ret = wc_PackOctets(packed, (word32)sizeof(packed), src, + (word32)sizeof(src), (word32)sizeof(src)); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + ret = wc_UnpackOctets(back, (word32)sizeof(back), packed, + (word32)sizeof(packed), (word32)sizeof(src)); + if (ret != 0) + return WC_TEST_RET_ENC_EC(ret); + if (XMEMCMP(src, back, sizeof(src)) != 0) + return WC_TEST_RET_ENC_NC; + + /* Cell 0 carries the first WC_OCTETS_PER_BYTE octets, low octet first. */ + expect = 0; + for (i = 0; i < WC_OCTETS_PER_BYTE; i++) { + expect |= (word32)src[i] << (8 * i); + } + if ((word32)packed[0] != expect) + return WC_TEST_RET_ENC_NC; + + /* Zero length is a no-op, not an error. */ + if (wc_PackOctets(packed, (word32)sizeof(packed), src, + (word32)sizeof(src), 0) != 0) + return WC_TEST_RET_ENC_NC; + if (wc_UnpackOctets(back, (word32)sizeof(back), packed, + (word32)sizeof(packed), 0) != 0) + return WC_TEST_RET_ENC_NC; + + if (wc_PackOctets(NULL, (word32)sizeof(packed), src, + (word32)sizeof(src), (word32)sizeof(src)) != BAD_FUNC_ARG) + return WC_TEST_RET_ENC_NC; + if (wc_UnpackOctets(back, (word32)sizeof(back), NULL, + (word32)sizeof(packed), (word32)sizeof(src)) != BAD_FUNC_ARG) + return WC_TEST_RET_ENC_NC; + + /* Source too short is now rejected, not read past. */ + if (wc_UnpackOctets(back, (word32)sizeof(back), packed, + WC_PACKED_CELLS(sizeof(src)) - 1, + (word32)sizeof(src)) != BUFFER_E) + return WC_TEST_RET_ENC_NC; + if (wc_PackOctets(packed, (word32)sizeof(packed), src, + (word32)sizeof(src) - 1, (word32)sizeof(src)) != BUFFER_E) + return WC_TEST_RET_ENC_NC; + + /* Exactly-sized destinations must succeed. */ + if (wc_PackOctets(packed, WC_PACKED_CELLS(sizeof(src)), src, + (word32)sizeof(src), (word32)sizeof(src)) != 0) + return WC_TEST_RET_ENC_NC; + if (wc_UnpackOctets(back, (word32)sizeof(src), packed, + WC_PACKED_CELLS(sizeof(src)), (word32)sizeof(src)) != 0) + return WC_TEST_RET_ENC_NC; + if (XMEMCMP(src, back, sizeof(src)) != 0) + return WC_TEST_RET_ENC_NC; + + /* Destination too small, each side of the size relation. */ + if (wc_UnpackOctets(back, (word32)sizeof(src) - 1, packed, + (word32)sizeof(packed), (word32)sizeof(src)) != BUFFER_E) + return WC_TEST_RET_ENC_NC; + if (wc_PackOctets(packed, WC_PACKED_CELLS(sizeof(src)) - 1, src, + (word32)sizeof(src), (word32)sizeof(src)) != BUFFER_E) + return WC_TEST_RET_ENC_NC; +#endif /* WOLFSSL_WIDE_BYTE */ + + return 0; +} + #ifndef NO_CODING WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base64_test(void) diff --git a/wolfcrypt/test/test.h b/wolfcrypt/test/test.h index ab1f22e85b7..389e3c3454c 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -116,6 +116,7 @@ wc_static_assert(-(long)MIN_CODE_E < 0x7ffL); extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t macro_test(void); extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t error_test(void); +extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t octets_test(void); extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base64_test(void); extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base16_test(void); extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t asn_test(void); diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index 48f3364813f..c0821f93360 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -329,6 +329,23 @@ typedef const char wcchar[]; * the stored octet. Used by the SHA-2/3, Hash-DRBG, base64 and ML-DSA packers. */ #define WC_OCTET(x) ((byte)((x) & 0xFF)) +/* Octet representation at the API boundary. + * + * Every wolfCrypt byte* buffer holds ONE octet per 'byte' cell. Where + * CHAR_BIT != 8 (e.g. the TI C2000 C28x, a 16-bit cell) that costs twice the + * RAM but leaves the octet values unchanged. Data from outside the CPU - + * flash, a serial link, a host tool - is instead PACKED: WC_OCTETS_PER_BYTE + * octets per cell, low octet first. Convert at the boundary with + * wc_UnpackOctets()/wc_PackOctets(), declared only where a cell is wider than + * an octet - elsewhere the two layouts coincide and there is nothing to do. */ +#define WC_OCTETS_PER_BYTE ((word32)(CHAR_BIT / 8)) +/* Cells needed to hold octetSz octets packed. Divides before adding the + * round-up so a near-WORD32_MAX octetSz cannot wrap. octetSz is evaluated + * more than once - do not pass an expression with side effects. */ +#define WC_PACKED_CELLS(octetSz) \ + (((word32)(octetSz) / WC_OCTETS_PER_BYTE) + \ + ((((word32)(octetSz)) % WC_OCTETS_PER_BYTE) != 0U ? 1U : 0U)) + #if defined(HAVE___UINT128_T) && !defined(NO_INT128) #ifndef WOLFSSL_UINT128_T_DEFINED #ifdef __SIZEOF_INT128__ @@ -1287,6 +1304,19 @@ binding for XSNPRINTF #endif #endif /* STRING_USER */ +#ifdef WOLFSSL_WIDE_BYTE +/* Packed octet stream -> one octet per byte cell. All sizes are in byte cells; + * out needs octetSz, in needs WC_PACKED_CELLS(octetSz), and they must not + * overlap. Returns 0, BAD_FUNC_ARG on NULL, BUFFER_E when either buffer is + * short. Declared only where a cell is wider than an octet; elsewhere the two + * layouts are identical and there is nothing to convert. */ +WOLFSSL_API int wc_UnpackOctets(byte* out, word32 outSz, const byte* in, + word32 inSz, word32 octetSz); +/* Inverse: out needs WC_PACKED_CELLS(octetSz), in needs octetSz. */ +WOLFSSL_API int wc_PackOctets(byte* out, word32 outSz, const byte* in, + word32 inSz, word32 octetSz); +#endif /* WOLFSSL_WIDE_BYTE */ + #ifdef USE_WOLF_STRTOK WOLFSSL_API char* wc_strtok(char *str, const char *delim, char **nextp); #endif diff --git a/wolfssl/wolfcrypt/wc_mldsa.h b/wolfssl/wolfcrypt/wc_mldsa.h index 64e0472ebc7..2ea4137f796 100644 --- a/wolfssl/wolfcrypt/wc_mldsa.h +++ b/wolfssl/wolfcrypt/wc_mldsa.h @@ -629,6 +629,16 @@ struct wc_MlDsaKey { const wc_MlDsaParams* params; wc_Shake shake; +#ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A + /* Caller-supplied matrix A, expanded off-target and typically flash + * resident. NULL means expand it here as usual. Read only, never freed: + * it must outlive the key. aPreLen and aPreRho record what it was bound + * to, so a later SetParams or key import cannot leave a stale matrix in + * use - see mldsa_precomp_a(). */ + const sword32* aPre; + word32 aPreLen; + byte aPreRho[MLDSA_PUB_SEED_SZ]; +#endif #ifndef WC_MLDSA_FIXED_ARRAY #ifdef WC_MLDSA_CACHE_MATRIX_A sword32* a; @@ -759,6 +769,24 @@ WOLFSSL_API int wc_MlDsaKey_VerifyMu(wc_MlDsaKey* key, const byte* sig, word32 sigLen, const byte* mu, word32 muLen, int* res); +#ifdef WOLFSSL_MLDSA_VERIFY_PRECOMP_A +/* Supply a matrix A expanded off-target for this key's rho, so verify can skip + * the SHAKE128 rejection sampling that otherwise dominates it. a holds + * k * l * MLDSA_N sword32 in row-major (r, s) order, exactly as ExpandA + * produces them, and must remain valid for the life of the key. + * + * SECURITY: A is derived from the public key and must be protected exactly as + * the public key is - an attacker able to substitute it can influence + * verification. rho is checked against the imported key to catch a mismatched + * or stale matrix, so import the public key first. + * + * Returns 0, BAD_FUNC_ARG on NULL, a bad length, or no public key imported + * yet, PUBLIC_KEY_E when rho does not match the imported key. */ +WOLFSSL_API +int wc_MlDsaKey_SetPrecompA(wc_MlDsaKey* key, const sword32* a, word32 aLen, + const byte* rho, word32 rhoLen); +#endif + #ifndef WC_NO_CONSTRUCTORS WOLFSSL_API wc_MlDsaKey* wc_MlDsaKey_New(void* heap, int devId);