Skip to content

Add BLAKE2b-512, BLAKE2s-256 (RFC 7693) and BLAKE3 (#123) - #180

Closed
roy-basmacier wants to merge 18 commits into
bcgit:release/0.1.4alphafrom
roy-basmacier:feature/123-blake
Closed

roy-basmacier wants to merge 18 commits into
bcgit:release/0.1.4alphafrom
roy-basmacier:feature/123-blake

Conversation

@roy-basmacier

@roy-basmacier roy-basmacier commented Oct 25, 2026 •

Copy link
Copy Markdown

Adds the three BLAKE hashes requested in #123 for OpenSSL provider provisioning: two new crates, wired into the factories, the CLI and the stack-memory benches. Base: release/0.1.4alpha, rebased onto 6144224 (the #194 MAC rework). Other BLAKE2 digest lengths, salt/personalisation, BLAKE2bp/sp/X and tree mode are out of scope.

blake2 (new crate bouncycastle-blake2):

  • BLAKE2b_512 and BLAKE2s_256 implement Hash and Suspendable. G, F, the parameter block and the final-t rule are commented against RFC 7693 s. 3.1-3.3, and blake2b.rs and blake2s.rs are kept structurally identical.
  • The native keyed mode (not HMAC): the key objects BLAKE2b_512_MACKey and BLAKE2s_256_MACKey (FromAnyKeyMaterial, MacGenerationKey, MacVerificationKey at N = 64 / 32) hand out BLAKE2b_512_MAC<'k, Dir> / BLAKE2s_256_MAC<'k, Dir> workers. Key checks run in the order type, strength, length (1..=64 / 1..=32).
  • AlgorithmOID: id-blake2b512 and id-blake2s256 (RFC 7693 s. 4), on the hash and MAC types.
  • Suspended hash states are 212 bytes (BLAKE2b-512) and 108 bytes (BLAKE2s-256). Resume refuses an h that is not the unkeyed initial state at t == 0 (constant-time compare, which also catches a keyed initial state), a t that is not a whole number of blocks or lies in the upper half of its range (so a resumed state cannot wrap the counter), and an empty buffer after a compression.

blake3 (new crate bouncycastle-blake3):

  • BLAKE3 implements Hash and XOF in hash mode, and BLAKE3Squeezer streams the extended output. Both are Suspendable, with states of 1874 and 116 bytes. Hash resume refuses any chunk-state / CV-stack combination no input produces, including an empty chunk after the first. Squeezer resume accepts only a root a tree can have (a parent node with a 64-byte block, or a chunk's last block), refuses an output block counter above 2^58 (s. 2.6: output is below 2^64 bytes) so a resumed stream can never wrap, and recomputes the partly read output block instead of storing it.
  • derive_key is its own type, BLAKE3DeriveKey (Hash + XOF, constructed with a context string). It and its squeezer BLAKE3DeriveKeySqueezer are deliberately not Suspendable, because their state would contain key material. compile_fail doctests pin this.
  • keyed_hash: the key object BLAKE3KeyedHashKey (exactly 32 bytes) hands out BLAKE3KeyedHash<'k, Dir> workers, which read the key words through the borrow; the generator keeps an inherent into_squeezer for extended keyed output. BLAKE3KeyedSqueezer is neither Suspendable nor Clone.
  • The engine follows the reference implementation's chunk state and its 54-entry chaining-value stack (spec s. 2.4-2.6, 5.1). Chaining values and compression outputs outside the compression function are held in Secrets (key-derived in the keyed modes); the compression function's own working state is not zeroized, as in SHA-2 and BLAKE2 (doing so cost about 10% more instructions per MiB).

Both crates:

  • The MAC workers follow The MAC worker splits into MACGenerator and MACVerifier, with the tag length in the type #194: Dir = Encrypting implements MACGenerator, Dir = Decrypting MACVerifier; the tag is the full 64 / 32 / 32 bytes as an array, with no run-time truncation; verification returns Result<(), MACError> and recomputes the tag into a Secret. The workers borrow their key (BLAKE2's still copy the keyed state, which they advance), and implement neither Suspendable nor Clone, per QUALITY_AND_STYLE.md; a MAC in progress is paused by holding its worker.
  • Every _out function zeroizes its buffer before any error check. Partial-bit input: 0 bits is a whole-byte message, 1..=7 returns InvalidInput, and more returns InvalidLength.
  • Each crate's Memory Usage section gives the object sizes, the suspended-state lengths and the measured peak stack per operation (stack painted below a non-inlined call, release with thin LTO, x86_64, rustc 1.97.0-nightly): BLAKE2b about 1.1 KB and BLAKE2s about 0.7 KB, BLAKE3 about 5-7 KB, and BLAKE3 suspend+resume about 11.7 KB.

factory: HashFactory gains BLAKE2b-512, BLAKE2s-256 and BLAKE3; XOFFactory gains BLAKE3; MACFactory and MACFactoryWorker<'k, Dir> gain the three BLAKE MAC keys and workers, with their tag lengths through TagLen.

Open question: the factory enums' size. HashFactory and XOFFactory are 1888 bytes and MACFactoryWorker 1872, because BLAKE3's hasher carries a 1728-byte chaining-value stack sized for the full 2^64-byte input range (spec s. 5.4). Without BLAKE3 the largest variant is a SHA-3 or KMAC state of at most 440 bytes. An enum is as large as its largest variant, so every HashFactory / XOFFactory user pays this on its stack whichever algorithm it selects, and every move copies all of it. The factory crate's Memory Usage section documents it; nothing is changed yet. Options:

  1. Keep it, documented (the PR as it stands).
  2. Box the BLAKE3 variants: the enums go back to about 440 bytes, at the cost of a heap allocation for BLAKE3 and one more obstacle to no_std.
  3. Give BLAKE3 a smaller CV stack in the factory, capping its input there (an error path the other hashes do not have).
  4. Leave BLAKE3 out of HashFactory and XOFFactory, reachable only as its own type.

cli: blake2b-512, blake2s-256, blake2b-512-mac, blake2s-256-mac, blake3, blake3-keyed-hash and blake3-derive-key, all streaming (blake3-keyed-hash streams stdin in and the squeezed output out). cli/tests/test_blake.sh adds 17 tests on the shell harness. The BLAKE key loader reads --key / --key-file through the same load_secret as the HMAC/KMAC loader, so --key-file wins everywhere, as the help text says (test_hmac.sh pins this). It adds a per-algorithm length check with a message naming the limit. Both loaders warn on an all-zero key through one helper. That also fixes the HMAC/KMAC warning, which never fired: it tested for KeyType::Zeroized, and loaded keys are tagged Unknown.

tests: RFC 7693 Appendix A/B, selections from the official BLAKE2 KAT, the bc-test-data Blake2KAT.txt sweep (512 + 512 records), all 35 official BLAKE3 vectors in all three modes, and the core-test-framework suites for Hash, XOF, MAC keys, key construction and suspend/resume. Wycheproof has no BLAKE vectors. cargo mutants (apollo, 5014bcc; the later commits add mutants notes and docs only): blake2 326 tested, 244 caught, 70 unviable, 12 missed, all equivalent and noted at the site (two of them on the new t bound); blake3 422 tested, 342 caught, 61 unviable, 10 missed, 9 timeouts (mutants that make a fill or merge loop never end), every miss an equivalent noted at the site (one new, on the squeezer's buffer recompute); factory mac_factory.rs 55 tested, 25 caught, 30 unviable, 0 missed.

#![no_std] remains a todo in both crates, as in every hash crate, blocked by the Vec return types in core::traits::Hash.

Rebase onto 6144224: the six commits were replayed (the earlier merge commits of release are gone), with mac_factory.rs and its tests kept at the release's version during the replay, so the replayed commits in between do not build; the tip does. The port to the #194 traits is its own commit, followed by the review fixes below.

Review of 9059e06 (findings 1-9):

  1. BLAKE2 hash/MAC states interchangeable once t > 0: moot, because the MAC workers no longer suspend, so there is no MAC state to swap. With that gone the kk byte had nothing left to guard and was dropped from the state; the t == 0 check still refuses a keyed initial h.
  2. derive_key state holds the IKM: fixed, since BLAKE3DeriveKey and its squeezer are not Suspendable.
  3. Squeezer counter wrap: fixed, since resume refuses a counter above 2^58.
  4. t > 0 && buf_len == 0 accepted: fixed, InvalidData in both BLAKE2 variants.
  5. Non-constant-time resume compares: fixed. The keyed compares went with keyed suspend, and the remaining h and IV checks use ct_eq_bytes.
  6. CLI loaders with opposite precedence: fixed. Both read through load_secret (file wins) and share the zero-key warning. The BLAKE loader stays separate only for its length range.
  7. Factory enums at 1888 bytes: still open, see the question above.
  8. Repeated spec quotes: fixed. Each quote now has one home, on the Algorithm impl.
  9. Duplicate MIN_MAC_OUTPUT_LEN: gone with tag truncation in the port to The MAC worker splits into MACGenerator and MACVerifier, with the tag length in the type #194.

Review of 40aebcf (2026-11-10), all fixed:

  1. Keyed workers were Clone: the BLAKE MAC workers and BLAKE3KeyedSqueezer no longer are; compile_fail doctests pin the workers.
  2. BLAKE2 resume did not bound t: a forged t near the top wrapped the counter (a debug panic, a wrong digest in release); a t in the upper half of its range is now refused.
  3. BLAKE3 squeezer resume trusted a stored output buffer: the buffer is no longer stored and is recomputed.
  4. BLAKE3 squeezer resume accepted impossible root flags: only a parent node (b = 64) or a chunk's last block is accepted.
  5. BLAKE3 hash resume accepted an empty chunk after the first: refused.
  6. Verifiers left the recomputed tag in an unzeroized Vec: it is a Secret array.
  7. BLAKE2 suspend/resume staged state in plain arrays, and BLAKE3's keyed-mode chaining values were plain locals: Secrets now.
  8. blake3-keyed-hash panicked on a stdin read error and held the whole --length output: it streams through the shared helpers.
    Also from that review: the workers borrow their key, and public docs no longer carry internal detail (state layouts, other implementations' conventions).

Review of d279232 (2026-11-10, seven findings, no correctness bug):

  1. The MAC keys' generate repeated HMAC's and KMAC's draw-and-cap closure: fixed in core, KeyMaterial::from_rng_as_type(rng, len, key_type) is the one draw (from_rng wraps it), and the five generates are a line each.
  2. warn_if_zeroized_key hand-rolled the all-zero check: fixed, it uses bouncycastle_utils::ct::ct_eq_zero_bytes.
  3. load_blake_mac_key was a third MAC-key loader beside load_mac_key and load_key_object_typed, tagging the same bytes differently: fixed, helpers::load_mac_key(key, key_file, len) is the one MAC loader, with the length bound a parameter (None for HMAC and KMAC); the block ciphers' typed loader stays, being a different shape.
  4. BLAKE2's h_ct_eq re-serialized both sides to compare h with the initial state: fixed, from_suspended compares the state's own h bytes with the serialized constant, as BLAKE3 does, and h_ct_eq is gone.
  5. Err(..)? beside return Err(..) in BLAKE3KeyedHashKey's checks: fixed.
  6. Factory enum size: the open question above.
  7. Public from_suspended docs listed internal state combinations: fixed, they say a state the type did not produce is InvalidData; the checklist lives on the private read_state (BLAKE3) or at the checks themselves (BLAKE2, whose read_state doc defers to them), and the BLAKE2 state-length constants carry their layout as a code comment, as BLAKE3's do, not as public docs.

cargo mutants after the three commits (apollo): blake2 310 (230 caught, 70 unviable, 10 missed, the noted equivalents), blake3 417 (338 caught, 61 unviable, 9 missed, 9 timeouts, as noted), core from_rng / from_rng_as_type with the core, hmac and blake2 tests 10 (6 caught, 4 unviable, 0 missed).

Assisted-by: Claude:claude-mythos-5-1
Assisted-by: Claude:claude-fable-5-1
Assisted-by: Claude:claude-opus-5-5

@ounsworth ounsworth added this to the 0.1.4alpha milestone Oct 25, 2026
roy-basmacier pushed a commit to roy-basmacier/bc-rust that referenced this pull request Nov 2, 2026
blake2:
- The suspended state records the parameter block's key length kk (one
  byte; states grow to 213 / 109 bytes). After the first compression the
  hash and MAC states were byte-identical, so a MAC state resumed as a
  hash finished the MAC with no key, and a hash state resumed as a MAC
  verified a plain digest as a tag. Resume now refuses kk != 0 on the hash
  side and kk != the key's kk on the MAC side; a valid key of the same
  length is still not detected after a compression (documented).
- read_state rejects t > 0 with an empty buffer: update always keeps at
  least one byte back after a compression, and finishing such a state gave
  a digest of no byte string.
- The t == 0 h checks compare in constant time (ct_eq_bytes over the
  serialized words, held in Secret buffers).

blake3:
- derive_key moves to its own type, BLAKE3DeriveKey (Hash + XOF), which is
  not Suspendable, nor is its squeezer: a derive_key state held up to 64
  bytes of key material verbatim, which Suspendable forbids.
  BLAKE3::new_derive_key is removed; BLAKE3 is the hash mode only, and
  BLAKE3 / BLAKE3Squeezer resume refuse derive_key (and keyed) states.
- BLAKE3Squeezer resume rejects an output block counter above 2^58 (s. 2.6:
  ell < 2^64 bytes). A counter near u64::MAX panicked in debug builds and
  wrapped in release, re-emitting output already handed out.
- The hash-mode IV check compares in constant time.

Both crates use one MIN_MAC_OUTPUT_LEN, now in bouncycastle_core::traits
beside MAC, instead of a copy each.

cli: one MAC key loader for every MAC command (helpers::load_mac_key, with
the algorithm's key-length range). HMAC and KMAC now let --key-file win
over --key, as their help text always said, and report bad hex or a
missing key file instead of panicking. test_hmac.sh pins the precedence.

factory: a Memory Usage section. BLAKE3's 1728-byte chaining-value stack
makes HashFactory, XOFFactory and MACFactoryWorker 1888 bytes (SHA-3, at
440, was the largest before).

docs: each spec quote on security strength now lives once, on the
Algorithm impl, and the keyed BLAKE3 suspend note is stated once, on the
SuspendableKeyed impl. The derivation: the key words enter only as the
first chaining value of each chunk and parent (s. 2.4, 2.5), so while the
prefix is 65..=1024 bytes with an empty CV stack the state finishes any
extension within the first chunk without the key.
roy-basmacier pushed a commit to roy-basmacier/bc-rust that referenced this pull request Nov 2, 2026
blake2:
- The suspended state records the parameter block's key length kk (one
  byte; states grow to 213 / 109 bytes). After the first compression the
  hash and MAC states were byte-identical, so a MAC state resumed as a
  hash finished the MAC with no key, and a hash state resumed as a MAC
  verified a plain digest as a tag. Resume now refuses kk != 0 on the hash
  side and kk != the key's kk on the MAC side; a valid key of the same
  length is still not detected after a compression (documented).
- read_state rejects t > 0 with an empty buffer: update always keeps at
  least one byte back after a compression, and finishing such a state gave
  a digest of no byte string.
- The t == 0 h checks compare in constant time (ct_eq_bytes over the
  serialized words, held in Secret buffers).

blake3:
- derive_key moves to its own type, BLAKE3DeriveKey (Hash + XOF), which is
  not Suspendable, nor is its squeezer: a derive_key state held up to 64
  bytes of key material verbatim, which Suspendable forbids.
  BLAKE3::new_derive_key is removed; BLAKE3 is the hash mode only, and
  BLAKE3 / BLAKE3Squeezer resume refuse derive_key (and keyed) states.
- BLAKE3Squeezer resume rejects an output block counter above 2^58 (s. 2.6:
  ell < 2^64 bytes). A counter near u64::MAX panicked in debug builds and
  wrapped in release, re-emitting output already handed out.
- The hash-mode IV check compares in constant time.

Both crates use one MIN_MAC_OUTPUT_LEN, now in bouncycastle_core::traits
beside MAC, instead of a copy each.

cli: one MAC key loader for every MAC command (helpers::load_mac_key, with
the algorithm's key-length range). HMAC and KMAC now let --key-file win
over --key, as their help text always said, and report bad hex or a
missing key file instead of panicking. test_hmac.sh pins the precedence.

factory: a Memory Usage section. BLAKE3's 1728-byte chaining-value stack
makes HashFactory, XOFFactory and MACFactoryWorker 1888 bytes (SHA-3, at
440, was the largest before).

docs: each spec quote on security strength now lives once, on the
Algorithm impl, and the keyed BLAKE3 suspend note is stated once, on the
SuspendableKeyed impl. The derivation: the key words enter only as the
first chaining value of each chunk and parent (s. 2.4, 2.5), so while the
prefix is 65..=1024 bytes with an empty CV stack the state finishes any
extension within the first chunk without the key.
@dghgit
dghgit force-pushed the feature/key-rework-memory branch from 3c170c9 to 81cef74 Compare November 3, 2026 02:36
@hubot
hubot deleted the branch bcgit:feature/key-rework-memory November 3, 2026 13:17
@hubot
hubot deleted the branch bcgit:feature/key-rework-memory November 4, 2026 22:43
@dghgit
dghgit force-pushed the feature/123-blake branch from a1016d6 to 280b233 Compare November 5, 2026 01:35
dghgit pushed a commit to roy-basmacier/bc-rust that referenced this pull request Nov 5, 2026
blake2:
- The suspended state records the parameter block's key length kk (one
  byte; states grow to 213 / 109 bytes). After the first compression the
  hash and MAC states were byte-identical, so a MAC state resumed as a
  hash finished the MAC with no key, and a hash state resumed as a MAC
  verified a plain digest as a tag. Resume now refuses kk != 0 on the hash
  side and kk != the key's kk on the MAC side; a valid key of the same
  length is still not detected after a compression (documented).
- read_state rejects t > 0 with an empty buffer: update always keeps at
  least one byte back after a compression, and finishing such a state gave
  a digest of no byte string.
- The t == 0 h checks compare in constant time (ct_eq_bytes over the
  serialized words, held in Secret buffers).

blake3:
- derive_key moves to its own type, BLAKE3DeriveKey (Hash + XOF), which is
  not Suspendable, nor is its squeezer: a derive_key state held up to 64
  bytes of key material verbatim, which Suspendable forbids.
  BLAKE3::new_derive_key is removed; BLAKE3 is the hash mode only, and
  BLAKE3 / BLAKE3Squeezer resume refuse derive_key (and keyed) states.
- BLAKE3Squeezer resume rejects an output block counter above 2^58 (s. 2.6:
  ell < 2^64 bytes). A counter near u64::MAX panicked in debug builds and
  wrapped in release, re-emitting output already handed out.
- The hash-mode IV check compares in constant time.

Both crates use one MIN_MAC_OUTPUT_LEN, now in bouncycastle_core::traits
beside MAC, instead of a copy each.

cli: one MAC key loader for every MAC command (helpers::load_mac_key, with
the algorithm's key-length range). HMAC and KMAC now let --key-file win
over --key, as their help text always said, and report bad hex or a
missing key file instead of panicking. test_hmac.sh pins the precedence.

factory: a Memory Usage section. BLAKE3's 1728-byte chaining-value stack
makes HashFactory, XOFFactory and MACFactoryWorker 1888 bytes (SHA-3, at
440, was the largest before).

docs: each spec quote on security strength now lives once, on the
Algorithm impl, and the keyed BLAKE3 suspend note is stated once, on the
SuspendableKeyed impl. The derivation: the key words enter only as the
first chaining value of each chunk and parent (s. 2.4, 2.5), so while the
prefix is 65..=1024 bytes with an empty CV stack the state finishes any
extension within the first chunk without the key.
@dghgit
dghgit changed the base branch from feature/key-rework-memory to release/0.1.4alpha November 5, 2026 01:40
dghgit pushed a commit to roy-basmacier/bc-rust that referenced this pull request Nov 5, 2026
blake2:
- The suspended state records the parameter block's key length kk (one
  byte; states grow to 213 / 109 bytes). After the first compression the
  hash and MAC states were byte-identical, so a MAC state resumed as a
  hash finished the MAC with no key, and a hash state resumed as a MAC
  verified a plain digest as a tag. Resume now refuses kk != 0 on the hash
  side and kk != the key's kk on the MAC side; a valid key of the same
  length is still not detected after a compression (documented).
- read_state rejects t > 0 with an empty buffer: update always keeps at
  least one byte back after a compression, and finishing such a state gave
  a digest of no byte string.
- The t == 0 h checks compare in constant time (ct_eq_bytes over the
  serialized words, held in Secret buffers).

blake3:
- derive_key moves to its own type, BLAKE3DeriveKey (Hash + XOF), which is
  not Suspendable, nor is its squeezer: a derive_key state held up to 64
  bytes of key material verbatim, which Suspendable forbids.
  BLAKE3::new_derive_key is removed; BLAKE3 is the hash mode only, and
  BLAKE3 / BLAKE3Squeezer resume refuse derive_key (and keyed) states.
- BLAKE3Squeezer resume rejects an output block counter above 2^58 (s. 2.6:
  ell < 2^64 bytes). A counter near u64::MAX panicked in debug builds and
  wrapped in release, re-emitting output already handed out.
- The hash-mode IV check compares in constant time.

Both crates use one MIN_MAC_OUTPUT_LEN, now in bouncycastle_core::traits
beside MAC, instead of a copy each.

cli: one MAC key loader for every MAC command (helpers::load_mac_key, with
the algorithm's key-length range). HMAC and KMAC now let --key-file win
over --key, as their help text always said, and report bad hex or a
missing key file instead of panicking. test_hmac.sh pins the precedence.

factory: a Memory Usage section. BLAKE3's 1728-byte chaining-value stack
makes HashFactory, XOFFactory and MACFactoryWorker 1888 bytes (SHA-3, at
440, was the largest before).

docs: each spec quote on security strength now lives once, on the
Algorithm impl, and the keyed BLAKE3 suspend note is stated once, on the
SuspendableKeyed impl. The derivation: the key words enter only as the
first chaining value of each chunk and parent (s. 2.4, 2.5), so while the
prefix is 65..=1024 bytes with an empty CV stack the state finishes any
extension within the first chunk without the key.
@dghgit
dghgit force-pushed the feature/123-blake branch from 280b233 to d43cc3b Compare November 5, 2026 03:57
@hubot
hubot deleted the branch bcgit:release/0.1.4alpha November 7, 2026 20:03
@dghgit

dghgit commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

Merged release/0.1.4alpha (now at 563978a, the fix/supplied-nonce squash) into this branch and pushed 25e7334 to the PR head. The merge also brought in the #192 squash, which removed SuspendableKeyed from core, so a follow-up commit drops the BLAKE2b-512 MAC, BLAKE2s-256 MAC and BLAKE3 keyed-hash impls of it along with their state-length constants, docs and tests; the hash types and the BLAKE3 squeezer keep Suspendable as before. Workspace build, tests, rustdoc, rustfmt and test_blake.sh (16/16) pass on the new tip.

dghgit added a commit that referenced this pull request Nov 10, 2026
…it in two as the signature and cipher workers are split and moves the tag length into the type (item 1 of the core API consistency review of 2026-11-09, the MAC roles being the one pair whose split stopped at the key): in core, Encrypting, Decrypting and the sealed Direction move from bouncycastle-cipher to bouncycastle_core::direction (cipher re-exports them), MAC is replaced by MACGenerator<MAC_LEN> (do_update, do_final_out into a [u8; MAC_LEN], do_final) and MACVerifier<MAC_LEN> (do_update, do_verify_final(&[u8; MAC_LEN]) -> Result<(), MACError>) with no one-shots on the worker, no run-time length or InvalidLength on the streaming path, verification a Result with MACError::VerificationFailed and MACError::RNGError for a nonce draw, both dyn-compatible; MacGenerationKey<MAC_LEN> / MacVerificationKey<MAC_LEN> bound their workers on the pair and their one-shots take and return the array, GMAC gets the nonce-bound role pair NonceMacGenerationKey / NonceMacVerificationKey it lacked (create_generator(rng) returning the nonce, create_generator_nonce, create_verifier(nonce), the mac / mac_out / mac_nonce / mac_nonce_out / verify one-shots) in place of the inherent pair on BlockCipherKey, MacMode<P, MAC_LEN> has generate_init / verify_init for mac_init and NonceMacMode's GATs are bound; HMAC<'k, H, Dir> implements the pair with the role impls bound H: HMACParams<MACKey = KeyMaterial<N>> under a const assertion N == H::OUTPUT_LEN, hazmat::finish_out infallible, MIN_FIPS_DIGEST_LEN gone with the truncating finals, HMAC::keygen_from_rng now HmacKey::keygen_from_rng, the HMAC_SHA256<'k, Dir> aliases and the worker not Clone; KMAC's output length is the key type's const parameter (KMAC128Key<MAC_LEN = 32>, KMAC256Key<MAC_LEN = 64>, with_params taking only the customization string) with the SP 800-185 Sec 8.4.2 floor and right_encode cap a compile-time check on the key constructors and on KMACInternal::finish, so no tag is produced or verified below the floor even from a resumed state (compile_fail doctests at lengths 0 and 3, a 4-byte suspend round trip), KMAC128<Dir, MAC_LEN> the worker whose suspended state carries the length; HKDF's expand loop runs HMAC through the hazmat halves; Cmac<'k, P, Dir, BLOCK_LEN> with the tag the full block (CMAC_MIN_TAG_LEN gone), Gmac's inherent methods the two trait impls, GMAC's nonce drawn through modes::iv::random_iv, which returns RNGError; Poly1305<Dir> from Poly1305Key::into_generator / into_verifier with the alloc dependency gone and the verifier holding its recomputed tag in a Secret as HMAC's and KMAC's do, the AEAD holding Poly1305<Dir> through a private trait; the RFC 6979 HMAC calls in ecdsa taking the array final; AES_CMAC_128<'k, Dir> and the ARIA and Camellia siblings; MACFactory as the run-time form with inherent mac / mac_out / verify over slices, output_len read off the key and worker types through one private TagLen trait, and MACFactoryWorker<'k, Dir> whose do_final_out finishes into out[..N] and reports InvalidLength for a wrong-length buffer or tag; the CLI's do_mac making only the direction a command runs and refusing a wrong-length --verify before reading input, kmac* computing through KMACXOF's length-binding final read compared in constant time through bouncycastle-utils, aes*-gmac through the nonce-bound roles; the harnesses naming the directional workers; and, from the review rounds, every key-level one-shot that can fail before its zeroizing helper (the five drawing forms and the four declared-length CCM forms) clearing its buffer first with core_test_framework::FailingRNG and a CCM<13, 16> refusal case pinning it, the _out rule in QUALITY_AND_STYLE.md stating its one exception (an infallible [u8; N] overwritten in full), and the test-framework suites taking the tag as an array, sweeping every single-bit change, and covering the nonce-bound pair. Dropped with no twin: tag truncation through the API (a protocol truncates the returned array; a truncated tag cannot be verified through the role or factory APIs, SP 800-38B Sec 6.3 is not implemented, and the Wycheproof HMAC truncated groups and RFC 4231 case 5 are prefix-compared), Poly1305Key::into_mac, MACFactory's role-trait impls, and KMAC128Key::<16>::with_params in expression position since an alias's default const is not applied there. Reviewed in two rounds by Megan's three-agent setup (mythos, codex, muse; 0 Bug, the three Hardening items and sixteen Notes all fixed, "ready to approve" at c308fa2) and by a final high-effort pass before this squash; mutants on apollo in the PR: hmac 67 (42/24/1 equivalent), poly1305 184 (165/14/5 equivalent), chacha20-poly1305 121 (77/44/0), factory mac_factory.rs 54 (26/28/0), sha3 kmac.rs 111 (55/54/2 equivalent), cipher cmac/gmac/key.rs with the aes tests 162 (75/82/5 equivalent or unreachable), hkdf 120 (84/36/0), core MAC roles 28 (20/8/0). feature/eccdh, feature/rsa-sig, feature/tdes, PR #177 and PR #180 take the new trait names by merge. Created by David Hook.

Assisted-by: Claude:claude-mythos-5-1
royb and others added 13 commits November 10, 2026 15:07
Adds the three BLAKE hashes requested in bcgit#123 for OpenSSL provider
provisioning, on the key-object API of feature/key-rework-memory (bcgit#172):
two new crates wired into the factories, the CLI and the stack-memory
benches. Other BLAKE2 digest lengths, salt/personalisation, BLAKE2bp/sp/X
and tree mode are out of scope.

blake2 (new crate bouncycastle-blake2):
- BLAKE2b_512 and BLAKE2s_256 implement Hash and Suspendable; G, F, the
  parameter block and the final-t rule are commented against RFC 7693
  s. 3.1-3.3. blake2b.rs and blake2s.rs are kept structurally identical.
- The native keyed mode (not HMAC): key objects BLAKE2b_512_MACKey and
  BLAKE2s_256_MACKey (FromAnyKeyMaterial, MacGenerationKey,
  MacVerificationKey) hand out BLAKE2b_512_MAC / BLAKE2s_256_MAC workers.
  Key checks run type, then strength, then length (1..=64 / 1..=32).
- AlgorithmOID: id-blake2b512 and id-blake2s256 (RFC 7693 s. 4), on the
  hash and MAC types.

blake3 (new crate bouncycastle-blake3):
- BLAKE3 implements Hash and XOF in hash mode (new) and derive_key mode
  (new_derive_key); BLAKE3Squeezer streams the extended output.
- keyed_hash: key object BLAKE3KeyedHashKey (exactly 32 bytes) hands out
  BLAKE3KeyedHash workers, which keep an inherent into_squeezer for
  extended output. BLAKE3KeyedSqueezer is deliberately not Suspendable.
- The engine follows the reference implementation's chunk state and
  54-entry CV stack (spec s. 2.4-2.6, 5.1).

Both crates:
- MAC output truncates to the caller's buffer with a 4-byte floor
  (MIN_MAC_OUTPUT_LEN); every _out function zeroizes the buffer before any
  error check. Partial-bit input: 0 bits is a whole-byte message, 1..=7
  returns InvalidInput, more returns InvalidLength.
- Keyed workers implement SuspendableKeyed with the key object as the key.
  Suspended states hold no key bytes, and resume rebuilds the key-derived
  fields from the key object.
- Design note: a suspended keyed state still lets its holder compute tags
  for extensions of the absorbed prefix (BLAKE2 once the key block is
  compressed; BLAKE3 only within the first chunk). HMAC does not have this
  property. It is documented and pinned by tests.

factory: HashFactory gains BLAKE2b-512, BLAKE2s-256 and BLAKE3; XOFFactory
gains BLAKE3; MACFactory gains the three BLAKE MAC keys and workers.

cli: blake2b-512, blake2s-256, blake2b-512-mac, blake2s-256-mac, blake3,
blake3-keyed-hash and blake3-derive-key, all streaming. cli/tests/
test_blake.sh adds 16 tests on the shell harness.

tests: RFC 7693 Appendix A/B, selections from the official BLAKE2 KAT, the
bc-test-data Blake2KAT.txt sweep (512 + 512 records), all 35 official BLAKE3
vectors in all three modes, and the core-test-framework suites for Hash,
XOF, MAC keys, key construction and suspend/resume. Wycheproof has no BLAKE
vectors. cargo mutants: blake2 378 tested, 10 missed; blake3 384 tested,
9 missed, 8 timeouts (non-terminating loops); every miss is an equivalent
rewrite with a "Mutants note:" at the site.

the Vec return types in core::traits::Hash.

Assisted-by: Claude:claude-mythos-5-1
Assisted-by: Claude:claude-fable-5-1
Assisted-by: Claude:claude-opus-5-5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
blake2:
- The suspended state records the parameter block's key length kk (one
  byte; states grow to 213 / 109 bytes). After the first compression the
  hash and MAC states were byte-identical, so a MAC state resumed as a
  hash finished the MAC with no key, and a hash state resumed as a MAC
  verified a plain digest as a tag. Resume now refuses kk != 0 on the hash
  side and kk != the key's kk on the MAC side; a valid key of the same
  length is still not detected after a compression (documented).
- read_state rejects t > 0 with an empty buffer: update always keeps at
  least one byte back after a compression, and finishing such a state gave
  a digest of no byte string.
- The t == 0 h checks compare in constant time (ct_eq_bytes over the
  serialized words, held in Secret buffers).

blake3:
- derive_key moves to its own type, BLAKE3DeriveKey (Hash + XOF), which is
  not Suspendable, nor is its squeezer: a derive_key state held up to 64
  bytes of key material verbatim, which Suspendable forbids.
  BLAKE3::new_derive_key is removed; BLAKE3 is the hash mode only, and
  BLAKE3 / BLAKE3Squeezer resume refuse derive_key (and keyed) states.
- BLAKE3Squeezer resume rejects an output block counter above 2^58 (s. 2.6:
  ell < 2^64 bytes). A counter near u64::MAX panicked in debug builds and
  wrapped in release, re-emitting output already handed out.
- The hash-mode IV check compares in constant time.

Both crates use one MIN_MAC_OUTPUT_LEN, now in bouncycastle_core::traits
beside MAC, instead of a copy each.

cli: one MAC key loader for every MAC command (helpers::load_mac_key, with
the algorithm's key-length range). HMAC and KMAC now let --key-file win
over --key, as their help text always said, and report bad hex or a
missing key file instead of panicking. test_hmac.sh pins the precedence.

factory: a Memory Usage section. BLAKE3's 1728-byte chaining-value stack
makes HashFactory, XOFFactory and MACFactoryWorker 1888 bytes (SHA-3, at
440, was the largest before).

docs: each spec quote on security strength now lives once, on the
Algorithm impl, and the keyed BLAKE3 suspend note is stated once, on the
SuspendableKeyed impl. The derivation: the key words enter only as the
first chaining value of each chunk and parent (s. 2.4, 2.5), so while the
prefix is 65..=1024 bytes with an empty CV stack the state finishes any
extension within the first chunk without the key.
…nges

Rebased onto bb67b77, which reshaped Hash and MAC (711a571, 0e391c4):

- Hash and MAC lose max_security_strength(&self) for the AlgorithmInfo
  supertrait, which every Algorithm implements. The BLAKE impls drop their
  own copies; the values come from the Algorithm impls as before.
- The one-shot hash(data) / hash_out(data, out) move off Hash onto
  HashOneShot. BLAKE2b_512, BLAKE2s_256 and BLAKE3 implement it, so the call
  is BLAKE3::hash(msg). BLAKE3DeriveKey takes its context at construction,
  so like cSHAKE it has inherent hash / hash_out methods instead.
- factory: the BLAKE arms are added to the AlgorithmInfo impls and to
  MACFactoryWorker's MAC impl, whose forwarding macro became written-out
  matches upstream.

Tests, doc examples and the mem benches follow the same call forms.

The CLI MAC key loader now replaces the upstream fix to mac_cmd's own
loader (same file-wins order); upstream's HMAC precedence test is kept and
this branch's duplicate dropped.
…ver the rebase from feature/key-rework-memory

The release's CLI already reads every MAC key through mac_cmd's own load_mac_key, which takes
any length (HMAC hashes a long key, KMAC encodes its length), so the review's shared 64-byte
loader is not carried over; the bounded loader stays BLAKE-only as helpers::load_blake_mac_key,
now over the release's load_secret input rule. FixedSeedRNG moved to bouncycastle-rng and lost
its const parameter.

Assisted-by: Claude:claude-opus-5-5
release/0.1.4alpha removed SuspendableKeyed from core with the KEM squash (bcgit#192, the bcgit#173 rule that a keyed worker is not rebuilt without its key and its state is key-derived material), so the BLAKE2b-512 MAC, BLAKE2s-256 MAC and BLAKE3 keyed-hash impls of it go, with their SUSPENDED_*_MAC_STATE_LEN / SUSPENDED_BLAKE3_KEYED_HASH_STATE_LEN constants, the write_state zero_buf switch that only they used, the lib.rs paragraphs and security notes on suspended MAC states, the keyed cases of the BLAKE2 suspend tests (the kk-above-maximum check stays as suspend_rejects_bad_kk), the BLAKE3 keyed suspend tests and the harness's MAC suspend bench. The hash types and the BLAKE3 squeezer keep Suspendable unchanged.

Assisted-by: Claude:claude-mythos-5-1
…ables, zero-key warning

On top of 25e7334 (the MAC workers no longer suspend):

- blake2: with no keyed state to suspend, the suspended hash state's kk byte had
  nothing left to tell apart, so it goes: the states are 212 bytes (BLAKE2b-512)
  and 108 (BLAKE2s-256), and read_state loses its kk bound. The t == 0 check of h
  against the unkeyed initial state still refuses a keyed initial state, and a
  test now forges one. BLAKE2s objects shrink from 120 to 112 bytes.
- blake2, blake3: the Memory Usage sections give measured peak stack per
  operation (a stack-painting probe over the whole call chain, release, x86_64);
  the massif harnesses only resolve their own println! here.
- factory: XOFFactorySqueezer is 432 bytes and MACFactory 864, not 448 and 896;
  without BLAKE3 the largest variant is a SHA-3 or KMAC state of at most 440.
- blake3: a test for the byte count BLAKE3DeriveKey::hash_out returns, which
  cargo mutants found unchecked.
- mem_usage_benches: a BLAKE2s hash suspend bench beside the BLAKE2b one.
- cli: the BLAKE MAC key loader warns on an all-zero key as load_mac_key does,
  through one helper, warn_if_zeroized_key. The HMAC/KMAC warning never fired
  (it tested KeyType::Zeroized, and loaded keys are tagged Unknown); the helper
  checks the bytes.

cargo mutants: blake2 352 tested, 10 missed (documented equivalents); blake3 402
tested, 9 missed (documented equivalents), 8 timeouts (non-terminating loops).

Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-sonnet-5-5
The rebase onto release/0.1.4alpha 6144224 (bcgit#194) replaced the MAC trait
with MACGenerator<MAC_LEN> / MACVerifier<MAC_LEN> and moved the tag length
into the type. The replayed commits keep the release's mac_factory.rs and
its tests; the BLAKE wiring is redone here on the new shape.

- blake2, blake3: the workers are BLAKE2b_512_MAC<Dir>, BLAKE2s_256_MAC<Dir>
  and BLAKE3KeyedHash<Dir>, Encrypting implementing MACGenerator and
  Decrypting MACVerifier, as HMAC<'k, H, Dir> does. The keys implement
  MacGenerationKey<N> / MacVerificationKey<N> at N = 64, 32 and 32. The
  verifiers recompute the tag into a Secret and return
  MACError::VerificationFailed. into_squeezer is on the BLAKE3 generator only.
- Tag truncation goes, as it did for HMAC, KMAC and CMAC in bcgit#194, and with
  it core::traits::MIN_MAC_OUTPUT_LEN, which this PR had added.
- The workers and BLAKE3KeyedSqueezer are no longer Clone (QUALITY_AND_STYLE:
  keyed workers implement neither Suspendable nor Clone); compile_fail
  doctests pin the workers, and the mac_clone tests go.
- factory: MACFactory and MACFactoryWorker<'k, Dir> gain the three BLAKE
  variants, with their tag lengths through TagLen; the blake_mac_tests
  helper is rewritten for the slice-based factory methods.
- cli: blake2*-mac and blake3-keyed-hash go through do_mac_with_key. The
  keyed-hash generate path streams stdin and squeezes to stdout through the
  shared helpers, replacing a hand-rolled loop that panicked on a read error
  and allocated the whole --length output.
- Tests move from the truncation and bool-verify checks to the array forms,
  and the stack harnesses call the key's mac_out.

cargo test --workspace, cargo build --workspace --all-targets,
test_blake.sh (17/17) and test_hmac.sh pass.

Assisted-by: Claude:claude-opus-5-5
As QUALITY_AND_STYLE's API section asks ("A worker borrows the key it came
from"), the workers take the key's lifetime: BLAKE2b_512_MAC<'k, Dir>,
BLAKE2s_256_MAC<'k, Dir> and BLAKE3KeyedHash<'k, Dir>, so a worker cannot
outlive its key.

- blake3: the keyed worker reads the key words through the borrow instead of
  copying them. Blake3Core takes its key words as a type parameter, owned
  (Secret<[u32; 8]>, the default) by the unkeyed and derive_key hashers and
  &'k Secret<[u32; 8]> in the keyed worker; read_state moves to the owned
  impl. BLAKE3KeyedHash shrinks from 1880 to 1856 bytes and
  MACFactoryWorker from 1888 to 1872.
- blake2: the worker still copies the key's keyed state, since it advances
  h and overwrites the buffered key block, so the borrow is the lifetime
  only.
- Tests bind the key before the worker.

Assisted-by: Claude:claude-opus-5-5
…ers are Secrets

From the review of bcgit#180:

- read_state accepted any t that is a whole number of blocks, so a forged
  state with t = 2^64 - 64 (BLAKE2s) followed by one block of input wrapped
  the counter: a debug build panicked in add_to_t and a release build
  returned a digest over a wrapped t. A t above half the counter's range
  (RFC 7693 s. 2.1, "0 <= ll < 2**128" / "2**64") is now InvalidData, which
  leaves a resumed state at least 2^127 (2^63) bytes from wrapping.
  suspend_rejects_bad_t pins both sides of the bound.
- suspend and from_suspended staged h and the buffered input in plain stack
  arrays; they are Secret<[u8; CORE_STATE_LEN]> now, as the struct's fields
  are.

Assisted-by: Claude:claude-opus-5-5
…er, CVs in Secrets

From the review of bcgit#180:

- BLAKE3Squeezer's state no longer carries the 64-byte output buffer, which
  resume copied verbatim: a forged buffer handed out up to 63 bytes that were
  not root output, then carried on with genuine blocks. Resume recomputes it
  as root output block output_block_counter - 1 when the position is inside
  it. SUSPENDED_BLAKE3_SQUEEZER_STATE_LEN goes from 180 to 116.
- Squeezer resume accepted root flags no root has (none, CHUNK_START alone,
  PARENT with a chunk flag, PARENT with b != 64). It now requires a parent
  node (PARENT, b = 64, s. 2.5) or a chunk's last block (CHUNK_END, with or
  without CHUNK_START, s. 2.4).
- Hash resume refuses an empty chunk after the first (chunk_counter > 0,
  nothing compressed, nothing buffered), which no input produces (s. 5.1.1)
  and whose do_final would merge an empty chunk into the tree. Two tests
  that built such states now give the chunk a byte.
- The chaining values and compression outputs held outside compress (the CV
  popped off the stack, the merged and chunk CVs, a block's compression
  output, the derive_key context key and a squeezer block) are Secrets,
  declared before their loops: in keyed_hash and derive_key mode they derive
  from the key. compress writes into the caller's buffer; its own working
  state stays unzeroized, as in the SHA-2 and BLAKE2 compression functions.
  cachegrind on a 1 MiB hash: 27.38M instructions before, 27.14M after.

Assisted-by: Claude:claude-opus-5-5
From the review of bcgit#180, against QUALITY_AND_STYLE's "No internal
implementation detail in public API docs":

- blake2: do_final_partial_bits_out no longer compares itself with Ascon and
  KMACXOF; the MAC key docs drop how the key stores its keyed state; the
  crate's length-extension point is stated as the crate's own reasoning
  rather than read into RFC 7693 s. 3.2.
- blake3: the suspended-state byte layouts move from the two
  SUSPENDED_*_STATE_LEN docs into // comments, BLAKE3::from_suspended's doc
  states what it refuses without the layout, and BLAKE3KeyedSqueezer,
  BLAKE3DeriveKey and BLAKE3DeriveKeySqueezer say only that their state
  would contain the key or key material.

Assisted-by: Claude:claude-opus-5-5
The MAC rework, the borrowed BLAKE3 key words and the Secret CVs change the
call chains, so every row is measured again with one method and toolchain: a
probe paints 64 KiB below its frame, runs the operation through a
non-inlined call and reports the deepest byte disturbed, rounded up to 8
(the key-construction rows reproduce the old figures exactly). The input is
now a static rather than counted, so the hash rows read about 1 KiB
(BLAKE2) or 4 KiB (BLAKE3) lower than before for that reason alone; the MAC
rows are the key's mac_out. Release with thin LTO, x86_64, rustc
1.97.0-nightly (d3cd04068).

Assisted-by: Claude:claude-opus-5-5
cargo mutants (bouncycastle-blake2, 326 tested) misses `>` -> `>=` on the
new upper-half bound in read_state; t == MAX >> 1 is all ones and so already
refused as not a whole number of blocks.

Assisted-by: Claude:claude-opus-5-5
cargo mutants (bouncycastle-blake3, 422 tested) misses `<` -> `<=` on the
resume-time recompute of the partly read output block; at position 64 that
block is never read.

Assisted-by: Claude:claude-opus-5-5
…idiom, the shared zero check, and resume docs that say what a caller sees

From the review of d279232:

- BLAKE2's `from_suspended` compared `h` to the unkeyed initial state through `h_ct_eq`, which
  re-serialized both into two Secret buffers; it now serializes the public constant once and
  compares it against the state's own h bytes with `ct_eq_bytes`, as BLAKE3's resume compares
  its IV. `h_ct_eq` goes from both files.
- `BLAKE3KeyedHashKey`'s strength check returned through `Err(..)?` beside an explicit `return
  Err(MACError::KeyMaterialError(..))`; it is spelled out like its neighbours.
- The CLI's `warn_if_zeroized_key` hand-rolled an OR fold the optimiser may shorten; it uses
  `bouncycastle_utils::ct::ct_eq_zero_bytes`, which is barrier-hardened and already the MAC
  commands' comparison.
- The public `from_suspended` docs on `BLAKE2b_512`, `BLAKE2s_256` and `BLAKE3Squeezer` listed the
  internal state combinations they refuse, which a caller cannot act on; they now say a state
  the type did not produce is `InvalidData`, and the checklist lives once on the private
  `read_state` and in the body's comments (QUALITY_AND_STYLE.md, "No internal implementation
  detail in public API docs").

Assisted-by: Claude:claude-mythos-5-1
…nerate

Five `FromAnyKeyMaterial::generate` impls (HMAC, KMAC, BLAKE2b, BLAKE2s, BLAKE3) carried the same
twelve-line hazardous-operations closure: draw `len` bytes into a longer buffer, set the
length, tag it `MACKey`, cap the strength at the lesser of the RNG's and the length's.
`KeyMaterial::from_rng` in core did the same draw with `CryptographicRandom` hard-coded.

core gains `KeyMaterial::from_rng_as_type(rng, len, key_type)`: `len` bytes at most `KEY_LEN`
(else `InputDataLongerThanKeyCapacity`), the caller's type, SP 800-90A r1 Section 8.4's
`min(output_length, DRBG_security_strength)`, and a zero `len` back `Zeroized` without touching
the RNG. `from_rng` is that call with `KEY_LEN` and `CryptographicRandom`, and each `generate`
is one line over it. Found by the review of d279232 (PR bcgit#180). A core test covers the shorter
typed draw, the cap, the zero draw and the capacity refusal; the MAC crates' own generate tests
are unchanged.

Assisted-by: Claude:claude-mythos-5-1
@dghgit
dghgit removed the request for review from ounsworth November 10, 2026 06:50
`load_blake_mac_key` was a third loader beside `mac_cmd::load_mac_key` and the block ciphers'
`load_key_object_typed`, and the two MAC loaders tagged the same bytes differently: HMAC and
KMAC keys went through a hazardous `set_key_type` on the secret buffer with no stated strength,
BLAKE keys through a fresh `KeyMaterial512::from_bytes_as_type`, which gave them a
length-derived strength and a second copy of the key. The only thing BLAKE needed was a length
range with a message naming the limit. `helpers::load_mac_key(key, key_file, len)` is now the
one loader: `len` is `Some((alg_name, range))` for an algorithm that bounds its key (BLAKE2b 1
to 64, BLAKE2s 1 to 32, BLAKE3 exactly 32) and `None` for HMAC and KMAC, which take any length
the buffer holds; the tagging is the hazardous `set_key_type` for all of them, and the BLAKE
commands build their keys allow-weak as they already did. The block ciphers' loader stays: it
builds a typed fixed-length key object, a different shape. From the review of d279232 (item 3).

Assisted-by: Claude:claude-mythos-5-1
@dghgit

dghgit commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Third review, at d279232, by the project's code-review pass at high effort. The reviewer checked out the head in a scratch worktree: blake2, blake3 and factory tests pass with no warnings, the criterion and stack benches compile, the mem-bench doctests pass, and the CLI reproduces RFC 7693 Appendix A and the official BLAKE3 hash / keyed_hash vectors (cli/tests/test_blake.sh 17/17). No correctness bug in the compression functions, the incremental engines, the suspend/resume validation or the CLI; checked and sound: the RFC 7693 G/F transcription and SIGMA[i mod 10], the BLAKE2 counter semantics (t at the end of a block, the key block counted, empty keyed and unkeyed finals), the hold-one-byte-back invariant and its mirror in read_state, the BLAKE3 chunk and CV-stack logic, parent counter 0, ROOT applied only at output, derive_key's two-stage keying, the squeezer's recompute of block counter - 1, the factory wiring and TagLen, the CLI names and the --verify plus --length refusal.

Seven findings, on reuse, altitude and proportion. Six are fixed in the four commits above d279232 (5626d71, d3adf8e, c5b23f6, 667ced1); the seventh is the open question in the body.

  1. One key draw for every MAC key's generate (d3adf8e). The BLAKE2b, BLAKE2s and BLAKE3 keys' generate were a fourth and fifth copy of the twelve-line hazardous-operations closure HMAC and KMAC carry (draw len bytes into a longer buffer, set the length, tag MACKey, cap the strength at the lesser of the RNG's and the length's), while KeyMaterial::from_rng in core did the same draw with CryptographicRandom hard-coded. core gains KeyMaterial::from_rng_as_type(rng, len, key_type): len at most the capacity (else InputDataLongerThanKeyCapacity), the caller's type, SP 800-90A r1 Section 8.4's min(output_length, DRBG_security_strength), a zero len back Zeroized without touching the RNG. from_rng is that call with KEY_LEN and CryptographicRandom, and all five generate impls (HMAC, KMAC, BLAKE2b, BLAKE2s, BLAKE3) are one line over it, so the capping rule has one home. A core test covers the shorter typed draw, the cap, the zero draw and the capacity refusal; the MAC crates' own generate tests are unchanged.
  2. The CLI's zero-key warning hand-rolled an OR fold the optimiser may shorten: it uses bouncycastle_utils::ct::ct_eq_zero_bytes, barrier-hardened and already the MAC commands' comparison (5626d71).
  3. One MAC key loader (c5b23f6). load_blake_mac_key was a third loader beside mac_cmd::load_mac_key and the block ciphers' load_key_object_typed, and the two MAC loaders tagged the same bytes differently (BLAKE keys got a length-derived strength and a second 64-byte copy). helpers::load_mac_key(key, key_file, len) is now the one loader, len the algorithm's bound where it has one (BLAKE2b 1 to 64, BLAKE2s 1 to 32, BLAKE3 exactly 32) and None for HMAC and KMAC; the block ciphers' loader stays, being a typed fixed-length key object.
  4. BLAKE2's h_ct_eq re-serialized both sides to compare h with the unkeyed initial state: from_suspended compares the state's own h bytes with the serialized constant, as BLAKE3's resume compares its IV, and h_ct_eq is gone from both files (5626d71).
  5. Err(..)? beside return Err(..) in BLAKE3KeyedHashKey's checks: spelled out like its neighbours (5626d71).
  6. The factory enums' size (HashFactory and XOFFactory about 1888 bytes, MACFactoryWorker 1872, from BLAKE3's 1728-byte CV stack): the open question in the body. The reviewer leans towards a CV stack sized to a documented input cap (2^40 bytes gives 30 CVs, about 1 KB saved) over keeping the full depth; still to decide.
  7. Public from_suspended docs on BLAKE2b_512, BLAKE2s_256 and BLAKE3Squeezer listed the internal state combinations they refuse, which a caller cannot act on: they say a state the type did not produce is InvalidData (5626d71). The checklist has one home per file: BLAKE3's private read_state doc, and for BLAKE2 the comment at each check, with read_state's doc deferring to them; the BLAKE2 state-length constants carry their layout as a code comment, as BLAKE3's already did, instead of public rustdoc (667ced1).

Verified after the four commits (the fourth is docs and comments only): core, hmac, sha3, blake2, blake3, factory and core-test-framework suites; test_blake.sh 17/17, test_hmac.sh 6/6, test_kmac.sh 7/7, test_aes_mac.sh 6/6; the workspace builds every target; core's rustdoc; fmt; clippy adds nothing on the changed lines. Mutants (apollo): blake2 310 (230 caught, 70 unviable, 10 missed: the equivalents noted at the site, five per file, 16 mutants fewer than before with h_ct_eq and the draw closure gone); blake3 417 (338 caught, 61 unviable, 9 missed, 9 timeouts: the noted flag-bit |/^ swaps, the squeezer recompute's </<=, and the fill and merge loops that never end, one miss fewer than before); core from_rng / from_rng_as_type with the core, hmac and blake2 tests 10 (6 caught, 4 unviable, 0 missed).

Assisted-by: Claude:claude-mythos-5-1

…rs to its checks

The rest of the review's item 7 (d279232): `SUSPENDED_BLAKE2B_512_STATE_LEN` and
`SUSPENDED_BLAKE2S_256_STATE_LEN` carried the serialized layout in their public docs, internal
detail a caller cannot use; it is a `//` comment on the constant now, as the BLAKE3 constants
already have it. `read_state`'s doc re-listed the rejections its body justifies check by check;
it now says what the function does and leaves the why to the comments at each check, so the
list has one home per file.

Assisted-by: Claude:claude-mythos-5-1

@dghgit dghgit left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Rebased and reviewed with Mythos. A handful of minor issues fixed as described.

dghgit pushed a commit that referenced this pull request Nov 11, 2026
…E2s-256 (RFC 7693) and BLAKE3 (#123, for OpenSSL provider provisioning) as two new crates: bouncycastle-blake2 (BLAKE2b_512 and BLAKE2s_256 implementing Hash and Suspendable with G, F, the parameter block and the final-t rule commented against RFC 7693 Sec 3.1 to 3.3, the native keyed mode through the key objects BLAKE2b_512_MACKey and BLAKE2s_256_MACKey handing out BLAKE2b_512_MAC<'k, Dir> and BLAKE2s_256_MAC<'k, Dir> workers at the full 64 and 32-byte tag, key checks in the order type, strength, length, the id-blake2b512 and id-blake2s256 OIDs, suspended states of 212 and 108 bytes whose resume refuses an h that is not the unkeyed initial state at t == 0, a t that is not whole blocks or lies in the upper half of its range, and an empty buffer after a compression) and bouncycastle-blake3 (BLAKE3 implementing Hash and XOF with BLAKE3Squeezer streaming the extended output, both Suspendable with states of 1874 and 116 bytes, the hash resume refusing any chunk-state and CV-stack combination no input produces and the squeezer resume accepting only a root a tree can have, refusing an output block counter above 2^58 and recomputing the partly read block rather than storing it; BLAKE3DeriveKey and its squeezer as their own types that deliberately do not suspend, pinned by compile_fail doctests; BLAKE3KeyedHashKey handing out BLAKE3KeyedHash<'k, Dir> workers reading the key words through the borrow, with an inherent into_squeezer for extended keyed output; the engine following the reference implementation's chunk state and 54-entry chaining-value stack, with the chaining values and compression outputs outside the compression function in Secrets); in both crates the MAC workers follow #194 (Dir = Encrypting implements MACGenerator<N>, Dir = Decrypting MACVerifier<N>, no run-time truncation, verification a Result recomputing the tag into a Secret), borrow their key and implement neither Suspendable nor Clone, every _out function zeroizes its buffer before any error check, partial-bit input of 1 to 7 bits is InvalidInput, and the Memory Usage sections give the object sizes, state lengths and measured peak stack per operation; in core, KeyMaterial::from_rng_as_type(rng, len, key_type) is the one key draw that from_rng and the HMAC, KMAC and BLAKE generate methods wrap; in factory, HashFactory gains BLAKE2b-512, BLAKE2s-256 and BLAKE3, XOFFactory gains BLAKE3, and MACFactory and MACFactoryWorker<'k, Dir> gain the three BLAKE MAC keys and workers with their tag lengths through TagLen, which makes HashFactory and XOFFactory 1888 bytes and MACFactoryWorker 1872 because BLAKE3's hasher carries a 1728-byte chaining-value stack sized for the full 2^64-byte input range, documented in the factory crate's Memory Usage section and left as it stands; in the CLI, blake2b-512, blake2s-256, blake2b-512-mac, blake2s-256-mac, blake3, blake3-keyed-hash and blake3-derive-key, all streaming, with helpers::load_mac_key(key, key_file, len) the one MAC key loader (the length bound a parameter, None for HMAC and KMAC, --key-file winning everywhere) and the all-zero key warning through bouncycastle_utils::ct::ct_eq_zero_bytes, which also makes the HMAC/KMAC warning fire where it never had; the stack-memory harnesses; and the tests: RFC 7693 Appendix A/B, the official BLAKE2 KAT selections and the bc-test-data Blake2KAT.txt sweep, all 35 official BLAKE3 vectors in all three modes, the core-test-framework suites for Hash, XOF, MAC keys, key construction and suspend/resume, and cli/tests/test_blake.sh with 17 cases. Reviewed in three rounds (9059e06, 40aebcf, d279232: the interchangeable keyed states, the derive_key state holding the IKM, the squeezer counter wrap, the t and buffer bounds, the non-constant-time resume compares, the Clone keyed workers, the stored squeezer buffer, the impossible root flags, the unzeroized recomputed tags, the three MAC key loaders and the repeated spec quotes, all fixed); mutants on apollo in the PR: blake2 310 (230 caught, 70 unviable, 10 missed, the noted equivalents), blake3 417 (338 caught, 61 unviable, 9 missed as noted, 9 timeouts), factory mac_factory.rs 55 (25 caught, 30 unviable, 0 missed), core from_rng / from_rng_as_type 10 (6 caught, 4 unviable, 0 missed).

Assisted-by: Claude:claude-mythos-5-1
Assisted-by: Claude:claude-fable-5-1
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-sonnet-5-5
@dghgit

dghgit commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Squash-merged onto release/0.1.4alpha as c999768 (tree identical to 667ced1, author royb). The factory enum size question stays as option 1, documented in the factory crate's Memory Usage section. Closing by hand, as the merge happens on the private upstream.

@dghgit dghgit closed this Nov 11, 2026
dghgit added a commit that referenced this pull request Nov 11, 2026
…nd BLAKE3) into feature/eccdh

Two conflicts, both lists: the factory's dependency table (BLAKE2 and BLAKE3 beside concatkdf)
and the CLI's use list (BLAKE2Variant beside ConcatKDFVariant and ECCDHAction), each keeping
both sides. The factory, cli, eccdh, x963kdf and concatkdf suites, the eccdh, x963kdf, concatkdf,
hkdf and blake CLI shell suites, the all-targets build and rustdoc pass on the result.
dghgit added a commit that referenced this pull request Nov 11, 2026
…ment and ECC CDH with the X9.63 and concatenation KDFs; PR #180 squash: BLAKE2b-512, BLAKE2s-256 and BLAKE3; the post-#193 cleanups) into feature/tdes

Two keep-both conflicts: the mem_usage_benches manifest (the tdes stack bench beside the BLAKE2 and
BLAKE3 ones) and the umbrella crate's re-exports (tdes beside x963kdf). One adaptation: the CLI's
main thread stack goes from 8 MiB to 32 MiB. In a debug build the startup frame for the full
subcommand set already sat close to 8 MiB on the release, and the eleven tdes-* / tdes2-*
commands take it past, so the debug binary aborted on every invocation, --help included (the
release-profile binary was unaffected). That bound is the release's to set once; it is raised
here so the branch's shell suites run. The tdes, cipher, factory and cli suites, the tdes, blake,
eccdh, concatkdf, x963kdf and hkdf CLI shell suites and the all-targets build pass.
dghgit added a commit that referenced this pull request Nov 11, 2026
…ment and ECC CDH with the X9.63 and concatenation KDFs; PR #180 squash: BLAKE2b-512, BLAKE2s-256 and BLAKE3; the post-#193 cleanups) into feature/rsa-sig

No conflicts. One adaptation, as on feature/tdes: the CLI's main thread stack goes from 8 MiB to
32 MiB, since in a debug build the startup frame for the full subcommand set passes 8 MiB once
the rsa commands join the release's and the debug binary aborted on every invocation (the
release-profile binary was unaffected). That bound is the release's to set once. The rsa and
cli suites, the blake and eccdh CLI shell suites and the all-targets build pass.
dghgit added a commit that referenced this pull request Nov 11, 2026
…of PR #194, the BLAKE hashes of PR #180, two-party key agreement and ECC CDH with the X9.63 and concatenation KDFs of PR #193, and the post-#193 cleanups) into feature/simple-ciphers-arm

Two keep-both conflicts: the workspace dependency table (this branch's `default-features =
false` on bouncycastle-cipher beside the release's concatkdf entry) and the imports of the AES
CMAC key tests (this branch's AES*Params beside the release's Encrypting). The aes, cipher,
asm and cli suites, the aes-gcm, aes-cbc and blake CLI shell suites pass, and the aarch64
library type-checks in the default, declared-feature (+aes,+neon) and asm-off configurations.
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.

3 participants