@agent-score/commerce 1.3.3 → 1.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 +75 -29
- package/dist/core.js +1 -1
- package/dist/core.mjs +1 -1
- package/dist/discovery/index.js +1 -0
- package/dist/discovery/index.js.map +1 -1
- package/dist/discovery/index.mjs +1 -0
- package/dist/discovery/index.mjs.map +1 -1
- package/dist/identity/express.js +1 -1
- package/dist/identity/express.mjs +1 -1
- package/dist/identity/fastify.js +1 -1
- package/dist/identity/fastify.mjs +1 -1
- package/dist/identity/hono.js +1 -1
- package/dist/identity/hono.mjs +1 -1
- package/dist/identity/nextjs.js +1 -1
- package/dist/identity/nextjs.mjs +1 -1
- package/dist/identity/web.js +1 -1
- package/dist/identity/web.mjs +1 -1
- package/dist/index.d.mts +333 -84
- package/dist/index.d.ts +333 -84
- package/dist/index.js +395 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +376 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -4
package/dist/index.mjs
CHANGED
|
@@ -330,6 +330,13 @@ function readX402PaymentHeader(request) {
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
// src/identity/a2a.ts
|
|
333
|
+
var UCP_A2A_EXTENSION_URI = "https://ucp.dev/2026-04-08/specification/reference";
|
|
334
|
+
function ucpA2AExtension(capabilities = {}) {
|
|
335
|
+
return {
|
|
336
|
+
uri: UCP_A2A_EXTENSION_URI,
|
|
337
|
+
params: { capabilities }
|
|
338
|
+
};
|
|
339
|
+
}
|
|
333
340
|
var PROTOCOL_VERSION = "1.0";
|
|
334
341
|
var CARD_VERSION = 1;
|
|
335
342
|
function buildA2AAgentCard(input) {
|
|
@@ -361,17 +368,62 @@ function buildA2AAgentCard(input) {
|
|
|
361
368
|
if (input.description !== void 0) card.description = input.description;
|
|
362
369
|
if (input.url !== void 0) card.url = input.url;
|
|
363
370
|
if (input.capabilities !== void 0) card.capabilities = input.capabilities;
|
|
371
|
+
if (input.extensions && input.extensions.length > 0) card.extensions = input.extensions;
|
|
364
372
|
if (input.extras !== void 0) card.extras = input.extras;
|
|
365
373
|
return card;
|
|
366
374
|
}
|
|
367
375
|
|
|
368
376
|
// src/identity/ucp.ts
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
377
|
+
function ucpSigningKeyFromJWK(jwk) {
|
|
378
|
+
if (!jwk || typeof jwk !== "object") {
|
|
379
|
+
throw new Error(`ucpSigningKeyFromJWK expected a non-null object; got ${typeof jwk}.`);
|
|
380
|
+
}
|
|
381
|
+
if (typeof jwk.kid !== "string" || !jwk.kid) {
|
|
382
|
+
throw new Error("ucpSigningKeyFromJWK: JWK missing required field `kid` (or non-string).");
|
|
383
|
+
}
|
|
384
|
+
if (typeof jwk.kty !== "string" || !jwk.kty) {
|
|
385
|
+
throw new Error("ucpSigningKeyFromJWK: JWK missing required field `kty` (or non-string).");
|
|
386
|
+
}
|
|
387
|
+
if (jwk.kty !== "OKP" && jwk.kty !== "EC" && jwk.kty !== "RSA") {
|
|
388
|
+
throw new Error(
|
|
389
|
+
`ucpSigningKeyFromJWK: kty=${JSON.stringify(jwk.kty)} is not a supported asymmetric key type (expected OKP, EC, or RSA). Symmetric \`oct\` keys are rejected because they cannot publicly verify a JWS in the trust-mode UCP flow.`
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
if ((jwk.kty === "EC" || jwk.kty === "OKP") && (typeof jwk.crv !== "string" || !jwk.crv)) {
|
|
393
|
+
throw new Error(`ucpSigningKeyFromJWK: kty=${jwk.kty} requires a non-empty \`crv\` field (e.g., "P-256" for EC, "Ed25519" for OKP).`);
|
|
394
|
+
}
|
|
395
|
+
return jwk;
|
|
396
|
+
}
|
|
397
|
+
var DEFAULT_VERSION = "2026-04-08";
|
|
398
|
+
var AGENTSCORE_CAPABILITY_NAME = "sh.agentscore.identity";
|
|
372
399
|
var AGENTSCORE_CAPABILITY_VERSION = "1";
|
|
400
|
+
var AGENTSCORE_DEFAULT_SPEC_URL = "https://agentscore.sh/specification/identity";
|
|
401
|
+
var AGENTSCORE_DEFAULT_SCHEMA_URL = "https://agentscore.sh/schemas/ucp/sh-agentscore-identity-v1.json";
|
|
402
|
+
var AGENTSCORE_EXTENDS = ["dev.ucp.shopping.checkout", "dev.ucp.shopping.cart"];
|
|
403
|
+
var RESERVED_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
404
|
+
"ucp",
|
|
405
|
+
"signing_keys",
|
|
406
|
+
"signature",
|
|
407
|
+
"__proto__",
|
|
408
|
+
"constructor",
|
|
409
|
+
"prototype"
|
|
410
|
+
]);
|
|
411
|
+
var RESERVED_UCP_FIELDS = /* @__PURE__ */ new Set([
|
|
412
|
+
"version",
|
|
413
|
+
"name",
|
|
414
|
+
"services",
|
|
415
|
+
"capabilities",
|
|
416
|
+
"payment_handlers",
|
|
417
|
+
"supported_versions",
|
|
418
|
+
"__proto__",
|
|
419
|
+
"constructor",
|
|
420
|
+
"prototype"
|
|
421
|
+
]);
|
|
373
422
|
function buildUCPProfile(input) {
|
|
374
|
-
const
|
|
423
|
+
const capabilities = {};
|
|
424
|
+
for (const [name, bindings] of Object.entries(input.capabilities ?? {})) {
|
|
425
|
+
capabilities[name] = [...bindings];
|
|
426
|
+
}
|
|
375
427
|
if (input.data) {
|
|
376
428
|
const operatorId = input.data.resolved_operator;
|
|
377
429
|
if (operatorId) {
|
|
@@ -379,36 +431,332 @@ function buildUCPProfile(input) {
|
|
|
379
431
|
const accountVerification = input.data.account_verification;
|
|
380
432
|
const claims = {
|
|
381
433
|
operator_id: operatorId,
|
|
382
|
-
kyc_level: accountVerification?.kyc_level
|
|
434
|
+
kyc_level: accountVerification?.kyc_level || operatorVerification?.level || "none",
|
|
383
435
|
sanctions_clear: accountVerification?.sanctions_clear === true,
|
|
384
|
-
age_bracket: accountVerification?.age_bracket
|
|
385
|
-
jurisdiction: accountVerification?.jurisdiction
|
|
386
|
-
verified_at: accountVerification?.verified_at
|
|
436
|
+
age_bracket: accountVerification?.age_bracket || "unknown",
|
|
437
|
+
jurisdiction: accountVerification?.jurisdiction || "",
|
|
438
|
+
verified_at: accountVerification?.verified_at || operatorVerification?.verified_at || null,
|
|
387
439
|
verify_url: input.data.verify_url ?? null,
|
|
388
440
|
issuer: "https://agentscore.sh"
|
|
389
441
|
};
|
|
390
|
-
|
|
391
|
-
name: AGENTSCORE_CAPABILITY_NAME,
|
|
442
|
+
const agentscoreBinding = {
|
|
392
443
|
version: AGENTSCORE_CAPABILITY_VERSION,
|
|
393
|
-
|
|
444
|
+
spec: input.agentscore_spec_url ?? AGENTSCORE_DEFAULT_SPEC_URL,
|
|
445
|
+
schema: input.agentscore_schema_url ?? AGENTSCORE_DEFAULT_SCHEMA_URL,
|
|
446
|
+
extends: AGENTSCORE_EXTENDS,
|
|
447
|
+
// `claims` is our vendor extra on the binding; allowed per spec via the
|
|
448
|
+
// `[k: string]: unknown` index signature on UCPCapabilityBinding.
|
|
394
449
|
claims
|
|
395
|
-
}
|
|
450
|
+
};
|
|
451
|
+
const existing = capabilities[AGENTSCORE_CAPABILITY_NAME];
|
|
452
|
+
if (existing) existing.push(agentscoreBinding);
|
|
453
|
+
else capabilities[AGENTSCORE_CAPABILITY_NAME] = [agentscoreBinding];
|
|
396
454
|
}
|
|
397
455
|
}
|
|
398
|
-
const
|
|
456
|
+
const ucp = {
|
|
399
457
|
version: input.version ?? DEFAULT_VERSION,
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
458
|
+
services: input.services ?? {},
|
|
459
|
+
capabilities,
|
|
460
|
+
payment_handlers: input.payment_handlers ?? {}
|
|
461
|
+
};
|
|
462
|
+
if (input.name !== void 0) ucp.name = input.name;
|
|
463
|
+
if (input.supported_versions !== void 0) ucp.supported_versions = input.supported_versions;
|
|
464
|
+
if (input.ucp_extras) {
|
|
465
|
+
for (const k of Object.keys(input.ucp_extras)) {
|
|
466
|
+
if (RESERVED_UCP_FIELDS.has(k)) {
|
|
467
|
+
throw new Error(`buildUCPProfile: ucp_extras key "${k}" collides with a reserved \`ucp\` field; rejected.`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
Object.assign(ucp, input.ucp_extras);
|
|
471
|
+
}
|
|
472
|
+
const profile = {
|
|
473
|
+
ucp,
|
|
404
474
|
signing_keys: input.signing_keys
|
|
405
475
|
};
|
|
406
|
-
if (input.
|
|
407
|
-
|
|
476
|
+
if (input.extras) {
|
|
477
|
+
for (const k of Object.keys(input.extras)) {
|
|
478
|
+
if (RESERVED_TOP_LEVEL.has(k)) {
|
|
479
|
+
throw new Error(`buildUCPProfile: extras key "${k}" collides with a reserved profile field; rejected.`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
Object.assign(profile, input.extras);
|
|
483
|
+
}
|
|
408
484
|
return profile;
|
|
409
485
|
}
|
|
410
486
|
var AGENTSCORE_UCP_CAPABILITY = AGENTSCORE_CAPABILITY_NAME;
|
|
411
487
|
|
|
488
|
+
// src/identity/ucp-jwks.ts
|
|
489
|
+
var JOSE_INSTALL_HINT = "Install the optional peer dependency: `npm install jose@^6` (or `bun add jose`). Tested against jose v6.x.";
|
|
490
|
+
var ALLOWED_ALGS = ["EdDSA", "ES256"];
|
|
491
|
+
var PROFILE_TYP = "agentscore-profile+jws";
|
|
492
|
+
var UCPVerificationError = class extends Error {
|
|
493
|
+
constructor(code, message) {
|
|
494
|
+
super(message);
|
|
495
|
+
this.code = code;
|
|
496
|
+
this.name = "UCPVerificationError";
|
|
497
|
+
}
|
|
498
|
+
code;
|
|
499
|
+
};
|
|
500
|
+
async function loadJose() {
|
|
501
|
+
try {
|
|
502
|
+
return await import("jose");
|
|
503
|
+
} catch (err) {
|
|
504
|
+
throw new Error(
|
|
505
|
+
`UCP signing requires the \`jose\` library, which is an optional peer dependency. ${JOSE_INSTALL_HINT}
|
|
506
|
+
Original error: ${err instanceof Error ? err.message : String(err)}`
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
function canonicalizeProfile(profile) {
|
|
511
|
+
const stripped = { ...profile };
|
|
512
|
+
delete stripped.signature;
|
|
513
|
+
return stableStringify(stripped);
|
|
514
|
+
}
|
|
515
|
+
function stableStringify(value) {
|
|
516
|
+
if (value === void 0) {
|
|
517
|
+
throw new Error(
|
|
518
|
+
"stableStringify: undefined values are not allowed in canonicalized JSON. Object fields with no value must be omitted."
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
if (typeof value === "function" || typeof value === "symbol") {
|
|
522
|
+
throw new Error(`stableStringify: ${typeof value} values are not allowed in canonicalized JSON.`);
|
|
523
|
+
}
|
|
524
|
+
if (typeof value === "bigint") {
|
|
525
|
+
throw new Error("stableStringify: BigInt values are not allowed; use a decimal string.");
|
|
526
|
+
}
|
|
527
|
+
if (value instanceof Date) {
|
|
528
|
+
throw new Error(
|
|
529
|
+
"stableStringify: Date instances are not allowed; serialize to an ISO string before passing."
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
if (value instanceof Map || value instanceof Set || value instanceof WeakMap || value instanceof WeakSet) {
|
|
533
|
+
throw new Error(
|
|
534
|
+
`stableStringify: ${value.constructor.name} values are not allowed; convert to a plain object/array first.`
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
if (ArrayBuffer.isView(value)) {
|
|
538
|
+
throw new Error("stableStringify: typed arrays are not allowed; convert to a plain array first.");
|
|
539
|
+
}
|
|
540
|
+
if (typeof value === "number") {
|
|
541
|
+
if (!Number.isFinite(value)) {
|
|
542
|
+
throw new Error(
|
|
543
|
+
`UCP profile canonicalization rejects non-finite Number ${value}. Use a decimal string for any value that may be NaN/Infinity.`
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
if (!Number.isInteger(value)) {
|
|
547
|
+
throw new Error(
|
|
548
|
+
`UCP profile canonicalization rejects non-integer Number ${value}. Use a decimal string (e.g. "9.99") for monetary or fractional fields to preserve cross-language byte-parity.`
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
if (!Number.isSafeInteger(value)) {
|
|
552
|
+
throw new Error(
|
|
553
|
+
`stableStringify: integer ${value} exceeds Number.MAX_SAFE_INTEGER. For values >2^53, use a decimal string to preserve cross-language byte parity.`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (typeof value === "string") {
|
|
558
|
+
if (value.includes("\u2028") || value.includes("\u2029")) {
|
|
559
|
+
throw new Error(
|
|
560
|
+
"stableStringify: strings containing U+2028 (LINE SEPARATOR) or U+2029 (PARAGRAPH SEPARATOR) are not allowed; cross-language byte parity requires neither be present (Node JSON.stringify on older V8 escapes them; Python json.dumps with ensure_ascii=False does not)."
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
return JSON.stringify(value);
|
|
564
|
+
}
|
|
565
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
566
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
567
|
+
const obj = value;
|
|
568
|
+
const keys = Object.keys(obj).sort((a, b) => {
|
|
569
|
+
const aPoints = [...a].map((c) => c.codePointAt(0));
|
|
570
|
+
const bPoints = [...b].map((c) => c.codePointAt(0));
|
|
571
|
+
const len = Math.min(aPoints.length, bPoints.length);
|
|
572
|
+
for (let i = 0; i < len; i += 1) {
|
|
573
|
+
if (aPoints[i] !== bPoints[i]) return aPoints[i] - bPoints[i];
|
|
574
|
+
}
|
|
575
|
+
return aPoints.length - bPoints.length;
|
|
576
|
+
});
|
|
577
|
+
for (const k of keys) {
|
|
578
|
+
if (k.includes("\u2028") || k.includes("\u2029")) {
|
|
579
|
+
throw new Error(
|
|
580
|
+
"stableStringify: object keys containing U+2028 (LINE SEPARATOR) or U+2029 (PARAGRAPH SEPARATOR) are not allowed; cross-language byte parity (Node JSON.stringify on older V8 escapes them; Python json.dumps with ensure_ascii=False does not)."
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
const pairs = keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`);
|
|
585
|
+
return `{${pairs.join(",")}}`;
|
|
586
|
+
}
|
|
587
|
+
async function generateUCPSigningKey(opts) {
|
|
588
|
+
const jose = await loadJose();
|
|
589
|
+
const alg = opts.alg ?? "EdDSA";
|
|
590
|
+
const { privateKey, publicKey } = await jose.generateKeyPair(alg, { extractable: true });
|
|
591
|
+
const exportedJwk = await jose.exportJWK(publicKey);
|
|
592
|
+
const publicJWK = {
|
|
593
|
+
kid: opts.kid,
|
|
594
|
+
alg,
|
|
595
|
+
use: "sig",
|
|
596
|
+
...exportedJwk
|
|
597
|
+
};
|
|
598
|
+
return { privateKey, publicJWK };
|
|
599
|
+
}
|
|
600
|
+
async function signUCPProfile(profile, opts) {
|
|
601
|
+
const jose = await loadJose();
|
|
602
|
+
const alg = opts.alg ?? "EdDSA";
|
|
603
|
+
if (!ALLOWED_ALGS.includes(alg)) {
|
|
604
|
+
throw new Error(
|
|
605
|
+
`signUCPProfile: alg ${JSON.stringify(opts.alg)} is not in the supported set [${ALLOWED_ALGS.join(", ")}].`
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
if (typeof opts.kid !== "string" || !opts.kid) {
|
|
609
|
+
throw new Error("signUCPProfile: opts.kid must be a non-empty string.");
|
|
610
|
+
}
|
|
611
|
+
const kids = (profile.signing_keys ?? []).map((k) => k.kid);
|
|
612
|
+
if (!kids.includes(opts.kid)) {
|
|
613
|
+
throw new Error(
|
|
614
|
+
`signUCPProfile: kid ${JSON.stringify(opts.kid)} is not present in profile.signing_keys[] (declared kids: ${JSON.stringify(kids)}). Verifiers will not find the key.`
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
const canonicalBody = canonicalizeProfile(profile);
|
|
618
|
+
const payloadBytes = new TextEncoder().encode(canonicalBody);
|
|
619
|
+
const signature = await new jose.CompactSign(payloadBytes).setProtectedHeader({ alg, kid: opts.kid, typ: PROFILE_TYP }).sign(opts.signingKey);
|
|
620
|
+
return { ...profile, signature };
|
|
621
|
+
}
|
|
622
|
+
async function verifyUCPProfile(profile, jwks) {
|
|
623
|
+
if (profile === null || typeof profile !== "object" || Array.isArray(profile)) {
|
|
624
|
+
throw new UCPVerificationError(
|
|
625
|
+
"no_signature",
|
|
626
|
+
`UCP profile must be a JSON object; got ${profile === null ? "null" : Array.isArray(profile) ? "array" : typeof profile}.`
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
const jose = await loadJose();
|
|
630
|
+
if (!jwks || typeof jwks !== "object" || !Array.isArray(jwks.keys)) {
|
|
631
|
+
throw new UCPVerificationError(
|
|
632
|
+
"malformed_jwks",
|
|
633
|
+
`UCP verifier expected JWKS shape { keys: [...] }; got ${jwks === null ? "null" : typeof jwks === "object" ? "object without keys[] array" : typeof jwks}.`
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
const stripped = { ...profile };
|
|
637
|
+
const sig = stripped.signature;
|
|
638
|
+
delete stripped.signature;
|
|
639
|
+
if (typeof sig !== "string" || !sig) {
|
|
640
|
+
throw new UCPVerificationError(
|
|
641
|
+
"no_signature",
|
|
642
|
+
`UCP profile signature must be a non-empty string; got ${sig === void 0 ? "undefined" : typeof sig}.`
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
let header;
|
|
646
|
+
try {
|
|
647
|
+
const protectedB64 = sig.split(".")[0];
|
|
648
|
+
if (!protectedB64) throw new Error("JWS protected header segment is empty.");
|
|
649
|
+
const headerJson = new TextDecoder().decode(jose.base64url.decode(protectedB64));
|
|
650
|
+
const parsed = JSON.parse(headerJson);
|
|
651
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
652
|
+
throw new Error("JWS protected header is not a JSON object.");
|
|
653
|
+
}
|
|
654
|
+
header = parsed;
|
|
655
|
+
} catch (err) {
|
|
656
|
+
throw new UCPVerificationError(
|
|
657
|
+
"malformed_jws",
|
|
658
|
+
`JWS protected header is not valid base64url-encoded JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
if (header.typ !== PROFILE_TYP) {
|
|
662
|
+
throw new UCPVerificationError("wrong_typ", `UCP signature typ must be "${PROFILE_TYP}"; got ${String(header.typ)}.`);
|
|
663
|
+
}
|
|
664
|
+
if (!ALLOWED_ALGS.includes(header.alg)) {
|
|
665
|
+
throw new UCPVerificationError("unsupported_alg", `UCP signing alg must be one of ${ALLOWED_ALGS.join(", ")}; got ${String(header.alg)}.`);
|
|
666
|
+
}
|
|
667
|
+
if (typeof header.kid !== "string" || !header.kid) {
|
|
668
|
+
throw new UCPVerificationError(
|
|
669
|
+
"missing_kid",
|
|
670
|
+
`UCP signature header kid must be a non-empty string; got ${header.kid === void 0 ? "undefined" : typeof header.kid}.`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
if ("crit" in header) {
|
|
674
|
+
const crit = header.crit;
|
|
675
|
+
if (!Array.isArray(crit) || crit.length === 0 || !crit.every((c) => typeof c === "string")) {
|
|
676
|
+
throw new UCPVerificationError(
|
|
677
|
+
"malformed_jws",
|
|
678
|
+
`JWS protected header crit must be a non-empty array of strings; got ${JSON.stringify(crit)}.`
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
throw new UCPVerificationError(
|
|
682
|
+
"unrecognized_critical_header",
|
|
683
|
+
`JWS protected header advertises unrecognized crit headers: ${JSON.stringify(crit)}.`
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
let signedPayload;
|
|
687
|
+
try {
|
|
688
|
+
const verified = await jose.compactVerify(
|
|
689
|
+
sig,
|
|
690
|
+
async (h) => {
|
|
691
|
+
const kid = h.kid;
|
|
692
|
+
if (typeof kid !== "string" || !kid) {
|
|
693
|
+
throw new UCPVerificationError(
|
|
694
|
+
"missing_kid",
|
|
695
|
+
`UCP signature header kid must be a non-empty string; got ${kid === void 0 ? "undefined" : typeof kid}.`
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
const matches = jwks.keys.filter(
|
|
699
|
+
(k) => k != null && typeof k === "object" && k.kid === kid
|
|
700
|
+
);
|
|
701
|
+
if (matches.length === 0) throw new UCPVerificationError("kid_not_found", `No JWK in JWKS matching kid=${JSON.stringify(kid)}.`);
|
|
702
|
+
if (matches.length > 1) throw new UCPVerificationError("duplicate_kid", `JWKS contains ${matches.length} keys with kid=${JSON.stringify(kid)}; expected exactly one.`);
|
|
703
|
+
const matchedKey = matches[0];
|
|
704
|
+
if (matchedKey.use != null && matchedKey.use !== "sig") {
|
|
705
|
+
throw new UCPVerificationError("unusable_key", `JWK with kid=${kid} has use=${JSON.stringify(matchedKey.use)}; expected "sig".`);
|
|
706
|
+
}
|
|
707
|
+
if (matchedKey.alg != null && matchedKey.alg !== h.alg) {
|
|
708
|
+
throw new UCPVerificationError(
|
|
709
|
+
"unusable_key",
|
|
710
|
+
`JWK alg ${JSON.stringify(matchedKey.alg)} does not match JWS header alg ${JSON.stringify(h.alg)}.`
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
return jose.importJWK(matches[0], h.alg);
|
|
714
|
+
}
|
|
715
|
+
);
|
|
716
|
+
signedPayload = verified.payload;
|
|
717
|
+
} catch (err) {
|
|
718
|
+
if (err instanceof UCPVerificationError) throw err;
|
|
719
|
+
if (err instanceof Error && err.name === "JOSEAlgNotAllowed") {
|
|
720
|
+
throw new UCPVerificationError("unsupported_alg", `UCP signing alg not allowed: ${err.message}`);
|
|
721
|
+
}
|
|
722
|
+
if (err instanceof Error && err.name === "JWSSignatureVerificationFailed") {
|
|
723
|
+
throw new UCPVerificationError("signature_invalid", `UCP signature verification failed: ${err.message}`);
|
|
724
|
+
}
|
|
725
|
+
if (err instanceof Error && err.name === "JWSInvalid") {
|
|
726
|
+
throw new UCPVerificationError("malformed_jws", `Malformed JWS: ${err.message}`);
|
|
727
|
+
}
|
|
728
|
+
if (err instanceof Error && err.name === "JOSENotSupported") {
|
|
729
|
+
throw new UCPVerificationError("unrecognized_critical_header", `UCP signing rejected unrecognized critical header: ${err.message}`);
|
|
730
|
+
}
|
|
731
|
+
throw err;
|
|
732
|
+
}
|
|
733
|
+
let canonicalBody;
|
|
734
|
+
try {
|
|
735
|
+
canonicalBody = canonicalizeProfile(stripped);
|
|
736
|
+
} catch (err) {
|
|
737
|
+
throw new UCPVerificationError(
|
|
738
|
+
"body_mismatch",
|
|
739
|
+
`Failed to canonicalize received profile for verification: ${err instanceof Error ? err.message : String(err)}`
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
const expectedPayload = new TextEncoder().encode(canonicalBody);
|
|
743
|
+
if (!constantTimeEqual(signedPayload, expectedPayload)) {
|
|
744
|
+
throw new UCPVerificationError("body_mismatch", "UCP profile body does not match the signed payload (tampered or non-canonical).");
|
|
745
|
+
}
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
function constantTimeEqual(a, b) {
|
|
749
|
+
if (a.length !== b.length) return false;
|
|
750
|
+
let diff = 0;
|
|
751
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
752
|
+
diff |= a[i] ^ b[i];
|
|
753
|
+
}
|
|
754
|
+
return diff === 0;
|
|
755
|
+
}
|
|
756
|
+
function buildJWKSResponse(keys) {
|
|
757
|
+
return { keys };
|
|
758
|
+
}
|
|
759
|
+
|
|
412
760
|
// src/identity/policy.ts
|
|
413
761
|
function policyToGateOptions(policy, base) {
|
|
414
762
|
if (!policy || !policy.enforcement) return null;
|
|
@@ -458,21 +806,29 @@ function shippingStateAllowed(state, country, policy) {
|
|
|
458
806
|
export {
|
|
459
807
|
AGENTSCORE_UCP_CAPABILITY,
|
|
460
808
|
FIXABLE_DENIAL_REASONS,
|
|
809
|
+
UCPVerificationError,
|
|
810
|
+
UCP_A2A_EXTENSION_URI,
|
|
461
811
|
buildA2AAgentCard,
|
|
462
812
|
buildAgentMemoryHint,
|
|
463
813
|
buildContactSupportNextSteps,
|
|
814
|
+
buildJWKSResponse,
|
|
464
815
|
buildSignerMismatchBody,
|
|
465
816
|
buildUCPProfile,
|
|
466
817
|
denialReasonStatus,
|
|
467
818
|
denialReasonToBody,
|
|
468
819
|
extractPaymentSigner,
|
|
469
820
|
extractPaymentSignerAddress,
|
|
821
|
+
generateUCPSigningKey,
|
|
470
822
|
isFixableDenial,
|
|
471
823
|
policyToGateOptions,
|
|
472
824
|
readX402PaymentHeader,
|
|
473
825
|
runGateWithEnforcement,
|
|
474
826
|
shippingCountryAllowed,
|
|
475
827
|
shippingStateAllowed,
|
|
476
|
-
|
|
828
|
+
signUCPProfile,
|
|
829
|
+
ucpA2AExtension,
|
|
830
|
+
ucpSigningKeyFromJWK,
|
|
831
|
+
verificationAgentInstructions,
|
|
832
|
+
verifyUCPProfile
|
|
477
833
|
};
|
|
478
834
|
//# sourceMappingURL=index.mjs.map
|