<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE rfc>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude"
     category="exp"
     docName="draft-adyta-mls-pq-00"
     ipr="trust200902"
     obsoletes=""
     updates=""
     submissionType="IETF"
     xml:lang="en"
     version="3"
     consensus="false">

  <front>
    <title abbrev="MLS Hybrid KEM">A Hybrid Post-Quantum KEM for the Messaging Layer Security (MLS) Protocol</title>
    <seriesInfo name="Internet-Draft" value="draft-adyta-mls-pq-00" stream="IETF"/>

    <author fullname="Luis Maia" initials="L." surname="Maia">
      <organization>Adyta, LDA</organization>
      <address>
        <email>lmaia@adyta.pt</email>
        <uri>https://adyta.pt</uri>
      </address>
    </author>

    <author fullname="Marcelo Santos" initials="M." surname="Santos">
      <organization>Adyta, LDA</organization>
      <address>
      	<email>medgarfsantos@adyta.pt</email>
        <uri>https://adyta.pt</uri>
      </address>
    </author>

    <author fullname="Tadeu Freitas" initials="T." surname="Freitas">
      <organization>Adyta, LDA</organization>
      <address>
      	<email>tadeu.freitas@adyta.pt</email>
        <uri>https://adyta.pt</uri>
      </address>
    </author>

    <date year="2026"/>

    <area>Security</area>
    <workgroup>Messaging Layer Security</workgroup>

    <keyword>MLS</keyword>
    <keyword>post-quantum</keyword>
    <keyword>hybrid</keyword>
    <keyword>KEM</keyword>
    <keyword>ML-KEM</keyword>

    <abstract>
      <t>
        This document specifies a hybrid classical/post-quantum Key Encapsulation
        Mechanism (KEM) for use in the ratchet trees of the Messaging Layer
        Security (MLS) protocol. Each parent node of the ratchet tree carries both
        a classical and a post-quantum public key, and a direct path update
        encapsulates to both. The combiner binds both ciphertexts and both public
        keys, so that the derived node secret is pseudorandom if at least one of
        the two KEMs remains IND-CCA secure, including against an adversary that
        controls the keys and ciphertexts of the broken KEM.
      </t>
      <t>
        This document also specifies an optional asynchronous mode, in which fresh
        post-quantum output is produced only on designated rotation commits rather
        than on every commit. Asynchronous mode trades a bounded delay in
        post-quantum post-compromise recovery for a reduction in post-quantum
        bandwidth proportional to the rotation interval. Forward secrecy within
        the rotation window is retained by a one-way ratchet.
      </t>
      <t>
        This document is independent of the MLS Combiner draft, which achieves
        hybrid security by a different mechanism: running a pure post-quantum and
        a traditional MLS session in parallel and injecting a secret exported from
        the former into the latter as a pre-shared key.
      </t>
    </abstract>
  </front>

  <middle>

    <section anchor="introduction">
      <name>Introduction</name>
      <t>
        The Messaging Layer Security (MLS) protocol <xref target="RFC9420"/>
        provides continuous group key agreement with cost logarithmic in the group
        size, by arranging members as the leaves of a ratchet tree. An adversary
        who records MLS ciphertext today may be able to decrypt it once a
        cryptographically relevant quantum computer exists. Migrating MLS to
        post-quantum key agreement therefore has value independent of when such a
        computer arrives.
      </t>
      <t>
        A hybrid construction runs a classical and a post-quantum primitive
        together, so that confidentiality survives the failure of either. This
        document specifies such a construction for the MLS ratchet tree. Each
        parent node carries a classical and a post-quantum public key; a direct
        path update encapsulates to both and derives the node secret using the
        combiner of <xref target="combiner-construction"/>.
      </t>
      <t>
        The objective is that the robustness property, namely that the node secret
        is pseudorandom if at least one of the two KEMs is IND-CCA secure, holds
        under a precise and implementable construction, and holds against an
        adversary that controls the broken KEM's keys and ciphertexts.
      </t>
      <t>
        <xref target="async-mode"/> defines an optional asynchronous mode in which
        fresh post-quantum output is produced only on designated rotation commits.
        Post-quantum bandwidth is reduced by a factor equal to the rotation
        interval, at the cost of a bounded delay in post-quantum post-compromise
        recovery.
      </t>

      <section anchor="relationship">
        <name>Relationship to the MLS Combiner Draft</name>
        <t>
          This document is not an update to <xref target="I-D.ietf-mls-combiner"/>.
          That document specifies a different mechanism: two complete MLS sessions
          are run in parallel over the same membership, and hybrid security is
          obtained by exporting a secret from the post-quantum session and
          importing it into the traditional session as a Pre-Shared Key, carried by
          an ordinary PreSharedKey proposal. It contains no per-commit key mixing
          step, no classical and post-quantum shared secret pair, and no combiner
          context string. The requirements in this document therefore have no
          counterpart in its text.
        </t>
        <t>
          The two approaches are alternatives rather than revisions of one another.
          The parallel session approach requires no new cryptographic primitive and
          runs on unmodified MLS implementations, at the cost of duplicated group
          state, KeyPackages and commit traffic. The approach specified here
          requires a new ciphersuite and changes to the tree KEM, and keeps a
          single group state and a single commit stream.
        </t>
      </section>
    </section>

    <section anchor="conventions">
      <name>Conventions and Definitions</name>
      <t>
        The key words "<bcp14>MUST</bcp14>", "<bcp14>MUST NOT</bcp14>",
        "<bcp14>REQUIRED</bcp14>", "<bcp14>SHALL</bcp14>",
        "<bcp14>SHALL NOT</bcp14>", "<bcp14>SHOULD</bcp14>",
        "<bcp14>SHOULD NOT</bcp14>", "<bcp14>RECOMMENDED</bcp14>",
        "<bcp14>NOT RECOMMENDED</bcp14>", "<bcp14>MAY</bcp14>", and
        "<bcp14>OPTIONAL</bcp14>" in this document are to be interpreted as
        described in BCP 14 <xref target="RFC2119"/> <xref target="RFC8174"/>
        when, and only when, they appear in all capitals, as shown here.
      </t>
      <t>This document uses the following notation.</t>
      <dl newline="false" spacing="normal">
        <dt>K_C, ct_C, pk_C:</dt>
        <dd>
          The shared secret, ciphertext and public key of the classical KEM, for
          example DHKEM over X25519 as defined in <xref target="RFC9180"/>.
        </dd>
        <dt>K_P, ct_P, pk_P:</dt>
        <dd>
          The shared secret, ciphertext and public key of the post-quantum KEM,
          for example ML-KEM-768 as defined in <xref target="FIPS203"/>.
        </dd>
        <dt>CTX:</dt>
        <dd>The node context, defined in <xref target="context-binding"/>.</dd>
        <dt>ALG_C, ALG_P:</dt>
        <dd>
          Algorithm identifiers drawn from the registries established in
          <xref target="iana"/>.
        </dd>
        <dt>combiner_version:</dt>
        <dd>An 8-bit unsigned integer. The value defined by this document is 0x01.</dd>
        <dt>L:</dt>
        <dd>The length in bytes of the derived node secret.</dd>
        <dt>lv(x):</dt>
        <dd>
          The length-prefixed encoding len(x) || x, where len(x) is a 4-byte
          big-endian unsigned integer giving the length of x in bytes. All
          variable-length fields in this document are encoded with lv(). No
          delimiter characters are used, and no restriction is placed on the byte
          content of any field.
        </dd>
      </dl>
      <t>
        The operator || denotes concatenation. HKDF-Extract and HKDF-Expand are as
        defined in <xref target="RFC5869"/>.
      </t>
    </section>

    <section anchor="combiner-construction">
      <name>Combiner Construction</name>
      <t>Implementations <bcp14>MUST</bcp14> compute the node secret as follows.</t>
      <artwork type="ascii-art"><![CDATA[
ctx_enc = lv("MLS-Hybrid-KEM-Combiner")
          || combiner_version
          || lv(CTX)
          || lv(ALG_C)
          || lv(ALG_P)

IKM     = lv(K_C)  || lv(K_P)
          || lv(ct_C) || lv(ct_P)
          || lv(pk_C) || lv(pk_P)

PRK     = HKDF-Extract(salt = ctx_enc, IKM = IKM)

KEY     = HKDF-Expand(PRK, info = ctx_enc, L)
]]></artwork>
      <t>
        The ciphertext and public key fields in IKM <bcp14>MUST NOT</bcp14> be
        omitted. Without them the construction is not robust against an adversary
        that controls the broken KEM, because distinct ciphertexts that decapsulate
        to the same shared secret would yield the same node secret. See
        <xref target="security"/>.
      </t>
      <t>
        All secret material <bcp14>MUST</bcp14> be supplied in IKM and
        <bcp14>MUST NOT</bcp14> be supplied in the salt argument, so that the
        construction remains within the extract-then-expand model without assuming
        that HMAC is a dual-PRF.
      </t>
      <t>
        The info argument <bcp14>MUST</bcp14> be ctx_enc verbatim. It
        <bcp14>MUST NOT</bcp14> be an independent re-concatenation of the same
        fields, so that the expand stage cannot be made ambiguous independently of
        the extract stage.
      </t>
      <t>
        The hash function <bcp14>MUST</bcp14> be SHA-256 for L = 32 and SHA-512 for
        L = 64, and <bcp14>MUST</bcp14> match the hash of the negotiated MLS
        ciphersuite. K_C, K_P, IKM, PRK and all intermediate buffers
        <bcp14>MUST</bcp14> be zeroized immediately after use.
      </t>

      <section anchor="multi-input">
        <name>Multi-Input Extension</name>
        <t>
          For n &gt; 2 inputs, callers <bcp14>MUST</bcp14> sort the algorithm
          identifiers lexicographically, append lv(ALG_i) to ctx_enc in that order,
          and append lv(K_i) || lv(ct_i) || lv(pk_i) to IKM in the same order.
        </t>
        <t>
          The number of fields is determined by the algorithm list carried in
          ctx_enc. Together with universal length prefixing, this keeps the
          encoding injective for every n.
        </t>
      </section>
    </section>

    <section anchor="context-binding">
      <name>Context, Transcript Binding and Negotiation</name>
      <t>CTX <bcp14>MUST</bcp14> be computed as follows.</t>
      <artwork type="ascii-art"><![CDATA[
CTX = lv(group_id)
      || epoch
      || lv(confirmed_transcript_hash)
      || node_index
]]></artwork>
      <t>
        Here epoch is an 8-byte big-endian unsigned integer and node_index is a
        4-byte big-endian unsigned integer, both as defined by
        <xref target="RFC9420"/>.
      </t>
      <t>
        The node index <bcp14>MUST</bcp14> be included so that two nodes on the
        same direct path cannot derive the same secret. The epoch and confirmed
        transcript hash <bcp14>MUST</bcp14> be included so that the derivation
        cannot be replayed into another commit.
      </t>
      <t>
        A HybridKEMParameters structure <bcp14>MUST</bcp14> be carried in MLS
        Commit and Welcome messages.
      </t>
      <sourcecode type="tls-presentation"><![CDATA[
struct {
    uint8  combiner_version;
    opaque algorithms<V>;       /* ordered list of ALG ids */
    uint16 desired_key_length;  /* L in bytes */
} HybridKEMParameters;
]]></sourcecode>
      <t>
        Recipients <bcp14>MUST</bcp14> verify that the listed algorithms match
        those they support, and that combiner_version is recognised. On mismatch
        they <bcp14>MUST</bcp14> reject the commit. Silent fallback to a non-hybrid
        mode <bcp14>MUST NOT</bcp14> be permitted.
      </t>
    </section>

    <section anchor="input-requirements">
      <name>Input Requirements</name>
      <t>
        Each input K_i supplied to IKM <bcp14>MUST</bcp14> be the full, untruncated
        shared secret output of a registered classical or post-quantum primitive,
        and <bcp14>MUST</bcp14> be at least 256 bits in length.
      </t>
      <t>
        Pre-hashing or compression of K_i prior to combiner invocation
        <bcp14>MUST</bcp14> preserve the source min-entropy. Lossy transformations
        <bcp14>MUST NOT</bcp14> be applied.
      </t>
      <t>
        Ciphersuite registrations whose shared secret output does not meet this
        floor <bcp14>MUST NOT</bcp14> be listed in
        HybridKEMParameters.algorithms.
      </t>
    </section>

    <section anchor="failure-handling">
      <name>Failure Handling and Rekey Semantics</name>
      <t>
        If decapsulation of any listed input fails, the recipient
        <bcp14>MUST</bcp14> abort processing of the Commit, <bcp14>MUST NOT</bcp14>
        advance the group epoch, and <bcp14>MUST</bcp14> emit an MLS-level error
        indicating the failing algorithm identifier.
      </t>
      <t>
        Implementations <bcp14>MUST NOT</bcp14> substitute zero, constant or
        fallback values for a failed input, and <bcp14>MUST NOT</bcp14> derive KEY
        from a strict subset of the listed inputs.
      </t>
      <t>
        ML-KEM <xref target="FIPS203"/> uses implicit rejection and returns a
        pseudorandom value rather than an error when decapsulation fails. This
        output <bcp14>MAY</bcp14> be supplied to the combiner only if no
        out-of-band signal indicates failure. An example of such a signal is a
        subsequent MAC verification on a key derived from KEY. On such a signal the
        recipient <bcp14>MUST</bcp14> treat the commit as failed and trigger a
        rekey through the standard MLS Update and Commit flow.
      </t>
    </section>

    <section anchor="async-mode">
      <name>Asynchronous Mode</name>
      <t>
        An implementation <bcp14>MAY</bcp14> operate the hybrid KEM specified above
        in asynchronous mode, in which fresh post-quantum output is produced only
        on designated rotation commits. The preceding sections remain normative for
        rotation commits themselves.
      </t>
      <t>
        Asynchronous mode reduces post-quantum bandwidth by a factor equal to the
        rotation interval k. It provides a strictly weaker post-quantum guarantee
        than synchronous operation, as described in
        <xref target="async-security"/>.
      </t>

      <section anchor="async-state">
        <name>State</name>
        <t>Each member <bcp14>MUST</bcp14> maintain the following additional state.</t>
        <dl newline="false" spacing="normal">
          <dt>S_t:</dt>
          <dd>The post-quantum ratchet secret for the current epoch t, of length L.</dd>
          <dt>pq_rotation_counter:</dt>
          <dd>The number of commits since the most recent rotation.</dd>
          <dt>pq_rotation_ctx:</dt>
          <dd>
            An injective encoding binding the epoch and confirmed transcript hash
            of the most recent rotation.
          </dd>
        </dl>
        <t>
          The counter and the rotation context <bcp14>MUST</bcp14> be carried in a
          GroupContext extension, so that every member derives them identically.
        </t>
      </section>

      <section anchor="async-policy">
        <name>Rotation Policy</name>
        <t>
          A commit <bcp14>MUST</bcp14> be a rotation if pq_rotation_counter has
          reached k, or if the commit contains an Add, Remove or ReInit proposal,
          or if the commit is an external commit. The counter <bcp14>MUST</bcp14>
          be reset to zero on every rotation, from either cause, so that the window
          is at most k commits.
        </t>
        <t>
          The rotation interval k <bcp14>MUST</bcp14> be fixed by the ciphersuite.
          It <bcp14>MUST NOT</bcp14> be negotiable, carried in any message field, or
          otherwise influenced by group members. A negotiable window would be an
          attacker-influenceable parameter whose only useful direction of attack is
          to widen it, lengthening exposure and delaying recovery.
        </t>
        <t>
          Ciphersuites defined by this document use k = 32. The reasoning behind
          that value is given in <xref target="k-rationale"/>. Ciphersuites defined
          elsewhere <bcp14>MUST</bcp14> state their own value of k, and
          <bcp14>SHOULD</bcp14> justify it against the same considerations.
        </t>
        <t>
          The requirement to rotate on membership change is load bearing. A removed
          member holds S_t at the moment of its removal, and the ratchet step of
          <xref target="async-ratchet"/> is a public function of S_t and public
          context. Absent a rotation, a removed member could compute all successor
          values indefinitely and would retain the post-quantum half of the group's
          protection after removal.
        </t>
      </section>

      <section anchor="async-enforcement">
        <name>Enforcement</name>
        <t>
          Recipients <bcp14>MUST</bcp14> independently compute whether the current
          commit is required to be a rotation, and <bcp14>MUST</bcp14> reject any
          commit that fails to carry a post-quantum direct path update when one is
          required, or whose pq_rotation_counter does not match their own
          computation.
        </t>
        <t>
          Implementations <bcp14>MUST NOT</bcp14> accept a commit on the grounds
          that the committer asserted no rotation was due. Without this check, a
          malicious committer could decline to rotate indefinitely, reducing the
          construction to classical MLS with a stale secret mixed in.
        </t>
      </section>

      <section anchor="async-ratchet">
        <name>Ratchet</name>
        <t>
          On a rotation at epoch r with post-quantum root secret P_r, the ratchet is
          initialised as follows.
        </t>
        <artwork type="ascii-art"><![CDATA[
S_r = HKDF-Expand(
          HKDF-Extract(mix_ctx_r, lv(P_r)),
          "pq-ratchet-init", L)
]]></artwork>
        <t>On every other epoch, the ratchet is advanced as follows.</t>
        <artwork type="ascii-art"><![CDATA[
S_t+1 = HKDF-Expand(
            HKDF-Extract(mix_ctx_t+1, lv(S_t)),
            "pq-ratchet-step", L)
]]></artwork>
        <t>
          S_t <bcp14>MUST</bcp14> be zeroized immediately after S_t+1 is derived.
        </t>
        <t>
          Implementations <bcp14>MUST NOT</bcp14> retain P_r beyond the derivation
          of S_r, and <bcp14>MUST NOT</bcp14> carry P_r forward in place of the
          ratcheted value. Carrying a constant value forfeits forward secrecy across
          the entire rotation window on a single compromise.
        </t>
        <t>
          The ratchet step and the mixing step of <xref target="async-mixing"/>
          share mix_ctx_t, which is available only once the confirmed transcript
          hash for epoch t has been computed. Implementations <bcp14>MUST</bcp14>
          sequence the epoch as follows: compute confirmed_transcript_hash_t,
          derive S_t from S_t-1, zeroize S_t-1, then derive
          hybrid_commit_secret_t. The two formulae are not circular, but the
          ordering is normative rather than an implementation detail.
        </t>
      </section>

      <section anchor="async-mixing">
        <name>Mixing</name>
        <t>
          The epoch's classical commit_secret, as defined in
          <xref target="RFC9420"/>, and the ratchet secret <bcp14>MUST</bcp14> be
          combined as follows.
        </t>
        <artwork type="ascii-art"><![CDATA[
mix_ctx_t = lv("MLS-Async-Hybrid-Mixer")
            || version
            || lv(group_id)
            || epoch_t
            || lv(confirmed_transcript_hash_t)
            || lv(ALG_C)
            || lv(ALG_P)
            || lv(pq_rotation_ctx)

hybrid_commit_secret_t =
    HKDF-Extract(salt = mix_ctx_t,
                 IKM  = lv(commit_secret_t) || lv(S_t))
]]></artwork>
        <t>
          hybrid_commit_secret_t <bcp14>MUST</bcp14> be used wherever MLS uses
          commit_secret.
        </t>
        <t>
          pq_rotation_ctx <bcp14>MUST</bcp14> be included. It binds the ratchet
          state to the rotation that produced it, and prevents substitution of a
          state descended from a different rotation. On a non-rotation commit there
          is no post-quantum ciphertext to bind, so this field takes the place of
          the per-node ciphertext binding required by
          <xref target="combiner-construction"/>.
        </t>
      </section>

      <section anchor="async-failure">
        <name>Failure Handling in Asynchronous Mode</name>
        <t>
          On rotation commits, <xref target="failure-handling"/> applies unchanged.
        </t>
        <t>
          On non-rotation commits there is no post-quantum decapsulation and
          therefore no decapsulation failure. However, if a member's S_t is absent,
          corrupt, or inconsistent with pq_rotation_ctx, the member
          <bcp14>MUST</bcp14> abort processing and re-synchronise. It
          <bcp14>MUST NOT</bcp14> derive hybrid_commit_secret from commit_secret
          alone, which would be a silent downgrade to classical-only operation.
        </t>
      </section>
      <section anchor="k-rationale">
        <name>Rationale for the Rotation Interval</name>
        <t>
          This subsection is non-normative. It records why k = 32 was selected, so
          that the choice can be revisited on the same basis if ML-KEM parameters,
          MLS framing, or deployment evidence change.
        </t>
        <t>
          k sets the average post-quantum cost of a commit and not the peak. A
          rotation commit carries the full post-quantum direct path whatever k is.
          Deployments whose binding constraint is maximum message size or
          fragmentation gain nothing from raising k, and should use synchronous
          operation instead.
        </t>
        <t>
          The trigger counts commits rather than elapsed time because
          <xref target="async-enforcement"/> requires every recipient to compute
          independently whether a rotation is due. That is possible only when the
          trigger is a deterministic function of shared group state. MLS carries no
          clock in the GroupContext, so a time bound would leave members with
          skewed clocks rejecting each other's honest commits, or would reduce to
          the committer asserting that the deadline had not passed, which is what
          <xref target="async-enforcement"/> exists to prevent.
        </t>
        <t>
          Post-quantum cost falls as 1/k and saturates quickly, while the exposure
          window grows linearly in k and does not saturate. Beyond a certain point
          each doubling of k buys a fraction of a percent of bandwidth in exchange
          for twice the worst-case recovery delay.
        </t>
        <t>
          The value was therefore chosen as the smallest k at which post-quantum
          material ceases to dominate the commit. For a group of 1000 members
          (h = 10) using ML-KEM-768, a rotation commit carries 2272 bytes per node,
          being one 1088-byte ciphertext and one 1184-byte public key, or about
          22.2 KiB in total. Against a classical direct path of roughly 1.3 KiB for
          the same tree, k = 32 reduces the overhead of a commit from about 18
          times the classical baseline to about 1.5 times, capturing roughly 97
          percent of the available reduction. k = 64 recovers a further 1.5
          percentage points for twice the window, and k = 16 leaves the
          post-quantum contribution roughly equal to the entire classical commit.
        </t>
        <t>
          k bounds commits and not elapsed time, and the two are related by a
          commit rate that varies by orders of magnitude between deployments. No
          fixed constant can bound both, which argues for choosing conservatively.
          Two effects limit the consequences: membership changes force a rotation,
          so k binds only in membership-stable groups, and the window is
          exploitable only by an adversary that has both compromised ratchet state
          and broken the classical KEM.
        </t>
        <t>
          Making k a function of group size was considered. It would satisfy
          <xref target="async-enforcement"/>, since the tree is shared state, and
          would let one ciphersuite serve a wide range of group sizes. It is not
          adopted here because it would weaken the property that the worst-case
          window is known from the ciphersuite alone, group size being something
          participants can influence, and because the case for it rests on
          commit-rate measurements that are not yet available. A clamped form, with
          a coefficient, a floor and a ceiling all registered alongside the
          ciphersuite, would bound the worst case and remains the natural candidate
          if evidence later shows that a single constant serves the extremes of the
          size range badly.
        </t>
      </section>
    </section>

    <section anchor="security">
      <name>Security Considerations</name>
      <t>
        Under the HKDF extract-then-expand model, if at least one component of IKM
        has computational min-entropy t from the adversary's view, with t at least
        256 as required by <xref target="input-requirements"/>, then KEY is
        indistinguishable from random with bounds derived from the
        randomness-extractor security of HKDF-Extract.
      </t>
      <t>
        The guarantee is stated against a strong adversary. One of the two KEMs may
        be entirely broken, with the adversary choosing its public key, ciphertext
        and shared secret adaptively. Robustness in that setting depends on two
        properties of <xref target="combiner-construction"/> that are easily lost in
        implementation.
      </t>
      <t>
        First, IKM is an injective encoding of its six fields, so that no two
        distinct input tuples collide. This is why every field is length prefixed,
        and why no delimiter characters appear anywhere, including in fields such as
        group_id whose byte content is unconstrained by <xref target="RFC9420"/>.
      </t>
      <t>
        Second, both ciphertexts and both public keys are bound into the extract
        step, so that a broken KEM admitting two ciphertexts that decapsulate to the
        same shared secret still yields two distinct node secrets. A construction
        that mixes only K_C and K_P satisfies neither property and
        <bcp14>MUST NOT</bcp14> be used.
      </t>
      <t>
        This requirement is not specific to MLS. The same binding appears in
        <xref target="I-D.connolly-cfrg-xwing-kem"/>, which hashes the X25519
        ciphertext and public key alongside the ML-KEM shared secret for the same
        reason.
      </t>
      <t>
        Implementations <bcp14>SHOULD</bcp14> provide constant-time KDF code paths
        and platform-level zeroization.
      </t>

      <section anchor="prk-handling">
        <name>PRK and Intermediate State Handling</name>
        <t>
          PRK is a per-invocation secret. Implementations <bcp14>MUST NOT</bcp14>
          cache PRK across invocations, <bcp14>MUST NOT</bcp14> write PRK to
          persistent storage, and <bcp14>MUST NOT</bcp14> reuse PRK across distinct
          combinations of CTX, algorithms and combiner_version, even when one or
          more K_i are unchanged between invocations.
        </t>
        <t>
          This requirement is generic KDF hygiene in synchronous operation, but it
          is load bearing in asynchronous mode. Because one input is unchanged
          between rotations, a PRK object held across epochs is an obvious
          implementation shortcut. mix_ctx_t includes the epoch and the confirmed
          transcript hash, so the extract input does differ at every epoch and a
          correct implementation has no reusable value; the risk is that a
          long-lived PRK object in an HKDF or HPKE wrapper keeps material alive
          that the ratchet of <xref target="async-ratchet"/> is required to have
          destroyed.
        </t>
        <t>
          PRK, IKM, every K_i, and any platform scratch buffers used by the KDF
          <bcp14>MUST</bcp14> be zeroized before the combiner returns. KEY itself
          <bcp14>MUST NOT</bcp14> be reused across commits.
        </t>
      </section>

      <section anchor="async-security">
        <name>Security Properties of Asynchronous Mode</name>
        <t>
          Let R be the set of rotation epochs, and for an epoch t let r(t) be the
          greatest element of R that is less than or equal to t. The epoch secret at
          epoch t is pseudorandom if either of the following holds.
        </t>
        <ol spacing="normal">
          <li>
            The classical KEM is secure at epoch t, and the classical state is
            uncompromised, by the standard MLS argument.
          </li>
          <li>
            The post-quantum KEM was secure at epoch r(t), and no member's
            post-quantum ratchet state was compromised at any epoch in the interval
            from r(t) to t inclusive.
          </li>
        </ol>
        <t>
          The second condition is weaker than its synchronous counterpart, which
          reads simply that the post-quantum KEM is secure at epoch t. The one-way
          ratchet of <xref target="async-ratchet"/> sharpens it: a compromise at
          epoch u exposes only the epochs from u up to the next rotation, and not
          those preceding u.
        </t>
        <t>
          Forward secrecy therefore holds within the rotation window. Post-quantum
          post-compromise security does not hold within the window, and is restored
          only at the next rotation: within k commits, and immediately on any
          membership change.
        </t>
        <t>
          Because S_t is mixed into the key schedule of every epoch, and not only
          into rotation epochs, there is no epoch whose secret depends on the
          classical primitive alone. An adversary that has broken the classical KEM
          entirely still cannot derive any epoch secret without the post-quantum
          material.
        </t>
        <t>
          Implementations <bcp14>MUST</bcp14> document the rotation interval k of
          each ciphersuite they support, so that deployments can evaluate the
          worst-case post-compromise recovery delay.
        </t>
      </section>
    </section>

    <section anchor="iana">
      <name>IANA Considerations</name>
      <t>
        This document requests that IANA establish two new registries in the
        "Messaging Layer Security (MLS)" group of registries, and requests
        allocations in an existing registry as described below.
      </t>

      <section anchor="iana-versions">
        <name>MLS Hybrid KEM Combiner Versions</name>
        <t>
          The registration policy for this registry is Specification Required. Each
          entry contains a version value (an 8-bit unsigned integer), a name, a
          reference, and a change controller. The initial contents are as follows.
        </t>
        <table anchor="tbl-versions">
          <name>MLS Hybrid KEM Combiner Versions</name>
          <thead>
            <tr><th>Value</th><th>Name</th><th>Reference</th></tr>
          </thead>
          <tbody>
            <tr><td>0x00</td><td>Reserved</td><td>This document</td></tr>
            <tr><td>0x01</td><td>MLS-Hybrid-KEM-Combiner-v1</td><td>This document</td></tr>
            <tr><td>0x02-0xFF</td><td>Unassigned</td><td></td></tr>
          </tbody>
        </table>
      </section>

      <section anchor="iana-algs">
        <name>MLS Hybrid KEM Algorithm Identifiers</name>
        <t>
          The registration policy for this registry is Specification Required. Each
          entry contains an identifier, the name of the KEM, the length in bytes of
          its shared secret output, whether the KEM is classical or post-quantum, a
          reference, and a change controller.
        </t>
        <t>
          A registration whose shared secret output is shorter than 256 bits
          <bcp14>MUST NOT</bcp14> be accepted, as required by
          <xref target="input-requirements"/>. The initial contents are as follows.
        </t>
        <table anchor="tbl-algs">
          <name>MLS Hybrid KEM Algorithm Identifiers</name>
          <thead>
            <tr><th>Identifier</th><th>KEM</th><th>Secret bytes</th><th>Class</th><th>Reference</th></tr>
          </thead>
          <tbody>
            <tr><td>DHKEM-X25519</td><td>DHKEM(X25519, HKDF-SHA256)</td><td>32</td><td>Classical</td><td>RFC 9180</td></tr>
            <tr><td>DHKEM-P256</td><td>DHKEM(P-256, HKDF-SHA256)</td><td>32</td><td>Classical</td><td>RFC 9180</td></tr>
            <tr><td>ML-KEM-768</td><td>ML-KEM-768</td><td>32</td><td>Post-quantum</td><td>FIPS 203</td></tr>
            <tr><td>ML-KEM-1024</td><td>ML-KEM-1024</td><td>32</td><td>Post-quantum</td><td>FIPS 203</td></tr>
          </tbody>
        </table>
      </section>

      <section anchor="iana-ciphersuites">
        <name>MLS Ciphersuites</name>
        <t>
          This document requests allocation of MLS ciphersuite code points pairing
          each registered classical KEM with each registered post-quantum KEM. A
          ciphersuite that supports asynchronous mode
          (<xref target="async-mode"/>) <bcp14>MUST</bcp14> register its rotation
          interval k as part of the ciphersuite definition, because k is not
          negotiable. The ciphersuites requested by this document register
          k = 32, for the reasons given in <xref target="k-rationale"/>.
        </t>
      </section>
    </section>

    <section anchor="implementation-status">
      <name>Implementation Requirements Summary</name>
      <t>
        This non-normative section collects the requirements most likely to be
        missed in implementation, in approximate order of consequence. It
        introduces no new requirements.
      </t>
      <ol spacing="normal">
        <li>
          Ciphertext and public key binding: ct_C, ct_P, pk_C and pk_P must appear
          in IKM. Omitting them is the difference between a robust combiner and one
          that fails when the adversary controls the broken half.
        </li>
        <li>
          Injective encoding: every variable-length field, secret and public alike,
          must be length prefixed. Delimiter characters must not be used, because
          CTX and the algorithm identifiers are unconstrained byte strings.
        </li>
        <li>
          Secret placement: all secret material goes in IKM, never in the salt.
        </li>
        <li>
          Min-entropy floor: each K_i must be a full, untruncated shared secret of
          at least 256 bits.
        </li>
        <li>
          Failure handling: any decapsulation failure aborts the commit. Zero or
          constant substitution, and derivation from a subset of inputs, are
          forbidden.
        </li>
        <li>
          PRK hygiene: PRK is per-invocation and must not be cached, persisted or
          reused.
        </li>
        <li>
          No silent downgrade: an unsupported algorithm or version causes
          rejection, never a non-hybrid fallback.
        </li>
        <li>
          In asynchronous mode, ratchet rather than carry: S_t must be advanced by
          the one-way step and the previous value zeroized.
        </li>
        <li>
          In asynchronous mode, rotation is verified rather than asserted:
          recipients independently compute whether a rotation is due and reject
          commits that skip one.
        </li>
        <li>
          In asynchronous mode, k is not a protocol field: it comes from the
          ciphersuite and is never negotiated.
        </li>
        <li>
          In asynchronous mode, derive before mixing: the transcript hash, then
          the ratchet advance and zeroization, then the mixing extract.
        </li>
      </ol>
    </section>

  </middle>

  <back>
    <references>
      <name>References</name>

      <references>
        <name>Normative References</name>

        <reference anchor="RFC2119" target="https://www.rfc-editor.org/info/rfc2119">
          <front>
            <title>Key words for use in RFCs to Indicate Requirement Levels</title>
            <author initials="S." surname="Bradner" fullname="S. Bradner"/>
            <date year="1997" month="March"/>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="2119"/>
          <seriesInfo name="DOI" value="10.17487/RFC2119"/>
        </reference>

        <reference anchor="RFC8174" target="https://www.rfc-editor.org/info/rfc8174">
          <front>
            <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
            <author initials="B." surname="Leiba" fullname="B. Leiba"/>
            <date year="2017" month="May"/>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="8174"/>
          <seriesInfo name="DOI" value="10.17487/RFC8174"/>
        </reference>

        <reference anchor="RFC5869" target="https://www.rfc-editor.org/info/rfc5869">
          <front>
            <title>HMAC-based Extract-and-Expand Key Derivation Function (HKDF)</title>
            <author initials="H." surname="Krawczyk" fullname="H. Krawczyk"/>
            <author initials="P." surname="Eronen" fullname="P. Eronen"/>
            <date year="2010" month="May"/>
          </front>
          <seriesInfo name="RFC" value="5869"/>
          <seriesInfo name="DOI" value="10.17487/RFC5869"/>
        </reference>

        <reference anchor="RFC9180" target="https://www.rfc-editor.org/info/rfc9180">
          <front>
            <title>Hybrid Public Key Encryption</title>
            <author initials="R." surname="Barnes" fullname="R. Barnes"/>
            <author initials="K." surname="Bhargavan" fullname="K. Bhargavan"/>
            <author initials="B." surname="Lipp" fullname="B. Lipp"/>
            <author initials="C." surname="Wood" fullname="C. Wood"/>
            <date year="2022" month="February"/>
          </front>
          <seriesInfo name="RFC" value="9180"/>
          <seriesInfo name="DOI" value="10.17487/RFC9180"/>
        </reference>

        <reference anchor="RFC9420" target="https://www.rfc-editor.org/info/rfc9420">
          <front>
            <title>The Messaging Layer Security (MLS) Protocol</title>
            <author initials="R." surname="Barnes" fullname="R. Barnes"/>
            <author initials="B." surname="Beurdouche" fullname="B. Beurdouche"/>
            <author initials="R." surname="Robert" fullname="R. Robert"/>
            <author initials="J." surname="Millican" fullname="J. Millican"/>
            <author initials="E." surname="Omara" fullname="E. Omara"/>
            <author initials="K." surname="Cohn-Gordon" fullname="K. Cohn-Gordon"/>
            <date year="2023" month="July"/>
          </front>
          <seriesInfo name="RFC" value="9420"/>
          <seriesInfo name="DOI" value="10.17487/RFC9420"/>
        </reference>

        <reference anchor="FIPS203" target="https://doi.org/10.6028/NIST.FIPS.203">
          <front>
            <title>Module-Lattice-Based Key-Encapsulation Mechanism Standard</title>
            <author>
              <organization>National Institute of Standards and Technology</organization>
            </author>
            <date year="2024" month="August"/>
          </front>
          <seriesInfo name="FIPS" value="203"/>
          <seriesInfo name="DOI" value="10.6028/NIST.FIPS.203"/>
        </reference>

      </references>

      <references>
        <name>Informative References</name>

        <reference anchor="I-D.ietf-mls-combiner" target="https://datatracker.ietf.org/doc/draft-ietf-mls-combiner/">
          <front>
            <title>A Hybrid PQ/T Combiner for MLS</title>
            <author>
              <organization>IETF MLS Working Group</organization>
            </author>
            <date year="2025"/>
          </front>
          <seriesInfo name="Internet-Draft" value="draft-ietf-mls-combiner"/>
        </reference>

        <reference anchor="I-D.connolly-cfrg-xwing-kem" target="https://datatracker.ietf.org/doc/draft-connolly-cfrg-xwing-kem/">
          <front>
            <title>X-Wing: general-purpose hybrid post-quantum KEM</title>
            <author>
              <organization>IRTF CFRG</organization>
            </author>
            <date year="2024"/>
          </front>
          <seriesInfo name="Internet-Draft" value="draft-connolly-cfrg-xwing-kem"/>
        </reference>

      </references>
    </references>

    <section anchor="acknowledgements" numbered="false">
      <name>Acknowledgements</name>
      <t>
        The analysis that led to this document benefited from a detailed review
        that identified a mischaracterisation of
        <xref target="I-D.ietf-mls-combiner"/> in an earlier draft, and from the
        observation that a combiner over shared secrets alone is not robust against
        an adversary controlling the broken half of the hybrid.
      </t>
    </section>

  </back>
</rfc>
