Skip to content

sm4: add bouncycastle-sm4, a constant-time SM4 block cipher - #140

Open
dghgit wants to merge 24 commits into
feature/key-rework-memoryfrom
feature/sm4
Open

dghgit wants to merge 24 commits into
feature/key-rework-memoryfrom
feature/sm4

Conversation

@dghgit

@dghgit dghgit commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds bouncycastle-sm4: a constant-time, table-free, low-memory SM4 block cipher (GB/T 32907-2016, as described in draft-ribose-cfrg-sm4-10), ported from Bouncy Castle Java's SM4Engine, on the key objects of #172: SM4Key<M> over the engine SM4Internal.

Rebased onto feature/key-rework-memory (#172) on 2026-10-25 and ported to its key-object design; the base is now that branch. The review items of 2026-10-27 are addressed in the last three commits: the key's leftover MAC-key path is gone, the CLI matches the AES commands, the Memory Usage table is measured, the style and nit items are fixed, and the SM4 Wycheproof GCM and CCM suites run through the mode traits. The four-mode scope (no AEAD roles) is unchanged.

Description

  • The S-box is a 127-gate Boolean circuit (found by exhaustive search, reusing the Boyar-Peralta AES non-linear section via a field isomorphism), not a lookup table.
  • Bit-sliced over 16-bit planes, four blocks at a time.
  • SM4Key<M> mirrors AESKey at SM4's one key length: the key holds the mode's state, its workers borrow it, and which role pair a key carries is decided by its mode. The crate aliases CBC, CFB, CFB8 and CTR (SM4_CBC<'k, Dir, Pad>, SM4_CFB<'k, Dir>, ...); the key carries the symmetric and block role pairs only, which cover those four modes; the engine implements ModeNames for all nine, so the mode types over it are named, but a key under GCM, CCM, CMAC or ECB<Blocks> has no role. Keys and engines implement PartialEq / Eq in constant time over the expanded schedule, as on Key object rework: the traits lose their constructors, every mode gets a key, and workers borrow the key #172.
  • CLI subcommands sm4-{cbc,cfb,cfb8,ctr}, through the key object, with the aes128-* contract: sm4-cbc is CBC<PKCS7> at any length, every command takes --iv/--iv-file (the IV or nonce is in band otherwise) and -d/--direction.

Scope and Risk

New crate only; the shared changes it carries (the bouncycastle-sm4 workspace entry, the umbrella re-export, the memory bench) are additive. No behavioural change to AES or other existing ciphers.

Validation

  • draft-ribose-cfrg-sm4-10 Appendix A.1 vectors (all 6 examples, every round key and round output, the two 1,000,000-fold iterations under --release), A.2.1 ECB and BC Java's own SM4Test vectors, through the engine; A.2.2 CBC through SM4Key<CBC<Blocks>>, A.2.4 CFB through SM4Key<CFB>, A.2.5 CTR through a reference built from the Sec 8.7.1 equations which SM4Key<CTR> is held to, and the Sec 8.5.2 equations at s = 8 for SM4Key<CFB8>.
  • RFC 8998 Appendix A: the SM4-GCM and SM4-CCM vectors through the GCM and CCM<12, 16> hazmat mode traits over the permutation's key state (new with the port); Wycheproof sm4_gcm_test.json (65 of 104 cases dispatchable, the 96-bit-IV ones) and sm4_ccm_test.json (162 of 184, those within SP 800-38C A.1's nonce and tag sets) the same way.
  • Shared frameworks over the key: TestFrameworkFromKeyMaterial for every mode, and the block, symmetric and stream suites.
  • cargo test --workspace: all passing; cargo test --workspace --all-targets --no-run builds the benches; cargo +nightly fmt --check clean. The CLI integration tests pin the draft's A.2.2 CBC and A.2.4 CFB vectors through the binary in both directions, encryption under --iv.
  • cargo mutants -p bouncycastle-sm4 after the review fixes: 395 mutants, 368 caught, 23 unviable, 4 missed (the XOR/OR equivalences in ortho::swap, sbox and tau); the key.rs misses of the review are gone.

AI Usage Statement

  • Yes, non-trivial code changes were generated by AI

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

🤖 Generated with Claude Code

@ounsworth

ounsworth commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

We had agreed not to add more ciphers until we get through reviewing and locking in the overall architecture via the AES work. Closing this PR for now.

