Skip to main content
[AVAILABLE], demonstrated in this session with the Parmana server confirmed stopped throughout the verify step, commit 651497a.

Goal

Prove the independent-verification claim literally: take a trust record as plain JSON, verify it with nothing but Parmana’s public key, while Parmana itself is not running.

Prerequisites

  • Parmana’s public key file, keys/default.public.pem (already committed for local dev). You do not need the private key, default.private.pem, for any step in this guide.
  • A trust record to verify, saved as JSON. Get one from Quickstart, or reuse any ExecutionTrustRecord you already have on disk.
  • Read Execution trust records first, this guide verifies exactly the hash and signature that page describes.

Steps

1. Get a trust record and save it, then stop the server

curl -s -X POST http://localhost:3000/execute -H "Content-Type: application/json" -d '{ ... }' \
  > trust-record.json
Now stop the server completely. Everything after this point runs with no Parmana process alive:
curl -m 3 http://localhost:3000/health
# curl: (7) Failed to connect to localhost port 3000

2. Verify it with VerificationCrypto, offline

VerificationCrypto (@parmana/crypto) takes a plain ExecutionTrustRecord object and a FileKeyProvider reading only local PEM files, no network call, no database, no running Runtime:
import { readFileSync } from "node:fs";
import { VerificationCrypto } from "@parmana/crypto";

const trustRecord = JSON.parse(readFileSync("trust-record.json", "utf8"));
const crypto = new VerificationCrypto();

const recomputedHash = await crypto.hash(trustRecord);
const hashMatches = recomputedHash === trustRecord.trustRecordHash;
const signatureValid = await crypto.verifySignature(trustRecord);
const fullyVerified = await crypto.verify(trustRecord);
FileKeyProvider.getPublicKey() only checks for <keyId>.public.pem (packages/crypto/src/providers/key/FileKeyProvider.ts:118-133), it never reads or checks for the private key. Verifying a record you received from someone else never needs, and never has access to, the key that signed it.

Verify

Real output, this session, server confirmed stopped (see step 1):
Recomputed hash:   4d695da8e1d39d45599fdc0d91561ba680891a9d199d410db7d4d02a8b9ea66d
Stored hash:       4d695da8e1d39d45599fdc0d91561ba680891a9d199d410db7d4d02a8b9ea66d
Hash matches:      true
Signature valid:   true
Fully verified:    true
Now the negative case, proving this isn’t just always returning true. Change one nested field, executions[0].evidence.parameters.amount, in the saved JSON, and rerun the exact same script against the tampered file:
Recomputed hash:   6ea2874ad4a6db6312d5c2a531edac17b789976af80d07ed01081f2be38cfab5
Stored hash:       4d695da8e1d39d45599fdc0d91561ba680891a9d199d410db7d4d02a8b9ea66d
Hash matches:      false
Signature valid:   false
Fully verified:    false
The hash comparison alone would have caught this. verifySignature() also fails because it verifies the signature against the current (now-mutated) content, not a cached original, the mutation invalidates both checks independently, matching what the hash and signature actually cover.

Troubleshoot

  • Public key not found: default. VerificationCrypto resolves keys via PARMANA_KEY_DIR (or ./keys if unset), confirm keys/default.public.pem exists at that path relative to where you run the script.
  • Verification fails on a record you’re sure is untampered. Confirm you copied the JSON exactly as returned, re-serializing through a tool that reorders keys or reformats timestamps can change byte-level content even though the JSON is semantically identical, canonical serialization is order-sensitive by design.
  • You need this in Python, not TypeScript. The Python SDK’s client.verification.verify() calls POST /verify over HTTP, it requires a running server, there is currently no offline/local verification path in the Python SDK, only in @parmana/crypto.

Next

Detect tampering

The same tamper-then-verify pattern, applied to the authorization envelope instead.

Execution trust records

Exactly what’s inside the hash this guide recomputed.