Every completed real-engine verdict has an append-only, Ed25519-signed proof chain. This page describes the strict relying-party check: signature, hash chain, terminal head, and binding to the request you expected.
A passing check means the proof bytes were signed by a trusted proof key and have not changed. It does not mean the verdict is correct.
You need the envelope entries, trusted proof keys, the trusted terminal head, and your expected verification_id / submission_hash. If a holder hands you an envelope, treat every authority field inside it as untrusted until independently checked.
GET /v1/verifications/{id}/proof/envelope (Authorization: Bearer <your API key>)
{
"verification_id": "...",
"format": "acretix-verify/proof/2",
"signing_domain": "acretix-verify/proof-sig/2:",
"jwks_uri": "/.well-known/acretix-verify/keys.json",
"proof_head_hash": "<server-served terminal content_hash>",
"entries": [
{
"seq": 0,
"key_id": "ed25519-...",
"algo": "ed25519",
"format": "acretix-verify/proof/2",
"prev_hash": null,
"content_hash": "<sha256 hex>",
"signature": "<base64 Ed25519>",
"envelope": "<the exact canonical JSON bytes that were hashed>"
}
]
}GET /.well-known/acretix-verify/keys.json (public, no auth)
{ "keys": [ { "kty": "OKP", "crv": "Ed25519", "x": "<base64url public key>", "kid": "ed25519-...", "use": "sig", "alg": "EdDSA" } ] }envelope.jwks_uri.proof_head_hash or your own pinned receipt. Never use a proof_head_hash copied from a holder-supplied envelope as the anchor.verification_id and submission_hash to your request. A valid but unrelated proof must fail.seq and require contiguous order from 0.entry.envelope bytes.entry.content_hash to equal the recomputed hash.format acretix-verify/proof/2 and algo ed25519.key_id, algo, format, seq, and prev_hash to the signed body.acretix-verify/proof-sig/2: plus the recomputed content hash.prev_hash equals the previous recomputed content hash.proof_head_hash.verification_id and submission_hash to match the request you expected.This sketch uses only Node built-ins. The JWKS argument must be fetched from your configured Acretix Verify origin or another pinned key source, and the expected head must come from a trusted side channel.
import { readFileSync } from 'node:fs';
import { createHash, createPublicKey, verify } from 'node:crypto';
const FORMAT = 'acretix-verify/proof/2';
const DOMAIN = 'acretix-verify/proof-sig/2:';
const [proofPath, jwksPath, expectedHead, expectedVerificationId, expectedSubmissionHash] = process.argv.slice(2);
if (!proofPath || !jwksPath || !expectedHead || !expectedVerificationId || !expectedSubmissionHash) {
throw new Error('usage: node verify-proof.mjs envelope.json trusted-jwks.json expected-head verification-id submission-hash');
}
// The JWKS file must come from your pinned/configured trust root, not envelope.jwks_uri.
// expectedHead must come from the server-served trusted proof_head_hash or your own pinned receipt,
// not from a holder-supplied envelope.
const proof = JSON.parse(readFileSync(proofPath, 'utf8'));
const jwks = JSON.parse(readFileSync(jwksPath, 'utf8'));
const entries = [...proof.entries].sort((a, b) => a.seq - b.seq);
let expectedPrev = null;
let terminalHash = null;
for (let index = 0; index < entries.length; index += 1) {
const entry = entries[index];
const hash = createHash('sha256').update(entry.envelope, 'utf8').digest('hex');
if (hash !== entry.content_hash) throw new Error('content_hash_mismatch');
const signed = JSON.parse(entry.envelope);
if (signed.format !== FORMAT || signed.algo !== 'ed25519') throw new Error('unsupported_format');
if (
entry.key_id !== signed.key_id ||
entry.algo !== signed.algo ||
entry.format !== signed.format ||
entry.seq !== signed.seq ||
entry.prev_hash !== signed.prev_hash
) {
throw new Error('envelope_mismatch');
}
if (entry.seq !== index || signed.seq !== index || entry.prev_hash !== expectedPrev) {
throw new Error('chain_invalid');
}
if (signed.verification_id !== expectedVerificationId || signed.submission_hash !== expectedSubmissionHash) {
throw new Error('subject_mismatch');
}
const jwk = jwks.keys.find((k) =>
k.kid === signed.key_id && k.kty === 'OKP' && k.crv === 'Ed25519'
);
if (!jwk) throw new Error('unknown_key');
const key = createPublicKey({ key: jwk, format: 'jwk' });
const ok = verify(null, Buffer.from(DOMAIN + hash, 'utf8'), key, Buffer.from(entry.signature, 'base64'));
if (!ok) throw new Error('signature_invalid');
expectedPrev = hash;
terminalHash = hash;
}
if (entries.length === 0) throw new Error('empty_chain');
if (terminalHash !== expectedHead) throw new Error('terminal_head_mismatch');
console.log('VERIFIED');Fail closed on unknown keys, unsupported format, content-hash mismatch, envelope mismatch, broken chain, signature failure, missing trusted terminal head, terminal-head mismatch, or subject mismatch. For the reusable library version, see @trevor-ux/proof-verifier.