<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE rfc [
]>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude"
     ipr="trust200902"
     docName="draft-fassbender-scitt-time-anchor-03"
     category="info"
     submissionType="independent"
     version="3"
     xml:lang="en"
     tocInclude="true"
     sortRefs="true"
     symRefs="true">

  <front>
    <title abbrev="Bitcoin-Anchored Temporal Proof">Bitcoin-Anchored Temporal Proof for Transparency Services</title>
    <seriesInfo name="Internet-Draft" value="draft-fassbender-scitt-time-anchor-03"/>
    <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="July" day="24"/>
    <area>Security</area>
    <workgroup>SCITT</workgroup>
    <keyword>timestamping</keyword>
    <keyword>Bitcoin</keyword>
    <keyword>OpenTimestamps</keyword>
    <keyword>transparency</keyword>
    <abstract>
      <t>This document defines a mechanism for temporal anchoring of digital artifacts by committing cryptographic hashes 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 the anchoring service. The SCITT Architecture is used as the primary integration example. No changes to the SCITT architecture 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. The security of an RFC 3161 timestamp 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, with a revocation reasonCode indicating that the key was not compromised, leaves tokens issued before revocation valid under <xref target="RFC3161"/>; revocation without a reasonCode, or with keyCompromise, does not. Certificate expiry alone need not be fatal: <xref target="RFC9921"/> exists to support signatures that must remain verifiable after the signing certificate has expired. What each of these modes has 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>The distinction is structural:</t>
