WP-01 — Threat Model.
Roughly 18 minutes to read.
The threat model Lattice is designed against. Three tiers of adversary, with explicit defences and explicit limits at each tier. Includes the out-of-scope cases — every threat model has them, and pretending otherwise damages trust.
Authors: Lattice project · Version: 1.0 (draft) · License: CC-BY-4.0 · Status: draft, will be signed and frozen at v1.0 launch.
1. Scope and assumptions.
1.1 What "Lattice" means in this paper.
The Lattice app installed on an unrooted, unjailbroken iOS or Android phone, with the user's seed phrase generated on-device and stored hardware-protected by the Secure Enclave (iOS) or StrongBox / TEE (Android). The user has set a strong PIN and enrolled biometric unlock. The phone is updated to a current major OS version (iOS 17+ / Android 10+).
1.2 What we assume about the user.
- The user can verify a contact's identity through at least one trusted channel — in person (QR scan), by voice call (four-word phrase comparison via Lattice Invites), or via cryptographically-signed introduction from someone they already trust (RFC-0014 introductions).
- The user has written down their 12-word seed phrase and stored it somewhere physically safer than the phone itself.
- The user has set a non-trivial PIN. We reject obvious sequences (123456, 000000, 111111).
If any of these assumptions fail, the security model degrades in well-defined ways documented per-tier below.
1.3 What we don't assume.
- That the network is honest. The internet, the cellular network, and other Lattice users in the mesh may all be hostile.
- That the cryptography we use will remain unbroken indefinitely. We use a hybrid construction precisely because future attacks against either classical or post-quantum primitives are conceivable.
- That the user is technically sophisticated. The defaults must be safe; the dangerous options must require explicit opt-in with educational copy.
2. Three tiers of adversary.
The threat model is organised into three tiers based on capability. Tier 1 is the most common; tier 3 is the most resourceful. Each subsequent tier inherits the defences of the previous tier and adds more sophisticated attacks.
T1 — Local passive radio observer
|
| + active manipulation
v
T2 — Local active radio adversary
|
| + multiple observation points + traffic confirmation
v
T3 — Network-level adversary (state-level resources)
Out of scope:
Endpoint compromise (rooted device, OS-level malware)
Supply-chain compromise of dependencies
Physical coercion of an unlocked user
Loss of the seed phrase
2.1 T1 — Local passive radio observer.
Capability. An adversary within Bluetooth-LE / Wi-Fi-Aware radio range of the user, capable of capturing every packet transmitted in the air. They cannot inject, modify, or replay packets. They have unlimited storage and computation, including future quantum computation.
Goal. Read message contents, identify communicating parties, infer relationships, build a profile of the user's communications.
Defences.
- End-to-end encryption. Every packet is encrypted with ChaCha20-Poly1305 AEAD (RFC 8439). The session key is derived from a Noise XX hybrid handshake mixing X25519 and ML-KEM-768 (FIPS 203). Forward secrecy is preserved by the standard Noise ratchet plus per-message nonce. Implementation:
core/crates/lattice-crypto, with property tests for round-trip preservation, tampered-AEAD rejection, and ML-KEM-768 implicit rejection (FO-transform). - Padding to fixed buckets. Every packet is one of 256, 512, 1024, or 2048 bytes after encryption + padding. An observer cannot infer the size of the message body from the packet size on the wire. Implementation:
core/crates/lattice-packet/src/wire.rs::select_bucket. - Sealed-sender recipient tags. The 16-byte recipient tag in the outer header is HKDF-derived from a (shared_secret, epoch) pair. Only the legitimate recipient can derive their tag for the current epoch. Relays cannot tell who a packet is for. Implementation:
core/crates/lattice-packet/src/tag.rs. - Optional Poisson cover traffic. When enabled, the device emits dummy packets at a Poisson(λ) rate (default λ = 1/60 per second), indistinguishable from real packets at the network level (same buckets, same encryption). This decouples the user's typing pattern from the wire activity. Implementation:
core/crates/lattice-mesh/src/cover.rs. - Optional onion wrapping. For high-threat sends, the user can enable 3-layer onion routing through the mesh. Each relay sees only the previous and next hops; only the final relay sees the recipient tag. Implementation:
core/crates/lattice-mesh/src/onion.rs.
Limits.
- The user's presence on the mesh is detectable. Lattice advertises a known BLE service UUID; an observer can detect that a Lattice user is in range. They cannot read messages, identify who the user is communicating with, or attribute the BLE address to a real-world identity without additional information. But they know somebody is running Lattice.
- The fact that two specific phones are in range of each other is observable to anyone with sufficient receivers. We do not address this; we accept it as an inherent cost of working without a server.
2.2 T2 — Local active radio adversary.
Capability. Same range as T1, plus the ability to inject arbitrary packets, modify packets in transit, replay packets, and selectively drop packets. They have unlimited storage and computation, including future quantum computation.
Goal. Substitute keys (man-in-the-middle), inject false messages, replay legitimate messages, deny service, perform downgrade attacks against the protocol.
Defences.
- AEAD authentication tags. Every modified byte invalidates the Poly1305 tag and the packet is rejected at decrypt. Property test in
core/crates/lattice-crypto/tests/property_tests.rs::xx_tampered_ciphertext_at_any_position_rejects. - Replay protection. Time-windowed Bloom dedupe at the mesh layer (10,000 packets / 10 minutes window, < 1% false-positive rate at load) drops duplicate packet IDs. Sliding-window nonce protection at the session layer drops out-of-order replays inside an established session.
- Identity authentication via Ed25519. Introduction bundles, group proposals + votes, and remote-burn commands are all signed with the user's long-term Ed25519 intro key. A man-in-the-middle who substitutes any of these is rejected at signature verification.
- Lattice Invite four-word verification phrase. The two-channel principle: the link travels via channel A (WhatsApp, SMS), the four-word phrase is derived from the link's contents and compared via channel B (a phone call). A MITM substituting the link must produce different four-word phrases on each side, which the user catches by comparison. ~32 bits of entropy = ~4 billion to 1 against accidental match. Defeats casual interception comfortably; not a defence against an adversary controlling both channels.
- Hybrid PQ in every session. Even an adversary who breaks X25519 in the future cannot retroactively decrypt sessions, because ML-KEM-768 was mixed in. (And vice versa for ML-KEM-768.)
- Strict parsers. Every wire-format byte is checked. Reserved bits are rejected. Unknown packet kinds are rejected. Malformed varints are rejected. Zero-padding within buckets is constant-time-checked. Property tests + 100 CPU-hours of fuzzing per parser at the M13 hardening gate.
Limits.
- Denial of service via radio jamming. An adversary actively transmitting noise on the BLE / Wi-Fi bands the user is on can deny service. No application-layer software can prevent this; jamming is a physical-layer attack that requires physical-layer countermeasures (frequency-hopping radios, regulatory enforcement). We do not promise to defend against jamming.
- Substitution of the four-word phrase via call interception. If the adversary controls both the WhatsApp / SMS channel and the phone call, they can substitute the link and coach the inviter into reciting the substituted phrase. This is the explicit limit of the two-channel scheme; it is documented in onboarding copy. The defence in this case is to meet in person and scan QR codes.
- Selective drops on the radio. An active adversary in range can selectively drop packets without revealing themselves. The store-and-forward outbox retries automatically over 7 days; a sufficiently determined adversary in continuous range can deny delivery by repeatedly dropping. The mesh is best-effort, not guaranteed.
2.3 T3 — Network-level adversary (state-level resources, traffic confirmation).
Capability. An adversary with access to multiple physical observation points across a wide area, capable of doing classical traffic-confirmation analysis: correlating timing, volume, and frequency of transmissions across many points to infer relationships, even without reading content. Resources at the level of a national-scale traffic-tap programme. Same compute assumptions as T1 / T2 (unlimited, including future quantum).
Goal. De-anonymise users, map the social graph, identify relationships even when content is unreadable.
Defences.
- Cover traffic in high-threat mode. Constant-rate emission rather than activity-driven decouples the user's actual messaging pattern from the wire signal.
- Onion wrapping. 3-layer onion through the mesh ensures intermediate relays cannot directly correlate sender and recipient.
- Tor v3 bridge for internet-bridged paths. When a user explicitly opts in, the message can travel over a Tor circuit, providing standard onion-routing anonymity.
- Encrypted Plus Code routing hints. When location-based routing is in use, the location precision exposed in routing hints is coarse (precision 6, ~5km cells) and wrapped per-recipient.
Limits.
- Traffic confirmation against a small anonymity set. Tor's threat model openly accepts that an adversary with access to both endpoints of a circuit can correlate timing and break the anonymity. Lattice's threat model is the same. The defence is a large anonymity set; in the early days of Lattice, the set is small.
- Membership inference. A nation-state observer can determine that a user is running Lattice based on the BLE service UUID (T1 defence applies to content but not to membership). In contexts where Lattice itself is dangerous to be running, our honest advice is not to run it.
- Long-term metadata accumulation. Even with cover traffic, very long observation windows allow some statistical inference. The defence is a much larger user base than we currently have; we acknowledge this and document it.
3. Out of scope.
These categories are explicitly outside Lattice's threat model. We will not defend against them and we will not pretend we do.
3.1 Endpoint compromise.
If the operating system is compromised — rooted Android, jailbroken iOS, or malware running with sufficient privilege — the OS sees what the user sees. Decryption keys are in the OS keystore (which the rooted OS can access). Plaintext is in app memory. The screen renders plaintext to a framebuffer the OS controls.
Lattice's defences end at the application boundary. The Secure Enclave / StrongBox protect the seed key from a rooted-but-otherwise-honest OS, but a sufficiently privileged attacker on the device can extract any plaintext that the user can read. This is a fundamental limit shared by every messenger.
3.2 Supply-chain compromise.
If a transitive dependency in our Cargo / Gradle dependency tree is backdoored, that backdoor lands in our binaries. We mitigate by:
- Pinning
Cargo.lock. - Running
cargo auditandcargo denyon every PR (RustSec advisory database). - Publishing dependency hashes in our
BUILD-MANIFEST. - Reproducible Android builds (per reproducible-builds.org guidelines) so a third party can verify what we shipped matches what we tagged.
But ultimately, if RustSec misses a backdoor in a transitive dep, we miss it too. We rely on the broader open-source ecosystem here.
3.3 Physical coercion.
Our duress-PIN feature wipes the device on entry of a duress credential rather than unlocking it. Our duress-biometric feature (post-launch) does the same on a configured second biometric. These are intentionally absent until the user explicitly enables them, with educational copy explaining the trade-offs.
However, an adversary with physical control of the user and the phone can extract whatever the user knows by means we cannot mitigate. No software fixes this. We try to make it harder by giving the user destructive options.
3.4 Loss of the seed phrase.
By design. There is no recovery path. If the user loses both the phone and the seed phrase, the identity is gone. The trade-off is real and chosen deliberately: a recovery path is a thing an attacker can attack.
4. Defence-in-depth checklist.
Mapping of every concrete defence to the source file that implements it. This table is the contract: each row is testable, each row is reviewable.
| Defence | Implementation | Test |
|---|---|---|
| ChaCha20-Poly1305 AEAD per packet | lattice-crypto/src/noise.rs | property: round-trip + tamper rejection |
| Padding to bucket sizes | lattice-packet/src/wire.rs | property: select_bucket monotone |
| Sealed-sender tags | lattice-packet/src/tag.rs | property: tag determinism + epoch rotation |
| Hybrid X25519 + ML-KEM-768 | lattice-crypto/src/{noise,kem}.rs | property: kem implicit rejection + round-trip |
| Replay protection (Bloom) | lattice-mesh/src/bloom.rs | property: no false negatives + count tracking |
| Time-windowed dedupe | lattice-mesh/src/bloom.rs | property: hold-within-window + roll-out |
| Cover traffic (Poisson) | lattice-mesh/src/cover.rs | property: bounded-emission + determinism |
| Onion wrapping (3-layer) | lattice-mesh/src/onion.rs | property: round-trip arbitrary path |
| Ed25519 introduction sigs | lattice-intro/src/lib.rs | property: tamper rejection + revocation |
| Lattice Invite four-word phrase | lattice-invite/src/lib.rs | unit: deterministic + diverges on distinct keys |
| BurnCommand authentication | lattice-burn/src/lib.rs | property: tampered + stale + version + target |
| MLS group security | lattice-mls/src/{lib,envelope}.rs | unit: 4-member round trip; envelopes signed |
| Strict wire-format parser | lattice-packet/src/wire.rs | fuzz: cargo-fuzz at 100 CPU-hours |
| Constant-time padding check | via subtle crate | review: documented use of ConstantTimeEq |
| Hardware-backed key wrap | ios/Lattice/Identity + android/.../IdentityVault.kt | manual: device test on real Secure Enclave / StrongBox |
| Burn arsenal | lattice-burn/src/lib.rs | property: counter, inactivity, dead-man, shake |
5. Where this paper is wrong.
If you find an error here — a defence that doesn't actually defend, a limit that's worse than we describe, a tier we've miscategorised, a citation that doesn't exist — please tell us. our Matrix room, encrypted to our PGP key, or a Matrix message tagged SECURITY.
This paper is the contract. If we publish it and the code disagrees with it, the code is wrong, not the paper. Please call us on it.
References.
- RFC 7748 — Elliptic Curves for Security (Curve25519, X25519).
- RFC 8032 — Edwards-Curve Digital Signature Algorithm (Ed25519).
- RFC 8439 — ChaCha20-Poly1305 AEAD (IETF profile).
- RFC 9420 — Messaging Layer Security (MLS).
- FIPS 203 — Module-Lattice Key-Encapsulation Mechanism (ML-KEM).
- BIP-39 — Mnemonic code for generating deterministic keys.
- The Noise Protocol Framework, revision 34. noiseprotocol.org/noise.html.
- WP-02 — The Invite Protocol.
- WP-05 — What Lattice Doesn't Do.