pub(crate) const CHAN_MAGIC: u8 = 0xC0;
pub(crate) const CHAN_VER: u8 = 1;
pub(crate) const CHAN_HEADER_LEN: usize = 4;
pub const SUITE_NULL: u16 = 0;
pub const SUITE_PQ: u16 = 1;
pub const SUITE_PNP: u16 = 2;
pub trait Suite: Sync {
fn id(&self) -> u16;
fn seal_body(&self, blob: &[u8]) -> Vec<u8>;
fn open_body(&self, body: &[u8]) -> Option<Vec<u8>>;
}
pub struct NullSuite;
impl Suite for NullSuite {
fn id(&self) -> u16 {
SUITE_NULL
}
fn seal_body(&self, blob: &[u8]) -> Vec<u8> {
blob.to_vec()
}
fn open_body(&self, body: &[u8]) -> Option<Vec<u8>> {
Some(body.to_vec())
}
}
pub fn suite_for(id: u16) -> Option<&'static dyn Suite> {
static NULL: NullSuite = NullSuite;
match id {
SUITE_NULL => Some(&NULL),
_ => None,
}
}
pub struct PqSuite {
key: [u8; 32],
seq: std::sync::atomic::AtomicU64,
}
impl PqSuite {
pub fn new(key: [u8; 32]) -> Self {
Self { key, seq: std::sync::atomic::AtomicU64::new(0) }
}
}
impl Suite for PqSuite {
fn id(&self) -> u16 {
SUITE_PQ
}
fn seal_body(&self, blob: &[u8]) -> Vec<u8> {
let seq = self.seq.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let mut nonce = [0u8; 12];
nonce[..8].copy_from_slice(&seq.to_le_bytes());
let sealed =
logicaffeine_system::aead::chacha20poly1305_seal(&self.key, &nonce, &SUITE_PQ.to_le_bytes(), blob);
let mut body = Vec::with_capacity(12 + sealed.len());
body.extend_from_slice(&nonce);
body.extend_from_slice(&sealed);
body
}
fn open_body(&self, body: &[u8]) -> Option<Vec<u8>> {
if body.len() < 12 {
return None;
}
let nonce: [u8; 12] = body[..12].try_into().ok()?;
logicaffeine_system::aead::chacha20poly1305_open(&self.key, &nonce, &SUITE_PQ.to_le_bytes(), &body[12..])
}
}
pub fn derive_aead_key(shared_secret: &[u8], label: &[u8]) -> [u8; 32] {
let mut input = Vec::with_capacity(shared_secret.len() + label.len() + 24);
input.extend_from_slice(b"logos-pq-channel-v1\x00");
input.extend_from_slice(label);
input.extend_from_slice(shared_secret);
let mut k = [0u8; 32];
k.copy_from_slice(&logicaffeine_system::keccak::shake256_bytes(&input, 32));
k
}
pub struct PqHandshake {
pub ek: Vec<u8>,
dk: Vec<u8>,
}
pub fn pq_handshake_initiator(seed_d: &[u8; 32], seed_z: &[u8; 32]) -> PqHandshake {
let (ek, dk) = logicaffeine_system::mlkem::keygen(seed_d, seed_z);
PqHandshake { ek, dk }
}
pub fn pq_handshake_responder(ek: &[u8], msg: &[u8; 32]) -> (Vec<u8>, PqSuite, PqSuite) {
let (ct, ss) = logicaffeine_system::mlkem::encaps(ek, msg);
(ct, PqSuite::new(derive_aead_key(&ss, b"i2r")), PqSuite::new(derive_aead_key(&ss, b"r2i")))
}
pub fn pq_handshake_finish(hs: &PqHandshake, ct: &[u8]) -> (PqSuite, PqSuite) {
let ss = logicaffeine_system::mlkem::decaps(&hs.dk, ct);
(PqSuite::new(derive_aead_key(&ss, b"i2r")), PqSuite::new(derive_aead_key(&ss, b"r2i")))
}
pub fn seal_with(suite: &dyn Suite, blob: &[u8]) -> Vec<u8> {
let body = suite.seal_body(blob);
let mut out = Vec::with_capacity(CHAN_HEADER_LEN + body.len());
out.push(CHAN_MAGIC);
out.push(CHAN_VER);
out.extend_from_slice(&suite.id().to_le_bytes());
out.extend_from_slice(&body);
out
}
pub fn open_with(suite: &dyn Suite, bytes: &[u8]) -> Option<Vec<u8>> {
if bytes.len() < CHAN_HEADER_LEN || bytes[0] != CHAN_MAGIC || bytes[1] != CHAN_VER {
return None;
}
if u16::from_le_bytes([bytes[2], bytes[3]]) != suite.id() {
return None;
}
suite.open_body(&bytes[CHAN_HEADER_LEN..])
}
pub fn seal(suite_id: u16, blob: &[u8]) -> Vec<u8> {
let suite = suite_for(suite_id).expect("seal with a registered suite");
let body = suite.seal_body(blob);
let mut out = Vec::with_capacity(CHAN_HEADER_LEN + body.len());
out.push(CHAN_MAGIC);
out.push(CHAN_VER);
out.extend_from_slice(&suite_id.to_le_bytes());
out.extend_from_slice(&body);
out
}
pub fn open(bytes: &[u8]) -> Option<Vec<u8>> {
if bytes.len() < CHAN_HEADER_LEN || bytes[0] != CHAN_MAGIC || bytes[1] != CHAN_VER {
return None;
}
let suite_id = u16::from_le_bytes([bytes[2], bytes[3]]);
let suite = suite_for(suite_id)?;
suite.open_body(&bytes[CHAN_HEADER_LEN..])
}
thread_local! {
static ACTIVE_SUITE: std::cell::Cell<Option<u16>> = const { std::cell::Cell::new(None) };
}
pub fn active_suite() -> Option<u16> {
ACTIVE_SUITE.with(|s| s.get())
}
pub fn with_suite<T>(suite: Option<u16>, f: impl FnOnce() -> T) -> T {
let prev = ACTIVE_SUITE.with(|s| s.replace(suite));
let out = f();
ACTIVE_SUITE.with(|s| s.set(prev));
out
}
pub trait ActiveSession {
fn seal(&self, bytes: &[u8]) -> Option<Vec<u8>>;
fn open(&self, bytes: &[u8]) -> Option<Vec<u8>>;
}
thread_local! {
static ACTIVE_SESSION: std::cell::RefCell<Option<std::rc::Rc<dyn ActiveSession>>> =
const { std::cell::RefCell::new(None) };
}
pub fn active_session() -> Option<std::rc::Rc<dyn ActiveSession>> {
ACTIVE_SESSION.with(|s| s.borrow().clone())
}
pub fn install_session(
session: Option<std::rc::Rc<dyn ActiveSession>>,
) -> Option<std::rc::Rc<dyn ActiveSession>> {
ACTIVE_SESSION.with(|s| std::mem::replace(&mut *s.borrow_mut(), session))
}
pub fn with_session<T>(session: Option<std::rc::Rc<dyn ActiveSession>>, f: impl FnOnce() -> T) -> T {
let prev = install_session(session);
let out = f();
install_session(prev);
out
}
pub fn seal_active(bytes: Vec<u8>) -> Vec<u8> {
match active_suite() {
Some(id) => seal(id, &bytes),
None => bytes,
}
}
pub fn seal_active_checked(bytes: Vec<u8>) -> Option<Vec<u8>> {
match active_session() {
Some(session) => session.seal(&bytes),
None => Some(seal_active(bytes)),
}
}
pub fn open_active(bytes: Vec<u8>) -> Option<Vec<u8>> {
if let Some(session) = active_session() {
return session.open(&bytes);
}
match active_suite() {
Some(_) => open(&bytes),
None => Some(bytes),
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::marshal::{message_to_wire_with, WireCodec, WireIntegrity};
use crate::interpreter::RuntimeValue;
#[test]
fn null_envelope_round_trips_and_rejects_malformed() {
let blob = message_to_wire_with("alice", &RuntimeValue::Int(7), WireCodec::Native, WireIntegrity::Checked)
.expect("encode");
let sealed = seal(SUITE_NULL, &blob);
assert_eq!(open(&sealed).as_deref(), Some(blob.as_slice()), "null suite round-trip");
assert!(open(&sealed[..2]).is_none(), "truncated envelope rejected");
let mut bad = sealed.clone();
bad[2] = 0xFF;
bad[3] = 0xFF;
assert!(open(&bad).is_none(), "unknown suite rejected");
}
fn splitmix64(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
#[test]
fn null_round_trips_arbitrary_blobs() {
let mut s = 0x1234_5678_9ABC_DEF0u64;
for _ in 0..2000 {
let len = (splitmix64(&mut s) % 257) as usize; let blob: Vec<u8> = (0..len).map(|_| splitmix64(&mut s) as u8).collect();
let sealed = seal(SUITE_NULL, &blob);
assert_eq!(open(&sealed), Some(blob), "null suite round-trip, len {len}");
}
}
#[test]
fn rejects_bad_magic_version_and_empty() {
let sealed = seal(SUITE_NULL, b"payload");
assert!(open(&[]).is_none(), "empty input rejected");
assert!(open(&sealed[..CHAN_HEADER_LEN - 1]).is_none(), "header-short rejected");
let mut bad_magic = sealed.clone();
bad_magic[0] ^= 0xFF;
assert!(open(&bad_magic).is_none(), "bad magic rejected");
let mut bad_ver = sealed.clone();
bad_ver[1] = bad_ver[1].wrapping_add(1);
assert!(open(&bad_ver).is_none(), "bad version rejected");
}
#[test]
fn seal_is_framed_not_raw() {
let blob = b"the relay sees only the envelope";
let sealed = seal(SUITE_NULL, blob);
assert_eq!(&sealed[..2], &[CHAN_MAGIC, CHAN_VER], "header magic+version present");
assert_eq!(&sealed[2..4], &SUITE_NULL.to_le_bytes(), "suite id bound little-endian");
assert_eq!(sealed.len(), CHAN_HEADER_LEN + blob.len(), "null body length-exact");
assert_ne!(sealed.as_slice(), blob.as_slice(), "sealed bytes differ from the raw blob");
}
#[test]
fn null_suite_trait_dispatch() {
let suite = suite_for(SUITE_NULL).expect("null suite registered");
assert_eq!(suite.id(), SUITE_NULL);
let body = suite.seal_body(b"abc");
assert_eq!(suite.open_body(&body).as_deref(), Some(&b"abc"[..]), "identity body");
assert!(suite_for(0xFFFF).is_none(), "unknown suite id is not registered");
}
#[test]
fn no_active_suite_is_passthrough() {
assert_eq!(active_suite(), None, "default: channel disengaged");
let blob = b"plain".to_vec();
assert_eq!(seal_active(blob.clone()), blob, "seal is identity when off");
assert_eq!(open_active(blob.clone()), Some(blob), "open is identity when off");
}
#[test]
fn with_suite_scopes_and_restores() {
assert_eq!(active_suite(), None);
with_suite(Some(SUITE_NULL), || {
assert_eq!(active_suite(), Some(SUITE_NULL), "suite active inside scope");
let blob = b"secret payload".to_vec();
let sealed = seal_active(blob.clone());
assert_ne!(sealed, blob, "sealed under an active suite is framed");
assert_eq!(open_active(sealed), Some(blob), "round-trip under the active suite");
});
assert_eq!(active_suite(), None, "prior suite restored after scope");
}
#[test]
fn open_active_drops_foreign_frame_under_active_suite() {
with_suite(Some(SUITE_NULL), || {
assert!(open_active(b"not an envelope".to_vec()).is_none(), "foreign frame dropped");
});
}
#[test]
fn pq_channel_seals_a_real_wire_message_end_to_end() {
let hs = pq_handshake_initiator(&[0xA1; 32], &[0xA2; 32]);
let (ct, resp_i2r, resp_r2i) = pq_handshake_responder(&hs.ek, &[0xB0; 32]);
let (init_i2r, init_r2i) = pq_handshake_finish(&hs, &ct);
let blob = message_to_wire_with(
"responder",
&RuntimeValue::Int(42),
WireCodec::Native,
WireIntegrity::Checked,
)
.expect("encode");
let sealed = seal_with(&resp_r2i, &blob);
assert_eq!(&sealed[..2], &[CHAN_MAGIC, CHAN_VER], "channel header present");
assert_eq!(&sealed[2..4], &SUITE_PQ.to_le_bytes(), "PQ suite id bound in the envelope");
assert_ne!(sealed[CHAN_HEADER_LEN..].to_vec(), blob, "body is ciphertext, not the raw blob");
assert_eq!(
open_with(&init_r2i, &sealed).as_deref(),
Some(blob.as_slice()),
"initiator opens the responder's PQ-sealed wire message"
);
let s2 = seal_with(&init_i2r, b"ack");
assert_eq!(open_with(&resp_i2r, &s2).as_deref(), Some(&b"ack"[..]), "i2r direction opens");
let sealed_again = seal_with(&resp_r2i, &blob);
assert_ne!(sealed, sealed_again, "fresh counter nonce ⇒ a distinct frame each time");
assert_eq!(open_with(&init_r2i, &sealed_again).as_deref(), Some(blob.as_slice()));
let mut tampered = sealed.clone();
let last = tampered.len() - 1;
tampered[last] ^= 1;
assert!(open_with(&init_r2i, &tampered).is_none(), "tampered PQ frame rejected");
let eve = PqSuite::new(derive_aead_key(&[0u8; 32], b"r2i"));
assert!(open_with(&eve, &sealed).is_none(), "wrong session key cannot open");
}
#[test]
fn pq_handshake_disagrees_on_a_corrupted_ciphertext() {
let hs = pq_handshake_initiator(&[1; 32], &[2; 32]);
let (ct, _resp_i2r, resp_r2i) = pq_handshake_responder(&hs.ek, &[3; 32]);
let mut bad_ct = ct.clone();
bad_ct[0] ^= 1;
let (_init_i2r, init_r2i) = pq_handshake_finish(&hs, &bad_ct);
let sealed = seal_with(&resp_r2i, b"top secret");
assert!(
open_with(&init_r2i, &sealed).is_none(),
"a corrupted handshake yields divergent keys ⇒ frames don't open"
);
}
}