FIPS 205 defines twelve SLH-DSA parameter sets. OxiCrypto shipped ten of them for two releases straight — the pinned slh-dsa dependency had the last two the whole time, nobody had wired them up.
Today we released OxiCrypto 0.3.0 — the release that closes that gap, completes the TLS 1.3 cipher-suite negotiation quartet with a new negotiate_aead, adds coverage-guided fuzzing across four previously-untested crates (and fixes a fifth that turned out not to even build), gives every sub-crate that lacked one a runnable example, and closes a panic-DoS in bcrypt hash verification.
No OpenSSL. No BoringSSL. No ring, no aws-lc-rs in the default closure. No FFI. No -sys crates. OxiCrypto 0.3.0 still compiles to a single static binary (or WASM) with zero apt-get install and no C toolchain — exactly as it has since 0.2.0.
Why OxiCrypto 0.3.0 is a game changer
Even a mature Pure Rust crypto stack accumulates quiet gaps once you look for them:
- A post-quantum signature scheme advertised as “SLH-DSA support” that’s actually missing 2 of its 12 FIPS 205 parameter sets is a silent portfolio hole — the upstream crate had
Shake192s/Shake192fsitting right there, unused, because nobody had finished wiring them through the workspace’simpl_slh_dsa_param!macro. - Three of four TLS 1.3 cipher-suite legs (MAC, signature, key exchange) already resolved from a wire identifier via
negotiate_mac/negotiate_sig/negotiate_kex— but the fourth leg, the AEAD that actually touches every byte of every record, was left for every caller to hand-roll. - Nine of fourteen crates in the workspace had zero runnable examples. The only way to see a working call was to read a test file.
- Four of the highest-risk untrusted-input surfaces — AEAD unsealing, HMAC truncated-tag verification, PQ key-share wire decoding, bcrypt hash-string parsing — had no fuzz coverage at all. Worse, the one fuzz crate that did exist (
oxicrypto-hash) turned out not to build:cargo buildfrom inside it failed outright for lack of its own[workspace]table. - A bcrypt hash string sourced from outside the process — read from a database, say — that happened to contain a multi-byte UTF-8 character landing on one of the format’s fixed byte offsets triggered a Rust string-slicing panic instead of a clean rejection. That’s a denial-of-service vector on any verification path that trusts external input, which is most of them.
OxiCrypto 0.3.0 ends all of that.
- All 12 of 12 FIPS 205 parameter sets, finally.
SlhDsaShake192s/SlhDsaShake192f(security category 3, SHAKE) land via the existingimpl_slh_dsa_param!macro, with SK/VK/signature length constants and key-size + sign/verify/tamper tests, same as the other ten. - The fourth negotiation leg.
negotiate_aead(suite: TlsCipherSuite) -> Result<Box<dyn Aead>, CryptoError>in a newoxicrypto-aead::tlsmodule resolves any of the five TLS 1.3 cipher suites (RFC 8446 §B.4) straight to a boxedAead— noOxiTLScrate dependency required. - A fuzz suite that actually runs. Four new coverage-guided
cargo-fuzzharnesses (oxicrypto-aead,oxicrypto-mac,oxicrypto-pq,oxicrypto-kdf) plus a fix that makes the pre-existingoxicrypto-hashfuzz crate buildable at all — all five were smoke-tested from tens of thousands to 1M+ iterations with zero crashes. - Nine new runnable examples, one per previously-example-less sub-crate, each a real
cargo run -p <crate> --example <name>-verified walkthrough of that crate’s headline API. - The bcrypt panic is closed, with an ASCII gate applied independently in all three call paths (
bcrypt_verify,parse_bcrypt_string,extract_hash_part) so no code path depends on call order. - A silent-truncation bug in the post-quantum wire format is now a typed error instead.
PqKeyShare::to_wirereturnsResult<Vec<u8>, CryptoError>and rejects an oversized payload instead of wrapping its 2-byte length field.
Technical Deep Dive: parameter-set completion, the fourth negotiation leg, and a fuzz suite that builds
- SLH-DSA completion (
oxicrypto-pq). The pinnedslh-dsa 0.2.0-rc.5dependency already implementedShake192s/Shake192f; OxiCrypto’s ownimpl_slh_dsa_param!macro just hadn’t been pointed at them yet. Wiring them through gives every one of FIPS 205’s twelve(hash family × size × speed)combinations — SHA2/SHAKE, 128/192/256-bit, small/fast — a matchingSlhDsa*type in the crate. negotiate_aead—oxicrypto-aead::tls.TlsCipherSuitecovers the five TLS 1.3 suites, withfrom_iana_name/wire_codefor parsing and serializing the wire identifier, andaead_name_for_suiteas a pure naming helper.negotiate_aeadmapsAes128GcmSha256/Aes256GcmSha384/Chacha20Poly1305Sha256/Aes128CcmSha256to their AEAD implementations, and returnsCryptoError::UnsupportedAlgorithm— not a silently wrong 16-byte-tag substitute — forAes128Ccm8Sha256, whose 8-byte truncated tag this crate doesn’t yet implement. The module mirrorsoxicrypto_mac::negotiate_mac,oxicrypto_sig::negotiate_sig, andoxicrypto_kex::negotiate_kexexactly, so a TLS 1.3 stack now resolves every leg of a cipher suite through the same pattern.- The fuzz suite.
fuzz_sealed_box_open_no_panicandfuzz_key_unwrap_no_panic(oxicrypto-aead) exerciseopen_boxand the RFC 3394aes{128,256}_key_unwrap.fuzz_hmac_truncated_no_panic(oxicrypto-mac) is a direct regression guard for the truncated-HMAC panics fixed back in 0.2.1.fuzz_pq_key_share_from_wire(oxicrypto-pq) round-tripsPqKeyShare::from_wire/decode-re-encode.fuzz_bcrypt_verify_no_panic(oxicrypto-kdf) is a direct regression guard for the bcrypt fix below. Eachfuzz/Cargo.tomlcarries its own[workspace]table — the exact thingoxicrypto-hash’s pre-existing fuzz crate was missing, which is whycargo metadatafrom inside it had been failing with “current package believes it’s in a workspace when it’s not.” - The bcrypt fix and the PQ wire-format hardening.
bcrypt_verify/parse_bcrypt_string/extract_hash_partpreviously validated only the byte length of a hash string before indexing into it as&str(&hash_part[..22]and similar). A multi-byte character straddling that offset panicked with Rust’s “byte index N is not a char boundary” instead of erroring. The fix,ensure_ascii_hash, is justified because bcrypt’s modular-crypt format and base64 alphabet are ASCII-only by definition — so rejecting non-ASCII input outright is correct, not merely defensive. Separately,PqKeyShare::to_wireused to compute its 2-byte wire length field aslen as u16, which wraps silently for a payload over 65535 bytes; it’s nowResult-returning and rejects that case withCryptoError::Encoding. No currently-definedPqGroupproduces a payload anywhere near that size, but the encode helpers accept arbitrary caller-supplied byte slices, so the bound wasn’t guaranteed by the type system.
Getting Started
cargo add oxicrypto
[dependencies]
oxicrypto = "0.3.0"
# Post-quantum primitives (off by default):
oxicrypto = { version = "0.3.0", features = ["pq-preview"] }
Hashing:
use oxicrypto::{blake3, sha256, sha512};
let digest = sha256(b"hello world");
let digest = sha512(b"hello world");
let digest = blake3(b"hello world");
Resolving a TLS 1.3 cipher suite straight to an AEAD — the new part in 0.3.0:
use oxicrypto_aead::{negotiate_aead, TlsCipherSuite};
let aead = negotiate_aead(TlsCipherSuite::Aes256GcmSha384)?;
assert_eq!(aead.name(), "AES-256-GCM");
assert_eq!(aead.key_len(), 32);
assert_eq!(aead.nonce_len(), 12);
assert_eq!(aead.tag_len(), 16);
Every sub-crate now has a runnable, headline-API example:
cargo run -p oxicrypto-aead --example aead_basics
cargo run -p oxicrypto-sig --example sig_basics
cargo run -p oxicrypto-pq --example pq_basics --features pq-preview
What’s New in 0.3.0
- Added:
SlhDsaShake192s/SlhDsaShake192f, completing all 12 FIPS 205 parameter sets; thenegotiate_aeadTLS 1.3 cipher-suite resolver in a newoxicrypto-aead::tlsmodule; four newcargo-fuzzharnesses across AEAD/MAC/PQ/KDF; runnableexamples/for all 9 previously-example-less sub-crates; workspace-rootrustfmt.toml/clippy.toml(MSRV pinned to 1.89 inclippy.toml); aTODO.mdforoxicrypto-cipher, the one crate that was missing one. - Changed (breaking):
PqKeyShare::to_wirenow returnsResult<Vec<u8>, CryptoError>instead ofVec<u8>, rejecting an over-u16::MAX-byte payload instead of silently truncating the wire length field.deny.toml’sringban now carries a scoped exception foroxicrypto-bench’s dev-only comparative benchmarks..gitignore’sfuzz/patterns widened to match the five per-cratefuzz/directories instead of only a nonexistent top-level one.oxicodebumped 0.2.4 → 0.2.6. - Fixed: a broken rustdoc intra-doc link in the new
tlsmodule (referenced a crateoxicrypto-aeaddoesn’t depend on) that brokeRUSTDOCFLAGS="-D warnings" cargo doc; the previously-non-buildableoxicrypto-hashfuzz crate now builds like its four new siblings. - Security: a byte/char-boundary panic in
bcrypt_verify/parse_bcrypt_string/extract_hash_parton non-ASCII input — a panic-DoS on any path verifying a bcrypt hash sourced from outside the process — fixed with an ASCII gate applied independently in all three functions. - 1627 tests pass with
cargo nextest run --workspace(default features), plus 26 passing doctests.
Tips
- If your TLS stack still hand-rolls the AEAD leg of cipher-suite negotiation, replace it with
negotiate_aead. It’s the last of the fournegotiate_*helpers (oxicrypto_mac::negotiate_mac,oxicrypto_sig::negotiate_sig,oxicrypto_kex::negotiate_kex, nowoxicrypto_aead::negotiate_aead) — resolve a whole TLS 1.3 cipher suite through the same pattern on every leg. Remember it returnsErr(CryptoError::UnsupportedAlgorithm)forTLS_AES_128_CCM_8_SHA256; that 8-byte-tag variant isn’t implemented, and the function won’t silently hand you the wrong tag length instead. - Don’t know where to start with a sub-crate’s API? Run its example first. All 14 crates now have one —
cargo run -p oxicrypto-kex --example kex_basicswalks X25519 agreement through HKDF into a session key;oxicrypto-hash’s example additionally builds under--no-default-featuresto demonstrate the alloc-free surface. - If you call
PqKeyShare::to_wiredirectly, update the call site for 0.3.0. It’s the one breaking change this release: the method now returnsResult<Vec<u8>, CryptoError>instead ofVec<u8>. Every currently-definedPqGrouppayload is well under theu16::MAXbound in practice, so the common case is a mechanical?or.expect(...). - If you verify bcrypt hashes sourced from outside your process — a database, a config file, an API payload — upgrade now. Pre-0.3.0, a non-ASCII byte anywhere in the hash string could panic the verifying thread; 0.3.0 rejects it cleanly via
CryptoError, and no call-site change is needed to pick up the fix. - Reach for the newly-completed SLH-DSA matrix if
Shake192s/Shake192fwere the gap blocking you. All 12 FIPS 205 parameter sets are now available behindpq-preview; the 18 slower-s-parameter variants remain#[ignore]-gated in the test suite (they’re slow, not unfinished) — run them explicitly if you need to exercise them locally. - Point
cargo-fuzzat the new harnesses if you’re auditing untrusted-input paths.oxicrypto-aead’sfuzz_sealed_box_open_no_panic/fuzz_key_unwrap_no_panic,oxicrypto-mac’sfuzz_hmac_truncated_no_panic,oxicrypto-pq’sfuzz_pq_key_share_from_wire, andoxicrypto-kdf’sfuzz_bcrypt_verify_no_panicall build now (eachfuzz/crate has its own[workspace]table, socargo +nightly fuzz run <target>works standalone without touching the parent workspace).
This is the foundation
OxiCrypto depends on nothing — it’s the foundation layer the rest of the COOLJAPAN cryptography stack stands on. It is already depended on by:
- OxiTLS — the Pure Rust TLS stack.
- OxiStore — encrypted storage;
oxistore-blob-gcssigns throughoxicrypto-sig, andoxistore-encryptderives keys throughoxicrypto(with an optionaloxicrypto-adapter-pkcs11bridge for HSM-backed keys). - OxiSQL — the Pure Rust SQL layer.
- OxiQuic —
oxiquic-cryptodepends onoxicrypto’spure/stdfeatures for QUIC’s TLS 1.3 handshake and header-protection primitives. - oxify, mielin, and oxirs — application-layer consumers spanning multiple
oxicrypto-*sub-crates. - oxionnx (model signing) and oxirag (content addressing).
Around it sit sibling Pure Rust projects removing their own native dependency from the floor of the ecosystem — OxiArc (compression, replacing zip/flate2/zstd), OxiBLAS, OxiFFT, and OxiZ (the Pure Rust SMT solver).
Repository: https://github.com/cool-japan/oxicrypto
Star the repo if you want a post-quantum signature portfolio with no gaps, a TLS 1.3 negotiation surface with no leg left unresolved, and cryptographic parsers that reject bad input instead of panicking on it.
The era of a “complete” crypto crate quietly missing two parameter sets nobody checked for is over. Pure Rust cryptography is here — complete, fuzzed, and sovereign.
— KitaSan at COOLJAPAN OÜ August 6, 2026