<rfc ipr="trust200902" docName="draft-fassbender-scitt-time-anchor-08" category="info" submissionType="independent" version="3" xml:lang="en" tocInclude="true" sortRefs="true" symRefs="true">

  <front>
    <title abbrev="Umarise Temporal Proofs">Umarise's Bitcoin-Anchored Temporal Proofs</title>
    <seriesInfo name="Internet-Draft" value="draft-fassbender-scitt-time-anchor-08" />
    <author initials="J." surname="Fassbender" fullname="Jonna Fassbender">
      <organization>Umarise</organization>
      <address>
        <postal>
          <country>NL</country>
        </postal>
        <email>j.fassbender@umarise.com</email>
        <uri>https://umarise.com</uri>
      </address>
    </author>
    <date year="2026" month="October" day="23" />
    <area>Security</area>
    <keyword>timestamping</keyword>
    <keyword>Bitcoin</keyword>
    <keyword>OpenTimestamps</keyword>
    <keyword>transparency</keyword>
    <abstract>
      <t>This document describes the mechanism Umarise uses for temporal anchoring of digital artifacts: cryptographic hashes are committed to the Bitcoin blockchain via the OpenTimestamps protocol. The resulting proof is independently verifiable by any party with access to independently validated Bitcoin chain data, without contacting Umarise or any other anchoring service. The document is published so that other parties can produce and verify such proofs. The SCITT Architecture is used as the primary integration example; no changes to it are required.</t>
    </abstract>
  </front>

  <middle>
<section anchor="sec-1-introduction"><name>Introduction</name>
<t>Cryptographic time-stamping -- proving that a datum existed at or before a given point in time -- is a foundational primitive for audit, compliance, and non-repudiation on the Internet.</t>
<t>RFC 3161 <xref target="RFC3161" /> defines a widely deployed protocol in which a trusted Time Stamping Authority (TSA) signs a timestamp token binding a hash to a point in time. Its security depends on the TSA's private key, its operational continuity, and the validity of its certificate chain. These failure modes are not equivalent: an orderly cessation leaves tokens issued beforehand valid under <xref target="RFC3161" />, whereas revocation for key compromise does not. What they have in common is that continued verifiability depends on preserving validation material that the relying party does not control.</t>
<t>This document defines a complementary mechanism in which temporal proof derives from inclusion in a public, append-only ledger maintained by computational consensus -- specifically, the Bitcoin blockchain. The resulting proof is independently verifiable by any party with access to ledger state, without reliance on any single authority or certificate chain.</t>
<t>These approaches are not mutually exclusive. A system MAY use both RFC 3161 timestamps and ledger-based anchoring to provide complementary assurance under different trust assumptions.</t>
<t>The SCITT Architecture <xref target="RFC9943" /> is used as the primary integration example throughout this document. SCITT defines a framework for Transparency Services that record signed claims about digital artifacts. A Transparency Service receives Signed Statements, appends them to a verifiable log, and returns cryptographic Receipts proving inclusion.</t>
<t>SCITT is deliberately ledger-agnostic and does not mandate a specific time source. Registration time is recorded by the Transparency Service itself, as the moment it added the Signed Statement to its verifiable data structure (<xref target="RFC9943" /> Section 7). This creates an architectural gap: the system that manages the evidence also manages the timeline. The operator can:</t>
<ul>
<li>Delay recording without detection</li>
<li>Backdate entries (within operational constraints)</li>
<li>Present different log views to different auditors (equivocation)</li>
</ul>
<t>SCITT requires non-equivocation of the verifiable data structure, and Receipts may carry consistency proofs, but such proofs are relative to the log itself. There is no external reference point.</t>
<t>This document describes the profile Umarise implements to narrow this gap, by anchoring operations to the Bitcoin blockchain <xref target="NAKAMOTO" /> via the OpenTimestamps protocol <xref target="OTS" />. Use of the profile is optional for a Transparency Service. Verification does not depend on Umarise, and implementations by other parties exist (<xref target="sec-appendix-c-implementation-status" />).</t>
<t><strong>Reading this document.</strong> The procedure this profile specifies is VERIFY-ANCHOR, given as eleven steps in <xref target="sec-3-1-verification-algorithm-verify-anchor" />. It consumes the proof format of <xref target="sec-2-4-anchor-proof-format" /> and establishes the temporal claim of <xref target="sec-5-1-claim" />. A reader who wants the procedure first may turn to it directly: <xref target="sec-1-3-terminology" /> and <xref target="sec-1-4-notation" /> define the terms and the pseudocode conventions it uses, and <xref target="sec-1-5-implementing-this-profile" /> gives the reading order for building a verifier and for building an anchoring service.</t>
<t><strong>Implementation status at the time of writing.</strong> The producing side of this profile has been implemented independently of the author, from this document and the OpenTimestamps release pinned in <xref target="sec-5-2-1-note-on-assumption-a4" />, and that implementation evaluates the confirmation-depth gate of <xref target="sec-6-11-threat-premature-anchored-state-false-finality" /> before it promotes a proof to <tt>anchored</tt>. Of the two deployments Umarise operates, the second evaluates that gate and the first does not, and the reference verifier Umarise publishes obtains block headers from a single block explorer, which under <xref target="sec-3-1-verification-algorithm-verify-anchor" /> yields <tt>unverifiable</tt> rather than <tt>valid</tt>. A reader should therefore not assume that the status token <tt>anchored</tt>, as recorded by any particular running system, carries the meaning this document gives it. <xref target="sec-appendix-c-implementation-status" /> records what each implementation does and does not do.</t>
<section anchor="sec-1-1-motivating-example"><name>Motivating Example</name>
<t>An AI research laboratory produces model weights and safety evaluations that must be auditable by regulators and the public. The lab registers each artifact with a SCITT Transparency Service and receives a Receipt. However, regulators ask: "How do we know the lab did not register these weights after the safety evaluation was already public -- backdating the claim?"</t>
<t>Under this profile, the Transparency Service submits the SHA-256 hash of the Signed Statement to an anchoring service. The anchoring service returns an Anchor Proof -- a portable, self-contained cryptographic proof that the hash was committed to the Bitcoin blockchain. The proof is independently verifiable by any party with access to independently validated Bitcoin chain data, without contacting the anchoring service or the Transparency Service.</t>
<t>Once the commitment has been included in a Bitcoin block and that block has reached the confirmation depth this profile requires, the regulator obtains a temporal guarantee: these model weights existed no later than block height H. Inclusion is not immediate: the commitment waits for a calendar's next round and for its transaction to be mined, and the confirmation depth of <xref target="sec-6-11-threat-premature-anchored-state-false-finality" /> applies on top of that (<xref target="sec-on-chain-footprint-and-latency" />). The anchoring service provides proof of existence and time, not claims about authorship, quality, or regulatory compliance. Nor does it carry the legal presumption that some jurisdictions attach to qualified time stamping; see <xref target="sec-2-6-6-relationship-to-wall-clock-time-stamping" />.</t>
</section>
<section anchor="sec-2-7-architectural-overview"><name>Architectural Overview</name>
<t>This section provides a view of the anchoring and verification flows. Bitcoin proof-of-work, not any intermediary, is the trust anchor that makes the temporal claim binding.</t>
<section anchor="sec-2-7-1-anchoring-flow"><name>Anchoring Flow</name>
<artset>
<artwork type="svg">
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny" width="640" height="660" viewBox="0 0 640 660">
<rect x="10" y="10" width="620" height="250" fill="none" stroke="black" stroke-width="1" stroke-dasharray="6,4"/>
<text x="20" y="30" text-anchor="start" font-family="sans-serif" font-size="13" font-weight="bold">Anchoring service (Section 5.3)</text>
<rect x="80" y="45" width="180" height="34" fill="white" stroke="black" stroke-width="1"/>
<text x="170" y="67" text-anchor="middle" font-family="sans-serif" font-size="13">h1 (received)</text>
<line x1="170" y1="79" x2="170" y2="118" stroke="black" stroke-width="1"/>
<polygon points="166,117 174,117 170,125" fill="black"/>
<text x="178" y="106" text-anchor="start" font-family="sans-serif" font-size="12">append n1, SHA-256</text>
<rect x="80" y="125" width="180" height="34" fill="white" stroke="black" stroke-width="1"/>
<text x="170" y="147" text-anchor="middle" font-family="sans-serif" font-size="13">blinded leaf 1</text>
<rect x="380" y="45" width="180" height="34" fill="white" stroke="black" stroke-width="1"/>
<text x="470" y="67" text-anchor="middle" font-family="sans-serif" font-size="13">h2 (received)</text>
<line x1="470" y1="79" x2="470" y2="118" stroke="black" stroke-width="1"/>
<polygon points="466,117 474,117 470,125" fill="black"/>
<text x="478" y="106" text-anchor="start" font-family="sans-serif" font-size="12">append n2, SHA-256</text>
<rect x="380" y="125" width="180" height="34" fill="white" stroke="black" stroke-width="1"/>
<text x="470" y="147" text-anchor="middle" font-family="sans-serif" font-size="13">blinded leaf 2</text>
<line x1="170" y1="159" x2="300" y2="205" stroke="black" stroke-width="1"/>
<line x1="470" y1="159" x2="340" y2="205" stroke="black" stroke-width="1"/>
<rect x="220" y="205" width="200" height="34" fill="white" stroke="black" stroke-width="1"/>
<text x="320" y="227" text-anchor="middle" font-family="sans-serif" font-size="13">local Merkle root R</text>
<line x1="320" y1="239" x2="320" y2="323" stroke="black" stroke-width="1"/>
<polygon points="316,322 324,322 320,330" fill="black"/>
<text x="332" y="277" text-anchor="start" font-family="sans-serif" font-size="12">R, one per batch, to one or</text>
<text x="332" y="292" text-anchor="start" font-family="sans-serif" font-size="12">more OpenTimestamps servers</text>
<rect x="10" y="300" width="620" height="100" fill="none" stroke="black" stroke-width="1" stroke-dasharray="6,4"/>
<text x="20" y="320" text-anchor="start" font-family="sans-serif" font-size="13" font-weight="bold">OpenTimestamps</text>
<rect x="120" y="330" width="400" height="52" fill="white" stroke="black" stroke-width="1"/>
<text x="320" y="352" text-anchor="middle" font-family="sans-serif" font-size="13">Calendar: aggregates R with roots from other</text>
<text x="320" y="370" text-anchor="middle" font-family="sans-serif" font-size="13">submitters into a calendar root C, once per round</text>
<line x1="320" y1="382" x2="320" y2="463" stroke="black" stroke-width="1"/>
<polygon points="316,462 324,462 320,470" fill="black"/>
<text x="332" y="425" text-anchor="start" font-family="sans-serif" font-size="12">one transaction per round</text>
<rect x="10" y="440" width="620" height="210" fill="none" stroke="black" stroke-width="1" stroke-dasharray="6,4"/>
<text x="20" y="460" text-anchor="start" font-family="sans-serif" font-size="13" font-weight="bold">Bitcoin</text>
<rect x="120" y="470" width="400" height="34" fill="white" stroke="black" stroke-width="1"/>
<text x="320" y="492" text-anchor="middle" font-family="sans-serif" font-size="13">Transaction with an OP_RETURN output carrying C</text>
<line x1="320" y1="504" x2="320" y2="573" stroke="black" stroke-width="1"/>
<polygon points="316,572 324,572 320,580" fill="black"/>
<text x="332" y="535" text-anchor="start" font-family="sans-serif" font-size="12">committed through the block's</text>
<text x="332" y="550" text-anchor="start" font-family="sans-serif" font-size="12">transaction Merkle tree</text>
<rect x="120" y="580" width="400" height="52" fill="white" stroke="black" stroke-width="1"/>
<text x="320" y="602" text-anchor="middle" font-family="sans-serif" font-size="13">Block header at height H: holds that Merkle root,</text>
<text x="320" y="620" text-anchor="middle" font-family="sans-serif" font-size="13">not the OP_RETURN output</text>
</svg>
</artwork>
<artwork type="ascii-art">
  Anchoring service (Section 5.3)
  +----------------------------------------------------------+
  |  h1 (received)               h2 (received)               |
  |      | append n1, SHA-256        | append n2, SHA-256    |
  |      v                           v                       |
  |  blinded leaf 1              blinded leaf 2              |
  |           \                    /                         |
  |            local Merkle root R                           |
  +--------------------------|-------------------------------+
                             | R, one per batch, to one or
                             | more OpenTimestamps servers
  OpenTimestamps             v
  +----------------------------------------------------------+
  |  Calendar: aggregates R with roots from other            |
  |  submitters into a calendar root C, once per round       |
  +--------------------------|-------------------------------+
                             | one transaction per round
  Bitcoin                    v
  +----------------------------------------------------------+
  |  Transaction with an OP_RETURN output carrying C         |
  |      | committed through the block's transaction         |
  |      v Merkle tree                                       |
  |  Block header at height H: holds that Merkle root,       |
  |  not the OP_RETURN output                                |
  +----------------------------------------------------------+
</artwork>
</artset>
<t>Figure 1: The Anchoring Path</t>
<t>An Anchor Proof records the whole of this path as operations: from the artifact hash through the blinding nonce and the local tree to R, through the calendar's aggregation to C, through the bytes of the transaction to its identifier, and through the block's transaction Merkle tree to the Merkle root in the header at height H. A verifier replays the operations and compares the result with that header (<xref target="sec-3-1-verification-algorithm-verify-anchor" />). It contacts neither the anchoring service nor the calendar.</t>
<t>Key trust property: the Verifier establishes the header at the attested height on a chain it has validated itself (<xref target="sec-3-1-verification-algorithm-verify-anchor" />). The Verifier does NOT need to trust any Calendar Server: a Calendar Server may commit two divergent branches, and proofs on both may verify, but it cannot fabricate inclusion in a block that was mined earlier. The trust root is Bitcoin's Proof-of-Work consensus, not any intermediary.</t>
<t>The block at height H is accepted by the network via proof-of-work consensus. This is the moment the temporal anchor becomes binding and independently verifiable. Until inclusion in a confirmed block, the producer records the proof as "pending" (F9, <xref target="sec-2-4-1-abstract-field-requirements" />), and the proof MUST NOT be relied upon as a temporal claim.</t>
</section>
<section anchor="sec-2-7-2-verification-flow"><name>Verification Flow</name>
<artwork>
                    VERIFICATION FLOW

  Auditor         Bitcoin       Bitcoin       Transparency
  (Verifier)      Network       Block H       Service
     |               |             |             |
     |- fetch header&gt;|             |             |
     |&lt;- block hdr --|             |             |
     |               |             |             |
     |  replay the .ots operations locally;      |
     |  digest = Merkle root of header at H      |
     |               |             |             |
     |  [optional] verify Receipt against TS     |
     |------------------------------------------&gt;|
     |&lt;------------------------------------------|
     |               |             |             |
     |  RESULT: artifact committed in block at height H
     |  (block-height binding, Section 7.1)
     |  No contact with the anchoring service required.
     |  No contact with OTS Calendar required.
     |  Only Bitcoin block headers needed.
</artwork>
<t>Figure 2: Actor-Level Verification Flow</t>
<t>The Verifier's only required dependency outside the proof is the Bitcoin block header chain, which it must be able to place on the validated most-work chain; see the note on header sources in <xref target="sec-3-1-verification-algorithm-verify-anchor" />. Contact with the Transparency Service is OPTIONAL and only relevant if the Verifier wishes to additionally confirm SCITT log inclusion. This separation is what allows the Anchor Proof to satisfy the self-containment requirement defined in <xref target="sec-2-4-anchor-proof-format" />.</t>
</section>
</section>
<section anchor="sec-1-5-implementing-this-profile"><name>Implementing This Profile</name>
<t>This section is informative. It states where each component of an implementation is specified, in the order an implementer needs it, and names what this profile does not specify. It introduces no requirement; every statement below points at a section that carries the requirement itself.</t>

<t><strong>Read this first: the proof format is not specified in this document.</strong> Reading the serialised <tt>.ots</tt> byte stream into an operation sequence and its attestations is <tt>OTS-DESERIALIZE</tt> (<xref target="sec-3-1-verification-algorithm-verify-anchor" />), whose input format is defined by <xref target="OTS" />. <xref target="sec-5-2-1-note-on-assumption-a4" /> states what an implementer obtains from this document and what must be obtained from <xref target="OTS" />; an implementer intending to write a parser rather than call an existing OpenTimestamps library should read it before starting, not after.</t>

<t><strong>To build a verifier</strong>, in order:</t>
<ol>
<li>Inputs: <xref target="sec-3-2-verification-independence" /> lists what a conformant verifier requires, and what it MUST NOT require.</li>
<li>Input structure: <xref target="sec-2-4-3-cddl-definition-of-the-proof-bundle" /> defines the Proof Bundle passed to the verifier. The <tt>.ots</tt> proof is carried within it as an opaque byte string.</li>
<li>Parse: <tt>OTS-DESERIALIZE</tt>, per <xref target="OTS" />. See above.</li>
<li>Verify: <xref target="sec-3-1-verification-algorithm-verify-anchor" /> specifies <tt>VERIFY-ANCHOR</tt> normatively, as eleven steps. <xref target="sec-3-3-error-conditions" /> gives the error conditions.</li>
<li>Interpret the result: <xref target="sec-5-1-claim" /> states what a <tt>valid</tt> result establishes; it is conditional on the assumptions of <xref target="sec-5-2-assumptions" />. <xref target="sec-2-6-4-non-claims" /> states what it does not establish. Both bind what an implementation may present to a relying party.</li>
<li>Check the implementation: <xref target="sec-appendix-e-test-vectors" /> gives four anchored artifacts with their proofs, block heights and expected outputs. They verify against the public Bitcoin chain without contacting the author or any anchoring service. <xref target="sec-e-6-independent-verification" /> gives four verification routes in decreasing order of independence.</li>
</ol>

<t><strong>To build an anchoring service</strong>, in order:</t>
<ol>
<li>Construction: <xref target="sec-d-1-construction-algorithm-anchor" /> (<tt>CONSTRUCT-ANCHOR</tt>). It takes a digest computed by the caller, not the artifact bytes; see <xref target="sec-1-3-terminology" />.</li>
<li>Upgrade cycle: <xref target="sec-d-2-upgrade-algorithm-bitcoin-confirmation" />, including the confirmation-depth gate at step 2f, below which a proof is not promoted.</li>
<li>Batching, if used: <xref target="sec-d-3-batch-anchoring-with-merkle-trees" /> gives the aggregation in full; a deployment that aggregates differently produces a different root from the same input, and will not reproduce the test vectors of <xref target="sec-appendix-e-test-vectors" />.</li>
<li>What the states mean: <xref target="sec-2-6-temporal-precision" />, and <xref target="sec-6-11-threat-premature-anchored-state-false-finality" /> for what <tt>anchored</tt> must not be taken to mean.</li>
<li>What is deployed today: <xref target="sec-appendix-c-implementation-status" /> records the state of the author's own implementations against this profile, including the requirements they do not yet meet, and the independent implementations known to the author.</li>
</ol>

<t><strong>To integrate with a Transparency Service</strong>: <xref target="sec-4-integration-with-scitt-architecture" />. No change to the SCITT architecture is required.</t>
</section>

