Repository navigation
Conversation
…f modulo 64 (as SHA-256 does) or exceeds 2^61 - 1 bytes, so a resumed state can no longer finalise to a digest of no message, wrap the length field, or trip the do_update debug assert; the message-length doc says 2^61 - 1 bytes rather than 2^64; a comment and a known answer (55 bytes + 7 bits, from an independent reference checked against openssl) pin the padding at l = 447 mod 512, where the draft's prose and closed-form k disagree; Usage Examples heading, draft-sca-cfrg-sm3-02 citation and a paren fixed; cargo mutants: 188 mutants, 171 caught, 10 unviable, 1 timeout and 6 missed, all documented equivalents (the MAX_MESSAGE_BYTES pair the review found is now caught) Assisted-by: Claude:claude-opus-5-5
…adding (implemented for the primitives, bool, char and arrays of them), so a padded ZeroizablePrimitive defined in safe code can still be held and zeroized but no longer compared, which read its uninitialised padding through from_raw_parts (a compile_fail doctest pins it; ZeroizablePrimitive stays safe, so mlkem and mldsa keep forbid(unsafe_code); AESParams::Schedule gains the NoPadding bound its constant-time key comparison needs); Condition::is_bit_set returns FALSE for an index of BITS or more instead of panicking in debug and masking the shift in release (all four widths, with tests); add_lib_ver and check_lib_ver assert SERIALIZED_LEN >= LIB_VERSION_LEN at compile time, which removes check_lib_ver's runtime length check and its unwrap; parse_version_component fails the build on a component above 255 or an empty one whatever -C overflow-checks says, where a release build wrapped "300" to 44; doc fixes: check_lib_ver's stale return description, the negate example that assumed TRUE == 1, ct_eq_zero_bytes returning a public bool, bounded_usize's max, a tautological debug_assert in zeroize, two typos; cargo mutants: 395 mutants, 267 caught, 100 unviable, 1 timeout (+= as *= in the new parse loop never terminates), 27 missed, none in changed code: 7 | as ^ equivalents and Secret's drop as before, 19 in the suspendable_state Cursor, check_lib_ver and bounded_usize that the review lists as missing tests Assisted-by: Claude:claude-opus-5-5
…ontract as XOF128 and CXOF128 do, num_bits 0 is the byte-aligned final and above 7 is InvalidLength, where both refused every value with InvalidInput (the _out form now zeroes its output first); the decryptor's from_suspended refuses held-back bytes outside the Data phase, which no live decryptor holds and which a crafted Init-phase state could release as plaintext; Security Considerations state the SP 800-232 R6/R7 2^54-byte per-key data limit and the R5 2^96 decryption-failure limit, both the caller's to track, that a cloned encryptor reuses the keystream, and correct the partial-byte paragraph; tests for squeezes smaller than the buffered remainder and for AsconCXof128::hash_out's count; cargo mutants: 704 mutants, 598 caught, 98 unviable, 8 missed, the equivalents the review lists (the four test-gap survivors are now caught) Assisted-by: Claude:claude-opus-5-5
…es): an sm3::hkdf module with the HKDF_SM3 alias, SUSPENDED_HKDF_SM3_STATE_LEN and HKDF_SM3_NAME (moved there from sm3::hmac, as sha2 keeps its HKDF names), a KDFFactory arm, an hkdf-sm3 CLI subcommand and a criterion bench; with no published HKDF-SM3 vectors, the known answers are RFC 5869 A.1-A.3's inputs through OpenSSL 3.0.13, whose SHA-256 output for the same inputs reproduces the RFC, checked for the PRK, the OKM, the streaming extract and the CLI, plus the KDF and suspendable-state frameworks; cargo mutants on kdf_factory.rs: 19 mutants, 6 caught, 11 unviable, 2 missed (default_256_bit as Default::default() is equivalent, default_128_bit's was untested before), the new HKDF-SM3 _out tests catching the four derive_key_out and derive_key_from_multiple_out survivors; sm3/src/hkdf.rs has nothing to mutate Assisted-by: Claude:claude-opus-5-5
…'s shape (each entry point called through a black_boxed fn pointer under an #[inline(never)] measure closure, inputs built in sibling frames), and a per-entry-point stack table in sm3's Memory Usage section replacing the 272-byte message-schedule estimate: the deepest frame chain below each entry point from -C remark=prologepilog and --emit=asm after thin LTO, return addresses included and panic paths not followed, x86-64 Linux, rustc 1.97.0-nightly: do_update 320 B, do_final_out 336, do_final_partial_bits_out 464, hash_out 592, suspend 8, from_suspended 80, HmacKey::<SM3>::from_key_material 992, HmacKey::<SM3>::mac_out 928, HKDF_SM3::extract_and_expand_out 2560; every chain bottoms out in SM3::compress's 264-byte frame Assisted-by: Claude:claude-opus-5-5
…SCON_AEAD128_NONCE_LEN and ASCON_AEAD128_TAG_LEN, so a glob import of the crate no longer collides with another crate's lengths and the name says which algorithm it is, as the AES and cipher crates prefix theirs (CCM_NONCE_LEN, GCM_NONCE_LEN); no aliases for the old names, the library being alpha; rename only, comments that it pushed past 100 columns reflowed; cargo mutants: 704 mutants, 598 caught, 98 unviable, 8 missed, the same equivalents as before Assisted-by: Claude:claude-opus-5-5
…EST_LEN) is cited to FIPS 140-3 IG C.L Resolution C.4, which carries SP 800-107r1 Section 5.3.3's requirement while NIST withdraws that document (announced 2022-12-20), in place of FIPS 140-2 IG A.8, and the error says FIPS 140-3; MIN_FIPS_DIGEST_LEN's doc also said 32 bits "exceeds" RFC 2104's 80-bit lower bound, where it is below it; the SP 800-107r1 citations with no new home yet (Section 5.3.4's min(K, 2C) key-strength ceiling, Table 1's digest strengths) stay, checked against the current text; docs and one error string only Assisted-by: Claude:claude-opus-5-5
…e::finish_absorb that pads, clears the buffered message bytes and marks the sponge as squeezing with nothing buffered, so an AsconXof128Squeezer or AsconCXof128Squeezer suspended before its first output now resumes as a squeezer, where its state (not yet squeezing) was refused by the squeezer's from_suspended and accepted by the absorbing type's, the open item of the 2026-10-31 suspended-state audit; the output stream is unchanged, the first squeeze still beginning with the permutation, and AsconHash256 uses the same entry; the suspended-state layout is unchanged; tests for both XOFs fail on the old code; cargo mutants: 702 mutants, 596 caught, 98 unviable, 8 missed, the equivalents the review lists Assisted-by: Claude:claude-opus-5-5
…ide its window, the strength at min(RNG strength, what KEY_LEN bytes carry) per SP 800-90A r1 Section 8.4, so the one guard in set_security_strength covers it, where it wrote the three fields directly after the window (core review C1, whose cap the key-object rework had already added); a KeyMaterial<0> from an RNG is Zeroized rather than CryptographicRandom; set_key_type and zeroize drop the strength to None whenever the key becomes Zeroized or Unknown, as KeyType::Unknown's doc says it must, which removes the reachable panic in truncate (from_rng, then set_key_type(Unknown), then truncate) and turns its unwrap into an expect with a true justification; KeyMaterial's Clone is written out so a clone taken inside a do_hazardous_operations window has the window closed, where the derived Clone copied the open flag for good; cargo mutants on key_material.rs with the core and hmac tests: 129 mutants, 94 caught, 28 unviable, 7 missed, none in changed code (five KeyType TryFrom<u8> arms and allows_hazardous_operations untested before, truncate's > as >= equivalent) Assisted-by: Claude:claude-opus-5-5
…256-bit RNG, end to end: the merged key-object rework already tags the 28-byte draw at 192 bits (core review C2) and pins that rule on KeyMaterial, but no test generated and used an HMAC-SHA224 key Assisted-by: Claude:claude-opus-5-5
…to a block with the new Base64Error::TruncatedInput, where it built two bytes from whatever an earlier block had left in the decoder (decode("QUJDR") gave [65, 66, 67, 69, 66]; RFC 4648 Section 4 allows a final quantum of two or three symbols only); padding is accepted only after two or three symbols of a block and may be followed only by the rest of that padding, and whitespace when skipped, anything else being the new Base64Error::InvalidPadding(index), where everything after the first "=" was silently dropped, a second message included (decode("QQ==QQ==") gave [65]; RFC 4648 Section 3.3: "MUST reject the encoded data if it contains characters outside the base alphabet"); missing padding is still tolerated; tests with RFC 4648 Section 10's vectors, padded, unpadded and with whitespace, and the reviewer's cases; cargo mutants: 136 mutants, 117 caught, 8 unviable, 3 timeouts (loop counters), 8 missed, all | as ^ on disjoint shifted bits
Assisted-by: Claude:claude-opus-5-5
…and from_suspended evaluates H0, so an invalid t is a compile error on the resume path too, where SHA512t::<384>, <0> and <512>::from_suspended built and hashed (<384> giving SHA-512 truncated to 384 bits under the name SHA512/384, which FIPS 180-4 Section 5.3.6 excludes) and <520> resumed and panicked in do_final; a compile_fail doctest on the resume path, which compiles against the old code, and the docs say the error comes at code generation; SHA-224/256 from_suspended refuses a byte_count above 2^61 - 1, the longest message the 64-bit length block encodes, where a state claiming 2^61 bytes finished to the empty-message digest; cargo mutants: 427 mutants, 344 caught, 61 unviable, 7 timeouts (loop counters as *=), 15 missed: 10 annotated | / ^ equivalents and the five SUSPENDED_HKDF_* "+ 14" constants the review lists, the MAX_MESSAGE_BYTES pair now caught Assisted-by: Claude:claude-opus-5-5
…lt_256_bit, "Default256Bit") is HMAC-SHA512, where it was HMAC-SHA256, which the library tags 128-bit and which accepted a 16-byte key that HMAC-SHA512 and KMAC256 refuse (factory review F1); a test that each default's worker meets its strength and that the 256-bit default refuses a 16-byte key; the review's F2 (MAC::new and new_allow_weak_key as unimplemented!() on the factory) is gone with the key-object rework, which took the constructors off the MAC trait Assisted-by: Claude:claude-opus-5-5
… the optimiser would rewrite into a multiply on a secret (the AES round-key widening); the volatile store/load and its caveats are unchanged Assisted-by: Claude:claude-fable-5-1
…ythos.md, at 94d4e96) worked through: the round-key widening in the 2- and 4-block paths no longer folds into a multiply of a secret off x86-64/aarch64, expand takes the key array, the plane-agreement debug_assert goes, GCM_NONCE_LEN is re-exported, the CCM drawing in-place one-shot is tested, two doc nits A1 (hardening): `PlaneWord::splat` widens a round-key half-word into the 2- and 4-block lanes by shifts and ORs; LLVM knows the zero-extended word's high bits are zero, so it rewrites the disjoint ORs into a multiply by 0x0001_0001(_0001_0001). A no_std harness of the widening built with -Zbuild-std for five targets shows the fold as a `__mulsi3` / `__muldi3` call on AVR, `muls` / `__aeabi_lmul` on Cortex-M0 and `__muldi3` on RISC-V 32I (none on msp430 and x86-64; the review saw it on aarch64), i.e. a software multiply of key material where one is variable-time. Now the word passes through ct::value_barrier before the ORs, which hides the known-zero bits so the ORs stay ORs: the harness shows no multiply and no branch on any of the five. The barrier is skipped on x86-64 and aarch64 (a const, so both bodies compile everywhere), where the multiply is fixed-latency: with it, the 2-block path lost 60 % and the 4-block path 190 % of its throughput (one volatile round trip per widened word, 88 per 4-block call); without it the bench is within noise of before (AES-128 16 KiB: block 254 -> 267 us, 2 blocks 141 -> 143 us, 4 blocks 79 -> 78 us). The crate docs' constant-time section says which targets get which. A2: `expand` takes `&[u8; KEY_LEN]` with `KEY_LEN == P::KEY_LEN` asserted at compile time and reads the words with `as_chunks`, so the debug_assert on the length and the "cannot fail" unwrap go; the three constructors convert the validated slice once, mapping the impossible mismatch to InvalidLength rather than unwrapping. A3: the debug_assert over the eight broadcast planes in `sub_word` short-circuits on S-box output of key bytes in a debug build; dropped, the unit test already pins the property, and the docs now say debug builds are outside the constant-time claim. A5: `GCM_NONCE_LEN` is re-exported from the crate root beside CTR_NONCE_LEN, CCM_NONCE_LEN and CCM_TAG_LEN. A11: the inverse S-box costs 32 extra XORs (two inv_affine applications), not 16; the stack table is stated as x86-64 only. Mutants gap: `encrypt_detached_in_place(rng, ..)` on the CCM key was reached by no test (four survivors); `ccm_key_tests.rs` now checks it against the hazmat reference and the `_nonce` form under the drawn nonce at every length. Not changed: A4 (three naming grammars: a decision before the factory grows AES), A6/A7/A8/A9 (resolved by PR #172 and the from_rng fix on this branch: the bare constructors are pub(crate), MAC::new is gone, every mode has a key path), A10 (#[must_use], issue #74). Assisted-by: Claude:claude-fable-5-1
…W-2026-11-03-sha3-correctness-mythos.md, at 94d4e96; S1, the KMAC output ceiling, had landed with PR #172): TupleHash and ParallelHash bound output_len in new and on resume, SHA3 refuses a squeezing state on resume, the KDF entropy and ParallelHash block counters saturate and resume width-checked, squeeze cannot wrap its bit count on a 32-bit target; plus the review's two recommended tests S2: `TupleHashInternal::new` and `ParallelHashInternal::new` return `Result<Self, HashError>` and refuse an `output_len` above TUPLEHASH_MAX_OUTPUT_LEN / PARALLELHASH_MAX_OUTPUT_LEN (u64::MAX / 8, the KMAC cap), and `read_state` holds a resumed length to the same bound; past it `(n as u64) * 8` wrapped, and at exactly 2^61 bytes to zero, so `right_encode(0)` turned the fixed-length function into the XOF. The call sites (tests, benches, the sha3 CLI, which reports the length as an error) follow. S3: `SHA3Internal::from_suspended` refuses a state in the squeezing phase, as SHAKE, cSHAKE and ParallelHash already did; a SHA3 is only ever suspended while absorbing, and every finalisation of such a state failed on absorb_bits' phase check. S4: the KDF entropy counter is read back through `bounded_usize(.., usize::MAX)` (no truncation on a 32-bit target) and `mix_key_internal` adds to it with `saturating_add`, for SHA3 and SHAKE. S5: `ParallelState::absorb_block_digest` increments the block count with `saturating_add`; the resumed value is unbounded by design. S6: `KeccakInternal::squeeze` squeezes in slices of at most `usize::MAX / 8` bytes, so `out.len() << 3` cannot wrap (512 MiB on a 32-bit target wrapped the bound and the loop stopped early, writing only `(len * 8 mod 2^32) / 8` bytes); one iteration on 64-bit. Tests: the suspend-state suite covers the three new rejections and the two saturating counters on resume, the SHA3 suite the squeezing phase byte and a saturated entropy counter, the TupleHash and ParallelHash suites the constructor cap. The two tests the review's mutants pass asked for: TupleHash at L = 264 bits against Sec 5.3 written out by hand over cSHAKE128 (`right_encode(264)` is `01 08 02`, a two-byte value with a non-zero low byte, which no sample value exercises), and SHAKE's partial byte with all-ones against all-zeros in the unused low bits. Left from the review's notes: S-N1 (output_len 0 is accepted, as the spec allows), S-N2/S-N3 docs, S-N4/S-N5 API observations. Assisted-by: Claude:claude-fable-5-1
…Hash_DRBG vectors; the 2026-11-03 crate review's four bugs R1-R4 (REVIEW-2026-11-03-rng-correctness-mythos.md, at 94d4e96) R1: V and C are seedlen bits, 440 for SHA-256 and 888 for SHA-512 (Section 10.1 Table 2, 10.1.1.1), where both were 512. The parameter sets carry SEED_LEN (and OUTPUT_LEN); the backing arrays are sized for the larger and every use is `[..H::SEED_LEN]`, so Hash_df fills seedlen bytes, C is derived from 0x00 || V over seedlen bytes, and the `mod 2^seedlen` additions run over seedlen bytes. R2: Hash_df (Section 10.3.1 step 4.1) encodes no_of_bits_to_return as the 32-bit integer the text says, big-endian; it was little-endian. The counter is written as the one byte it is. R3: the generate algorithm's step 5 adds reseed_counter as an integer, i.e. its big-endian bytes at the low end of V; the little-endian bytes in that position added the byte-reversed counter (2^56 for 1). R4: step 4's H is an outlen-bit integer; for SHA-256 it was added through a 64-byte buffer, i.e. shifted left by 256 bits. The buffer is sliced to the hash's outlen before the addition, and Hashgen's data is seedlen bytes. Every output of the generator for a given seed changes. The crate's one known-answer test (test_hash_df) compared against a value the code itself had produced and anchored R2; its expected values are now computed independently in Python from Section 10.3.1, for SHA-256 and SHA-512. The new tests/cavp_hash_drbg.rs runs NIST's drbgtestvectors (CAVS 14.3, 2013-04-02) Hash_DRBG.rsp from drbgvectors_no_reseed and drbgvectors_pr_false, the SHA-256 and SHA-512 sections with and without personalization string and additional input: 240 + 240 vectors per file, all passing (they all failed before). The files are read from bc-test-data/crypto/cavp/drbg/<set>/Hash_DRBG.rsp through the shared loader, so a missing checkout skips with its warning; they need adding to bc-test-data (the three sets as NIST ships them, pr_true included for when prediction resistance is supported). The crate gains bouncycastle-hex as a dev-dependency for the parser. No other test in the workspace pinned DRBG output. Not changed: R5-R8 (hardening: prediction-resistance todo!(), the empty-nonce check, the pre-check allocation in generate, V copies as plain arrays) and the notes. Assisted-by: Claude:claude-fable-5-1
… and K3 (REVIEW-2026-11-03-hmac-hkdf-correctness-mythos.md, at 94d4e96): a resumed state's strength label and entropy counter are checked against the hash and the state tag, and a salt credits entropy only when the caller types it MACKey; K1 and the bug H1 had landed with PR #172 and 967ae57 K2: from_suspended refuses a security strength above the hash's cap (half the output, the min(K, 2C) rule credit_entropy already applies, now the tracker's MAX_STRENGTH) and refuses a non-zero entropy counter or a non-None strength on a state whose tag is Uninitialized, which new() never writes. Before, a pre-init state with those two bytes edited, holding no secret at all, resumed and labelled the PRK of HMAC(0^64, "") as CryptographicRandom at _256bit on HKDF-SHA256. K3: do_extract_init credits the salt only when it is typed MACKey. RFC 5869 Section 2.2 defines the salt as "a non-secret random value" and SP 800-133r2 Section 6.3 note (g) counts the component keys' entropy, not the salt's, so a CryptographicRandom salt (a nonce from an RNG) with an empty IKM gave a PRK anyone who knows the salt can compute, labelled full-entropy material at full strength; it is now Unknown at None. A MACKey salt is SP 800-56Cr2 Section 5.1's secret salt and is credited as before, which is how every existing test and the CLI type theirs. An Unknown-typed IKM stays accepted and uncredited, as the core KDF trait describes; the HKDF docs said it was refused and now say what happens. The crate's and sha2's Security Considerations state the salt rule. Tests: a_public_salt_credits_no_entropy (one-shot and streaming, the same PRK bytes either way) and a_forged_state_is_refused (strength above the cap, the cap itself, entropy or strength on a pre-init state, an honest pre-init state). Not changed from the review: K1 (saturating counter, width-checked on resume) and H1 (HmacKey::generate's strength cap) are already on this branch; H2 and H3 are hmac's; the notes K4-K10 and H4-H7. Assisted-by: Claude:claude-fable-5-1
…s described as the plain hash of the concatenated inputs it is, not as SP 800-56C's one-step KDF (which puts a counter first); the Memory Usage rows for KMACXOF (448) and KMAC (456) match the bench; SHAKE's `new` doc says SHAKE Assisted-by: Claude:claude-fable-5-1
…nstantiation table lists all six SHA-2 and four SHA-3 aliases and HMAC_SM3, the usage section has the heading QUALITY_AND_STYLE names, and Display/Debug print HMACParams::HMAC_ALG_NAME rather than "HMAC-" plus the hash's name Assisted-by: Claude:claude-fable-5-1
…ed in bytes, the PRK is HashLen bytes, the SP 800-56Cr2 citation quotes Section 5.1's own sentence on RFC 5869 and says the expand layout is RFC 5869's rather than SP 800-108r1 Section 4.2's, the Drop comment gives the right reason (the hash types zeroize the pad states), two typos Assisted-by: Claude:claude-fable-5-1
…rbg docs no longer promise an InsufficientSeedEntropy check the code never made (Section 8.4 limits the strength of a long output, not its length; the per-request limit is 2^19 bits as GenericError), say bytes where the code returns bytes, quote Section 8.6.7 for the nonce instead of "no entropy requirement", state the reseed policy (ReseedRequired after 2^48 requests until a full Section 10.1.1.3 reseed by the caller), that prediction resistance is unsupported and the OS-seeded constructors panic on an OS failure, and that the health test is not implemented; the four Table 2 limit constants are named for the bytes they hold; typos Assisted-by: Claude:claude-fable-5-1
…(REVIEW-2026-11-03-cipher-correctness-mythos.md, at 94d4e96): GCM refuses a resumed counter below 2, CCM bounds a resumed counter by the payload the mode can carry, an empty data call leaves GCM's AAD phase open as CCM's, the CMAC doc no longer claims an all-zero key is accepted; Ci1, Ci3, Ci4, Ci5, Ci7, Ci9 had landed with #172 and Ci10 went with the worker constructors Ci2: `Gcm::read_state` refuses a CTR counter below 2. GCTR starts at inc32(J0), counter 2 (SP 800-38D Algorithm 4 step 3); J0 itself, counter 1, is the tag mask (step 6) and 0 is never used, so a state claiming either would emit CIPH_K(J0) as keystream. The CTR component's own check was only the top of the range. Ci6: a CCM keystream resumes only with `next_ctr` in `1..=MAX_NEXT_CTR`, one past the last counter value the largest payload (`2^8q - 1` bytes) can use. The bound was `MAX_COUNTER`, the counter field's width, which for `q = 8` is `u64::MAX`: a resumed index there passed and the next increment overflowed, in release wrapping `next_ctr` to 0 (the tag mask's counter) and `remaining_blocks` with it. Ci11: `absorb_data` and the decryptor's `do_decrypt_out` treat an empty call as a no-op that leaves the AAD phase open, which is what CCM documents and does and what the AEAD suite in the test framework now holds the family to; a non-empty call still ends the phase even when every byte is held back as a possible tag. Ci8: the key route refuses a Zeroized key by type and resume goes through the key object, so the module doc's "(or an all-zero key, as HMAC does)" was stale. Tests: `gcm_refuses_a_resumed_counter_below_two` (0 and 1 refused, 2 resumes), `the_counter_index_is_held_to_its_range` moves to the new bound and adds `u64::MAX`. Assisted-by: Claude:claude-fable-5-1
… a no-op that leaves the AAD phase open, in both directions (the cipher review's Ci11: GCM closed it, CCM did not, and the trait left it unsaid) Assisted-by: Claude:claude-fable-5-1
…the test framework now checks; a non-empty decrypt call still closes it even while its bytes are held back as the tag Assisted-by: Claude:claude-fable-5-1
…not only that there is no padding (P190-utils-1) Assisted-by: Claude:claude-fable-5-1
… enforces, and from_rng's doc says a zero-capacity key never consults the RNG (P190-cipher-3, P190-core-1) Assisted-by: Claude:claude-fable-5-1
Assisted-by: Claude:claude-fable-5-1
…endent reproduction with OpenSSL 3.6.3 and bc-java 1.86 (P190-sm3-2) Assisted-by: Claude:claude-fable-5-1
…ounded only by the cap once initialized, so the next audit does not re-derive it (P190-hkdf-1) Assisted-by: Claude:claude-fable-5-1
… than panic! (P190-base64-1) Assisted-by: Claude:claude-fable-5-1
… 800-90A Rev. 1 Section 8.6.7's (3/2 security_strength) alternative, and the CAVP test header notes the LF line endings of the bc-test-data copies (P190-rng-1, P190-rng-2) Assisted-by: Claude:claude-fable-5-1
…ree and in CI -- dev_scripts/aes_ct_harness holds the two splat bodies with and without the barrier, dev_scripts/check_aes_ct_asm.sh builds it on nightly with -Zbuild-std=core for thumbv6m and riscv32i and greps the barriered bodies for a multiply, call or branch (none, in 10/20 and 7/14 instructions) and the plain ones for the multiply the barrier prevents, and aes-ct-asm.yml runs it on every pull request; the splat comment records that the architecture-only gate makes 32-bit Arm, x86 and wasm32 pay the barrier too, and the dead allow(unused_imports) above the GCM_NONCE_LEN re-export goes (P190-aes-1, P190-aes-2, P190-aes-3) Assisted-by: Claude:claude-fable-5-1
…ream::counter_from_state rather than by its byte offset, and the CCM counter-index test adds the q = 8 case (a 7-byte nonce) that motivated the bound (P190-cipher-1, P190-cipher-2) Assisted-by: Claude:claude-fable-5-1
…rrett_reduce documents and tests Assisted-by: Claude:claude-fable-5-1
…ch pins the reduction before inv_ntt that M1 restored (P190-mlkem-2) Assisted-by: Claude:claude-fable-5-1
…ch-freeness, which only the assembly shows (P190-mldsa-2) Assisted-by: Claude:claude-fable-5-1
…e64 command repeats the data-after-padding check for later chunks (P190-cli-3, P190-base64-2) Assisted-by: Claude:claude-fable-5-1
…nds the emitted .s by search, since the nightly on CI keeps intermediate artifacts in a separate build directory Assisted-by: Claude:claude-fable-5-1
Contributor
Author
dghgit
added a commit
that referenced
this pull request
Nov 4, 2026
…m the crate-by-crate review of release/0.1.3alpha (fifteen reports, one commit per crate) and the fixes from the 2026-11-04 verification of them: utils (NoPadding required for Secret<T>: PartialEq, which closes a soundness gap, a public ct::value_barrier, is_bit_set past the width, the version-header checks), core (KeyMaterial::from_rng through the setters, a dropped strength on Zeroized or Unknown, clones closing the hazardous window, SignatureError gains RejectionLimitExceeded and PreHashTooWeak and ConsistencyCheckFailed is a unit variant, the do_update_aad empty-call rule), sha2 and sha3 (an invalid SHA512t<T> is a compile error on resume, the SHA-224/256 resume byte-count bound, TupleHash and ParallelHash constructors return a Result and the output length is held to u64::MAX / 8, saturating counters, 32-bit-safe squeeze, SHAKE128 and SHAKE256 implement HashAlgParams per RFC 8702 so they serve as HashML-DSA pre-hashes, FIPS 202 and SP 800-185 citations), sm3 (HKDF-SM3 and the stack figures), ascon (AsconHash256::do_final_partial_bits on the Hash contract, the decryptor's and the squeezer's resume checks, both decrypt entry points on one phase machine, the renamed ASCON_AEAD128_* length constants, SP 800-232's limits in the security considerations), hmac and hkdf (the salt rule: a non-MACKey salt no longer credits entropy, truncation citations, resume checks), base64 (padding and truncation are refused with two new Base64Error variants), factory (the 256-bit default MAC is HMAC-SHA512), rng (Hash_DRBG now matches SP 800-90A Rev. 1 and passes all 960 CAVP Hash_DRBG vectors where it passed none, reseed counter and seed-material fixes), aes (the round-key widening goes through a barrier off x86-64 and aarch64 so LLVM cannot fold it into a multiply, with a cross-target assembly harness and a CI job that greps it on Cortex-M0 and RISC-V 32I; expand takes the key array), cipher (the CCM and GCM resume bounds, the empty-data rule in the AEAD framework), ML-KEM (a reduction before inv_ntt, Barrett in i32, Secret buffers through keygen, encaps and decaps) and ML-DSA (branch-free check_norm and make_hint, the low-memory output cap, Secret rho' and rho''); the CLI on one input rule (inline values hex, files and stdin raw unless --hex-input), secrets in zeroized buffers, exit 255 with an Error: line on every failure, streaming XOF output, and parity subcommands (hmac-sha224/384 and hmac-sha3-*, cshake*, the *xof forms, aes*-cmac and aes*-gmac, hash-mldsa --hash, --deterministic, the mu commands, rng --hash and --seed-file), with the reviewers' known-answer scripts as its test suite. Created by David Hook. Assisted-by: Claude:claude-fable-5-1 Assisted-by: Claude:claude-opus-5-5
Contributor
Author
|
Merged by hand as squash commit. |
dghgit
added a commit
that referenced
this pull request
Nov 4, 2026
…feature/aria: the CLI dispatch keeps this branch's ARIA arms beside the release's rewritten SP 800-185 arms, the shared CCM runner stays pub(crate) under the release's clippy allowance, the memory-bench manifest lists both new binaries, and ARIAParams::Schedule gains the NoPadding bound that Secret's equality now requires, as AESParams::Schedule has Assisted-by: Claude:claude-fable-5-1
dghgit
added a commit
that referenced
this pull request
Nov 4, 2026
…feature/sm4: the CLI dispatch keeps this branch's SM4 arms beside the release's rewritten SP 800-185 arms, the shared CCM runner stays pub(crate) under the release's clippy allowance, and the memory-bench manifest and module list carry both new binaries Assisted-by: Claude:claude-fable-5-1
dghgit
added a commit
that referenced
this pull request
Nov 4, 2026
…feature/tdes: the CLI dispatch keeps this branch's TDES arms beside the release's rewritten SP 800-185 arms, the block-mode helper keeps DecryptOnlyAction ahead of the release's reworded key loader, and the memory-bench manifest and module list carry both new binaries Assisted-by: Claude:claude-fable-5-1
dghgit
added a commit
that referenced
this pull request
Nov 4, 2026
…feature/camellia: the CLI dispatch keeps this branch's Camellia arms beside the release's rewritten SP 800-185 arms, the shared CCM runner stays pub(crate) under the release's clippy allowance, the memory-bench manifest lists both new binaries, and CamelliaParams::Schedule gains the NoPadding bound that Secret's equality now requires, as AESParams::Schedule has Assisted-by: Claude:claude-fable-5-1
dghgit
added a commit
that referenced
this pull request
Nov 4, 2026
…feature/ecdsa: the CLI's ECDSA and SM2 subcommands and dispatch arms are re-placed beside the release's rewritten ones, and ecdsa_cmd.rs and sm2_cmd.rs move onto the release's input rule -- private keys through the zeroized secret readers, public keys, signatures and identities as raw bytes or hex under --hex-input, with the hex-prefix guessing gone, stdout through the shared writers; Secret::zeroize keeps this branch's zeroize_in_place helper, which does the volatile write and fence the release inlined; the memory-bench module list carries both new binaries Assisted-by: Claude:claude-fable-5-1
This was referenced Nov 4, 2026
dghgit
added a commit
that referenced
this pull request
Nov 4, 2026
… the squash merge of PR #190) into feature/rsa-sig: the CLI imports were the one conflict, and rsa_cmd.rs and rsasve_cmd.rs move onto the release's input rule as ecdsa_cmd.rs did -- private keys through the zeroized secret readers, public keys, signatures and ciphertexts as raw bytes or hex under --hex-input, stdout through the shared writers; the RSA CLI tests write their key and signature files as raw bytes Assisted-by: Claude:claude-fable-5-1
1 task done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue Link
No issue filed. These are fixes for mwcw's correctness reviews of
release/0.1.3alphaat 94d4e96: sm3, utils, ascon, sha2, hex/base64, core, factory, and now (2026-11-04) aes, sha3, rng, hkdf and cipher. The branch is rebased onto 615065a, the #172 merge. It's taken a while as we keep running out of tokens!Pick the sentence that isn't AI generated... At the moment we're half way through the review, #172 is now on release/0.1.3alpha as it's turned out it fixed a good proportion of what was raised to date. The intention is this will form the cap of release/0.1.3alpha and the head of it will become the base of release/0.1.4alpha
Summary
Each finding the reviews rated as a bug or hardening item is fixed here, with a test, except the ones listed under Deferred. The branch also adds HKDF-SM3 and SM3 stack figures, renames Ascon's length constants, and updates the HMAC truncation citation. The 2026-11-04 update adds the aes, sha3, rng, hmac/hkdf, cipher, ML-KEM, ML-DSA and CLI reports (8 to 15): the rng one found that Hash_DRBG did not match SP 800-90A and failed every CAVP vector; it now passes all 960. There is one commit per crate; a commit includes the factory or CLI wiring that belongs to it. The 2026-11-04 verification of this PR against the fifteen reviews (every claimed fix confirmed, no regression) raised one hardening item and a set of notes; all are addressed in the commits above 32b09ad, listed in the last Description section.
Description
sm3:
from_suspendedrefuses a byte count that disagrees with the buffer offset, or that is above 2^61−1.sm3, factory, cli: HKDF-SM3, the HKDF the RFC 8998 TLS 1.3 SM suites use:
KDFFactoryarm, anhkdf-sm3subcommand and a bench;sm3, mem_usage_benches:
bench_sm3_mem_usageand a measured per-entry-point stack table for SM3, HMAC-SM3 and HKDF-SM3.utils:
Secret's equality now needs the newunsafe trait NoPadding, which closes a soundness gap: a padded user type could have its uninitialised padding read by==.ZeroizablePrimitivestays safe, so mlkem and mldsa keepforbid(unsafe_code).is_bit_setreturns FALSE for an index past the width.ascon:
AsconHash256::do_final_partial_bitsnow follows theHashcontract.KEY_LEN/NONCE_LEN/TAG_LENbecomeASCON_AEAD128_*_LEN.hmac, sha2, sha3, sm3: the 32-bit truncation floor is cited to FIPS 140-3 IG C.L Resolution C.4, which carries SP 800-107r1 §5.3.3 while NIST withdraws that document.
core:
KeyMaterial::from_rngsets its metadata through the setters, and a zero-length draw is Zeroized.truncate.hmac: an end-to-end test of
HmacKey::<SHA224>::generatewith the default 256-bit RNG.base64:
TruncatedInput, where the decoder built bytes from leftover state.InvalidPadding, where it was silently dropped. This follows RFC 4648 §3.3 and §4.sha2:
SHA512t<T>is a compile error on the resume path too.from_suspendedrefuses a byte count above 2^61−1.factory:
MACFactory's 256-bit default is HMAC-SHA512, where it was the 128-bit HMAC-SHA256.utils, aes (report 8):
ct::value_barrieris public.MACKeysalt no longer credits entropy to HKDF's output label.no_stdharness built with-Zbuild-stdshows a__mulsi3/__muldi3call on AVR,muls/__aeabi_lmulon Cortex-M0 and__muldi3on RISC-V 32I. Off x86-64 and aarch64 the word now passes through the barrier first, and the harness shows no multiply and no branch on any of the five targets. On x86-64 and aarch64, where the multiply is fixed-latency, the fold is left alone: the barrier there cost 60 % of the 2-block and 190 % of the 4-block throughput.expandtakes the key array (the length is a compile-time assertion,as_chunksreplaces the unwrap); the plane-agreementdebug_assertthat short-circuited on key bytes goes;GCM_NONCE_LENis re-exported; two doc nits; the CCM key's drawing in-place one-shot, which no test reached, is tested.sha3, cli (report 9):
TupleHash*::newandParallelHash*::newreturn aResultand refuse anoutput_lenaboveu64::MAX / 8(the KMAC cap), and resume holds the stored length to the same bound: past it the bit count wrapped, and at 2^61 bytes the fixed-length function silently became the XOF.squeezeworks in slices of at mostusize::MAX / 8bytes, so the bit count cannot wrap on 32-bit targets.right_encodevalue with a non-zero low byte), and SHAKE's partial byte with its unused bits all ones against all zeros.rng (report 10): Hash_DRBG matches SP 800-90A Rev. 1 and passes all 960 CAVP
Hash_DRBG.rspvectors (no-reseed and pr-false, SHA-256 and SHA-512, with and without personalization string and additional input), where it failed every one:Hash_dfencodesno_of_bits_to_returnbig-endian.reseed_counteris added as an integer, not its byte-reversed bytes.His added at the low end of V, not shifted by 256 bits.bc-test-data/crypto/cavp/drbg/, which needs them added there (the three NIST sets as shipped, except that the copies have LF line endings where the zip has CRLF; the parser trims).hkdf, sha2 (report 11, the HKDF items):
CryptographicRandomat 256-bit.MACKey(SP 800-56Cr2 Section 5.1's secret salt). RFC 5869 Section 2.2's salt is "a non-secret random value", so a random public salt with an empty IKM gave a full-entropy, full-strength label on a PRK anyone who knows the salt can compute; it is nowUnknown. Existing callers and the CLI type their saltsMACKey, so nothing else changes.HmacKey::generateon sub-32-byte hashes) and K1 (the resumed entropy counter) were already closed on this branch.cipher, core-test-framework, ascon (report 12):
inc32(J0)(SP 800-38D Algorithm 4 step 3), J0 itself is the tag mask, so a state claiming 0 or 1 would emitCIPH_K(J0)as keystream.u64::MAXand overflowed on the next block.mlkem-common, mlkem, mlkem-lowmemory (report 13):
inv_ntt's first butterfly adds two of them in i16 (limit 9.84q). FIPS 203 Section 3.3 allows deferring a reduction only while the result is right for every input; the margin here was empirical (the review's search reached 8.9q on a real key), and an overflow would have made m′ wrong and the honest decapsulation return K̄. bc-java and the reference implementation reduce at this point. No known-answer output changes.barrett_reduceis valid for every i16 (M2): at or below −9q thet·qproduct does not fit an i16 and the narrowed subtraction overflowed in debug builds. The subtraction is done in i32 and the unit test covers the whole range.Secretbuffers, and the private-key comparison encodes intoSecretbuffers rather than plain arrays of up to 3168 bytes (M3). The hazmatencaps_with_randomnesskeeps its by-value contract.KeyType::CryptographicRandom, the variant the check accepts (M5).core, mldsa-common, mldsa, mldsa-lowmemory (report 14):
Secret, so the error paths wipe them too; the full crate used a plain fill on the success path only and the low-memory crate never wiped ρ″ (M2). The zeroization docs say which polynomial temporaries stay plain.check_normOR-accumulates over all 256 coefficients andmake_hintis a mask expression, removing the two secret-dependent branches inside the rejection loop (M3). FIPS 204 asks for neither and the reference implementations branch; a new test compares both with the comparisons they replace over every input.SignatureErrorgainsRejectionLimitExceededandPreHashTooWeakin place ofGenericErrorstrings,ConsistencyCheckFailedloses its empty parentheses, the seed error names aKeyTypethat exists, the unit-error decoders returnOption, and the key, worker and crate docs say signing can fail at the cap (M4).tr()returns a reference as the full crate's does, and both crates re-exportMLDSA_SEED_LEN.compute_z_componentno longer returns aResultwith no error path, and the signer doc's duplicated opening is gone (M6).HashAlgParamswith the fixed digests of RFC 8702 Section 3.1 ("the output length of SHAKE128 or SHAKE256 as the message digest MUST be 32 or 64 bytes, respectively"), which is FIPS 204 Algorithm 4's SHAKE128(M, 256) under id-shake128. SHAKE128's 128-bit strength fits ML-DSA-44 only, SHAKE256 fits all three; a test in each ML-DSA crate checks the signature against Algorithm 4's M′ built by hand with the OID bytes of line 18, through the external-mu path. The module doc no longer says "any approved hash"; the strength check stays the only gate, pending the workspace FIPS-mode design (PR Simple ciphers: stream + symmetric cipher unification off the release branch, plus SHA-512/t for any t #133). M8 is documented behaviour and unchanged.cli (report 15), five commits:
mlkem1024 keygencalled the ML-KEM-512 keygen, so it emitted a 1632-byte key no mlkem1024 command accepted (L1); the ML-KEM-768 public-key parser used the 512 length (L2). L3 (base64 after the padding) was already closed by the base64 fix on this branch.--hex-input. Nothing is guessed from a file's contents, so a raw key whose bytes happen to be hex digits is no longer silently decoded into a different key.kmac* -kis the key file as forhmac-*,ascon-aead128 --adis--aadwith an--aad-file,ascon-cxof128 --customizationis the string and--customization-hexthe bytes, and the SP 800-185 commands gain--customization-hex. The conventions are written up on thehelpersmodule.KeyMaterialbuffer, never aVec(L20). A stdin read error, a missing or non-hex key, salt, IKM or verify value, a bad seed, a context over 255 bytes and an unwritable--ctfilereport with exit 255 rather than a panic (L5 to L9). XOF output is squeezed to stdout in 1 KiB pieces; the fixed-length KMAC, TupleHash and ParallelHash outputs are capped at 256 MiB with a pointer to the XOF forms (L10). MAC keys take any length up to 16 KiB, so RFC 4231's long-key cases run (L14). Every failure line starts withError:(L15);hkdf -xends in a newline (L16); the help-text errors are fixed (L18);#![forbid(unsafe_code)]and a clippy-clean package (L19).hmac-sha224,hmac-sha384,hmac-sha3-*,cshake*,kmacxof*,tuplehashxof*,parallelhashxof*,aes*-cmac,aes*-gmac,--hash sha256|sha512|shake128|shake256onhash-mldsa*,--deterministic,compute-mu/sign-mu/verify-mu, andrng --hashand the hazardousrng --seed-file. Not added: the low-memory twins (same outputs) andSHA512tat othert(a const generic).timeout, BSDwcpadding) are fixed. 20 files, 281 tests, all passing.Doc corrections from reports 9 to 11 (sha3 S-N2/S-N3, hmac H5, hkdf K8, rng R9/R10/R16), one commit per crate: the SHA-3
KDFimpl is described as the plain hash it is rather than SP 800-56C's one-step KDF, and the KMAC rows of its memory table match the bench; the HMAC instantiation table lists all eleven aliases andDisplayprints the HMAC name; HKDF reports credited entropy in bytes, quotes SP 800-56Cr2 Section 5.1's own sentence on RFC 5869, and states the right reason for having noDrop; the DRBG trait docs drop theInsufficientSeedEntropycheck that was never made, quote Section 8.6.7 for the nonce, and state the reseed policy, the unsupported prediction resistance and the panicking OS-seeded constructors; the Table 2 limit constants are named for the bytes they hold.From the 2026-11-04 verification of this PR (every claimed fix confirmed present and doing what the finding asked, two partials as stated above, no regression; its ids are
P190-<crate>-<n>), one commit per crate:do_decrypt_inplacenow ends the AAD phase on its first non-empty call, asdo_decrypt_outdoes, so the two decrypt entry points run one phase machine; a test drives both with every byte held back.dev_scripts/aes_ct_harness(the twosplatbodies with and without the barrier),dev_scripts/check_aes_ct_asm.sh(nightly,-Zbuild-std=core, thumbv6m and riscv32i; the barriered bodies must show no multiply, call or branch, the plain ones must show the multiply) andaes-ct-asm.yml. Thesplatcomment records that the architecture-only gate makes 32-bit Arm, x86 and wasm32 pay the barrier. The deadallow(unused_imports)is gone.CtrKeyStream::counter_from_state; the coredo_update_aaddocs carry the empty-call rule on both sides.dot_productreturns reduced coefficients, which pins the reduction M1 restored; it showedpoly_reduce's doc giving[0, q)for a reduction into[0, q], fixed in mlkem-common.NoPaddingcontract is "every byte initialized"), core P190-core-1, sha3 P190-sha3-2, sm3 P190-sm3-2, hkdf P190-hkdf-1, base64 P190-base64-1 (unreachable!), rng P190-rng-1/2 (the nonce sentence cites Section 8.6.7's 3/2-strength alternative), mldsa-common P190-mldsa-2, cli P190-cli-3 and P190-base64-2 (why the CLI repeats the padding check for later chunks).Deferred, with reasons
Conditionconstructors: this needs a decision on losingconst fnand a cross-target asm check.do_*_initsites it would share went with Key object rework: the traits lose their constructors, every mode gets a key, and workers borrow the key #172.hash_mldsa.rssays the choice is the caller's.todo!(), the empty-nonce check, the pre-check allocation ingenerate, V copies as plain arrays) and the notes R9 to R17.keygen_from_rngre-typing outside a hazardous window) and the hmac/hkdf notes K4 to K10, H4 to H7. hmac H2 (resume skipping the key policy) is not deferred: Key object rework: the traits lose their constructors, every mode gets a key, and workers borrow the key #172 closed it, sinceSuspendableKeyed::Key = HmacKeyand the bare key constructor is private.+ TAG_LENin_out_len, the two MACs' key retagging, the F.5 CTR vectors).Scope and Risk
utils,core,core-test-framework,ascon,sm3,sha2,sha3,hmac,hkdf,base64,aes,cipher,rng,factory,cli,mem_usage_benches.NoPaddingis required forSecret<T>: PartialEq.Base64Errorhas two new variants.MACFactory's 256-bit default is a different MAC.TupleHash*::newandParallelHash*::newreturn aResult.ct::value_barrieris public.MACKeysalt no longer credits entropy to HKDF's output label.SignatureError::ConsistencyCheckFailedis a unit variant (amatchonConsistencyCheckFailed()stops compiling), andSignatureErrorgainsRejectionLimitExceededandPreHashTooWeak.bouncycastle_mldsa_lowmemory::MLDSAPrivateKeyTrait::trreturns&[u8; 64]rather than[u8; 64].bouncycastle_mlkem_common::polynomial::Polynomial::from_msgtakes&[u8; 32](a#[doc(hidden)]item, reachable all the same).HKDF_SM3_NAMEmoved fromsm3::hmactosm3::hkdf.cli/tests/test_all.sh): 20 files, all passing. cargo-mutants is not meaningful for theclipackage, which has no Rust tests.|/^equivalents the review lists plus the threeSymmetricCipherKeyfast-path arms, whose removal changes no output); sha3 827 (654 caught, 152 unviable, 8 timeouts in the squeeze and ParallelHash loops, 13 missed: the review's seven inherent TupleHash one-shot wrappers, five bit-mixing equivalents, and thesqueezeslice size, larger than any buffer either way); rng 190 (re-run after the two test additions for the reseed counter's increment andadd_seed_keymaterial: 142 caught, 27 unviable, 1 timeout from thedefault/new_from_osrecursion, 20 missed: the byte-valued Table 2 limits that no test can reach at 4 GiB and the/ 8on those constants); hkdf 160 (116 caught, 44 unviable, none missed). ascon 172 (145 caught, 21 unviable, 5 missed:pos % 8topos + 8in the three state-byte helpers, where a release build masks the shift count to six bits, and|to^after the byte is masked out); cipher 773 (486 caught, 185 unviable, 1 timeout, 101 missed: nearly all in the streaming CCM worker,tag_block,impl_mul64andGcmKeyState::eq, whose known-answer tests live in the aes crate and are out of a crate-scoped run's reach, so a re-run with the aes tests included is queued on apollo, as are the three ML-KEM runs for report 13 and the three ML-DSA runs for report 14; numbers to follow).Assisted-by: Claude:claude-opus-5-5
🤖 Generated with Claude Code