<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE rfc [
  <!ENTITY nbsp    "&#160;">
  <!ENTITY zwsp   "&#8203;">
  <!ENTITY nbhy   "&#8209;">
  <!ENTITY wj     "&#8288;">
]>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude"
     category="exp"
     docName="draft-vso-cpp-core-02"
     ipr="trust200902"
     submissionType="independent"
     xml:lang="en"
     version="3">

  <front>
    <title abbrev="CPP Core">Content Provenance Profile (CPP) Core</title>
    <seriesInfo name="Internet-Draft" value="draft-vso-cpp-core-02"/>

    <author fullname="Tokachi Kamimura" initials="T." surname="Kamimura">
      <organization>VeritasChain Co., Ltd.</organization>
      <address>
        <email>kamimura@veritaschain.org</email>
      </address>
    </author>

    <date year="2026" month="February" day="7"/>

    <area>Security</area>
    <workgroup>Independent Submission</workgroup>

    <keyword>provenance</keyword>
    <keyword>timestamp</keyword>
    <keyword>merkle tree</keyword>
    <keyword>RFC 3161</keyword>
    <keyword>media authenticity</keyword>
    <keyword>content provenance</keyword>
    <keyword>depth analysis</keyword>
    <keyword>screen detection</keyword>

    <abstract>
      <t>The Content Provenance Profile (CPP) is an open specification for
      cryptographically verifiable media capture provenance. This document
      defines the core data model, hashing conventions, Merkle tree
      construction rules, RFC 3161 Time-Stamp Authority (TSA) anchoring
      protocol, and offline verification procedures for CPP.</t>

      <t>CPP enables capture devices to produce tamper-evident provenance
      records that bind media content to external timestamps via trusted
      third parties. Unlike self-attestation models, CPP requires independent
      timestamp verification through RFC 3161 TSA services, providing
      externally verifiable proof of when media was captured.</t>

      <t>This revision (-01) incorporates implementation experience from
      multi-platform deployments, adding self-attested signer identity,
      hardware-backed key requirements, chain context for partial submission
      detection, depth analysis extensions for screen detection, and a
      Pre-Publish Verification Extension for social media sharing workflows.
      It also defines interoperability mappings with the C2PA specification.</t>
    </abstract>
  </front>

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

      <section anchor="problem-statement">
        <name>Problem Statement</name>
        <t>Digital media authenticity faces several fundamental challenges:</t>
        <ul>
          <li><em>Self-Attestation Weakness</em>: Systems where creators
          sign their own claims provide no independent verification. A verifier
          must trust that the creator's claimed timestamp is accurate.</li>
          <li><em>Metadata Stripping</em>: Social media platforms and
          messaging applications routinely strip embedded metadata, breaking
          provenance chains that depend on file-level embedding.</li>
          <li><em>Omission Attacks</em>: Systems that prove individual
          media authenticity cannot detect when unfavorable evidence has been
          selectively deleted from a collection.</li>
          <li><em>Terminology Confusion</em>: Terms like "verified"
          mislead users into believing content truthfulness has been established,
          when only provenance has been recorded.</li>
          <li><em>Analog Hole Attacks</em>: Photographing screens displaying
          manipulated content can bypass digital provenance systems that only
          track file-level operations.</li>
        </ul>
      </section>

      <section anchor="design-goals">
        <name>Design Goals</name>
        <t>CPP addresses these challenges through the following design
        principles:</t>
        <ol>
          <li><em>External Timestamp Verification</em>: Timestamps MUST
          be anchored to independent RFC 3161 Time-Stamp Authorities, enabling
          third-party verification without trusting the capture device.</li>
          <li><em>Omission Detection</em>: The Completeness Invariant
          mechanism enables detection of deleted events within a collection.</li>
          <li><em>Offline Verification</em>: All data necessary for
          verification is included in the Evidence Pack, enabling verification
          without network access.</li>
          <li><em>Provenance != Truth</em>: CPP proves when and by what
          device media was captured. It does NOT prove content truthfulness or
          scene authenticity.</li>
          <li><em>Hardware-Backed Security</em>: Private keys SHOULD be
          stored in hardware security modules where available (Secure Enclave,
          StrongBox, TPM).</li>
        </ol>
      </section>

      <section anchor="scope">
        <name>Scope</name>
        <t>This document specifies:</t>
        <ul>
          <li>The CPP event data model</li>
          <li>Hash computation and canonicalization rules</li>
          <li>Merkle tree construction and proof verification</li>
          <li>RFC 3161 TSA anchoring requirements</li>
          <li>Verification procedures for implementers</li>
          <li>Self-attested signer identity (SignerInfo)</li>
          <li>Chain context for partial submission detection</li>
          <li>Depth analysis extension for screen detection (OPTIONAL)</li>
          <li>Pre-Publish Verification extension (OPTIONAL)</li>
        </ul>
        <t>This document does NOT specify:</t>
        <ul>
          <li>Application user interface requirements</li>
          <li>Network protocols for proof distribution</li>
          <li>Key management or certificate policies</li>
          <li>Content authenticity claims</li>
        </ul>
      </section>

      <section anchor="changes-from-01">
        <name>Changes from draft-vso-cpp-core-01</name>
        <t>This revision incorporates the following changes:</t>

        <t><em>BREAKING CHANGE — Merkle Tree Domain Separation</em>:
        This document mandates domain-separated Merkle hashing
        (<xref target="domain-separation"/>).
        LeafHash MUST be computed as SHA256(0x00 || EventHash_bytes) and
        internal nodes as SHA256(0x01 || Left || Right).
        Earlier CPP specification documents (v1.0 through v1.4) used
        non-domain-separated hashing (LeafHash = SHA256(EventHash_bytes),
        Node = SHA256(Left || Right)). Those constructions are now
        deprecated. Implementations using the legacy (non-prefixed)
        construction MUST migrate to the domain-separated construction
        defined in this document. Test vectors in
        <xref target="test-vectors"/> reflect the domain-separated
        construction and differ from pre-domain-separation outputs.</t>

        <t><em>BREAKING CHANGE — AnchorDigest Verification</em>:
        Verifiers MUST now perform explicit format and digest matching
        checks on AnchorDigest, MerkleRoot, and TSA messageImprint
        fields as specified in <xref target="anchordigest-verification-requirements"/>.
        These checks were RECOMMENDED in -01 but are now REQUIRED.</t>

        <ul>
          <li>Added <xref target="anchordigest-verification-requirements"/>
          with mandatory format checks, TSA binding verification,
          and PROHIBITED implementation patterns</li>
          <li>Added Normative Authority and Legacy Migration guidance
          to <xref target="rfc6962-relationship"/></li>
          <li>Strengthened Domain Separation (<xref target="domain-separation"/>)
          with MUST/MUST NOT/DEPRECATED language</li>
          <li>Added cross-reference from TSA Verification
          (<xref target="tsa-verification"/>) to AnchorDigest
          verification requirements</li>
          <li>Clarified test vector applicability for legacy
          migration (<xref target="test-vectors"/>)</li>
          <li>Corrected author contact email address</li>
        </ul>
      </section>

      <section anchor="changes-from-00">
        <name>Changes from draft-vso-cpp-core-00</name>
        <t>The -01 revision incorporated the following changes:</t>
        <ul>
          <li>Added SignerInfo for self-attested identity
          (<xref target="signer-info"/>)</li>
          <li>Added DeviceInfo structure for cross-platform support
          (<xref target="device-info"/>)</li>
          <li>Added CaptureContext for environmental metadata
          (<xref target="capture-context"/>)</li>
          <li>Added Chain Context for partial submission detection
          (<xref target="chain-context"/>)</li>
          <li>Added Depth Analysis Extension for screen detection
          (<xref target="depth-analysis"/>)</li>
          <li>Added Pre-Publish Verification Extension
          (<xref target="pre-publish"/>)</li>
          <li>Added C2PA interoperability mappings
          (<xref target="c2pa-interop"/>)</li>
          <li>Clarified hardware-backed key storage requirements</li>
          <li>Added EXPORT and additional event types</li>
          <li>Expanded implementation experience with Android and
          cross-platform validation</li>
        </ul>
      </section>

      <section anchor="rfc6962-relationship">
        <name>Relationship to Other Specifications</name>
        <t>CPP defines its own Merkle tree construction that is NOT compatible
        with Certificate Transparency <xref target="RFC6962"/>. While inspired
        by similar principles, CPP uses different domain separation prefixes
        and padding rules optimized for media provenance use cases.
        Implementations MUST NOT assume RFC 6962 compatibility.</t>
        <t>CPP is complementary to the C2PA specification
        <xref target="C2PA"/>. C2PA tracks edit history of content;
        CPP proves capture provenance with deletion detection. See
        <xref target="c2pa-interop"/> for interoperability mappings.</t>
        <t><em>Normative Authority</em>: Where the cryptographic
        algorithms defined in this document (domain-separated Merkle
        hashing, AnchorDigest computation, verification procedures)
        conflict with earlier CPP specification documents (v1.0 through
        v1.5), this document takes precedence. The CPP specification
        series published by VSO serves as the design-level reference;
        this Internet-Draft is the normative interoperability
        specification for implementations seeking cross-platform
        compatibility.</t>
        <t><em>Legacy Migration</em>: Implementations using
        non-domain-separated Merkle hashing (LeafHash = SHA256(EventHash_bytes)
        without the 0x00 prefix) MUST migrate to the domain-separated
        construction defined in <xref target="domain-separation"/>.
        During a transition period, implementations MAY accept both
        legacy and domain-separated proofs for verification but MUST
        generate only domain-separated proofs. Implementations SHOULD
        log a deprecation warning when encountering legacy proofs.</t>
      </section>
    </section>

    <section anchor="conventions">
      <name>Conventions and Definitions</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>Additionally, this document uses the following terms:</t>

      <dl>
        <dt>Event</dt>
        <dd>A discrete, signed record representing a provenance action
        (capture, export, deletion).</dd>

        <dt>EventHash</dt>
        <dd>A SHA-256 hash of the canonicalized event data, formatted as
        <tt>sha256:&lt;64_hex_chars&gt;</tt>.</dd>

        <dt>LeafHash</dt>
        <dd>SHA-256(0x00 || EventHash_bytes), used as input to the Merkle tree.
        The 0x00 prefix provides domain separation from internal nodes.</dd>

        <dt>MerkleRoot</dt>
        <dd>The root hash of a binary Merkle tree containing one or more
        LeafHashes.</dd>

        <dt>AnchorDigest</dt>
        <dd>The 32-byte value submitted to the TSA. Represented as a 64-character
        lowercase hexadecimal string without prefix. MUST equal the MerkleRoot
        value (after stripping the <tt>sha256:</tt> prefix).</dd>

        <dt>TreeSize</dt>
        <dd>The count of original leaves in the Merkle tree before any padding.
        An unsigned integer. MUST be >= 1.</dd>

        <dt>PaddedSize</dt>
        <dd>The count of leaves after padding to a power of 2. Computed as the
        smallest power of 2 greater than or equal to TreeSize.</dd>

        <dt>Evidence Pack</dt>
        <dd>A self-contained data structure containing all information necessary
        for offline verification.</dd>

        <dt>Tombstone</dt>
        <dd>An event that records the legitimate deletion of a previous event.</dd>

        <dt>Provenance Available</dt>
        <dd>The recommended terminology for UI display, indicating capture
        provenance is recorded without implying content truth verification.</dd>

        <dt>Genesis PrevHash</dt>
        <dd>The constant value used as PrevHash for the first event in a chain:
        <tt>sha256:0000000000000000000000000000000000000000000000000000000000000000</tt>
        (64 zeros).</dd>

        <dt>SignerInfo</dt>
        <dd>An OPTIONAL self-attested identity claim included in events. Not
        independently verified; provides tamper-evident name association.</dd>

        <dt>Chain Context</dt>
        <dd>Metadata embedded in proofs describing the event's position within
        its chain, enabling detection of partial evidence submission.</dd>

        <dt>DeviceClass</dt>
        <dd>A classification of the capture device: SMARTPHONE, TABLET,
        EMBEDDED, PHYSICAL_CAMERA, DRONE, or INDUSTRIAL.</dd>
      </dl>
    </section>

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

      <section anchor="addressed-threats">
        <name>Addressed Threats</name>
        <table>
          <thead>
            <tr><th>Threat</th><th>Mitigation</th></tr>
          </thead>
          <tbody>
            <tr>
              <td>Timestamp forgery</td>
              <td>RFC 3161 TSA provides independent timestamp</td>
            </tr>
            <tr>
              <td>Evidence tampering</td>
              <td>EventHash binds content; Merkle proof binds to anchor</td>
            </tr>
            <tr>
              <td>Selective deletion</td>
              <td>Completeness Invariant detects missing events</td>
            </tr>
            <tr>
              <td>TSA token swapping</td>
              <td>messageImprint must match AnchorDigest</td>
            </tr>
            <tr>
              <td>Partial evidence submission</td>
              <td>Chain Context reveals event position and deletion counts</td>
            </tr>
            <tr>
              <td>Screen photography (analog hole)</td>
              <td>Depth Analysis Extension detects flat surfaces (OPTIONAL)</td>
            </tr>
          </tbody>
        </table>
      </section>

      <section anchor="non-threats">
        <name>Explicitly Not Addressed</name>
        <ul>
          <li>Content truthfulness (scene staging, deepfakes)</li>
          <li>Device compromise before capture</li>
          <li>Key extraction from secure hardware</li>
          <li>TSA collusion with adversary</li>
          <li>Identity verification (SignerInfo is self-attested only)</li>
        </ul>
      </section>
    </section>

    <section anchor="data-model">
      <name>Data Model</name>

      <section anchor="events">
        <name>Events</name>
        <t>An Event is the fundamental unit of provenance in CPP. Events are
        signed records that capture discrete provenance actions.</t>

        <section anchor="event-types">
          <name>Event Types</name>
          <table>
            <thead>
              <tr><th>Type</th><th>Description</th></tr>
            </thead>
            <tbody>
              <tr><td>INGEST</td><td>Media captured from device sensor</td></tr>
              <tr><td>SEAL</td><td>Collection sealed with Completeness Invariant</td></tr>
              <tr><td>EXPORT</td><td>Proof shared externally</td></tr>
              <tr><td>TOMBSTONE</td><td>Legitimate deletion record</td></tr>
            </tbody>
          </table>
        </section>

        <section anchor="event-structure">
          <name>Event Structure</name>
          <t>The following fields are REQUIRED for all events:</t>
          <table>
            <thead>
              <tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr>
            </thead>
            <tbody>
              <tr><td>EventID</td><td>string</td><td>REQUIRED</td><td>Unique identifier (UUID per <xref target="RFC9562"/>)</td></tr>
              <tr><td>ChainID</td><td>string</td><td>REQUIRED</td><td>Identifier linking events in a sequence</td></tr>
              <tr><td>PrevHash</td><td>string</td><td>REQUIRED</td><td>Hash of previous event in chain</td></tr>
              <tr><td>Timestamp</td><td>string</td><td>REQUIRED</td><td>ISO 8601 timestamp with millisecond precision</td></tr>
              <tr><td>EventType</td><td>string</td><td>REQUIRED</td><td>One of: INGEST, SEAL, EXPORT, TOMBSTONE</td></tr>
              <tr><td>HashAlgo</td><td>string</td><td>REQUIRED</td><td>Always "SHA256"</td></tr>
              <tr><td>SignAlgo</td><td>string</td><td>REQUIRED</td><td>"ES256" or "Ed25519"</td></tr>
              <tr><td>EventHash</td><td>string</td><td>REQUIRED</td><td>SHA-256 hash of canonicalized event</td></tr>
              <tr><td>Signature</td><td>string</td><td>REQUIRED</td><td>Raw Base64-encoded signature (no prefix)</td></tr>
              <tr><td>SignerInfo</td><td>object</td><td>OPTIONAL</td><td>Self-attested identity claim</td></tr>
              <tr><td>DeviceInfo</td><td>object</td><td>OPTIONAL</td><td>Device metadata</td></tr>
              <tr><td>CaptureContext</td><td>object</td><td>OPTIONAL</td><td>Environmental capture metadata</td></tr>
            </tbody>
          </table>
        </section>

        <section anchor="encoding-requirements">
          <name>Encoding Requirements</name>
          <t>All Base64-encoded fields (Signature, TSA.Token, public_key) MUST conform to:</t>
          <ul>
            <li><xref target="RFC4648"/> Section 4 (standard base64 alphabet, NOT base64url)</li>
            <li>No whitespace characters (no line breaks, spaces, or tabs)</li>
            <li>Padding characters (<tt>=</tt>) MUST be included when required</li>
          </ul>
          <t>PROHIBITED:</t>
          <ul>
            <li><tt>base64:MEUCIQDx...</tt> - Prefixes not allowed</li>
            <li><tt>data:application/octet-stream;base64,...</tt> - Data URIs not allowed</li>
            <li>Base64url alphabet (<tt>-</tt> and <tt>_</tt> instead of <tt>+</tt> and <tt>/</tt>)</li>
            <li>Line wrapping or embedded whitespace</li>
          </ul>
        </section>

        <section anchor="ingest-event">
          <name>INGEST Event</name>
          <t>INGEST events MUST include:</t>
          <table>
            <thead>
              <tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr>
            </thead>
            <tbody>
              <tr><td>Asset.AssetHash</td><td>string</td><td>REQUIRED</td><td>SHA-256 hash of media bytes</td></tr>
              <tr><td>Asset.AssetType</td><td>string</td><td>REQUIRED</td><td>IMAGE or VIDEO</td></tr>
              <tr><td>Asset.MimeType</td><td>string</td><td>REQUIRED</td><td>MIME type of asset</td></tr>
              <tr><td>Asset.AssetID</td><td>string</td><td>OPTIONAL</td><td>Unique asset identifier</td></tr>
              <tr><td>Asset.AssetName</td><td>string</td><td>OPTIONAL</td><td>Original filename</td></tr>
              <tr><td>Asset.AssetSize</td><td>integer</td><td>OPTIONAL</td><td>File size in bytes</td></tr>
            </tbody>
          </table>
        </section>

        <section anchor="seal-event">
          <name>SEAL Event</name>
          <t>SEAL events finalize a collection and commit the Completeness Invariant.
          A SEAL event MUST include:</t>
          <table>
            <thead>
              <tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr>
            </thead>
            <tbody>
              <tr><td>CollectionID</td><td>string</td><td>REQUIRED</td><td>Identifier for the sealed collection</td></tr>
              <tr><td>EventCount</td><td>integer</td><td>REQUIRED</td><td>Number of events in collection (excluding SEAL)</td></tr>
              <tr><td>CompletenessInvariant</td><td>object</td><td>REQUIRED</td><td>Completeness verification data</td></tr>
              <tr><td>MerkleRoot</td><td>string</td><td>REQUIRED</td><td>Root hash of events in collection</td></tr>
            </tbody>
          </table>

          <section anchor="completeness-invariant-object">
            <name>CompletenessInvariant Object</name>
            <table>
              <thead>
                <tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr>
              </thead>
              <tbody>
                <tr><td>ExpectedCount</td><td>integer</td><td>REQUIRED</td><td>Number of events that MUST be present</td></tr>
                <tr><td>HashSum</td><td>string</td><td>REQUIRED</td><td>XOR of all EventHash values (sha256: format)</td></tr>
                <tr><td>FirstTimestamp</td><td>string</td><td>REQUIRED</td><td>ISO 8601 timestamp of first event</td></tr>
                <tr><td>LastTimestamp</td><td>string</td><td>REQUIRED</td><td>ISO 8601 timestamp of last event</td></tr>
              </tbody>
            </table>
            <t>The HashSum is computed as:</t>
            <artwork><![CDATA[
HashSum = EventHash[0] XOR EventHash[1] XOR ... XOR EventHash[n-1]
]]></artwork>
            <t>Where XOR operates on the 32-byte binary values of each EventHash.</t>
          </section>

          <section anchor="seal-anchoring-pattern">
            <name>SEAL Anchoring Pattern</name>
            <t>The recommended anchoring pattern for SEAL events:</t>
            <ol>
              <li>Compute the Merkle tree over all INGEST events in the collection</li>
              <li>Create the SEAL event with MerkleRoot referencing this tree</li>
              <li>Include the SEAL event's EventHash as an additional leaf in a NEW Merkle tree</li>
              <li>Anchor this new tree (containing the SEAL event) to a TSA</li>
            </ol>
            <t>This pattern ensures the Completeness Invariant itself is bound to an
            external timestamp. The SEAL event's EventHash covers all CI fields, so
            the TSA anchor proves the CI existed at GenTime.</t>
            <t><em>Alternative Pattern (NOT RECOMMENDED)</em>: Including the
            SEAL event in the same Merkle tree it references creates a circular
            dependency and is prohibited.</t>
          </section>
        </section>

        <section anchor="tombstone-event">
          <name>TOMBSTONE Event</name>
          <t>TOMBSTONE events MUST additionally include:</t>
          <table>
            <thead>
              <tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr>
            </thead>
            <tbody>
              <tr><td>DeletedEventId</td><td>string</td><td>REQUIRED</td><td>EventID being invalidated</td></tr>
              <tr><td>Reason</td><td>string</td><td>REQUIRED</td><td>Deletion reason code</td></tr>
              <tr><td>DeletedAt</td><td>string</td><td>REQUIRED</td><td>ISO 8601 deletion timestamp</td></tr>
            </tbody>
          </table>
        </section>
      </section>

      <section anchor="signer-info">
        <name>SignerInfo</name>
        <t>SignerInfo provides self-attested identity claims. It is OPTIONAL
        and MUST NOT be interpreted as verified identity.</t>
        <table>
          <thead>
            <tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr>
          </thead>
          <tbody>
            <tr><td>Name</td><td>string</td><td>REQUIRED</td><td>Self-attested display name</td></tr>
            <tr><td>Identifier</td><td>string</td><td>OPTIONAL</td><td>Self-attested identifier (email, URL)</td></tr>
            <tr><td>AttestedAt</td><td>string</td><td>REQUIRED</td><td>ISO 8601 timestamp of attestation</td></tr>
          </tbody>
        </table>

        <section anchor="signer-info-semantics">
          <name>Verification Semantics</name>
          <t>When a third party verifies a CPP proof with SignerInfo:</t>
          <table>
            <thead>
              <tr><th>What CPP Proves</th><th>What CPP Does NOT Prove</th></tr>
            </thead>
            <tbody>
              <tr>
                <td>Someone claimed this name at capture time</td>
                <td>The name is real or legal</td>
              </tr>
              <tr>
                <td>The claim is tamper-evident (signed)</td>
                <td>Identity verification occurred</td>
              </tr>
              <tr>
                <td>The claim existed before TSA timestamp</td>
                <td>The person is who they claim</td>
              </tr>
            </tbody>
          </table>
          <t>Implementations displaying SignerInfo MUST use terminology such
          as "Self-Attested Name" and MUST NOT use "Verified Identity" or
          similar phrases that imply independent verification.</t>
        </section>
      </section>

      <section anchor="device-info">
        <name>DeviceInfo</name>
        <t>DeviceInfo provides metadata about the capture device. It is
        OPTIONAL but RECOMMENDED for INGEST events.</t>
        <table>
          <thead>
            <tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr>
          </thead>
          <tbody>
            <tr><td>Manufacturer</td><td>string</td><td>OPTIONAL</td><td>Device manufacturer</td></tr>
            <tr><td>Model</td><td>string</td><td>OPTIONAL</td><td>Device model identifier</td></tr>
            <tr><td>DeviceClass</td><td>string</td><td>OPTIONAL</td><td>One of: SMARTPHONE, TABLET, EMBEDDED, PHYSICAL_CAMERA, DRONE, INDUSTRIAL</td></tr>
            <tr><td>OSName</td><td>string</td><td>OPTIONAL</td><td>Operating system name</td></tr>
            <tr><td>OSVersion</td><td>string</td><td>OPTIONAL</td><td>Operating system version</td></tr>
            <tr><td>AppVersion</td><td>string</td><td>OPTIONAL</td><td>Capture application version</td></tr>
          </tbody>
        </table>
      </section>

      <section anchor="capture-context">
        <name>CaptureContext</name>
        <t>CaptureContext provides environmental metadata at capture time.
        It is OPTIONAL for INGEST events.</t>
        <table>
          <thead>
            <tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr>
          </thead>
          <tbody>
            <tr><td>SensorData</td><td>object</td><td>OPTIONAL</td><td>Sensor readings (GPS, accelerometer)</td></tr>
            <tr><td>HumanAttestation</td><td>object</td><td>OPTIONAL</td><td>Biometric verification record (boolean result only)</td></tr>
            <tr><td>DepthAnalysis</td><td>object</td><td>OPTIONAL</td><td>Screen detection results (see <xref target="depth-analysis"/>)</td></tr>
          </tbody>
        </table>
      </section>

      <section anchor="hash-chain">
        <name>Hash Chain</name>
        <t>Events form a hash chain through the PrevHash field:</t>
        <artwork><![CDATA[
Event 1: PrevHash = sha256:0000...0000 (genesis - 64 zeros)
Event 2: PrevHash = EventHash(Event 1)
Event 3: PrevHash = EventHash(Event 2)
]]></artwork>
        <t>Verification of chain integrity:</t>
        <ol>
          <li>For each event after the first, the verifier MUST compute EventHash
          of the previous event.</li>
          <li>The computed hash MUST match the current event's PrevHash field.</li>
          <li>If any mismatch is detected, verification MUST fail with status
          CHAIN_INTEGRITY_VIOLATION.</li>
        </ol>
      </section>

      <section anchor="merkle-tree-structure">
        <name>Merkle Tree Structure</name>
        <t>CPP defines its own binary Merkle tree construction optimized for media
        provenance. This construction uses domain separation prefixes to prevent
        attacks where leaf values could be confused with internal node values.</t>
        <t><em>Important</em>: CPP Merkle trees are NOT compatible with
        RFC 6962 (Certificate Transparency). Implementations MUST use the exact
        algorithms specified in this section.</t>

        <section anchor="domain-separation">
          <name>Domain Separation</name>
          <t>CPP MUST use single-byte prefixes to separate leaf and internal
          node domains. This construction prevents second preimage attacks
          where an attacker substitutes a leaf value for an internal node
          or vice versa.</t>
          <t>Implementations MUST apply these prefixes. The legacy
          (non-prefixed) construction where LeafHash = SHA256(EventHash_bytes)
          and Node = SHA256(Left || Right) is DEPRECATED and MUST NOT be
          used for generating new proofs.</t>
          <table>
            <thead>
              <tr><th>Domain</th><th>Prefix Byte</th><th>Description</th></tr>
            </thead>
            <tbody>
              <tr><td>Leaf</td><td>0x00</td><td>Applied to EventHash bytes</td></tr>
              <tr><td>Internal</td><td>0x01</td><td>Applied to concatenated child hashes</td></tr>
            </tbody>
          </table>
        </section>

        <section anchor="leaf-nodes">
          <name>Leaf Nodes</name>
          <artwork><![CDATA[
LeafHash = SHA256(0x00 || EventHash_bytes)
]]></artwork>
          <t>Where:</t>
          <ul>
            <li>0x00 is a single byte with value zero</li>
            <li>EventHash_bytes is the 32-byte binary representation of EventHash
            (after stripping the <tt>sha256:</tt> prefix)</li>
            <li>|| denotes byte concatenation</li>
          </ul>
        </section>

        <section anchor="internal-nodes">
          <name>Internal Nodes</name>
          <artwork><![CDATA[
InternalHash = SHA256(0x01 || Left_bytes || Right_bytes)
]]></artwork>
          <t>Where:</t>
          <ul>
            <li>0x01 is a single byte with value one</li>
            <li>Left_bytes is the 32-byte hash of the left child</li>
            <li>Right_bytes is the 32-byte hash of the right child</li>
            <li>|| denotes byte concatenation</li>
          </ul>
        </section>

        <section anchor="tree-construction">
          <name>Tree Construction</name>
          <t><em>Step 1: Compute Leaf Hashes</em></t>
          <t>For each event, compute LeafHash = SHA256(0x00 || EventHash_bytes).</t>

          <t><em>Step 2: Determine Padding</em></t>
          <t>PaddedSize is the smallest power of 2 >= TreeSize:</t>
          <artwork><![CDATA[
function computePaddedSize(treeSize):
    if treeSize == 0:
        return 0  // Invalid - TreeSize MUST be >= 1
    paddedSize = 1
    while paddedSize < treeSize:
        paddedSize = paddedSize * 2
    return paddedSize
]]></artwork>

          <t><em>Step 3: Pad Leaf Array</em></t>
          <t>If TreeSize &lt; PaddedSize, duplicate the last leaf hash until the
          array length equals PaddedSize.</t>

          <t><em>Step 4: Build Tree</em></t>
          <artwork><![CDATA[
function buildTree(paddedLeaves):
    levels = [paddedLeaves]
    current = paddedLeaves

    while current.length > 1:
        nextLevel = []
        for i in range(0, current.length, 2):
            left = current[i]
            right = current[i + 1]
            parent = SHA256(0x01 || left || right)
            nextLevel.append(parent)
        levels.append(nextLevel)
        current = nextLevel

    return levels  // levels[0] = leaves, levels[-1] = [root]
]]></artwork>
        </section>

        <section anchor="merkle-proof-structure">
          <name>Merkle Proof Structure</name>
          <table>
            <thead>
              <tr><th>Field</th><th>Type</th><th>Description</th></tr>
            </thead>
            <tbody>
              <tr><td>TreeSize</td><td>integer</td><td>Original leaf count (before padding), unsigned, MUST be >= 1</td></tr>
              <tr><td>LeafHashMethod</td><td>string</td><td>MUST be exactly <tt>SHA256(0x00||EventHash)</tt> (18 ASCII characters)</td></tr>
              <tr><td>LeafHash</td><td>string</td><td>Computed LeafHash for this event with sha256: prefix</td></tr>
              <tr><td>LeafIndex</td><td>integer</td><td>0-based position in tree, range [0, TreeSize-1]</td></tr>
              <tr><td>Proof</td><td>array</td><td>Sibling hashes from bottom to top, each with sha256: prefix</td></tr>
              <tr><td>Root</td><td>string</td><td>MerkleRoot with sha256: prefix</td></tr>
            </tbody>
          </table>
        </section>
      </section>

      <section anchor="anchor-structure">
        <name>Anchor Structure</name>
        <table>
          <thead>
            <tr><th>Field</th><th>Type</th><th>Description</th></tr>
          </thead>
          <tbody>
            <tr><td>AnchorID</td><td>string</td><td>Unique anchor identifier</td></tr>
            <tr><td>AnchorType</td><td>string</td><td>MUST be "RFC3161"</td></tr>
            <tr><td>AnchorDigest</td><td>string</td><td>MerkleRoot without prefix, 64 lowercase hex chars</td></tr>
            <tr><td>AnchorDigestAlgorithm</td><td>string</td><td>MUST be "sha-256"</td></tr>
            <tr><td>Merkle</td><td>object</td><td>Merkle proof structure</td></tr>
            <tr><td>TSA</td><td>object</td><td>TSA response data</td></tr>
          </tbody>
        </table>
        <t>The TSA object MUST include:</t>
        <table>
          <thead>
            <tr><th>Field</th><th>Type</th><th>Description</th></tr>
          </thead>
          <tbody>
            <tr><td>Token</td><td>string</td><td>Complete DER-encoded TimeStampToken, Base64</td></tr>
            <tr><td>MessageImprint</td><td>object</td><td>Extracted messageImprint from TSTInfo</td></tr>
            <tr><td>GenTime</td><td>string</td><td>Extracted GenTime from TSTInfo, ISO 8601</td></tr>
            <tr><td>Service</td><td>string</td><td>TSA service URL (informational)</td></tr>
          </tbody>
        </table>
        <t>The MessageImprint object MUST include:</t>
        <table>
          <thead>
            <tr><th>Field</th><th>Type</th><th>Description</th></tr>
          </thead>
          <tbody>
            <tr><td>HashAlgorithm</td><td>string</td><td>MUST be "sha-256"</td></tr>
            <tr><td>HashedMessage</td><td>string</td><td>64 lowercase hex chars, MUST equal AnchorDigest</td></tr>
          </tbody>
        </table>
      </section>

      <section anchor="chain-context">
        <name>Chain Context</name>
        <t>Chain Context is OPTIONAL metadata embedded in Evidence Packs to
        enable partial submission detection. It describes the event's position
        within its chain without requiring the full chain for initial assessment.</t>
        <table>
          <thead>
            <tr><th>Field</th><th>Type</th><th>Description</th></tr>
          </thead>
          <tbody>
            <tr><td>ChainID</td><td>string</td><td>Unique chain identifier</td></tr>
            <tr><td>TotalEvents</td><td>integer</td><td>Total events in chain (including Tombstones)</td></tr>
            <tr><td>ActiveEvents</td><td>integer</td><td>Non-invalidated events</td></tr>
            <tr><td>TombstoneCount</td><td>integer</td><td>Number of deleted events</td></tr>
            <tr><td>EventPosition</td><td>integer</td><td>Position of this event (1-indexed)</td></tr>
            <tr><td>CompletenessInvariant</td><td>object</td><td>XOR-based completeness data for the chain</td></tr>
            <tr><td>GeneratedAt</td><td>string</td><td>ISO 8601 timestamp of context generation</td></tr>
          </tbody>
        </table>
        <t>Chain Context is informational when embedded in a single proof.
        Full completeness verification requires the complete chain (Forensic Export).
        Verifiers SHOULD use Chain Context to alert users when:</t>
        <ul>
          <li>TombstoneCount > 0 (events have been deleted)</li>
          <li>ActiveEvents &lt; TotalEvents (some events invalidated)</li>
          <li>Only a subset of the chain is presented</li>
        </ul>
      </section>
    </section>

    <section anchor="canonicalization-and-hashing">
      <name>Canonicalization and Hashing</name>

      <section anchor="json-canonicalization">
        <name>JSON Canonicalization</name>
        <t>Events MUST be canonicalized using <xref target="RFC8785"/> (JSON
        Canonicalization Scheme) before hashing.</t>
        <t>The following fields MUST be excluded from canonicalization:</t>
        <ul>
          <li>EventHash</li>
          <li>Signature</li>
        </ul>
        <t>All other fields, including SignerInfo if present, MUST be included.
        Field names in the canonical event object use PascalCase
        (e.g., EventID, ChainID, PrevHash).</t>
      </section>

      <section anchor="eventhash-computation">
        <name>EventHash Computation</name>
        <artwork><![CDATA[
function computeEventHash(event):
    eventCopy = copy(event)
    delete eventCopy.EventHash
    delete eventCopy.Signature
    canonical = JCS_canonicalize(eventCopy)  // RFC 8785
    hashBytes = SHA256(canonical)
    return "sha256:" + lowercase_hex(hashBytes)
]]></artwork>
        <t>The resulting EventHash is a 71-character string: the prefix "sha256:"
        followed by 64 lowercase hexadecimal characters.</t>
        <t>Note: SignerInfo, DeviceInfo, and CaptureContext are included in the
        EventHash computation when present. This ensures these fields are
        tamper-evident and bound to the TSA timestamp.</t>
      </section>

      <section anchor="leafhash-computation">
        <name>LeafHash Computation</name>
        <artwork><![CDATA[
function computeLeafHash(eventHash):
    hexStr = eventHash.substring(7)  // Remove "sha256:" prefix
    eventHashBytes = hexDecode(hexStr)  // 32 bytes
    prefixedData = [0x00] + eventHashBytes  // 33 bytes
    leafHashBytes = SHA256(prefixedData)
    return "sha256:" + lowercase_hex(leafHashBytes)
]]></artwork>
      </section>

      <section anchor="internal-node-hash-computation">
        <name>Internal Node Hash Computation</name>
        <artwork><![CDATA[
function computeInternalHash(left, right):
    leftBytes = hexDecode(left.substring(7))
    rightBytes = hexDecode(right.substring(7))
    prefixedData = [0x01] + leftBytes + rightBytes  // 65 bytes
    hashBytes = SHA256(prefixedData)
    return "sha256:" + lowercase_hex(hashBytes)
]]></artwork>
      </section>

      <section anchor="anchordigest-computation">
        <name>AnchorDigest Computation</name>
        <t>AnchorDigest is the MerkleRoot value WITHOUT the <tt>sha256:</tt> prefix,
        represented as 64 lowercase hexadecimal characters.</t>
        <artwork><![CDATA[
function computeAnchorDigest(merkleRoot):
    return lowercase(merkleRoot.substring(7))
]]></artwork>
        <t>PROHIBITED:</t>
        <ul>
          <li>SHA256(merkleRoot) - This would create double hashing</li>
          <li>SHA256(stringEncode(merkleRoot)) - This would hash the string representation</li>
          <li>Any transformation other than prefix removal</li>
          <li>Mixed case output - MUST be lowercase</li>
        </ul>
      </section>
    </section>

    <section anchor="anchoring-protocol">
      <name>Anchoring Protocol</name>

      <section anchor="tsa-request-construction">
        <name>TSA Request Construction</name>
        <t>The messageImprint in TimeStampReq <xref target="RFC3161"/> MUST contain:</t>
        <ul>
          <li>hashAlgorithm: SHA-256 (OID 2.16.840.1.101.3.4.2.1)</li>
          <li>hashedMessage: AnchorDigest as 32-byte OCTET STRING</li>
        </ul>
        <artwork><![CDATA[
TimeStampReq ::= SEQUENCE {
   version         INTEGER { v1(1) },
   messageImprint  MessageImprint,
   reqPolicy       OBJECT IDENTIFIER OPTIONAL,
   nonce           INTEGER OPTIONAL,
   certReq         BOOLEAN DEFAULT FALSE,
   extensions      [0] IMPLICIT Extensions OPTIONAL
}

MessageImprint ::= SEQUENCE {
   hashAlgorithm   AlgorithmIdentifier,  -- SHA-256
   hashedMessage   OCTET STRING          -- AnchorDigest (32 bytes)
}
]]></artwork>

        <section anchor="certreq-recommendation">
          <name>certReq Recommendation</name>
          <t>Producers SHOULD set certReq to TRUE to request the TSA's signing
          certificate be included in the response. This enables:</t>
          <ul>
            <li>Offline verification without fetching certificates separately</li>
            <li>Long-term verification even if TSA infrastructure changes</li>
            <li>Self-contained Evidence Packs</li>
          </ul>
          <t>If certReq is FALSE and the TSA certificate is not included in the
          response, verifiers MUST attempt to obtain the certificate through other
          means (e.g., AIA extension, local cache) or return VALID_WARNING.</t>
        </section>
      </section>

      <section anchor="tsa-response-processing">
        <name>TSA Response Processing</name>
        <t>Upon receiving TimeStampResp, the producer:</t>
        <ol>
          <li>MUST verify the response status is granted (0) or grantedWithMods (1)</li>
          <li>MUST extract the TimeStampToken from the response</li>
          <li>MUST store the complete DER-encoded TimeStampToken</li>
          <li>MUST extract and store the messageImprint from TSTInfo</li>
          <li>MUST extract and store GenTime from TSTInfo</li>
        </ol>
      </section>

      <section anchor="single-leaf-tree-rules">
        <name>Single-Leaf Tree Rules</name>
        <t>When TreeSize equals 1, the following invariants MUST hold:</t>
        <ul>
          <li>LeafIndex MUST equal 0</li>
          <li>Proof MUST be an empty array</li>
          <li>Root MUST equal LeafHash</li>
          <li>LeafHash MUST equal SHA256(0x00 || EventHash_bytes)</li>
        </ul>
        <t>If any of these conditions fail, verification MUST return INVALID.</t>
      </section>

      <section anchor="multi-leaf-tree-rules">
        <name>Multi-Leaf Tree Rules</name>
        <t>For TreeSize greater than 1:</t>
        <ul>
          <li>LeafIndex MUST be in range [0, TreeSize-1]</li>
          <li>PaddedSize = smallest power of 2 >= TreeSize</li>
          <li>Proof length MUST NOT exceed log2(PaddedSize)</li>
          <li>Sibling hashes are ordered from bottom (leaf level) to top (root level)</li>
          <li>Index parity determines pairing order: even=left, odd=right</li>
          <li>All internal nodes use SHA256(0x01 || left || right)</li>
        </ul>
      </section>
    </section>

    <section anchor="verification-procedures">
      <name>Verification Procedures</name>

      <section anchor="verification-result-codes">
        <name>Verification Result Codes</name>
        <table>
          <thead>
            <tr><th>Code</th><th>Meaning</th></tr>
          </thead>
          <tbody>
            <tr><td>VALID</td><td>All checks passed, including TSA signature verification</td></tr>
            <tr><td>VALID_WARNING</td><td>Cryptographic checks passed, but TSA certificate chain could not be fully validated</td></tr>
            <tr><td>INVALID</td><td>Cryptographic verification failed</td></tr>
            <tr><td>CHAIN_INTEGRITY_VIOLATION</td><td>Hash chain is broken</td></tr>
            <tr><td>COMPLETENESS_VIOLATION</td><td>Completeness Invariant mismatch</td></tr>
          </tbody>
        </table>
      </section>

      <section anchor="event-verification">
        <name>Event Verification</name>
        <artwork><![CDATA[
function verifyEvent(event, publicKey):
    // Step 1: Recompute EventHash
    computedHash = computeEventHash(event)
    if computedHash != event.EventHash:
        return INVALID("EventHash mismatch")

    // Step 2: Verify signature
    hashBytes = hexDecode(event.EventHash.substring(7))
    sigBytes = base64Decode(event.Signature)
    if not verifySignature(publicKey, hashBytes, sigBytes):
        return INVALID("Signature verification failed")

    return VALID
]]></artwork>
      </section>

      <section anchor="merkle-proof-verification">
        <name>Merkle Proof Verification</name>
        <artwork><![CDATA[
function verifyMerkleProof(eventHash, leafIndex, proof,
                           expectedRoot, treeSize):
    // Step 1: Validate inputs
    if treeSize < 1:
        return INVALID("TreeSize must be >= 1")
    if leafIndex < 0 or leafIndex >= treeSize:
        return INVALID("LeafIndex out of range")

    paddedSize = computePaddedSize(treeSize)
    maxProofLength = log2(paddedSize)
    if proof.length > maxProofLength:
        return INVALID("Proof too long")

    // Step 2: Compute leaf hash with domain separation
    currentHash = computeLeafHash(eventHash)

    // Step 3: Handle single-leaf case
    if treeSize == 1:
        if leafIndex != 0:
            return INVALID("LeafIndex must be 0 for single-leaf")
        if proof.length != 0:
            return INVALID("Proof must be empty for single-leaf")
        if lowercase(currentHash) != lowercase(expectedRoot):
            return INVALID("Root != LeafHash for single-leaf")
        return VALID

    // Step 4: Traverse proof from bottom to top
    index = leafIndex
    for siblingHash in proof:
        if index % 2 == 0:
            currentHash = computeInternalHash(currentHash, siblingHash)
        else:
            currentHash = computeInternalHash(siblingHash, currentHash)
        index = floor(index / 2)

    // Step 5: Compare with expected root
    if lowercase(currentHash) != lowercase(expectedRoot):
        return INVALID("Computed root != expected root")

    return VALID
]]></artwork>
      </section>

      <section anchor="tsa-verification">
        <name>TSA Verification</name>
        <t>TSA verification ensures the timestamp token was legitimately
        issued by a Time-Stamp Authority and binds the correct digest.
        Implementations MUST also perform the format and binding
        checks specified in
        <xref target="anchordigest-verification-requirements"/>
        prior to or as part of this procedure.</t>
        <artwork><![CDATA[
function verifyTSAAnchor(eventHash, anchor):
    // Step 1: Verify Merkle structure
    merkle = anchor.Merkle
    result = verifyMerkleProof(
        eventHash, merkle.LeafIndex, merkle.Proof,
        merkle.Root, merkle.TreeSize)
    if result != VALID:
        return result

    // Step 2: Verify LeafHashMethod
    if merkle.LeafHashMethod != "SHA256(0x00||EventHash)":
        return INVALID("Unsupported LeafHashMethod")

    // Step 3: Verify AnchorDigest == MerkleRoot
    expectedDigest = lowercase(merkle.Root.substring(7))
    if lowercase(anchor.AnchorDigest) != expectedDigest:
        return INVALID("AnchorDigest != MerkleRoot")

    // Step 4: Parse TSA Token (RFC 5652 ContentInfo)
    tsaToken = base64Decode(anchor.TSA.Token)
    contentInfo = parseContentInfo(tsaToken)
    signedData = parseSignedData(contentInfo.content)
    tstInfo = parseTSTInfo(signedData.encapContentInfo.eContent)

    // Step 5: Verify hash algorithm is SHA-256
    if tstInfo.messageImprint.hashAlgorithm != SHA256_OID:
        return INVALID("Unsupported TSA hash algorithm")

    // Step 6: Verify messageImprint == AnchorDigest
    tstImprint = lowercase_hex(tstInfo.messageImprint.hashedMessage)
    if tstImprint != lowercase(anchor.AnchorDigest):
        return INVALID("TSA messageImprint != AnchorDigest")

    // Step 7: Verify CMS signature over TSTInfo
    signerInfo = signedData.signerInfos[0]
    signatureValid = verifyCMSSignature(
        signedData.encapContentInfo.eContent,
        signerInfo.signature,
        signerInfo.signatureAlgorithm,
        extractSignerCert(signedData.certificates, signerInfo.sid))
    if not signatureValid:
        return INVALID("TSA signature verification failed")

    // Step 8: Verify certificate chain (SHOULD)
    certValid = verifyCertificateChain(
        signedData.certificates, signerInfo.sid, trustAnchors)

    if certValid:
        return VALID(genTime = tstInfo.genTime)
    else:
        return VALID_WARNING(genTime = tstInfo.genTime,
            warning = "TSA certificate chain could not be verified")
]]></artwork>

        <section anchor="cms-signature-verification-requirements">
          <name>CMS Signature Verification Requirements</name>
          <t>Per <xref target="RFC5652"/>, verifiers MUST:</t>
          <ol>
            <li>Parse the TimeStampToken as a ContentInfo structure</li>
            <li>Extract the SignedData from the content field</li>
            <li>Locate the SignerInfo corresponding to the TSA</li>
            <li>Verify the signature over the encapsulated TSTInfo</li>
            <li>Verify the signer's certificate was valid at signing time</li>
          </ol>
          <t>Verifiers SHOULD:</t>
          <ol>
            <li>Build and validate the certificate chain to a trust anchor</li>
            <li>Verify the TSA certificate contains the id-kp-timeStamping extended key usage</li>
            <li>Check for certificate revocation</li>
          </ol>
        </section>
      </section>

      <section anchor="anchordigest-verification-requirements">
        <name>AnchorDigest Verification Requirements</name>
        <t>This section consolidates the mandatory checks for AnchorDigest,
        MerkleRoot, and TSA messageImprint consistency. These checks are
        REQUIRED and failure of any single check MUST result in INVALID.</t>

        <t><em>Format Requirements</em>:</t>
        <ol>
          <li>MerkleRoot MUST be a 71-character string consisting of the
          prefix <tt>sha256:</tt> followed by exactly 64 lowercase hexadecimal
          characters (regex: <tt>^sha256:[0-9a-f]{64}$</tt>). Verifiers
          MUST reject MerkleRoot values containing uppercase characters,
          missing prefix, or incorrect length.</li>
          <li>AnchorDigest MUST be exactly 64 lowercase hexadecimal characters
          (regex: <tt>^[0-9a-f]{64}$</tt>) representing the 32-byte binary
          digest. No prefix, no whitespace, no uppercase.</li>
          <li>AnchorDigest MUST exactly equal MerkleRoot with the
          <tt>sha256:</tt> prefix stripped. No rehashing, no encoding
          transformation — purely prefix removal.</li>
        </ol>

        <t><em>TSA Binding Requirements</em>:</t>
        <ol>
          <li>Verifiers MUST extract messageImprint from TSTInfo
          within the TimeStampToken.</li>
          <li>Verifiers MUST confirm that
          messageImprint.hashAlgorithm equals the SHA-256 OID
          (2.16.840.1.101.3.4.2.1). If the OID differs, verification
          MUST return INVALID.</li>
          <li>Verifiers MUST confirm that the lowercase hexadecimal
          encoding of messageImprint.hashedMessage (32 bytes) is
          identical to AnchorDigest. This is a byte-for-byte comparison
          after hex encoding; case-insensitive comparison is NOT
          sufficient because AnchorDigest is already required to be
          lowercase.</li>
          <li>If an implementation transmits AnchorDigest to the TSA as a
          UTF-8 string rather than a 32-byte binary OCTET STRING, the TSA
          will timestamp a 64-byte value (the hex encoding) rather than the
          correct 32-byte digest. This is a known implementation error.
          Verifiers MUST detect this by comparing the messageImprint length:
          if hashedMessage is not exactly 32 bytes, verification MUST return
          INVALID.</li>
        </ol>

        <t><em>PROHIBITED Patterns</em> (known implementation errors):</t>
        <ul>
          <li>Submitting the hex string of AnchorDigest (64 ASCII bytes)
          instead of the decoded binary (32 bytes) to the TSA — results
          in messageImprint mismatch</li>
          <li>Computing SHA256(MerkleRoot) as the TSA input — results in
          double-hashing</li>
          <li>Computing SHA256(AnchorDigest_as_string) — results in
          hashing the hex encoding rather than the binary value</li>
          <li>Using the MerkleRoot string (with <tt>sha256:</tt> prefix)
          directly as the TSA hashedMessage — results in a 71-byte input
          instead of 32 bytes</li>
        </ul>

        <artwork><![CDATA[
function verifyAnchorDigestBinding(anchor):
    // Format checks
    if not matches(anchor.Merkle.Root, /^sha256:[0-9a-f]{64}$/):
        return INVALID("MerkleRoot format violation")

    if not matches(anchor.AnchorDigest, /^[0-9a-f]{64}$/):
        return INVALID("AnchorDigest format violation")

    // Prefix-strip equivalence
    expectedDigest = anchor.Merkle.Root.substring(7)
    if anchor.AnchorDigest != expectedDigest:
        return INVALID("AnchorDigest != MerkleRoot hex part")

    // TSA binding
    tstInfo = extractTSTInfo(anchor.TSA.Token)
    if tstInfo.messageImprint.hashAlgorithm != SHA256_OID:
        return INVALID("TSA hash algorithm is not SHA-256")

    imprintBytes = tstInfo.messageImprint.hashedMessage
    if length(imprintBytes) != 32:
        return INVALID("TSA hashedMessage is not 32 bytes"
                       + " (string-as-binary error?)")

    imprintHex = lowercase_hex(imprintBytes)
    if imprintHex != anchor.AnchorDigest:
        return INVALID("TSA messageImprint != AnchorDigest")

    return VALID
]]></artwork>
      </section>
      <section anchor="chain-integrity-verification">
        <name>Chain Integrity Verification</name>
        <artwork><![CDATA[
GENESIS_PREV_HASH = "sha256:00000000000000000000000000000000" +
                    "00000000000000000000000000000000"

function verifyChainIntegrity(events):
    if events.length == 0:
        return VALID

    if events[0].PrevHash != GENESIS_PREV_HASH:
        return CHAIN_INTEGRITY_VIOLATION("Invalid genesis PrevHash")

    for i in range(1, events.length):
        expectedPrevHash = events[i-1].EventHash
        if events[i].PrevHash != expectedPrevHash:
            return CHAIN_INTEGRITY_VIOLATION(
                "Break at event " + i)

    return VALID
]]></artwork>
      </section>

      <section anchor="completeness-invariant-verification">
        <name>Completeness Invariant Verification</name>
        <artwork><![CDATA[
function verifyCompleteness(events, sealEvent):
    ci = sealEvent.CompletenessInvariant

    // Step 1: Verify count
    if events.length != ci.ExpectedCount:
        return COMPLETENESS_VIOLATION("Count mismatch")

    // Step 2: Compute XOR hash sum
    computed = bytes(32)
    for event in events:
        eventHashBytes = hexDecode(event.EventHash.substring(7))
        computed = XOR(computed, eventHashBytes)

    // Step 3: Compare with sealed value
    expectedHashSum = hexDecode(ci.HashSum.substring(7))
    if computed != expectedHashSum:
        return COMPLETENESS_VIOLATION("Hash sum mismatch")

    // Step 4: Verify timestamp bounds
    for event in events:
        if event.Timestamp < ci.FirstTimestamp:
            return COMPLETENESS_VIOLATION("Before collection start")
        if event.Timestamp > ci.LastTimestamp:
            return COMPLETENESS_VIOLATION("After collection end")

    return VALID
]]></artwork>

        <section anchor="attack-detection">
          <name>Attack Detection</name>
          <table>
            <thead>
              <tr><th>Attack</th><th>Detection</th></tr>
            </thead>
            <tbody>
              <tr><td>Delete event</td><td>Hash sum mismatch and/or count mismatch</td></tr>
              <tr><td>Add fake event</td><td>Count mismatch and/or hash sum mismatch</td></tr>
              <tr><td>Reorder events</td><td>Chain integrity violation (PrevHash mismatch)</td></tr>
              <tr><td>Modify event</td><td>EventHash mismatch in chain</td></tr>
            </tbody>
          </table>
        </section>
      </section>
    </section>

    <section anchor="depth-analysis">
      <name>Depth Analysis Extension</name>
      <t>The Depth Analysis Extension is OPTIONAL and provides screen detection
      capabilities to mitigate analog hole attacks (photographing screens
      displaying manipulated content).</t>

      <section anchor="depth-analysis-structure">
        <name>DepthAnalysis Structure</name>
        <t>When present in CaptureContext, DepthAnalysis MUST include:</t>
        <table>
          <thead>
            <tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr>
          </thead>
          <tbody>
            <tr><td>SensorType</td><td>string</td><td>REQUIRED</td><td>Type of depth sensor used</td></tr>
            <tr><td>FlatnessScore</td><td>number</td><td>REQUIRED</td><td>0.0 (natural scene) to 1.0 (flat screen)</td></tr>
            <tr><td>DepthVariance</td><td>number</td><td>REQUIRED</td><td>Statistical variance of depth values</td></tr>
            <tr><td>ScreenDetected</td><td>boolean</td><td>REQUIRED</td><td>Whether a screen was detected</td></tr>
            <tr><td>Confidence</td><td>number</td><td>REQUIRED</td><td>Detection confidence 0.0 to 1.0</td></tr>
            <tr><td>AnalysisVersion</td><td>string</td><td>REQUIRED</td><td>Version of analysis algorithm</td></tr>
          </tbody>
        </table>
      </section>

      <section anchor="sensor-types">
        <name>Sensor Types</name>
        <table>
          <thead>
            <tr><th>Value</th><th>Description</th></tr>
          </thead>
          <tbody>
            <tr><td>LIDAR</td><td>LiDAR time-of-flight sensor</td></tr>
            <tr><td>STRUCTURED_LIGHT</td><td>Structured light projection</td></tr>
            <tr><td>STEREO</td><td>Stereo camera pair</td></tr>
            <tr><td>TOF</td><td>Non-LiDAR time-of-flight</td></tr>
            <tr><td>RADAR</td><td>Millimeter-wave radar</td></tr>
            <tr><td>ULTRASONIC</td><td>Ultrasonic depth sensing</td></tr>
            <tr><td>MONOCULAR_ESTIMATED</td><td>AI-estimated depth from single camera</td></tr>
            <tr><td>MULTI_CAMERA</td><td>Multi-camera triangulation</td></tr>
            <tr><td>ACTIVE_IR</td><td>Active infrared projection</td></tr>
            <tr><td>HYBRID</td><td>Multiple sensor fusion</td></tr>
            <tr><td>UNKNOWN</td><td>Sensor type not determined</td></tr>
            <tr><td>NONE</td><td>No depth sensor available</td></tr>
          </tbody>
        </table>
      </section>

      <section anchor="depth-verification">
        <name>Depth Analysis Verification</name>
        <t>Depth analysis data is included in the EventHash computation,
        making it tamper-evident. Verifiers MAY use DepthAnalysis to assess
        capture environment but MUST NOT treat it as definitive proof of
        scene authenticity. Depth sensors can be spoofed by sophisticated
        adversaries.</t>
        <t>When ScreenDetected is true, implementations SHOULD display a
        warning to users but MUST NOT automatically reject the proof.</t>
      </section>
    </section>

    <section anchor="pre-publish">
      <name>Pre-Publish Verification Extension</name>
      <t>The Pre-Publish Verification Extension is OPTIONAL and enables
      verification of CPP provenance at the moment of social media sharing.
      It allows users to indicate their content has traceable origin without
      blocking the sharing flow or making truth claims.</t>

      <section anchor="pre-publish-design">
        <name>Design Principles</name>
        <ul>
          <li><em>Silent Failure</em>: If verification fails or times out,
          sharing MUST proceed without any user-visible error or delay.</li>
          <li><em>No Truth Claims</em>: The indicator communicates provenance
          availability, not content truthfulness.</li>
          <li><em>Performance Budget</em>: Verification MUST complete within
          200ms or short-circuit to silent passthrough.</li>
        </ul>
      </section>

      <section anchor="verification-result-types">
        <name>VerificationResult</name>
        <table>
          <thead>
            <tr><th>Status</th><th>Behavior</th></tr>
          </thead>
          <tbody>
            <tr><td>PROVENANCE_AVAILABLE</td><td>Show indicator, attach metadata</td></tr>
            <tr><td>PROVENANCE_PARTIAL</td><td>Silent passthrough (no indicator)</td></tr>
            <tr><td>PROVENANCE_UNAVAILABLE</td><td>Silent passthrough</td></tr>
            <tr><td>VERIFICATION_TIMEOUT</td><td>Silent passthrough</td></tr>
            <tr><td>VERIFICATION_ERROR</td><td>Silent passthrough</td></tr>
          </tbody>
        </table>
        <t>Only PROVENANCE_AVAILABLE results in visible indication. All other
        statuses MUST result in silent passthrough where the original content
        is shared without modification or delay.</t>
      </section>

      <section anchor="prohibited-terms">
        <name>Prohibited Terminology</name>
        <t>Implementations MUST NOT use the following terms in user-facing
        displays related to CPP provenance:</t>
        <ul>
          <li>"Verified" or "Verified Content"</li>
          <li>"Authentic" or "Authenticated"</li>
          <li>"True" or "Truthful"</li>
          <li>"Certified"</li>
          <li>"Guaranteed"</li>
          <li>"Real"</li>
          <li>"Trustworthy"</li>
        </ul>
        <t>Recommended terminology: "Provenance Available", "Capture Provenance
        Recorded", "Origin Traceable".</t>
      </section>
    </section>

    <section anchor="c2pa-interop">
      <name>C2PA Interoperability</name>
      <t>Implementations MAY support both CPP and C2PA <xref target="C2PA"/>
      manifests. This section defines field mappings for dual-standard
      implementations.</t>

      <section anchor="c2pa-field-mapping">
        <name>Field Mapping</name>
        <table>
          <thead>
            <tr><th>CPP Field</th><th>C2PA Equivalent</th><th>Notes</th></tr>
          </thead>
          <tbody>
            <tr><td>DeviceInfo.Manufacturer</td><td>claim_generator</td><td>Partial mapping</td></tr>
            <tr><td>DeviceInfo.Model</td><td>claim_generator</td><td>Partial mapping</td></tr>
            <tr><td>Timestamp</td><td>dc:created</td><td>ISO 8601 format</td></tr>
            <tr><td>SensorData.GPS</td><td>Exif:GPS*</td><td>Standard EXIF mapping</td></tr>
            <tr><td>Anchor.TSA</td><td>c2pa.time_stamp</td><td>RFC 3161 compatible</td></tr>
            <tr><td>DepthAnalysis</td><td>No equivalent</td><td>CPP-specific extension</td></tr>
            <tr><td>HumanAttestation</td><td>No equivalent</td><td>CPP-specific extension</td></tr>
            <tr><td>CompletenessInvariant</td><td>No equivalent</td><td>CPP-specific extension</td></tr>
          </tbody>
        </table>
      </section>

      <section anchor="c2pa-dual-output">
        <name>Dual-Standard Output</name>
        <t>Implementations generating both CPP and C2PA manifests MUST ensure
        shared fields (Timestamp, GPS, DeviceInfo) are consistent across both
        manifests. CPP-specific fields (DepthAnalysis, HumanAttestation,
        CompletenessInvariant) have no C2PA equivalent and are carried only
        in the CPP manifest.</t>
      </section>
    </section>

    <section anchor="privacy-considerations">
      <name>Privacy Considerations</name>

      <section anchor="location-data">
        <name>Location Data</name>
        <t>Location collection SHOULD be disabled by default. When enabled,
        implementations SHOULD:</t>
        <ul>
          <li>Clearly indicate when location is being recorded</li>
          <li>Allow users to delete location from individual events</li>
          <li>Consider privacy implications of location precision</li>
          <li>Support approximate location modes that reduce GPS precision</li>
        </ul>
      </section>

      <section anchor="biometric-data">
        <name>Biometric Data</name>
        <t>Implementations MUST NOT store raw biometric data (fingerprints,
        face images). Human presence verification, if implemented, SHOULD:</t>
        <ul>
          <li>Process biometrics locally on-device</li>
          <li>Store only verification results (boolean flags)</li>
          <li>Never transmit biometric data to external services</li>
        </ul>
      </section>

      <section anchor="tombstone-privacy">
        <name>Tombstone Privacy</name>
        <t>When events are deleted via TOMBSTONE:</t>
        <ul>
          <li>Original event content is removed</li>
          <li>TOMBSTONE preserves chain integrity</li>
          <li>Reason codes allow selective disclosure</li>
        </ul>
      </section>

      <section anchor="shareable-vs-forensic-proofs">
        <name>Shareable vs Forensic Proofs</name>
        <table>
          <thead>
            <tr><th>Level</th><th>Includes</th><th>Use Case</th></tr>
          </thead>
          <tbody>
            <tr><td>Shareable</td><td>Timestamp, device info, asset hash</td><td>Social sharing</td></tr>
            <tr><td>Forensic</td><td>All metadata including location, chain context</td><td>Legal proceedings</td></tr>
          </tbody>
        </table>
      </section>
    </section>

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

      <section anchor="hash-algorithm-agility">
        <name>Hash Algorithm Agility</name>
        <t>This specification mandates SHA-256 for all hash computations. Future
        versions MAY define additional algorithms via the HashAlgo field.
        Verifiers MUST reject unknown hash algorithms.</t>
        <t>Post-quantum consideration: ML-DSA-65 (NIST Module-Lattice Digital
        Signature Algorithm) is RESERVED for future adoption. XOR-based
        accumulators used in the Completeness Invariant, being purely symmetric
        operations, face fewer quantum vulnerabilities than public-key
        constructions.</t>
      </section>

      <section anchor="signature-algorithm-requirements">
        <name>Signature Algorithm Requirements</name>
        <t>Implementations MUST support ES256 (ECDSA with P-256 and SHA-256,
        per <xref target="FIPS186-5"/>) for
        mobile device compatibility. Ed25519 MAY be supported for non-mobile
        implementations.</t>
        <t>Private keys SHOULD be stored in hardware security modules where
        available:</t>
        <ul>
          <li>iOS: Secure Enclave (kSecAttrTokenIDSecureEnclave)</li>
          <li>Android: StrongBox or TEE via Android Keystore</li>
          <li>Desktop/Server: TPM 2.0 or HSM</li>
        </ul>
        <t>Hardware-backed keys provide non-exportability guarantees that
        strengthen the binding between device and signature.</t>
      </section>

      <section anchor="tsa-trust">
        <name>TSA Trust</name>
        <t>Security of timestamp proofs depends on TSA trustworthiness.
        Implementations:</t>
        <ul>
          <li>MUST verify the CMS signature in the TimeStampToken per
          <xref target="RFC5652"/></li>
          <li>SHOULD validate the certificate chain to a configured trust anchor</li>
          <li>SHOULD use TSAs with published certificate policies</li>
          <li>MAY support multiple TSA services for redundancy</li>
        </ul>
      </section>

      <section anchor="merkle-tree-security">
        <name>Merkle Tree Security</name>
        <t>The 0x00/0x01 prefix bytes ensure leaf hashes cannot equal internal
        node hashes for any input. This prevents second preimage attacks on
        the tree structure. This construction differs from Certificate
        Transparency <xref target="RFC6962"/> which uses a similar but
        incompatible scheme.</t>
      </section>

      <section anchor="clock-accuracy">
        <name>Clock Accuracy</name>
        <t>Device timestamps (Timestamp field) are self-attested and may be
        inaccurate. The authoritative timestamp is GenTime from the TSA response.
        Implementations SHOULD warn users when device time differs significantly
        from TSA GenTime (e.g., more than 5 minutes).</t>
      </section>

      <section anchor="deletion-detection-limitations">
        <name>Deletion Detection Limitations</name>
        <t>The Completeness Invariant detects deletions within a sealed collection.
        It does NOT detect:</t>
        <ul>
          <li>Events never created (adversary captured but never recorded)</li>
          <li>Events in other collections</li>
          <li>Deletions before sealing</li>
        </ul>
        <t>XOR is commutative and self-inverse. An attacker who can forge TWO
        events with EventHashes that XOR to zero can delete both without
        detection. The CI is designed to work WITH the Merkle tree anchor, not
        replace it.</t>
      </section>

      <section anchor="depth-analysis-security">
        <name>Depth Analysis Security</name>
        <t>Depth sensors can be spoofed by sophisticated adversaries using 3D
        displays or structured light interference. Depth analysis provides an
        additional signal but is not a definitive screen detection mechanism.
        Implementations MUST NOT represent depth analysis as conclusive proof
        of scene authenticity.</t>
      </section>

      <section anchor="canonicalization-attacks">
        <name>Canonicalization Attacks</name>
        <t>JSON canonicalization per <xref target="RFC8785"/> prevents ordering
        and whitespace attacks. Implementations MUST ensure field names exactly
        match the specification (PascalCase for events) and Unicode normalization
        is handled consistently.</t>
      </section>
    </section>

    <section anchor="iana-considerations">
      <name>IANA Considerations</name>
      <t>This document has no IANA actions.</t>
    </section>

    <section anchor="implementation-experience">
      <name>Implementation Experience</name>

      <section anchor="verasnap-ios">
        <name>VeraSnap iOS (Non-Normative)</name>
        <t>VeraSnap is a consumer iOS application implementing CPP, available in
        175 countries with 10-language localization. It demonstrates:</t>
        <ul>
          <li>Secure Enclave key storage with ES256 signatures</li>
          <li>RFC 3161 TSA integration with multiple providers (failover)</li>
          <li>Merkle tree construction per this specification</li>
          <li>Offline proof verification</li>
          <li>LiDAR-based depth analysis for screen detection</li>
          <li>Cross-platform proof export and QR code sharing</li>
        </ul>
      </section>

      <section anchor="verasnap-android">
        <name>VeraSnap Android (Non-Normative)</name>
        <t>A Kotlin-based Android implementation validates cross-platform
        interoperability:</t>
        <ul>
          <li>Android Keystore (StrongBox/TEE) key storage with ES256</li>
          <li>RFC 8785 JSON canonicalization producing identical hashes to iOS</li>
          <li>Proof JSON verification across platforms</li>
          <li>QR code generation and scanning interoperability</li>
        </ul>
        <t>Cross-platform testing confirmed that proofs generated on iOS verify
        correctly on Android and vice versa, validating the canonicalization
        and hashing specifications.</t>
      </section>
    </section>
  </middle>

  <back>
    <references>
      <name>References</name>

      <references>
        <name>Normative References</name>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.2119.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.3161.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.4648.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.5652.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8174.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8785.xml"/>
      </references>

      <references>
        <name>Informative References</name>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6962.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9562.xml"/>

        <reference anchor="C2PA" target="https://c2pa.org/specifications/">
          <front>
            <title>C2PA Specification</title>
            <author>
              <organization>Coalition for Content Provenance and Authenticity</organization>
            </author>
            <date year="2024"/>
          </front>
        </reference>

        <reference anchor="FIPS186-5" target="https://csrc.nist.gov/publications/detail/fips/186/5/final">
          <front>
            <title>Digital Signature Standard (DSS)</title>
            <author>
              <organization>National Institute of Standards and Technology</organization>
            </author>
            <date year="2023" month="February"/>
          </front>
          <seriesInfo name="FIPS" value="186-5"/>
        </reference>
      </references>
    </references>

    <section anchor="json-examples">
      <name>JSON Examples</name>

      <section anchor="canonical-event-normative">
        <name>Canonical Event with SignerInfo (Normative)</name>
        <t>The canonical event structure uses PascalCase field names. This is
        the structure that MUST be used for EventHash computation.</t>
        <artwork type="json"><![CDATA[
{
  "EventID": "550e8400-e29b-41d4-a716-446655440001",
  "ChainID": "urn:uuid:550e8400-e29b-41d4-a716-446655440000",
  "PrevHash": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
  "Timestamp": "2026-01-27T10:30:00.000Z",
  "EventType": "INGEST",
  "HashAlgo": "SHA256",
  "SignAlgo": "ES256",
  "Asset": {
    "AssetID": "asset-001",
    "AssetType": "IMAGE",
    "AssetHash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    "AssetName": "IMG_0001.HEIC",
    "MimeType": "image/heic"
  },
  "SignerInfo": {
    "Name": "John Doe",
    "Identifier": null,
    "AttestedAt": "2026-01-27T10:29:55.000Z"
  },
  "DeviceInfo": {
    "Manufacturer": "Apple",
    "Model": "iPhone 15 Pro",
    "DeviceClass": "SMARTPHONE",
    "OSName": "iOS",
    "OSVersion": "17.3",
    "AppVersion": "1.5.34"
  },
  "EventHash": "sha256:7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730",
  "Signature": "MEUCIQDKsRwMv..."
}
]]></artwork>
      </section>

      <section anchor="anchor-structure-normative">
        <name>Anchor Structure with MessageImprint (Normative)</name>
        <artwork type="json"><![CDATA[
{
  "Anchor": {
    "AnchorID": "anchor-001",
    "AnchorType": "RFC3161",
    "AnchorDigest": "719f871f1018a17ebe199d4f0db27e3a4929f8ab3e46f5c0d30054f4b331e929",
    "AnchorDigestAlgorithm": "sha-256",
    "Merkle": {
      "TreeSize": 1,
      "LeafHashMethod": "SHA256(0x00||EventHash)",
      "LeafHash": "sha256:719f871f1018a17ebe199d4f0db27e3a4929f8ab3e46f5c0d30054f4b331e929",
      "LeafIndex": 0,
      "Proof": [],
      "Root": "sha256:719f871f1018a17ebe199d4f0db27e3a4929f8ab3e46f5c0d30054f4b331e929"
    },
    "TSA": {
      "Token": "MIIEzAYJKoZIhvcNAQcCoIIEvTCCBLkCAQMx...",
      "MessageImprint": {
        "HashAlgorithm": "sha-256",
        "HashedMessage": "719f871f1018a17ebe199d4f0db27e3a4929f8ab3e46f5c0d30054f4b331e929"
      },
      "GenTime": "2026-01-27T10:31:00.000Z",
      "Service": "https://freetsa.org/tsr"
    }
  }
}
]]></artwork>
      </section>

      <section anchor="chain-context-example">
        <name>Chain Context Example (Non-Normative)</name>
        <artwork type="json"><![CDATA[
{
  "ChainContext": {
    "ChainID": "urn:uuid:550e8400-e29b-41d4-a716-446655440000",
    "TotalEvents": 100,
    "ActiveEvents": 53,
    "TombstoneCount": 47,
    "EventPosition": 15,
    "CompletenessInvariant": {
      "ExpectedCount": 100,
      "HashSum": "sha256:a3f2c8d1e5b9...",
      "FirstTimestamp": "2026-01-27T10:30:00.000Z",
      "LastTimestamp": "2026-01-27T17:45:00.000Z"
    },
    "GeneratedAt": "2026-01-27T18:00:00.000Z"
  }
}
]]></artwork>
      </section>

      <section anchor="depth-analysis-example">
        <name>Depth Analysis Example (Non-Normative)</name>
        <artwork type="json"><![CDATA[
{
  "CaptureContext": {
    "DepthAnalysis": {
      "SensorType": "LIDAR",
      "FlatnessScore": 0.12,
      "DepthVariance": 0.847,
      "ScreenDetected": false,
      "Confidence": 0.95,
      "AnalysisVersion": "1.4.0"
    }
  }
}
]]></artwork>
      </section>
    </section>

    <section anchor="test-vectors">
      <name>Test Vectors</name>
      <t>All test vectors in this section use the domain-separated hash
      construction defined in this specification. These vectors are
      identical to those in draft-vso-cpp-core-00 and -01, which also used domain
      separation. Note: implementations migrating from legacy
      (non-domain-separated) CPP hashing will produce different LeafHash
      and MerkleRoot values for the same EventHash inputs. The domain-separated
      outputs below are the only correct values for compliant
      implementations.</t>

      <section anchor="test-vector-1">
        <name>Test Vector 1: Single-Leaf Tree</name>
        <t><em>Input:</em></t>
        <artwork><![CDATA[
EventHash = "sha256:7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730"
]]></artwork>
        <t><em>Computation:</em></t>
        <artwork><![CDATA[
LeafHash = SHA256(0x00 || EventHash_bytes)
         = sha256:719f871f1018a17ebe199d4f0db27e3a4929f8ab3e46f5c0d30054f4b331e929

For TreeSize=1:
  Root = LeafHash
  LeafIndex = 0
  Proof = []
  AnchorDigest = 719f871f1018a17ebe199d4f0db27e3a4929f8ab3e46f5c0d30054f4b331e929

Result: VALID
]]></artwork>
      </section>

      <section anchor="test-vector-2">
        <name>Test Vector 2: Two-Leaf Tree</name>
        <artwork><![CDATA[
EventHash[0] = "sha256:aaaa...aaaa"  (32 bytes of 0xaa)
EventHash[1] = "sha256:bbbb...bbbb"  (32 bytes of 0xbb)

L0 = SHA256(0x00 || 0xaa...aa)
   = sha256:e0bb82791bae3c50bd9c20fa4ccdcb8064a56e5c12bc69b07e6712ac9b4429e6
L1 = SHA256(0x00 || 0xbb...bb)
   = sha256:4f16119d36ccd0da91102f57692d73934fd0ad2494280df88449accedbbfb7ea

Root = SHA256(0x01 || L0 || L1)
     = sha256:03938e2c8f758e6cae443d499b41c899c373eb0c0198bae61796a069f2b05904

For index 0: Proof = [L1]
For index 1: Proof = [L0]

Result: VALID
]]></artwork>
      </section>

      <section anchor="test-vector-3">
        <name>Test Vector 3: TSA messageImprint Verification</name>
        <artwork><![CDATA[
AnchorDigest = "719f871f1018a17ebe199d4f0db27e3a4929f8ab3e46f5c0d30054f4b331e929"

TSTInfo.messageImprint.hashAlgorithm = SHA-256
TSTInfo.messageImprint.hashedMessage = 0x719f871f...1e929

lowercase_hex(hashedMessage) == AnchorDigest ? YES

Result: VALID
]]></artwork>
      </section>
    </section>

    <section anchor="acknowledgements" numbered="false">
      <name>Acknowledgements</name>
      <t>The authors thank:</t>
      <ul>
        <li>The VeritasChain Standards Organization (VSO) for developing the CPP
        specification series (v1.0 through v1.5)</li>
        <li>The implementers of VeraSnap (iOS and Android) for cross-platform
        validation feedback that improved this specification</li>
        <li>Early reviewers who identified the domain separation and Completeness
        Invariant modeling issues</li>
        <li>Contributors to the depth analysis and pre-publish verification
        extensions</li>
      </ul>
    </section>
  </back>
</rfc>