<section anchor="sec-1-7-layering"><name>Layering: What Is General and What Is Bitcoin-Specific</name>
<t>This section is informative. It exists so that a reader can see, without reconstructing it from the body, which parts of this profile are properties of the anchoring model and which are properties of the ledger this profile selects.</t>
<t><strong>Ledger-independent.</strong> The anchoring model of <xref target="sec-2-anchoring-model" /> does not name a ledger. The two anchoring levels of Sections <xref target="sec-2-2-statement-anchoring" format="counter" /> and <xref target="sec-2-3-log-root-anchoring" format="counter" />, the abstract field requirements of <xref target="sec-2-4-1-abstract-field-requirements" /> in which the ledger is identified by its genesis block hash, the four qualification properties of <xref target="sec-2-5-anchor-ledger-requirements" />, the exclusions of <xref target="sec-2-6-4-non-claims" />, the shape of the verification procedure in <xref target="sec-3-1-verification-algorithm-verify-anchor" />, the three outcomes of Table 5, and the whole of the SCITT integration in <xref target="sec-4-integration-with-scitt-architecture" /> hold for any Anchor Ledger that satisfies <xref target="sec-2-5-anchor-ledger-requirements" />. None of them changes if the ledger changes.</t>
<t><strong>Bitcoin-specific.</strong> Four things in this document are properties of Bitcoin rather than of the model. The wire mapping in Table 4 of <xref target="sec-2-4-2-opentimestamps-wire-format-mapping" /> uses the OpenTimestamps Bitcoin attestation, which identifies the ledger family but not the network; the verifier's validated chain supplies the network, and the release pinned in <xref target="sec-5-2-1-note-on-assumption-a4" /> defines no attestation type for any other ledger. The confirmation depth of <xref target="sec-6-11-threat-premature-anchored-state-false-finality" /> is a risk threshold priced against the cost of producing six blocks on one particular chain, and it is not a constant of the model. The retrieval step in <xref target="sec-3-1-verification-algorithm-verify-anchor" /> assumes a header chain whose most-work branch a verifier can validate for itself. And the wall-clock projection of <xref target="sec-2-6-6-relationship-to-wall-clock-time-stamping" /> is derived from a Bitcoin block header field with Bitcoin's consensus bounds.</t>
<t><strong>What a profile for another ledger would have to supply.</strong> <xref target="sec-2-5-anchor-ledger-requirements" /> states that a future profile may extend this work to another qualifying ledger. Concretely, the author of such a profile would have to supply four things and could reuse the rest: evidence that the ledger satisfies each of the four qualification properties of <xref target="sec-2-5-anchor-ledger-requirements" />, with the first stated in terms of the cost of rewriting that ledger's own history; an attestation format capable of expressing a commitment on that ledger, since the format used here cannot; a confirmation-depth requirement derived from that ledger's own work rate, since the value used here is not portable and carrying it over unchanged would be an error rather than a simplification; and a retrieval procedure appropriate to that ledger's headers or equivalent structure. The remainder of this document, being the ledger-independent part listed above, would carry over unchanged.</t>
</section>
</section>
<section anchor="sec-conventions-and-terminology"><name>Conventions and Terminology</name>
<section anchor="sec-1-2-requirements-language"><name>Requirements Language</name>
<t>The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 <xref target="RFC2119" /> <xref target="RFC8174" /> when, and only when, they appear in all capitals, as shown here.</t>
</section>
<section anchor="sec-1-3-terminology"><name>Terminology</name>
<ul>
<li><strong>Temporal Anchor</strong>: A cryptographic commitment of a hash value to a public, append-only ledger, establishing that the hashed content existed before the validated ledger state containing that commitment was produced. Under this profile the anchor is external to the system whose artifacts are being anchored: in the SCITT context, external to the Transparency Service's verifiable log, committed to a separate ledger (the Anchor Ledger, below) controlled by neither the Transparency Service operator nor any single authority.</li>
</ul>
<ul>
<li><strong>Anchor Proof</strong>: A portable, self-contained proof object (e.g., an OpenTimestamps <tt>.ots</tt> file, see <xref target="OTS" />) that is independently verifiable without contacting the anchoring service.</li>
</ul>
<ul>
<li><strong>Proof Bundle</strong>: The structured input to the VERIFY-ANCHOR algorithm (<xref target="sec-3-1-verification-algorithm-verify-anchor" />). A Proof Bundle MUST contain the following fields:</li>
</ul>
<ul>
<li><strong>ots_proof</strong> (REQUIRED): The binary OpenTimestamps proof file (.ots), as specified in <xref target="sec-2-4-2-opentimestamps-wire-format-mapping" />.</li>
<li><strong>claimed_hash</strong> (REQUIRED): The SHA-256 hash of the artifact, encoded as 64 lowercase hexadecimal characters with an optional <tt>"sha256:"</tt> prefix (e.g., <tt>"sha256:781bb71a..."</tt>). The verifier MUST recompute the hash from the artifact bytes and MUST NOT use <tt>claimed_hash</tt> as the artifact digest. <tt>claimed_hash</tt> is a consistency check only: a mismatch with the recomputed value returns <tt>invalid</tt> (<xref target="sec-3-1-verification-algorithm-verify-anchor" />, step 2), and a match establishes nothing on its own, since step 3 compares the proof with the recomputed value.</li>
<li><strong>origin_id</strong> (OPTIONAL): An implementation-defined identifier for the anchoring record. Not used in verification; included for traceability.</li>
<li><strong>bitcoin_block_height</strong> (OPTIONAL): A cached block height from a prior verification. Not used in verification; included for informational purposes only.</li>
</ul>
<t>The normative CDDL definition of the Proof Bundle structure is given in <xref target="sec-2-4-3-cddl-definition-of-the-proof-bundle" />.</t>
<ul>
<li><strong>Anchor Ledger</strong>: In this document, the Anchor Ledger is the Bitcoin blockchain <xref target="NAKAMOTO" />. The properties that make Bitcoin suitable for this role -- public verifiability, append-only history, absence of a single controlling authority, and independent verifiability -- are documented in <xref target="sec-2-5-anchor-ledger-requirements" />.</li>
</ul>
<ul>
<li><strong>OpenTimestamps (OTS)</strong>: A protocol and reference implementation for committing hashes to the Bitcoin blockchain via aggregating Calendar Servers, producing a portable proof file (the <tt>.ots</tt> file) that is independently verifiable against Bitcoin block headers. See <xref target="OTS" />, <xref target="OTS-SITE" />, and <xref target="OTS-DESIGN" />.</li>
</ul>
<ul>
<li><strong>Attestation</strong>: The term has two distinct uses in this document. (1) In the OpenTimestamps wire format <xref target="OTS" />, a typed terminal element of an <tt>.ots</tt> proof that binds it to an external commitment: the Bitcoin attestation (tag <tt>0x0588960d73d71901</tt>), which binds the proof to a confirmed Bitcoin block, and the pending attestation, which references a Calendar Server URL. (2) In implementation-level contexts (<xref target="sec-appendix-c-implementation-status" />, pseudocode field <tt>origin_attestations</tt>), an anchored origin record stored by the anchoring service. Neither use implies legal or evidentiary attestation in any jurisdictional sense.</li>
</ul>
<ul>
<li><strong>Calendar Server</strong>: A server in the OpenTimestamps protocol that aggregates submitted hashes into a Merkle tree and periodically commits the Merkle root to the Bitcoin blockchain. A Calendar Server is a convenience for batching; it is not a trust authority. Verification of a completed Anchor Proof does not require contacting any Calendar Server (see <xref target="sec-3-2-verification-independence" />).</li>
</ul>
<ul>
<li><strong>Bitcoin Miner</strong>: A network participant that constructs and proposes new Bitcoin blocks via proof-of-work <xref target="NAKAMOTO" />. Miners are relevant here only as the consensus mechanism by which Calendar Server commitments become embedded in block history; this document depends on no specific miner.</li>
</ul>
<ul>
<li><strong>Transparency Service</strong>: As defined in <xref target="RFC9943" />, a service that records signed claims about digital artifacts in a verifiable log and returns cryptographic Receipts proving inclusion.</li>
</ul>
<ul>
<li><strong>Signed Statement</strong>: As defined in <xref target="RFC9943" /> Section 3 and elaborated in Section 6 of that document, a signed claim about a digital artifact submitted by a Producer to a Transparency Service for inclusion in the verifiable log.</li>
</ul>
<ul>
<li><strong>Receipt</strong>: As defined in <xref target="RFC9943" /> Section 3, a cryptographic structure returned by a Transparency Service that proves inclusion of a Signed Statement in the log. The COSE Receipt structure and the verifiable data structure proof formats are specified in <xref target="RFC9942" />.</li>
</ul>
<ul>
<li><strong>Producer</strong>: A party that creates a digital artifact and submits a Signed Statement about it to a Transparency Service. Also referred to as the Submitter when the emphasis is on the act of submission rather than artifact creation. See <xref target="RFC9943" />.</li>
</ul>
<ul>
<li><strong>Verifier</strong>: A party that evaluates an Anchor Proof (and optionally a Receipt) to determine whether an artifact's temporal claim is <tt>valid</tt>, <tt>invalid</tt>, or <tt>unverifiable</tt>. The Verifier is independent from the Transparency Service operator and the anchoring service.</li>
</ul>
<ul>
<li><strong>Auditor</strong>: A specialized Verifier acting on behalf of regulators, courts, or compliance functions, with the additional goal of detecting equivocation or backdating by the Transparency Service operator. An Auditor uses the same verification procedure (<xref target="sec-3-1-verification-algorithm-verify-anchor" />) as any other Verifier.</li>
</ul>
<ul>
<li><strong>Median Time Past (MTP)</strong>: As specified in <xref target="BIP113" />, the median timestamp of the eleven Bitcoin blocks immediately preceding a given block. By the consensus median-time rule it is a strict lower bound on the block's own declared timestamp; because block timestamps are miner-set within consensus limits, it is not a lower bound on the wall-clock time at which a block was accepted. The normative temporal claim under this profile is the block-height binding (<xref target="sec-5-1-claim" />).</li>
</ul>
<ul>
<li><strong>OP_RETURN</strong>: A Bitcoin transaction output type that creates a provably unspendable output capable of carrying data. OpenTimestamps Calendar Servers use it to carry a 32-byte commitment to a Merkle root. See <xref target="OTS" /> for the specific use in this profile. The limits a node will relay or mine are policy rather than consensus and have changed over time; nothing in this profile depends on them.</li>
</ul>
<ul>
<li><strong>Merkle path</strong>: An ordered sequence of recorded byte strings and operations that, when replayed from a leaf hash, deterministically reproduces a committed value. The OpenTimestamps <tt>.ots</tt> proof encodes a Merkle path from the artifact hash to the transaction Merkle root of a Bitcoin block header. Each step prepends a sibling value, appends one, or hashes; the path therefore carries its own position and requires no external ordering convention. See <xref target="OTS" />; the underlying tree construction is due to <xref target="MERKLE" />.</li>
</ul>
<ul>
<li><strong>Block header</strong>: As specified in <xref target="NAKAMOTO" />, the fixed-size 80-byte structure at the start of every Bitcoin block, containing the block's version, previous block hash, Merkle root of transactions, timestamp, difficulty target, and nonce. Block headers are sufficient to verify Bitcoin proof-of-work without downloading transaction data.</li>
</ul>
<ul>
<li><strong>Full node</strong>: A Bitcoin network participant that independently validates the entire blockchain (all blocks and transactions) against consensus rules. A full node requires no trust in third parties for the data it has validated. See <xref target="NAKAMOTO" />.</li>
</ul>
<ul>
<li><strong>Block explorer</strong>: A public web service that exposes Bitcoin blockchain data via HTTP. An explorer reports a chain rather than demonstrating the work behind it; the note on header sources in <xref target="sec-3-1-verification-algorithm-verify-anchor" /> states what a verifier must be able to establish before returning <tt>valid</tt>, and <xref target="sec-e-3-block-height-binding"/> notes the byte-order convention explorers use.</li>
</ul>
<ul>
<li><strong>Confirmation depth</strong>: The number of blocks on the Bitcoin blockchain at the height of a given block and above, counting that block itself. A transaction with <tt>k</tt> confirmations is considered increasingly resistant to chain reorganization as <tt>k</tt> grows. Under this profile a producing implementation SHOULD NOT promote an Anchor Proof from <tt>pending</tt> to <tt>anchored</tt> before <tt>k &gt;= 6</tt> (see <xref target="sec-6-11-threat-premature-anchored-state-false-finality" />).</li>
</ul>
<ul>
<li><strong>Block-height binding</strong>: The normative chronological binding established by an Anchor Proof under this profile. The binding identifies the Bitcoin block at height <tt>H</tt> that contains the OP_RETURN commitment of (the Merkle root containing) the artifact's hash. Block-height binding is exact: a proof either binds an artifact to block H or it does not. The binding is consensus-verified and is not subject to clock drift, miner-set timestamp manipulation, or the consensus limits on <tt>block.time</tt> discussed in <xref target="sec-2-6-6-relationship-to-wall-clock-time-stamping" />.</li>
</ul>
<ul>
<li><strong>Reference Wall-Clock Projection</strong>: The wall-clock value <tt>block.time(H)</tt>, where <tt>H</tt> is the block height of the block-height binding (above). The Reference Wall-Clock Projection is the human-readable temporal value exposed by deployed OpenTimestamps verifiers and SHOULD be presented alongside <tt>H</tt> for human consumption. The Reference Wall-Clock Projection is informative and is NOT the normative temporal value of the proof; the normative value is the block-height binding (see <xref target="sec-5-1-claim" />).</li>
</ul>
<t><strong>Top-level anchoring abstraction:</strong></t>
<ul>
<li><tt>AnchorDigest(alg, digest, AnchorLedger)</tt> -- takes a hash algorithm identifier <tt>alg</tt>, a <tt>digest</tt> already computed by the caller under <tt>alg</tt>, and an Anchor Ledger reference, and returns an Anchor Proof binding <tt>digest</tt> to a commitment on that ledger. The operation does not hash: an anchoring service under this profile receives a digest and never the artifact bytes from which it was derived. This is an abstract operation; its concrete instantiation for the Bitcoin Anchor Ledger via OpenTimestamps is specified in <xref target="sec-d-1-construction-algorithm-anchor" /> (<tt>CONSTRUCT-ANCHOR</tt>). The verifier is not symmetric with it: <tt>VERIFY-ANCHOR</tt> (<xref target="sec-3-1-verification-algorithm-verify-anchor" />) does take the artifact bytes, because a verifier holds the artifact and re-derives the digest from it (step 1).</li>
</ul>
<t><strong>Cryptographic primitives:</strong></t>
<ul>
<li><tt>SHA-256(x)</tt> -- the SHA-256 hash function applied to byte sequence <tt>x</tt>, as specified in <xref target="FIPS180-4" />.</li>
<li><tt>MerkleRoot(L)</tt> -- the root of the Merkle tree constructed over the ordered list <tt>L</tt> by the aggregation of <xref target="sec-d-3-batch-anchoring-with-merkle-trees" />.</li>
</ul>
</section>
<section anchor="sec-1-4-notation"><name>Notation</name>
<t>This document uses pseudocode in Sections <xref target="sec-2-2-statement-anchoring" format="counter" />, <xref target="sec-2-3-log-root-anchoring" format="counter" />, <xref target="sec-3-1-verification-algorithm-verify-anchor" format="counter" />, and <xref target="sec-appendix-d-ots-anchoring-protocol-construction-and-verification" />. The notation follows these conventions:</t>
<t><strong>Function and algorithm names</strong> are written in UPPER-CASE with hyphens as word separators (e.g., <tt>VERIFY-ANCHOR</tt>, <tt>OTS-DESERIALIZE</tt>). The hyphen is part of the identifier and MUST NOT be parsed as subtraction. This convention is used to distinguish algorithm names from arithmetic expressions.</t>
<t><strong>Variables and abstract objects</strong> are written in <tt>snake_case</tt> (e.g., <tt>artifact_bytes</tt>, <tt>anchor_proof</tt>). Abstract objects passed as parameters refer to terms defined in <xref target="sec-1-3-terminology" /> (Terminology); when an abstract object name appears in pseudocode, it denotes an instance of the term defined there.</t>
<t>The following abstract objects appear as pseudocode parameters and refer to defined terms:</t>
<ul>
<li><tt>AnchorLedger</tt> -- the Anchor Ledger as defined in <xref target="sec-1-3-terminology" />.</li>
<li><tt>AnchorProof</tt> -- the Anchor Proof as defined in <xref target="sec-1-3-terminology" />.</li>
<li><tt>SignedStatement</tt> -- a Signed Statement as defined in <xref target="sec-1-3-terminology" />, serialized to a canonical byte sequence prior to hashing. The exact serialization is specified by <xref target="RFC9943" /> and is out of scope for this profile.</li>
<li><tt>TransparencyLog</tt> -- the verifiable log of a Transparency Service, termed the Append-only Log in <xref target="RFC9943" /> Section 3, whose required security properties are given in Section 5.1.3 of that document.</li>
</ul>
<t>Pseudocode functions are defined where they are first needed: <tt>AnchorDigest</tt>, <tt>SHA-256</tt> and <tt>MerkleRoot</tt> in <xref target="sec-1-3-terminology" />, the functions of VERIFY-ANCHOR in <xref target="sec-3-1-verification-algorithm-verify-anchor" />, and those of the construction algorithms at the start of <xref target="sec-appendix-d-ots-anchoring-protocol-construction-and-verification" />.</t>
<t><strong>Storage and control flow:</strong></t>
<t>The pseudocode uses <tt>INSERT</tt>, <tt>SELECT</tt>, <tt>UPDATE</tt>, <tt>STORE</tt>, and <tt>LOG-ERROR</tt> as implementation-detail placeholders for persistent storage operations. These are not normative: an implementation MAY use any storage mechanism. Standard control flow keywords (<tt>IF</tt>, <tt>WHILE</tt>, <tt>FOR EACH</tt>, <tt>RETURN</tt>, <tt>ASSERT</tt>, <tt>CONTINUE</tt>, <tt>APPEND</tt>, <tt>CALL</tt>) follow the conventions of pseudocode as commonly used in the IETF (e.g., <xref target="RFC9162" />).</t>
<t>The arrow <tt>&lt;-</tt> denotes assignment. The symbols <tt>||</tt> denote concatenation over raw bytes throughout this document. The function notation <tt>f(x, y)</tt> denotes function application; brackets <tt>[a, b, c]</tt> denote ordered lists.</t>
<t><strong>Single-letter symbols in running text and the security argument:</strong> This document uses the following single-letter symbols, with meaning fixed by local context. Each occurrence specifies its referent the first time it appears in a given section.</t>
<ul>
<li><tt>A</tt> -- an artifact (byte sequence) under verification. Used in the formal security argument (<xref target="sec-5-formal-security-argument" />).</li>
<li><tt>B</tt> -- a Bitcoin block. Used in the formal security argument (<xref target="sec-5-formal-security-argument" />) and threat model (<xref target="sec-6-security-considerations" />).</li>
<li><tt>H</tt> -- a Bitcoin block height (a non-negative integer identifying a position on the canonical chain). Used in Sections <xref target="sec-1-3-terminology" format="counter" />, <xref target="sec-5-1-claim" format="counter" />, <xref target="sec-5-3-proof" format="counter" />, and throughout for the block-height binding.</li>
<li><tt>h</tt> -- a hash value (a 256-bit byte string). Used in the formal security argument (<xref target="sec-5-3-proof" />) for the hash committed in an Anchor Proof. The lower-case form distinguishes the hash value from the block height <tt>H</tt>.</li>
<li><tt>V</tt> -- the verification function defined in <xref target="sec-2-4-anchor-proof-format" />, invoked as <tt>V(ArtifactBytes, AnchorProof, AnchorLedger)</tt>.</li>
<li><tt>k</tt> -- a Bitcoin confirmation depth (a non-negative integer counting the blocks at the height of the block containing the commitment and above, including that block itself, as defined for <tt>CONFIRMATIONS-ON</tt> in <xref target="sec-3-1-verification-algorithm-verify-anchor" />).</li>
</ul>
<t>These symbols are used with the meanings above only. The distinction between <tt>H</tt> (block height) and <tt>h</tt> (hash value) is maintained throughout the document; where confusion could arise the surrounding text states the referent explicitly.</t>
</section>
</section>
<section anchor="sec-2-anchoring-model"><name>Anchoring Model</name>
<section anchor="sec-2-1-overview"><name>Overview</name>
<t>This profile adds an independent time reference to SCITT operations without modifying the SCITT architecture. A Transparency Service that implements this profile MUST anchor operations to an Anchor Ledger at one or both of the following levels:</t>
<ol>
<li><strong>Statement Anchoring</strong> (per-statement)</li>
<li><strong>Log Root Anchoring</strong> (periodic)</li>
</ol>
</section>
<section anchor="sec-2-2-statement-anchoring"><name>Statement Anchoring</name>
<t>When a Transparency Service receives a Signed Statement, it MAY compute a Temporal Anchor for that statement:</t>
<artwork>
anchor_input = SHA-256(SignedStatement)
anchor_proof = AnchorDigest("sha256", anchor_input, AnchorLedger)
</artwork>
<t>The resulting Anchor Proof is stored alongside the SCITT Receipt. Each is verified on its own, and co-location establishes no relation between the two; see <xref target="sec-4-3-receipt-association" />. Taken separately, they provide:</t>
<ul>
<li><strong>Receipt</strong>: proof of inclusion in the Transparency Service log.</li>
<li><strong>Anchor Proof</strong>: proof that the bytes of <tt>SignedStatement</tt> were committed to the Bitcoin blockchain, where the commitment is included in the block at height <tt>H</tt>. This block-height binding is the normative temporal claim of this profile; its precise definition appears in <xref target="sec-5-1-claim" />. The Reference Wall-Clock Projection <tt>block.time(H)</tt> (<xref target="sec-1-3-terminology" />) SHOULD be presented alongside <tt>H</tt> for human readability and is informative, not normative.</li>
</ul>
</section>
<section anchor="sec-2-3-log-root-anchoring"><name>Log Root Anchoring</name>
<t>A Transparency Service SHOULD periodically anchor the root of its verifiable data structure:</t>
<artwork>
log_root = MerkleRoot(TransparencyLog)
anchor_proof = AnchorDigest("sha256", log_root, AnchorLedger)
</artwork>
<t>This establishes that the root value existed before the Bitcoin block at height <tt>H</tt>. The block-height binding (<xref target="sec-1-3-terminology" />) of the log root anchor is the normative temporal reference for this checkpoint.</t>

<t><strong>What a bare root does not carry.</strong> <tt>log_root</tt> is 32 bytes. It does not identify the Transparency Service that produced it, the verifiable data structure type in use, the tree size or sequence number at which it was taken, or the epoch it belongs to. <xref target="RFC9943" /> admits multiple verifiable data structure types, and what a root denotes, and whether two roots are successive states of one log or concurrent claims about the same state, is a property of that type. Anchoring a bare root therefore establishes the existence of the value and nothing about the log it came from.</t>
<t>In practice this has three consequences. A relying party holding an anchored root cannot tell, from the anchor, which Transparency Service's log it belongs to, so an anchor cannot be attributed to a log by its holder. Two anchored roots cannot be compared: without the tree size or sequence number of each, a verifier cannot tell whether they are successive states of one log, which is consistent, or two versions of the same state, which is equivocation (<xref target="sec-6-4-threat-transparency-service-equivocation" />). And a root cannot be connected to the entries it covers, so the anchor bounds a value and not a set of registrations. A relying party that needs any of these needs the checkpoint object described next.</t>

<t>An anchor that is to support equivocation detection SHOULD therefore commit to a checkpoint object rather than to a bare root: one carrying at least the service identifier, the verifiable data structure profile, the tree size or sequence number, the root digest, the hash algorithm, and a reference to the previous checkpoint. This profile does not define such an object. Defining it is a matter for the Transparency Service specification whose log is being anchored, not for a ledger-facing profile; this document specifies what anchoring establishes about whatever byte sequence it is given.</t>
</section>
<section anchor="sec-2-4-anchor-proof-format"><name>Anchor Proof Format</name>
<t>An Anchor Proof MUST be:</t>
<ol>
<li><strong>Self-contained</strong>: Verifiable without contacting the anchoring service or the Transparency Service</li>
<li><strong>Portable</strong>: A standalone object that travels with the Receipt</li>
<li><strong>Deterministic</strong>: Given the same input bytes and ledger state, verification MUST produce the same result. Every step of <xref target="sec-3-1-verification-algorithm-verify-anchor" /> is required of a conformant verifier, so two conformant verifiers with the same view of the chain return the same output; none of them is optional in a way that would let the answer depend on which steps were chosen</li>
</ol>
<t>The verification function V is defined as follows:</t>
<artwork>
V(ArtifactBytes, AnchorProof, AnchorLedger)
    → { valid | invalid | unverifiable }
</artwork>
<t>Where:</t>
<ul>
<li><strong>ArtifactBytes</strong> -- the byte sequence being verified</li>
<li><strong>AnchorProof</strong> -- the Anchor Proof (<xref target="sec-1-3-terminology" />)</li>
<li><strong>AnchorLedger</strong> -- the Anchor Ledger (<xref target="sec-1-3-terminology" />)</li>
</ul>
<t>The short-form notation <tt>V(B, P, L)</tt> from the Anchoring Specification <xref target="ANCHORING" /> Section 4 is equivalent: this profile uses the descriptive parameter names recommended in the companion-note of <xref target="ANCHORING" /> Section 4 for clarity in running text.</t>
<section anchor="sec-2-4-1-abstract-field-requirements"><name>Abstract Field Requirements</name>
<t><strong>What the proof does not carry.</strong> Field F1 is the digest of a byte sequence. Whether that sequence is a Signed Statement (<xref target="sec-2-2-statement-anchoring" />), a log root (<xref target="sec-2-3-log-root-anchoring" />), or some other object anchored under this profile is not recorded in the proof and cannot be recovered from it. A Transparency Service SHOULD publish <tt>anchor_level</tt> among its service parameters (<xref target="sec-4-2-metadata-extension" />), but that is a statement about the service and not about any individual proof. A verifier that obtains <tt>valid</tt> for a proof has established that a 32-byte value existed no later than the block at the attested height, and nothing about what that value denotes.</t>
<t><strong>Fields carried by the proof.</strong> An Anchor Proof that carries a Bitcoin attestation MUST encode the following, regardless of serialization format. A proof that carries none is subject to the rule for pending proofs below. These are the values a verifier recovers by parsing the proof; the mapping to the OpenTimestamps serialization is given in <xref target="sec-2-4-2-opentimestamps-wire-format-mapping" />.</t>
<artwork>
+-----+-----------------+------+-----------------+------------------+
| #   | Field           | Reqd | CDDL Type       | Description      |
+-----+-----------------+------+-----------------+------------------+
| F1  | artifact_hash   | MUST | bstr .size 32   | SHA-256 digest   |
|     |                 |      |                 | of anchored byte |
|     |                 |      |                 | sequence         |
+-----+-----------------+------+-----------------+------------------+
| F2  | hash_algorithm  | MUST | "sha256"        | Algorithm        |
|     |                 |      |                 | identifier       |
+-----+-----------------+------+-----------------+------------------+
| F3  | merkle_path     | MUST | ots-path        | Ordered          |
|     |                 |      |                 | operations       |
|     |                 |      |                 | linking F1 to    |
|     |                 |      |                 | the ledger       |
|     |                 |      |                 | commitment       |
+-----+-----------------+------+-----------------+------------------+
| F5  | block_height    | MUST | uint            | Block number     |
|     |                 |      |                 | containing the   |
|     |                 |      |                 | commitment       |
+-----+-----------------+------+-----------------+------------------+
| F7  | tx_id           | MAY  | bstr .size 32   | Transaction      |
|     |                 |      |                 | identifier       |
|     |                 |      |                 | containing the   |
|     |                 |      |                 | commitment       |
+-----+-----------------+------+-----------------+------------------+
| F10 | calendar_url    | MAY  | tstr / nil      | URL of calendar  |
|     |                 |      |                 | server used for  |
|     |                 |      |                 | submission       |
+-----+-----------------+------+-----------------+------------------+
</artwork>
<t>Table 1: Fields Carried by the Anchor Proof</t>
<t><strong>Values obtained during verification.</strong> The following are not carried by the proof. F4 is the identity of the chain the verifier has validated (<xref target="sec-2-5-anchor-ledger-requirements" />). F6 and F8 are read from the Bitcoin block header the verifier has established at the height given by F5, and are therefore available only once step 7b of <xref target="sec-3-1-verification-algorithm-verify-anchor" /> has returned one. No requirement level is given, because there is nothing for the proof to encode.</t>
<artwork>
+-----+-----------------+-----------------+-------------------------+
| #   | Field           | CDDL Type       | Description             |
+-----+-----------------+-----------------+-------------------------+
| F4  | ledger_id       | tstr (64 hex)   | Genesis block hash of   |
|     |                 |                 | validated_chain (3.5).  |
|     |                 |                 | The attestation tag     |
|     |                 |                 | identifies Bitcoin, not |
|     |                 |                 | a network.              |
+-----+-----------------+-----------------+-------------------------+
| F6  | block_hash      | bstr .size 32   | Block header hash, read |
|     |                 |                 | from the header         |
|     |                 |                 | retrieved at F5         |
+-----+-----------------+-----------------+-------------------------+
| F8  | block_time      | uint            | Block header nTime;     |
|     |                 |                 | informative only, see   |
|     |                 |                 | Section 3.6             |
+-----+-----------------+-----------------+-------------------------+
</artwork>
<t>Table 2: Values Obtained During Verification</t>
<t>Confirmation depth is likewise not a field. It is derived from the state of the chain at the moment of verification and changes as the chain grows, which is why it appears in the algorithm (<xref target="sec-3-1-verification-algorithm-verify-anchor" />, step 9) and in no table here.</t>
<t><strong>Recorded by the producing implementation.</strong> The following is not an OpenTimestamps field and is not part of any proof. It is the status a producing implementation records alongside a proof in its own records, and <xref target="sec-2-6-5-pending-proofs-and-temporal-definition" /> defines what its values are permitted to mean. Metadata of this kind is non-normative and has no effect on the verification outcome; a verifier neither reads it nor requires it (<xref target="sec-2-6-5-pending-proofs-and-temporal-definition" />).</t>
<artwork>
+-----+-----------------+-----------------+-------------------------+
| #   | Field           | CDDL Type       | Description             |
+-----+-----------------+-----------------+-------------------------+
| F9  | anchor_status   | status-tstr     | "pending" / "anchored"  |
|     |                 |                 | / "failed"; see Section |
|     |                 |                 | 3.6.1                   |
+-----+-----------------+-----------------+-------------------------+
</artwork>
<t>Table 3: Status Recorded by the Producing Implementation</t>
<t>The "Reqd" column of Table 1 uses MUST and MAY per <xref target="RFC2119" />. The "CDDL Type" column gives the CDDL <xref target="RFC8610" /> type that each abstract field takes when it is represented as data; <tt>status-tstr</tt> is a tstr restricted to the three enumerated values shown in the Description column. These fields are abstract: they are values, not members of a single wire object. The CDDL definition in <xref target="sec-2-4-3-cddl-definition-of-the-proof-bundle" /> is a schema for one such object only, the Proof Bundle passed to the verifier, and defines the four fields of that bundle rather than the fields named here.</t>
<t><strong>Note on F3.</strong> The recorded operations form a tree: a linear run of operations ends either in an attestation or in a fork into two or more continuations, each replayed separately (<xref target="sec-3-1-verification-algorithm-verify-anchor" />, steps 5 to 8). As data, F3 takes the type below. This describes the abstract structure; it is not a schema for the OpenTimestamps binary encoding (<xref target="sec-2-4-3-cddl-definition-of-the-proof-bundle" />). An attestation at a point from which further operations continue, as in the proofs of <xref target="sec-appendix-e-test-vectors" />, is a fork in which one continuation is the attestation alone; several attestations at one point are a fork with one such continuation each.</t>
<artwork>
ots-path   = [* ots-op, ots-end]
ots-end    = attestation / [2* ots-path]
ots-op     = append / prepend / sha256
append     = ["append",  bstr]
prepend    = ["prepend", bstr]
sha256     = ["sha256"]
attestation = ["bitcoin", uint] / ["pending", tstr]
</artwork>
<t>The literal <tt>"sha256"</tt> is used for the F2 <tt>hash_algorithm</tt> field, aligned with the OpenTimestamps internal naming convention used throughout this profile. The same algorithm is registered as <tt>sha-256</tt> in the IANA Named Information Hash Algorithm Registry <xref target="RFC6920" />; both names refer to the function specified in <xref target="FIPS180-4" />. Implementations MUST use the literal <tt>"sha256"</tt> in the F2 field for compatibility with the canonical hash-prefix notation <tt>"sha256:" || HEX(h)</tt> employed in the algorithms of <xref target="sec-appendix-d-ots-anchoring-protocol-construction-and-verification" />.</t>
<t>Of the fields a proof carries (Table 1), F1, F2, F3 and F5 are REQUIRED once the proof carries a Bitcoin attestation, F7 is OPTIONAL and F10 is informational. F4, F6 and F8 are not carried by the proof but obtained during verification (Table 2), and F9 is recorded by the producing implementation rather than by the proof (Table 3).</t>
<t>A proof recorded by its producer with anchor_status "pending" MUST contain at minimum F1, F2 and a partial merkle_path (F3) sufficient for later upgrade; F9 itself is recorded alongside the proof by the producing implementation, not within it. The value "failed" denotes a proof whose calendar commitment was never answered; it carries no temporal claim and is an operational status only.</t>

<t>F9 is recorded alongside an Anchor Proof, and therefore has no value before a proof exists. An origin record for which submission has been deferred, as in the batch mode of <xref target="sec-d-1-construction-algorithm-anchor" />, carries no F9 value. An anchoring service MAY report such a record to its own clients under a status of its choosing, but that status is outside this profile and MUST NOT be encoded in field F9.</t>
</section>
<section anchor="sec-2-4-2-opentimestamps-wire-format-mapping"><name>OpenTimestamps Wire Format Mapping</name>
<t>This profile uses the OpenTimestamps binary format as the reference serialization. The mapping from abstract fields to OTS encoding is as follows:</t>
<artwork>
+-----+---------------------+-------------------------------------+
| #   | Abstract Field      | OTS Encoding                        |
+-----+---------------------+-------------------------------------+
| F1  | artifact_hash       | Initial hash input to the proof     |
|     |                     | chain                               |
+-----+---------------------+-------------------------------------+
| F2  | hash_algorithm      | File hash operation in the proof    |
|     |                     | header: SHA-256 (0x08). Checked at  |
|     |                     | Section 4.1, step 3.                |
+-----+---------------------+-------------------------------------+
| F3  | merkle_path         | Sequence of append (0xf0), prepend  |
|     |                     | (0xf1), and hash (0x08)             |
|     |                     | operations                          |
+-----+---------------------+-------------------------------------+
| F4  | ledger_id           | Not encoded. The Bitcoin            |
|     |                     | attestation tag                     |
|     |                     | (0x0588960d73d71901) identifies the |
|     |                     | ledger family only; ledger identity |
|     |                     | is that of validated_chain (Table   |
|     |                     | 2).                                 |
+-----+---------------------+-------------------------------------+
| F5  | block_height        | Read from the Bitcoin attestation;  |
|     |                     | the header is fetched by this height|
+-----+---------------------+-------------------------------------+
| F6  | block_hash          | Derived: verifier resolves from     |
|     |                     | Bitcoin block headers               |
+-----+---------------------+-------------------------------------+
| F7  | tx_id               | Not marked in the proof. The        |
|     |                     | operations may pass through the     |
|     |                     | transaction, but the proof does not |
|     |                     | mark where it begins or ends.       |
|     |                     | Verification does not use F7.       |
+-----+---------------------+-------------------------------------+
| F8  | block_time          | Derived: from block header          |
|     |                     | timestamp field                     |
+-----+---------------------+-------------------------------------+
| F9  | anchor_status       | Not encoded (Table 3). The proof    |
|     |                     | encodes its proof state, the        |
|     |                     | presence or absence of a Bitcoin    |
|     |                     | attestation; producer status is a   |
|     |                     | separate value (Section 3.6.1).     |
+-----+---------------------+-------------------------------------+
| F10 | calendar_url        | Encoded as pending attestation URL  |
|     |                     | in incomplete proofs                |
+-----+---------------------+-------------------------------------+
</artwork>
<t>Table 4: OTS Wire Format Mapping</t>
<t>Note: The Bitcoin attestation encodes the block height (F5) directly; the verifier reads it from the proof and fetches the block header at that height. F6 (block_hash) and F8 (block_time) are then derived from that header. F7 (tx_id) is not marked in the proof: its operations may pass through the transaction, but this profile does not specify how to recover F7 from an arbitrary proof, and verification does not need it; no transaction is retrieved from the ledger (<xref target="sec-3-1-verification-algorithm-verify-anchor" />). The proof is compact and self-contained: it carries the operations, and the verifier supplies only the header, fetched by height. F9 is listed only to state that it is not encoded; <xref target="sec-2-6-5-pending-proofs-and-temporal-definition" /> separates the proof state from the producer status. A proof that carries a Bitcoin attestation also carries the pending calendar attestations that remain from submission; its proof state is determined by the presence of the Bitcoin attestation, not by the absence of pending ones.</t>
<t>The normative semantic distinction between F5 (<tt>block_height</tt>, the block-height binding) and F8 (<tt>block_time</tt>, the Reference Wall-Clock Projection) is defined in <xref target="sec-5-1-claim" />. Both values are recoverable from the block header at height F5; the temporal claim of the proof is established by F5, with F8 presented for human readability.</t>
<t>VERIFY-ANCHOR (<xref target="sec-3-1-verification-algorithm-verify-anchor" />) accepts the OpenTimestamps serialization only, and the Proof Bundle carries no format identifier. A future specification MAY define another encoding, for example one based on CBOR (<xref target="sec-4-4-relationship-to-rfc-9921-cose-timestamp-headers" />); it would need to define both the encoding and how a verifier recognises it.</t>
</section>
<section anchor="sec-2-4-3-cddl-definition-of-the-proof-bundle"><name>CDDL Definition of the Proof Bundle</name>
<t>The following CDDL definition <xref target="RFC8610" /> specifies the Proof Bundle structure passed to VERIFY-ANCHOR (<xref target="sec-3-1-verification-algorithm-verify-anchor" />). This is the normative definition of the <tt>proof_bundle</tt> input type referenced in <xref target="sec-1-3-terminology" />.</t>
<t>The OpenTimestamps proof is carried within it as an opaque byte string. Its internal structure is a binary encoding defined by the OpenTimestamps format, and its mapping to the abstract fields of <xref target="sec-2-4-1-abstract-field-requirements" /> is given in <xref target="sec-2-4-2-opentimestamps-wire-format-mapping" />. CDDL describes CBOR and JSON data models <xref target="RFC8610" /> and is not used in this profile to describe that binary encoding.</t>
<artwork>
; CDDL definition of Proof Bundle (input to VERIFY-ANCHOR)

