Titandocs

Verify a cycle

Fetch your records for any cycle, recompute the Merkle root with SHA-256, and check it against the root published on the Canton ledger.

This page gives the exact rules, so anyone can check Titan with a few lines of code and no trust in the exchange. If you only want the idea, read Commitments first.

What you need

  • Your records: GET /v1/me/batches/{batchNum}/proof, signed with your session. It only ever answers about your own account.
  • The published root: read the BatchManifest for that batch straight from the Canton ledger through the public observer party, which can see manifests and nothing else. The same data is also served at GET /v1/manifests/{batchNum} for convenience; the ledger is the source you do not have to trust.
  • SHA-256. Nothing else.

The hashing rules

leafHash(bytes)       = SHA-256( 0x00 ‖ bytes )
nodeHash(left, right) = SHA-256( 0x01 ‖ left ‖ right )
  • A level with an odd number of nodes duplicates its last node.
  • A tree with one leaf has that leaf's hash as its root.
  • An empty tree has a root of 32 zero bytes.
  • The users tree is built over payloads sorted ascending by leaf hash, so your position in it says nothing about you.
  • Your event tree keeps your leaves in the order they happened.

All hashes are shown as lowercase hex.

The four checks

The proof response contains your payload, its leafHash, your event leaves, your state, the manifest and a sibling path. The hex fields are hex strings: decode them to bytes before hashing.

The payload is yours

leafHash(payload.hex) must equal leafHash.

The payload was published

Start from leafHash and walk path from the bottom up. Each entry is { hash, side }:

side = "left"   →  acc = nodeHash(hash, acc)
side = "right"  →  acc = nodeHash(acc, hash)

The final acc must equal the usersRoot in the manifest you read from the ledger. This is the step that ties your data to what the exchange published. An empty path means you were the only active user that cycle, and leafHash is itself the users root. If path is empty and verification.errors is not, no proof could be built. There is nothing to fold.

Your events are the ones committed

Build the Merkle root of leaves in the order given. It must equal the event root inside payload.hex.

Your account state is the one committed

leafHash(state.hex) must equal the state root inside payload.hex.

The response also carries parsed fields and the exchange's own verification.status, for convenience. Read the roots from the hex bytes rather than the parsed fields, and do not rely on the status. The point is that you do the arithmetic. Where the roots sit inside payload.hex is in Byte layouts.

If something does not match

  • 503 proof_not_ready names its reason. manifest_pending and users_incomplete clear with time; retry a few times, then report it. You will never be handed a partial proof.
  • 404 means no batch with that number includes you.
  • A real mismatch: keep the full response and the manifest you read. Together with the orders you signed, that is proof the records and the published root disagree. Contact the Titan team and share it; the auditor can check the same root independently.

The rules above are complete: any SHA-256 library is enough to run every check.

On this page