krypteia-tessera — Shared Hash Primitives for the krypteia workspace
Pure-Rust implementations of every hash, XOF, keyed-hash, key-derivation
function and checksum the workspace needs — SHA-2, SHA-3 / Keccak, the
SHAKE / cSHAKE XOFs, MGF1, the legacy/alt family (SHA-1, RIPEMD-160,
BLAKE2b/2s, BLAKE3), the SP 800-185 keyed constructions (KMAC, TupleHash,
ParallelHash), HMAC, the KDF family (HKDF, PBKDF2, SP 800-108, scrypt,
Argon2), and the non-cryptographic CRC-16/32 checksums. tessera is the
single home for every hash in the krypteia
workspace: quantica (post-quantum) and arcana (classical) both build
on it rather than carrying their own copies of Keccak or SHA-2.
A tessera was the Roman token of identity and proof — a fitting name for a crate whose whole job is fingerprints and derivation.
Design rules
The crate inherits the krypteia workspace design rules:
Pure Rust, zero external crates — only
coreandalloc. The crate is#![no_std];allocis used only where an output length is not known at the call site (hkdf::extract, the SP 800-185 helpers). No workspace dependency at all —tesserasits at the bottom of the dependency graph so that botharcanaandquanticacan depend on it without a cycle.Embedded-friendly — fixed-output APIs write into caller-provided slices (
finalize(&mut [u8]),pbkdf2(…, dk: &mut [u8])), so the hot paths allocate nothing. Target devices: secure elements, STM32 (Cortex-M0/M4/M33), RISC-V (ESP32-C3, …).Constant-timeness is scoped, not blanket — the hash compression functions process public data, so their (data-oblivious) structure is CT by construction but CT is not a stated goal for them. The secret-handling primitives (HMAC, HKDF, PBKDF2, SP 800-108) are data-oblivious in the key. The memory-hard functions (scrypt, Argon2d/id) have known data-dependent memory access and are not constant-time — see Side-channel posture for the exact leaking paths. CRC is non-cryptographic and non-CT by design.
Validated — every algorithm is tested against pinned FIPS / NIST SP / RFC reference vectors plus the official NIST CAVP (SHS) and ACVP (hashes) corpora. See Test validation.
C / JS-exposable — the companion crates
tessera_ffi(C ABI) andtessera_wasm(WebAssembly) mirror the public surface.
Algorithms
Hash functions (impl Digest)
Algorithm |
Output |
Module |
Standard |
|---|---|---|---|
SHA-1 |
160 b |
|
FIPS 180-4 |
SHA-224 |
224 b |
|
FIPS 180-4 |
SHA-256 |
256 b |
|
FIPS 180-4 |
SHA-384 |
384 b |
|
FIPS 180-4 |
SHA-512 |
512 b |
|
FIPS 180-4 |
SHA-512/224 |
224 b |
|
FIPS 180-4 |
SHA-512/256 |
256 b |
|
FIPS 180-4 |
SHA3-224 |
224 b |
|
FIPS 202 |
SHA3-256 |
256 b |
|
FIPS 202 |
SHA3-384 |
384 b |
|
FIPS 202 |
SHA3-512 |
512 b |
|
FIPS 202 |
RIPEMD-160 |
160 b |
|
ISO/IEC 10118-3 |
BLAKE2b |
1–64 B |
|
RFC 7693 |
BLAKE2s |
1–32 B |
|
RFC 7693 |
BLAKE3 |
32 B / XOF |
|
BLAKE2b/2s expose a variable-length constructor with_output_len(n); the
default Digest::OUTPUT_LEN is the full 64 B / 32 B width.
Extendable-output functions (impl Xof)
Algorithm |
Module |
Standard |
|---|---|---|
SHAKE128 |
|
FIPS 202 |
SHAKE256 |
|
FIPS 202 |
cSHAKE128 |
|
NIST SP 800-185 |
cSHAKE256 |
|
NIST SP 800-185 |
MGF1 |
|
RFC 8017 §B.2.1 |
BLAKE3 (XOF mode) |
|
mgf1 is generic over any Digest, mirroring how RSA-OAEP / RSA-PSS in
arcana consume it. sha3::KeccakState (rate + domain suffix, absorb /
squeeze) is public so quantica can drive Keccak on its hot path and
arcana can build cSHAKE-keyed constructions on top.
Keyed hashes / MACs
Algorithm |
Module |
Standard |
|---|---|---|
HMAC (over any |
|
RFC 2104, FIPS 198-1 |
KMAC128 / KMAC256 (+XOF) |
|
NIST SP 800-185 |
TupleHash128 / 256 (+XOF) |
|
NIST SP 800-185 |
ParallelHash128 / 256 (+XOF) |
|
NIST SP 800-185 |
hmac::<H>(key, data, out) instantiates HMAC over any hash implementing
Digest, so HMAC-SHA-256/384/512, HMAC-SHA3-*, HMAC-SHA-1 and
HMAC-RIPEMD-160 all come from one generic function. hmac_multi takes a
slice of message parts to avoid an intermediate concatenation.
Key-derivation functions
Algorithm |
Module |
Standard |
|---|---|---|
HKDF |
|
RFC 5869 |
PBKDF2 |
|
RFC 2898 / SP 800-132 |
SP 800-108 KBKDF |
|
NIST SP 800-108r1 |
scrypt |
|
RFC 7914 |
Argon2 d / i / id |
|
RFC 9106 |
HKDF, PBKDF2 and SP 800-108 are HMAC-derived and generic over the hash.
scrypt carries an inline Salsa20/8 core (tessera cannot depend on
arcana where the full Salsa20 lives, so the 8-round core is reimplemented
locally — RFC 7914 §3). Argon2 is built on the crate’s own BLAKE2b and
covers all three variants (Variant::{D, I, Id}), with the optional secret
(keyed) and associated-data inputs of RFC 9106 §3.1.
Checksums (non-cryptographic — deliberately not Digest)
Family |
Named variants (under |
Reference |
|---|---|---|
CRC-32 |
|
RevEng / Rocksoft model |
CRC-16 |
|
RevEng / Rocksoft model |
Crc16 / Crc32 are streaming engines over a 'static algorithm
descriptor built at compile time (const fn), returning the native
u16 / u32. A user can define an arbitrary variant with
Crc32Algorithm::new(poly, init, refin, refout, xorout, check). CRC types
do not implement Digest or Xof — this is a deliberate misuse
barrier: hmac::<Crc32> and mgf1::<Crc32> will not compile, so a linear
checksum can never be mistaken for a MAC. See
Side-channel posture.
Traits
Two small traits keep the generic code (hmac, mgf1, hkdf, pbkdf2,
the FIPS 204/205 encoders in quantica) written once:
pub trait Digest: Sized {
const OUTPUT_LEN: usize; // digest length, octets
const BLOCK_LEN: usize; // Merkle–Damgård / sponge rate (HMAC needs it)
fn new() -> Self;
fn update(&mut self, data: &[u8]);
fn finalize(self, out: &mut [u8]); // writes OUTPUT_LEN octets
fn digest(data: &[u8], out: &mut [u8]) { /* … */ } // one-shot default
}
pub trait Xof: Sized {
const BLOCK_LEN: usize; // sponge rate, octets
fn new() -> Self;
fn update(&mut self, data: &[u8]);
fn squeeze(&mut self, out: &mut [u8]); // repeatable
}
Output sizes are caller-provided slices to keep the trait no_std and
allocation-free.
Cargo features
tessera has no Cargo features — it is #![no_std] + alloc,
zero-dependency, in every configuration. There is no std gate and no
feature-gated SCA layer: the crate is a leaf, and its consumers
(arcana, quantica) own the feature surface.
[dependencies]
tessera = { package = "krypteia-tessera", path = "../tessera" }
Quick start
Hashing (SHA-256)
use tessera::{Digest, Sha256};
let mut out = [0u8; 32];
Sha256::digest(b"hello, tessera", &mut out);
assert_eq!(out.len(), 32);
XOF (SHAKE128, arbitrary output)
use tessera::{Xof, Shake128};
let mut xof = Shake128::new();
xof.update(b"squeeze me");
let mut out = [0u8; 100];
xof.squeeze(&mut out); // may be called again for more
HMAC-SHA-256
use tessera::{hmac, Sha256};
let mut tag = [0u8; 32];
hmac::<Sha256>(b"secret key", b"message", &mut tag);
HKDF-SHA-256 (extract-and-expand)
use tessera::{hkdf, Sha256};
let prk = hkdf::extract::<Sha256>(b"salt", b"input keying material");
let mut okm = [0u8; 42];
hkdf::expand::<Sha256>(&prk, b"context info", &mut okm).unwrap();
Password hashing (Argon2id)
use tessera::argon2::{argon2, Variant};
let mut tag = [0u8; 32];
argon2(
Variant::Id,
b"password", b"saltsaltsalt",
&[], &[], // no secret, no associated data
64, // memory: 64 KiB
3, // t_cost: 3 passes
1, // lanes / parallelism
&mut tag,
).unwrap();
CRC-32 (ISO-HDLC / zlib)
use tessera::{Crc32, crc::CRC32_ISO_HDLC};
let checksum = Crc32::checksum(&CRC32_ISO_HDLC, b"123456789");
assert_eq!(checksum, 0xCBF4_3926); // the RevEng `check` value
Side-channel posture
Scope.
tesseradeals mostly in public data (message hashing, checksums), so most of it has no secret to leak. The exceptions below are called out precisely, per the workspace rule that a non-constant-time secret-dependent path is named explicitly, never shipped silently.
Data-oblivious (no secret-dependent branch or memory index)
All hash compression functions (SHA-1/2/3, RIPEMD-160, BLAKE2, BLAKE3) and the SHAKE / cSHAKE / MGF1 XOFs — their control flow and memory access depend only on the input length, not on input values.
HMAC, HKDF, SP 800-108 (counter/feedback/double-pipeline), KMAC — the key is absorbed through the same data-oblivious compression function; there is no key-value-dependent branch. Tag/PRK comparison, when a caller needs it, must use
silentops::ct_eq(tessera does not compare secrets itself).
Known non-constant-time paths (by algorithm design)
Path |
Leak |
Why it is inherent |
|---|---|---|
|
Memory index |
RFC 7914 §6: memory-hardness requires data-dependent addressing. |
|
Reference-block index is data-dependent → cache-timing. |
RFC 9106 §4: Argon2d/id trade SCA resistance for GPU-cracking resistance. |
|
The password is the HMAC key; a key longer than the block is hashed first, so the number of compression calls reveals the password length class (≤ block vs > block). |
RFC 2104 key-length normalisation. |
|
Table-indexed by data bytes; non-cryptographic, no secret expected. |
CRC is an error-detection code, not a MAC. |
Mitigation guidance. For side-channel-sensitive password hashing prefer
Argon2i (Variant::I), which uses data-independent addressing
throughout and is the RFC 9106 choice when timing/cache leakage is in the
threat model. scrypt has no data-independent mode — its memory-hardness is
built on data-dependent access and cannot be made cache-oblivious without
losing the property. These are documented in each module’s //! header.
Not defended
CRC is a linear, trivially forgeable checksum: it detects accidental
corruption of public data, and is neither a MAC nor an integrity-against-
tampering primitive. The Digest omission is the enforcement of that
boundary at the type level.
Test validation
The inline src/*.rs tests are minimal smoke KATs; the bulk of the
conformance evidence lives in the integration tests/ tree, driven by
official external corpora parsed by the dependency-free readers under
tests/common/.
Suite ( |
Coverage |
Vector source |
|---|---|---|
|
SHA-1, SHA-2 family (short/long/Monte-Carlo) |
NIST CAVP SHS |
|
SHA-3, SHAKE, SHA-2 |
NIST ACVP JSON |
|
cSHAKE, KMAC, TupleHash, ParallelHash (+XOF) |
NIST SP 800-185 sample 1/2/4 |
|
HMAC-SHA-1/2/3 |
RFC 4231, RFC 2202 |
|
BLAKE3 hash / keyed / derive_key |
upstream BLAKE3 test vectors |
|
RIPEMD-160, BLAKE2b/2s |
ISO 10118-3, RFC 7693 §B |
|
11 CRC-16/32 variants — each self-checked against its |
RevEng catalogue + zlib/binascii cross-check |
|
HKDF (RFC 5869 A.1-A.3), PBKDF2 (RFC 6070), SP 800-108 (counter gold), scrypt (RFC 7914 §12), Argon2 d/i/id (RFC 9106 §5) |
RFC vectors + independent Python cross-checks |
Argon2’s three RFC 9106 §5 reference tags are pinned inline in
argon2.rs. The SP 800-108 counter mode is gold-validated against
the cryptography library; feedback / double-pipeline carry a
[TO CONFIRM] pending NIST CAVP KDF-108 wiring.
cargo test -p krypteia-tessera # lib smoke + all integration suites
cargo build -p krypteia-tessera --no-default-features # no_std build (alloc only)
cargo clippy -p krypteia-tessera --all-targets -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc -p krypteia-tessera --no-deps
Bindings
Companion |
Kind |
Status |
|---|---|---|
|
C ABI ( |
v0.2 — mirrors the digest / XOF / HMAC / SP 800-185 surface. |
|
WebAssembly (wasm-bindgen) |
v0.2 — JS-facing digests, XOFs and keyed hashes. |
Hashes need no CSPRNG, so tessera_wasm — unlike arcana_wasm /
quantica_wasm — pulls in no web-sys::Crypto random source.
Module map
tessera/src/
lib.rs Crate root: re-exports + Digest / Xof traits
traits.rs Digest, Xof
sha1.rs SHA-1 (FIPS 180-4) — legacy
sha2.rs SHA-224/256/384/512/512-224/512-256 (FIPS 180-4)
sha3.rs Keccak-f, SHA3-224/256/384/512, SHAKE128/256, cSHAKE128/256, KeccakState
ripemd160.rs RIPEMD-160 (ISO/IEC 10118-3) — legacy
blake2.rs BLAKE2b, BLAKE2s (RFC 7693)
blake3.rs BLAKE3: hash / keyed_hash / derive_key (fixed + XOF)
mgf1.rs MGF1 XOF over any Digest (RFC 8017 §B.2.1)
hmac.rs HMAC over any Digest (RFC 2104, FIPS 198-1)
sp800_185.rs KMAC128/256, TupleHash128/256, ParallelHash128/256 (+XOF)
hkdf.rs HKDF extract / expand / derive (RFC 5869)
pbkdf2.rs PBKDF2 (RFC 2898 / SP 800-132)
sp800_108.rs KBKDF counter / feedback / double-pipeline (SP 800-108r1)
scrypt.rs scrypt (RFC 7914) — inline Salsa20/8 core
argon2.rs Argon2 d/i/id (RFC 9106) — BLAKE2b core, secret + AD
crc.rs CRC-16/32 (RevEng catalogue) — non-cryptographic, not a Digest
References
FIPS 180-4 — SHA-1, SHA-2 family
FIPS 202 — SHA-3, SHAKE
NIST SP 800-185 — cSHAKE, KMAC, TupleHash, ParallelHash
FIPS 198-1 — HMAC
RFC 2104 — HMAC
RFC 7693 — BLAKE2
BLAKE3 spec — BLAKE3
ISO/IEC 10118-3 — RIPEMD-160
RFC 5869 — HKDF
RFC 2898 / NIST SP 800-132 — PBKDF2
NIST SP 800-108r1 — KBKDF (counter / feedback / double-pipeline)
RFC 7914 — scrypt
RFC 9106 — Argon2
RFC 8017 — PKCS#1 (MGF1)
RevEng CRC catalogue — CRC parameter sets
NIST CAVP — SHS conformance vectors
NIST ACVP-Server — modern conformance vectors
License
Apache-2.0.