Support

We're here to help you get the most out of Truestamp. Find answers to common questions or get in touch with our support team.

Community & Updates

Share feedback, follow what we're building next, and see what just shipped. Each opens right here in a popup, so no separate account is needed.

Knowledge Base

Browse the concepts behind Truestamp. Pick a domain to explore, follow the links between concepts, or search across everything.

Proof Bundle Wire Format

The self-describing JSON and CBOR wire format of a Truestamp proof bundle, its integer top-level t type code (block/beacon/item/entropy plus Stellar/Bitcoin commitment codes), the fixed-width Ed25519 signature payload layout, and how the bundle is verifiable offline.

Overview

A proof bundle is the receipt you download when you timestamp something with Truestamp. It is a single small file that anyone can check for themselves, without asking Truestamp whether it is real. That is the whole point: you do not have to trust us, because the file carries its own evidence. Everything after this section is the technical, field-by-field reference for developers who build software that reads or writes these files.

What a bundle proves is a submission window. It shows that a subject (a submitted item, an entropy observation, or a Truestamp block) was submitted before a Truestamp block was finalized, and that the block was later committed to a transaction on a public blockchain. It does NOT prove when the underlying content was originally created, and it does not prove who authored it. For the exact timing rules see verify a proof.

Under the hood, a Truestamp proof bundle is a compact, self-contained artifact that lets a verifier reconstruct the cryptographic chain from the subject out to the public-blockchain transaction, without ever contacting a Truestamp server. Every bundle shares one top-level schema discriminated by an integer t type code, carries a single Ed25519 signature over a fixed-width binary payload, and serializes to two interchangeable encodings: a human-readable JSON form and a compact binary CBOR form (CBOR is a standardized binary encoding for the same data JSON holds). This concept is the field reference for that wire format. It does not walk through the verification steps (see verify a proof) or the proof lifecycle (see the proof lifecycle).

The bundle is designed to be verifiable offline. The external calls a verifier can make are to the public blockchains (Stellar Horizon, a Bitcoin node) when confirming the commitment transactions exist, and, for an entropy subject, to the original randomness source when re-fetching the recorded value. Both are optional, and neither is a Truestamp server. Trusting the signing key does need one out-of-band fetch of Truestamp’s published keyring, but that is pinned once and reused, not fetched per verification. A bundle carries everything else it needs inline.

Top-level structure

Every bundle is a single map (JSON object or CBOR map) with these top-level keys. The integer t code selects the subject type and determines which of the optional subject keys are present.

Key Type Presence Meaning
v integer always Format version. Currently always 1.
t integer always Top-level subject type code (see the code registry below).
pk string always Base64 Ed25519 public key, 32 bytes, standard base64 with padding.
sig string always Base64 Ed25519 signature over the proof payload, 64 bytes, standard base64 with padding.
ts string always ISO 8601 generation timestamp, truncated to whole seconds.
s map absent for block-like (t in {10, 11}) The subject: {id, d, mh, kid}.
ip string absent for block-like (t in {10, 11}) Base64url (no padding) Merkle inclusion proof of the subject into the block Merkle root.
b map always The block: {id, ph, mr, mh, kid}.
cx list always, non-empty External commitments to public blockchains, at least one entry.

The s subject map has these keys:

Key Type Meaning
id string Subject id. ULID for items (t=20), UUIDv7 for entropy (t in {30, 31, 32}).
d map Subject data: the item claims map, or the entropy observation data map.
mh string Hex SHA-256 metadata hash of the subject.
kid string Hex 4-byte signing-key id of the subject.

The b block map has these keys:

Key Type Meaning
id string Block id (UUIDv7).
ph string Hex SHA-256 previous block hash.
mr string Hex SHA-256 Merkle root of the block.
mh string Hex SHA-256 metadata hash of the block.
kid string Hex 4-byte signing-key id of the block.

The non-empty commitment invariant

cx is required and MUST contain at least one entry. A bundle with no external commitment would resolve only to Truestamp’s own signature chain, which contradicts the promise of offline verifiability without trusting Truestamp. A subject whose block has not yet been committed to a public blockchain cannot produce a bundle: generation fails until the next epoch commit lands.

The t type code registry

The same unified integer registry identifies both subject types (the top-level t) and external commitment chain types (cx[].t), namespaced by value range. These codes are a long-lived contract: never renumbered, never reused, never renamed. New types claim the next unused integer in their category.

Code Name Category Where it appears
10 block subject (block-like) top-level t
11 beacon subject (block-like) top-level t
20 item subject top-level t
30 entropy_nist subject top-level t
31 entropy_stellar subject top-level t
32 entropy_bitcoin subject top-level t
40 commitment_stellar external commitment cx[].t
41 commitment_bitcoin external commitment cx[].t

