@nexart/cli
v1.3.0
Published
NexArt CLI — Code Mode and AI execution certification
Downloads
419
Maintainers
Readme
@nexart/cli
Version: 1.3.0 (release candidate, not yet published). The CLI verifies both AI Execution record schemas — cer.ai.execution.v1 and cer.ai.execution.v2 — and provides an AIEF v0.2.5 Section 9 projection via --aief. Package versions are release-versioning only; the canonicalisation protocol remains 1.3.1 (jcs-v1).
Command-line interface for NexArt — run, replay, and verify deterministic generative art, plus AI execution certification commands.
NexArt verification is designed so that trust does not depend on NexArt itself.
Independent Verification
A NexArt record (CER bundle) can be verified independently, using only standard cryptographic primitives. You do not need to trust — or run — NexArt to check a record.
Verification requires only:
- the CER bundle (the record itself)
- a public key (to check the signature, when one is present)
- SHA-256 and Ed25519 (standard, widely available primitives)
It does not require:
- the NexArt SDK
- a NexArt account
- a network call (integrity verification is fully offline)
A simple example:
npx nexart verify-bundle ./bundle.jsonThis command allows a third party to verify record integrity and signer authenticity without relying on NexArt systems or source code.
What this verifies
- Integrity — the record's
certificateHashis recomputed from the record's contents and compared to the stored value. A privacy-redacted public record (input/output content stripped, hashes kept) is reported as AUTHENTIC (REDACTED) rather than failed — see the 3-state model. - Authenticity — the Ed25519 signature over the node's receipt is verified, when a signature is present.
Trust boundaries
This command verifies:
- Record integrity
- Signature authenticity (when a signature is present)
This command does not verify:
- Independent timestamp / time-of-existence
- Completeness of execution
- Inclusion in any transparency log
These boundaries are deliberate: the command establishes what a record contains and who signed it, and makes no claim beyond that.
protocolVersion: 1.2.0 vs 1.3.0
Verified records carry a protocolVersion that selects how their bytes are
canonicalised before hashing — ai verify surfaces it in both human-readable and
--json output (cli.protocolVersion).
- 1.2.0 (legacy, default): the original NexArt canonicalisation (
nexart-v1), fully supported and frozen. - 1.3.0 (stranger-verifiable): RFC 8785 (JSON Canonicalization Scheme,
jcs-v1). Required for independent / external verification by a third party who does not run our exact code.
A record with an unknown or unsupported protocolVersion fails closed (verify code
SCHEMA_ERROR, reason code SCHEMA_VERSION_UNSUPPORTED, exit 1) — it is never
mis-verified. The CLI adds no fallback or default.
Legacy 1.2.0 records are fully supported. Early 1.2.0 records that serialize
snapshot.parameters as null (the canonical form was an empty object {}) verify
correctly — ai verify reports VERIFIED (exit 0). This is handled in the SDK
without any change to canonicalization or the certificateHash. 1.3.0 remains
strict: a null parameters value on a 1.3.0 record fails closed.
Installation
# Global install (recommended)
npm install -g @nexart/cli
# After global install, use the `nexart` command directly:
nexart run sketch.js --seed 12345Quickstart: Run an Example
The SDK includes example sketches in the examples/ folder:
# 1. Set up authentication
export NEXART_RENDERER_ENDPOINT=https://nexart-canonical-renderer-production.up.railway.app
export NEXART_API_KEY=nx_live_your_key_here
# 2. Run the main example sketch (outputs to current directory)
npx @nexart/cli run ./examples/sketch.js --seed 12345 --include-code --out ./out.png
# Creates: ./out.png and ./out.snapshot.json
# 3. Verify the output is deterministic
npx @nexart/cli verify ./out.snapshot.json
# Output: [nexart] Result: PASSOutput Files
When you run with --out ./out.png, the CLI creates:
./out.png— The rendered PNG image./out.snapshot.json— Snapshot for replay/verify
Example Sketches
| File | Description |
|------|-------------|
| examples/sketch.js | Main example — VAR controls + random palette, protocol-safe |
| examples/sketch-minimal.js | Simple shapes, no randomness — identical output every run |
| examples/sketch-vars.js | Uses VAR + random() — demonstrates determinism with same seed |
Canonical Size
The canonical renderer enforces a fixed canvas size:
| Property | Value | |----------|-------| | Width | 1950 | | Height | 2400 |
Important: Do not pass custom --width or --height to the canonical renderer endpoint. The canonical size is enforced server-side for consistent, verifiable output.
# Correct: use default canonical size
nexart run sketch.js --seed 12345
# Avoid: custom sizes may be rejected by canonical renderer
# nexart run sketch.js --width 800 --height 600 # NOT recommendedQuick Start (npx)
Run without installing globally:
# Using npx (no install required)
npx @nexart/cli run sketch.js --seed 12345
npx @nexart/cli verify out.snapshot.json
npx @nexart/cli replay out.snapshot.json --out replay.pngOverview
The CLI has two command groups:
Code Mode commands — render generative art sketches, create verifiable snapshots, replay and verify them.
AI certification commands — create, certify, and verify tamper-evident Canonical Execution Records (CERs) for AI operations via the NexArt node API. Also includes local verification of project bundles (cer.project.bundle.v1) with nexart ai project-verify.
Authentication
Remote rendering requires an API key for metered access.
Setup
# Set environment variables
export NEXART_RENDERER_ENDPOINT=https://nexart-canonical-renderer-production.up.railway.app
export NEXART_API_KEY=nx_live_your_key_here
# Run with authentication
npx @nexart/cli run sketch.js --seed 12345 --include-code --out out.pngAPI Key Options
# Via environment variable (recommended)
export NEXART_API_KEY=nx_live_...
nexart run sketch.js --seed 12345
# Via CLI flag
nexart run sketch.js --seed 12345 --api-key nx_live_...Error Messages
If authentication fails:
[nexart] Error: Missing API key for remote rendering.
[nexart] Set NEXART_API_KEY environment variable or pass --api-key.
[nexart] Get your API key at: https://nexart.art/dashboard/apiCommands
run
Execute a sketch and create a snapshot:
# With remote renderer (default, requires API key for remote endpoints)
nexart run sketch.js --seed 12345 --out render.png
# With code embedded for standalone verify/replay
nexart run sketch.js --seed 12345 --include-code
# Local deterministic mode (no auth required; requires the optional `canvas` dependency)
nexart run sketch.js --renderer local --include-codeDeterministic Replay (local renderer). With
--renderer local, the CLI renders the sketch using the same canonical CodeMode render core as the SDK and hashes the encoded PNG bytes — the identicaloutputHashdefinition used by the remote renderer. For controlled CodeMode / canonical render snapshots this is byte-reproducible within the same runtime and render libraries (Node version, platform/arch, andcanvasnative libs). To make that reproducibility checkable, local snapshots additionally embed an optional, additiveenvironmentfingerprint (see below). Reproducibility across different environments is not guaranteed;replay/verifywill warn when the environment differs. The local renderer requires the optionalcanvaspackage and its native libraries — if they are missing, local rendering fails with a clear, actionable error (never a silent placeholder).
Options:
| Flag | Default | Description |
|------|---------|-------------|
| --out, -o | out.png | Output PNG path |
| --seed, -s | random | PRNG seed |
| --vars, -v | 0,0,0,0,0,0,0,0,0,0 | VAR values (comma-separated) |
| --width, -w | 1950 | Canvas width (use default for canonical) |
| --height | 2400 | Canvas height (use default for canonical) |
| --renderer | remote | remote or local |
| --endpoint | env/localhost:5000 | Remote renderer URL |
| --api-key | env | API key for authentication |
| --include-code | false | Embed code in snapshot |
| --runtime-hash | auto | Override runtime hash |
Outputs:
render.png— The rendered imagerender.snapshot.json— Snapshot for replay/verify
Snapshot environment field (additive, optional). Snapshots produced with
--renderer local include an environment object describing the render
environment (cliVersion, sdkVersion, protocolVersion, node, platform,
arch, canvas). It is descriptive metadata only — it is never hashed and
never affects pass/fail. It exists so replay/verify can warn when the replay
environment differs from the capture environment. Legacy snapshots (and
remote-rendered snapshots) omit this field and continue to verify and replay
exactly as before.
verify
Check that a snapshot produces the expected output:
# Verify against the canonical remote renderer (default)
nexart verify render.snapshot.json
# Verify locally with the deterministic CodeMode renderer (requires `canvas`)
nexart verify render.snapshot.json --renderer local
# With external code file
nexart verify render.snapshot.json --code sketch.jsThe local path performs a real render (no placeholder) — a missing canvas
dependency fails clearly rather than producing a false pass.
Exit codes:
0— PASS (hashes match)1— FAIL (hashes differ or error)
replay
Re-execute from a snapshot, compare the result against the snapshot, and report
PASS/FAIL:
# Deterministic local replay with PASS/FAIL evidence
nexart replay render.snapshot.json --renderer local --out replay.png \
--evidence replay.evidence.json
# With external code file
nexart replay render.snapshot.json --code sketch.js --out replay.pngreplay recomputes the output hash and compares it to the snapshot's
outputHash, and independently checks that the replayed code matches the
snapshot's codeHash. The overall result is PASS only when both match.
Options:
| Flag | Default | Description |
|------|---------|-------------|
| --out, -o | replay.png | Output PNG path |
| --code, -c | — | Code file (if not embedded in snapshot) |
| --renderer | remote | remote or local (deterministic CodeMode renderer) |
| --endpoint | env/localhost:5000 | Remote renderer URL |
| --evidence | — | Write a JSON replay-evidence file (PASS/FAIL + hash comparison) |
| --api-key | env | API key for authentication |
Exit codes:
0— PASS (output and code match the snapshot)1— FAIL (output or code differs, or render error)
When --evidence <file> is given, replay writes a
codemode.replay.evidence.v1 JSON document recording the PASS/FAIL result,
the expected vs. actual outputHash and codeHash, the renderer used, and the
snapshot vs. current environment fingerprints (with any differing fields).
AI Certification Commands (nexart ai)
These commands interact with the NexArt node API to manage Canonical Execution Records (CERs) for AI operations. CERs are tamper-evident bundles that cryptographically certify an AI execution (input, output, model, parameters).
Environment Variables
| Variable | Description |
|----------|-------------|
| NEXART_NODE_ENDPOINT | NexArt node API URL (default: https://node.nexart.art) |
| NEXART_API_KEY | Shared API key for authenticated requests |
nexart ai create
Create a CER bundle from an AI execution record.
# From file
nexart ai create execution.json
# From stdin
cat execution.json | nexart ai create
# With explicit endpoint and API key
nexart ai create execution.json \
--endpoint https://node.nexart.art \
--api-key nx_live_...
# Save to file
nexart ai create execution.json --out cer.jsonInput format (JSON file or stdin):
{
"executionId": "exec-001",
"provider": "openai",
"model": "gpt-4o",
"input": "What is 2+2?",
"output": "4",
"parameters": { "temperature": 0 }
}Output: CER bundle JSON printed to stdout (or saved via --out).
Options:
| Flag | Default | Description |
|------|---------|-------------|
| --endpoint | env/https://node.nexart.art | NexArt node URL |
| --api-key | env | API key |
| --out | stdout | Save bundle to file |
| --signals-file | (none) | Path to a JSON array of context signals to attach |
Exit codes:
0— Success1— API error or missing input
nexart ai certify
Certify an AI execution and receive an attested CER bundle. The node API signs the bundle and attaches an attestation.
# From file
nexart ai certify execution.json
# From stdin
cat execution.json | nexart ai certify
# JSON output (machine-readable)
nexart ai certify execution.json --json
# Save output
nexart ai certify execution.json --out certified.jsonDefault output:
[nexart] CER certified successfully
bundleType : cer.ai.execution.v1
hash : sha256:a1b2c3...
attestation: presentJSON output (--json):
{
"ok": true,
"bundleType": "cer.ai.execution.v1",
"certificateHash": "sha256:a1b2c3...",
"hasAttestation": true,
"bundle": { ... }
}Options:
| Flag | Default | Description |
|------|---------|-------------|
| --endpoint | env/https://node.nexart.art | NexArt node URL |
| --api-key | env | API key |
| --out | stdout | Save bundle to file |
| --json | false | Machine-readable JSON output |
| --signals-file | (none) | Path to a JSON array of context signals to attach |
Exit codes:
0— Certified successfully1— API error (e.g. 401 Unauthorized), missing input, or invalid response
nexart ai seal (v0.8.0+)
Create a Certified Execution Record locally — no node required. Produces an integrity-only bundle suitable for offline workflows, CI, pre-certification, or piping straight into nexart ai verify. All snapshot construction, canonicalization, and certificate hashing is delegated to createSnapshot() + sealCer() from @nexart/ai-execution — the canonical source of truth.
# Seal from file
nexart ai seal execution.json
# Seal from stdin
cat execution.json | nexart ai seal
# Seal then verify in a pipe (--quiet keeps the pipe clean)
cat execution.json | nexart ai seal --quiet | nexart ai verify
# Save sealed bundle to file
nexart ai seal execution.json --out cer.jsonDefault output (stdout — bundle JSON):
{
"bundleType": "cer.ai.execution.v1",
"certificateHash": "sha256:...",
"createdAt": "2026-04-30T12:00:00.000Z",
"version": "0.1",
"snapshot": { ... }
}Status banner (stderr):
[nexart] Local CER created
[nexart] State: Sealed (integrity only)
[nexart] No node attestation present
[nexart] This record is not certified — use `nexart ai certify` to attest via a node
[nexart] certificateHash: sha256:...Options:
| Flag | Default | Description |
|------|---------|-------------|
| --out / -o | stdout | Save sealed CER bundle to this file |
| --quiet / -q | false | Suppress the stderr banner (clean pipe output) |
Sealed vs Certified:
- Sealed (
ai seal): integrity only. No node receipt, no signature, no envelope. Verifies asVERIFIEDwith Receipt and Envelope reported asSKIPPED— that is correct, not a failure. - Certified (
ai certify): integrity + node attestation receipt + signature. Requires a node and an API key.
Exit codes:
0— Sealed successfully1— Missing required fields, invalid input JSON, or stdin error
nexart ai verify
Verify a CER bundle or CER package locally. Supports both record schemas: cer.ai.execution.v1 and cer.ai.execution.v2 (V2 records — including confidential V2 records with committed fields — verify offline exactly like V1; commitments do not require openings for base integrity). For V2 records the human output reports the requested model (model, producer-declared) and, when present, the response-declared model (modelEvidence.responseDeclaredModel, producer-transcribed from the provider response — not independently attested). No network call. All verification logic — bundleType discriminant, package detection / inner-CER unwrap, canonicalization, hash recomputation, and 3-layer status (Integrity / Receipt / Envelope) — is delegated to verifyAiCerBundleDetailed() from @nexart/ai-execution. The CLI owns input routing and output formatting only. Fully compatible with v0.11.0+ CERs that include context.signals, v0.6.0+ CER packages, and v0.8.0+ locally-sealed bundles from nexart ai seal.
# Verify a raw CER bundle from file
nexart ai verify cer.json
# Verify a CER package (inner bundle verified; envelope fields are not verified)
nexart ai verify cer-package.json
# From stdin
cat cer.json | nexart ai verify
# JSON output
nexart ai verify cer.json --jsonDefault output (PASS — raw bundle):
Verification result: VERIFIED
bundleIntegrity: PASS
nodeSignature: SKIPPED
receiptConsistency: SKIPPEDDefault output (PASS — CER package):
Input type: CER package
Verification result: VERIFIED
bundleIntegrity: PASS
nodeSignature: SKIPPED
receiptConsistency: SKIPPED
Note: Verified inner CER bundle only. Package-level receipt/signature/envelope not verified by this command.Default output (FAIL):
Verification result: FAILED
bundleIntegrity: FAIL
[nexart] Expected hash: sha256:a1b2c3...
[nexart] Computed hash: sha256:d4e5f6...JSON output (--json) — raw bundle:
{
"status": "VERIFIED",
"checks": {
"bundleIntegrity": "PASS",
"nodeSignature": "SKIPPED",
"receiptConsistency": "SKIPPED"
},
"reasonCodes": [],
"certificateHash": "sha256:a1b2c3...",
"bundleType": "cer.ai.execution.v1",
"verifiedAt": "2026-04-30T12:00:00.000Z",
"verifier": "@nexart/ai-execution",
"cli": {
"version": "0.9.0",
"sdkVersion": "0.16.1",
"inputType": "bundle"
}
}JSON output (--json) — CER package:
{
"status": "VERIFIED",
"checks": {
"bundleIntegrity": "PASS",
"nodeSignature": "SKIPPED",
"receiptConsistency": "SKIPPED"
},
"reasonCodes": [],
"certificateHash": "sha256:a1b2c3...",
"bundleType": "cer.ai.execution.v1",
"verifiedAt": "2026-04-30T12:00:00.000Z",
"verifier": "@nexart/ai-execution",
"cli": {
"version": "0.9.0",
"sdkVersion": "0.16.1",
"inputType": "package",
"verifiedInnerCer": true,
"packageTrustLayersVerified": false
}
}The top-level fields (status, checks, reasonCodes, certificateHash, bundleType, verifiedAt, verifier) are the SDK's CerVerificationResult verbatim. The additive cli envelope carries CLI-only metadata (version, sdkVersion, inputType, and package-specific flags when applicable).
Enterprise trust layers (--anchors, --timestamps): these flags opt in to the
non-evidentiary secondary-proof layers. They are reported separately and never change the
base verification result or the exit code — they cannot turn a VERIFIED bundle into FAILED or
vice-versa.
# Report anchor status (VALID / INVALID / NOT PRESENT)
nexart ai verify cer.json --anchors
# Report anchor + trusted timestamp status with a trust summary
nexart ai verify cer.json --anchors --timestampsWhen enabled, the human-readable output adds a Layers: block (Anchoring / Timestamp lines) and a
Trust summary block (Signature / Anchors / Timestamp / Protocol Version); the JSON output adds
top-level anchorStatus and timestampStatus objects ({ checked, valid, errors? }).
AIEF projection (--aief): projects the verification result into the
AIEF v0.2.5 Section 9 interoperability shape. Works for both
cer.ai.execution.v1 and cer.ai.execution.v2 records.
# AIEF v0.2.5 §9 projection (JSON output)
nexart ai verify cer.json --aief --json- Output is JSON in the §9 shape:
result,reason, and achecksobject containingschemaSupported,integrityValid,protectedSetValid,chainValid. - Exit code
0whenresultisPASS,1whenFAIL. - The projection covers artifact integrity only — it does not itself establish Node authenticity (receipt/signature) or RFC 3161 timestamp trust; those remain separate layers.
Options:
| Flag | Default | Description |
|------|---------|-------------|
| --json | false | Machine-readable JSON output |
| --aief | false | Emit the AIEF v0.2.5 Section 9 projection (JSON); exit 0 = PASS, 1 = FAIL |
| --anchors | false | Verify and report anchor status (non-evidentiary; does not affect exit code) |
| --timestamps | false | Verify and report trusted-timestamp status + trust summary (non-evidentiary) |
Exit codes:
0— PASS (inner bundle hash matches)1— FAIL (hash mismatch, invalid bundle, or unsupported format)
V2-aware related commands: nexart ai verify on a CER package unwraps and
verifies the inner V1 or V2 CER verbatim; nexart export-verification emits a
descriptive export artifact carrying the CER verbatim (never resealed/re-certified),
for either schema; nexart ai project-verify verifies cer.project.bundle.v1
project bundles containing V1, V2, or mixed V1/V2 steps under the unchanged
Project Hash contract.
nexart ai attach-anchor
Attach an external anchor (transparency log / TSA / blockchain) to a CER bundle. This is an append-only, non-evidentiary operation — it does not re-hash or re-sign the bundle.
# Attach an anchor and verify it in one pipe
nexart ai attach-anchor cer.json anchor.json | nexart ai verify --anchorsnexart export-verification
Produce a self-contained verification package that a third party can verify offline, without the NexArt node or this SDK. It is read-only — nothing is re-hashed or re-signed.
# Print a verification package to stdout
nexart export-verification cer.json
# Resolve the signing public key from a node-keys document and save the package
nexart export-verification cer.json --node-keys keys.json --out pkg.json
# From stdin
cat cer.json | nexart export-verificationThe package contains { bundle, publicKey, verificationInstructions }, where
verificationInstructions lists the protocolVersion, canonicalMode, certificateHash,
signatureAlgorithm, the present layer flags, and ordered verification steps.
Options:
| Flag | Default | Description |
|------|---------|-------------|
| --out, -o | — | Save the package to this file (otherwise printed to stdout) |
| --node-keys | — | Path to a NodeKeysDocument JSON to resolve the signing public key |
| --public-key | — | Provide the public key directly (takes precedence over --node-keys) |
Independent Verification (nexart verify-bundle)
Standalone, SDK-independent verification of a NexArt record.
nexart verify-bundle verifies a CER bundle (cer.ai.execution.v1) completely
independently of NexArt. Unlike nexart ai verify (which delegates to
@nexart/ai-execution) and export-verification (which only emits instructions),
this command performs the cryptographic checks itself.
All verification logic lives in a single, self-contained file
(src/standaloneVerify.ts) that imports only node:crypto — nothing from
@nexart/*, no network, no external dependencies. An auditor can read that one
file end-to-end and reproduce the result with just the bundle JSON, the signer's
public key, and the Node.js standard library.
# Recompute the certificate hash offline (integrity only)
nexart verify-bundle cer.json
# Also verify the node's Ed25519 receipt signature
nexart verify-bundle cer.json --public-key node-keys.json
# Machine-readable output
nexart verify-bundle cer.json --public-key node-keys.json --json
# Show every intermediate step (canonical mode, byte counts, hashes)
nexart verify-bundle cer.json --public-key node-keys.json --verbose
# Additionally verify an embedded RFC 3161 timestamp against trusted TSA roots
nexart verify-bundle cer.json --verify-timestamp --tsa-roots tsa-roots.pemWhat it checks
Integrity — recomputes
certificateHashfrom the bundle's evidentiary payload (bundleType,createdAt,snapshot,version, and, when present,context/contextSummary/policyEvaluation) and compares it to the stored value. The wholesnapshotis hashed, so an optionalsnapshot.identityassertion (v0.23.0+) is bound bycertificateHashand tampering with it FAILS — no special-casing in the verifier. The identity is PII-safe by construction — only the IdP provider/subject anchor plus sha256 commitments (assertionHash, optionalemailHash), never a raw JWT or email — and anonymous mode is simply its absence: records withoutsnapshot.identityverify identically (the report showsIdentity: none). Canonicalisation is bound tosnapshot.protocolVersion:1.2.0→ nexart-v1 (NexArt's original canonical JSON)1.3.0→ jcs-v1 (RFC 8785 JSON Canonicalization Scheme)
An unknown/unsupported
protocolVersionfails closed (it is never silently coerced to a default).Signature — when a signed node attestation receipt is present (at
bundle.receipt+bundle.signature, orbundle.meta.attestation.*) and a public key is available, the Ed25519 signature is verified over the canonical JSON of the receipt. The receipt'scertificateHashmust also match the bundle's. If no receipt is present, or no public key is supplied, the signature check is reported as skipped (integrity can still pass).If a signature is not present, integrity can still pass, but authenticity is not established.
Timestamp (RFC 3161) (optional, additive —
--verify-timestamp) — when the bundle carries an RFC 3161 timestamp (intrustedTimestamps[]or a singulartimestamp), this offline check confirms the token anchors the bundle to a point in time. Both wire shapes are accepted: a fullTimeStampResp(the certification node's output) and a bareTimeStampToken(e.g. OpenSSL's-token_out) — the response wrapper is detected and unwrapped automatically. It verifies, in order: (1) the TSA's CMS signature over the embeddedTSTInfo; (2) that the token's message imprint equals the bundle'scertificateHashdigest; (3) that the TSA signing certificate chains to a root supplied via--tsa-roots; (4) that the certificate is valid at the timestamp; and (5) that the timestamp is at or after the bundle'screatedAt.This layer is strictly additive: it never modifies
certificateHash, is never included in canonicalisation, and never affects the Integrity or Signature results or the exit code. The ASN.1 stack (pkijs) is loaded only when--verify-timestampis passed — ordinary verification stays dependency-light. Without the flag, an existing timestamp is reported aspresent (not verified); with no timestamp at all,not present.
Output
Human-readable (default):
✔ Integrity: PASS
✔ Signature: PASS
Protocol: 1.3.0
✔ Externally anchored timestamp: valid
Timestamp Details:
Provider: freeTSA
Time: 2026-06-17T10:52:12Z
Imprint: matchThe phrasing — "externally anchored timestamp" — reflects that the check
proves the bundle was anchored to an independent time authority, not an internal
clock. When a token was actually verified, a Timestamp Details block follows
with the provider, the token's genTime (sub-second precision stripped), and
whether the message imprint matched the bundle's certificateHash.
The status line is one of:
✔ Externally anchored timestamp: valid
✖ Externally anchored timestamp: invalid (signature)
✖ Externally anchored timestamp: invalid (imprint mismatch)
✖ Externally anchored timestamp: untrusted (unknown root)
✖ Externally anchored timestamp: invalid (expired)
✖ Externally anchored timestamp: invalid (time)
✖ Externally anchored timestamp: invalid (malformed token)
○ Externally anchored timestamp: present (not verified) # token present, flag absent
○ Externally anchored timestamp: not present # no token in the bundleMachine-readable (--json):
{
"status": "verified",
"integrity": "pass",
"signature": "pass",
"protocolVersion": "1.3.0",
"timestamp": { "status": "valid", "tsa": "freeTSA", "genTime": "2026-06-17T10:52:12.000Z" }
}status is verified when integrity passes and the signature is not invalid
(pass or skipped); otherwise it is failed. The timestamp field is always
present and purely informational — it never changes status or the exit code.
The process exits 0 when verified and 1 otherwise, so it is safe to use in CI.
Options:
| Flag | Default | Description |
|------|---------|-------------|
| --public-key | — | Path to the Ed25519 public key — PEM, JWK, a NodeKeysDocument JSON, or a raw base64url key |
| --kid | — | Key id to select when --public-key is a NodeKeysDocument. Strict — if the requested kid is absent, the signature fails closed (no fallback to another key) |
| --json | false | Emit a machine-readable result |
| --verbose | false | Print intermediate verification steps |
| --verify-timestamp | false | Additionally verify an embedded RFC 3161 timestamp (offline; never affects the exit code) |
| --tsa-roots | — | Path to trusted TSA root certificate(s) (PEM) used by --verify-timestamp. Without trusted roots, a present token reports UNTRUSTED |
Note on the command name. This command is named
verify-bundlerather thanverifybecausenexart verify <snapshot>already exists for Code Mode snapshots. The standalone CER verifier is purely additive and changes no existing command.
Cryptography. Integrity uses SHA-256 and signatures use Ed25519, both from the Node.js built-in
cryptomodule (RFC 8032 pure Ed25519) — chosen so the verifier needs zero third-party dependencies and stays trivially auditable.
Verifying a full CER record (nexart verify-record)
nexart verify-record verifies a full CER record — the self-contained
envelope a relying party receives — rather than a bare bundle. A record bundles
the evidentiary payload together with its signed node attestation receipt and an
optional externally anchored timestamp:
{
"bundle": { /* the cer.ai.execution.v1 bundle */ },
"verification": {
"receipt": { /* signed node attestation receipt */ },
"receiptSignature": "base64url-ed25519-signature"
},
"timestamp": { "token": "base64-rfc3161-timestamptoken" } // optional
}Fully automatic since v0.16.0. Give it a record file and nothing else — the command verifies integrity, the receipt signature, and the RFC 3161 timestamp automatically, and prints an audit-grade report. It is self-contained: a third party needs only the record and this CLI.
# Full automatic verification: integrity + signature + timestamp
nexart verify-record record.json
# Machine-readable report
nexart verify-record record.json --json
# Read the record from stdin (use "-" as the path)
curl -s https://node.example/records/abc.json | nexart verify-record -
# One-line verdict only (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED)
nexart verify-record record.json --short
# Fully offline, with an explicit signing key
nexart verify-record record.json --offline --public-key node-keys.jsonFlexible input. The bundle is taken from record.bundle when present, or the
record root is treated as the bundle itself (a certified bundle with its receipt and
signature embedded works directly). There is no "missing bundle" error — a
structurally invalid record simply reports a clean Integrity: FAIL.
Three-state model (since v0.16.1). A public record served from a node may be
privacy-redacted: the snapshot's input / output content is stripped while the
inputHash / outputHash anchors are kept. Because certificateHash is sealed over the
full snapshot, it can no longer be recomputed from a redacted snapshot — but that is
not tampering. verify-record (and verify-url / verify-hash) detect this case and
report a third outcome:
| Outcome | When | Integrity line | Exit |
| --- | --- | --- | --- |
| VERIFIED | content present, certificateHash recomputes, signature not invalid | ✔ Integrity: PASS | 0 |
| AUTHENTIC (REDACTED) | content stripped (hashes kept), recompute not possible, signature passes | ⚠ Integrity: NOT RECOMPUTABLE (redacted) | 0 |
| FAILED | a real integrity mismatch (content present but hash differs) or an invalid signature | ✖ Integrity: FAIL | 1 |
A redacted record is reclassified only when the snapshot is genuinely content-stripped
and the signature still verifies — a true content mismatch is never softened to
redacted, an invalid signature is always FAILED, and a redacted record whose hash still
recomputes simply stays VERIFIED. The certificateHash computation, canonicalisation,
and signature/timestamp logic are unchanged; this is purely a classification and
presentation layer.
What AUTHENTIC (REDACTED) does and does not prove. It proves the record carries a valid node signature over the sealed
certificateHash— i.e. it is authentic. It does not recompute the full snapshot, so it cannot prove the content behind the keptinputHash/outputHashwas not altered before redaction. Redaction detection is structural (content absent, hashes present), so treat AUTHENTIC (REDACTED) as trustworthy only when the record comes from an authenticated node source (e.g. the node's public endpoint over TLS). For the strongest guarantee, obtain the full, un-redacted record and re-runverify-recordto get VERIFIED.
Signing key resolution (by priority).
--public-key <file>— an explicit override (PEM, JWK, NodeKeysDocument, or raw base64url).- Auto-fetch — otherwise the node's published key set is fetched from
https://node.nexart.io/.well-known/nexart-node.jsonand the key whosekidmatches the receipt'sattestorKeyIdis used. This is the only network call in the verify path; it lives entirely in the CLI layer (the auditable core never touches the network) and fails gracefully — a network error just falls through to the next source. Override the URL withNEXART_NODE_WELLKNOWN_URL, or disable the fetch entirely with--offline. - Embedded key — a key embedded in the bundle, if any.
Automatic timestamp verification. An RFC 3161 token at
verification.timestamp.token, timestamp.token, or embedded in the bundle is
verified automatically and offline against an embedded DigiCert trust store
(DigiCert Trusted Root G4 + DigiCert Assured ID Root CA). Override the trusted roots
with --tsa-roots <file> (PEM). The ASN.1 stack (pkijs / asn1js) loads
only when a token is actually present. The timestamp layer is strictly
additive: it never modifies certificateHash, is never canonicalised, and never
affects the Integrity / Signature result or the exit code (which is 0 only when
integrity passes and the signature is not invalid, 1 otherwise) — an
untrusted-root, imprint-mismatch, or unreadable---tsa-roots outcome is reported as
a timestamp status, never a crash.
Input from a file or stdin. Pass a file path, or - to read the record from
standard input — handy for pipelines such as curl -s … | nexart verify-record -.
Report. The default output is an audit-grade summary rendered in a fixed section
order — Signature, Integrity, Timestamp (VERIFIED / INVALID (<reason>) /
PRESENT BUT NOT VERIFIED / NOT INCLUDED, plus generation time and authority),
Node (node id, key id, key source, operator), Identity (asserted identity, or
none), and Execution (protocol, provider, model, input/output hashes, parameters,
created-at). A present-but-unverifiable token (a parse failure, or verification not
attempted) is shown as a caution — ⚠ Timestamp: PRESENT BUT NOT VERIFIED — never as an
error, and the timestamp never affects FINAL STATUS. The report ends with a single
headline line — FINAL STATUS: VERIFIED EVIDENCE RECORD, FINAL STATUS: AUTHENTIC
(REDACTED), or FINAL STATUS: FAILED. For a redacted record the Signature line is
shown before the Integrity line and a tight context block follows the headline:
FINAL STATUS: AUTHENTIC (REDACTED)
Mode: Public Verification (Redacted)
Redaction: Applied by certification node (privacy mode)
! Full integrity verification requires access to original execution data--json emits the same information as a structured object (with status of verified /
authentic-redacted / failed, integrity including redacted, and a redacted
boolean); --short prints only ✔ VERIFIED, ⚠ AUTHENTIC (REDACTED), or ✖ FAILED.
| Option | Description |
| --- | --- |
| --public-key <file> | Override the signing key. Default: auto-fetched from the node .well-known key set. |
| --offline | Do not contact the network; use --public-key or a bundle-embedded key only. |
| --kid <id> | Key id to select from the key set (default: the receipt's attestorKeyId). |
| --tsa-roots <file> | Override the default DigiCert TSA trust roots with custom root certificate(s) (PEM). |
| --json | Emit a machine-readable report. |
| --short | Print only a single verdict line (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED). |
| --verbose | Show intermediate verification steps. |
verify-bundleis unchanged. The redesigned output and automatic key/timestamp resolution apply toverify-recordonly;verify-bundlekeeps its existing behaviour, flags, and byte-for-byte output.
Offline verification (nexart verify-offline)
nexart verify-offline <record.json> is a convenience alias for
verify-record --offline for relying parties who want a single, unambiguous
"verify this locally, never touch the network" command. It runs the exact same
verification core — integrity (certificate-hash recompute), Ed25519 receipt
signature, and timestamp reporting — and never contacts a NexArt node. The
signing key must come from --keys (an alias for --public-key) or be embedded in
the record.
# Verify a record locally with an explicit node key file
nexart verify-offline record.json --keys node-keys.json
# Verify a record that embeds its own signing key
nexart verify-offline record.json
# Read the record from stdin
curl -s https://node.example/records/abc.json | nexart verify-offline - --keys node-keys.jsonIt differs from verify-record only in presentation: a passing record reports
FINAL STATUS: AUTHENTIC (OFFLINE) with an explicit Mode: Offline
Verification line (the redaction note is still appended for a privacy-redacted
record), while a real mismatch or invalid signature stays FINAL STATUS:
FAILED with exit 1. --json adds "mode": "offline". Hashing,
canonicalisation, the 3-state core, and exit codes are identical to
verify-record — this is a UX wrapper, not new verification logic. As with
verify-record, a missing or unverifiable timestamp is reported but never
changes the verdict or exit code.
This verifies authenticity and integrity, not re-execution. A pass confirms the record was signed by a NexArt node and has not been tampered with; it does not re-run the workload.
Verifying from a URL (nexart verify-url)
nexart verify-url <url> is the one-command verification experience: it fetches a
CER record (or a bare bundle) as JSON from a URL and verifies it with the exact same
automatic key/timestamp resolution and audit-grade renderer as verify-record.
# Fetch and fully verify a published record
nexart verify-url https://node.example/records/abc.json
# One-line verdict (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED)
nexart verify-url https://node.example/records/abc.json --short
# Machine-readable report
nexart verify-url https://node.example/records/abc.json --jsonThe response is auto-detected as a full record (has verification.receipt) or a
raw bundle; both route through the same rich verifier (a bare bundle is accepted
directly). The URL fetch is the record source and always happens; --offline
here only disables the separate signing-key auto-fetch (so pair it with --public-key
or rely on a bundle-embedded key). A non-2xx response, a network error, or a
non-JSON body fails cleanly with exit 1. All other options match verify-record
(--public-key, --kid, --tsa-roots, --json, --short, --verbose).
Verifying by certificate hash (nexart verify-hash)
nexart verify-hash <certificate_hash> resolves a CER record straight from its
certificate hash. It constructs the public node lookup URL
(https://node.nexart.io/v1/cer/public?certificate_hash=<hash>) and then verifies it
through verify-url, so the output is identical to verify-url.
# Resolve and fully verify a record by certificate hash
nexart verify-hash sha256:abc123…
# One-line verdict (✔ VERIFIED / ⚠ AUTHENTIC (REDACTED) / ✖ FAILED)
nexart verify-hash sha256:abc123… --short
# Machine-readable report
nexart verify-hash sha256:abc123… --jsonThe hash must include the canonical sha256: prefix; a missing prefix fails
cleanly with exit 1 (before any network call). Network failures behave exactly like
verify-url. Options match verify-url (--public-key, --kid, --tsa-roots,
--json, --short, --offline, --verbose); as with verify-url, --offline only
disables the signing-key auto-fetch — the record itself is always fetched.
Note: this is distinct from
nexart verify <snapshot>, which verifies that a snapshot reproduces its expected output.
nexart ai project-verify (v0.7.0+)
Verify a project bundle (cer.project.bundle.v1) locally. No network required. Delegates all verification logic to verifyProjectBundle() from @nexart/ai-execution — the canonical source of truth for project bundle semantics. The CLI provides the command surface and output formatting.
# From file
nexart ai project-verify project-bundle.json
# From stdin
cat project-bundle.json | nexart ai project-verify
# Machine-readable JSON output
nexart ai project-verify project-bundle.json --jsonChecks performed (in order, via @nexart/ai-execution):
bundleTypediscriminant: must becer.project.bundle.v1- Required fields:
version,protocolVersion,projectBundleId,projectTitle,startedAt,completedAt,integrityblock shape stepRegistryis a non-empty arrayembeddedBundlesis a plain object keyed bystepIdtotalStepsmust equalstepRegistry.length- Per-step: embedded bundle exists;
executionIdandcertificateHashmatch the registry entry; inner CERcertificateHashis independently valid integrity.projectHashis recomputed from all material project metadata and compared to the declared value
Default output (PASS):
Verification result: VERIFIED
bundleType: cer.project.bundle.v1
projectBundleId: proj-abc123
projectTitle: My Project
totalSteps: 3
passedSteps: 3
failedSteps: 0
projectHashIntegrity: PASS
stepIntegrity: PASSDefault output (FAIL):
Verification result: FAILED
bundleType: cer.project.bundle.v1
projectBundleId: proj-abc123
projectTitle: My Project
totalSteps: 3
passedSteps: 2
failedSteps: 1
projectHashIntegrity: FAIL
stepIntegrity: FAIL
[nexart] Error: integrity.projectHash mismatch: expected sha256:..., got sha256:...JSON output (--json):
{
"status": "VERIFIED",
"bundleType": "cer.project.bundle.v1",
"projectBundleId": "proj-abc123",
"projectTitle": "My Project",
"totalSteps": 3,
"passedSteps": 3,
"failedSteps": 0,
"projectHashValid": true,
"checks": {
"structuralValid": true,
"projectHashIntegrity": "PASS",
"stepIntegrity": "PASS"
},
"steps": [
{
"stepId": "step-001",
"sequence": 0,
"executionId": "exec-abc",
"certificateHash": "sha256:...",
"ok": true,
"errors": []
}
],
"errors": [],
"verifiedAt": "2026-04-06T12:00:00.000Z",
"verifier": "@nexart/cli"
}Options:
| Flag | Default | Description |
|------|---------|-------------|
| --json | false | Machine-readable JSON output |
Exit codes:
0— VERIFIED (all checks pass)1— FAILED (any structural, step, or hash error)
Context Signals (v0.5.0+)
Context signals attach upstream evidence — CI approvals, webhook events, human-review records — to a CER. They are included in the context.signals array and are tamper-evident: the certificateHash covers the full context block, so any post-creation modification to signals is detected by nexart ai verify.
Preparing a signals file
A signals file is a plain JSON array of signal objects:
[
{
"type": "approval",
"source": "github-actions",
"step": 0,
"timestamp": "2026-03-15T12:00:00.000Z",
"actor": "alice",
"status": "ok",
"payload": { "pr": 42 }
}
]Attaching signals when creating / certifying
# Create CER with signals
nexart ai create execution.json --signals-file signals.json --out cer.json
# Certify with signals
nexart ai certify execution.json --signals-file signals.jsonThe CLI reads the JSON array from --signals-file, merges it into the request body as { signals: [...] }, and sends it to the node API. The node then embeds the signals in the CER under context.signals and includes them in the certificateHash.
Verifying a signals-containing CER
No extra flags are needed — nexart ai verify handles both plain CERs and CERs with context.signals:
nexart ai verify cer.json
# Verification result: VERIFIED
# bundleIntegrity: PASSIf any signal field is altered after creation, the hash will mismatch and verification fails:
# (tampered context.signals)
nexart ai verify tampered-cer.json
# Verification result: FAILED
# bundleIntegrity: FAILCER Packages (v0.6.0+)
A CER package is an envelope object that wraps a raw CER bundle with optional metadata layers — receipt, signature, and custom envelope fields:
{
"cer": {
"bundleType": "cer.ai.execution.v1",
"certificateHash": "sha256:...",
"version": "1.0.0",
"createdAt": "...",
"snapshot": { ... }
},
"receipt": { ... },
"signature": "base64url-sig=="
}nexart ai verify automatically detects the format:
- Raw bundle — input has
bundleType === 'cer.ai.execution.v1'at the top level. Behavior is identical to pre-v0.6.0. - CER package — input has a
cerfield whose innerbundleType === 'cer.ai.execution.v1'. The CLI verifies only the inner bundle; receipt, signature, and other envelope fields are not verified.
Detection is transparent — no flags are needed. Both formats use the same nexart ai verify command.
What IS verified (inner bundle)
certificateHashis recomputed from the protected set (bundleType,version,createdAt,snapshot) and the optionalcontextblock (signals). A mismatch reportsBUNDLE_HASH_MISMATCH.
What is NOT verified (package envelope)
receiptfields (attestationId, nodeRuntimeHash, etc.)signature(cryptographic envelope signature)- Custom envelope fields
Full package trust verification (receipt + envelope signature) is planned for a future release.
Remote Renderer
The CLI calls a canonical Node.js renderer endpoint for real PNG generation.
Endpoint Configuration
# Via environment variable
export NEXART_RENDERER_ENDPOINT=https://nexart-canonical-renderer-production.up.railway.app
export NEXART_API_KEY=nx_live_...
# Via CLI flags
nexart run sketch.js --endpoint https://render.nexart.io --api-key nx_live_...Expected API
POST /api/render
Content-Type: application/json
Authorization: Bearer <api_key>
{
"code": "...",
"seed": 12345,
"VAR": [0,0,0,0,0,0,0,0,0,0],
"width": 1950,
"height": 2400,
"protocolVersion": "1.2.0"
}
Response: image/png (binary)
Headers:
X-Runtime-Hash: <hash>Equivalent curl
curl -X POST https://nexart-canonical-renderer-production.up.railway.app/api/render \
-H "Content-Type: application/json" \
-H "Authorization: Bearer nx_live_..." \
-d '{"code":"createCanvas(1950,2400);\nbackground(0);\n","seed":12345,"VAR":[0,0,0,0,0,0,0,0,0,0],"width":1950,"height":2400,"protocolVersion":"1.2.0"}' \
--output render.pngSnapshot Format (v1)
{
"protocol": "nexart",
"protocolVersion": "1.2.0",
"runtime": "canonical",
"runtimeHash": "<sha256>",
"codeHash": "<sha256>",
"seed": 12345,
"VAR": [0,0,0,0,0,0,0,0,0,0],
"canvas": { "width": 1950, "height": 2400 },
"outputHash": "<sha256>",
"createdAt": "2026-01-25T...",
"code": "..." // optional, if --include-code
}Hash definitions:
outputHash= SHA-256 of PNG bytescodeHash= SHA-256 of normalized coderuntimeHash= From renderer or SHA-256 of SDK version
Renderer Modes
| Mode | Description |
|------|-------------|
| --renderer remote | Default. Calls canonical renderer with auth, produces real PNG output. |
| --renderer local | NOT implemented yet. Outputs a 1x1 placeholder image only. No auth required. |
# Remote (default) — real PNG output, requires API key for remote endpoints
nexart run sketch.js --seed 12345
# Local — NOT implemented, placeholder only, no auth
nexart run sketch.js --renderer localReal local/offline rendering is planned for a future release.
Environment Variables
| Variable | Description |
|----------|-------------|
| NEXART_RENDERER_ENDPOINT | Remote renderer URL (default: http://localhost:5000) |
| NEXART_NODE_ENDPOINT | NexArt node API URL (default: https://node.nexart.art) |
| NEXART_API_KEY | API key for authenticated requests |
License
MIT — Free for all use including commercial.
See Core vs Edges for the NexArt business model.