@ounsworth ounsworth closed this Sep 19, 2026
@dghgit dghgit reopened this Sep 19, 2026
@hubot
hubot deleted the feature/sm4 branch September 28, 2026 15:21
@hubot
hubot deleted the branch feature/key-rework-memory September 28, 2026 15:21
@hubot
hubot restored the feature/sm4 branch September 28, 2026 15:45
@dghgit
dghgit changed the base branch from feature/xof-cshake to feature/simple-ciphers September 28, 2026 15:49
dghgit and others added 17 commits October 25, 2026 16:40
…om bc-java

New crate `crypto/sm4` (`bouncycastle::sm4`): the SM4 block cipher
(GB/T 32907-2016, read via draft-ribose-cfrg-sm4-10) as a raw keyed
permutation implementing ElectronicCodeBook, ported from BC Java's SM4Engine.

The S-box is a 127-gate Boolean circuit over eight u32 bit-planes rather
than the Java engine's 256-byte table, so there is no secret-indexed memory
access in the cipher or the key schedule. The SM4 S-box is GF(2^8) inversion
between two affine maps -- a decomposition found by exhaustive search against
the spec's table -- so a field isomorphism lets the non-linear section of the
Boyar-Peralta AES circuit (copied from aes) do the inversion, with
generated affine top and bottom layers. A round substitutes four bytes per
block, so the engine works on eight blocks per circuit pass:
encrypt_8blocks/decrypt_8blocks are the full-rate path, encrypt_2blocks is
overridden to use two lanes, and single-block calls do eight blocks' work.
One stored schedule (128 B, in a Secret) serves both directions.

Also: SM4_CBC<Dir> alias over bouncycastle-modes; `sm4-cbc` CLI subcommand,
with aes_cbc_cmd.rs generalised into cbc_cmd.rs so the four CBC commands
share one implementation; criterion bench; mem_usage_benches harness;
umbrella re-export; release notes.

Verified against every value in the draft's Appendix A.1 (Examples 1-6,
including all 32 round keys and all 32 per-round outputs of Examples 1 and
4, and both 1,000,000-fold iterated ciphertexts, the latter in release
builds only), the SM4-ECB and SM4-CBC vectors of Appendix A.2, the vectors
of BC Java's core and provider SM4Test including test1000000(), an
exhaustive 256-input S-box test, and a table-driven transcription of the
Java engine kept in the tests, which the engine must agree with on
thousands of keys and blocks in every lane and both directions.
cargo mutants: 364 mutants, 353 caught, 8 unviable, 3 missed -- all three
proven XOR/OR equivalences, commented at the site.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The S-box circuit moves from eight u32 bit-planes to eight u16 planes,
matching the four-lane shape of bouncycastle-aes and bouncycastle-camellia. A round substitutes four bytes per
block, so the u16 planes hold the T argument of four blocks -- each split into
two 16-bit halves -- and encrypt_4blocks/decrypt_4blocks become the natural
unit; the trait's blocks8 is two passes, blocks2 uses two lanes, and a
single-block call does four blocks' work. The per-call working state drops from
about 160 B (128 B of block state, 32 B of planes) to about 100 B (64 B of
block state, 16 B of T arguments, 16 B of planes), at half the throughput on a
32/64-bit machine -- the trade this design makes. The 127-gate
circuit itself is unchanged, and the transpose is the same BearSSL routine on
u16 masks; the key schedule's T' now fills four lanes instead of eight.

Also: the ElectronicCodeBook::encrypt_8blocks doc in core now names both
four-lane crates, and the release notes and the CLI's import follow.

