---
title: "Verification"
description: "These functions check evidence you have been given, rather than producing any."
source: "Integrity Python SDK"
---
These functions check evidence you have been given, rather than producing any. They are the Python
counterpart of the checks the Integrity explorer runs in the browser, and they answer two separate
questions:

- **Is this statement still what it says it is?** Every lineage statement is identified by a hash of
  its own canonicalized content, so recomputing that hash and comparing it with the statement's `@id`
  detects any modification after the fact.
- **Is this credential's signature good, and is it about the statement I think it is?** A valid
  signature over some *other* subject proves nothing about the statement in hand, so the two checks
  belong together.

Both run **fully offline**. JSON-LD contexts resolve against documents compiled into the package or
supplied by you via `contexts=`, and credentials are restricted to DID methods that can be resolved
from the identifier itself, so verification never reaches the network. This is what makes them usable
in an air-gapped or reproducible pipeline.

There is no manifest-level entry point. Verifying a whole manifest means looping these two functions
over its `statements`, which is what the explorer does:

```python
import json
from eqty_sdk import verify_statement, verify_vc

manifest = json.loads(open("manifest.json").read())
contexts = manifest.get("contexts")

for statement in manifest["statements"].values():
    if not verify_statement(json.dumps(statement), contexts):
        print("modified since it was created:", statement["@id"])
```

## `verify_statement`

Pass the manifest's `contexts` map whenever you have one. Statements reference their context by CID
(`"@context": "urn:cid:..."`), and while the common contexts ship inside the package, a statement
written against a custom context cannot be canonicalized without it.

A `True` result means every field the statement's `@context` defines is unmodified. It does not mean
the bytes are unmodified: the identifier commits to the statement's canonicalized RDF, and JSON-LD
expansion drops keys the context does not define.

### `eqty_sdk.verify_statement`

```python
verify_statement(statement_json: str, contexts: Optional[Dict[str, Any]] = None) -> bool
```

Verifies that a lineage statement's content still hashes to its `@id`. Canonicalizes the statement as JSON-LD, recomputes its BLAKE3 RDFC CID and compares it with the `@id` the statement carries. Returns `False` when they differ, meaning the statement was modified after it was created. `contexts` maps a JSON-LD context URI to its context document, for contexts this build does not embed. Values may be dicts or JSON strings, so a manifest's `contexts` field can be passed straight through. Note that a supplied context takes precedence over an embedded one of the same URI. Runs fully offline. A context that is neither embedded nor supplied raises rather than being fetched. A `True` result means every field the statement's `@context` defines is unmodified. It does not mean the bytes are unmodified: the identifier commits to the statement's canonicalized RDF, and JSON-LD expansion drops keys the context does not define. Raises `ValueError` if the statement is not a JSON object or has no string `@id`, and `RuntimeError` if a context cannot be resolved.

## `verify_vc`

Supply `statement_id` whenever you know which statement the credential is supposed to attest. Without
it, only the signature is checked.

Supply `contexts` whenever the credential's `@context` references a document this build does not
embed. Verification re-expands the credential, so an unresolvable context means the proof cannot be
checked at all — and because nothing is fetched, that surfaces as `False` rather than an error,
indistinguishable from a bad signature. If a credential you expect to be valid returns `False`, an
unsupplied context is the first thing to check.

This verifies the cryptographic proof alone. Whether a credential has since been **revoked or
suspended** lives in its status list, which has to be fetched over the network and is deliberately
not consulted here.

### `eqty_sdk.verify_vc`

```python
verify_vc(vc_json: str, statement_id: Optional[str] = None, contexts: Optional[Dict[str, Any]] = None) -> bool
```

Verifies a W3C Verifiable Credential's proof offline. When `statement_id` is given, also checks that the credential's `credentialSubject.id` is that statement. A valid signature over some other subject says nothing about the statement in hand. Returns `False` when the credential does not verify or is bound to a different subject. Raises `ValueError` when the input is not a credential, meaning it is not JSON, has no `credentialSubject`, or has more than one subject. Raises `RuntimeError` when the credential's DID method would need network access: only `did:key`, `did:jwk` and `did:pkh` resolve offline. Checks the cryptographic proof only. Revocation and suspension live in the credential's status list, which is fetched over the network and is not consulted here. `contexts` maps a JSON-LD context URI to its context document, for contexts this build does not embed. Values may be dicts or JSON strings, so a manifest's `contexts` field can be passed straight through. Note that a supplied context takes precedence over an embedded one of the same URI. Runs fully offline. Verifying a credential re-expands it, so a context that is neither embedded nor supplied is never fetched — it reports `False`, alongside the other reasons a proof may not check out.