@kya-brasil/agent-tools 0.4.1 → 0.5.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 +17 -7
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js +9 -5
- package/dist/base64url.d.ts +4 -0
- package/dist/base64url.js +14 -0
- package/dist/index.d.ts +61 -56
- package/dist/index.js +36 -16
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ Node 18+ (global `fetch`, WebCrypto). ESM.
|
|
|
19
19
|
## Verifier: you receive an action from someone else's agent
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
|
-
import { createKyaClient, verifyAttestation } from "@kya-brasil/agent-tools";
|
|
22
|
+
import { createKyaClient, verifyAttestation, verifyCredentialOffline, companyText, signWithECnpj } from "@kya-brasil/agent-tools";
|
|
23
23
|
|
|
24
24
|
const kya = createKyaClient({ privateKey: process.env.KYA_PAYER_PK as `0x${string}` }); // wallet optional
|
|
25
25
|
|
|
@@ -33,14 +33,25 @@ if (!verdict.allowed) reject(verdict.reason);
|
|
|
33
33
|
|
|
34
34
|
await verifyAttestation(verdict); // local Ed25519 check of the issuer's signature
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
// max_per_day: pass your own spent_today; the issuer does not record acts.
|
|
37
|
+
|
|
38
|
+
const offline = await verifyCredentialOffline(credential); // issuer down: signature + expires_at only,
|
|
39
|
+
// live_status "unknown" (your risk until expires_at)
|
|
39
40
|
```
|
|
40
41
|
|
|
41
42
|
Also check that the key that signed the agent's request is `v.agent.id`; the credential_id alone proves nothing.
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
Company side, after issuance: see every agent in your name and get signed events (issued, renewed, suspended,
|
|
45
|
+
reinstated, revoked) at your webhook. Both requests are signed with the e-CNPJ:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const text = companyText("webhook", cnpj, { url: "https://acme.com.br/kya-events" });
|
|
49
|
+
await kya.setCompanyWebhook(cnpj, { signed_text: text, ecnpj_signature: await signWithECnpj(material, text) });
|
|
50
|
+
const inv = await kya.companyAgents(cnpj, { signed_text: t2, ecnpj_signature: s2 }); // t2 = companyText("agents", cnpj)
|
|
51
|
+
// on your webhook: (await verifyAttestation(body)).valid before trusting the event
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Agent frameworks (verifier tools `kya_verify`, `kya_check_action`, `kya_verify_paid`):
|
|
44
55
|
|
|
45
56
|
```ts
|
|
46
57
|
import { toOpenAI, toVercelAI, toLangChain, toAnthropic } from "@kya-brasil/agent-tools";
|
|
@@ -127,9 +138,8 @@ Renew every 90 days (`kya.renew` then `kya.renewProve`), revoke at once (`kya.re
|
|
|
127
138
|
|
|
128
139
|
| Call | Price |
|
|
129
140
|
|---|---|
|
|
130
|
-
| verify, verifyByKey, prove, renewProve, revoke, revocations | free |
|
|
141
|
+
| verify, verifyByKey, prove, renewProve, revoke, revocations, companyAgents, setCompanyWebhook | free |
|
|
131
142
|
| verified (signed receipt, no limit), allowed (signed verdict) | 0.01 |
|
|
132
|
-
| registerAct (signed receipt of what the agent did) | 0.02 |
|
|
133
143
|
| renew | 0.10 |
|
|
134
144
|
| register | 0.50 |
|
|
135
145
|
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ActionQuery, type AgentKey, type CredentialStatus, type KyaClient, type Mandate, type Verdict, type Verification, type KyaOptions } from "./index";
|
|
1
|
+
import { type ActionQuery, type AgentKey, type CredentialStatus, type KyaClient, type Mandate, type Verdict, type Verification, type KyaOptions } from "./index.js";
|
|
2
2
|
export declare const KYA_HEADERS: {
|
|
3
3
|
readonly credential: "kya-credential";
|
|
4
4
|
readonly created: "kya-created";
|
|
@@ -81,7 +81,7 @@ export interface LocalVerdict {
|
|
|
81
81
|
/**
|
|
82
82
|
* Local evaluation of the reserved mandate fields: actions, max_per_operation, currencies, counterparties
|
|
83
83
|
* (with `*.` wildcards), valid_from / valid_until. `max_per_day` is listed as not evaluated: only the issuer
|
|
84
|
-
* knows
|
|
84
|
+
* knows nothing of it either: pass your own spent_today to ctx.allowed.
|
|
85
85
|
*/
|
|
86
86
|
export declare function checkMandate(mandate: Mandate, q: ActionQuery, now?: Date): LocalVerdict;
|
|
87
87
|
export interface KyaAuth {
|
package/dist/auth.js
CHANGED
|
@@ -27,12 +27,13 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { verifyMessage } from "viem";
|
|
29
29
|
import { privateKeyToAccount } from "viem/accounts";
|
|
30
|
-
import { createKyaClient } from "./index";
|
|
30
|
+
import { createKyaClient } from "./index.js";
|
|
31
|
+
import { fromBase64Url, toBase64Url } from "./base64url.js";
|
|
31
32
|
export const KYA_HEADERS = { credential: "kya-credential", created: "kya-created", expires: "kya-expires", signature: "kya-signature", content: "kya-content-sha256" };
|
|
32
33
|
const enc = new TextEncoder();
|
|
33
34
|
const hex = (buf) => [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
34
|
-
const b64u =
|
|
35
|
-
const unb64u =
|
|
35
|
+
const b64u = toBase64Url;
|
|
36
|
+
const unb64u = fromBase64Url;
|
|
36
37
|
export async function sha256Hex(data) {
|
|
37
38
|
const bytes = typeof data === "string" ? enc.encode(data) : data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
38
39
|
return hex(await crypto.subtle.digest("SHA-256", bytes));
|
|
@@ -80,12 +81,15 @@ function normalizeCounterparty(c) {
|
|
|
80
81
|
return digits;
|
|
81
82
|
if (/^0x[0-9a-fA-F]{40}$/.test(t))
|
|
82
83
|
return t.toLowerCase();
|
|
83
|
-
|
|
84
|
+
// Domain or URL: drop the scheme, then everything from the first "/" (indexOf, no backtracking regex).
|
|
85
|
+
const host = t.toLowerCase().replace(/^https?:\/\//, "");
|
|
86
|
+
const slash = host.indexOf("/");
|
|
87
|
+
return slash === -1 ? host : host.slice(0, slash);
|
|
84
88
|
}
|
|
85
89
|
/**
|
|
86
90
|
* Local evaluation of the reserved mandate fields: actions, max_per_operation, currencies, counterparties
|
|
87
91
|
* (with `*.` wildcards), valid_from / valid_until. `max_per_day` is listed as not evaluated: only the issuer
|
|
88
|
-
* knows
|
|
92
|
+
* knows nothing of it either: pass your own spent_today to ctx.allowed.
|
|
89
93
|
*/
|
|
90
94
|
export function checkMandate(mandate, q, now = new Date()) {
|
|
91
95
|
const checks = [];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Base64url without padding (RFC 4648 §5), for agent signatures and keys. Linear time, no regex. */
|
|
2
|
+
export declare function stripPadding(s: string): string;
|
|
3
|
+
export declare function toBase64Url(bytes: Uint8Array): string;
|
|
4
|
+
export declare function fromBase64Url(s: string): Uint8Array<ArrayBuffer>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Base64url without padding (RFC 4648 §5), for agent signatures and keys. Linear time, no regex. */
|
|
2
|
+
export function stripPadding(s) {
|
|
3
|
+
let end = s.length;
|
|
4
|
+
while (end > 0 && s[end - 1] === "=")
|
|
5
|
+
end--;
|
|
6
|
+
return s.slice(0, end);
|
|
7
|
+
}
|
|
8
|
+
export function toBase64Url(bytes) {
|
|
9
|
+
return stripPadding(btoa(String.fromCodePoint(...bytes)).replaceAll("+", "-").replaceAll("/", "_"));
|
|
10
|
+
}
|
|
11
|
+
export function fromBase64Url(s) {
|
|
12
|
+
const b64 = s.replaceAll("-", "+").replaceAll("_", "/");
|
|
13
|
+
return Uint8Array.from(atob(b64.padEnd(Math.ceil(b64.length / 4) * 4, "=")), (c) => c.codePointAt(0) ?? 0);
|
|
14
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -132,44 +132,30 @@ export interface Proof {
|
|
|
132
132
|
agent_signature: string;
|
|
133
133
|
ecnpj_signature: string;
|
|
134
134
|
}
|
|
135
|
-
export
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
registrant?: string;
|
|
144
|
-
agent_signature?: string;
|
|
135
|
+
export type CompanyEvent = "credential.issued" | "credential.renewed" | "credential.suspended" | "credential.reinstated" | "credential.revoked";
|
|
136
|
+
/** Body POSTed to the company's webhook. Check it with verifyAttestation(event) before trusting it. */
|
|
137
|
+
export interface CompanyWebhookEvent {
|
|
138
|
+
event: CompanyEvent;
|
|
139
|
+
cnpj: string;
|
|
140
|
+
at: string;
|
|
141
|
+
data: Record<string, unknown>;
|
|
142
|
+
attestation?: Attestation;
|
|
145
143
|
}
|
|
146
|
-
export interface
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
144
|
+
export interface CompanyInventory {
|
|
145
|
+
cnpj: string;
|
|
146
|
+
cnpj_formatted: string;
|
|
147
|
+
count: number;
|
|
148
|
+
agents: {
|
|
149
|
+
credential_id: string;
|
|
150
|
+
agent: AgentKey;
|
|
151
|
+
status: CredentialStatus | "pending";
|
|
152
|
+
status_reason: string | null;
|
|
153
|
+
mandate: Mandate;
|
|
154
|
+
certificate_sha256: string | null;
|
|
155
|
+
created_at: string;
|
|
156
|
+
issued_at: string | null;
|
|
157
|
+
expires_at: string | null;
|
|
159
158
|
}[];
|
|
160
|
-
not_evaluated: string[];
|
|
161
|
-
spent_today_registered_before: {
|
|
162
|
-
currency: string;
|
|
163
|
-
amount: number;
|
|
164
|
-
} | null;
|
|
165
|
-
spent_today_registered_after: {
|
|
166
|
-
currency: string;
|
|
167
|
-
amount: number;
|
|
168
|
-
} | null;
|
|
169
|
-
registered_at: string;
|
|
170
|
-
receipt_url: string;
|
|
171
|
-
attestation?: Attestation;
|
|
172
|
-
[k: string]: unknown;
|
|
173
159
|
}
|
|
174
160
|
export declare class KyaError extends Error {
|
|
175
161
|
readonly code: string;
|
|
@@ -224,26 +210,20 @@ export interface KyaClient {
|
|
|
224
210
|
credential_id: string;
|
|
225
211
|
status: "revoked";
|
|
226
212
|
}>;
|
|
227
|
-
/**
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
agent: AgentKey;
|
|
235
|
-
text: string;
|
|
236
|
-
}>;
|
|
237
|
-
/** Free, company only: the trail of an agent's acts, request signed with the e-CNPJ over actsExportText(id). */
|
|
238
|
-
exportActs(credentialId: string, input: {
|
|
213
|
+
/** Free, company only: every credential registered for the CNPJ, any status. signed_text = companyText("agents", cnpj), signed with the e-CNPJ. */
|
|
214
|
+
companyAgents(cnpj: string, input: {
|
|
215
|
+
signed_text: string;
|
|
216
|
+
ecnpj_signature: string;
|
|
217
|
+
}): Promise<CompanyInventory>;
|
|
218
|
+
/** Free, company only: sets (or removes, with "-") the https URL that receives signed CompanyWebhookEvent. signed_text = companyText("webhook", cnpj, { url }). */
|
|
219
|
+
setCompanyWebhook(cnpj: string, input: {
|
|
239
220
|
signed_text: string;
|
|
240
221
|
ecnpj_signature: string;
|
|
241
|
-
since?: string;
|
|
242
222
|
}): Promise<{
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
223
|
+
cnpj: string;
|
|
224
|
+
webhook: string | null;
|
|
225
|
+
signed_at: string;
|
|
226
|
+
events: CompanyEvent[];
|
|
247
227
|
}>;
|
|
248
228
|
/** Free: credentials revoked since a date (default 30 days). */
|
|
249
229
|
revocations(since?: string): Promise<{
|
|
@@ -317,8 +297,14 @@ export interface ECnpjMaterial {
|
|
|
317
297
|
* Works with A1 certificates (file). A3 (token or smart card) must sign through the vendor's software.
|
|
318
298
|
*/
|
|
319
299
|
export declare function signWithECnpj(material: ECnpjMaterial, text: string): Promise<string>;
|
|
320
|
-
/**
|
|
321
|
-
|
|
300
|
+
/**
|
|
301
|
+
* The text the company signs for its inventory or its webhook. The issuer accepts it for 10 minutes after `at`
|
|
302
|
+
* (and a webhook change only if newer than the last accepted one). For the webhook, url "-" removes it.
|
|
303
|
+
*/
|
|
304
|
+
export declare function companyText(verb: "agents" | "webhook", cnpj: string, opts?: {
|
|
305
|
+
url?: string;
|
|
306
|
+
at?: Date;
|
|
307
|
+
}): string;
|
|
322
308
|
/** The text the company signs to revoke a credential. */
|
|
323
309
|
export declare function revocationText(credentialId: string, nonce?: string): string;
|
|
324
310
|
export interface VerifyResult {
|
|
@@ -338,4 +324,23 @@ export declare function verifyAttestation(doc: Record<string, unknown>, opts?: {
|
|
|
338
324
|
publicJwk?: JsonWebKey;
|
|
339
325
|
fetch?: typeof fetch;
|
|
340
326
|
}): Promise<VerifyResult>;
|
|
341
|
-
export
|
|
327
|
+
export interface OfflineCheck {
|
|
328
|
+
/** Issuer signature over the credential: proves what was issued and that it was not modified. */
|
|
329
|
+
signature: VerifyResult;
|
|
330
|
+
/** expires_at is in the future (the credential's own deadline). */
|
|
331
|
+
within_validity: boolean;
|
|
332
|
+
/**
|
|
333
|
+
* Always "unknown": a signature cannot say whether the credential was suspended or revoked after it was issued.
|
|
334
|
+
* Check verify() (or /v1/revocations) when you can; accepting an offline-only check is your risk until expires_at.
|
|
335
|
+
*/
|
|
336
|
+
live_status: "unknown";
|
|
337
|
+
usable_offline: boolean;
|
|
338
|
+
}
|
|
339
|
+
/** Checks a credential with no call to the issuer's API (only the published key, unless publicJwk is given). */
|
|
340
|
+
export declare function verifyCredentialOffline(credential: Credential, opts?: {
|
|
341
|
+
baseUrl?: string;
|
|
342
|
+
publicJwk?: JsonWebKey;
|
|
343
|
+
fetch?: typeof fetch;
|
|
344
|
+
now?: Date;
|
|
345
|
+
}): Promise<OfflineCheck>;
|
|
346
|
+
export * from "./auth.js";
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import { toClientEvmSigner } from "@x402/evm";
|
|
|
21
21
|
import { wrapFetchWithPayment } from "@x402/fetch";
|
|
22
22
|
import { privateKeyToAccount } from "viem/accounts";
|
|
23
23
|
import * as pkijs from "pkijs";
|
|
24
|
+
import { toBase64Url } from "./base64url.js";
|
|
24
25
|
export const DEFAULT_BASE_URL = "https://kya-br-agents.marcelfons.workers.dev";
|
|
25
26
|
export class KyaError extends Error {
|
|
26
27
|
code;
|
|
@@ -80,10 +81,8 @@ export function createKyaClient(opts = {}) {
|
|
|
80
81
|
renewProve: (id, proof) => call("POST", `/v1/agents/${encodeURIComponent(id)}/renew/prove`, proof),
|
|
81
82
|
revoke: (id, input) => call("POST", `/v1/agents/${encodeURIComponent(id)}/revoke`, input),
|
|
82
83
|
revocations: (since) => call("GET", `/v1/revocations${q({ since })}`),
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
actText: (id, act) => call("POST", `/v1/agents/${encodeURIComponent(id)}/acts/text`, act),
|
|
86
|
-
exportActs: (id, input) => call("POST", `/v1/agents/${encodeURIComponent(id)}/acts/export`, input),
|
|
84
|
+
companyAgents: (cnpj, input) => call("POST", `/v1/companies/${encodeURIComponent(cnpj)}/agents`, input),
|
|
85
|
+
setCompanyWebhook: (cnpj, input) => call("POST", `/v1/companies/${encodeURIComponent(cnpj)}/webhook`, input),
|
|
87
86
|
tools: [],
|
|
88
87
|
execute: async (name, args) => {
|
|
89
88
|
const t = client.tools.find((x) => x.name === name);
|
|
@@ -106,12 +105,6 @@ export function createKyaClient(opts = {}) {
|
|
|
106
105
|
parameters: { type: "object", properties: { credential_id: ID, action: { type: "string" }, amount: { type: "number" }, currency: { type: "string" }, counterparty: { type: "string", description: "Your CNPJ, domain or wallet address" }, spent_today: { type: "number" } }, required: ["credential_id"], additionalProperties: false },
|
|
107
106
|
execute: async (a) => client.allowed(String(a.credential_id), { action: a.action, amount: a.amount, currency: a.currency, counterparty: a.counterparty, spent_today: a.spent_today })
|
|
108
107
|
},
|
|
109
|
-
{
|
|
110
|
-
name: "kya_register_act", priceUsd: 0.02,
|
|
111
|
-
description: "Register an action you accepted from an agent ($0.02): the issuer re-checks the mandate including everything registered today by anyone (so daily ceilings hold across counterparties) and returns a signed receipt, your proof of what the agent could and did do. Denied acts are recorded too.",
|
|
112
|
-
parameters: { type: "object", properties: { credential_id: ID, action: { type: "string" }, amount: { type: "number" }, currency: { type: "string" }, counterparty: { type: "string", description: "You: CNPJ, domain or wallet" }, reference: { type: "string" }, content_sha256: { type: "string" } }, required: ["credential_id", "action"], additionalProperties: false },
|
|
113
|
-
execute: async (a) => { const { credential_id, ...act } = a; return client.registerAct(String(credential_id), act); }
|
|
114
|
-
},
|
|
115
108
|
{
|
|
116
109
|
name: "kya_verify_paid", priceUsd: 0.01,
|
|
117
110
|
description: "Same as kya_verify without the daily limit, plus a signed receipt of the verification ($0.01).",
|
|
@@ -143,7 +136,7 @@ export async function signChallengeWithEvmKey(privateKey, text) {
|
|
|
143
136
|
export async function signChallengeWithEd25519(privateKey, text) {
|
|
144
137
|
const key = typeof privateKey === "string" ? await crypto.subtle.importKey("pkcs8", Uint8Array.from(atob(privateKey), (c) => c.charCodeAt(0)), { name: "Ed25519" }, false, ["sign"]) : privateKey;
|
|
145
138
|
const sig = new Uint8Array(await crypto.subtle.sign({ name: "Ed25519" }, key, new TextEncoder().encode(text)));
|
|
146
|
-
return
|
|
139
|
+
return toBase64Url(sig);
|
|
147
140
|
}
|
|
148
141
|
const pemToDer = (pem) => Uint8Array.from(atob(pem.replace(/-----[^-]+-----/g, "").replace(/\s+/g, "")), (c) => c.charCodeAt(0)).buffer;
|
|
149
142
|
const pemBlocks = (pem) => [...pem.matchAll(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g)].map((m) => m[0]);
|
|
@@ -173,22 +166,43 @@ export async function signWithECnpj(material, text) {
|
|
|
173
166
|
s += String.fromCharCode(b);
|
|
174
167
|
return btoa(s);
|
|
175
168
|
}
|
|
176
|
-
/**
|
|
177
|
-
|
|
178
|
-
|
|
169
|
+
/**
|
|
170
|
+
* The text the company signs for its inventory or its webhook. The issuer accepts it for 10 minutes after `at`
|
|
171
|
+
* (and a webhook change only if newer than the last accepted one). For the webhook, url "-" removes it.
|
|
172
|
+
*/
|
|
173
|
+
export function companyText(verb, cnpj, opts = {}) {
|
|
174
|
+
const digits = cnpj.replace(/\D/g, "");
|
|
175
|
+
const extra = verb === "webhook" ? [`url: ${opts.url ?? "-"}`] : [];
|
|
176
|
+
return [`KYA Brasil: ${verb} ${digits}`, ...extra, `at: ${(opts.at ?? new Date()).toISOString()}`].join("\n");
|
|
179
177
|
}
|
|
180
178
|
/** The text the company signs to revoke a credential. */
|
|
181
179
|
export function revocationText(credentialId, nonce = crypto.randomUUID()) {
|
|
182
180
|
return `KYA Brasil: revoke ${credentialId}\n${nonce}`;
|
|
183
181
|
}
|
|
184
182
|
// --- Local verification of a credential or a signed answer (no network once you hold the key) ---
|
|
183
|
+
/**
|
|
184
|
+
* Key order of the canonical JSON: UTF-16 code units, exactly what a comparator-less sort() does. Not
|
|
185
|
+
* localeCompare: a locale-dependent order would change the signed bytes between machines and break every
|
|
186
|
+
* signature already issued.
|
|
187
|
+
*/
|
|
188
|
+
function byCodeUnit(a, b) {
|
|
189
|
+
if (a < b)
|
|
190
|
+
return -1;
|
|
191
|
+
if (a > b)
|
|
192
|
+
return 1;
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
185
195
|
function canonicalize(v) {
|
|
186
196
|
if (v === null || typeof v !== "object")
|
|
187
197
|
return JSON.stringify(v);
|
|
188
198
|
if (Array.isArray(v))
|
|
189
199
|
return `[${v.map(canonicalize).join(",")}]`;
|
|
190
200
|
const o = v;
|
|
191
|
-
|
|
201
|
+
const members = Object.keys(o)
|
|
202
|
+
.filter((k) => o[k] !== undefined)
|
|
203
|
+
.sort(byCodeUnit)
|
|
204
|
+
.map((k) => JSON.stringify(k) + ":" + canonicalize(o[k]));
|
|
205
|
+
return "{" + members.join(",") + "}";
|
|
192
206
|
}
|
|
193
207
|
/**
|
|
194
208
|
* Verifies the Ed25519 attestation of a credential or a signed answer: recomputes the SHA-256 of the
|
|
@@ -218,4 +232,10 @@ export async function verifyAttestation(doc, opts = {}) {
|
|
|
218
232
|
const ok = await crypto.subtle.verify({ name: "Ed25519" }, key, sig, new TextEncoder().encode(`${hex}\n${att.signed_at}`));
|
|
219
233
|
return ok ? { valid: true, key_id: att.key_id, signed_at: att.signed_at } : { valid: false, reason: "signature does not verify", key_id: att.key_id, signed_at: att.signed_at };
|
|
220
234
|
}
|
|
221
|
-
|
|
235
|
+
/** Checks a credential with no call to the issuer's API (only the published key, unless publicJwk is given). */
|
|
236
|
+
export async function verifyCredentialOffline(credential, opts = {}) {
|
|
237
|
+
const signature = await verifyAttestation(credential, opts);
|
|
238
|
+
const within_validity = Date.parse(credential.expires_at) > (opts.now ?? new Date()).getTime();
|
|
239
|
+
return { signature, within_validity, live_status: "unknown", usable_offline: signature.valid && within_validity && credential.status === "active" };
|
|
240
|
+
}
|
|
241
|
+
export * from "./auth.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kya-brasil/agent-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "KYA Brasil for AI agents: verify that an agent acts for a Brazilian company (e-CNPJ-signed credential, revocation and company registration checked daily), check an action against its mandate, or give your own agent a credential. Sign in with KYA middleware (Hono, Express, Workers) and request signing. x402 payments (USDC on Base) handled for you. Adapters for Vercel AI SDK, LangChain, OpenAI and Anthropic.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -58,12 +58,13 @@
|
|
|
58
58
|
"viem": "^2.57.5"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
+
"@kya-br/core": "workspace:*",
|
|
61
62
|
"@peculiar/asn1-schema": "^2.3.15",
|
|
62
63
|
"@peculiar/asn1-x509": "^2.3.15",
|
|
63
64
|
"@peculiar/x509": "^2.1.0",
|
|
65
|
+
"@types/node": "^22.20.5",
|
|
64
66
|
"reflect-metadata": "^0.2.2",
|
|
65
67
|
"typescript": "^5.9.3",
|
|
66
|
-
"vitest": "^
|
|
67
|
-
"@kya-br/core": "workspace:*"
|
|
68
|
+
"vitest": "^4.1.11"
|
|
68
69
|
}
|
|
69
70
|
}
|