Metadata-Version: 2.4
Name: parmana-verifier
Version: 1.6.3
Summary: Offline verifier for Parmana Execution Trust Records (Python).
License: Apache-2.0
Project-URL: Repository, https://github.com/pavancharak/parmana-verifier
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: cryptography>=42
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"

# parmana-verifier (Python)

Every action stays within what your business allowed, and Parmana keeps proof of what happened. This package checks that proof using only a record and a trust file. No network access, no account, no Parmana server needed.

This is the Python counterpart of the Node verifier in the repository root. It checks the same things in the same order, and it returns the same result shape and exit codes. Its results match the Node verifier on every test vector and on records generated by each implementation.

## What it checks, and what it does not

**Checked**

- The record hash (`trustRecordHash`) against the canonical record fields.
- The Ed25519 signature, over the legacy message, from a key in your trust file.
- Ed25519 signatures in a hybrid `signatures` array, and the whole-record (schema version 3) message.
- Trust rules: the key's fingerprint, its validity window, and the `compromised` flag.
- The `hybridRequiredFrom` cutover, when the trust file sets one. A record with no hybrid envelope is refused unless it predates the cutover, or the trust file sets `"allowLegacyOnly": true` (and no cutover is set).

**Not checked: ML-DSA-65 (`dilithium3`) signatures.** The `cryptography` package does not provide ML-DSA. A record with a `dilithium3` entry is reported as invalid, so this verifier never accepts a hybrid record on its Ed25519 part alone. To check the ML-DSA-65 part, use the Node verifier (`@parmana/verifier`) on Node 24 or later with OpenSSL 3.5 or later.

**Other differences from the Node verifier**

- A timestamp without a time zone offset is rejected. Node reads it in the local time zone, so this verifier gives the same answer on every machine.
- Integers beyond 2^53 are rounded the way JavaScript rounds them when it parses JSON. Use records written by Parmana, which never contain such integers.

## Install

Requires Python 3.11 or later (tested on 3.11, 3.12 and 3.13). The only dependency is `cryptography` (version 42 or later).

```sh
pip install parmana-verifier
```

From a clone of this repository:

```sh
cd python
pip install -e .
```

For the tests, install the `test` extra as well: `pip install -e ".[test]"`.

## Quick start

From a clone of this repository, at the repository root:

```sh
pip install -e python
parmana-verify test/vectors/record-python-ed25519.json test/vectors/trust-python-ed25519.json
```

```json
{"valid":true,"coverage":"legacy","errors":[],"checks":{"trustRecordHash":true,"signature":true}}
```

The exit code is `0`. Change the record and it is rejected:

```sh
parmana-verify test/vectors/record-python-ed25519-tampered.json test/vectors/trust-python-ed25519.json
```

The exit code is `1`, and the output lists the failed checks. The same check from Python:

```python
import json
from parmana_verifier import parse_trust_policy, verify_execution_trust_record

record = json.load(open("test/vectors/record-python-ed25519.json"))
policy = parse_trust_policy(json.load(open("test/vectors/trust-python-ed25519.json")))
result = verify_execution_trust_record(record, policy.keys, policy.hybrid_required_from)
print(result["valid"], result["errors"])
```

The sample files are test material, signed with a fixed test key. For real records, pin the keys you trust with `parmana-verify key-fingerprint` (see below).

## Command line

```sh
parmana-verify <proof.json> <trust.json>
parmana-verify key-fingerprint <public-key.pem>
```

- `verify` prints one line of JSON and exits `0` when the record is valid, `1` when it is invalid, and `2` for a usage or read error.
- `key-fingerprint` prints the `sha256:...` value to pin in a trust file.

The output has the same fields as the Node CLI: `valid`, `coverage`, `errors` and `checks`.

## Python API

```python
import json
from parmana_verifier import parse_trust_policy, verify_execution_trust_record

record = json.load(open("proof.json"))
policy = parse_trust_policy(json.load(open("trust.json")))

result = verify_execution_trust_record(record, policy.keys, policy.hybrid_required_from)
print(result["valid"], result["errors"])
```

## Trust file

The trust file lists each key you accept, pinned by its fingerprint. See the [main README](../README.md#installation-and-usage) for the format. The verifier never fetches keys.

## Tests

```sh
cd python
pip install -e ".[test]"
pytest
```

The tests run the shared test vectors in `../test/vectors`, check the canonical serialization against JavaScript's number and string formatting, and generate Ed25519 records at test time to cover the trust rules and the CLI.
