Open data

Evidence digests

Recompute it yourself. Every SHA-256 the published price rows cite as their evidence, grouped by UTC day with one Merkle root per day. The manifest carries digests only — no payload, so nothing a source restricts is redistributed. Below: how to recompute a digest from its retained raw file, and a day's root from the manifest.

The manifest

/evidence/digests.json lists each day’s distinct digests in leaf order, each with the evidence reference that pins it and the /price-history.json series whose row on that day cites it. A raw-payload digest is the SHA-256 of a retained source payload (a Vast.ai book retained from collector live-quotes-v6 on is hashed as its allowlist projection, an earlier one as the response received). An observation-set digest is the SHA-256 of the sorted IDs of the observations behind a daily class aggregate, joined with commas; those IDs live in the price database, so that digest is checked on request.

Each build checks the published datasets for digests:

Verify a digest

  1. Request the raw file. A raw-payload reference reads raw-snapshots@<commit>:<path>#source=<source>: an exact commit on the site’s private raw-snapshots branch, the gzipped bundle at that path, and the payload’s source. Raw payloads are archived privately and available on request: email data@aicomputetracker.com with the reference.
  2. Decompress the bundle and find the entry in its evidence list whose source is the reference’s decoded #source= value.
  3. Hash its rawTexts: a single text as its UTF-8 bytes; several in order, each as its UTF-8 byte length in decimal, a colon, then its bytes. The hex result is the digest the manifest lists.
# python3 verify_digest.py <bundle.json.gz> <the reference's #source= value>
import gzip, hashlib, json, sys, urllib.parse

bundle = json.loads(gzip.open(sys.argv[1]).read())
source = urllib.parse.unquote(sys.argv[2])
for item in bundle["evidence"]:
    if item["source"] != source:
        continue
    texts = item["rawTexts"]
    digest = hashlib.sha256()
    if len(texts) == 1:
        digest.update(texts[0].encode("utf-8"))
    else:
        for text in texts:
            body = text.encode("utf-8")
            digest.update(f"{len(body)}:".encode("ascii") + body)
    print(item.get("gpu", "-"), digest.hexdigest())

Recompute a day’s root

A day’s root is the RFC 6962 Merkle Tree Hash of its digests. The day's distinct digests, each as its 32 raw bytes, in ascending order of their lowercase hex; each leaf hash is SHA-256(0x00 || digest). Each interior node is SHA-256(0x01 || left || right). A list of one leaf hashes to its leaf hash; a list of n > 1 leaves splits after the first k, where k is the largest power of two smaller than n, and the root is the node over the two halves' roots.

# python3 verify_root.py digests.json <YYYY-MM-DD>
import hashlib, json, sys

def tree_hash(leaves):
    if len(leaves) == 1:
        return hashlib.sha256(b"\x00" + leaves[0]).digest()
    split = 1
    while split * 2 < len(leaves):
        split *= 2
    return hashlib.sha256(
        b"\x01" + tree_hash(leaves[:split]) + tree_hash(leaves[split:])
    ).digest()

day = next(d for d in json.load(open(sys.argv[1]))["days"] if d["date"] == sys.argv[2])
leaves = sorted({entry["sha256"] for entry in day["digests"]})
print(tree_hash([bytes.fromhex(d) for d in leaves]).hex() == day["root"])

Test vector

The SHA-256 digests of “a”, “b” and “c”, in leaf order:

  • 2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6
  • 3e23e8160039594a33894f6564e1b1348bbd7a0088d42c4acb73eeaed59c009d
  • ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb

give the root dd67a4e94fcb4fff954bcb093257364a5b5d0832bda9ffb7a5b6340e45ca647b.

Roots by day

The 30 newest of 81 days; the manifest lists every day. A day is open until the ledger has been exported 2 days after it: its root may still change, and only a closed day is timestamped. A day shows a timestamp only once a proof for its current root is committed; a later change to a day’s digests makes a new root, which needs its own proof.

