moq L. Curley Internet-Draft 24 September 2026 Intended status: Informational Expires: 28 March 2027 MoQ End-to-End Encryption Profile draft-lcurley-moq-e2ee-00 Abstract This document specifies moq-e2ee-00, a versioned profile for end-to- end encryption of MoQ application payloads. Authorized publishers and subscribers share a 32-byte broadcast secret out of band. Each publisher instance mints an epoch and publishes under an opaque broadcast path ending in it. HKDF-SHA-256 derives opaque physical track names and per-track AES-128-GCM keys from the secret and the epoch; grouped frames and datagrams use separate key domains. Media frames and datagrams carry only ciphertext plus a 16-byte tag. The profile binds object identity through derivation and the nonce, not an on-wire header. Note to Readers This document was generated by an AI model from the implementation at github.com/moq-dev/moq (https://github.com/moq-dev/moq) and is maintained alongside it. Submit an issue (https://github.com/moq- dev/moq/issues) or PR (https://github.com/moq-dev/moq/pulls) if this spec sucks and you want to fix anything. Status of This Memo This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79. Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet- Drafts is at https://datatracker.ietf.org/drafts/current/. Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress." This Internet-Draft will expire on 28 March 2027. Curley Expires 28 March 2027 [Page 1] Internet-Draft moq-e2ee September 2026 Copyright Notice Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved. This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/ license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. Table of Contents 1. Conventions and Definitions . . . . . . . . . . . . . . . . . 3 2. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 3 3. Relationship to Other Formats . . . . . . . . . . . . . . . . 3 4. Profile Version . . . . . . . . . . . . . . . . . . . . . . . 4 5. Credential . . . . . . . . . . . . . . . . . . . . . . . . . 4 6. Epoch and Broadcast Path . . . . . . . . . . . . . . . . . . 5 7. Canonical Encoding . . . . . . . . . . . . . . . . . . . . . 6 8. Key Derivation . . . . . . . . . . . . . . . . . . . . . . . 6 9. Object Identity . . . . . . . . . . . . . . . . . . . . . . . 8 9.1. Grouped Frames . . . . . . . . . . . . . . . . . . . . . 8 9.2. Datagrams . . . . . . . . . . . . . . . . . . . . . . . . 8 9.3. Nonce . . . . . . . . . . . . . . . . . . . . . . . . . . 8 10. Payload Protection . . . . . . . . . . . . . . . . . . . . . 8 10.1. Plaintext Ceiling . . . . . . . . . . . . . . . . . . . 9 10.2. Catalogs . . . . . . . . . . . . . . . . . . . . . . . . 9 11. Bounds . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 12. Failure Behavior . . . . . . . . . . . . . . . . . . . . . . 10 13. Test Vectors . . . . . . . . . . . . . . . . . . . . . . . . 11 14. Security Considerations . . . . . . . . . . . . . . . . . . . 11 15. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 12 16. References . . . . . . . . . . . . . . . . . . . . . . . . . 12 16.1. Normative References . . . . . . . . . . . . . . . . . . 12 16.2. Informative References . . . . . . . . . . . . . . . . . 13 Changelog . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 draft-lcurley-moq-e2ee-00 . . . . . . . . . . . . . . . . . . . 14 Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . 14 Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 14 Curley Expires 28 March 2027 [Page 2] Internet-Draft moq-e2ee September 2026 1. Conventions and Definitions The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. 2. Introduction MoQ relays forward named tracks of groups and frames ([moql], [moqt]) without parsing application payloads. This profile encrypts those payloads and hides the semantic broadcast and track names that would otherwise describe them, so a relay cannot recover content. It reuses AES-128-GCM and the 96-bit group/frame nonce shape of [secure] where those identities map, and specifies the moq-lite and datagram bindings that draft does not cover. The profile does not distribute keys, sign senders, pad payloads, or rotate a key inside an epoch. Applications that need those properties terminate this profile and run a different one. 3. Relationship to Other Formats [secure] encrypts a MoQ Transport object under a per-track base key. Its nonce is a per-track salt XOR uint64_be(group) || uint32_be(object), its AAD includes publisher priority and immutable properties, and the Key ID rides in those properties. The object payload is a length-prefixed plaintext plus an optional encrypted- properties list. This profile keeps AES-128-GCM, HKDF-SHA-256, a 96-bit nonce of uint64_be(group) || uint32_be(frame), and the rule that an identity is used at most once. It differs where the models diverge: * One 32-byte broadcast secret authorizes every track. Per-track keys are derived, not supplied. * The Key ID is part of the out-of-band credential. No per-frame header or immutable property carries it. * The salt is not derived from the key. The publisher instance's epoch is an input to every derivation and the last segment of the broadcast path (Section 6), so a restarted publisher derives new keys instead of needing a new Key ID. Curley Expires 28 March 2027 [Page 3] Internet-Draft moq-e2ee September 2026 * The nonce is the identity counter itself, not a salt XOR. The derived key is already unique per credential, epoch, physical name, and domain. * The payload is ciphertext concatenated with the 16-byte tag, with no inner length prefix, encrypted properties, or padding. * moq-lite frame indices are implied by position in the group ([moql] Section "Frame"), not an on-wire object ID. This profile still uses that index as the 32-bit nonce half, because it is the only canonical end-to-end frame identity on both moq-lite and MoQ Transport. * moq-lite datagrams share the group sequence namespace of the same track ([moql] Section "Datagrams"). A grouped frame 0 and a datagram with that sequence would collide under one AES-GCM key, so datagrams use a separate key domain with frame ID zero. SFrame [sframe] is the cryptographic ancestor of [secure]. This profile is not SFrame: it has no SFrame header, no CTR field on the wire, and no per-object KID. The experimental moq-secure format (https://github.com/cathode-ray- tube/moq-secure) is prior art only. Its independent counter, 17-byte per-frame header, ChaCha20-Poly1305 suite, signing lease, payload padding, and bytes-only processor are not compatibility requirements. 4. Profile Version This document defines profile moq-e2ee-00. The profile is named out of band alongside the credential; an application MUST refuse a credential that names any other profile. A new profile is a new document; implementations MUST NOT fall back to plaintext or to an older profile because a catalog, announcement, or peer suggested one. 5. Credential The application supplies an immutable credential: Credential { context (b) kid (u64) secret (32) } Curley Expires 28 March 2027 [Page 4] Internet-Draft moq-e2ee September 2026 *context*: Opaque bytes chosen by the application as the broadcast's end-to-end identity. Both ends MUST use identical bytes. The MoQ broadcast path is visible to relays and may be remounted under another prefix, so it is not this field unless the application copies it in. *kid*: Selects among credentials the application retains. It never changes in place; rotating the secret is a new kid. *secret*: Exactly 32 bytes from a cryptographically secure random generator. It MUST NOT be a password, passphrase, or other guessable input. kid MUST be in 0..=2^53-1 inclusive, the largest integer TypeScript can represent exactly. context, every epoch, and every semantic broadcast or track name MUST be at most 65535 bytes, the bytes encoding width. An implementation MUST refuse a credential outside those ranges (identity) or whose secret is not 32 bytes (invalid_secret). Applications distribute credentials over their own authenticated channel. MoQ announcements, catalogs, paths, and relay authorization MUST NOT carry the secret or authenticate it. Implementations MUST let the application retain more than one credential and select among them; they MUST NOT infer the kid from the transport. 6. Epoch and Broadcast Path A generation is one credential under one epoch. Every derivation and every nonce is scoped to a generation. *epoch*: Opaque nonempty bytes minted by the publisher instance, containing no /. Each instance of a broadcast MUST mint an epoch that no other instance under the same credential has used or will use. Two instances MUST NOT share an epoch: they would derive the same keys and collide on nonces. The RECOMMENDED epoch is the lowercase text of a UUID version 7 ([RFC9562]): its leading 48-bit timestamp makes epochs sort by creation time and its random bits make collisions negligible. A protected broadcast is published at /, where is the 22-character base64url segment derived from the credential and the application's semantic broadcast name according to Section 8, and is the epoch text. The opaque derivation does not include the epoch, so every instance of the same semantic broadcast shares a discovery prefix. The path carries no format or protection marker; for example, the semantic name meeting.hang appears only as an input to the opaque derivation. A plaintext Curley Expires 28 March 2027 [Page 5] Internet-Draft moq-e2ee September 2026 consumer that opens the protected broadcast fails because it cannot find the plaintext catalog it expects, not because of a path naming rule. Subscribers discover instances by the / prefix and select the greatest epoch when epochs are UUID version 7 text, where greatest is newest. Opaque epochs carry no creation order, so any other epoch form needs an application rule for which instance is current. A subscriber that already knows the full path takes the epoch from its last segment. The epoch and the path are not secret and are not authenticated. A relay that presents a wrong epoch causes authentication failure; a relay that withholds a newer instance denies service. Neither can cause a nonce to repeat, because only the publisher instance chooses the epoch it encrypts under. A restart or replacement of a publisher is a new instance and mints a new epoch. Transport sequence numbers therefore restart freely without any coordination between instances. Ended instances remain readable at their own path for as long as relays or archives retain them. 7. Canonical Encoding HKDF info fields use unique encodings, not varints. * u16 / u32 / u64: unsigned big-endian integers of that width. * bytes: u16(length) || data, length at most 65535. * ASCII labels are the UTF-8 bytes of the quoted string, with no length prefix of their own. Integers used as group, frame, or kid identities are refused before encoding if they fail Section 11. 8. Key Derivation Keys and physical names are derived with HKDF-SHA-256 [RFC5869]. Let salt be the ASCII bytes of "moq-e2ee-00". prk = HKDF-Extract(salt, secret) Opaque broadcast path material is 16 bytes: Curley Expires 28 March 2027 [Page 6] Internet-Draft moq-e2ee September 2026 path_info = "moq-e2ee-00 path" || bytes(context) || u64(kid) || bytes(semantic_broadcast) opaque = HKDF-Expand(prk, path_info, 16) semantic_broadcast is the UTF-8 bytes of the application's semantic broadcast name. The opaque path segment is the unpadded base64url encoding of opaque ([RFC4648] Section 5): 22 ASCII characters. The epoch is deliberately absent from this derivation, so a subscriber can derive the prefix before discovering an instance. Physical track name material is 16 bytes: name_info = "moq-e2ee-00 name" || bytes(context) || bytes(epoch) || u64(kid) || bytes(semantic_name) physical = HKDF-Expand(prk, name_info, 16) semantic_name is the UTF-8 bytes of the application's track name (catalog.json, video, and so on). The physical track name is the unpadded base64url encoding of physical ([RFC4648] Section 5): 22 ASCII characters, which is a valid moq-lite track name. This function may hide any track-shaped name the application wants a relay not to read; it is not limited to media tracks. AEAD keys are 16 bytes, one per physical name and domain: key_info = "moq-e2ee-00 key" || bytes(context) || bytes(epoch) || u64(kid) || bytes(physical_name) || domain key = HKDF-Expand(prk, key_info, 16) domain is a single byte: 0x00 for grouped frames, 0x01 for datagrams. physical_name here is the 22-character ASCII string, not the raw 16-byte material. A given (generation, physical_name, domain) tuple has one key. Implementations MUST derive names from semantic names, then keys from the resulting physical names. A subscriber that learns a physical name from a decrypted catalog derives its key without ever knowing the semantic name. Curley Expires 28 March 2027 [Page 7] Internet-Draft moq-e2ee September 2026 9. Object Identity A protected object is the tuple (generation, physical_name, domain, group, frame). 9.1. Grouped Frames A grouped frame uses domain = 0x00, the group's sequence as group, and the frame index within that group as frame. moq-lite numbers frames from 0 in write order ([moql]). On MoQ Transport, frame is the explicit Object ID, never its arrival ordinal. Publishers supporting both transports MUST assign contiguous Object IDs from zero so that each matches its moq-lite write-order index; relays MUST NOT renumber protected objects. An Object ID above 2^32-1 MUST be refused as identity before encryption or decryption. 9.2. Datagrams A datagram uses domain = 0x01, its 64-bit sequence as group, and frame = 0. MoQ Transport has no datagram mapping in this profile; shared vectors cover grouped tracks on both transports and datagrams on moq-lite only. 9.3. Nonce The 96-bit AES-GCM nonce is: nonce = u64(group) || u32(frame) AES-GCM's internal block counter is not frame. Implementations MUST call a standard AEAD API [RFC5116] with this nonce and an empty AAD. The empty AAD is deliberate: profile version, context, epoch, kid, physical name, and domain are bound by HKDF; group and frame are bound by the nonce. Rewritten timestamps and mutable routing properties are not authenticated. 10. Payload Protection Let Nt = 16. AES-128-GCM encrypts the application bytes with key, nonce, and empty AAD. The bytes placed in the MoQ frame or datagram payload are ciphertext concatenated with the 16-byte tag, in the [RFC5116] convention. There is no inner header. Curley Expires 28 March 2027 [Page 8] Internet-Draft moq-e2ee September 2026 Within a generation a publisher MUST allocate group and datagram sequences monotonically and frame indices in write order, so an identity is encrypted at most once. Encrypting at an identity the same instance already used is reuse and MUST be refused. Relays and caches forward ciphertext unchanged, so replay from a cache never re- encrypts. 10.1. Plaintext Ceiling Protected payload length is plaintext length plus Nt. A publisher MUST refuse plaintext that would make the protected payload exceed the transport payload limit for that object (oversize), before it touches the network. The interoperable grouped-frame payload cap matching current moq-net implementations is 32 MiB, so grouped plaintext MUST be at most 32 MiB - 16 bytes. moq-lite datagram bodies MUST remain at most 1200 bytes including Subscribe ID, Group Sequence, and Timestamp ([moql] Section "Datagrams"). Those three varints are at most 24 bytes, so datagram plaintext MUST be at most 1200 - 24 - 16 = 1160 bytes; a publisher cannot observe the Subscribe ID each hop will encode and MUST NOT budget for a smaller header. 10.2. Catalogs A catalog is a track like any other: published under the physical name derived from its semantic name, with each snapshot or delta protected at its own group and frame identity. Hang [hang] catalog.json and catalog.json.z and MSF's catalog are semantic names; authorized clients derive those physical names from the generation, then learn the remaining opaque names from the decrypted catalog. A Hang rendition-map key in that catalog is the physical name of the track. If a representation is compressed, compression is applied to the catalog bytes before AEAD and reversed after decryption. Encrypting then compressing is forbidden: ciphertext does not compress, and the .z sibling would leak the uncompressed size ratio. 11. Bounds Implementations MUST refuse non-integer identities (including NaN and infinities) and identities outside these bounds before encoding or AEAD: * group (grouped sequence or datagram sequence) and kid: 0..=2^53-1. Above that is identity. Curley Expires 28 March 2027 [Page 9] Internet-Draft moq-e2ee September 2026 * frame: 0..=2^32-1. 2^32 and above is identity. * AEAD operations with one key: at most 2^24 invocations and at most 2^36 plaintext bytes (2^32 16-byte blocks). Exceeding either is exhausted. 2^53-1 is Number.MAX_SAFE_INTEGER. It is the strictest exact integer bound across current TypeScript and Rust implementations. The 32-bit frame width is the nonce field. The 2^24 invocation cap is the interoperable AES-GCM record limit from [aeadlimits]. GCM authenticity also depends on total processed blocks, so 2^24 frames at the 32 MiB transport ceiling would be about 2^45 blocks; the 2^36-byte cap is the matching total-block bound. Small records hit the invocation cap first; large records hit the byte cap first. A receiver counts failed opens too, since each is an AEAD invocation under that key. 12. Failure Behavior E2EE is an explicit per-broadcast mode. There is no plaintext fallback. Typed failures: * invalid_secret: secret is not 32 bytes. * identity: an integer is outside Section 11, a bytes field exceeds 65535, an epoch is empty or contains /, a physical name is not 22 base64url characters, or domain is not 0x00/0x01. * exhausted: the next AEAD operation would exceed 2^24 uses of that key or 2^36 plaintext bytes under that key. * reuse: encrypting at an identity this instance already used. * oversize: plaintext plus tag exceeds the transport payload limit, or a ciphertext is shorter than Nt or larger than that limit. * authentication: AEAD open fails. Relocation across context, epoch, kid, physical name, domain, group, or frame is this failure. * duplicate: a receiver has already opened this datagram sequence inside its retained window. Operational, not a cryptographic event. Curley Expires 28 March 2027 [Page 10] Internet-Draft moq-e2ee September 2026 Authentication failure on a grouped track MUST end that track with authentication. Authentication failure on a datagram MUST drop that datagram and emit authentication; the track continues. Grouped frames need no duplicate window: a transport delivers each frame of a group once, in order, at its index. Receivers SHOULD suppress datagram sequences they still retain; the window MUST be bounded, and 1024 sequences below the greatest opened is RECOMMENDED. The AEAD identity and epoch rules are the security boundary; a relay may still delay, reorder, suppress, or replay ciphertext outside a receiver's window. A late subscriber MAY start at any group the publisher still holds. Gaps are not errors. A receiver MUST NOT require group 0 or any prior identity before opening a later authentic object. 13. Test Vectors Known-answer and negative vectors live in moq-e2ee-00.json beside this draft. Hex strings are octet sequences. The JSON is authoritative for primitive interop; an implementation of this profile MUST pass every vector. Each negative row specifies an operation, its inputs, and its expected typed error. Non-finite frame inputs use the strings NaN, Infinity, and -Infinity; group inputs in identity tests are decimal strings. Implementations whose types cannot represent an invalid input MUST reject it at their input boundary. The file covers opaque path derivation, key derivation, physical naming, grouped frames, a datagram at the fixed budget, the same identity under two epochs, relocation across every identity dimension, tag failure, malformed physical names, identity bounds, and oversize plaintext. The shared verifier is stateless. It does not verify reuse, exhausted, duplicate, or failure propagation. Each language core MUST test those lifecycle requirements, including monotonic allocation, per-key invocation and plaintext-byte accounting, bounded datagram suppression, and that a new instance under a new epoch authenticates while the old epoch does not. Passing the primitive vectors alone is not profile conformance. 14. Security Considerations Relays, caches, recorders, and control planes are untrusted for content. Authorized endpoints that hold the broadcast secret are trusted. Sender authenticity against another endpoint that also holds the secret is not a goal of moq-e2ee-00. Curley Expires 28 March 2027 [Page 11] Internet-Draft moq-e2ee September 2026 A relay can still observe the outer broadcast path including the opaque segment and epoch, opaque physical names, group and frame structure, timestamps, sizes, and traffic patterns. Padding and metadata-flow confidentiality are out of scope. The opaque segment hides the application's semantic broadcast name from a relay only while the application does not publish or otherwise expose the same name in plaintext. The epoch remains visible to every relay on the path. Nonce reuse under one key is catastrophic for AES-GCM. The profile prevents it by deriving every key from an epoch that only one publisher instance ever uses, allocating identities monotonically within that instance, separating datagram and grouped domains, and capping invocations and plaintext bytes per key. No state survives an instance: nothing needs to be persisted across restarts to stay safe. Empty AAD does not weaken the binding: every immutable end-to-end field is in the HKDF info or the nonce. Timestamps are excluded because relays rewrite them; a relay can therefore shift or reorder authentic objects in time within a receiver's tolerance. The opaque path segment is a deterministic function of the secret and semantic broadcast name; physical track names are deterministic functions of the secret and epoch. An attacker without the secret cannot predict them; an attacker with the secret can derive every name, which is intended. 15. IANA Considerations This document requests no registrations. 16. References 16.1. Normative References [moql] Curley, L., "Media over QUIC - Lite", Work in Progress, Internet-Draft, draft-lcurley-moq-lite-05, 30 June 2026, . [moqt] Nandakumar, S., Vasiliev, V., Swett, I., and A. Frindell, "Media over QUIC Transport", Work in Progress, Internet- Draft, draft-ietf-moq-transport-21, 8 September 2026, . Curley Expires 28 March 2027 [Page 12] Internet-Draft moq-e2ee September 2026 [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, . [RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data Encodings", RFC 4648, DOI 10.17487/RFC4648, October 2006, . [RFC5116] McGrew, D., "An Interface and Algorithms for Authenticated Encryption", RFC 5116, DOI 10.17487/RFC5116, January 2008, . [RFC5869] Krawczyk, H. and P. Eronen, "HMAC-based Extract-and-Expand Key Derivation Function (HKDF)", RFC 5869, DOI 10.17487/RFC5869, May 2010, . [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, . [RFC9562] Davis, K., Peabody, B., and P. Leach, "Universally Unique IDentifiers (UUIDs)", RFC 9562, DOI 10.17487/RFC9562, May 2024, . 16.2. Informative References [aeadlimits] Günther, F., Thomson, M., and C. A. Wood, "Usage Limits on AEAD Algorithms", Work in Progress, Internet-Draft, draft- irtf-cfrg-aead-limits-13, 3 September 2026, . [hang] Curley, L., "Media over QUIC - Hang", Work in Progress, Internet-Draft, draft-lcurley-moq-hang-02, 3 August 2026, . [secure] Jennings, C. F., Nandakumar, S., and R. Barnes, "End-to- End Secure Objects for Media over QUIC Transport", Work in Progress, Internet-Draft, draft-ietf-moq-secure-objects- 01, 6 July 2026, . Curley Expires 28 March 2027 [Page 13] Internet-Draft moq-e2ee September 2026 [sframe] Omara, E., Uberti, J., Murillo, S. G., Barnes, R., Ed., and Y. Fablet, "Secure Frame (SFrame): Lightweight Authenticated Encryption for Real-Time Media", RFC 9605, DOI 10.17487/RFC9605, August 2024, . Changelog draft-lcurley-moq-e2ee-00 * Initial moq-e2ee-00 profile: out-of-band credential, opaque broadcast path with a publisher-minted epoch as its last segment, HKDF physical names and keys, AES-128-GCM payloads, identity bounds, typed failures, and shared primitive vectors. Acknowledgments This document was drafted with the assistance of Grok, an AI assistant by xAI. Author's Address Luke Curley Email: kixelated@gmail.com Curley Expires 28 March 2027 [Page 14]