Prove on Traza Cloud
The engine’s ledger guide defines the hash chain and evidence packs, and is explicit about what they prove and what they do not. Traza Cloud adds a notary: a signer outside the store, whose signatures you can check without trusting us.
Evidence packs
Any read key can fetch your project’s pack. since and until are Unix
nanoseconds; they default to the epoch and now.
curl -s "https://api.trazadb.com/v1/evidence?since=1790812800000000000" \
-H "Authorization: Bearer $TRAZA_API_KEY" -o pack.json
The pack covers your project’s tenant only: its activity, retention, usage and
erasures, the chain entries in the window and the chain’s verification. Other
tenants’ erasure entries on the same cell appear as stubs that keep the chain
followable but say nothing about whose they were. A window too large to fold
within the engine’s query budget answers 400; narrow it.
Check a pack’s digest
The digest is SHA-256 over the canonical JSON of every other field. Decimal numbers must be kept exactly as written, which this script, from the engine’s guide, does with the Python standard library:
import hashlib, json, sys
class Raw(str): pass # a non-integer number, kept exactly as the server wrote it
def canon(v):
if isinstance(v, dict):
return "{" + ",".join(json.dumps(k, ensure_ascii=False) + ":" + canon(v[k]) for k in sorted(v)) + "}"
if isinstance(v, list):
return "[" + ",".join(canon(x) for x in v) + "]"
return str(v) if isinstance(v, Raw) else json.dumps(v, ensure_ascii=False)
pack = json.load(open(sys.argv[1], encoding="utf-8"), parse_float=Raw)
claimed = pack.pop("digest")
ok = hashlib.sha256(canon(pack).encode()).hexdigest() == claimed
print("digest ok" if ok else "digest MISMATCH")
sys.exit(0 if ok else 2)
$ python3 check_pack.py pack.json
digest ok
Do not round-trip a pack through a parser that reads numbers as doubles, such
as JavaScript’s JSON.parse: timestamps are above 253 and the
recomputed digest will not match.
The notary
The notary signs with Ed25519. Its keys, current and retired, are published:
curl -s https://app.trazadb.com/.well-known/traza-notary.json
{"authority": "notary.trazadb.com", "algorithm": "ed25519",
"keys": [{"id": "ntk_…", "public_key_hex": "…", "created_at": …, "retired_at": null}],
"log": "https://app.trazadb.com/notary/log"}
It does two things:
- Anchors. Every hour, for each cell whose ledger head has moved, it signs a
canonical statement of the head (
authority,cell,store_id,seq,head,anchored_at), records the anchor back into that cell’s ledger and appends the statement to its public transparency log. Anchor entries appear whole in every evidence pack from that cell, on every plan. - Attestations (Team and above). From a project’s Evidence tab, choose a window. The console fetches the engine’s pack, re-verifies its digest over the exact bytes, and has the notary sign a statement naming your org, project, tenant, window, the pack’s digest and the ledger head. Download gives a bundle with the statement, the signature, the key and the pack, byte for byte.
Verify an attestation
You need jq, xxd, Python 3 and OpenSSL 3 (openssl pkeyutl -rawin is not
in LibreSSL or OpenSSL 1.1). From a downloaded bundle.json:
# The statement exactly as signed (-j: no trailing newline), and the signature.
jq -j .attestation.statement_json bundle.json > statement.json
jq -r .attestation.signature_hex bundle.json | xxd -r -p > sig.bin
# The raw Ed25519 key as a PEM public key: the fixed SPKI prefix, then the key.
{ printf '302a300506032b6570032100'; jq -r .attestation.public_key_hex bundle.json; } | xxd -r -p > key.der
openssl pkey -pubin -inform DER -in key.der -out notary.pem
openssl pkeyutl -verify -pubin -inkey notary.pem -rawin -in statement.json -sigfile sig.bin
Signature Verified Successfully
Then tie the signature to the notary and to your pack:
- The bundle’s
key_idandpublic_key_hexmust match a key published at/.well-known/traza-notary.json(pin it the first time you see it). jq -r .pack_digest statement.jsonmust equaljq -r .pack.digest bundle.json.- The pack’s own digest must recompute. Run the script above with
json.load(...)["pack"]in place of the pack file; it keeps decimals exact either way.
Verify an anchor
An anchor entry in your pack carries target_seq, target_head, authority,
anchored_at_ns, signature and public_key. To check it:
-
Confirm
target_headis the head the chain has attarget_seq(the engine’s ledger script recomputes every head). -
Find the transparency log entry with the same
store_idandledger_seq:curl -s "https://app.trazadb.com/notary/log?limit=1000" | jq '.entries[] | select(.ledger_seq == 4)' -
Verify its
signature_hexover itsstatement_jsonwith the recipe above, and check the statement’sheadis yourtarget_head.
Proof links
On Pro and up, any run can become a proof link: in the console, open the run
and create one. The link is a read-only certificate page at
app.trazadb.com/p/<token> with the run’s tape summary, the ledger head when
the link was created, and the notary’s Ed25519 signature over the run, its
tape digest, that ledger head and the time of issue. The page shows how to
verify the signature with openssl against the published notary key, so the
person you send it to needs neither an account nor trust in the page. Links
expire, and can be revoked at any time.
The transparency log
The log is append-only and hash-chained, like the engine’s ledger:
hash = sha256("traza-notary/1\n" + prev_hash + "\n" + statement_json)
Entry 0’s prev_hash is 64 zeros. Entries come oldest first; page through
them with after (the last seq you read) and limit (up to 1,000). It carries store ids, ledger heads
and signatures, and no tenant data.
import hashlib, json, sys, urllib.request
base, after, prev, ok = sys.argv[1], None, "0" * 64, True
while True:
url = f"{base}/notary/log?limit=1000" + ("" if after is None else f"&after={after}")
page = json.load(urllib.request.urlopen(url, timeout=30))
for e in page["entries"]:
want = hashlib.sha256(f"traza-notary/1\n{prev}\n{e['statement_json']}".encode()).hexdigest()
good = e["prev_hash"] == prev and e["hash"] == want
ok &= good
print(e["seq"], e["hash"], "ok" if good else "BROKEN")
prev, after = e["hash"], e["seq"]
if not page["entries"]:
break
sys.exit(0 if ok else 2)
python3 check_notary_log.py https://app.trazadb.com