Skip to content

Commit ea5d265

Browse files
committed
feat(book): write proteus chapter
1 parent 1367c31 commit ea5d265

File tree

1 file changed

+137
-14
lines changed

1 file changed

+137
-14
lines changed
Lines changed: 137 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,139 @@
11
= Proteus
22

3-
// What the Signal Protocol is (double ratchet, forward secrecy, break-in recovery)
4-
// Why Wire built Proteus: a Signal-compatible implementation for the Wire protocol
5-
// The prekey model: how a recipient's prekeys allow a sender to open a session without them being online
6-
// One-to-one only: each conversation is a bilateral session; group messaging is n*(n-1) sessions
7-
// Why that doesn't scale: O(n²) messages per group update, no forward secrecy for large groups
8-
// Why Proteus is being deprecated in favor of MLS
9-
10-
// == CoreCrypto's Proteus Interface
11-
12-
// ProteusCentral and what it owns (identity, session cache)
13-
// Session lifecycle: proteus_session_from_prekey vs. proteus_session_from_message
14-
// Encrypt/decrypt, fingerprints, last-resort prekey
15-
// How Proteus state lives inside the same TransactionContext as MLS
16-
// Migration note: pointing users toward the CC9→CC10 migration guide
3+
Proteus is Wire's implementation of the https://signal.org/docs/[Signal Protocol]: a double-ratchet scheme providing forward secrecy and break-in recovery for one-to-one encrypted sessions.
4+
5+
Proteus is intrinsically a direct protocol: every message is individually encrypted for each recipient.
6+
Groups in Proteus are a fiction managed collaboratively between the backend and the client; they do not exist at the protocol level.
7+
Encrypting a message for a group of _n_ clients requires sending _n_ individually encrypted copies.
8+
This effect is compounded by the fact that a logical user in the sense of a human is very likely to have multiple clients in terms of individual devices.
9+
This O(n²) overhead is manageable for small groups, but becomes expensive as they scale, and it was the primary motivation for adopting MLS.
10+
11+
Proteus remains supported for backwards compatibility, but has not received direct development work in some time.
12+
Its implementaion is already feature-gated for easy removal, and is intended to be removed as soon as we can confirm that a sufficiency of clients have upgraded their conversations to MLS.
13+
14+
== Initialization
15+
16+
Before any Proteus operation, the Proteus subsystem must be explicitly initialized within a transaction:
17+
18+
[source]
19+
----
20+
transaction_context.proteusInit()
21+
----
22+
23+
This loads the device's Proteus identity from the keystore, creating it if it does not yet exist.
24+
All other Proteus methods will return an error if called before `proteusInit()` has succeeded.
25+
26+
== Identity and Fingerprints
27+
28+
A Proteus identity is a long-lived Ed25519 keypair tied to the device.
29+
CoreCrypto exposes three fingerprint accessors, each returning a lowercase hex string:
30+
31+
`proteusFingerprint()`::
32+
The local device's public key.
33+
Remains stable for the lifetime of the keystore.
34+
35+
`proteusFingerprintLocal(sessionId)`::
36+
The local public key as seen from within a specific session.
37+
Equivalent to `proteusFingerprint()` but scoped to a session for API symmetry.
38+
39+
`proteusFingerprintRemote(sessionId)`::
40+
The remote peer's public key within a session.
41+
Can be used to verify the peer's identity out-of-band.
42+
43+
`proteusFingerprintPrekeybundle(prekey)`::
44+
Extracts the public key fingerprint from a serialized prekey bundle without opening a session.
45+
Useful for verifying a peer's identity before establishing a session.
46+
47+
== Prekeys
48+
49+
Prekeys are the mechanism that allows a sender to open a Proteus session with a recipient who is offline.
50+
The recipient publishes a set of one-time prekey bundles to the delivery service in advance; the sender fetches one and uses it to bootstrap the session.
51+
52+
CoreCrypto provides two ways to generate prekeys:
53+
54+
`proteusNewPrekey(id)`::
55+
Generates a prekey with an explicit numeric ID (a `u16`).
56+
Use this when your app manages the prekey ID space itself.
57+
58+
`proteusNewPrekeyAuto()`::
59+
Generates a prekey with an automatically incremented ID and returns both the ID and the serialized bundle.
60+
Prefer this unless you have a specific reason to control IDs manually.
61+
62+
Both return a CBOR-serialized prekey bundle ready to upload to the delivery service.
63+
64+
=== The Last Resort Prekey
65+
66+
Proteus reserves prekey ID 65535 (`u16::MAX`) as a last resort prekey.
67+
Unlike one-time prekeys, the last resort prekey is never consumed — it stays in the keystore indefinitely so that a sender can always open a session even when all one-time prekeys have been exhausted.
68+
69+
[source]
70+
----
71+
prekey = transaction_context.proteusLastResortPrekey()
72+
----
73+
74+
The constant `proteusLastResortPrekeyId()` returns `65535` and can be used to exclude this ID when generating ordinary prekeys.
75+
76+
== Sessions
77+
78+
A Proteus session represents an established encrypted channel with one remote client, identified by a caller-supplied `sessionId` string.
79+
Session state is cached in memory and persisted to the keystore.
80+
81+
There are two ways to establish a new session, depending on which side initiates:
82+
83+
`proteusSessionFromPrekey(sessionId, prekey)`::
84+
Used by the *sender* to initiate a session.
85+
Takes the remote client's serialized prekey bundle (fetched from the delivery service) and creates a local session ready to encrypt.
86+
87+
`proteusSessionFromMessage(sessionId, envelope)`::
88+
Used by the *recipient* upon receiving the first message.
89+
Decrypts the initial envelope, establishes the session, and returns the plaintext in a single step.
90+
91+
Once established, a session can be checked for existence with `proteusSessionExists(sessionId)` and explicitly removed with `proteusSessionDelete(sessionId)`.
92+
Manual saves via `proteusSessionSave(sessionId)` are available but not normally required — sessions are persisted automatically when encrypting or decrypting.
93+
94+
== Encrypting and Decrypting
95+
96+
With a session established, encryption and decryption are straightforward:
97+
98+
[source]
99+
----
100+
ciphertext = transaction_context.proteusEncrypt(sessionId, plaintext)
101+
plaintext = transaction_context.proteusDecrypt(sessionId, ciphertext)
102+
----
103+
104+
=== Batched Encryption
105+
106+
Because a group message in Proteus requires one encrypted copy per recipient client, CoreCrypto provides a batched variant to reduce FFI round-trips:
107+
108+
[source]
109+
----
110+
map = transaction_context.proteusEncryptBatched(sessionIds, plaintext)
111+
// map: { sessionId -> ciphertext }
112+
----
113+
114+
This is more efficient than calling `proteusEncrypt` in a loop and should be preferred whenever sending to multiple sessions simultaneously.
115+
116+
=== Safe Decrypt
117+
118+
`proteusDecryptSafe(sessionId, ciphertext)` is a convenience wrapper that handles the common case where you do not know in advance whether the session already exists.
119+
It opens the session from the message envelope if necessary, then decrypts, in a single call.
120+
For high-volume decryption to an existing session, the plain `proteusDecrypt` call is slightly more efficient.
121+
122+
== Error Handling
123+
124+
Proteus errors are surfaced as a `ProteusError` with the following variants:
125+
126+
`SessionNotFound`::
127+
The requested session ID does not exist in the keystore or in-memory cache.
128+
129+
`DuplicateMessage`::
130+
The ciphertext has already been decrypted.
131+
The double-ratchet discards keys after use, so replaying a message is always an error.
132+
133+
`RemoteIdentityChanged`::
134+
The remote peer's identity key no longer matches what was recorded when the session was established.
135+
This typically indicates a device reset or, in the worst case, a key compromise.
136+
137+
`Other { error_code }`::
138+
A lower-level Proteus error that does not map to one of the above categories.
139+
The numeric `error_code` corresponds to the https://github.com/wireapp/proteus/blob/develop/crates/proteus-traits/src/lib.rs[proteus-traits error table].

0 commit comments

Comments
 (0)