Repository navigation
Conversation
cargo mutants -- full-crate confirmation run, pre-trait-wiring state (
|
cargo mutants -- scoped run over the trait-wiring + keygen commits (
|
Follow-up:
|
5ff6d96 to
0e05947
Compare
84ec037 to
09533a3
Compare
b248fd0 to
0e05947
Compare
007ec12 to
1d4ea0b
Compare
09533a3 to
52481c6
Compare
1d4ea0b to
41f62fc
Compare
440206e to
0ccf465
Compare
440206e to
d5f1367
Compare
f800f7e to
ec55823
Compare
|
Merged feature/ecdsa at a42ce74 (which carries release/0.1.3alpha 029a17b, the squash of PR #190) into this branch as b5e25d7. The RSA and RSA-SVE CLI commands moved onto the release's input rule from #190: private keys through the zeroized secret readers, public keys, signatures and ciphertexts as raw bytes or hex under the global --hex-input; the RSA CLI tests write raw key files. Workspace suite passes (2408 tests). |
b5e25d7 to
7bb63e9
Compare
|
Replayed onto feature/ecdsa as rebased onto release/0.1.4alpha (69df164): feature/rsa-sig is now 7bb63e9, 26 commits on that tip, replayed from b5e25d7 with the ecdsa merges dropped and the tree restored to the old tip plus the 0.1.4 line's setup (version bump and notes file). The base stays feature/ecdsa. Workspace suite passes (2408 tests). |
a582209 to
de882ed
Compare
Empty crate for RSASSA-PKCS1-v1_5 and RSASSA-PSS (RFC 8017), depending on bouncycastle-ec for its constant-time limb arithmetic (nat/montgomery) rather than a dedicated bignum type. No algorithms yet -- this is step 1 of the RSA signature plan on this branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds modexp.rs: a Montgomery-based mod_pow generic over limb count L, built on bouncycastle-ec's nat/montgomery primitives. Unlike every curve in that crate, RSA's modulus arrives at runtime rather than as a compile-time constant, so this derives its own Montgomery setup (n' by Newton's method, R mod n and R^2 mod n by repeated doubling-and-reduction) instead of using precomputed per-curve constants -- both operate on the modulus alone, which is public in RSA, so they run in variable time. mod_pow itself is constant time in the exponent via Joye & Yen's Montgomery powering ladder (CHES 2002): one multiply and one square per modulus bit, moved into place with a branch-free conditional swap. Validated against known-answer vectors computed with Python's arbitrary- precision pow() (not from recall) at L=1, 2, 4, and 32 (2048-bit, the actual RSA-2048 limb count), plus a genuine 128-bit toy RSA keypair's sign/verify round trip and the classic textbook n=3233 example. cargo mutants -p bouncycastle-rsa --jobs 3: 26 mutants, 23 caught, 2 unviable, 1 missed (an OR/XOR equivalence in redc_normalize's subtraction mask -- documented inline, not reachable given montgomery::redc's own <2n postcondition). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nd SignatureVerifier methods 60634ad on feature/ecdsa renamed the streaming halves of the signature traits to do_sign_init / do_sign_update / do_sign_final / do_sign_final_out and do_verify_init / do_verify_update / do_verify_final, and moved do_hazardous_operations from bouncycastle_core::key_material to bouncycastle_core::hazmat. The preceding merge brought those changes in, but the RSA crate, its tests and the rsa CLI command still used the old names, so the workspace did not build. This is the mechanical rename and import fix, nothing else. Assisted-by: Claude:claude-mythos-5-1
RSASSA-PKCS1-v1_5, RSASSA-PSS and RSASSA-PSS with SHAKE lose their `Signer`/`SignatureVerifier` impls and the `sign_randomized`, `set_signer_salt` and `do_sign_*` surface that came with them; each (scheme, hash, size) type is now an algorithm marker (`Algorithm` + `SignatureAlgorithm`, named "RSASSA-PKCS1-v1_5", "RSASSA-PSS" and "RSASSA-PSS-SHAKE", strength from the modulus length) that `bouncycastle_core::signature_key`'s role traits are implemented over. `RsaPrivateKey` implements `SigningKey` for the PSS markers, whose `create_signer` draws the salt from the caller's RNG, and `DeterministicSigningKey` for PKCS#1 v1.5, which draws nothing; `RsaPublicKey` implements `VerifyingKey` for all three. The workers (`RsaPssSigner`, `RsaPssShakeSigner`, `RsaPkcs1v15Signer` and their verifiers) borrow the key, and the PSS signers keep `set_salt` for a known-answer test. The role impls are bounded on the key's `SignaturePrivateKey`/`SignaturePublicKey` impl, so a 1024- or 1536-bit key still has no signing role, as the `compile_fail` doctest pins. Since one key type serves every marker, a call spells it: `SigningKey::<RSASSA_PSS_SHA256, 256>::sign(&sk, &mut rng, msg, None)`. A signature of the wrong length is a `LengthError`, as the other signature crates report it, where it was `SignatureVerificationFailed`. The no-argument `keygen()` goes and `keygen_from_rng` becomes `keygen(rng)` in each size module and in RSASVE, the rule the rest of the library follows; the CLI's keygen draws from `HashDRBG_SHA512::new_from_os()`. The EMSA encoders take `HashOneShot` and call `H::hash_out`. Tests, benches, the stack harness and the CLI move onto the key traits; the state-confusion test of the old `do_sign_init` went with the method it exercised. Assisted-by: Claude:claude-mythos-5-1
…ried over the rebase onto its release-based history This branch took feature/key-rework-memory by merge, and the merge commits carried the adaptations to what arrived. The rebase onto the branch's new history (key-rework-memory rebased onto release/0.1.3alpha, which carries feature/key-rework as one squash commit) dropped those merges; this commit restores the affected files to the branch's pre-rebase tip, so the tree is identical to it. Assisted-by: Claude:claude-mythos-5-1
…er the rebase onto feature/ecdsa's release/0.1.4alpha history This branch took feature/ecdsa (which carried release/0.1.3alpha 029a17b, the squash merge of PR #190) by merge, and that merge commit carried the adaptations to what arrived. The rebase onto feature/ecdsa as replayed onto release/0.1.4alpha dropped the merge; this commit restores the affected files to the branch's pre-rebase tip, so the tree is identical to it apart from the 0.1.4 line's own version bump and notes file. Assisted-by: Claude:claude-fable-5-1
An audit against QUALITY_AND_STYLE's rule on secret-holding locals. The key material itself was already in `Secret` (the CRT quintuple in the key, `p`, `q`, `d` and the quintuple in key generation, `Z` and `z` in RSASVE); what the rule found was the plain locals around it. `crt_exp` left every intermediate of the CRT on the stack: the two half-signatures (either one factors `n`, through `gcd(s - s1, n)`), `m mod p`, `m mod q`, `h` and the recombination temporaries, plus two `MontgomeryContext`s each holding a prime. The nine are zeroized before `s` is returned, and the contexts are built inside `Secret`: `MontgomeryContext` gains `Copy` and a `ZeroizablePrimitive` impl for that. `mod_pow` left its second ladder register and the per-bit products; both are now declared before the ladder and zeroized with `r0` after it, and `from_montgomery` and `mul_mod` zeroize their own temporaries. The products inside `mont_mul` and `mont_square` are the callee intermediates the rule does not reach, and are left. `RsaPrivateKey::from_bytes_raw` decoded through five byte-array copies and five limb arrays; the byte arrays are now borrowed from the caller's buffer and the limb arrays zeroized once the key holds them. In key generation, `is_probable_prime` holds `w - 1`, `m`, the context over `w` and the Miller-Rabin `z` in `Secret` (the function has several exits), `primes_too_close` zeroizes the differences it takes, and `keygen_from_rng`'s two blocks zeroize `g`, `(q-1)/g`, `q mod p` and `p - 2` and hold the context over `p` in `Secret`. `primes_too_close` gains the unit test its reject path and boundary need, which no key generation reaches (two random primes land within `2^(nlen/2 - 100)` with probability about `2^-100`): the private known-answer exception, with the cases either side of `2^28` at `N = 2` and in both argument orders. No behavioural change. cargo test -p bouncycastle-rsa 245 passed, 4 ignored; clippy locations identical. Mutants (apollo, `--in-diff`): 47 tested, 36 caught, 11 unviable, 0 missed. Caught: keygen 18 (the `primes_too_close` restructure and its new tests among them), keys 10, modexp 6, rsa_core 2; the unviable ones are whole-function replacements of types without `Default`. A removed `zeroize` call is not a mutant cargo mutants generates. Assisted-by: Claude:claude-mythos-5-1
de2f09a to
57c5c09
Compare
…feature/rsa-sig: no conflicts Assisted-by: Claude:claude-opus-5-5
…llia and SM4; fix/block-cipher-key; fix/shared-bitslice) into feature/rsa-sig: no conflicts; the workspace manifest, the umbrella crate, cli/src/main.rs and the mem_usage_benches manifest and lib keep both sides' entries Assisted-by: Claude:claude-mythos-5-1
…ature worker traits are dyn-compatible) into feature/rsa-sig: no conflicts Assisted-by: Claude:claude-opus-5-5
…nd decapsulation on the key objects with encaps_out / decaps_out, KdfParams<K, LEN> with KeyLen, the KDF trait reshaped, SHA-3 and SHAKE no longer KDFs, SuspendableKeyed gone) into feature/rsa-sig: no conflicts
… the largest KEM and does not repeat The seed was a 256-byte counting pattern, so two consecutive draws of 256, 512 or 1024 bytes, RSASVE's secret at 2048, 4096 and 8192 bits, were identical and the "two encapsulations differ" check failed for every size but 3072; a 4096-byte hashed pattern now. Assisted-by: Claude:claude-mythos-5-1
The squash of PR #192 (f3fdb8b) replaced the static KEM traits: the KEM-only key types implement `EncapsulatingKey<()>` / `DecapsulatingKey<()>` in each size module, naming `RSASVE` as the `KEMAlgorithm`, and hand out `RsasveEncapsulator` / `RsasveDecapsulator` workers whose `encaps_out` is RSASVE.GENERATE and `decaps_out` RSASVE.RECOVER, writing into the caller's buffers and leaving them zeroized on error; `encaps`, `encaps_rng` and `decaps` go, and a wrong-length ciphertext is a type error, so its test and `KEMError::LengthError` go with them. The tests, benches and crate docs call the roles. Assisted-by: Claude:claude-mythos-5-1
…em's ciphertext reader Assisted-by: Claude:claude-mythos-5-1
Assisted-by: Claude:claude-mythos-5-1
…ier types the port replaced Assisted-by: Claude:claude-mythos-5-1
…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.
…generator per curve in bouncycastle-ec's hazmat, which the ecdsa and eccdh keygens and derive_pks now wrap) into feature/rsa-sig
8943048 to
4c3fe7f
Compare
…markers and the key pair extension traits; fix/ec-curve-trait squash: curve::Curve in bouncycastle-ec; fix/eccdh-generic squash: ECC CDH once over Curve, utils ConstStr, the ec re-export; fix/ecdsa-generic squash: ECDSA once over Curve and the hash, and KdfParams over a KdfDescription an HSM can read) into feature/rsa-sig No conflicts and no adaptation: the rsa crate, its CLI commands and its stack harness are untouched by the four squashes. RSASVE implements the KEM roles over `()`, so the change of KdfParams to borrow a KdfDescription does not reach it; a KDF-paired RSASVE would be written over the descriptions. bouncycastle-utils' ConstStr, which the size-marker refactor on fix/rsa-size-markers cherry-picked ahead of this merge, now comes from the release. Assisted-by: Claude:claude-mythos-5-1
…h: the hkdf description clone test and the KDF mutants numbers in the release notes) into feature/rsa-sig No conflicts and no adaptation. Assisted-by: Claude:claude-mythos-5-1
…eme over a modulus-size marker and gives RSASVE a KDF pairing: the six per-size modules each expanded the key types, the three signature schemes at every hash and RSASVE through macros over up to twelve const parameters, so a scheme was six copies and a signature of it unreadable. The size is now a marker type, RSA1024, RSA1536, RSA2048, RSA3072, RSA4096 and RSA8192 at the crate root, implementing size::RsaSize (the lengths as consts, the limb and byte arrays as associated types through a small Array<T> trait, and forwarders to the public-key arithmetic at that width) and, from 2048 up, size::RsaSigningSize (FIPS 186-5 Table B.1's Miller-Rabin rounds, keygen, and the private key's arithmetic: the CRT exponentiation, the prime-width codecs and comparison, and the modulus from its primes), the latter the compile-time form of the crate's rule that RSA-1024 and RSA-1536 verify only: the private key's constructors and encoder, the signing roles, rsasp1, RSASVE's key generation and the KEM private key are all on impl<K: RsaSigningSize>, so no private key value and no private-key arithmetic exists at the two legacy sizes. The keys are RsaPublicKey<K> and RsaPrivateKey<K>, the schemes RSASSA_PKCS1_v1_5<K, H>, RSASSA_PSS<K, H> and RSASSA_PSS_SHAKE<K, X>, named by scheme and hash through utils' ConstStr ("RSASSA-PSS-SHA256") at the lesser of the modulus's and the hash's strength, and the KEM RSASVE<K> ("RSASVE-2048") over RsaKEMPublicKey<K> and RsaKEMPrivateKey<K>; the role traits that name a [u8; N] are implemented once each over an equality bound on the marker's array type, as the ECDSA and ML-DSA crates do, so a call needs no annotation beyond the algorithm; size::SignatureKeyPair and size::KemKeyPair hang the two generators on a signing size's marker. RSASP1 and RSAVP1 are rsasp1::<K> and rsavp1::<K>; MGF1 and the EMSA encoders take the digest as a slice and the modulus length as the one const they need; the PSS signers hold the salt in a buffer sized for the largest hash. The per-size modules stay as alias modules (rsa_2048::RSASSA_PSS_SHA256 is RSASSA_PSS<RSA2048, SHA256>, RSA2048PrivateKey is RsaPrivateKey<RSA2048>, SIG_LEN is RSA2048::K_LEN), so the CLI, the benches, the stack harness and every known-answer suite are unchanged in what they exercise. One behaviour is dropped: a private key built from CRT components of the wrong width was a decoding error at run time and is now a type error. RSASVE gains the pairing the KDF description was made for: SP 800-56B Rev. 2 passes the secret value Z through a key-derivation method (its Section 5.5) before any use, and rsasve_kdf.rs gives the key-establishment keys the KEM roles a second time over KdfParams<'p, HkdfDescription<'s, H>, SS_LEN>, the shape the ML-KEM pairings have: RSASVE_HKDF<K, H>, named "RSASVE-<bits>-HKDF-<hash>" at the lesser of the size's and the hash's strength, for any modulus size and any HMAC hash since the parameter type is the library's description and nothing foreign can be paired; the workers run RSASVE.GENERATE or RSASVE.RECOVER for Z into one local zeroized when it drops, build the HKDF engine from the description, derive the key of the length the parameters name (one byte to 255 hash outputs, checked at compile time where a worker is made) and cap its label at the algorithm's strength, with a ciphertext for a key the caller never got cleared and the caller's buffer left zeroized on every error; the description's salt and the info are what a token key reads to hand its device the same derivation. No CLI subcommand yet: the rsasve-* commands hand out the raw Z as before. Tests: the RSASVE suite's macros take the marker and the direct generic calls name it; rsasve_hkdf_tests runs the core framework KEM suite over RSA-2048 with HKDF-SHA256 and HKDF-SHA512, the derivation against HKDF by hand over the raw Z (the crate's example key, so no key generation), another info and the absent salt, the prefix and maximum lengths, the refused ciphertext, and the names and strengths at three sizes; keys_tests checks the CRT range checks at every signing size, the RSA-1024 and RSA-1536 suites the out-of-range signature representative through their own range check, and rsassa_worker_tests the three schemes' workers naming their algorithm and PSS's random-salt sign_from_hash drawing a fresh salt that verifies. Verified: the rsa suite (the 4096 and 8192 keygens still #[ignore]d), the rsa doctests, the cli suite, the all-targets build, nightly rustfmt, clippy adding nothing on the changed lines, rustdoc with warnings denied on the crate apart from modexp.rs's pre-existing private-item links. Mutants on apollo, scoped to the crate's sources against the subset of its suites the earlier runs validated: 1168 (639 caught, 362 unviable, 25 timeouts, 142 missed: keygen.rs's 65, where bounds were the only change and the file's mutants are the slow, nondeterministic ones documented before; the check_lengths and len_check const assertions; emsa_pkcs1_v1_5's checks noted as equivalent at their sites and MAX_T_LEN's arithmetic; the PSS encoders' step 3 length check that no modulus here reaches and their 8 * K_LEN arithmetic; mgf1's block count; modexp's disjoint-bit ORs; the defensive length checks in the KDF derivations; rsasve's kem_error arms, covered by the keygen test the scoped run skips; and the forwarders, worker names and random-salt entry points the final commit's tests close).
Assisted-by: Claude:claude-mythos-5-1
…eme and hash: a key served every scheme, so sk.sign(..) was ambiguous and every call spelled SigningKey::<RSASSA_PSS<RSA2048, SHA256>, 256>::sign(&sk, ..), and nothing stopped one key pair signing under two schemes, which SP 800-57 Part 1 Rev. 5 asks a key pair not to do. The key types now carry their algorithm, as the ECDSA keys carry their curve and hash: what RsaPublicKey<K> and RsaPrivateKey<K> were is keys::RsaPublicComponents<K> and RsaPrivateComponents<K>, the material with its validation and raw encoding and no role, and RsaPublicKey<A> and RsaPrivateKey<A> wrap them for the algorithm A their type names, so that sk.sign(&mut rng, msg, None), sk.sign_deterministic(msg, None) and pk.verify(msg, None, &sig) resolve from the key, a PSS key has no sign_deterministic and a PKCS#1 v1.5 key no sign; the components turn into a key for a scheme through key::<A>() / into_key::<A>(), the one deliberate step by which the same material serves two schemes, and the bytes carry no scheme, so a key file is decoded under the key type it is for. The roles are written once, in the new rsassa module, over three traits the markers implement: RsaSignatureAlgorithm (the size, the hash and the scheme's EMSA verification), RsaDeterministicSignatureAlgorithm (PKCS#1 v1.5, an encoding of the hash alone) and RsaRandomizedSignatureAlgorithm (PSS, an encoding of the hash and a salt), split by what the signer draws because two blanket role impls distinguished only by a bound on A overlap under coherence; three workers, RsaSigner with set_salt, RsaDeterministicSigner and RsaVerifier, replace the nine per-scheme ones, the scheme modules keep only their markers, and the pre-hash entry points are rsassa::sign_from_hash, sign_from_hash_with_salt, sign_from_hash_deterministic and verify_from_hash over <A, K_LEN>. The signing-size markers generate a pair for one algorithm, RSA2048::generate_pkcs1_v1_5_signature_key_pair::<SHA256>(&mut rng) and generate_pss_signature_key_pair::<SHA256>, the hash spelled and never defaulted as on the ECDSA markers, and the plain generate_signature_key_pair goes; RsaSigningSize::keygen returns the components. The size modules lose RSA2048PublicKey, RSA2048PrivateKey and keygen, a key having no meaning without a scheme, and keep the scheme aliases and lengths; the key-establishment keys wrap the components as before, with their own Display. The CLI's generic rsa_pss_cmd, rsa_pkcs1_cmd and do_verify are over the algorithm alone and each size's command passes the marker's generator for the pairing it dispatches, subcommands, flags and outputs unchanged; the stack harness names its keys with their scheme. Tests: every suite builds the components once and types a key per pairing, the encoding tests under one scheme since the bytes are the same under any, and the rsa CLI tests type the documented key with the scheme each case checks. Verified: the rsa suite (the 4096 and 8192 keygens still #[ignore]d), the doctests, the cli suite, the all-targets build, nightly rustfmt, clippy adding nothing on the changed lines, rustdoc with warnings denied on the crate apart from modexp.rs's pre-existing private-item links. Mutants on apollo, scoped to keys.rs, rsassa.rs, the three scheme files, size.rs and rsasve.rs against the signature and RSASVE suites at 2048 and the verify-only sizes: 505 (162 caught, 330 unviable, 2 timeouts, both rsasve's encaps_out attempt counter made to loop, and 11 missed: the typed keys' e accessor, Debug and PartialEq, which the next squash's keys test closes; rsasve's kem_error arms, covered by the keygen test the scoped run skips; and size.rs's check_lengths const assertion and is_less at the two verify-only sizes, as before). Assisted-by: Claude:claude-mythos-5-1
…a type and makes SHAKE a hash like any other: RFC 8017 §9.1 parameterises EMSA-PSS by the hash, the mask generation function and the salt length, and the crate fixed MGF1 over the hash and sLen = hLen, with a second marker, RSASSA_PSS_SHAKE<K, X>, for RFC 8702's XOF-as-mask. The marker is now RSASSA_PSS<K, H, P> over rsassa_pss::PssParams<H> (the salt length, the mask, and what the name says after RSASSA-PSS-<hash>), P defaulting to Conventional, which reads the hash's conventional pairing off PssHash, a trait implemented once per hash family: MGF1 over the hash itself with a salt one output long for SHA256Internal, SHA512Internal and SHA3Internal (§8.1's recommendation, §9.1 note 4's typical length), the XOF as the mask with its output length as the salt for SHAKEInternal (RFC 8702 §3.2.1), so RSASSA_PSS<RSA2048, SHAKE128> is RSASSA-PSS-SHAKE128 and a SHAKE pair comes from generate_pss_signature_key_pair::<SHAKE128> like a SHA-2 one. Spelled parameters are the other two types: Mgf1<M, SALT_LEN>, MGF1 over another hash and any salt from 1 to 64 bytes, which §8.1 allows and Appendix A.2.3's RSASSA-PSS-params carry, named -MGF1-<hash>-<sLen>; and NoSalt<M>, §9.1 note 4's zero salt, which makes the encoding a function of the key and message alone and so puts the key on the deterministic role (sign_deterministic, create_signer_deterministic) rather than the salted one, the SaltedPss / UnsaltedPss markers deciding which of the two rsassa algorithm traits a marker implements. A key is for one (H, P), so a verifier never guesses the mask or the salt length, and a conventional key rejects a zero-salt or short-salt signature over the same material. emsa_pss is written once over H and P; emsa_pss_shake, rsassa_pss_shake, the RSASSA_PSS_SHAKE alias and generate_pss_shake_signature_key_pair are gone, generate_pss_signature_key_pair_with::<H, P> added for the spelled forms, and the signer's set_salt takes [u8; A::SALT_LEN]. The strength stays min(size, H): the mask's hash does not enter the proof (§8.1). utils gains ConstStr::decimal, the digits of a number for a name, with its tests. The CLI's only change is the generator name; the per-size aliases and every command are as before. Tests: tests/rsa_2048_pss_params_tests.rs runs Wycheproof's rsa_pss_2048_sha512_mgf1sha256_32_params_test.json (178 vectors, 132 valid and 46 invalid) under RSASSA_PSS<RSA2048, SHA512, Mgf1<SHA256, 32>> and rsa_pss_2048_sha256_mgf1_0_test.json (103 vectors, 61 valid and 42 invalid) under RSASSA_PSS<RSA2048, SHA256, NoSalt<SHA256>>, the names, the strengths against the size's and the hash's, each family's conventional mask and salt length, no-salt signing deterministic and reproduced through the streaming signer, a 16-byte salt pinned through set_salt, the parameters not interchangeable, and the generator with spelled parameters; keys_tests checks the typed keys' e, Debug and PartialEq, the misses of the previous squash's run; the SHAKE suites and rsassa_worker_tests use the one marker. Verified: the rsa suite, the utils suite, the rsa doctests, the rsa CLI tests, the all-targets build, nightly rustfmt, clippy adding nothing on the changed lines, rustdoc with warnings denied apart from modexp.rs's pre-existing private-item links. Mutants on apollo, scoped to keys.rs, rsassa_pss.rs, emsa_pss.rs, rsassa.rs and size.rs against the signature suites at 2048 and the verify-only sizes alone (the KEM suites left out, which is what makes the run 19 minutes rather than 32): 481 (187 caught, 264 unviable, no timeouts, 30 missed, of which min_strength's comparison and the SHA-512 and SHA-3 masks emptied were then caught by the two test commits in a targeted re-run, leaving 27: emsa_pss's buffer-size arithmetic over MAX_HASH_LEN and its step 3 length check that no modulus here reaches, as before; size.rs's check_lengths const assertion and is_less at the two verify-only sizes, as before; and keys.rs's modulus_strength and size.rs's is_less and half_limbs_from_be_bytes at 3072, 4096 and 8192, which only the RSASVE suites reach and which the previous squash's run, with those suites, caught); utils const_str.rs 39 (19 caught, 19 unviable, 1 timeout: the copy loop's increment made a no-op, so const evaluation never finishes and the build never completes, a kill in effect). Assisted-by: Claude:claude-mythos-5-1
Summary
RSA digital signatures, plus the RSASVE KEM, across a limited range of modulus sizes up to the maximum originally requested, 8192 bits: RSASSA-PKCS1-v1_5 and RSASSA-PSS (RFC 8017 §8), the latter with its parameters as a type, the conventional MGF1 pairing, PSS-SHAKE128/256 (RFC 8702 §3.2.1), another MGF1 hash or salt length (§8.1, Appendix A.2.3) and the zero salt (§9.1 note 4, deterministic), over RSA-1024/1536 (verify-only) through RSA-2048/3072/4096/8192 (sign+verify), on
bouncycastle_core's key roles. The size is a marker type (RSA2048), a scheme is one generic marker over the size, the hash and its parameters (RSASSA_PSS<RSA2048, SHA256>,RSASSA_PSS<RSA2048, SHAKE128>,RSASSA_PSS<RSA2048, SHA512, Mgf1<SHA256, 32>>), and a key is a key for one of them:RsaPrivateKey<A>implementsSigningKeyorDeterministicSigningKeyby what the scheme draws andRsaPublicKey<A>VerifyingKey, handing outSignatureSigner/SignatureVerifierworkers, with the material behind them asRsaPrivateComponents<K>/RsaPublicComponents<K>. RSASVE (SP 800-56B Rev. 2 §7.2.1) is the KEM rolesEncapsulatingKey/DecapsulatingKeyover()on KEM-only key types at 2048/3072/4096/8192, andRSASVE_HKDF<K, H>the same roles over aKdfParams<HkdfDescription>for a derived secret. Key pair generation is FIPS 186-5 Appendix A.1.3, from the size marker for one scheme (RSA2048::generate_pss_signature_key_pair::<SHA256>(&mut rng)). A new crate,bouncycastle-rsa, with its CLI subcommands, criterion benches and stack-usage harness.Description
bouncycastle_ec's constant-time fixed-width limb arithmetic (bouncycastle_ec::nat,bouncycastle_ec::montgomery) rather than a dedicated BigInteger type, for the same constant-time-on-secret-exponent reasoning that motivates that crate's own custom-curve arithmetic: a modular exponentiation over a secret exponent has to run in the same time regardless of the exponent's value, which needs fixed-width operands.modexpis Montgomery exponentiation over a runtime modulus (f7f2f12);keysholds the CRT key types andrsa_corethe CRT-based RSASP1/RSAVP1 (182eb3c).size::RsaSize, and only those from 2048 upsize::RsaSigningSize, on which the private key's constructors and encoder, the signing roles, RSASP1, key generation and the KEM private key are all written, so no private key value and no private-key arithmetic exists at the two legacy sizes (acompile_faildoctest inrsa_1024.rspins it).RSASSA_PKCS1_v1_5<K, H>andRSASSA_PSS<K, H, P = Conventional>(spec spelling per QUALITY_AND_STYLE) are the markers;Pisrsassa_pss::PssParams<H>, the mask and the salt length:Conventionalreads the hash's conventional pairing offPssHash(MGF1 over the hash with a salt one output long for SHA-2 and SHA-3, the XOF itself as the mask for SHAKE),Mgf1<M, SALT_LEN>spells another,NoSalt<M>is the zero salt and puts the key on the deterministic role. A key isRsaPrivateKey<A>/RsaPublicKey<A>over the components, sosk.sign(..)andpk.verify(..)resolve from the key, a PSS key cannot sign PKCS#1 v1.5 (SP 800-57 Part 1 Rev. 5's one scheme per key pair), and the components become a key for a scheme throughkey::<A>(); the roles are implemented once inrsassaover three algorithm traits, with three workers (RsaSignerwithset_salt,RsaDeterministicSigner,RsaVerifier) and the*_from_hashentry points. Per-size alias modules (rsa_2048::RSASSA_PSS_SHA256,SIG_LEN) serve code at one size. The schemes were first wired asSigner/SignatureVerifiertypes (a996164), the per-size free functions dropped once the traits covered them (c84e7a6), and the whole ported onto the key role traits when those landed on the release (c6ea90d).e = 65537;|p - q|andd > 2^(nlen/2)criteria from A.1.1), structured after BC Java'sRSAKeyPairGenerator, on the crate's own limb arithmetic, no bignum dependency. Exposed on the signing sizes assize::SignatureKeyPair(RSA2048::generate_pkcs1_v1_5_signature_key_pair::<SHA256>(&mut rng),generate_pss_signature_key_pair::<H>,generate_pss_signature_key_pair_with::<H, P>, a pair for one algorithm, the hash spelled and never defaulted) andsize::KemKeyPair, withRsaSigningSize::keygengiving the components; the RNG must be at least the modulus's strength. The private-key paths scrub what they derive from the primes (57c5c09).FipsRSA.KTS_SVE(JCA "RSA-KAS-KEM") and OpenSSL 3 as its "RSASVE" KEM operation. b539612 is a behaviour-preserving refactor that splits the exponentiation out ofrsavp1/rsasp1into crate-privatepublic_exp/crt_exp, so RSASVE can reuse it under SP 800-56B's1 < x < n - 1range checks instead of RFC 8017's. The KEM-only key typesRsaKEMPublicKey/RsaKEMPrivateKeykeep key establishment and signing apart (§6.1 item 5) and add the §6.2.1 checks (e >= 65537,nof exactlynlenbits). After the squash of PR KEM encapsulation and decapsulation move onto the key objects (EncapsulatingKey / DecapsulatingKey, ML-KEM ported, KeyType::SharedSecret) #192 they implement the KEM roles over(), namingRSASVEas the algorithm, withRsasveEncapsulator::encaps_outas RSASVE.GENERATE andRsasveDecapsulator::decaps_outas RSASVE.RECOVER into the caller's buffers; the ciphertext is a[u8; CT_LEN], so a wrong length is a type error. The shared secret is the rawZat the modulus's full length, typedCryptographicRandomat the modulus's strength, for the caller's KDF;RSASVE_HKDF<K, H>(rsasve_kdf, in eb4aefd) gives the same key types the KEM roles overKdfParams<HkdfDescription<H>>, SP 800-56B Rev. 2 Section 5.5's derivation of the secret fromZ, the parameters being the library's HSM-readable description.rsa-<size>andrsasve-<size>, which stream stdin through the workers in this CLI's usual 1 KB chunks; criterion benches (including keygen at 2048/3072); a stack-usage harness.Scope and Risk
New crate only (
bouncycastle-rsa) plus its CLI wiring andmem_usage_benchesharness, based onrelease/0.1.4alpha. Two changes outside those:utils::const_str::ConstStr::decimal, the digits of a number for an algorithm name (-MGF1-SHA256-32), with its tests; andcore-test-framework's KEM suite (dce5490): its fixed seed was a 256-byte counting pattern, so two consecutive draws of 256, 512 or 1024 bytes, RSASVE's secret at 2048, 4096 and 8192 bits, were identical and the "two encapsulations differ" check failed; it is a 4096-byte hashed pattern now. The branch has taken each release tip by merge, most recently f213c9e (the ECDSA-over-Curve and KDF-description squashes).Validation
cargo test --workspacepasses at the head, all targets build, rustfmt is clean, and thersa-*/rsasve-*CLI tests pass. For the crate: Wycheproof PKCS1-v1.5/PSS/PSS-SHAKE vectors per size and hash, itsrsa_pss_2048_sha512_mgf1sha256_32_paramsandrsa_pss_2048_sha256_mgf1_0files for the spelled parameters (178 and 103 vectors), BC Java cross-check vectors at 8192,core-test-framework's signature conformance suite for every pairing at every size, and its KEM conformance and key suites for RSASVE at every size.tests/rsasve_tests.rs, vectors inbc-test-data/crypto/rsa): NIST ACVP-Server KAS-IFC-SSC-Sp800-56Br2, the KAS1 group (2048, CRT) and the KAS2 group (3072, CRT, both directions),decapsrecoveringZfromCand an encapsulation fed the vector'sZreproducing itsC, with the failChangedZ cases not matching; ACVP-Server RSA-DecryptionPrimitive-Sp800-56Br2, CRT groups at 2048/3072/4096 including the out-of-range ciphertexts; OpenSSL 3.0's "RSASVE" KEM operation at every size, 8192 included../dev_scripts/quality_stats.sh ./crypto/rsaat the head: 4383 core lines, 1290 docstring lines, test ratio 0.95, 1 unwrap and 36Err()sites in core code.cargo mutants, as reported in the comments below (the hashes there belong to the pre-rebase history): the full-crate run of the pre-trait-wiring state, 646 mutants, 446 caught, 6 missed (documented OR/XOR and boundary equivalents), 3 timeouts, 191 unviable; the scoped run over the 14 trait-wiring and keygen files, 751 mutants, 476 caught, 0 missed, 180 unviable, 95 timeouts, of which the--iterate --timeout 7200re-run left 14 genuine infinite loops, all itemised; RSASVE'srsasve.rsand the KEM code of the four size modules, 168 mutants, 72 caught, 0 missed, 95 unviable, one real hang (attempts += 1to*= 1removes the draw cap). Since then, each squash carries its own scoped run in its message: the size markers and RSASVE-HKDF (eb4aefd), 1168 mutants, 639 caught, 362 unviable, 25 timeouts, 142 missed and itemised, keygen.rs's slow nondeterministic ones among them; the scheme keys (010cf1a), 505 mutants, 162 caught, 330 unviable, 2 timeouts, 11 missed; the PSS parameters (5c31875), 481 mutants, 187 caught, 264 unviable, no timeouts, 27 missed after the targeted re-runs, plusutilsconst_str.rs, 39 mutants, 19 caught, 19 unviable, 1 build-time hang. Each squash message itemises its misses.AI Usage Statement
Assisted-by: Claude:claude-sonnet-5
Assisted-by: Claude:claude-fable-5-1
Assisted-by: Claude:claude-opus-5-5
Assisted-by: Claude:claude-mythos-5-1
🤖 Generated with Claude Code