@ixo/credentials-kit 0.0.0-stage → 0.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.
Files changed (72) hide show
  1. package/README.md +469 -2
  2. package/dist/chunk-2SF56NEJ.js +1053 -0
  3. package/dist/chunk-2SF56NEJ.js.map +1 -0
  4. package/dist/chunk-46VRPQPD.js +176 -0
  5. package/dist/chunk-46VRPQPD.js.map +1 -0
  6. package/dist/chunk-AQ3IINHC.js +138 -0
  7. package/dist/chunk-AQ3IINHC.js.map +1 -0
  8. package/dist/chunk-FSKCDHZ7.js +613 -0
  9. package/dist/chunk-FSKCDHZ7.js.map +1 -0
  10. package/dist/chunk-I6GBYFIJ.js +89 -0
  11. package/dist/chunk-I6GBYFIJ.js.map +1 -0
  12. package/dist/chunk-JBUX6V2Q.js +210 -0
  13. package/dist/chunk-JBUX6V2Q.js.map +1 -0
  14. package/dist/chunk-Q3MZNMCK.js +183 -0
  15. package/dist/chunk-Q3MZNMCK.js.map +1 -0
  16. package/dist/chunk-RKKRO4ZK.js +133 -0
  17. package/dist/chunk-RKKRO4ZK.js.map +1 -0
  18. package/dist/chunk-STMMFNVW.js +391 -0
  19. package/dist/chunk-STMMFNVW.js.map +1 -0
  20. package/dist/chunk-VXKONZK4.js +189 -0
  21. package/dist/chunk-VXKONZK4.js.map +1 -0
  22. package/dist/chunk-WIEGZFUO.js +337 -0
  23. package/dist/chunk-WIEGZFUO.js.map +1 -0
  24. package/dist/chunk-WND2LZ4O.js +66 -0
  25. package/dist/chunk-WND2LZ4O.js.map +1 -0
  26. package/dist/chunk-XREZY53L.js +260 -0
  27. package/dist/chunk-XREZY53L.js.map +1 -0
  28. package/dist/chunk-ZZY4WGQ6.js +13 -0
  29. package/dist/chunk-ZZY4WGQ6.js.map +1 -0
  30. package/dist/did.d.ts +182 -0
  31. package/dist/did.js +6 -0
  32. package/dist/did.js.map +1 -0
  33. package/dist/display.d.ts +117 -0
  34. package/dist/display.js +5 -0
  35. package/dist/display.js.map +1 -0
  36. package/dist/errors-BKQmNpXX.d.ts +11 -0
  37. package/dist/holder.d.ts +85 -0
  38. package/dist/holder.js +8 -0
  39. package/dist/holder.js.map +1 -0
  40. package/dist/http-BhuJ_-RZ.d.ts +25 -0
  41. package/dist/index.d.ts +15 -0
  42. package/dist/index.js +16 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/jose.d.ts +134 -0
  45. package/dist/jose.js +4 -0
  46. package/dist/jose.js.map +1 -0
  47. package/dist/oid4vci.d.ts +428 -0
  48. package/dist/oid4vci.js +11 -0
  49. package/dist/oid4vci.js.map +1 -0
  50. package/dist/oid4vp.d.ts +416 -0
  51. package/dist/oid4vp.js +11 -0
  52. package/dist/oid4vp.js.map +1 -0
  53. package/dist/profile.d.ts +218 -0
  54. package/dist/profile.js +4 -0
  55. package/dist/profile.js.map +1 -0
  56. package/dist/sdjwt.d.ts +133 -0
  57. package/dist/sdjwt.js +5 -0
  58. package/dist/sdjwt.js.map +1 -0
  59. package/dist/sender.d.ts +8 -0
  60. package/dist/sender.js +7 -0
  61. package/dist/sender.js.map +1 -0
  62. package/dist/status.d.ts +108 -0
  63. package/dist/status.js +7 -0
  64. package/dist/status.js.map +1 -0
  65. package/dist/util-DOTEZgTt.d.ts +3 -0
  66. package/dist/verify.d.ts +144 -0
  67. package/dist/verify.js +9 -0
  68. package/dist/verify.js.map +1 -0
  69. package/dist/webhook.d.ts +62 -0
  70. package/dist/webhook.js +7 -0
  71. package/dist/webhook.js.map +1 -0
  72. package/package.json +168 -4