Verified as before: every value of draft-ribose-cfrg-sm4-10 Appendix A.1
(round keys and per-round outputs of Examples 1 and 4, now pinned in every lane
of the four-block path and every slot of the eight-block path, and both
1,000,000-fold iterated ciphertexts in release builds), the A.2 ECB and CBC
vectors, BC Java's SM4Test including test1000000(), the exhaustive 256-input
S-box test (sixteen passes of sixteen positions), and the table-driven
transcription of the Java engine on thousands of keys and blocks in every lane
and slot, both directions; the sm4-cbc CLI tests.
cargo mutants: 376 mutants, 364 caught, 8 unviable, 4 missed -- all four proven
|/^ equivalences on disjoint bits (the transpose, the half-word join in tau)
or the documented Boyar-Peralta t37 gate, commented at the site.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ode projection, and the block-aligned vectors move onto modes::Cbc
Doc comments scattered per-file citations to BC Java's SM4Engine
(its table-driven S-box, its F0..F3 round structure, its per-direction
key expansion, its CK/FK tables, its algorithm name) throughout
lib.rs, sm4.rs and schedule.rs. The crate's Provenance section already
names BC Java's SM4Engine as the source implementation this crate is
ported from and describes what it reproduces; the scattered
comparisons are replaced with self-contained descriptions of the same
points (a table-driven S-box's cache-timing exposure, a
direction-aware engine's per-direction expansion) that don't depend on
the reader having BC Java's source open. Test files that cross-check
against BC Java's own vectors (tests/bc_java_tests.rs, tests/common/mod.rs)
are untouched, since BC Java is literally their subject.

No behavioural change; cargo fmt --check is clean; cargo test -p
bouncycastle-sm4 passes (14 passed, 2 ignored long-running cases).

Assisted-by: Claude:claude-sonnet-5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same issue as PR #139 (tdes): mem_usage_benches/src/lib.rs makes every
bench source a module of a lib target, so its //! header is rustdoc'd
and an indented block compiles as a Rust doctest by default. Fenced
as ```text like the other bench binaries.

Assisted-by: Claude:claude-sonnet-5

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…/ sm4-cfb8 / sm4-ctr CLI commands, so SM4 covers the same four modes AES does rather than CBC alone -- `bouncycastle-modes` is cipher-agnostic and its Cfb, Cfb8 and Ctr already work over any ElectronicCodeBook, so each alias is three lines of type plus the const parameters SM4 fills in (KEY_LEN = BLOCK_LEN = 16, and a 12-byte CTR nonce leaving the 4-byte counter the mode caps at, the same split bouncycastle-aes chooses for its 16-byte block). SM4_CFB is draft-ribose-cfrg-sm4-10's SM4-CFB-128 and SM4_CFB8 its SM4-CFB-8, the two variants Sec 8.5.1 names; SM4_CTR is Sec 8.7, whose counter sequence the draft leaves to the caller ("any sequence that does not repeat within the block size"), so nonce || counter is an admissible choice rather than a deviation. Test coverage follows what the draft actually publishes: Appendix A.2.4's two SM4-CFB examples are straight known-answer tests through SM4_CFB, driven under the vector's own IV via a FixedSeedRNG exactly as the existing A.2.2 CBC tests are; Appendix A.2.5's SM4-CTR examples make the whole 16-byte IV the first counter block and increment it as a unit, which the nonce/counter split cannot produce, so those vectors pin a reference CTR written from the Sec 8.7.1 equations over the permutation and SM4_CTR is then required to agree with that reference on the counter blocks it does produce; SM4-CFB-8 has no published example at all, so it is pinned the same way against the Sec 8.5.2 equations at s = 8. stream_mode_alias_tests.rs covers the wiring the vectors do not -- that each alias names the mode it claims to, round-trips at any length with no padding, gives the same answer whatever the chunking, draws fresh init data per encryption, and that CFB128, CFB8 and CTR are mutually distinct. The three CLI commands are thin dispatchers over the existing stream_mode_cmd plumbing, so they inherit the IV-in-the-ciphertext convention, the 1 KiB streaming chunk and the key loader unchanged; their tests pin the A.2.4 vectors end to end through the pipe for sm4-cfb and hold sm4-cfb8 and sm4-ctr to the library for the two modes without vectors. No new mutants: `cargo mutants -p bouncycastle-sm4 --list` reports the same 374 before and after, because the three new source files are type aliases and documentation with no executable code of their own. quality_stats.sh's "unwraps in core code" for sm4 goes 3 -> 29, all of it `.unwrap()` inside the new doctests, which the script counts from src/ without distinguishing doc comments; the doctests are written in the same style as crypto/aes/src/{cfb,cfb8,ctr}.rs, which is why aes reports 58.

Assisted-by: Claude:claude-opus-5

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…pe on the error paths, which is what failed the Rust Tests workflow on fc35da9 -- `a_key_of_the_wrong_length_is_rejected` in sm4_cfb8_cli_tests.rs writes its stdin payload from the test thread and `.expect()`s the result, but the command it invokes rejects the key and `exit`s before reading a byte, so the write races the child's exit and gets EPIPE. The assertion the test actually makes -- non-zero status, and the algorithm named in stderr -- is unaffected, because `wait_with_output` still returns both, so the fix is to match on the write result and ignore `ErrorKind::BrokenPipe` while still panicking on every other write error. That is the same treatment, and the same reasoning, that aes_ctr_cli_tests.rs already documents for its threaded harness; the harness is copied per file, so all three new suites (cfb, cfb8, ctr) get it. The race is timing-dependent -- it passed locally and failed on the loaded CI runner -- so sm4_cfb8_cli_tests.rs also gains a deterministic guard: a 4 MiB payload to a command that exits on a rejected key cannot fit in a pipe buffer, so the write is certain to get EPIPE rather than merely likely to. Verified by reverting the harness under that guard, which reproduces the CI panic exactly, and re-applying it, which does not. Also drops an unused `tohex` helper that sm4_cfb8_cli_tests.rs never called, which was the one warning `cargo test --all` still emitted. No behaviour outside the test harness changes.

Assisted-by: Claude:claude-opus-5

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eam-mode suites got in 703ca45, closing the last copy of that race in this crate's CLI tests -- `a_key_of_the_wrong_length_is_rejected` writes its stdin payload from the test thread and `.expect()`s the result, but `sm4-cbc` rejects the key and `exit`s before reading a byte, so the write races the child's exit and gets EPIPE. This file predates the stream modes and has never failed CI, but the defect is identical and was demonstrated here rather than assumed: dropping a 4 MiB error-path payload into the unpatched harness reproduces exactly the panic that failed the Rust Tests workflow on the cfb8 suite. The write result is now matched on, `ErrorKind::BrokenPipe` ignored and every other write error still a panic, which is what the five aes_*_cli_tests.rs suites have always done; an audit of cli/tests confirms no suite in the tree still `.expect()`s that write. The new `a_large_payload_on_an_error_path_does_not_break_the_harness` is the deterministic guard for it -- a payload that cannot fit in a pipe buffer makes EPIPE certain rather than merely likely, so the harness cannot silently regress to the flaky form. Also reflows the over-width comment lines this series left in sm4_cfb8_cli_tests.rs: rustfmt does not wrap comments, so they passed `cargo fmt --check` while sitting past the 100-column max_width every other comment in the tree respects. Nothing the existing tests assert on changes: `wait_with_output` still returns the exit status and the stderr they match against, and no non-test code is touched.

Assisted-by: Claude:claude-opus-5

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…hich no longer exist since 334cd2b made the methods required

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bsorbed through its merges (SymmetricCipher/BlockCipherPadding renames, the ElectronicCodeBook batch methods, AES*Internal, core::security_strength, PaddedBlockCipher* adapters and the rest), restored in one commit after the linear rebase dropped those merges; the tree is identical to merging c5f639d with 2d4d038

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tests, stream-mode tests, the CLI CFB8/CTR tests, the raw-Cbc draft test and the crate's doc examples take the renamed in-place one-shots (encrypt_in_place/decrypt_in_place), renamed only where the call is in place (the padded SM4_CBC Vec one-shots are unchanged), and import the SymmetricCipher* supertraits their do_*_init calls now come from

Assisted-by: Claude:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SM4 moves to bouncycastle_sm4::hazmat with BLOCK_LEN, KEY_LEN and LANES defined at the crate root;
everything else is the path in use lines and doc links. No logic change, no mutation run owed.

Assisted-by: Claude Code:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Follows aes 160ac18; the padding crate's PaddedMode copy goes with it. Type alias only, no
behaviour change, no mutation run owed.

Assisted-by: Claude Code:claude-fable-5-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…and the workspace wiring

The replay of this branch onto feature/key-rework-memory drops its merges from
feature/simple-ciphers, and the move of bouncycastle-modes and bouncycastle-padding
into bouncycastle-cipher, with Direction and ElectronicCodeBook's new homes, was
resolved only in those merges. This is the sm4 crate, its CLI commands and tests
and its memory bench exactly as the branch tip 4c336bf had them, with the four
sm4 subcommands in main.rs restored, whose conflicts the replay resolved to the
base.

Assisted-by: Claude:claude-mythos-5-1
…e engine becomes SM4Internal

The engine is renamed for what it is under the key objects of
feature/key-rework-memory: SM4 -> SM4Internal, Clone, implementing
ElectronicCodeBook (new) and BlockPermutation (the block methods) separately,
and ModeNames for all nine modes, since SM4 has published modes beyond the four
aliased here (draft-ribose-cfrg-sm4-10 Sec 8.3 ECB, RFC 8998 Sec 3.5 GCM and
CCM).

SM4Key<M: Mode<SM4Internal>> mirrors AESKey at the one key length: the key
holds the mode's key state, FromKeyMaterial<16> builds it, and the role impls
-- symmetric, block, ECB, AEAD and MAC pairs -- forward to the mode traits, so
which roles a key carries is decided by its mode. The crate aliases CBC, CFB,
CFB8 and CTR as before, now with the key-state lifetime (SM4_CBC<'k, Dir, Pad>,
...); the other modes are reached by naming them.

Tests: cbc_alias_tests and stream_mode_alias_tests become padded_alias_tests,
alg_name_tests and a key-side stream_mode_alias_tests; mode_key_tests runs the
FromKeyMaterial, block, symmetric, stream and AEAD frameworks over the key; the
draft's A.2.2 CBC vectors drive SM4Key<CBC<Blocks>>, its A.2.4 CFB vectors, the
A.2.5 CTR reference and the CFB8 equations the stream keys, and A.2.1, A.1,
bc-java and the reference transcription the engine. rfc8998_tests is new: the
SM4-GCM and SM4-CCM vectors of RFC 8998 Appendix A through SM4Key<GCM> and
SM4Key<CCM<12, 16>>, the two AEAD roles the key carries.

CLI: the four sm4 commands load an SM4Key and keep their contract -- whole
blocks under NoPadding for CBC, the IV or nonce in band, no --iv -- and their
integration tests replay the draft's vectors through the binary and check the
stream commands against the key's in-place decryption.

Assisted-by: Claude:claude-mythos-5-1
@dghgit
dghgit changed the base branch from feature/simple-ciphers to feature/key-rework-memory October 25, 2026 05:55
…and MAC pairs go

SM4Key<M> carried the full AESKey role set: the AEAD and declared-AEAD pairs
(GCM, CCM), the hazmat ECB pair (ECB<Blocks>) and the MAC pair (CMAC), none of
which this crate aliases or exposes in the CLI. They were there for parity with
AESKey only. The symmetric and block pairs remain; between them they cover CBC,
CFB, CFB8 and CTR, the four modes the SM4_* aliases name, and a key under any
other mode has no role.

mode_key_tests drops the ECB<Blocks> and GCM construction and framework runs.
rfc8998_tests keeps the SM4-GCM and SM4-CCM vectors of RFC 8998 Appendix A but
runs them through the hazmat mode traits over the permutation's key state, the
path a key under those modes would forward to, since no key role reaches them
any more. ModeNames keeps all nine names, since the mode types over the engine
name themselves by them.

Assisted-by: Claude:claude-mythos-5-1
Lifted onto this branch from feature/key-rework-memory (5607ba9), where AESKey
and AESInternal gained the same: the engine compares the schedule through
Secret's byte-wise equality, which inspects every byte so the time taken does
not say where two differ, and a key is equal when its mode state is, bounded
on the state rather than on Mode::KeyState. The key objects never hand the
material back, so this is the only way two keys can be compared.

mode_key_tests: equality follows the material for a clone, the same bytes and
a single flipped bit.

Assisted-by: Claude:claude-mythos-5-1
@dghgit
dghgit requested a review from roy-basmacier October 27, 2026 14:41
@roy-basmacier

Copy link
Copy Markdown

Reviewed the PR with Mythos. Everything looks fine expect for the keys with no roles and the cli doesn't match the AES CLI. There's also some editorial findings as well as small nitpicks. Here is the findings below

  1. Key roles (intended scope; cleanup incomplete). SM4Key carries only the symmetric and block
    pairs by design: commit 8df9e35 removed the AEAD, ECB and MAC pairs because they "were there for
    parity with AESKey only", and the PR description says so. So an inert key under GCM, CCM, CMAC
    or ECB<Blocks> is expected. Two things remain:
    • Leftover code (should fix). as_cipher_key and the _ => arm in from_key_material exist
      only for MAC-mode keys, which no longer have a role; all four missed key.rs mutants are there.
      Delete them (or test them if they are meant to stay).
    • Scope vs the Wycheproof rule. QUALITY_AND_STYLE requires Wycheproof tests;
      the only SM4 Wycheproof vectors in bc-test-data are sm4_gcm_test.json (104) and
      sm4_ccm_test.json (184), and those are the RFC 8998 TLS 1.3 suites the PR already proves through
      the hazmat traits. Accepting the four-mode scope means accepting that gap; restoring the AEAD
      roles (plus aliases, Wycheproof suites, CLI) closes it.
  2. CLI contract. sm4-cbc is CBC<NoPadding> (whole blocks only) with no --iv/--iv-file;
    on this base aes128-cbc is PKCS7, any length, with --iv/--iv-file, and the stream commands have
    --iv too. The sm4-cbc help says "See aes128-cbc for the IV convention, block-alignment requirement"
    — neither is true of aes128-cbc any more. The helpers already take Option<[u8; N]> (the SM4 commands
    pass None). Recommend matching AES; the CLI tests could then pin encryption against A.2.2/A.2.4.

Should fix (style guide)

  1. lib.rs:208 heading must be # 🚨 Security Considerations 🚨; fix the anchor at lib.rs:15.
  2. Memory Usage (lib.rs:191–206) has no measured per-entry-point stack table; AES tabulates new,
    encrypt_block, encrypt_2blocks, encrypt_4blocks. Measure and tabulate the same way.
  3. Unit tests in src that only test public behaviour (test_alg_name_and_strength, test_lanes_constant,
    test_engine_size_is_exactly_the_round_keys) duplicate alg_name_tests.rs; keep the per-round pins and
    the exhaustive S-box test, move/drop the rest.

Nits

  1. crypto/sm4/Cargo.toml:9 comment is stale (cipher is used by key.rs, ModeNames, all four aliases) and
    lines 11–12 are a dangling comment.
  2. schedule.rs:7 links crate::SM4::decrypt_block (no such item; private module, so rustdoc is silent).
  3. bitslice.rs:31–32 cites bouncycastle-camellia, which is not in the workspace.
  4. gbt32907_tests.rs:11 says src/sm4.rs; it is src/hazmat/sm4.rs.
  5. cli/tests/sm4_*: cite aes_cbc_cli_tests.rs, cli/src/cbc_cmd.rs, cli/src/stream_mode_cmd.rs;
    none exist (AES CLI tests are test_aes_*.sh, helpers are under cli/src/helpers/).
  6. key.rs:6–7 lists the symmetric pair as CFB/CFB8/CTR/CBC; ECB<PKCS7> also works and is tested.
  7. hazmat/sm4.rs:88–90, 121, 131, 139 say ElectronicCodeBook::encrypt_block etc.; those methods are on
    BlockPermutation on this base.
  8. Over-width doc lines: lib.rs:118 (183 cols), :119 (149), and ~25 others >100; rustfmt does not wrap
    comments, and commit c0b19d0 claims a reflow.
  9. PR description: mutants count is 406 after the port, not 374; the new misses are the key.rs path in (1).

Context, not findings

  • Single-block calls fill all four lanes with copies, so CBC and CFB encryption (serial) run at a quarter
    of the 4-block throughput. Inherent to the u16-plane "lowmemory" choice and documented.
  • The two unused dependency warnings in the test log are from base64 and cipher on the base branch.

…table is measured, and the doc nits

From the review of PR #140. SM4Key::from_key_material carried as_cipher_key and
a `_ =>` arm for MAC-mode keys, which lost their role in 8df9e35; both go, and
with them check_key_type, since the permutation's own validation is the same
check for every mode that has a role here (a scoped `cargo mutants` on key.rs
is now 0 missed where it was 4; the crate lists 395 mutants, from 406). The
key docs name ECB<PKCS7> among the symmetric-pair modes, which mode_key_tests
already covers.

The Security Considerations heading takes the style guide's form and the
anchor follows it. The Memory Usage section tabulates the measured stack per
entry point, as the AES crate does: bench_sm4_mem_usage takes the AES bench's
measure-closure shape for new and the six block entry points, and the figures
are the closure frames from the release build's frame-layout remarks plus the
return address.

The three unit tests in hazmat/sm4.rs that tested public behaviour only (the
engine size, the algorithm name, the LANES constant) move to alg_name_tests or
go; the per-round pins stay. Nits: the manifest comment, the dead
`crate::SM4::decrypt_block` link in schedule.rs, the camellia citation in
bitslice.rs, the `src/sm4.rs` path in gbt32907_tests.rs, the
ElectronicCodeBook attributions in hazmat/sm4.rs for methods that are on
BlockPermutation, and every doc or comment line past 100 columns.

Assisted-by: Claude:claude-mythos-5-1
…le and -d

From the review of PR #140: sm4-cbc was CBC<NoPadding> with no --iv while
aes128-cbc on this base is PKCS7 at any length with --iv/--iv-file, and the
sm4 help pointed at aes128-cbc for an alignment rule it no longer has. sm4-cbc
is now CBC<PKCS7> over the same block-mode helpers; the three stream commands
take --iv/--iv-file through load_iv as the aes ones do; and all four take the
direction as -d/--direction, which every other cipher command already does.

The CLI tests pin encryption as well as decryption now that the IV can be
supplied: sm4-cbc encrypt --iv reproduces the draft's A.2.2 ciphertexts
followed by the PKCS7 block (computed from the permutation), sm4-cfb encrypt
--iv reproduces A.2.4 exactly, and sm4-cfb8 and sm4-ctr under --iv are held to
the library. The alignment test becomes an any-length test plus a rejected
unpadded ciphertext. Their module docs cite the files that exist
(cli/src/helpers/, test_aes_*.sh).

Assisted-by: Claude:claude-mythos-5-1
…ts over the engine

The review of PR #140 noted that the only SM4 Wycheproof vectors are
sm4_gcm_test.json and sm4_ccm_test.json and that the four-mode scope left them
unrun. They run now the way rfc8998_tests already runs the RFC 8998 vectors:
over the hazmat mode traits and the permutation's key state, since the key
carries no AEAD role. The suites follow crypto/aes/tests/{gcm,ccm}_wycheproof.rs
at the one key length: GCM dispatches the 65 96-bit-IV cases (38 valid, 27
invalid) and counts the other 39 as unsupported; CCM dispatches the 162 cases
within A.1's nonce and tag sets (135 valid, 27 invalid) and counts the other
22. All pass. The crate docs' provenance list names both files.

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

dghgit commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Review items addressed in d4df9254, a964b864 and 2868bba9 (branch head 2868bba9):

  1. Key roles. as_cipher_key and the _ => arm are gone, and check_key_type with them: the permutation's own validation is the same check for every mode that has a role, and keeping it left a surviving mutant. A scoped cargo mutants on key.rs is 0 missed (was 4). The four-mode scope is unchanged. The Wycheproof gap is closed on that scope: sm4_gcm_test.json (65 of 104 cases, the 96-bit-IV ones) and sm4_ccm_test.json (162 of 184, those within SP 800-38C A.1's nonce and tag sets) run through the GCM / CCM mode traits over the engine, as rfc8998_tests already did, and the rest are counted as unsupported as the AES suites do.
  2. CLI. sm4-cbc is CBC<PKCS7> at any length; all four commands take --iv/--iv-file and -d/--direction like every other cipher command. The CLI tests pin encryption now: sm4-cbc encrypt --iv reproduces A.2.2 (plus the PKCS7 block, computed from the permutation) and sm4-cfb encrypt --iv reproduces A.2.4 exactly.
  3. Heading is # 🚨 Security Considerations 🚨, anchor updated.
  4. Memory Usage tabulates measured per-entry-point stack (new 272 B, one- and four-block 96 B, two-block 112 B). The bench takes the AES bench's measure-closure shape and the figures are the closure frames from the release build's frame-layout remarks plus the return address, rustc 1.97 nightly on x86-64. Note: the AES table's figures do not reproduce by that method on this toolchain (its encrypt_block closure measures 40 B, not 208 B), so the SM4 table states its method rather than matching the AES numbers.
  5. The three public-behaviour unit tests moved to alg_name_tests.rs or went; the per-round pins stay.
    6–13. All fixed: manifest comment, schedule.rs link, camellia citation, src/hazmat/sm4.rs path, CLI test citations (cli/src/helpers/, test_aes_*.sh), ECB<PKCS7> in the key docs, BlockPermutation attributions, every doc line within 100 columns.
  6. cargo mutants -p bouncycastle-sm4 after the changes: 395 mutants, 368 caught, 23 unviable, 4 missed (the XOR/OR equivalences in ortho::swap, sbox and tau). PR description updated.

cargo test --workspace, --all-targets --no-run, rustdoc with warnings denied and rustfmt are clean.

dghgit added a commit that referenced this pull request Oct 28, 2026
…tack table is measured, and the doc nits

The same round as PR #140's review, applied here. CamelliaKey's three
constructors carried as_cipher_key and a `_ =>` arm for MAC-mode keys, which
lost their role in 922236e; both go, and with them check_key_type, since the
permutation's own validation is the same check for every mode that has a role
(a scoped `cargo mutants` on key.rs is 0 missed; the crate lists 577 mutants).
The key docs name ECB<PKCS7> among the symmetric-pair modes.

The Security Considerations heading takes the style guide's form and the
anchor follows it. The Memory Usage section tabulates the measured stack per
entry point and key length: bench_camellia_mem_usage takes the AES bench's
measure-closure shape for new and the six block entry points at each key
length, and the figures are the closure frames from the release build's
frame-layout remarks plus the return address.

The three unit tests in hazmat/camellia.rs that tested public behaviour only
(the engine sizes, the algorithm names, the LANES constant) move to
alg_name_tests or go; the Sec 2.3.3 subkey-swap pin stays. Nits: the manifest
comment, the sm4 citation in bitslice.rs, the ElectronicCodeBook attributions
for methods that are on BlockPermutation, "An Camellia", and every doc or
comment line past 100 columns, including the two rustfmt could not fit.

Assisted-by: Claude:claude-mythos-5-1
dghgit added a commit that referenced this pull request Oct 28, 2026
…file and -d

As for sm4 on PR #140: camellia*-cbc was CBC<NoPadding> with no --iv while
aes*-cbc on this base is PKCS7 at any length with --iv/--iv-file, and the
camellia help pointed at aes128-cbc for an alignment rule it no longer has. The
three CBC commands are now CBC<PKCS7> over the same block-mode helpers; the
nine stream commands take --iv/--iv-file through load_iv as the aes ones do;
and all twelve take the direction as -d/--direction, which every other cipher
command already does.

The CLI tests pin encryption as well as decryption now that the IV can be
supplied: camellia*-cbc encrypt --iv reproduces the OpenSSL evpciph_camellia
CBC chains followed by the PKCS7 block (computed from the permutation of the
key's length), camellia*-cfb encrypt --iv reproduces the CFB chains exactly,
and camellia128-cfb8 and -ctr under --iv are held to the library. The
alignment test becomes an any-length test plus a rejected unpadded
ciphertext. Their module docs cite the files that exist.

Assisted-by: Claude:claude-mythos-5-1
dghgit added a commit that referenced this pull request Oct 28, 2026
…ack table is measured, and the doc nits

The same round as PR #140's review, applied here. TDESKey's two constructors
ran check_key_type ahead of the engine's own validation, which is the same
check for every mode that has a role; it goes (a scoped `cargo mutants` on
key.rs is 0 missed; the crate lists 500 mutants). The constructors' docs say
the checks are the engine's.

The Security Considerations heading takes the style guide's form and the
anchor follows it. The Memory Usage section tabulates the measured stack per
entry point and engine: bench_tdes_mem_usage takes the AES bench's
measure-closure shape for new and the block entry points of both engines, the
two-key engine's decrypting ones only, under the TECBMMT3 COUNT = 0 bundle,
and the figures are the closure frames from the release build's frame-layout
remarks plus the return address.

The unit tests in hazmat/tdes.rs and hazmat/tdes2.rs that tested public
behaviour only (the engine sizes, the algorithm names and strengths, the
ENCRYPTION_APPROVED gate) move to alg_name_tests; the split/join, parity and
schedule pins stay. Nits: the manifest comment, the reference-style link in
key.rs that could not fit, and every doc or comment line past 100 columns.

Assisted-by: Claude:claude-mythos-5-1
@roy-basmacier

Copy link
Copy Markdown

Looks good, there are 2 new issues found after the fix:

New issues:

  1. The stack table undercounts by about 4x. It reports only the top frame of each call. But encrypt_4blocks, tau and ortho each get their own frame underneath it, which I confirmed in the disassembly. The AES table, which this one claims to follow, sums the whole chain. Same build, with return addresses:

    Entry point PR table Deepest chain
    new 272 B ~704 B
    one- and four-block calls 96 B ~416 B
    two-block calls 112 B ~432 B
  2. A key under a MAC mode now contradicts its own KEY_TYPE (minor). For SM4Key<CMAC<16>>, KEY_TYPE is still MACKey. The constructor now rejects MAC keys and accepts cipher keys, and generate() always fails. This breaks the constructor trait's contract, but such a key can't do anything. A one-line key-type check against the mode, plus a test, would make it reject consistently.

This branch has not been deployed

No deployments
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