<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE rfc [
  <!ENTITY nbsp    "&#160;">
  <!ENTITY zwsp   "&#8203;">
  <!ENTITY nbhy   "&#8209;">
  <!ENTITY wj     "&#8288;">
]>
<?xml-stylesheet type="text/xsl" href="rfc2629.xslt" ?>
<!-- generated by https://github.com/cabo/kramdown-rfc version 1.7.29 (Ruby 3.4.4) -->
<rfc xmlns:xi="http://www.w3.org/2001/XInclude" ipr="trust200902" docName="draft-denis-uricrypt-02" category="info" submissionType="independent" tocInclude="true" sortRefs="true" symRefs="true" version="3">
  <!-- xml2rfc v2v3 conversion 3.30.2 -->
  <front>
    <title abbrev="URICrypt">Prefix-Preserving Encryption for URIs</title>
    <seriesInfo name="Internet-Draft" value="draft-denis-uricrypt-02"/>
    <author initials="F." surname="Denis" fullname="Frank Denis">
      <organization>Fastly Inc.</organization>
      <address>
        <email>fde@00f.net</email>
      </address>
    </author>
    <date year="2025"/>
    <keyword>Internet-Draft</keyword>
    <abstract>
      <?line 19?>

<t>This document specifies URICrypt, a deterministic, prefix-preserving
encryption scheme for Uniform Resource Identifiers (URIs). URICrypt
encrypts URI paths while preserving their hierarchical structure,
enabling systems that rely on URI prefix relationships to continue
functioning with encrypted URIs. The scheme provides authenticated
encryption for each URI path component, preventing tampering,
reordering, or mixing of encrypted segments.</t>
    </abstract>
    <note removeInRFC="true">
      <name>Discussion Venues</name>
      <t>Source for this draft and an issue tracker can be found at
    <eref target="https://github.com/jedisct1/draft-denis-uricrypt"/>.</t>
    </note>
  </front>
  <middle>
    <?line 29?>

<section anchor="introduction">
      <name>Introduction</name>
      <t>This document specifies URICrypt, a method for encrypting Uniform
Resource Identifiers (URIs) while preserving their hierarchical
structure. The primary motivation is to enable systems that rely on
URI prefix relationships for routing, filtering, or access control to
continue functioning with encrypted URIs.</t>
      <t>URICrypt achieves prefix preservation through a chained encryption
model where the encryption of each URI component depends
cryptographically on all preceding components. This ensures that URIs
sharing common prefixes produce ciphertexts that also share common
encrypted prefixes.</t>
      <t>The scheme uses an extendable-output function (XOF) as its cryptographic primitive
and provides authenticated encryption for each component, preventing
tampering, reordering, or mixing of encrypted segments. URICrypt is a
reversible encryption scheme: encrypted URIs can be fully decrypted to
recover the original URIs, but only with possession of the secret key.</t>
      <section anchor="use-cases-and-motivations">
        <name>Use Cases and Motivations</name>
        <t>The main motivations include:</t>
        <ul spacing="normal">
          <li>
            <t>Access Control in CDNs: Content Delivery Networks often use URI
prefixes for routing and access control. URICrypt allows encryption of
resource paths while preserving the prefix structure needed for
CDN operations.</t>
          </li>
          <li>
            <t>Privacy-Preserving Logging: Systems can log encrypted URIs
without exposing sensitive path information, while still enabling
analysis based on URI structure.</t>
          </li>
          <li>
            <t>Confidential Data Sharing: When sharing links to sensitive
resources, URICrypt prevents the path structure itself from
revealing confidential information.</t>
          </li>
          <li>
            <t>Token-Based Access Systems: Systems that issue time-limited
access tokens can use URICrypt to obfuscate the underlying
resource location while maintaining routability.</t>
          </li>
          <li>
            <t>Multi-tenant Systems: In systems where multiple tenants share
infrastructure, URICrypt can isolate tenant data while allowing
shared components to be processed efficiently.</t>
          </li>
          <li>
            <t>Privacy-preserving Analytics: URICrypt can complement IPCrypt
<xref target="I-D.draft-denis-ipcrypt"/>. Together, they enable systems to perform
analytics on encrypted network flows and resource access patterns
without exposing sensitive information about either the network
endpoints or the specific resources being accessed.</t>
          </li>
        </ul>
      </section>
    </section>
    <section anchor="terminology">
      <name>Terminology</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>
      <t>Throughout this document, the following terms and conventions apply:</t>
      <ul spacing="normal">
        <li>
          <t>URI: Uniform Resource Identifier as defined in <xref target="RFC3986"/>.</t>
        </li>
        <li>
          <t>URI Component: A segment of a URI path, terminated by ‘/’, ‘?’, or
