# Verifier Quick Start

From source to a `VALID` verdict in a few minutes, using `octet-verify`, the
open-source command-line tool that independently checks an Octet proof.

:::note

This page is the how-to. [Verifying Proofs](/docs/concepts/verifying-proofs/) explains
what the verifier does and doesn't establish, and why it can run offline.

:::

---

## 1. Build it

The verifier is public Rust. Clone and build:

```bash
git clone https://github.com/octetproof/octet-verify
cd octet-verify
cargo build --release
# binary at ./target/release/octet-verify
```

The default build is **offline and dependency-light**. It pulls no networking
or JSON libraries, so the cryptographic path you're trusting stays small and
auditable. Two optional features sit behind build flags:

- `--features net` adds [backend fetch mode](#3-fetch-from-the-ingestion-api).
- `--features appattest` adds offline [hardware-attestation](/docs/concepts/device-attestation/)
  validation: iOS App Attest to Apple's root, and the Android key-attestation
  chain to Google's root. Without it, the `attestation-root` check stays
  `NOT-CHECKED`.

---

## 2. Verify a proof you received

If you have the proof bytes as a file (the SDK can export one, or a backend can
hand you the raw bytes), that's all you need. There is no network, and no keys
to trust beyond what is inside the proof:

```bash
octet-verify proof.bin
# or: cat proof.bin | octet-verify
```

A passing run looks like this:

```
== octet-verify ==
verdict: VALID

  [       PASS] freshness              42 s old (limit 300 s)
  [       PASS] nullifier              replay token present (32 bytes)
  [       PASS] stage-chain            8 stages, hash linkage intact
  [       PASS] stage-signatures       all 8 stage signatures verify
  [       PASS] chain-assembly         final stage binds all 7 prior signatures
  [       PASS] field-binding          commitment, nullifier, zkProof bound to signed stage hashes
  [       PASS] semantic-binding        verdict / region / level / integrity / commitment bound
  [       PASS] region-claim           claims country:US
  [NOT-CHECKED] attestation-root       hardware key trusted as carried (rebuild --features appattest to validate)
  ...

11 pass · 0 fail · 0 warn · 3 not-checked
```

The **exit code** is the automation-friendly signal:

| Code | Meaning |
|---|---|
| `0` | **authentic**: valid and signatures cryptographically verified |
| `1` | **invalid**: a check failed |
| `2` | usage / IO / decode / backend error |
| `3` | **inconclusive**: structurally valid but signatures not verified (e.g. no hardware key) |

So a CI gate is just `octet-verify proof.bin && deploy`. Exit `0` means
authentic, and an unverified proof exits `3`, never `0`.

Add `--json` for one machine-readable object per proof (`valid` reports
*authenticity*, plus `signatures_verified` and the `verdict` string):

```bash
octet-verify proof.bin --json | jq 'select(.valid == false)'
```

Useful flags: `--hardware-pubkey <file>` (supply the device key for iOS proofs
that carry no certificate), `--expect-region <id>` (assert a claimed region), and
`--max-age-seconds <n>` (widen the freshness window). See the repo's
[`INTEGRATION.md`](https://github.com/octetproof/octet-verify/blob/main/INTEGRATION.md)
for the full reference.

---

## 3. Fetch from the ingestion API

If your proofs are uploaded to the Octet proof ingestion API, the verifier can
fetch and check them in one step. This needs the network-enabled build:

```bash
cargo build --release --features net
```

The backend is **untrusted**. Fetched bytes run through the exact same checks
as a local file. You just need a token to read your license's proofs.

### Getting an activation bearer

`--token` is an **activation bearer**: a short-lived credential minted from your
license key. The SDK obtains one automatically when it activates your license
(`Octet.start`, see [License & Activation](/docs/concepts/license-activation/)). To
fetch from a script or CI, mint one yourself against the public activation
endpoint with your license key:

```bash
export OCTET_TOKEN=$(curl -fsS https://api.octetproof.com/v1/activate \
  -H "Content-Type: application/json" \
  -d '{
    "token":     "<your-license-key>",
    "device_fp": "verifier-cli",
    "os":        "ios",
    "rt":        "native"
  }' | jq -r '.bearer')
```

- **`token`**: your license key (from [sdk.octetproof.com/signup](https://sdk.octetproof.com/signup)).
- **`device_fp`**: any stable identifier for this caller (<=128 chars).
- **`os`** in `ios`/`android`/`windows`/`macos`/`linux`/`web` and **`rt`** in `native`/`flutter`/`reactnative`/`web`. Set them to a platform your license covers.

The response's `bearer` field is what you pass to `--token`. The CLI then
exchanges it for a short-lived, read-scoped token internally. Re-run the
activation if the bearer expires.

### Fetch and verify

```bash
octet-verify fetch <proof-id> --backend https://api.octetproof.com --token "$OCTET_TOKEN"
```

Two more modes:

- `octet-verify watch ...`: poll for new proofs and verify each as it arrives (handy for a live demo or dashboard).
- `octet-verify range --since <ts> --until <ts> ...`: verify every proof in a window. Exits non-zero if any fails.

> Proofs are stored for **less than 24 hours**, then purged. Fetch and verify
> promptly. An aged-out id returns `404`.

---

## 4. The full loop, end to end

Putting the pieces together, from device to independent verdict:

```mermaid
flowchart LR
    A[Your app + OctetSDK<br/>generate a proof] -->|upload| B[Ingestion API<br/>stores under 24h, bytes only]
    B -->|fetch by id| C[octet-verify<br/>checks signatures]
    C --> D[VALID / INVALID]
```

1. **Produce.** Your app calls the SDK. On a `YES`/`NO` it gets a signed proof
   and uploads it. (Setup is in the [iOS](/docs/getting-started/ios-quickstart/) and
   [Android](/docs/getting-started/android-quickstart/) quick starts.)
2. **Store.** The ingestion API keeps the raw bytes for less than 24 hours, keyed by
   id. It never decodes or signs anything.
3. **Verify.** `octet-verify fetch <proof-id> ...` pulls those bytes and validates
   them against the keys *inside* the proof. The verdict depends only on the
   bytes, not on anything the backend says.

The result: a proof produced on a phone is confirmed by a separate, public
binary, using only the bytes and the keys inside the proof. If the proof had
been mutated, replaced, or fabricated anywhere along the way, the verifier
rejects it.

---

## Troubleshooting

| Symptom | Fix |
|---|---|
| `octet-verify: command not found` | Build it (step 1) and use `./target/release/octet-verify`, or put it on your `PATH`. |
| `verdict: INCONCLUSIVE` | The signatures weren't checked, usually an iOS proof with no embedded certificate. Pass the device key with `--hardware-pubkey <file>`. |
| `verdict: INVALID` | A check failed. The output names which one. Common during development: a stale proof from before a wire change, or a key that isn't the one that signed the proof. |
| `404` / no proof on fetch | The proof hasn't been uploaded yet, or it has aged past the retention window (less than 24 hours). Generate a fresh one and fetch within the day. |
| `... requires a build with the net feature` | Rebuild with `cargo build --release --features net` to use `fetch` / `watch` / `range`. |

## Reference

- [`README.md`](https://github.com/octetproof/octet-verify): overview and what the verifier does and does not check.
- [`INTEGRATION.md`](https://github.com/octetproof/octet-verify/blob/main/INTEGRATION.md): full CLI reference, flags, and consumer guide.
- [`VERIFICATION-SPEC.md`](https://github.com/octetproof/octet-verify/blob/main/VERIFICATION-SPEC.md): the byte-exact signing contract every check implements.