@@ -0,0 +1,260 @@
1
+ import { CredentialsKitError, decodeJws, JoseError, isRecord } from './chunk-VXKONZK4.js';
2
+ import { base64urlEncode, utf8, bytesToHex, fromUtf8, base64urlDecode } from './chunk-AQ3IINHC.js';
3
+ import { sha256 } from '@noble/hashes/sha2.js';
4
+
5
+ var MAX_SD_JWT_LENGTH = 32 * 1024;
6
+ var MAX_DISCLOSURES = 256;
7
+ var REFUSED_CLAIM_NAMES = /* @__PURE__ */ new Set([
8
+ "__proto__",
9
+ "constructor",
10
+ "prototype"
11
+ ]);
12
+ var NOT_A_DISCLOSURE_NAME = /* @__PURE__ */ new Set(["_sd", "...", "_sd_alg"]);
13
+ var MAX_CLAIM_DEPTH = 32;
14
+ var SD_JWT_MESSAGES = {
15
+ malformed: "The SD-JWT is not well formed.",
16
+ bad_disclosure: "An SD-JWT disclosure is not well formed.",
17
+ unknown_claim: "The SD-JWT has no disclosure for a requested claim."
18
+ };
19
+ var SdJwtError = class extends CredentialsKitError {
20
+ constructor(code) {
21
+ super(code, SD_JWT_MESSAGES[code]);
22
+ }
23
+ };
24
+ function sdDigest(s) {
25
+ return base64urlEncode(sha256(utf8(s)));
26
+ }
27
+ function parseDisclosure(raw) {
28
+ let arr;
29
+ try {
30
+ arr = JSON.parse(fromUtf8(base64urlDecode(raw)));
31
+ } catch {
32
+ throw new SdJwtError("bad_disclosure");
33
+ }
34
+ if (!Array.isArray(arr) || typeof arr[0] !== "string") {
35
+ throw new SdJwtError("bad_disclosure");
36
+ }
37
+ const digest = sdDigest(raw);
38
+ if (arr.length === 3) {
39
+ if (typeof arr[1] !== "string") throw new SdJwtError("bad_disclosure");
40
+ return {
41
+ raw,
42
+ salt: arr[0],
43
+ name: arr[1],
44
+ value: arr[2],
45
+ digest
46
+ };
47
+ }
48
+ if (arr.length === 2) {
49
+ return { raw, salt: arr[0], name: null, value: arr[1], digest };
50
+ }
51
+ throw new SdJwtError("bad_disclosure");
52
+ }
53
+ function parseSdJwt(str) {
54
+ if (typeof str !== "string" || str.length > MAX_SD_JWT_LENGTH) {
55
+ throw new SdJwtError("malformed");
56
+ }
57
+ const segments = str.split("~");
58
+ if (segments.length < 2) throw new SdJwtError("malformed");
59
+ const jwt = segments[0];
60
+ let decoded;
61
+ try {
62
+ decoded = decodeJws(jwt);
63
+ } catch (e) {
64
+ if (e instanceof JoseError) throw new SdJwtError("malformed");
65
+ throw e;
66
+ }
67
+ const last = segments[segments.length - 1];
68
+ const middle = segments.slice(1, -1);
69
+ if (middle.length > MAX_DISCLOSURES) throw new SdJwtError("malformed");
70
+ const disclosures = middle.map((raw) => {
71
+ if (raw.length === 0 || raw.includes("."))
72
+ throw new SdJwtError("malformed");
73
+ return parseDisclosure(raw);
74
+ });
75
+ const parsed = {
76
+ jwt,
77
+ header: decoded.header,
78
+ payload: decoded.payload,
79
+ disclosures
80
+ };
81
+ if (last.length > 0) {
82
+ try {
83
+ decodeJws(last);
84
+ } catch {
85
+ throw new SdJwtError("malformed");
86
+ }
87
+ parsed.kbJwt = last;
88
+ }
89
+ return parsed;
90
+ }
91
+ function serializeSdJwt(jwt, disclosureRaws, kbJwt) {
92
+ return `${jwt}~${disclosureRaws.map((d) => `${d}~`).join("")}${kbJwt ?? ""}`;
93
+ }
94
+ function topLevelDigests(payload) {
95
+ const sd = payload._sd;
96
+ return new Set(
97
+ Array.isArray(sd) ? sd.filter((x) => typeof x === "string") : []
98
+ );
99
+ }
100
+ function selectDisclosures(parsed, names) {
101
+ const wanted = new Set(names);
102
+ const top = topLevelDigests(parsed.payload);
103
+ const found = /* @__PURE__ */ new Set();
104
+ const out = [];
105
+ for (const d of parsed.disclosures) {
106
+ if (d.name !== null && top.has(d.digest) && wanted.has(d.name) && !found.has(d.name)) {
107
+ found.add(d.name);
108
+ out.push(d.raw);
109
+ }
110
+ }
111
+ if (found.size !== wanted.size) throw new SdJwtError("unknown_claim");
112
+ return out;
113
+ }
114
+ function sdHash(jwtAndDisclosures) {
115
+ if (!jwtAndDisclosures.endsWith("~")) throw new SdJwtError("malformed");
116
+ return sdDigest(jwtAndDisclosures);
117
+ }
118
+ function processDisclosures(payload, disclosures) {
119
+ const byDigest = /* @__PURE__ */ new Map();
120
+ for (const d of disclosures) {
121
+ if (byDigest.has(d.digest)) throw new SdJwtError("bad_disclosure");
122
+ byDigest.set(d.digest, d);
123
+ }
124
+ const seenDigests = /* @__PURE__ */ new Set();
125
+ const used = /* @__PURE__ */ new Set();
126
+ const take = (digest) => {
127
+ if (typeof digest !== "string") throw new SdJwtError("malformed");
128
+ if (seenDigests.has(digest)) throw new SdJwtError("bad_disclosure");
129
+ seenDigests.add(digest);
130
+ const d = byDigest.get(digest);
131
+ if (d) used.add(digest);
132
+ return d;
133
+ };
134
+ const walk = (value, depth, top) => {
135
+ if (depth > MAX_CLAIM_DEPTH) throw new SdJwtError("malformed");
136
+ if (Array.isArray(value)) {
137
+ const out2 = [];
138
+ for (const element of value) {
139
+ if (isRecord(element) && Object.hasOwn(element, "...")) {
140
+ if (Object.keys(element).length !== 1)
141
+ throw new SdJwtError("malformed");
142
+ const d = take(element["..."]);
143
+ if (!d) continue;
144
+ if (d.name !== null) throw new SdJwtError("bad_disclosure");
145
+ out2.push(walk(d.value, depth + 1, false));
146
+ } else {
147
+ out2.push(walk(element, depth + 1, false));
148
+ }
149
+ }
150
+ return out2;
151
+ }
152
+ if (!isRecord(value)) return value;
153
+ const out = {};
154
+ const disclosedHere = [];
155
+ for (const [key, v] of Object.entries(value)) {
156
+ if (REFUSED_CLAIM_NAMES.has(key) || key === "...")
157
+ throw new SdJwtError("malformed");
158
+ if (key === "_sd") continue;
159
+ if (key === "_sd_alg") {
160
+ if (!top) throw new SdJwtError("malformed");
161
+ continue;
162
+ }
163
+ out[key] = walk(v, depth + 1, false);
164
+ }
165
+ if (value._sd !== void 0) {
166
+ if (!Array.isArray(value._sd)) throw new SdJwtError("malformed");
167
+ for (const digest of value._sd) {
168
+ const d = take(digest);
169
+ if (!d) continue;
170
+ if (d.name === null) throw new SdJwtError("bad_disclosure");
171
+ if (NOT_A_DISCLOSURE_NAME.has(d.name))
172
+ throw new SdJwtError("bad_disclosure");
173
+ if (REFUSED_CLAIM_NAMES.has(d.name)) throw new SdJwtError("malformed");
174
+ if (Object.hasOwn(out, d.name)) throw new SdJwtError("bad_disclosure");
175
+ out[d.name] = walk(d.value, depth + 1, false);
176
+ disclosedHere.push(d.name);
177
+ }
178
+ }
179
+ if (top) topDisclosed.push(...disclosedHere);
180
+ return out;
181
+ };
182
+ const topDisclosed = [];
183
+ const claims = walk(payload, 0, true);
184
+ if (used.size !== byDigest.size) throw new SdJwtError("bad_disclosure");
185
+ return { claims, disclosed: topDisclosed };
186
+ }
187
+ var STRUCTURAL = /* @__PURE__ */ new Set([
188
+ "iss",
189
+ "sub",
190
+ "iat",
191
+ "nbf",
192
+ "exp",
193
+ "vct",
194
+ "cnf",
195
+ "status",
196
+ "_sd",
197
+ "_sd_alg",
198
+ "jti",
199
+ "aud"
200
+ ]);
201
+ function credentialRecordFromSdJwt(sdjwt, opts = {}) {
202
+ const parsed = parseSdJwt(sdjwt.trim());
203
+ if (parsed.kbJwt !== void 0) throw new SdJwtError("malformed");
204
+ const { payload } = parsed;
205
+ if (typeof payload.iss !== "string" || typeof payload.sub !== "string" || typeof payload.vct !== "string" || typeof payload.iat !== "number") {
206
+ throw new SdJwtError("malformed");
207
+ }
208
+ if (opts.holderDid !== void 0 && payload.sub !== opts.holderDid) {
209
+ throw new SdJwtError("malformed");
210
+ }
211
+ for (const key of Object.keys(payload)) {
212
+ if (REFUSED_CLAIM_NAMES.has(key)) throw new SdJwtError("malformed");
213
+ }
214
+ for (const d of parsed.disclosures) {
215
+ if (d.name !== null && REFUSED_CLAIM_NAMES.has(d.name))
216
+ throw new SdJwtError("malformed");
217
+ }
218
+ const processed = processDisclosures(payload, parsed.disclosures).claims;
219
+ const top = topLevelDigests(payload);
220
+ const claims = {};
221
+ for (const key of Object.keys(payload)) {
222
+ if (!STRUCTURAL.has(key))
223
+ claims[key] = { value: processed[key], sd: "never" };
224
+ }
225
+ for (const d of parsed.disclosures) {
226
+ if (d.name !== null && top.has(d.digest))
227
+ claims[d.name] = { value: processed[d.name], sd: "allowed" };
228
+ }
229
+ return {
230
+ recordType: "SdJwtCredentialRecord",
231
+ format: "dc+sd-jwt",
232
+ credential: sdjwt,
233
+ issuerDid: payload.iss,
234
+ ...opts.issuerName !== void 0 ? { issuerName: opts.issuerName } : {},
235
+ holderDid: payload.sub,
236
+ vct: payload.vct,
237
+ issuedAt: new Date(payload.iat * 1e3).toISOString(),
238
+ ...typeof payload.exp === "number" ? { expiresAt: new Date(payload.exp * 1e3).toISOString() } : {},
239
+ claims,
240
+ holderBinding: !!payload.cnf
241
+ };
242
+ }
243
+ function canonicalize(value) {
244
+ if (Array.isArray(value)) return value.map(canonicalize);
245
+ if (value && typeof value === "object") {
246
+ return Object.keys(value).sort().reduce((acc, key) => {
247
+ acc[key] = canonicalize(value[key]);
248
+ return acc;
249
+ }, {});
250
+ }
251
+ return value;
252
+ }
253
+ function credentialCid(record) {
254
+ const canonical = JSON.stringify(canonicalize(record));
255
+ return `sha256:${bytesToHex(sha256(utf8(canonical)))}`;
256
+ }
257
+
258
+ export { MAX_CLAIM_DEPTH, MAX_DISCLOSURES, MAX_SD_JWT_LENGTH, REFUSED_CLAIM_NAMES, SdJwtError, credentialCid, credentialRecordFromSdJwt, parseSdJwt, processDisclosures, sdDigest, sdHash, selectDisclosures, serializeSdJwt };
259
+ //# sourceMappingURL=chunk-XREZY53L.js.map
260
+ //# sourceMappingURL=chunk-XREZY53L.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sdjwt.ts"],"names":["out"],"mappings":"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","file":"chunk-XREZY53L.js","sourcesContent":["/**\n * `@ixo/credentials-kit/sdjwt` — SD-JWT (RFC 9901) parsing and serialization, the Vault record\n * the Self web app and the portal store, and its CID.\n *\n * SD-JWT <issuer-signed JWT>~<disclosure 1>~…~<disclosure n>~\n * SD-JWT+KB <issuer-signed JWT>~<disclosure 1>~…~<disclosure n>~<KB-JWT>\n * disclosure base64url(UTF-8 JSON [salt, name, value]) (array element: [salt, value])\n * digest base64url(SHA-256(US-ASCII of the disclosure string))\n * sd_hash base64url(SHA-256(US-ASCII of \"<jwt>~<d1>~…~<dn>~\"))\n *\n * Parsing does not verify signatures (see `verify`); it checks structure only.\n * `processDisclosures` is the RFC 9901 §7.1 step 3–5 algorithm (recursive: nested object and\n * array-element disclosures), used by `verify` after the signature has been checked.\n */\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport {\n base64urlDecode,\n base64urlEncode,\n bytesToHex,\n fromUtf8,\n utf8,\n} from './internal/bytes';\nimport { CredentialsKitError } from './internal/errors';\nimport { isRecord, type JsonObject } from './internal/util';\nimport { JoseError, decodeJws } from './jose';\n\n/** Largest SD-JWT (with KB-JWT) the kit parses: 32 KiB. */\nexport const MAX_SD_JWT_LENGTH = 32 * 1024;\n/** Most disclosures one SD-JWT may carry. */\nexport const MAX_DISCLOSURES = 256;\n\nexport interface Disclosure {\n /** The disclosure exactly as it appears in the SD-JWT (base64url). */\n raw: string;\n salt: string;\n /** The claim name; null for an array-element disclosure (`[salt, value]`). */\n name: string | null;\n value: unknown;\n /** base64url(SHA-256(raw)), the value found in `_sd`. */\n digest: string;\n}\n\nexport interface ParsedSdJwt {\n /** The issuer-signed JWT. */\n jwt: string;\n header: JsonObject;\n payload: JsonObject;\n disclosures: Disclosure[];\n /** The Key Binding JWT, when the input was an SD-JWT+KB. */\n kbJwt?: string;\n}\n\nexport type SdJwtErrorCode = 'malformed' | 'bad_disclosure' | 'unknown_claim';\n\n/**\n * Claim names refused anywhere in a credential (plaintext or disclosed): they would change an\n * object's prototype or shadow its constructor in JavaScript consumers (SPEC §13.3).\n */\nexport const REFUSED_CLAIM_NAMES: ReadonlySet<string> = new Set([\n '__proto__',\n 'constructor',\n 'prototype',\n]);\n/** Names a disclosure may never carry (RFC 9901 §4.2.1, plus `_sd_alg`). */\nconst NOT_A_DISCLOSURE_NAME = new Set(['_sd', '...', '_sd_alg']);\n/** Deepest nesting `processDisclosures` walks. */\nexport const MAX_CLAIM_DEPTH = 32;\n\nconst SD_JWT_MESSAGES: Record<SdJwtErrorCode, string> = {\n malformed: 'The SD-JWT is not well formed.',\n bad_disclosure: 'An SD-JWT disclosure is not well formed.',\n unknown_claim: 'The SD-JWT has no disclosure for a requested claim.',\n};\n\nexport class SdJwtError extends CredentialsKitError {\n declare readonly code: SdJwtErrorCode;\n constructor(code: SdJwtErrorCode) {\n super(code, SD_JWT_MESSAGES[code]);\n }\n}\n\n/** base64url(SHA-256(US-ASCII bytes of `s`)), the SD-JWT digest (sha-256 only). */\nexport function sdDigest(s: string): string {\n return base64urlEncode(sha256(utf8(s)));\n}\n\nfunction parseDisclosure(raw: string): Disclosure {\n let arr: unknown;\n try {\n arr = JSON.parse(fromUtf8(base64urlDecode(raw)));\n } catch {\n throw new SdJwtError('bad_disclosure');\n }\n if (!Array.isArray(arr) || typeof arr[0] !== 'string') {\n throw new SdJwtError('bad_disclosure');\n }\n const digest = sdDigest(raw);\n if (arr.length === 3) {\n if (typeof arr[1] !== 'string') throw new SdJwtError('bad_disclosure');\n return {\n raw,\n salt: arr[0],\n name: arr[1],\n value: arr[2] as unknown,\n digest,\n };\n }\n if (arr.length === 2) {\n return { raw, salt: arr[0], name: null, value: arr[1] as unknown, digest };\n }\n throw new SdJwtError('bad_disclosure');\n}\n\n/**\n * Parse an SD-JWT or SD-JWT+KB. Throws `SdJwtError` (`malformed`, `bad_disclosure`). Does not\n * verify anything cryptographic and does not check that disclosures are referenced.\n */\nexport function parseSdJwt(str: string): ParsedSdJwt {\n if (typeof str !== 'string' || str.length > MAX_SD_JWT_LENGTH) {\n throw new SdJwtError('malformed');\n }\n const segments = str.split('~');\n if (segments.length < 2) throw new SdJwtError('malformed');\n const jwt = segments[0]!;\n let decoded;\n try {\n decoded = decodeJws(jwt);\n } catch (e) {\n if (e instanceof JoseError) throw new SdJwtError('malformed');\n throw e;\n }\n const last = segments[segments.length - 1]!;\n const middle = segments.slice(1, -1);\n if (middle.length > MAX_DISCLOSURES) throw new SdJwtError('malformed');\n const disclosures = middle.map(raw => {\n if (raw.length === 0 || raw.includes('.'))\n throw new SdJwtError('malformed');\n return parseDisclosure(raw);\n });\n const parsed: ParsedSdJwt = {\n jwt,\n header: decoded.header,\n payload: decoded.payload,\n disclosures,\n };\n if (last.length > 0) {\n try {\n decodeJws(last);\n } catch {\n throw new SdJwtError('malformed');\n }\n parsed.kbJwt = last;\n }\n return parsed;\n}\n\n/**\n * `<jwt>~<d1>~…~<dn>~` plus the KB-JWT when given. With no KB-JWT the result ends with `~`\n * (RFC 9901 §4).\n */\nexport function serializeSdJwt(\n jwt: string,\n disclosureRaws: readonly string[],\n kbJwt?: string\n): string {\n return `${jwt}~${disclosureRaws.map(d => `${d}~`).join('')}${kbJwt ?? ''}`;\n}\n\n/** The digests listed in the payload's own (top-level) `_sd`. */\nfunction topLevelDigests(payload: JsonObject): Set<string> {\n const sd = payload._sd;\n return new Set(\n Array.isArray(sd)\n ? sd.filter((x): x is string => typeof x === 'string')\n : []\n );\n}\n\n/**\n * The raw disclosures for the TOP-LEVEL claim `names`: object-property disclosures whose digest\n * is listed in the payload's own `_sd`, in the order the SD-JWT carries them. A claim name is an\n * exact path of one segment: a disclosure of the same name nested inside another claim is never\n * selected (it would release a value nobody asked for), and the nested disclosures inside a\n * selected value are not released either. Throws `unknown_claim` when a name has no top-level\n * disclosure, so a caller never silently presents less (or something else) than it meant to.\n */\nexport function selectDisclosures(\n parsed: ParsedSdJwt,\n names: readonly string[]\n): string[] {\n const wanted = new Set(names);\n const top = topLevelDigests(parsed.payload);\n const found = new Set<string>();\n const out: string[] = [];\n for (const d of parsed.disclosures) {\n if (\n d.name !== null &&\n top.has(d.digest) &&\n wanted.has(d.name) &&\n !found.has(d.name)\n ) {\n found.add(d.name);\n out.push(d.raw);\n }\n }\n if (found.size !== wanted.size) throw new SdJwtError('unknown_claim');\n return out;\n}\n\n/**\n * The KB-JWT `sd_hash` over `<jwt>~<d1>~…~<dn>~` (must end with `~`, as presented: the\n * issuer-signed JWT and the chosen disclosures, without the KB-JWT).\n */\nexport function sdHash(jwtAndDisclosures: string): string {\n if (!jwtAndDisclosures.endsWith('~')) throw new SdJwtError('malformed');\n return sdDigest(jwtAndDisclosures);\n}\n\nexport interface ProcessedSdJwt {\n /**\n * The payload with every presented disclosure put in place, recursively, and every `_sd`,\n * `_sd_alg` and unmatched `{\"...\": digest}` array element removed (RFC 9901 §7.1 step 3).\n */\n claims: JsonObject;\n /** Names of the top-level claims that came from a disclosure, in payload order. */\n disclosed: string[];\n}\n\n/**\n * RFC 9901 §7.1 steps 3–5 over an (already signature-checked) payload: every `_sd` digest list\n * in every object and every `{\"...\": digest}` element of every array, recursively into\n * disclosed values. Throws `SdJwtError('bad_disclosure')` when a digest appears more than once,\n * a disclosure matches no digest (or is presented twice), an object-property disclosure is\n * found in an array (or the reverse), a disclosed name is `_sd`, `...` or `_sd_alg`, or a\n * disclosed name already exists at its level. Throws `SdJwtError('malformed')` for a malformed\n * `_sd` or `...` structure, a claim named `__proto__` / `constructor` / `prototype`, `_sd_alg`\n * below the top level, or nesting deeper than `MAX_CLAIM_DEPTH`. `sha-256` digests only (the\n * caller checks `_sd_alg`).\n */\nexport function processDisclosures(\n payload: JsonObject,\n disclosures: readonly Disclosure[]\n): ProcessedSdJwt {\n const byDigest = new Map<string, Disclosure>();\n for (const d of disclosures) {\n // The same disclosure presented twice can be referenced only once (§7.1 step 5).\n if (byDigest.has(d.digest)) throw new SdJwtError('bad_disclosure');\n byDigest.set(d.digest, d);\n }\n const seenDigests = new Set<string>();\n const used = new Set<string>();\n\n const take = (digest: unknown): Disclosure | undefined => {\n if (typeof digest !== 'string') throw new SdJwtError('malformed');\n if (seenDigests.has(digest)) throw new SdJwtError('bad_disclosure');\n seenDigests.add(digest);\n const d = byDigest.get(digest);\n if (d) used.add(digest);\n return d;\n };\n\n const walk = (value: unknown, depth: number, top: boolean): unknown => {\n if (depth > MAX_CLAIM_DEPTH) throw new SdJwtError('malformed');\n if (Array.isArray(value)) {\n const out: unknown[] = [];\n for (const element of value) {\n if (isRecord(element) && Object.hasOwn(element, '...')) {\n if (Object.keys(element).length !== 1)\n throw new SdJwtError('malformed');\n const d = take(element['...']);\n if (!d) continue; // withheld or decoy: the element is removed\n if (d.name !== null) throw new SdJwtError('bad_disclosure');\n out.push(walk(d.value, depth + 1, false));\n } else {\n out.push(walk(element, depth + 1, false));\n }\n }\n return out;\n }\n if (!isRecord(value)) return value;\n const out: JsonObject = {};\n const disclosedHere: string[] = [];\n for (const [key, v] of Object.entries(value)) {\n if (REFUSED_CLAIM_NAMES.has(key) || key === '...')\n throw new SdJwtError('malformed');\n if (key === '_sd') continue;\n if (key === '_sd_alg') {\n if (!top) throw new SdJwtError('malformed');\n continue;\n }\n out[key] = walk(v, depth + 1, false);\n }\n if (value._sd !== undefined) {\n if (!Array.isArray(value._sd)) throw new SdJwtError('malformed');\n for (const digest of value._sd) {\n const d = take(digest);\n if (!d) continue; // withheld or decoy\n if (d.name === null) throw new SdJwtError('bad_disclosure');\n if (NOT_A_DISCLOSURE_NAME.has(d.name))\n throw new SdJwtError('bad_disclosure');\n if (REFUSED_CLAIM_NAMES.has(d.name)) throw new SdJwtError('malformed');\n // A disclosure never overwrites a claim at its level (§7.1 step 3.c.ii.3).\n if (Object.hasOwn(out, d.name)) throw new SdJwtError('bad_disclosure');\n out[d.name] = walk(d.value, depth + 1, false);\n disclosedHere.push(d.name);\n }\n }\n if (top) topDisclosed.push(...disclosedHere);\n return out;\n };\n\n const topDisclosed: string[] = [];\n const claims = walk(payload, 0, true) as JsonObject;\n if (used.size !== byDigest.size) throw new SdJwtError('bad_disclosure');\n return { claims, disclosed: topDisclosed };\n}\n\n// ---------------------------------------------------------------------------------------------\n// The Vault record (port of ixo-self/web/src/vault/sdjwt.ts `toCredentialRecord`).\n\n/**\n * The `ixo.credential` payload the portal / jambo / Self web app store\n * (`SdJwtCredentialRecord`): the raw SD-JWT is authoritative; `claims` is a display cache with\n * each claim's disclosure rule.\n */\nexport interface SdJwtCredentialRecord {\n recordType: 'SdJwtCredentialRecord';\n format: 'dc+sd-jwt';\n credential: string;\n issuerDid: string;\n issuerName?: string;\n holderDid: string;\n vct: string;\n issuedAt: string;\n expiresAt?: string;\n claims: Record<\n string,\n { value: unknown; sd: 'never' | 'allowed' | 'always' }\n >;\n holderBinding: boolean;\n}\n\n/** JWT registered / SD-JWT structural claims that are not user-facing claims (Self's set). */\nconst STRUCTURAL = new Set([\n 'iss',\n 'sub',\n 'iat',\n 'nbf',\n 'exp',\n 'vct',\n 'cnf',\n 'status',\n '_sd',\n '_sd_alg',\n 'jti',\n 'aud',\n]);\n\nexport interface CredentialRecordOptions {\n /** Display name of the issuer (Self stores `'IXO Identity'`). Omitted when not given. */\n issuerName?: string;\n /** When given, the credential's `sub` must equal it (it was issued to this holder). */\n holderDid?: string;\n}\n\n/**\n * The Vault record of an issued SD-JWT VC, byte-identical (same members, same order) to Self's\n * `toCredentialRecord` for the same flat credential, so the Hub and the browser de-duplicate by\n * CID. Every field comes from the SD-JWT itself; the same credential always yields the same\n * record. `claims` holds the TOP-LEVEL claims only: plaintext ones (`sd: 'never'`), then the\n * top-level disclosures in the order the SD-JWT carries them (`sd: 'allowed'`). A nested\n * disclosure stays inside its parent's value (RFC 9901 §7.1 processing), so it can never stand\n * in for, or overwrite, a top-level claim of the same name. Throws `SdJwtError('malformed')`\n * when `iss`, `sub`, `vct` or `iat` is missing, when `holderDid` does not match, or when a claim\n * is named `__proto__`, `constructor` or `prototype`, and `SdJwtError('bad_disclosure')` (or\n * `malformed`) when the disclosures break RFC 9901 §7.1 (unreferenced, repeated, overwriting).\n *\n * This only PARSES: it does not check the issuer's signature, `cnf` or validity. A wallet must\n * call `verifyIssuedCredential` (kit `verify`) first and store only what passed (RFC 9901 §7.2).\n */\nexport function credentialRecordFromSdJwt(\n sdjwt: string,\n opts: CredentialRecordOptions = {}\n): SdJwtCredentialRecord {\n const parsed = parseSdJwt(sdjwt.trim());\n if (parsed.kbJwt !== undefined) throw new SdJwtError('malformed');\n const { payload } = parsed;\n if (\n typeof payload.iss !== 'string' ||\n typeof payload.sub !== 'string' ||\n typeof payload.vct !== 'string' ||\n typeof payload.iat !== 'number'\n ) {\n throw new SdJwtError('malformed');\n }\n if (opts.holderDid !== undefined && payload.sub !== opts.holderDid) {\n throw new SdJwtError('malformed');\n }\n\n // A claim named __proto__ / constructor / prototype would rewire the record (L-5): refused.\n for (const key of Object.keys(payload)) {\n if (REFUSED_CLAIM_NAMES.has(key)) throw new SdJwtError('malformed');\n }\n for (const d of parsed.disclosures) {\n if (d.name !== null && REFUSED_CLAIM_NAMES.has(d.name))\n throw new SdJwtError('malformed');\n }\n // RFC 9901 §7.1: every disclosure in its place, nested ones inside their parent's value.\n const processed = processDisclosures(payload, parsed.disclosures).claims;\n const top = topLevelDigests(payload);\n const claims: SdJwtCredentialRecord['claims'] = {};\n for (const key of Object.keys(payload)) {\n if (!STRUCTURAL.has(key))\n claims[key] = { value: processed[key], sd: 'never' };\n }\n for (const d of parsed.disclosures) {\n if (d.name !== null && top.has(d.digest))\n claims[d.name] = { value: processed[d.name], sd: 'allowed' };\n }\n\n return {\n recordType: 'SdJwtCredentialRecord',\n format: 'dc+sd-jwt',\n credential: sdjwt,\n issuerDid: payload.iss,\n ...(opts.issuerName !== undefined ? { issuerName: opts.issuerName } : {}),\n holderDid: payload.sub,\n vct: payload.vct,\n issuedAt: new Date(payload.iat * 1000).toISOString(),\n ...(typeof payload.exp === 'number'\n ? { expiresAt: new Date(payload.exp * 1000).toISOString() }\n : {}),\n claims,\n holderBinding: !!payload.cnf,\n };\n}\n\n// ---------------------------------------------------------------------------------------------\n// CID (port of ixo-self/web/src/vault/cid.ts, itself byte-identical to the portal's\n// lib/kyc/kycVault.ts and jambo's utils/matrixCredential.ts).\n\n// Recursively sort object keys so JSON.stringify yields a canonical, hash-stable form. Must stay\n// byte-identical to jambo's utils/matrixCredential.ts: the CID is the cross-app dedup key for the\n// same credential payload.\nfunction canonicalize(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(canonicalize);\n if (value && typeof value === 'object') {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- kept as ported\n return Object.keys(value as Record<string, unknown>)\n .sort()\n .reduce<Record<string, unknown>>((acc, key) => {\n acc[key] = canonicalize((value as Record<string, unknown>)[key]);\n return acc;\n }, {});\n }\n return value;\n}\n\n/** `\"sha256:\" + hex(sha256(JSON.stringify(canonicalize(record))))`, the Vault's dedup key. */\nexport function credentialCid(record: unknown): string {\n const canonical = JSON.stringify(canonicalize(record));\n return `sha256:${bytesToHex(sha256(utf8(canonical)))}`;\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import { IXO_PERSON_DID, IXO_ENTITY_DID, parseDidWeb } from './chunk-WIEGZFUO.js';
2
+
3
+ // src/sender.ts
4
+ function senderKeyOf(did) {
5
+ if (typeof did !== "string") return null;
6
+ if (did.startsWith("did:ixo:"))
7
+ return IXO_PERSON_DID.test(did) || IXO_ENTITY_DID.test(did) ? did : null;
8
+ return parseDidWeb(did) ? did : null;
9
+ }
10
+
11
+ export { senderKeyOf };
12
+ //# sourceMappingURL=chunk-ZZY4WGQ6.js.map
13
+ //# sourceMappingURL=chunk-ZZY4WGQ6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sender.ts"],"names":[],"mappings":";;;AA4BO,SAAS,YAAY,GAAA,EAA4B;AACtD,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,EAAU,OAAO,IAAA;AACpC,EAAA,IAAI,GAAA,CAAI,WAAW,UAAU,CAAA;AAC3B,IAAA,OAAO,cAAA,CAAe,KAAK,GAAG,CAAA,IAAK,eAAe,IAAA,CAAK,GAAG,IAAI,GAAA,GAAM,IAAA;AACtE,EAAA,OAAO,WAAA,CAAY,GAAG,CAAA,GAAI,GAAA,GAAM,IAAA;AAClC","file":"chunk-ZZY4WGQ6.js","sourcesContent":["/**\n * `@ixo/credentials-kit/sender` — who a sender is, for block lists, pending caps and the catalog\n * (SPEC §16.1, §17.4).\n *\n * One rule for everyone, no list of services: every DID is its own sender, exactly as written. A\n * sender's KEY is the DID itself in its canonical form:\n * - a did:ixo in its canonical form (SPEC §17.5, PD-07): a person `did:ixo:ixo1…` (lower-case\n * bech32 of a 20-byte address, `IXO_PERSON_DID`) or an entity `did:ixo:entity:<32 lower-case\n * hex>` (`IXO_ENTITY_DID`), the only did:ixo forms the resolver resolves. Any other did:ixo\n * (`did:ixo:IXO1…`, `did:ixo:anything`) is null. The bech32 checksum is not verified (the kit\n * has no bech32 decoder); such a DID never resolves, so it can never deposit anything;\n * - a did:web in canonical form (`parseDidWeb`, the parser the resolver uses), whatever its host\n * and path: `did:web:example.com`, `did:web:a.example.com` and `did:web:example.com:user:alice`\n * are three senders, each with its own caps, its own block and its own catalog entry. Blocking\n * one never blocks another DID on the same domain.\n *\n * The did:web must be in canonical form, so one DID has one spelling and a second spelling of one\n * DID can never be a second sender: `senderKeyOf` answers null for any other spelling (trailing\n * dot, upper case, percent-encoding in the host other than `%3A` before a port, an IDN not in\n * punycode, …), and callers refuse it.\n */\nimport { IXO_ENTITY_DID, IXO_PERSON_DID, parseDidWeb } from './did';\n\n/**\n * The sender key of `did`: the DID itself when it is a did:ixo person or entity DID, or a did:web\n * in canonical form. Null for any other did:ixo or did:web spelling (refuse it) and for anything\n * that is not a did:ixo or did:web.\n */\nexport function senderKeyOf(did: string): string | null {\n if (typeof did !== 'string') return null;\n if (did.startsWith('did:ixo:'))\n return IXO_PERSON_DID.test(did) || IXO_ENTITY_DID.test(did) ? did : null;\n return parseDidWeb(did) ? did : null;\n}\n"]}
package/dist/did.d.ts ADDED
@@ -0,0 +1,182 @@
1
+ import { CredentialsKitError } from './index.js';
2
+ import { F as FetchLike, H as HostPolicy } from './http-BhuJ_-RZ.js';
3
+ export { i as isFetchableUrl, a as isPrivateHost } from './http-BhuJ_-RZ.js';
4
+ import { JsonObject } from './index.js';
5
+
6
+ /**
7
+ * The cache the kit's network functions accept. The shape is a subset of Cloudflare's
8
+ * `KVNamespace`, so a KV binding can be passed as is; `createMemoryCache()` is an in-isolate
9
+ * implementation for Node, browsers and tests. A cache is never a source of truth: a read or
10
+ * write failure is treated as a miss and ignored.
11
+ */
12
+ interface KeyValueCache {
13
+ get(key: string): Promise<string | null>;
14
+ put(key: string, value: string, options?: {
15
+ expirationTtl?: number;
16
+ }): Promise<void>;
17
+ }
18
+ /** An in-memory cache with per-entry TTL and a size bound (oldest entry evicted first). */
19
+ declare function createMemoryCache(opts?: {
20
+ maxEntries?: number;
21
+ now?: () => number;
22
+ }): KeyValueCache;
23
+
24
+ interface VerificationMethod {
25
+ id: string;
26
+ type: string;
27
+ controller: string;
28
+ publicKeyMultibase?: string;
29
+ publicKeyBase58?: string;
30
+ publicKeyHex?: string;
31
+ publicKeyJwk?: JsonObject;
32
+ [name: string]: unknown;
33
+ }
34
+ type VerificationRelationshipEntry = string | VerificationMethod;
35
+ interface DidDocument {
36
+ id: string;
37
+ controller?: string[];
38
+ verificationMethod?: VerificationMethod[];
39
+ authentication?: VerificationRelationshipEntry[];
40
+ assertionMethod?: VerificationRelationshipEntry[];
41
+ capabilityInvocation?: VerificationRelationshipEntry[];
42
+ capabilityDelegation?: VerificationRelationshipEntry[];
43
+ keyAgreement?: VerificationRelationshipEntry[];
44
+ service?: unknown[];
45
+ [name: string]: unknown;
46
+ }
47
+ /** `null`: the DID does not exist. Throws `ResolverUnavailable` when the source cannot be read. */
48
+ type DidResolver = (did: string) => Promise<DidDocument | null>;
49
+ /** The relationships a credentials key may be selected under. */
50
+ type KeyRelationship = 'assertionMethod' | 'authentication';
51
+ interface ResolvedKey {
52
+ /** The verification method's absolute id (a DID URL). */
53
+ id: string;
54
+ /** Raw 32-byte Ed25519 public key. */
55
+ publicKey: Uint8Array;
56
+ }
57
+ type ResolverSource = 'blocksync' | 'did_web';
58
+ /** The DID's source could not be read (down, timed out, errored, malformed answer). */
59
+ declare class ResolverUnavailable extends CredentialsKitError {
60
+ readonly code: 'resolver_unavailable';
61
+ readonly source: ResolverSource;
62
+ constructor(source: ResolverSource);
63
+ }
64
+ /** `did:ixo` of a person: an ixo1 account address (20-byte bech32). */
65
+ declare const IXO_PERSON_DID: RegExp;
66
+ /** `did:ixo` of an entity. */
67
+ declare const IXO_ENTITY_DID: RegExp;
68
+ /** The DID part of a DID URL (`did:x:y#frag` → `did:x:y`). */
69
+ declare function didFromKid(kid: string): string;
70
+ /**
71
+ * A DID document in the kit's normal form: every id absolute, relationships as arrays,
72
+ * `controller` as an array. Unknown members are kept. Returns null when `v` is not a document
73
+ * for `did`.
74
+ */
75
+ declare function normalizeDidDocument(did: string, v: unknown): DidDocument | null;
76
+ /**
77
+ * The raw Ed25519 key of a verification method, or null when it has none. Key strings longer
78
+ * than an Ed25519 key can be (`MAX_KEY_STRING_LENGTHS`) are refused before decoding.
79
+ */
80
+ declare function ed25519KeyOfMethod(vm: VerificationMethod): Uint8Array | null;
81
+ /** Most verification methods `keysFor` considers under one relationship. */
82
+ declare const MAX_METHODS_PER_RELATIONSHIP = 32;
83
+ /**
84
+ * The Ed25519 keys listed under `relationship` whose verification method is controlled by the
85
+ * document's own DID and whose id is a DID URL of that DID. Only the first
86
+ * `MAX_METHODS_PER_RELATIONSHIP` distinct entries are considered.
87
+ */
88
+ declare function keysFor(doc: DidDocument, relationship: KeyRelationship): ResolvedKey[];
89
+ /**
90
+ * The ONE key `kid` names, if it is an Ed25519 key of this document under `relationship`
91
+ * (see `keysFor`). A `#fragment` kid is taken relative to the document. Null otherwise. Only
92
+ * that method's key material is decoded.
93
+ */
94
+ declare function findKey(doc: DidDocument, kid: string, relationship: KeyRelationship): ResolvedKey | null;
95
+ /** First resolver that knows the DID wins; `ResolverUnavailable` from any of them propagates. */
96
+ declare function composeResolvers(...resolvers: DidResolver[]): DidResolver;
97
+ /** A resolver over fixed documents (tests, local development, the service's own DID). */
98
+ declare function createStaticResolver(docs: DidDocument[]): DidResolver;
99
+ interface IxoResolverOptions {
100
+ /** Blocksync GraphQL endpoint, e.g. `https://blocksync.ixo.earth/graphql`. */
101
+ blocksyncUrl: string;
102
+ fetch: FetchLike;
103
+ /** Positive results only. Default: a per-resolver in-memory cache. */
104
+ cache?: KeyValueCache;
105
+ /** Default 600 s. A key removed from the IID keeps working for up to this long. */
106
+ cacheTtlSeconds?: number;
107
+ timeoutMs?: number;
108
+ }
109
+ /**
110
+ * did:ixo resolver over Blocksync GraphQL. Resolves people (`did:ixo:ixo1…`) and entities
111
+ * (`did:ixo:entity:<32 hex>`); returns null for any other DID without a network call.
112
+ */
113
+ declare function createIxoResolver(opts: IxoResolverOptions): DidResolver;
114
+ interface WebResolverOptions {
115
+ fetch: FetchLike;
116
+ /**
117
+ * Resolve a did:web whose host is localhost / 127.0.0.1 / [::1] over plain http (local
118
+ * development only). Every other host is always https. Needs `allowLocal` as well.
119
+ */
120
+ allowHttp?: boolean;
121
+ /**
122
+ * Resolve a did:web whose host is loopback, private, link-local, unique-local or a local name.
123
+ * Default false (such a DID resolves to null). Local development only.
124
+ */
125
+ allowLocal?: boolean;
126
+ cache?: KeyValueCache;
127
+ cacheTtlSeconds?: number;
128
+ timeoutMs?: number;
129
+ }
130
+ /** Longest did:web accepted. */
131
+ declare const MAX_DID_WEB_LENGTH = 2048;
132
+ /** A did:web in canonical form, split into its parts (`parseDidWeb`). */
133
+ interface DidWeb {
134
+ /** The host: lower case, ASCII (an IDN in punycode), no trailing dot, no port. */
135
+ host: string;
136
+ /** The port, only when the DID names one (never 443). */
137
+ port?: number;
138
+ /** The host part exactly as the DID writes it (`issuer.example`, `localhost%3A34672`). */
139
+ authority: string;
140
+ /** The path segments after the host, as written (empty for a bare-host did:web). */
141
+ path: string[];
142
+ }
143
+ /**
144
+ * Is `host` a host name in canonical form: lower-case LDH labels, no empty label (so no trailing
145
+ * dot), at most 253 characters, and one the WHATWG URL parser leaves exactly as it is (that
146
+ * refuses punycode that does not decode, and numeric forms such as `0x7f.1` that it would rewrite
147
+ * to another address)?
148
+ */
149
+ declare function isCanonicalHost(host: string): boolean;
150
+ /**
151
+ * Parse a did:web (did:web method spec, https://w3c-ccg.github.io/did-method-web/:
152
+ * `did:web:<host>[%3A<port>][:<path>…]`), accepting ONLY its canonical spelling, so that one DID
153
+ * has one spelling (SPEC §17.4, R3-W-03). Any host (a subdomain is fine), any port (written
154
+ * `%3A<port>`) and any path the spec allows are accepted. Refused (null): upper case in the host,
155
+ * a trailing dot, any percent-encoding in the host other than the `%3A` before a port, `%3a`, a
156
+ * port of 443 (the URL `https://` already implies; naming it is a second spelling) or with a
157
+ * leading zero, a host that is not ASCII (write an IDN in punycode), an IP address (the spec: the
158
+ * identifier "MUST NOT include IP addresses"), an empty path segment, a segment with a character
159
+ * DID syntax does not allow (`idchar`: letters, digits, `.`, `-`, `_` and percent-encoding), and a
160
+ * segment that is or percent-encodes `.`, `..`, `/`, `\`, `?` or `#`. In a path segment (SPEC
161
+ * §17.5, PD-06) also: lower-case hex in a percent-encoding (`%7e`; write `%7E`), a `%` not followed
162
+ * by two hex digits, a percent-encoded unreserved character (`%61lice` is `alice`; RFC 3986
163
+ * §6.2.2.2: `A-Z a-z 0-9 - . _ ~`, so a `~` cannot be written at all: DID syntax has no bare `~`),
164
+ * and a `.well-known` segment in any letter case (`did:web:example.com:.well-known` would read the
165
+ * bare host's document). This says nothing about whether the host may be fetched (see
166
+ * `didWebUrl`).
167
+ */
168
+ declare function parseDidWeb(did: string): DidWeb | null;
169
+ /**
170
+ * The URL of a did:web document (did:web method spec §3.2): `did:web:<host>[%3A<port>][:<path>…]`
171
+ * → `https://<host>[:<port>]/.well-known/did.json` or `https://<host>[:<port>]/<path…>/did.json`.
172
+ * Null when the DID is not a did:web in canonical form (`parseDidWeb`: one spelling per server, so
173
+ * a server cannot appear under several DIDs), or when the host is local/private and `allowLocal`
174
+ * is not set. `allowHttp` switches a loopback host to http. A boolean `policy` (the earlier
175
+ * signature) means `{ allowHttp: b, allowLocal: b }`.
176
+ */
177
+ declare function didWebUrl(did: string, policy?: boolean | HostPolicy): string | null;
178
+ /** did:web resolver. 404/410 → null; any other failure → `ResolverUnavailable`. */
179
+ declare function createWebResolver(opts: WebResolverOptions): DidResolver;
180
+
181
+ export { FetchLike, HostPolicy, IXO_ENTITY_DID, IXO_PERSON_DID, MAX_DID_WEB_LENGTH, MAX_METHODS_PER_RELATIONSHIP, ResolverUnavailable, composeResolvers, createIxoResolver, createMemoryCache, createStaticResolver, createWebResolver, didFromKid, didWebUrl, ed25519KeyOfMethod, findKey, isCanonicalHost, keysFor, normalizeDidDocument, parseDidWeb };
182
+ export type { DidDocument, DidResolver, DidWeb, IxoResolverOptions, KeyRelationship, KeyValueCache, ResolvedKey, ResolverSource, VerificationMethod, VerificationRelationshipEntry, WebResolverOptions };
package/dist/did.js ADDED
@@ -0,0 +1,6 @@
1
+ export { IXO_ENTITY_DID, IXO_PERSON_DID, MAX_DID_WEB_LENGTH, MAX_METHODS_PER_RELATIONSHIP, ResolverUnavailable, composeResolvers, createIxoResolver, createMemoryCache, createStaticResolver, createWebResolver, didFromKid, didWebUrl, ed25519KeyOfMethod, findKey, isCanonicalHost, keysFor, normalizeDidDocument, parseDidWeb } from './chunk-WIEGZFUO.js';
2
+ export { isFetchableUrl, isPrivateHost } from './chunk-Q3MZNMCK.js';
3
+ import './chunk-VXKONZK4.js';
4
+ import './chunk-AQ3IINHC.js';
5
+ //# sourceMappingURL=did.js.map
6
+ //# sourceMappingURL=did.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"did.js"}
@@ -0,0 +1,117 @@
1
+ import { HostPolicy } from './did.js';
2
+
3
+ /**
4
+ * `@ixo/credentials-kit/display` — the card look of a credential (SPEC §15.1): what an issuer may
5
+ * set (`CardDisplay`), how it is validated (`parseCardDisplay`), and the look a wallet shows,
6
+ * with a neutral fallback derived from the issuer DID (`cardLook`, `markLook`).
7
+ *
8
+ * Pure functions: no fetch, no dependencies, no Node built-ins (workerd, Node 22, browsers). The
9
+ * image URLs are only validated here (https, default port, no query string or fragment: see
10
+ * `parseCardUri`); a wallet that shows them fetches them itself under its own rules (the Auth Hub
11
+ * proxies them: png, jpeg or webp, at most 256 KB and 2048 px per side, no animation, never SVG).
12
+ *
13
+ * Member names follow OpenID4VCI credential display metadata, flattened: `background_color`,
14
+ * `text_color`, `logo.uri` → `logo_uri`, `background_image.uri` → `background_image_uri`.
15
+ */
16
+
17
+ /** The look an issuer may set for a credential. Every member is optional. */
18
+ interface CardDisplay {
19
+ /** `#rrggbb`, lower case once parsed. */
20
+ background_color?: string;
21
+ /** `#rrggbb`; a wallet ignores it when its contrast with the background is below 4.5:1. */
22
+ text_color?: string;
23
+ /** https URL (default port, no query or fragment) of the issuer's mark (png, jpeg or webp), shown small. */
24
+ logo_uri?: string;
25
+ /** https URL (default port, no query or fragment) of an image that covers the card (png, jpeg or webp). */
26
+ background_image_uri?: string;
27
+ }
28
+ /** The members of `CardDisplay`, in a fixed order. */
29
+ declare const CARD_DISPLAY_MEMBERS: readonly ["background_color", "text_color", "logo_uri", "background_image_uri"];
30
+ type CardDisplayMember = (typeof CARD_DISPLAY_MEMBERS)[number];
31
+ /** Longest accepted image URL (characters, before and after normalising). */
32
+ declare const MAX_CARD_URI_LENGTH = 512;
33
+ /** The only colour form accepted anywhere: `#rrggbb`. */
34
+ declare const CARD_COLOR_PATTERN: RegExp;
35
+ /** Minimum WCAG contrast for the text colour an issuer chose (WCAG 2.x AA, normal text). */
36
+ declare const MIN_TEXT_CONTRAST = 4.5;
37
+ /** Foregrounds used when the issuer's text colour is missing or too faint. */
38
+ declare const CARD_LIGHT_TEXT = "#ffffff";
39
+ declare const CARD_DARK_TEXT = "#111111";
40
+ /**
41
+ * The fixed set of deep hues the neutral card picks from (by a hash of the issuer DID). Every
42
+ * one has at least 7:1 contrast with white.
43
+ */
44
+ declare const CARD_HUES: readonly string[];
45
+ /** Number of generated card patterns; `pattern` is 0 … CARD_PATTERNS - 1. */
46
+ declare const CARD_PATTERNS = 6;
47
+ /** What a wallet draws for a card. Colours are `#rrggbb`, lower case. */
48
+ interface CardLook {
49
+ background: string;
50
+ foreground: string;
51
+ /** 1–2 upper-case letters (`?` only when neither a name nor a DID gives one). */
52
+ monogram: string;
53
+ /** 0–5, from the issuer DID. */
54
+ pattern: number;
55
+ }
56
+ /** What a wallet draws for a party (issuer or verifier) mark. */
57
+ interface MarkLook {
58
+ color: string;
59
+ monogram: string;
60
+ }
61
+ /** A `#rrggbb` colour in lower case, or `undefined` for anything else (never throws). */
62
+ declare function parseCardColor(value: unknown): string | undefined;
63
+ /**
64
+ * An image URL a wallet may fetch for a card (SPEC §15.1, §15.4 L7), returned in normalised form
65
+ * (`URL.href`: lower-case scheme and host, IDN hosts in punycode, an explicit default port
66
+ * removed), or `undefined` for anything else (never throws). Rules:
67
+ * - https on the default port (`:443` is accepted and normalised away; any other port is refused);
68
+ * - no query string and no fragment, not even an empty `?` or `#` (a per-person query string
69
+ * would turn the image into a tracking pixel; an odd port would turn the wallet's image proxy
70
+ * into a fetch relay);
71
+ * - written as `https://host/…` (the parser's repairs of `https:host` or `https:///host` refused);
72
+ * - the kit's fetchable-URL rules (`isFetchableUrl`): no user name or password (not even an
73
+ * empty `@`), a public host;
74
+ * - no trailing dot on the host (`cdn.example.com.` is a second spelling of the same host);
75
+ * - at most `MAX_CARD_URI_LENGTH` characters, before and after normalising.
76
+ *
77
+ * Local development (`allowHttp` and `allowLocal` together) also accepts http, and any port, on a
78
+ * loopback host (`localhost`, `127.0.0.1`, `[::1]`) only; `allowLocal` alone also accepts https
79
+ * on other local hosts on the default port. The query string and fragment rule has no exception.
80
+ */
81
+ declare function parseCardUri(value: unknown, policy?: HostPolicy): string | undefined;
82
+ /**
83
+ * Validate an issuer's `display` look: returns only the members that pass (colours `#rrggbb`,
84
+ * normalised to lower case; URLs as `parseCardUri`). Unknown members, wrong types and anything
85
+ * that is not an object give nothing. Never throws.
86
+ */
87
+ declare function parseCardDisplay(input: unknown, policy?: HostPolicy): CardDisplay;
88
+ /**
89
+ * WCAG contrast ratio of two `#rrggbb` colours, 1 … 21 (order does not matter). Throws
90
+ * `TypeError` for anything that is not `#rrggbb`.
91
+ */
92
+ declare function contrastRatio(a: string, b: string): number;
93
+ /**
94
+ * The look of a credential's card. `background` is the issuer's `background_color`, else a deep
95
+ * hue picked by the DID hash from `CARD_HUES`; `foreground` is the issuer's `text_color` when it
96
+ * has at least 4.5:1 contrast with the background, else white or near-black, whichever contrasts
97
+ * more; `monogram` is 1–2 upper-case initials of `issuerName` (or one character from the DID);
98
+ * `pattern` is 0–5 from the DID hash. `display` may be unvalidated: only valid colours are used.
99
+ * Never throws.
100
+ */
101
+ declare function cardLook(input: {
102
+ display?: CardDisplay | null;
103
+ issuerDid: string;
104
+ issuerName?: string | null;
105
+ }): CardLook;
106
+ /**
107
+ * The mark of a party (issuer or verifier, e.g. in inbox rows): its `color` when valid, else the
108
+ * same DID-derived hue `cardLook` uses; the same monogram rule. Never throws.
109
+ */
110
+ declare function markLook(input: {
111
+ color?: string | null;
112
+ did: string;
113
+ name?: string | null;
114
+ }): MarkLook;
115
+
116
+ export { CARD_COLOR_PATTERN, CARD_DARK_TEXT, CARD_DISPLAY_MEMBERS, CARD_HUES, CARD_LIGHT_TEXT, CARD_PATTERNS, MAX_CARD_URI_LENGTH, MIN_TEXT_CONTRAST, cardLook, contrastRatio, markLook, parseCardColor, parseCardDisplay, parseCardUri };
117
+ export type { CardDisplay, CardDisplayMember, CardLook, MarkLook };