The decades are meaningful: 10-19 block-like subjects, 20-29 item subjects, 30-39 entropy sources, 40-49 external commitment chains, 50+ reserved.

Block-like subjects

Both block (t=10) and beacon (t=11) share the same structural shape: no s, no ip, b present, cx non-empty. For these the block is the subject, so there is no intra-block Merkle inclusion step. The two are cryptographically distinct only through the t byte inside the signed payload, so a beacon signature does not verify as a plain block signature and vice versa, even for the same block. This gives a verifier a self-describing discriminator without relying on filename or out-of-band metadata.

External commitments (cx entries)

Each entry in cx records one public-blockchain commitment of the block hash into an epoch Merkle root, and carries the epoch Merkle proof (ep, base64url, no padding) that walks the block hash up to that root. An epoch Merkle root is the single hash that summarizes a batch of Truestamp block hashes; that one root value is what actually gets written to the public blockchain, and the epoch proof shows this block belongs under it. The entry’s own t code selects the chain.

Presence in a cx entry is graded by consequence, in three tiers, and only the first is a rejection. Required means the whole bundle is rejected without it: lacking the epoch proof or the chain’s epoch-root key, the entry cannot enter the epoch-proof walk or the signed epoch-root list at all. Confirmation means the field is needed only to look the transaction up on the public chain: without it that chain’s confirmation step has nothing to query and is reported skipped, exactly as a network failure is. Optional means its absence narrows what the confirmation step can establish and is never a failure.

A Stellar commitment (cx[].t == 40):

Key Type Presence Meaning
t integer required 40.
memo string required Hex 32-byte epoch Merkle root, stored in the Stellar transaction memo.
ep string required Base64url Merkle proof: block hash to the epoch Merkle root.
tx string confirmation Hex 32-byte transaction hash.
net string optional testnet or public.
l integer optional Ledger number.
ts string optional ISO 8601 commitment timestamp.

A Bitcoin commitment (cx[].t == 41):

Key Type Presence Meaning
t integer required 41.
op string required Hex 32-byte epoch Merkle root, carried in the transaction OP_RETURN output.
ep string required Base64url Merkle proof: block hash to the epoch Merkle root.
tx string confirmation Hex 32-byte txid.
h integer confirmation Block height.
net string optional regtest, testnet, or mainnet.
txp string optional Hex Bitcoin txoutproof (partial Merkle tree).
rtx string optional Hex Bitcoin raw transaction.
bmr string optional Hex 32-byte Bitcoin block Merkle root.
ts string optional ISO 8601 commitment timestamp.

What an absent optional field does

Truestamp legitimately emits entries that omit optional fields, so a verifier that treats them as required rejects real bundles. The Bitcoin offline payload txp, rtx, and bmr is the case that matters in practice, and each field’s absence skips exactly the checks that consume it:

  • an absent txp skips the txoutproof parse, the partial-Merkle-tree walk, and the check that the txid is in the matched transaction set;
  • an absent rtx skips the OP_RETURN extraction, the txid recomputation, and the block-hash recomputation from the 80-byte header;
  • an absent bmr skips the cross-check of the recorded block Merkle root against the txoutproof header;
  • an entry carrying none of the three carries no offline Bitcoin evidence at all, and its Bitcoin commitment is reported skipped.

An absent net on its own never skips a confirmation step: a Stellar entry with no net still resolves to the non-public Horizon instance, while a verifier must not guess a Bitcoin network, which leaves it with no lookup and therefore a skip. An absent tx (either chain) or h (Bitcoin) leaves the confirmation step with nothing to query, so it is likewise skipped, never failed.

The signature payload

Every bundle carries exactly one Ed25519 signature (sig) computed over SHA256(0x61 || payload), where payload is a fixed-width, big-endian binary layout. The leading 0x61 is a domain prefix: a fixed one-byte tag prepended before hashing so that this hash can never collide with a hash computed for a different purpose in the system. Each kind of hash uses its own prefix (see domain-separated hashing).

offset  size   field
------  ----   -----
0       1      v            version, uint8, currently always 1
1       2      t            type code, uint16 big-endian (same value as top-level t)
3       4      kid          signing-key id of the proof signer, derived from pk
7       8      ts_ms        generation timestamp, milliseconds since Unix epoch, uint64 big-endian
15      32     subject_hash raw bytes
47      32     block_hash   raw bytes
79      2      N            count of epoch roots, uint16 big-endian
81      32*N   epoch_roots  concatenated raw epoch roots, in cx array order

