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
txpskips the txoutproof parse, the partial-Merkle-tree walk, and the check that the txid is in the matched transaction set; - an absent
rtxskips the OP_RETURN extraction, the txid recomputation, and the block-hash recomputation from the 80-byte header; - an absent
bmrskips 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
tvalue in the payload equals the top-leveltexactly, with no translation. Changingtin the bundle changes the pre-image and fails the signature check. This is what makes block and beacon cryptographically distinct. - The
kidin the payload identifies the key that signed the proof and is derived at verification time from the suppliedpk(SHA256(0x51 || pk)truncated to 4 bytes), not read fromb.kidors.kid. Under steady state all three match; under legitimate key rotation the signer’skidmay differ from the block or subject keys, and that is allowed. - The epoch roots are the
memo(Stellar) orop(Bitcoin) value of eachcxentry, concatenated in array order. Reordering or alteringcxchanges 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 (memofor Stellar,opfor 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
vto a new integer. A bundle carrying a differentvvalue is a different format, and a verifier written forv: 1should not attempt to parse or verify it. - Non-breaking additions do NOT bump
v. Newttype codes and new optional fields can appear withinv: 1, because thetregistry 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
tcode is a hard rejection.tis a required field, and it is what selects the subject’s shape (whethersandipare present) and the byte-prefix family used to derive the subject hash (0x11and0x13for items,0x21and0x23for 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. Rejectingt = 12as 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
- RFC 8949: Concise Binary Object Representation (CBOR). Defines the CBOR encoding and the self-describing tag
55799used by the compact binary bundle. - RFC 8785: JSON Canonicalization Scheme (JCS). Canonical byte form of the subject data map used to derive subject hashes.
- RFC 6962: Certificate Transparency. Basis for the domain-separated Merkle leaf and internal-node hashing the inclusion and epoch proofs walk.
- Beacon API
- Bitcoin
- Byte-Prefix Registry
- CBOR
- Claims
- Compact Merkle Proof Encoding
- Compact proof encoding
- Ed25519 signature
- Ed25519 Signatures
- Entropy source
- Epoch root
- External Commitment
- JCS (JSON Canonicalization Scheme)
- Offline verification
- OpenTimestamps
- Proof bundle
- Public Entropy Beacons
- RFC 8785: JSON Canonicalization Scheme (JCS)
- Signing-key ID (kid)
- Stellar
- Submission Window
- t type code
- The Proof Lifecycle
- Truestamp CLI
- Verify a Proof