Core concepts /Transparency log

Transparency log

The transparency log is an append-only, RFC-6962 Merkle log of mirrored receipts, so the cloud cannot quietly drop or rewrite a receipt it has already accepted.

The audit chain already proves your own receipt stream is internally consistent: a local BLAKE3 link ties each receipt to the one before it, so altering an earlier receipt breaks every hash after it. But that guarantee belongs to whoever holds the stream. The transparency log adds public, append-only evidence for the receipts that reach the cloud mirror, so an outside observer can check the log without holding it.

HESO uses the same design as Certificate Transparency: an RFC-6962 Merkle tree built with SHA-256, where each leaf is a logged receipt. When a receipt is mirrored and logged, its transparency[] array gets an entry recording where it landed.

What the log proves — and what it doesn't

A transparency proof shows a receipt is in the log at a known index and that the log only ever grew. It does not re-check the receipt itself — the leaf is just a hash. You still recompute the hash and check the Ed25519 signatures with offline verification. And the log only covers receipts that reached the cloud: a receipt that never left your machine is not in it.

The transparency[] entry

Every receipt carries a transparency[]array. It is empty until the receipt is mirrored and logged; once it is, the log adds one entry that records the receipt’s place in the tree.

log_idstringrequired
The log this receipt was included in, e.g. log.heso.ca.
leaf_indexnumberrequired
The receipt's zero-based, org-local position in the log — its place in the org's leaf sequence.
inclusion_proofstring[]required
The sibling hashes (base64) from the leaf up to the root — the inclusion proof.
checkpointstringrequired
The signed log checkpoint (C2SP signed-note form) over the root the proof is against. Byte-identical for every receipt of an epoch.
transparency.jsonjson
// A receipt's transparency[] entry, once it has been logged.
"transparency": [
  {
    "log_id": "log.heso.ca",          // the log this receipt was included in
    "leaf_index": 128,                // the receipt's org-local position in the log
    "inclusion_proof": ["…", "…"],    // sibling hashes from the leaf up to the root
    "checkpoint": "log.heso.ca\n256\n…"  // signed log checkpoint over that root
  }
]

These values are everything an observer needs to rebuild the root from the leaf up and confirm the receipt is in the log. The leaf is the receipt’s action_hash and nothing more, so an entry reveals nothing about the action beyond what the receipt already published.

HESO’s log is two-stage — a per-org leaf tree under a single append-only epoch tree — so a full proof also carries optional stage-2 fields (org_root, org_tree_size, epoch, org_id, top_leaf_index, top_inclusion_proof). A verifier runs the second stage only when they are present; a single-tree proof omits them.

The two proofs

A Merkle tree boils any number of leaves down to one hash, the root. Change one leaf, reorder two, or drop one, and the root changes. The log publishes that root, and you can check two things against it.

  • Inclusion — this receipt is in the log at index N. You supply the leaf, its index, the tree size, the root, and the audit path; the verifier rebuilds the root and checks it matches.
  • Consistency — the log only ever grew, never rewrote history. Given a root you saved earlier and the current root, it proves the later tree is the earlier one with new leaves appended, nothing changed.

Both proofs grow only with the logarithm of the tree size, so they stay small even for a log with millions of receipts.

Inclusion: this receipt is in the log

An inclusion proof answers one question: is this leaf in the tree of this size that produced this root? You pull the leaf value, index, and proof straight from the receipt’s transparency[] entry.

leafValueHexstringrequired
The leaf value, lowercase hex — the receipt’s action_hash.
indexnumberrequired
The receipt's zero-based leaf_index, from the transparency[] entry.
sizenumberrequired
The tree size that the root commits to.
rootHexstringrequired
The published checkpoint root, lowercase hex.
proofHashesJsonstringrequired
A JSON array of the inclusion_proof sibling hashes from the leaf to the root.

The call returns a boolean. true means this exact leaf is in that exact tree at that index; anything else returns false rather than throwing.

inclusion.tsts
import init, { verifyInclusion } from "@hesohq/verify-wasm"

