Verification
Verify the feed yourself. Every entry is signed, every entry names the one before it, and every page carries a freshly signed statement of the newest entry. The rules are exact enough to write your own verifier, and three reference verifiers pass all 125 published test vectors.
A signed entry
record_hash: SHA-256 of the entry's canonical text, lowercase hex.signature: Ed25519 signature over the 64-character hash string, hex.public_key_id: the key in public-key.json that made it. Retired keys stay in the file so old entries keep verifying.prev_seqandprev_hash: theseqandrecord_hashof the entry before.feed_head, on every page:{"type":"feed_head","seq":N,"record_hash":"...","signed_at":"..."}, signed at the time of the request.
The checks
| Check | What it establishes |
|---|---|
| Hash and signature of each entry | The text is exactly what was signed, by a key in the key file. |
seq and chain | Entries are in order and each names the one before it, so nothing was changed, inserted or removed in the middle. |
| Publications | Revisions follow one another: revision rises by one and supersedes_seq names the previous entry. |
| Status entries | Standing changes are well formed, and the set of revoked entries is derived from them. |
| Signed head | The head is genuine and agrees with the entries; if the last page ends before the head's seq, entries were dropped from the end. |
| Key revocation | Entries signed by a revoked key are trusted only up to the last trusted entry named in a signed notice. |
Section 4 of the rules states exactly what a clean result does and does not establish, including how to compare heads across AI Platforms.
The kit
Everything is at https://feed.aigistry.com/verify/, with a SHA-256 checksum for every file.
- VERIFICATION.md: the rules, version 1.2.
- verify_feed.py (Python, needs
cryptography), verify-record.php.txt (PHP, needs sodium; save asverify-record.php) and verify-feed.mjs (Node 18 or later). - vectors/: 125 test vectors, one JSON file each, with expected results: valid feeds, tampering, omissions, truncation, status entries, revoked keys. A verifier you write should pass every one.
Run it
python3 verify_feed.py --keys public-key.json page1.json page2.json
php verify-record.php --keys public-key.json page1.json page2.json
node verify-feed.mjs --keys public-key.json page1.json page2.json
python3 verify_feed.py --vectors vectors # the folder of vector files
Options: --prev-seq N --prev-hash HEX (anchor: the last entry you verified last time), --filtered, --json. Exit status: 0 nothing wrong was found, 1 problems were found, 2 the input could not be read. Pass pages in order. Keep reading while a page's has_more is true, using next_after as the next after.
A clean result
- Every entry was signed by a key in the public key file, and its text is exactly what was signed.
- The entries are in order and each names the one before it.
- If the last page carried a signed head, nothing was cut from the end.
- The verifier also works out which publications are revoked from the signed status entries, so you can compare it with the lookup route.
Filtered reads and key revocation
A read filtered to one authority record, GovTech Provider or government returns the matching entries unchanged and in order. Tell the verifier the read was filtered (--filtered); it never takes that from the unsigned filter member. A filtered read authenticates every entry it returns; read the whole feed without a filter when completeness matters. If a signing key is ever revoked, public-key.json carries a signed notice with the last trusted entry, and the verifiers apply it.
Entry text is authenticated, not safe: title and body are written by GovTech Providers, so treat them as untrusted data.
