> ## Documentation Index
> Fetch the complete documentation index at: https://docs.parmanasystems.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Verify a Trust Record Independently

> Take a trust record someone else gave you and verify its hash and signature with only the public key, no Parmana server, no database, no network call.

<Info>**\[AVAILABLE]**, demonstrated in this session with the Parmana server confirmed stopped throughout the verify step, commit `651497a`.</Info>

## 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](/quickstart), or reuse
  any `ExecutionTrustRecord` you already have on disk.
* Read [Execution trust records](/concepts/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

```bash theme={null}
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:

```bash theme={null}
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:

```typescript theme={null}
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](/concepts/execution-trust-records).

## 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

<CardGroup cols={2}>
  <Card title="Detect tampering" icon="bug" href="/guides/detect-tampering">
    The same tamper-then-verify pattern, applied to the authorization envelope instead.
  </Card>

  <Card title="Execution trust records" icon="file-shield" href="/concepts/execution-trust-records">
    Exactly what's inside the hash this guide recomputed.
  </Card>
</CardGroup>
