<?xml version="1.0" encoding="UTF-8"?>
<rfc version="3"
     ipr="trust200902"
     category="std"
     submissionType="IETF"
     docName="draft-verma-cirp-01">

  <front>
    <title abbrev="CIRP">A Capability-Oriented Intent Routing Protocol</title>

    <author fullname="Saurabh Verma" initials="S." surname="Verma">
      <organization>Independent</organization>
      <address>
        <postal>
          <country>United States</country>
        </postal>
        <email>saurabh.sbay@gmail.com</email>
      </address>
    </author>

    <date/>
    <area>Security</area>
    <workgroup>Individual Submission</workgroup>

    <keyword>capability routing</keyword>
    <keyword>intent routing</keyword>
    <keyword>ticket-based authorization</keyword>
    <keyword>secure discovery</keyword>
    <keyword>encrypted session establishment</keyword>

    <abstract>
      <t>
        This document specifies a transport-layer protocol for routing
        capability-oriented intent between cryptographically identified
        endpoints across heterogeneous trust domains. The protocol defines
        capability identifiers with mandatory versioning, scoped discovery
        across five visibility levels, ticket-based session authorization,
        negotiated cryptographic suites including hybrid post-quantum
        key establishment, encrypted peer-to-peer session establishment,
        and mutually attested invocation receipts with hash-chained
        integrity. Payload semantics are opaque to the transport layer.
      </t>
      <t>
        This revision clarifies session-level capability binding
        semantics, aligns discovery behavior with deployed FIND_EX
        wire formats, and refines session teardown security
        properties.
      </t>
    </abstract>
  </front>

  <middle>

    <section anchor="introduction">
      <name>Introduction</name>

      <t>
        Existing transport protocols provide general-purpose data
        delivery between network endpoints. They do not address
        the requirements of systems that need to discover
        capabilities by function, route structured invocations to
        providers of those capabilities, and produce
        cryptographically verifiable records of fulfillment.
        These requirements arise in domains including autonomous
        systems, industrial control, medical device coordination,
        software service composition, and other environments
        where heterogeneous endpoints must locate and invoke
        capabilities across trust boundaries.
      </t>

      <t>
        The Capability-Oriented Intent Routing Protocol (CIRP)
        is a transport-layer protocol that addresses these
        requirements. It provides: structured capability
        identifiers with mandatory versioning; scoped discovery
        that respects organizational and cryptographic trust
        boundaries; ticket-based session authorization that
        removes the authorizing registry from the data path;
        encrypted peer-to-peer sessions with cryptographic
        agility including hybrid post-quantum key exchange;
        typed invocation envelopes with opaque payloads; and
        mutually attested fulfillment receipts.
      </t>

      <t>
        CIRP operates at the transport layer. It does not
        interpret payload semantics, evaluate business logic,
        rank providers, manage economic relationships, or
        implement domain-specific processing. These concerns
        belong to application layers built above the transport.
        The protocol is designed to carry executable intent for
        any domain: the payload may contain JSON, binary
        protocol encodings, robotics command sequences, medical
        device instructions, industrial control messages, or
        any other octet sequence. The concept of an "Internet
        of Intent" describes a network infrastructure where
        such invocations can be routed across heterogeneous
        trust domains using a common transport protocol.
      </t>

      <t>
        This document specifies the protocol messages, encoding
        formats, cryptographic operations, and security
        properties of CIRP. It defines an initial transport
        binding for UDP and establishes IANA registries for
        cryptographic suites, capability URI schemes, and
        protocol parameters.
      </t>

      <section anchor="requirements-language">
        <name>Requirements Language</name>
        <t>
          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
          <xref target="RFC2119"/> <xref target="RFC8174"/>
          when, and only when, they appear in all capitals, as
          shown here.
        </t>
      </section>

      <section anchor="non-goals">
        <name>Non-Goals</name>
        <t>
          The following topics are explicitly outside the scope
          of this specification:
        </t>

        <ul>
          <li>Orchestration, coordination, or workflow semantics
              between endpoints.</li>
          <li>Economic models, billing, pricing, or monetization
              of capabilities.</li>
          <li>Reputation, ranking, or quality-of-service scoring
              of providers.</li>
          <li>Capability namespace governance beyond the reserved
              prefix defined in
              <xref target="namespace-considerations"/>.</li>
          <li>Registry federation synchronization protocol.
              (Federation is acknowledged but defined in a
              companion specification.)</li>
          <li>Application payload interpretation, validation, or
              transformation.</li>
          <li>NAT traversal mechanisms beyond registry-mediated
              locator distribution.</li>
        </ul>
      </section>
    </section>

    <section anchor="terminology">
      <name>Terminology</name>
      <dl>
        <dt>Intent</dt>
        <dd>A cryptographically signed, versioned invocation of a capability that is routable over the network and produces a verifiable fulfillment response.</dd>
        <dt>Capability</dt>
        <dd>A named, versioned unit of functionality addressable via a structured URI (<xref target="capability-identifiers"/>).</dd>
        <dt>Endpoint Identifier (EID)</dt>
        <dd>A node's cryptographic identity, equal to its public verification key.</dd>
        <dt>Registry</dt>
        <dd>Infrastructure that provides capability discovery and session authorization. Registries do not participate in data plane communication.</dd>
        <dt>Consumer</dt>
        <dd>The party that initiates an invocation.</dd>
        <dt>Provider</dt>
        <dd>The party that fulfills an invocation.</dd>
        <dt>ConnectTicket</dt>
        <dd>A signed, time-bounded authorization artifact issued by a Registry that permits a Consumer to establish a direct session with a Provider.</dd>
        <dt>Trust Domain</dt>
        <dd>A set of identities sharing a common trust anchor, used to restrict capability visibility.</dd>
        <dt>Suite</dt>
        <dd>A named combination of cryptographic algorithms used for key exchange, authentication, encryption, and key derivation within a session.</dd>
        <dt>Fulfillment Record</dt>
        <dd>A dual-signed receipt attesting that a specific invocation was processed and fulfilled, constructed cooperatively by Provider and Consumer (<xref target="fulfillment-record"/>).</dd>
        <dt>Scope</dt>
        <dd>A visibility descriptor attached to a capability advertisement that restricts which Consumers may discover it. Five scope levels are defined (<xref target="capability-scoping"/>).</dd>
        <dt>Policy Receipt</dt>
        <dd>A diagnostic record included in discovery responses when the Registry applies policy that modifies the result set.</dd>
      </dl>
    </section>

    <section anchor="architecture">
      <name>Architecture Overview</name>

      <t>
        CIRP defines a layered architecture for routing
        capability-oriented intent between cryptographically
        identified endpoints. The architecture separates
        concerns into distinct layers, each with well-defined
        responsibilities and trust boundaries.
      </t>

      <section anchor="layer-model">
        <name>Layer Model</name>

        <t>
          The protocol operates at Layer 1 in the following
          conceptual stack:
        </t>

        <artwork><![CDATA[
+---------------------------------------------------+
| Layer 3: Domain Applications                      |
|   DSL-specific logic, vertical solutions          |
+---------------------------------------------------+
| Layer 2: Implementation Ecosystems                |
|   Agent frameworks, SDKs, runtime environments    |
+---------------------------------------------------+
| Layer 1: Intent Routing Transport  [this document]|
|   Capability addressing, discovery, sessions,     |
|   invocation envelopes, receipts                  |
+---------------------------------------------------+
| Layer 0: Cryptographic Identity                   |
|   Ed25519 keypairs, EID derivation                |
+---------------------------------------------------+
        ]]></artwork>

        <t>
          This document specifies Layer 1. Layers above the
          transport are unconstrained by this specification:
          any agent framework, domain-specific language, or
          application architecture may operate over CIRP
          provided it uses the defined capability addressing,
          session establishment, and invocation envelope
          formats.
        </t>

        <t>
          Layer 0 is the identity foundation. An Endpoint
          Identifier (EID) is the Ed25519 public key of a
          node. Because EID equals the public key, identity
          verification requires no certificate authority,
          no public key infrastructure, and no trusted third
          party. A node proves its identity by signing
          messages with the corresponding private key.
        </t>
      </section>

      <section anchor="roles">
        <name>Protocol Roles</name>

        <t>
          Three roles participate in the protocol:
        </t>

        <dl>
          <dt>Registry</dt>
          <dd>A Registry provides two services: capability
              discovery (mapping capability identifiers to
              Provider endpoints) and session authorization
              (issuing ConnectTickets that permit direct
              peer-to-peer sessions). A Registry does not
              participate in data plane communication and
              never observes invocation content or session
              keys. Multiple Registries MAY operate
              concurrently, and endpoints MAY interact with
              different Registries for different
              capabilities.</dd>

          <dt>Consumer</dt>
          <dd>A Consumer discovers capabilities through a
              Registry, obtains authorization via a
              ConnectTicket, and initiates a direct session
              with a Provider to send invocations and
              receive fulfillments.</dd>

          <dt>Provider</dt>
          <dd>A Provider advertises capabilities to one or
              more Registries via presence announcements,
              accepts authorized sessions from Consumers,
              processes invocations, and returns fulfillment
              responses.</dd>
        </dl>

        <t>
          A single endpoint MAY act as both Consumer and
          Provider simultaneously for different capabilities.
          The protocol does not impose a fixed client-server
          relationship; any endpoint with a cryptographic
          identity may assume either role.
        </t>
      </section>

      <section anchor="trust-assumptions">
        <name>Trust Assumptions</name>

        <t>
          The protocol operates under the following trust
          model:
        </t>

        <dl>
          <dt>Endpoints are self-sovereign</dt>
          <dd>Each endpoint generates its own Ed25519 keypair
              and derives its EID from the public key. No
              registration authority or certificate issuer
              is required. Identity is established through
              cryptographic proof, not through delegation.</dd>

          <dt>Registries are semi-trusted</dt>
          <dd>Registries are trusted to perform discovery
              and authorization honestly, but they are not
              trusted with session content. The protocol
              is designed so that a compromised or malicious
              Registry cannot forge endpoint identities,
              decrypt data plane traffic, or tamper with
              invocation records. A detailed analysis of
              what a Registry can and cannot do is provided
              in <xref target="security"/>.</dd>

          <dt>The network is untrusted</dt>
          <dd>The protocol assumes an active network attacker
              who can observe, inject, modify, and replay
              messages. All data plane communication is
              encrypted and authenticated. Control plane
              messages are signed. Tickets are bound to
              specific identities and time-limited.</dd>
        </dl>
      </section>
    </section>


    <section anchor="transport-model">
      <name>Transport Model</name>

      <t>
        CIRP separates protocol operations into two planes with
        distinct security properties and operational
        characteristics.
      </t>

      <section anchor="control-plane">
        <name>Control Plane</name>

        <t>
          The control plane handles registry-mediated operations:
          presence registration, capability advertisement,
          discovery queries, admission control, and ConnectTicket
          issuance. Control plane messages are exchanged between
          endpoints and registries.
        </t>

        <t>
          Control plane messages are small, fixed-structure, and
          designed to fit within a single transport datagram for
          typical deployments. This design prioritizes low-latency
          registry interactions and avoids fragmentation on common
          network paths.
        </t>

        <t>
          The control plane does not carry invocation content. It
          handles only the metadata necessary for discovery and
          session authorization.
        </t>
      </section>

      <section anchor="data-plane">
        <name>Data Plane</name>

        <t>
          The data plane handles peer-to-peer communication
          between consumer and provider after session
          establishment. All data plane traffic is encrypted
          using session keys derived during key exchange
          (<xref target="key-exchange"/>). The registry is not
          involved in data plane operations: it does not relay
          data, does not possess session keys, and cannot observe
          invocation content.
        </t>

        <t>
          The data plane carries invocation envelopes, response
          envelopes, and fulfillment records as defined in
          <xref target="intent-invocation"/> and
          <xref target="receipts"/>. Payload content within
          these envelopes is opaque to the transport.
        </t>
      </section>

      <section anchor="transport-bindings">
        <name>Transport Binding</name>

        <t>
          This specification defines the protocol abstractly in
          terms of messages exchanged between roles. A transport
          binding maps these abstract messages onto a concrete
          network transport.
        </t>

        <t>
          The initial transport binding is UDP. Control plane
          messages are exchanged as UDP datagrams between
          endpoints and registries. Data plane encrypted frames
          are exchanged as UDP datagrams between peers.
          Implementations are responsible for handling MTU
          constraints and chunking large payloads into multiple
          datagrams as needed.
        </t>

        <t>
          The protocol does not rely on TCP semantics. It does
          not assume ordered delivery, connection state, or
          stream-oriented framing at the transport layer. Each
          protocol message is self-contained within its
          datagram.
        </t>

        <t>
          Future transport bindings (for example, QUIC) may be
          defined in companion specifications. The protocol's
          message semantics are transport-independent; only the
          framing and delivery characteristics change with the
          binding.
        </t>
      </section>

      <section anchor="congestion-control">
        <name>Congestion Control</name>

        <t>
          Implementations using the UDP transport binding MUST conform
          to the guidelines in <xref target="RFC8085"/>. In particular:
        </t>

        <t>
          The control plane (Section 4.1) is self-limiting: each
          discovery query produces at most one response, presence
          announcements are periodic with configurable intervals,
          and admission exchanges are bounded by session establishment
          rate. Deployments MUST ensure that control plane traffic
          does not constitute sustained high-rate uncontrolled UDP
          transmission.
        </t>

        <t>
          The data plane (Section 4.2) carries application traffic
          within encrypted frames. Implementations that sustain
          high-rate data plane traffic MUST implement appropriate
          congestion control. CIRP does not define a congestion
          control algorithm; implementations MAY use any mechanism
          that provides appropriate congestion response, including
          rate limiting, application-level pacing, or integration
          with a congestion-controlled transport (such as QUIC in
          a future binding).
        </t>

        <t>
          Implementations SHOULD implement per-destination rate
          limiting on control plane transmissions to prevent
          amplification. A registry that receives a high rate of
          discovery queries for a single capability SHOULD apply
          local rate limits rather than forwarding unbounded traffic
          to providers.
        </t>
      </section>

      <section anchor="flow-control">
        <name>Flow Control</name>

        <t>
          CIRP does not define a protocol-level flow control
          mechanism. Each endpoint manages its own resource
          consumption locally. Implementations SHOULD enforce
          local limits on:
        </t>

        <t>
          Maximum inbound frame rate per session. Maximum encrypted
          frame size (bounded by the MTU of the underlying transport
          binding). Maximum concurrent sessions per endpoint.
        </t>

        <t>
          If an endpoint's local capacity is exceeded, it SHOULD
          stop reading from the transport socket, allowing natural
          backpressure. The sending peer will observe increased
          loss (for UDP) or reduced throughput (for future
          congestion-controlled bindings) and adapt accordingly.
        </t>

        <t>
          Sessions that exceed locally configured resource bounds
          MAY be terminated via the idle timeout mechanism
          (Section 8.7) or via an authenticated close frame.
        </t>
      </section>
    </section>
    <section anchor="capability-identifiers">
      <name>Capability Identifiers and Versioning</name>

      <t>
        Capabilities in CIRP are addressed using structured URIs
        that combine a hierarchical namespace with mandatory version
        information. This addressing scheme enables precise discovery,
        prevents semantic drift across protocol versions, and supports
        independent evolution of capabilities within distinct
        organizational namespaces.
      </t>

      <section anchor="capability-uri-syntax">
        <name>URI Syntax</name>

        <t>
          A capability URI uses the "cap" scheme and has the following
          structure:
        </t>

        <artwork><![CDATA[
cap-uri   = "cap:" path "/" version
path      = segment 1*("." segment)
segment   = ALPHA *(ALPHA / DIGIT / "-")
version   = "v" major "." minor
major     = 1*DIGIT
minor     = 1*DIGIT
        ]]></artwork>

        <t>
          The path component consists of dot-delimited segments
          forming a hierarchical namespace. The grammar requires at
          least two segments (one dot), establishing a minimum
          structure of namespace.action. Deeper hierarchies are
          permitted (e.g., "acme.robotics.arm.wave").
        </t>

        <t>
          Segments MUST begin with an ASCII letter and MAY contain
          ASCII letters, digits, and hyphens. Segments are
          case-sensitive.
        </t>

        <t>
          Examples of valid capability URIs:
        </t>

        <artwork><![CDATA[
cap:echo.ping/v1.0
cap:robot.wave/v1.0
cap:acme.robotics.arm.wave/v2.1
cap:org.medical.imaging.analyze/v1.0
        ]]></artwork>

        <t>
          Examples of invalid capability URIs:
        </t>

        <artwork><![CDATA[
cap:echo/v1.0          (single segment, no dot)
cap:robot.wave         (missing version)
cap:robot.wave/1.0     (version missing "v" prefix)
cap:123.test/v1.0      (segment begins with digit)
        ]]></artwork>
      </section>

      <section anchor="capability-versioning">
        <name>Versioning</name>

        <t>
          Every capability URI MUST include a version component in the
          format "v" followed by major.minor integers. Both the major
          and minor version numbers are REQUIRED.
        </t>

        <t>
          Version matching in this specification uses exact semantics:
          a Provider advertising cap:x.y/v1.2 MUST NOT fulfill
          requests for cap:x.y/v1.3 unless the Provider explicitly
          advertises that version as well. Consumers MUST include
          the complete version in all discovery requests and
          invocation envelopes.
        </t>

        <t>
          Compatible-range version matching (where a request for
          v1.2 could be fulfilled by a Provider advertising v1.3)
          is not defined in this specification. Future revisions
          MAY define version compatibility semantics.
        </t>
      </section>

      <section anchor="capability-hashing">
        <name>Capability Hashing</name>

        <t>
          For wire efficiency, capabilities are referenced in
          protocol messages by their hash rather than their full
          URI string. The capability hash is the SHA-256 digest of
          the capability's canonical name string.
        </t>

        <t>
          The canonical name string is the path component of the
          capability URI including the version suffix, without the
          "cap:" scheme prefix. For the capability URI
          "cap:acme.robotics.arm.wave/v1.0", the canonical name
          string is the exact UTF-8 byte sequence of
          "acme.robotics.arm.wave/v1.0". The format is
          "{path}/v{major}.{minor}" where {path} is the dotted
          capability path and "v{major}.{minor}" follows the
          version grammar defined in
          <xref target="capability-versioning"/>.
        </t>

        <t>
          The hash input is case-sensitive. No case folding, Unicode
          normalization, or whitespace removal is applied. The hash
          is computed over the exact UTF-8 byte sequence.
        </t>

        <t>
          For indexed lookups, the cap64 index key is the first
          8 bytes of the 32-byte SHA-256 digest, interpreted as a
          big-endian unsigned 64-bit integer.
        </t>

        <t>
          The following test vectors are normative. Any
          implementation that produces a different digest for either
          input has a conformance error.
        </t>

        <t>
          Vector 1: Input "system.echo/v1.0" produces SHA-256
          e81664e525710d5a2d0cece876c00f10ed79dec5d6c775869c5723fff7018ca7
          and cap64 0xe81664e525710d5a.
        </t>

        <t>
          Vector 2: Input "acme.robotics.arm.wave/v1.0" produces
          SHA-256
          386ed68f47809bde0663dc04a322766fd55aa9cdd41d7b6a1e147a90f9d96b85
          and cap64 0x386ed68f47809bde.
        </t>

        <t>
          Capability hashes are used in Authorization Requests
          (<xref target="session-authorization-phase"/>),
          ConnectTickets
          (<xref target="connect-ticket-structure"/>), and
          presence advertisements. The full URI string is carried
          only in invocation envelopes
          (<xref target="intent-invocation"/>) where human
          readability and version information are required.
        </t>
      </section>

      <section anchor="namespace-considerations">
        <name>Namespace Considerations</name>

        <t>
          The prefix "cap:proto." is RESERVED for capabilities
          defined by this protocol and its companion specifications.
          Implementations MUST NOT advertise capabilities under
          the "proto" namespace unless those capabilities are
          defined in a CIRP specification document.
        </t>

        <t>
          Namespace allocation and governance beyond the reserved
          prefix is a deployment concern. A registry for well-known
          capability namespaces may be established in a future
          revision of this specification. Deployments SHOULD use
          organizational domain names in reverse notation
          (e.g., "com.example.service.action") to minimize
          namespace collisions.
        </t>
      </section>
    </section>


    <section anchor="discovery">
      <name>Discovery</name>

      <t>
        Discovery is the process by which a Consumer locates
        Providers of a given capability. The protocol defines
        a query-response mechanism mediated by Registries and
        a scoping model that controls capability visibility
        across organizational and trust boundaries.
      </t>

      <t>
        Within an admitted scope, discovery results are unranked;
        however, Registries may apply policy controls including
        admission, scope enforcement, rate limiting, or denial of
        service. The protocol defines no preference ordering among
        eligible results. Visibility is governed by scope, not by
        policy ranking or commercial preference.
      </t>

      <section anchor="discovery-queries">
        <name>Discovery Queries</name>

        <t>
          A Consumer discovers Providers by sending a
          FIND_EX_REQUEST (TLV type 0x76) to a Registry. The
          request is a fixed-size 64-byte payload:
        </t>

        <dl>
          <dt>capability_hash (32 bytes)</dt>
          <dd>The SHA-256 hash of the desired capability,
              computed as specified in
              <xref target="capability-hashing"/>.</dd>

          <dt>initiator_eid (32 bytes)</dt>
          <dd>The Consumer's Endpoint Identifier.</dd>
        </dl>

        <t>
          The Consumer does not need to be authenticated to submit
          a discovery query, but the Registry MAY require the
          Consumer to be in an admitted state depending on
          deployment policy.
        </t>

        <t>
          Future revisions of this specification may define
          additional discovery message types that carry parameters
          such as result limits or locality hints. These are out
          of scope for the initial protocol binding.
        </t>
      </section>

      <section anchor="discovery-responses">
        <name>Discovery Responses</name>

        <t>
          The Registry responds with a FIND_EX_RESPONSE (TLV type
          0x77) containing at most one Provider entry. The response
          includes a status code indicating the outcome and, on
          success, the following fields:
        </t>

        <dl>
          <dt>provider_eid (32 bytes)</dt>
          <dd>The Provider's Endpoint Identifier.</dd>

          <dt>provider_locator (7 or 19 bytes)</dt>
          <dd>The Provider's network locator, consisting of an
              address type indicator (1 byte: 0x04 for IPv4,
              0x06 for IPv6), a port number (2 bytes,
              big-endian), and the IP address (4 or 16 bytes).
              The locator reflects the Provider's observed
              network address as determined by the Registry,
              not a self-reported value.</dd>
        </dl>

        <t>
          The Registry uses the Provider's observed network
          address (the source address of the Provider's most
          recent presence message) as the authoritative locator.
          Self-reported addresses in presence messages are not
          used for routing, preventing stale or spoofed locators
          from directing traffic to incorrect endpoints.
        </t>

        <t>
          Providers whose most recent presence beacon is older
          than a deployment-configured freshness threshold are
          excluded from query results. The freshness check uses
          the original beacon timestamp as recorded by the
          originating Registry, not timestamps assigned during
          inter-Registry synchronization. This prevents
          synchronized records from appearing artificially fresh.
        </t>

        <t>
          When a Registry applies policy that modifies the result
          set (e.g., scope restrictions or tier-based limits),
          the response SHOULD include a Policy Receipt indicating
          the nature and reason for the modification. This
          supports the principle that policy enforcement is
          permitted but silent policy enforcement is not: consumers
          and diagnostic tools can observe how policies affect
          discovery results.
        </t>
      </section>

      <section anchor="capability-scoping">
        <name>Capability Scoping</name>

        <t>
          Capability advertisements carry a scope descriptor that
          controls their visibility during discovery. Scope
          enforcement is performed by the Registry during query
          processing, not by peer-side filtering logic. This
          ensures that scope is enforced consistently regardless
          of Consumer implementation.
        </t>

        <t>
          Five scope levels are defined:
        </t>

        <section anchor="scope-levels">
          <name>Scope Levels</name>

          <table>
            <thead>
              <tr>
                <th>Level</th>
                <th>Value</th>
                <th>Qualifier</th>
                <th>Semantics</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>Local</td>
                <td>0x00</td>
                <td>None</td>
                <td>Visible only to the advertising endpoint
                    itself. Used for internal bookkeeping or
                    credential-agent patterns where a capability
                    serves only its owner.</td>
              </tr>
              <tr>
                <td>Identity</td>
                <td>0x01</td>
                <td>target_eid (32 bytes)</td>
                <td>Visible only to the endpoint identified by
                    target_eid. Used for pre-arranged
                    point-to-point capabilities.</td>
              </tr>
              <tr>
                <td>Trust Domain</td>
                <td>0x02</td>
                <td>anchor_hash (32 bytes)</td>
                <td>Visible to endpoints whose identity chain
                    includes the trust anchor identified by
                    anchor_hash.</td>
              </tr>
              <tr>
                <td>Organization</td>
                <td>0x03</td>
                <td>anchor_hash (32 bytes)</td>
                <td>Visible to endpoints within the organization
                    identified by anchor_hash. Semantically
                    equivalent to Trust Domain but conventionally
                    used for organizational boundaries.</td>
              </tr>
              <tr>
                <td>Global</td>
                <td>0x04</td>
                <td>None</td>
                <td>Visible to all endpoints without
                    restriction.</td>
              </tr>
            </tbody>
          </table>
        </section>

        <section anchor="scope-encoding">
          <name>Scope Encoding</name>

          <t>
            The scope descriptor is carried in capability
            advertisements as a compact binary encoding:
          </t>

          <artwork><![CDATA[
Scope Descriptor
+-------+------+----------------------------------------+
|Offset | Size | Field                                  |
+-------+------+----------------------------------------+
|   0   |   1  | scope_level                            |
|   1   | 0/32 | qualifier (present for levels 0x01-0x03)|
+-------+------+----------------------------------------+

Total size:
  Global (0x04):        1 byte
  Local (0x00):         1 byte
  Identity (0x01):     33 bytes (1 + 32-byte target EID)
  Trust Domain (0x02): 33 bytes (1 + 32-byte anchor hash)
  Organization (0x03): 33 bytes (1 + 32-byte anchor hash)
          ]]></artwork>

          <t>
            The anchor_hash for Trust Domain and Organization
            scopes is the SHA-256 hash of the trust anchor's
            Ed25519 public key. This allows scope enforcement
            without transmitting the full public key in every
            advertisement.
          </t>
        </section>

        <section anchor="scope-enforcement">
          <name>Scope Enforcement</name>

          <t>
            When processing a Discovery Query, the Registry
            evaluates the scope of each candidate Provider's
            capability advertisement against the querying
            Consumer's identity:
          </t>

          <dl>
            <dt>Global</dt>
            <dd>No restriction. The capability is included
                in results for any Consumer.</dd>

            <dt>Organization / Trust Domain</dt>
            <dd>The Registry checks whether the Consumer's
                identity chain includes the trust anchor
                identified by anchor_hash. If not, the
                capability is excluded from results.</dd>

            <dt>Identity</dt>
            <dd>The Registry checks whether the Consumer's
                EID matches the target_eid in the scope
                qualifier. If not, the capability is excluded
                from results.</dd>

            <dt>Local</dt>
            <dd>The capability is never included in Discovery
                Responses. Local capabilities are visible
                only through the advertising endpoint's own
                internal interfaces.</dd>
          </dl>

          <t>
            The mechanism by which a Registry determines
            membership in a trust domain is deployment-specific.
            Possible approaches include signed membership
            assertions from the trust anchor, pre-configured
            identity-to-domain mappings, or delegation chains.
            This specification defines the scope encoding and
            enforcement semantics but does not mandate a specific
            trust domain membership protocol.
          </t>
        </section>
      </section>

      <section anchor="federated-discovery">
        <name>Federated Discovery</name>

        <t>
          In deployments with multiple Registries, Providers may
          register with different Registries. To provide a
          consistent view of available capabilities, Registries
          MAY synchronize presence information through a
          federation protocol.
        </t>

        <t>
          When a Registry includes federated records in Discovery
          Responses, the freshness of those records MUST be
          evaluated using the original beacon timestamp as
          recorded by the Provider's home Registry, not the
          timestamp at which the record was received via
          federation. This prevents synchronized records from
          bypassing freshness filters.
        </t>

        <t>
          The federation synchronization protocol is outside the
          scope of this specification and will be defined in a
          companion document.
        </t>
      </section>

      <section anchor="per-capability-discovery">
        <name>Per-Capability Discovery Index</name>

        <t>
          A Provider that advertises multiple capabilities SHOULD
          emit a TLV_CAP_LIST frame (type 0x41) alongside each
          Presence announcement. A Provider that advertises
          multiple capabilities and does not emit TLV_CAP_LIST
          will not be discoverable by individual capability and
          will only be reachable via aggregate-hash discovery.
          The payload format is:
        </t>

        <artwork><![CDATA[
+--------+-----------------+------------------------------------------+
| 0x41   | Length (BE u16) | eid[32] | cap_hash_0[32] | ... | cap_N  |
+--------+-----------------+------------------------------------------+
]]></artwork>

        <t>
          The first 32 bytes of the payload are the Provider's
          Endpoint Identifier. The remainder is a sequence of
          32-byte capability hashes, one per advertised capability.
          There is no explicit count field; the number of
          capabilities is derived from the payload length:
          cap_count = (payload_length - 32) / 32.
        </t>

        <t>
          If the payload length is less than 32 bytes, or if
          (payload_length - 32) is not evenly divisible by 32, the
          frame is malformed and MUST be silently discarded per
          standard TLV processing rules.
        </t>

        <t>
          TLV_CAP_LIST is a companion to the Presence announcement
          and MAY arrive in the same transport datagram or in a
          separate datagram. If a TLV_CAP_LIST arrives before any
          Presence record exists for the given EID, the Registry
          SHOULD hold it in a short-lived pending cache
          (RECOMMENDED 5-second TTL, bounded to a
          deployment-configurable maximum entry count) and apply it
          when the next Presence announcement for that EID arrives.
        </t>

        <t>
          The Registry MUST maintain a per-capability discovery
          index that maps the first 8 bytes of each individual
          capability hash to sets of EIDs. When processing a
          Discovery Query, the Registry MUST check the
          per-capability index first. If results are found, those
          are returned. If the per-capability index contains no
          entries for the requested hash, the Registry MUST fall
          back to the aggregate capability index for backward
          compatibility with Providers that do not emit
          TLV_CAP_LIST.
        </t>
      </section>

      <section anchor="discovery-scope-exclusions">
        <name>Discovery Scope Exclusions</name>

        <t>
          The following functions are explicitly outside the scope
          of the discovery mechanism defined in this specification:
        </t>

        <t>
          Capability name resolution. The protocol operates
          exclusively on capability hashes. Translation from
          human-readable names, aliases, partial strings, or
          natural-language descriptions to canonical capability
          identifiers is a client-side concern and is not defined
          by this specification. Implementations MAY provide
          resolution libraries or local catalogs, but these are
          not part of the protocol.
        </t>

        <t>
          Capability enumeration. The protocol does not provide a
          mechanism for a Consumer to enumerate all capabilities
          available on the network. Discovery is query-by-hash
          only. A Consumer MUST know the hash of the capability it
          seeks. Registry-wide enumeration would create privacy and
          scalability concerns incompatible with the protocol's
          design goals.
        </t>

        <t>
          Fuzzy or semantic matching. The Registry performs exact
          hash comparison only. Approximate matching,
          natural-language understanding, embedding-based
          similarity, and recommendation are application-layer
          concerns above the transport protocol. The protocol's
          hash-based discovery ensures deterministic, reproducible
          results regardless of implementation.
        </t>

        <t>
          Capability ranking or recommendation. Within a given
          scope, discovery results are unranked. The protocol does
          not define ordering, scoring, or preferential treatment
          of Providers. Any ranking, prioritization, or
          recommendation of discovery results is a platform concern
          outside the scope of this specification.
        </t>
      </section>
    </section>


    <section anchor="session-establishment">
      <name>Session Establishment</name>

      <t>
        Session establishment in CIRP follows a three-phase model that
        separates discovery, authorization, and cryptographic session
        setup. The critical architectural property is that the Registry
        participates only in the first two phases. Once authorization
        is complete, the Registry exits the protocol path entirely.
        All subsequent communication occurs on a peer-to-peer encrypted
        channel that the Registry can neither observe nor influence.
      </t>

      <t>
        The three phases are:
      </t>

      <ol>
        <li>Registry Discovery: The Consumer selects a Registry based
            on measured network latency.</li>
        <li>Authorization and Ticket Issuance: The Consumer requests
            authorization to contact a Provider of a given capability.
            The Registry issues a signed ConnectTicket encoding the
            authorization decision.</li>
        <li>Peer Session: The Consumer contacts the Provider directly,
            presenting the ConnectTicket. The peers negotiate a
            cryptographic suite, perform key exchange, and establish
            an encrypted channel.</li>
      </ol>

      <t>
        This design ensures that connection throughput scales with the
        number of endpoints, not with Registry capacity. The Registry
        handles O(1) authorization per connection; it does not relay
        ongoing session traffic.
      </t>

      <section anchor="session-discovery-phase">
        <name>Registry Discovery</name>

        <t>
          A Consumer MAY be aware of multiple Registries through
          configuration, DNS service records, or prior interaction.
          When multiple Registries are available, the Consumer SHOULD
          select the Registry with the lowest measured round-trip
          latency to minimize authorization delay.
        </t>

        <t>
          Latency measurement is performed by sending a Probe
          message to each candidate Registry and measuring the time
          until a ProbeResponse is received. The Probe message
          contains an 8-byte nonce; the ProbeResponse echoes the
          nonce and MAY include the Registry's geographic locality
          hint. Registries MUST process Probe messages before any
          authentication or admission checks to ensure the measured
          latency reflects network conditions rather than processing
          overhead.
        </t>

        <t>
          Probe messages are subject to a separate rate limit from
          other Registry interactions to prevent measurement traffic
          from interfering with authorization and discovery operations.
        </t>
      </section>

      <section anchor="session-authorization-phase">
        <name>Authorization and Ticket Issuance</name>

        <t>
          To establish a session with a Provider of a given capability,
          the Consumer sends an Authorization Request to a Registry.
          The request contains the SHA-256 hash of the desired capability
          URI and the Consumer's Endpoint Identifier (EID).
        </t>

        <t>
          Upon receiving an Authorization Request, the Registry performs
          the following steps in order:
        </t>

        <ol>
          <li>Validates that the Consumer is in an admitted state. If
              not, the Registry returns a NotAdmitted status and takes
              no further action.</li>
          <li>Checks per-Consumer, per-capability rate limits. If the
              request exceeds the Consumer's rate allocation, the
              Registry returns a RateLimited status.</li>
          <li>Queries its capability directory for Providers currently
              advertising the requested capability. Providers whose
              most recent presence beacon is older than a freshness
              threshold (measured from original beacon timestamp,
              not from any intermediate synchronization event) are
              excluded from the candidate set.</li>
          <li>Applies any applicable visibility scope restrictions
              (see <xref target="discovery"/>).</li>
          <li>Selects a Provider from the eligible candidate set.</li>
          <li>Mints a ConnectTicket binding the Consumer, the
              selected Provider, and the requested capability.</li>
          <li>Signs the ConnectTicket with the Registry's current
              signing key.</li>
          <li>Returns an Authorization Response containing the
              Provider's EID, the Provider's network locator, and the
              signed ConnectTicket.</li>
        </ol>

        <section anchor="connect-ticket-structure">
          <name>ConnectTicket Structure</name>

          <t>
            The ConnectTicket is a fixed-size, binary-encoded
            authorization artifact. Its structure is as follows:
          </t>

          <artwork><![CDATA[
ConnectTicket (272 bytes)
+-------+------+----------------------------------------+
|Offset | Size | Field                                  |
+-------+------+----------------------------------------+
|   0   |  32  | consumer_eid                           |
|  32   |  32  | consumer_vk                            |
|  64   |  32  | provider_eid                           |
|  96   |  32  | capability_hash                        |
| 128   |   1  | scope_flags                            |
| 129   |   1  | tier                                   |
| 130   |   2  | rate_window_secs (BE)                  |
| 132   |   1  | rate_limit                             |
| 133   |   8  | issued_at (Unix seconds, BE)           |
| 141   |   8  | expires_at (Unix seconds, BE)          |
| 149   |  16  | nonce                                  |
| 165   |   8  | bucket_id                              |
| 173   |  32  | issuer_eid                             |
| 205   |   1  | issuer_key_id                          |
| 206   |   2  | issuer_locality (top 16 bits)          |
| 208   |  64  | signature (Ed25519, over bytes 0..208) |
+-------+------+----------------------------------------+
          ]]></artwork>

          <t>
            The signature covers the first 208 bytes of the ticket
            (all fields except the signature itself). The signature
            is computed using the Registry's Ed25519 signing key
            identified by issuer_key_id.
          </t>

          <t>
            The ConnectTicket has the following security properties:
          </t>

          <dl>
            <dt>Identity-bound</dt>
            <dd>The ticket names both the Consumer and Provider by EID.
                Because EID equals the node's Ed25519 public key, the
                Consumer must prove possession of the corresponding
                private key when using the ticket (by signing the
                session initiation message). A stolen ticket is
                useless without the Consumer's private key.</dd>

            <dt>Time-bounded</dt>
            <dd>The expires_at field limits the ticket's validity
                window. Implementations SHOULD use a validity period
                of 30 seconds. Providers MUST apply bounded clock
                skew tolerance when validating ticket timestamps.
                A ticket is valid if: now_secs is less than or equal
                to expires_at + leeway. A ticket MUST be rejected
                with a ClockSkew error if: issued_at exceeds
                now_secs + leeway. The RECOMMENDED default leeway
                is 10 seconds. This value accommodates typical NTP
                drift across cloud regions (2-5 seconds) with
                margin, while keeping the effective ticket validity
                window short enough that replay concerns remain
                bounded. The leeway SHOULD be
                deployment-configurable.</dd>

            <dt>Capability-scoped</dt>
            <dd>The ticket authorizes contact for a specific capability
                only, identified by capability_hash.</dd>

            <dt>Locally verifiable</dt>
            <dd>The Provider verifies the ticket by checking the
                Registry's signature using the Registry's public key.
                No round-trip to the Registry is required. The
                Provider learns the Registry's public key through
                control plane interactions (e.g., presence
                acknowledgments that carry the Registry's verifying
                key).</dd>

            <dt>Replay-resistant</dt>
            <dd>The nonce field, combined with nonce-tracking at the
                Provider, limits reuse of a single ticket. Tickets
                MAY be presented multiple times within their TTL
                (RECOMMENDED maximum 3 uses) to accommodate
                retransmission over lossy transports. Providers
                MUST check session idempotency (whether a session
                for this ticket already exists) before consuming a
                nonce slot. The nonce tracking window (RECOMMENDED
                60 seconds) MUST exceed the worst-case ticket
                validity window (TTL + leeway) to ensure replay
                protection covers the entire period during which a
                ticket could be presented.</dd>
          </dl>

          <t>
            The issuer_key_id field supports key rotation. Registries
            maintain a keyring of signing keys indexed by rotation
            identifier. When a Provider receives a ticket, it
            extracts issuer_key_id and looks up the corresponding
            verifying key. This allows Registries to rotate signing
            keys without invalidating tickets issued by the previous
            key during an overlap window.
          </t>
        </section>

        <section anchor="authorization-status-codes">
          <name>Authorization Status Codes</name>

          <t>
            The Authorization Response includes a status byte
            indicating the outcome:
          </t>

          <table>
            <thead>
              <tr><th>Code</th><th>Name</th><th>Description</th></tr>
            </thead>
            <tbody>
              <tr><td>0x00</td><td>Success</td>
                  <td>Ticket issued; Provider EID and locator included.</td></tr>
              <tr><td>0x01</td><td>NoMatchingProviders</td>
                  <td>No Provider currently advertises the capability.</td></tr>
              <tr><td>0x02</td><td>RateLimited</td>
                  <td>Consumer has exceeded its rate allocation.</td></tr>
              <tr><td>0x03</td><td>NotAdmitted</td>
                  <td>Consumer is not in an admitted state.</td></tr>
              <tr><td>0x04</td><td>PolicyBlocked</td>
                  <td>A visibility or scope policy prevents the request.</td></tr>
            </tbody>
          </table>

          <t>
            When the status is not Success, the response MUST NOT
            contain a ConnectTicket. Implementations SHOULD include
            a policy receipt (see <xref target="discovery"/>) with
            non-Success responses to support transparent diagnostics.
          </t>
        </section>
      </section>

      <section anchor="crypto-suite-negotiation">
        <name>Cryptographic Suite Negotiation</name>

        <t>
          After receiving an Authorization Response with a
          ConnectTicket, the Consumer initiates a direct connection
          to the Provider. Before exchanging key material, the peers
          MUST negotiate a cryptographic suite.
        </t>

        <t>
          Suite negotiation occurs as the first exchange on the
          peer-to-peer channel, before any key exchange messages.
          This keeps the Registry out of cryptographic decisions
          and avoids coupling Registry upgrades to cipher suite
          changes.
        </t>

        <section anchor="suite-identifiers">
          <name>Suite Identifiers</name>

          <t>
            A suite identifier is a string token registered in the
            CIRP Crypto Suite Registry (see
            <xref target="iana-crypto-suite"/>). Each suite
            identifier specifies the complete set of algorithms used
            for a session:
          </t>

          <dl>
            <dt>Key agreement</dt>
            <dd>The algorithm(s) used to establish a shared
                secret (e.g., X25519, X25519 combined with
                ML-KEM-768).</dd>

            <dt>Authentication</dt>
            <dd>The signature algorithm used to authenticate key
                exchange messages and verify identity
                (e.g., Ed25519).</dd>

            <dt>Symmetric encryption</dt>
            <dd>The authenticated encryption algorithm used for
                session data (e.g., ChaCha20-Poly1305).</dd>

            <dt>Key derivation</dt>
            <dd>The key derivation function used to derive session
                keys from the shared secret
                (e.g., HKDF-SHA-256).</dd>
          </dl>

          <t>
            The following suite is mandatory to implement:
          </t>

          <artwork><![CDATA[
CIRP_X25519_ED25519_CHACHA20POLY1305_SHA256

  Key agreement:          X25519 [RFC7748]
  Authentication:         Ed25519 [RFC8032]
  Symmetric encryption:   ChaCha20-Poly1305 [RFC8439]
  Key derivation:         HKDF-SHA-256 [RFC5869]
          ]]></artwork>

          <t>
            The following hybrid suite is defined for
            post-quantum transition:
          </t>

          <artwork><![CDATA[
CIRP_X25519MLKEM768_ED25519_CHACHA20POLY1305_SHA256

  Key agreement:          X25519 [RFC7748] combined with
                          ML-KEM-768 [FIPS203]
  Authentication:         Ed25519 [RFC8032]
  Symmetric encryption:   ChaCha20-Poly1305 [RFC8439]
  Key derivation:         HKDF-SHA-256 [RFC5869]
          ]]></artwork>
        </section>

        <section anchor="negotiation-protocol">
          <name>Negotiation Protocol</name>

          <t>
            Suite negotiation consists of two messages:
          </t>

          <dl>
            <dt>SuiteOffer (Consumer to Provider)</dt>
            <dd>Contains the Consumer's ConnectTicket, the Consumer's
                session identifier, and an ordered list of supported
                suite identifiers. The first entry is the Consumer's
                most preferred suite. The Consumer MUST sign the
                SuiteOffer with its Ed25519 private key to prove
                ownership of the EID named in the ticket.</dd>

            <dt>SuiteSelect (Provider to Consumer)</dt>
            <dd>Contains the Provider's selected suite identifier
                (a single value). The Provider MUST sign the
                SuiteSelect with its Ed25519 private key.</dd>
          </dl>

          <t>
            Upon receiving a SuiteOffer, the Provider:
          </t>

          <ol>
            <li>Validates the ConnectTicket (verifies the Registry's
                signature, checks expiration, confirms the Provider's
                own EID matches the ticket's provider_eid, and
                verifies the Consumer's signature on the SuiteOffer
                against the consumer_vk in the ticket).</li>
            <li>Selects the first suite from the Consumer's ordered
                list that the Provider also supports.</li>
            <li>If no common suite exists, the Provider silently
                drops the connection. The Consumer will observe a
                timeout.</li>
            <li>Returns a signed SuiteSelect containing the
                chosen suite identifier.</li>
          </ol>

          <t>
            Upon receiving a SuiteSelect, the Consumer:
          </t>

          <ol>
            <li>Verifies the Provider's signature.</li>
            <li>Confirms the selected suite was present in the
                Consumer's original SuiteOffer. If the selected
                suite was NOT offered, the Consumer MUST abort
                the connection. This prevents downgrade attacks
                where a network attacker substitutes the
                SuiteSelect message.</li>
            <li>Proceeds to key exchange using the negotiated
                suite.</li>
          </ol>
        </section>
      </section>

      <section anchor="key-exchange">
        <name>Key Exchange</name>

        <t>
          After suite negotiation, the peers perform a key exchange
          to establish shared session keys. The key exchange protocol
          depends on the negotiated suite but follows a uniform
          structure.
        </t>

        <section anchor="classical-key-exchange">
          <name>Classical Key Exchange</name>

          <t>
            For suites using X25519 as the sole key agreement
            algorithm, the key exchange proceeds as follows:
          </t>

          <ol>
            <li>Each peer generates an ephemeral X25519 keypair
                for this session.</li>
            <li>Each peer sends a KeyExchange message containing:
                the session identifier, a role indicator
                (0x01 for Consumer, 0x02 for Provider), its
                ephemeral X25519 public key (32 bytes), and an
                Ed25519 signature over these fields using the
                peer's long-term identity key.</li>
            <li>Each peer performs X25519 Diffie-Hellman using
                its ephemeral private key and the other peer's
                ephemeral public key, producing a 32-byte shared
                secret.</li>
            <li>Session keys are derived from the shared secret
                using the key schedule defined in
                <xref target="key-schedule"/>.</li>
          </ol>

          <t>
            The Ed25519 signatures on the KeyExchange messages
            bind the ephemeral keys to the peers' long-term
            identities. An attacker who intercepts the ephemeral
            public keys cannot forge the signatures without
            possessing the peers' long-term private keys.
          </t>

          <t>
            Ephemeral keys MUST be generated fresh for each
            session and MUST NOT be reused. This provides forward
            secrecy: compromise of a peer's long-term Ed25519
            signing key does not expose the session keys of
            previously established sessions.
          </t>
        </section>

        <section anchor="hybrid-key-exchange">
          <name>Hybrid Post-Quantum Key Exchange</name>

          <t>
            For suites combining X25519 with a post-quantum key
            encapsulation mechanism (such as ML-KEM-768 as
            specified in <xref target="FIPS203"/>), the key
            exchange produces two independent shared secrets that
            are combined via the key schedule.
          </t>

          <t>
            The hybrid exchange proceeds as follows:
          </t>

          <ol>
            <li>The Consumer generates an ephemeral X25519 keypair
                and an ephemeral ML-KEM-768 keypair. The Consumer
                sends both ephemeral public keys in its KeyExchange
                message, signed with its Ed25519 identity key.</li>
            <li>The Provider generates an ephemeral X25519 keypair.
                The Provider performs X25519 Diffie-Hellman to
                obtain the classical shared secret. The Provider
                encapsulates against the Consumer's ML-KEM-768
                public key to obtain a PQ ciphertext and the
                PQ shared secret. The Provider sends its X25519
                ephemeral public key and the ML-KEM-768 ciphertext,
                signed with its Ed25519 identity key.</li>
            <li>The Consumer performs X25519 Diffie-Hellman to
                obtain the classical shared secret. The Consumer
                decapsulates the ML-KEM-768 ciphertext to obtain
                the PQ shared secret.</li>
            <li>Both peers now hold two shared secrets:
                classical_ss (from X25519) and pq_ss (from
                ML-KEM-768). Session keys are derived using
                the key schedule in
                <xref target="key-schedule"/>.</li>
          </ol>

          <t>
            When operating in hybrid mode, both the classical
            and post-quantum key exchanges MUST succeed. If either
            exchange fails, session establishment MUST fail. An
            implementation MUST NOT fall back to classical-only
            key agreement when a hybrid suite has been negotiated.
            This prevents downgrade to classical-only security when
            both peers have agreed to hybrid protection.
          </t>
        </section>

        <section anchor="key-schedule">
          <name>Key Schedule</name>

          <t>
            Regardless of the negotiated suite, session keys are
            derived using HKDF <xref target="RFC5869"/> with
            SHA-256 as the hash function, following the
            extract-then-expand paradigm of
            <xref target="NIST-SP-800-56C"/>:
          </t>

          <artwork><![CDATA[
For classical suites:
  ikm = classical_ss

For hybrid suites:
  ikm = classical_ss || pq_ss

PRK   = HKDF-Extract(salt=session_id, ikm)
key   = HKDF-Expand(PRK, info, L=32)

where:
  session_id = 16-byte session identifier
  info       = "cirp-hybrid-kx" || suite_id ||
               consumer_eid || provider_eid
  L          = 32 (256 bits, for ChaCha20-Poly1305)
          ]]></artwork>

          <t>
            The info string binds the derived key material to the
            session context and both peer identities. The suite_id
            is the ASCII-encoded suite identifier string as
            negotiated. The consumer_eid and provider_eid are the
            32-byte EIDs of the respective peers.
          </t>

          <t>
            Using the same KDF structure for both classical and
            hybrid suites simplifies implementation and allows
            the key derivation path to be verified independently
            of the key agreement mechanism.
          </t>
        </section>
      </section>

      <section anchor="encrypted-session">
        <name>Encrypted Session</name>

        <t>
          Once session keys are derived, the peers communicate
          using authenticated encrypted frames. Each frame uses
          ChaCha20-Poly1305 <xref target="RFC8439"/> for
          authenticated encryption with associated data (AEAD).
        </t>

        <t>
          The encrypted frame format is:
        </t>

        <artwork><![CDATA[
Encrypted Frame
+-------+------+----------------------------------------+
|Offset | Size | Field                                  |
+-------+------+----------------------------------------+
|   0   |   4  | magic (0x41 0x49 0x43 0x46)            |
|   4   |  16  | session_id                             |
|  20   |   8  | counter (monotonic, BE)                |
|  28   |  12  | nonce (ChaCha20-Poly1305)              |
|  40   |   N  | ciphertext                             |
| 40+N  |  16  | authentication_tag (Poly1305)          |
+-------+------+----------------------------------------+
Minimum frame size: 56 bytes (empty payload)
        ]]></artwork>

        <t>
          The counter field is a monotonically increasing 64-bit
          integer, incremented for each frame sent within a session.
          Receivers MUST reject frames with a counter value less than
          or equal to the highest counter previously accepted in the
          same session. This provides replay protection without
          requiring synchronized state beyond the highest seen counter.
        </t>

        <t>
          The nonce is constructed deterministically from the counter
          and session key material. Implementations MUST NOT reuse
          a nonce with the same key.
        </t>

        <t>
          The protocol imposes no semantic limit on payload size
          within encrypted frames. Implementations MAY enforce
          practical limits based on the underlying transport binding.
          For UDP transport, implementations typically target
          application payloads that fit within common path MTU
          limits and handle larger payloads through fragmentation
          and reassembly at the session layer.
        </t>

        <t>
          The ciphertext is opaque to the transport layer. It
          carries application data (including intent invocation
          envelopes as defined in <xref target="intent-invocation"/>)
          without interpretation, canonicalization, or transformation
          by the transport.
        </t>
      </section>

      <section anchor="session-capability-binding">
        <name>Session Capability Binding</name>

        <t>
          Prior to establishing a session, the Provider MUST verify
          that the capability_hash field of the presented
          ConnectTicket identifies a capability that the Provider
          actively advertises. Formally, the Provider MUST confirm
          that ticket.capability_hash is a member of the set of
          per-capability hashes for which the Provider maintains
          current presence advertisements. If the capability_hash
          does not match any actively advertised capability, the
          Provider MUST reject the connection attempt with reason
          CapabilityNotServed (0x04).
        </t>

        <t>
          Upon successful session establishment, the session MUST
          retain the authorized capability_hash as immutable session
          state. The capability binding is fixed at session creation
          and MUST NOT change for the lifetime of the session.
          Implementations MUST NOT permit rebinding of a session to
          a different capability after establishment.
        </t>

        <t>
          The bound capability_hash MUST be available to the
          application layer for the purposes of session routing and
          audit. Providers serving multiple capabilities MUST use
          this binding to dispatch inbound sessions to the
          appropriate capability handler.
        </t>

        <t>
          The capability match verification set and the
          per-capability advertisement set MUST be maintained as a
          single atomic unit. If these sets can diverge, a ticket
          issued for a discovered capability could be rejected
          after discovery, or a ticket for a withdrawn capability
          could be accepted.
        </t>
      </section>

      <section anchor="session-lifecycle">
        <name>Session Lifecycle and Teardown</name>

        <t>
          A CIRP session transitions through five states: IDLE,
          DISCOVERY, AUTHORIZED, ESTABLISHED, and CLOSED. Once
          ESTABLISHED, the session remains active until one of the
          following conditions is met:
        </t>

        <t>
          Idle Timeout. If no encrypted frame
          (as defined in <xref target="encrypted-session"/>) is
          received from the remote peer within the locally configured
          inactivity window, the endpoint MUST transition the session
          to CLOSED and release all associated resources. The idle
          timeout is the normative teardown mechanism for CIRP
          sessions.
        </t>

        <t>
          Implementations SHOULD use a default idle timeout of
          120 seconds. Deployments MAY configure smaller values for
          resource-constrained or latency-sensitive environments.
          The idle timeout is a local configuration parameter; it is
          not negotiated between peers and need not be symmetric.
          Each endpoint independently manages its own session
          lifetime.
        </t>

        <t>
          Authenticated Close. An endpoint MAY send an encrypted
          control frame (type 0x01) within the AEAD-protected
          channel to signal intentional session termination. This
          frame carries a CloseReason code: 0x00 Normal (orderly
          shutdown), 0x01 GoingAway (endpoint shutting down),
          0x02 PolicyViolation (peer violated session constraints),
          0x03 InternalError (unrecoverable local failure).
        </t>

        <t>
          Receipt of an authenticated close frame SHOULD cause the
          local endpoint to transition the session to CLOSED. The
          authenticated close is a courtesy notification; the idle
          timeout provides the normative guarantee that abandoned
          sessions do not persist indefinitely.
        </t>

        <t>
          Unauthenticated Close Prohibition. Implementations MUST
          NOT transition session state based on receipt of any
          unauthenticated message. In particular, plaintext frames
          carrying session identifiers MUST be silently discarded
          without affecting session state. Any session teardown
          signal that is not protected by the session's AEAD keys
          is indistinguishable from a spoofed packet and MUST be
          ignored.
        </t>

        <t>
          Session Identifier Uniqueness. Each session MUST use a
          unique session_id generated from a cryptographically
          secure random source. A session_id MUST NOT be reused
          across sessions between the same pair of endpoints.
          Implementations SHOULD use 128-bit (16-octet) random
          values.
        </t>

        <t>
          Key Material Erasure. Upon transition to CLOSED,
          implementations MUST erase all ephemeral key material
          associated with the session, including X25519 private
          keys, derived session encryption keys, and AEAD nonce
          state. Implementations SHOULD use explicit memory
          zeroization rather than relying on garbage collection or
          deallocation.
        </t>
      </section>

      <section anchor="interoperability">
        <name>Backward Compatibility</name>

        <t>
          The per-capability discovery mechanism (TLV_CAP_LIST,
          type 0x41) is designed for incremental deployment with no
          flag day. Implementations MUST NOT require coordinated
          upgrades. Each side may upgrade independently. Full
          per-capability discovery activates when both Provider and
          Registry support TLV_CAP_LIST.
        </t>

        <t>
          Pre-0x41 Provider with pre-0x41 Registry: discovery
          operates via the aggregate capability index only.
          Single-capability Providers are discoverable;
          multi-capability Providers require exact aggregate hash
          match.
        </t>

        <t>
          Pre-0x41 Provider with 0x41-aware Registry: the Provider
          does not emit TLV_CAP_LIST. The Registry receives no
          per-capability hashes. FIND_EX queries fall back to the
          aggregate index. No degradation.
        </t>

        <t>
          0x41-aware Provider with pre-0x41 Registry: the Provider
          emits TLV_CAP_LIST (0x41). The Registry does not
          recognize type 0x41 and silently ignores it per standard
          TLV processing rules. Discovery continues via the
          aggregate index. No error, no degradation.
        </t>

        <t>
          0x41-aware Provider with 0x41-aware Registry: the
          Provider emits TLV_CAP_LIST alongside Presence
          announcements. The Registry indexes each individual
          capability hash. Multi-capability Providers are fully
          discoverable by any single capability they advertise.
        </t>
      </section>

      <section anchor="wire-compatibility">
        <name>Wire Compatibility</name>

        <t>
          The changes described in this revision introduce no
          modifications to existing wire formats. The Presence
          beacon (TLV 0x35) format is unchanged. The ConnectTicket
          wire format (272 octets) is unchanged. The FIND_EX_REQUEST
          (TLV 0x76) and FIND_EX_RESPONSE (TLV 0x77) formats are
          unchanged.
        </t>

        <t>
          TLV_CAP_LIST (type 0x41) is a new, additive TLV frame
          type. It does not replace or modify any existing frame.
          Per the TLV forward-compatibility rule, receivers that do
          not recognize type 0x41 silently ignore the frame.
        </t>

        <t>
          The encrypted frame format
          (<xref target="encrypted-session"/>) is unchanged. The
          addition of an authenticated close control frame (type
          0x01 within the AEAD-protected channel) uses the existing
          encrypted frame envelope and does not modify the frame
          header.
        </t>

        <t>
          No existing TLV type codes have been reassigned or
          removed. Implementations conforming to the previous
          revision will interoperate with implementations
          conforming to this revision without modification.
        </t>
      </section>
    </section>


    <section anchor="message-framing">
      <name>Message Framing and Encapsulation</name>

      <t>
        CIRP uses distinct framing for control plane and data
        plane messages to enable demultiplexing on shared
        transport sockets.
      </t>

      <section anchor="control-plane-framing">
        <name>Control Plane Framing</name>

        <t>
          Control plane messages between endpoints and Registries
          use a type-length-value (TLV) envelope:
        </t>

        <artwork><![CDATA[
Control Plane TLV Envelope
+-------+------+----------------------------------------+
|Offset | Size | Field                                  |
+-------+------+----------------------------------------+
|   0   |   1  | message_type                           |
|   1   |   2  | payload_length (BE)                    |
|   3   | var  | payload                                |
+-------+------+----------------------------------------+
        ]]></artwork>

        <t>
          The message_type field identifies the control plane
          operation (e.g., presence announcement, discovery
          query, authorization request). The payload_length
          field is a 16-bit big-endian unsigned integer
          specifying the number of bytes following the header.
        </t>
      </section>

      <section anchor="data-plane-framing">
        <name>Data Plane Framing</name>

        <t>
          Data plane messages between peers use magic-byte
          prefixed frames. Two frame types are defined:
        </t>

        <dl>
          <dt>Key Exchange Frame (magic: 0x41 0x49 0x4B 0x58,
              ASCII "AIKX")</dt>
          <dd>Carries suite negotiation, ephemeral public keys,
              and signatures during session establishment
              (<xref target="key-exchange"/>). Fixed structure:
              magic (4 bytes) + session_id (16) + role (1) +
              ephemeral_pk (32) + signature (64) = 117 bytes
              for classical suites. Hybrid suites include
              additional key material.</dd>

          <dt>Encrypted Frame (magic: 0x41 0x49 0x43 0x46,
              ASCII "AICF")</dt>
          <dd>Carries encrypted application data including
              invocation and fulfillment envelopes. Structure
              defined in <xref target="encrypted-session"/>:
              magic (4) + session_id (16) + counter (8) +
              nonce (12) + ciphertext (N) + tag (16).
              Minimum 56 bytes.</dd>
        </dl>

        <t>
          Receivers distinguish control plane from data plane
          messages by inspecting the first byte: TLV type
          values occupy a different range from the ASCII 'A'
          (0x41) that begins both data plane magic sequences.
        </t>
      </section>

      <section anchor="framing-properties">
        <name>Framing Properties</name>

        <t>
          The framing layer provides the following guarantees:
        </t>

        <dl>
          <dt>Payload opacity</dt>
          <dd>The framing layer does not inspect, parse, or
              transform the contents of encrypted frames.
              Ciphertext is carried as opaque bytes.</dd>

          <dt>Byte safety</dt>
          <dd>No canonicalization, character encoding
              conversion, or normalization is applied to
              payload data at any layer of the protocol.
              Binary payloads are preserved exactly as
              submitted.</dd>

          <dt>Replay protection</dt>
          <dd>Encrypted frames carry a monotonically
              increasing counter. Receivers reject frames
              with counter values not greater than the
              highest previously accepted value in the
              same session.</dd>
        </dl>
      </section>
    </section>


    <section anchor="intent-invocation">
      <name>Intent Invocation</name>

      <t>
        Intent invocation is the mechanism by which a Consumer
        requests execution of a capability and receives a
        fulfillment response from a Provider. Invocation messages
        are carried within the encrypted peer-to-peer session
        established in <xref target="session-establishment"/>.
        The transport layer does not interpret, validate, or
        transform the payload contents of invocations or
        fulfillments.
      </t>

      <t>
        Invocation envelopes are encoded using CBOR
        <xref target="RFC8949"/> with deterministic encoding
        as specified in Section 4.2 of that document. Deterministic
        encoding ensures that the same logical content always
        produces identical byte sequences, which is essential for
        stable signatures and hash computation. CBOR map keys
        are encoded as unsigned integers for compactness.
      </t>

      <section anchor="invocation-request">
        <name>Request Envelope</name>

        <t>
          The invocation request envelope contains the following
          fields, encoded as a CBOR map with integer keys:
        </t>

        <table>
          <thead>
            <tr>
              <th>Key</th><th>Field</th><th>CBOR Type</th>
              <th>Size</th><th>Description</th>
            </tr>
          </thead>
          <tbody>
            <tr><td>1</td><td>invocation_id</td>
                <td>bstr</td><td>16</td>
                <td>Unique identifier for this invocation,
                    generated randomly by the Consumer.</td></tr>
            <tr><td>2</td><td>capability_uri</td>
                <td>tstr</td><td>variable</td>
                <td>Full capability URI including version
                    (e.g., "cap:robot.wave/v1.0"). This is
                    the human-readable form; the hash used
                    during discovery can be recomputed from
                    this value.</td></tr>
            <tr><td>3</td><td>payload_type</td>
                <td>tstr</td><td>variable</td>
                <td>Identifier for the payload encoding.
                    This MAY be a MIME type or an
                    application-defined type string. The
                    protocol does not interpret this value.</td></tr>
            <tr><td>4</td><td>payload</td>
                <td>bstr</td><td>variable</td>
                <td>Opaque payload bytes. The protocol does
                    not parse, canonicalize, or transform
                    this field. Content interpretation is
                    entirely the responsibility of the
                    Consumer and Provider.</td></tr>
            <tr><td>5</td><td>consumer_eid</td>
                <td>bstr</td><td>32</td>
                <td>Consumer's Endpoint Identifier.</td></tr>
            <tr><td>6</td><td>consumer_send_ts</td>
                <td>uint</td><td>8</td>
                <td>Consumer's local send timestamp,
                    unsigned 64-bit milliseconds since
                    Unix epoch.</td></tr>
            <tr><td>7</td><td>prev_invocation_hash</td>
                <td>bstr</td><td>32</td>
                <td>SHA-256 hash of the previous invocation
                    envelope in this Consumer-Provider
                    relationship. Set to 32 zero bytes for
                    the first invocation. See
                    <xref target="hash-chaining"/>.</td></tr>
            <tr><td>8</td><td>consumer_signature</td>
                <td>bstr</td><td>64</td>
                <td>Ed25519 signature computed over the
                    deterministic CBOR encoding of fields
                    1 through 7 (all fields except the
                    signature itself).</td></tr>
          </tbody>
        </table>

        <t>
          The Consumer MUST generate the invocation_id using a
          cryptographically secure random number generator.
          Invocation identifiers are used for correlation between
          requests, responses, error frames, and fulfillment
          records.
        </t>

        <t>
          The signature is computed over the canonical CBOR
          encoding of a map containing keys 1 through 7 with
          their values. The Consumer uses its Ed25519 signing
          key (the private key corresponding to consumer_eid)
          to produce the signature. This binds the invocation
          content to the Consumer's identity and prevents
          tampering in transit.
        </t>
      </section>

      <section anchor="invocation-response">
        <name>Response Envelope</name>

        <t>
          The Provider responds to an invocation with a
          fulfillment response encoded as a CBOR map:
        </t>

        <table>
          <thead>
            <tr>
              <th>Key</th><th>Field</th><th>CBOR Type</th>
              <th>Size</th><th>Description</th>
            </tr>
          </thead>
          <tbody>
            <tr><td>1</td><td>invocation_id</td>
                <td>bstr</td><td>16</td>
                <td>Copied from the request for
                    correlation.</td></tr>
            <tr><td>2</td><td>fulfillment_status</td>
                <td>uint</td><td>1</td>
                <td>Status of the fulfillment: 0x00 for
                    success, 0x01 for partial, 0x02 for
                    application error (details in
                    payload).</td></tr>
            <tr><td>3</td><td>payload_type</td>
                <td>tstr</td><td>variable</td>
                <td>Identifier for the response payload
                    encoding.</td></tr>
            <tr><td>4</td><td>payload</td>
                <td>bstr</td><td>variable</td>
                <td>Opaque response payload. Not parsed
                    by the protocol.</td></tr>
            <tr><td>5</td><td>provider_eid</td>
                <td>bstr</td><td>32</td>
                <td>Provider's Endpoint Identifier.</td></tr>
            <tr><td>6</td><td>provider_recv_ts</td>
                <td>uint</td><td>8</td>
                <td>Provider's local timestamp when the
                    request was received.</td></tr>
            <tr><td>7</td><td>provider_send_ts</td>
                <td>uint</td><td>8</td>
                <td>Provider's local timestamp when the
                    response was sent.</td></tr>
            <tr><td>8</td><td>request_hash</td>
                <td>bstr</td><td>32</td>
                <td>SHA-256 hash of the complete request
                    envelope (all bytes including the
                    Consumer's signature). Binds the
                    response to a specific request.</td></tr>
            <tr><td>9</td><td>provider_signature</td>
                <td>bstr</td><td>64</td>
                <td>Ed25519 signature over the
                    deterministic CBOR encoding of fields
                    1 through 8.</td></tr>
          </tbody>
        </table>

        <t>
          The Provider MUST include the request_hash computed
          over the entire request envelope as received. This
          cryptographically binds the response to the specific
          request, preventing a malicious party from associating
          a valid response with a different request.
        </t>

        <t>
          When the fulfillment_status is 0x02 (application error),
          the payload field carries application-specific error
          information. The protocol does not define the structure
          of application-level errors; they are opaque bytes
          interpreted solely by the Consumer and Provider.
        </t>
      </section>

      <section anchor="invocation-errors">
        <name>Error Semantics</name>

        <t>
          CIRP distinguishes between protocol-level errors and
          application-level errors. Application-level errors are
          carried inside the response payload (fulfillment_status
          0x02) and are opaque to the protocol. Protocol-level
          errors indicate failures in session establishment,
          authorization, or transport that prevent an invocation
          from reaching the Provider or a response from reaching
          the Consumer.
        </t>

        <t>
          Protocol-level errors are conveyed in signed error
          frames. Signing error frames prevents an active
          network attacker from injecting spurious errors to
          disrupt communication.
        </t>

        <t>
          An error frame is encoded as a CBOR map:
        </t>

        <table>
          <thead>
            <tr>
              <th>Key</th><th>Field</th><th>CBOR Type</th>
              <th>Description</th>
            </tr>
          </thead>
          <tbody>
            <tr><td>1</td><td>invocation_id</td>
                <td>bstr</td>
                <td>Correlation identifier from the
                    request, if available. Set to 16
                    zero bytes if the error is not
                    associated with a specific
                    invocation.</td></tr>
            <tr><td>2</td><td>error_code</td>
                <td>uint</td>
                <td>Numeric error code from the CIRP
                    Error Code registry.</td></tr>
            <tr><td>3</td><td>error_detail</td>
                <td>tstr</td>
                <td>Optional human-readable description.
                    Implementations MUST NOT rely on the
                    content of this field for
                    programmatic error handling.</td></tr>
            <tr><td>4</td><td>error_origin</td>
                <td>uint</td>
                <td>Indicates which component generated
                    the error: 0x01 for Registry
                    (authorization phase), 0x02 for
                    Provider (invocation phase), 0x03
                    for transport (timeout or connection
                    failure).</td></tr>
            <tr><td>5</td><td>originator_eid</td>
                <td>bstr</td>
                <td>EID of the entity that generated
                    the error.</td></tr>
            <tr><td>6</td><td>signature</td>
                <td>bstr</td>
                <td>Ed25519 signature over fields
                    1 through 5.</td></tr>
          </tbody>
        </table>

        <section anchor="error-codes">
          <name>Protocol Error Codes</name>

          <t>
            The following error codes are defined. Additional
            codes may be registered in the CIRP Protocol
            Parameters Registry
            (<xref target="iana-protocol-params"/>).
          </t>

          <table>
            <thead>
              <tr><th>Code</th><th>Name</th>
                  <th>Description</th></tr>
            </thead>
            <tbody>
              <tr><td>0x01</td>
                  <td>CAPABILITY_NOT_FOUND</td>
                  <td>No Provider advertises the requested
                      capability.</td></tr>
              <tr><td>0x02</td>
                  <td>PROVIDER_UNAVAILABLE</td>
                  <td>The Provider is not reachable or has
                      become unresponsive.</td></tr>
              <tr><td>0x03</td>
                  <td>AUTHORIZATION_EXPIRED</td>
                  <td>The ConnectTicket has expired.</td></tr>
              <tr><td>0x04</td>
                  <td>TICKET_INVALID</td>
                  <td>The ConnectTicket failed
                      validation.</td></tr>
              <tr><td>0x05</td>
                  <td>SUITE_MISMATCH</td>
                  <td>No common cryptographic suite
                      exists between the peers.</td></tr>
              <tr><td>0x06</td>
                  <td>RATE_LIMITED</td>
                  <td>The request exceeds the Consumer's
                      rate allocation.</td></tr>
              <tr><td>0x07</td>
                  <td>SCOPE_DENIED</td>
                  <td>The Consumer's identity does not
                      satisfy the capability's scope
                      requirements.</td></tr>
              <tr><td>0x08</td>
                  <td>TIMEOUT</td>
                  <td>The operation exceeded the
                      applicable time limit.</td></tr>
              <tr><td>0x09</td>
                  <td>INTERNAL_ERROR</td>
                  <td>An unspecified internal error
                      occurred.</td></tr>
            </tbody>
          </table>
        </section>
      </section>
    </section>


    <section anchor="receipts">
      <name>Receipts and Integrity</name>

      <t>
        CIRP provides a mutually attested record of each
        invocation-fulfillment exchange. This record, called
        a fulfillment receipt, is constructed cooperatively
        by the Provider and Consumer through a two-phase
        signing ceremony. The receipt establishes a
        tamper-evident, non-repudiable audit trail without
        relying on a trusted third party or synchronized
        clocks.
      </t>

      <section anchor="fulfillment-record">
        <name>Fulfillment Record Structure</name>

        <t>
          The fulfillment record is encoded as a CBOR map
          using deterministic encoding
          (<xref target="RFC8949"/>, Section 4.2). The record
          is constructed in two phases:
        </t>

        <section anchor="receipt-phase-one">
          <name>Phase 1: Provider Attestation</name>

          <t>
            After processing an invocation and generating a
            response, the Provider constructs a partial
            fulfillment record containing the following
            fields:
          </t>

          <table>
            <thead>
              <tr><th>Key</th><th>Field</th>
                  <th>CBOR Type</th><th>Description</th></tr>
            </thead>
            <tbody>
              <tr><td>1</td><td>invocation_id</td>
                  <td>bstr (16)</td>
                  <td>Identifier of the invocation this
                      receipt covers.</td></tr>
              <tr><td>2</td><td>request_hash</td>
                  <td>bstr (32)</td>
                  <td>SHA-256 hash of the complete
                      request envelope as received.</td></tr>
              <tr><td>3</td><td>response_hash</td>
                  <td>bstr (32)</td>
                  <td>SHA-256 hash of the complete
                      response envelope as sent.</td></tr>
              <tr><td>4</td><td>provider_recv_ts</td>
                  <td>uint</td>
                  <td>Provider's local clock reading when
                      the request was received
                      (milliseconds since epoch).</td></tr>
              <tr><td>5</td><td>provider_send_ts</td>
                  <td>uint</td>
                  <td>Provider's local clock reading when
                      the response was sent.</td></tr>
              <tr><td>6</td><td>provider_eid</td>
                  <td>bstr (32)</td>
                  <td>Provider's Endpoint
                      Identifier.</td></tr>
              <tr><td>7</td><td>provider_signature</td>
                  <td>bstr (64)</td>
                  <td>Ed25519 signature over the
                      deterministic CBOR encoding of
                      fields 1 through 6.</td></tr>
            </tbody>
          </table>

          <t>
            The Provider sends this partial record alongside
            the response envelope. The Provider's signature
            attests that the Provider processed the identified
            request and produced the identified response at
            the stated times.
          </t>
        </section>

        <section anchor="receipt-phase-two">
          <name>Phase 2: Consumer Finalization</name>

          <t>
            Upon receiving the response and the Provider's
            partial record, the Consumer appends additional
            fields and produces the final fulfillment record:
          </t>

          <table>
            <thead>
              <tr><th>Key</th><th>Field</th>
                  <th>CBOR Type</th><th>Description</th></tr>
            </thead>
            <tbody>
              <tr><td>1-7</td><td>(Provider fields)</td>
                  <td>(as above)</td>
                  <td>All fields from Phase 1 including
                      the Provider's signature.</td></tr>
              <tr><td>8</td><td>consumer_send_ts</td>
                  <td>uint</td>
                  <td>Consumer's local clock reading when
                      the request was sent.</td></tr>
              <tr><td>9</td><td>consumer_recv_ts</td>
                  <td>uint</td>
                  <td>Consumer's local clock reading when
                      the response was received.</td></tr>
              <tr><td>10</td><td>consumer_eid</td>
                  <td>bstr (32)</td>
                  <td>Consumer's Endpoint
                      Identifier.</td></tr>
              <tr><td>11</td><td>consumer_signature</td>
                  <td>bstr (64)</td>
                  <td>Ed25519 signature over the
                      deterministic CBOR encoding of
                      fields 1 through 10 (including
                      the Provider's signature).</td></tr>
            </tbody>
          </table>

          <t>
            The Consumer's signature covers the entire record
            including the Provider's signature at field 7. This
            creates a nested attestation: the Consumer attests
            that it received the Provider's signed partial record
            and that the Consumer's own timestamps are accurate.
            Neither party can tamper with the other's attested
            fields without invalidating the corresponding
            signature.
          </t>
        </section>

        <section anchor="receipt-verification">
          <name>Receipt Verification</name>

          <t>
            A third party verifying a fulfillment record
            performs the following checks:
          </t>

          <ol>
            <li>Reconstruct the CBOR map of fields 1 through 6
                using deterministic encoding. Verify the
                Provider's signature (field 7) against this
                encoding using the provider_eid as the
                verification key.</li>
            <li>Reconstruct the CBOR map of fields 1 through 10
                using deterministic encoding. Verify the
                Consumer's signature (field 11) against this
                encoding using the consumer_eid as the
                verification key.</li>
            <li>Confirm that the provider_eid and consumer_eid
                match the expected parties.</li>
            <li>Optionally verify that request_hash and
                response_hash match the actual request and
                response envelopes if those are available.</li>
          </ol>

          <t>
            If both signatures verify, the receipt
            cryptographically establishes that both parties
            participated in the exchange and attested to its
            contents.
          </t>
        </section>
      </section>

      <section anchor="timestamp-model">
        <name>Timestamp Model</name>

        <t>
          The fulfillment record contains four timestamps
          representing the complete round-trip of an
          invocation:
        </t>

        <artwork><![CDATA[
Consumer              Provider
   |                     |
   |-- consumer_send_ts  |
   |   (request sent)    |
   |                     |
   |         provider_recv_ts --
   |         (request received) |
   |                     |
   |         provider_send_ts --
   |         (response sent)    |
   |                     |
   |-- consumer_recv_ts  |
   |   (response received)|
   |                     |
        ]]></artwork>

        <t>
          Timestamps are encoded as unsigned 64-bit integers
          representing milliseconds since the Unix epoch
          (1970-01-01T00:00:00Z).
        </t>

        <t>
          Clock synchronization between Consumer and Provider
          is NOT assumed. Each party records timestamps using
          its own local clock. The timestamp model is designed
          for relative timing analysis rather than absolute
          time agreement:
        </t>

        <dl>
          <dt>Provider processing time</dt>
          <dd>provider_send_ts - provider_recv_ts (single clock,
              reliable).</dd>

          <dt>Observed round-trip time</dt>
          <dd>consumer_recv_ts - consumer_send_ts (single clock,
              reliable).</dd>

          <dt>Estimated one-way latency</dt>
          <dd>(consumer_recv_ts - consumer_send_ts) -
              (provider_send_ts - provider_recv_ts), divided
              by two. This estimate requires no clock
              synchronization because the subtracted intervals
              are each measured on a single clock.</dd>
        </dl>

        <t>
          The consumer_send_ts and provider_send_ts fields are
          REQUIRED (MUST be present). The provider_recv_ts and
          consumer_recv_ts fields SHOULD be present; omitting
          them degrades the usefulness of the receipt for
          latency analysis but does not invalidate the record.
        </t>

        <t>
          Implementations MUST NOT reject receipts solely because
          timestamps appear inconsistent (e.g., provider_recv_ts
          earlier than consumer_send_ts). Such inconsistency
          indicates clock skew, not necessarily fraud.
          Implementations MAY flag such records for review.
        </t>
      </section>

      <section anchor="hash-chaining">
        <name>Hash Chaining</name>

        <t>
          Each invocation request envelope includes a
          prev_invocation_hash field (see
          <xref target="invocation-request"/>) that references
          the SHA-256 hash of the immediately preceding request
          envelope in the same Consumer-Provider relationship.
          For the first invocation between a given Consumer and
          Provider, this field is set to 32 zero bytes.
        </t>

        <t>
          This creates a hash chain that provides the following
          properties:
        </t>

        <dl>
          <dt>Ordering evidence</dt>
          <dd>Each invocation cryptographically references its
              predecessor, establishing an order that cannot
              be rearranged without detection.</dd>

          <dt>Deletion detection</dt>
          <dd>Removing an invocation from the chain breaks the
              hash reference in the subsequent invocation,
              making omission detectable.</dd>

          <dt>Per-relationship isolation</dt>
          <dd>The chain is scoped to a specific Consumer-Provider
              pair. Invocations between different pairs maintain
              independent chains.</dd>
        </dl>

        <t>
          Hash chains are maintained by the Consumer. A Consumer
          MUST track the hash of its most recent request envelope
          for each active Provider relationship. If a Consumer
          loses this state (e.g., due to a restart), it SHOULD
          set prev_invocation_hash to 32 zero bytes, effectively
          starting a new chain. The Provider MAY note chain
          resets for audit purposes but MUST NOT reject
          invocations solely because the hash chain was reset.
        </t>
      </section>
    </section>


    <section anchor="security">
      <name>Security Considerations</name>

      <t>
        This section analyzes the security properties of CIRP
        under a threat model that assumes an active network
        attacker, potentially compromised Registries, and
        potentially compromised peer endpoints. The analysis
        addresses each threat class and identifies the
        cryptographic mechanisms that mitigate it.
      </t>

      <section anchor="threat-model">
        <name>Threat Model</name>

        <t>
          The protocol is designed to resist the following
          adversaries:
        </t>

        <dl>
          <dt>Active network attacker</dt>
          <dd>An adversary positioned on the network path who
              can observe, inject, modify, delay, replay, and
              drop messages. This is the standard Dolev-Yao
              network attacker model.</dd>

          <dt>Compromised Registry</dt>
          <dd>A Registry that deviates from protocol
              specification, either through compromise or
              malicious operation. The analysis bounds what
              such a Registry can achieve.</dd>

          <dt>Compromised peer</dt>
          <dd>An endpoint whose long-term signing key has been
              compromised. The analysis addresses the impact
              on past and future sessions.</dd>

          <dt>Harvest-now-decrypt-later attacker</dt>
          <dd>An adversary who records encrypted traffic today
              with the expectation of decrypting it in the
              future using advances in cryptanalysis or
              quantum computing.</dd>
        </dl>
      </section>

      <section anchor="mutual-authentication">
        <name>Mutual Authentication</name>

        <t>
          Every endpoint in CIRP is identified by its Ed25519
          <xref target="RFC8032"/> public key. The Endpoint
          Identifier (EID) is the public key itself, not a
          hash or derivative. This identity model has several
          consequences:
        </t>

        <ul>
          <li>No certificate authority is required. Identity is
              self-asserted and verified by checking signatures
              against the claimed EID.</li>
          <li>Impersonation requires possession of the victim's
              Ed25519 private key. There is no weaker path
              (such as compromising a CA) to forge an
              identity.</li>
          <li>All protocol messages that assert identity
              (ConnectTickets, SuiteOffer, SuiteSelect,
              KeyExchange, invocation envelopes, response
              envelopes, error frames, and fulfillment records)
              are signed by the originator's Ed25519 key.</li>
        </ul>

        <t>
          During session establishment, mutual authentication
          is achieved through the following chain: the Consumer
          proves identity by signing the SuiteOffer (which
          includes the ConnectTicket naming the Consumer's EID),
          and the Provider proves identity by signing the
          SuiteSelect and KeyExchange messages using the key
          corresponding to the EID named in the ticket's
          provider_eid field. Both parties verify these
          signatures before proceeding to key exchange.
        </t>
      </section>

      <section anchor="registry-trust-boundary">
        <name>Registry Trust Boundary</name>

        <t>
          Registries occupy a semi-trusted role. The following
          analysis enumerates what a Registry can and cannot
          do, and provides cryptographic justification for
          each boundary.
        </t>

        <section anchor="registry-can">
          <name>Actions a Registry Can Perform</name>

          <dl>
            <dt>Deny service</dt>
            <dd>A Registry can refuse to process discovery
                queries or authorization requests, preventing
                new sessions from being established through
                that Registry. Mitigation: endpoints can use
                alternative Registries.</dd>

            <dt>Filter discovery results</dt>
            <dd>A Registry can omit Providers from discovery
                responses, effectively hiding capabilities.
                Mitigation: Policy Receipts make filtering
                observable; multi-Registry deployments provide
                independent views.</dd>

            <dt>Refuse ticket issuance</dt>
            <dd>A Registry can decline to issue ConnectTickets,
                blocking specific Consumer-Provider pairs.
                Mitigation: same as denial of service.</dd>

            <dt>Shape visibility via scoping</dt>
            <dd>A Registry enforces scope restrictions during
                discovery. A malicious Registry could
                misapply scope rules to restrict or expand
                visibility beyond the Provider's intent.</dd>

            <dt>Observe authorization metadata</dt>
            <dd>A Registry necessarily observes which Consumer
                requested authorization to contact which
                Provider for which capability. This metadata
                is visible to the Registry by design.</dd>
          </dl>
        </section>

        <section anchor="registry-cannot">
          <name>Actions a Registry Cannot Perform</name>

          <dl>
            <dt>Forge endpoint identities</dt>
            <dd>Because EID equals the Ed25519 public key and
                there is no certificate authority, a Registry
                cannot create a valid identity for an endpoint
                it does not control. Signing a message as a
                given EID requires the corresponding private
                key, which the Registry does not possess.</dd>

            <dt>Decrypt data plane traffic</dt>
            <dd>Session keys are derived from ephemeral
                X25519 <xref target="RFC7748"/> Diffie-Hellman
                exchanges performed directly between peers.
                The Registry is not a party to the key exchange
                and does not receive ephemeral key material.
                Without the shared secret, the Registry cannot
                derive session keys or decrypt any data plane
                frame.</dd>

            <dt>Observe invocation content</dt>
            <dd>Invocation envelopes, fulfillment responses,
                and application payloads are carried
                exclusively on the data plane within encrypted
                frames. The Registry is architecturally
                excluded from the data plane
                (<xref target="data-plane"/>). It never
                receives, relays, or processes data plane
                messages.</dd>

            <dt>Tamper with invocation records</dt>
            <dd>Fulfillment records are dual-signed: the
                Provider signs fields 1 through 6, and the
                Consumer signs fields 1 through 10 including
                the Provider's signature
                (<xref target="fulfillment-record"/>).
                Modifying any field invalidates the
                corresponding signature. Since the Registry
                possesses neither party's signing key, it
                cannot produce valid replacement
                signatures.</dd>

            <dt>Issue tickets for endpoints it does not serve</dt>
            <dd>A ConnectTicket is signed by the issuing
                Registry's key. A Provider verifies the
                ticket signature against the Registry's
                known public key. A Registry cannot issue
                tickets that will be accepted by Providers
                that trust a different Registry, unless
                it possesses that other Registry's signing
                key.</dd>
          </dl>
        </section>
      </section>

      <section anchor="ticket-security">
        <name>Ticket Security</name>

        <t>
          The ConnectTicket
          (<xref target="connect-ticket-structure"/>) is the
          authorization boundary between the control plane and
          the data plane. Its security depends on several
          properties:
        </t>

        <dl>
          <dt>Identity binding</dt>
          <dd>The ticket names both Consumer and Provider by
              EID. The Consumer must sign the SuiteOffer
              using the private key corresponding to the
              consumer_eid in the ticket. A stolen ticket
              is useless without the Consumer's private
              key.</dd>

          <dt>Time bounding</dt>
          <dd>The ticket carries an expires_at field.
              Implementations SHOULD use a 30-second
              validity window. Providers MUST reject expired
              tickets. Short validity limits the window for
              ticket theft and replay.</dd>

          <dt>Nonce-based replay resistance</dt>
          <dd>The ticket contains a 16-byte nonce. Providers
              track seen nonces and reject tickets with
              previously used nonces. Combined with time
              bounding, this limits replay to the narrow
              validity window.</dd>

          <dt>Capability scoping</dt>
          <dd>The ticket binds authorization to a specific
              capability hash. A ticket issued for one
              capability cannot be used to invoke a
              different capability.</dd>

          <dt>Local verifiability</dt>
          <dd>Providers verify the Registry's signature on
              the ticket using the Registry's known public
              key. No round-trip to the Registry is needed.
              This means ticket verification succeeds even
              if the Registry becomes temporarily
              unreachable after ticket issuance.</dd>
        </dl>
      </section>

      <section anchor="forward-secrecy">
        <name>Forward Secrecy</name>

        <t>
          Each session uses freshly generated ephemeral X25519
          keypairs for key exchange. Session keys are derived
          from the ephemeral shared secret, not from the
          peers' long-term Ed25519 keys. Compromise of a
          peer's long-term signing key allows an attacker to
          impersonate that peer in future sessions, but it
          does not expose the session keys of previously
          established sessions because the ephemeral X25519
          private keys are discarded after key derivation.
        </t>

        <t>
          Long-term Ed25519 keys are used solely for
          authentication (signing key exchange messages and
          protocol artifacts), not for key agreement. The
          key agreement function (X25519) operates on
          independent ephemeral key material.
        </t>
      </section>

      <section anchor="replay-protection">
        <name>Replay Protection</name>

        <t>
          Replay attacks are addressed at multiple layers:
        </t>

        <dl>
          <dt>Encrypted frames</dt>
          <dd>Each encrypted frame carries a monotonically
              increasing counter
              (<xref target="encrypted-session"/>).
              Receivers reject frames with counter values
              less than or equal to the highest previously
              accepted counter in the session. Because the
              counter is included in the authenticated
              encryption, an attacker cannot modify it
              without detection.</dd>

          <dt>ConnectTickets</dt>
          <dd>Tickets are time-bounded (expires_at) and
              nonce-tracked at the Provider. Replaying an
              expired ticket or reusing a seen nonce results
              in rejection.</dd>

          <dt>Key exchange messages</dt>
          <dd>Each KeyExchange message is bound to a specific
              session_id and signed by the sender's
              long-term key. Replaying a KeyExchange message
              from a previous session in a new session
              produces a mismatched session_id, causing
              verification failure.</dd>
        </dl>
      </section>

      <section anchor="downgrade-protection">
        <name>Downgrade Protection</name>

        <t>
          Two mechanisms prevent cryptographic downgrade
          attacks:
        </t>

        <dl>
          <dt>Suite selection validation</dt>
          <dd>The Consumer MUST abort the connection if the
              Provider selects a suite that was not present
              in the Consumer's SuiteOffer
              (<xref target="negotiation-protocol"/>). This
              prevents an active attacker from substituting
              the SuiteSelect message to force a weaker
              suite. Both the SuiteOffer and SuiteSelect
              are signed, so the attacker cannot modify
              them without detection.</dd>

          <dt>Hybrid exchange integrity</dt>
          <dd>When a hybrid post-quantum suite is negotiated,
              both the classical and post-quantum key
              exchanges MUST succeed
              (<xref target="hybrid-key-exchange"/>). If
              either fails, session establishment fails
              entirely. An attacker cannot force fallback
              to classical-only key agreement after both
              peers have agreed to hybrid protection.</dd>
        </dl>
      </section>

      <section anchor="pqc-transition">
        <name>Post-Quantum Transition</name>

        <t>
          CIRP addresses the threat of harvest-now-decrypt-later
          attacks through its cryptographic agility framework
          and hybrid key exchange construction.
        </t>

        <t>
          The protocol supports hybrid post-quantum key
          establishment via negotiated cipher suites. In hybrid
          mode, session keys are derived from both a classical
          X25519 shared secret and a post-quantum shared secret
          (e.g., from ML-KEM-768 as specified in
          <xref target="FIPS203"/>), combined through HKDF
          (<xref target="key-schedule"/>). This construction
          ensures that the session key is at least as strong
          as the stronger of the two components: even if the
          post-quantum algorithm is later found to be weak,
          the classical component still provides security, and
          vice versa.
        </t>

        <t>
          The suite negotiation mechanism
          (<xref target="crypto-suite-negotiation"/>) allows
          new cryptographic suites to be deployed without
          protocol revision. As post-quantum algorithms mature
          and new key encapsulation mechanisms are standardized,
          they can be registered in the CIRP Crypto Suite
          Registry and adopted by implementations through
          normal suite negotiation.
        </t>

        <t>
          Implementations MUST support suite negotiation as
          defined in <xref target="crypto-suite-negotiation"/>.
          Implementations MUST support at least the mandatory
          classical suite. Implementations SHOULD support at
          least one hybrid post-quantum suite.
          Future revisions of this specification may strengthen
          the hybrid requirement to MUST as post-quantum
          algorithms achieve broader deployment maturity.
        </t>
      </section>

      <section anchor="invocation-integrity">
        <name>Invocation Integrity</name>

        <t>
          Invocation envelopes and response envelopes are
          signed by the originating party using Ed25519. The
          signature covers the deterministic CBOR encoding of
          all fields except the signature itself
          (<xref target="invocation-request"/>,
          <xref target="invocation-response"/>). This provides:
        </t>

        <ul>
          <li>Authentication: the envelope was produced by the
              claimed originator.</li>
          <li>Integrity: the envelope has not been modified
              since signing.</li>
          <li>Non-repudiation: the originator cannot deny
              having produced the envelope without claiming
              key compromise.</li>
        </ul>

        <t>
          The response envelope includes a request_hash field
          that binds the response to a specific request. This
          prevents an attacker from associating a legitimate
          response with a different request.
        </t>

        <t>
          Fulfillment records extend these properties through
          cooperative dual-signing
          (<xref target="fulfillment-record"/>), creating
          a mutually attested audit trail that neither party
          can unilaterally repudiate.
        </t>
      </section>

      <section anchor="error-frame-security">
        <name>Error Frame Security</name>

        <t>
          Protocol-level error frames are signed by the
          originating entity
          (<xref target="invocation-errors"/>). This prevents
          an active network attacker from injecting spurious
          error messages to disrupt communication. A receiver
          MUST verify the signature on an error frame before
          acting on it. Unsigned or incorrectly signed error
          frames MUST be discarded.
        </t>
      </section>

      <section anchor="scope-security">
        <name>Scope Enforcement Security</name>

        <t>
          Capability visibility scoping
          (<xref target="capability-scoping"/>) is enforced
          by the Registry during discovery. Because scope
          enforcement depends on the Registry correctly
          evaluating scope rules, a compromised Registry
          could bypass scope restrictions and expose
          capabilities intended to be private.
        </t>

        <t>
          Deployments with strict confidentiality requirements
          for capability visibility SHOULD use dedicated
          Registries operated within the trust domain.
          Capability scoping provides defense in depth but
          does not replace network-level access controls
          where confidentiality of capability existence is
          critical.
        </t>
      </section>

      <section anchor="dos-considerations">
        <name>Denial of Service Considerations</name>

        <t>
          Several protocol elements are susceptible to denial
          of service attacks:
        </t>

        <dl>
          <dt>Discovery flooding</dt>
          <dd>An attacker may send a high volume of discovery
              queries to exhaust Registry resources. Registries
              SHOULD implement per-source rate limiting on
              discovery queries. The admission control mechanism
              provides a first line of defense: unadmitted
              endpoints can be rejected before query processing
              begins.</dd>

          <dt>Ticket exhaustion</dt>
          <dd>An attacker may request ConnectTickets at high
              rate to exhaust Provider nonce tracking state.
              Per-consumer, per-capability rate limiting at the
              Registry bounds the rate at which tickets are
              issued. The short ticket validity window (30
              seconds) limits the volume of unexpired tickets
              in circulation.</dd>

          <dt>Probe flooding</dt>
          <dd>An attacker who obtains valid ConnectTickets may
              flood a Provider with connection probes. Providers
              SHOULD implement per-source connection rate limits.
              The four-phase validation order (structural,
              cryptographic, semantic, replay) ensures that
              invalid probes are rejected with minimal
              processing, with cryptographically invalid probes
              silently dropped to prevent oracle attacks.</dd>
        </dl>
      </section>

      <section anchor="time-skew-considerations">
        <name>Time Skew Considerations</name>

        <t>
          The protocol relies on timestamp comparison for
          ConnectTicket expiration checking. Clock skew between
          the Registry (which sets issued_at and expires_at) and
          the Provider (which checks expiration) may cause
          valid tickets to be incorrectly rejected or expired
          tickets to be incorrectly accepted.
        </t>

        <t>
          Implementations MUST apply bounded clock skew
          tolerance as defined in
          <xref target="connect-ticket-structure"/>. The
          RECOMMENDED 10-second leeway accommodates typical NTP
          drift across cloud regions (2-5 seconds) with margin.
          The future-rejection rule (rejecting tickets where
          issued_at exceeds now + leeway) prevents acceptance of
          tickets with severely desynchronized or maliciously
          future-dated issuance timestamps. Without this check,
          a compromised Registry could issue tickets with
          far-future issued_at values that would remain valid
          indefinitely under expiry-only validation. Deployments
          operating across geographically distributed
          infrastructure SHOULD ensure that Registry and Provider
          clocks are synchronized to within a few seconds using
          NTP or equivalent mechanisms.
        </t>

        <t>
          Fulfillment record timestamps
          (<xref target="timestamp-model"/>) explicitly do not
          assume clock synchronization between Consumer and
          Provider. Each party records timestamps on its own
          clock. The timestamp model is designed for single-clock
          interval analysis (processing time, round-trip time)
          rather than cross-clock absolute time comparisons.
        </t>
      </section>

      <section anchor="teardown-security">
        <name>Session Teardown Security</name>

        <t>
          The prohibition on unauthenticated close
          (<xref target="session-lifecycle"/>) eliminates a
          denial-of-service vector present in protocols that
          permit unsigned session teardown. An on-path attacker
          who observes a session_id during session establishment
          cannot forge a teardown message, because all
          state-changing messages after session establishment are
          protected by AEAD encryption with keys derived from the
          ephemeral key exchange.
        </t>

        <t>
          The idle timeout mechanism ensures that sessions are
          eventually reclaimed even if both endpoints lose
          connectivity simultaneously or if one endpoint crashes
          without sending an authenticated close. The combination
          of authenticated close (best-effort notification) and
          idle timeout (guaranteed reclamation) provides both
          promptness and reliability.
        </t>

        <t>
          Implementations MUST erase ephemeral key material on
          session close to preserve forward secrecy. An attacker
          who later compromises an endpoint's long-term signing
          key MUST NOT be able to derive past session keys.
        </t>
      </section>

      <section anchor="locator-integrity">
        <name>Locator Integrity and Misdirection Attacks</name>

        <t>
          The FIND_EX response envelope, including the Provider's
          RLOC (routing locator), is not independently signed. An
          active attacker between Consumer and Registry could
          modify the RLOC to redirect the Consumer to a different
          network address. However, the attacker cannot complete
          the subsequent key exchange because it does not possess
          the Provider's private key. This is a
          misdirection-for-DoS vector, not a confidentiality or
          integrity compromise.
        </t>

        <t>
          Deployments requiring authenticated locator distribution
          SHOULD use a transport binding that provides channel
          integrity (e.g., DTLS or QUIC).
        </t>
      </section>
    </section>

    <section anchor="iana">
      <name>IANA Considerations</name>

      <t>
        This document requests the creation of the following
        registries upon publication.
      </t>

      <section anchor="iana-crypto-suite">
        <name>CIRP Crypto Suite Registry</name>

        <t>
          IANA is requested to create a "CIRP Crypto Suite
          Registry" with the following initial entries.
          New entries require Specification Required
          registration policy.
        </t>

        <table>
          <thead>
            <tr>
              <th>Suite Identifier</th>
              <th>Status</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>CIRP_X25519_ED25519_CHACHA20POLY1305_SHA256</td>
              <td>Mandatory</td>
            </tr>
            <tr>
              <td>CIRP_X25519MLKEM768_ED25519_CHACHA20POLY1305_SHA256</td>
              <td>Recommended</td>
            </tr>
          </tbody>
        </table>

        <t>
          Each suite identifier encodes its component algorithms
          in the name: key agreement, authentication, AEAD, and
          KDF, separated by underscores. The algorithms for
          each initial entry are specified in
          <xref target="suite-identifiers"/>.
        </t>

        <t>
          Suite identifiers are ASCII strings. The naming
          convention concatenates the key agreement, authentication,
          AEAD, and KDF algorithm names separated by underscores,
          prefixed with "CIRP_".
        </t>

        <t>
          The ML-KEM-768 algorithm in the hybrid suite is as
          specified in <xref target="FIPS203"/>.
        </t>
      </section>

      <section anchor="iana-uri-scheme">
        <name>URI Scheme Registration</name>

        <t>
          IANA is requested to register the "cap" URI scheme
          in the "Uniform Resource Identifier (URI) Schemes"
          registry per <xref target="RFC7595"/>.
        </t>

        <dl>
          <dt>Scheme name</dt>
          <dd>cap</dd>
          <dt>Status</dt>
          <dd>Permanent</dd>
          <dt>Applications/protocols that use this scheme</dt>
          <dd>CIRP (Capability-Oriented Intent Routing Protocol)</dd>
          <dt>Contact</dt>
          <dd>IESG</dd>
          <dt>Change controller</dt>
          <dd>IETF</dd>
          <dt>Reference</dt>
          <dd>This document,
              <xref target="capability-uri-syntax"/></dd>
        </dl>
      </section>

      <section anchor="iana-protocol-params">
        <name>CIRP Protocol Parameters Registry</name>

        <t>
          IANA is requested to create a "CIRP Protocol Parameters"
          registry with the following sub-registries. New entries
          in each sub-registry require Specification Required
          registration policy.
        </t>

        <section anchor="iana-error-codes">
          <name>Error Codes</name>

          <t>
            Initial entries are as defined in
            <xref target="error-codes"/>. The registry contains:
            code (uint8), name (string), and description (string).
          </t>

          <t>
            Error codes are scoped to their enclosing TLV message
            type. Identical numeric values in different message
            types carry different semantics (e.g., 0x01 is
            TicketExpired in DirectAck but CapabilityNotFound in
            FindExStatus). Implementations MUST interpret error
            codes in the context of the message type that carries
            them. Four allocation ranges are defined: 0x00-0x3F
            for protocol-defined codes, 0x40-0xBF for
            profile-defined codes, 0xC0-0xFE for private use,
            and 0xFF reserved for future use.
          </t>
        </section>

        <section anchor="iana-scope-types">
          <name>Scope Types</name>

          <t>
            Initial entries are the five scope levels defined
            in <xref target="scope-levels"/>. The registry
            contains: value (uint8), name (string), qualifier
            size (uint8), and description (string).
          </t>
        </section>

        <section anchor="iana-message-types">
          <name>Message Types</name>

          <t>
            This sub-registry tracks control plane TLV message
            types and data plane magic-byte prefixes. Initial
            entries include the message types referenced in
            <xref target="message-framing"/>.
          </t>
        </section>
      </section>
    </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"/><date month="March" year="1997"/></front>
          <seriesInfo name="BCP" value="14"/><seriesInfo name="RFC" value="2119"/>
        </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"/><date month="May" year="2017"/></front>
          <seriesInfo name="BCP" value="14"/><seriesInfo name="RFC" value="8174"/>
        </reference>
        <reference anchor="RFC8032" target="https://www.rfc-editor.org/info/rfc8032">
          <front><title>Edwards-Curve Digital Signature Algorithm (EdDSA)</title><author initials="S." surname="Josefsson"/><author initials="I." surname="Liusvaara"/><date month="January" year="2017"/></front>
          <seriesInfo name="RFC" value="8032"/>
        </reference>
        <reference anchor="RFC7748" target="https://www.rfc-editor.org/info/rfc7748">
          <front><title>Elliptic Curves for Security</title><author initials="A." surname="Langley"/><author initials="M." surname="Hamburg"/><author initials="S." surname="Turner"/><date month="January" year="2016"/></front>
          <seriesInfo name="RFC" value="7748"/>
        </reference>
        <reference anchor="RFC8439" target="https://www.rfc-editor.org/info/rfc8439">
          <front><title>ChaCha20 and Poly1305 for IETF Protocols</title><author initials="Y." surname="Nir"/><author initials="A." surname="Langley"/><date month="June" year="2018"/></front>
          <seriesInfo name="RFC" value="8439"/>
        </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"/><author initials="P." surname="Eronen"/><date month="May" year="2010"/></front>
          <seriesInfo name="RFC" value="5869"/>
        </reference>
        <reference anchor="RFC8949" target="https://www.rfc-editor.org/info/rfc8949">
          <front><title>Concise Binary Object Representation (CBOR)</title><author initials="C." surname="Bormann"/><author initials="P." surname="Hoffman"/><date month="December" year="2020"/></front>
          <seriesInfo name="STD" value="94"/><seriesInfo name="RFC" value="8949"/>
        </reference>
        <reference anchor="RFC7595" target="https://www.rfc-editor.org/info/rfc7595">
          <front><title>Guidelines and Registration Procedures for URI Schemes</title><author initials="D." surname="Thaler"/><author initials="T." surname="Hansen"/><author initials="T." surname="Hardie"/><date month="June" year="2015"/></front>
          <seriesInfo name="BCP" value="35"/><seriesInfo name="RFC" value="7595"/>
        </reference>
        <reference anchor="RFC8085" target="https://www.rfc-editor.org/info/rfc8085">
          <front><title>UDP Usage Guidelines</title><author initials="L." surname="Eggert"/><author initials="G." surname="Fairhurst"/><author initials="G." surname="Shepherd"/><date month="March" year="2017"/></front>
          <seriesInfo name="BCP" value="145"/><seriesInfo name="RFC" value="8085"/>
        </reference>
      </references>
      <references>
        <name>Informative References</name>
        <reference anchor="FIPS203">
          <front><title>Module-Lattice-Based Key-Encapsulation Mechanism Standard</title><author><organization>NIST</organization></author><date month="August" year="2024"/></front>
          <seriesInfo name="FIPS" value="203"/>
        </reference>
        <reference anchor="NIST-SP-800-56C" target="https://csrc.nist.gov/publications/detail/sp/800-56c/rev-2/final">
          <front><title>Recommendation for Key-Derivation Methods in Key-Establishment Schemes</title><author><organization>NIST</organization></author><date month="August" year="2020"/></front>
          <seriesInfo name="SP" value="800-56C Rev. 2"/>
        </reference>
      </references>
    </references>

    <section anchor="examples" numbered="false">
      <name>Examples</name>

      <section anchor="example-multi-cap" numbered="false">
        <name>B.1. Multi-Capability Provider Registration</name>

        <t>
          A compliance agent advertises three capabilities:
          compliance.assess/v1.0, compliance.report/v1.0, and
          compliance.audit/v1.0. The agent computes per-capability
          hashes:
        </t>

        <artwork><![CDATA[
cap_hash_1 = SHA-256("compliance.assess/v1.0")
cap_hash_2 = SHA-256("compliance.report/v1.0")
cap_hash_3 = SHA-256("compliance.audit/v1.0")

sorted = sort(["compliance.assess/v1.0",
                "compliance.audit/v1.0",
                "compliance.report/v1.0"])
caps_hash = SHA-256(join(sorted, "\n"))
        ]]></artwork>

        <t>
          The agent sends a single UDP datagram containing two
          coalesced TLVs:
        </t>

        <artwork><![CDATA[
+------+--------+------------------------------------------+
| 0x35 | len    | Presence beacon (v2)                     |
|      |        |   eid[32] + flags[1] + rloc[7] +         |
|      |        |   ttl[4] + caps_hash[32] + vk[32] +      |
|      |        |   signature[64] + token[74]?              |
+------+--------+------------------------------------------+
| 0x41 | len    | TLV_CAP_LIST                             |
|      |        |   eid[32] +                               |
|      |        |   cap_hash_1[32] +                        |
|      |        |   cap_hash_2[32] +                        |
|      |        |   cap_hash_3[32]                          |
+------+--------+------------------------------------------+
        ]]></artwork>

        <t>
          Total: 3-byte TLV header + presence payload + 3-byte
          TLV header + 128-byte cap list payload. Both TLVs fit
          within the 1400-byte MTU budget. The Registry processes
          both TLVs: the Presence beacon updates the aggregate
          index; the cap list creates three entries in the
          per-capability index.
        </t>
      </section>

      <section anchor="example-individual-discovery" numbered="false">
        <name>B.2. Discovery by Individual Capability</name>

        <t>
          A Consumer agent needs compliance reporting. It computes:
        </t>

        <artwork><![CDATA[
query_hash = SHA-256("compliance.report/v1.0")
        ]]></artwork>

        <t>
          The Consumer sends FIND_EX_REQUEST (TLV 0x76):
        </t>

        <artwork><![CDATA[
+------+--------+------------------------------------------+
| 0x76 | 0x0040 | capability_hash[32] + initiator_eid[32]  |
+------+--------+------------------------------------------+
        ]]></artwork>

        <t>
          The Registry checks the per-capability index for
          query_hash. It finds the compliance agent registered in
          Example B.1, because cap_hash_2 from the agent's
          TLV_CAP_LIST matches query_hash exactly. The Registry
          issues a FIND_EX_RESPONSE (TLV 0x77) containing status
          Success (0x00), the compliance agent's EID and RLOC, and
          a ConnectTicket (272 octets) binding initiator_eid +
          responder_eid + query_hash.
        </t>
      </section>

      <section anchor="example-mismatch" numbered="false">
        <name>B.3. Capability Mismatch Rejection</name>

        <t>
          A compliance agent advertises three capabilities. An
          attacker obtains a valid ConnectTicket where
          capability_hash = SHA-256("compliance.report/v1.0") and
          attempts to misuse it in two ways.
        </t>

        <t>
          Scenario A: Ticket tampering. The attacker modifies the
          ticket's capability_hash field to
          SHA-256("compliance.audit/v1.0") before presenting the
          DirectProbe. The Provider verifies the ticket's Ed25519
          signature. Because capability_hash is within the signed
          preimage (octets 0-207), the modification invalidates
          the signature. The Provider silently drops the probe per
          Phase 2 (cryptographic validation).
        </t>

        <t>
          Scenario B: Session misuse. The attacker presents the
          unmodified ticket and successfully establishes a session.
          The session is bound to
          SHA-256("compliance.report/v1.0") at establishment time
          per <xref target="session-capability-binding"/>. This
          binding is immutable. The Provider dispatches this
          session to the compliance.report handler. The attacker
          cannot reach a different capability handler through this
          session regardless of payload content.
        </t>
      </section>

      <section anchor="example-fallback" numbered="false">
        <name>B.4. Aggregate Index Fallback</name>

        <t>
          An agent running a pre-0x41 SDK sends a Presence beacon
          with caps_hash but does NOT emit TLV_CAP_LIST. The agent
          advertises a single capability: system.echo/v1.0.
        </t>

        <t>
          A Consumer queries FIND_EX with:
        </t>

        <artwork><![CDATA[
query_hash = SHA-256("system.echo/v1.0")
        ]]></artwork>

        <t>
          The Registry checks the per-capability index. No entry
          exists (agent did not send TLV_CAP_LIST). The Registry
          falls back to the aggregate index. For a
          single-capability agent, caps_hash equals the individual
          cap_hash, so the aggregate lookup succeeds. The Consumer
          receives a valid FIND_EX_RESPONSE and proceeds normally.
          The legacy agent is fully discoverable without any
          upgrade.
        </t>
      </section>
    </section>


  </back></rfc>