ProofBundle = {
  ots_proof:              bytes,   ; REQUIRED: binary .ots file
  claimed_hash: tstr .regexp "(sha256:)?[0-9a-f]{64}", ; REQUIRED
  ? origin_id:            tstr,    ; OPTIONAL: traceability id
  ? bitcoin_block_height: uint,    ; OPTIONAL: cached block height
}
</artwork>
<t>An implementation constructing a <tt>ProofBundle</tt> MUST populate <tt>ots_proof</tt> and <tt>claimed_hash</tt>. The verifier MUST recompute the hash from the artifact bytes and MUST NOT use <tt>claimed_hash</tt> as the artifact digest. <tt>claimed_hash</tt> is a consistency check only: a mismatch with the recomputed value returns <tt>invalid</tt> (<xref target="sec-3-1-verification-algorithm-verify-anchor" />, step 2), and a match establishes nothing on its own, since step 3 compares the proof with the recomputed value.</t>
</section>
</section>
<section anchor="sec-2-5-anchor-ledger-requirements"><name>Anchor Ledger Requirements</name>
<t>This profile uses the Bitcoin blockchain <xref target="NAKAMOTO" /> as the Anchor Ledger. This section defines the qualification properties that any system used as an Anchor Ledger under this profile MUST satisfy, and establishes that the Bitcoin blockchain satisfies all of them.</t>
<t>An implementation MUST NOT treat a system as an Anchor Ledger under this profile unless it satisfies all of the following properties:</t>
<ol>
<li><strong>Append-Only Property</strong>: The ledger MUST be append-only. Once data is recorded, it cannot be modified or removed without detection. Any attempt to alter historical entries MUST be computationally or economically infeasible.</li>
<li><strong>Public Accessibility</strong>: The ledger MUST be publicly accessible for verification. Any party MUST be able to independently retrieve and inspect the relevant data required to validate an Anchor Proof.</li>
<li><strong>Decentralization / No Single Controlling Authority</strong>: The ledger MUST NOT be controlled by a single trusted party -- including the proof issuer. Its integrity MUST derive from distributed consensus, cryptographic verification, or equivalent mechanisms that prevent unilateral manipulation of historical state or timestamps.</li>
<li><strong>Independent Verifiability</strong>: The ledger MUST allow independent verification of inclusion proofs without reliance on the original anchoring service. A verifier MUST be able to validate that a given commitment exists in the ledger using only publicly available data and standard verification procedures.</li>
</ol>
<t>This profile is deliberately scoped to the Bitcoin blockchain. Implementations MAY in principle use any other ledger that demonstrably satisfies the four properties above; however, this document does not specify the verification semantics for any ledger other than Bitcoin. Table 4 of <xref target="sec-2-4-2-opentimestamps-wire-format-mapping" /> uses the OpenTimestamps Bitcoin attestation for that reason, and it does not narrow the abstract requirement, which remains as stated above. A future profile MAY extend this work to additional qualifying ledgers; <xref target="sec-1-7-layering" /> sets out what it would have to supply.</t>
<t>The Bitcoin blockchain satisfies all four properties:</t>
<ul>
<li><strong>Append-only</strong>: Bitcoin's proof-of-work consensus and the cumulative work of the longest chain make alteration of historical blocks economically infeasible. See Assumption A2 (<xref target="sec-5-2-assumptions" />) for the residual probability of a rewrite and the depth of six confirmations.</li>
</ul>
<ul>
<li><strong>Public Accessibility</strong>: Bitcoin block headers and transaction data are public. Any party can run a full node, query a public block explorer, or use the Bitcoin peer-to-peer network to retrieve the data required to verify an Anchor Proof.</li>
</ul>
<ul>
<li><strong>Decentralization</strong>: Bitcoin has no central operator. The network consensus is established by globally distributed miners and full nodes operating under publicly specified consensus rules. No single entity -- including the proof issuer or the anchoring service -- can rewrite historical blocks or timestamps.</li>
</ul>
<ul>
<li><strong>Independent Verifiability</strong>: Verification of an Anchor Proof requires only the artifact bytes, the OpenTimestamps <tt>.ots</tt> file, and a validated view of the Bitcoin header chain (<xref target="sec-3-1-verification-algorithm-verify-anchor" />). No contact with the anchoring service or the OpenTimestamps Calendar Server is required (see <xref target="sec-3-2-verification-independence" />).</li>
</ul>
<t><strong>Identifying a ledger.</strong> An Anchor Ledger is identified by the hash of its genesis block, written as 64 lowercase hexadecimal characters in the byte order in which block hashes are conventionally displayed, the reverse of their internal byte order. For Bitcoin mainnet that value is <tt>000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f</tt>. The identifier is unique by construction and a verifier holding the chain can confirm it without consulting any registry or naming authority; this document therefore defines none. A human-readable label such as <tt>bitcoin-mainnet</tt> MAY accompany the identifier, but carries no uniqueness guarantee and MUST NOT be used to identify or match a ledger. The ledger identity a verifier establishes (F4, Table 2) and the service parameters of <xref target="sec-4-2-metadata-extension" /> both use the identifier in this form.</t>
<t>The qualification properties above are narrower than those of <xref target="ANCHORING" /> Section 7, and where they are narrower they govern conformance to this profile. That is not a claim of authority over the specification: Section 12 of that document reserves the last word to the specification itself, and this profile does not contradict it. A profile selects and constrains; it does not amend. A ledger that fails Section 7 of the specification is not qualified here either, and nothing in this section admits one that the specification excludes.</t>
</section>
<section anchor="sec-2-6-temporal-precision"><name>Proof States and Wall-Clock Time</name>
<section anchor="sec-2-6-5-pending-proofs-and-temporal-definition"><name>Pending Proofs and Temporal Definition</name>
<t><strong>Four distinct things are described by these words, and this document keeps them apart.</strong> The <em>proof state</em> is a property of the bytes: whether the proof carries a Bitcoin attestation at all, which is what the wire mapping of <xref target="sec-2-4-2-opentimestamps-wire-format-mapping" /> reports. The <em>confirmation state</em> is a property of the chain at the moment of asking: how many blocks stand at the attested height and above, counting the attested block itself. It changes as the chain grows and is not recorded in the proof. The <em>verification state</em> is the result a verifier returns for a given proof and a given chain view, one of <tt>valid</tt>, <tt>invalid</tt> or <tt>unverifiable</tt> (<xref target="sec-3-1-verification-algorithm-verify-anchor" />). <tt>valid</tt> is reserved for the case where the verifier has itself placed the attested block on the validated most-work chain and established that at least MIN_CONFIRMATIONS blocks stand at that height or above on it; where it has not done both, the result is <tt>unverifiable</tt>, whatever the proof's own consistency. The <em>producer status</em> is field F9: the label a producing implementation has recorded alongside a proof in its own records. A proof may carry a Bitcoin attestation, and so have a proof state that some tooling calls anchored, while its confirmation state is below the depth this profile requires and its verification state is therefore <tt>unverifiable</tt>. The states listed below are producer statuses, and <xref target="sec-6-11-threat-premature-anchored-state-false-finality" /> governs when a producing implementation may set them.</t>
<t>Anchor Proofs produced by this profile have two producer statuses with respect to the Bitcoin blockchain (field F9, <xref target="sec-2-4-1-abstract-field-requirements" />, enumerates a third, <tt>failed</tt>, which is operational and carries no temporal claim):</t>
<ul>
<li><strong>pending</strong>: the artifact hash has been submitted to one or more OpenTimestamps Calendar Servers, but no Bitcoin block containing the resulting commitment has yet been observed with sufficient confirmation depth.</li>
<li><strong>anchored</strong>: the producing implementation has upgraded the proof (<xref target="sec-d-2-upgrade-algorithm-bitcoin-confirmation" />) and has itself established that it binds to a specific block at height <tt>H</tt> which had at least six confirmations at the moment the status was set. The status records what the producer established then; it is not a claim a verifier may accept in place of its own check (<xref target="sec-3-1-verification-algorithm-verify-anchor" />), and the confirmation state it refers to is not frozen by it.</li>
</ul>
<t>The block-height binding (<xref target="sec-5-1-claim" />) is defined by the proof state and the verification, not by the producer status: it becomes available when the proof carries a Bitcoin attestation and a verifier has confirmed that the recorded operations replay to the Merkle root of the attested block. A proof that carries no Bitcoin attestation supports no normative temporal claim under this profile: the hash is in transit toward a block, but no block has yet been identified. Setting the producer status to <tt>anchored</tt> does not establish the binding; it records that the producer established it.</t>
<t>A Verifier does not read the producer status and need not be given it. It inspects the proof itself, and on finding no Bitcoin attestation MUST return <tt>unverifiable</tt> (<xref target="sec-3-1-verification-algorithm-verify-anchor" />, step 4) rather than attempting to construct a temporal claim from the calendar commitment alone. Calendar commitments are not consensus- verified; relying on them for temporal claims would reintroduce trust in the Calendar Server, which <xref target="sec-3-2-verification-independence" /> explicitly excludes.</t>
</section>
<section anchor="sec-2-6-6-relationship-to-wall-clock-time-stamping"><name>Relationship to Wall-Clock Time Stamping</name>
<t>Time Stamping Authorities (TSAs) operating under <xref target="RFC3161" /> deliver a wall-clock timestamp as their normative value. That timestamp is derived from the TSA's local clock and signed by the TSA's private key. Verification depends on trust in the TSA, its certificate chain, and the integrity of its operational clock.</t>
<t>This profile takes a structurally different approach. The normative value delivered by a verified Anchor Proof is the block-height binding (<xref target="sec-5-1-claim" />): the artifact's commitment is included in the Bitcoin block at height <tt>H</tt>. The reasons for this divergence from TSA convention are:</t>
<ul>
<li>A TSA's timestamp is the assertion of a single trusted authority based on that authority's local clock. The block-height binding is a consensus-verified fact: the block at height <tt>H</tt> contains the commitment because the Bitcoin network agreed, via proof-of-work, to include it there. The trust root differs (single authority vs. computational consensus), and this profile names the consensus-verified value as normative.</li>
</ul>
<t>Bitcoin block timestamps are miner-set within consensus bounds, and the declared value can move backward relative to the parent block: block 156114 declared a time about two hours before its parent, and block 790402 two minutes before. Such non-monotonicity is consensus-valid, and a miner may equally declare a time ahead of the moment of mining. The bounds themselves are given below.</t>
<t>These properties make <tt>block.time</tt> unsuitable as a normative wall-clock value. The block-height binding is not affected by these properties: a block exists at a specific height, or it does not, and that fact is determined by consensus.</t>
<ul>
<li>Deployed OpenTimestamps verifiers, including the upstream reference implementation <xref target="OTS" />, surface <tt>block.time(H)</tt> as the temporal value associated with a verified proof. This profile preserves that behaviour by defining <tt>block.time(H)</tt> as the Reference Wall-Clock Projection (<xref target="sec-1-3-terminology" />) and recommending its presentation alongside <tt>H</tt> for human readability.</li>
</ul>
<t>The result is a two-layer model: the block-height binding is normative and exact, while the Reference Wall-Clock Projection is informative and subject to the bounds below. Separating what is proved from what is displayed lets this profile claim only what Bitcoin consensus guarantees while still showing a familiar wall-clock value.</t>
<t><strong>What the projection bounds, and what it does not.</strong> The value is not arbitrary. Consensus places it above the Median Time Past of the eleven preceding blocks, and no more than approximately two hours ahead of the clock of each node that accepted the block. These are two constraints of different kinds. They are not equal offsets around a common reference time: the lower bound is relative to the declared times of eleven earlier blocks, the upper bound to the clock of each validating node. Those two bounds are the whole of its security properties: within them a miner chooses the value freely. The projection therefore supports no claim finer than those two limits allow, and in particular no ordering claim between artifacts anchored in nearby blocks. The block-height binding is unaffected, because those limits are a property of a declared timestamp and not of block inclusion.</t>
<t><strong>Relationship to NOW().</strong> <tt>NOW()</tt> (<xref target="sec-appendix-d-ots-anchoring-protocol-construction-and-verification" />) is the anchoring service's own clock, read when an artifact is submitted. <tt>block.time(H)</tt> is a miner's declared clock for the block that later carries the commitment. They are unrelated clocks and neither is authoritative for this profile. A recorded submission time MAY read later than the <tt>block.time(H)</tt> of the block that anchors it, since a miner may declare a time ahead of the moment of mining; that is not evidence of a fault in either value. The only ordering this profile establishes between the two events is the one the block-height binding gives, and it runs in a single direction: the artifact hash existed before block H.</t>
<t><strong>Neither value carries a legal presumption under this profile.</strong> Some jurisdictions attach evidentiary presumptions to particular time stamping mechanisms: Regulation (EU) No 910/2014 (eIDAS), Article 41(2), attaches to a qualified electronic time stamp a presumption of the accuracy of the date and time it indicates. This profile establishes a block-height binding and assigns no presumption of that kind to it, nor does it claim one. A deployment that requires such a presumption requires a qualified electronic time stamp, which this profile does not provide and which <xref target="sec-4-4-relationship-to-rfc-9921-cose-timestamp-headers" /> describes carrying alongside an Anchor Proof. What weight a forum gives any exhibit is outside the scope of this document.</t>
</section>
</section>
<section anchor="sec-on-chain-footprint-and-latency"><name>On-Chain Footprint and Latency</name>
<t>This section is informative.</t>
<t>Anchoring adds no Bitcoin transaction per artifact. An anchoring service aggregates the digests it receives into one root per batch (<xref target="sec-d-3-batch-anchoring-with-merkle-trees" />) and submits that root to one or more OpenTimestamps calendars. A calendar aggregates the roots it receives from all of its submitters and commits once per round, in a single transaction carrying a 32-byte OP_RETURN output. The number of transactions is therefore set by the calendars' commit rounds, not by the number of artifacts anchored nor by the number of anchoring services submitting. A service that submits each root to several calendars for redundancy adds no transactions of its own: each calendar commits on its own schedule whatever it has received. An OP_RETURN output is provably unspendable and is not kept in the set of unspent transaction outputs that nodes maintain.</t>
<t>A proof becomes verifiable as valid only after three intervals have passed: the anchoring service's batching interval, the calendar's commit round together with the mining of its transaction, and the confirmation depth of <xref target="sec-6-11-threat-premature-anchored-state-false-finality" />. The confirmation depth counts the block that carries the commitment, so MIN_CONFIRMATIONS of six requires five further blocks, about fifty minutes on average at Bitcoin's ten-minute target. This profile fixes neither of the first two intervals. A deployment states its own. <xref target="sec-3-3-error-conditions" /> records the intervals observed in one deployment between submission and a Bitcoin attestation.</t>
<t>For a relying party the latency affects when a proof is available, not what it establishes: the block-height binding is the same whether the proof is completed after an hour or a day. A use case that needs a temporal reference at the moment of registration has, until the proof completes, only the Transparency Service's own registration time, which is the gap this profile exists to close (<xref target="sec-1-introduction" />). The motivating example of <xref target="sec-1-1-motivating-example" /> is of this kind: the regulator's question concerns the ordering of a registration against a later public event, which an anchor completed hours later still answers, since the later event has its own place in time. A use case that must act on an independent temporal reference within minutes of registration is not served by this profile, and <xref target="sec-5-4-strength-and-limitations" /> names the complementary mechanism for finer wall-clock precision.</t>
</section>
</section>
<section anchor="sec-3-verification-procedure"><name>Verification Procedure</name>
<t>This section defines the normative verification algorithm for Anchor Proofs produced under this profile. An implementation that claims conformance to this profile MUST implement the procedure specified in <xref target="sec-3-1-verification-algorithm-verify-anchor" />.</t>
<section anchor="sec-3-1-verification-algorithm-verify-anchor"><name>Verification Algorithm (VERIFY-ANCHOR)</name>
<t>The verification function V (<xref target="sec-2-4-anchor-proof-format" />) is instantiated as follows, with <tt>artifact_bytes</tt> for ArtifactBytes, <tt>proof_bundle</tt> for AnchorProof, and <tt>validated_chain</tt> for AnchorLedger. The third input is the verifier's own validated view of the chain (see <tt>validated_chain</tt> below): the algorithm reads the ledger from it and obtains it from nowhere else, so what a verifier may conclude is bounded by what it holds before verification begins. The algorithm is header-only: the only data it consults from outside the proof bundle is Bitcoin block headers. No transaction is retrieved from the ledger and no OP_RETURN output is inspected; the transaction bytes that appear in the replay are operands carried within the proof itself (<xref target="sec-e-4-proof-structure"/>), not data fetched during verification. This follows from the OpenTimestamps attestation format rather than from a simplification adopted here. A Bitcoin attestation records a block height and no further block data; the digest produced by replaying the proof's recorded operations is the Merkle root of that block's header <xref target="OTS" />.</t>
<t>The algorithm takes the artifact itself as input. A party holding only the commitment, and not the bytes it was computed from, cannot perform steps 1 and 2 and therefore cannot establish which artifact, if any, the proof binds; the remaining steps establish that the recorded hash existed no later than the attested block, and nothing about what that value denotes. Because what is transmitted between parties is the commitment rather than the artifact, this boundary applies to any party other than a holder of the artifact.</t>
<t><strong>Functions used.</strong> The algorithm below uses <tt>SHA-256</tt> (<xref target="sec-1-3-terminology" />) and the following functions.</t>
<t><strong>Bitcoin protocol operations:</strong></t>
<ul>
<li><tt>validated_chain</tt> -- the verifier's own validated view of the Bitcoin most-work chain: a full or pruned node under its control which has validated the chain, or a header set whose proof-of-work the verifier has itself verified from the genesis block or from a checkpoint it holds. It is an input to <xref target="sec-3-1-verification-algorithm-verify-anchor" />, not something that algorithm obtains while running. Bytes reported by a third party, a block explorer among them, are not a validated chain, and a verifier holding no such view cannot complete step 7b or step 9. Defined operationally per <xref target="NAKAMOTO" />.</li>
<li><tt>FETCH-BLOCK-HEADER-BY-HEIGHT(H)</tt> -- retrieves 80 bytes purporting to be the Bitcoin block header at height <tt>H</tt>, from whatever source the verifier uses, and returns <tt>NULL</tt> where no such bytes are obtained. This function performs retrieval and nothing else: it makes no statement about which chain the bytes belong to, and what it returns is a candidate until <tt>VALIDATE-HEADER-ON-CHAIN</tt> has established otherwise. The one restriction on its source is the exclusion stated in the note on header sources in <xref target="sec-3-1-verification-algorithm-verify-anchor" />.</li>
<li><tt>VALIDATE-HEADER-ON-CHAIN(validated_chain, H, candidate)</tt> -- returns <tt>candidate</tt> where <tt>validated_chain</tt> establishes that the block header at height <tt>H</tt> is exactly those 80 bytes, and <tt>NULL</tt> in every other case, including where the view does not reach height <tt>H</tt>. This function is what distinguishes a header the verifier has placed on the validated most-work chain from one that was reported to it, and a verifier that treats retrieval as sufficient has not performed it. Defined operationally per <xref target="NAKAMOTO" />.</li>
<li><tt>CONFIRMATIONS-ON(validated_chain, H)</tt> -- returns the number of blocks <tt>validated_chain</tt> establishes at height <tt>H</tt> and above, counting the block at <tt>H</tt> itself. A verifier with access to a node it operates obtains this from the chain tip; a verifier working from a stored, validated header set obtains it from the extent of that set, and needs no network access to do so. The count is read from the same view that <tt>VALIDATE-HEADER-ON-CHAIN</tt> consults and from no other source. Returns <tt>NULL</tt> where that view cannot establish the count.</li>
</ul>
<t><strong>OpenTimestamps operations:</strong></t>
<t>The following functions are operations on OpenTimestamps proof structures, as documented in <xref target="OTS" /> and <xref target="OTS-DESIGN" />. The normative semantics required by this document are specified in <xref target="sec-appendix-d-ots-anchoring-protocol-construction-and-verification" /> and in <xref target="sec-3-1-verification-algorithm-verify-anchor" />, with one exception: <tt>OTS-DESERIALIZE</tt>, whose input format this profile does not specify. Beyond deserialisation, a conformant implementation need only implement the algorithms specified here and need not depend on any particular OTS software library. See <xref target="sec-5-2-1-note-on-assumption-a4" />.</t>
<ul>
<li><tt>OTS-DESERIALIZE(ots_bytes)</tt> -- parses a binary <tt>.ots</tt> proof into an internal structure, yielding the file hash operation recorded in the proof header (<tt>file_hash_op</tt>), the message the proof commits to (<tt>msg</tt>), the recorded operation sequence, and the attestations that terminate its branches. Returns <tt>NULL</tt> if parsing fails. The serialisation format it consumes is defined by <xref target="OTS" />. It is the one operation in this list for which this profile gives no algorithm; see <xref target="sec-5-2-1-note-on-assumption-a4" />.</li>
<li><tt>OTS-ATTESTED-HEIGHT(branch)</tt> -- returns the Bitcoin block height recorded in the Bitcoin attestation that terminates a single branch of the proof. The attestation records the height and no other block data. A proof carrying more than one Bitcoin branch is evaluated branch by branch, and the lowest height among the branches that verified is selected; see <xref target="sec-3-1-verification-algorithm-verify-anchor" />, steps 6 and 8, and the note on multiple attestations.</li>
<li><tt>OTS-REPLAY(branch, hash)</tt> -- replays the sequence of append, prepend and SHA-256 operations along a single Bitcoin branch of the proof, starting from <tt>hash</tt>, and returns the resulting 32-byte digest. The OpenTimestamps format admits further operations, including hash functions other than SHA-256, and Assumption A1 (<xref target="sec-5-2-assumptions" />) is stated for SHA-256 alone; a verifier conforming to this profile MUST NOT replay a branch containing any operation other than append, prepend and SHA-256. For such a branch the function returns <tt>NULL</tt>, as it does when the replay cannot be completed for any other reason, and <xref target="sec-3-1-verification-algorithm-verify-anchor" /> step 8 classifies the branch as unevaluated rather than as failing. Under this profile the resulting digest is the Merkle root of that branch's block header. A proof carrying more than one Bitcoin attestation forms a branching structure, and each branch is replayed separately. See <xref target="sec-3-1-verification-algorithm-verify-anchor" />, step 5.</li>
</ul>
<t><strong>Encoding utilities:</strong></t>
<ul>
<li><tt>HEX(b)</tt> -- the lowercase hexadecimal string representation of byte sequence <tt>b</tt>, with no separators or prefixes (e.g., <tt>HEX(0xAB12) = "ab12"</tt>).</li>
<li><tt>STRIP-PREFIX(s)</tt> -- given a string <tt>s</tt> of the form <tt>"sha256:" || h</tt>, returns the substring <tt>h</tt>. If no prefix is present, returns <tt>s</tt> unchanged.</li>
</ul>
<artwork>
Algorithm: VERIFY-ANCHOR(artifact_bytes, proof_bundle,
                         validated_chain)

Input:
  artifact_bytes  -- the original artifact byte sequence
  proof_bundle    -- ots_proof (.ots), claimed_hash, origin_id
  validated_chain -- the verifier's own validated view of the
                     Bitcoin most-work chain (Section 4.1).
                     A verifier holding none cannot return
                     valid; see step 7b.

Output:
  { valid, invalid, unverifiable }

Steps:

1.  RECOMPUTE HASH
    computed_hash &lt;- SHA-256(artifact_bytes)
    // The verifier MUST recompute the hash from the original
    // bytes and MUST NOT rely on any claimed hash value.

2.  COMPARE HASH
    IF HEX(computed_hash) != STRIP-PREFIX(claimed_hash):
      RETURN invalid

3.  PARSE OTS PROOF
    parsed &lt;- OTS-DESERIALIZE(proof_bundle.ots_proof)
    IF parsed IS NULL
       OR parsed.file_hash_op != SHA-256
       OR parsed.msg != computed_hash:
      RETURN invalid
    // The proof header records the operation that produced msg.
    // Under this profile it is SHA-256 (OTS tag 0x08, field F2);
    // a proof recording any other operation is not a proof of
    // this profile.

4.  CHECK PROOF STATUS
    IF parsed carries no Bitcoin attestation
       (tag 0x0588960d73d71901):
      RETURN unverifiable
      // Calendar commitment only; not yet on the ledger.

5.  REPLAY EACH BITCOIN BRANCH
    // A proof is a tree. Each Bitcoin attestation terminates
    // its own branch of the recorded operations. Steps 5 to 8
    // keep each branch's digest and height together and MUST
    // NOT pair them across branches. Every operand is carried
    // in the proof; the verifier supplies only the artifact
    // hash, and the branch includes the blinding nonce
    // appended at submission (Section 5.3, step 2).
    FOR EACH Bitcoin branch b IN parsed:
      b.digest &lt;- OTS-REPLAY(b, computed_hash)

6.  READ EACH BRANCH'S ATTESTED HEIGHT
    FOR EACH Bitcoin branch b IN parsed:
      b.H &lt;- OTS-ATTESTED-HEIGHT(b)
    // Each attestation records its own height and no other
    // block data; heights need not agree across branches.
    // See the note on multiple attestations below.

7.  ESTABLISH EACH BRANCH'S BLOCK HEADER
    // Retrieval and validation are separate. A verifier that
    // performs 7a and omits 7b has obtained bytes, not a
    // header on the validated most-work chain, and the two
    // are not interchangeable at any later step.

7a. RETRIEVE A CANDIDATE HEADER, PER BRANCH
    FOR EACH Bitcoin branch b IN parsed:
      b.candidate &lt;- FETCH-BLOCK-HEADER-BY-HEIGHT(b.H)
    // 80 bytes each, from whatever source the verifier uses,
    // subject to the exclusion in the note on header sources
    // below. These bytes are a claim about the chain and not
    // yet a finding of this algorithm.

7b. PLACE EACH CANDIDATE ON THE VALIDATED CHAIN
    FOR EACH Bitcoin branch b IN parsed:
      b.header &lt;- VALIDATE-HEADER-ON-CHAIN(validated_chain,
                                           b.H, b.candidate)
    // This step, not 7a, is what the rest of the algorithm
    // relies on. It returns NULL where validated_chain does
    // not establish that the header at b.H is exactly those
    // bytes, including where the verifier holds no such view
    // at all; the branch is then left unevaluated and is
    // handled at step 8. A verifier that cannot perform this
    // step for any branch cannot reach step 11.

8.  CLASSIFY EACH BRANCH, THEN COMBINE
    FOR EACH Bitcoin branch b IN parsed:
      IF b.digest IS NULL:
        b.state &lt;- unsupported   // step 5 could not replay it
      ELSE IF b.header IS NULL:
        b.state &lt;- unavailable   // step 7b established none
      ELSE IF b.digest = b.header.merkle_root:
        b.state &lt;- verified
      ELSE:
        b.state &lt;- mismatched
    verified &lt;- { b IN parsed : b.state = verified }
    IF verified IS EMPTY:
      unevaluated &lt;- { b IN parsed :
                         b.state = unavailable
                         OR b.state = unsupported }
      IF unevaluated IS NOT EMPTY:
        RETURN unverifiable
      RETURN invalid
    // A branch is evaluated only when it was replayed and
    // its header established. Both failures leave it
    // unevaluated: neither is evidence against the proof,
    // and either may resolve later, one when the header
    // becomes available and the other under a profile that
    // covers the operation. One verified branch suffices.
    // The proof is invalid only when every branch was
    // evaluated and none replayed to the Merkle root of its
    // own block. That comparison is the whole of the ledger
    // check: the artifact hash resolves, through a branch's
    // recorded operations, to a value committed by Bitcoin
    // proof-of-work.
    b* &lt;- the branch in verified with the lowest b.H
    H &lt;- b*.H
    header &lt;- b*.header
    // Lowest verified height: the tightest bound (7.1).
    // Both digests are compared as internal byte order, the
    // order in which OTS-REPLAY produces the digest and in
    // which merkle_root appears in the 80-byte header. A
    // value read from a block explorer is typically
    // byte-reversed; see Appendix D.3.

9.  CHECK CONFIRMATION DEPTH
    k &lt;- CONFIRMATIONS-ON(validated_chain, H)
    IF k IS NULL OR k &lt; MIN_CONFIRMATIONS:
      RETURN unverifiable
    // MIN_CONFIRMATIONS is 6 under this profile
    // (Assumption A2). Below that depth the immutability
    // assumption is not yet sound, and a count the verifier
    // could not establish is not a count. This step counts
    // the blocks at H and above on the validated chain,
    // including the block at H itself; it does not ask
    // what the current tip is. A verifier holding a stored
    // header set that extends far enough past H can answer
    // it without network access. See the note on
    // confirmation depth below.

10. DERIVE THE TEMPORAL VALUES
    block_height_H  &lt;- H
    // The normative binding (Section 7.1). Exact.
    block_time_RWCP &lt;- header.nTime
    // The Reference Wall-Clock Projection (Section 2.2).
    // Informative, miner-set, bounded by the uncertainty in
    // Section 3.6.2. Verifiers SHOULD present it alongside
    // block_height_H for human readability and MUST NOT
    // present it as the normative temporal value.
    //
    // OPTIONAL: a verifier MAY compute the Median Time Past
    // per [BIP113] from the eleven preceding headers as a
    // strict lower bound on block_time_RWCP.

11. RETURN valid
    // These exact bytes were committed to the Bitcoin block
    // at height block_height_H and therefore existed before
    // that block was mined. No authorship, ownership, or
    // identity claim is made. See Section 7.5.
</artwork>
<t>Figure 3: VERIFY-ANCHOR Algorithm</t>

