# 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/v3.0/concepts/verifying-proofs/) command-line tool and reports the same tri-state verdict.

:::

A [proof of location](/docs/v3.0/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 ECDSA P-256 stage-signature verification, plus the structural, binding, freshness, region, and wire-format self-consistency checks. These checks catch a tampered field, an edited region, a stale proof, or a forged signature, all offline. When the proof's attestation can be anchored, it also checks the device attestation against the bundled Apple and Google roots, so the proof is hardware-attested. Android proofs carry their own key-attestation chain. For an iOS proof that carries only an assertion, pass `attestationBundle`.

Two properties need state that a single proof does not carry, so on the device they always report `NOT-CHECKED`:

- **Cross-proof replay-uniqueness.** The Octet proofs backend enforces nullifier uniqueness when a proof is uploaded. `octet-verify --nullifier-store <file>` adds a local seen-before check across your own runs.
- **Revocation.** The Octet backend checks Google's keybox-revocation list. Neither `Octet.verify` nor `octet-verify` consults a revocation list.

`Octet.verify` is the fast local gate.

## The verdict

`Octet.verify` returns a [`ProofVerification`](/docs/v3.0/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`. A `NOT-CHECKED` signature check does not count. |

The rules match `octet-verify`. A check that could not run is reported as `NOT-CHECKED`, and `NOT-CHECKED` never makes a proof 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` (or `verdict == valid`) when you need the cryptographic guarantee. Reading `isValid` alone accepts an `inconclusive` proof. A disc answer verifies whether it was signed `VERIFIED` or `PLAUSIBLE`, so also check `spoofingVerdict`, or set `requireVerdict`, before accepting one.

## Relationship to octet-verify

`Octet.verify` and [`octet-verify`](/docs/v3.0/concepts/verifying-proofs/) share one check recipe and one verdict model, so a proof gets the same verdict in both. Use the on-device call for an immediate local gate on the phone. Use `octet-verify` off-device for an audit trail independent of the app, for hardware-attestation validation (`appattest` build), or for a local replay check with `--nullifier-store`.

## Where to go next

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