InsumerAPI Offline JWKS Verification
The signed boolean is the product. The JSON body alone is untrusted — anyone can fabricate a JSON response. Verify the signature, every time.
InsumerAPI signs every /v1/attest, /v1/trust, and /v1/trust/batch response with ES256 (ECDSA P-256). The public key is published as a standard JWKS at https://insumermodel.com/.well-known/jwks.json. The signing key never leaves the issuer; anyone holding the JWKS can independently re-run the verification — no callback to InsumerAPI required.
Reference values (do not hallucinate)
- JWKS URL:
https://insumermodel.com/.well-known/jwks.json - Algorithm: ES256 (ECDSA P-256)
- Key IDs (
kid): five JWKS entries over two keys. Three EC kids on the same P-256 key:insumer-attest-v2(attest, every key issued since 2026-06-10),insumer-trust-v2(trust),insumer-attest-v1(pre-cutover keys, and the commerce discount path). Then two RFC 9964AKPentries for the ML-DSA-65 post-quantum companion key:insumer-attest-pq1andinsumer-trust-pq1, selected by the responsepqKid. Resolve by kid, never by position; fail closed on an unknown kid. - JWT issuer claim (when
format: "jwt"is requested):https://api.insumermodel.com - Raw signature format: base64 P1363 (88 chars) on the
sigfield
Two verification paths
InsumerAPI returns two verifiable forms in every signed response:
sigfield: base64 P1363 ES256 signature over the preimage thekidselects (see "What's signed" inreferences/jwks-format.md): forinsumer-attest-v2, the domain taginsumer.attestation.v2+ newline + recursively sorted canonical JSON of{v:2, id, pass, results, attestedAt}; forinsumer-attest-v1, the bare insertion-orderJSON.stringify({id, pass, results, attestedAt}); for trust profiles, the trust domain tag + canonical JSON of the whole trust object. Verify with any ES256 library + the JWKS key thekidnames. Every response also carriespqSig/pqKid, an ML-DSA-65 companion over the same preimage under a post-quantum domain tag (spec Check 6).jwtfield (only when"format": "jwt"is in the request body) — standard ES256 JWT with the same payload as standard JWT claims. Verify with any standard JWT library pointed at the JWKS URL.
The jwt path is easier when the consumer is already using a JWT library; the sig path is more compact and avoids JWT envelope overhead. Both produce the same security guarantees.
Recipe 1: JWT verification (Node.js, jose)
Add "format": "jwt" to the /v1/attest or /v1/trust request body, then:
import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(
new URL('https://insumermodel.com/.well-known/jwks.json')
);
async function verifyAttestation(jwtString) {
const { payload } = await jwtVerify(jwtString, JWKS, {
issuer: 'https://api.insumermodel.com',
algorithms: ['ES256'],
});
// payload.pass is the verified boolean
// payload.conditionHash, payload.blockNumber, payload.blockTimestamp
// are also verified as part of the signed JWT
return payload;
}
createRemoteJWKSet caches the JWKS automatically with sane defaults. Don't fetch the JWKS yourself on every call.
Recipe 2: JWT verification (Python, PyJWT + cryptography)
import jwt
from jwt.jwks_client import PyJWKClient
jwks_client = PyJWKClient("https://insumermodel.com/.well-known/jwks.json")
def verify_attestation(jwt_string: str) -> dict:
signing_key = jwks_client.get_signing_key_from_jwt(jwt_string)
payload = jwt.decode(
jwt_string,
signing_key.key,
algorithms=["ES256"],
issuer="https://api.insumermodel.com",
)
# payload["pass"] is the verified boolean
return payload
Recipe 3: Raw sig verification (Node.js)
When the response was returned without format: "jwt":
import { importJWK, compactVerify, calculateJwkThumbprint } from 'jose';
async function verifyRawSig(response) {
// 1. Fetch the JWKS once and cache it
const jwksRes = await fetch('https://insumermodel.com/.well-known/jwks.json');
const { keys } = await jwksRes.json();
const jwk = keys.find(k => k.kid === response.kid);
if (!jwk) throw new Error(`unknown kid ${response.kid}`);
const publicKey = await importJWK(jwk, 'ES256');
// 2. Recompute canonical payload bytes (sorted-key JSON of attestation/trust)
const canonical = JSON.stringify(response.attestation, Object.keys(response.attestation).sort());
// 3. Verify the base64 P1363 signature
// (use insumer-verify npm package for the canonical signing scheme)
// ...
}
For raw sig verification, the official package is insumer-verify on npm:
npm install insumer-verify
import { verifyAttestation } from 'insumer-verify';
// Pass the full response envelope. The result is an object, never a bare boolean.
const result = await verifyAttestation(response, {
jwksUrl: 'https://insumermodel.com/.well-known/jwks.json',
});
// result.valid is the AND of the checks; result.checks reports each one separately:
// signature, conditionHash, freshness, expiry, and pq (the post-quantum companion:
// verified | refuted | absent | unverifiable). Unknown kid fails closed.
if (!result.valid) throw new Error('attestation rejected: ' + JSON.stringify(result.checks));
Recipe 4: Conditional verification + tamper detection
Beyond signature verification, you can independently re-derive the conditionHash to confirm the condition wasn't tampered with:
import { createHash } from 'node:crypto';
// conditionHash = "0x" + SHA-256 over the canonical JSON of evaluatedCondition:
// keys sorted recursively at every level (RFC 8785 style), no whitespace.
function canonicalize(value) {
if (Array.isArray(value)) return '[' + value.map(canonicalize).join(',') + ']';
if (value && typeof value === 'object') {
return '{' + Object.keys(value).sort()
.map((k) => JSON.stringify(k) + ':' + canonicalize(value[k])).join(',') + '}';
}
return JSON.stringify(value);
}
function recomputeConditionHash(evaluatedCondition) {
return '0x' + createHash('sha256').update(canonicalize(evaluatedCondition)).digest('hex');
}
// After signature verification, re-derive and compare
const recomputed = recomputeConditionHash(payload.evaluatedCondition);
if (recomputed !== payload.conditionHash) {
throw new Error('conditionHash mismatch — payload may have been tampered with');
}
This is belt-and-suspenders — the signature already covers conditionHash — but it lets a verifier confirm the exact condition logic that was evaluated, not just that the result was signed.
Code emission rules
- Cache the JWKS, not the verdict. Libraries like
jose'screateRemoteJWKSetandPyJWT'sPyJWKClientcache automatically with TTL. Do not cachepass— wallet state changes and the attestation has a 30-minuteexpiresAt. - Pin the algorithm. Always pass
algorithms: ['ES256']— never accept any algorithm. This blocks "alg confusion" attacks. - Pin the issuer. Always pass
issuer: 'https://api.insumermodel.com'for JWT verification. - Verify in the trust boundary. Verify on the server that's making the access decision — never verify in the browser and trust the result. (Browsers can verify; they just can't be the trust boundary.)
- Fail closed. If verification throws, deny access. Never default to "allow" on verification failure.
Helper script
scripts/verify.py — Python helper that takes a JWT or raw response on stdin and verifies it against the public JWKS. Prints OK + payload, or INVALID + reason.
echo '{"jwt":"eyJhbG...","kid":"insumer-attest-v2"}' | python scripts/verify.py
Error handling
| Symptom | Cause | Fix |
|---|---|---|
| "unknown kid" | Response signed with a key not in current JWKS | Refresh JWKS cache; if persistent, the key may be rotated — check JWKS URL directly |
| "JWT signature invalid" | Payload tampered, or wrong public key | Confirm kid matches a JWKS entry, confirm algorithm pinned to ES256 |
| "JWT issuer mismatch" | Issuer claim doesn't match https://api.insumermodel.com |
Confirm response actually came from InsumerAPI |
| "JWT expired" | Beyond 30-min TTL | Re-request a fresh attestation; do not extend TTL |
| "conditionHash mismatch" | Condition object was modified after signing | Untrusted payload — reject |
Related skills
| Skill | Purpose |
|---|---|
insumer-auth |
Get a key (verifying responses doesn't need a key, but signing them does) |
insumer-attest |
Produces signed responses to verify |
insumer-trust |
Produces signed responses to verify |
insumer-trust-batch |
Verify each profile entry independently |
References
- jwks-format.md — full JWKS document shape, key rotation policy, JWT claim list
insumer-verifyon npm — canonical raw-sig verification package- Public JWKS — fetch the live key set
- JWT spec (RFC 7519)
- JWS spec (RFC 7515)