<ul>
<li>RFC 3161: a trusted authority attests to the time of a hash commitment. Verification requires trust in that authority.</li>
<li>This document: temporal proof is a consequence of ledger inclusion. Verification requires only access to public ledger state.</li>
</ul>
<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>Furthermore, the Verifier does not need to trust any Calendar Server or anchoring intermediary -- the trust root is Bitcoin consensus itself. This property, termed "verification independence" in this document (Section 3.2), is the primary architectural distinction from existing time-stamping mechanisms. 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 defines an OPTIONAL profile that narrows this gap by anchoring operations to the Bitcoin blockchain <xref target="NAKAMOTO"/> via the OpenTimestamps protocol <xref target="OTS"/>.</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: calendar aggregation typically adds hours before a commitment reaches a block, and the confirmation depth of Section 6.11 applies on top of that. The anchoring service provides proof of existence and time, not claims about authorship, quality, or regulatory compliance.</t>
</section>
<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.</li>
</ul>
<ul>
<li><strong>External Temporal Anchor</strong>: A Temporal Anchor that is computed and stored outside the system whose artifacts are being anchored. In the SCITT context, "external" means external to the Transparency Service's verifiable log: the anchor is committed to a separate ledger (the Anchor Ledger, see 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 (Section 3.1). 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 Section 2.4.2.</li>
<li><strong>claimed_hash</strong> (REQUIRED): The SHA-256 hash of the artifact, encoded as a hex string with optional <tt>"sha256:"</tt> prefix (e.g., <tt>"sha256:781bb71a..."</tt>). The verifier MUST recompute this value independently and MUST NOT rely on the <tt>claimed_hash</tt> field as authoritative.</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 Section 2.4.3.</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 Section 2.5.</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"/>, an attestation is a typed terminal element of an <tt>.ots</tt> proof that binds the proof to an external commitment. This document references two such types: 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 pending Bitcoin confirmation. (2) In implementation-level contexts (Appendix C, pseudocode field <tt>origin_attestations</tt>), the word denotes 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 Section 3.2).</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"/>. In the context of this document, miners are relevant only as the consensus mechanism by which OpenTimestamps Calendar Server commitments become embedded in immutable block history. This document does not depend on the identity, intent, or cooperation of any 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 (Section 3.1) 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 (Section 2.6.1).</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 (blocks, transactions, headers) via HTTP. Block explorers are convenient sources of block header data, but a header they report is not thereby established to lie on the validated most-work chain: an explorer reports a chain rather than demonstrating the work behind it. A Verifier may consult one, and Appendix E.3 notes the byte-order convention they use, but see the note on header sources in Section 3.1 for what a verifier must be able to establish before returning <tt>valid</tt>.</li>
</ul>
<ul>
<li><strong>Confirmation depth</strong>: The number of blocks that have been appended to the Bitcoin blockchain after a given block, including the 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 Section 6.11).</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 ±2 hour <tt>block.time</tt> envelope discussed in Section 2.6.6.</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 Section 2.6.1).</li>
</ul>
</section>
<section anchor="sec-1-4-notation"><name>Notation</name>
<t>This document uses pseudocode in Sections 2.2, 2.3, 3.1, and Appendix D. 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 Section 1.3 (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 Section 1.3.</li>
<li><tt>AnchorProof</tt> -- the Anchor Proof as defined in Section 1.3.</li>
<li><tt>SignedStatement</tt> -- a Signed Statement as defined in Section 1.3, 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>The following pseudocode functions are used. Each is either a standard cryptographic primitive, a Bitcoin protocol operation, an OpenTimestamps operation, or an implementation utility. All are defined or referenced as follows:</t>
<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 Appendix D.1 (<tt>CONSTRUCT-ANCHOR</tt>). The verifier is not symmetric with it: <tt>VERIFY-ANCHOR</tt> (Section 3.1) 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 Appendix D.3.</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>DECODE-HEX(s)</tt> -- the byte sequence represented by the hexadecimal string <tt>s</tt>. The inverse of <tt>HEX</tt>. Used in Appendix D.2, step 2, and Appendix D.3, step 2.</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>
<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 Appendix D.3, 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, in UTC ISO 8601 format (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 Section 2.6).</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 Appendix D.1, step 4, or a Merkle root in Appendix D.3, step 4. Not security-critical: Calendar Server compromise is addressed in Section 6.3.</li>
</ul>
<t><strong>Bitcoin protocol operations:</strong></t>
<ul>
<li><tt>FETCH-BLOCK-HEADER-BY-HEIGHT(H)</tt> -- retrieves the 80-byte Bitcoin block header at height <tt>H</tt> on the validated most-work chain. It returns a header only where the verifier can establish that the header lies on that chain: from a Bitcoin full node or pruned node under the verifier's control which has validated the chain, or from a header-only client which has verified the proof-of-work of the header chain from the genesis block or from a checkpoint the verifier itself holds. Where the verifier cannot establish this, the function returns <tt>NULL</tt>, as it does when no header is available at all; <xref target="sec-3-1-verification-algorithm-verify-anchor"/> treats both alike. Defined operationally per <xref target="NAKAMOTO"/>; not specific to any single client implementation.</li>
<li><tt>CONFIRMATIONS-ON(H)</tt> -- returns the number of blocks the verifier can establish, on the validated most-work chain, 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 requirement on the source is the one stated for <tt>FETCH-BLOCK-HEADER-BY-HEIGHT</tt>: a count reported by a party whose chain the verifier has not validated establishes nothing. Returns <tt>NULL</tt> where the verifier cannot establish the count.</li>
<li><tt>FETCH-PREV-HEADERS(header, n)</tt> -- retrieves the <tt>n</tt> block headers immediately preceding the given header, walking the <tt>prev_block</tt> field. Used to compute Median Time Past per <xref target="BIP113"/>.</li>
<li><tt>MEDIAN(values)</tt> -- the statistical median of an ordered list of integer values.</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 Appendix D and in Section 3.1, 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-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 Appendix D.1 the operation sequence is empty and the proof carries the digest and the commitments alone. In the batch mode of Appendix D.3 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 Appendix D.3.</li>
<li><tt>OTS-DESERIALIZE(ots_bytes)</tt> -- parses a binary <tt>.ots</tt> proof into an internal structure, yielding 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-UPGRADE(pending_proof)</tt> -- contacts a Calendar Server and replaces a calendar-level commitment with a Bitcoin Merkle path, when available. Returns serialised <tt>.ots</tt> bytes, or <tt>NULL</tt> where no Bitcoin Merkle path is yet available. See Appendix D.2.</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 Section 3.1, 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 hash 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 hash operations 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 outside those named here. 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 Section 3.1, step 5.</li>
</ul>
<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>Subscript notation:</strong> In running text and security arguments, subscripts are occasionally used to disambiguate values that share a common name (for example, distinguishing the timestamp value of block <tt>b</tt> from a generic value <tt>T</tt>). Subscripts are notational only; they do not denote function application or array indexing unless explicitly stated.</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 (Section 6).</li>
<li><tt>H</tt> -- a Bitcoin block height (a non-negative integer identifying a position on the canonical chain). Used in Sections 1.3, 2.6.1, <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 Section 2.4, invoked as <tt>V(ArtifactBytes, AnchorProof, AnchorLedger)</tt>.</li>
<li><tt>k</tt> -- a Bitcoin confirmation depth (a non-negative integer counting blocks appended on top of the block containing the commitment).</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>
<t><strong>Top-level procedures.</strong> The procedures <tt>VERIFY-ANCHOR</tt>, <tt>CONSTRUCT-ANCHOR</tt>, <tt>UPGRADE-PENDING-PROOFS</tt>, and <tt>BATCH-ANCHOR</tt> are defined in Section 3.1 and Appendix D (D.1 through D.3). They use the functions and conventions defined above.</t>
</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-1-3-terminology"/>), the one operation of this profile for which no algorithm is given here. Its input format is defined by <xref target="OTS"/>, at the release identified in that reference. <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. The first recomputes the construction from the printed values and needs neither network access nor OpenTimestamps software; the second uses none for the replay, though recovering the operation sequence from the byte stream still depends on the format. See above.</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.</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-6-relationship-to-the-anchoring-specification"><name>Relationship to the Anchoring Specification</name>
<t>This section is informative.</t>
<t>The Anchoring Specification <xref target="ANCHORING"/> is a normative reference of this profile, and it is maintained by the author of this document rather than by a standards body. A reader is entitled to ask what that means for the stability of the conformance criteria, since some of them sit outside the RFC series. The specification answers the question in its own text, and the relevant provisions are these.</t>
<t>Its Section 11 states that a published version is never edited in place: where a published version is found to contain an error, the error remains in that version and the correction is issued as a new version, so that the change is itself dateable. All published versions remain permanently retrievable at their canonical URIs. A reference to version 1.3, as made here, therefore denotes a fixed text and cannot be altered under it.</t>
<t>Its Section 12 states plainly that this is a single-maintainer specification, that there is no foundation, consortium or multi-stakeholder governance body, and that governance independence is not claimed. It distinguishes that from technical independence, the property that proofs verify without the issuer, which its Section 9 establishes and which this profile relies on in <xref target="sec-3-2-verification-independence"/>. The specification is released into the public domain, so any party may fork, mirror or republish it.</t>
<t>What this profile takes from it is narrow: the definitions of the verification outputs (Section 5), the proof structure requirements (Section 6), the ledger qualification properties (Section 7), the semantic exclusions (Section 8) and the independence requirement (Section 9). Where this profile is stricter, it is stricter within those bounds and does not extend them; see <xref target="sec-2-5-anchor-ledger-requirements"/> and <xref target="sec-3-3-error-conditions"/>.</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>
<t>The following diagram illustrates the actors and message flow in a federated anchoring deployment:</t>
<artwork><![CDATA[
  Submitter      Calendar Servers      Bitcoin Miners    Verifier
     |            (Alice, Bob, Finney)        |              |
     |                                        |              |
  1. |--SHA-256(A)-->|                        |              |
     |               |                        |              |
  2. |               |--aggregate--> root      |              |
     |               |                        |              |
  3. |               |--OP_RETURN(root)------->|              |
     |               |                        |              |
  4. |               |           <--block H----|              |
     |               |             (block.time)|              |
  5. |<--.ots proof--|                        |              |
     |  (pending->anchored)                   |              |
     |                                        |              |
  6. |                                        | <-artifact+.ots
     |                                        |              |
  7. |                                  +-----+              |
     |                                  | Fetch block        |
     |                                  | headers directly   |
     |                                  | (any full node)    |
     |                                  +-----+              |
  8. |                                        |--valid/invalid
     |                                        |              |
]]></artwork>
<t>Figure 1: Federated Anchoring Message Flow</t>
<t>Key trust property: the Verifier (step 7) retrieves block headers directly from the Bitcoin network. 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>
</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><![CDATA[
anchor_input = SHA-256(SignedStatement)
anchor_proof = AnchorDigest("sha256", anchor_input, AnchorLedger)
]]></artwork>
<t>The resulting Anchor Proof is stored alongside the SCITT Receipt. Together, 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 Section 2.6.1. The Reference Wall-Clock Projection <tt>block.time(H)</tt> (Section 1.3) 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><![CDATA[
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 (Section 1.3) 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>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 file 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><![CDATA[
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 (Section 1.3)</li>
<li><strong>AnchorLedger</strong> -- the Anchor Ledger (Section 1.3)</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>Ten abstract fields are named in this profile, and they do not belong to one object. Some are carried by the proof, some are obtained by the verifier while checking it, and one is recorded by the producing implementation alongside the proof. The three tables below keep them apart; treating them as a single set of proof contents will mislead an implementer, since a proof cannot be required to carry what it does not contain.</t>
<t><strong>Fields carried by the proof.</strong> A conformant Anchor Proof MUST encode the following, regardless of serialization format. These are the values a verifier recovers by parsing the proof; the mapping to the OpenTimestamps serialization is given in Section 2.4.2.</t>
<artwork><![CDATA[
+-----+-----------------+------+-----------------+------------------+
| #   | 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 | [* operation]   | Ordered          |
|     |                 |      |                 | operations       |
|     |                 |      |                 | linking F1 to    |
|     |                 |      |                 | the ledger       |
|     |                 |      |                 | commitment       |
+-----+-----------------+------+-----------------+------------------+
| F4  | ledger_id       | MUST | tstr            | Anchor Ledger    |
|     |                 |      |                 | identifier       |
|     |                 |      |                 | (e.g., "bitcoin- |
|     |                 |      |                 | mainnet")        |
+-----+-----------------+------+-----------------+------------------+
| F5  | block_height    | MUST | uint            | Block number     |
|     |                 |      |                 | containing the   |
|     |                 |      |                 | commitment       |
+-----+-----------------+------+-----------------+------------------+
| F7  | tx_id           | SHLD | 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. A verifier reads them from the Bitcoin block header it retrieves at the height given by F5, and they are therefore available only once that retrieval has succeeded (Section 3.1, step 7). No requirement level is given, because there is nothing for the proof to encode.</t>
<artwork><![CDATA[
+-----+-----------------+-----------------+-------------------------+
| #   | Field           | CDDL Type       | Description             |
+-----+-----------------+-----------------+-------------------------+
| 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 2.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 (Section 3.1, 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 Section 2.6.5 defines what its values are permitted to mean. <xref target="ANCHORING"/> Section 6 classifies attestation metadata of this kind as non-normative, with no effect on the verification outcome; a verifier neither reads it nor requires it (Section 2.6.5).</t>
<artwork><![CDATA[
+-----+-----------------+-----------------+-------------------------+
| #   | Field           | CDDL Type       | Description             |
+-----+-----------------+-----------------+-------------------------+
| F9  | anchor_status   | status-tstr     | "pending" / "anchored"  |
|     |                 |                 | / "failed"; see Section |
|     |                 |                 | 2.6.5                   |
+-----+-----------------+-----------------+-------------------------+
]]></artwork>
<t>Table 3: Status Recorded by the Producing Implementation</t>
<t>The "Reqd" column of Table 1 uses MUST, SHLD (SHOULD), 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 Section 2.4.3 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 CDDL type <tt>[* operation]</tt> describes a single linear sequence, which is what a proof carrying one Bitcoin attestation contains. A proof may carry more than one, in which case the recorded operations form a branching structure rather than one list, and each branch is replayed separately (Section 3.1, steps 5 to 8). The type shown is therefore the single-branch case; it is not a schema for the general one.</t>
<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 Appendix D.</t>
<t>Of the fields a proof carries (Section 2.4.1, Table 1), F1 through F5 are REQUIRED, F7 is RECOMMENDED and F10 is informational. 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 with anchor_status "pending" MUST contain at minimum F1, F2, F9, and a partial merkle_path (F3) sufficient for later upgrade. 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 a property of 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 Appendix D.1, 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><![CDATA[
+-----+---------------------+-------------------------------------+
| #   | Abstract Field      | OTS Encoding                        |
+-----+---------------------+-------------------------------------+
| F1  | artifact_hash       | Initial hash input to the proof     |
|     |                     | chain                               |
+-----+---------------------+-------------------------------------+
| F2  | hash_algorithm      | Implicit: SHA-256 (OTS magic byte   |
|     |                     | 0x08)                               |
+-----+---------------------+-------------------------------------+
| F3  | merkle_path         | Sequence of append (0xf0), prepend  |
|     |                     | (0xf1), and hash (0x08)             |
|     |                     | operations                          |
+-----+---------------------+-------------------------------------+
| F4  | ledger_id           | Attestation tag: Bitcoin (0x0588960d|
|     |                     | 73d71901)                           |
+-----+---------------------+-------------------------------------+
| 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               | Encoded in the proof operations;    |
|     |                     | no transaction is retrieved         |
|     |                     | (Section 3.1)                       |
+-----+---------------------+-------------------------------------+
| F8  | block_time          | Derived: from block header          |
|     |                     | timestamp field                     |
+-----+---------------------+-------------------------------------+
| F9  | anchor_status       | Implicit: Bitcoin attestation (F4)  |
|     |                     | present = "anchored". Only pending  |
|     |                     | calendar attestations = "pending"   |
+-----+---------------------+-------------------------------------+
| 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), where recorded, is recoverable from the proof's own operations, which pass through the transaction; no transaction is retrieved from the ledger (Section 3.1). The mapping given for F9 describes what the proof bytes encode, not when a producing implementation may set that value: the presence of a Bitcoin attestation is what the wire format records, while promotion to <tt>anchored</tt> is additionally subject to the confirmation depth of Section 6.11. The proof is compact and self-contained: it carries the operations, and the verifier supplies only the header, fetched by height. For F9, an anchored proof carries the Bitcoin attestation alongside the pending calendar attestations that remain from submission; anchor_status is determined by the presence of the Bitcoin attestation (F4), 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 Section 2.6.1. 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>An implementation MAY use an alternative serialization format provided it encodes all REQUIRED abstract fields from Table 1. A future specification MAY define a CBOR-based encoding (see Section 4.4).</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 (Section 3.1). This is the normative definition of the <tt>proof_bundle</tt> input type referenced in Section 1.3.</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 Section 2.4.1 is given in Section 2.4.2. CDDL describes CBOR and JSON data models <xref target="RFC8610"/> and is not used in this profile to describe that binary encoding.</t>
<artwork><![CDATA[
; CDDL definition of Proof Bundle (input to VERIFY-ANCHOR)

ProofBundle = {
  ots_proof:              bytes,   ; REQUIRED: binary .ots file
  claimed_hash:           tstr,    ; REQUIRED: "sha256:<hex>"
  ? 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 NOT treat <tt>claimed_hash</tt> as authoritative; it MUST recompute the hash independently from <tt>artifact_bytes</tt> (Section 3.1, step 1).</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>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. The residual probability of a successful rewrite decreases with confirmation depth without reaching zero at any fixed depth; the depth of six confirmations used by this profile is a risk threshold, not a point of consensus finality (see Assumption A2, <xref target="sec-5-2-assumptions"/>).</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 Bitcoin block headers. No contact with the anchoring service or the OpenTimestamps Calendar Server is required (see Section 3.2).</li>
</ul>
<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. A future profile MAY extend this work to additional qualifying ledgers; such a profile would specify the ledger-specific verification algorithm, attestation format, and confirmation-depth requirements analogous to those defined for Bitcoin in Section 3.1 and Section 6.11.</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>Temporal Precision</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-2-6-1-temporal-claim"><name>Temporal Claim</name>
<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) = 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> (Section 1.3). 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>The block-height binding implies an existence claim: artifact <tt>A</tt> existed before block <tt>H</tt> was added to the canonical chain, since the hash of <tt>A</tt> was necessarily computed before it could be committed to a transaction included in <tt>H</tt>.</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 (Section 1.3). 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 its drift envelope appears in Section 2.6.6.</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-2-6-2-assumptions"><name>Assumptions</name>
<t>The temporal claim holds under the assumptions A1 through A4 stated in <xref target="sec-5-2-assumptions"/>: A1 (SHA-256 Security), A2 (Ledger Immutability), A3 (Commitment Causality) and A4 (OTS Correctness). <xref target="sec-5-3-proof"/> derives the claim above from those four, and <xref target="sec-5-1-claim"/> states the conditions under which that derivation applies. A5 (Operator Independence) is not a premise of the derivation: it is an operational property that follows from it, namely that the derivation requires nothing from the anchoring service, and it is stated in <xref target="sec-5-2-assumptions"/> for that reason. This section does not restate the assumptions.</t>
<t>Note: the claim "the artifact existed before block H was mined" is independent of <tt>block.time(H)</tt>. The Reference Wall-Clock Projection <tt>block.time(H)</tt> (Section 1.3) provides a human-readable approximation of when block <tt>H</tt> was mined but is not part of the normative temporal claim. See Sections 2.6.3 and 2.6.6.</t>
</section>
<section anchor="sec-2-6-3-temporal-bound"><name>Temporal Bound</name>
<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>When the Reference Wall-Clock Projection <tt>block.time(H)</tt> is presented alongside the block-height binding, an additional uncertainty applies. Bitcoin block timestamps are miner-set within consensus bounds: a block's declared timestamp must be greater than the Median Time Past of the eleven preceding blocks, and must not exceed network-adjusted time by more than approximately two hours. These are two different constraints, and they do not form a symmetric envelope around the wall-clock moment at which the block was produced (see Section 2.6.6 for full discussion). This uncertainty applies to the projection only; the block-height binding itself is exact.</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 Section 4.4) 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 Section 2.6.6)</li>
</ul>
<t>These exclusions are consistent with <xref target="ANCHORING"/> Section 8 (Semantic Exclusions) and Section 16 (Non-Retroactivity).</t>
</section>
<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 Section 2.4.2 reports. The <em>confirmation state</em> is a property of the chain at the moment of asking: how many blocks have been appended after the attested block. 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> (Section 3.1). <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 Section 6.11 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, Section 2.4.1, 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 (Appendix D.2) 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 (Section 3.1), and the confirmation state it refers to is not frozen by it.</li>
</ul>
<t>The block-height binding (Section 2.6.1) 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> (Section 3.1, 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 Section 3.2 explicitly excludes.</t>
<t>This treatment is structurally cleaner than alternatives that would assign provisional temporal values to pending proofs: under this profile, a temporal claim either exists (because a block-height binding has been established) or does not (because no block has yet been identified). There is no intermediate state in which a partial temporal claim is asserted.</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 (Section 2.6.1): 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>
<ul>
<li>Bitcoin block timestamps (<tt>block.time</tt> or <tt>nTime</tt>) are miner-set within consensus-allowed bounds. The consensus rules permit a block's <tt>nTime</tt> to be in the range approximately <tt>[MTP + 1, network_time + 2h]</tt>, where <tt>network_time</tt> is the median of the Bitcoin node's peers' reported times <xref target="BIP113"/>. As a consequence:</li>
</ul>
<ul>
<li><tt>block.time</tt> can move backward relative to its parent block. Documented examples include block 156114 (~2 hours before its parent) and block 790402 (2 minutes before its parent). Such non-monotonicity is consensus- valid.</li>
<li>A miner can set <tt>block.time</tt> up to approximately two hours ahead of the actual mining moment, which would cause an anchored artifact to appear to have existed earlier than it did.</li>
</ul>
<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 (Section 1.3) and recommending its presentation alongside <tt>H</tt> for human readability.</li>
</ul>
<t>The result is a two-layer model:</t>
<ul>
<li><strong>Normative layer (cryptographic):</strong> the block-height binding. Exact, consensus-verified, not subject to clock drift or miner manipulation.</li>
<li><strong>Informative layer (human-readable):</strong> the Reference Wall-Clock Projection <tt>block.time(H)</tt>. Convenient for display, subject to the bounded uncertainty described above.</li>
</ul>
<t>This separation -- of what is proved from what is displayed -- allows this profile to claim only what Bitcoin consensus guarantees, while still enabling implementations to present a familiar wall-clock value to human consumers of the proof.</t>
<t>A profile that combined this mechanism with an <xref target="RFC3161"/> TSA timestamp can present both a consensus-verified block-height binding and a CA-verified wall-clock timestamp. The two are complementary; neither subsumes the other. See Section 4.4 for the COSE-level binding pattern.</t>
</section>
</section>
<section anchor="sec-2-7-architectural-overview"><name>Architectural Overview</name>
<t>This section provides a complete actor-level view of the anchoring and verification flows. The role of Bitcoin miners is shown explicitly, since miners -- not any intermediary -- are the trust anchor that makes the temporal claim binding.</t>
<section anchor="sec-2-7-1-anchoring-flow"><name>Anchoring Flow</name>
<artwork><![CDATA[
                    ANCHORING FLOW

  Producer       Transparency   OTS          Bitcoin       Bitcoin
  (Artifact)     Service        Calendar     Miners        Network
     |               |             |            |             |
     |- SHA-256(A) ->|             |            |             |
     |               |- hash ----->|            |             |
     |               |           |- Merkle root>|             |
     |               |           |              |- PoW ------>|
     |               |           |              |  consensus  |
     |               |           |              |             |
     |               |<- Receipt-|              |<- Block N --|
     |<- Receipt + --|           |<- .ots proof-|             |
     |   Anchor Proof|           |              |             |
]]></artwork>
<t>Figure 2: Actor-Level Anchoring Flow</t>
<t>Note: Miners include the Merkle root in a block. The block 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 Anchor Proof carries status "pending" (see F9 in Section 2.4.1) and MUST NOT be relied upon as a temporal claim.</t>
</section>
<section anchor="sec-2-7-2-verification-flow"><name>Verification Flow</name>
<artwork><![CDATA[
                    VERIFICATION FLOW

  Auditor         Bitcoin       Bitcoin       Transparency
  (Verifier)      Network       Block N       Service
     |               |             |             |
     |- fetch header>|             |             |
     |<- block hdr --|             |             |
     |               |             |             |
     |- walk Merkle path (from .ots proof) ----->|
     |<- verify: digest matches header root, blk N-|
     |               |             |             |
     |  [optional] verify Receipt against TS     |
     |------------------------------------------>|
     |<------------------------------------------|
     |               |             |             |
     |  RESULT: artifact committed in block at height H
     |  (block-height binding, Section 2.6.1)
     |  No contact with Umarise required.
     |  No contact with OTS Calendar required.
     |  Only Bitcoin block headers needed.
]]></artwork>
<t>Figure 3: 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 Section 3.1. 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 Section 2.4.</t>
</section>
</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 Section 3.1.</t>
<section anchor="sec-3-1-verification-algorithm-verify-anchor"><name>Verification Algorithm (VERIFY-ANCHOR)</name>
<t>The verification function V (Section 2.4) is instantiated as follows. 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 (Appendix E.4), 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>
<artwork><![CDATA[
Algorithm: VERIFY-ANCHOR(artifact_bytes, proof_bundle)

Input:
  artifact_bytes  -- the original artifact byte sequence
  proof_bundle    -- ots_proof (.ots), claimed_hash, origin_id

Output:
  { valid, invalid, unverifiable }

Steps:

1.  RECOMPUTE HASH
    computed_hash <- 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 <- OTS-DESERIALIZE(proof_bundle.ots_proof)
    IF parsed IS NULL OR parsed.msg != computed_hash:
      RETURN invalid

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 (Appendix D.3, step 2).
    FOR EACH Bitcoin branch b IN parsed:
      b.digest <- OTS-REPLAY(b, computed_hash)

6.  READ EACH BRANCH'S ATTESTED HEIGHT
    FOR EACH Bitcoin branch b IN parsed:
      b.H <- 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.  FETCH EACH BRANCH'S BLOCK HEADER BY HEIGHT
    FOR EACH Bitcoin branch b IN parsed:
      b.header <- FETCH-BLOCK-HEADER-BY-HEIGHT(b.H)
    IF b.header IS NULL FOR EVERY branch b:
      RETURN unverifiable          // ledger unavailable
    // 80 bytes each. The header must be one the verifier can
    // place on the validated most-work chain; a value merely
    // reported by a third party is not one. A branch whose
    // header cannot be so established is left unevaluated and
    // is handled at step 8. See the note on header sources
    // below.

8.  CLASSIFY EACH BRANCH, THEN COMBINE
    FOR EACH Bitcoin branch b IN parsed:
      IF b.digest IS NULL:
        b.state <- unsupported   // step 5 could not replay it
      ELSE IF b.header IS NULL:
        b.state <- unavailable   // step 7 established no header
      ELSE IF b.digest = b.header.merkle_root:
        b.state <- verified
      ELSE:
        b.state <- mismatched
    verified <- { b IN parsed : b.state = verified }
    IF verified IS EMPTY:
      unevaluated <- { 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* <- the branch in verified with the lowest b.H
    H <- b*.H
    header <- b*.header
    // Lowest verified height: the tightest bound (2.6.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 E.3.

9.  CHECK CONFIRMATION DEPTH
    k <- CONFIRMATIONS-ON(H)
    IF k IS NULL OR k < 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 asks
    // how many blocks follow H on the validated chain, not
    // 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  <- H
    // The normative binding (Section 2.6.1). Exact.
    block_time_RWCP <- header.nTime
    // The Reference Wall-Clock Projection (Section 1.3).
    // Informative, miner-set, bounded by the uncertainty in
    // Section 2.6.6. 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 2.6.
]]></artwork>
<t>Figure 4: VERIFY-ANCHOR Algorithm</t>

<t><strong>Note on header sources.</strong> Steps 7 and 9 are the only points at which a verifier consults anything outside the proof bundle, and both require only block-header data. 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.</t>
<t>Beyond that exclusion, the requirement is not about which party supplies the bytes but about what the verifier has established. A header is of use to step 8 only if the verifier can place it on the validated most-work chain. Validating the chain, or verifying the proof-of-work of the header chain from a checkpoint the verifier already trusts, establishes that. 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>. 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 asks how many blocks follow the attested block on the validated chain, not 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, Section 2.4.1) 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 (Appendix D.2); 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>

</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. The note on header sources in <xref target="sec-3-1-verification-algorithm-verify-anchor"/> gives the requirement: the verifier must be able to place the attested block on the validated most-work chain, 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: <xref target="ANCHORING"/> Section 5 requires an implementation to return exactly one of <tt>valid</tt>, <tt>invalid</tt> or <tt>unverifiable</tt>, and not to return intermediate, partial or qualified results outside that set; Section 17 of that document makes extending them non-conformant. A profile of the anchoring primitive may therefore narrow when each output is returned, as this one does, but may 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 header could not be placed on the validated most-work chain as well as the case where no header was retrieved at all: 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><![CDATA[
+----------------+------------------------------------------+
| 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 2.6.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  |
|                | (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 block header is available   |
|                | at the attested height (step 7), 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>
</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:</t>
<artwork><![CDATA[
{
  "anchor_ledger": "bitcoin-mainnet",
  "anchor_method": "opentimestamps",
  "anchor_level": "log_root",
  "anchor_interval": "batch",
  "anchor_spec": "https://anchoring-spec.org/v1.3/"
}
]]></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 Section 6.11, which is normally hours later (Section 1.1). 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><![CDATA[
+--------------------+----------------------------------+
|  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 implement both -- producing a dual-anchored Anchor Proof that combines CA-rooted precision (RFC 3161 via RFC 9921) with consensus-rooted independence (OpenTimestamps via this profile).</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 (Section 2.4.1, 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>Formal Security Argument</name>
<t>This section provides a formal argument supporting the central claim of this profile.</t>
<section anchor="sec-5-1-claim"><name>Claim</name>
<t><strong>Theorem (Existence-before-block-H)</strong>: If VERIFY-ANCHOR(A, P) = <tt>valid</tt> (<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 of <xref target="sec-2-6-1-temporal-claim"/>).</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> (the note on header sources in <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>
</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 Section 2.6.1.)</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 normative algorithms for construction and verification are specified in Appendix D of this document; 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 Section 3.2: 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 <xref target="OTS"/> is a deployed open-source protocol with multiple independent implementations and a public project site <xref target="OTS-SITE"/>. It does not, however, have a formal IETF or ISO specification. This profile therefore <strong>depends normatively on the serialised proof format and the operation semantics of the pinned release identified in <xref target="OTS"/>, and does not require that any particular software be executed</strong>. An implementation may parse and replay a proof with code of its own; what it may not do is depart from the format and semantics that release defines. That is why <xref target="OTS"/> is listed as a normative reference: it must be read in order to implement this profile, not run.</t>
<t>The release is identified in two independent ways, so that neither identification depends on a single host remaining available. The source revision is the commit given in <xref target="OTS"/>; that revision is archived by Software Heritage under the persistent identifier</t>
<artwork><![CDATA[
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><![CDATA[
56726ccde97fb67f336a7f237ce36808e5593c3089d68d900b1c83d0ebf9dcfa
]]></artwork>
<t>The security argument of <xref target="sec-5-3-proof"/> does not rely on the correctness of any particular OTS software library. Assumption A4 reduces to the correctness of two well-understood primitives that are fully specified in Appendix D:</t>
<ol>
<li><strong>SHA-256 Merkle path replay</strong> (Appendix D.1, D.2): applying the recorded prepend, append and hash operations of an OpenTimestamps proof <xref target="OTS"/>.</li>
<li><strong>Block header retrieval and Merkle root comparison</strong> (Section 3.1, steps 5 to 9): replaying the recorded operation sequence, fetching the header at the attested height, and comparing the resulting digest against that header's Merkle root.</li>
</ol>
<t>Both primitives are independently verifiable against the Bitcoin blockchain without reference to any OTS software. Appendix D and Section 3.1 specify them in full: an implementer who holds the recorded operation sequence of a proof can verify that proof from the algorithms in this document alone.</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> (Section 1.3), whose input format this profile does not specify and which, as stated above, has no formal specification independent of the OpenTimestamps project. 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, not what a verifier requires. The requirement of Section 3.2 is unaffected: it constrains the inputs a conformant verifier consumes at run time, namely the artifact bytes, the <tt>.ots</tt> proof and access to independently validated Bitcoin chain data, and knowledge of the serialisation format is embedded in the verifier rather than supplied to it. A verifier built under this profile still requires nothing from the anchoring service, from a Calendar Server, or from any certificate authority.</t>
<t>Consequently, if the OTS reference implementation were to become unavailable, the algorithms specified here would remain correct and sufficient for any proof whose operation sequence has been recovered, but recovering that sequence from an existing <tt>.ots</tt> file would require reconstructing the serialisation format from <xref target="OTS"/> as archived.</t>
</section>
</section>
<section anchor="sec-5-3-proof"><name>Proof</name>
<t>Let <tt>A</tt> be an artifact (byte sequence), and let <tt>P</tt> be an Anchor Proof for which <tt>VERIFY-ANCHOR(ArtifactBytes, ProofBundle) = valid</tt>.</t>
<t><strong>Step 1</strong>: By steps 1-2 of the verification algorithm (Section 3.1), <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 a premise of this argument, discharged by the verifier's validation of its header source (Section 3.1, note on header sources). 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>: The commitment chain is causal at every link. To produce <tt>h</tt>, the artifact <tt>A</tt> must have existed before <tt>h</tt> was computed. For <tt>h</tt> to appear in a sequence of operations terminating at <tt>M</tt>, <tt>h</tt> must have existed before <tt>M</tt> was computed. For <tt>M</tt> to be the Merkle root of the header at height <tt>H</tt>, <tt>M</tt> must have existed before that header was hashed.</t>
<t><strong>Therefore</strong>: <tt>A</tt> existed → <tt>h</tt> was computed → <tt>M</tt> was computed → the block at height <tt>H</tt> was mined. The artifact <tt>A</tt> existed before the block at height <tt>H</tt> was added to the canonical chain (the block-height binding of Section 2.6.1).</t>
<t>This argument does not depend on the block's <tt>nTime</tt> field, on the Median Time Past, on any wall-clock interpretation of the binding, or on the contents of any individual transaction. The Reference Wall-Clock Projection <tt>block.time(H)</tt> (Section 1.3) is informative only and is not required for the security argument above. ∎</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 (Section 6.1). 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>The argument does NOT prove:</t>
<ul>
<li>When exactly <tt>A</tt> was created (only that <tt>A</tt> existed before the block at height <tt>H</tt> was mined)</li>
<li>Who created <tt>A</tt> (no identity binding)</li>
<li>That <tt>A</tt> has not been modified since anchoring (only that these specific bytes existed)</li>
<li>Any wall-clock interpretation of the binding (the Reference Wall-Clock Projection is informative only; see Section 2.6.6)</li>
</ul>
<t>These limitations are inherent to the mechanism. They are temporal and integrity-related, and follow from <xref target="ANCHORING"/> Section 3 (Definition of Anchoring) and Section 15 (Time Semantics); the exclusions of Section 8 are a different set, covering authorship, ownership, originality, identity, intent, legal enforceability and truthfulness.</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 (Section 2.5) 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> (Section 2.4.1), 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 Section 3.1 without network access, provided that set covers the attested height and extends at least MIN_CONFIRMATIONS blocks beyond it: steps 7 and 8 read the header at that height, and step 9 asks how many blocks follow it, 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 (Section 2.3) 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. <xref target="RFC9943"/> requires non-equivocation of the verifiable data structure (Section 5.1.3), 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>, Section 2.4.1) 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 sustaining a majority attack on Bitcoin, which is the only ledger for which this profile specifies verification semantics. <xref target="ANCHORING"/> Section 7 states four qualification properties and is ledger-agnostic; it qualifies no particular ledger, and Section 2.5 records that others may in principle satisfy them. The economic and operational cost of producing a competing chain with greater accumulated proof-of-work depends on then-current Bitcoin network conditions and is external to this specification. Assumption A2 (<xref target="sec-5-2-assumptions"/>) holds only at a sufficient confirmation depth: the Verification Algorithm (Section 3.1) evaluates that depth in step 9, which is required, and a producing implementation is separately constrained not to promote a proof to <tt>anchored</tt> below MIN_CONFIRMATIONS (Section 6.11). A verifier that cannot establish the depth returns <tt>unverifiable</tt> rather than asserting a result the Theorem of <xref target="sec-5-1-claim"/> would not support.</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 (Section 2.1, step 7). 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 (Section 2.3) 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 (Section 2.3). 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 Section 2.3.</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><strong>Variant 1: claiming an earlier creation time.</strong> A claimant could assert that the proof establishes the artifact's creation at a specific moment, rather than its existence before the block at height <tt>H</tt> was mined.</t>
<t>This is remediated by the explicit semantics defined in Section 2.6.1: the normative claim is the block-height binding ("<tt>A</tt> was committed to the Bitcoin blockchain in the block at height <tt>H</tt>"), which implies "<tt>A</tt> existed before block <tt>H</tt> was mined." The proof does not establish a creation time. Section 2.6.4 (Non-Claims) makes this exclusion explicit. Verifiers MUST NOT interpret the binding as a creation-time assertion.</t>
<t><strong>Variant 2: misrepresenting the Reference Wall-Clock Projection as normative.</strong> A claimant could present <tt>block.time(H)</tt> (the Reference Wall-Clock Projection, Section 1.3) as if it were the normative temporal value of the proof, ignoring that <tt>block.time</tt> is miner-set within consensus bounds and may diverge from true wall-clock time by up to approximately two hours (Section 2.6.6).</t>
<t>This is remediated by the explicit two-layer model in Section 2.6.6: the block-height binding is normative and exact; the Reference Wall-Clock Projection is informative. Implementations that present the projection to humans SHOULD clearly indicate that the value is a block-derived projection, not a verified wall-clock timestamp. The Median Time Past (<xref target="BIP113"/>) bounds only the block's declared timestamp and not the wall-clock time of acceptance (Section 2.6.1); it therefore does not provide a verified wall-clock bound, and the block-height binding remains the only normative temporal claim.</t>
<t>The block-height binding itself is not subject to inflation: a block exists at a specific height or it does not, and that fact is determined by Bitcoin consensus.</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 (Section 3.1), 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 (Appendix D.1), 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 (Section 2.1). 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>, Section 2.4.1) 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 anchored with SHA-256 retain their validity for the period during which SHA-256 was considered secure. The Anchoring Specification <xref target="ANCHORING"/> Section 15 defines the temporal semantics that bound this validity window.</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 collision resistance (Section 6.8) and the Anchor Ledger remains accessible (Section 6.7).</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. First, the Verification Algorithm (Section 3.1) has the verifier retrieve the block header at the attested height from a source independent of the anchoring service and compare it against the digest replayed from the proof (step 8) -- the verifier does not rely on any status field asserted by the anchoring service. Step 9 has the verifier establish the confirmation depth for itself, from the same validated chain data. Second, Assumption A2 (<xref target="sec-5-2-assumptions"/>) treats a depth of MIN_CONFIRMATIONS as the threshold at which the residual probability of replacement is accepted; below it, the immutability assumption is not yet sound. Producing implementations SHOULD NOT promote a proof from "pending" to "anchored" (field F9, Section 2.4.1) before the attested block has reached MIN_CONFIRMATIONS. The note on confirmation depth in Section 3.1 states the same requirement, and Appendix D.2, step 2f illustrates one construction that meets it. A deployment policy MAY require a greater depth; it MUST NOT accept a lesser one. Third, Section 2.6.5 (Pending Proofs and Temporal Definition) establishes that a proof carrying no Bitcoin attestation supports no normative temporal claim: a Verifier inspects the proof rather than the asserted status, and MUST return <tt>unverifiable</tt> on finding no attestation (Section 3.1, step 4) rather than treating the calendar commitment as a provisional binding.</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 (Appendix D.3), 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 receives its own <tt>.ots</tt> proof, in which the position of its leaf is recorded as the sequence of prepend and append operations that carry it to the committed value (Appendix D.3). Batch integrity is therefore established per hash, by replaying that sequence: a leaf associated with an incorrect position does not reproduce the committed value. No global guarantee about leaf ordering is required, and this profile makes none. Each hash in a batch retains its own origin record and its own proof (Appendix D.3). Any record an implementation keeps of which hashes were aggregated together is operational metadata. It is not carried in any proof, no step of Section 3.1 consults it, and a relying party MUST NOT treat it as evidence of batch membership. An implementation is free to keep such a record, and free to discard it, without effect on any proof. Post-batch verification sampling SHOULD be performed by implementations to detect systematic construction errors before they are exposed to relying parties.</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 Appendix D.3, 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 (Appendix D.3, step 2). This does not apply to the direct mode of Appendix D.1, 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="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="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="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="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><![CDATA[
+---------------------------------------------+
|  L4  Evidence Format        (Partner/SCITT) |
|       Signed Statements, manifests, SBOMs   |
+---------------------------------------------+
|  L3  Signing & Identity     (Partner/TSA)   |
|       SCITT Receipts, X.509, passkeys       |
+---------------------------------------------+
|  L2  Anchor Primitive       (This Profile)  |
|       SHA-256 -> .ots -> 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 (Section 3.1).</t>
</section>
<section anchor="sec-appendix-b-example-anchored-scitt-flow"><name>Example: Anchored SCITT Flow</name>
<artwork><![CDATA[
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 records the state of implementation at the time of writing. It is informative. The profile does not depend on any of the software described here, and a proof produced by any conformant implementation is verifiable without contact with its producer.</t>

<t>Every component described in this appendix was written by the author's organisation, and this appendix should be read as a self-report rather than as evidence of interoperability.</t>

<t><strong>Two producing deployments.</strong> The anchoring service runs in two deployments, each with its own registry and its own independently written anchoring worker. The two workers are implemented in different languages and use different OpenTimestamps client libraries. The first holds the historical registry. The second produced the proofs given in Appendix E.</t>

<t>Both implement the construction of Appendix D.3: each artifact hash is blinded with a per-submission nonce, and the resulting leaves are aggregated by sequential pairing over raw bytes in capture order. This was established by recomputing the construction from proof bytes issued by each, rather than by inspecting either implementation. The two arrive at it differently: one performs the blinding and the aggregation in its own code, the other delegates both to its OpenTimestamps client library. Both submit the resulting Merkle root to several OpenTimestamps aggregation servers.</t>

<t>Proofs from both deployments have been replayed successfully, and for a proof issued by the first deployment, committed to the Bitcoin block at height 942485, the step 8 digest comparison has been observed rather than assumed: the proof was replayed from its artifact hash and the resulting digest compared against the Merkle root of that block's header, obtained from a source independent of either deployment. The comparison succeeded, and no interaction with the producing implementation was required at any point. That is not the same as satisfying Section 3.1 in full: no verifier described in this appendix currently meets all the conditions required to return <tt>valid</tt>, for the reasons given below.</t>

<t>That result does not depend on the two deployments sharing a construction, and this distinction matters. A verifier replays the sequence of operations recorded in a proof and derives its result from those operations alone. It forms no expectation about the tree that produced them. A deployment may therefore change its aggregation, or be replaced entirely, without invalidating a proof it has already issued.</t>

<t>Implementations known to the author:</t>

<ul>
<li><strong>Umarise Core API</strong>: the anchoring service, in the two deployments described above. Artifact hashes are blinded before aggregation. Origin records and proofs are held per artifact. The first deployment additionally runs the scheduled verification audit described above. No RFC 3161 timestamp is issued alongside an Anchor Proof by either deployment.</li>

<li><strong>verify-anchoring.org</strong>: fully client-side verification tool. No API contact, no account, no tracking. It implements the header-only procedure of Section 3.1 by delegating to the OpenTimestamps reference library, which performs the comparison between the replayed digest and the Merkle root of the attested block header internally. Block headers are obtained from a public block explorer; the tool does not presently corroborate across independent header sources, nor does it evaluate confirmation depth (Section 3.1, steps 7 and 9). Public domain.</li>

<li><strong>@umarise/cli</strong>: Node.js CLI for automated anchoring. Published on npm.</li>

<li><strong>anchor-action</strong>: GitHub Actions integration for CI/CD pipeline anchoring. Published on GitHub Marketplace.</li>
</ul>

<t><strong>Requirements not yet implemented.</strong> In both deployments the anchoring worker advances a proof's status from pending to anchored on the presence of a Bitcoin attestation in the upgraded proof, and on nothing further. In the terms of Section 2.6.5 they set the producer status from the proof state alone, without establishing the confirmation state that the status is defined to record. Neither worker retrieves the block header at the attested height, and so neither compares the replayed digest against the Merkle root of that header (Appendix D.2, step 2e) before advancing the status, nor records that header's nTime as block_time (step 2g). Neither evaluates confirmation depth (Section 3.1, step 9; Appendix D.2, step 2f).</t>

<t><strong>What this means for conformance.</strong> Section 3.1 and Section 6.11 state that a producing implementation SHOULD NOT promote a proof before the attested block has reached MIN_CONFIRMATIONS; Appendix D.2 step 2f illustrates a construction that meets this, in which a proof whose depth is below MIN_CONFIRMATIONS, or whose depth cannot be established, is not promoted. Section 3.1 step 9 requires a verifier to establish that depth for itself, and a verifier that cannot returns <tt>unverifiable</tt>. No producing deployment presently enforces the depth before advancing a proof to anchored, and the second deployment does not yet perform the header comparison at all; to that extent no producing deployment currently meets the requirement stated in Section 3.1 and Section 6.11, which Appendix D.2 illustrates. This profile retains those requirements rather than relaxing them, and closing the gap is implementation work in progress. The steps not yet enforced are those of the producing side: a relying party that verifies a proof under Section 3.1 performs the header comparison and the confirmation-depth check itself, and so obtains the full result independently of the producer, whatever status that producer recorded. The anchored status recorded by a producing deployment therefore denotes inclusion in a block, not that MIN_CONFIRMATIONS has been reached.</t>

<t>The first deployment performs the digest comparison out of band. A scheduled task replays every proof recorded as anchored, retrieves the header at the attested height from a public block explorer, compares the two, and raises an alert on any mismatch. This is a periodic audit and not a gate: a proof carries the anchored status before it is checked, and the check does not evaluate confirmation depth. It is recorded here because it is the only component of either deployment that performs the comparison of Section 3.1, step 8, and because the distinction between a gate and an audit is exactly the distinction Section 6.11 draws.</t>

<t>The second deployment performs no comparison against a block header at all, in band or out of band: its verification currently rests on the anchoring service's own records. A check against the Bitcoin blockchain is planned for it but not yet built. verify-anchoring.org performs the digest comparison, by delegation to the OpenTimestamps reference library, but likewise does not evaluate confirmation depth. None of these components establishes that the header it uses lies on the validated most-work chain: each that performs the comparison at all obtains headers from a single public block explorer, which reports a header rather than demonstrating the work behind the chain it belongs to. Under Section 3.1 such a verifier returns <tt>unverifiable</tt> rather than <tt>valid</tt>, and the author's own reference verifier is in that position today. All are expected to adopt the outstanding steps. They are recorded here so that the normative text of this profile is not mistaken for a description of deployed behaviour.</t>

<t><strong>Note on availability.</strong> The continued availability of any particular deployment depends on its hosting arrangements. The verification guarantees of this profile 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.</t>

</section>
<section anchor="sec-appendix-d-ots-anchoring-protocol-construction-and-verification"><name>OTS Anchoring Protocol -- Construction and Verification</name>
<t>Sections D.1 through D.3 are informative examples of a compliant construction. Sections D.4 and D.5 have been promoted to the normative main body (Section 3) and are retained here as cross-references only. This appendix 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 D.1 step 4 and D.3 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 Appendix C, but do not depend on it. One exception is recorded there: the confirmation-depth gate of D.2, step 2f is specified here and is not yet performed by either producing deployment.</t>
<t>For background on the OpenTimestamps protocol design, see <xref target="OTS-SITE"/> and <xref target="OTS-DESIGN"/>.</t>
<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 Appendix D.3, 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 Appendix D.2 in both cases.</t>
<artwork><![CDATA[
Algorithm: CONSTRUCT-ANCHOR(hash_algorithm, hash_value, mode)

Input:
  hash_algorithm    -- "sha256" (field F2, Section 2.4.1)
  mode              -- DIRECT or BATCH. Selects whether the
                       hash is submitted on its own (step 4)
                       or deferred to Appendix D.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)

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 1.3.
    ASSERT hash_algorithm = "sha256"            // F2
    ASSERT LENGTH(hash_value) = 32              // [FIPS180-4]
    hash_string <- "sha256:" || HEX(hash_value) // canonical

2.  TIMESTAMP CAPTURE
    captured_at <- NOW()                        // UTC ISO 8601

3.  ORIGIN REGISTRATION
    origin_id   <- UUID-v4()                    // unique identifier
    short_token <- 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 (Appendix D.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 2.4.1) has
      // no value for this record.

    // DIRECT mode. Submit hash to >=1 OpenTimestamps
    // Calendar Server(s) [OTS].
    FOR EACH calendar IN configured_calendars:
      pending_commitment <- HTTP-POST(calendar.url, hash_value)
      STORE pending_commitment

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 <- OTS-SERIALIZE(hash_value, [],
                                  pending_commitments)
    INSERT core_ots_proofs {
      origin_id, ots_proof: pending_proof, status: "pending"
    }

6.  RETURN {origin_id, hash_string, captured_at,
            proof_status: "pending"}
]]></artwork>
<t>Figure 5: 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, Section 2.4.1) on which relying parties may act. It therefore performs, before it makes that assertion, the same two checks the verifier performs in Section 3.1: 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 (Section 3.1). The producer's header source is subject to the same independence requirement as the verifier's (Section 3.1, note on header sources). A producing implementation that operates its own Bitcoin node satisfies that requirement directly.</t>
<artwork><![CDATA[
Algorithm: UPGRADE-PENDING-PROOFS()

// 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:
  none (reads from core_ots_proofs WHERE status = "pending")

Steps:

1.  pending <- 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 <- DECODE-HEX(STRIP-PREFIX(proof.hash))

    2a. CONTACT CALENDAR
        upgraded_proof <- 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:
        // The calendar returned nothing new. Retry next
        // cycle, unless the commitment has gone unanswered
        // for so long that the submission is presumed lost.
        IF NOW() - proof.created_at > FAIL_AFTER:
          UPDATE core_ots_proofs SET status = "failed"
            WHERE origin_id = proof.origin_id
          // Status "failed" (field F9, Section 2.4.1). The
          // origin record survives; the artifact hash may be
          // resubmitted. No temporal claim was ever made.
        CONTINUE

    2c. PARSE
        parsed <- 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 3.1, step 4.
          UPDATE core_ots_proofs SET ots_proof = upgraded_proof
            WHERE origin_id = proof.origin_id
          CONTINUE

    2d. FETCH EACH BRANCH'S BLOCK HEADER BY HEIGHT
        // 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 3.1.
        FOR EACH Bitcoin branch b IN parsed:
          b.H <- OTS-ATTESTED-HEIGHT(b)
          b.header <- FETCH-BLOCK-HEADER-BY-HEIGHT(b.H)
        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 <- OTS-REPLAY(b, artifact_hash)
          IF b.digest IS NULL OR b.header IS NULL:
            b.state <- unevaluated
          ELSE IF b.digest = b.header.merkle_root:
            b.state <- verified
          ELSE:
            b.state <- mismatched
        verified <- { 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 3.1, step 8.
            CONTINUE
          LOG-ERROR("Upgrade verification failed", proof.origin_id)
          CONTINUE
        b* <- the branch in verified with the lowest b.H
        H <- b*.H
        header <- b*.header
        // The comparison of Section 3.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 <- CONFIRMATIONS-ON(H)
        IF k IS NULL OR k < 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 Sections 3.1 (step 9) and 6.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 2.6.1) and is the normative value.
        // block_time is field F8 (Section 2.4.1), the
        // Reference Wall-Clock Projection (Section 1.3), and
        // is informative. Section 2.6.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 3.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 2.6.1
        // and nothing else. An implementation is free to name
        // this column otherwise, or to omit it.
]]></artwork>
<t>Figure 6: 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> (Appendix D.1). 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 (Appendix D.1). This procedure operates on records that already exist. It produces one proof per origin and persists nothing else.</t>

<artwork><![CDATA[
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
    FOR EACH origin IN origin_list:
      ASSERT origin.hash matches /^(sha256:)?[0-9a-f]{64}$/

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 <- []
    FOR EACH origin IN origin_list:
      origin.digest <- DECODE-HEX(STRIP-PREFIX(origin.hash))
      nonce         <- RANDOM-BYTES(16)
      origin.path   <- [ 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   <- leaves
    members <- [ [origin_list[i]] FOR i <- 0 TO |leaves| - 1 ]
    WHILE |level| > 1:
      next_level   <- []
      next_members <- []
      FOR i <- 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   <- next_level
      members <- next_members
    merkle_root <- 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 <- 0
    root_commitments <- []
    FOR EACH server IN ots_servers:
      TRY:
        commitment <- HTTP-POST(server, merkle_root)
        root_commitments.APPEND(commitment)
        accepted <- 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 <- []
    FOR EACH origin IN origin_list:
      ots_bytes <- OTS-SERIALIZE(origin.digest, origin.path,
                                 root_commitments)
      proofs.APPEND((origin.origin_id, ots_bytes))
      STORE {origin_id, ots_proof: ots_bytes,
             status: "pending"}

    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 appendix 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 Appendix D.2 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 anchor="sec-d-4-verification-algorithm"><name>Verification Algorithm</name>
<t>The normative verification algorithm has been promoted to Section 3.1 of this document. This appendix section is retained as a cross-reference for continuity.</t>
<t>See Section 3.1 (VERIFY-ANCHOR) for the full eleven-step verification procedure with MUST/SHOULD requirements.</t>
</section>
<section anchor="sec-d-5-verification-independence"><name>Verification Independence</name>
<t>The normative verification independence requirements have been promoted to Section 3.2 of this document.</t>
<t>See Section 3.2 for the definitive statement of what a conformant verifier requires and what it MUST NOT depend on.</t>
</section>
</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 Appendix D.3 is given, so that a reader can recompute the root from the values printed here without executing any software beyond a SHA-256 implementation. Appendix E.4 gives an abridged trace of the first vector's operation sequence, and Appendix E.5 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>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 (Section 2.6.1); and the Reference Wall-Clock Projection, which is informative (Section 1.3).</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 Appendix D.3, step 2.</t>
<artwork><![CDATA[
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 Appendix D.3, 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><![CDATA[
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 (Appendix D.3, note on aggregation depth).</t>
<artwork><![CDATA[
Block-height binding: Bitcoin block at height 957289
                      (normative; Section 2.6.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 1.3)

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 2.6.1)

Expected VERIFY-ANCHOR result: valid, for a verifier
                      that has placed block 957289 on the
                      validated most-work chain; otherwise
                      unverifiable (Section 3.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 Section 3.1 against a value obtained from an explorer MUST 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 (Appendix D.3): 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 Section 7. 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 Section 2.6.6 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><![CDATA[
  1  append   14ed82e243f4aff3f1794e6754bfae3b   (nonce)
  2  sha256   -> aafc3638...   (leaf_1)
  3  append   7306ab5d...      (leaf_2)
  4  sha256   -> 034dd422...   (node_12)
  5  append   7c629aed...      (node_34)
  6  sha256   -> b4d19ae2...   (root)
  ...
 40  prepend  0100000001...6a20   (89 tx bytes)
 41  append   689b0e00           (4 tx bytes)
 42  sha256
 43  sha256   -> 3cad384e...   (transaction id)
  ...
 82  sha256   -> 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 (Section 3.1, 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><![CDATA[
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><![CDATA[
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><![CDATA[
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><![CDATA[
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 MUST return <tt>invalid</tt> if the artifact bytes do not match the anchored hash. Substituting any byte in the artifact while presenting the original proof yields:</t>
<artwork><![CDATA[
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>
</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 E.1 and E.2 using any SHA-256 implementation. This confirms the construction of Appendix D.3 and requires no network access.</li>
<li>Take a proof from Appendix E.5, recover its operation sequence and attestations from its byte stream, and then execute the procedure of Section 3.1 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 Section 3.1 and Appendix D 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 Appendix E.5, 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 Section 3.1 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 obtains the block header from 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 Section 3.1 such a verifier returns <tt>unverifiable</tt>, not <tt>valid</tt>. 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> SHOULD be presented alongside it, and never in place of it.</t>
</section>

</section>
<section numbered="false" anchor="sec-acknowledgements"><name>Acknowledgements</name>
<t>The authors thank 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 authors thank 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 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>
