# On-device Verification

:::note[In short]

`Octet.verify` checks a proof on the device, offline, with no network call. It runs the same checks as the [`octet-verify`](/docs/concepts/verifying-proofs/) command-line tool and reports the same tri-state verdict.

:::

A [proof of location](/docs/concepts/proof-of-location/) is verified by re-checking its signatures against the keys inside it. `octet-verify` does that as a standalone tool. `Octet.verify` (new in 2.0) does the same work in-process, so an app can check a proof it just produced, or one it received, without a round trip.

## What it runs

`Octet.verify` runs the real cryptographic core, not a stub: ECDSA P-256 stage-signature verification, plus the structural, binding, freshness, region, and wire-format self-consistency checks. That is enough to catch a tampered field, an edited region, a stale proof, or a forged signature, all offline. With a hardware-attestation bundle it also anchors the device attestation to the bundled Apple and Google roots (Tier 3).

Two checks depend on state only the backend holds, so on-device they always report `NOT-CHECKED`, never a pass:

- **Cross-proof replay-uniqueness.** Whether this proof's nullifier was seen before needs the backend's record of every proof.
- **Revocation.** Whether the signing key was revoked needs the backend's revocation list.

For those, verify a proof against the [`octet-verify`](/docs/concepts/verifying-proofs/) service as the authoritative check. `Octet.verify` is the fast local gate.

## The verdict

`Octet.verify` returns a [`ProofVerification`](/docs/api-reference/octet-verify/) with a tri-state `verdict` and two booleans:

| Field | Meaning |
|---|---|
| `verdict` | `valid`, `invalid`, or `inconclusive`. |
| `isValid` | No check is `FAIL`. `NOT-CHECKED` and `WARN` never reject. |
| `isAuthentic` | `isValid` **and** `stage-signatures` is `PASS`: the signatures actually verified, not merely went un-failed. |

The rules mirror `octet-verify` exactly, including the anti-fail-open invariant: a check that could not run is reported as `NOT-CHECKED`, and a report full of `NOT-CHECKED` items can never read as authentic.

- `verdict` is `invalid` if any check is `FAIL`.
- Otherwise `valid` if the proof is authentic.
- Otherwise `inconclusive`: the structure passed but the signatures were not verified (for example, an iOS proof with no embedded certificate and no attestation bundle supplied).

Read `isAuthentic` when you need the cryptographic guarantee. Reading `verdict == valid` alone accepts an `inconclusive` proof.

## Relationship to octet-verify

`Octet.verify` and [`octet-verify`](/docs/concepts/verifying-proofs/) share one check recipe and one verdict model, so a proof verdicts the same way in both. Use the on-device call for an immediate local gate on the phone. Use the `octet-verify` service or CLI off-device when you need the backend-only checks (replay-uniqueness, revocation) or an audit trail independent of the app.

## Where to go next

- [`Octet.verify` API reference](/docs/api-reference/octet-verify/): the exact signatures, options, and check taxonomy.
- [Verifying Proofs](/docs/concepts/verifying-proofs/): the `octet-verify` tool and the full trust model.
- [Device Attestation](/docs/concepts/device-attestation/): what the Tier-3 attestation anchoring proves.
