Specifications.
Long. ~25 minutes if you read every link.
This page exists for researchers, journalists, and anyone who wants to verify our claims by reading the actual standards. Every protocol Lattice uses, every RFC we conform to, every NIST publication we follow, every ISO/IEC reference. No shortcuts.
If something on this page looks wrong, please tell us in our Matrix room. We'd rather correct an error than have it sit.
1. The stack at a glance.
┌──────────────────────────────────────────────────────────┐
│ Application │
│ ChatStore, GroupChat, IntroductionBundle, BurnCommand │
├──────────────────────────────────────────────────────────┤
│ Group security │
│ MLS (RFC 9420) via openmls 0.8 │
├──────────────────────────────────────────────────────────┤
│ Session security │
│ Noise XX (Curve25519+ChaCha20-Poly1305+SHA-256) │
│ Hybrid post-quantum: ML-KEM-768 (FIPS 203) │
├──────────────────────────────────────────────────────────┤
│ Mesh routing │
│ Cluster-bounded gossip + Bloom dedupe + onion (3-hop) │
├──────────────────────────────────────────────────────────┤
│ Wire format │
│ Sealed-sender packet, padding buckets {256,512,1024, │
│ 2048}, ChaCha20-Poly1305 IETF AEAD per packet │
├──────────────────────────────────────────────────────────┤
│ Transport (any of) │
│ Bluetooth LE (5.0+, GATT + L2CAP CoC) │
│ Wi-Fi Aware (NAN v4, IEEE 802.11) │
│ Tor v3 onion (opt-in, internet-bridged) │
├──────────────────────────────────────────────────────────┤
│ Identity / key derivation │
│ BIP-39 mnemonic → PBKDF2-HMAC-SHA512 → HKDF-SHA-512 │
│ Per-persona: X25519, Ed25519, ML-KEM-768, AES-256-GCM │
├──────────────────────────────────────────────────────────┤
│ Storage │
│ SQLite (file-format) + ChaCha20-Poly1305 per-cell │
│ AEAD; key wrapped by Secure Enclave / StrongBox │
├──────────────────────────────────────────────────────────┤
│ Hardware key storage │
│ iOS: Apple Secure Enclave (NIST P-256 wrap) │
│ Android: StrongBox (StrongBox Keymaster) / TEE │
└──────────────────────────────────────────────────────────┘
2. Cryptographic primitives.
2.1 Symmetric encryption
- ChaCha20-Poly1305 AEAD. Specified in RFC 8439 (the IETF profile, with 96-bit nonce + 128-bit tag). Used for every encrypted packet on the wire and every encrypted blob at rest. Implementation:
chacha20poly1305(RustCrypto, audited by NCC Group, 2020). - AES-256-GCM. Specified in NIST SP 800-38D. Used only in the Android StrongBox-wrapped key, where the OS demands AES. We do not use AES on the wire because ChaCha20 is faster on phones without AES-NI hardware.
2.2 Hash functions
- SHA-256. FIPS 180-4. Used for the Bullet ID (32-byte digest of the canonical concatenation of public keys), for tag derivation, for the four-word verification phrase derivation in Lattice Invites, and as the hash inside Noise XX.
- SHA-512. Same FIPS publication. Used inside HKDF for persona-root derivation.
2.3 Key derivation
- HKDF. RFC 5869. SHA-512 underneath at the persona-root level, SHA-256 at leaf-key level. Domain-separated by labels per RFC-0002 §2 (in our internal RFC tree).
- PBKDF2-HMAC-SHA512. RFC 2898 §5.2. 2048 iterations, exactly as specified by BIP-39. Used to convert a BIP-39 mnemonic phrase + optional passphrase into a 64-byte seed. The 2048 iteration count is the BIP-39 specification value; we accept the trade-off it represents (modest GPU resistance + universal interoperability with hardware wallets).
2.4 Asymmetric primitives — classical
- X25519. RFC 7748. Elliptic-curve Diffie-Hellman on Curve25519 (Bernstein 2006). Used for Noise XX's static + ephemeral key agreements and for Lattice Invite ephemeral keys. Implementation:
x25519-dalek(audited by Quarkslab, 2020). - Ed25519. RFC 8032 §5.1. Edwards-curve digital-signature algorithm on Edwards25519. Used for: introduction bundles (RFC-0014), introduction revocations, BurnCommand authenticity, GroupPolicy proposal/vote envelopes, Lattice Invite signing. Implementation:
ed25519-dalekfrom the same suite as above.
2.5 Asymmetric primitives — post-quantum
- ML-KEM-768. FIPS 203 (August 2024). Key-encapsulation mechanism, NIST PQC standardisation Module-Lattice family. We use the 768-bit security parameter (NIST Category 3, ~AES-192-equivalent post-quantum security). Implementation:
ml-kemfrom RustCrypto. - Hybrid construction. Both X25519 and ML-KEM-768 outputs are mixed into the symmetric state during the Noise XX handshake. An adversary who breaks either primitive — including a future quantum adversary who breaks X25519 — still cannot derive the session key without breaking the other. Detailed in our internal RFC-0011 (Hybrid PQ Handshake), available with the source.
The hybrid choice is deliberate. Pure post-quantum is premature; the algorithms are new and have less analytic history. Pure classical is dead in the long term against any quantum adversary. Hybrid is the standard cautious approach taken by Apple iMessage's PQ3, Signal's PQXDH, and OpenSSH.
3. Identity and onboarding.
3.1 Mnemonic
- BIP-39 — 12 or 24 words from the standard 2048-word English wordlist. Validated against the BIP-39 checksum word.
- Standard PBKDF2 derivation:
seed = PBKDF2-HMAC-SHA512(phrase, "mnemonic" || passphrase, 2048, 64). - Vectors locked: the canonical "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" mnemonic with empty passphrase produces seed
5eb00bbd…ce9e38e4. Test incore/crates/lattice-identity/src/mnemonic.rs.
3.2 Persona derivation tree
seed (64 B from BIP-39)
│
│ HKDF-SHA-512 with salt "Lattice/v1"
▼
master_secret (64 B)
│
│ HKDF-Expand info = "persona/" || u32_be(persona_index)
▼
persona_root (64 B)
│
├── HKDF-Expand "noise_x25519" → 32 B → X25519 secret
├── HKDF-Expand "kem_ml_kem_768" → 32 B (placeholder; full KEM key
│ generated separately at M4
│ since FIPS 203 keys are
│ not deterministic from a
│ small seed)
├── HKDF-Expand "sig_ed25519" → 32 B → Ed25519 secret
├── HKDF-Expand "intro_ed25519" → 32 B → Ed25519 intro key
└── HKDF-Expand "storage_wrap" → 32 B → ChaCha20-Poly1305 key
for the local SQLite vault
3.3 Bullet ID
- Full Bullet ID =
SHA-256(canonical_concatenation(public_keys)), 32 bytes. - Short form: first 60 bits, encoded as 12 characters of Base32-Crockford (no I/L/O/U), grouped
XXXX-XXXX-XXXX. - Fingerprint: first 88 bits → eight words from a curated 256-word phonetically-distinct list (derivation table in
core/crates/lattice-identity/src/wordlist.rs).
4. Session security — Noise XX hybrid.
The Noise XX pattern, specified in The Noise Protocol Framework revision 34. We extend it with ML-KEM-768 mixing per the Hybrid Forward Secrecy "hfs" pattern (work-in-progress draft adopted with documentation).
Pattern: Noise_XXhfs_25519+Kyber768_ChaChaPoly_SHA256
message 1 (initiator → responder):
-> e X25519 ephemeral pubkey
ML-KEM-768 ciphertext (initiator-encapsulated to
responder's static KEM key, but XX has no static yet,
so this ciphertext is to a *transient* KEM key — see
RFC-0011 §3 for the precise mixing)
message 2 (responder → initiator):
<- e, ee, s, es Both DH and KEM outputs mixed into the symmetric state.
message 3 (initiator → responder):
-> s, se Identity revealed. Final mixing.
At completion, both sides have:
- a confirmed handshake hash (transcript binding)
- a 32-byte send key, a 32-byte receive key
- a counter for nonce uniqueness, replay-protection sliding window 64
The hybrid mixing is forward-secure under the standard Noise security analysis even if one of {X25519, ML-KEM-768} is broken. Internal property tests at core/crates/lattice-crypto/tests/property_tests.rs verify: round-trip on random plaintexts, tampered AEAD rejected at any byte position, ML-KEM-768 implicit rejection (FO-transform), distinct sessions yield non-interchangeable keys.
5. Group security — MLS.
- RFC 9420 — The Messaging Layer Security (MLS) Protocol, July 2023. Standardised by the IETF.
- Implementation: openmls 0.8, the de-facto reference implementation. Audited by Cure53 (2023).
- Ciphersuite:
MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519(the suite openmls implements robustly today; we expect to migrate to a hybrid PQ ciphersuite once MLS PQ extensions stabilise). - Group state advances by epoch on every Add / Remove / Update / Commit. Forward secrecy and post-compromise security as defined in §16 of the RFC.
- Custom GroupContextExtension type
0x4C41("LA" ASCII) carries our GroupPolicy (RFC-0013): who can propose what, what quorum is required, proposal TTL.
6. Mesh routing.
6.1 Wire format
Outer packet (clear, AAD'd): ┌─────────────────────────────────────────────────────────────────┐ │ version (1 B) │ flags (1 B) │ ttl (1 B) │ kind (1 B) │ │ epoch (2 B BE) │ recipient_tag (16 B) │ │ packet_id (8 B BE) │ ct_len (2 B BE) │ reserved (4 B = 0) │ ├─────────────────────────────────────────────────────────────────┤ │ ciphertext (ct_len bytes) — ChaCha20-Poly1305(key, nonce, │ │ payload, AAD = outer_header) │ ├─────────────────────────────────────────────────────────────────┤ │ ciphertext continued + 16 B Poly1305 tag │ └─────────────────────────────────────────────────────────────────┘ Total padded to one of: 256, 512, 1024, or 2048 bytes. Padding bucket selected to fit body+overhead; remainder PKCS#7-ish.
Padding to bucket sizes is the standard traffic-analysis defence: a packet on the wire is always one of four sizes, so an observer cannot infer the size of your message body from the packet length.
6.2 Sealed-sender recipient tags
The 16-byte recipient_tag is HKDF-derived from a (shared_secret, epoch) pair, where epoch is a 30-minute increment from a 2026-01-01 anchor. Only the legitimate recipient can derive their tag for the current epoch — relays cannot tell who a packet is for, just that it is for somebody.
6.3 Cluster-bounded gossip
Each phone maintains an "active neighbour set" — the small bounded
group of peers it actually exchanges traffic with. Capacity per
density class:
Sparse (few neighbours) cap = 12
Normal (handful of neighbours) cap = 8
Dense (a stadium row) cap = 6
Crowd (festival pocket) cap = 6
Score per visible peer:
score = signal_quality × route_utility × freshness × diversity
Promotion / demotion rate-limited to ≤ 1 swap per 30 s to prevent
thrash.
On packet receipt:
1. Bloom-dedupe against a time-windowed filter (10k packets / 10 min).
2. Decrement TTL (max 16).
3. For each active neighbour ≠ source, forward with probability
p = 0.5 * ttl_term + 0.3 * battery_term + 0.2 * link_quality_term
clamped to [0, 1].
Detailed analysis with the three reference environments (supermarket, airport, Glastonbury festival) in WP-04 — Density and Crowd Behaviour.
6.4 Onion wrapping (optional)
For high-threat send paths the user can enable 3-layer onion routing through the mesh. Each hop has its own ephemeral X25519 keypair and ChaCha20-Poly1305 layer; each relay sees only the previous and next hops. Implementation in core/crates/lattice-mesh/src/onion.rs with property tests covering hop isolation and tamper rejection.
7. Transport layer.
7.1 Bluetooth LE
- Bluetooth Core Specification 5.3 (and forward-compatible to 5.4 / 6.0 where the OS exposes them).
- Service UUID:
4C617474-6963-6500-0000-001000000001("Lattice" in ASCII). - Bulk data: L2CAP Connection-Oriented Channel (CoC) where the OS exposes it (iOS 11+, Android 10+). PSM
0x4C61. - Fallback: GATT characteristic write-with-fragmentation when L2CAP CoC is unavailable. Characteristic UUID
4C617474-6963-6500-0000-001000000002. - Background mode on iOS:
bluetooth-central+bluetooth-peripheralwith state preservation/restoration. - Background mode on Android: foreground service of type
connectedDevice.
7.2 Wi-Fi Aware (NAN)
- Wi-Fi Aware v4 (Neighbour Awareness Networking, IEEE 802.11-based).
- iOS 26+ via the
WiFiAwareframework, Apple's first-party API. - Android 12+ via
android.net.wifi.aware. - Service name:
lattice-mesh-v1. - Pairing v4 (mutual key confirmation) for first contact between previously-unpaired devices.
- iOS↔Android NAN interop is hardware-dependent; we attempt and fall back to BLE gracefully when it fails.
7.3 Tor (optional bridged)
- Tor Rendezvous Specification v3 for hidden-service connectivity.
- Used only when the user has explicitly opted into high-threat mode and internet is reachable. The mesh-bridged Tor path delivers messages between users who can't reach each other via radio because they are too far apart, while still avoiding any plaintext exposure to a server.
8. Storage at rest.
- SQLite file format 3. We do not use SQLCipher; we encrypt at the cell level with our own ChaCha20-Poly1305 wrapper, so the database file is structurally a normal SQLite file but every encrypted column is opaque.
- Per-row nonce: 12 bytes from
OsRngperencrypt()call. Tag: 16 bytes Poly1305. Format on disk:nonce || ciphertext_with_tag. - The wrap key (32 bytes) is the
storage_wrapleaf from the persona derivation tree. It lives in memory only while the vault is open, and is zeroised on close. The user's seed phrase never touches the database.
9. Hardware-backed key storage.
9.1 iOS — Secure Enclave
- Apple Secure Enclave on A7 and newer SoCs. NIST P-256 ECC keys with
kSecAttrTokenIDSecureEnclave. - Access control:
kSecAccessControlBiometryCurrentSet+kSecAccessControlPrivateKeyUsage. Removing or changing Face ID re-rolls the access policy and re-presents biometric. - The seed itself doesn't live in the Enclave (the SE doesn't store arbitrary blobs); we keep a Secure-Enclave-held P-256 wrapping key, use it to encrypt the seed, and store the wrapped seed in the iOS Keychain.
9.2 Android — StrongBox / TEE
- Android Keystore. We request
setIsStrongBoxBacked(true), falling back to the platform TEE on devices without StrongBox. - Access control:
setUserAuthenticationRequired(true)+setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG). - AES-256-GCM key wraps the seed;
KeyPermanentlyInvalidatedExceptionhandled correctly when biometric set changes.
10. Notifications.
- iOS:
UNNotificationCenter+UNNotificationCategorywith custom action identifiers (Reply, Mark Read, Approve, Reject, View, Dismiss). No push servers — every notification is local, fired on message decrypt. Apple-imposed reliability bands documented honestly: foreground 100%, recently-backgrounded 80–90%, long-locked 40–60%. - Android:
NotificationCompat.Builderwith channels per category. Foreground service of typeconnectedDevice+dataSyncwhen scanning in background. - Content rule: notifications never include the message body, only the sender's display name. This prevents leaks via Apple Watch mirrors, CarPlay, Android Auto, smart-display mirrors, lock-screen previews. User opens app to read.
- Reply-from-lock-screen: encrypted with the existing per-conversation session key, which is itself protected by the Secure-Enclave / StrongBox-held wrap key marked accessible-when-unlocked-this-device-only.
11. Threat-model summary.
Three tiers of adversary, with explicit defences and explicit limits at each. The full table with citations to specific code paths is in WP-01.
- T1 — Local passive radio observer. Adversary in BLE / Wi-Fi range, capturing packets. Defended: ChaCha20-Poly1305 AEAD on every packet, padding buckets, sealed-sender recipient tags, optional Poisson cover traffic. Not defended: presence of a Lattice user is detectable.
- T2 — Local active radio adversary. Same range plus injection. Defended: AEAD authentication tags, Bloom dedupe, replay-protection sliding window. Not defended: denial-of-service via BLE-band jamming (no app can defend against jamming on consumer phones).
- T3 — Endpoint compromise (rooted device, malware on the phone). Out of scope — explicitly. If the attacker has root on the device, they can read anything the user can read. Lattice's defences end at the app boundary; we do not promise to protect against a compromised OS.
12. Compliance and accessibility.
- WCAG 2.2 AA for all UI surfaces, AAA for body text contrast where achievable. (spec)
- ISO/IEC 18004 for QR-code generation in the contact-add flow (Reed-Solomon ECC, version-auto sizing).
- RFC 8259 JSON for any debug logs the user explicitly exports. We don't transmit JSON; the wire is CBOR.
- RFC 8949 CBOR for every signed structure (introduction bundles, revocations, BurnCommands, GroupPolicy extensions, MLS proposal/vote envelopes, Lattice Invite tokens). Canonical encoding rules per §4.2.
- FIPS 180-4 / 203 / 198-1 for SHA, ML-KEM, HMAC respectively.
- Apple Privacy Nutrition Label: Data Not Collected across every category. App Store encryption export compliance: standard cryptography → annual self-classification report.
- Google Play Data Safety: equivalent declaration of zero collection.
- GDPR: there is no controller / processor relationship because we do not collect personal data. The privacy policy explains the model.
13. Build provenance and reproducibility.
- Cargo dependencies: pinned via
Cargo.lock(committed to the repo). - Rust toolchain:
rust-toolchain.tomlpins a specific stable channel. - Android: deterministic Gradle build per Reproducible Builds JVM guidance. F-Droid-compatible. APK hash published per release.
- iOS: source-attestation rather than full reproducible build. We sign the source tarball at submission time with the project Ed25519 release key; the signature is published, the source tag in Git is named
v1.x.y-source, and any researcher can compare. - BUILD-MANIFEST: every release ships a signed manifest listing source commit, dependency versions, build environment, and resulting hashes.
tools/verify-build.shwalks a third party through verification.
14. Audits and disclosure.
- Pre-v1.0 audit being commissioned with one of: Trail of Bits, Cure53, Latacora. Scope: cryptography, identity, invite protocol, MLS integration.
- Coordinated security disclosure policy — researchers are publicly credited (or anonymous on request), explicitly safe-harboured against legal action.
- Reports published in full, except for unfixed-critical sections which are delayed until fix.
14a. Key rotation.
Three independent rotation regimes:
What rotates When How
──────────────────────────────────────────────────────────────────────
Per-invite ephemeral Every Lattice Invite generated Fresh
X25519 keypair (single-use, rejected on re-use). X25519
Default 24h expiry, max 7d. gen.
Per-handshake Noise XX Every new session. Standard
ephemeral Noise
XX e/ee/se.
Per-message nonce Every encrypted packet. Counter +
IV per RFC 8439.
MLS group epoch keys Every Add / Remove / Update / RFC 9420
Commit on the group. tree update.
What does NOT rotate Why Mitigation
──────────────────────────────────────────────────────────────────────
Long-term identity Wallet model: the seed phrase IS If a long-term
keys (X25519 static, the user's identity. A rotation key is suspected
Ed25519 sig, Ed25519 mechanism is something an compromised, the
intro, ML-KEM static) attacker can attack — phish the user burns the
user, social-engineer, etc. We identity (T-M8-08
chose the wallet model remote burn) and
deliberately so there is no onboards a new
such surface to attack. one. Contacts
re-add via the
fresh Bullet ID.
Bullet ID Same — it's a SHA-256 of the Same.
long-term key bundle.
Forward secrecy follows from the per-handshake and per-message rotation: an attacker who obtains today's session key cannot read yesterday's traffic, and an attacker who later breaks one of {X25519, ML-KEM-768} still can't read past traffic because both were mixed.
15. Source.
- Repository: published with the v1.0 release. The codebase is currently developed in a private mirror to keep churn-driven design changes off public-record until the protocol is stable.
- Codeberg + self-hosted git mirrors land alongside the public repo at v1.0.
- License: Mozilla Public License 2.0. File-level copyleft; combine freely with proprietary code as long as Lattice files themselves stay open.
If you want to verify a specific claim on this page, the table of contents in the source's docs/ directory cross-references every claim back to a source file or test. Ask if anything is unclear.