Skip to content
Preview (Alpha), release 0.1. No independent cryptographic review of the protocol or its reference verifiers has been done.

Your first verification, in Python

In this tutorial you run loquitur-verify, the Python verifier, over the test bundles that ship in its repository. Only the first two steps use the network: the clone, and the installation, which downloads the two dependencies of the verifier and its build backend unless they are already cached. Every verification after them runs offline, on your machine, and contacts no service.

You need Git and Python 3.11 or newer. The commands are written for a POSIX shell; the Windows section at the end says what changes in PowerShell.

Get the material

Clone the repository at the release tag. This step needs the network, and so does the installation in the next section.

Terminal window
git clone --branch v0.1.0 https://github.com/loquitur-dev/loquitur-verify-py.git

Install the verifier

You need Python 3.11 or newer and a checkout of the loquitur-verify-py repository. From the directory that contains the checkout:

Terminal window
python -m pip install ./loquitur-verify-py
cd loquitur-verify-py
loquitur-verify --version

That installs the verifier and its two dependencies, rfc8785 and cryptography. Network access is needed only to download them when they are not already cached.

The optional narrative extra, which adds --narrative, needs loquitur-schemas (>=0.1.0,<0.2.0): install it the same way from its own checkout, before loquitur-verify.

Included from loquitur-verify-py/README.md, “Installation”. MIT OR Apache-2.0.

A PASS, a FAIL and a REVIEW

The first run asks the two questions every run asks: is this the shape of a Loquitur bundle (structure), and do the files match the digests that the bundle itself declares (integrity). The tampered bundles answer the second with FAIL. A bundle that leaves one input out answers REVIEW: nothing failed, but one question could not be answered.

With --json a run prints the whole JSON report, a few kilobytes of it. Look in it for status, verdict and exit_code. Each expected result below lists the status and the verdict of the report and the exit code of the process. To see the exit code of the last command, run echo $? in a POSIX shell or $LASTEXITCODE in PowerShell. For a short summary instead of the report, use --pretty in place of --json.

With the verifier installed, run these commands from the root of the loquitur-verify-py checkout. They verify the golden fixtures under tests/fixtures/.

Verify the valid golden fixture:

Terminal window
loquitur-verify tests/fixtures/valid_bundle.zip --json

Expected result:

status: OFFLINE_OK
verdict: PASS
exit code: 0

Verify a tampered manifest fixture:

Terminal window
loquitur-verify tests/fixtures/tampered_manifest_bundle.zip --json

Expected result:

status: OFFLINE_FAIL
verdict: FAIL
exit code: 1

Verify a tampered canonical input fixture:

Terminal window
loquitur-verify tests/fixtures/tampered_canonical_input_bundle.zip --json

Expected result:

status: OFFLINE_FAIL
verdict: FAIL
exit code: 1

Verify a bundle missing canonical input:

Terminal window
loquitur-verify tests/fixtures/missing_canonical_input_bundle.zip --json

Expected result:

status: OFFLINE_REVIEW
verdict: REVIEW
exit code: 2

For a local workflow that accepts a named review reason, --accept-review lists the reasons to accept; the report is unchanged:

Terminal window
loquitur-verify tests/fixtures/missing_canonical_input_bundle.zip --json --accept-review canonical_input_not_provided

Expected result:

status: OFFLINE_REVIEW
verdict: REVIEW
exit code: 0

Only the process exit changes: the report still says OFFLINE_REVIEW, and its exit_code member stays 2 (report-contract.md section 4).

Each Expected result block lists status, verdict and the process exit code, then members of the JSON report named by their path in it.

Included from loquitur-verify-py/README.md, “5-minute quickstart from source”. MIT OR Apache-2.0.

Link the bundle to its public entry

A registry entry is the small public record an issuer publishes. Giving it to the verifier adds the link check: does this public entry refer to this bundle, and to no other.

The link check runs only when you supply a registry entry:

Terminal window
loquitur-verify tests/fixtures/link_valid_bundle.zip --registry-entry tests/fixtures/link_valid_registry_entry.json --json

Expected result:

status: OFFLINE_OK
verdict: PASS
exit code: 0
outcome.reason: local_integrity_and_registry_link_checks_passed

The check holds the supplied entry and the bundle’s receipt to their closed contracts, then compares them with each other and with the bundle, value for value. The entry’s own committed verdict caps the result: only a PASS entry can reach OFFLINE_OK. A passing link check means only that the supplied entry is consistent with the supplied bundle, not who signed the entry or that any registry holds it. registry-link.md specifies every comparison. Without --registry-entry, the report’s registry_link is null.

Included from loquitur-verify-py/README.md, “Link check”. MIT OR Apache-2.0.

Check who signed the entry

A trust registry says which keys may sign entries, and a trust root says who may sign the trust registry. Giving both adds the authorization check. The three runs below reach three of the outcomes it can have: REVIEW without a trust root, PASS with one, and REVIEW again at an evaluation time after the trust registry expired. A revoked key, or a signature that does not verify, gives FAIL.

The commands below continue their lines with \, which a POSIX shell reads. In PowerShell, end each line with a backtick instead, or write the command on one line.

