insumer-verify 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # insumer-verify
2
2
 
3
- Reference verifier for [InsumerAPI](https://insumermodel.com/developers/) attestations. Validates ECDSA P-256 signatures, condition hashes, block freshness, and attestation expiry using the Web Crypto API. Zero runtime dependencies. Works in Node.js 18+ and modern browsers.
3
+ Client-side verifier for [InsumerAPI](https://insumermodel.com/developers/) attestations. Validates ECDSA P-256 signatures, condition hashes, block freshness, and attestation expiry. Zero runtime dependencies. Web Crypto API. Node.js 18+ and modern browsers.
4
+
5
+ **In production:** [DJD Agent Score](https://github.com/jacobsd32-cpu/djdagentscore) (Coinbase x402 ecosystem) uses insumer-verify for client-side cryptographic verification in their AI agent wallet trust scoring pipeline. [Case study](https://insumermodel.com/blog/djd-agent-score-insumer-api-integration.html).
6
+
7
+ Part of the InsumerAPI ecosystem: [REST API](https://insumermodel.com/developers/) (17 endpoints, 31 chains) | [MCP server](https://www.npmjs.com/package/mcp-server-insumer) (npm) | [LangChain](https://pypi.org/project/langchain-insumer/) (PyPI) | [OpenAI GPT](https://chatgpt.com/g/g-699c5e43ce2481918b3f1e7f144c8a49-insumerapi-verify) (GPT Store)
4
8
 
5
9
  ## Install
6
10
 
@@ -91,6 +95,18 @@ const result = await verifyAttestation(apiResponse, { maxAge: 120 });
91
95
 
92
96
  Results without `blockTimestamp` (Covalent and Solana chains) are skipped, not treated as failures.
93
97
 
98
+ ### JWKS key discovery
99
+
100
+ By default, insumer-verify uses the hardcoded InsumerAPI public key. You can opt in to dynamic key discovery via the JWKS endpoint:
101
+
102
+ ```typescript
103
+ const result = await verifyAttestation(apiResponse, {
104
+ jwksUrl: "https://insumermodel.com/.well-known/jwks.json"
105
+ });
106
+ ```
107
+
108
+ When `jwksUrl` is set, the library fetches the JWKS, matches the key by `kid` from the attestation response, and uses it for signature verification. This enables automatic key rotation without library updates.
109
+
94
110
  ## API
95
111
 
96
112
  ### `verifyAttestation(response, options?)`
@@ -99,6 +115,7 @@ Results without `blockTimestamp` (Covalent and Solana chains) are skipped, not t
99
115
  |-----------|------|-------------|
100
116
  | `response` | `unknown` | Full InsumerAPI response (must contain `data.attestation` and `data.sig`) |
101
117
  | `options.maxAge` | `number` | Optional max age in seconds for block freshness check |
118
+ | `options.jwksUrl` | `string` | Optional JWKS URL for dynamic key discovery (e.g. `https://insumermodel.com/.well-known/jwks.json`) |
102
119
 
103
120
  Returns `Promise<VerifyResult>`:
104
121
 
package/build/index.d.ts CHANGED
@@ -23,6 +23,8 @@ export interface CheckResult {
23
23
  export interface VerifyOptions {
24
24
  /** Maximum acceptable age of blockTimestamp in seconds. Results without blockTimestamp are skipped. */
25
25
  maxAge?: number;
26
+ /** JWKS URL for dynamic key discovery. When set, fetches the signing key from this URL instead of using the hardcoded key. Example: "https://insumermodel.com/.well-known/jwks.json" */
27
+ jwksUrl?: string;
26
28
  }
27
29
  /**
28
30
  * Verify an InsumerAPI attestation response.
package/build/index.js CHANGED
@@ -34,6 +34,21 @@ function bytesToHex(bytes) {
34
34
  }
35
35
  return hex;
36
36
  }
37
+ async function fetchJwksKey(jwksUrl, kid) {
38
+ const res = await fetch(jwksUrl);
39
+ if (!res.ok) {
40
+ throw new Error(`JWKS fetch failed: ${res.status} ${res.statusText}`);
41
+ }
42
+ const jwks = (await res.json());
43
+ if (!jwks.keys || !Array.isArray(jwks.keys) || jwks.keys.length === 0) {
44
+ throw new Error("JWKS response contains no keys");
45
+ }
46
+ // Match by kid if provided, otherwise use first key
47
+ const key = kid
48
+ ? jwks.keys.find((k) => k.kid === kid) || jwks.keys[0]
49
+ : jwks.keys[0];
50
+ return { kty: key.kty, crv: key.crv, x: key.x, y: key.y };
51
+ }
37
52
  function parseResponse(response) {
38
53
  const obj = response;
39
54
  const data = obj?.data;
@@ -63,15 +78,16 @@ function parseResponse(response) {
63
78
  if (typeof attestation.expiresAt !== "string") {
64
79
  throw new Error("Invalid response: missing attestation.expiresAt");
65
80
  }
66
- return { data: { attestation, sig } };
81
+ const kid = data.kid;
82
+ return { data: { attestation, sig, kid } };
67
83
  }
68
84
  // ── Verification checks ───────────────────────────────────────────
69
- async function checkSignature(attestation, sig) {
85
+ async function checkSignature(attestation, sig, keyJwk) {
70
86
  if (!subtle) {
71
87
  return { passed: false, reason: "Web Crypto API not available" };
72
88
  }
73
89
  try {
74
- const key = await subtle.importKey("jwk", PUBLIC_KEY_JWK, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
90
+ const key = await subtle.importKey("jwk", keyJwk || PUBLIC_KEY_JWK, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
75
91
  // Reconstruct the exact payload that was signed server-side.
76
92
  // The Cloud Function signs: JSON.stringify({ id, pass, results, attestedAt })
77
93
  const payload = JSON.stringify({
@@ -167,9 +183,27 @@ function checkExpiry(attestation) {
167
183
  */
168
184
  export async function verifyAttestation(response, options) {
169
185
  const parsed = parseResponse(response);
170
- const { attestation, sig } = parsed.data;
186
+ const { attestation, sig, kid } = parsed.data;
187
+ // If jwksUrl is provided, fetch the signing key dynamically
188
+ let keyJwk;
189
+ if (options?.jwksUrl) {
190
+ try {
191
+ keyJwk = await fetchJwksKey(options.jwksUrl, kid);
192
+ }
193
+ catch (e) {
194
+ return {
195
+ valid: false,
196
+ checks: {
197
+ signature: { passed: false, reason: `JWKS fetch error: ${e.message}` },
198
+ conditionHashes: { passed: false, reason: "Skipped (JWKS fetch failed)" },
199
+ freshness: { passed: false, reason: "Skipped (JWKS fetch failed)" },
200
+ expiry: { passed: false, reason: "Skipped (JWKS fetch failed)" },
201
+ },
202
+ };
203
+ }
204
+ }
171
205
  const [signature, conditionHashes, freshness, expiry] = await Promise.all([
172
- checkSignature(attestation, sig),
206
+ checkSignature(attestation, sig, keyJwk),
173
207
  checkConditionHashes(attestation.results),
174
208
  Promise.resolve(checkFreshness(attestation.results, options?.maxAge)),
175
209
  Promise.resolve(checkExpiry(attestation)),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "insumer-verify",
3
- "version": "1.0.1",
4
- "description": "Reference verifier for InsumerAPI attestations. Validates ECDSA signatures, condition hashes, block freshness, and expiry. Zero dependencies.",
3
+ "version": "1.1.1",
4
+ "description": "Client-side verifier for InsumerAPI attestations. ECDSA P-256 signatures, condition hashes, block freshness, expiry. Zero dependencies. Used by DJD Agent Score (Coinbase x402).",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
7
7
  "types": "build/index.d.ts",
@@ -26,7 +26,11 @@
26
26
  "verification",
27
27
  "blockchain",
28
28
  "on-chain",
29
- "web-crypto"
29
+ "web-crypto",
30
+ "ai-agents",
31
+ "token-gating",
32
+ "merkle-proof",
33
+ "agent-trust"
30
34
  ],
31
35
  "author": "Douglas Borthwick",
32
36
  "license": "MIT",