nx_media_transport.nx
buildroot/runtime/nx_media_transport.nx
about
nx_media_transport.nx -- sovereign secure-datagram media transport.
THE missing layer the rest of the media stack already expects but
nobody had written yet:
* nx_audio_session.nx: "Playback: caller hands us wire bytes
(decoded by TRANSPORT from the remote peer)" <- this module.
* nx_call.nx: "zero new transport logic here ... nx_signaling.nx
-- SDP exchange + ICE candidates" <- nx_signaling never existed;
this is the bytes-on-the-wire half.
It is COMPOSED from primitives that already ship -- no new crypto:
nx_aead (ChaCha20-Poly1305 seal/open, RFC 8439)
nx_udp_rv (native-lane UDP; rv64-numbered so the compiler's
syscall translation is correct)
NAT traversal is the already-proven nx_turn_relay (Arc 4 A.5.2).
Sovereign equivalent of WebRTC's DTLS-SRTP: AEAD-sealed media
datagrams over UDP. Payload-agnostic: carries nx_voice_frame audio,
video frames, or chat bytes identically.
=== SENDER-SCOPED NONCE (fixes the multi-party keystream-reuse bug) ===
Wire datagram:
[0..7] seq u64 LE, cleartext, authenticated as AAD
[8..11] sender_id u32 LE, cleartext, authenticated as AAD
[12..] ciphertext = AEAD-sealed payload (same length as payload)
[tail 16] Poly1305 tag
nonce (12 B) = seq(8 LE) || sender_id(4 LE). In a multi-sender room
with ONE shared room key, two peers both start seq at 0/1 -- WITHOUT
the sender_id that is a catastrophic (key, nonce) reuse (ChaCha20
keystream + Poly1305 break). Binding the sender_id into the nonce
makes every (sender, seq) pair unique, so the E2EE-through-hub
property holds for the shared-room-key case. Each sender MUST use a
distinct sender_id and a strictly increasing seq.
Key exchange is OUT OF SCOPE here: x25519 + ML-KEM (kyber) already
ship; this module takes the 32-byte session key they produce.
PERF NOTE (honest): aead_seal/aead_open sys_mmap scratch per call.
Fine for proofs; sustained real-time should arena-hoist the AEAD
dependencies 2 imports · 0 importers
imports: nx_aead.nxnx_udp_rv.nx
imported by: nobody (leaf or entry point)
structs
| none |
consts
| 46 | const NX_MXPORT_SEQ_BYTES: i64 = 8 |
| 47 | const NX_MXPORT_SID_BYTES: i64 = 4 |
| 48 | const NX_MXPORT_HDR_BYTES: i64 = 12 // seq(8) + sender_id(4) = AAD + nonce material |
| 49 | const NX_MXPORT_TAG_BYTES: i64 = 16 |
| 50 | const NX_MXPORT_OVERHEAD: i64 = 28 // 12 header + 16 tag |
functions
| 52 | func _mx_put_u64_le(buf: *u8, off: i64, v: i64) -> i64 |
| 57 | func _mx_get_u64_le(buf: *u8, off: i64) -> i64 called by 1: nx_mxport_open |
| 63 | func _mx_put_u32_le(buf: *u8, off: i64, v: i64) -> i64 |
| 67 | func _mx_get_u32_le(buf: *u8, off: i64) -> i64 |
| 72 | func _mx_nonce(nonce: *u8, sender_id: i64, seq: i64) -> i64 |
| 80 | func nx_mxport_seal(key: *u8, sender_id: i64, seq: i64, |
| 98 | func nx_mxport_open(key: *u8, wire: *u8, wire_len: i64, |
| 115 | func nx_mxport_sender(wire: *u8, wire_len: i64) -> i64 calls 1: _mx_get_u32_le |
| 121 | func nx_mxport_send(fd: i64, dest16: *u8, key: *u8, sender_id: i64, seq: i64, |