<t><strong>Note on header sources.</strong> Steps 7a, 7b and 9 are the only points at which a verifier consults anything outside the proof bundle, and all three require only block-header data. Step 7a reaches outside for candidate bytes; steps 7b and 9 read <tt>validated_chain</tt>, which the verifier brought with it. A verifier MUST NOT obtain block headers from the anchoring service whose proof is under verification: the issuer of a proof cannot also be the oracle for the ledger against which it is checked. Where the anchoring service publishes <tt>anchor_ledger</tt> (<xref target="sec-4-2-metadata-extension" />), the verifier MUST use a <tt>validated_chain</tt> of the ledger it identifies: one validated from the block with that hash, or from a checkpoint the verifier holds on that ledger.</t>
<t>Beyond that exclusion, the requirement is not about which party supplies the bytes but about what the verifier has established, which is why step 7a places no restriction on the source and step 7b decides the outcome. A header is of use to step 8 only where step 7b returned it. Validating the chain, or verifying the proof-of-work of the header chain from a checkpoint the verifier already trusts, is what allows that step to return anything at all. Receiving the same bytes from two independent services does not: agreement between sources is corroboration, not validation, and neither source has demonstrated the work behind the chain it reports.</t>
<t>A verifier that cannot establish this returns <tt>unverifiable</tt> rather than <tt>valid</tt>, and reaches that outcome through step 7b and step 8 rather than through a reader's diligence. This is not a defect in such a verifier. It is the accurate report of its position: the proof is internally consistent, and the verifier is not in a position to say which chain the attested block belongs to. Reporting <tt>valid</tt> in that position would assert the conclusion of the claim in <xref target="sec-5-1-claim" /> while leaving one of its conditions unchecked.</t>

<t><strong>Note on confirmation depth.</strong> Step 9 is required. An earlier revision of this profile made it RECOMMENDED, which left <tt>valid</tt> meaning two things: a verifier that performed the step and one that did not returned the same word for proofs of very different standing, and the Theorem of <xref target="sec-5-1-claim" /> had to carry the depth as a premise the reader supplied. It is required here so that the word carries one meaning. A verifier that cannot establish the depth is not obliged to guess; it returns <tt>unverifiable</tt>, which reports its position accurately. Step 8 alone still establishes the block-height binding against a header that Bitcoin proof-of-work has committed, and a verifier is free to present that finding; what it may not do is call the result <tt>valid</tt>. The step counts the blocks at the attested height and above on the validated chain, including the attested block itself, and does not ask what the current tip is, so a verifier working from a stored header set that extends far enough past it can perform the step without network access (see <xref target="sec-5-7-ledger-availability" />). Producing implementations are separately constrained: they SHOULD NOT promote a proof from "pending" to "anchored" (field F9, <xref target="sec-2-4-1-abstract-field-requirements" />) before the attested block has reached MIN_CONFIRMATIONS.</t>

<t><strong>Note on why no transaction is retrieved.</strong> A Bitcoin attestation deliberately records the block height and nothing more. The OpenTimestamps reference implementation states the reason directly: recording additional block data would encourage implementations to accept it in place of an independent header lookup <xref target="OTS" />. Retrieving the transaction, extracting an OP_RETURN output and comparing that value would substitute a transaction-serving service for proof-of-work. It would also be redundant, since the recorded operation sequence already passes through the transaction and the block's transaction tree, terminating at the header Merkle root.</t>

<t><strong>Note on multiple attestations.</strong> A hash submitted to more than one Calendar Server may, once upgraded, carry a Bitcoin attestation from each. The attestations are independent: each records its own height, each terminates its own branch of the recorded operation sequence, and each branch replays to the Merkle root of its own block header. The heights need not agree, since calendars commit in different blocks. Steps 5 through 8 evaluate every such branch, pairing each branch's digest with the height read from the same branch. A proof is valid if any branch verifies; step 8 selects the lowest height among the branches that verified, since that height yields the tightest bound on the artifact's existence. A producing implementation applies the same rule (<xref target="sec-d-2-upgrade-algorithm-bitcoin-confirmation" />); a rule that selects an arbitrary attestation yields a bound that is sound but needlessly loose, and that may vary between runs.</t>

<t><strong>Note on reorganisation.</strong> The attested height is invalidated only by a chain reorganisation deep enough to displace the block at height H. In that event the header at height H carries a different Merkle root and step 8 returns <tt>invalid</tt>. No separate reorganisation check is required. Step 9 bounds the probability of this occurring after a proof has once verified.</t>

<t><strong>Note on input size.</strong> A verifier handling proofs from untrusted sources SHOULD bound the size of <tt>ots_proof</tt>, and the length of the variable-length integers within it, before calling <tt>OTS-DESERIALIZE</tt>. This profile does not fix values for those bounds.</t>

</section>
<section anchor="sec-3-2-verification-independence"><name>Verification Independence</name>
<t>A conformant verifier MUST be able to complete the VERIFY-ANCHOR procedure using ONLY:</t>
<ol>
<li>The artifact bytes (possessed by the verifier)</li>
<li>The .ots proof file (portable, self-contained)</li>
<li>Access to independently validated Bitcoin chain data, obtained without reference to the anchoring service</li>
</ol>
<t>A conformant verifier MUST NOT require:</t>
<ul>
<li>Contact with any anchoring service or Transparency Service</li>
<li>An API key, account, or authentication credential</li>
<li>Trust in any certificate authority</li>
<li>The Calendar Server that issued the pending commitment</li>
</ul>
<t>Item 3 states what the verifier must be able to establish, not who may supply the bytes. Step 7b of <xref target="sec-3-1-verification-algorithm-verify-anchor" /> is where that is decided: the verifier must be able to place the attested block on the validated most-work chain it holds, and returns <tt>unverifiable</tt> where it cannot. This does not weaken independence; it is the stronger reading of it. A verifier that validates the chain itself depends on no one, whereas one that accepts a header on report has substituted a new party for the one this section excludes.</t>
<t>This satisfies the Independence Requirement defined in <xref target="ANCHORING" /> Section 9: verification is possible even if the anchoring service ceases to exist.</t>
</section>
<section anchor="sec-3-3-error-conditions"><name>Error Conditions</name>
<t>The VERIFY-ANCHOR algorithm produces three possible outputs. The set is fixed. An implementation of this profile MUST return exactly one of <tt>valid</tt>, <tt>invalid</tt> or <tt>unverifiable</tt>, and MUST NOT return intermediate, partial or qualified results outside that set. A profile may narrow when each output is returned, as this one does, but MUST NOT add a fourth. Implementations MUST handle each as follows:</t>
<t>The distinction between <tt>invalid</tt> and <tt>unverifiable</tt> is between contradiction and absence. <tt>invalid</tt> means the verifier established something that contradicts the claim. <tt>unverifiable</tt> means it could not establish the claim, and covers the case where a candidate header could not be placed on the validated most-work chain at step 7b as well as the case where step 7a retrieved nothing: in neither is the branch evaluated. A verifier MUST NOT report <tt>valid</tt> where the block-height binding rests on a header it cannot so place, since the table below attaches that binding to the <tt>valid</tt> result.</t>
<artwork>
+----------------+------------------------------------------+
| Output         | Meaning                                  |
+----------------+------------------------------------------+
| valid          | The artifact bytes match the anchored    |
|                | hash, the recorded operation sequence    |
|                | resolves to the Merkle root of the       |
|                | attested block header, and the           |
|                | confirmation depth check passed. The     |
|                | block-height binding (Section 7.1) is    |
|                | established: the artifact was committed  |
|                | in the block at height H and therefore   |
|                | existed before that block was mined.     |
+----------------+------------------------------------------+
| invalid        | The artifact does not match the anchored |
|                | hash (step 2), the proof fails to parse  |
|                | or does not record SHA-256 over the      |
|                | recomputed hash (step 3), or the         |
|                | replayed digest does not equal the       |
|                | Merkle root of the header at the         |
|                | attested height (step 8). The verifier   |
|                | SHOULD treat this as a verification      |
|                | failure.                                 |
+----------------+------------------------------------------+
| unverifiable   | The proof carries no Bitcoin attestation |
|                | (step 4); no Bitcoin branch verified and |
|                | at least one could not be evaluated,     |
|                | because its operations fall outside this |
|                | profile (step 5) or no header could be   |
|                | retrieved or placed on the validated     |
|                | chain at its attested height (steps 7a   |
|                | and 7b); or the attested block has not   |
|                | reached MIN_CONFIRMATIONS (step 9). The  |
|                | verifier SHOULD retry after a delay. A   |
|                | pending proof MAY become verifiable      |
|                | after Bitcoin confirmation.              |
+----------------+------------------------------------------+
</artwork>
<t>Table 5: VERIFY-ANCHOR Output Semantics</t>
<t><strong>Why two failure outcomes.</strong> A verifier that cannot reach a conclusion and a verifier that has reached a negative one are reporting different facts, and collapsing them loses the difference. <tt>unverifiable</tt> says the procedure could not be completed: no Bitcoin attestation is present yet, no header could be established at the attested height, or the depth could not be determined. Each of those has its own prospect of resolving, set out below. <tt>invalid</tt> says the procedure completed and contradicted the proof: the recomputed hash did not match, or every branch was evaluated and none replayed to the Merkle root of its own block header. That does not resolve later. A single outcome would report a network failure and a forged proof identically, and a relying party would have to treat the first as the second or the second as the first; neither is acceptable. The distinction also keeps the burden where it belongs, since a verifier that cannot establish a fact is required to say so rather than to guess.</t>
<t><strong>Retrying after unverifiable.</strong> An <tt>unverifiable</tt> outcome is not a single condition and does not carry a single expectation. A verifier that reports one SHOULD indicate which of the following it encountered, so that a relying party can decide whether to wait or to stop.</t>
<t>No Bitcoin attestation is present. The commitment has been made but not yet aggregated into a block. This resolves without action once a Calendar Server includes it. In one deployment the author has observed intervals of six minutes, forty-four minutes and one hour fifty-three minutes across three submission batches in August 2026, measured by a producing implementation that polls on a five-minute cycle, so each figure is an upper bound at that resolution. This interval is a property of the Calendar Servers rather than of this profile; <xref target="sec-2-6-5-pending-proofs-and-temporal-definition"/> gives the producing side of the same condition. It becomes permanent only if no Calendar Server ever aggregates the commitment, which a producing implementation detects by the elapsed time since submission rather than by re-verification.</t>
<t>The confirmation depth is not yet met. The attested block exists and the digest matched, but fewer than MIN_CONFIRMATIONS blocks have been established above it. This resolves at the ledger's own rate, and the number of blocks still required is known: a verifier reporting this SHOULD report the depth it established alongside the requirement, so that the wait is calculable rather than open-ended.</t>
<t>No header could be established at the attested height. This is a property of the verifier's access to the ledger rather than of the proof, and it resolves as soon as that access is restored or the locally validated chain reaches the attested height. It is permanent only where the attested height lies beyond any chain the verifier can validate, which is a defect in the proof rather than a transient condition, and repeated retrying will not change it.</t>
<t>An <tt>invalid</tt> outcome carries no such prospect. The procedure completed and the proof was contradicted, and retrying the same proof against the same artifact will produce the same result.</t>
<t><strong>What an outcome reports.</strong> An outcome is the result of the procedure in <xref target="sec-3-1-verification-algorithm-verify-anchor" /> and nothing beyond it. A <tt>valid</tt> outcome establishes that the digest supplied to the verifier was committed to the Bitcoin block at the attested height and that the required confirmation depth was met. It does not establish what the anchored bytes denote (<xref target="sec-2-4-1-abstract-field-requirements" />): whether they are a Signed Statement, a log root, or some other object is not carried in the proof. Where a service publishes <tt>anchor_level</tt> or an object type among its service parameters (<xref target="sec-4-2-metadata-extension" />), a report is clearer when it presents that as context supplied alongside the proof rather than as a property the proof verified, and clearer still when a property never claimed remains distinguishable from a property claimed but not established. What an operation then does with an outcome, including whether it proceeds on a <tt>valid</tt> result alone, is the policy of the relying party and not part of this procedure.</t>
</section>
</section>
<section anchor="sec-appendix-d-ots-anchoring-protocol-construction-and-verification"><name>OTS Anchoring Protocol -- Construction and Verification</name>
<t>Sections <xref target="sec-d-1-construction-algorithm-anchor" format="counter" /> through <xref target="sec-d-3-batch-anchoring-with-merkle-trees" format="counter" /> are informative examples of a compliant construction. This section supports Assumption A4 (<xref target="sec-5-2-assumptions" />). It defines the construction algorithms for OpenTimestamps-based anchoring as used in this document, and it is worth being exact about what that covers. The verification side of this profile can be implemented from this document alone, given the recorded operation sequence: an implementer who holds that sequence needs nothing further from any OpenTimestamps software. The producing side cannot. The byte encoding produced by OTS-SERIALIZE, the wire representation of branching and of terminal attestations, the Calendar Server protocol invoked at <xref target="sec-d-1-construction-algorithm-anchor" />, step 4, and <xref target="sec-d-3-batch-anchoring-with-merkle-trees" />, step 4, and the form of the commitments returned over that protocol are none of them specified here; they are imported from <xref target="OTS" />, as <xref target="sec-5-2-1-note-on-assumption-a4" /> records. The pseudocode gives the order of operations and the values, not the encoding. These algorithms correspond to the reference implementation described in <xref target="sec-appendix-c-implementation-status" />, but do not depend on it. One exception is recorded there: the confirmation-depth gate of <xref target="sec-d-2-upgrade-algorithm-bitcoin-confirmation" />, step 2f, is specified here and is performed by one of the two producing deployments and not by the other.</t>
<t>For background on the OpenTimestamps protocol design, see <xref target="OTS-SITE" /> and <xref target="OTS-DESIGN" />.</t>
<t><strong>Functions used.</strong> The algorithms of this section use the functions listed in <xref target="sec-3-1-verification-algorithm-verify-anchor" /> and the following.</t>
<t><strong>OpenTimestamps operations:</strong></t>
<ul>
<li><tt>OTS-SERIALIZE(digest, operations, commitments)</tt> -- produces a binary <tt>.ots</tt> proof structure containing the 32-byte input digest, the ordered operation sequence recorded against it, and the given commitments. In the direct mode of <xref target="sec-d-1-construction-algorithm-anchor" /> the operation sequence is empty and the proof carries the digest and the commitments alone. In the batch mode of <xref target="sec-d-3-batch-anchoring-with-merkle-trees" /> it is the sequence built in steps 2 and 3: the append of the origin's nonce, the hash that produces its leaf, and the sibling operations that carry the leaf onward.</li>
<li><tt>OP-APPEND(v)</tt>, <tt>OP-PREPEND(v)</tt> and <tt>OP-SHA256</tt> -- denote recorded operations, not operations performed: they name an entry in an operation sequence which a verifier will later replay. <tt>OP-APPEND(v)</tt> records "concatenate <tt>v</tt> to the right of the running value", <tt>OP-PREPEND(v)</tt> records "concatenate <tt>v</tt> to the left of it", and <tt>OP-SHA256</tt> records "replace the running value by its SHA-256 digest". Used in <xref target="sec-d-3-batch-anchoring-with-merkle-trees" />.</li>
<li><tt>OTS-UPGRADE(pending_proof)</tt> -- contacts a Calendar Server and merges into the proof any Bitcoin Merkle path it returns. It returns serialised <tt>.ots</tt> bytes where the merged proof carries at least one Bitcoin attestation, or where the call merged anything into it, and returns <tt>NULL</tt> only where the call merged nothing and the merged proof carries no Bitcoin attestation. The condition is on the content of the merged proof, not on what a single call retrieved. See <xref target="sec-d-2-upgrade-algorithm-bitcoin-confirmation" />.</li>
</ul>
<t><strong>Encoding utilities:</strong></t>
<ul>
<li><tt>DECODE-HEX(s)</tt> -- the byte sequence represented by the hexadecimal string <tt>s</tt>. The inverse of <tt>HEX</tt>. Used in <xref target="sec-d-2-upgrade-algorithm-bitcoin-confirmation" />, step 2, and <xref target="sec-d-3-batch-anchoring-with-merkle-trees" />, step 2.</li>
<li><tt>LENGTH(b)</tt> -- the number of bytes in the byte sequence <tt>b</tt>.</li>
<li><tt>RANDOM-BYTES(n)</tt> -- returns <tt>n</tt> bytes from a cryptographically secure random source. Used for the blinding nonce in <xref target="sec-d-3-batch-anchoring-with-merkle-trees" />, step 2.</li>
<li><tt>UUID-v4()</tt> -- generates a random (version 4) UUID per <xref target="RFC9562" /> Section 5.4.</li>
<li><tt>RANDOM-ALPHANUMERIC(n)</tt> -- returns a random string of <tt>n</tt> characters drawn from the alphabet <tt>[0-9a-zA-Z]</tt>. Used for human-readable references only; not security-critical.</li>
<li><tt>NOW()</tt> -- returns the current wall-clock time, as determined by the local system, as an <xref target="RFC3339" /> timestamp in UTC (e.g., <tt>"2026-04-29T12:26:30Z"</tt>). <tt>NOW()</tt> is used for recording when an artifact was submitted to the anchoring service; it is NOT a security-critical time source and MUST NOT be confused with the consensus-derived temporal claim established by an Anchor Proof (see <xref target="sec-5-1-claim" />).</li>
<li><tt>HTTP-POST(url, body)</tt> -- performs an HTTP POST request per <xref target="RFC9110" />. Used for submitting a value to a Calendar Server: an artifact hash in the direct mode of <xref target="sec-d-1-construction-algorithm-anchor" />, step 4, or a Merkle root in <xref target="sec-d-3-batch-anchoring-with-merkle-trees" />, step 4. Not security-critical: Calendar Server compromise is addressed in <xref target="sec-6-3-threat-calendar-server-equivocation" />.</li>
</ul>
<section anchor="sec-d-1-construction-algorithm-anchor"><name>Construction Algorithm (Anchor)</name>
<t>The construction algorithm <tt>CONSTRUCT-ANCHOR</tt> is defined as follows. Given a digest computed by the caller, it produces an origin record, and, where the artifact hash is submitted on its own, a pending OpenTimestamps proof. Where submission is instead deferred to the batch procedure of <xref target="sec-d-3-batch-anchoring-with-merkle-trees" />, the origin record is returned with status <tt>submitted</tt> and the pending proof is produced by that procedure. Subsequent upgrade to an anchored proof is specified in <xref target="sec-d-2-upgrade-algorithm-bitcoin-confirmation" /> in both cases.</t>
<artwork>
Algorithm: CONSTRUCT-ANCHOR(hash_algorithm, hash_value, mode)

Input:
  hash_algorithm    -- "sha256" (field F2, Section 3.4.1)
  mode              -- DIRECT or BATCH. Selects whether the
                       hash is submitted on its own (step 4)
                       or deferred to Section 5.3.
  hash_value        -- the 32-byte digest of the artifact,
                       computed by the caller under
                       hash_algorithm. The artifact bytes
                       themselves are not an input to this
                       algorithm and are not received by the
                       anchoring service.

Output:
  origin_record     -- {origin_id, hash, captured_at, proof_status}
  pending_proof     -- serialised OTS pending proof (.ots file);
                       DIRECT mode only. In BATCH mode it is
                       produced by Section 5.3.

Steps:

1.  CANONICALISE THE DIGEST
    // The digest is supplied, not computed here. Whoever
    // holds the artifact computes it; see the note on
    // AnchorDigest in Section 2.2.
    ASSERT hash_algorithm = "sha256"            // F2
    ASSERT LENGTH(hash_value) = 32              // [FIPS180-4]
    hash_string &lt;- "sha256:" || HEX(hash_value) // canonical

2.  TIMESTAMP CAPTURE
    captured_at &lt;- NOW()                        // UTC, RFC 3339

3.  ORIGIN REGISTRATION
    origin_id   &lt;- UUID-v4()                    // unique identifier
    short_token &lt;- RANDOM-ALPHANUMERIC(8)       // human-readable ref
    INSERT origin_attestations {
      origin_id, hash: hash_string, hash_algo: "sha256",
      captured_at, short_token
    }

4.  OTS SUBMISSION
    // Two modes.
    //
    // In DIRECT mode the artifact hash is submitted on its
    // own, and steps 4 and 5 complete here.
    //
    // In BATCH mode steps 4 and 5 are deferred to
    // BATCH-ANCHOR (Section 5.3), which blinds the artifact
    // hash, aggregates it with other origins under a single
    // Merkle root, and submits that root rather than the
    // hash. Both deployments described in Appendix C operate
    // in batch mode.
    IF mode = BATCH:
      RETURN {origin_id, hash_string, captured_at,
              proof_status: "submitted"}
      // "submitted" is a service-level response value. No
      // proof exists yet, so field F9 (Section 3.4.1) has
      // no value for this record.

    // DIRECT mode. Submit hash to &gt;=1 OpenTimestamps
    // Calendar Server(s) [OTS].
    // Submission to several servers is a redundancy measure;
    // one success is sufficient. If every submission fails,
    // no proof is persisted and the caller is told so here,
    // rather than receiving a pending proof that carries no
    // commitment at all (compare Section 5.3, step 4).
    accepted &lt;- 0
    pending_commitments &lt;- []
    FOR EACH calendar IN configured_calendars:
      TRY:
        pending_commitment &lt;- HTTP-POST(calendar.url, hash_value)
        pending_commitments.APPEND(pending_commitment)
        accepted &lt;- accepted + 1
      CATCH:
        LOG-ERROR(calendar.url)
        CONTINUE
    IF accepted = 0:
      // No proof is persisted, so field F9 (Section 3.4.1)
      // has no value for this record. The failure is reported
      // now rather than left to the FAIL_AFTER timeout of
      // Section 5.2.
      RETURN ERROR("no calendar accepted the submission")

5.  PENDING PROOF ASSEMBLY
    // The .ots file at this stage contains calendar commitment(s)
    // but NOT a Bitcoin anchor. It is NOT independently verifiable.
    pending_proof &lt;- OTS-SERIALIZE(hash_value, [],
                                  pending_commitments)
    INSERT core_ots_proofs {
      origin_id, ots_proof: pending_proof, status: "pending",
      created_at: NOW()
    }

6.  RETURN {origin_id, hash_string, captured_at,
            proof_status: "pending", pending_proof}
</artwork>
<t>Figure 4: CONSTRUCT-ANCHOR Algorithm</t>
</section>
<section anchor="sec-d-2-upgrade-algorithm-bitcoin-confirmation"><name>Upgrade Algorithm (Bitcoin Confirmation)</name>
<t>The upgrade algorithm <tt>UPGRADE-PENDING-PROOFS</tt> is defined as follows. It runs as a background worker, periodically converting calendar-level commitments into Bitcoin-level proofs once the relevant Bitcoin transaction has been confirmed.</t>
<t>A producing implementation asserts the <tt>anchored</tt> status (field F9, <xref target="sec-2-4-1-abstract-field-requirements" />) on which relying parties may act. It therefore performs, before it makes that assertion, the same two checks the verifier performs in <xref target="sec-3-1-verification-algorithm-verify-anchor" />: it compares the digest replayed from the proof against the Merkle root of the block header at the attested height, and it establishes that the block has reached <tt>MIN_CONFIRMATIONS</tt>. The block header is retrieved by height. It is not carried in the proof: an OpenTimestamps Bitcoin attestation records a block height and no further block data (<xref target="sec-3-1-verification-algorithm-verify-anchor" />). The producer's header source is subject to the same independence requirement as the verifier's (<xref target="sec-3-1-verification-algorithm-verify-anchor" />, note on header sources), and it establishes the header on a validated chain view of its own exactly as step 7b of <xref target="sec-3-1-verification-algorithm-verify-anchor" /> requires. A producing implementation that operates its own Bitcoin node satisfies that requirement directly.</t>
<artwork>
Algorithm: UPGRADE-PENDING-PROOFS(validated_chain)

// Runs periodically as a background worker. Converts
// calendar-level commitments to Bitcoin-level proofs.
//
// UPGRADE_BATCH, FAIL_AFTER, and the interval between
// cycles are operational parameters. They bear on the
// latency with which a proof reaches a terminal status,
// and on nothing else. No proof's validity depends on any
// of them.

Input:
  validated_chain -- the producer's own validated view of the
                     Bitcoin most-work chain (Section 4.1),
                     used at steps 2d and 2f
  (proofs are read from core_ots_proofs WHERE
   status = "pending")

Steps:

1.  pending &lt;- SELECT p.origin_id, p.ots_proof, p.created_at,
                      o.hash
               FROM core_ots_proofs p
               JOIN origin_attestations o
                 ON o.origin_id = p.origin_id
               WHERE p.status = "pending"
               ORDER BY p.created_at ASC
               LIMIT UPGRADE_BATCH
    // The origin's artifact hash is required in step 2e and is
    // not carried in core_ots_proofs.

2.  FOR EACH proof IN pending:

    artifact_hash &lt;- DECODE-HEX(STRIP-PREFIX(proof.hash))

    2a. CONTACT CALENDAR
        upgraded_proof &lt;- OTS-UPGRADE(proof.ots_proof)
        // OTS-UPGRADE contacts the Calendar Server that issued
        // the pending commitment and requests the Bitcoin
        // Merkle path if available. It returns serialised
        // .ots bytes, or NULL.

    2b. IF upgraded_proof IS NULL:
        // Nothing was merged and the stored proof carries no
        // Bitcoin attestation (Section 5). Retry next
        // cycle, unless the commitment has gone unanswered
        // for so long that the submission is presumed lost.
        IF proof.ots_proof carries no Bitcoin attestation
           AND NOW() - proof.created_at &gt; FAIL_AFTER:
          UPDATE core_ots_proofs SET status = "failed"
            WHERE origin_id = proof.origin_id
          // Status "failed" (field F9, Section 3.4.1) denotes
          // a proof whose calendar commitment was never
          // answered. A proof that already carries a Bitcoin
          // attestation has had its commitment answered, and
          // is never failed here: one held pending by the
          // depth gate at step 2f is retried until the depth
          // is established. Under Section 5 such a proof
          // does not reach this branch at all; the condition
          // above states that locally rather than leaving it
          // to be derived. The origin record survives; the
          // artifact hash may be resubmitted. No temporal
          // claim was ever made.
        CONTINUE

    2c. PARSE
        parsed &lt;- OTS-DESERIALIZE(upgraded_proof)
        IF parsed IS NULL:
          LOG-ERROR("Upgraded proof does not parse",
                    proof.origin_id)
          CONTINUE
        IF parsed carries no Bitcoin attestation:
          // Still calendar-only. Any additional calendar
          // commitments merged by OTS-UPGRADE are persisted;
          // the status does not change. Retry next cycle.
          // Compare Section 4.1, step 4.
          UPDATE core_ots_proofs SET ots_proof = upgraded_proof
            WHERE origin_id = proof.origin_id
          CONTINUE
        // The proof now carries a Bitcoin attestation. Persist
        // it with the status still pending, so that a proof held
        // back at step 2d, 2e or 2f keeps its Bitcoin path even
        // when a later cycle cannot reach the calendar (2b).
        UPDATE core_ots_proofs SET ots_proof = upgraded_proof
          WHERE origin_id = proof.origin_id

    2d. ESTABLISH EACH BRANCH'S BLOCK HEADER
        // A proof is a tree; each Bitcoin attestation
        // terminates its own branch at its own height. Height
        // and digest MUST be taken from the same branch. Each
        // attestation records its height and no other block
        // data. See the note on multiple attestations,
        // Section 4.1.
        // Retrieval and validation are separate here for the
        // same reason as in Section 4.1, steps 7a and 7b: the
        // producer asserts a status that relying parties act
        // on, so it establishes the header on a chain view it
        // holds rather than accepting one reported to it.
        FOR EACH Bitcoin branch b IN parsed:
          b.H &lt;- OTS-ATTESTED-HEIGHT(b)
          b.candidate &lt;- FETCH-BLOCK-HEADER-BY-HEIGHT(b.H)
          b.header &lt;- VALIDATE-HEADER-ON-CHAIN(validated_chain,
                                               b.H, b.candidate)
        IF b.header IS NULL FOR EVERY branch b:
          // Ledger unavailable. Leave the proof pending and
          // retry next cycle. Do not promote.
          CONTINUE

    2e. COMPARE DIGEST TO HEADER MERKLE ROOT, PER BRANCH
        FOR EACH Bitcoin branch b IN parsed:
          b.digest &lt;- OTS-REPLAY(b, artifact_hash)
          IF b.digest IS NULL OR b.header IS NULL:
            b.state &lt;- unevaluated
          ELSE IF b.digest = b.header.merkle_root:
            b.state &lt;- verified
          ELSE:
            b.state &lt;- mismatched
        verified &lt;- { b IN parsed : b.state = verified }
        IF verified IS EMPTY:
          IF ANY b IN parsed HAS b.state = unevaluated:
            // A branch could not be replayed, or its header
            // was not established. Leave the proof pending
            // and retry next cycle; this is not a failed
            // upgrade. Section 4.1, step 8.
            CONTINUE
          LOG-ERROR("Upgrade verification failed", proof.origin_id)
          CONTINUE
        b* &lt;- the branch in verified with the lowest b.H
        H &lt;- b*.H
        header &lt;- b*.header
        // The comparison of Section 4.1, step 8, applied per
        // branch; the lowest verified height is bound. The
        // producer performs it against a header it retrieved,
        // before asserting a status derived from it.

    2f. CHECK CONFIRMATION DEPTH
        k &lt;- CONFIRMATIONS-ON(validated_chain, H)
        IF k IS NULL OR k &lt; MIN_CONFIRMATIONS:
          // Not yet durable, or the depth is unknown. Leave
          // the proof pending and retry next cycle. A proof
          // whose depth could not be established is not
          // promoted. See Section 4.1, step 9, and Section 8.11.
          CONTINUE

    2g. PERSIST UPGRADED PROOF
        UPDATE core_ots_proofs SET
          ots_proof            = upgraded_proof,
          status               = "anchored",
          bitcoin_block_height = H,
          block_time           = header.nTime,
          anchored_at          = NOW()
        WHERE origin_id = proof.origin_id
        // bitcoin_block_height carries the block-height
        // binding (Section 7.1) and is the normative value.
        // block_time is field F8 (Section 3.4.1), the
        // Reference Wall-Clock Projection (Section 2.2), and
        // is informative. Section 7.1 governs how an
        // implementation that surfaces it may present it.
        // anchored_at records when this deployment performed
        // the promotion. It is a clock reading taken by the
        // producing implementation, is not carried in any
        // proof, and no step of Section 4.1 consults it. Its
        // name notwithstanding, a relying party MUST NOT
        // treat it as the time at which A was anchored: that
        // claim is the block-height binding of Section 7.1
        // and nothing else. An implementation is free to name
        // this column otherwise, or to omit it.
