The audit chain links your own receipts together, so tampering with an earlier one breaks every chain hash after it. That is enough for an operator who holds the whole stream. A transparency log adds a second, separate guarantee: someone who holds only a published root — the single hash at the top of the tree — can check two things. That a specific receipt is in the log, and that the log has only ever been added to.
HESO follows the same design as Certificate Transparency. The log is an RFC-6962 Merkle tree built with SHA-256, where each leaf is a receipt’s action_hash. The SDK checks the two proofs the tree can produce — inclusion and consistency — and nothing more.
A transparency proof shows that a receipt is in a tree of a given size and that the tree grew without rewriting history. 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. Inclusion and consistency answer “is this in the log, and was the log honest?”, not “is this receipt valid?”.
Append-only logs
A Merkle tree boils any number of leaves down to one hash, the root. Each leaf is hashed, neighboring hashes are paired and hashed again up the tree with SHA-256, and the single value at the top stands for every leaf below it and their order. Change one leaf, reorder two, or drop one, and the root changes.
An append-only log publishes that root as it grows. The root is small, so an observer can save it and re-check it later. Two things make the structure useful:
- Inclusion — given a root and a tree size, prove that a particular leaf sits at a particular index, using only a short path of sibling hashes.
- Consistency — given an earlier root and a later root, prove that the later tree contains the earlier one unchanged, with new leaves only appended after it.
Both proofs grow only with the logarithm of the tree size, so they stay small even for a log with millions of receipts. The leaf values are HESO action hashes, so the log holds no readable data — a transparency proof reveals nothing about the action beyond the hash you already published in the receipt.
Inclusion proofs
An inclusion proof answers one question: is this action_hash a leaf in the tree of this size that produced this root? You supply the leaf value, its index, the tree size, the root, and the sibling hashes that connect the leaf to the root. The verifier rebuilds the root from the leaf up; if the rebuilt root matches, the leaf is in the log.
- leafValueHexstringrequired
- The leaf value, lowercase hex — a receipt’s action_hash.
- indexnumberrequired
- The leaf's zero-based position in the tree.
- sizenumberrequired
- The tree size that the root commits to.
- rootHexstringrequired
- The published Merkle root, lowercase hex.
- proofHashesJsonstringrequired
- A JSON array of the sibling hashes (hex) on the path from the leaf to the root.
The call returns a boolean. A true result means this exact leaf is in that exact tree at that index; anything else returns false rather than throwing.
import init, { verifyInclusion } from "@hesohq/verify-wasm" await init() // Prove that leaf #128 (a receipt's action_hash) is in the// tree of size 256 that committed to this root.const ok = verifyInclusion( "9f2c…e1c0", // leafValueHex — the action_hash 128, // index — the leaf's position 256, // size — the tree size that root commits to "4ad1…77b9", // rootHex — the published Merkle root proofHashesJson, // JSON array of sibling hashes (hex))// ok === true → this receipt is in that log at that size
Consistency proofs
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 leaf in the old tree is still there, in the same order, and the only change is leaves added after it.
- oldSizenumberrequired
- The size of the earlier tree.
- oldRootHexstringrequired
- A root you saw at oldSize, lowercase hex.
- newSizenumberrequired
- The size of the later tree.
- newRootHexstringrequired
- The current root at newSize, lowercase hex.
- proofHashesJsonstringrequired
- A JSON array of the consistency-proof hashes (hex) linking the two roots.
It returns a boolean. A true result means the log only ever grew between the two roots. Save a root, come back later, and a consistency check against the new root tells you nothing you trusted has been rewritten — without re-downloading the whole log.
import init, { verifyConsistency } from "@hesohq/verify-wasm" await init() // Prove the new log is an append-only superset of the old one:// nothing earlier was changed, reordered, or removed.const ok = verifyConsistency( 256, // oldSize "4ad1…77b9", // oldRootHex — a root you saw earlier 512, // newSize "b30e…c104", // newRootHex — the current 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. This is the same path the HESO web console uses to check the log it shows you.
On the server, @hesohq/core exposes the same two checks as the verifyInclusionJs and verifyConsistencyJs variants — no async init step, identical arguments and results.
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 value you pass as leafValueHex is the receipt’s action_hash — the BLAKE3 hash over its canonical bytes that the receipt already carries. The Merkle tree itself hashes leaves and nodes with SHA-256, per RFC-6962. The two hash functions work at different layers and do not mix.
How it relates to the audit chain
HESO gives you two integrity guarantees over the same receipts, each covering what the other can’t:
- The audit chain is a per-receipt BLAKE3 link — chainHash = BLAKE3(prevChainHash ++ actionHash) — that ties your own receipt stream together. Altering any earlier receipt breaks every chain hash after it. The holder of the stream is the one who checks it.
- The transparency log is an RFC-6962 Merkle tree whose leaves are those same action hashes. It adds third-party-checkable inclusion and consistency proofs against a small published root — something an outside observer can check without holding the whole log.
The chain proves your stream is internally consistent; the transparency log proves to anyone else that a receipt is in a log that has only grown. Use both, and a single recorded root lets a reviewer confirm a specific action was committed and that the operator never quietly rewrote the past.
