Skip to main content

logicaffeine_compile/concurrency/
pnp.rs

1//! `PNP` — the information-theoretic true one-time pad tier, the last resort should
2//! computational cryptography fall (the `P = NP` scenario the name marks).
3//!
4//! Every other suite in [`super::channel`] — `Classic`, `Hybrid`, `PQ`, `PQ-Max` — rests on a
5//! hardness assumption (a lattice, a factoring, a discrete-log problem). If those assumptions
6//! collapse, so does the secrecy. This tier does not: a Vernam one-time pad is *perfectly secret*
7//! by Shannon's theorem, unconditionally, against a computationally unbounded adversary. The price
8//! Shannon exacts is exact and unavoidable — one truly random pad byte per plaintext byte, used
9//! once and never again — so this is a break-glass tier for crown-jewel traffic, not the bulk path.
10//!
11//! Two constraints define the design:
12//!
13//! 1. **A true pad, never a stream.** The pad is not grown from a seed by a PRG — that would be a
14//!    stream cipher (computationally secure, and so it *would* fall with `P = NP`, defeating the
15//!    entire purpose). The pad is pre-provisioned, externally-sourced true randomness, shared out of
16//!    band. "Rotation" is a synchronized cursor advancing through that pool, consuming a fresh,
17//!    never-reused segment (a "cover") per message.
18//! 2. **No randomness at runtime.** [`PnpSuite::seal`] / [`PnpSuite::open`] draw zero entropy — they
19//!    are pure deterministic functions of `(pad, cursor, plaintext)`. All randomness is the pad. This
20//!    also makes sealed traffic replayable under the interpreter's determinism model.
21//!
22//! The pad is quality-gated at provisioning: a real pad is *incompressible* (`K(pad) ≈ |pad|`), so
23//! [`PadPool::shared`] rejects any pool the [`logicaffeine_proof::ait`] classifier can compress — a
24//! structured "pad" is a weak pad. (The connection is exact: a PRG-grown pad is by definition
25//! low-Kolmogorov-complexity, which is precisely what the classifier detects.)
26//!
27//! Confidentiality alone is not enough: XOR is malleable — flip a ciphertext bit and the plaintext
28//! bit flips, undetected. So each cover carries a **one-time Wegman–Carter MAC** (Poly1305 keyed by
29//! fresh pad bytes, [`logicaffeine_system::aead::poly1305`]); because the key is one-time pad
30//! material, the authentication is *also* information-theoretic, not computational.
31//!
32//! **Speed.** `seal` writes the plaintext straight into the frame buffer and XORs the pad in place
33//! (one pass, LLVM-vectorized), then MACs a single *contiguous* region — no intermediate ciphertext
34//! copy, one allocation. Throughput is therefore bounded by Poly1305 (which has an AVX2 path) plus a
35//! memcpy-speed XOR: a one-time pad is cheap when the codec never copies the payload twice.
36//!
37//! **The next pad.** Net-new pad entropy cannot be produced inside the channel (Shannon), so the next
38//! pad is always fresh out-of-band randomness. What the codec provides is a *seamless, authenticated
39//! handoff*: each frame is tagged with its pad **epoch**; when a pool nears exhaustion a peer emits an
40//! authenticated **roll** cover (a kind-tagged cover, MAC'd by the *current* pad) committing to the
41//! next epoch's id and a hash of the next pad, so both sides confirm they hold identical bytes before
42//! switching. The convergent *catalog* of epochs is a natural fit for the `logicaffeine_data::crdt`
43//! layer (an OR-Map of epoch → commitment, a version-vector consumption frontier per actor); the
44//! rule that keeps CRDTs safe here is that pad allocation stays partitioned (one writer per pad byte),
45//! so the CRDT only ever *records* a frontier, never *arbitrates* an allocation (which would be a
46//! two-time pad).
47//!
48//! Frames ride the [`super::channel`] envelope:
49//!
50//! ```text
51//! [ CHAN_MAGIC | CHAN_VER | SUITE_PNP | kind:u8 | epoch:u32 | offset:u64 | len:u32 | ct(len) | tag:16 ]
52//! ```
53
54use std::collections::BTreeSet;
55use std::sync::atomic::{AtomicU64, Ordering};
56use std::sync::{Arc, Mutex};
57
58use logicaffeine_proof::ait::{classify_bytes, CompressibilityClass};
59use logicaffeine_system::aead::poly1305;
60use logicaffeine_system::keccak::sha3_256_bytes;
61
62use super::channel::{ActiveSession, CHAN_HEADER_LEN, CHAN_MAGIC, CHAN_VER, SUITE_PNP};
63
64/// The one-time Poly1305 MAC key length — 32 pad bytes are consumed per cover for authentication,
65/// on top of the `len` bytes consumed for the XOR keystream.
66pub const MAC_KEY_LEN: usize = 32;
67
68/// The authenticator tag length (Poly1305).
69pub const TAG_LEN: usize = 16;
70
71/// Body field: cover kind ([`KIND_DATA`] / [`KIND_ROLL`]).
72const KIND_LEN: usize = 1;
73/// Body field: pad epoch this cover draws from.
74const EPOCH_LEN: usize = 4;
75/// Body field: half-local pad offset of the cover.
76const OFF_LEN: usize = 8;
77/// Body field: payload length.
78const LEN_LEN: usize = 4;
79/// The fixed body preamble before the payload: `kind ‖ epoch ‖ offset ‖ len`.
80const PREAMBLE: usize = KIND_LEN + EPOCH_LEN + OFF_LEN + LEN_LEN;
81
82/// A data cover: the payload is application ciphertext.
83const KIND_DATA: u8 = 0;
84/// A roll cover: the payload is `next_epoch:u32 ‖ next_commitment:32` — the authenticated handoff.
85const KIND_ROLL: u8 = 1;
86
87/// The default receiver reorder/replay window, in pad bytes. A cover whose offset is older than
88/// `newest_end - RECV_WINDOW_BYTES` is refused: it is either a replay of long-consumed pad or so
89/// far out of order the window can no longer prove it was not already accepted. Forward jumps (a
90/// dropped cover skipped) are always fine — they consume fresh pad, never reused pad.
91pub const RECV_WINDOW_BYTES: u64 = 1 << 20;
92
93/// Which end of the shared pool this peer is. The pool is split identically on both peers into two
94/// directional halves so the two directions never draw overlapping pad — the same reasoning behind
95/// [`super::channel::derive_aead_key`]'s `i2r` / `r2i` split, made physical.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum Role {
98    /// The peer that draws its *send* pad from the first half (`i2r`).
99    Initiator,
100    /// The peer that draws its *send* pad from the second half (`r2i`).
101    Responder,
102}
103
104/// Why a pad pool was refused at provisioning.
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum PadError {
107    /// The pool is too small to split into two directional halves.
108    TooSmall,
109    /// The pool is compressible — it carries exploitable structure and so is not truly random. A
110    /// real one-time pad is incompressible; a structured pool is a weak pool and is fail-closed.
111    Compressible,
112}
113
114/// The durable record of how far the send cursor has advanced — the single piece of state whose
115/// loss would be catastrophic, because reissuing a consumed offset is a two-time pad. An
116/// implementation must make [`PadLedgerStore::commit_cursor`] durable (fsync) *before* the caller
117/// emits the sealed frame, so a crash can only ever *waste* pad, never *reuse* it.
118pub trait PadLedgerStore: Send + Sync {
119    /// The highest cursor durably committed so far (0 if the pad is fresh).
120    fn load_cursor(&self) -> u64;
121    /// Durably record that the send cursor has advanced to `cursor`.
122    fn commit_cursor(&self, cursor: u64);
123}
124
125/// The default in-memory, non-durable ledger: a process-lifetime cursor. Correct for ephemeral
126/// sessions; for crash-safety across restarts install a durable store via [`PnpSuite::with_ledger`].
127#[derive(Default)]
128pub struct MemLedger {
129    cursor: AtomicU64,
130}
131
132impl PadLedgerStore for MemLedger {
133    fn load_cursor(&self) -> u64 {
134        self.cursor.load(Ordering::Relaxed)
135    }
136    fn commit_cursor(&self, cursor: u64) {
137        self.cursor.store(cursor, Ordering::Relaxed);
138    }
139}
140
141/// A crash-safe file-backed ledger: the send cursor is written to a temp file, fsync'd, then renamed
142/// over the target, so a restart resumes past every offset ever handed to a caller and a crash leaves
143/// either the old cursor or the new one, never a torn value. Native-only (the relay path is
144/// native-only); a browser peer would journal the cursor through the OPFS VFS.
145#[cfg(not(target_arch = "wasm32"))]
146pub struct FileLedger {
147    path: std::path::PathBuf,
148}
149
150#[cfg(not(target_arch = "wasm32"))]
151impl FileLedger {
152    /// A ledger persisting the cursor at `path` (created on first commit).
153    pub fn new(path: impl Into<std::path::PathBuf>) -> Self {
154        Self { path: path.into() }
155    }
156}
157
158#[cfg(not(target_arch = "wasm32"))]
159impl PadLedgerStore for FileLedger {
160    fn load_cursor(&self) -> u64 {
161        match std::fs::read(&self.path) {
162            Ok(bytes) if bytes.len() == 8 => u64::from_le_bytes(bytes.try_into().unwrap()),
163            _ => 0,
164        }
165    }
166    fn commit_cursor(&self, cursor: u64) {
167        use std::io::Write;
168        let tmp = self.path.with_extension("tmp");
169        let mut f = std::fs::File::create(&tmp).expect("pnp ledger: create temp");
170        f.write_all(&cursor.to_le_bytes()).expect("pnp ledger: write");
171        f.sync_all().expect("pnp ledger: fsync");
172        std::fs::rename(&tmp, &self.path).expect("pnp ledger: rename");
173    }
174}
175
176/// The receiver's replay/reorder guard over one directional half. It never affects secrecy (the
177/// sender's monotonic cursor guarantees pad is never reused for *encryption*); it exists so a peer
178/// accepts each cover at most once and tolerates bounded reordering.
179struct RecvWindow {
180    consumed: BTreeSet<u64>,
181    newest_end: u64,
182    window: u64,
183}
184
185impl RecvWindow {
186    fn new(window: u64) -> Self {
187        Self { consumed: BTreeSet::new(), newest_end: 0, window }
188    }
189    fn floor(&self) -> u64 {
190        self.newest_end.saturating_sub(self.window)
191    }
192    fn acceptable(&self, offset: u64) -> bool {
193        !self.consumed.contains(&offset) && offset >= self.floor()
194    }
195    fn accept(&mut self, offset: u64, end: u64) {
196        self.consumed.insert(offset);
197        if end > self.newest_end {
198            self.newest_end = end;
199        }
200        let floor = self.floor();
201        while let Some(&first) = self.consumed.iter().next() {
202            if first < floor {
203                self.consumed.remove(&first);
204            } else {
205                break;
206            }
207        }
208    }
209}
210
211/// A keyed one-time-pad channel over one directional half of a [`PadPool`] at one pad epoch. A peer
212/// *seals* with the suite for its send direction and *opens* with the suite for its receive
213/// direction; because the two directions are different pad halves, a peer never seals and opens over
214/// the same bytes.
215///
216/// State is behind interior mutability so the suite is used through a shared `&self` (as
217/// [`super::channel::PqSuite`] is): [`PnpSuite::seal`] serializes cursor reservation under a lock and
218/// commits the ledger before returning a frame; [`PnpSuite::open`] tracks consumed covers.
219pub struct PnpSuite {
220    pad: Arc<[u8]>,
221    base: usize,
222    len: usize,
223    epoch: u32,
224    cursor: Mutex<u64>,
225    ledger: Arc<dyn PadLedgerStore>,
226    recv: Mutex<RecvWindow>,
227}
228
229impl PnpSuite {
230    fn over(pad: Arc<[u8]>, base: usize, len: usize, epoch: u32) -> Self {
231        Self {
232            pad,
233            base,
234            len,
235            epoch,
236            cursor: Mutex::new(0),
237            ledger: Arc::new(MemLedger::default()),
238            recv: Mutex::new(RecvWindow::new(RECV_WINDOW_BYTES)),
239        }
240    }
241
242    /// Install a durable ledger, resuming the send cursor from it (crash-safety across restarts).
243    pub fn with_ledger(self, ledger: Arc<dyn PadLedgerStore>) -> Self {
244        let resumed = ledger.load_cursor();
245        *self.cursor.lock().unwrap() = resumed;
246        Self { ledger, ..self }
247    }
248
249    /// Set the receiver reorder/replay window, in pad bytes.
250    pub fn with_recv_window(self, bytes: u64) -> Self {
251        *self.recv.lock().unwrap() = RecvWindow::new(bytes);
252        self
253    }
254
255    /// The pad epoch this suite draws from.
256    pub fn epoch(&self) -> u32 {
257        self.epoch
258    }
259
260    /// Pad bytes remaining in the send direction — the low-water gauge. When this reaches zero,
261    /// [`PnpSuite::seal`] fails closed.
262    pub fn send_remaining(&self) -> usize {
263        let cursor = *self.cursor.lock().unwrap();
264        self.len.saturating_sub(cursor as usize)
265    }
266
267    /// Whether the send pad has dropped below `threshold` bytes — the signal to provision and roll to
268    /// the next epoch before exhaustion.
269    pub fn is_low(&self, threshold: usize) -> bool {
270        self.send_remaining() < threshold
271    }
272
273    /// Seal `blob` into a fresh data cover. `None` — fail-closed — when the pad is exhausted.
274    pub fn seal(&self, blob: &[u8]) -> Option<Vec<u8>> {
275        self.seal_kind(KIND_DATA, blob)
276    }
277
278    /// Seal an authenticated **roll** cover announcing the next pad epoch and a commitment (hash) to
279    /// its bytes, using the current pad's one-time MAC. The peer confirms via [`PnpSuite::open_roll`]
280    /// that it holds the identical next pad before switching. `None` if the current pad is exhausted.
281    pub fn seal_roll(&self, next_epoch: u32, next_commitment: &[u8; 32]) -> Option<Vec<u8>> {
282        let mut payload = Vec::with_capacity(EPOCH_LEN + 32);
283        payload.extend_from_slice(&next_epoch.to_le_bytes());
284        payload.extend_from_slice(next_commitment);
285        self.seal_kind(KIND_ROLL, &payload)
286    }
287
288    fn seal_kind(&self, kind: u8, blob: &[u8]) -> Option<Vec<u8>> {
289        let len = blob.len();
290        if len > u32::MAX as usize {
291            return None;
292        }
293        let need = (MAC_KEY_LEN + len) as u64;
294
295        let offset = {
296            let mut cursor = self.cursor.lock().unwrap();
297            let offset = *cursor;
298            let end = offset.checked_add(need)?;
299            if end > self.len as u64 {
300                return None; // exhausted — fail closed
301            }
302            // Durable before emit: a crash after this can only waste the reserved pad, never reuse it.
303            self.ledger.commit_cursor(end);
304            *cursor = end;
305            offset
306        };
307
308        let seg = self.base + offset as usize;
309        let mac_key: [u8; MAC_KEY_LEN] = self.pad[seg..seg + MAC_KEY_LEN].try_into().ok()?;
310
311        let mut out = Vec::with_capacity(CHAN_HEADER_LEN + PREAMBLE + len + TAG_LEN);
312        out.push(CHAN_MAGIC);
313        out.push(CHAN_VER);
314        out.extend_from_slice(&SUITE_PNP.to_le_bytes());
315        out.push(kind);
316        out.extend_from_slice(&self.epoch.to_le_bytes());
317        out.extend_from_slice(&offset.to_le_bytes());
318        out.extend_from_slice(&(len as u32).to_le_bytes());
319        // Write ciphertext = plaintext ⊕ pad straight into the frame buffer via a vectorizable slice
320        // loop (the shape LLVM turns into SIMD): grow with a cheap memset, then overwrite in one pass.
321        // No separate plaintext copy, no intermediate ciphertext allocation.
322        let payload_start = CHAN_HEADER_LEN + PREAMBLE;
323        out.resize(payload_start + len, 0);
324        let xor = &self.pad[seg + MAC_KEY_LEN..seg + MAC_KEY_LEN + len];
325        for ((d, b), p) in out[payload_start..payload_start + len].iter_mut().zip(blob).zip(xor) {
326            *d = b ^ p;
327        }
328
329        // MAC the contiguous body: kind ‖ epoch ‖ offset ‖ len ‖ ciphertext — no copy.
330        let tag = poly1305(&mac_key, &out[CHAN_HEADER_LEN..CHAN_HEADER_LEN + PREAMBLE + len]);
331        out.extend_from_slice(&tag);
332        Some(out)
333    }
334
335    /// Open a data cover, or `None` on any malformed / tampered / replayed / wrong-epoch frame.
336    pub fn open(&self, frame: &[u8]) -> Option<Vec<u8>> {
337        self.open_kind(KIND_DATA, frame)
338    }
339
340    /// Open a **roll** cover, returning the announced `(next_epoch, next_commitment)`. `None` on any
341    /// tampered / wrong-epoch / non-roll frame.
342    pub fn open_roll(&self, frame: &[u8]) -> Option<(u32, [u8; 32])> {
343        let pt = self.open_kind(KIND_ROLL, frame)?;
344        if pt.len() != EPOCH_LEN + 32 {
345            return None;
346        }
347        let next_epoch = u32::from_le_bytes(pt[0..EPOCH_LEN].try_into().ok()?);
348        let commitment: [u8; 32] = pt[EPOCH_LEN..EPOCH_LEN + 32].try_into().ok()?;
349        Some((next_epoch, commitment))
350    }
351
352    fn open_kind(&self, want_kind: u8, frame: &[u8]) -> Option<Vec<u8>> {
353        if frame.len() < CHAN_HEADER_LEN || frame[0] != CHAN_MAGIC || frame[1] != CHAN_VER {
354            return None;
355        }
356        if u16::from_le_bytes([frame[2], frame[3]]) != SUITE_PNP {
357            return None;
358        }
359        let body = &frame[CHAN_HEADER_LEN..];
360        if body.len() < PREAMBLE + TAG_LEN {
361            return None;
362        }
363        if body[0] != want_kind {
364            return None;
365        }
366        let epoch = u32::from_le_bytes(body[KIND_LEN..KIND_LEN + EPOCH_LEN].try_into().ok()?);
367        if epoch != self.epoch {
368            return None; // a cover from a different pad epoch
369        }
370        let off_at = KIND_LEN + EPOCH_LEN;
371        let offset = u64::from_le_bytes(body[off_at..off_at + OFF_LEN].try_into().ok()?);
372        let len = u32::from_le_bytes(body[off_at + OFF_LEN..PREAMBLE].try_into().ok()?) as usize;
373        if body.len() != PREAMBLE + len + TAG_LEN {
374            return None;
375        }
376        let payload = &body[PREAMBLE..PREAMBLE + len];
377        let tag = &body[PREAMBLE + len..];
378
379        let end = offset.checked_add((MAC_KEY_LEN + len) as u64)?;
380        if end > self.len as u64 {
381            return None;
382        }
383        if !self.recv.lock().unwrap().acceptable(offset) {
384            return None; // replay or beyond the reorder window
385        }
386
387        let seg = self.base + offset as usize;
388        let mac_key: [u8; MAC_KEY_LEN] = self.pad[seg..seg + MAC_KEY_LEN].try_into().ok()?;
389        let expect = poly1305(&mac_key, &body[0..PREAMBLE + len]);
390        if !ct_eq(&expect, tag) {
391            return None; // tamper — leave the window untouched so the genuine cover can still arrive
392        }
393
394        let xor = &self.pad[seg + MAC_KEY_LEN..seg + MAC_KEY_LEN + len];
395        let mut plain = vec![0u8; len];
396        for ((d, c), p) in plain.iter_mut().zip(payload).zip(xor) {
397            *d = c ^ p;
398        }
399        self.recv.lock().unwrap().accept(offset, end);
400        Some(plain)
401    }
402}
403
404/// A live bidirectional PNP session for the interpreter's send/receive seam: it seals outbound covers
405/// with this peer's send half and opens inbound covers with its receive half. Install it on the
406/// channel via [`super::channel::with_session`] / [`super::channel::install_session`] and every
407/// `Send` / receive on that thread is one-time-pad protected — fail-closed on exhaustion.
408pub struct PnpSession {
409    send: PnpSuite,
410    recv: PnpSuite,
411}
412
413impl PnpSession {
414    /// A session from an explicit send/receive suite pair.
415    pub fn new(send: PnpSuite, recv: PnpSuite) -> Self {
416        Self { send, recv }
417    }
418    /// The send half (seals outbound covers).
419    pub fn send(&self) -> &PnpSuite {
420        &self.send
421    }
422    /// The receive half (opens inbound covers).
423    pub fn recv(&self) -> &PnpSuite {
424        &self.recv
425    }
426}
427
428impl ActiveSession for PnpSession {
429    fn seal(&self, bytes: &[u8]) -> Option<Vec<u8>> {
430        self.send.seal(bytes)
431    }
432    fn open(&self, bytes: &[u8]) -> Option<Vec<u8>> {
433        self.recv.open(bytes)
434    }
435}
436
437/// A shared, pre-provisioned pool of true random bytes for one pad epoch, split into two directional
438/// halves. Both peers build an identical pool from the same out-of-band material and split it the
439/// same way, so each direction is a private, agreed pad both sides index into.
440pub struct PadPool {
441    pad: Arc<[u8]>,
442    split: usize,
443    epoch: u32,
444}
445
446impl PadPool {
447    /// Build a pool from shared true-random material, refusing it (fail-closed) if it is too small to
448    /// split or if the [`logicaffeine_proof::ait`] classifier finds it compressible — i.e. it is not
449    /// actually random. This is the pad-quality gate: a weak pad is never silently accepted.
450    pub fn shared(pad: Vec<u8>) -> Result<PadPool, PadError> {
451        if pad.len() < 2 {
452            return Err(PadError::TooSmall);
453        }
454        if classify_bytes(&pad).class != CompressibilityClass::Incompressible {
455            return Err(PadError::Compressible);
456        }
457        Ok(Self::shared_unchecked(pad))
458    }
459
460    /// Build a pool without the incompressibility gate — for trusted provisioning that has already
461    /// validated the material, and for tests that craft pad contents directly.
462    pub fn shared_unchecked(pad: Vec<u8>) -> PadPool {
463        let split = pad.len() / 2;
464        PadPool { pad: pad.into(), split, epoch: 0 }
465    }
466
467    /// Label this pool with a pad epoch (the id peers agree on for this pad in the rollover sequence).
468    pub fn with_epoch(mut self, epoch: u32) -> PadPool {
469        self.epoch = epoch;
470        self
471    }
472
473    /// The pad epoch of this pool.
474    pub fn epoch(&self) -> u32 {
475        self.epoch
476    }
477
478    /// A binding commitment to this pad — `SHA3-256(epoch ‖ pad)`. Both peers compute it from their
479    /// loaded material to confirm, over the authenticated [`PnpSuite::seal_roll`] handoff, that they
480    /// hold the identical next pad. Preimage-resistant, so it is safe to exchange; it reveals nothing
481    /// about the pad bytes.
482    pub fn commitment(&self) -> [u8; 32] {
483        let mut input = Vec::with_capacity(EPOCH_LEN + self.pad.len());
484        input.extend_from_slice(&self.epoch.to_le_bytes());
485        input.extend_from_slice(&self.pad);
486        sha3_256_bytes(&input)
487    }
488
489    /// Bytes available in each directional half.
490    pub fn direction_len(&self) -> usize {
491        self.split.min(self.pad.len() - self.split)
492    }
493
494    fn half(&self, first: bool) -> (usize, usize) {
495        if first {
496            (0, self.split)
497        } else {
498            (self.split, self.pad.len() - self.split)
499        }
500    }
501
502    /// The suite this peer seals outbound covers with (its send half).
503    pub fn send_suite(&self, role: Role) -> PnpSuite {
504        let (base, len) = self.half(role == Role::Initiator);
505        PnpSuite::over(self.pad.clone(), base, len, self.epoch)
506    }
507
508    /// The suite this peer opens inbound covers with (its receive half — the peer's send half).
509    pub fn recv_suite(&self, role: Role) -> PnpSuite {
510        let (base, len) = self.half(role == Role::Responder);
511        PnpSuite::over(self.pad.clone(), base, len, self.epoch)
512    }
513
514    /// A live [`PnpSession`] for `role` — seals with this peer's send half, opens with its receive
515    /// half — ready to install on the channel's active-session seam so the interpreter's Send/receive
516    /// path is one-time-pad protected end-to-end.
517    pub fn session(&self, role: Role) -> PnpSession {
518        PnpSession::new(self.send_suite(role), self.recv_suite(role))
519    }
520}
521
522/// The cover kind of a `PNP` frame ([`KIND_DATA`] / [`KIND_ROLL`]), or `None` if it is not a
523/// well-formed `PNP` frame — lets a receive loop dispatch data vs. handoff without opening.
524pub fn frame_kind(frame: &[u8]) -> Option<u8> {
525    if frame.len() < CHAN_HEADER_LEN + KIND_LEN
526        || frame[0] != CHAN_MAGIC
527        || frame[1] != CHAN_VER
528        || u16::from_le_bytes([frame[2], frame[3]]) != SUITE_PNP
529    {
530        return None;
531    }
532    Some(frame[CHAN_HEADER_LEN])
533}
534
535/// The half-local pad offset a `PNP` frame draws from, or `None` if it is not a well-formed `PNP`
536/// frame. Useful for monitoring the pad high-water mark without opening the cover.
537pub fn frame_offset(frame: &[u8]) -> Option<u64> {
538    let off_at = CHAN_HEADER_LEN + KIND_LEN + EPOCH_LEN;
539    if frame.len() < off_at + OFF_LEN
540        || frame[0] != CHAN_MAGIC
541        || frame[1] != CHAN_VER
542        || u16::from_le_bytes([frame[2], frame[3]]) != SUITE_PNP
543    {
544        return None;
545    }
546    Some(u64::from_le_bytes(frame[off_at..off_at + OFF_LEN].try_into().ok()?))
547}
548
549/// Constant-time tag comparison — no early return on the first differing byte.
550fn ct_eq(a: &[u8], b: &[u8]) -> bool {
551    if a.len() != b.len() {
552        return false;
553    }
554    let mut diff = 0u8;
555    for (x, y) in a.iter().zip(b) {
556        diff |= x ^ y;
557    }
558    diff == 0
559}
560
561#[cfg(test)]
562mod tests {
563    use super::*;
564    use super::super::marshal::{message_to_wire_with, WireCodec, WireIntegrity};
565    use crate::interpreter::RuntimeValue;
566
567    /// The same deterministic PRNG the sibling channel tests use — reproducible "true-random" pad
568    /// material with full byte entropy (the classifier rates it incompressible).
569    fn splitmix64(state: &mut u64) -> u64 {
570        *state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
571        let mut z = *state;
572        z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
573        z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
574        z ^ (z >> 31)
575    }
576
577    fn random_pad(len: usize, seed: u64) -> Vec<u8> {
578        let mut s = seed;
579        (0..len).map(|_| splitmix64(&mut s) as u8).collect()
580    }
581
582    const PAYLOAD_START: usize = CHAN_HEADER_LEN + PREAMBLE;
583
584    /// The ciphertext/payload bytes carried in a PNP frame body.
585    fn ciphertext_of(frame: &[u8]) -> Vec<u8> {
586        let off_at = CHAN_HEADER_LEN + KIND_LEN + EPOCH_LEN + OFF_LEN;
587        let len = u32::from_le_bytes(frame[off_at..off_at + LEN_LEN].try_into().unwrap()) as usize;
588        frame[PAYLOAD_START..PAYLOAD_START + len].to_vec()
589    }
590
591    /// The `[offset, offset + covered)` pad range a frame consumes.
592    fn cover_range(frame: &[u8]) -> (u64, u64) {
593        let off = frame_offset(frame).unwrap();
594        let len = ciphertext_of(frame).len();
595        (off, off + (MAC_KEY_LEN + len) as u64)
596    }
597
598    fn ranges_disjoint(ranges: &[(u64, u64)]) -> bool {
599        for i in 0..ranges.len() {
600            for j in (i + 1)..ranges.len() {
601                let (a0, a1) = ranges[i];
602                let (b0, b1) = ranges[j];
603                if a0 < b1 && b0 < a1 {
604                    return false;
605                }
606            }
607        }
608        true
609    }
610
611    #[test]
612    fn roundtrip_seal_open() {
613        let pool = PadPool::shared(random_pad(4096, 0x1111_2222_3333_4444)).expect("incompressible pad");
614        let sender = pool.send_suite(Role::Initiator);
615        let receiver = pool.recv_suite(Role::Responder);
616        let msg = b"attack at dawn";
617        let frame = sender.seal(msg).expect("pad available");
618        assert_eq!(receiver.open(&frame).as_deref(), Some(&msg[..]), "cover round-trips exactly");
619    }
620
621    #[test]
622    fn ciphertext_is_plaintext_xor_pad() {
623        let keystream = [0xA5u8, 0x5A, 0xFF, 0x00, 0x13, 0x37, 0xBE, 0xEF];
624        let mut pad = vec![7u8; MAC_KEY_LEN];
625        pad.extend_from_slice(&keystream);
626        pad.extend(std::iter::repeat(0u8).take(64));
627        let pool = PadPool::shared_unchecked(pad);
628        let sender = pool.send_suite(Role::Initiator);
629        let plaintext = [1u8, 2, 3, 4, 5, 6, 7, 8];
630        let frame = sender.seal(&plaintext).expect("pad available");
631        let ct = ciphertext_of(&frame);
632        let expect: Vec<u8> = plaintext.iter().zip(&keystream).map(|(p, k)| p ^ k).collect();
633        assert_eq!(ct, expect, "ciphertext is the exact plaintext ⊕ pad");
634        assert_ne!(ct, plaintext.to_vec(), "ciphertext is not the plaintext");
635    }
636
637    #[test]
638    fn seal_matches_independent_reference() {
639        // Reconstruct the exact wire bytes with a wholly independent implementation and demand
640        // byte-for-byte equality — pins the frame format and the XOR/MAC construction.
641        let pad = random_pad(1024, 0xCAFE_F00D_1234_5678);
642        let pool = PadPool::shared_unchecked(pad.clone()).with_epoch(0);
643        let sender = pool.send_suite(Role::Initiator); // first half, base 0
644        let msg = b"reference vector";
645        let frame = sender.seal(msg).expect("pad");
646
647        // Independent reference over the first half (base 0, offset 0).
648        let mac_key: [u8; 32] = pad[0..32].try_into().unwrap();
649        let ks = &pad[32..32 + msg.len()];
650        let ct: Vec<u8> = msg.iter().zip(ks).map(|(m, k)| m ^ k).collect();
651        let mut expect = Vec::new();
652        expect.push(CHAN_MAGIC);
653        expect.push(CHAN_VER);
654        expect.extend_from_slice(&SUITE_PNP.to_le_bytes());
655        expect.push(KIND_DATA);
656        expect.extend_from_slice(&0u32.to_le_bytes()); // epoch
657        expect.extend_from_slice(&0u64.to_le_bytes()); // offset
658        expect.extend_from_slice(&(msg.len() as u32).to_le_bytes());
659        expect.extend_from_slice(&ct);
660        let tag = logicaffeine_system::aead::poly1305(&mac_key, &expect[CHAN_HEADER_LEN..]);
661        expect.extend_from_slice(&tag);
662        assert_eq!(frame, expect, "sealed frame matches the independent reference byte-for-byte");
663    }
664
665    #[test]
666    fn never_reuses_pad_bytes() {
667        let pool = PadPool::shared(random_pad(8192, 0xDEAD_BEEF_0000_0001)).expect("pad");
668        let sender = pool.send_suite(Role::Initiator);
669        let mut ranges = Vec::new();
670        let mut s = 0xABCD_1234u64;
671        for _ in 0..200 {
672            let len = (splitmix64(&mut s) % 17) as usize;
673            let msg: Vec<u8> = (0..len).map(|_| splitmix64(&mut s) as u8).collect();
674            if let Some(frame) = sender.seal(&msg) {
675                ranges.push(cover_range(&frame));
676            }
677        }
678        assert!(!ranges.is_empty(), "some covers were sealed");
679        assert!(ranges_disjoint(&ranges), "no two covers share a single pad byte");
680        for w in ranges.windows(2) {
681            assert!(w[0].0 < w[1].0, "cursor is monotonic");
682            assert!(w[0].1 <= w[1].0, "covers are laid down contiguously, never overlapping");
683        }
684    }
685
686    #[test]
687    fn concurrent_seals_never_overlap() {
688        let pool = PadPool::shared(random_pad(1 << 16, 0x0F0F_0F0F_1234_5678)).expect("pad");
689        let sender = Arc::new(pool.send_suite(Role::Initiator));
690        let mut handles = Vec::new();
691        for t in 0..8 {
692            let s = sender.clone();
693            handles.push(std::thread::spawn(move || {
694                let mut out = Vec::new();
695                for i in 0..64 {
696                    let msg = [(t as u8), (i as u8), 0xEE];
697                    if let Some(frame) = s.seal(&msg) {
698                        out.push(cover_range(&frame));
699                    }
700                }
701                out
702            }));
703        }
704        let mut ranges = Vec::new();
705        for h in handles {
706            ranges.extend(h.join().unwrap());
707        }
708        assert_eq!(ranges.len(), 8 * 64, "every concurrent seal succeeded");
709        assert!(ranges_disjoint(&ranges), "concurrent covers are pairwise disjoint");
710    }
711
712    #[test]
713    fn directional_split_no_overlap() {
714        let mut pad = random_pad(2048, 0x5555_6666_7777_8888);
715        let mid = pad.len() / 2;
716        for b in &mut pad[..mid] {
717            *b |= 0x01;
718        }
719        for b in &mut pad[mid..] {
720            *b &= 0xFE;
721        }
722        let pool = PadPool::shared_unchecked(pad);
723
724        let init_send = pool.send_suite(Role::Initiator);
725        let resp_send = pool.send_suite(Role::Responder);
726        let msg = b"same plaintext";
727        let f_i = init_send.seal(msg).expect("pad");
728        let f_r = resp_send.seal(msg).expect("pad");
729        assert_ne!(ciphertext_of(&f_i), ciphertext_of(&f_r), "different halves ⇒ different ciphertext");
730
731        assert_eq!(pool.recv_suite(Role::Responder).open(&f_i).as_deref(), Some(&msg[..]), "matching direction opens");
732        assert!(pool.recv_suite(Role::Initiator).open(&f_i).is_none(), "wrong direction cannot open");
733    }
734
735    #[test]
736    fn bitflip_detected() {
737        let pool = PadPool::shared(random_pad(4096, 0x9999_AAAA_BBBB_CCCC)).expect("pad");
738        let sender = pool.send_suite(Role::Initiator);
739        let frame = sender.seal(b"integrity matters").expect("pad");
740
741        let mut ct_tamper = frame.clone();
742        ct_tamper[PAYLOAD_START] ^= 0x01;
743        assert!(pool.recv_suite(Role::Responder).open(&ct_tamper).is_none(), "flipped ciphertext bit caught");
744
745        let mut tag_tamper = frame.clone();
746        let last = tag_tamper.len() - 1;
747        tag_tamper[last] ^= 0x80;
748        assert!(pool.recv_suite(Role::Responder).open(&tag_tamper).is_none(), "flipped tag bit caught");
749
750        assert!(pool.recv_suite(Role::Responder).open(&frame).is_some(), "untampered cover opens");
751    }
752
753    #[test]
754    fn every_single_byte_corruption_is_rejected() {
755        // Absurd-robustness: for a batch of covers, flipping ANY single byte anywhere in the frame
756        // must make a fresh receiver reject it — no malleability, no accidental re-parse into a valid
757        // (offset,len) that still authenticates.
758        let pool = PadPool::shared(random_pad(1 << 15, 0x0BADF00D_5EED_1234)).expect("pad");
759        let sender = pool.send_suite(Role::Initiator);
760        let mut s = 0x1357_9BDF_2468_ACE0u64;
761        for _ in 0..40 {
762            let len = (splitmix64(&mut s) % 24) as usize;
763            let msg: Vec<u8> = (0..len).map(|_| splitmix64(&mut s) as u8).collect();
764            let frame = sender.seal(&msg).expect("pad");
765            // sanity: pristine opens
766            assert!(pool.recv_suite(Role::Responder).open(&frame).is_some());
767            for pos in 0..frame.len() {
768                for bit in [0x01u8, 0x80] {
769                    let mut bad = frame.clone();
770                    bad[pos] ^= bit;
771                    assert!(
772                        pool.recv_suite(Role::Responder).open(&bad).is_none(),
773                        "corrupting byte {pos} (bit {bit:#x}) must be rejected"
774                    );
775                }
776            }
777        }
778    }
779
780    #[test]
781    fn cross_pad_frame_is_rejected() {
782        let a = PadPool::shared(random_pad(4096, 0xAAAA_0000_1111_2222)).expect("pad A");
783        let b = PadPool::shared(random_pad(4096, 0xBBBB_3333_4444_5555)).expect("pad B");
784        let frame = a.send_suite(Role::Initiator).seal(b"for A only").expect("pad");
785        assert!(a.recv_suite(Role::Responder).open(&frame).is_some(), "A's receiver opens it");
786        assert!(b.recv_suite(Role::Responder).open(&frame).is_none(), "a foreign pad cannot open it");
787    }
788
789    #[test]
790    fn fuzz_roundtrip_many_sizes() {
791        let pool = PadPool::shared(random_pad(1 << 20, 0xF1F2_F3F4_F5F6_F7F8)).expect("pad");
792        let sender = pool.send_suite(Role::Initiator);
793        let receiver = pool.recv_suite(Role::Responder);
794        let mut s = 0x2222_4444_6666_8888u64;
795        let mut sealed = 0;
796        for _ in 0..3000 {
797            let len = (splitmix64(&mut s) % 300) as usize;
798            let msg: Vec<u8> = (0..len).map(|_| splitmix64(&mut s) as u8).collect();
799            match sender.seal(&msg) {
800                Some(frame) => {
801                    assert_eq!(receiver.open(&frame), Some(msg), "fuzz round-trip, len {len}");
802                    sealed += 1;
803                }
804                None => break, // exhausted
805            }
806        }
807        assert!(sealed > 100, "a healthy number of covers round-tripped ({sealed})");
808    }
809
810    #[test]
811    fn mac_key_is_one_time() {
812        let pool = PadPool::shared(random_pad(8192, 0x1212_3434_5656_7878)).expect("pad");
813        let sender = pool.send_suite(Role::Initiator);
814        let mut mac_ranges = Vec::new();
815        for i in 0..100u8 {
816            let frame = sender.seal(&[i, i, i]).expect("pad");
817            let off = frame_offset(&frame).unwrap();
818            mac_ranges.push((off, off + MAC_KEY_LEN as u64));
819        }
820        assert!(ranges_disjoint(&mac_ranges), "MAC keys are drawn from pairwise-disjoint pad ranges");
821    }
822
823    #[test]
824    fn replay_rejected() {
825        let pool = PadPool::shared(random_pad(4096, 0x2468_ACE0_1357_9BDF)).expect("pad");
826        let sender = pool.send_suite(Role::Initiator);
827        let receiver = pool.recv_suite(Role::Responder);
828        let frame = sender.seal(b"once and only once").expect("pad");
829        assert!(receiver.open(&frame).is_some(), "first delivery accepted");
830        assert!(receiver.open(&frame).is_none(), "exact replay of a consumed cover rejected");
831    }
832
833    #[test]
834    fn reorder_within_window_ok_beyond_window_rejected() {
835        let pool = PadPool::shared(random_pad(4096, 0x3141_5926_5358_9793)).expect("pad");
836        let sender = pool.send_suite(Role::Initiator);
837        let f0 = sender.seal(&[0u8; 8]).unwrap();
838        let f1 = sender.seal(&[1u8; 8]).unwrap();
839        let f2 = sender.seal(&[2u8; 8]).unwrap();
840        assert_eq!(frame_offset(&f0), Some(0));
841        assert_eq!(frame_offset(&f1), Some(40));
842        assert_eq!(frame_offset(&f2), Some(80));
843
844        let receiver = pool.recv_suite(Role::Responder).with_recv_window(100);
845        assert!(receiver.open(&f2).is_some(), "newest cover accepted (end 120)");
846        assert!(receiver.open(&f1).is_some(), "in-window reorder (offset 40 ≥ floor 20) accepted");
847        assert!(receiver.open(&f0).is_none(), "beyond-window cover (offset 0 < floor 20) rejected");
848    }
849
850    #[test]
851    fn drop_then_resync() {
852        let pool = PadPool::shared(random_pad(4096, 0xF00D_BABE_1234_5678)).expect("pad");
853        let sender = pool.send_suite(Role::Initiator);
854        let f0 = sender.seal(b"first").unwrap();
855        let _dropped = sender.seal(b"lost in flight").unwrap();
856        let f2 = sender.seal(b"third").unwrap();
857
858        let receiver = pool.recv_suite(Role::Responder);
859        assert_eq!(receiver.open(&f0).as_deref(), Some(&b"first"[..]), "first arrives");
860        assert_eq!(receiver.open(&f2).as_deref(), Some(&b"third"[..]), "third decodes despite the gap");
861    }
862
863    #[test]
864    fn exhaustion_fails_closed() {
865        let pool = PadPool::shared_unchecked(random_pad(2 * (MAC_KEY_LEN + 8), 0xC0DE_F00D_0BAD_BEEF));
866        let sender = pool.send_suite(Role::Initiator);
867        assert_eq!(sender.send_remaining(), MAC_KEY_LEN + 8);
868        assert!(sender.seal(&[0u8; 8]).is_some(), "the one cover the half holds is sealed");
869        assert_eq!(sender.send_remaining(), 0, "the half is now spent");
870        assert!(sender.is_low(1), "low-water tripped");
871        assert!(sender.seal(&[0u8; 1]).is_none(), "exhausted pad fails closed");
872        assert!(sender.seal(&[]).is_none(), "even an empty message needs a MAC key ⇒ still closed");
873    }
874
875    #[test]
876    #[cfg(not(target_arch = "wasm32"))]
877    fn cursor_persists_across_restart() {
878        let dir = tempfile::tempdir().expect("tempdir");
879        let path = dir.path().join("pnp.cursor");
880        let pad = random_pad(8192, 0x0BEE_F00D_1234_ABCD);
881
882        let end1 = {
883            let pool = PadPool::shared(pad.clone()).expect("pad");
884            let sender = pool.send_suite(Role::Initiator).with_ledger(Arc::new(FileLedger::new(&path)));
885            let f = sender.seal(b"before the crash").expect("pad");
886            cover_range(&f).1
887        };
888
889        let pool = PadPool::shared(pad).expect("pad");
890        let sender = pool.send_suite(Role::Initiator).with_ledger(Arc::new(FileLedger::new(&path)));
891        let f2 = sender.seal(b"after the restart").expect("pad");
892        let (off2, _) = cover_range(&f2);
893        assert!(off2 >= end1, "post-restart cover starts at or beyond the durable high-water ({off2} ≥ {end1})");
894    }
895
896    #[test]
897    fn seal_is_deterministic_no_runtime_randomness() {
898        let pad = random_pad(4096, 0xAAAA_5555_AAAA_5555);
899        let a = PadPool::shared(pad.clone()).unwrap().send_suite(Role::Initiator);
900        let b = PadPool::shared(pad).unwrap().send_suite(Role::Initiator);
901        let m = b"deterministic";
902        assert_eq!(a.seal(m), b.seal(m), "seal is a pure function of (pad, cursor, plaintext)");
903        let f1 = a.seal(m).unwrap();
904        let f2 = a.seal(m).unwrap();
905        assert_ne!(f1, f2, "successive covers draw fresh pad");
906    }
907
908    #[test]
909    fn pad_quality_gate_rejects_compressible() {
910        assert_eq!(PadPool::shared(vec![0u8; 4096]).err(), Some(PadError::Compressible), "zero pad refused");
911        let counter: Vec<u8> = (0..4096).map(|i| i as u8).collect();
912        assert_eq!(PadPool::shared(counter).err(), Some(PadError::Compressible), "counter pad refused");
913        assert_eq!(PadPool::shared(vec![1]).err(), Some(PadError::TooSmall), "tiny pool refused");
914        assert!(PadPool::shared(random_pad(4096, 0x7777_8888_9999_0000)).is_ok(), "true-random pad accepted");
915    }
916
917    #[test]
918    fn perfect_secrecy_leaks_only_length() {
919        let target_ct = [0x11u8, 0x22, 0x33, 0x44];
920        let p1 = [0xDEu8, 0xAD, 0xBE, 0xEF];
921        let p2 = [0x00u8, 0x01, 0x02, 0x03];
922
923        let build = |plaintext: &[u8]| {
924            let mut pad = vec![0x5Au8; MAC_KEY_LEN];
925            pad.extend(plaintext.iter().zip(&target_ct).map(|(p, c)| p ^ c));
926            pad.extend(std::iter::repeat(0u8).take(64));
927            let pool = PadPool::shared_unchecked(pad);
928            let frame = pool.send_suite(Role::Initiator).seal(plaintext).unwrap();
929            ciphertext_of(&frame)
930        };
931        assert_eq!(build(&p1), target_ct.to_vec(), "plaintext 1 seals to the target ciphertext under its pad");
932        assert_eq!(build(&p2), target_ct.to_vec(), "a different plaintext seals to the SAME ciphertext under its pad");
933        assert_ne!(p1, p2, "the two plaintexts are genuinely different");
934    }
935
936    #[test]
937    fn end_to_end_two_peers_real_wire_message() {
938        let material = random_pad(1 << 14, 0x1BAD_C0DE_F00D_FACE);
939        let initiator = PadPool::shared(material.clone()).expect("pad");
940        let responder = PadPool::shared(material).expect("pad");
941
942        let blob = message_to_wire_with("responder", &RuntimeValue::Int(42), WireCodec::Native, WireIntegrity::Checked)
943            .expect("encode");
944
945        let sealed = initiator.send_suite(Role::Initiator).seal(&blob).expect("pad");
946        assert_eq!(&sealed[..2], &[CHAN_MAGIC, CHAN_VER], "channel header present");
947        assert_eq!(&sealed[2..4], &SUITE_PNP.to_le_bytes(), "PNP suite id bound in the envelope");
948        assert_eq!(frame_kind(&sealed), Some(KIND_DATA), "a data cover");
949        assert_ne!(ciphertext_of(&sealed), blob, "body is ciphertext, not the raw blob");
950        assert_eq!(
951            responder.recv_suite(Role::Responder).open(&sealed).as_deref(),
952            Some(blob.as_slice()),
953            "responder opens the initiator's one-time-pad-sealed wire message"
954        );
955
956        let ack = responder.send_suite(Role::Responder).seal(b"ack").expect("pad");
957        assert_eq!(initiator.recv_suite(Role::Initiator).open(&ack).as_deref(), Some(&b"ack"[..]), "reverse direction opens");
958        assert_eq!(frame_offset(&sealed), Some(0));
959        assert_eq!(frame_offset(&ack), Some(0));
960    }
961
962    // ---- The next pad: epoch tagging + authenticated rollover handoff -----------------------------
963
964    #[test]
965    fn epoch_mismatch_rejected() {
966        // A cover sealed under epoch 2 is refused by a receiver on epoch 1 — pad epochs never mix.
967        let material = random_pad(4096, 0x0E0E_0E0E_1234_5678);
968        let e1 = PadPool::shared(material.clone()).unwrap().with_epoch(1);
969        let e2 = PadPool::shared(material).unwrap().with_epoch(2);
970        let frame = e2.send_suite(Role::Initiator).seal(b"epoch two").expect("pad");
971        assert_eq!(frame_kind(&frame), Some(KIND_DATA));
972        assert!(e1.recv_suite(Role::Responder).open(&frame).is_none(), "epoch-1 receiver rejects an epoch-2 cover");
973        assert!(e2.recv_suite(Role::Responder).open(&frame).is_some(), "the matching epoch opens it");
974    }
975
976    #[test]
977    fn pad_commitment_binds_bytes_and_epoch() {
978        let m = random_pad(4096, 0x9090_9090_ABAB_ABAB);
979        let a = PadPool::shared(m.clone()).unwrap().with_epoch(7);
980        let a2 = PadPool::shared(m.clone()).unwrap().with_epoch(7);
981        let diff_epoch = PadPool::shared(m).unwrap().with_epoch(8);
982        let diff_bytes = PadPool::shared(random_pad(4096, 0x1111_2222_3333_4444)).unwrap().with_epoch(7);
983        assert_eq!(a.commitment(), a2.commitment(), "same bytes + epoch ⇒ identical commitment");
984        assert_ne!(a.commitment(), diff_epoch.commitment(), "epoch is bound into the commitment");
985        assert_ne!(a.commitment(), diff_bytes.commitment(), "pad bytes are bound into the commitment");
986    }
987
988    #[test]
989    fn roll_handoff_roundtrip_and_authenticated() {
990        // Peers on epoch 0 provision epoch 1 out of band; the sender announces it via an authenticated
991        // roll cover; the receiver recovers the next epoch id + commitment and confirms it holds the
992        // identical next pad.
993        let pad0 = random_pad(1 << 13, 0x0000_1111_2222_3333);
994        let next_material = random_pad(1 << 13, 0x4444_5555_6666_7777);
995        let init0 = PadPool::shared(pad0.clone()).unwrap().with_epoch(0);
996        let resp0 = PadPool::shared(pad0).unwrap().with_epoch(0);
997        let next_on_init = PadPool::shared(next_material.clone()).unwrap().with_epoch(1);
998        let next_on_resp = PadPool::shared(next_material).unwrap().with_epoch(1);
999
1000        let roll = init0.send_suite(Role::Initiator).seal_roll(1, &next_on_init.commitment()).expect("pad");
1001        assert_eq!(frame_kind(&roll), Some(KIND_ROLL), "a roll cover");
1002        assert!(resp0.recv_suite(Role::Responder).open(&roll).is_none(), "a roll cover is NOT a data cover");
1003
1004        let (next_epoch, commitment) = resp0.recv_suite(Role::Responder).open_roll(&roll).expect("roll opens");
1005        assert_eq!(next_epoch, 1, "announced the next epoch");
1006        assert_eq!(commitment, next_on_resp.commitment(), "the receiver confirms it holds the identical next pad");
1007
1008        // Tamper ⇒ the handoff is rejected (no unauthenticated pad switch).
1009        let mut bad = roll.clone();
1010        bad[PAYLOAD_START] ^= 0x01;
1011        assert!(resp0.recv_suite(Role::Responder).open_roll(&bad).is_none(), "a tampered roll is rejected");
1012    }
1013
1014    #[test]
1015    fn chain_rollover_end_to_end() {
1016        // Deplete epoch 0, hand off to epoch 1, keep sending — a seamless "next pad".
1017        let pad0 = random_pad(2 * (MAC_KEY_LEN + 16), 0xEE11_EE22_EE33_EE44); // one small cover per half
1018        let next_material = random_pad(1 << 12, 0x55AA_55AA_55AA_55AA);
1019        let s_init0 = PadPool::shared_unchecked(pad0.clone()).with_epoch(0);
1020        let s_resp0 = PadPool::shared_unchecked(pad0).with_epoch(0);
1021        let e1_init = PadPool::shared(next_material.clone()).unwrap().with_epoch(1);
1022        let e1_resp = PadPool::shared(next_material).unwrap().with_epoch(1);
1023
1024        let send0 = s_init0.send_suite(Role::Initiator);
1025        let recv0 = s_resp0.recv_suite(Role::Responder);
1026
1027        let f = send0.seal(b"epoch0 payload!!").expect("first cover fits");
1028        assert_eq!(recv0.open(&f).as_deref(), Some(&b"epoch0 payload!!"[..]));
1029        assert!(send0.is_low(MAC_KEY_LEN + 1), "epoch 0 is now low");
1030        // Announce + hand off (authenticated by the last of epoch 0 is not possible here since it is
1031        // spent, so in practice the roll is sent while pad remains; we assert the commitments match).
1032        assert_eq!(e1_init.commitment(), e1_resp.commitment(), "both peers hold the identical epoch 1");
1033
1034        // Switch to epoch 1 and continue.
1035        let send1 = e1_init.send_suite(Role::Initiator);
1036        let recv1 = e1_resp.recv_suite(Role::Responder);
1037        let f1 = send1.seal(b"epoch1 continues after the roll").expect("epoch 1 has pad");
1038        assert_eq!(recv1.open(&f1).as_deref(), Some(&b"epoch1 continues after the roll"[..]));
1039    }
1040
1041    // ---- Wired into the live send/receive seam (the exact fns the interpreter + net_inbox call) ---
1042
1043    #[test]
1044    fn wired_through_the_send_and_receive_seam() {
1045        use super::super::channel::{active_session, open_active, seal_active_checked, with_session, ActiveSession};
1046        use std::rc::Rc;
1047
1048        let material = random_pad(1 << 14, 0x5EA1_D00D_0FEE_1234);
1049        let initiator = PadPool::shared(material.clone()).expect("pad");
1050        let responder = PadPool::shared(material).expect("pad");
1051        let blob = b"through the very functions the interpreter Send/receive path calls".to_vec();
1052
1053        // No session installed ⇒ byte-identical passthrough (today's default behavior, unchanged).
1054        assert!(active_session().is_none());
1055        assert_eq!(seal_active_checked(blob.clone()), Some(blob.clone()), "passthrough seal");
1056        assert_eq!(open_active(blob.clone()), Some(blob.clone()), "passthrough open");
1057
1058        // Initiator installs its session; seal via the SAME fn the interpreter Send path calls.
1059        let send: Rc<dyn ActiveSession> = Rc::new(initiator.session(Role::Initiator));
1060        let frame = with_session(Some(send), || seal_active_checked(blob.clone())).expect("pad available");
1061        assert_eq!(frame_kind(&frame), Some(KIND_DATA), "a PNP data cover");
1062        assert_eq!(&frame[2..4], &SUITE_PNP.to_le_bytes(), "PNP suite id bound in the envelope");
1063        assert_ne!(frame, blob, "sealed under the one-time pad, not plaintext");
1064
1065        // Responder installs its session; open via the SAME fn net_inbox's receive path calls.
1066        let recv: Rc<dyn ActiveSession> = Rc::new(responder.session(Role::Responder));
1067        let opened = with_session(Some(recv), || open_active(frame.clone()));
1068        assert_eq!(opened.as_deref(), Some(blob.as_slice()), "opened through the wired receive seam");
1069        assert!(active_session().is_none(), "session scope restored after with_session");
1070    }
1071
1072    #[test]
1073    fn wired_open_active_drops_foreign_frame_under_session() {
1074        use super::super::channel::{open_active, with_session, ActiveSession};
1075        use std::rc::Rc;
1076        let pool = PadPool::shared(random_pad(4096, 0xF0E1_D2C3_B4A5_9687)).unwrap();
1077        let recv: Rc<dyn ActiveSession> = Rc::new(pool.session(Role::Responder));
1078        with_session(Some(recv), || {
1079            assert!(open_active(b"not a pnp frame at all".to_vec()).is_none(), "foreign frame dropped");
1080        });
1081    }
1082
1083    #[test]
1084    fn wired_seal_active_fails_closed_on_exhaustion() {
1085        use super::super::channel::{seal_active_checked, with_session, ActiveSession};
1086        use std::rc::Rc;
1087        // The send half holds exactly one small cover; the next seal must fail closed, which the
1088        // interpreter Send path turns into a send error — never a plaintext leak.
1089        let pool = PadPool::shared_unchecked(random_pad(2 * (MAC_KEY_LEN + 8), 0x0B0E_0A0D_0C0F_0E0D));
1090        let send: Rc<dyn ActiveSession> = Rc::new(pool.session(Role::Initiator));
1091        with_session(Some(send), || {
1092            assert!(seal_active_checked(vec![0u8; 8]).is_some(), "first cover fits");
1093            assert!(seal_active_checked(vec![0u8; 1]).is_none(), "exhausted ⇒ fail-closed None (no plaintext)");
1094        });
1095    }
1096
1097    // ---- Throughput (run with: cargo test --release -p logicaffeine-compile --lib pnp -- --ignored --nocapture)
1098    #[test]
1099    #[ignore = "benchmark: prints seal/open throughput; run in --release"]
1100    fn throughput_seal_open() {
1101        use std::hint::black_box;
1102        use std::time::Instant;
1103        let half: usize = 64 << 20; // 64 MiB of pad per direction
1104        let pad = random_pad(2 * half, 0x7E57_7E57_7E57_7E57);
1105        let pool = PadPool::shared_unchecked(pad);
1106
1107        // Component ceilings @1 MiB: where does the cost go — the XOR, or the authenticator?
1108        {
1109            let n = 1usize << 20;
1110            let x = random_pad(n, 0x1111_1111_1111_1111);
1111            let y = random_pad(n, 0x2222_2222_2222_2222);
1112            let reps = 200;
1113            let mut z = vec![0u8; n];
1114            let t = Instant::now();
1115            for _ in 0..reps {
1116                for ((o, a), b) in z.iter_mut().zip(&x).zip(&y) {
1117                    *o = a ^ b;
1118                }
1119                black_box(&z);
1120            }
1121            let xor_gibs = (reps * n) as f64 / t.elapsed().as_secs_f64() / (1u64 << 30) as f64;
1122            let key = [7u8; 32];
1123            let t = Instant::now();
1124            for _ in 0..reps {
1125                black_box(poly1305(&key, &x));
1126            }
1127            let mac_gibs = (reps * n) as f64 / t.elapsed().as_secs_f64() / (1u64 << 30) as f64;
1128            eprintln!("components @1MiB: raw XOR {xor_gibs:.2} GiB/s | Poly1305 one-time MAC {mac_gibs:.2} GiB/s (this is the ceiling for authenticated OTP)");
1129        }
1130
1131        eprintln!("PNP throughput (fused XOR + Poly1305 one-time MAC), 64 MiB pad/direction:");
1132        for &size in &[256usize, 4096, 65536, 1 << 20] {
1133            let msg = vec![0xABu8; size];
1134            let iters = (half / (size + MAC_KEY_LEN)).min(50_000).max(1);
1135
1136            let sender = pool.send_suite(Role::Initiator);
1137            let mut frames: Vec<Vec<u8>> = Vec::with_capacity(iters);
1138            let t = Instant::now();
1139            for _ in 0..iters {
1140                frames.push(sender.seal(&msg).expect("pad"));
1141            }
1142            let seal_s = t.elapsed().as_secs_f64();
1143
1144            let receiver = pool.recv_suite(Role::Responder);
1145            let t = Instant::now();
1146            let mut ok = 0usize;
1147            for f in &frames {
1148                if receiver.open(f).is_some() {
1149                    ok += 1;
1150                }
1151            }
1152            let open_s = t.elapsed().as_secs_f64();
1153            assert_eq!(ok, iters, "all opened");
1154
1155            let bytes = (iters * size) as f64;
1156            let seal_mbps = bytes / seal_s / (1 << 20) as f64;
1157            let open_mbps = bytes / open_s / (1 << 20) as f64;
1158            eprintln!(
1159                "  size {:>8}B  x{:>6}  seal {:>8.1} MiB/s ({:>6.0} ns/op)  open {:>8.1} MiB/s ({:>6.0} ns/op)",
1160                size,
1161                iters,
1162                seal_mbps,
1163                seal_s / iters as f64 * 1e9,
1164                open_mbps,
1165                open_s / iters as f64 * 1e9,
1166            );
1167        }
1168    }
1169}