</artwork>
<t>Figure 5: UPGRADE-PENDING-PROOFS Algorithm</t>
</section>
<section anchor="sec-d-3-batch-anchoring-with-merkle-trees"><name>Batch Anchoring with Merkle Trees</name>

<t>The batch anchoring procedure <tt>BATCH-ANCHOR</tt> is a high-throughput variant of <tt>CONSTRUCT-ANCHOR</tt> (<xref target="sec-d-1-construction-algorithm-anchor" />). It aggregates a set of artifact hashes under a single Merkle root, so that N artifact hashes require the submission of one value rather than N. Each artifact retains its own origin record and its own complete <tt>.ots</tt> proof.</t>

<t>Origin records are created when an artifact hash is submitted (<xref target="sec-d-1-construction-algorithm-anchor" />). This procedure operates on records that already exist. It produces one proof per origin and persists nothing else.</t>

<artwork>
Algorithm: BATCH-ANCHOR(origin_list)

Input:
  origin_list  -- ordered list of origin records, each holding
                  a SHA-256 artifact hash. The order is the
                  order in which the records were captured.

Output:
  proofs       -- list of (origin_id, ots_bytes), one entry
                  per input origin

Steps:

1.  VALIDATE
    ASSERT |origin_list| &gt;= 1
    FOR EACH origin IN origin_list:
      ASSERT origin.hash matches /^(sha256:)?[0-9a-f]{64}$/
    // A batch of one is valid: its blinded leaf is the root.

2.  DERIVE LEAVES
    // Each artifact hash is blinded before it enters the
    // tree. A fresh 16-byte nonce is appended to the artifact
    // hash and the result is hashed. The blinded value, not
    // the artifact hash, becomes the Merkle leaf.
    //
    // Blinding is a confidentiality measure. The aggregation
    // servers, and any party who observes the tree, learn
    // nothing about the artifact hashes of other submitters
    // in the same batch. The nonce and the hash that follows
    // it are recorded as the first two entries of this
    // origin's operation sequence, so the artifact hash still
    // resolves to the leaf during verification. That
    // sequence, not the leaf, is what the proof carries.
    leaves &lt;- []
    FOR EACH origin IN origin_list:
      origin.digest &lt;- DECODE-HEX(STRIP-PREFIX(origin.hash))
      nonce         &lt;- RANDOM-BYTES(16)
      origin.path   &lt;- [ OP-APPEND(nonce), OP-SHA256 ]
      leaves.APPEND(SHA-256(origin.digest || nonce))

3.  AGGREGATE
    // Pair-and-promote Merkle aggregation. Pairing is
    // sequential over the list in capture order.
    // Concatenation is over raw bytes. No lexicographic sort
    // is applied and no domain-separation prefix is used. An
    // odd trailing element is promoted unchanged to the next
    // level, where it appears last.
    //
    // Each pairing records two operations, one on each side:
    // the left value gains "append the right value", the
    // right value gains "prepend the left value". Position
    // within the tree is therefore held inside each leaf's
    // own proof, and is not a property of the leaf list.
    // members[i] holds the origins whose running value is
    // level[i]. It is what allows each pairing to be recorded
    // in the operation sequence of every origin beneath it.
    level   &lt;- leaves
    members &lt;- [ [origin_list[i]] FOR i &lt;- 0 TO |leaves| - 1 ]
    WHILE |level| &gt; 1:
      next_level   &lt;- []
      next_members &lt;- []
      FOR i &lt;- 0 TO |level| - 2 STEP 2:
        FOR EACH origin IN members[i]:
          origin.path.APPEND(OP-APPEND(level[i+1]))
          origin.path.APPEND(OP-SHA256)
        FOR EACH origin IN members[i+1]:
          origin.path.APPEND(OP-PREPEND(level[i]))
          origin.path.APPEND(OP-SHA256)
        next_level.APPEND(SHA-256(level[i] || level[i+1]))
        next_members.APPEND(members[i] + members[i+1])
      IF |level| is odd:
        // Promoted unchanged: the running value does not
        // change, so no operation is recorded for the
        // origins beneath it.
        next_level.APPEND(level[|level| - 1])
        next_members.APPEND(members[|level| - 1])
      level   &lt;- next_level
      members &lt;- next_members
    merkle_root &lt;- level[0]

4.  SUBMIT ROOT
    // The root is submitted to one or more OpenTimestamps
    // aggregation servers. Each returns a calendar
    // commitment against the root, and every commitment
    // received is carried into each origin's proof at step 5.
    // Submission to several servers is a redundancy measure;
    // one success is sufficient. If every submission fails,
    // no proof is persisted and the batch is retried on the
    // next cycle.
    accepted &lt;- 0
    root_commitments &lt;- []
    FOR EACH server IN ots_servers:
      TRY:
        commitment &lt;- HTTP-POST(server, merkle_root)
        root_commitments.APPEND(commitment)
        accepted &lt;- accepted + 1
      CATCH:
        LOG-ERROR(server)
        CONTINUE
    IF accepted = 0:
      RETURN []

5.  SERIALISE PER-ORIGIN PROOFS
    // Each origin's proof is serialised independently. It
    // records that origin's own path: the append of its
    // nonce, the hash that produces its leaf, and the sibling
    // operations that carry the leaf onward. No origin's
    // proof reveals any other origin's artifact hash.
    proofs &lt;- []
    FOR EACH origin IN origin_list:
      ots_bytes &lt;- OTS-SERIALIZE(origin.digest, origin.path,
                                 root_commitments)
      proofs.APPEND((origin.origin_id, ots_bytes))
      STORE {origin_id, ots_proof: ots_bytes,
             status: "pending", created_at: NOW()}

    RETURN proofs
</artwork>

<t><strong>Note on the construction.</strong> Step 3 restates the aggregation performed by the OpenTimestamps reference implementation <xref target="OTS" />, which documents that routine as belonging to its consensus-critical core and states that the algorithm will not be changed. It is specified here in full, and can be implemented without depending on any OpenTimestamps software library. What an implementation cannot do is substitute a different tree. A different pairing rule, a lexicographic sort of the leaves, or the domain-separation prefixes of <xref target="RFC6962" /> each produce a different root. An OpenTimestamps verifier replays whatever operations a proof carries and would not reject such a proof on that ground. What such a construction will not do is reproduce the test vectors of <xref target="sec-appendix-e-test-vectors" />. This section is informative and does not itself determine conformance; what a verifier requires is stated in <xref target="sec-3-1-verification-algorithm-verify-anchor" />, and any construction whose proofs satisfy it is acceptable to this profile.</t>

<t><strong>Note on verification.</strong> The <tt>.ots</tt> format encodes a Merkle path as an explicit, ordered sequence of operations: prepend a sibling value, append a sibling value, hash. A verifier replays that sequence from the artifact hash and either arrives at the value committed to the ledger or does not. Soundness of this replay does not depend on any global statement about leaf ordering, and this profile makes none. An implementation that recomputes the tree from an assumed ordering, rather than replaying the recorded operations, does not interoperate with <xref target="OTS" />.</t>

<t><strong>Note on aggregation depth.</strong> The Merkle root produced in step 3 is not committed to Bitcoin directly. OpenTimestamps aggregation servers combine roots from many submitters into further trees before a commitment is placed in a Bitcoin transaction. The proof then continues through the raw bytes of that transaction and through the block's transaction tree, terminating at the Merkle root of the block header. Each proof therefore contains operations belonging to more than one tree, and carries the transaction rather than referring to it. This is transparent to verification: the recorded operation sequence spans every level, and <xref target="sec-d-2-upgrade-algorithm-bitcoin-confirmation" /> upgrades the proof once the ledger commitment is confirmed.</t>

<t><strong>Note on batch size.</strong> The number of origins per batch is an operational parameter. It bounds the length of the Merkle path recorded in each proof and the number of values submitted. It has no bearing on the validity of any individual proof.</t>

</section>
</section>
<section anchor="sec-4-integration-with-scitt-architecture"><name>Integration with SCITT Architecture</name>
<section anchor="sec-4-1-no-protocol-changes-required"><name>No Protocol Changes Required</name>
<t>This profile is additive. It does not modify:</t>
<ul>
<li>The Signed Statement format</li>
<li>The Receipt format</li>
<li>The Transparency Service API</li>
<li>The consistency or inclusion proof mechanisms</li>
</ul>
<t>A Transparency Service operator MAY adopt this profile unilaterally. Auditors MAY verify Anchor Proofs independently of the SCITT verification flow.</t>
</section>
<section anchor="sec-4-2-metadata-extension"><name>Metadata Extension</name>
<t>A Transparency Service that implements this profile SHOULD include anchor metadata in its service parameters. The object is defined in CDDL <xref target="RFC8610" /> as follows.</t>
<artwork>
anchor-metadata = {
  anchor_ledger: ledger-id
  ? anchor_ledger_name: tstr
  anchor_method: "opentimestamps"
  anchor_level: "signed_statement" / "log_root"
  anchor_interval: "direct" / "batch"
  anchor_spec: uri
}

ledger-id = tstr .regexp "[0-9a-f]{64}"
uri       = tstr
</artwork>
<t>The fields are as follows. <tt>anchor_ledger</tt> identifies the Anchor Ledger by the hash of its genesis block (<xref target="sec-2-5-anchor-ledger-requirements" />); it is the only field of this object on which a ledger may be matched. <tt>anchor_ledger_name</tt> is a label for human readers, such as <tt>bitcoin-mainnet</tt>; it SHOULD be present where the ledger has a commonly used name, and it is informative only: it carries no uniqueness guarantee and MUST NOT be used to identify or match a ledger. <tt>anchor_method</tt> names the anchoring mechanism, which under this profile is OpenTimestamps. <tt>anchor_level</tt> states what the service anchors, a Signed Statement (<xref target="sec-2-2-statement-anchoring" />) or a log root (<xref target="sec-2-3-log-root-anchoring" />). <tt>anchor_interval</tt> states whether hashes are submitted individually or aggregated into batches (Sections <xref target="sec-d-1-construction-algorithm-anchor" format="counter" /> and <xref target="sec-d-3-batch-anchoring-with-merkle-trees" format="counter" />). <tt>anchor_spec</tt> is a URI identifying the version of the Anchoring Specification the service implements.</t>
<t>This object describes a service, not any individual proof. Nothing in <xref target="sec-3-1-verification-algorithm-verify-anchor" /> reads it, and a verifier that never sees it can complete the whole of verification: the values here are context published alongside proofs rather than properties a proof establishes (Sections <xref target="sec-2-4-1-abstract-field-requirements" format="counter" /> and <xref target="sec-3-3-error-conditions" format="counter" />).</t>
<t>An example, with the ledger identifier shown in full as it is always written:</t>
<artwork>
{
  "anchor_ledger":
 "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
  "anchor_ledger_name": "bitcoin-mainnet",
  "anchor_method": "opentimestamps",
  "anchor_level": "log_root",
  "anchor_interval": "batch",
  "anchor_spec": "https://doi.org/10.5281/zenodo.21389583"
}
</artwork>
</section>
<section anchor="sec-4-3-receipt-association"><name>Receipt Association</name>
<t>An Anchor Proof and a Receipt may be carried together, for instance in a single bundle. This document defines no cryptographic binding between the two, and none should be inferred from co-location. Ordering is the reason: a Receipt is issued when a Signed Statement is registered, while an Anchor Proof reaches the ledger only after calendar aggregation and the confirmation depth of <xref target="sec-6-11-threat-premature-anchored-state-false-finality" />, which is later by the intervals described in <xref target="sec-on-chain-footprint-and-latency" />. A Receipt therefore exists before the proof that would have to be referenced in it, and neither [RFC9943] nor [RFC9942] defines a Receipt field for such a reference. A deployment that requires a binding would have to define a separate object, signed after the upgrade, that names both; specifying one is out of scope here.</t>
</section>
<section anchor="sec-4-4-relationship-to-rfc-9921-cose-timestamp-headers"><name>Relationship to RFC 9921 (COSE Timestamp Headers)</name>
<t><xref target="RFC9921" /> defines two COSE header parameters -- <tt>3161-ctt</tt> and <tt>3161-ttc</tt> -- for embedding RFC 3161 Time-Stamp Tokens directly inside COSE_Sign1 structures <xref target="RFC9052" />. This establishes a precedent: COSE already supports binding external temporal proofs to signed objects without modifying the object's payload.</t>
<t>This profile extends the same principle to a different trust model:</t>
<artwork>
+--------------------+----------------------------------+
|  RFC 9921          |  This Profile                    |
+--------------------+----------------------------------+
|  Trust root: CA    |  Trust root: Consensus (PoW)     |
|  Proof: TST token  |  Proof: .ots file                |
|  Precision: ms     |  Precision: block (~10 min)      |
|  Binding: COSE hdr |  Binding: Anchor Proof or hdr    |
|  Verifier trusts:  |  Verifier trusts:                |
|    TSA + CA chain  |    Bitcoin blockchain            |
|  Offline verify:   |  Offline verify:                 |
|    With CA certs   |    With block headers            |
+--------------------+----------------------------------+
</artwork>
<t>The two mechanisms are complementary. A Transparency Service MAY carry both over the same digest: an RFC 3161 Time-Stamp Token, embedded through <xref target="RFC9921" />, and an Anchor Proof under this profile. Each is verified on its own terms, and neither depends on the other (<xref target="sec-5-4-strength-and-limitations" />).</t>
<t>A COSE header parameter for OpenTimestamps proofs is out of scope for this document but could be defined in a future specification, following the pattern established by RFC 9921. Such a specification could define a CBOR-based serialization of the fields a proof carries (<xref target="sec-2-4-1-abstract-field-requirements" />, Table 1), using COSE_Sign1 as the envelope, analogous to how RFC 9921 embeds RFC 3161 TST tokens. The abstract field table in this document is designed to facilitate such mapping without requiring changes to the anchoring model itself.</t>
</section>
</section>
<section anchor="sec-5-formal-security-argument"><name>Temporal Claim and Security Argument</name>
<t>This section formally defines the temporal claim established by a verified Anchor Proof, the assumptions under which the claim holds, and the bounds on temporal uncertainty.</t>
<section anchor="sec-5-1-claim"><name>Claim</name>
<t><strong>Theorem (Existence-before-block-H)</strong>: If VERIFY-ANCHOR(A, P, C) = <tt>valid</tt>, where <tt>C</tt> is the verifier's own validated view of the most-work chain (<xref target="sec-3-1-verification-algorithm-verify-anchor" />), then the byte sequence A existed before the Bitcoin block at height H containing the anchor commitment was added to the validated most-work chain (the block-height binding defined below).</t>
<t>The statement has one premise where earlier revisions of this profile had three. Both of the conditions that used to stand beside the result are now conditions of it. A <tt>valid</tt> result requires that the verifier itself placed the attested block on the validated most-work chain, since a verifier that cannot do so returns <tt>unverifiable</tt> (step 7b of <xref target="sec-3-1-verification-algorithm-verify-anchor" />). It also requires that the verifier established at least MIN_CONFIRMATIONS blocks at that height or above on the same chain, since step 9 returns <tt>unverifiable</tt> otherwise. What a reader must supply alongside a <tt>valid</tt> result is therefore nothing: the word carries the whole of the claim, and a verifier that cannot support the whole of it does not use the word.</t>
<t>Given artifact <tt>A</tt> and Anchor Proof <tt>P</tt> that successfully verifies against the Bitcoin blockchain (i.e., <tt>VERIFY-ANCHOR(ArtifactBytes, ProofBundle, ValidatedChain) = valid</tt>), the normative temporal claim established by <tt>P</tt> is:</t>
<t>A was committed to the Bitcoin blockchain in the block at height H.</t>
<t>This claim is referred to as the <strong>block-height binding</strong> (<xref target="sec-1-3-terminology" />). The binding is exact: a verified proof either binds the artifact to block height <tt>H</tt> or it does not. The binding is consensus-verified by the same proof-of-work process that secures the Bitcoin blockchain itself.</t>
<t><strong>Reference Wall-Clock Projection.</strong> The wall-clock value <tt>block.time(H)</tt>, also known as the <tt>nTime</tt> field of the block header at height <tt>H</tt>, SHOULD be presented to human verifiers as the Reference Wall-Clock Projection of the binding (<xref target="sec-1-3-terminology" />). This value is the temporal reading returned by deployed OpenTimestamps verifiers. It is informative and is NOT the normative temporal value. Discussion of <tt>block.time</tt> and the consensus limits on it appears in <xref target="sec-2-6-6-relationship-to-wall-clock-time-stamping" />.</t>
<t><strong>Note on Median Time Past.</strong> Verifiers MAY compute the Median Time Past (MTP) of block <tt>H</tt> per <xref target="BIP113" />. By the median-time rule, MTP is a strict lower bound on the block's own declared timestamp <tt>block.time(H)</tt>; it is not a lower bound on the wall-clock time at which block <tt>H</tt> was accepted, since block timestamps are miner-set within consensus limits and may run ahead of real time. Computing MTP requires retrieval of the eleven block headers immediately preceding <tt>H</tt>. The block-height binding above does not require MTP and is normative regardless of whether MTP is computed.</t>
<t><strong>Backwards-compatibility note.</strong> Anchor Proofs created under earlier interpretations of this profile, where the temporal value <tt>T</tt> was inferred to be <tt>block.time(H)</tt> rather than the block-height binding at <tt>H</tt>, remain valid under this profile. Such proofs remain structurally verifiable: the recorded operations are unchanged, and they bind the artifact to block <tt>H</tt> exactly as before. The temporal claim, however, is not the same one. The earlier wall-clock reading is not preserved by this profile, because <tt>block.time(H)</tt> is miner-declared and is not the value bound here; what such a proof establishes under this profile is the block-height binding at <tt>H</tt>, as for any other proof. A wall-clock interpretation must ultimately resolve to a specific block height to be verifiable.</t>
</section>
<section anchor="sec-5-2-assumptions"><name>Assumptions</name>
<t>The proof relies on the following assumptions, each of which is a well-established property of the underlying primitives:</t>
<ul>
<li><strong>A1 (SHA-256 Security)</strong>: SHA-256 provides preimage resistance, second-preimage resistance, and collision resistance <xref target="FIPS180-4" />. That is, given a digest it is computationally infeasible to find any input that produces it; given an input it is computationally infeasible to find a distinct input with the same digest; and it is computationally infeasible to find any two distinct inputs x ≠ y such that SHA-256(x) = SHA-256(y).</li>
</ul>
<ul>
<li><strong>A2 (Ledger Immutability)</strong>: The Bitcoin blockchain is append-only. Rewriting a confirmed block requires an adversary to produce an alternative chain with greater cumulative proof-of-work than the honest chain, and the probability of doing so decreases rapidly with the number of confirmations. For an adversary controlling a minority of the network's hash power this probability is small but non-zero, decreasing with depth without reaching zero at any fixed depth <xref target="NAKAMOTO" />. A depth of six confirmations is therefore a risk threshold at which the residual probability is treated as acceptable for relying parties, not a point of absolute consensus finality.</li>
</ul>
<ul>
<li><strong>A3 (Commitment Causality)</strong>: A value committed by the transaction Merkle root of a Bitcoin block necessarily existed before that block was mined. The Merkle root is an input to the block header, and the header is the input to the proof-of-work computation. A value that did not exist when the root was computed cannot be committed by it, and a value substituted afterwards yields a different root, a different header, and a header hash that no longer satisfies the block's difficulty target. This assumption follows from the construction of the block header and does not depend on transaction propagation, on miner behaviour, or on the specific value of any timestamp field in the header. (For verifiers seeking a conservative wall-clock interpretation, the Median Time Past per <xref target="BIP113" /> provides a strict lower bound on <tt>block.time(H)</tt>; see <xref target="sec-5-1-claim" />.)</li>
</ul>
<ul>
<li><strong>A4 (OTS Correctness)</strong>: The OpenTimestamps proof format provides a deterministic, publicly verifiable chain of operations from an input hash <tt>h</tt> to the transaction Merkle root of a Bitcoin block header <xref target="OTS" />. Each operation prepends a recorded byte string, appends one, or hashes; every operand is carried in the proof. Determinism guarantees that a given proof replays to a single digest; it does not by itself establish unforgeability. That an adversary cannot construct an operation path from a chosen artifact to a fixed, previously mined Merkle root follows from Assumption A1: reaching that root would require finding a SHA-256 preimage of it, or a second preimage of an intermediate value on the path, which is computationally infeasible. The verification algorithm is specified normatively in <xref target="sec-3-1-verification-algorithm-verify-anchor" />, and the construction algorithms are given as informative examples in <xref target="sec-appendix-d-ots-anchoring-protocol-construction-and-verification" />; see <xref target="sec-5-2-1-note-on-assumption-a4" />.</li>
</ul>
<ul>
<li><strong>A5 (Operator Independence)</strong>: The anchoring service and any intermediary may fail, be compromised, or cease operations without affecting the validity of previously issued proofs. The temporal claim is grounded in Bitcoin consensus, not in the continued operation or trustworthiness of the anchoring service operator. This assumption is supported by the verification independence requirement in <xref target="sec-3-2-verification-independence" />: a conformant verifier requires only the artifact bytes, the .ots proof, and access to independently validated Bitcoin chain data.</li>
</ul>
<section anchor="sec-5-2-1-note-on-assumption-a4"><name>Note on Assumption A4</name>
<t>OpenTimestamps has no formal specification independent of its project. This profile therefore depends normatively on the serialised proof format and the operation semantics of the release pinned in <xref target="OTS"/>, not on running any particular software.</t>
<t>The release is identified twice over, so that neither identification depends on one host. The source revision is the commit given in <xref target="OTS" />; that revision is archived by Software Heritage under the persistent identifier</t>
<artwork>
swh:1:rev:a90094e3ca9a8229abd016be8816d7b90b9fd1e6
</artwork>
<t>The corresponding release artifact is the source distribution <tt>opentimestamps-0.4.5.tar.gz</tt>, whose SHA-256 digest is:</t>
<artwork>
56726ccde97fb67f336a7f237ce36808e5593c3089d68d900b1c83d0ebf9dcfa
</artwork>
<t>Assumption A4 reduces to two primitives that this document specifies in full and that are verifiable against the Bitcoin blockchain without reference to any OTS software: replaying the recorded prepend, append and SHA-256 operations of a proof, and retrieving the header at the attested height and comparing its Merkle root against the replayed digest (<xref target="sec-3-1-verification-algorithm-verify-anchor"/>, steps 5 to 9).</t>
<t><strong>Deserialisation lies outside that boundary.</strong> Those algorithms take a parsed proof as their input. Recovering the operation sequence and its attestations from the serialised <tt>.ots</tt> byte stream is performed by <tt>OTS-DESERIALIZE</tt> (<xref target="sec-3-1-verification-algorithm-verify-anchor" />), whose input format this profile does not specify. An implementer working from this document alone therefore reaches the verification algorithms but not the parser that feeds them. The independence established by this section is independence from the OpenTimestamps software, which an implementation of this profile need not run; it is not independence from the OpenTimestamps format, which an implementer must obtain from <xref target="OTS" /> in order to write the parser that this profile does not specify.</t>
<t>This bounds what an implementer must obtain in order to build a verifier, not what a verifier consumes when it runs. The requirement of <xref target="sec-3-2-verification-independence"/> is unaffected: knowledge of the serialisation format is embedded in the verifier rather than supplied to it, and a verifier built under this profile still requires nothing from the anchoring service, from a Calendar Server or from any certificate authority.</t>
<t><strong>When the pinned release changes.</strong> Conformance to this profile is defined against the serialised format and the operation semantics of the release identified above, and not against whatever release is current. A later OpenTimestamps release that leaves both unchanged raises no question of conformance, and an implementation may track it freely. A release that changes either requires a revision of this profile pinning the new one; until such a revision is published, an implementation that follows the changed format is not conformant to this document, and the two identifiers above remain sufficient to obtain the format this document does specify. This profile does not delegate its normative content to releases made after publication.</t>
<t>A later release, or an implementation written independently of the pinned one, is conformant to this profile where it accepts and produces the serialised format described above, follows the same operation semantics, and reproduces the test vectors of <xref target="sec-appendix-e-test-vectors"/>. Changes internal to a release, including security fixes, that leave this externally visible behaviour unchanged raise no question of conformance. Where conformance is reported, naming the implementation and release actually exercised makes the report checkable; that is operational record rather than a normative dependency.</t>
</section>
</section>
<section anchor="sec-5-3-proof"><name>Proof</name>
<t>Let <tt>A</tt> be an artifact (byte sequence), let <tt>C</tt> be the verifier's own validated view of the most-work chain, and let <tt>P</tt> be an Anchor Proof for which <tt>VERIFY-ANCHOR(ArtifactBytes, ProofBundle, ValidatedChain) = valid</tt>.</t>
<t><strong>Step 1</strong>: By steps 1-2 of the verification algorithm (<xref target="sec-3-1-verification-algorithm-verify-anchor" />), <tt>SHA-256(A) = h</tt>, where <tt>h</tt> is the hash committed in the Anchor Proof. By Assumption A1, it is computationally infeasible to find any input that produces <tt>h</tt> (preimage resistance) or a distinct input <tt>A'</tt> ≠ <tt>A</tt> that also produces <tt>h</tt> (second-preimage resistance); no adversary can therefore substitute a different artifact for the one bound to <tt>h</tt>.</t>
<t><strong>Step 2</strong>: By steps 3 and 5, the <tt>.ots</tt> proof contains a deterministic sequence of operations that carries <tt>h</tt> to a digest <tt>M = f(h)</tt>, where <tt>f</tt> is the composition of those operations. By Assumption A4 this chain is correct and publicly verifiable, and every operand it requires is recorded in the proof itself.</t>
<t><strong>Step 3</strong>: By steps 6, 7 and 8, <tt>M</tt> equals the transaction Merkle root of a Bitcoin block header at height <tt>H</tt>. That this header lies on the canonical chain is not established by the proof itself; it is discharged by step 7b, which returns a header only where <tt>C</tt> establishes it at that height. Given such a validated header at a depth of at least MIN_CONFIRMATIONS, Assumption A2 makes replacement of the block computationally infeasible, and by Assumption A1 it is computationally infeasible to produce a distinct input yielding the same <tt>M</tt>.</t>
<t><strong>Step 4</strong>: By Assumption A3 (Commitment Causality), a value committed by <tt>M</tt> existed before the block at height <tt>H</tt> was mined, since <tt>M</tt> is an input to the header over which proof-of-work was performed. The digest <tt>M</tt> commits to <tt>h</tt>, and <tt>h</tt> commits to <tt>A</tt>.</t>
<t><strong>Step 5</strong>: Each link is causal: <tt>A</tt> existed before <tt>h</tt> was computed, <tt>h</tt> before <tt>M</tt> was computed, and <tt>M</tt> before the header at height <tt>H</tt> was hashed. Therefore <tt>A</tt> existed before the block at height <tt>H</tt> was added to the canonical chain, which is the block-height binding of <xref target="sec-5-1-claim"/>.</t>
<t>The argument depends on neither the block's <tt>nTime</tt> field nor the Median Time Past, and on the contents of no individual transaction. ∎</t>
</section>
<section anchor="sec-5-4-strength-and-limitations"><name>Strength and Limitations</name>
<t>The above argument is a <strong>computational security</strong> argument, not an information-theoretic proof. Its strength is bounded by:</t>
<ol>
<li>The second-preimage resistance of SHA-256, which binds an artifact to its committed hash, together with its collision resistance (a 128-bit generic bound), which additionally bounds the case of an adversary who crafts two artifacts before anchoring (<xref target="sec-6-1-threat-hash-collision-forged-commitment" />). The generic second-preimage bound is the full output length, but it does not hold uniformly for long messages, and this profile places no bound on artifact length</li>
<li>The cost of a sustained 51% attack on Bitcoin, which depends on then-current network conditions and is external to this specification</li>
<li>The granularity of Bitcoin's block-mining process: the block-height binding identifies a block but does not resolve when within the block's mining interval the binding became fixed</li>
</ol>
<t>It does not establish when <tt>A</tt> was created, who created it, or that it has not since been modified, and it supports no wall-clock interpretation of the binding. <xref target="sec-2-6-4-non-claims"/> states these exclusions in full.</t>
<t>The temporal resolution of the block-height binding is bounded by Bitcoin's block interval, which targets an average of approximately ten minutes per block under the network's difficulty adjustment mechanism. The actual interval between two consecutive blocks is variable; individual block intervals can range from seconds to several hours.</t>
<t>For use cases requiring wall-clock precision finer than the block interval (e.g., sub-minute timestamps), an additional time source such as RFC 3161 <xref target="RFC3161" /> MAY be combined with this profile to provide a complementary, finer-grained timestamp. The two mechanisms operate under different trust models (see <xref target="sec-4-4-relationship-to-rfc-9921-cose-timestamp-headers" />) and can be presented together.</t>
</section>
<section anchor="sec-2-6-4-non-claims"><name>Non-Claims</name>
<t>An Anchor Proof explicitly does NOT establish:</t>
<ul>
<li>That <tt>A</tt> was created at the time block <tt>H</tt> was mined (only: <tt>A</tt> existed before block <tt>H</tt> was mined)</li>
<li>That <tt>A</tt> was created by any specific party</li>
<li>That <tt>A</tt> did not exist before block <tt>H</tt> was mined</li>
<li>That the Reference Wall-Clock Projection <tt>block.time(H)</tt> corresponds to true wall-clock time (it is a miner-set value within consensus bounds; see <xref target="sec-2-6-6-relationship-to-wall-clock-time-stamping" />)</li>
<li>That <tt>A</tt> was the only artifact its producer anchored, or the artifact the producer committed to (<xref target="sec-threat-selective-disclosure-of-anchored-alternatives" />)</li>
<li>That the log or registry from which the anchored bytes were taken recorded everything it ought to have recorded</li>
</ul>
<t><strong>On omission.</strong> The last exclusion above concerns what an anchor establishes about a log rather than about an artifact, and it bears on log root anchoring (<xref target="sec-2-3-log-root-anchoring" />) in particular. Anchoring a root bounds the state of that log at the attested height. It establishes nothing about whether every entry that ought to have been registered was registered: an entry never submitted, or submitted late, leaves nothing in the ledger for a verifier to detect, and no comparison against a block header can contradict an absence. The anchor establishes what is there, not what ought to have been there. A relying party that requires assurance of completeness must obtain it from the guarantees of the Transparency Service whose log is anchored, and not from the anchor.</t>
<t>These exclusions are consistent with <xref target="ANCHORING" /> Section 8 (Semantic Exclusions) and Section 16 (Non-Retroactivity).</t>
</section>
<section anchor="sec-5-5-anchor-proof-integrity"><name>Anchor Proof Integrity</name>
<t>The Anchor Proof does not replace the SCITT Receipt. It provides an independent temporal claim. If the Anchor Proof is lost or corrupted, the Receipt remains valid within the SCITT framework. The temporal independence guarantee is degraded but the claim integrity is unaffected.</t>
</section>
<section anchor="sec-5-6-hash-algorithm-agility"><name>Hash Algorithm Agility</name>
<t>This profile specifies SHA-256 as the hash function for anchor inputs. If SHA-256 is deprecated, the Anchor Ledger requirement (<xref target="sec-2-5-anchor-ledger-requirements" />) remains valid with a successor hash function. The mechanism is not hash-agile at runtime: the F2 <tt>hash_algorithm</tt> field MUST be <tt>"sha256"</tt> (<xref target="sec-2-4-1-abstract-field-requirements" />), the canonical prefix is <tt>"sha256:"</tt>, and the OpenTimestamps opcode is SHA-256-specific. It is instead future-extensible: migration to a successor hash function would require a revision of this profile, not a runtime selection.</t>
</section>
<section anchor="sec-5-7-ledger-availability"><name>Ledger Availability</name>
<t>Bitcoin's availability characteristics exceed those of any single-operator Transparency Service. Anchor Proof verification requires block-header data from the validated most-work chain, obtained as described in the note on header sources in <xref target="sec-3-1-verification-algorithm-verify-anchor" />. A verifier holding a validated header set can perform the whole of <xref target="sec-3-1-verification-algorithm-verify-anchor" /> without network access, provided that set covers the attested height and extends to at least height H + MIN_CONFIRMATIONS - 1: steps 7 and 8 read the header at that height, and step 9 counts the blocks at that height and above, which the extent of the set answers. A set that stops short of that leaves the depth unestablished and the result <tt>unverifiable</tt>, which is the accurate report rather than a defect in the verifier. What availability governs is whether a verifier can reach the position from which the ledger check can be made, not whether the proof remains intact: the proof itself is self-contained and does not decay.</t>
</section>
<section anchor="sec-5-8-equivocation-detection"><name>Equivocation Detection</name>
<t>Log Root Anchoring (<xref target="sec-2-3-log-root-anchoring" />) strengthens equivocation detection: if a Transparency Service presents different log states to different parties, the anchored root provides a public commitment that can be compared when it is a checkpoint object (<xref target="sec-2-3-log-root-anchoring" />). <xref target="RFC9943" /> requires non-equivocation of the verifiable data structure (Section 5.1.3 of that document), and Receipts may carry consistency proofs (Section 3 of that document). What anchoring adds is a reference point outside the log, which does not depend on the Transparency Service remaining honest, available, or in possession of its signing key.</t>
</section>
</section>
<section anchor="sec-6-security-considerations"><name>Security Considerations</name>
<t>This section follows the guidelines in <xref target="RFC3552" /> for describing threats and mitigations relevant to this profile.</t>
<t>The attacker model assumes the following capabilities:</t>
<ul>
<li>The attacker can observe all network traffic between the submitter, the anchoring service, Calendar Servers, and the Anchor Ledger.</li>
<li>The attacker can operate one or more Calendar Servers.</li>
<li>The attacker can submit arbitrary hashes to the anchoring service.</li>
<li>The attacker cannot find collisions or second pre-images for SHA-256 (Assumption A1, <xref target="sec-5-2-assumptions" />).</li>
<li>The attacker cannot rewrite confirmed Bitcoin blocks with k ≥ 6 confirmations, except with the residual probability stated in Assumption A2 (<xref target="sec-5-2-assumptions" />).</li>
<li>The attacker cannot control a majority of Bitcoin's network hash power.</li>
</ul>
<t>The following subsections enumerate specific threats under this model.</t>
<section anchor="sec-6-1-threat-hash-collision-forged-commitment"><name>Threat: Second-Preimage Substitution (Forged Commitment)</name>
<t>Given an artifact A that has already been anchored, an attacker could seek a second artifact B such that SHA-256(B) = SHA-256(A), thereby claiming that the anchor for artifact A also proves the existence of artifact B. Because A and its hash are already fixed, this is a second-preimage attack rather than a collision search; the case in which an adversary crafts both artifacts before anchoring is a collision and is treated in <xref target="sec-5-4-strength-and-limitations" />.</t>
<t>This is remediated by the second-preimage resistance of SHA-256 <xref target="FIPS180-4" />. No known practical second-preimage or collision attack exists against SHA-256 as of the date of this document. This profile is not hash-agile at runtime: field F2 (<tt>hash_algorithm</tt>, <xref target="sec-2-4-1-abstract-field-requirements" />) MUST be <tt>"sha256"</tt>. It is future-extensible, and migration to a successor hash function would require a revision of this profile rather than a runtime selection (<xref target="sec-5-6-hash-algorithm-agility" />).</t>
</section>
<section anchor="sec-6-2-threat-anchor-ledger-rewrite-51-attack"><name>Threat: Anchor Ledger Rewrite (51% Attack)</name>
<t>An attacker with majority hash power on the Anchor Ledger could rewrite the block containing the commitment, thereby invalidating or altering the temporal proof.</t>
<t>This is remediated by the economic cost of producing a competing chain with greater accumulated proof-of-work, which depends on then-current network conditions and is external to this specification. Assumption A2 holds only at the confirmation depth this profile requires; <xref target="sec-3-1-verification-algorithm-verify-anchor"/> evaluates it at step 9.</t>
</section>
<section anchor="sec-6-3-threat-calendar-server-equivocation"><name>Threat: Calendar Server Equivocation</name>
<t>An attacker operating a compromised OpenTimestamps calendar server could return divergent intermediate commitments to different clients, or withhold a valid commitment entirely.</t>
<t>This is remediated by the OTS protocol design: multiple independent Calendar Servers provide redundant commitment paths. The final anchor is a Bitcoin transaction, not a calendar assertion. A verifier does not need to trust any Calendar Server: verification uses only the Bitcoin blockchain (<xref target="sec-3-1-verification-algorithm-verify-anchor" />, steps 7 to 9). An equivocating Calendar Server can commit divergent branches, and a proof on each may verify against the block that carries it; what it cannot do is place a commitment in a block that was mined before the commitment was made. The temporal claim of this profile is therefore unaffected.</t>
</section>
<section anchor="sec-6-4-threat-transparency-service-equivocation"><name>Threat: Transparency Service Equivocation</name>
<t>A malicious Transparency Service operator could present different log states to different relying parties while anchoring only one version.</t>
<t>Log Root Anchoring (<xref target="sec-2-3-log-root-anchoring" />) constrains this, and the limit of what it constrains should be stated plainly. Anchoring establishes that a given root value existed before a given block height. Two parties holding proofs of two different roots can therefore establish that both values existed and bound each in time, which denies the operator the defence that a divergent state was constructed later.</t>

