Subject
base16ct is a no_std-capable pure-Rust implementation of Base16 (hexadecimal, RFC 4648) encoding and decoding. It implements lower-case, upper-case, and mixed-case variants using branchless arithmetic that aims to provide portable "best effort" constant-time operation with respect to the data being encoded/decoded (not with respect to message length). The crate is part of the RustCrypto formats workspace and is intended for use in cryptographic contexts where data-dependent timing or cache side-channels matter.
The public API consists of:
lower::{encode, encode_str, encode_string, decode, decode_vec}
upper::{encode, encode_str, encode_string, decode, decode_vec}
mixed::{decode, decode_vec} (decode only, accepts both cases)
HexDisplay<'a>(&'a [u8]) implementing Display, UpperHex, LowerHex
Error (InvalidEncoding, InvalidLength) and decoded_len/encoded_len helpers
The crate has no runtime dependencies and two optional features: alloc (enables _vec/_string APIs) and std (gates std::error::Error impl).
Methodology
The published crate was unpacked and diffed against the upstream Git repository at the commit recorded in .cargo_vcs_info.json. The repository at https://github.com/RustCrypto/formats/tree/master/base16ct was cloned manually because openvet's default VCS resolution treated the deep-tree URL literally.
All six source files (lib.rs, lower.rs, upper.rs, mixed.rs, display.rs, error.rs; ~384 lines total) and the integration test file (tests/lib.rs, 163 lines) were read in full. The benchmark file was inspected. The Cargo.toml/Cargo.toml.orig were compared and the manifest metadata (categories, dependencies, features, build configuration) was reviewed.
The branchless decode_nibble/encode_nibble routines were manually traced for boundary inputs (0x2f, 0x30, 0x39, 0x3a, 0x40, 0x41, 0x46, 0x47, 0x60, 0x61, 0x66, 0x67) in both lower and upper variants to confirm correctness of the sign-mask arithmetic. The four unsafe sites were enumerated and their preconditions checked against the surrounding callers.
Results
The published crate matches the upstream VCS contents: the only differences from vcs/ are Cargo.toml normalisation by cargo (key reordering, quoting, comment header) and the addition of .cargo_vcs_info.json and Cargo.toml.orig. Cargo.toml.orig matches vcs/Cargo.toml byte-for-byte. No source files diverge.
The crate ships no binaries (justifying has-binaries = false), no build.rs, no proc-macro library, no [build-dependencies], and no install-time hooks (justifying has-build-exec = false and has-install-exec = false). It has no runtime dependencies.
The source-code review found that the crate performs no I/O of any kind: no filesystem access, no network access, no environment-variable access, no process execution, no JIT or interpreter, no concurrency primitives, no cryptographic primitives (it is a hex codec, not a crypto algorithm; the constant-time property is what makes it crypto-adjacent). This justifies uses-filesystem, uses-network, uses-environment, uses-exec, uses-jit, uses-interpreter, uses-concurrency, uses-crypto, impl-crypto, impl-interpreter, impl-jit, impl-protocol, impl-datastructure, impl-algorithm, and impl-concurrency, all as false. The crate implements a parser/encoder for the hex format (justifying impl-parser = true).
Four unsafe blocks were found, all of the form from_utf8_unchecked(...) applied to the output of encode. The invariant (encoder produces only ASCII) holds: encode_nibble is called with nibbles in 0..16 and produces bytes only in 0x30-0x39 plus 0x61-0x66 (lower) or 0x30-0x39 plus 0x41-0x46 (upper). The integration tests in tests/lib.rs exercise all four sites via encode_str/encode_string/HexDisplay, justifying has-integration-tests. The crate ships no in-module #[cfg(test)] blocks, justifying has-unit-tests. This justifies uses-unsafe = true, unsafe-safe = true, unsafe-minimal = true, unsafe-tested = true. The blocks lack // SAFETY: comments, justifying unsafe-documented = false (finding FINDING-1).
The branchless decode_nibble was manually verified to reject all bytes outside the valid hex ranges at both boundaries (0x2f/0x3a for digits, 0x40/0x47 for upper, 0x60/0x67 for lower); errors propagate via 0xFFFF sentinel through decode_inner, which OR-accumulates into err and returns InvalidEncoding at the end without short-circuiting. The implementation matches RFC 4648 section 8 (Base 16 Encoding), justifying parser-impl-safe and parser-impl-correct.
The audit produced two low-severity quality findings:
- FINDING-1: missing
// SAFETY: comments on the four unsafe blocks (the invariant holds; only documentation is absent).
- FINDING-2: limited test coverage for a constant-time crypto-adjacent parser: six hand-written test vectors and one fixed-byte length-sweep, with no fuzz target, no property tests, no exhaustive byte-rejection coverage, and no externally-sourced RFC 4648 vectors. Justifies has-fuzz-tests = false, has-property-tests = false, and parser-impl-tested = false.
Conclusion
The crate is small, focused, dependency-free, and contains no malicious or unexpected behaviour (justifying is-benign = true). The hex codec is correctly implemented with a sound branchless construction, and the four unsafe sites are minimal uses of from_utf8_unchecked whose invariants demonstrably hold. The findings are both low-severity quality issues: missing safety comments and a testing gap. Neither impacts the soundness or correctness of the current code. The crate is suitable for production use in the constant-time-encoding role for which RustCrypto consumes it.