Three properties make this payload the tamper-evidence root for the whole bundle:

  • The t value in the payload equals the top-level t exactly, with no translation. Changing t in the bundle changes the pre-image and fails the signature check. This is what makes block and beacon cryptographically distinct.
  • The kid in the payload identifies the key that signed the proof and is derived at verification time from the supplied pk (SHA256(0x51 || pk) truncated to 4 bytes), not read from b.kid or s.kid. Under steady state all three match; under legitimate key rotation the signer’s kid may differ from the block or subject keys, and that is allowed.
  • The epoch roots are the memo (Stellar) or op (Bitcoin) value of each cx entry, concatenated in array order. Reordering or altering cx changes the payload.

For block-like subjects (t in {10, 11}), subject_hash == block_hash: the same 32 bytes fill both slots. That degenerate case is intentional and harmless.

JSON and CBOR encodings

A bundle serializes to one of two interchangeable encodings that encode the same logical bundle and pass through the same verification.

The JSON form is human-readable. Hash-type fields are lowercase hex strings; pk and sig are standard base64 strings; the Merkle proofs ip and cx[].ep are base64url strings with no padding.

The CBOR form (RFC 8949) is the compact binary form. The whole bundle is wrapped in the CBOR self-describing tag 55799 (the three bytes 0xd9 0xd9 0xf7), so a stream parser can detect the format from its prefix. On encode, a fixed set of fields that are hex or base64 strings in JSON become CBOR byte strings (major type 2) for compactness; every other field keeps its natural CBOR type.

Field JSON CBOR
Block/subject hashes mh, ph, mr hex string (64 chars) byte string (32 bytes)
Signing-key id kid hex string (8 chars) byte string (4 bytes)
Commitment hashes tx, memo, op, bmr hex string (64 chars) byte string (32 bytes)
Public key pk base64 string byte string (32 bytes)
Signature sig base64 string byte string (64 bytes)
Merkle proofs ip, ep base64url string text string (base64url, unchanged)
Bitcoin hex txp, rtx hex string text string (hex, unchanged)
Subject data s.d JSON object CBOR map (structure preserved)
v, t, l, h numbers numbers
ids, net, ts text strings text strings

The Merkle proofs (ip, ep) and the Bitcoin hex fields (txp, rtx) stay text strings in CBOR rather than being converted to byte strings. A downstream verifier should accept either shape for these fields.

Offline verifiability

The bundle is self-contained by design. A verifier reconstructs the full chain using only the bundle contents:

  • The subject data (s.d), metadata hash (s.mh), and signing-key id (s.kid) regenerate the subject hash for non-block-like subjects.
  • The inclusion proof (ip) walks the subject hash up to the block Merkle root (b.mr).
  • The block fields (b) regenerate the block hash.
  • Each epoch proof (cx[].ep) walks the block hash up to an epoch Merkle root that equals the on-chain value (memo for Stellar, op for Bitcoin).
  • The signature (sig), the public key (pk), and the reconstructed payload confirm Truestamp signed exactly this bundle.

Confirming that the commitment transactions actually exist on Stellar or Bitcoin, and re-fetching an entropy subject’s original source, are the steps that reach the network, and both are optional: a verifier can run fully offline and simply mark those checks as skipped. Tampering with any signed input (subject data, block fields, cx entries, the signature, or the t discriminator) breaks verification.

Integers in subject data must stay inside the safe range

Subject data (s.d) is canonicalized with JCS before it is hashed, and RFC 8785 defines every JSON number by parsing it into an IEEE-754 double. An integer outside the range a double represents exactly therefore canonicalizes differently depending on the verifier: Truestamp emits it verbatim, while a JavaScript, Go, or Python verifier reads a rounded value back and derives a different digest. Such a proof is cryptographically sound but not portably verifiable, and a verifier that does not notice will report a valid proof as failed.

RFC 8785 states the bound in Appendix B as a SHOULD: integers should lie within -9007199254740991 to 9007199254740991, that is plus or minus 2^53 - 1, which is JavaScript’s Number.MAX_SAFE_INTEGER. Truestamp enforces it at submission: an Item whose claims carry an integer outside that range is rejected, with an error naming the offending key. Send a larger value as a string instead.

Floats are unaffected and are not restricted. A float is already a double, so every implementation reads back the same value, and the canonical form is determined by ECMA-262’s number-to-string rules that RFC 8785 adopts.

The rule applies to the subject data a verifier canonicalizes. Entropy observation payloads are captured verbatim from NIST, Stellar, and Bitcoin, so Truestamp does not control their contents; a client reading one should be prepared for the same limit. The metadata maps behind s.mh and b.mh are never re-canonicalized by a verifier and are not affected.

The metadata hashes are opaque

A bundle carries s.mh and b.mh but never the metadata maps behind them. A verifier must treat each as an opaque 32-byte input it cannot independently recompute. Tampering is still caught, because both values feed hashes the bundle does derive: s.mh feeds the composite subject hash, b.mh feeds the block hash, and both feed the signed payload through those hashes.

