# GRRC Certification — Independent Verification Specification (v1)

**Status:** Implementer specification — Phase 34 / Slice 222 (`VERIFY-API-COMPLETE`). Authored FROM the built resources (drift-resistant). This document defines, completely enough that a skeptic can **write their own verifier**, how an independent relying party confirms a GRRC robot certification **without trusting — or even reaching — GRRC** (the offline floor; trust-model (iv-2), the Tank Doctrine extended to verification).

**Audience:** an insurer's / reinsurer's technical reviewer, a standards body, or any relying party who wants to verify a robot's GRRC certification themselves and **mirror the inputs** so GRRC withholding can only ever show *staleness*, never forge validity.

**Companions:** the **Bounded-Claim Statement** (`Phase34_Slice222_GRRC_BoundedClaimStatement.md` — what verification asserts and explicitly does **not**, owner-authored (iv-1) verbatim) and the **API Reference** (`Phase34_Slice222_GRRC_VerificationApiReference.md`). The governance posture is owner-authored in `RES_GRRC_TrustModel_v1.0.md` (content v1.2); this is engineering only.

> **No operator/company name appears anywhere in this surface.** The root of trust is a crypto-agile **key-id + algorithm tag**, never a name (CD #864). "GRRC" is an abstract role.

---

## 0 — What verification proves (and the two independent legs)

A complete verification runs **two independent checks against two distinct keys**, and is honest about what each proves:

1. **Genuineness** — the clearance carries a **certifier** signature, verifiable against the **published certifier anchor**, that the robot **cannot forge** (the robot holds only the public anchor, never the certifier private key). This is the load-bearing guarantee against self-clearing: *fixer ≠ certifier.*
2. **Chain integrity** — the robot's audit chain is intact and single-headed through a stated sequence: recompute SHA-256 over each entry's canonical encoding, confirm the `PrevChainHash → ChainHash` linkage, and verify each entry's **robot audit-key** signature.

Plus **revocation currency** (the signing key-id is still in the current signed trusted-anchor list) and a **stamped-lifetime** check.

Two exposure tiers (the (iv-3) privacy boundary, owner-authored, load-bearing):

- **Public tier** (unauthenticated): confirms cert status + genuineness + "chain intact and single-headed through sequence N" **via a certifier-signed checkpoint** — no enumerable per-event timeline is exposed. A layperson confirms it with signature checks alone.
- **Authorized full-record tier** (owner / authorized auditor): additionally carries the **chain segment** + the **published robot audit public key**, so the auditor runs the **full independent chain-integrity walk** and confirms the public checkpoint honestly reflects it. The chain segment operates over the **encrypted envelope (ciphertext), never plaintext** — *verify ≠ decrypt*; it is erasure-compatible by construction (a crypto-shredded payload stays unrecoverable while the chain still verifies).

---

## 1 — Cryptographic primitives

| Element | Value |
|---|---|
| Signature algorithm tag | `ECDSA-P256-SHA256-IEEE-P1363` |
| Curve | NIST P-256 (secp256r1) |
| Hash | SHA-256 |
| Signature encoding | IEEE-P1363 fixed-field `r‖s` (64 bytes), **not** DER |
| Public key encoding | `SubjectPublicKeyInfo` (DER), base64 |

All signatures in this surface (the grant, the checkpoint, the anchor/revocation statement, and each audit-chain entry) use this one primitive. A verifier checks each with a standard ECDSA-P256 `VerifyData`/`VerifyHash` over the stated bytes, the IEEE-P1363 signature format.

---

## 2 — The resources (stable, versioned formats)

Each verification input is an independently-fetchable, cacheable, **re-hostable** resource carrying an explicit `FormatVersion`. A consumer **MUST reject** a resource whose `FormatVersion` it does not understand (fail-closed) — never best-effort parse it. The current versions:

| Resource | Format version | Media type |
|---|---|---|
| Verification bundle (offline artifact) | `schemaVersion` 1 | `application/vnd.grrc.verification-bundle.v1+json` |
| Trusted-anchor set | 1 | `application/vnd.grrc.trust-anchor-set.v1+json` |
| Revocation / anchor-currency statement | 1 | `application/vnd.grrc.revocation-statement.v1+json` |
| Full-record artifact (authorized) | `schemaVersion` 1 | `application/vnd.grrc.full-record.v1+json` |

### 2.1 — The verification bundle / full-record artifact

JSON object (the offline artifact). Public-tier bundles carry `RobotAuditPublicKeySpkiBase64 = ""` and `ChainSegment = []`; full-record artifacts populate both.

```
{
  "SchemaVersion": 1,
  "RobotUuid": "<guid>",
  "GrantPayloadBase64": "<base64 of the grant payload — §3.1>",
  "CheckpointPayloadBase64": "<base64 of the checkpoint payload — §3.2>",
  "AnchorStatementPayloadBase64": "<base64 of the anchor/revocation statement payload — §3.3>",
  "RobotAuditPublicKeySpkiBase64": "<base64 SPKI of the robot audit key, or empty>",
  "ChainSegment": [ <entry>, ... ]   // empty for the public tier
}
```

Each `ChainSegment` entry (encrypted envelope only — no plaintext field exists on the wire):

```
{
  "Sequence": <int64>, "EntryId": "<guid>", "Domain": "<string>", "EventType": "<string>",
  "DomainRecordId": "<guid>", "DecisionCycleId": "<guid|null>",
  "OccurredAt": "<ISO-8601>", "CallerTimestamp": "<ISO-8601|null>",
  "AccessTier": <int>, "SchemaVersion": <int>, "TenantId": "<guid>",
  "PayloadBase64": "<base64 ciphertext envelope>",
  "ChainHashBase64": "<base64 32 bytes>", "PrevChainHashBase64": "<base64 32 bytes>",
  "SignatureBase64": "<base64 robot audit-key signature over ChainHash>"
}
```

### 2.2 — The trusted-anchor set

```
{ "FormatVersion": 1, "Anchors": [ { "KeyId": "<string>", "Algorithm": "ECDSA-P256-SHA256-IEEE-P1363", "PublicKeySpkiBase64": "<base64 SPKI>" } ] }
```

### 2.3 — The revocation / anchor-currency statement

```
{ "FormatVersion": 1, "AnchorStatementPayloadBase64": "<base64 of the anchor-statement payload — §3.3>" }
```

---

## 3 — The signed-content canonical encodings

Every signed payload follows the same two-layer shape: a **signed-content** canonical byte string (lex-sorted JSON keys, no whitespace) that the certifier signs SHA-256 of; and a **payload envelope** that stores those exact signed bytes alongside the base64 signature, so a verifier checks the signature over **precisely the bytes that were signed** (no canonicalization ambiguity). Envelope, for all three:

```
{ "schemaVersion": 1, "signature": "<base64 IEEE-P1363>", "signedContent": "<base64 of the signed-content bytes>" }
```

### 3.1 — Recertification grant (the genuineness leg)

Signed-content JSON, lex-sorted keys, no whitespace:

```
{"algorithm":"ECDSA-P256-SHA256-IEEE-P1363","expiresAtUtc":"<ISO-8601 O>","faultMarker":"<string>","issuedAtUtc":"<ISO-8601 O>","keyId":"<string>","robotUuid":"<guid D>","supersededDeCertCycleId":<int>,"supersededThroughCycleId":<int>}
```

The certifier signs SHA-256 of these bytes. `faultMarker` is the reasoning-integrity fault the clearance supersedes; `supersededDeCertCycleId` is the specific self-de-certification it clears.

### 3.2 — Cert-status checkpoint (the public-tier intactness claim)

Signed-content JSON, lex-sorted keys, no whitespace:

```
{"algorithm":"...","asOfUtc":"<ISO-8601 O>","certStatus":"<token>","headChainHashBase64":"<base64>","headSequence":<int64>,"keyId":"<string>","robotUuid":"<guid D>"}
```

It asserts *"robot R's chain is single-headed and intact through sequence N (head hash H); cert status = {token}; under key-id K; as of {timestamp}."* `certStatus` ∈ {`valid`,`expired`,`revoked`,`self-de-certified`,`superseded`} (§5).

### 3.3 — Trusted-anchor / revocation statement (currency)

Signed-content JSON, lex-sorted keys, no whitespace; `trustedKeyIds` sorted ordinal + deduped:

```
{"algorithm":"...","asOfUtc":"<ISO-8601 O>","signerKeyId":"<string>","trustedKeyIds":["<key-id>", ...]}
```

A clearance's key-id is **current (not revoked)** iff it appears in `trustedKeyIds`. Revocation is "pull the anchor" ((vi)): removing a key-id invalidates every clearance under it. GRRC withholding a fresher statement cannot forge currency — the last signed statement stands, and its `asOfUtc` makes staleness visible to the relying party, who weights it accordingly.

---

## 4 — The audit-chain canonical encoding (the chain-integrity leg)

For each `ChainSegment` entry, recompute `ChainHash = SHA-256( canonicalEncoding(entry, prevChainHash) )`. The encoding is **length-prefixed and big-endian**, portable across runtimes. The format version is selected by the entry's own stored `SchemaVersion`: `0 → v1`, `1 → v2`, `≥2 → v3`. **`Sequence` is NOT in the encoding** (it is store-assigned). Byte layout (v3 shown; v2 omits the TenantId field; v1 omits both the schemaVersion stamp and TenantId):

```
0..3    format version, int32 big-endian (1 | 2 | 3)
[v2/v3] schemaVersion, int32 big-endian
[v3]    TenantId, 16 bytes, .NET Guid.TryWriteBytes layout (MIXED-endian — first three
        fields little-endian, final 8 bytes in declaration order; NOT RFC 4122)
        EntryId, 16 bytes (same MIXED-endian Guid layout)
        Domain length (int32 big-endian) + UTF-8 bytes
        DomainRecordId, 16 bytes (Guid layout)
        DecisionCycleId present-flag (1 byte: 0x00 absent / 0x01 present) + 16 bytes if present
        OccurredAt: int64 big-endian UnixTimeMilliseconds + int16 big-endian UTC-offset minutes
        AccessTier, int32 big-endian
        Payload length (int32 big-endian) + payload bytes (the ciphertext envelope, verbatim)
        PrevChainHash, 32 bytes (all-zeros for the scope genesis)
```

> **Guid byte order is the .NET `Guid.TryWriteBytes` MIXED-endian layout, not RFC 4122.** A non-.NET verifier must replicate it: the first three components (Data1 int32, Data2 int16, Data3 int16) are little-endian; the final 8 bytes are in declaration order. `EventType` and `CallerTimestamp` are **not** hashed.

The walk is **per scope** and **genesis-rooted**: the first entry's `PrevChainHash` must be the 32 all-zeros genesis sentinel; each successor's `PrevChainHash` must equal its predecessor's recomputed `ChainHash`. Linkage is by `PrevChainHash` (gap-tolerant on `Sequence`, so legitimate identity-burn gaps do not false-fail; a deleted in-chain entry still breaks linkage). For each entry, also verify `SignatureBase64` is a valid robot-audit-key signature over the 32-byte `ChainHash` (ECDSA-P256 `VerifyHash`, IEEE-P1363). The published robot audit public key is carried in the full-record artifact (`RobotAuditPublicKeySpkiBase64`).

---

## 5 — The verification algorithm, in order

Given the bundle (or full-record artifact), the published anchor set, and the verification time `asOfUtc`:

1. **Decode** the three signed payloads (grant §3.1, checkpoint §3.2, anchor statement §3.3). A malformed artifact ⇒ result `indeterminate` (fail closed).
2. **Genuineness** (the certifier signature + the four (vii) conditions):
   - Resolve the certifier anchor for the grant's `keyId`; confirm the algorithm tag matches.
   - Verify the grant's certifier signature over its signed-content bytes (§3.1) against the anchor's SPKI.
   - Confirm the grant binds the **same robot UUID** as the artifact and has a well-formed lifetime.
   - If any fails ⇒ `genuineness-failed`.
3. **Chain integrity:**
   - **Authorized tier (segment present):** run the per-scope walk (§4) over the segment; then confirm the walked head hash equals the checkpoint's `headChainHashBase64`. If the walk fails or the heads disagree ⇒ `integrity-broken`.
   - **Public tier (no segment):** confirm the checkpoint signature (§3.2) against the anchor and that it binds the same robot UUID; intactness rests on that certifier-signed claim through sequence N. If the checkpoint signature fails ⇒ `integrity-broken`.
4. **Revocation currency:** verify the anchor-statement signature (§3.3) against the anchor; confirm the grant's `keyId` ∈ `trustedKeyIds`. Statement signature invalid ⇒ currency unprovable; key-id absent ⇒ `revoked`.
5. **Lifetime:** if `asOfUtc > grant.expiresAtUtc` ⇒ `expired`.
6. **Determine the bounded status** (first failing gate wins): `genuineness-failed` → `integrity-broken` → `revoked` → `expired` → an explicit certifier-stated `self-de-certified` / `superseded` from the trusted checkpoint → otherwise `valid`.

`OverallVerified` is true **iff** the status is `valid`.

---

## 6 — The bounded result

```
CertStatus            : valid | expired | revoked | self-de-certified | superseded
                        | genuineness-failed | integrity-broken | indeterminate
OverallVerified       : bool
GenuinenessConfirmed  : bool   (+ GenuinenessKeyId — the certifier key-id)
ChainIntact           : bool   (+ IntactThroughSequence — authorized/offline tier only)
RevocationCurrent     : bool
AsOfUtc               : the verification time
DoesNotAssert         : the owner-authored (iv-1) explicit-negative, VERBATIM (see the Bounded-Claim Statement)
```

Every result carries the explicit negative so **"verified" cannot be read as a broader endorsement** of the robot's conduct or the owner's rules. Read it before relying on the result.

---

## 7 — Independence, mirroring, and stability

- **Offline floor (mandatory).** Given {bundle + published anchor [+ revocation statement] [+ full-record artifact]} a relying party reaches the bounded result with **no network** and **no GRRC dependency**. A hosted endpoint is a *courier* for these materials, never the only path and never a gate.
- **Mirror the inputs yourself.** Fetch each resource (§2) and re-host it on infrastructure you control. Verify the freshest signed copy you hold. GRRC's availability or willingness never gates your verification; withholding only ever shows as staleness via the statement timestamps.
- **Format stability.** A GRRC clearance lives ~1 year. Each resource's `FormatVersion` is bumped only on a breaking wire change; additive optional fields keep the version. Reject an unrecognized version rather than mis-parsing it.

---

*End of the GRRC Independent Verification Specification (v1) — Phase 34 / Slice 222. The claim is not self-referential: this document is sufficient to build an independent verifier. The reference implementation is the offline verifier shipped with RES (`Res.Core.GrrcVerification` / the `Res.Tools.GrrcVerify` tool).*