await init()

// Prove that leaf #128 is in the tree of size 256 that committed to this root.
const ok = verifyInclusion(
  "9f2c…e1c0",        // leafValueHex — the receipt's action_hash
  128,                 // index — the receipt's leaf_index
  256,                 // size — the tree size that root commits to
  "4ad1…77b9",        // rootHex — the root the checkpoint commits to
  proofHashesJson,     // JSON array of the inclusion_proof sibling hashes
)
// ok === true  →  this receipt is in that log at that index

Consistency: the log only grew

Inclusion alone does not stop a dishonest log from quietly rewriting an old leaf and publishing a fresh root. A consistency proof closes that gap. Given a root you saved earlier and the current root, it proves the new tree is the old one with new leaves added on the end — every old leaf still there, in the same order.

oldSizenumberrequired
The size of the earlier tree.
oldRootHexstringrequired
A checkpoint root you saw at oldSize, lowercase hex.
newSizenumberrequired
The size of the later tree.
newRootHexstringrequired
The current checkpoint root at newSize, lowercase hex.
proofHashesJsonstringrequired
A JSON array of the consistency-proof hashes (hex) linking the two roots.

It returns a boolean. true means the log only ever grew between the two roots. Save a root, come back later, and a consistency check against the new root confirms nothing you trusted was rewritten — without re-downloading the whole log.

consistency.tsts
import init, { verifyConsistency } from "@hesohq/verify-wasm"

await init()

// Prove the log only ever grew: nothing earlier was changed, reordered, or removed.
const ok = verifyConsistency(
  256,                 // oldSize
  "4ad1…77b9",        // oldRootHex — a checkpoint root you saw earlier
  512,                 // newSize
  "b30e…c104",        // newRootHex — the current checkpoint root
  proofHashesJson,     // JSON array of consistency-proof hashes (hex)
)
// ok === true  →  every leaf in the old tree is unchanged in the new one

Verifying proofs

Both proofs run wherever HESO verification runs, against the same Rust core, so you get the same result in the browser and on Node. In the browser, use @hesohq/verify-wasm: call init() once, then verifyInclusion and verifyConsistency synchronously. On the server, @hesohq/core exposes the same two checks as verifyInclusionJs and verifyConsistencyJs — no async init, identical arguments and results.

verify.node.tsts
import { verifyInclusionJs, verifyConsistencyJs } from "@hesohq/core"

const included = verifyInclusionJs("9f2c…e1c0", 128, 256, "4ad1…77b9", proofHashesJson)
const grewOnly = verifyConsistencyJs(256, "4ad1…77b9", 512, "b30e…c104", proofHashesJson)
The leaf is a hash, hashed twice

A receipt’s action_hash is a BLAKE3 digest over its canonical bytes — that is the leaf value the tree commits to. The Merkle tree then hashes leaves and nodes with SHA-256, per RFC-6962, to produce the leaf hash and the root. The two hash functions work at different layers and do not mix.

How it relates to the audit chain

The two guarantees cover what the other can’t, over the same receipts.

  • The audit chain is the source of truth: a local BLAKE3 link — chainHash = BLAKE3(prevChainHash ++ actionHash) — that ties your own receipt stream together and runs entirely offline. The holder of the stream checks it.
  • The transparency log is public, append-only evidence for the receipts that reach the cloud mirror. An outside observer who saved an earlier root can prove a receipt is in the log and that the log grew honestly from that point — without holding the whole stream.

Use both. The chain proves your stream is internally consistent and unedited; the transparency log lets a reviewer who pinned a root confirm a specific receipt was committed and that the log has only grown since.

The boundary — don't overclaim

Be precise about what the log does and doesn’t cover. Receipts that never reach the cloud are not in the log. There is a short window between when a receipt is accepted and the next checkpoint, during which it may not yet be provable. And while checkpoints are publicly anchored and tamper-evident, no independent third party co-signs them yet — so treat the log as publicly anchored and tamper-evident, not as independently witnessed.

Next steps