Offline verification

Don't trust us.
Check it yourself.

A 5arz seal is an ES256 JWT signed by a public key. Fetch that key once, cache it, and every credential after that verifies on your own hardware — no API call, no account, no permission from us.

If we went dark tomorrow, every credential we ever issued would still verify. That isn't generosity. It's the only way the proof means anything.

Verification is public and free. You need an account to create credentials — never to check one.

What "offline" means here

Precision matters, so here is the exact claim.

What is true

You fetch the public key set once over HTTPS and cache it. Every verification after that is a local elliptic-curve signature check. No network call, no round trip, no dependency on 5arz being up, and nothing you do is visible to us.

What is not true

It is not magic. The first key fetch needs the internet, and you should refresh the key set periodically to pick up rotations. A credential also carries an expiry — checking that is your job, and it is one line.

Why this is the whole product

Most verification vendors return a score, and the score is only worth what your trust in the vendor is worth. You cannot audit it, reproduce it, or check it after they are gone. We return a signature instead. A signature is either valid or it isn't, and anyone holding the public key can settle that question without asking anyone's permission — including ours.

Four steps, once

1

Fetch the key set

GET https://api.5arz.com/.well-known/jwks.json — public, CORS-open, no auth.

2

Cache it

Keep it in memory, on disk, or committed to your repo. Refresh daily to pick up rotation. Keys are identified by kid, so old and new coexist safely.

3

Match the header's kid to a key

Decode the JWT header, find the key with that kid. The live signing key today is 5arz-oracle-2.

4

Verify the ES256 signature and read the claims

Standard P-256 / SHA-256. Every language has it in the standard library or one dependency. Then check exp and read what the credential actually asserts.

Verify one right now

This runs entirely in your browser using the WebCrypto API. Open your network tab — after the key set loads, we make no further requests. Nothing you paste leaves this page.

Key set: not loaded yet.

Don't have a credential handy? The tamper button takes whatever is in the box, flips one character of the payload, and re-verifies — which is the more interesting demonstration anyway. A single changed byte breaks the signature.

The code

Complete and runnable. No 5arz SDK, because there isn't one and you shouldn't need one.

// npm i jose
import { createRemoteJWKSet, jwtVerify } from 'jose';

// Fetched once, then cached in-process and refreshed on rotation.
const JWKS = createRemoteJWKSet(
  new URL('https://api.5arz.com/.well-known/jwks.json'),
  { cacheMaxAge: 86400000 }   // 24h
);

export async function verifySeal(token) {
  const { payload, protectedHeader } = await jwtVerify(token, JWKS, {
    algorithms: ['ES256'],
    issuer: 'https://5arz.com'
  });
  // Signature is valid and exp/nbf were enforced by jwtVerify.
  return { ok: true, kid: protectedHeader.kid, claims: payload };
}
# pip install pyjwt[crypto] requests
import jwt, requests, time
from jwt import PyJWKClient

# PyJWKClient caches the key set and only refetches on an unknown kid.
_jwks = PyJWKClient("https://api.5arz.com/.well-known/jwks.json",
                    cache_keys=True, lifespan=86400)

def verify_seal(token: str) -> dict:
    key = _jwks.get_signing_key_from_jwt(token).key
    claims = jwt.decode(
        token, key,
        algorithms=["ES256"],
        issuer="https://5arz.com",
    )  # raises on bad signature or expiry
    return claims
// go get github.com/lestrrat-go/jwx/v2
package seal

import (
    "context"
    "time"
    "github.com/lestrrat-go/jwx/v2/jwk"
    "github.com/lestrrat-go/jwx/v2/jwt"
)

const jwksURL = "https://api.5arz.com/.well-known/jwks.json"

var cache = jwk.NewCache(context.Background())

func Init() { cache.Register(jwksURL, jwk.WithMinRefreshInterval(24*time.Hour)) }