<t>It does not by itself establish equivocation. Detecting that requires knowing that the two roots are claims about the same log at the same tree size or sequence number, and a bare 32-byte root carries none of that (<xref target="sec-2-3-log-root-anchoring" />). An operator anchoring only one version of a divergent state is detected only if the parties can determine what each anchored root is a root of. That determination is not something this profile supplies, and a Transparency Service that requires it SHOULD anchor a checkpoint object as described in <xref target="sec-2-3-log-root-anchoring" />.</t>
</section>
<section anchor="sec-6-5-threat-temporal-claim-inflation"><name>Threat: Temporal Claim Inflation</name>
<t>A claimant could attempt to present an Anchor Proof with a temporal interpretation broader than what the proof establishes. Two variants are possible under this profile:</t>
<t>The first is claiming a creation time. The normative claim is the block-height binding, which implies only that the artifact existed before block H was mined; <xref target="sec-2-6-4-non-claims"/> excludes creation time explicitly, and verifiers MUST NOT read the binding as a creation-time assertion.</t>
<t>The second is presenting the Reference Wall-Clock Projection as normative. <xref target="sec-2-6-6-relationship-to-wall-clock-time-stamping"/> gives its bounds and states that it is informative; an implementation that displays it to a user MUST indicate that the value is claimed by the miner of the block rather than proved by this profile, and MUST NOT present it in a way that suggests it carries the same standing as the block-height binding. The block-height binding is not itself subject to inflation: a block exists at a given height or it does not.</t>
</section>
<section anchor="sec-6-6-threat-anchor-proof-tampering"><name>Threat: Anchor Proof Tampering</name>
<t>An attacker could modify an Anchor Proof after generation, either by altering the recorded operations in the <tt>.ots</tt> file or by substituting a different value for the hash presented alongside it.</t>
<t>This is remediated by the Verification Algorithm (<xref target="sec-3-1-verification-algorithm-verify-anchor" />), which trusts neither. It re-derives the hash from the artifact bytes rather than accepting <tt>claimed_hash</tt> (step 1), and it replays the recorded operations to the Merkle root of a block header obtained from the ledger (steps 5 to 8): a substituted hash no longer replays to that root, and altered operations no longer reach it. Metadata carried alongside a proof, such as an origin record's <tt>captured_at</tt> field (<xref target="sec-d-1-construction-algorithm-anchor" />), is not among the bytes committed by the proof. Altering it therefore does not break the OTS chain and is not detected by verification; a relying party MUST NOT treat such metadata as anchored.</t>
</section>
<section anchor="sec-6-7-threat-denial-of-anchoring-service"><name>Threat: Denial of Anchoring Service</name>
<t>An attacker could prevent the Transparency Service from submitting commitments to the Anchor Ledger, for example via a denial-of-service attack on the Calendar Servers or the Transparency Service's network connectivity.</t>
<t>This is remediated by the asynchronous design of the protocol (<xref target="sec-2-6-5-pending-proofs-and-temporal-definition" />). Commitments can be retried. The Transparency Service retains the pending .ots proof and resubmits when connectivity is restored. During the outage, existing anchored proofs remain independently verifiable. New Signed Statements are still recorded by the Transparency Service; only their external temporal anchoring is delayed.</t>
</section>
<section anchor="sec-6-8-threat-long-term-hash-algorithm-compromise"><name>Threat: Long-Term Hash Algorithm Compromise</name>
<t>Over decades, SHA-256 may become vulnerable to collision or pre-image attacks due to advances in computing (including quantum computing).</t>
<t>This is remediated by revision of this profile rather than at runtime. Field F2 (<tt>hash_algorithm</tt>, <xref target="sec-2-4-1-abstract-field-requirements" />) MUST be <tt>"sha256"</tt> under this profile, and a successor hash function would be introduced by a revised profile that defines a further permitted value (<xref target="sec-5-6-hash-algorithm-agility" />). Existing proofs retain what they establish, but the two hash properties involved age differently. Collision resistance matters at the moment of anchoring, because an adversary who crafts two artifacts must do so before the commitment is fixed (<xref target="sec-5-4-strength-and-limitations" />); a later break does not reach back to proofs already anchored. Second-preimage resistance is required for as long as a proof is relied upon, because it is what binds an already-anchored commitment to the artifact presented against it (<xref target="sec-6-1-threat-hash-collision-forged-commitment" />). The temporal semantics of <xref target="sec-5-1-claim" /> are therefore stable in one respect and not in the other: that the committed value existed before the attested block does not weaken as the hash function ages, while the binding of that value to a particular artifact does.</t>
</section>
<section anchor="sec-6-9-trust-boundary-hash-intake"><name>Trust Boundary: Hash Intake</name>
<t>The anchoring service establishes that a committed value existed before the validated block at height H was produced. It does not, and cannot, establish the truth, accuracy, or origin of the data that produced that value.</t>
<t>An attacker could submit the hash of a fabricated or falsified artifact before anchoring. The resulting proof would be cryptographically valid and temporally bound, yet the underlying data would be false.</t>
<t>This is not remediated by the protocol. It is an explicit trust boundary: the anchoring service guarantees temporal existence of a hash commitment, not the integrity or authenticity of the pre-image data. Consumers of anchor proofs MUST apply independent verification of the artifact content, authorship, and provenance outside the scope of this specification.</t>
</section>
<section anchor="sec-6-10-positive-property-no-long-term-key-dependency"><name>Positive Property: No Long-Term Key Dependency</name>
<t>Unlike certificate-based timestamping mechanisms (e.g., RFC 3161), Anchor Proofs do not depend on any signing key, certificate chain, or key management infrastructure. The proof's validity derives from the mathematical properties of hash functions and the computational consensus of the Anchor Ledger.</t>
<t>This eliminates three threat categories that apply to key-based timestamping:</t>
<ul>
<li><strong>Key compromise</strong>: There is no private key that, if exposed, would allow an attacker to forge timestamps.</li>
<li><strong>Certificate expiration</strong>: There is no certificate whose expiry would invalidate existing proofs.</li>
<li><strong>Authority revocation</strong>: There is no trusted authority whose revocation would render proofs unverifiable.</li>
</ul>
<t>An Anchor Proof verified today remains verifiable indefinitely, provided SHA-256 retains its second-preimage resistance (<xref target="sec-6-8-threat-long-term-hash-algorithm-compromise" />) and the Anchor Ledger remains accessible (<xref target="sec-5-7-ledger-availability" />).</t>
</section>
<section anchor="sec-6-11-threat-premature-anchored-state-false-finality"><name>Threat: Premature Anchored Status (False Finality)</name>
<t>An attacker -- or a faulty implementation -- could mark a proof as "anchored" before the Bitcoin transaction has reached a durable confirmation depth. A relying party that accepts an "anchored" status asserted by the anchoring service could be misled into relying on a block-height binding that is subsequently invalidated by a chain reorganization.</t>
<t>This is remediated by three independent controls. The verifier retrieves the header itself and compares it against the replayed digest (<xref target="sec-3-1-verification-algorithm-verify-anchor"/>, step 8), so no status field asserted by the anchoring service is relied upon, and it establishes the confirmation depth for itself at step 9. Assumption A2 treats MIN_CONFIRMATIONS as the threshold below which immutability is not yet sound, and a producing implementation SHOULD NOT promote a proof to <tt>anchored</tt> before the attested block reaches it; a deployment policy MAY require more and MUST NOT accept less. And a proof carrying no Bitcoin attestation supports no temporal claim at all: the verifier returns <tt>unverifiable</tt> at step 4 rather than treating a calendar commitment as a provisional binding (<xref target="sec-2-6-5-pending-proofs-and-temporal-definition"/>).</t>
</section>
<section anchor="sec-6-12-threat-batch-integrity-compromise-merkle-mixing"><name>Threat: Batch Integrity Compromise (Merkle Mixing)</name>
<t>In the batch anchoring mode (<xref target="sec-d-3-batch-anchoring-with-merkle-trees" />), an implementation error in the Merkle tree construction could incorrectly associate hashes from different submitters within the same batch. A defective batch could produce a valid-appearing Anchor Proof that binds a hash to an incorrect Merkle position, undermining the evidence integrity of all origins in the batch.</t>
<t>This is remediated by the structure of the proof rather than by any ordering convention. Each hash in a batch carries its own proof, in which the position of its leaf is recorded as the operations that carry it to the committed value (<xref target="sec-d-3-batch-anchoring-with-merkle-trees"/>); a leaf at an incorrect position does not reproduce that value. Integrity is therefore established per hash, and this profile makes no global claim about leaf ordering. Any record of which hashes were aggregated together is operational metadata: it is carried in no proof, consulted by no step of <xref target="sec-3-1-verification-algorithm-verify-anchor"/>, and MUST NOT be treated as evidence of batch membership. Implementations SHOULD sample-verify completed batches to detect systematic construction errors.</t>
</section>
<section anchor="sec-threat-selective-disclosure-of-anchored-alternatives"><name>Threat: Selective Disclosure of Anchored Alternatives</name>
<t>A producer could anchor several alternative artifacts and later disclose only the one that suits it. Each such Anchor Proof verifies, and each establishes the block-height binding for its own artifact. None reveals the others: as <xref target="sec-7-privacy-considerations" /> describes, a ledger entry is unlinkable to any artifact absent its proof, so no ledger-only observer can tell how many alternatives were anchored, or by whom.</t>
<t>A verified Anchor Proof therefore establishes that the disclosed artifact existed before block <tt>H</tt> was mined. It does not establish that the disclosed artifact was the only one its producer anchored, or the one the producer committed to, and relying parties MUST NOT read it as evidence of either.</t>
<t>Where selection matters, the digest needs to be made known to a party other than the producer before the relevant moment. Registering a Signed Statement that carries the digest with a Transparency Service <xref target="RFC9943" /> serves this purpose, since alternative Signed Statements from the same Issuer then remain visible in its append-only history. Committing to a set of artifacts through a single digest over a list of their digests extends the property from one artifact to the set. As with the checkpoint object of <xref target="sec-2-3-log-root-anchoring" />, this profile does not define such a list; it specifies what anchoring establishes about whatever byte sequence it is given. The anchor remains useful alongside such a registration, because it binds the committed digest to a block height independently of the party that received it.</t>
</section>
</section>
<section anchor="sec-7-privacy-considerations"><name>Privacy Considerations</name>
<t>This profile operates on cryptographic hashes only. No artifact content, personal data, or identity information is transmitted to or stored on the Anchor Ledger.</t>
<t>In the batch procedure of <xref target="sec-d-3-batch-anchoring-with-merkle-trees" />, an artifact hash is blinded before it enters the aggregation tree: a fresh 16-byte nonce is appended to it and the result is hashed to form the Merkle leaf (<xref target="sec-d-3-batch-anchoring-with-merkle-trees" />, step 2). This does not apply to the direct mode of <xref target="sec-d-1-construction-algorithm-anchor" />, which submits the artifact hash to the Calendar Server unblinded; the properties below are stated for the batch procedure. The value committed to the ledger is an aggregated Merkle root. The artifact hash reaches that root only through its blinded leaf and a sequence of sibling operations that are recorded solely in the <tt>.ots</tt> proof; the nonce is not public and appears only in the proof. The following privacy-relevant properties follow from this construction.</t>
<ul>
<li>Anchoring an artifact does not create a publicly searchable identifier for it. An observer who possesses the artifact can compute its hash, but cannot find a matching commitment on the ledger: deriving the blinded leaf requires the nonce, and reconstructing the path to the committed root requires the sibling values, both of which are carried only in the proof. The privacy-relevant disclosure is the proof, not the ledger entry.</li>
<li>The ledger record is permanent: an aggregated commitment cannot be removed once confirmed. It remains unlinkable to any artifact absent the corresponding proof.</li>
<li>Temporal ordering between specific artifacts is not exposed to a ledger-only observer. Because ledger entries are unlinkable to particular artifacts, no observer can assert from the ledger alone that one artifact was anchored before another. An ordering statement about specific artifacts requires their proofs, and a proof binds an artifact to a block height, not to a position within a block.</li>
</ul>
<t>Implementations that anchor hashes of privacy-sensitive artifacts SHOULD protect the <tt>.ots</tt> proof accordingly: it is the proof, and not the ledger, that links an artifact to its anchored commitment. A party who obtains a proof can confirm that the corresponding artifact was anchored.</t>
<t>In multi-tenant deployments, the blinded leaves of different tenants are aggregated into shared Merkle roots and committed within the same Bitcoin blocks. A ledger-only observer cannot determine co-batching or cross-tenant ordering from this, since the on-ledger commitment is unlinkable to any tenant's artifacts. A party that holds two <tt>.ots</tt> proofs can determine that the two artifacts share aggregation ancestry, from the branches that their operation sequences share, and not from a shared block height: many unrelated commitments are aggregated into a single block. Shared ancestry is not the same as shared producer batch: a proof does not mark where the producing implementation's tree ends and calendar aggregation begins, so attributing shared ancestry to a particular batch requires knowledge the proof does not carry. Implementations that anchor on behalf of multiple tenants and distribute proofs SHOULD be aware that a holder of two proofs can establish shared aggregation ancestry. Co-batching observed in this way is a confidentiality consideration only; as explained in <xref target="sec-6-12-threat-batch-integrity-compromise-merkle-mixing" />, batch membership is not established by any proof and is not evidence on which a relying party may rely.</t>
</section>
<section anchor="sec-8-iana-considerations"><name>IANA Considerations</name>
<t>This document has no IANA actions.</t>
</section>
  </middle>

  <back>
    <references>
      <name>References</name>
      <references>
        <name>Normative References</name>
<reference anchor="RFC8610" target="https://www.rfc-editor.org/info/rfc8610">
  <front><title>Concise Data Definition Language (CDDL): A Notational Convention to Express Concise Binary Object Representation (CBOR) and JSON Data Structures</title>
  <author initials="H." surname="Birkholz" /><author initials="C." surname="Vigano" />
  <author initials="C." surname="Bormann" /><date year="2019" month="June" /></front>
  <seriesInfo name="RFC" value="8610" /><seriesInfo name="DOI" value="10.17487/RFC8610" />
</reference>

<reference anchor="OTS" target="https://github.com/opentimestamps/python-opentimestamps/tree/python-opentimestamps-v0.4.5">
  <front><title>python-opentimestamps: Python3 Library for Creating and Verifying OpenTimestamps Proofs</title>
  <author initials="P." surname="Todd" /><date year="2023" month="January" /></front>
  <refcontent>Reference Implementation, Release v0.4.5, commit a90094e3ca9a8229abd016be8816d7b90b9fd1e6</refcontent>
</reference>

<reference anchor="RFC3339" target="https://www.rfc-editor.org/rfc/rfc3339">
  <front><title>Date and Time on the Internet: Timestamps</title>
  <author initials="G." surname="Klyne" /><author initials="C." surname="Newman" /><date year="2002" month="July" /></front>
  <seriesInfo name="RFC" value="3339" /><seriesInfo name="DOI" value="10.17487/RFC3339" />
</reference>
<reference anchor="RFC2119" target="https://www.rfc-editor.org/info/rfc2119">
  <front><title>Key words for use in RFCs to Indicate Requirement Levels</title>
  <author initials="S." surname="Bradner" /><date year="1997" month="March" /></front>
  <seriesInfo name="BCP" value="14" /><seriesInfo name="RFC" value="2119" />
  <seriesInfo name="DOI" value="10.17487/RFC2119" />
</reference>
<reference anchor="RFC8174" target="https://www.rfc-editor.org/info/rfc8174">
  <front><title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
  <author initials="B." surname="Leiba" /><date year="2017" month="May" /></front>
  <seriesInfo name="BCP" value="14" /><seriesInfo name="RFC" value="8174" />
  <seriesInfo name="DOI" value="10.17487/RFC8174" />
</reference>
<reference anchor="FIPS180-4" target="https://doi.org/10.6028/NIST.FIPS.180-4">
  <front><title>Secure Hash Standard (SHS)</title>
  <author><organization>National Institute of Standards and Technology</organization></author>
  <date year="2015" month="August" /></front>
  <seriesInfo name="FIPS PUB" value="180-4" />
  <seriesInfo name="DOI" value="10.6028/NIST.FIPS.180-4" />
</reference>
<reference anchor="RFC3552" target="https://www.rfc-editor.org/info/rfc3552">
  <front><title>Guidelines for Writing RFC Text on Security Considerations</title>
  <author initials="E." surname="Rescorla" /><author initials="B." surname="Korver" />
  <date year="2003" month="July" /></front>
  <seriesInfo name="BCP" value="72" /><seriesInfo name="RFC" value="3552" />
  <seriesInfo name="DOI" value="10.17487/RFC3552" />
</reference>

      </references>
      <references>
        <name>Informative References</name>
<reference anchor="ANCHORING" target="https://doi.org/10.5281/zenodo.21389583">
  <front><title>Anchoring Specification</title>
  <author initials="J." surname="Fassbender" /><date year="2026" month="July" /></front>
  <seriesInfo name="Version" value="1.3" />
  <seriesInfo name="DOI" value="10.5281/zenodo.21389583" />
  <refcontent>Canonical URI https://anchoring-spec.org/v1.3/</refcontent>
</reference>
<reference anchor="RFC4648" target="https://www.rfc-editor.org/info/rfc4648">
  <front><title>The Base16, Base32, and Base64 Data Encodings</title>
  <author initials="S." surname="Josefsson" /><date year="2006" month="October" /></front>
  <seriesInfo name="RFC" value="4648" />
  <seriesInfo name="DOI" value="10.17487/RFC4648" />
</reference>
<reference anchor="RFC9942" target="https://www.rfc-editor.org/info/rfc9942">
  <front><title>CBOR Object Signing and Encryption (COSE) Receipts</title>
  <author initials="O." surname="Steele" /><author initials="H." surname="Birkholz" /><author initials="A." surname="Delignat-Lavaud" /><author initials="C." surname="Fournet" />
  <date year="2026" month="June" /></front>
  <seriesInfo name="RFC" value="9942" />
  <seriesInfo name="DOI" value="10.17487/RFC9942" />
</reference>

<reference anchor="RFC3161" target="https://www.rfc-editor.org/info/rfc3161">
  <front><title>Internet X.509 Public Key Infrastructure Time-Stamp Protocol (TSP)</title>
  <author initials="C." surname="Adams" /><author initials="P." surname="Cain" />
  <author initials="D." surname="Pinkas" /><author initials="R." surname="Zuccherato" />
  <date year="2001" month="August" /></front>
  <seriesInfo name="RFC" value="3161" /><seriesInfo name="DOI" value="10.17487/RFC3161" />
</reference>
<reference anchor="RFC9562" target="https://www.rfc-editor.org/info/rfc9562">
  <front><title>Universally Unique IDentifiers (UUIDs)</title>
  <author initials="K." surname="Davis" /><author initials="B." surname="Peabody" /><author initials="P." surname="Leach" />
  <date year="2024" month="May" /></front>
  <seriesInfo name="RFC" value="9562" /><seriesInfo name="DOI" value="10.17487/RFC9562" />
</reference>
<reference anchor="RFC6920" target="https://www.rfc-editor.org/info/rfc6920">
  <front><title>Naming Things with Hashes</title>
  <author initials="S." surname="Farrell" /><author initials="D." surname="Kutscher" />
  <author initials="C." surname="Dannewitz" /><author initials="B." surname="Ohlman" />
  <author initials="A." surname="Keranen" /><author initials="P." surname="Hallam-Baker" />
  <date year="2013" month="April" /></front>
  <seriesInfo name="RFC" value="6920" /><seriesInfo name="DOI" value="10.17487/RFC6920" />
