Skip to content

Fixes from the crate-by-crate review of release/0.1.3alpha (reports 1–15) - #190

Closed
dghgit wants to merge 57 commits into
release/0.1.3alphafrom
feature/release-review
Closed

dghgit wants to merge 57 commits into
release/0.1.3alphafrom
feature/release-review

Conversation

@dghgit

@dghgit dghgit commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Issue Link

No issue filed. These are fixes for mwcw's correctness reviews of release/0.1.3alpha at 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_suspended refuses a byte count that disagrees with the buffer offset, or that is above 2^61−1.
    • The docs give the correct message-length limit.
    • The padding at l ≡ 447 (mod 512), where the draft's prose and formula disagree, is pinned by a known answer from an independent reference checked against openssl.
  • sm3, factory, cli: HKDF-SM3, the HKDF the RFC 8998 TLS 1.3 SM suites use:

    • an alias, a suspended-state length, a KDFFactory arm, an hkdf-sm3 subcommand and a bench;
    • known answers are RFC 5869 A.1–A.3's inputs through OpenSSL 3.0.13 with SM3, since there are no published HKDF-SM3 vectors.
  • sm3, mem_usage_benches: bench_sm3_mem_usage and a measured per-entry-point stack table for SM3, HMAC-SM3 and HKDF-SM3.

  • utils:

    • Secret's equality now needs the new unsafe trait NoPadding, which closes a soundness gap: a padded user type could have its uninitialised padding read by ==. ZeroizablePrimitive stays safe, so mlkem and mldsa keep forbid(unsafe_code).
    • is_bit_set returns FALSE for an index past the width.
    • The version-header length is a compile-time check.
    • A version component above 255 fails the build in release too.
    • Doc fixes.
  • ascon:

    • AsconHash256::do_final_partial_bits now follows the Hash contract.
    • The decryptor's resume refuses held-back bytes outside the data phase.
    • A squeezer suspended before its first output resumes as a squeezer.
    • The Security Considerations section adds SP 800-232 R5–R7's limits and the clone hazard.
    • KEY_LEN/NONCE_LEN/TAG_LEN become ASCON_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_rng sets its metadata through the setters, and a zero-length draw is Zeroized.
    • Becoming Zeroized or Unknown drops the strength, which removes a reachable panic in truncate.
    • A clone taken inside a hazardous-operations window has the window closed.
  • hmac: an end-to-end test of HmacKey::<SHA224>::generate with the default 256-bit RNG.

  • base64:

    • Input ending one symbol into a block is TruncatedInput, where the decoder built bytes from leftover state.
    • Anything after the padding other than more padding (and whitespace, when it is skipped) is InvalidPadding, where it was silently dropped. This follows RFC 4648 §3.3 and §4.
  • sha2:

    • An invalid SHA512t<T> is a compile error on the resume path too.
    • SHA-224/256 from_suspended refuses 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_barrier is public.
    • A non-MACKey salt no longer credits entropy to HKDF's output label.
    • The round-key widening in the 2- and 4-block paths folds into a multiply of a secret: a no_std harness built with -Zbuild-std shows a __mulsi3 / __muldi3 call on AVR, muls / __aeabi_lmul on Cortex-M0 and __muldi3 on 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.
    • expand takes the key array (the length is a compile-time assertion, as_chunks replaces the unwrap); the plane-agreement debug_assert that short-circuited on key bytes goes; GCM_NONCE_LEN is 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*::new and ParallelHash*::new return a Result and refuse an output_len above u64::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.
    • SHA3 refuses a resumed state in the squeezing phase, as SHAKE did.
    • The KDF entropy counter and the ParallelHash block counter saturate, and the entropy counter is width-checked on resume.
    • squeeze works in slices of at most usize::MAX / 8 bytes, so the bit count cannot wrap on 32-bit targets.
    • The two tests the review asked for: TupleHash at 264 bits against Section 5.3 written out by hand over cSHAKE (a right_encode value 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.rsp vectors (no-reseed and pr-false, SHA-256 and SHA-512, with and without personalization string and additional input), where it failed every one:

    • V and C are seedlen bits (440 / 888), not 512.
    • Hash_df encodes no_of_bits_to_return big-endian.
    • reseed_counter is added as an integer, not its byte-reversed bytes.
    • For SHA-256, H is added at the low end of V, not shifted by 256 bits.
    • The one known-answer test compared against the code's own output; its values are now computed independently from Section 10.3.1. The CAVP files are read from 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):

    • On resume, a strength label above the hash's cap (half its output), or any entropy or strength on a state that was never initialized, is refused: a pre-init state with those two bytes edited used to label the PRK of HMAC(0^64, "") as CryptographicRandom at 256-bit.
    • A salt credits entropy only when typed 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 now Unknown. Existing callers and the CLI type their salts MACKey, so nothing else changes.
    • The report's bug H1 (HmacKey::generate on sub-32-byte hashes) and K1 (the resumed entropy counter) were already closed on this branch.
  • cipher, core-test-framework, ascon (report 12):

    • GCM refuses a resumed CTR counter below 2: GCTR starts at inc32(J0) (SP 800-38D Algorithm 4 step 3), J0 itself is the tag mask, so a state claiming 0 or 1 would emit CIPH_K(J0) as keystream.
    • CCM bounds a resumed counter index by the payload the mode can carry rather than the counter field's width, which for a 7-byte nonce is u64::MAX and overflowed on the next block.
    • One rule for the family, now checked by the AEAD suite: an empty data call is a no-op that leaves the AAD phase open. GCM and Ascon closed it; CCM did not.
    • The CMAC doc no longer claims an all-zero key is accepted.
    • Ci1, Ci3, Ci4, Ci5, Ci7 and Ci9 had landed with Key object rework: the traits lose their constructors, every mode gets a key, and workers borrow the key #172; Ci10 went with the worker constructors.
  • mlkem-common, mlkem, mlkem-lowmemory (report 13):

    • The k-term dot product is reduced before the inverse NTT (M1). Each product coefficient is a sum of two Montgomery reductions in (−q, q), so the sum reaches 16q for ML-KEM-1024, and 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_reduce is valid for every i16 (M2): at or below −9q the t·q product 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.
    • m′, K′, r′, K̄ and the shared secret of decapsulation, m and K of encapsulation, and d and σ of key generation are Secret buffers, and the private-key comparison encodes into Secret buffers rather than plain arrays of up to 3168 bytes (M3). The hazmat encaps_with_randomness keeps its by-value contract.
    • The seed error text names KeyType::CryptographicRandom, the variant the check accepts (M5).
    • M4 and M6 to M13 were notes; nothing changed for them.
  • core, mldsa-common, mldsa, mldsa-lowmemory (report 14):

    • The low-memory signer's iteration-cap return clears the caller's buffer, which held a rejected candidate's c̃ and z (M1; FIPS 204 Section 6.2: "no other output, destroying the results of the unsuccessful signing attempts"). The cap is reachable only at about 2^−315.
    • ρ″ in both signers and ρ′ in the full key generation are 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_norm OR-accumulates over all 256 coefficients and make_hint is 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.
    • SignatureError gains RejectionLimitExceeded and PreHashTooWeak in place of GenericError strings, ConsistencyCheckFailed loses its empty parentheses, the seed error names a KeyType that exists, the unit-error decoders return Option, and the key, worker and crate docs say signing can fail at the cap (M4).
    • Twin alignment (M5): the low-memory public key and the full crate's key-pair check compare in constant time, the low-memory tr() returns a reference as the full crate's does, and both crates re-export MLDSA_SEED_LEN. compute_z_component no longer returns a Result with no error path, and the signer doc's duplicated opening is gone (M6).
    • M7: SHAKE128 and SHAKE256 are now pre-hashes. The sha3 crate gives the SHAKE types HashAlgParams with 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:

    • Bugs: mlkem1024 keygen called 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.
    • The six ML-DSA and three ML-KEM command bodies and their twelve key parsers are one generic body each over a table of the parameter set's key types, lengths and functions, so a length or a function can no longer be right for two sets and wrong for the third (L22); the bit-count and name dispatches with panicking wildcard arms are enums.
    • One input rule for every subcommand (L11, L12, L13): inline values are hex; files, and the stdin a command decodes, are raw bytes, or hex text under the new global --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* -k is the key file as for hmac-*, ascon-aead128 --ad is --aad with an --aad-file, ascon-cxof128 --customization is the string and --customization-hex the bytes, and the SP 800-185 commands gain --customization-hex. The conventions are written up on the helpers module.
    • Keys, seeds, private keys, salts and IKM are read into a zeroized KeyMaterial buffer, never a Vec (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 --ctfile report 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 with Error: (L15); hkdf -x ends in a newline (L16); the help-text errors are fixed (L18); #![forbid(unsafe_code)] and a clippy-clean package (L19).
    • Parity with the library (L17, L21): hmac-sha224, hmac-sha384, hmac-sha3-*, cshake*, kmacxof*, tuplehashxof*, parallelhashxof*, aes*-cmac, aes*-gmac, --hash sha256|sha512|shake128|shake256 on hash-mldsa*, --deterministic, compute-mu/sign-mu/verify-mu, and rng --hash and the hazardous rng --seed-file. Not added: the low-memory twins (same outputs) and SHA512t at other t (a const generic).
    • Tests: the reviewer's eight scripts (sha2, sha3, sm3, hmac KATs, encoders, mlkem, mldsa, rng), read and adapted to the input rule, plus their extensions for the new subcommands and a CMAC/GMAC script against the ACVP files; every known answer names its vector file. The three macOS failures of the existing suite (GNU timeout, BSD wc padding) 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 KDF impl 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 and Display prints 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 no Drop; the DRBG trait docs drop the InsufficientSeedEntropy check 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:

    • ascon P190-ascon-1 (hardening): do_decrypt_inplace now ends the AAD phase on its first non-empty call, as do_decrypt_out does, so the two decrypt entry points run one phase machine; a test drives both with every byte held back.
    • aes P190-aes-1/2/3: the cross-target assembly check is in the tree and in CI: dev_scripts/aes_ct_harness (the two splat bodies 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) and aes-ct-asm.yml. The splat comment records that the architecture-only gate makes 32-bit Arm, x86 and wasm32 pay the barrier. The dead allow(unused_imports) is gone.
    • cipher P190-cipher-1/2, core P190-cipher-3: the CCM counter-index test adds the q = 8 case; GCM's resumed-counter rule reads the counter through a new CtrKeyStream::counter_from_state; the core do_update_aad docs carry the empty-call rule on both sides.
    • mlkem P190-mlkem-2: a unit test that dot_product returns reduced coefficients, which pins the reduction M1 restored; it showed poly_reduce's doc giving [0, q) for a reduction into [0, q], fixed in mlkem-common.
    • Docs and comments: utils P190-utils-1 (the NoPadding contract 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).
    • Recorded, no change: P190-core-2 (moves with C10), P190-sha2-1, P190-sha3-1 (C15), P190-hmac-1, P190-hkdf-2, P190-factory-1 (F4), P190-cli-1/2 (probe-script aging; an allowed ParallelHash block size), P190-mldsa-1 and P190-mlkem-1 (the breaking list below), P190-mldsa-3 (deferred below).

Deferred, with reasons

Scope and Risk

  • Packages: utils, core, core-test-framework, ascon, sm3, sha2, sha3, hmac, hkdf, base64, aes, cipher, rng, factory, cli, mem_usage_benches.
  • Breaking changes, which the alpha status allows:
    • NoPadding is required for Secret<T>: PartialEq.
    • Ascon's length constants are renamed.
    • Base64Error has two new variants.
    • MACFactory's 256-bit default is a different MAC.
    • base64 now refuses input it used to accept.
    • TupleHash*::new and ParallelHash*::new return a Result.
    • Every Hash_DRBG output for a given seed changes (it is now the standard's).
    • ct::value_barrier is public.
    • A non-MACKey salt no longer credits entropy to HKDF's output label.
    • SignatureError::ConsistencyCheckFailed is a unit variant (a match on ConsistencyCheckFailed() stops compiling), and SignatureError gains RejectionLimitExceeded and PreHashTooWeak.
    • bouncycastle_mldsa_lowmemory::MLDSAPrivateKeyTrait::tr returns &[u8; 64] rather than [u8; 64].
    • bouncycastle_mlkem_common::polynomial::Polynomial::from_msg takes &[u8; 32] (a #[doc(hidden)] item, reachable all the same).
    • HKDF_SM3_NAME moved from sm3::hmac to sm3::hkdf.
  • Regression likelihood: low. No known-answer suite changed, and each fix has a test that fails without it.
  • Verified:
    • Every commit builds.
    • The tip passes all 182 workspace test suites and the CLI shell tests, and fmt is clean.
    • cargo-mutants ran on each changed crate before the rebase onto Key object rework: the traits lose their constructors, every mode gets a key, and workers borrow the key #172. The numbers are in each commit message, and every miss is either an equivalent mutant or a gap the reviews already list.
    • The CLI shell suite (cli/tests/test_all.sh): 20 files, all passing. cargo-mutants is not meaningful for the cli package, which has no Rust tests.
    • 2026-11-04: the tip passes the whole workspace; cargo-mutants on apollo, scoped to the changed files: aes 278 mutants (182 caught, 81 unviable, 15 missed: the twelve |/^ equivalents the review lists plus the three SymmetricCipherKey fast-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 the squeeze slice size, larger than any buffer either way); rng 190 (re-run after the two test additions for the reseed counter's increment and add_seed_keymaterial: 142 caught, 27 unviable, 1 timeout from the default/new_from_os recursion, 20 missed: the byte-valued Table 2 limits that no test can reach at 4 GiB and the / 8 on those constants); hkdf 160 (116 caught, 44 unviable, none missed). ascon 172 (145 caught, 21 unviable, 5 missed: pos % 8 to pos + 8 in 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_mul64 and GcmKeyState::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

dghgit added 13 commits November 3, 2026 23:42
…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
@hubot
hubot deleted the feature/release-review branch November 3, 2026 13:17
@hubot
hubot deleted the branch release/0.1.3alpha November 3, 2026 13:17
@hubot
hubot restored the feature/release-review branch November 3, 2026 14:53
… 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
@dghgit dghgit changed the title Fixes from the crate-by-crate review of release/0.1.3alpha (reports 1–7) Fixes from the crate-by-crate review of release/0.1.3alpha (reports 1–10) Nov 3, 2026
… 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
@dghgit dghgit changed the title Fixes from the crate-by-crate review of release/0.1.3alpha (reports 1–10) Fixes from the crate-by-crate review of release/0.1.3alpha (reports 1–11) Nov 3, 2026
…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
dghgit added 14 commits November 4, 2026 12:51
…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
…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
@dghgit

dghgit commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Squash-merged onto release/0.1.3alpha as 029a17b (tree identical to this branch's head c3c5e9d, plus the release-notes commit 9068290 that was already on the branch). Verified on the squashed tree before committing: fmt clean, every target builds, 1282 workspace tests pass, rustdoc 0 warnings.

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
@dghgit

dghgit commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Merged by hand as squash commit.

@dghgit dghgit closed this Nov 4, 2026
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
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant