Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Native library reference. Extended CLI examples use jacs-compat. This source reference can include unreleased changes; see the package guide for installation and MCP profiles for available tools.

Verifying Signed Documents

Verify a JACS-signed document in under 2 minutes. Verification checks that the document has not changed and was signed by the key selected under your trust policy. A signature alone does not establish a real-world identity or permission to act.

Verification does NOT require creating an agent. You need the signed document and independently trusted public evidence for its signer.

Raw JSON is decoded strictly before signing, canonicalization, or verification. Duplicate object names are rejected at every nesting level, including names that become equal after JSON escape decoding (for example, agentID and agent\u0049D). This prevents two consumers from interpreting the same signed bytes with different first-key/last-key rules. Repeated values in arrays are ordinary JSON and remain supported.

Strict vs permissive verification

verify-text and verify-image use a permissive default: a missing signature is a typed status, not an error. Strict mode opts in to error-on-missing. The choice maps onto every binding the same way.

SurfacePermissive (default)Strict (--strict / strict=True / { strict: true } / Strict: true)
jacs-compat verify-text (CLI)Exit 0 valid · 2 missing-sig · 1 invalidExit 0 valid · 1 missing OR invalid (stderr: no JACS signature found)
jacs-compat verify-image (CLI)Same as aboveSame as above
Python (jacs.verify_text, jacs.verify_image)Returns result with status == "missing_signature"Raises MissingSignatureError
Node (jacs.verifyText, jacs.verifyImage)Returns result with status === "missing_signature"Promise rejects with MissingSignature-bearing message
Go (jacs.VerifyText, jacs.VerifyImage)Returns Status == "missing_signature"Returns error matching errors.Is(err, jacs.ErrMissingSignature)
Rust (jacs::simple::advanced)Successful result with a missing-signature variant/statusErr(JacsError::MissingSignature)

Pre-existing verify surfaces (jacs-compat verify, jacs-compat document verify, jacs.verify) are unaffected — they keep their existing exit codes and return shapes.

For hands-on examples see Inline Text Signatures and Image and Media Signatures.

Portable CLI

cargo install jacs-cli --locked
jacs verify --public-identity trusted-public-identity.json --input signed-document.json

Obtain trusted-public-identity.json independently of the untrusted document. The portable CLI uses explicit --input and --public-identity arguments; it does not use the native config or remote/key-directory options below.

Install cargo install jacs-cli-compat --locked for the extended native commands.

CLI: jacs-compat verify

The fastest way to verify a document from the command line. No config file, no agent setup.

# Verify a local file
jacs-compat verify signed-document.json

# Verify with JSON output (for scripting)
jacs-compat verify signed-document.json --json

# Verify a remote document by URL
jacs-compat verify --remote https://example.com/signed-doc.json

# Specify a directory containing public keys
jacs-compat verify signed-document.json --key-dir ./trusted-keys/

Output on success:

Status:    VALID
Signer:    550e8400-e29b-41d4-a716-446655440000
Signed at: 2026-02-10T12:00:00Z

JSON output (--json):

{
  "valid": true,
  "signerId": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2026-02-10T12:00:00Z"
}

The exit code is 0 for valid, 1 for invalid or error. Use this in CI/CD pipelines:

if jacs-compat verify artifact.json --json; then
  echo "Artifact is authentic"
else
  echo "Verification failed" >&2
  exit 1
fi

If a jacs.config.json and agent keys exist in the current directory, the CLI uses them automatically. Otherwise, it creates a temporary ephemeral verifier internally.

Python

With an agent loaded

import jacs.simple as jacs

jacs.load("./jacs.config.json")

result = jacs.verify(signed_json)
if result.valid:
    print(f"Signed by: {result.signer_id}")
else:
    print(f"Errors: {result.errors}")

Without an agent (standalone)

import jacs.simple as jacs

result = jacs.verify_standalone(
    signed_json,
    key_resolution="local",
    key_directory="./trusted-keys/"
)
print(f"Valid: {result.valid}, Signer: {result.signer_id}")

verify_standalone does not use a global agent. Pass the key resolution strategy and directories explicitly.

Verify by document ID

If the document is in local storage and you know its ID:

result = jacs.verify_by_id("550e8400-e29b-41d4:1")

Node.js

With an agent loaded

import * as jacs from '@hai.ai/jacs/simple';

await jacs.load('./jacs.config.json');

const result = await jacs.verify(signedJson);
console.log(`Valid: ${result.valid}, Signer: ${result.signerId}`);

Without an agent (standalone)

import { verifyStandalone } from '@hai.ai/jacs/simple';

const result = verifyStandalone(signedJson, {
  keyResolution: 'local',
  keyDirectory: './trusted-keys/',
});
console.log(`Valid: ${result.valid}, Signer: ${result.signerId}`);

Verify by document ID

const result = await jacs.verifyById('550e8400-e29b-41d4:1');

DNS Verification

DNS verification checks that an agent's public key hash matches a DNS TXT record published at _v1.agent.jacs.<domain>. This provides a decentralized trust anchor: anyone can look up the agent's expected key fingerprint via DNS without contacting a central server.

Publishing a DNS record

jacs-compat agent dns --domain example.com --provider plain

This outputs the TXT record to add to your DNS zone. Provider options: plain, aws, azure, cloudflare.

Looking up an agent by domain

jacs-compat agent lookup example.com

This inspection command fetches and prints https://example.com/.well-known/jacs-pubkey.json and the DNS TXT record at _v1.agent.jacs.example.com. It does not compare the fetched key against the TXT fingerprint or establish trust. Use the verification APIs and an independently selected key for that decision.

CLI verification with DNS

# Require DNS validation (fail if no DNS record)
jacs-compat agent verify --require-dns

# Require strict DNSSEC validation
jacs-compat agent verify --require-strict-dns

For full DNS setup instructions, see DNS-Based Verification and DNS Trust Anchoring.

Cross-Language Verification

JACS signatures are language-agnostic. A document signed by a Rust agent verifies identically in Python and Node.js, and vice versa. This holds for both Ed25519 and post-quantum (ML-DSA-87/pq2025) algorithms.

Legacy-v1 signatures that lack signatureContentVersion are denied by default because their signer, timestamp, nonce, and algorithm metadata was never covered by the signature. Use the migration API to re-sign them as v2. For a narrowly audited archive workflow, JACS_ALLOW_LEGACY_SIGNATURE_CONTENT=true enables payload-only compatibility; verification results deliberately leave signer, agent-version, and timestamp fields empty. Do not use that mode for authorization or identity attribution. The historical JACS_REJECT_LEGACY_SIGNATURE_CONTENT=true setting remains supported and takes precedence.

This is tested on every commit: current v2 fixtures verify by default across bindings, while committed v1 fixtures prove both default rejection and explicit payload-only compatibility. Each binding also countersigns the fixture with a different algorithm, proving round-trip interoperability.

Test sources:

  • Rust fixture generator: jacs/tests/cross_language/mod.rs
  • Python consumer: jacspy/tests/test_cross_language.py
  • Node.js consumer: jacsnpm/test/cross-language.test.js

Key Resolution Order

Native document verification uses JACS_KEY_RESOLUTION (environment only), defaulting to local,registry. Local storage supplies cached/trusted public keys; registry lookup requires the relevant network policy. Adding dns, with DNS and remote key fetch allowed and a configured or signed publisher domain, fetches the exact signed version's key over HTTPS and pins it to the TXT record's raw-key fingerprint. Discovered keys do not enroll a trusted identity. See the key discovery guide, DNS verification and trust enrollment.