</reference>
<reference anchor="RFC9052" target="https://www.rfc-editor.org/info/rfc9052">
  <front><title>CBOR Object Signing and Encryption (COSE): Structures and Process</title>
  <author initials="J." surname="Schaad" /><date year="2022" month="August" /></front>
  <seriesInfo name="RFC" value="9052" /><seriesInfo name="DOI" value="10.17487/RFC9052" />
</reference>
<reference anchor="RFC9110" target="https://www.rfc-editor.org/info/rfc9110">
  <front><title>HTTP Semantics</title>
  <author initials="R." surname="Fielding" role="editor" />
  <author initials="M." surname="Nottingham" role="editor" />
  <author initials="J." surname="Reschke" role="editor" /><date year="2022" month="June" /></front>
  <seriesInfo name="STD" value="97" /><seriesInfo name="RFC" value="9110" />
  <seriesInfo name="DOI" value="10.17487/RFC9110" />
</reference>
<reference anchor="RFC9921" target="https://www.rfc-editor.org/info/rfc9921">
  <front><title>CBOR Object Signing and Encryption (COSE) Header Parameter for Timestamp Tokens as Defined in RFC 3161</title>
  <author initials="H." surname="Birkholz" /><author initials="T." surname="Fossati" />
  <author initials="M." surname="Riechert" /><date year="2026" month="February" /></front>
  <seriesInfo name="RFC" value="9921" /><seriesInfo name="DOI" value="10.17487/RFC9921" />
</reference>
<reference anchor="RFC9162" target="https://www.rfc-editor.org/info/rfc9162">
  <front><title>Certificate Transparency Version 2.0</title>
  <author initials="B." surname="Laurie" /><author initials="E." surname="Messeri" />
  <author initials="R." surname="Stradling" /><date year="2021" month="December" /></front>
  <seriesInfo name="RFC" value="9162" /><seriesInfo name="DOI" value="10.17487/RFC9162" />
</reference>
<reference anchor="RFC9943" target="https://www.rfc-editor.org/info/rfc9943">
  <front><title>An Architecture for Trustworthy and Transparent Digital Supply Chains</title>
  <author initials="H." surname="Birkholz" /><author initials="A." surname="Delignat-Lavaud" />
  <author initials="C." surname="Fournet" /><author initials="Y." surname="Deshpande" />
  <author initials="S." surname="Lasker" /><date year="2026" month="June" /></front>
  <seriesInfo name="RFC" value="9943" /><seriesInfo name="DOI" value="10.17487/RFC9943" />
</reference>
<reference anchor="OTS-SITE" target="https://opentimestamps.org">
  <front><title>OpenTimestamps</title>
  <author initials="P." surname="Todd" /><date /></front>
  <refcontent>Project Site</refcontent>
</reference>
<reference anchor="OTS-DESIGN" target="https://petertodd.org/2016/opentimestamps-announcement">
  <front><title>OpenTimestamps: Scalable, Trust-Minimized, Distributed Timestamping with Bitcoin</title>
  <author initials="P." surname="Todd" /><date year="2016" month="September" day="15" /></front>
  <refcontent>Announcement</refcontent>
</reference>
<reference anchor="NAKAMOTO" target="https://doi.org/10.2139/ssrn.3440802">
  <front><title>Bitcoin: A Peer-to-Peer Electronic Cash System</title>
  <author initials="S." surname="Nakamoto" /><date year="2008" month="October" /></front>
  <seriesInfo name="DOI" value="10.2139/ssrn.3440802" />
</reference>
<reference anchor="BIP113" target="https://github.com/bitcoin/bips/blob/24e96e870fffaa257b465ce1f0370c14aac588e8/bip-0113.mediawiki">
  <front><title>Median time-past as endpoint for lock-time calculations</title>
  <author initials="T." surname="Kerin" /><author initials="M." surname="Friedenbach" />
  <date year="2015" month="August" /></front>
  <seriesInfo name="BIP" value="113" />
</reference>
<reference anchor="RFC6962" target="https://www.rfc-editor.org/info/rfc6962">
  <front><title>Certificate Transparency</title>
  <author initials="B." surname="Laurie" /><author initials="A." surname="Langley" />
  <author initials="E." surname="Kasper" /><date year="2013" month="June" /></front>
  <seriesInfo name="RFC" value="6962" /><seriesInfo name="DOI" value="10.17487/RFC6962" />
</reference>
<reference anchor="MERKLE">
  <front><title>A Digital Signature Based on a Conventional Encryption Function</title>
  <author initials="R." surname="Merkle" /><date year="1988" /></front>
  <refcontent>Advances in Cryptology -- CRYPTO '87, LNCS vol 293, Springer</refcontent>
  <seriesInfo name="DOI" value="10.1007/3-540-48184-2_32" />
</reference>

      </references>
    </references>
<section anchor="sec-appendix-a-relationship-to-four-layer-evidence-stack"><name>Relationship to Four-Layer Evidence Stack</name>
<artwork>
+---------------------------------------------+
|  L4  Evidence Format        (Partner/SCITT) |
|       Signed Statements, manifests, SBOMs   |
+---------------------------------------------+
|  L3  Signing &amp; Identity     (Partner/TSA)   |
|       SCITT Receipts, X.509, passkeys       |
+---------------------------------------------+
|  L2  Anchor Primitive       (This Profile)  |
|       SHA-256 -&gt; .ots -&gt; Bitcoin            |
+---------------------------------------------+
|  L1  Consensus Layer        (Bitcoin)       |
|       Proof-of-work, append-only            |
+---------------------------------------------+
</artwork>
<t>SCITT operates at L3-L4. This profile defines the L2 integration. L1 is the Bitcoin consensus layer. The layers are separable in that a failure at one does not invalidate the evidence held at another, and a Receipt remains verifiable whether or not an Anchor Proof exists. They are not independent in the direction that matters here: an L2 result depends on validated L1 data, since a verifier returns <tt>valid</tt> only where it can place the attested block on the validated most-work chain (<xref target="sec-3-1-verification-algorithm-verify-anchor" />).</t>
</section>
<section anchor="sec-appendix-b-example-anchored-scitt-flow"><name>Example: Anchored SCITT Flow</name>
<artwork>
1. Producer creates artifact A
2. Producer signs Signed Statement S about A
3. Transparency Service receives S
4. Transparency Service appends S to log → Receipt R
5. Transparency Service computes SHA-256(S) → h
6. Transparency Service submits h to anchoring service
7. Anchoring service returns Anchor Proof P (.ots)
8. Transparency Service stores (R, P) together

Verification (by any auditor):
  a. Verify R against Transparency Service log  → SCITT valid
  b. Verify P against Bitcoin                   → Temporal valid
  c. Compare: h in P == SHA-256(S)              → Binding valid
</artwork>
<t>Note: <xref target="RFC9943" /> Section 4 states that a Receipt is universally verifiable without online access to the Transparency Service, so step (a) does not require the service to remain reachable. It does require the applicable verification material to have been preserved, including the Transparency Service verification key and support for the Receipt and verifiable data structure profile in use. What the loss of the service removes is the ability to obtain fresh Receipts, to audit or replay later log state, and to obtain later consistency evidence. Steps (b) and (c) are unaffected: the temporal and binding proofs do not depend on the log at all.</t>
</section>
<section anchor="sec-appendix-c-implementation-status"><name>Implementation Status</name>
<t>This appendix is informative and records the state of implementation at the time of writing. The profile depends on none of the software described, and a proof produced by any conformant implementation is verifiable without contact with its producer. Entries 1 to 4 were written by the author's organisation and are a self-report, not evidence of interoperability; entries 5 to 7 were not.</t>
<ol>
<li><t><strong>Umarise, first deployment</strong> (JavaScript, OpenTimestamps library 0.4.9; holds the historical registry, which contains the great majority of the anchors Umarise has produced).</t>
<ul>
<li>Implements the construction of <xref target="sec-d-3-batch-anchoring-with-merkle-trees" />; blinding and aggregation are delegated to its OpenTimestamps library. Submits each root to several OpenTimestamps aggregation servers.</li>
<li>Promotes a proof to <tt>anchored</tt> on the presence of a Bitcoin attestation alone: it does not retrieve the header, compare the digest (<xref target="sec-d-2-upgrade-algorithm-bitcoin-confirmation" />, step 2e), record <tt>block_time</tt> (step 2g) or evaluate confirmation depth (step 2f). To that extent it does not meet the requirement of <xref target="sec-6-11-threat-premature-anchored-state-false-finality" />, and an <tt>anchored</tt> status it records denotes inclusion in a block, not that MIN_CONFIRMATIONS was reached.</li>
<li>A scheduled audit replays every anchored proof, compares the digest with the header at the attested height from a public block explorer, and alerts on mismatch. It is an audit, not a gate, which is the distinction <xref target="sec-6-11-threat-premature-anchored-state-false-finality" /> draws, and it does not evaluate depth.</li>
<li>For a proof committed to block 942485, the step 8 comparison was observed rather than assumed: replayed from the artifact hash, the digest matched the Merkle root of that block's header obtained from a source independent of either deployment, with no interaction with the producer.</li>
</ul></li>
<li><t><strong>Umarise, second deployment</strong> (python-opentimestamps 0.4.5, the release pinned in <xref target="sec-5-2-1-note-on-assumption-a4" />; produced the proofs of <xref target="sec-appendix-e-test-vectors" />).</t>
<ul>
<li>Implements the construction of <xref target="sec-d-3-batch-anchoring-with-merkle-trees" />, blinding and aggregation in its own code. Submits each root to four public OpenTimestamps aggregation servers in turn (a.pool.opentimestamps.org, b.pool.opentimestamps.org, a.pool.eternitywall.com and ots.btc.catallaxy.com); a server that does not answer within the timeout is skipped, so a proof may carry fewer than four calendar attestations. It holds no Bitcoin and pays no transaction fee; the calendars commit.</li>
<li>Since August 2026 validates each header before relying on it: proof-of-work against its own nBits, retarget arithmetic, and hash linkage back to a pinned checkpoint on a retarget boundary. Public block explorers are transport for raw header bytes only. Compares the replayed digest with the validated header, records its nTime as <tt>block_time</tt>, and counts depth against the locally validated tip before promoting.</li>
<li>Limits: cumulative work is accumulated but not used to choose between branches, and reorganisation recovery stops at 20 blocks; so every header is shown to be mined and to chain to a fixed point, which is not the same as establishing the most-work chain. The checkpoint was confirmed on 24 September 2026 against two sources independent of those the deployment queries; the deployment inherits that history rather than holding it.</li>
</ul></li>
<li><t><strong>verify-anchoring.org</strong>: client-side verifier, without API contact, account or tracking; public domain.</t>
<ul>
<li>It compares the replayed digest with the Merkle root of the attested header as reported by a single public block explorer, without validating that header itself, and does not evaluate confirmation depth. It reports such a result as "Valid, ledger-confirmed"; under <xref target="sec-3-1-verification-algorithm-verify-anchor" /> the result is <tt>unverifiable</tt>.</li>
</ul></li>
<li><t><strong>@umarise/cli</strong> (Node.js command-line client, published on npm) and <strong>anchor-action</strong> (GitHub Actions integration for CI/CD pipelines, published on GitHub Marketplace): anchoring clients.</t></li>
<li><t><strong>Tiago Pinto, independent producing implementation and verifier</strong> of -03, written from the published text and the OpenTimestamps release pinned in <xref target="sec-5-2-1-note-on-assumption-a4" /> without guidance from the author, under his own condition that any ambiguity be recorded as a defect rather than asked.</t>
<ul>
<li>Given the artifact hashes and nonces of <xref target="sec-e-1-artifacts-and-leaves" />, its aggregation reproduces the first six operations of vector 1 in <xref target="sec-e-5-proof-bytes" />.</li>
<li>Cross-checked in both directions: it returns <tt>valid</tt> for the proofs of <xref target="sec-appendix-e-test-vectors" />, and the OpenTimestamps reference client verifies a proof it produced.</li>
<li>It anchored an artifact of its own and promoted it only after comparing the digest with the header from a Bitcoin full node under its own control and establishing at least six confirmations: the gate the first deployment does not evaluate, and which the second evaluates against a header chain it validates itself rather than against a node of its own.</li>
<li>On 27 August 2026 it verified a proof from the second deployment against that node, the comparison performed in its own code rather than delegated to a library: replay and depth succeeded at 833 confirmations. Steps 1 and 2 were not evaluated, since the commitment and not the artifact was transmitted, so the run did not return <tt>valid</tt> in the sense of <xref target="sec-3-1-verification-algorithm-verify-anchor" />; it establishes that the registered hash existed no later than the attested block, not which artifact, if any, produced it. The run log's SHA-256 was published before the log, and the log is itself anchored.</li>
<li>Package, construction record and defect report: https://donttrustverify.pt/interop/fassbender-scitt-time-anchor-03/. The defect report occasioned the clarifications, in an earlier revision, to the definition of <tt>OTS-UPGRADE</tt> (<xref target="sec-appendix-d-ots-anchoring-protocol-construction-and-verification" />) and to <xref target="sec-d-2-upgrade-algorithm-bitcoin-confirmation" />, step 2b.</li>
</ul></li>
<li><t><strong>Iman Schrock (EMILIA Protocol), independent verifier core</strong> of -06, written under the EMILIA Protocol project.</t>
<ul>
<li>Built under a declared source boundary: the published -06 and the hash-pinned OpenTimestamps v0.4.5 source only, without consulting the author. Prior OpenTimestamps work within the same organisation was disclosed in advance and not used.</li>
<li>Rebuilds the vectors of <xref target="sec-appendix-e-test-vectors" /> byte for byte in synthetic tests. A verifier core, not a producing implementation; not run against a live full node by its author.</li>
<li>Release v0.1.0-draft-06, commit 6ea9afb257df9c9de28fc04001e9cc70124aa6fd: https://github.com/emiliaprotocol/scitt-time-anchor-verifier. Its defect record occasioned changes in this revision.</li>
</ul></li>
<li><t><strong>Cross-check between entries 5 and 6.</strong></t>
<ul>
<li>A proof produced by Tiago Pinto's implementation (-03) for an artifact of its own, carrying three Bitcoin branches (two attested at height 968588, one at 968635), was verified by Iman Schrock's verifier core (-06, commit 6ea9afb, unmodified).</li>
<li>The two agree on the proof: each branch replays to the Merkle root of the block at its attested height, and the lowest verified height, 968588, is the binding.</li>
<li>They differ only where <xref target="sec-3-1-verification-algorithm-verify-anchor" /> allows a verifier to differ, on whether it can place the attested block on a validated chain: without a validated chain source the verifier core returns <tt>unverifiable</tt>; run by Tiago Pinto against a Bitcoin full node he operates, through his own adapter to the verifier core's header-source interface, it returns <tt>valid</tt> at 968588, as his own verifier does. That run has not been reproduced on Iman Schrock's side.</li>
<li>Only one direction was possible, since the verifier core produces no proofs. The publication states that nothing in it shows either implementation correct beyond this vector: https://donttrustverify.pt/interop/fassbender-scitt-time-anchor-crosscheck-emilia/</li>
</ul></li>
</ol>
<ul>
<li>That entries 1 and 2 implement the construction of <xref target="sec-d-3-batch-anchoring-with-merkle-trees" /> was established by recomputing it from proof bytes each issued, not by inspecting either implementation. Proofs from both have been replayed successfully, and both hold origin records and proofs per artifact.</li>
<li>A verifier replays the operations recorded in a proof and forms no expectation about the tree that produced them, so the results above do not depend on the deployments sharing a construction, and a deployment may change its aggregation, or be replaced, without invalidating proofs already issued.</li>
<li>The steps the first deployment does not enforce are those of the producing side. A relying party performs the header comparison and the depth check itself under <xref target="sec-3-1-verification-algorithm-verify-anchor" />, and so obtains the full result independently of the producer, whatever status that producer recorded. This profile keeps the requirement rather than relaxing it, and makes no forecast of when that state will change.</li>
<li>Neither deployment issues an RFC 3161 timestamp alongside an Anchor Proof.</li>
<li>The presentation requirement of <xref target="sec-6-5-threat-temporal-claim-inflation" /> is not yet met by any component above. verify-anchoring.org labels the projection but does not indicate that it is claimed by the miner. In the second deployment, the email notification and the partner webhook would render the projection with the same treatment as the block-height binding, rounded to a calendar day; neither path has yet been exercised, as that deployment has no registered user and no configured webhook destination.</li>
<li>A second reader arrived independently at the same resolution of the defect in entry 5, from the published text alone and without consultation with the author or with that implementer. That reader's own deployment does not implement this profile and its leaf and receipt structures differ; only the concurrence on the reading is recorded.</li>
<li>The continued availability of any particular deployment depends on its hosting arrangements. The verification guarantees require no anchoring service to remain reachable: an <tt>.ots</tt> proof is self-contained relative to the Bitcoin blockchain, and a holder of the proof and the artifact needs nothing else.</li>
</ul>
</section>

<section anchor="sec-appendix-e-test-vectors"><name>Test Vectors</name>

<t>This appendix provides test vectors produced by a single production anchoring run. All four artifacts were submitted within one batch and share one Merkle root and one Bitcoin block. Every intermediate value of the Merkle construction of <xref target="sec-d-3-batch-anchoring-with-merkle-trees" /> is given, so that a reader can recompute the root from the values printed here without executing any software beyond a SHA-256 implementation. <xref target="sec-e-4-proof-structure"/> gives an abridged trace of the first vector's operation sequence, and <xref target="sec-e-5-proof-bytes"/> reproduces all four proof files in full.</t>

<t>The artifacts are the four ASCII strings shown below, chosen so that the vectors identify themselves. No file is required: the artifact bytes are the string, without a trailing newline.</t>
<t>The artifact strings carry the label -03 because the vectors were created for that revision. They are kept byte for byte, since changing the label would change every hash and proof below. The label names the revision the vectors were made for, not the revision being implemented.</t>

<t>For each vector the following values are provided: the artifact string and its SHA-256 hash; the 16-byte blinding nonce recorded in that vector's proof; the resulting Merkle leaf; the block-height binding, which is the normative temporal value (<xref target="sec-5-1-claim" />); and the Reference Wall-Clock Projection, which is informative (<xref target="sec-1-3-terminology" />).</t>

<section anchor="sec-e-1-artifacts-and-leaves"><name>Artifacts, Nonces and Leaves</name>
<t>Each leaf is <tt>SHA-256(artifact_hash || nonce)</tt> over raw bytes, per <xref target="sec-d-3-batch-anchoring-with-merkle-trees" />, step 2. The leaf is not the value a proof commits to: each proof carries the artifact hash as its message, and the nonce append and the Merkle steps appear inside the proof as recorded operations. <xref target="sec-3-1-verification-algorithm-verify-anchor" />, step 3, compares that message against the hash the verifier recomputed from the artifact bytes.</t>
<artwork>
Vector 1
  artifact: "draft-fassbender-scitt-time-anchor-03 vector 1"
  hash:     a183f624efbbda7d6208b9b5a6f9b0c2
            c95d90382eb209c44a1131954071f30b
  nonce:    14ed82e243f4aff3f1794e6754bfae3b
  leaf:     aafc3638a4795c8553b974d36c66aee2
            a7268ede5535a015fbcc73d01a3b8d52

Vector 2
  artifact: "draft-fassbender-scitt-time-anchor-03 vector 2"
  hash:     0df073a986e5e5141c96ec32303b8552
            ded255e4404d0aff996936a846b48cff
  nonce:    b0de8a5526115745c2801d65397db556
  leaf:     7306ab5d2290e681f11699730c07e11a
            6b7b0b4babe30349f3c74d37ba100728

Vector 3
  artifact: "draft-fassbender-scitt-time-anchor-03 vector 3"
  hash:     d82bf84f17057c0e3d7aa6f249dfc2e8
            0a877b910ce4ce14dc40e653fd5be40b
  nonce:    f6518213abf9ba8a92c3cb3bcdb82355
  leaf:     d051a6258f904499056f475bfb69eea5
            0a5018bf58144d25121bf5de22fab9dc

Vector 4
  artifact: "draft-fassbender-scitt-time-anchor-03 vector 4"
  hash:     94a86d117047f2af9a2f5635d0c530cc
            85fc23d20f5d4b42926b94abee7a06ec
  nonce:    2fb8ccfdfb881b481695a37deeced8e6
  leaf:     8864a03154f5dae2ca5f77aab02ad7fa
            4ab56129dd1fbe2e2eb2615a5208413d
</artwork>
</section>

<section anchor="sec-e-2-merkle-construction"><name>Merkle Construction</name>
<t>The four leaves are aggregated by the construction of <xref target="sec-d-3-batch-anchoring-with-merkle-trees" />, step 3: sequential pairing in capture order, concatenation over raw bytes, SHA-256, no sort and no domain-separation prefix. The intermediate values are:</t>
<artwork>
node_12 = SHA-256(leaf_1 || leaf_2)
        = 034dd422ce07afeb555eb7fdbb93418a
          f23455a9c00b79779c8b02d9217778da

node_34 = SHA-256(leaf_3 || leaf_4)
        = 7c629aed686c15987a7684fd2e3f12d0
          90447c00cb98568b6b9d99069ba008e3

root    = SHA-256(node_12 || node_34)
        = b4d19ae256a6c3bceb27d1a31fbb3dd3
          e4574616ebe99a367d6ad7224b7e0749
</artwork>
<t>The position of each leaf appears in its own proof as the direction of the sibling operation. In the proof for vector 1, <tt>leaf_2</tt> is appended; in the proof for vector 2, <tt>leaf_1</tt> is prepended. The same asymmetry holds for vectors 3 and 4. No party needs to agree on the order of the leaf list for either proof to verify.</t>
</section>

<section anchor="sec-e-3-block-height-binding"><name>Block-Height Binding</name>
<t>The root above is not committed to Bitcoin directly. It was submitted to OpenTimestamps aggregation servers, which combined it into further trees; the resulting commitment was placed in a Bitcoin transaction, and that transaction was included in a block. The proof carries the raw transaction bytes and the block's transaction Merkle path, terminating at the Merkle root of the block header (<xref target="sec-d-3-batch-anchoring-with-merkle-trees" />, note on aggregation depth).</t>
<artwork>
Block-height binding: Bitcoin block at height 957289
                      (normative; Section 7.1)

Block hash:           000000000000000000018e5b8c68a63c
                      45b3eeac3a05ecf621a3b3f7890e3534

Header Merkle root,   81383c9ce0d3174bcc6c352a0a821f9e
as committed:         5151e0bc608322ace6aeecccff3c43ef

Header Merkle root,   ef433cffccecaee6ac228360bce05151
as displayed by       9e1f820a2a356ccc4b17d3e09c3c3881
block explorers:

RWCP block.time(H):   2026-07-09T10:02:59Z
                      (Reference Wall-Clock Projection;
                      informative; Section 2.2)

Median Time Past:     2026-07-09T09:26:48Z
                      (strict lower bound on the block's
                      declared nTime, not on physical
                      acceptance time; [BIP113];
                      optional; Section 7.1)

Expected VERIFY-ANCHOR result: valid, for a verifier
                      that has placed block 957289 on the
                      validated most-work chain; otherwise
                      unverifiable (Section 4.1)
</artwork>
<t>Block explorers conventionally display the header Merkle root with its bytes reversed. Both forms are given above; they denote the same value. A verifier comparing the digest produced by step 8 of <xref target="sec-3-1-verification-algorithm-verify-anchor" /> against a value obtained from an explorer has to account for this convention.</t>

<t>Two properties of this vector are worth drawing out. First, the four artifacts share a single block-height binding, which is the expected consequence of batch anchoring (<xref target="sec-d-3-batch-anchoring-with-merkle-trees" />): a shared binding shows that the four commitments share aggregation ancestry, and carries no further meaning; it does not establish that they were submitted together, for the reason given in <xref target="sec-7-privacy-considerations" />. Second, the Median Time Past of the block precedes the moment at which these artifacts were created. This is not a defect. Median Time Past is the median of the eleven preceding blocks' declared timestamps, not a reading of when these artifacts existed, and <xref target="sec-2-6-6-relationship-to-wall-clock-time-stamping" /> sets out the bounded uncertainty of any wall-clock interpretation. The normative claim is unaffected: the artifacts existed before the block at height 957289 was mined.</t>
</section>

<section anchor="sec-e-4-proof-structure"><name>Proof Structure</name>
<t>Each of the four <tt>.ots</tt> proofs is 1877 bytes and records 82 operations on the path to the Bitcoin attestation, together with pending attestations from the calendar servers that also witnessed the submission. Read from the artifact hash, the operations of vector 1 begin:</t>
<artwork>
  1  append   14ed82e243f4aff3f1794e6754bfae3b   (nonce)
  2  sha256   -&gt; aafc3638...   (leaf_1)
  3  append   7306ab5d...      (leaf_2)
  4  sha256   -&gt; 034dd422...   (node_12)
  5  append   7c629aed...      (node_34)
  6  sha256   -&gt; b4d19ae2...   (root)
  ...
 40  prepend  0100000001...6a20   (89 tx bytes)
 41  append   689b0e00           (4 tx bytes)
 42  sha256
 43  sha256   -&gt; 3cad384e...   (transaction id)
  ...
 82  sha256   -&gt; 81383c9c...   (header Merkle root)
</artwork>
<t>Operations 7 to 39 are the aggregation levels contributed by the OpenTimestamps servers. Operations 40 and 41 carry the Bitcoin transaction itself: the prepended bytes end in <tt>0x6a20</tt>, an OP_RETURN opcode followed by a 32-byte push, and the appended bytes are the transaction's locktime. The double SHA-256 that follows yields the transaction identifier. The verifier never retrieves that transaction; it is an operand in the proof (<xref target="sec-3-1-verification-algorithm-verify-anchor" />, note on why no transaction is retrieved).</t>
</section>

<section anchor="sec-e-5-proof-bytes"><name>Proof Bytes</name>
<t>The four proof files are reproduced here in Base64 <xref target="RFC4648" />. They are given so that the verification routes of <xref target="sec-e-6-independent-verification" /> which operate on the byte stream can be carried out from this document alone, without retrieving anything from the author or from any anchoring service. Each file is 1877 octets. The SHA-256 digest given with each block is the digest of the file itself, so that a copy obtained by any other route can be checked against it.</t>
<t>These digests are properties of the files, not of the artifacts: the artifact hashes are those of <xref target="sec-e-1-artifacts-and-leaves" />, and each proof commits to the corresponding artifact hash as its message.</t>

