Public API Reference
Side-channel claim limit. Nothing on this page claims constant-time behaviour or side-channel resistance for any target: the side-channel harness covers nothing (finding A48) and no measurement exists. The Rust and C cores are designed for constant time and unmeasured; managed runtimes (JIT, GC, bounds checks) make no timing claim. Where this page names constant-time or side-channel techniques, in prose or in code, they are design intent or recommendations, not measured properties.
Release line: 1.0.0-rc.7 (release candidate; no package is published on a public registry yet)
Licence: Apache-2.0
Source of truth: documents/public-release/api-contract.md (contract version 1.0.0-draft.5) and the release catalog, documents/public-release/catalog.json, in the source repository. This page summarises them; where they and this page differ, they win.
What the public API is
The release catalog lists 49 profiles in 16 algorithm families, 45 of them mandatory, across 10 targets (Rust, C, Swift, Go, Python, TypeScript, Kotlin, Java, C#, WASM). A profile is one algorithm, one parameter set, one wire encoding and one operation set, named by its catalog identifier (ml-kem-768, falcon-512.r3-padded, …).
The public entry surface for the post-quantum profiles is a facade over the validated algorithm implementations. At this revision the facade admits 37 profiles:
| family | profiles | kind | operations |
|---|---|---|---|
| Falcon (Round 3) | falcon-512.r3-compressed, falcon-512.r3-padded, falcon-1024.r3-compressed, falcon-1024.r3-padded |
signature | keygen, sign, verify |
| ML-KEM (FIPS 203) | ml-kem-512, ml-kem-768, ml-kem-1024 |
KEM | keygen, encapsulate, decapsulate |
| ML-DSA (FIPS 204) | ml-dsa-44, ml-dsa-65, ml-dsa-87 |
signature | keygen, sign, verify, with a context string |
| SLH-DSA (FIPS 205) | slh-dsa-sha2-{128,192,256}{s,f}, slh-dsa-shake-{128,192,256}{s,f} (12) |
signature | keygen, sign, verify, with a context string |
| SMAUG-T (KpqC) | smaug-t.v1_2_0.mode1, .mode3, .mode5 |
KEM | keygen, encapsulate, decapsulate |
| HAETAE (KpqC) | haetae-2, haetae-3, haetae-5 |
signature | keygen, sign, verify, with a context string |
| AIMer v3 (KpqC) | aimer-{128,192,256}{s,f} (6) |
signature | keygen, sign, verify, with a context string |
| NTRU+ (KpqC) | ntru-plus-768, ntru-plus-864, ntru-plus-1152 |
KEM | keygen, encapsulate, decapsulate (decapsulation can fail) |
The braces are shorthand in this table only; each identifier is its own profile with its own module and types.
Which targets carry a facade:
| target | facade | per-algorithm API |
|---|---|---|
| Rust | metamui-crypto crate (metamui_crypto::<profile module>) |
the metamui-<algorithm> crates, no stability promise from the contract |
| TypeScript / WASM | @metamui/crypto |
@metamui/<algorithm> |
| Python | metamui_crypto.facade |
the metamui_crypto.<module> modules |
| Swift | MetaMUICrypto umbrella product (since 2026-10-08) |
one SwiftPM product per module (MetaMUIFalcon, MetaMUIMLKem, …) |
| C | none; per-algorithm API only is the decision for 1.0 | CMake package metamui-crypto-c, targets metamui::<target>, headers under include/metamui/ |
| Go, Java, Kotlin, C# | none yet (planned after 1.0) | per-algorithm modules: mlkem.New(mlkem.MLKEM768), new MLKem768(), MLKem768(), new MLKEM768() |
The classical profiles (blake3, sha3-256, shake-256, keccak-256, aes-256-gcm, hkdf-sha256, ed25519, sr25519) are outside the facade in every target and are reached through each target’s per-algorithm modules; see the language pages linked at the end.
Rules every facade is held to
- Explicit, exact, non-substituting selection. There is no default profile and no “strongest available”. Identifiers resolve by exact, case-sensitive match;
falcon512,FALCON-512.R3-PADDEDand a family name such asml-kemare refused with an unsupported profile error. Nothing is substituted. - Validated types at every byte boundary. A constructor checks length, header and, where the standard defines one, structure (the FIPS 203 §7.2 modulus check on an encapsulation key, the §7.3 hash check on a decapsulation key); a value that exists is well-formed for its profile. Secret types are zeroized on drop and print no material.
- No boolean verify. Verification returns success or an invalid signature error that carries no reason.
- Context strings are required arguments. ML-DSA, SLH-DSA, HAETAE and AIMer sign and verify under a context of at most 255 bytes; the caller that wants none passes the empty string. A signature made under one context does not verify under another.
- Two rejection behaviours. ML-KEM and SMAUG-T reject implicitly: a well-sized but invalid ciphertext decapsulates to a pseudorandom secret, never an error. NTRU+ rejects explicitly with a decapsulation failed error and returns no secret. The two are not interchangeable.
- Raw standard encodings only. No length prefix, version tag or envelope is produced or parsed. For example ML-KEM-768: encapsulation key 1184 bytes, decapsulation key 2400, ciphertext 1088, shared secret 32; Falcon-512: public key 897, secret key 1281, padded signature exactly 666, compressed signature at most 752.
- Operating-system randomness. The easy entry points draw from the OS; Rust and the bindings that have a CSPRNG abstraction also take a caller RNG. No seeded, deterministic or known-answer entry point is public facade API.
The per-algorithm surfaces of C, Swift products, Go, Java/Kotlin and C# do not meet every rule above (typed selectors instead of profile strings, boolean verify, some seeded entry points exported); the contract records each deviation per target in its §12.4 and tracks them as follow-up work.
Error model
Rust (metamui_crypto::Error) and Python (metamui_crypto.facade) spell the same set:
| Rust variant | Python exception | when |
|---|---|---|
UnsupportedProfile { requested } |
UnsupportedProfileError |
identifier not admitted, or a family name |
InvalidLength { what, expected, actual } |
InvalidLengthError |
a key, signature, ciphertext or context of the wrong length |
InvalidHeader { what, expected, actual } |
InvalidHeaderError |
wrong Falcon header byte |
MalformedKey { what } |
MalformedKeyError |
right length and header, but the bytes do not decode to a key |
InvalidSignature |
InvalidSignatureError |
verification failed; no reason |
SigningFailed { reason }, KeyGenerationFailed { reason }, EncapsulationFailed { reason } |
SigningFailedError, KeyGenerationFailedError, EncapsulationFailedError |
backend failure |
EntropyUnavailable |
EntropyUnavailableError |
the OS randomness source refused a request |
DecapsulationFailed |
DecapsulationFailedError |
NTRU+ explicit rejection only |
Executed examples
Both blocks below were run on 2026-10-16 against this source tree at the 1.0.0-rc.7 revision (Rust: a consumer crate with a path dependency on metamui-crypto-rust/metamui-crypto, cargo run --release; Python: pip install metamui-crypto-python/ into a fresh Python 3.12 virtual environment, then python -I on the block). The runs are recorded in the source repository’s documents/public-release/evidence/229-documentation.md (2026-10-16 section).
Rust
use metamui_crypto::{falcon512, mldsa65, mlkem768, Profile};
fn main() -> Result<(), metamui_crypto::Error> {
let mut rng = rand::rngs::OsRng;
let kp = falcon512::generate_keypair(&mut rng)?;
let sig = falcon512::sign_padded(&kp.secret_key, b"hello", &mut rng)?;
falcon512::verify_padded(&kp.public_key, b"hello", &sig)?;
let kem = mlkem768::generate_keypair(&mut rng)?;
let (ct, ss) = mlkem768::encapsulate(&kem.encapsulation_key, &mut rng)?;
assert_eq!(mlkem768::decapsulate(&kem.decapsulation_key, &ct)?, ss);
let dsa = mldsa65::generate_keypair(&mut rng)?;
let sig = mldsa65::sign(&dsa.secret_key, b"hello", b"my-protocol/v1", &mut rng)?;
mldsa65::verify(&dsa.public_key, b"hello", b"my-protocol/v1", &sig)?;
assert!(mldsa65::verify(&dsa.public_key, b"hello", b"", &sig).is_err()); // another context
assert!(Profile::parse("haetae").is_err()); // a family is not a profile
Ok(())
}
Python
from metamui_crypto.facade import profile
sig = profile("ml-dsa-65")
keys = sig.generate_keypair()
signature = sig.sign(keys.secret_key, b"message", context=b"my-app")
sig.verify(keys.public_key, b"message", signature, context=b"my-app") # raises InvalidSignatureError on failure
kem = profile("ml-kem-768")
ek, dk = kem.generate_keypair()
ct, ss = kem.encapsulate(ek)
assert kem.decapsulate(dk, ct) == ss
The other languages’ examples are on the Quick Start page; each of those was run against a locally built candidate package on 2026-10-09.
What is not part of the public API
- The algorithm crates and modules behind the facade (other parameter sets, batch APIs, KAT harnesses): available as separate packages with no stability promise from the contract.
- The deferred families (HQC, FrodoKEM, Classic McEliece, NTRU Prime, LMS/HSS, XMSS/XMSS^MT): implemented in the tree, not catalogued, not packaged.
- Any “authenticated encryption” built from a KEM: the facade labels no KEM output as authenticated and defines no key derivation from a shared secret.
- Claims the evidence does not back: no independent audit, no CMVP or CAVP certificate, no performance comparison with another library, and no constant-time or side-channel property (A48).
Per-language pages
Rust · Python · TypeScript · WASM · Swift · C · Go · Java · Kotlin · C#
Threat-model pages per algorithm: Threat Models.