One consequence is load-bearing for timing claims. The item’s captured head-block reference, the fact that pins the submitted-after edge of the submission window, lives inside that absent metadata map. Only s.mh travels, so a bundle-only verifier cannot re-derive that edge and must report it as not established from the bundle rather than asserting it. It may retrieve the referenced block out of band as an optional extension. The submitted-before edge is different: it is grounded by the cx transaction that carries an epoch root covering this block, which is in the bundle.

Versioning

The top-level v field is the format version, and it is currently 1. That is the only version that has ever shipped, so every bundle in circulation today carries v: 1.

v is a hard compatibility boundary that governs how the format may evolve:

  • A breaking change to the bundle schema or to the signature payload bumps v to a new integer. A bundle carrying a different v value is a different format, and a verifier written for v: 1 should not attempt to parse or verify it.
  • Non-breaking additions do NOT bump v. New t type codes and new optional fields can appear within v: 1, because the t registry only ever claims unused integers and never renumbers, reuses, or renames an existing code.

The two kinds of “something I have not seen before” are handled in opposite ways, and a verifier must not merge them:

  • An unrecognized t code is a hard rejection. t is a required field, and it is what selects the subject’s shape (whether s and ip are present) and the byte-prefix family used to derive the subject hash (0x11 and 0x13 for items, 0x21 and 0x23 for entropy). A verifier that has never seen the code cannot derive the subject hash at all, so “a bundle whose signed inputs still verify” is not a state it can reach. It must reject the bundle as unsupported (invalid_subject_type_code) before producing any step results, must not infer a shape from whichever keys happen to be present, and in particular must NOT treat an unknown code as block-like. Rejecting t = 12 as unsupported is the correct behavior, not a defect; the decade contract is what makes that rejection survivable rather than fatal to the format.
  • An unrecognized optional field inside a known structure is not. A verifier should ignore it and continue, because a field it never reads cannot change a digest it does compute.

In short, v tells a consumer whether it can read the bundle at all, a t it does not implement tells it to stop, and unknown optional fields evolve the format underneath a stable v: 1. See verify a proof for how each outcome is reported.

Example (JSON, item subject)

An item bundle (t=20) committed to Stellar. Hash and key values are truncated here for readability; on the wire they are full-length hex or base64 strings.

{
  "v": 1,
  "t": 20,
  "pk": "b3J1c3RhbXBQdWJsaWNLZXlCYXNlNjRFbmNvZGVkMzJC",
  "sig": "U2lnbmF0dXJlQmFzZTY0RW5jb2RlZDY0Qnl0ZXNPdmVyVGhlUHJvb2ZQYXlsb2Fk",
  "ts": "2026-07-05T00:00:00Z",
  "s": {
    "id": "01J8XZ3K2M9QeXAMPLEULID26CHARS",
    "d": { "description": "Contract v3 signed", "hash": "…", "hash_type": "sha256" },
    "mh": "aa11…64hexchars…ff",
    "kid": "1a2b3c4d"
  },
  "ip": "AwABiZ…base64url-no-padding…",
  "b": {
    "id": "0192a1b2-c3d4-71e5-8f60-112233445566",
    "ph": "0000…64hexchars…00",
    "mr": "bb22…64hexchars…cc",
    "mh": "cc33…64hexchars…dd",
    "kid": "1a2b3c4d"
  },
  "cx": [
    {
      "t": 40,
      "net": "public",
      "l": 51234567,
      "tx": "dd44…64hexchars…ee",
      "memo": "ee55…64hexchars…ff",
      "ep": "AgAByZ…base64url-no-padding…",
      "ts": "2026-07-05T00:01:12Z"
    }
  ]
}

A block-like bundle (t=10 or t=11) drops the s and ip keys entirely and keeps v, t, pk, sig, ts, b, and cx.

Citations

  1. RFC 8949: Concise Binary Object Representation (CBOR). Defines the CBOR encoding and the self-describing tag 55799 used by the compact binary bundle.
  2. RFC 8785: JSON Canonicalization Scheme (JCS). Canonical byte form of the subject data map used to derive subject hashes.
  3. RFC 6962: Certificate Transparency. Basis for the domain-separated Merkle leaf and internal-node hashing the inclusion and epoch proofs walk.

Get Help

API Documentation

Comprehensive guides for the REST and GraphQL APIs, including interactive documentation and code examples.

View API Docs

FAQ

Quick answers to the most commonly asked questions about timestamping and verification.

Browse FAQ

Email Support

Send us a message and our team will respond within 24 hours.

[email protected]

Security Issues

Report security vulnerabilities through our responsible disclosure program.

[email protected]