1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
//! A constrained device that speaks the Offline Protocol as a real peer.
//!
//! A leaf node is a door lock, a sensor, or a mains-powered relay: something
//! with a radio, a few hundred kilobytes of flash and no operating system. It
//! parses frames, validates addressing, runs RFC 9420 MLS, and holds an
//! end-to-end encrypted conversation with a phone **under the same guarantees
//! a phone gets**. Same frames, same envelope, same trust gates, no second
//! sealing path and no reduced properties.
//!
//! That is the decision in
//! [ADR 0021](https://github.com/Offline-Protocol/offline-protocol-sdk/blob/main/docs/adr/0021-a-leaf-node-speaks-mls.md),
//! and it is affordable because a phone paired with one device is a
//! **two-member group**: the ratchet tree is three nodes, per-commit cost is
//! two elliptic-curve operations, and per-message cost is symmetric only. What
//! both ends must agree on lives in
//! [`offline_protocol_sealed`], so the two MLS implementations never disagree
//! about a byte outside themselves.
//!
//! # What this crate is
//!
//! [`LeafDevice`] is a frame-level state machine, not a bag of primitives. It
//! takes an inbound [`Message`](offline_protocol_core::Message) and hands back
//! the frames to send and what happened. The choreography it implements is
//! security-critical and easy to get subtly wrong: the derive-and-compare gate
//! at every site that accepts an identity claim, the binding of a Welcome to
//! the key package this device minted for the peer that sent it (a package
//! travels unencrypted, so whoever copies one off the air satisfies every
//! other gate honestly), the roster re-read at every use of the group that
//! keeps a pair a pair once commits start arriving, the confirmation that has
//! to be a group-aware decrypt, and the reset sequence that a driven rekey
//! arrives as.
//!
//! ```
//! use offline_protocol_leaf::{shared_store, LeafDevice, MemoryStore};
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // A real device implements `LeafStore` over its secure key storage.
//! let store = shared_store(MemoryStore::new());
//! let mut device = LeafDevice::open(store, "com.example.lock")?;
//!
//! // `now` comes from the radio stack, the commissioner, or the pairing
//! // exchange. It is a parameter because a device has no clock, and an MLS
//! // implementation that reads one it does not have stamps 1970.
//! let now = 1_787_314_332;
//! let peer = device.address().to_string();
//! let advertisement = device.key_package_frame(&peer, now)?;
//! assert!(advertisement.content.starts_with("__MLS_KEY_PKG__"));
//! # Ok(())
//! # }
//! ```
//!
//! # Four obligations this crate cannot discharge for you
//!
//! **A time source at pairing.** Every entry point that needs a clock takes
//! `now_unix_secs`. A device that supplies something wrong emits a key package
//! the peer refuses as expired, and it never pairs at all. Validity is a
//! freshness bound rather than an authentication mechanism, so a wrong clock
//! costs availability, not confidentiality.
//!
//! **Real entropy.** This crate registers no `getrandom` backend, on purpose:
//! doing so would let firmware link and run with randomness this crate
//! invented, and MLS key generation is exactly as strong as what that symbol
//! returns. Wire it to the part's hardware entropy source.
//!
//! **Durable storage.** [`LeafStore`] must be durable and atomic per entry.
//! This crate orders every persist before the emit it belongs to, so a store
//! that lies is the one remaining way to reuse an AEAD nonce after a power
//! cut.
//!
//! **Authorization.** A session proves *who* a peer is and never that they may
//! do anything. Every gate in this crate answers the first question, and any
//! address in radio range can complete a pairing, because producing a key that
//! derives to its own address costs nothing. So firmware decides when the radio
//! accepts a new pairing, and firmware decides what a message from a given peer
//! may actuate, by the address on the event. A lock that opens for whatever
//! arrives on an established session opens for anyone patient enough to pair
//! with it. [`LeafDevice::peers`] is how firmware audits what a device
//! accumulated and [`LeafDevice::unpair`] is how it removes one.
//!
//! # One writer
//!
//! Every operation that advances state takes `&mut self`, so a device is one
//! value exclusively held rather than a handle to share between tasks. Two
//! seals at once would reuse an AEAD nonce without any power cut being
//! involved; see [`LeafDevice`] for that argument and for what a replayed
//! control frame can still do.
//!
//! # Bare metal
//!
//! Builds with `--no-default-features` for a target with no `std`, which is
//! the configuration the CI job gates. The `std` build is the same code with
//! its dependencies' `std` features on, and is what the unit tests run under.
//!
//! CI builds and lints it for a Cortex-M33 (`thumbv8m.main-none-eabihf`), for
//! RISC-V with the atomic extension (`riscv32imac-unknown-none-elf`: ESP32-C6
//! and ESP32-H2), and for the two classes with no compare-and-swap at all
//! (`riscv32imc-unknown-none-elf`: ESP32-C3 and ESP32-C2; `thumbv6m-none-eabi`:
//! every Cortex-M0). On those last two the firmware must link a
//! critical-section implementation, because the store handle's reference count
//! and mls-rs's own locks are built on one; see [`SharedStore`].
//!
//! Compiling is all that is claimed. Nothing here is linked into an image or
//! run on a board by CI. ESP32-C3 and ESP32-C2 cannot yet be built on esp-hal
//! at all: mls-rs selects portable-atomic's `critical-section` backend on them,
//! esp-hal selects `unsafe-assume-single-core`, and portable-atomic refuses the
//! pair.
extern crate alloc;
extern crate std;
pub use ;
pub use ;
pub use CIPHERSUITE;
pub use ;
pub use ;
pub use MemoryStore;