‘#’ characters. For encryption purposes, components include the
trailing terminator except for the final component.</t>
        </li>
        <li>
          <t>Scheme: The URI scheme (e.g., “https://”) which is preserved in
plaintext.</t>
        </li>
        <li>
          <t>XOF: Extendable-Output Function, a cryptographic function that can
produce output of arbitrary length.</t>
        </li>
        <li>
          <t>SIV: Synthetic Initialization Vector, a value derived from the
accumulated state of all previous components, used for
authentication and as input to keystream generation.</t>
        </li>
        <li>
          <t>SIVLEN: The length of the Synthetic Initialization Vector in bytes,
defined as 16 bytes (128 bits) for this specification.</t>
        </li>
        <li>
          <t>PADBS: Padding Block Size, the number of bytes to which ciphertext
components are aligned. Defined as 3 bytes for this specification
to ensure efficient Base64url encoding without padding characters.</t>
        </li>
        <li>
          <t>Domain Separation: The practice of using distinct inputs to
cryptographic functions to ensure outputs for different purposes
are not compatible.</t>
        </li>
        <li>
          <t>Prefix-preserving Encryption: An encryption scheme where, if two
plaintexts share a common prefix, their corresponding ciphertexts
also share a common (encrypted) prefix.</t>
        </li>
        <li>
          <t>Chained Encryption: A mode where encryption of each component
depends cryptographically on all preceding components.</t>
        </li>
      </ul>
    </section>
    <section anchor="uri-processing">
      <name>URI Processing</name>
      <t>This section describes how URIs are processed for encryption and
decryption.</t>
      <t>The overall encryption flow transforms a plaintext URI into an encrypted
URI while preserving its hierarchical structure:</t>
      <artwork><![CDATA[
+-------------------------------------------------------------+
|                         Input URI                           |
|          "https://example.com/path/to/resource"             |
+-------------------------------------------------------------+
                              |
                              v
+-------------------------------------------------------------+
|                    URI Decomposition                        |
+-------------------------------------------------------------+
|  Scheme: "https://"                                         |
|  Components: ["example.com/", "path/", "to/", "resource"]   |
+-------------------------------------------------------------+
                              |
                              v
+-------------------------------------------------------------+
|                 Chained Encryption Process                  |
+-------------------------------------------------------------+
|  For each component in sequence:                            |
|    1. Update state with plaintext                           |
|    2. Generate SIV from accumulated state                   |
|    3. Derive keystream using SIV                            |
|    4. Encrypt component with keystream                      |
|    5. Output: SIV || encrypted_component                    |
+-------------------------------------------------------------+
                              |
                              v
+-------------------------------------------------------------+
|                    Encoding & Assembly                      |
+-------------------------------------------------------------+
|  1. Concatenate all (SIV || encrypted_component) pairs      |
|  2. Apply base64url encoding                                |
|  3. Prepend original scheme                                 |
+-------------------------------------------------------------+
                              |
                              v
+-------------------------------------------------------------+
|                       Encrypted URI                         |
|          "https://HOGo9vauZ3b3xsPNPQng5apS..."              |
+-------------------------------------------------------------+
]]></artwork>
      <section anchor="uri-component-extraction">
        <name>URI Component Extraction</name>
        <t>Before encryption, a URI must be split into its scheme and path
components. The path is further divided into individual components for
chained encryption. Components are terminated by ‘/’, ‘?’, or ‘#’
characters, which allows proper handling of query strings and fragments.</t>
        <section anchor="full-uris">
          <name>Full URIs</name>
          <t>For a full URI including a scheme:</t>
          <artwork><![CDATA[
Input:  "https://example.com/a/b/c"

Components:

- Scheme: "https://"
- Component 1: "example.com/"
- Component 2: "a/"
- Component 3: "b/"
- Component 4: "c"
]]></artwork>
          <t>For a URI with query parameters:</t>
          <artwork><![CDATA[
Input:  "https://example.com/path?foo=bar&baz=qux"

Components:

- Scheme: "https://"
- Component 1: "example.com/"
- Component 2: "path?"
- Component 3: "foo=bar&baz=qux"
]]></artwork>
          <t>For a URI with a fragment:</t>
          <artwork><![CDATA[
Input:  "https://example.com/path#section"

Components:

- Scheme: "https://"
- Component 1: "example.com/"
- Component 2: "path#"
- Component 3: "section"
]]></artwork>
          <t>Note that all components except the last include their trailing terminator
character (‘/’, ‘?’, or ‘#’). This ensures proper reconstruction during decryption.</t>
        </section>
        <section anchor="path-only-uris">
          <name>Path-Only URIs</name>
          <t>For absolute paths (URIs starting with ‘/’ but without a scheme), the
leading ‘/’ is treated as the first component:</t>
          <artwork><![CDATA[
Input:  "/a/b/c"

Components:

- Scheme: "" (empty)
- Component 1: "/"
- Component 2: "a/"
- Component 3: "b/"
- Component 4: "c"
]]></artwork>
          <t>For a path with query parameters:</t>
          <artwork><![CDATA[
Input:  "/path/to/file?param=value"

Components:

- Scheme: "" (empty)
- Component 1: "/"
- Component 2: "path/"
- Component 3: "to/"
- Component 4: "file?"
- Component 5: "param=value"
]]></artwork>
          <t>The leading ‘/’ is explicitly encrypted as a component to maintain
consistency and enable proper prefix preservation for absolute paths.</t>
          <t>This character receives its own SIV and is encrypted, ensuring that the
root path is authenticated like any other path component and that
different keys and contexts produce different ciphertexts for that path,
consistently with other paths.</t>
          <t>In applications where all paths are guaranteed to be absolute and the <tt>'/'</tt> path
can be considered a special case, ciphertext expansion can be reduced by
removing the leading <tt>'/'</tt> character from the URI prior to encryption,
making the path relative with <tt>'/'</tt> as an implicit current path.</t>
        </section>
      </section>
      <section anchor="component-reconstruction">
        <name>Component Reconstruction</name>
        <t>During decryption, components are joined to reconstruct the original
path:</t>
        <artwork><![CDATA[
Components: ["example.com/", "a/", "b/", "c"]
Reconstructed Path: "example.com/a/b/c"

When combined with the scheme: "https://example.com/a/b/c"
]]></artwork>
        <t>For absolute paths without a scheme:</t>
        <artwork><![CDATA[
Components: ["/", "a/", "b/", "c"]
Reconstructed Path: "/a/b/c"
]]></artwork>
      </section>
    </section>
    <section anchor="cryptographic-operations">
      <name>Cryptographic Operations</name>
      <t>The chained encryption model creates cryptographic dependencies between components, ensuring prefix preservation.</t>
      <artwork><![CDATA[
  URI: "https://example.com/path/to/resource"

  +-------------------+
  |   Component 1:    |
  |  "example.com/"   |
  +-------------------+
            |
            | Plaintext absorbed into components_xof
            v
  +-------------------+
  | SIV1 generation   |------> SIV1 (SIVLEN bytes)
  +-------------------+         |
                                |
                                v
                      Encrypt("example.com/")
                                |
                                v
                      Output1 = SIV1 || Ciphertext1
            |
            | State carries forward
            v
  +-------------------+
  |   Component 2:    |
  |     "path/"       |
  +-------------------+
            |
            | Plaintext absorbed (includes Component 1 state)
            v
  +-------------------+
  | SIV2 generation   |------> SIV2 (depends on 1)
  +-------------------+         |
                                |
                                v
                      Encrypt("path/")
                                |
                                v
                      Output2 = SIV2 || Ciphertext2
            |
            | State carries forward
            v
  +-------------------+
  |   Component 3:    |
  |      "to/"        |
  +-------------------+
            |
            | Plaintext absorbed (includes 1 + 2 state)
            v
  +-------------------+
  | SIV3 generation   |------> SIV3 (depends on 1, 2)
  +-------------------+         |
                                |
                                v
                      Encrypt("to/")
                                |
                                v
                      Output3 = SIV3 || Ciphertext3
            |
            | State carries forward
            v
  +-------------------+
  |   Component 4:    |
  |    "resource"     |
  +-------------------+
            |
            | Plaintext absorbed (includes 1 + 2 + 3 state)
            v
  +-------------------+
  | SIV4 generation   |------> SIV4 (depends on 1, 2, 3)
  +-------------------+         |
                                |
                                v
                      Encrypt("resource")
                                |
                                v
                      Output4 = SIV4 || Ciphertext4

  Final Output: Output1 || Output2 || Output3 || Output4
]]></artwork>
      <t>If URIs share a common prefix <tt>example.com/path/</tt>, their <tt>Output1</tt> and <tt>Output2</tt> will be identical.</t>
      <section anchor="xof-init">
        <name>XOF Initialization</name>
        <t>The base XOF is initialized with the secret key and context
parameters using length-prefixed encoding to prevent ambiguities.</t>
        <t>Two XOF instances are derived from the base XOF:</t>
        <ol spacing="normal" type="1"><li>
            <t>Components XOF: Updated with each component’s plaintext to
generate SIVs</t>
          </li>
          <li>
            <t>Base Keystream XOF: Used as the starting point for generating
keystream for each component</t>
          </li>
        </ol>
        <artwork><![CDATA[
  Input: len(key) || key || len(context) || context

  +-----------------------------------------------------+
  | base_xof = TurboSHAKE128(domain_sep=0x1F)           |
  | base_xof.update(len(secret_key))                    |
  | base_xof.update(secret_key)                         |
  | base_xof.update(len(context))                       |
  | base_xof.update(context)                            |
  +-----------------------------------------------------+
                            |
                            v
               +------------------------+
               |   Clone Base State     |
               +------------------------+
                            |
           +----------------+----------------+
           v                                 v
  +--------------------+          +--------------------+
  |  Components XOF    |          | Base Keystream XOF |
  +--------------------+          +--------------------+
  |   update("IV")     |          |   update("KS")     |
  +--------------------+          +--------------------+
           |                                 |
           |                                 |
           v                                 v
   For SIV Generation              For Keystream Base
   (Updated with each              (Cloned for each
    component plaintext)            component's keystream)
]]></artwork>
        <t>The initialization process is:</t>
        <artwork><![CDATA[
base_xof = TurboSHAKE128()
base_xof.update(len(secret_key))
base_xof.update(secret_key)
base_xof.update(len(context))
base_xof.update(context)

components_xof = base_xof.clone()
components_xof.update("IV")

base_keystream_xof = base_xof.clone()
base_keystream_xof.update("KS")
]]></artwork>
        <t>Note on XOF cloning: The <tt>.clone()</tt> operation creates a new XOF instance with
an identical internal state, preserving all previously absorbed data. After
cloning, the original and cloned XOFs can be updated and read from
independently. This allows the <tt>components_xof</tt> to maintain a running state
across all components while <tt>base_keystream_xof</tt> remains unchanged for creating
per-component keystreams.</t>
      </section>
      <section anchor="component-encryption">
        <name>Component Encryption</name>
        <t>For each component, the encryption process follows a precise sequence
that ensures both confidentiality and authenticity:</t>
        <ol spacing="normal" type="1"><li>
            <t>Update <tt>components_xof</tt> with the component plaintext</t>
          </li>
          <li>
            <t>Squeeze the SIV from <tt>components_xof</tt> (<tt>SIVLEN</tt> bytes). This requires cloning <tt>components_xof</tt> before reading, as reading may finalize the XOF.</t>
          </li>
          <li>
            <t>Create <tt>keystream_xof</tt> by cloning <tt>base_keystream_xof</tt> and updating it with SIV</t>
          </li>
          <li>
            <t>Calculate padding needed for base64 encoding</t>
          </li>
          <li>
            <t>Generate a keystream of length <tt>(component_length + padding)</tt></t>
          </li>
          <li>
            <t>XOR the padded component with the keystream</t>
          </li>
          <li>
            <t>Output SIV concatenated with <tt>encrypted_component</tt></t>
          </li>
        </ol>
        <t>The padding ensures clean base64url encoding without padding characters. Since
base64 encoding works with groups of 3 bytes (producing 4 characters), we pad each
<tt>(SIV || encrypted_component)</tt> pair to have a length that’s a multiple of PADBS:</t>
        <artwork><![CDATA[
total_bytes = SIVLEN (SIV) + component_len
padding_len = (PADBS - total_bytes % PADBS) % PADBS
]]></artwork>
        <t>This formula calculates:</t>
        <ul spacing="normal">
          <li>
            <t>How many bytes are needed to reach the next multiple of PADBS</t>
          </li>
          <li>
            <t>The outer modulo handles the case where <tt>total_bytes</tt> is already a multiple of PADBS</t>
          </li>
        </ul>
        <t>The <tt>components_xof</tt> maintains state across all components.
After generating the SIV for component <tt>N</tt>, the XOF can be updated with component <tt>N+1</tt>’s
plaintext. This chaining ensures that each component’s encryption depends on
all previous components, thus enabling the prefix-preserving property.</t>
      </section>
      <section anchor="component-decryption">
        <name>Component Decryption</name>
        <t>For each encrypted component, the decryption process is:</t>
        <ol spacing="normal" type="1"><li>
            <t>Read SIV from input (<tt>SIVLEN</tt> bytes)</t>
          </li>
          <li>
            <t>Create <tt>keystream_xof</tt> by cloning <tt>base_keystream_xof</tt> and updating it with SIV</t>
          </li>
          <li>
            <t>Decrypt bytes incrementally to determine component boundaries:
            </t>
            <ul spacing="normal">
              <li>
                <t>Generate keystream bytes one at a time from the XOF</t>
              </li>
              <li>
                <t>XOR each encrypted byte with its corresponding keystream byte</t>
              </li>
              <li>
                <t>Check each decrypted byte for component terminators (<tt>'/'</tt>, <tt>'?'</tt>, <tt>'#'</tt>)</t>
              </li>
              <li>
                <t>When a terminator is found, the component is complete.</t>
              </li>
              <li>
                <t>Skip any padding bytes (null bytes) after the component</t>
              </li>
            </ul>
          </li>
          <li>
            <t>Update <tt>components_xof</tt> with the complete plaintext component (including terminator)</t>
          </li>
          <li>
            <t>Generate the expected SIV from <tt>components_xof</tt></t>
          </li>
          <li>
            <t>Compare the expected SIV with the received SIV (constant-time)</t>
          </li>
          <li>
            <t>If mismatch, return <tt>error</tt></t>
          </li>
        </ol>
        <section anchor="component-boundary-detection">
          <name>Component Boundary Detection</name>
          <t>During decryption, component boundaries are discovered dynamically by examining the decrypted plaintext:</t>
          <ul spacing="normal">
            <li>
              <t>Each component (except possibly the last) ends with a terminator character (<tt>'/'</tt>, <tt>'?'</tt>, or <tt>'#'</tt>)</t>
            </li>
            <li>
              <t>When a terminator is encountered, we know the component is complete</t>
            </li>
            <li>
              <t>After finding the terminator, we skip padding bytes to align to the next PADBS-byte boundary.</t>
            </li>
            <li>
              <t>The padding length can be calculated: <tt>padding = (PADBS - ((SIV_size + bytes_read) % PADBS)) % PADBS</tt></t>
            </li>
          </ul>
          <t>This approach eliminates the need for explicit length encoding, as the component structure itself provides the necessary boundary information.</t>
          <t>Any tampering with the encrypted data will cause the SIV comparison to fail.</t>
        </section>
      </section>
      <section anchor="padding-and-encoding">
        <name>Padding and Encoding</name>
        <t>To enable clean base64url encoding without padding characters (‘=’), each
encrypted component pair <tt>(SIV || ciphertext)</tt> is padded to be a multiple of PADBS bytes.
This is necessary because base64 encoding processes 3 bytes at a time to produce
4 characters of output.</t>
        <t>The padding calculation <tt>(PADBS - (SIVLEN + component_len) % PADBS) % PADBS</tt> ensures the following:</t>
        <ul spacing="normal">
          <li>
            <t>If <tt>(SIVLEN + component_len) % PADBS = 0</tt>: no padding needed (already aligned)</t>
          </li>
          <li>
            <t>If <tt>(SIVLEN + component_len) % PADBS = 1</tt>: add 2 bytes of padding</t>
          </li>
          <li>
            <t>If <tt>(SIVLEN + component_len) % PADBS = 2</tt>: add 1 byte of padding</t>
          </li>
        </ul>
        <t>With the default value of <tt>PADBS=3</tt>, this padding scheme provides partial length-hiding.
For example, with <tt>SIVLEN=16</tt>, components “abc”, “abcd”, and “abcde” all produce 21-byte
outputs after padding. Without the secret key, a passive adversary cannot determine
the exact original component size.</t>
        <t>The final output is encoded using URL-safe base64 <xref target="RFC4648"/>, with ‘-‘ replacing
‘+’ and ‘_’ replacing ‘/’ for URI compatibility.</t>
      </section>
    </section>
    <section anchor="algorithm-specification">
      <name>Algorithm Specification</name>
      <t>This section provides the complete algorithms for encryption and
decryption. The following functions and operations are used throughout
the algorithms:</t>
      <ul spacing="normal">
        <li>
          <t><tt>TurboSHAKE128()</tt>: Creates a new TurboSHAKE128 XOF instance with domain separation parameter 0x1F. This function produces an extensible output function (XOF) that can generate arbitrary-length outputs.</t>
        </li>
        <li>
          <t><tt>.update(data)</tt>: Absorbs the provided data into the XOF state. Data is processed sequentially and updates the internal state of the XOF.</t>
        </li>
        <li>
          <t><tt>.read(length)</tt>: Squeezes the specified number of bytes from the XOF’s output. Each call continues from where the previous read left off, producing a continuous stream of pseudorandom bytes.</t>
        </li>
        <li>
          <t><tt>.clone()</tt>: Creates a new XOF instance with an identical internal state to the original. This enables multiple independent computation paths from the same initial state.</t>
        </li>
        <li>
          <t>XOR operation: The bitwise exclusive OR operation between two byte sequences of equal length. This operation is used to combine plaintext with keystream for encryption, and ciphertext with keystream for decryption.</t>
        </li>
        <li>
          <t><tt>base64url_encode(data)</tt>: Converts binary data to a base64 string using URL-safe encoding (replacing ‘+’ with ‘-‘ and ‘/’ with ‘_’) and omitting padding characters.</t>
        </li>
        <li>
          <t><tt>base64url_decode(string)</tt>: Converts a URL-safe base64 string back to binary data, automatically handling the absence of padding characters.</t>
        </li>
        <li>
          <t><tt>Stream(data)</tt>: Creates a sequential reader for binary data, enabling byte-by-byte or block-based access to the contents.</t>
        </li>
        <li>
          <t><tt>constant_time_compare(a, b)</tt>: Compares two byte sequences in constant time, regardless of their contents. This prevents timing attacks by ensuring the comparison duration does not depend on which bytes differ.</t>
        </li>
        <li>
          <t><tt>len(data)</tt>: Returns the length of the provided data in bytes.</t>
        </li>
        <li>
          <t>Concatenation: The operation of joining two byte sequences end-to-end to form a single sequence.</t>
        </li>
        <li>
          <t><tt>zeros(count)</tt>: Generates a sequence of zero-valued bytes of the specified length, used for padding.</t>
        </li>
        <li>
          <t><tt>remove_padding(data)</tt>: Removes trailing zero bytes from a byte sequence to recover the original data length.</t>
        </li>
        <li>
          <t><tt>join(components)</tt>: Combines multiple path components into a single path string, preserving the terminator characters (<tt>'/'</tt>, <tt>'?'</tt>, <tt>'#'</tt>) that are included in each component.</t>
        </li>
      </ul>
      <section anchor="encryption-algorithm">
        <name>Encryption Algorithm</name>
        <t>Input: secret_key, context, uri_string</t>
        <t>Output: encrypted_uri</t>
        <t>Steps:</t>
        <ol spacing="normal" type="1"><li>
            <t>Split URI into scheme and components</t>
          </li>
          <li>
            <t>Initialize XOF instances as described in <xref target="xof-init"/></t>
          </li>
          <li>
            <t><tt>encrypted_output = empty byte array</tt></t>
          </li>
          <li>
            <t>For each component:
            </t>
            <ul spacing="normal">
              <li>
                <t>Update <tt>components_xof</tt> with <tt>component</tt>.</t>
              </li>
              <li>
                <t><tt>SIV = components_xof.clone().read(SIVLEN)</tt>.</t>
              </li>
              <li>
                <t><tt>keystream_xof = base_keystream_xof.clone()</tt>.</t>
              </li>
              <li>
                <t><tt>keystream_xof.update(SIV)</tt>.</t>
              </li>
              <li>
                <t><tt>padding_len = (PADBS - (SIVLEN + len(component)) % PADBS) % PADBS</tt>.</t>
              </li>
              <li>
                <t><tt>keystream = keystream_xof.read(len(component) + padding_len)</tt>.</t>
              </li>
              <li>
                <t><tt>padded_component = component concatenated with zeros(padding_len)</tt>.</t>
              </li>
              <li>
                <t><tt>encrypted_part = padded_component XOR keystream</tt>.</t>
              </li>
              <li>
                <t><tt>encrypted_output = encrypted_output concatenated with SIV concatenated with encrypted_part</tt>.</t>
              </li>
            </ul>
          </li>
          <li>
            <t><tt>base64_output = base64url_encode(encrypted_output)</tt>.</t>
          </li>
          <li>
            <t>If scheme is not empty: Return <tt>scheme + base64_output</tt></t>
          </li>
          <li>
            <t>Else if original URI started with ‘/’: Return <tt>'/' + base64_output</tt></t>
          </li>
          <li>
            <t>Else: Return <tt>base64_output</tt></t>
          </li>
        </ol>
      </section>
      <section anchor="decryption-algorithm">
        <name>Decryption Algorithm</name>
        <t>Input: secret_key, context, encrypted_uri</t>
        <t>Output: encrypted_uri</t>
        <t>Note: For path-only URIs (those starting with ‘/’), the output format is:
- ‘/’ followed by the base64url-encoded encrypted components
- This preserves the absolute path indicator in the encrypted form or error</t>
        <t>Steps:</t>
        <ol spacing="normal" type="1"><li>
            <t>Split encrypted URI into scheme and base64 part</t>
          </li>
          <li>
            <t><tt>decoded = base64url_decode(base64_part)</tt>. If decoding fails, return <tt>error</tt>.</t>
          </li>
          <li>
            <t>Initialize XOF instances as described in <xref target="xof-init"/></t>
          </li>
          <li>
            <t><tt>decrypted_components = empty list</tt></t>
          </li>
          <li>
            <t><tt>position = 0</tt></t>
          </li>
          <li>
            <t>While <tt>position &lt; len(decoded)</tt>:
            </t>
            <ul spacing="normal">
              <li>
                <t><tt>SIV = decoded[position:position+SIVLEN]</tt>. If not enough bytes, return <tt>error</tt>.</t>
              </li>
              <li>
                <t><tt>keystream_xof = base_keystream_xof.clone().update(SIV)</tt>.</t>
              </li>
              <li>
                <t><tt>component_start = position + SIVLEN</tt></t>
              </li>
              <li>
                <t><tt>component = empty byte array</tt></t>
              </li>
              <li>
                <t><tt>position = position + SIVLEN</tt></t>
              </li>
              <li>
                <t>While <tt>position &lt; len(decoded)</tt>:
                </t>
                <ul spacing="normal">
                  <li>
                    <t><tt>decrypted_byte = decoded[position] XOR keystream_xof.read(1)</tt></t>
                  </li>
                  <li>
                    <t><tt>position = position + 1</tt></t>
                  </li>
                  <li>
                    <t>If <tt>decrypted_byte == 0x00</tt>: continue (skip padding)</t>
                  </li>
                  <li>
                    <t><tt>component.append(decrypted_byte)</tt></t>
                  </li>
                  <li>
                    <t>If <tt>decrypted_byte</tt> is <tt>'/'</tt>, <tt>'?'</tt>, or <tt>'#'</tt>:
                    </t>
                    <ul spacing="normal">
                      <li>
                        <t><tt>total_len = position - component_start</tt></t>
                      </li>
                      <li>
                        <t><tt>position = position + ((PADBS - ((SIVLEN + total_len) % PADBS)) % PADBS)</tt></t>
                      </li>
                      <li>
                        <t>Break inner loop</t>
                      </li>
                    </ul>
                  </li>
                </ul>
              </li>
              <li>
                <t>Update <tt>components_xof</tt> with <tt>component</tt>.</t>
              </li>
              <li>
                <t><tt>expected_SIV = components_xof.clone().read(SIVLEN)</tt>.</t>
              </li>
              <li>
                <t>If <tt>constant_time_compare(SIV, expected_SIV) == false</tt>, return <tt>error</tt>.</t>
              </li>
              <li>
                <t><tt>decrypted_components.append(component)</tt>.</t>
              </li>
            </ul>
          </li>
          <li>
            <t><tt>decrypted_path = join(decrypted_components)</tt>.</t>
          </li>
          <li>
            <t>Return <tt>scheme + decrypted_path</tt></t>
          </li>
        </ol>
      </section>
    </section>
    <section anchor="implementation-details">
      <name>Implementation Details</name>
      <section anchor="turboshake128-usage">
        <name>TurboSHAKE128 Usage</name>
        <t>Implementations MUST use TurboSHAKE128 with a domain separation
parameter of <tt>0x1F</tt> for all operations. The TurboSHAKE128 XOF is used
for:</t>
        <ul spacing="normal">
          <li>
            <t>Generating SIVs from the components XOF</t>
          </li>
          <li>
            <t>Generating keystream for encryption/decryption</t>
          </li>
          <li>
            <t>All XOF operations in the initialization</t>
          </li>
        </ul>
        <t>TurboSHAKE128 is specified in <xref target="RFC9861"/> and provides the security
properties needed for this construction.</t>
      </section>
      <section anchor="key-and-context-handling">
        <name>Key and Context Handling</name>
        <t>The secret key MUST be at least <tt>SIVLEN</tt> bytes long. Keys shorter than <tt>SIVLEN</tt>
bytes MUST be rejected. Implementations SHOULD validate that the key
does not consist of repeated patterns (e.g., identical first and
second halves) as a best practice.</t>
        <t>The context parameter is a string that provides domain separation.
Different applications SHOULD use different context strings to prevent
cross-application attacks. The context string MAY be empty.</t>
        <t>Both key and context are length-prefixed when absorbed into the base
XOF:</t>
        <artwork><![CDATA[
base_xof.update(len(secret_key) as uint8)
base_xof.update(secret_key)
base_xof.update(len(context) as uint8)
base_xof.update(context)
]]></artwork>
        <t>The length is encoded as a single byte, limiting keys and contexts to
255 bytes. This is sufficient for all practical use cases.</t>
      </section>
      <section anchor="error-handling">
        <name>Error Handling</name>
        <t>Implementations MUST NOT reveal the cause of decryption failures. All
error conditions (invalid base64, incorrect padding, SIV mismatch,
insufficient data) MUST result in identical, generic error messages.</t>
        <t>SIV comparison MUST be performed in constant-time to prevent timing
attacks.</t>
      </section>
    </section>
    <section anchor="security-guarantees">
      <name>Security Guarantees</name>
      <t>URICrypt provides the following cryptographic security guarantees:</t>
      <section anchor="confidentiality">
        <name>Confidentiality</name>
        <t>URICrypt achieves semantic security for URI path components through its use of TurboSHAKE128 as a pseudorandom function. Each component is encrypted using a unique keystream derived from the following:</t>
        <ul spacing="normal">
          <li>
            <t>The secret key</t>
          </li>
          <li>
            <t>The application context</t>
          </li>
          <li>
            <t>A synthetic initialization vector (SIV) that depends on all preceding components</t>
          </li>
        </ul>
        <t>This construction ensures that:</t>
        <ul spacing="normal">
          <li>
            <t>An attacker without the secret key cannot recover plaintext components from ciphertexts.</t>
          </li>
          <li>
            <t>The keystream generation is computationally indistinguishable from random for each unique (key, context, path-prefix) tuple.</t>
          </li>
          <li>
            <t>Components are protected by at least 128 bits of security against brute-force attacks.</t>
          </li>
        </ul>
      </section>
      <section anchor="authenticity-and-integrity">
        <name>Authenticity and Integrity</name>
        <t>Each URI component is authenticated through the SIV mechanism:</t>
        <ul spacing="normal">
          <li>
            <t>The SIV acts as a Message Authentication Code (MAC) computed over the component and all preceding components.</t>
          </li>
          <li>
            <t>Any modification to a component will cause the SIV verification to fail during decryption.</t>
          </li>
          <li>
            <t>The chained construction ensures that reordering, insertion, or deletion of components is detected.</t>
          </li>
          <li>
            <t>Authentication provides 128-bit security against forgery attempts.</t>
          </li>
        </ul>
      </section>
      <section anchor="prefix-preserving-property">
        <name>Prefix-Preserving Property</name>
        <t>URICrypt maintains a controlled information leakage pattern:</t>
        <ul spacing="normal">
          <li>
            <t>URIs sharing a common prefix will produce ciphertexts with the same encrypted prefix.</t>
          </li>
          <li>
            <t>This property is deterministic and intentional, enabling systems to perform prefix-based operations.</t>
          </li>
          <li>
            <t>The leakage is limited to prefix structure only—no information about non-matching suffixes is revealed.</t>
          </li>
        </ul>
      </section>
      <section anchor="domain-separation">
        <name>Domain Separation</name>
        <t>The context parameter provides cryptographic domain separation:</t>
        <ul spacing="normal">
          <li>
            <t>Different contexts with the same key produce completely independent ciphertexts.</t>
          </li>
          <li>
            <t>This prevents cross-context attacks where ciphertexts from one application could be used in another.</t>
          </li>
          <li>
            <t>Context binding is cryptographically enforced through the XOF initialization.</t>
          </li>
        </ul>
      </section>
      <section anchor="key-commitment">
        <name>Key Commitment</name>
        <t>URICrypt provides full key-commitment security.</t>
        <t>The scheme is fully key-committing, meaning that a ciphertext can only be decrypted with the exact key that was used to encrypt it. It is computationally infeasible to find two different keys that successfully decrypt the same ciphertext to valid plaintexts.</t>
      </section>
      <section anchor="resistance-to-common-attacks">
        <name>Resistance to Common Attacks</name>
        <t>URICrypt resists several categories of attacks:</t>
        <t>Chosen-plaintext Attacks (CPA): While deterministic, URICrypt is CPA-secure for unique inputs. The determinism is a design requirement for prefix preservation.</t>
        <t>Tampering Detection: Any bit flip, truncation, or modification in the ciphertext will be detected with overwhelming probability (1 - 2<sup>-128</sup>).</t>
        <t>Length-extension Attacks: The use of length-prefixed encoding and domain separation prevents length-extension attacks.</t>
        <t>Replay Attacks: Within a single (key, context) pair, replay is possible due to determinism. Applications requiring replay protection should incorporate timestamps or nonces into the context.</t>
        <t>Key Recovery: TurboSHAKE128’s security properties ensure that observing ciphertexts does not leak information about the secret key.</t>
      </section>
      <section anchor="security-bounds">
        <name>Security Bounds</name>
        <t>The security of URICrypt is bounded by the following:</t>
        <ul spacing="normal">
          <li>
            <t>Key strength: Minimum 128-bit security with <tt>SIVLEN</tt>-byte keys</t>
          </li>
          <li>
            <t>Collision resistance: 2<sup>64</sup> birthday bound for SIV collisions</t>
          </li>
          <li>
            <t>Authentication security: 2<sup>-128</sup> probability of successful forgery</t>
          </li>
          <li>
            <t>Computational security: Based on TurboSHAKE128’s proven security as an XOF</t>
          </li>
        </ul>
      </section>
      <section anchor="limitations-and-trade-offs">
        <name>Limitations and Trade-offs</name>
        <t>URICrypt makes specific security trade-offs for functionality, including the following:</t>
        <ul spacing="normal">
          <li>
            <t>Deterministic encryption: Same inputs produce same outputs, enabling certain traffic analysis</t>
          </li>
          <li>
            <t>Partial length obfuscation: With <tt>PADBS=3</tt>, exact component lengths are partially hidden</t>
          </li>
          <li>
            <t>Prefix structure leakage: The hierarchical structure of URIs is preserved by design</t>
          </li>
          <li>
            <t>SIV length configuration: Implementations MAY adjust <tt>SIVLEN</tt> for different usage bounds. Larger values (24 or 32 bytes) increase birthday bound resistance at the cost of ciphertext expansion. However, 16 bytes is generally recommended as it provides practical collision resistance with acceptable overhead</t>
          </li>
          <li>
            <t>Padding block size configuration: The default <tt>PADBS=3</tt> already provides partial length-hiding. Implementations MAY adjust <tt>PADBS</tt> to increase size obfuscation. Larger values create larger size buckets but increase ciphertext expansion. The value MUST remain a multiple of 3 to ensure efficient Base64url encoding without padding characters</t>
          </li>
        </ul>
        <t>These trade-offs are intentional and necessary for the prefix-preserving functionality. Applications requiring stronger privacy guarantees should evaluate whether URICrypt’s properties align with their threat model.</t>
      </section>
    </section>
    <section anchor="security-considerations">
      <name>Security Considerations</name>
      <t>URICrypt provides confidentiality and integrity for URI paths while
preserving prefix relationships. The encryption is fully reversible:
encrypted URIs can be decrypted to recover the original plaintext URIs,
but only with knowledge of the secret key. The security properties depend on:</t>
      <ul spacing="normal">
        <li>
          <t>Key Secrecy: The security of URICrypt depends entirely on the
secrecy of the secret key. Keys MUST be generated using a
cryptographically secure random number generator <xref target="RFC4086"/> and
stored securely.</t>
        </li>
        <li>
          <t>Deterministic Encryption: URICrypt is deterministic - identical
inputs produce identical outputs. This allows observers to detect
when the same URI is encrypted multiple times. Applications
requiring unlinkability SHOULD incorporate additional entropy (e.g.,
via the context parameter).</t>
        </li>
        <li>
          <t>Prefix Preservation: While essential for functionality, prefix
preservation leaks information about URI structure. Systems where
this information is sensitive SHOULD consider alternative
approaches.</t>
        </li>
        <li>
          <t>Context Separation: The context parameter prevents cross-context
attacks. Applications MUST use distinct contexts for different
purposes, even when sharing keys.</t>
        </li>
        <li>
          <t>Component Authentication: Each component is authenticated via
the SIV mechanism. Any modification, reordering, or truncation of
components will be detected during decryption.</t>
        </li>
        <li>
          <t>Length Obfuscation: The default <tt>PADBS=3</tt> configuration provides partial
length-hiding. Applications requiring stronger length-hiding
SHOULD consider using larger <tt>PADBS</tt> values or padding components to fixed lengths.</t>
        </li>
        <li>
          <t>Key Reuse: Using the same key with different contexts is safe, but
using the same (key, context) pair for different applications is
NOT RECOMMENDED.</t>
        </li>
      </ul>
    </section>
    <section numbered="false" anchor="iana-considerations">
      <name>IANA Considerations</name>
      <t>This document has no actions for IANA.</t>
    </section>
    <section numbered="false" anchor="acknowledgments">
      <name>Acknowledgments</name>
      <t>The author would like to thank Maciej Soltysiak for highlighting the importance of properly supporting query parameters and fragments in URI encryption.</t>
    </section>
  </middle>
  <back>
    <references anchor="sec-normative-references">
      <name>Normative References</name>
      <reference anchor="I-D.draft-denis-ipcrypt">
        <front>
          <title>Methods for IP Address Encryption and Obfuscation</title>
          <author fullname="Frank Denis" initials="F." surname="Denis">
            <organization>Fastly Inc.</organization>
          </author>
          <date day="19" month="September" year="2025"/>
          <abstract>
            <t>   This document specifies secure, efficient methods for encrypting IP
   addresses for privacy-preserving storage, logging, and analytics.
   Unlike truncation, which destroys data irreversibly, these methods
   are reversible with the encryption key while providing strong privacy
   guarantees.

   Four modes are defined: ipcrypt-deterministic (format-preserving,
   16-byte output), ipcrypt-pfx (prefix-preserving, native address
   size), ipcrypt-nd and ipcrypt-ndx (non-deterministic with random
   tweaks).  All support high-performance processing at network speeds
   and produce interoperable results across implementations.

            </t>
          </abstract>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-denis-ipcrypt-12"/>
      </reference>
      <reference anchor="RFC2119">
        <front>
          <title>Key words for use in RFCs to Indicate Requirement Levels</title>
          <author fullname="S. Bradner" initials="S." surname="Bradner"/>
          <date month="March" year="1997"/>
          <abstract>
            <t>In many standards track documents several words are used to signify the requirements in the specification. These words are often capitalized. This document defines these words as they should be interpreted in IETF documents. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.</t>
          </abstract>
        </front>
        <seriesInfo name="BCP" value="14"/>
        <seriesInfo name="RFC" value="2119"/>
        <seriesInfo name="DOI" value="10.17487/RFC2119"/>
      </reference>
      <reference anchor="RFC8174">
        <front>
          <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
          <author fullname="B. Leiba" initials="B." surname="Leiba"/>
          <date month="May" year="2017"/>
          <abstract>
            <t>RFC 2119 specifies common key words that may be used in protocol specifications. This document aims to reduce the ambiguity by clarifying that only UPPERCASE usage of the key words have the defined special meanings.</t>
          </abstract>
        </front>
        <seriesInfo name="BCP" value="14"/>
        <seriesInfo name="RFC" value="8174"/>
        <seriesInfo name="DOI" value="10.17487/RFC8174"/>
      </reference>
      <reference anchor="RFC3986">
        <front>
          <title>Uniform Resource Identifier (URI): Generic Syntax</title>
          <author fullname="T. Berners-Lee" initials="T." surname="Berners-Lee"/>
          <author fullname="R. Fielding" initials="R." surname="Fielding"/>
          <author fullname="L. Masinter" initials="L." surname="Masinter"/>
          <date month="January" year="2005"/>
          <abstract>
            <t>A Uniform Resource Identifier (URI) is a compact sequence of characters that identifies an abstract or physical resource. This specification defines the generic URI syntax and a process for resolving URI references that might be in relative form, along with guidelines and security considerations for the use of URIs on the Internet. The URI syntax defines a grammar that is a superset of all valid URIs, allowing an implementation to parse the common components of a URI reference without knowing the scheme-specific requirements of every possible identifier. This specification does not define a generative grammar for URIs; that task is performed by the individual specifications of each URI scheme. [STANDARDS-TRACK]</t>
          </abstract>
        </front>
        <seriesInfo name="STD" value="66"/>
        <seriesInfo name="RFC" value="3986"/>
        <seriesInfo name="DOI" value="10.17487/RFC3986"/>
      </reference>
      <reference anchor="RFC4648">
        <front>
          <title>The Base16, Base32, and Base64 Data Encodings</title>
          <author fullname="S. Josefsson" initials="S." surname="Josefsson"/>
          <date month="October" year="2006"/>
          <abstract>
            <t>This document describes the commonly used base 64, base 32, and base 16 encoding schemes. It also discusses the use of line-feeds in encoded data, use of padding in encoded data, use of non-alphabet characters in encoded data, use of different encoding alphabets, and canonical encodings. [STANDARDS-TRACK]</t>
          </abstract>
        </front>
        <seriesInfo name="RFC" value="4648"/>
        <seriesInfo name="DOI" value="10.17487/RFC4648"/>
      </reference>
      <reference anchor="RFC9861">
        <front>
          <title>*** BROKEN REFERENCE ***</title>
          <author>
            <organization/>
          </author>
          <date/>
        </front>
      </reference>
      <reference anchor="RFC4086">
        <front>
          <title>Randomness Requirements for Security</title>
          <author fullname="D. Eastlake 3rd" initials="D." surname="Eastlake 3rd"/>
          <author fullname="J. Schiller" initials="J." surname="Schiller"/>
          <author fullname="S. Crocker" initials="S." surname="Crocker"/>
          <date month="June" year="2005"/>
          <abstract>
            <t>Security systems are built on strong cryptographic algorithms that foil pattern analysis attempts. However, the security of these systems is dependent on generating secret quantities for passwords, cryptographic keys, and similar quantities. The use of pseudo-random processes to generate secret quantities can result in pseudo-security. A sophisticated attacker may find it easier to reproduce the environment that produced the secret quantities and to search the resulting small set of possibilities than to locate the quantities in the whole of the potential number space.</t>
            <t>Choosing random quantities to foil a resourceful and motivated adversary is surprisingly difficult. This document points out many pitfalls in using poor entropy sources or traditional pseudo-random number generation techniques for generating such quantities. It recommends the use of truly random hardware techniques and shows that the existing hardware on many systems can be used for this purpose. It provides suggestions to ameliorate the problem when a hardware solution is not available, and it gives examples of how large such quantities need to be for some applications. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.</t>
          </abstract>
        </front>
        <seriesInfo name="BCP" value="106"/>
        <seriesInfo name="RFC" value="4086"/>
        <seriesInfo name="DOI" value="10.17487/RFC4086"/>
      </reference>
    </references>
    <?line 810?>

<section anchor="pseudocode">
      <name>Pseudocode</name>
      <section anchor="uri-component-extraction-1">
        <name>URI Component Extraction</name>
        <artwork><![CDATA[
function extract_components(uri_string):
  if uri_string contains "://":
     scheme = substring up to and including "://"
     path = substring after "://"
  else:
     scheme = ""
     path = uri_string

  components = []

  // For absolute paths, treat leading "/" as first component
  if path starts with "/":
     components.append("/")
     path = substring after first "/"

  while path is not empty:
     terminator_pos = find_next_terminator(path)
     if terminator_pos found:
        component = substring(path, 0, terminator_pos + 1)
        path = substring(path, terminator_pos + 1)
        components.append(component)
     else:
        components.append(path)
        path = ""

  return (scheme, components)

function find_next_terminator(path):
  for i from 0 to length(path) - 1:
     if path[i] == '/' or path[i] == '?' or path[i] == '#':
        return i
  return not_found
]]></artwork>
      </section>
      <section anchor="xof-initialization">
        <name>XOF Initialization</name>
        <artwork><![CDATA[
function initialize_xofs(secret_key, context):
  // Initialize base XOF
  base_xof = TurboSHAKE128(0x1F)

  // Absorb key and context with length prefixes
  base_xof.update(uint8(len(secret_key)))
  base_xof.update(secret_key)
  base_xof.update(uint8(len(context)))
  base_xof.update(context)

  // Create components XOF
  components_xof = base_xof.clone()
  components_xof.update("IV")

  // Create base keystream XOF
  base_keystream_xof = base_xof.clone()
  base_keystream_xof.update("KS")

  return (components_xof, base_keystream_xof)
]]></artwork>
      </section>
      <section anchor="encryption-algorithm-1">
        <name>Encryption Algorithm</name>
        <artwork><![CDATA[
function uricrypt_encrypt(secret_key, context, uri_string):
  // Extract components
  (scheme, components) = extract_components(uri_string)

  // Initialize XOF instances with secret key and context
  (components_xof, base_keystream_xof) =
      initialize_xofs(secret_key, context)
  if error: return error

  encrypted_output = byte_array()

  // Process each component
  for component in components:
     // Update components XOF for SIV computation
     components_xof.update(component)

     // Generate SIVLEN-byte Synthetic Initialization Vector (SIV)
     siv = components_xof.squeeze(SIVLEN)

     // Create keystream XOF for this component
     keystream_xof = base_keystream_xof.clone()
     keystream_xof.update(siv)

     // Calculate padding for base64 encoding alignment
     // The total bytes (SIV + component) must be a multiple of PADBS
     // to produce clean base64 output without padding characters
     component_len = len(component)
     padding_len = (PADBS - (SIVLEN + component_len) % PADBS) % PADBS

     // Generate keystream
     keystream = keystream_xof.squeeze(component_len + padding_len)

     // Pad component to align with base64 encoding requirements
     padded_component = component + byte_array(padding_len)

     // Encrypt using XOR with keystream
     encrypted_part = xor_bytes(padded_component, keystream)

     // Append to output
     encrypted_output.extend(siv)
     encrypted_output.extend(encrypted_part)

  // Base64 encode with URL-safe characters and no padding
  base64_output = base64_urlsafe_no_pad_encode(encrypted_output)

  // Return with appropriate prefix
  if scheme != "":
     return scheme + base64_output
  else if uri_string starts with "/":
     return "/" + base64_output
  else:
     return base64_output
]]></artwork>
      </section>
      <section anchor="decryption-algorithm-1">
        <name>Decryption Algorithm</name>
        <artwork><![CDATA[
function uricrypt_decrypt(secret_key, context, encrypted_uri):
  // Split scheme and base64
  if encrypted_uri contains "://":
     scheme = substring up to and including "://"
     base64_part = substring after "://"
  else:
     scheme = ""
     base64_part = encrypted_uri

  // Decode base64
  try:
     decoded = base64_urlsafe_no_pad_decode(base64_part)
  catch:
     return error("Decryption failed")

  // Initialize XOF instances with secret key and context
  (components_xof, base_keystream_xof) =
      initialize_xofs(secret_key, context)
  if error: return error

  decrypted_components = []
  input_stream = ByteStream(decoded)

  // Process each component
  while not input_stream.empty():
     // Read SIV
     siv = input_stream.read(SIVLEN)
     if len(siv) != SIVLEN:
        return error("Decryption failed")

     // Create keystream XOF
     keystream_xof = base_keystream_xof.clone()
     keystream_xof.update(siv)

     // Determine component length by checking padding constraints
     remaining = input_stream.remaining()
     if remaining == 0:
        return error("Decryption failed")

     // Find valid component length by checking padding alignment
     component_data = None
     for possible_len in range(1, remaining + 1):
        total_len = SIVLEN + possible_len
        padding_len = (PADBS - total_len % PADBS) % PADBS
        if possible_len >= padding_len:
           component_data = input_stream.peek(possible_len)
           break

     if component_data is None:
        return error("Decryption failed")

     // Read encrypted data
     encrypted_part = input_stream.read(len(component_data))

     // Generate keystream and decrypt
     keystream = keystream_xof.squeeze(len(encrypted_part))
     padded_plaintext = xor_bytes(encrypted_part, keystream)

     // Remove padding bytes added for base64 alignment
     padding_len = (PADBS - (SIVLEN + len(encrypted_part)) % PADBS) % PADBS
     component = padded_plaintext[:-padding_len] if padding_len > 0 else padded_plaintext

     // Update XOF with plaintext
     components_xof.update(component)

     // Generate expected SIV
     expected_siv = components_xof.squeeze(SIVLEN)

     // Authenticate using constant-time comparison to prevent timing attacks
     if not constant_time_equal(siv, expected_siv):
        return error("Decryption failed")

     decrypted_components.append(component)

  // Reconstruct URI
  if scheme and decrypted_components:
     path = "".join(decrypted_components)
     return scheme + path
  elif decrypted_components:
     return "/" + "".join(decrypted_components)
  else:
     return ""
]]></artwork>
      </section>
      <section anchor="padding-and-encoding-1">
        <name>Padding and Encoding</name>
        <artwork><![CDATA[
function calculate_padding(component_len):
  // Calculate padding needed for base64 encoding alignment
  // The combined SIV (SIVLEN bytes) + component must be divisible by PADBS
  // for clean base64 encoding without '=' padding characters
  total_len = SIVLEN + component_len
  return (3 - total_len % 3) % 3

function base64_urlsafe_no_pad_encode(data):
  // Use standard base64 encoding
  encoded = standard_base64_encode(data)
  // Make URL-safe and remove padding for URI compatibility
  encoded = encoded.replace('+', '-')
                   .replace('/', '_')
                   .rstrip('=')
  return encoded

function base64_urlsafe_no_pad_decode(encoded):
  // Add padding if needed for standard decoder
  padding = (4 - len(encoded) % 4) % 4
  if padding > 0:
     encoded = encoded + ('=' * padding)
  // Make standard base64
  encoded = encoded.replace('-', '+')
                   .replace('_', '/')
  // Decode
  return standard_base64_decode(encoded)
]]></artwork>
      </section>
    </section>
    <section anchor="test-vectors">
      <name>Test Vectors</name>
      <t>These test vectors were generated using the reference Rust implementation
of URICrypt with TurboSHAKE128.</t>
      <artwork><![CDATA[
Test Configuration:
secret_key (hex): 0102030405060708090a0b0c0d0e0f10
context: "test-context"
]]></artwork>
      <section anchor="test-vector-1-full-uri">
        <name>Test Vector 1: Full URI</name>
        <artwork><![CDATA[
Input: "https://example.com/a/b/c"
Output: "https://HOGo9vauZ3b3xsPNPQng5apSzL5V7QW94C7USgN8mHZJ337AKSWOu
         cUwMuD-uUfF95SsSHCNgBkXUnH1uGll_YtBltXSqKEHNcYJJwbdFdhfWz19"
]]></artwork>
      </section>
      <section anchor="test-vector-2-path-only-uri">
        <name>Test Vector 2: Path-Only URI</name>
        <artwork><![CDATA[
Input: "/a/b/c"
Output: "/b9bCOhqZsvU9XxGOMk6d8QFQhTIdI_xYKpds2lWXpZCms5-az9wtfUft3rec
         3d9YkUo0N7VcxO5MXfxE5UobvgTJX8UpRdNN"
]]></artwork>
      </section>
      <section anchor="test-vector-3-multi-component-path">
        <name>Test Vector 3: Multi-Component Path</name>
        <artwork><![CDATA[
Input: "https://cdn.example.com/videos/2025/03/file.mp4"
Output: "https://hxUM2N3txwYjGxjvCpWn30SznxR0v0fDbkSQgCTXCUu7Rq8iSbWP4
         0OvYxKs9zC3kw1JNzAc4Wuj7RZvRd0VUprJWLs5KJPnWsA9Kguxa_J7XviTS3G
         Tqf-XZdPxYyq1Y1MXVE9_4ojHwm6jBDUkVthAkuNe5Cqk_h6d"
]]></artwork>
      </section>
      <section anchor="test-vector-4-root-with-scheme">
        <name>Test Vector 4: Root with Scheme</name>
        <artwork><![CDATA[
Input: "https://example.com/"
Output: "https://HOGo9vauZ3b3xsPNPQng5apSzL5V7QW94C7USgN8"
]]></artwork>
      </section>
      <section anchor="test-vector-5-simple-path">
        <name>Test Vector 5: Simple Path</name>
        <artwork><![CDATA[
Input: "/path/to/resource"
Output: "/b9bCOhqZsvU9XxGOMk6d8QFQPTuMlsQKDBhAbc77JvsdRj0kxiFipunATQmm
         CkNhAe0BPP2EqQoxORElY_ukfUYSrr9mIMfiO9joa3Kn5RS7eSKr"
]]></artwork>
      </section>
      <section anchor="test-vector-6-uri-with-query-parameters">
        <name>Test Vector 6: URI with Query Parameters</name>
        <artwork><![CDATA[
Input: "https://example.com/search?q=test&limit=10"
Output: "https://HOGo9vauZ3b3xsPNPQng5apSzL5V7QW94C7USgN8cl2BBtuWmxTsI
         Ij59ka3KeDsaqXFGnKgW9aLLR36YvUf9ORkMnVE5PTR_3DiO43hL9WjdSu7L9
         FN"
]]></artwork>
      </section>
      <section anchor="test-vector-7-uri-with-fragment">
        <name>Test Vector 7: URI with Fragment</name>
        <artwork><![CDATA[
Input: "https://docs.example.com/guide#installation"
Output: "https://ypHTiw0JUMcr4bUjQH9Dxo8wGWHyfFlLq8VrOE-zX6IbgLFxYX_Jm
         2hzivywvrpIBWa-9Jl6nSZLq2pd35QwkDsc1-_Kao2BvyBB19ndu1PpwQv1wy
         uA"
]]></artwork>
      </section>
      <section anchor="test-vector-8-uri-with-query-and-fragment">
        <name>Test Vector 8: URI with Query and Fragment</name>
        <artwork><![CDATA[
Input: "/api/v2/users?id=123#profile"
Output: "/b9bCOhqZsvU9XxGOMk6d8QFQwcP2C3bJVNVZDge7zfub_ai4x6LaUlXp-XjZ
         XOgZlLloIbasK-JKlbeKeKV2rctq5bX9zQh1KogN2zaggTMZioUb4kwGIKp8Z
         y744xQwGDG64n6GhN56XEM8LvBfJuEj6ZgsjeLbTPIMbCmO0pJhzVSh"
]]></artwork>
      </section>
    </section>
  </back>
  <!-- ##markdown-source:
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
pzeXmx/vXp0cXW/0X7x7+W50cdA/uLr79HbaT1fGHz5OP+9O0vWm/23zNhtc
DrLVJOiZbaz2Nz9dX8ZLx8/f9+5O1o8+Du721y/j7s3w4s3HF5fTs/7xcfV6
V9viCM2OTeO6xfVXH0ivH7XsQ8EIgzh9trK0sv5saZVe525NpmsVxzS6uzxa
OV7N7m4/fXl19+Vmd/ohWl06/xbdnS3dLA32utfn74a7Fx93L/PnZ19fhOfd
D6drZn9LJzef7t6mm992V69vl98cf9vurX3Ivzw/+3xz1l96fzlN3nw4TNff
vjmNPqTbm2+H+Z1/9eb5x5vw4nz1lRnn4uug+fFz//Tu0+zr8qflo4/v9zev
1uIvr28nG1929i6v32ej7ev8OFjf/Xp9NdroVwNtrS3O8ClsLshDgsHDGPwH
kLd6FettcU5XveLEKp7ufRjnTi/yo3H67u3ezmi723v+/M1N2j/7snR9F74M
p3m0ffFuMjGw3L0+Hm0HSzunpyv7X9/Fdydn++NPV/n14PLTeZJsTg6OBuHJ
5pfYX30brZ+dPw/O3ybVG9mgKDGG5juKSTjVMQkPwzUNMEr4p69bSHH+RolU
W8tLfwDavfHKzk6Wf5jcXaQHZr8HX9Y3r2EzwV7qf/348lX0dvhh0z88PFvd
+HRzOdg8Obs+it7vr59enF2t7oUna6ujw80PX/rn+fPDTTPMyzkX8bkFhJcy
BqN68/24lzrXcJjDPXxCJqwxh0lW7H42fX0R3i69uTzqJWvdyy/vXm/u3cUv
bl99eD0bvBwffn3xPjnZb377uHHQHR6+vPv08eqNddoro2/hzez2Jpke7Hzw
m5tvxhvR+efDryvT/ur6u9vrvbS33Lx668crOzeznZ3lzaifL59Ob9/dLN/O
zDD5dvXuX5RQAAWjajg886fhs5uVZ3kK+PFT2N9aXll9Mk1iJD+PQfPb3unK
7mr3zfvj95/3hsHzb4O8e+WHa3cbh/7l+OO0+fHLZ7PijyfDz+PDcXwAzPtt
883bcTd4G7x9v5L0sq/r3Y+b396Nlt/Gw+OVb/5weHH0OYwvu2vXt68O3k5f
WMPMnq+t3b27fbX3amMt2ng1Ol7f+Lh/9OLwZmfwJt//svF5mH4JDrsXpwdH
3d3JydL0zejb+/MRQ+u/AWsbSoBEowAA

-->

</rfc>
