| Internet-Draft | Bitcoin-Anchored Temporal Proof | May 2026 |
| Fassbender | Expires 29 November 2026 | [Page] |
This document defines a mechanism for temporal anchoring of digital artifacts by committing cryptographic hashes to the Bitcoin blockchain via the OpenTimestamps protocol [OTS]. The resulting proof is independently verifiable by any party with access to Bitcoin block headers, without reliance on a trusted third party. The SCITT Architecture [RFC9943] is used as the primary integration example. No changes to the SCITT architecture are required.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 29 November 2026.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document.¶
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.¶
RFC 3161 [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. If the TSA ceases operations, its certificate expires without renewal, or its key is compromised, previously issued timestamps may become unverifiable or disputed.¶
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.¶
The distinction is structural:¶
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.¶
The SCITT Architecture [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.¶
SCITT is deliberately ledger-agnostic and does not mandate a specific time source. Time is derived from log position -- an internal clock controlled by the Transparency Service operator. This creates an architectural gap: the system that manages the evidence also manages the timeline. The operator can:¶
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 mitigates equivocation through consistency proofs, but these proofs are relative to the log itself. There is no external reference point.¶
This document defines an OPTIONAL profile that closes this gap by anchoring operations to the Bitcoin blockchain [NAKAMOTO] via the OpenTimestamps protocol [OTS].¶
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?"¶
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 Bitcoin block headers, without contacting the anchoring service or the Transparency Service.¶
Within the next Bitcoin confirmation -- typically 10 to 60 minutes -- the regulator obtains a temporal guarantee: these model weights existed no later than block height H. The anchoring service provides proof of existence and time, not claims about authorship, quality, or regulatory compliance.¶
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 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
.ots file, see [OTS]) that is independently verifiable without contacting the anchoring service.¶
"sha256:" prefix (e.g., "sha256:781bb71a..."). The verifier MUST recompute this value independently and MUST NOT rely on the claimed_hash field as authoritative.¶
The normative CDDL definition of the Proof Bundle structure is given in Section 2.4.3.¶
.ots file) that is independently verifiable against Bitcoin block headers. See [OTS], [OTS-SITE], and [OTS-DESIGN].¶
.ots proof that binds the proof to an external commitment. This document references two such types: the Bitcoin attestation (tag 0x0588960d73d71901), 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 origin_attestations), the word denotes an anchored origin record stored by the anchoring service. Neither use implies legal or evidentiary attestation in any jurisdictional sense.¶
valid, invalid, or unverifiable. The Verifier is independent from the Transparency Service operator and the anchoring service.¶
.ots proof encodes a Merkle path from the artifact hash to the OP_RETURN value of a Bitcoin transaction. See [RFC6962] Section 2.1.1 for the general construction.¶
k confirmations is considered increasingly resistant to chain reorganization as k grows. This profile requires k >= 6 before promoting an Anchor Proof from pending to anchored (see Section 6.11).¶
H 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 block.time envelope discussed in Section 2.6.6.¶
block.time(H), where H 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 H 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).¶
This document uses pseudocode in Sections 2.2, 2.3, 3.1, and Appendix D. The notation follows these conventions:¶
Function and algorithm names are written in UPPER-CASE with hyphens as word separators (e.g., VERIFY-ANCHOR, OTS-DESERIALIZE). 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.¶
Variables and abstract objects are written in snake_case (e.g., artifact_bytes, anchor_proof). 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.¶
The following abstract objects appear as pseudocode parameters and refer to defined terms:¶
AnchorLedger -- the Anchor Ledger as defined in Section 1.3.¶
AnchorProof -- the Anchor Proof as defined in Section 1.3.¶
SignedStatement -- a Signed Statement as defined in Section 1.3, serialized to a canonical byte sequence prior to hashing. The exact serialization is specified by [RFC9943] and is out of scope for this profile.¶
TransparencyLog -- the verifiable log of a Transparency Service as defined in [RFC9943] Section 4.¶
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:¶
Top-level anchoring abstraction:¶
Anchor(input, AnchorLedger) -- takes a byte sequence input and an Anchor Ledger reference, and returns an Anchor Proof binding input to a commitment on that ledger. This is an abstract operation; its concrete instantiation for the Bitcoin Anchor Ledger via OpenTimestamps is specified in Appendix D.1 (CONSTRUCT-ANCHOR).¶
Cryptographic primitives:¶
SHA-256(x) -- the SHA-256 hash function applied to byte sequence x, as specified in [FIPS180-4].¶
MerkleRoot(L) -- the root of a Merkle tree constructed over the ordered list L, as specified in [RFC6962] Section 2.1.¶
Encoding utilities:¶
HEX(b) -- the lowercase hexadecimal string representation of byte sequence b, with no separators or prefixes (e.g., HEX(0xAB12) = "ab12").¶
STRIP-PREFIX(s) -- given a string s of the form "sha256:" || h, returns the substring h. If no prefix is present, returns s unchanged.¶
UUID-v4() -- generates a random UUID per [RFC4122] Section 4.4.¶
RANDOM-ALPHANUMERIC(n) -- returns a random string of n characters drawn from the alphabet [0-9a-zA-Z]. Used for human-readable references only; not security-critical.¶
NOW() -- returns the current wall-clock time, as determined by the local system, in UTC ISO 8601 format (e.g., "2026-04-29T12:26:30Z"). NOW() 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).¶
HTTP-POST(url, body) -- performs an HTTP POST request per [RFC9110]. Used for submitting hashes to Calendar Servers (Appendix D.1, step 4). Not security-critical: Calendar Server compromise is addressed in Section 6.3.¶
Bitcoin protocol operations:¶
FETCH-TX(tx_id) -- retrieves a Bitcoin transaction by its identifier from any Bitcoin full node, block explorer, or cached source. Returns the transaction or NULL if not found. Defined operationally per [NAKAMOTO]; not specific to any single client implementation.¶
FETCH-BLOCK-HEADER(block_hash) -- retrieves a Bitcoin block header by its hash. Same source independence as FETCH-TX.¶
FETCH-PREV-HEADERS(header, n) -- retrieves the n block headers immediately preceding the given header, walking the prev_block field. Used to compute Median Time Past per [BIP113].¶
MEDIAN(values) -- the statistical median of an ordered list of integer values.¶
OpenTimestamps operations:¶
The following functions are operations on OpenTimestamps proof structures, as documented in [OTS] and [OTS-DESIGN]. The normative semantics required by this document are specified in Appendix D; conformant implementations need only implement the algorithms there and need not depend on any particular OTS software library.¶
OTS-SERIALIZE(hash, commitments) -- produces a binary .ots proof structure containing the input hash and the given commitments. See Appendix D.1.¶
OTS-DESERIALIZE(ots_bytes) -- parses a binary .ots proof into an internal structure. Returns NULL if parsing fails.¶
OTS-UPGRADE(pending_proof) -- contacts a Calendar Server and replaces a calendar-level commitment with a Bitcoin Merkle path, when available. See Appendix D.2.¶
OTS-VERIFY(proof, hash) -- returns true if the proof's internal Merkle path correctly resolves the given input hash to the embedded ledger commitment. See Section 3.1.¶
OTS-EXTRACT-TX(parsed) -- extracts the Bitcoin transaction identifier referenced by the OTS attestation in parsed.¶
OTS-EXTRACT-BLOCK-HEIGHT(proof) -- extracts the Bitcoin block height referenced by the proof.¶
OTS-EXTRACT-BLOCK-TIME(proof) -- extracts the Bitcoin block timestamp referenced by the proof.¶
OTS-WALK-MERKLE-PATH(parsed, hash) -- replays the sequence of append/prepend/hash operations encoded in parsed, starting from hash, to derive the expected ledger commitment value. See Section 3.1, step 5.¶
Storage and control flow:¶
The pseudocode uses INSERT, SELECT, UPDATE, STORE, and LOG-ERROR as implementation-detail placeholders for persistent storage operations. These are not normative: an implementation MAY use any storage mechanism. Standard control flow keywords (IF, WHILE, FOR EACH, RETURN, ASSERT, CONTINUE, APPEND, CALL) follow the conventions of pseudocode as commonly used in the IETF (e.g., [RFC9162]). The SORT(L) operation returns the input list L in ascending lexicographic order; it is used in Appendix D.3 to ensure deterministic Merkle leaf ordering.¶
The arrow ← denotes assignment. The symbols || denote concatenation. The operand types determine whether this is byte or string concatenation. Where the distinction is interoperability-critical, the surrounding pseudocode states the operand type explicitly (see, for example, the Merkle construction in Appendix D.3, step 2c, which performs hex-string concatenation). The function notation f(x, y) denotes function application; brackets [a, b, c] denote ordered lists.¶
Subscript notation: 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 b from a generic value T). Subscripts are notational only; they do not denote function application or array indexing unless explicitly stated.¶
Single-letter symbols in running text and the security argument: 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.¶
A -- an artifact (byte sequence) under verification. Used in the formal security argument (Section 5).¶
B -- a Bitcoin block. Used in the formal security argument (Section 5) and threat model (Section 6).¶
H -- a Bitcoin block height (a non-negative integer identifying a position on the canonical chain). Used in Sections 1.3, 2.6.1, 5.3, and throughout for the block-height binding.¶
h -- a hash value (a 256-bit byte string). Used in the formal security argument (Section 5.3) for the hash committed in an Anchor Proof. The lower-case form distinguishes the hash value from the block height H.¶
V -- the verification function defined in Section 2.4, invoked as V(ArtifactBytes, AnchorProof, AnchorLedger).¶
k -- a Bitcoin confirmation depth (a non-negative integer counting blocks appended on top of the block containing the commitment).¶
These symbols are used with the meanings above only. The distinction between H (block height) and h (hash value) is maintained throughout the document; where confusion could arise the surrounding text states the referent explicitly.¶
Top-level procedures. The procedures VERIFY-ANCHOR, CONSTRUCT-ANCHOR, UPGRADE-PENDING-PROOFS, and BATCH-ANCHOR are defined in Section 3.1 and Appendix D (D.1 through D.3). They use the functions and conventions defined above.¶
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:¶
The following diagram illustrates the actors and message flow in a federated anchoring deployment:¶
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
| | |
¶
Figure 1: Federated Anchoring Message Flow¶
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 -- if a Calendar Server equivocated, the .ots proof simply fails verification against the blockchain. The trust root is Bitcoin's Proof-of-Work consensus, not any intermediary.¶
When a Transparency Service receives a Signed Statement, it MAY compute a Temporal Anchor for that statement:¶
anchor_input = SHA-256(SignedStatement) anchor_proof = Anchor(anchor_input, AnchorLedger)¶
The resulting Anchor Proof is stored alongside the SCITT Receipt. Together, they provide:¶
SignedStatement were committed to the Bitcoin blockchain, where the commitment is included in the block at height H. 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 block.time(H) (Section 1.3) SHOULD be presented alongside H for human readability and is informative, not normative.¶
A Transparency Service SHOULD periodically anchor the root of its verifiable data structure:¶
log_root = MerkleRoot(TransparencyLog) anchor_proof = Anchor(log_root, AnchorLedger)¶
This creates an external checkpoint anchored to a specific Bitcoin block height H. If the operator later presents a different log state, the anchored root provides a publicly verifiable commitment against which inconsistencies can be detected. The block-height binding (Section 1.3) of the log root anchor is the normative temporal reference for this checkpoint.¶
An Anchor Proof MUST be:¶
The verification function V is defined as follows:¶
V(ArtifactBytes, AnchorProof, AnchorLedger)
→ { valid | invalid | unverifiable }
¶
Where:¶
The short-form notation V(B, P, L) from the Anchoring Specification [ANCHORING] Section 4 is equivalent: this profile uses the descriptive parameter names recommended in the companion-note of [ANCHORING] Section 4 for clarity in running text.¶
A conformant Anchor Proof MUST encode the following abstract fields, regardless of serialization format:¶
+-----+-----------------+------+-----------------+------------------+ | # | 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 | +-----+-----------------+------+-----------------+------------------+ | F6 | block_hash | SHLD | bstr .size 32 | Block header | | | | | | hash for cross- | | | | | | verification | +-----+-----------------+------+-----------------+------------------+ | F7 | tx_id | SHLD | bstr .size 32 | Transaction | | | | | | identifier | | | | | | containing the | | | | | | commitment | +-----+-----------------+------+-----------------+------------------+ | F8 | block_time | MUST | uint | Block header | | | | | | nTime (see | | | | | | Section 2.6) | +-----+-----------------+------+-----------------+------------------+ | F9 | anchor_status | MUST | status-tstr | "submitted" / | | | | | | "pending" / | | | | | | "anchored" / | | | | | | "failed" | +-----+-----------------+------+-----------------+------------------+ | F10 | calendar_url | MAY | tstr / nil | URL of calendar | | | | | | server used for | | | | | | submission | +-----+-----------------+------+-----------------+------------------+¶
Table 1: Anchor Proof Abstract Fields¶
The "Reqd" column uses MUST, SHLD (SHOULD), and MAY per [RFC2119]. The "CDDL Type" column lists the concrete CDDL [RFC8610] type for each field; status-tstr is a tstr restricted to the four enumerated values shown in the Description column. The complete normative CDDL schema is published as a machine-readable file (Section 2.4.3).¶
The literal "sha256" is used for the F2 hash_algorithm field, aligned with the OpenTimestamps internal naming convention used throughout this profile. The same algorithm is registered as sha-256 in the IANA Named Information Hash Algorithm Registry [RFC6920]; both names refer to the function specified in [FIPS180-4]. Implementations MUST use the literal "sha256" in the F2 field for compatibility with the canonical hash-prefix notation "sha256:" || HEX(h) employed in the algorithms of Appendix D.¶
Fields F1-F5, F8, and F9 are REQUIRED for a proof with anchor_status "anchored". Fields F6-F7 are RECOMMENDED. Field F10 is informational.¶
A proof with anchor_status "pending" MUST contain at minimum F1, F2, F9, and a partial merkle_path (F3) sufficient for later upgrade.¶
This profile uses the OpenTimestamps binary format as the reference serialization. The mapping from abstract fields to OTS encoding is as follows:¶
+-----+---------------------+-------------------------------------+ | # | 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 | Derived: verifier resolves from | | | | Bitcoin block headers | +-----+---------------------+-------------------------------------+ | F6 | block_hash | Derived: verifier resolves from | | | | Bitcoin block headers | +-----+---------------------+-------------------------------------+ | F7 | tx_id | Derived: verifier resolves from | | | | Bitcoin block data | +-----+---------------------+-------------------------------------+ | F8 | block_time | Derived: from block header | | | | timestamp field | +-----+---------------------+-------------------------------------+ | F9 | anchor_status | Implicit: presence of attestation | | | | tag = "anchored"; absence = | | | | "pending" | +-----+---------------------+-------------------------------------+ | F10 | calendar_url | Encoded as pending attestation URL | | | | in incomplete proofs | +-----+---------------------+-------------------------------------+¶
Table 2: OTS Wire Format Mapping¶
Note: Fields F5-F8 are "derived" in OTS because the binary proof encodes the Merkle path to the Bitcoin transaction, not the block metadata directly. The verifier extracts these values by replaying the proof operations against the Bitcoin blockchain. This is a design strength: the proof is compact and self-contained, while the ledger provides the authoritative metadata.¶
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).¶
The following CDDL schema [RFC8610] defines the concrete encoding of the abstract fields in Table 1 for the OpenTimestamps reference implementation. An implementation MAY use an alternative encoding provided all REQUIRED fields from Table 1 are present.¶
; CDDL schema for OpenTimestamps Anchor Proof bundle
; Corresponds to abstract fields F1-F10 in Table 1
AnchorProofBundle = {
artifact_hash: bytes .size 32, ; F1: SHA-256 digest
hash_algorithm: HashAlgorithm .default "sha256",
; F2: algorithm id
merkle_path: [+ MerkleOp], ; F3: path to ledger
ledger_id: "bitcoin-mainnet", ; F4: anchor ledger id
? block_height: uint, ; F5: block number
; (block-height
; binding,
; Section 2.6.1;
; REQUIRED when
; anchor_status =
; "anchored")
? block_hash: bytes .size 32, ; F6: block header hash
? tx_id: bytes .size 32, ; F7: transaction id
? block_time: uint, ; F8: Unix timestamp
; (block header
; nTime; Reference
; Wall-Clock
; Projection,
; Section 1.3;
; REQUIRED when
; anchor_status =
; "anchored")
anchor_status: AnchorStatus, ; F9: proof lifecycle
? calendar_url: uri, ; F10: submission calendar
}
AnchorStatus = "pending" / "anchored" / "failed"
HashAlgorithm = "sha256" ; SHA-256 [FIPS180-4]
MerkleOp = PrependOp / AppendOp / HashOp
PrependOp = { 1 => bytes } ; OTS prepend (0xf1)
AppendOp = { 2 => bytes } ; OTS append (0xf0)
HashOp = { 3 => HashAlgorithm } ; OTS hash op (0x08)
¶
Fields F5-F8 are derived by the verifier from the Bitcoin blockchain during execution of the VERIFY-ANCHOR algorithm (Section 3.1, steps 6-7) and need not be present in the serialized proof bundle. Their presence in the CDDL schema reflects the logical fields of a fully-verified proof, not mandatory wire format fields.¶
The normative semantic distinction between F5 (block_height, the block-height binding) and F8 (block_time, the Reference Wall-Clock Projection) is defined in Section 2.6.1. The CDDL schema records both values; the temporal claim of the proof is established by F5, with F8 presented for human readability.¶
The following CDDL schema defines the Proof Bundle structure passed to VERIFY-ANCHOR (Section 3.1). This is the normative definition of the proof_bundle input type referenced in Section 1.3.¶
; 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
}
¶
An implementation constructing a ProofBundle MUST populate ots_proof and claimed_hash. The verifier MUST NOT treat claimed_hash as authoritative; it MUST recompute the hash independently from artifact_bytes (Section 3.1, step 1).¶
The CDDL schema published at https://anchoring-spec.org/v1.2/cddl/anchor-proof-bundle.cddl is semantically equivalent to the schema above and serves as the machine-readable normative form. Implementations MUST treat that file as the authoritative concrete syntax in case of any typesetting discrepancy with this document.¶
This profile uses the Bitcoin blockchain [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.¶
An implementation MUST NOT treat a system as an Anchor Ledger under this profile unless it satisfies all of the following properties:¶
The Bitcoin blockchain satisfies all four properties:¶
.ots file, and Bitcoin block headers. No contact with the anchoring service or the OpenTimestamps Calendar Server is required (see Section 3.2).¶
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.¶
The qualification properties above take precedence over [ANCHORING] Section 7 in case of conflict for the purposes of conformance to this profile.¶
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.¶
Given artifact A and Anchor Proof P that successfully verifies against the Bitcoin blockchain (i.e., VERIFY-ANCHOR(ArtifactBytes, ProofBundle) = valid), the normative temporal claim established by P is:¶
A was committed to the Bitcoin blockchain in the block at height H.¶
This claim is referred to as the block-height binding (Section 1.3). The binding is exact: a verified proof either binds the artifact to block height H or it does not. The binding is consensus-verified by the same proof-of-work process that secures the Bitcoin blockchain itself.¶
The block-height binding implies an existence claim: artifact A existed before block H was added to the canonical chain, since the hash of A was necessarily computed before it could be committed to a transaction included in H.¶
Reference Wall-Clock Projection. The wall-clock value block.time(H), also known as the nTime field of the block header at height H, 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 block.time and its drift envelope appears in Section 2.6.6.¶
Optional tighter bound: Median Time Past. Verifiers MAY additionally compute the Median Time Past (MTP) of block H per [BIP113]. MTP is a strict consensus-derived lower bound: the actual time at which block H was accepted by the network is provably greater than MTP. Verifiers seeking a strict-lower-bound interpretation of "existed at or before [wall-clock time]" MAY use MTP as that bound. Computing MTP requires retrieval of the eleven block headers immediately preceding H. The block-height binding above does not require MTP and is normative regardless of whether MTP is computed.¶
Backwards-compatibility note. Anchor Proofs created under earlier interpretations of this profile, where the temporal value T was inferred to be block.time(H) rather than the block-height binding at H, remain valid under this profile. The two interpretations are logically equivalent for verification purposes: any proof that successfully binds an artifact to block H also fixes block.time(H) as a derivative value, and any wall-clock interpretation must ultimately resolve to a specific block height to be verifiable.¶
The temporal claim holds under the following assumptions:¶
ASSUMPTION 1 (Hash Collision Resistance): No second-preimage or collision has been found for the hash algorithm identified in P. For SHA-256 [RFC6234], this assumption is supported by the current state of cryptanalytic research.¶
ASSUMPTION 2 (Ledger Immutability): The block at height H, identified by its block hash, has not been replaced by a competing chain. This assumption strengthens with each subsequent confirmation. After six confirmations (~60 minutes), reorganization is considered computationally infeasible under current network conditions.¶
ASSUMPTION 3 (Block-Inclusion Causality): A transaction included in the block at height H was necessarily broadcast to the Bitcoin network before block H was mined. By extension, the OP_RETURN payload of that transaction (and hence the artifact hash committed to it) existed before block H was mined. This assumption follows directly from Bitcoin protocol mechanics; it does not depend on the specific value of any timestamp field in the block header.¶
Note: the claim "the artifact existed before block H was mined" is independent of block.time(H). The Reference Wall-Clock Projection block.time(H) (Section 1.3) provides a human-readable approximation of when block H was mined but is not part of the normative temporal claim. See Sections 2.6.3 and 2.6.6.¶
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.¶
When the Reference Wall-Clock Projection block.time(H) is presented alongside the block-height binding, an additional uncertainty applies. Bitcoin block timestamps are miner-set within consensus-allowed bounds and may diverge from the actual block-mining wall-clock time by up to approximately two hours in either direction (see Section 2.6.6 for full discussion). This uncertainty applies to the projection only; the block-height binding itself is exact.¶
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 [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.¶
An Anchor Proof explicitly does NOT establish:¶
A was created at the time block H was mined (only: A existed before block H was mined)¶
A was created by any specific party¶
A did not exist before block H was mined¶
block.time(H) corresponds to true wall-clock time (it is a miner-set value within consensus bounds; see Section 2.6.6)¶
These exclusions are consistent with [ANCHORING] Section 8 (Semantic Exclusions) and Section 15 (Non-Retroactivity).¶
Anchor Proofs produced by this profile have two states with respect to the Bitcoin blockchain (see also Section 2.4.1 field F9, anchor_status):¶
H with at least six confirmations.¶
The block-height binding (Section 2.6.1) becomes defined only when the proof reaches the anchored state. Pending proofs carry no normative temporal claim under this profile: the hash is in transit toward a block, but no block has yet been identified.¶
A Verifier that encounters a pending proof MUST return unverifiable (see 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.¶
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.¶
Time Stamping Authorities (TSAs) operating under [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.¶
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 H. The reasons for this divergence from TSA convention are:¶
H 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.¶
block.time or nTime) are miner-set within consensus-allowed bounds. The consensus rules permit a block's nTime to be in the range approximately [MTP + 1, network_time + 2h], where network_time is the median of the Bitcoin node's peers' reported times [BIP113]. As a consequence:¶
block.time 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.¶
block.time 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.¶
These properties make block.time 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.¶
block.time(H) as the temporal value associated with a verified proof. This profile preserves that behaviour by defining block.time(H) as the Reference Wall-Clock Projection (Section 1.3) and recommending its presentation alongside H for human readability.¶
The result is a two-layer model:¶
block.time(H). Convenient for display, subject to the bounded uncertainty described above.¶
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.¶
A profile that combined this mechanism with an [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.¶
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.¶
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| | | |
¶
Figure 2: Actor-Level Anchoring Flow¶
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.¶
VERIFICATION FLOW
Auditor Bitcoin Bitcoin Transparency
(Verifier) Network Block N Service
| | | |
|- fetch TX --->| | |
|<- transaction-| | |
| | | |
|- fetch header>| | |
|<- block hdr --| | |
| | | |
|- walk Merkle path (from .ots proof) ----->|
|<- verify: hash matches OP_RETURN, block 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.
¶
Figure 3: Actor-Level Verification Flow¶
The Verifier's only required trust dependency is the Bitcoin block header chain, which can be obtained from any full node or independent block explorer. 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.¶
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.¶
The verification function V (Section 2.4) is instantiated as follows:¶
Algorithm: VERIFY-ANCHOR(artifact_bytes, proof_bundle)
Input:
artifact_bytes -- the original artifact byte sequence
proof_bundle -- contains: ots_proof (.ots file),
claimed_hash, origin_id,
bitcoin_block_height (optional)
Output:
{ valid, invalid, unverifiable }
Steps:
1. RECOMPUTE HASH
computed_hash ← SHA-256(artifact_bytes)
The verifier MUST recompute the hash from the original
bytes. The verifier MUST NOT rely on any claimed hash
value without independent computation.
2. COMPARE HASH
IF HEX(computed_hash) ≠ STRIP-PREFIX(
proof_bundle.claimed_hash):
RETURN invalid
// The artifact does not match the anchored hash.
3. PARSE OTS PROOF
parsed ← OTS-DESERIALIZE(proof_bundle.ots_proof)
The verifier MUST parse the binary .ots proof according
to the OpenTimestamps wire format (Section 2.4.2).
IF parsed IS NULL OR parsed.hash ≠ computed_hash:
RETURN invalid
4. CHECK PROOF STATUS
IF parsed contains only a calendar commitment (no
Bitcoin attestation tag 0x0588960d73d71901):
RETURN unverifiable
// The proof has not been upgraded to a Bitcoin
// anchor. It depends on the Calendar Server.
5. VERIFY BITCOIN MERKLE PATH
The verifier MUST replay the sequence of append (0xf0),
prepend (0xf1), and hash (0x08) operations encoded in
the proof to derive the expected OP_RETURN value.
tx_id ← OTS-EXTRACT-TX(parsed)
expected_op_return ← OTS-WALK-MERKLE-PATH(
parsed, computed_hash)
6. VERIFY AGAINST BITCOIN
The verifier MUST retrieve the Bitcoin transaction from
any full node, block explorer, or local block header
cache. The verifier MUST NOT depend on any single
service for this lookup.
bitcoin_tx ← FETCH-TX(tx_id) [NAKAMOTO]
IF bitcoin_tx IS NULL:
RETURN unverifiable // ledger unavailable
IF bitcoin_tx.OP_RETURN ≠ expected_op_return:
RETURN invalid
7. EXTRACT BLOCK-HEIGHT BINDING AND REFERENCE WALL-CLOCK
PROJECTION
block_header ← FETCH-BLOCK-HEADER(
bitcoin_tx.block_hash)
block_height_H ← OTS-EXTRACT-BLOCK-HEIGHT(parsed)
// The block_height_H is the normative temporal binding
// (Section 2.6.1). It is exact and consensus-verified.
block_time_RWCP ← block_header.nTime
// block_time_RWCP is the Reference Wall-Clock Projection
// (Section 1.3). It is informative, not normative, and
// is subject to the bounded uncertainty described in
// Section 2.6.6. Verifiers SHOULD present block_time_RWCP
// alongside block_height_H for human readability.
// OPTIONAL: a verifier MAY additionally compute the
// Median Time Past per [BIP113] as a strict-lower-bound
// wall-clock interpretation of the binding. This step
// is OPTIONAL and is not required for conformance.
// prev_headers ← FETCH-PREV-HEADERS(block_header, 11)
// mtp_optional ← MEDIAN(prev_headers.nTime)
8. RETURN valid
// The proof demonstrates: these exact bytes were
// committed to the Bitcoin blockchain in the block at
// height block_height_H, and therefore existed before
// that block was mined. The Reference Wall-Clock
// Projection block_time_RWCP SHOULD be presented for human
// readability. No authorship, ownership, or identity
// claim is made. See Section 2.6 (Temporal Precision)
// for full semantics.
¶
Figure 4: VERIFY-ANCHOR Algorithm¶
A conformant verifier MUST be able to complete the VERIFY-ANCHOR procedure using ONLY:¶
A conformant verifier MUST NOT require:¶
This satisfies the Independence Requirement defined in [ANCHORING] Section 9: verification is possible even if the anchoring service ceases to exist.¶
The VERIFY-ANCHOR algorithm produces three possible outputs. Implementations MUST handle each as follows:¶
+----------------+------------------------------------------+ | Output | Meaning | +----------------+------------------------------------------+ | valid | The artifact bytes match the anchored | | | hash, the Merkle path is correct, and | | | the Bitcoin transaction confirms the | | | commitment. 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 Merkle path does not | | | match the Bitcoin OP_RETURN (step 6). | | | The verifier SHOULD treat this as a | | | verification failure. | +----------------+------------------------------------------+ | unverifiable | The proof is pending (step 4) or the | | | Bitcoin ledger is unavailable (step 6). | | | The verifier SHOULD retry after a delay. | | | A pending proof MAY become verifiable | | | after Bitcoin confirmation (~2-4 hours). | +----------------+------------------------------------------+¶
Table 3: VERIFY-ANCHOR Output Semantics¶
This profile is additive. It does not modify:¶
A Transparency Service operator MAY adopt this profile unilaterally. Auditors MAY verify Anchor Proofs independently of the SCITT verification flow.¶
A Transparency Service that implements this profile SHOULD include anchor metadata in its service parameters:¶
{
"anchor_ledger": "bitcoin",
"anchor_method": "opentimestamps",
"anchor_level": "log_root",
"anchor_interval": "batch",
"anchor_spec": "https://anchoring-spec.org/v1.0/"
}
¶
An Anchor Proof MAY be associated with a Receipt by including the Anchor Proof hash in the Receipt's metadata, or by co-locating the .ots file with the Receipt in a proof bundle.¶
RFC 9921 defines two COSE header parameters -- 3161-ctt and 3161-ttc -- for embedding RFC 3161 Time-Stamp Tokens directly inside COSE_Sign1 structures [RFC9052]. This establishes a precedent: COSE already supports binding external temporal proofs to signed objects without modifying the object's payload.¶
This profile extends the same principle to a different trust model:¶
+--------------------+----------------------------------+ | 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 | +--------------------+----------------------------------+¶
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).¶
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 abstract fields in 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.¶
This section provides a formal argument supporting the central claim of this profile.¶
Theorem (Existence-at-or-before-T): If VERIFY-ANCHOR(A, P) = valid (Section 3.1), then the byte sequence A existed at or before time T, where T is the timestamp of the Bitcoin block containing the anchor commitment.¶
The proof relies on the following assumptions, each of which is a well-established property of the underlying primitives:¶
H -- and hence the artifact hash committed to that payload -- existed before block H was mined. This assumption follows from Bitcoin protocol mechanics and does not depend on the specific value of any timestamp field in the block header. (For verifiers seeking a conservative wall-clock interpretation, the Median Time Past per [BIP113] provides a strict lower bound on block.time(H); see Section 2.6.1.)¶
h to a value embedded in a Bitcoin transaction's OP_RETURN output [OTS]. This chain is publicly verifiable. The normative algorithms for construction and verification are specified in Appendix D of this document; see Section 5.2.1.¶
OpenTimestamps [OTS] is a deployed open-source protocol with multiple independent implementations and a public project site [OTS-SITE]. It does not, however, have a formal IETF or ISO specification. This profile therefore treats OpenTimestamps as a reference implementation, not as a normative dependency.¶
The security argument of Section 5.3 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:¶
Both primitives are independently verifiable against the Bitcoin blockchain without reference to any OTS software. Appendix D provides self-contained pseudocode sufficient to implement verification from first principles. If the OTS reference implementation were to become unavailable, the algorithms in Appendix D remain sufficient to verify any proof produced under this profile.¶
Let A be an artifact (byte sequence), and let P be an Anchor Proof for which VERIFY-ANCHOR(ArtifactBytes, ProofBundle) = valid.¶
Step 1: By steps 1-2 of the verification algorithm (Section 3.1), SHA-256(A) = h, where h is the hash committed in the Anchor Proof. By Assumption A1, A is the unique preimage of h with overwhelming probability (2^-128 security level for second preimage).¶
Step 2: By steps 3 and 5, the .ots proof contains a deterministic sequence of hash operations linking h to a value V embedded in a Bitcoin transaction TX. By Assumption A4, this chain is correct and verifiable: V = f(h) where f is the composition of the Merkle path operations.¶
Step 3: By step 6, the Bitcoin transaction TX exists in the block at height H and TX.OP_RETURN = V. By Assumption A2, the inclusion of TX in the block at height H cannot be retroactively altered.¶
Step 4: By Assumption A3 (Block-Inclusion Causality), TX was necessarily broadcast to the Bitcoin network before the block at height H was mined. Therefore the OP_RETURN payload V -- which commits to h, which commits to A -- existed before the block at height H was mined.¶
Step 5: The hash commitment is causal: to produce h, artifact A must have existed before h was computed. To include h in the OTS Merkle path that produces V, h must have existed before TX was broadcast. To include TX in the block at height H, TX must have existed before that block was mined.¶
Therefore: A existed → h was computed → TX was broadcast → block at height H was mined. The artifact A existed before the block at height H was added to the canonical chain (the block-height binding of Section 2.6.1).¶
This argument does not depend on the block's nTime field, on the Median Time Past, or on any wall-clock interpretation of the binding. The Reference Wall-Clock Projection block.time(H) (Section 1.3) is informative only and is not required for the security argument above. ∎¶
The above argument is a computational security argument, not an information-theoretic proof. Its strength is bounded by:¶
The argument does NOT prove:¶
A was created (only that A existed before the block at height H was mined)¶
A (no identity binding)¶
A has not been modified since anchoring (only that these specific bytes existed)¶
These limitations are inherent to the mechanism and are documented in [ANCHORING] Section 8 (Semantic Exclusions).¶
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.¶
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 anchoring mechanism is hash-agile by design.¶
Bitcoin's availability characteristics exceed those of any single-operator Transparency Service. However, Anchor Proof verification requires access to the Bitcoin blockchain (or a trusted copy). Offline verification is possible with a local blockchain copy or cached block headers.¶
Log Root Anchoring (Section 2.3) enables equivocation detection: if a Transparency Service presents different log states to different parties, the anchored root provides a public commitment that can be compared. This is a strictly stronger guarantee than SCITT provides without external anchoring.¶
This section follows the guidelines in [RFC3552] for describing threats and mitigations relevant to this profile.¶
The attacker model assumes the following capabilities:¶
The following subsections enumerate specific threats under this model.¶
An attacker could construct 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.¶
This is remediated by the collision resistance of SHA-256 [FIPS180-4]. No known practical collision attack exists against SHA-256 as of the date of this document. The Construction Algorithm (Appendix D.1) is hash-algorithm- agile: if SHA-256 is weakened, the hash_algo field permits migration to a successor algorithm without protocol changes.¶
An attacker with majority hash power on the Anchor Ledger could rewrite the block containing the commitment, thereby invalidating or altering the temporal proof.¶
This is remediated by the economic cost of sustaining a majority attack on Bitcoin, which is the only Anchor Ledger currently qualified under the Anchoring Specification [ANCHORING] Section 7 (Ledger Qualification). 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. The Verification Algorithm (Section 3.1) requires a minimum confirmation depth before accepting an anchor as valid.¶
An attacker operating a compromised OpenTimestamps calendar server could return divergent intermediate commitments to different clients, or withhold a valid commitment entirely.¶
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). A Calendar Server that equivocates produces proofs that fail verification.¶
A malicious Transparency Service operator could present different log states to different relying parties while anchoring only one version.¶
This is remediated by Log Root Anchoring (Section 2.3). Because the anchored Merkle root is committed to the public ledger, any party holding a Receipt can independently compute the expected root and compare it against the anchored value. Divergent log states are detectable by any two parties that compare their anchored roots.¶
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:¶
Variant 1: claiming an earlier creation time. 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 H was mined.¶
This is remediated by the explicit semantics defined in Section 2.6.1: the normative claim is the block-height binding ("A was committed to the Bitcoin blockchain in the block at height H"), which implies "A existed before block H 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.¶
Variant 2: misrepresenting the Reference Wall-Clock Projection as normative. A claimant could present block.time(H) (the Reference Wall-Clock Projection, Section 1.3) as if it were the normative temporal value of the proof, ignoring that block.time is miner-set within consensus bounds and may diverge from true wall-clock time by up to approximately two hours (Section 2.6.6).¶
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. Verifiers that require strict-lower-bound wall-clock semantics MAY compute the Median Time Past per [BIP113] as documented in Section 2.6.1 ("Optional tighter bound").¶
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.¶
An attacker could modify the certificate.json or .ots proof file within an Anchor Proof after generation, for example by altering the captured_at timestamp or substituting a different hash value.¶
This is remediated by the cryptographic binding between the components. The .ots proof commits to a specific hash value; any modification to certificate.json that changes the hash breaks the OTS verification chain. The Verification Algorithm (Section 3.1) re-derives the hash from the original artifact bytes and verifies it against the .ots proof independently -- it does not trust the metadata in certificate.json.¶
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.¶
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.¶
Over decades, SHA-256 may become vulnerable to collision or pre-image attacks due to advances in computing (including quantum computing).¶
This is remediated by the algorithm-agility provision in the protocol. The hash_algo field in the anchor record (Section 2.4.1, field F2) permits migration to a successor hash algorithm. Existing proofs anchored with SHA-256 retain their validity for the period during which SHA-256 was considered secure. The Anchoring Specification [ANCHORING] Section 15 defines the temporal semantics that bound this validity window.¶
The anchoring service proves that a given hash existed at or before a certain time. It does not, and cannot, prove the truth, accuracy, or origin of the data that produced that hash.¶
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.¶
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.¶
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.¶
This eliminates three threat categories that apply to key-based timestamping:¶
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).¶
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.¶
This is remediated by three independent controls. First, the Verification Algorithm (Section 3.1) requires the verifier to independently retrieve the Bitcoin transaction and confirm block inclusion -- the verifier does not rely on any status field asserted by the anchoring service. Second, Assumption A2 (Section 5.2) specifies that a minimum of six confirmations (~60 minutes) must be reached before the immutability assumption is considered computationally sound. Implementations MUST NOT promote a proof from "pending" to "anchored" (field F9, Section 2.4.1) until the transaction has reached the minimum confirmation depth required by their deployment policy. Third, Section 2.6.5 (Pending Proofs and Temporal Definition) establishes that a pending proof carries no normative temporal claim: a Verifier encountering a pending proof MUST return unverifiable (Section 3.1, step 4) rather than treating the calendar commitment as a provisional binding.¶
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.¶
This is remediated by the Batch Anchoring algorithm (Appendix D.3), which specifies deterministic leaf ordering (lexicographic sort before concatenation) and requires individual origin records per hash in addition to the shared Merkle root anchor. A verifier can independently recompute the Merkle path from a leaf hash to the root and confirm correct positioning. Post-batch verification sampling SHOULD be performed by implementations to detect systematic construction errors before they are exposed to relying parties.¶
This profile operates on cryptographic hashes only. No artifact content, personal data, or identity information is transmitted to or stored on the Anchor Ledger.¶
However, the following privacy-relevant properties apply:¶
Implementations that anchor hashes of privacy-sensitive artifacts SHOULD inform users that the hash becomes a permanent, publicly queryable identifier on the anchor ledger.¶
In multi-tenant deployments, anchored hashes from different tenants appear on the same public ledger within the same Bitcoin blocks. An observer with access to hashes from multiple tenants can determine temporal ordering relationships across tenants -- including whether two artifacts were anchored in the same batch -- even when no artifact content is disclosed. Implementations that anchor on behalf of multiple tenants SHOULD be aware that the public ledger creates a permanent, cross-tenant ordering record. The 2-hour maximum forward drift in Bitcoin block timestamps (Section 2.6.3, Assumption A3) defines the worst-case window within which ordering observations are unreliable; within a single confirmed block (~10 minutes), ordering between co-batched hashes is not preserved by the protocol.¶
This document has no IANA actions.¶
+---------------------------------------------+ | 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 | +---------------------------------------------+¶
SCITT operates at L3-L4. This profile defines the L2 integration. L1 is the Bitcoin consensus layer. The layers are independent: each can be verified without the others.¶
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¶
Note: If the Transparency Service is no longer available, step (a) cannot be performed -- steps (b) and (c) remain independently valid. The temporal and binding proofs do not depend on the continued operation of the log.¶
As of March 2026, the following implementations exist:¶
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 (Section 5.2). It defines the construction algorithms for OpenTimestamps-based anchoring as used in this document. The algorithms are presented in pseudocode and are self-contained: they can be implemented independently of any OpenTimestamps software library. They correspond to the reference implementation described in Appendix C, but do not depend on it.¶
For background on the OpenTimestamps protocol design, see [OTS-SITE] and [OTS-DESIGN].¶
The construction algorithm CONSTRUCT-ANCHOR is defined as follows. Given an artifact byte sequence, it produces an origin record and a pending OpenTimestamps proof; subsequent upgrade to an anchored proof is specified in Appendix D.2.¶
Algorithm: CONSTRUCT-ANCHOR(artifact_bytes)
Input:
artifact_bytes -- arbitrary byte sequence (the artifact)
Output:
origin_record -- {origin_id, hash, captured_at, proof_status}
pending_proof -- serialised OTS pending proof (.ots file)
Steps:
1. HASH COMPUTATION
hash_value ← SHA-256(artifact_bytes) // [FIPS180-4]
hash_string ← "sha256:" || HEX(hash_value) // canonical form
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 CALENDAR SUBMISSION
// 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"}
¶
Figure 5: CONSTRUCT-ANCHOR Algorithm¶
The upgrade algorithm UPGRADE-PENDING-PROOFS is defined as follows. It runs as a background worker, periodically converting calendar-level commitments produced by D.1 into Bitcoin-level proofs once the relevant Bitcoin transaction has been confirmed.¶
Algorithm: UPGRADE-PENDING-PROOFS()
// Runs periodically (e.g. every 15 minutes) as a background worker.
// Converts calendar-level commitments to Bitcoin-level proofs.
Input:
none (reads from core_ots_proofs WHERE status = "pending")
Steps:
1. pending_proofs ← SELECT * FROM core_ots_proofs
WHERE status = "pending"
AND created_at < NOW() - INTERVAL '2 hours'
2. FOR EACH proof IN pending_proofs:
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.
2b. IF upgraded_proof IS NULL:
// Bitcoin transaction not yet confirmed, or calendar
// not yet merged into a block. Retry next cycle.
CONTINUE
2c. EXTRACT BITCOIN BINDING
block_height ← OTS-EXTRACT-BLOCK-HEIGHT(upgraded_proof)
block_time ← OTS-EXTRACT-BLOCK-TIME(upgraded_proof)
2d. VERIFY LOCALLY
// Verify the Merkle path from hash → Bitcoin OP_RETURN
valid ← OTS-VERIFY(upgraded_proof, proof.origin_hash)
IF NOT valid:
LOG-ERROR("Upgrade verification failed", proof.origin_id)
CONTINUE
2e. PERSIST UPGRADED PROOF
UPDATE core_ots_proofs SET
ots_proof = upgraded_proof,
status = "anchored",
bitcoin_block_height = block_height,
anchored_at = block_time,
upgraded_at = NOW()
WHERE origin_id = proof.origin_id
¶
Figure 6: UPGRADE-PENDING-PROOFS Algorithm¶
The batch anchoring algorithm BATCH-ANCHOR is defined as follows. It is a high-throughput variant of CONSTRUCT-ANCHOR (Appendix D.1) that anchors a single Merkle root for up to 1000 hashes in one Bitcoin commitment, instead of one commitment per hash. Each individual hash retains its own origin record; only the Merkle root is submitted to the OpenTimestamps Calendar Server(s).¶
Algorithm: BATCH-ANCHOR(hash_list)
// Anchors up to 1000 hashes via a single Merkle root.
// Each hash retains its own origin record (step 3); only
// the Merkle root is submitted to the OTS calendar(s) in
// step 4. This reduces N OTS submissions to 1.
Input:
hash_list -- ordered list of SHA-256 hashes [h_0 ... h_{n-1}]
Output:
batch_record -- {batch_id, merkle_root, merkle_origin_id,
origins[]}
Steps:
1. VALIDATE
ASSERT 1 ≤ |hash_list| ≤ 1000
FOR EACH h IN hash_list:
ASSERT h matches /^(sha256:)?[0-9a-f]{64}$/
2. COMPUTE MERKLE ROOT // [RFC9162] §2.1
// Canonical leaf ordering: strip "sha256:" prefix, sort pairs
// lexicographically before concatenation. After STRIP-PREFIX,
// level elements are hex-encoded strings; concatenation in
// step 2c below is hex-string concatenation, and SHA-256 is
// applied to the resulting ASCII byte sequence. A second
// implementer reading || as raw-byte concatenation of decoded
// hash bytes would compute a different root and lose
// interoperability with this profile.
level ← [STRIP-PREFIX(h) FOR h IN hash_list]
WHILE |level| > 1:
next_level ← []
FOR i ← 0 TO |level|-1 STEP 2:
IF i+1 < |level|:
pair ← SORT([level[i], level[i+1]])
// pair[0] and pair[1] are hex strings; || concatenates
// them as ASCII; SHA-256 hashes the resulting bytes.
next_level.APPEND(SHA-256(pair[0] || pair[1]))
ELSE:
next_level.APPEND(level[i]) // odd element: promote
level ← next_level
merkle_root ← "sha256:" || level[0]
3. CREATE INDIVIDUAL ORIGIN RECORDS
// Per-hash bookkeeping only; no OTS calendar submission
// is performed at this step (the Merkle root is anchored
// once in step 4 below).
FOR EACH h IN hash_list:
origin_id ← UUID-v4() [RFC4122]
short_token ← RANDOM-ALPHANUMERIC(8)
captured_at ← NOW() // UTC ISO 8601
INSERT origin_attestations {
origin_id, hash: h, hash_algo: "sha256",
captured_at, short_token
}
4. ANCHOR MERKLE ROOT
// Only the root is submitted to OTS calendar(s).
// This reduces N OTS operations to 1.
CALL CONSTRUCT-ANCHOR(merkle_root)
5. LINK BATCH
INSERT batch_submissions {
batch_id, merkle_root, merkle_origin_id,
hashes: hash_list, origin_ids: [per-hash origin_ids]
}
6. RETURN batch_record
¶
Figure 7: BATCH-ANCHOR Algorithm¶
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.¶
See Section 3.1 (VERIFY-ANCHOR) for the full eight-step verification procedure with MUST/SHOULD requirements.¶
The normative verification independence requirements have been promoted to Section 3.2 of this document.¶
See Section 3.2 for the definitive statement of what a conformant verifier requires and what it MUST NOT depend on.¶
This appendix provides concrete test vectors derived from a production anchoring run. Each vector can be independently verified using the VERIFY-ANCHOR algorithm (Section 3.1) and the verification tool at https://verify-anchoring.org.¶
The vectors are drawn from an AI model training pipeline (a Hugging Face Transformers fine-tuning run) in which each pipeline event was anchored to Bitcoin via the Umarise anchoring service. The pipeline anchored four events: on_train_begin, on_evaluate, on_train_end, and a post-run manifest. All four resulting Anchor Proofs are Bitcoin-confirmed.¶
For each vector the following values are provided:¶
block.time(H) -- the nTime field of the Bitcoin block header, presented for human readability. This is informative only (Section 1.3, Section 2.6.6).¶
A verifier MAY additionally compute the Median Time Past per [BIP113] as a strict-lower-bound wall-clock interpretation of the binding (Section 2.6.1, "Optional tighter bound"). MTP is not required for conformance and is not reported in the vectors below.¶
This vector represents the on_train_begin event from a Hugging Face training pipeline. The vector binds the serialised pipeline state at training start to a specific Bitcoin block.¶
Artifact description: Hugging Face training pipeline --
on_train_begin event (serialised
pipeline state at training start)
SHA-256(artifact): 781bb71a88c82d1f009178d3e2a48fba
5023f52f510553ce74bf8d64db9985dd
Anchor Proof file: 1777911147926_on_train_begin.ots
Proof file size: 2086 bytes
Anchor Ledger: Bitcoin mainnet
Block-height binding: Bitcoin block at height 947124
(normative; Section 2.6.1)
RWCP block.time(H): 2026-04-29T08:29:20Z
(Reference Wall-Clock Projection;
informative; Section 1.3)
Expected VERIFY-ANCHOR result: valid
¶
Expected step-by-step execution of VERIFY-ANCHOR:¶
Step 1: SHA-256(artifact) = 781bb71a...9985dd [matches proof]
Step 2: STRIP-PREFIX match: PASS
Step 3: OTS-DESERIALIZE(.ots): valid OTS structure
Step 4: Bitcoin attestation tag present: 0x0588960d73d71901
Step 5: OTS-WALK-MERKLE-PATH produces
OP_RETURN value matching Bitcoin
transaction in block 947124: PASS
Step 6: FETCH-TX confirms block inclusion: PASS
Step 7: Block-height binding established:
block_height_H = 947124
Reference Wall-Clock Projection:
block_time_RWCP = 2026-04-29T08:29:20Z
Step 8: RETURN valid
¶
Interpretation: The bytes of the on_train_begin event were committed to the Bitcoin block at height 947124. By Block-Inclusion Causality (Section 2.6.2, Assumption A3), the artifact existed before block 947124 was mined. The Reference Wall-Clock Projection 2026-04-29T08:29:20Z is presented as a human-readable approximation; it is not the normative temporal value of the proof.¶
This vector represents the on_evaluate event captured during the training run. Together with E.1 and E.3, it provides a sequence of block-height bindings spanning the pipeline.¶
Artifact description: Hugging Face training pipeline --
on_evaluate event (serialised
evaluation state during training)
SHA-256(artifact): 67b86bc99ad01edf7351610f57291d28
84c20309bf3355fbc4020b6426eaa6be
Anchor Proof file: on_evaluate.ots
Proof file size: 2877 bytes
Anchor Ledger: Bitcoin mainnet
Block-height binding: Bitcoin block at height 947150
(normative; Section 2.6.1)
RWCP block.time(H): 2026-04-29T12:26:30Z
(Reference Wall-Clock Projection;
informative; Section 1.3)
Expected VERIFY-ANCHOR result: valid
¶
Interpretation: The bytes of the on_evaluate event were committed to the Bitcoin block at height 947150. The artifact existed before that block was mined.¶
Artifact description: Hugging Face training pipeline --
on_train_end event (serialised
pipeline state at training end)
SHA-256(artifact): 644f84d34a4dd97a6ce3e8dfd9c73adb
7ba2d97bd98d92adbeb38d2e3780e70d
Anchor Proof file: 1777911147927_on_train_end.ots
Proof file size: 2877 bytes
Anchor Ledger: Bitcoin mainnet
Block-height binding: Bitcoin block at height 947150
(normative; Section 2.6.1)
RWCP block.time(H): 2026-04-29T12:26:30Z
(Reference Wall-Clock Projection;
informative; Section 1.3)
Expected VERIFY-ANCHOR result: valid
¶
Interpretation: The bytes of the on_train_end event were committed to the Bitcoin block at height 947150. The artifact existed before that block was mined.¶
Artifact description: Hugging Face training pipeline --
post-run manifest (serialised record
of pipeline outputs and hashes)
SHA-256(artifact): 367a1cf0f5e5c80dceea6564c440b7a4
99e273e5673be8833524bfc867ec7583
Anchor Proof file: 1777911147927_post_run_manifest.ots
Proof file size: 2877 bytes
Anchor Ledger: Bitcoin mainnet
Block-height binding: Bitcoin block at height 947150
(normative; Section 2.6.1)
RWCP block.time(H): 2026-04-29T12:26:30Z
(Reference Wall-Clock Projection;
informative; Section 1.3)
Expected VERIFY-ANCHOR result: valid
¶
Interpretation: The bytes of the post-run manifest were committed to the Bitcoin block at height 947150. The artifact existed before that block was mined.¶
Note on E.2, E.3, E.4: these three artifacts share the same block-height binding (block 947150). This is consistent with batched anchoring (Appendix D.3): multiple artifact hashes can be committed via a single Merkle root in one Bitcoin transaction. Each artifact retains its own .ots proof; the proofs differ in their Merkle path while sharing the same OP_RETURN commitment.¶
A conformant implementation MUST return invalid if the artifact bytes do not match the anchored hash. Substituting any byte in the artifact while presenting the original .ots proof MUST cause VERIFY-ANCHOR to return invalid at step 2 (hash mismatch).¶
Modified artifact: [any single byte change to the
original artifact]
Original proof: any .ots proof from E.1-E.4
Expected result: invalid (step 2: hash mismatch)
¶
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.¶
All test vectors can be independently verified without contacting the Umarise anchoring service:¶
The verifier contacts Bitcoin block explorers directly and performs no server-side computation on behalf of Umarise. This demonstrates the verification independence property defined in Section 3.2.¶
The verifier UI presents the result with the block height as the primary (normative) value and block.time(H) as the Reference Wall-Clock Projection, consistent with the two-layer model defined in Section 2.6.6.¶
All four positive vectors (E.1-E.4) have been independently verified using https://verify-anchoring.org and return "VALID, LEDGER-CONFIRMED" with the block heights and block.time(H) values shown above.¶
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.¶
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.¶
The OpenTimestamps protocol was designed by Peter Todd, whose reference implementation [OTS] underpins the anchoring mechanism described in this document.¶
The Merkle tree construction in Appendix D.3 follows the conventions established in RFC 6962 (Certificate Transparency).¶