Each of the 30 newest days’ digest count, Merkle root and timestamp status
Day (UTC)DigestsRootTimestamp
open — root may change1266a8a277d7edaf31fa7e519e96f6d55827f10b5dbe24c06c15851519c30e0454Not yet timestamped
open — root may change114ddc6f15c9de3dfe70b7de4d5b3a50442c5ef574df65934332c31669ea576a6dNot yet timestamped
137e630bd4cd126c99026628decde0cd4c9c9633ab13dfa358e37cbe516898db09Not yet timestamped
23e23765e3daebaa34a33e402198afba188dd2fbef40b593d8d7d5995aac9f6ec1Not yet timestamped
18ad93a77c052b56d742a6ec1d0af74fde2ad474c21e7f0fa2aac3515f607c8b35Not yet timestamped
230ca0ed049793b2ed21e3c989b8f2ef5bd78c653a62dffa2b7a80c0ca1ed459a8Not yet timestamped
23d8679eb205e2549a01f8a0f9a37b68ed208b3d68f2a9225a7830a80a0e22bc7cNot yet timestamped
23914d93716455ec2d17a738f3a19b4ad4019a61a043a705d4720d6d3b1e23ecbeNot yet timestamped
21953563e337c884f4a8f0456eb6c628633e3b73571faeb94d174f8e2c538f9e69Not yet timestamped
2186b64dcfdcbdb33bdda635a1573a151dbc13545e2b7984f15f54c515c55400d9Not yet timestamped
213456688a72547efe05fb39ddca7723a9f21aeb646f0ca35fe42b771711104161Not yet timestamped
212b1b1aab459fb05e6ba75749e2778782e91e10125d078accd42d544f43db941eNot yet timestamped
23e1f65f74d4360b617c42551513324c93fa86bd8db8622d74c1efc1ad26e3c2ccNot yet timestamped
21546775ba05a6f9a12797cfb7a43606f798f81e365075da04ac1484c3df222d0cNot yet timestamped
21a989ab010a0e6b1890757e963f98807f789205e5d783005285a2562ee5d2e41eNot yet timestamped
23a99f6ba1b069870058dfca849d729d18601c5bc03fed5b74f935b5251b0d8184Not yet timestamped
2178e76252302542fcb38c83a84151dce5190c18fad9c45b2355f2072318e50b47Not yet timestamped
216197b6bcc107e0e3ea5325d2a30bd1f797ab2ccff2378fb8f9b588388c080555Not yet timestamped
219d7614bab61899ba9eb1450ed0199b545c8987b52c52cfaa9357e68ff69dfa93Not yet timestamped
237dd079580abf97aec5358a675e75144a64d76d706f9c548f1436fa03026c81abNot yet timestamped
2343100ad885a03364d8326332bd842c8d6a4ce85ce9d88b841dc901898859bb62Not yet timestamped
19cdabfcf97528ea3042b498df87a8e2370cfbba55c9617c1d0d7d10738129bbddNot yet timestamped
235d4cfc7fc8aec3370dbe8981a63435e6251ba5ee264cc2eab87bc47b3187e6f0Not yet timestamped
19feed6f4bb72fdbbba80d7e30d3afc6d4745cd2dcd797e2a85788d91e32af600dNot yet timestamped
21879b8ed888139f103297fe3492bac7579b6cabdb44b0213412a4be24c8dc7442Not yet timestamped
2111de365acee4096171b188d2840053af88d080423737b908890db42fad8a873bNot yet timestamped
218f48856159600a45b7f8e7a447bd58408ded4fadf2a145b8cda238f26de8a300Not yet timestamped
219dcb13c09ce4d0a2646d5e24261631c5e2becc863bd5dc8bc4f12b0999c65064Not yet timestamped
210438fb7ff32e4a21580ec7df7321b7fb2a71df13ac371c697ee8506fdc86c5b1Not yet timestamped
21bfcb3398a53d7b3797488d15603b031c6e0cdfcfc1683d2d26be001730bf5103Not yet timestamped