The authorization check runs only when you supply a registry entry and a trust registry. Three runs over the same fixtures show the three outcomes it can reach. Run them from the repository root.

Without a trust root, a run that passes every other authorization condition still reviews: nothing authenticates the trust registry itself.

Terminal window
loquitur-verify tests/fixtures/link_valid_bundle.zip \
--registry-entry tests/fixtures/authorization_valid_registry_entry.json \
--trust-registry tests/fixtures/authorization_quickstart_trust_registry.json \
--evaluation-time 2026-05-20T00:00:00Z \
--json

Expected result:

status: OFFLINE_REVIEW
verdict: REVIEW
exit code: 2
outcome.reason: trust_registry_root_not_pinned
trust_registry.authorization_status: AUTHORIZED_ACTIVE_KEY
trust_registry.signature_status: VALID_SIGNATURE
trust_registry.trust_registry_root_signature_status: NO_PINNED_TRUST_ROOT

With --trust-root, the trust registry is authenticated against the root that the trust-root document names, and the run passes:

Terminal window
loquitur-verify tests/fixtures/link_valid_bundle.zip \
--registry-entry tests/fixtures/authorization_valid_registry_entry.json \
--trust-registry tests/fixtures/authorization_quickstart_trust_registry.json \
--trust-root tests/fixtures/authorization_quickstart_trust_root.json \
--evaluation-time 2026-05-20T00:00:00Z \
--json

Expected result:

status: OFFLINE_OK
verdict: PASS
exit code: 0
outcome.reason: local_integrity_registry_link_and_trust_checks_passed
trust_registry.trust_registry_root_signature_status: ROOT_SIGNATURE_VALID

The evaluation time is yours, never one of the issuer’s dates; without --evaluation-time, authorization is not established. One second after the trust registry expires, the run reviews rather than fails, because no time attested to the signature exists in this version, and root authentication is not reached:

Terminal window
loquitur-verify tests/fixtures/link_valid_bundle.zip \
--registry-entry tests/fixtures/authorization_valid_registry_entry.json \
--trust-registry tests/fixtures/authorization_quickstart_trust_registry.json \
--trust-root tests/fixtures/authorization_quickstart_trust_root.json \
--evaluation-time 2026-05-29T12:00:01Z \
--json

Expected result:

status: OFFLINE_REVIEW
verdict: REVIEW
exit code: 2
outcome.reason: trust_registry_not_current
trust_registry.authorization_status: TRUST_REGISTRY_EXPIRED
trust_registry.trust_registry_root_signature_status: ROOT_SIGNATURE_NOT_EVALUATED

The trust root of these runs was generated in memory, used once to sign authorization_quickstart_trust_registry.json and discarded; nothing can sign with it again. That root has no authority over anything: it exists to show what --trust-root does. A real deployment pins a root it obtained out of band, from a party it trusts (trust-root.md section 7).

The check first establishes that the supplied trust registry authorizes the entry’s issuer key at your evaluation time, and only then verifies the entry’s signature with that key. The conditions, their order and the status each one reports are specified in trust-registry-authorization.md. --trust-registry requires --registry-entry; without --trust-registry, the report’s trust_registry is null.

A key is judged as a key and not by its label. The check stops with TRUST_REGISTRY_AMBIGUOUS_KEY when more than one trust entry of the issuer carries the entry’s issuer_key_id, and when the trust entry found shares its secp256k1 point with another trust entry of the registry, whatever their statuses, issuers and key types and in either SEC1 spelling, compressed or uncompressed. The same issuer_key_id under another issuer_id with a key of its own is not a duplicate.

Included from loquitur-verify-py/README.md, “Authorization check”. MIT OR Apache-2.0.

What you have shown

With the last PASS you have shown three things about the test bundle: its bytes match the digests committed to in the registry entry you supplied; the registry entry refers to this bundle; and the entry’s signature verifies with a key that the trust registry authorizes at the evaluation time you chose, a trust registry that the root you pinned authenticates.

You have not shown that a valuation is right, who the issuer is in the world, when the entry was signed, or that any registry other people see holds the entry. Section 2 of the threat model states exactly what a PASS asserts, and What a PASS means explains it.

On Windows

In PowerShell, from the directory that contains the checkout:

Terminal window
python -m pip install .\loquitur-verify-py
cd loquitur-verify-py
loquitur-verify tests\fixtures\valid_bundle.zip --pretty

Expected abbreviated result:

Loquitur offline verification: OFFLINE_OK / PASS
registry: REGISTRY_ENTRY_NOT_PROVIDED (signature_supported=False)

The other loquitur-verify commands of the quickstart and of the link and authorization checks are written for a POSIX shell. They run unchanged in PowerShell, except that the authorization commands continue lines with \: in PowerShell, end those lines with a backtick instead, or write the command on one line.

Included from loquitur-verify-py/README.md, “Windows quickstart”. MIT OR Apache-2.0.

Next

Project site: loquitur.dev

Written for this site; text licensed CC BY 4.0. Included sections keep the licence of their source, named under each.