func VerifySeal(ctx context.Context, token []byte) (jwt.Token, error) {
    set, err := cache.Get(ctx, jwksURL)   // served from cache
    if err != nil { return nil, err }
    return jwt.Parse(token,
        jwt.WithKeySet(set),
        jwt.WithValidate(true),
        jwt.WithIssuer("https://5arz.com"),
    )
}
# Look at the key set — this is the only network call verification needs.
curl -s https://api.5arz.com/.well-known/jwks.json

# {
#   "keys": [{
#     "kty": "EC", "crv": "P-256",
#     "x": "amC_s6qLClaKsDqE5594am8QSc5GP6agTTbjbM_jRdM",
#     "y": "GYq97U3A7ZuBGJdGEnC_f6dq6HG4MS1RBuKRhJf1T2U",
#     "kid": "5arz-oracle-2", "alg": "ES256",
#     "use": "sig", "key_ops": ["verify"]
#   }]
# }

# Save it. From here on, verification is local:
curl -s https://api.5arz.com/.well-known/jwks.json > jwks.json

# Read a credential's claims without verifying (inspection only —
# NEVER trust these until the signature checks out):
echo "$JWT" | cut -d. -f2 | base64 -d 2>/dev/null | jq .
// No dependencies — WebCrypto is in every modern browser and in
// Node 16+ via globalThis.crypto. This is what the widget above runs.

const b64u = s => Uint8Array.from(
  atob(s.replace(/-/g,'+').replace(/_/g,'/')
        .padEnd(Math.ceil(s.length/4)*4,'=')),
  c => c.charCodeAt(0));

async function verify(token, jwks) {
  const [h, p, s] = token.split('.');
  const hdr = JSON.parse(new TextDecoder().decode(b64u(h)));
  const jwk = jwks.keys.find(k => k.kid === hdr.kid);
  if (!jwk) throw new Error('unknown kid: ' + hdr.kid);

  // IMPORTANT: strip use/key_ops before importKey. Chrome and Safari
  // reject a JWK whose key_ops does not exactly match the usages you
  // request, and our JWKS publishes both fields. This one line is the
  // difference between working and a DataError.
  const { use, key_ops, ...clean } = jwk;

  const key = await crypto.subtle.importKey(
    'jwk', clean, { name:'ECDSA', namedCurve:'P-256' }, false, ['verify']);

  return crypto.subtle.verify(
    { name:'ECDSA', hash:'SHA-256' }, key,
    b64u(s), new TextEncoder().encode(h + '.' + p));
}

That use / key_ops strip is the one thing that trips people up. We publish both fields because the JWKS spec allows it; WebCrypto is stricter than the spec. Every other language's library handles it for you.

What the claims mean

A valid signature tells you the credential is authentic and unmodified. It does not tell you what the credential says — that's in the claims, and you should read them.

ClaimWhat it asserts
issWho signed it. Should be https://5arz.com — pin this, don't just check the signature.
iat / expWhen it was issued and when it stops being valid. Enforce exp.
subThe subject the proof is about — a session, a task, or a piece of content.
testPresent and true on sandbox credentials. A test credential is not proof of anything. Reject it in production.

Two things a signature does not prove

It does not tell you who the person is. A 5arz seal asserts that a real human did a specific thing. It is deliberately not an identity document, and it carries no name, no biometric, and nothing you would have to store carefully.

It does not stop copying. If someone screen-records a sealed video, they have a copy. What they cannot do is produce a valid seal for it — which is what makes the theft provable rather than preventable. We think that distinction is worth being blunt about.

Why not just call an API?

You can — we have one. But here is what changes when verification is local.

 Vendor API checkOffline signature check
Works if the vendor is downNoYes
Works if the vendor is goneNoYes
Vendor sees what you verifyYesNo
LatencyNetwork round tripSub-millisecond, local
Rate limitedUsuallyNo
Costs money at scaleUsuallyFree, unlimited
Auditable by a third partyNoYes

The honest caveat: an offline check cannot know about a revocation issued after your key set was cached. If that matters for your use case, shorten your cache window or call the API. Most uses of a short-lived credential do not need it.

Free tier · no card

Verifying is free. Always was.

You never needed us to check a seal. You need an account to create them — two fields, no card, no sales call.