Verify Predge signed records offline: no call to the issuer, no account, no runtime dependencies.
$ npx predge-verify record.json --keys predge-keys.json
AUTHENTIC kid=3b6551a6439b9bda 10/10 checks passed
Origin: built during the Amazon Developer Hackathon as the verifier used by predge-alexa-mcp.
A signed record is only worth something if you can check it without trusting the party that sent it. If the check is "ask the API whether this is valid", you have moved the trust, not removed it: the issuer can be down, hacked, or lying, and every check leaks what you are looking at. With an ed25519 signature and a published key set, the check is a few lines of local math. You can run it in a voice assistant skill, in a CI job, in an air-gapped audit, or years later when the API is gone.
This matters most for the case that actually happens: a key leaks. Predge publishes a key set at https://api.predge.io/.well-known/predge-keys.json. A key marked revoked must fail regardless of the record's timestamp, because whoever holds a leaked key can backdate. predge-verify enforces that: a record under a revoked key returns key_revoked, even if its signature is mathematically valid. Save the key set, and refresh it when you can.
git clone https://github.com/predgeAI/predge-verify && cd predge-verify && npm install && npm test
node bin/predge-verify.js <file.json|url>
(Not published to npm. npx github:predgeAI/predge-verify <file|url> also works; it builds on install.) Node 18+; the browser build needs a browser with WebCrypto Ed25519.
predge-verify <file.json|url> [--keys <file>] [--keys-url <url>] [--body-signature "<header>"] [--json]
- A file may be a full API response, a bare
attestationenvelope, or an evidence pack. --keys <file>: a savedpredge-keys.json. With it nothing touches the network (for a file input). Without it the key set is fetched fromapi.predge.io.- For a URL, the raw body and the
x-predge-body-signature/x-predge-body-sha256headers are checked automatically. For a saved file, save the body unchanged (curl -sD h.txt -o record.json URL) and pass--body-signature. - Exit code:
0authentic,1not authentic,2usage error.
import { verifyRecord } from "predge-verify"; // node:crypto
// import { verifyRecord } from "predge-verify/browser"; // WebCrypto
const result = await verifyRecord(record, {
keys, // parsed predge-keys.json (or its keys[] array), OR
keysUrl, // where to fetch it (default: api.predge.io/.well-known/predge-keys.json)
body, // optional Uint8Array: raw HTTP body, for the detached body signature
bodySignature, // optional: value of x-predge-body-signature
bodySha256, // optional: value of x-predge-body-sha256
});
// { status, kid, checks: [{ name, ok, detail? }] }status is one of:
| status | meaning |
|---|---|
authentic |
every check passed under a listed, non-revoked key |
key_revoked |
the signing key is listed as revoked (checked first, timestamp ignored) |
invalid_signature |
the payload, hash or a signature does not verify (tampering) |
unknown_key |
the kid is not in the key set (or the key set could not be loaded) |
malformed |
not a predge-attest-v1 envelope |
Checks, in order: key set and key status (active/retired pass, revoked fails); the envelope key matches the key set; C(payload) == canonical (canonical JSON, a subset of RFC 8785); sha256(canonical) == content_hash; ed25519 signature over canonical; envelope_signature over the whole envelope when present (so kind/data_basis cannot be swapped); and, if you pass the body, sha256(body) and the detached signature over "predge-body-v1\n" + raw body. Retired keys still verify older records.
canonicalJson(value) and verifyWith(backend, ...) are exported too, so you can plug in another crypto backend.
What this does not check: that the facts in the payload are true. Settlement-risk records name chain facts (block, tx hashes) you can re-check on a Polygon RPC; that is outside this library. It proves who signed what, and that nothing changed since.
Format spec: https://data.predge.io/canonicalization.md
npm test builds and runs node:test suites against a live record fetched from the API (test/fixtures/), a tampered copy, a revoked key (the published 13fa3d18a369e6c7 entry and a generated keypair), malformed input, the body signature and the CLI.
Apache-2.0