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

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

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

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

    <keyword>intent routing</keyword>
    <keyword>capability addressing</keyword>
    <keyword>post-quantum</keyword>
    <keyword>transport protocol</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>
    </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>

    <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 computed as follows:
        </t>

        <ol>
          <li>Take the complete capability URI as a UTF-8 byte
              string (e.g., "cap:robot.wave/v1.0").</li>
          <li>Compute the SHA-256 digest of this byte string.</li>
          <li>The resulting 32-byte value is the capability hash.</li>
        </ol>

        <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>
        At the protocol layer, discovery is unfiltered within
        the applicable scope: Registries do not rank, suppress,
        or prioritize Providers beyond the scope restrictions
        declared in capability advertisements. 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 Discovery
          Query to a Registry. The query contains:
        </t>

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

          <dt>max_results (2 bytes, unsigned integer, big-endian)</dt>
          <dd>The maximum number of Providers to return.</dd>

          <dt>locality_hint (8 bytes, optional)</dt>
          <dd>A geographic locality indicator that the Registry
              MAY use to prefer geographically proximate Providers.
              The locality hint is advisory; the Registry is not
              required to honor it.</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>
      </section>

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

        <t>
          The Registry responds to a Discovery Query with a
          Discovery Response containing zero or more Provider
          entries. Each entry includes:
        </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>


    <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 reject tickets where
                the current time exceeds expires_at.</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.</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>


    <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 SHOULD allow a small clock skew
          tolerance (on the order of seconds) when checking
          ticket expiration. The 30-second recommended validity
          window provides margin for modest clock drift.
          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>

    <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>
        </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>
      </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-robotics" numbered="false">
        <name>Example 1: Cross-Domain Intent Routing (Robotics)</name>

        <t>
          This example demonstrates an invocation of a robotics
          capability using CIRP. A Consumer instructs a robotic
          arm to perform a wave gesture.
        </t>

        <t>
          The capability URI is cap:robot.wave/v1.0. The
          capability hash is SHA-256("cap:robot.wave/v1.0"),
          yielding a 32-byte value used during discovery and
          authorization.
        </t>

        <t>
          Step 1: Discovery and Authorization. The Consumer sends
          an Authorization Request containing the capability hash
          and its EID. The Registry locates a Provider advertising
          cap:robot.wave/v1.0, mints a ConnectTicket (272 bytes)
          binding the Consumer, Provider, and capability, and
          returns the ticket with the Provider's network locator.
        </t>

        <t>
          Step 2: Suite Negotiation. The Consumer sends a SuiteOffer
          to the Provider containing the ConnectTicket, a
          session_id, and offered suites
          [CIRP_X25519MLKEM768_ED25519_CHACHA20POLY1305_SHA256,
          CIRP_X25519_ED25519_CHACHA20POLY1305_SHA256], signed
          with the Consumer's Ed25519 key. The Provider validates
          the ticket, selects the hybrid suite, and returns a
          signed SuiteSelect.
        </t>

        <t>
          Step 3: Key Exchange. The Consumer sends a KeyExchange
          message containing its ephemeral X25519 public key
          (32 bytes) and its ephemeral ML-KEM-768 encapsulation
          key, signed with its identity key. The Provider performs
          X25519 DH and ML-KEM-768 encapsulation, returning its
          ephemeral X25519 public key and the ML-KEM-768
          ciphertext, signed. Both derive the session key via
          HKDF.
        </t>

        <t>
          Step 4: Invocation. The Consumer constructs a request
          envelope:
        </t>

        <artwork><![CDATA[
Request Envelope (CBOR map, integer keys):
{
  1: h'A3B4...C5D6'            // invocation_id (16 bytes)
  2: "cap:robot.wave/v1.0"    // capability_uri
  3: "application/json"       // payload_type
  4: h'7B22...'               // payload: {"gesture":"wave",
                               //   "amplitude":0.8,"cycles":3}
  5: h'1234...5678'            // consumer_eid (32 bytes)
  6: 1708012800000             // consumer_send_ts
  7: h'0000...0000'            // prev_invocation_hash (first)
  8: h'9ABC...DEF0'            // consumer_signature (64 bytes)
}
        ]]></artwork>

        <t>
          The envelope is CBOR-encoded and sent within an
          encrypted AICF frame. The payload contains a
          JSON-encoded robotics command. The transport does
          not parse or interpret the JSON; it is carried as
          opaque bytes.
        </t>

        <t>
          Step 5: Fulfillment. The Provider processes the
          command, actuates the robotic arm, and returns a
          response envelope:
        </t>

        <artwork><![CDATA[
Response Envelope (CBOR map):
{
  1: h'A3B4...C5D6'            // invocation_id (correlation)
  2: 0                         // fulfillment_status: success
  3: "application/json"       // payload_type
  4: h'7B22...'               // payload: {"status":"completed",
                               //   "duration_ms":1247}
  5: h'ABCD...EF01'            // provider_eid (32 bytes)
  6: 1708012800050             // provider_recv_ts
  7: 1708012801297             // provider_send_ts
  8: h'F0E1...D2C3'            // request_hash (32 bytes)
  9: h'5678...9ABC'            // provider_signature (64 bytes)
}
        ]]></artwork>

        <t>
          Step 6: Receipt. The Provider constructs Phase 1 of
          the fulfillment record (fields 1-7, Provider-signed)
          and sends it alongside the response. The Consumer
          appends fields 8-11 (consumer_send_ts, consumer_recv_ts,
          consumer_eid, consumer_signature) to produce the final
          dual-signed receipt.
        </t>

        <t>
          This example demonstrates payload neutrality: the
          robotics command could equally be a binary protocol
          buffer, a domain-specific language instruction, or
          any other byte sequence. The transport handles all
          payloads identically.
        </t>
      </section>

      <section anchor="example-org-scoped" numbered="false">
        <name>Example 2: Organization-Scoped Discovery</name>

        <t>
          This example demonstrates capability scoping within
          an organizational boundary.
        </t>

        <t>
          An organization operates an internal document analysis
          service at cap:acme.docs.analyze/v1.0. The Provider
          advertises this capability with Organization scope:
        </t>

        <artwork><![CDATA[
Scope Descriptor (33 bytes):
  scope_level:  0x03  (Organization)
  anchor_hash:  SHA-256(organization_signing_key)
                = h'7F8E...1A2B' (32 bytes)
        ]]></artwork>

        <t>
          When a Consumer queries for cap:acme.docs.analyze/v1.0,
          the Registry evaluates the scope: it checks whether
          the Consumer's identity chain includes the trust anchor
          identified by anchor_hash. If the Consumer is a member
          of the organization (its EID is bound to the
          organization's trust anchor through a deployment-specific
          membership mechanism), the capability appears in
          discovery results. If not, the Registry excludes the
          capability and returns a Policy Receipt indicating
          scope denial.
        </t>

        <t>
          An external Consumer querying the same Registry sees
          no evidence that cap:acme.docs.analyze/v1.0 exists.
          The scope enforcement occurs at the Registry during
          query processing, not at the Consumer or Provider.
        </t>
      </section>

      <section anchor="example-handshake" numbered="false">
        <name>Example 3: Cryptographic Suite Negotiation</name>

        <t>
          This example shows the complete message-level flow
          of suite negotiation and hybrid key exchange between
          a Consumer (C) and Provider (P).
        </t>

        <artwork><![CDATA[
Message 1: SuiteOffer (C -> P)
+----------------------------------------------+
| ConnectTicket (272 bytes)                    |
| session_id: h'01020304...10111213' (16 bytes)|
| offered_suites: [                            |
|   "CIRP_X25519MLKEM768_ED25519_CHACHA20P...",|
|   "CIRP_X25519_ED25519_CHACHA20POLY1305_..." |
| ]                                            |
| consumer_signature: Ed25519(consumer_sk,     |
|   SHA-256(ticket || session_id || suites))   |
+----------------------------------------------+

  Provider validates:
    1. Ticket signature (Registry's VK)      -> OK
    2. Ticket expiration (now < expires_at)   -> OK
    3. Ticket provider_eid matches own EID    -> OK
    4. Consumer signature (consumer_vk from
       ticket)                                -> OK
    5. Select first mutually supported suite:
       hybrid                                 -> OK

Message 2: SuiteSelect (P -> C)
+----------------------------------------------+
| selected_suite:                              |
|   "CIRP_X25519MLKEM768_ED25519_CHACHA20P..."|
| provider_signature: Ed25519(provider_sk,     |
|   SHA-256(selected_suite))                   |
+----------------------------------------------+

  Consumer validates:
    1. Provider signature                     -> OK
    2. Selected suite in original offer       -> OK
       (downgrade protection)

Message 3: KeyExchange (C -> P)
+----------------------------------------------+
| magic: "AIKX" (0x41494B58)                   |
| session_id (16 bytes)                        |
| role: 0x01 (Consumer)                        |
| x25519_ephemeral_pk (32 bytes)               |
| mlkem768_encapsulation_key (1184 bytes)      |
| signature: Ed25519(consumer_sk,              |
|   session_id || role || x25519_pk || mlkem)  |
+----------------------------------------------+

Message 4: KeyExchange (P -> C)
+----------------------------------------------+
| magic: "AIKX" (0x41494B58)                   |
| session_id (16 bytes)                        |
| role: 0x02 (Provider)                        |
| x25519_ephemeral_pk (32 bytes)               |
| mlkem768_ciphertext (1088 bytes)             |
| signature: Ed25519(provider_sk,              |
|   session_id || role || x25519_pk || ct)     |
+----------------------------------------------+

Key Derivation (both parties):
  classical_ss = X25519(my_sk, peer_pk)       // 32 bytes
  pq_ss = ML-KEM-768-Decaps(ct, my_dk)        // 32 bytes
         or ML-KEM-768-Encaps output

  ikm = classical_ss || pq_ss                 // 64 bytes
  PRK = HKDF-Extract(
    salt = session_id,                         // 16 bytes
    ikm  = ikm)                                // 64 bytes
  session_key = HKDF-Expand(
    PRK,
    info = "cirp-hybrid-kx"
           || "CIRP_X25519MLKEM768_ED25519_CHACHA20POLY1305_SHA256"
           || consumer_eid
           || provider_eid,
    L = 32)                                    // 256-bit key

Message 5: First Encrypted Frame (C -> P)
+----------------------------------------------+
| magic: "AICF" (0x41494346)                   |
| session_id (16 bytes)                        |
| counter: 0x0000000000000001 (8 bytes, BE)    |
| nonce (12 bytes, derived from counter)       |
| ciphertext (N bytes, ChaCha20-Poly1305       |
|   encrypted invocation envelope)             |
| tag (16 bytes, Poly1305 auth tag)            |
+----------------------------------------------+
        ]]></artwork>

        <t>
          After Message 5, the encrypted session is fully
          operational. All subsequent invocation and fulfillment
          envelopes are carried in AICF frames with incrementing
          counters.
        </t>
      </section>
    </section>


  </back>
</rfc>
