reallyme-crypto
ReallyMe Crypto is the Rust facade for a cross-platform cryptography workspace spanning Rust, Swift, Kotlin, Android, and TypeScript. It exposes typed operation owners, explicit provider routing, and the package surfaces used by the native and WASM adapters.
The protobuf schema is the source of truth for executable structured requests,
responses, algorithm identifiers, and wire errors. Generated bindings feed a
single Rust operation boundary. provider_manifest.json fixes the provider
selected for each SDK lane, and positive and negative vectors prove the byte
and failure contract. Missing providers and unsupported algorithms fail closed.
Why
Modern cryptography APIs differ across platforms. Algorithms are exposed differently, key formats vary, providers have different capabilities, and error behavior is inconsistent.
ReallyMe Crypto makes platform differences explicit while preserving consistent algorithm identifiers, encodings, errors, and verification semantics for every supported route. Provider selection is deterministic and unavailable routes return typed errors instead of switching implementations.
Packages
| Language | Package | Notes |
|---|---|---|
| Rust | reallyme-crypto |
Umbrella crate for cryptographic APIs. |
| Swift | ReallyMeCrypto |
Swift Package at the repository root, with native Apple providers and Rust C ABI routes where needed. |
| Kotlin/JVM | me.really:crypto |
JVM package with explicit JCA/JCE, BouncyCastle, and Rust-backed routes. |
| Android | me.really:crypto-android |
Android AAR with jniLibs Rust provider packaging and the published me.really:codec-android dependency. |
| TypeScript | @reallyme/crypto |
npm package for Node, browsers, and WASM-backed primitives. |
| Protobuf | reallyme/crypto/v1/crypto.proto |
Canonical structured operation, algorithm identifier, and typed wire-error contract. |
General-purpose encoding, serialization, and multiformat codec APIs live in
github.com/reallyme/codec.
Supported Algorithms
| Category | Algorithms |
|---|---|
| AEAD and key wrap | AES-128/192/256-GCM, AES-256-GCM-SIV, AES-128/192/256-KW, ChaCha20-Poly1305, XChaCha20-Poly1305 |
| Hash, MAC, and KDF | SHA-2, SHA-3, HMAC-SHA-256/384/512, HKDF-SHA256/384, KMAC256 KDF, JWA Concat KDF (ECDH-ES), PBKDF2-HMAC-SHA-256/512, Argon2id |
| Signatures | Ed25519, ECDSA P-256/P-384/P-521, secp256k1 ECDSA, BIP-340 Schnorr, RSA verification, ML-DSA-44/65/87, SLH-DSA-SHA2-128s |
| Key agreement and KEM | X25519, P-256/P-384/P-521 ECDH, ML-KEM-512/768/1024, X-Wing-768 |
| Protocols | HPKE |
| Key and wire envelopes | JWK and public-key multikey bindings used by the crypto facades |
X-Wing-768 follows the IETF CFRG Internet-Draft
draft-connolly-cfrg-xwing-kem,
which defines a hybrid KEM built from X25519 and ML-KEM-768.
The ML-KEM and X-Wing deterministic key-derivation and encapsulation helpers are expert conformance APIs. They consume caller-supplied seed or encapsulation randomness and therefore must not replace randomized key generation or encapsulation in production protocols. Their public contract is gated by the same committed known-answer vectors used across supported SDK lanes.
Availability varies by SDK lane. The exact provider and support map lives in PROVIDER_POLICY.md. For each language lane, an algorithm is either handled by its declared provider or rejected with a typed unsupported-algorithm error.
HPKE Profiles
The cross-platform SDK facade exposes two RFC 9180 Base-mode profiles:
| Profile | KEM | KDF | AEAD |
|---|---|---|---|
DHKEM-P256-HKDF-SHA256-HKDF-SHA256-AES-256-GCM |
DHKEM(P-256, HKDF-SHA256) | HKDF-SHA256 | AES-256-GCM |
DHKEM-X25519-HKDF-SHA256-HKDF-SHA256-CHACHA20-POLY1305 |
DHKEM(X25519, HKDF-SHA256) | HKDF-SHA256 | ChaCha20-Poly1305 |
The Rust native operation contract also supports reviewed classical, post-quantum, and hybrid HPKE components. See the protobuf contract for the executable component set and operation-level constraints.
For OpenMLS adapters, the Rust HPKE API additionally exposes suite-generic PSK
sender and receiver contexts, typed PSK references, arbitrary-length IKM key
derivation through the selected KEM's DeriveKeyPair construction, and exact
aliases for the MLS 192/256-bit ML-KEM-1024 and MLKEM1024-P384 draft profiles.
Live contexts are deliberately non-exportable and remain outside serialized
SDK transports. Deterministic Base seal and sender export have operation-layer
entry points behind test-vectors; caller-controlled randomness is never part
of the production protobuf contract. The operation facade requires at least 32
bytes of high-entropy IKM; the explicit raw HPKE alias retains the KEM-defined
non-empty input contract.
The root reallyme_crypto::hpke facade makes its error boundary explicit.
Established unsuffixed functions and their *_operation aliases return the
workspace-wide OperationError; matching *_raw aliases return HpkeError
directly for protocol adapters that need HPKE-specific failure handling. Raw
split sender outputs use the RawHpkePskSenderSetupOutput and
RawHpkePskSenderContext names so traffic-state ownership is unambiguous.
Every provider route must implement identical input validation and normalization, output encodings, typed failure semantics, and edge-case behavior. Security-sensitive composition, canonical serialization, deterministic signatures, post-quantum primitives, memory-hard KDFs, and provider-ambiguous algorithms default to the ReallyMe Rust implementation through FFI, JNI, or WASM unless a native route is explicitly proven equivalent.
RSA support is intentionally verification-only for historical X.509 and eMRTD PKI interoperability. The package does not generate RSA keys, sign with RSA private keys, or provide RSA encryption/decryption APIs.
Install
Rust
The Rust crates require Rust 1.96.0 or newer. That MSRV is intentional:
ReallyMe Crypto tracks current stable Rust so the public packages can use the
compiler, dependency, lint, and target support expected by the conformance wall.
When default features are disabled, enable one backend lane and each algorithm surface your crate calls:
= { = "0.3.1", = false, = [
"native",
"ed25519",
"p256",
"secp256k1",
"sha2",
] }
Messaging-focused consumers can use the narrow primitive bundle instead of the default feature set:
= { = "0.3.1", = false, = [
"native",
"messaging-primitives",
] }
messaging-primitives enables only ChaCha20-Poly1305/XChaCha20-Poly1305,
HKDF, HMAC, ML-KEM-768, SHA-2, and X25519. The ML-KEM-768 and X25519 algorithm
features require the typed router, so this bundle also enables dispatch; it
does not enable signer.
Dispatch and signer surfaces are feature-gated by algorithm, so enabling the router does not pull in unrelated primitives unless the matching algorithm feature is also selected.
The native and wasm features select the Rust backend lane. They do not, by
themselves, enable every primitive. Algorithm features such as ed25519,
p256, or sha2 enable the root modules and re-exports. This keeps
no-default consumers from pulling unused cryptography while still forwarding
the selected backend into every enabled primitive crate. The wasm lane is for
wasm32 builds; host builds should use native.
Some Rust helper APIs are intentionally lane-scoped. P-256 raw scalar import is
available in both native and wasm lanes through
p256::generate_p256_keypair_from_secret_key; it validates an existing private
scalar and is not random key generation. P-384 and P-521 ECDH are native Rust
APIs today; the Swift, Kotlin, and TypeScript package facades expose their own
provider-backed P-384/P-521 ECDH surfaces.
The Swift package also includes a P-256 ECDH Secure Enclave / Keychain API for applications that need non-exportable private-key residency, such as JOSE/JWE decryption with platform-held keys. That API uses explicit handles and is separate from raw private-key bytes.
Swift
.package(
url: "https://github.com/reallyme/crypto",
from: "0.3.1"
)
.product(name: "ReallyMeCrypto", package: "crypto")
Kotlin
dependencies {
implementation("me.really:crypto:0.3.1")
}
TypeScript
For production deployments, pin exact package versions, release tags, or Git revisions so cryptographic behavior and conformance vectors remain identical across all language lanes.
Quick Start
Rust:
// This example requires the `ed25519` feature.
#
#
#
#
BIP-340 uses an x-only secp256k1 public key and requires callers to provide a 32-byte message representative and 32 bytes of auxiliary randomness explicitly:
// This example requires the `secp256k1` feature.
#
#
#
#
Hashing is owned by the semantic operation layer. Adapters should call this surface instead of selecting a primitive independently:
// This example requires the `sha2` feature.
#
#
#
#
HMAC authentication and fail-closed verification share the same semantic operation owner across Rust, structured protobuf, and C ABI adapters:
// This example requires the `hmac` feature.
#
#
#
#
Authenticated encryption uses the same operation owner for algorithm selection, typed failures, and zeroizing recovered plaintext:
// This example requires the `aes` feature.
#
#
#
#
MLS and HPKE derive nonces from their protocol key schedules. The focused AES-256-GCM facade therefore accepts an explicit typed nonce and deliberately does not offer a random-nonce overload:
// This example requires the `aes` feature.
#
#
#
#
The HPKE facade exposes explicit registry identifiers and derives its nonce internally; seal/open requests have no caller-supplied nonce field:
// This example requires the `hpke` and `native` features.
#
#
#
#
AES-KW uses the operation owner for suite selection and returns unwrapped key material in a zeroizing owner:
// This example requires the `aes-kw` feature.
#
#
#
#
Swift:
import ReallyMeCrypto
let digest = try ReallyMeCrypto.hash(.sha2_256, Array("abc".utf8))
Kotlin:
import me.really.crypto.ReallyMeCrypto
import me.really.crypto.ReallyMeHashAlgorithm
val digest = ReallyMeCrypto.hash(ReallyMeHashAlgorithm.SHA2_256, "abc".toByteArray())
TypeScript:
import { ReallyMeCrypto } from "@reallyme/crypto";
const digest = ReallyMeCrypto.hash("SHA2-256", new TextEncoder().encode("abc"));
Signature verification fails closed: an invalid signature returns an error rather than a boolean that can be accidentally ignored.
Protobuf
The canonical structured wire contract lives at
crates/proto/proto/reallyme/crypto/v1/crypto.proto.
Service, application, and storage protos can import it when they need the
structured operation boundary, stable algorithm identifiers, or typed errors.
ReallyMe Crypto uses raw bytes for single primitive outputs, protobuf bytes for fixed multi-field boundary results, and strict proto-JSON for Connect JSON, CLI, browser-adapter, and conformance boundaries that require JSON. Proto-JSON is available for operation requests, but it is not a casual JSON crypto facade and is not the preferred representation for secret-bearing payloads. JSON convenience shapes remain limited to public metadata such as JWK/JWKS.
For example, a JSON-only client can express a SHA2-256 hash request as strict proto-JSON:
See docs/proto-json.md for operation-family examples and the security notes for secret-bearing JSON payloads.
Rust adapters can enable the operation-response feature and call
reallyme_crypto::operation_contract::process_operation_response(request_bytes)
with one encoded CryptoOperationRequest: serialized request bytes in, binary
CryptoOperationResponse bytes out, with either a generated
CryptoOperationResult or generated CryptoError outcome. The ProtoJSON
entrypoint accepts only generated non-secret hash, verification,
key-generation, encapsulation, and sender-export requests and still returns
binary protobuf. Secret-bearing operations must use the binary protobuf route.
TypeScript exposes processOperationResponse
and processOperationResponseJson; Swift and Kotlin expose the same method
names on ReallyMeCrypto. Every structured adapter returns the generated
operation response directly. Native SDK methods remain the primary ergonomic
application API.
The generated proto adapters are available through:
| Language | Proto surface |
|---|---|
| Rust | reallyme-crypto-proto |
| Swift | ReallyMeCryptoProto and ReallyMeCryptoProtoAdapters |
| Kotlin | me.really.crypto.v1 and me.really.crypto.proto |
| TypeScript | @reallyme/crypto/proto |
See docs/protobuf.md for the boundary rules and adapter policy.
Documentation
- PROVIDER_POLICY.md — provider matrix and backend selection for every algorithm and lane.
- CONTRACT.md — the public package and wire contract.
- docs/jwk.md — JWK and multikey encoding.
- docs/protobuf.md — structured operations, algorithm identifiers, typed wire errors, and boundary rules.
- docs/proto-json.md — strict proto-JSON request examples.
- docs/conformance.md — running the conformance vectors.
- docs/dependency-updates.md — dependency update policy and Renovate review rules.
- docs/rust-publishing.md — publishing the Rust crates.
- SECURITY.md, SECURITY_MEMORY_MODEL.md — reporting security issues and how secret material is handled.
Security Rules
This repository is security-sensitive code. The project policy is:
- no panics, unwraps, or generic string errors in production paths;
- typed errors only;
- zeroizing owners for secret material;
- checked arithmetic for buffer sizes and offsets;
- negative tests and conformance vectors for every primitive;
- no silent platform fallback in release platform lanes.
Conformance
Shared vectors live in vectors. The generator and platform verifiers live in crates/conformance.
The everyday all-feature Rust check is:
The full release wall is documented in docs/conformance.md.