# External Verification

URL: https://docs.nexart.io/docs/external-verification

Verify any NexArt Certified Execution Record from scratch, without the NexArt SDK or CLI.

This guide is for implementers building a verifier in a language NexArt does not ship an SDK for, or for auditors who require an independent reference implementation. It uses only standard cryptographic primitives: SHA-256, Ed25519, and a JSON canonicalizer matching the bundle's `protocolVersion`.

## Prerequisites

- An HTTPS client.
- A JSON canonicalizer matching the bundle's `protocolVersion`(`nexart-v1` for 1.2.0, `jcs-v1` / RFC 8785 for 1.3.0).
- SHA-256.
- An Ed25519 signature verifier.
- The `certificateHash` of the record to verify (format: `sha256:<hex>`).

## Step 1 — Fetch the Public Record

The public endpoint returns a redacted CER bundle. `snapshot.input` and `snapshot.output` are SHA-256 digests, not raw payloads.

Fetch public CER

```
curl -fsS \
  "https://node.nexart.io/v1/cer/public?certificate_hash=sha256:7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069" \
  -o cer.json
```

## Step 2 — Fetch the Node Key Set

The node publishes its current and historical public keys at a stable well-known location. Keys are identified by `kid`.

Fetch node public keys

```
curl -fsS https://node.nexart.io/.well-known/nexart-node.json -o node-keys.json
```

See Key Management for the key set schema and rotation policy.

## Step 3 — Select the Canonicalization Profile

Read `meta.attestation.protocolVersion` and select the canonicalization profile. Unknown values MUST fail closed.

```
"1.2.0" -> profile "nexart-v1" (default, custom canonicalization)
"1.3.0" -> profile "jcs-v1"    (opt-in, RFC 8785 / JCS, standards-based)
other   -> FAILED
```

Canonicalization is protocol-bound. Do NOT assume RFC 8785 universally; records produced with 1.2.0 use `nexart-v1` and will not recompute to the same hash under JCS.

## Step 4 — Recompute certificateHash (Integrity)

Project the bundle to the hashed whitelist, canonicalize with the profile selected in Step 3, and SHA-256. Compare with the bundle's declared `certificateHash`.

Hashed projection (whitelist)

```
bundleType
version
createdAt
snapshot
context           (only when present)
contextSummary    (only when present)
policyEvaluation  (only when present)
```

Reference (pseudo-code) — example assumes a 1.3.0 (jcs-v1) bundle

```
import { canonicalize as jcs } from "rfc8785";       // for protocolVersion 1.3.0
import { canonicalize as nexart } from "./nexart-v1";  // for protocolVersion 1.2.0
import { createHash } from "node:crypto";

const cer = JSON.parse(fs.readFileSync("cer.json", "utf8"));
const profile = cer.meta?.attestation?.protocolVersion === "1.3.0" ? jcs
              : cer.meta?.attestation?.protocolVersion === "1.2.0" ? nexart
              : (() => { throw new Error("Unknown protocolVersion — FAILED"); })();

const whitelist = ["bundleType", "version", "createdAt", "snapshot",
                   "context", "contextSummary", "policyEvaluation"];
const projection = Object.fromEntries(
  whitelist.filter(k => cer[k] !== undefined).map(k => [k, cer[k]])
);

const bytes      = profile(projection);
const recomputed = "sha256:" + createHash("sha256").update(bytes).digest("hex");

if (recomputed !== cer.certificateHash) throw new Error("Integrity FAILED");
```

## Step 5 — Verify the Receipt Signature (Layer 2)

- Locate the receipt at `cer.meta.attestation.receipt`.
- Resolve the public key from the node key set by `receipt.kid`. If not present, fail closed.
- Canonicalize `receipt.payload` using the selected profile and verify the Ed25519 signature against the canonicalized bytes.
- Confirm `receipt.payload.certificateHash` equals `cer.certificateHash`.

## Step 6 — Verify the Envelope Signature (Layer 3, when present)

Reconstruct the strict whitelist projection and verify the Ed25519 signature. If the envelope is absent, report SKIPPED; this is not a failure.

Envelope signed projection

```
{
  attestation: { attestationId, attestedAt, kid, nodeRuntimeHash, protocolVersion },
  bundle:      { bundleType, version, createdAt, snapshot,
                 context?, contextSummary?, policyEvaluation? }
}
```

## Aggregate Status

```
VERIFIED   every applicable layer = PASS
FAILED     any layer = FAIL
NOT_FOUND  the record does not exist on the node
```

An aggregate VERIFIED with one or more SKIPPED layers is valid and expected for sealed (local) bundles and for public, redacted bundles where Layer 3 cannot be reconstructed. See Verification Statuses and Errors.
