SML — Secure Message Layer
API Reference
Complete technical reference for the SML C99 library. All public functions, types, constants, and integration patterns.
Getting Started
Installation
Clone the repository and build the static library. SML has no dependencies beyond a C99 compiler and standard POSIX headers.
git clone https://github.com/tlabs-technology/sml.git
cd sml
make # → build/libsml.a
make test # run test suite
Link against libsml.a and include
sml/sml.h:
CFLAGS += -I/path/to/sml/include
LDFLAGS += -L/path/to/sml/build -lsml
Quick Start
A minimal example: two parties establish a session via X3DH and exchange one encrypted message.
/* 1. Generate identities */
sml_identity_t alice, bob;
sml_identity_generate(&alice);
sml_identity_generate(&bob);
/* 2. Bob publishes a prekey bundle */
sml_prekey_bundle_t bundle;
sml_prekey_bundle_generate(&bob, &bundle);
/* 3. Alice opens a session with Bob's bundle */
sml_session_t alice_session;
sml_session_init(&alice_session, &alice);
sml_x3dh_initiate(&alice_session, &bundle);
/* 4. Alice encrypts */
uint8_t ciphertext[SML_MAX_MSG];
size_t ct_len;
sml_encrypt(&alice_session,
(const uint8_t *)"hello", 5,
ciphertext, &ct_len);
/* 5. Transmit ciphertext to Bob ... */
/* 6. Bob decrypts (session established from init message) */
uint8_t plaintext[SML_MAX_MSG];
size_t pt_len;
sml_decrypt(&bob_session, ciphertext, ct_len, plaintext, &pt_len);
Build Targets
| Target | Command | Output |
|---|---|---|
| Static library | make |
build/libsml.a |
| Shared library | make shared |
build/libsml.so |
| Tests | make test |
Exit 0 on pass |
| WebAssembly | make wasm |
build/sml.wasm |
| iOS (arm64) | make ios |
build/libsml-ios.a |
Core API
Identity & Keys
An sml_identity_t holds a long-term Ed25519 keypair and
the corresponding X25519 key for Diffie-Hellman. It is the root of trust for a participant.
/* Generate a new identity keypair */
sml_result_t sml_identity_generate(sml_identity_t *out);
/* Serialize / deserialize */
sml_result_t sml_identity_export(const sml_identity_t *id,
uint8_t *buf, size_t *len);
sml_result_t sml_identity_import(sml_identity_t *out,
const uint8_t *buf, size_t len);
/* Destroy — zeroes all key material */
void sml_identity_free(sml_identity_t *id);
/* Generate a signed prekey bundle for publication */
sml_result_t sml_prekey_bundle_generate(const sml_identity_t *id,
sml_prekey_bundle_t *out);
X3DH Handshake
X3DH establishes a shared secret between two parties without requiring the recipient to be online. The initiator uses the recipient's prekey bundle; the responder processes the initiator's first message.
/* Initiator: consume remote bundle, produce init message */
sml_result_t sml_x3dh_initiate(sml_session_t *s,
const sml_prekey_bundle_t *bundle);
/* Responder: process init message, derive same shared secret */
sml_result_t sml_x3dh_respond (sml_session_t *s,
const sml_init_message_t *msg);
/* Retrieve the serialized init message for transmission */
sml_result_t sml_session_get_init_msg(const sml_session_t *s,
sml_init_message_t *out);
NOTE
After sml_x3dh_initiate the session is in
SML_SESSION_PENDING state. The first call to
sml_encrypt transitions it to
SML_SESSION_ACTIVE and attaches the init message
to the ciphertext automatically.
Session Management
/* Initialize a new session bound to an identity */
sml_result_t sml_session_init(sml_session_t *s,
const sml_identity_t *id);
/* Destroy a session — zeroes all ratchet state */
sml_result_t sml_session_free (sml_session_t *s);
/* Serialize session state to opaque buffer (for storage) */
sml_result_t sml_session_serialize(const sml_session_t *s,
uint8_t *buf, size_t *len);
/* Restore session from serialized buffer */
sml_result_t sml_session_deserialize(sml_session_t *s,
const uint8_t *buf, size_t len);
/* Query session state */
sml_state_t sml_session_state(const sml_session_t *s);
Encrypt / Decrypt
/*
* sml_encrypt — encrypt plaintext using current ratchet state.
*
* ciphertext must be at least (pt_len + SML_OVERHEAD) bytes.
* SML_OVERHEAD = 96 (header 32B + tag 16B + padding 48B max)
*/
sml_result_t sml_encrypt(sml_session_t *s,
const uint8_t *plaintext, size_t pt_len,
uint8_t *ciphertext, size_t *ct_len);
/*
* sml_decrypt — decrypt ciphertext and advance ratchet.
*
* Handles out-of-order messages within the skipped-key window
* (default: SML_MAX_SKIP = 512 messages).
*/
sml_result_t sml_decrypt(sml_session_t *s,
const uint8_t *ciphertext, size_t ct_len,
uint8_t *plaintext, size_t *pt_len);
Error Codes
All functions return sml_result_t. Zero
is success.
| Constant | Value | Meaning |
|---|---|---|
SML_OK |
0 | Success |
SML_ERR_INVALID_ARG |
-1 | NULL pointer or invalid length |
SML_ERR_INVALID_STATE |
-2 | Session in wrong state for operation |
SML_ERR_DECRYPT_FAIL |
-3 | Authentication tag mismatch |
SML_ERR_SKIP_OVERFLOW |
-4 | Skipped-key cache exceeded SML_MAX_SKIP |
SML_ERR_BUFFER_TOO_SMALL |
-5 | Output buffer insufficient |
SML_ERR_CRYPTO |
-6 | Underlying crypto primitive failed |
SML_ERR_OOM |
-7 | Memory allocation failed |
Integration
Swift — Swift Package Manager
Add the SML package to your Package.swift. The Swift
wrapper provides a native, memory-safe API over the C ABI.
.package(url: "https://github.com/tlabs-technology/sml-swift.git",
from: "1.0.0")
import SML
let alice = try SMLIdentity()
let bundle = try alice.generatePrekeyBundle()
var session = try SMLSession(identity: alice)
try session.initiateX3DH(with: bundle)
let ciphertext = try session.encrypt(Data("hello".utf8))
Kotlin — JNI
The Kotlin wrapper uses JNI to call into libsml.so.
Include the AAR in your Gradle project.
dependencies {
implementation("technology.tlabs:sml-kotlin:1.0.0")
}
STATUS
The Kotlin/JNI wrapper is currently in development. Expected availability: Q2 2026.
JavaScript — Emscripten
Build the WebAssembly target with make wasm and load
via the generated JavaScript glue module.
import SML from './sml.js';
const sml = await SML();
const alice = sml.identity_generate();
const bundle = sml.prekey_bundle_generate(alice);
const session = sml.session_init(alice);
sml.x3dh_initiate(session, bundle);
Security
Threat Model
SML is designed to protect against the following adversary classes:
Passive Network Adversary
An adversary observing all ciphertext in transit cannot recover plaintext. Forward secrecy ensures past sessions remain safe even after private key compromise.
Compromised Session Key
The Double Ratchet's DH ratchet provides break-in recovery: after a session key is compromised, subsequent DH steps heal the session within one ratchet step.
Replay Attacks
Each message header includes a monotonically increasing message index. Re-delivered ciphertexts with the same index are rejected after the corresponding skipped key has been consumed.
Memory Safety
All key material lives in locked memory regions (via mlock
on supported platforms) and is zeroed using a compiler-barrier-protected memset on release.
Session state objects should be treated as secrets — do not write them to unencrypted storage
without applying your platform's secure enclave or keychain primitives.
Best Practices
Always call sml_session_free
when a session is no longer needed. Do not rely on process exit for key zeroing.
Rotate prekey bundles regularly. One-time prekeys should be used exactly once — remove a prekey from your server after it has been consumed by an initiator.
Do not reuse session objects across multiple remote peers.
Each peer requires an independent sml_session_t.
Before production deployment, conduct an independent cryptographic audit. SML is research software — no warranty of fitness for production is expressed or implied.