<t><strong>Vector 1</strong> (1877 bytes; SHA-256 of the file:
<tt>2c1ec4cbe4779d8d598b0f9b951d8d2b351b85ab1d9ad4fe0f897f0b8dd7fd97</tt>)</t>
<artwork>
AE9wZW5UaW1lc3RhbXBzAABQcm9vZgC/ieLohOiSlAEIoYP2JO+72n1iCLm1pvmw
wsldkDgusgnEShExlUBx8wvwEBTtguJD9K/z8XlOZ1S/rjsI8CBzBqtdIpDmgfEW
mXMMB+Eaa3sLS6vjA0nzx003uhAHKAjwIHximu1obBWYenaE/S4/EtCQRHwAy5hW
i2udmQaboAjjCP/wCEmfxZHZGUooCPAQ7QY/daVsNxqD7fYAwqrYBAjwIFJbnYvR
vdmZH7PL47V9hD+3TGkKpXXu8wb2Df4Qtvl2CPAg89OUVb24Ssc74Rx/mQOOrPoo
N/1Oe/nofTyRhd265WkI8QRqT24h8AhWGsaCyARDPwCD3+MNLvkMjiwraHR0cHM6
Ly9ib2IuYnRjLmNhbGVuZGFyLm9wZW50aW1lc3RhbXBzLm9yZ//wCIZSLhhMZ615
CPAQqLd6HLFDtpWhEDuO2GRMvwjwIG/7wd7tIAK8HBFntQSIE6K3f9Tsea5Zl0oR
8SUvGzz6CPAguIeKDBfFQ3dV7fkmaKfdb6ghkN0ZWV02HRA3Gn4iQH0I8CDKWfl+
bjSMljmTTC+vd2ZEUuAGGcVv43aw5b9bZwWj9AjxBGpPbiDwCMNKsyfcXcjc/wCD
3+MNLvkMji4taHR0cHM6Ly9hbGljZS5idGMuY2FsZW5kYXIub3BlbnRpbWVzdGFt
cHMub3JnCPAgB229mQIFtRh/osxswjuM4WyVtjxzOWeZO95RHPmnG8cI8CDXsjwu
B4VL48S/bUI/uNsoFru1SHFvcvkYmUX2xO2AygjxILINT4E93vnScObQCBvJ5lSC
62GKlVZOJ1LovQYEakj2CPAgxVX3MZSuyzDgnc9OGzgrIplBQLQcByEac+WBtd7J
Jl4I8SBNUwWUWnag6kv8fa4sXGanp6q1AtABxAM7H2MUiJfbBAjxIGJAbXahuAg3
AcLXjd8jroO+h9U1bCnOdcBJulRp2ZLZCPAgP/jgEmbliQj8n8e0kuCAU0sIsZ+a
5ug7QzDrhZSHxWUI8SAtgXBwnjSTnIYTjO3rbnIdEUBITcAhNOX5sSlIC/HOKQjw
IIP9zG3j0I6rOiCE7kEr/jgq6bR+29Z+ptoxuE2hcct5CPAgpQHkgt+Lx74KDK03
AzRpw0YHDbG0lLQm8VOwJyfMBO4I8VkBAAAAASSIecORoWcofWA6gNCf5I8+e+1R
zQcGpPa6xGGzpAuMxgAAAAD+////ArZNAAAAAAAAFgAULnA+/VPEpS48p9UybzKr
wVv2EcAAAAAAAAAAACJqIPAEaJsOAAgI8SDwliBXVBqocpAtUXlq3ywRwqbCCgFS
xigaLcYmtlSlEQgI8SBxMAag3VfFl9QpO3g4KLkOSRECKORrTa/CG/IgjjhAkAgI
8SAq5cnhYxOCmW/sua7QOCZlGE2aA6z/YPpyHA3EzffVsggI8CAiqOPNs6HbfJQh
u5nc8g7NJ/xoBDV9ZgWf66FM58QVawgI8CD0DpVaSyDS+IoU0hUrEZmfkPhsUkku
QI4LrLKO5tciQwgI8SC7wLDAGt43IRrU9JR2K5nwVm+5DrH5MQvlgt+P9f8lSAgI
8CDGeF0Lh8xVI1OXPsN6vtgYSO4Tq9s95IPaiEoi52edZggI8CC7H6WreEi4OXy7
/ptHgONhWkLZAU4qZJzQRTRtJI9shQgI8SDBeMZI3JBR7sbV/PL3zf7dcqG260yN
95cUTtObe2JAiQgI8SDkuG/hj5qN55IAtetwfqsmFXgT8IHZ6BR6KBaQ5pe86wgI
8CAQNiANT4fvD+1vKkjCFQOa4Ay+yiE6VtjLRwWrsCnS8ggI8CDxmzybFMTkRD6W
3UdihJh0XER+U1gnzbTx9IK0P7xs2wgI8CDxrZxNHtEZ9OG8BfLMit9cQxOR4OAp
w+KlcP13yA+v/AgIAAWIlg1z1xkBA+m2Ov/wEJBdIv6gwloz+kKAJu4wfUEI8SBo
29YiUYsK1iEdk6qwALjkN49PODlZdF+BvU3j+1aoJgjxIAoRJg5KQ6Bbn8k7G897
sPD3EfuP2+Jfu7anltcF+RjCCPEggdqGH1dXlA2HpKD6wA4IuHj8D/3RH9Lalm6N
Qhcdio8I8QRqT24j8Ah1k1IHPEGT2ACD3+MNLvkMjiMiaHR0cHM6Ly9idGMuY2Fs
ZW5kYXIuY2F0YWxsYXh5LmNvbfAQ4JpmZcA9i238c6jUbV3kaAjxIHY8+X6YJNwa
Ech2AbMgM24ZK7G6BekN5Bbrea4PeOcZCPEg23ru8ityQrA77MWfYYHJxPfeXb4D
Y7fUgcM4UQbICp4I8CDebd/tjJyWNt9+3G1xTnyD9Q2hEE6msKLeDtMU4BpOegjw
IOOpXL+sN5tkMseZHTiRbK1mG+Go3HXrrjj4RBnsH2MYCPEEak9uIvAIaUZsPtMo
9gcAg9/jDS75DI4pKGh0dHBzOi8vZmlubmV5LmNhbGVuZGFyLmV0ZXJuaXR5d2Fs
bC5jb20=
</artwork>

<t><strong>Vector 2</strong> (1877 bytes; SHA-256 of the file:
<tt>dc630f6039890b988b2de9c51cf5b93915a24488ab69f05b3d3ed4957febb900</tt>)</t>
<artwork>
AE9wZW5UaW1lc3RhbXBzAABQcm9vZgC/ieLohOiSlAEIDfBzqYbl5RQcluwyMDuF
Ut7SVeRATQr/mWk2qEa0jP/wELDeilUmEVdFwoAdZTl9tVYI8SCq/DY4pHlchVO5
dNNsZq7ipyaO3lU1oBX7zHPQGjuNUgjwIHximu1obBWYenaE/S4/EtCQRHwAy5hW
i2udmQaboAjjCP/wCEmfxZHZGUooCPAQ7QY/daVsNxqD7fYAwqrYBAjwIFJbnYvR
vdmZH7PL47V9hD+3TGkKpXXu8wb2Df4Qtvl2CPAg89OUVb24Ssc74Rx/mQOOrPoo
N/1Oe/nofTyRhd265WkI8QRqT24h8AhWGsaCyARDPwCD3+MNLvkMjiwraHR0cHM6
Ly9ib2IuYnRjLmNhbGVuZGFyLm9wZW50aW1lc3RhbXBzLm9yZ//wCIZSLhhMZ615
CPAQqLd6HLFDtpWhEDuO2GRMvwjwIG/7wd7tIAK8HBFntQSIE6K3f9Tsea5Zl0oR
8SUvGzz6CPAguIeKDBfFQ3dV7fkmaKfdb6ghkN0ZWV02HRA3Gn4iQH0I8CDKWfl+
bjSMljmTTC+vd2ZEUuAGGcVv43aw5b9bZwWj9AjxBGpPbiDwCMNKsyfcXcjc/wCD
3+MNLvkMji4taHR0cHM6Ly9hbGljZS5idGMuY2FsZW5kYXIub3BlbnRpbWVzdGFt
cHMub3JnCPAgB229mQIFtRh/osxswjuM4WyVtjxzOWeZO95RHPmnG8cI8CDXsjwu
B4VL48S/bUI/uNsoFru1SHFvcvkYmUX2xO2AygjxILINT4E93vnScObQCBvJ5lSC
62GKlVZOJ1LovQYEakj2CPAgxVX3MZSuyzDgnc9OGzgrIplBQLQcByEac+WBtd7J
Jl4I8SBNUwWUWnag6kv8fa4sXGanp6q1AtABxAM7H2MUiJfbBAjxIGJAbXahuAg3
AcLXjd8jroO+h9U1bCnOdcBJulRp2ZLZCPAgP/jgEmbliQj8n8e0kuCAU0sIsZ+a
5ug7QzDrhZSHxWUI8SAtgXBwnjSTnIYTjO3rbnIdEUBITcAhNOX5sSlIC/HOKQjw
IIP9zG3j0I6rOiCE7kEr/jgq6bR+29Z+ptoxuE2hcct5CPAgpQHkgt+Lx74KDK03
AzRpw0YHDbG0lLQm8VOwJyfMBO4I8VkBAAAAASSIecORoWcofWA6gNCf5I8+e+1R
zQcGpPa6xGGzpAuMxgAAAAD+////ArZNAAAAAAAAFgAULnA+/VPEpS48p9UybzKr
wVv2EcAAAAAAAAAAACJqIPAEaJsOAAgI8SDwliBXVBqocpAtUXlq3ywRwqbCCgFS
xigaLcYmtlSlEQgI8SBxMAag3VfFl9QpO3g4KLkOSRECKORrTa/CG/IgjjhAkAgI
8SAq5cnhYxOCmW/sua7QOCZlGE2aA6z/YPpyHA3EzffVsggI8CAiqOPNs6HbfJQh
u5nc8g7NJ/xoBDV9ZgWf66FM58QVawgI8CD0DpVaSyDS+IoU0hUrEZmfkPhsUkku
QI4LrLKO5tciQwgI8SC7wLDAGt43IRrU9JR2K5nwVm+5DrH5MQvlgt+P9f8lSAgI
8CDGeF0Lh8xVI1OXPsN6vtgYSO4Tq9s95IPaiEoi52edZggI8CC7H6WreEi4OXy7
/ptHgONhWkLZAU4qZJzQRTRtJI9shQgI8SDBeMZI3JBR7sbV/PL3zf7dcqG260yN
95cUTtObe2JAiQgI8SDkuG/hj5qN55IAtetwfqsmFXgT8IHZ6BR6KBaQ5pe86wgI
8CAQNiANT4fvD+1vKkjCFQOa4Ay+yiE6VtjLRwWrsCnS8ggI8CDxmzybFMTkRD6W
3UdihJh0XER+U1gnzbTx9IK0P7xs2wgI8CDxrZxNHtEZ9OG8BfLMit9cQxOR4OAp
w+KlcP13yA+v/AgIAAWIlg1z1xkBA+m2Ov/wEJBdIv6gwloz+kKAJu4wfUEI8SBo
29YiUYsK1iEdk6qwALjkN49PODlZdF+BvU3j+1aoJgjxIAoRJg5KQ6Bbn8k7G897
sPD3EfuP2+Jfu7anltcF+RjCCPEggdqGH1dXlA2HpKD6wA4IuHj8D/3RH9Lalm6N
Qhcdio8I8QRqT24j8Ah1k1IHPEGT2ACD3+MNLvkMjiMiaHR0cHM6Ly9idGMuY2Fs
ZW5kYXIuY2F0YWxsYXh5LmNvbfAQ4JpmZcA9i238c6jUbV3kaAjxIHY8+X6YJNwa
Ech2AbMgM24ZK7G6BekN5Bbrea4PeOcZCPEg23ru8ityQrA77MWfYYHJxPfeXb4D
Y7fUgcM4UQbICp4I8CDebd/tjJyWNt9+3G1xTnyD9Q2hEE6msKLeDtMU4BpOegjw
IOOpXL+sN5tkMseZHTiRbK1mG+Go3HXrrjj4RBnsH2MYCPEEak9uIvAIaUZsPtMo
9gcAg9/jDS75DI4pKGh0dHBzOi8vZmlubmV5LmNhbGVuZGFyLmV0ZXJuaXR5d2Fs
bC5jb20=
</artwork>

<t><strong>Vector 3</strong> (1877 bytes; SHA-256 of the file:
<tt>4e2a3ce0758ab8bef4b1e8311dea6e987b23a718d6eb40b6ed9f239e8cfe1f73</tt>)</t>
<artwork>
AE9wZW5UaW1lc3RhbXBzAABQcm9vZgC/ieLohOiSlAEI2Cv4TxcFfA49eqbySd/C
6AqHe5EM5M4U3EDmU/1b5AvwEPZRghOr+bqKksPLO824I1UI8CCIZKAxVPXa4spf
d6qwKtf6SrVhKd0fvi4usmFaUghBPQjxIANN1CLOB6/rVV63/buTQYryNFWpwAt5
d5yLAtkhd3jaCP/wCEmfxZHZGUooCPAQ7QY/daVsNxqD7fYAwqrYBAjwIFJbnYvR
vdmZH7PL47V9hD+3TGkKpXXu8wb2Df4Qtvl2CPAg89OUVb24Ssc74Rx/mQOOrPoo
N/1Oe/nofTyRhd265WkI8QRqT24h8AhWGsaCyARDPwCD3+MNLvkMjiwraHR0cHM6
Ly9ib2IuYnRjLmNhbGVuZGFyLm9wZW50aW1lc3RhbXBzLm9yZ//wCIZSLhhMZ615
CPAQqLd6HLFDtpWhEDuO2GRMvwjwIG/7wd7tIAK8HBFntQSIE6K3f9Tsea5Zl0oR
8SUvGzz6CPAguIeKDBfFQ3dV7fkmaKfdb6ghkN0ZWV02HRA3Gn4iQH0I8CDKWfl+
bjSMljmTTC+vd2ZEUuAGGcVv43aw5b9bZwWj9AjxBGpPbiDwCMNKsyfcXcjc/wCD
3+MNLvkMji4taHR0cHM6Ly9hbGljZS5idGMuY2FsZW5kYXIub3BlbnRpbWVzdGFt
cHMub3JnCPAgB229mQIFtRh/osxswjuM4WyVtjxzOWeZO95RHPmnG8cI8CDXsjwu
B4VL48S/bUI/uNsoFru1SHFvcvkYmUX2xO2AygjxILINT4E93vnScObQCBvJ5lSC
62GKlVZOJ1LovQYEakj2CPAgxVX3MZSuyzDgnc9OGzgrIplBQLQcByEac+WBtd7J
Jl4I8SBNUwWUWnag6kv8fa4sXGanp6q1AtABxAM7H2MUiJfbBAjxIGJAbXahuAg3
AcLXjd8jroO+h9U1bCnOdcBJulRp2ZLZCPAgP/jgEmbliQj8n8e0kuCAU0sIsZ+a
5ug7QzDrhZSHxWUI8SAtgXBwnjSTnIYTjO3rbnIdEUBITcAhNOX5sSlIC/HOKQjw
IIP9zG3j0I6rOiCE7kEr/jgq6bR+29Z+ptoxuE2hcct5CPAgpQHkgt+Lx74KDK03
AzRpw0YHDbG0lLQm8VOwJyfMBO4I8VkBAAAAASSIecORoWcofWA6gNCf5I8+e+1R
zQcGpPa6xGGzpAuMxgAAAAD+////ArZNAAAAAAAAFgAULnA+/VPEpS48p9UybzKr
wVv2EcAAAAAAAAAAACJqIPAEaJsOAAgI8SDwliBXVBqocpAtUXlq3ywRwqbCCgFS
xigaLcYmtlSlEQgI8SBxMAag3VfFl9QpO3g4KLkOSRECKORrTa/CG/IgjjhAkAgI
8SAq5cnhYxOCmW/sua7QOCZlGE2aA6z/YPpyHA3EzffVsggI8CAiqOPNs6HbfJQh
u5nc8g7NJ/xoBDV9ZgWf66FM58QVawgI8CD0DpVaSyDS+IoU0hUrEZmfkPhsUkku
QI4LrLKO5tciQwgI8SC7wLDAGt43IRrU9JR2K5nwVm+5DrH5MQvlgt+P9f8lSAgI
8CDGeF0Lh8xVI1OXPsN6vtgYSO4Tq9s95IPaiEoi52edZggI8CC7H6WreEi4OXy7
/ptHgONhWkLZAU4qZJzQRTRtJI9shQgI8SDBeMZI3JBR7sbV/PL3zf7dcqG260yN
95cUTtObe2JAiQgI8SDkuG/hj5qN55IAtetwfqsmFXgT8IHZ6BR6KBaQ5pe86wgI
8CAQNiANT4fvD+1vKkjCFQOa4Ay+yiE6VtjLRwWrsCnS8ggI8CDxmzybFMTkRD6W
3UdihJh0XER+U1gnzbTx9IK0P7xs2wgI8CDxrZxNHtEZ9OG8BfLMit9cQxOR4OAp
w+KlcP13yA+v/AgIAAWIlg1z1xkBA+m2Ov/wEJBdIv6gwloz+kKAJu4wfUEI8SBo
29YiUYsK1iEdk6qwALjkN49PODlZdF+BvU3j+1aoJgjxIAoRJg5KQ6Bbn8k7G897
sPD3EfuP2+Jfu7anltcF+RjCCPEggdqGH1dXlA2HpKD6wA4IuHj8D/3RH9Lalm6N
Qhcdio8I8QRqT24j8Ah1k1IHPEGT2ACD3+MNLvkMjiMiaHR0cHM6Ly9idGMuY2Fs
ZW5kYXIuY2F0YWxsYXh5LmNvbfAQ4JpmZcA9i238c6jUbV3kaAjxIHY8+X6YJNwa
Ech2AbMgM24ZK7G6BekN5Bbrea4PeOcZCPEg23ru8ityQrA77MWfYYHJxPfeXb4D
Y7fUgcM4UQbICp4I8CDebd/tjJyWNt9+3G1xTnyD9Q2hEE6msKLeDtMU4BpOegjw
IOOpXL+sN5tkMseZHTiRbK1mG+Go3HXrrjj4RBnsH2MYCPEEak9uIvAIaUZsPtMo
9gcAg9/jDS75DI4pKGh0dHBzOi8vZmlubmV5LmNhbGVuZGFyLmV0ZXJuaXR5d2Fs
bC5jb20=
</artwork>

<t><strong>Vector 4</strong> (1877 bytes; SHA-256 of the file:
<tt>4905b48a6d26ed43a12e92c6a75930b2d36ace7889a5937c61de45f6753b450d</tt>)</t>
<artwork>
AE9wZW5UaW1lc3RhbXBzAABQcm9vZgC/ieLohOiSlAEIlKhtEXBH8q+aL1Y10MUw
zIX8I9IPXUtCkmuUq+56BuzwEC+4zP37iBtIFpWjfe7O2OYI8SDQUaYlj5BEmQVv
R1v7ae6lClAYv1gUTSUSG/XeIvq53AjxIANN1CLOB6/rVV63/buTQYryNFWpwAt5
d5yLAtkhd3jaCP/wCEmfxZHZGUooCPAQ7QY/daVsNxqD7fYAwqrYBAjwIFJbnYvR
vdmZH7PL47V9hD+3TGkKpXXu8wb2Df4Qtvl2CPAg89OUVb24Ssc74Rx/mQOOrPoo
N/1Oe/nofTyRhd265WkI8QRqT24h8AhWGsaCyARDPwCD3+MNLvkMjiwraHR0cHM6
Ly9ib2IuYnRjLmNhbGVuZGFyLm9wZW50aW1lc3RhbXBzLm9yZ//wCIZSLhhMZ615
CPAQqLd6HLFDtpWhEDuO2GRMvwjwIG/7wd7tIAK8HBFntQSIE6K3f9Tsea5Zl0oR
8SUvGzz6CPAguIeKDBfFQ3dV7fkmaKfdb6ghkN0ZWV02HRA3Gn4iQH0I8CDKWfl+
bjSMljmTTC+vd2ZEUuAGGcVv43aw5b9bZwWj9AjxBGpPbiDwCMNKsyfcXcjc/wCD
3+MNLvkMji4taHR0cHM6Ly9hbGljZS5idGMuY2FsZW5kYXIub3BlbnRpbWVzdGFt
cHMub3JnCPAgB229mQIFtRh/osxswjuM4WyVtjxzOWeZO95RHPmnG8cI8CDXsjwu
B4VL48S/bUI/uNsoFru1SHFvcvkYmUX2xO2AygjxILINT4E93vnScObQCBvJ5lSC
62GKlVZOJ1LovQYEakj2CPAgxVX3MZSuyzDgnc9OGzgrIplBQLQcByEac+WBtd7J
Jl4I8SBNUwWUWnag6kv8fa4sXGanp6q1AtABxAM7H2MUiJfbBAjxIGJAbXahuAg3
AcLXjd8jroO+h9U1bCnOdcBJulRp2ZLZCPAgP/jgEmbliQj8n8e0kuCAU0sIsZ+a
5ug7QzDrhZSHxWUI8SAtgXBwnjSTnIYTjO3rbnIdEUBITcAhNOX5sSlIC/HOKQjw
IIP9zG3j0I6rOiCE7kEr/jgq6bR+29Z+ptoxuE2hcct5CPAgpQHkgt+Lx74KDK03
AzRpw0YHDbG0lLQm8VOwJyfMBO4I8VkBAAAAASSIecORoWcofWA6gNCf5I8+e+1R
zQcGpPa6xGGzpAuMxgAAAAD+////ArZNAAAAAAAAFgAULnA+/VPEpS48p9UybzKr
wVv2EcAAAAAAAAAAACJqIPAEaJsOAAgI8SDwliBXVBqocpAtUXlq3ywRwqbCCgFS
xigaLcYmtlSlEQgI8SBxMAag3VfFl9QpO3g4KLkOSRECKORrTa/CG/IgjjhAkAgI
8SAq5cnhYxOCmW/sua7QOCZlGE2aA6z/YPpyHA3EzffVsggI8CAiqOPNs6HbfJQh
u5nc8g7NJ/xoBDV9ZgWf66FM58QVawgI8CD0DpVaSyDS+IoU0hUrEZmfkPhsUkku
QI4LrLKO5tciQwgI8SC7wLDAGt43IRrU9JR2K5nwVm+5DrH5MQvlgt+P9f8lSAgI
8CDGeF0Lh8xVI1OXPsN6vtgYSO4Tq9s95IPaiEoi52edZggI8CC7H6WreEi4OXy7
/ptHgONhWkLZAU4qZJzQRTRtJI9shQgI8SDBeMZI3JBR7sbV/PL3zf7dcqG260yN
95cUTtObe2JAiQgI8SDkuG/hj5qN55IAtetwfqsmFXgT8IHZ6BR6KBaQ5pe86wgI
8CAQNiANT4fvD+1vKkjCFQOa4Ay+yiE6VtjLRwWrsCnS8ggI8CDxmzybFMTkRD6W
3UdihJh0XER+U1gnzbTx9IK0P7xs2wgI8CDxrZxNHtEZ9OG8BfLMit9cQxOR4OAp
w+KlcP13yA+v/AgIAAWIlg1z1xkBA+m2Ov/wEJBdIv6gwloz+kKAJu4wfUEI8SBo
29YiUYsK1iEdk6qwALjkN49PODlZdF+BvU3j+1aoJgjxIAoRJg5KQ6Bbn8k7G897
sPD3EfuP2+Jfu7anltcF+RjCCPEggdqGH1dXlA2HpKD6wA4IuHj8D/3RH9Lalm6N
Qhcdio8I8QRqT24j8Ah1k1IHPEGT2ACD3+MNLvkMjiMiaHR0cHM6Ly9idGMuY2Fs
ZW5kYXIuY2F0YWxsYXh5LmNvbfAQ4JpmZcA9i238c6jUbV3kaAjxIHY8+X6YJNwa
Ech2AbMgM24ZK7G6BekN5Bbrea4PeOcZCPEg23ru8ityQrA77MWfYYHJxPfeXb4D
Y7fUgcM4UQbICp4I8CDebd/tjJyWNt9+3G1xTnyD9Q2hEE6msKLeDtMU4BpOegjw
IOOpXL+sN5tkMseZHTiRbK1mG+Go3HXrrjj4RBnsH2MYCPEEak9uIvAIaUZsPtMo
9gcAg9/jDS75DI4pKGh0dHBzOi8vZmlubmV5LmNhbGVuZGFyLmV0ZXJuaXR5d2Fs
bC5jb20=
</artwork>
</section>

<section anchor="sec-e-5-negative-test-vector"><name>Negative Test Vector</name>
<t>A conformant implementation returns <tt>invalid</tt> (<xref target="sec-3-1-verification-algorithm-verify-anchor" />, step 2) if the artifact bytes do not match the anchored hash. Substituting any byte in the artifact while presenting the original proof yields:</t>
<artwork>
Modified artifact:    [any single byte change to any of the
                       four artifact strings above]
Original proof:       the corresponding .ots proof
Expected result:      invalid (step 2: hash mismatch)
</artwork>
<t>This vector demonstrates the binding between artifact bytes and Anchor Proof: a proof valid for one byte sequence is necessarily invalid for any other byte sequence.</t>
<t>A second vector exercises step 7b (<xref target="sec-3-1-verification-algorithm-verify-anchor" />). A conformant implementation returns <tt>unverifiable</tt> (<xref target="sec-3-1-verification-algorithm-verify-anchor" />, step 7b) where the attested header cannot be placed on the validated chain supplied to it, whatever source the candidate bytes came from:</t>
<artwork>
Artifact and proof:   any of the four pairs above,
                      unmodified
validated_chain:      a validated header set that does not
                      reach the attested height, or none
                      at all
Expected result:      unverifiable (step 7b returns NULL,
                      so step 8 leaves the branch
                      unevaluated)
</artwork>
<t>The vector is stated over the supplied view rather than over a particular source of bytes, because that is what the step turns on: an implementation that returns <tt>valid</tt> here has treated retrieval as validation, which is the failure <xref target="sec-appendix-c-implementation-status" /> records in deployed verifiers. An implementation whose header source is a block explorer and which holds no validated view of its own fails this vector by construction.</t>
</section>

<section anchor="sec-e-6-independent-verification"><name>Independent Verification</name>
<t>The vectors can be verified without contacting the anchoring service and without trusting this document. Four routes are available, in decreasing order of independence.</t>
<ol>
<li>Recompute the leaves and the root from the values in D.1 and D.2 using any SHA-256 implementation. This confirms the construction of <xref target="sec-d-3-batch-anchoring-with-merkle-trees" /> and requires no network access.</li>
<li>Take a proof from <xref target="sec-e-5-proof-bytes"/>, recover its operation sequence and attestations from its byte stream, and then execute the procedure of <xref target="sec-3-1-verification-algorithm-verify-anchor" /> directly: replay each Bitcoin branch from the artifact hash using the operations recorded in the proof, and compare each branch's digest against the Merkle root of the header at that branch's attested height, obtained from a Bitcoin node under the verifier's own control. This route uses no OpenTimestamps software for the replay: the algorithms of <xref target="sec-3-1-verification-algorithm-verify-anchor" /> and <xref target="sec-appendix-d-ots-anchoring-protocol-construction-and-verification" /> are sufficient for it. Only the recovery of the operation sequence depends on the serialisation format, which this profile does not specify; see <xref target="sec-5-2-1-note-on-assumption-a4" />.</li>
<li>Take a proof from <xref target="sec-e-5-proof-bytes"/>, replay it with an OpenTimestamps client, and compare the resulting digest against the header Merkle root of block 957289 obtained from a Bitcoin node under the verifier's own control. This confirms the ledger binding of <xref target="sec-3-1-verification-algorithm-verify-anchor" /> and depends on no third party. It differs from the preceding route only in that the replay is performed by OpenTimestamps software rather than by an implementation of this profile.</li>
<li>Use a client-side verification tool such as https://verify-anchoring.org, which performs the same replay in the browser and compares the result with the Merkle root reported by a public block explorer. This is convenient, and it reproduces the digest comparison, but it does not place the attested block on the validated most-work chain: the header is reported to it rather than established by it. Under the note on header sources in <xref target="sec-3-1-verification-algorithm-verify-anchor" /> the result is <tt>unverifiable</tt>, not <tt>valid</tt>, whatever the tool displays (<xref target="sec-appendix-c-implementation-status" />, entry 3). The route is useful for confirming that the replay reaches the expected value; it does not complete the ledger check.</li>
</ol>
<t>The block-height binding is the primary result in each case. The Reference Wall-Clock Projection <tt>block.time(H)</tt> is presented alongside it (<xref target="sec-1-3-terminology" />), and never in place of it.</t>
</section>

</section>
<section numbered="false" anchor="sec-acknowledgements"><name>Acknowledgements</name>
<t>The author thanks Eliot Lear (Independent Submissions Editor) for his detailed review of the initial submission and his concrete guidance on strengthening the formal security argument, algorithmic specification, and Security Considerations structure.</t>
<t>The author thanks Nicole Bates (Microsoft, SCITT Working Group Chair) for her review of the SCITT integration approach and her assessment that the mechanism requires no changes to existing SCITT protocols.</t>
<t>The author thanks Tiago Pinto for an independent implementation of the producing side of this profile, built from the published document without consultation, and for the defect report that occasioned the clarifications in the definition of <tt>OTS-UPGRADE</tt> (<xref target="sec-appendix-d-ots-anchoring-protocol-construction-and-verification" />) and in <xref target="sec-d-2-upgrade-algorithm-bitcoin-confirmation" />, step 2b.</t>
<t>The author thanks Aleksei Chirkunov for a review of revision -03, and for the exchange that followed it, read throughout from the position of an operator who would have to run this profile. That reading occasioned the statement of implementation status in <xref target="sec-1-introduction" />, the treatment of the pinned release in <xref target="sec-5-2-1-note-on-assumption-a4" /> and the conformance criterion added to it, the correction in <xref target="sec-2-2-statement-anchoring" />, the statement in <xref target="sec-2-4-1-abstract-field-requirements" /> of what a proof does not carry, the note under Table 5 on what an outcome reports, and the placement of the scoping statement in <xref target="sec-2-5-anchor-ledger-requirements" />.</t>
<t>The author thanks Henri Sirkkavaara for observing that a reader arriving through the motivating example would not find the profile's position on evidentiary presumptions, which occasioned the paragraph on that subject in <xref target="sec-2-6-6-relationship-to-wall-clock-time-stamping" /> and the pointer to it in <xref target="sec-1-1-motivating-example" />.</t>
<t>The author thanks Iman Schrock (EMILIA Protocol) for a review of revision -06 that occasioned the statement at the start of <xref target="sec-3-1-verification-algorithm-verify-anchor" /> of what a verifier holding only the commitment can establish, and a correction to <xref target="sec-appendix-c-implementation-status" />; and for an independent implementation of the verifier, built under a declared source boundary without consultation, whose defect record occasioned the clarifications in Sections <xref target="sec-1-7-layering" format="counter" />, <xref target="sec-1-3-terminology" format="counter" />, <xref target="sec-2-4-anchor-proof-format" format="counter" />, <xref target="sec-2-5-anchor-ledger-requirements" format="counter" />, <xref target="sec-3-1-verification-algorithm-verify-anchor" format="counter" />, <xref target="sec-3-3-error-conditions" format="counter" />, <xref target="sec-d-1-construction-algorithm-anchor" format="counter" />, <xref target="sec-d-3-batch-anchoring-with-merkle-trees" format="counter" /> and <xref target="sec-5-7-ledger-availability" format="counter" /> and in <xref target="sec-appendix-e-test-vectors" />.</t>
<t>The exclusion concerning omission in <xref target="sec-2-6-4-non-claims" />, and the discussion accompanying it, follows from observations made independently by Tiago Pinto and Aleksei Chirkunov.</t>
<t>The OpenTimestamps protocol was designed by Peter Todd, whose reference implementation <xref target="OTS" /> underpins the anchoring mechanism described in this document.</t>

</section>
  </back>
</rfc>