@suveren/gateway 0.19.5 → 0.20.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/content/integrations/crm.json +1 -1
- package/content/integrations/deploy-github.json +1 -1
- package/content/integrations/erp.json +1 -1
- package/content/integrations/mail.json +1 -1
- package/content/integrations/records.json +1 -1
- package/dist/control-plane/index.mjs +21 -13
- package/dist/mcp-server/{chunk-ZRRZFGRL.mjs → chunk-APIQIEUA.mjs} +39 -27
- package/dist/mcp-server/http.mjs +308 -97
- package/dist/mcp-server/report-verify-cli.mjs +1 -1
- package/dist/ui/assets/index-1s1q-XLL.js +116 -0
- package/dist/ui/index.html +1 -1
- package/node_modules/@hap/core/README.md +92 -29
- package/node_modules/@hap/core/dist/index.d.mts +614 -593
- package/node_modules/@hap/core/dist/index.d.ts +614 -593
- package/node_modules/@hap/core/dist/index.js +774 -749
- package/node_modules/@hap/core/dist/index.mjs +744 -724
- package/node_modules/@hap/core/package.json +1 -1
- package/node_modules/@hap/core/src/base64url.ts +39 -0
- package/node_modules/@hap/core/src/commitment-modes.ts +0 -21
- package/node_modules/@hap/core/src/errors.ts +76 -0
- package/node_modules/@hap/core/src/frame.ts +47 -178
- package/node_modules/@hap/core/src/gatekeeper.ts +219 -600
- package/node_modules/@hap/core/src/identity.ts +9 -12
- package/node_modules/@hap/core/src/index.ts +8 -4
- package/node_modules/@hap/core/src/mandate.ts +177 -166
- package/node_modules/@hap/core/src/owner-signature.ts +282 -0
- package/node_modules/@hap/core/src/profile.ts +234 -0
- package/node_modules/@hap/core/src/ticket.ts +148 -0
- package/node_modules/@hap/core/src/types.ts +191 -344
- package/node_modules/@hap/core/src/versions.ts +47 -0
- package/package.json +2 -2
- package/profiles/calendar/0.6.profile.json +191 -0
- package/profiles/charge/0.6.profile.json +211 -0
- package/profiles/customers/0.9.profile.json +182 -0
- package/profiles/delegation/0.3.profile.json +118 -0
- package/profiles/deploy/0.11.profile.json +177 -0
- package/profiles/email/0.8.profile.json +226 -0
- package/profiles/index.json +12 -1
- package/profiles/publish/0.6.profile.json +187 -0
- package/profiles/purchase/0.7.profile.json +235 -0
- package/profiles/records/0.6.profile.json +118 -0
- package/profiles/reporting/0.3.profile.json +98 -0
- package/profiles/sales/0.4.profile.json +291 -0
- package/dist/ui/assets/index-Du68zJhH.js +0 -116
- package/node_modules/@hap/core/src/attestation.ts +0 -173
- package/node_modules/@hap/core/src/receipt.ts +0 -74
|
@@ -23,6 +23,88 @@ function canonicalize(value) {
|
|
|
23
23
|
return "{" + parts.join(",") + "}";
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// src/base64url.ts
|
|
27
|
+
var STRICT_B64URL = /^[A-Za-z0-9_-]*$/;
|
|
28
|
+
function toBase64Url(bytes) {
|
|
29
|
+
return Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
30
|
+
}
|
|
31
|
+
function fromBase64Url(s) {
|
|
32
|
+
if (!STRICT_B64URL.test(s)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`${JSON.stringify(s)} is not strict base64url \u2014 standard base64 ("+"/"/"/padding) is retired (protocol.md \u2192 Mandate Payload, rule 3).`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const padding = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
|
|
38
|
+
return new Uint8Array(Buffer.from(s + padding, "base64"));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/errors.ts
|
|
42
|
+
var MANDATE_ERROR_CODES = [
|
|
43
|
+
"BOUNDS_HASH_MISMATCH",
|
|
44
|
+
"SCOPE_HASH_MISMATCH",
|
|
45
|
+
"INVALID_SIGNATURE",
|
|
46
|
+
"OWNER_NOT_COVERED",
|
|
47
|
+
"TTL_EXPIRED",
|
|
48
|
+
"PROFILE_NOT_FOUND",
|
|
49
|
+
"PROFILE_INVALID",
|
|
50
|
+
"PROFILE_HASH_MISMATCH",
|
|
51
|
+
"COVERAGE_INSUFFICIENT",
|
|
52
|
+
"MALFORMED_MANDATE",
|
|
53
|
+
"VERSION_UNSUPPORTED",
|
|
54
|
+
"OWNER_NOT_APPROVER",
|
|
55
|
+
"BOUNDS_INVALID_VALUE",
|
|
56
|
+
"SCOPE_INVALID_VALUE",
|
|
57
|
+
"ABOVE_CAP_CONFIG_INVALID",
|
|
58
|
+
"OWNER_SIGNATURE_REQUIRED",
|
|
59
|
+
"OWNER_SIGNATURE_INVALID"
|
|
60
|
+
];
|
|
61
|
+
var TICKET_ERROR_CODES = [
|
|
62
|
+
"MANDATE_NOT_FOUND",
|
|
63
|
+
"MANDATE_EXPIRED",
|
|
64
|
+
"MANDATE_REVOKED",
|
|
65
|
+
"BOUND_EXCEEDED",
|
|
66
|
+
"CUMULATIVE_LIMIT_EXCEEDED",
|
|
67
|
+
"COVERAGE_INSUFFICIENT",
|
|
68
|
+
"PROFILE_NOT_FOUND",
|
|
69
|
+
"INVALID_ACTION_TYPE",
|
|
70
|
+
"APPROVAL_REQUIRED",
|
|
71
|
+
"PROPOSAL_REQUIRED",
|
|
72
|
+
"PROPOSAL_NOT_FOUND",
|
|
73
|
+
"PROPOSAL_NOT_APPROVED",
|
|
74
|
+
"PROPOSAL_EXPIRED",
|
|
75
|
+
"PROPOSAL_REJECTED",
|
|
76
|
+
"PROPOSAL_MISMATCH",
|
|
77
|
+
"PROPOSAL_MANDATE_MISMATCH",
|
|
78
|
+
"PROPOSAL_ALREADY_EXECUTED",
|
|
79
|
+
"MALFORMED_TICKET_REQUEST",
|
|
80
|
+
"IDEMPOTENCY_KEY_REQUIRED",
|
|
81
|
+
"IDEMPOTENCY_MISMATCH",
|
|
82
|
+
"APPROVAL_SIGNATURE_REQUIRED",
|
|
83
|
+
"APPROVAL_SIGNATURE_INVALID"
|
|
84
|
+
];
|
|
85
|
+
var HapError = class extends Error {
|
|
86
|
+
constructor(code, message) {
|
|
87
|
+
super(`${code}: ${message}`);
|
|
88
|
+
this.code = code;
|
|
89
|
+
this.name = "HapError";
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/versions.ts
|
|
94
|
+
var PROTOCOL_VERSION = "0.7";
|
|
95
|
+
var SUPPORTED_VERSIONS = ["0.7"];
|
|
96
|
+
function negotiateVersion(requested, supported = SUPPORTED_VERSIONS) {
|
|
97
|
+
const requestedVersions = requested ?? ["0.6"];
|
|
98
|
+
const common = supported.filter((v) => requestedVersions.includes(v)).sort().reverse();
|
|
99
|
+
if (common.length === 0) {
|
|
100
|
+
throw new HapError(
|
|
101
|
+
"VERSION_UNSUPPORTED",
|
|
102
|
+
`no protocol version in [${requestedVersions.join(", ")}] is supported by this implementation (supports [${supported.join(", ")}])`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return common[0];
|
|
106
|
+
}
|
|
107
|
+
|
|
26
108
|
// src/content-binding.ts
|
|
27
109
|
import { createHash } from "crypto";
|
|
28
110
|
function canonicalizeText(input) {
|
|
@@ -166,9 +248,9 @@ function validateSubject(subject, opts) {
|
|
|
166
248
|
} else if (method === "eudi") {
|
|
167
249
|
if (trust_root !== "external") errors.push('eudi requires trust_root "external"');
|
|
168
250
|
if (!verifier) errors.push("eudi requires a verifier");
|
|
169
|
-
if (opts?.
|
|
170
|
-
const entry = opts.
|
|
171
|
-
if (!entry) errors.push('eudi requires
|
|
251
|
+
if (opts?.mandateOwners !== void 0) {
|
|
252
|
+
const entry = opts.mandateOwners.find((m) => m.did === did && m.binding === "eudi");
|
|
253
|
+
if (!entry) errors.push('eudi requires a mandate_owners entry with binding "eudi" for this DID');
|
|
172
254
|
}
|
|
173
255
|
} else {
|
|
174
256
|
errors.push(`high assurance requires method "as_vouched" or "eudi", got "${method}"`);
|
|
@@ -192,48 +274,6 @@ function deriveIdentityLine(subject, { operatorName }) {
|
|
|
192
274
|
|
|
193
275
|
// src/frame.ts
|
|
194
276
|
import { createHash as createHash3 } from "crypto";
|
|
195
|
-
function validateFrameParams(params, profile) {
|
|
196
|
-
const errors = [];
|
|
197
|
-
if (!profile.frameSchema) {
|
|
198
|
-
return { valid: false, errors: ["Profile does not have a frameSchema"] };
|
|
199
|
-
}
|
|
200
|
-
for (const [fieldName, fieldDef] of Object.entries(profile.frameSchema.fields)) {
|
|
201
|
-
if (fieldDef.required && !(fieldName in params)) {
|
|
202
|
-
errors.push(`Missing required field: ${fieldName}`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
for (const [field, value] of Object.entries(params)) {
|
|
206
|
-
const fieldDef = profile.frameSchema.fields[field];
|
|
207
|
-
if (!fieldDef) {
|
|
208
|
-
errors.push(`Unknown field "${field}" not defined in profile ${profile.id}`);
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
if (fieldDef.type === "number" && typeof value !== "number") {
|
|
212
|
-
errors.push(`Field "${field}" must be a number, got ${typeof value}`);
|
|
213
|
-
}
|
|
214
|
-
if (fieldDef.type === "string" && typeof value !== "string") {
|
|
215
|
-
errors.push(`Field "${field}" must be a string, got ${typeof value}`);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
return { valid: errors.length === 0, errors };
|
|
219
|
-
}
|
|
220
|
-
function canonicalFrame(params, profile) {
|
|
221
|
-
const validation = validateFrameParams(params, profile);
|
|
222
|
-
if (!validation.valid) {
|
|
223
|
-
throw new Error(`Invalid frame parameters: ${validation.errors.join("; ")}`);
|
|
224
|
-
}
|
|
225
|
-
const lines = profile.frameSchema.keyOrder.map(
|
|
226
|
-
(key) => `${key}=${String(params[key])}`
|
|
227
|
-
);
|
|
228
|
-
return lines.join("\n");
|
|
229
|
-
}
|
|
230
|
-
function frameHash(canonicalFrameString) {
|
|
231
|
-
const hash = createHash3("sha256").update(canonicalFrameString, "utf8").digest("hex");
|
|
232
|
-
return `sha256:${hash}`;
|
|
233
|
-
}
|
|
234
|
-
function computeFrameHash(params, profile) {
|
|
235
|
-
return frameHash(canonicalFrame(params, profile));
|
|
236
|
-
}
|
|
237
277
|
function validateBoundsParams(params, profile) {
|
|
238
278
|
const errors = [];
|
|
239
279
|
if (!profile.boundsSchema) {
|
|
@@ -259,23 +299,23 @@ function validateBoundsParams(params, profile) {
|
|
|
259
299
|
}
|
|
260
300
|
return { valid: errors.length === 0, errors };
|
|
261
301
|
}
|
|
262
|
-
function
|
|
302
|
+
function validateScopeParams(params, profile) {
|
|
263
303
|
const errors = [];
|
|
264
|
-
if (!profile.
|
|
304
|
+
if (!profile.scopeSchema) {
|
|
265
305
|
if (Object.keys(params).length > 0) {
|
|
266
|
-
errors.push("Profile does not have a
|
|
306
|
+
errors.push("Profile does not have a scopeSchema but scope params were provided");
|
|
267
307
|
}
|
|
268
308
|
return { valid: errors.length === 0, errors };
|
|
269
309
|
}
|
|
270
|
-
for (const [fieldName, fieldDef] of Object.entries(profile.
|
|
310
|
+
for (const [fieldName, fieldDef] of Object.entries(profile.scopeSchema.fields)) {
|
|
271
311
|
if (fieldDef.required && !(fieldName in params)) {
|
|
272
312
|
errors.push(`Missing required field: ${fieldName}`);
|
|
273
313
|
}
|
|
274
314
|
}
|
|
275
315
|
for (const [field, value] of Object.entries(params)) {
|
|
276
|
-
const fieldDef = profile.
|
|
316
|
+
const fieldDef = profile.scopeSchema.fields[field];
|
|
277
317
|
if (!fieldDef) {
|
|
278
|
-
errors.push(`Unknown field "${field}" not defined in
|
|
318
|
+
errors.push(`Unknown field "${field}" not defined in scopeSchema of profile ${profile.id}`);
|
|
279
319
|
continue;
|
|
280
320
|
}
|
|
281
321
|
if (fieldDef.type === "number" && typeof value !== "number") {
|
|
@@ -287,38 +327,6 @@ function validateContextParams(params, profile) {
|
|
|
287
327
|
}
|
|
288
328
|
return { valid: errors.length === 0, errors };
|
|
289
329
|
}
|
|
290
|
-
function validateBoundsRequiredFor(profile) {
|
|
291
|
-
const errors = [];
|
|
292
|
-
const boundsSchema = profile.boundsSchema;
|
|
293
|
-
if (!boundsSchema) return errors;
|
|
294
|
-
const actionTypes = boundsSchema.actionTypes ? [...boundsSchema.actionTypes] : void 0;
|
|
295
|
-
for (const [fieldName, fieldDef] of Object.entries(boundsSchema.fields)) {
|
|
296
|
-
const bt = fieldDef.boundType;
|
|
297
|
-
if (!bt) continue;
|
|
298
|
-
const requiredFor = bt.requiredFor;
|
|
299
|
-
if (requiredFor === void 0) continue;
|
|
300
|
-
if (!Array.isArray(requiredFor) || !requiredFor.every((v) => typeof v === "string")) {
|
|
301
|
-
errors.push(`Bound "${fieldName}": requiredFor must be a string array.`);
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
if (bt.kind !== "per_transaction") {
|
|
305
|
-
errors.push(
|
|
306
|
-
`Bound "${fieldName}": requiredFor is only meaningful on a per_transaction bound (found on kind "${bt.kind}") \u2014 every enforcement point ignores it there. Remove it.`
|
|
307
|
-
);
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
if (actionTypes) {
|
|
311
|
-
for (const actionType of requiredFor) {
|
|
312
|
-
if (!actionTypes.includes(actionType)) {
|
|
313
|
-
errors.push(
|
|
314
|
-
`Bound "${fieldName}": requiredFor names action type "${actionType}", which is not in this profile's boundsSchema.actionTypes registry [${actionTypes.join(", ")}]. It can never match a real execution and the requirement is dead.`
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return errors;
|
|
321
|
-
}
|
|
322
330
|
var CanonicalValueError = class extends Error {
|
|
323
331
|
code;
|
|
324
332
|
field;
|
|
@@ -365,23 +373,23 @@ function canonicalBounds(params, profile) {
|
|
|
365
373
|
}
|
|
366
374
|
return canonicalRecords(params, profile.boundsSchema.keyOrder, "BOUNDS_INVALID_VALUE");
|
|
367
375
|
}
|
|
368
|
-
function
|
|
369
|
-
if (!profile.
|
|
376
|
+
function canonicalScope(params, profile) {
|
|
377
|
+
if (!profile.scopeSchema || Object.keys(profile.scopeSchema.fields).length === 0) {
|
|
370
378
|
return "";
|
|
371
379
|
}
|
|
372
|
-
const validation =
|
|
380
|
+
const validation = validateScopeParams(params, profile);
|
|
373
381
|
if (!validation.valid) {
|
|
374
|
-
throw new Error(`Invalid
|
|
382
|
+
throw new Error(`Invalid scope parameters: ${validation.errors.join("; ")}`);
|
|
375
383
|
}
|
|
376
|
-
return canonicalRecords(params, profile.
|
|
384
|
+
return canonicalRecords(params, profile.scopeSchema.keyOrder, "SCOPE_INVALID_VALUE");
|
|
377
385
|
}
|
|
378
386
|
function computeBoundsHash(params, profile) {
|
|
379
387
|
const canonical = canonicalBounds(params, profile);
|
|
380
388
|
const hash = createHash3("sha256").update(canonical, "utf8").digest("hex");
|
|
381
389
|
return `sha256:${hash}`;
|
|
382
390
|
}
|
|
383
|
-
function
|
|
384
|
-
const canonical =
|
|
391
|
+
function computeScopeHash(params, profile) {
|
|
392
|
+
const canonical = canonicalScope(params, profile);
|
|
385
393
|
const hash = createHash3("sha256").update(canonical, "utf8").digest("hex");
|
|
386
394
|
return `sha256:${hash}`;
|
|
387
395
|
}
|
|
@@ -398,361 +406,672 @@ function allowedCommitmentModes(profile) {
|
|
|
398
406
|
function isCommitmentModeAllowed(profile, mode) {
|
|
399
407
|
return isSignable(mode) && allowedCommitmentModes(profile).includes(mode);
|
|
400
408
|
}
|
|
401
|
-
function validateCommitmentModes(profile) {
|
|
402
|
-
const declared = profile.commitment_modes;
|
|
403
|
-
if (declared === void 0) return [];
|
|
404
|
-
if (!Array.isArray(declared)) return ["commitment_modes must be a list of modes."];
|
|
405
|
-
const errors = [];
|
|
406
|
-
if (declared.length === 0) errors.push("commitment_modes is empty \u2014 no mandate could ever be signed under this profile.");
|
|
407
|
-
const seen = /* @__PURE__ */ new Set();
|
|
408
|
-
for (const m of declared) {
|
|
409
|
-
if (!isSignable(m)) errors.push(`commitment_modes: "${String(m)}" is not a mode a signer can choose (${SIGNABLE_COMMITMENT_MODES.join(", ")}).`);
|
|
410
|
-
if (seen.has(m)) errors.push(`commitment_modes: "${String(m)}" appears twice.`);
|
|
411
|
-
seen.add(m);
|
|
412
|
-
}
|
|
413
|
-
return errors;
|
|
414
|
-
}
|
|
415
409
|
|
|
416
|
-
// src/
|
|
410
|
+
// src/profile.ts
|
|
417
411
|
import { createHash as createHash4 } from "crypto";
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
try {
|
|
421
|
-
const base64 = blob.replace(/-/g, "+").replace(/_/g, "/");
|
|
422
|
-
const padding = base64.length % 4 === 0 ? "" : "=".repeat(4 - base64.length % 4);
|
|
423
|
-
const json = Buffer.from(base64 + padding, "base64").toString("utf8");
|
|
424
|
-
return JSON.parse(json);
|
|
425
|
-
} catch {
|
|
426
|
-
throw new Error("MALFORMED_ATTESTATION: Failed to decode attestation blob");
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
function encodeAttestationBlob(attestation) {
|
|
430
|
-
const json = JSON.stringify(attestation);
|
|
431
|
-
const base64 = Buffer.from(json, "utf8").toString("base64");
|
|
432
|
-
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
433
|
-
}
|
|
434
|
-
function attestationId(blob) {
|
|
435
|
-
const hash = createHash4("sha256").update(blob, "utf8").digest("hex");
|
|
412
|
+
function computeProfileHash(parsed) {
|
|
413
|
+
const hash = createHash4("sha256").update(canonicalize(parsed), "utf8").digest("hex");
|
|
436
414
|
return `sha256:${hash}`;
|
|
437
415
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
416
|
+
var RETIRED_PROFILE_KEYS = {
|
|
417
|
+
contextSchema: "scopeSchema",
|
|
418
|
+
ownerMandate: "ownerSignature",
|
|
419
|
+
receipt_lookup: "ticket_lookup",
|
|
420
|
+
frameSchema: "(removed \u2014 v0.3 execution frames are retired)"
|
|
421
|
+
};
|
|
422
|
+
function validateProfile(profile) {
|
|
423
|
+
const errors = [];
|
|
424
|
+
const p = profile;
|
|
425
|
+
for (const [retired, current] of Object.entries(RETIRED_PROFILE_KEYS)) {
|
|
426
|
+
if (retired in p) {
|
|
427
|
+
errors.push({
|
|
428
|
+
code: "PROFILE_INVALID",
|
|
429
|
+
field: retired,
|
|
430
|
+
message: `"${retired}" is retired in v0.7 \u2014 use "${current}" instead.`
|
|
431
|
+
});
|
|
447
432
|
}
|
|
448
|
-
} catch (error) {
|
|
449
|
-
if (error instanceof Error && error.message.startsWith("INVALID_SIGNATURE")) throw error;
|
|
450
|
-
throw new Error(`INVALID_SIGNATURE: Signature verification error: ${error}`);
|
|
451
433
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
);
|
|
434
|
+
if (Array.isArray(p.requiredGates) && p.requiredGates.includes("decision_owner")) {
|
|
435
|
+
errors.push({
|
|
436
|
+
code: "PROFILE_INVALID",
|
|
437
|
+
field: "requiredGates",
|
|
438
|
+
message: '"decision_owner" is retired in v0.7 \u2014 the required gate is "mandate_owner".'
|
|
439
|
+
});
|
|
458
440
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
441
|
+
const boundsSchema = profile.boundsSchema;
|
|
442
|
+
if (boundsSchema) {
|
|
443
|
+
const actionTypes = boundsSchema.actionTypes ? [...boundsSchema.actionTypes] : void 0;
|
|
444
|
+
for (const [fieldName, fieldDef] of Object.entries(boundsSchema.fields)) {
|
|
445
|
+
if (fieldName === "profile") continue;
|
|
446
|
+
const routing = ["path", "paths"].filter((k) => k in fieldDef);
|
|
447
|
+
if (routing.length > 0) {
|
|
448
|
+
errors.push({
|
|
449
|
+
code: "PROFILE_INVALID",
|
|
450
|
+
field: fieldName,
|
|
451
|
+
message: `Bounds field "${fieldName}" declares ${routing.map((k) => `"${k}"`).join(" and ")} \u2014 action-routing arrays are forbidden (Bounds Schema rule 6); use actionType and appliesTo.`
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
if (!fieldDef.boundType) {
|
|
455
|
+
errors.push({
|
|
456
|
+
code: "PROFILE_INVALID",
|
|
457
|
+
field: fieldName,
|
|
458
|
+
message: `Bounds field "${fieldName}" has no boundType \u2014 enforcement semantics undefined (Bounds Schema rule 2).`
|
|
459
|
+
});
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
const bt = fieldDef.boundType;
|
|
463
|
+
if (fieldDef.appliesTo && (bt.kind === "per_transaction" || bt.kind === "enum")) {
|
|
464
|
+
errors.push({
|
|
465
|
+
code: "PROFILE_INVALID",
|
|
466
|
+
field: fieldName,
|
|
467
|
+
message: `Bounds field "${fieldName}": appliesTo MUST NOT be declared on a ${bt.kind} bound (Bounds Schema rule 7).`
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
if (bt.kind === "cumulative_count" && !fieldDef.appliesTo?.length) {
|
|
471
|
+
errors.push({
|
|
472
|
+
code: "PROFILE_INVALID",
|
|
473
|
+
field: fieldName,
|
|
474
|
+
message: `Bounds field "${fieldName}": a cumulative_count bound on a v0.7+ profile MUST declare appliesTo (Bounds Schema rule 7) \u2014 absence would silently throttle every action type.`
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
if (fieldDef.appliesTo && actionTypes) {
|
|
478
|
+
for (const at of fieldDef.appliesTo) {
|
|
479
|
+
if (!actionTypes.includes(at)) {
|
|
480
|
+
errors.push({
|
|
481
|
+
code: "PROFILE_INVALID",
|
|
482
|
+
field: fieldName,
|
|
483
|
+
message: `Bounds field "${fieldName}": appliesTo names "${at}", which is not in boundsSchema.actionTypes [${actionTypes.join(", ")}].`
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (bt.kind !== "per_transaction" && bt.requiredFor !== void 0) {
|
|
489
|
+
errors.push({
|
|
490
|
+
code: "PROFILE_INVALID",
|
|
491
|
+
field: fieldName,
|
|
492
|
+
message: `Bounds field "${fieldName}": requiredFor is only meaningful on a per_transaction bound (found on kind "${bt.kind}") \u2014 every enforcement point ignores it there. Remove it.`
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
if (bt.kind === "per_transaction" && Array.isArray(bt.requiredFor) && actionTypes) {
|
|
496
|
+
for (const at of bt.requiredFor) {
|
|
497
|
+
if (!actionTypes.includes(at)) {
|
|
498
|
+
errors.push({
|
|
499
|
+
code: "PROFILE_INVALID",
|
|
500
|
+
field: fieldName,
|
|
501
|
+
message: `Bounds field "${fieldName}": requiredFor names "${at}", which is not in boundsSchema.actionTypes [${actionTypes.join(", ")}].`
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
463
507
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
508
|
+
const declaredModes = p.commitment_modes;
|
|
509
|
+
if (declaredModes !== void 0) {
|
|
510
|
+
if (!Array.isArray(declaredModes)) {
|
|
511
|
+
errors.push({ code: "PROFILE_INVALID", field: "commitment_modes", message: "commitment_modes must be a list of modes." });
|
|
512
|
+
} else {
|
|
513
|
+
if (declaredModes.length === 0) {
|
|
514
|
+
errors.push({
|
|
515
|
+
code: "PROFILE_INVALID",
|
|
516
|
+
field: "commitment_modes",
|
|
517
|
+
message: "commitment_modes is empty \u2014 no mandate could ever be signed under this profile."
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
const seen = /* @__PURE__ */ new Set();
|
|
521
|
+
for (const m of declaredModes) {
|
|
522
|
+
if (!SIGNABLE_COMMITMENT_MODES.includes(m)) {
|
|
523
|
+
errors.push({
|
|
524
|
+
code: "PROFILE_INVALID",
|
|
525
|
+
field: "commitment_modes",
|
|
526
|
+
message: `commitment_modes: "${String(m)}" is not a mode a signer can choose (${SIGNABLE_COMMITMENT_MODES.join(", ")}).`
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
if (seen.has(m)) {
|
|
530
|
+
errors.push({ code: "PROFILE_INVALID", field: "commitment_modes", message: `commitment_modes: "${String(m)}" appears twice.` });
|
|
531
|
+
}
|
|
532
|
+
seen.add(m);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
472
535
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
536
|
+
if (Array.isArray(declaredModes) && declaredModes.length > 0 && allowedCommitmentModes(profile).length === 0) {
|
|
537
|
+
errors.push({
|
|
538
|
+
code: "PROFILE_INVALID",
|
|
539
|
+
field: "commitment_modes",
|
|
540
|
+
message: "commitment_modes declares no mode this profile can actually use."
|
|
541
|
+
});
|
|
477
542
|
}
|
|
543
|
+
return errors;
|
|
478
544
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
checkAttestationExpiry(attestation.payload);
|
|
490
|
-
verifyBoundsHash(attestation, expectedBoundsHash);
|
|
491
|
-
verifyContextHash(attestation, expectedContextHash);
|
|
492
|
-
return attestation.payload;
|
|
545
|
+
function checkDiscloseSubset(mandateDiscloseFields, profileDiscloseFields) {
|
|
546
|
+
if (!mandateDiscloseFields || mandateDiscloseFields.length === 0) return;
|
|
547
|
+
const allowed = new Set(profileDiscloseFields ?? []);
|
|
548
|
+
const widened = mandateDiscloseFields.filter((f) => !allowed.has(f));
|
|
549
|
+
if (widened.length > 0) {
|
|
550
|
+
throw new HapError(
|
|
551
|
+
"MALFORMED_MANDATE",
|
|
552
|
+
`disclose_fields [${widened.join(", ")}] is not a subset of the profile's disclose_fields [${[...allowed].join(", ")}] \u2014 a mandate may narrow disclosure, never widen it.`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
493
555
|
}
|
|
494
556
|
|
|
495
|
-
// src/
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
557
|
+
// src/mandate.ts
|
|
558
|
+
import { createHash as createHash5 } from "crypto";
|
|
559
|
+
import * as ed from "@noble/ed25519";
|
|
560
|
+
|
|
561
|
+
// src/did-key.ts
|
|
562
|
+
var B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
563
|
+
var B58_MAP = {};
|
|
564
|
+
for (let i = 0; i < B58_ALPHABET.length; i++) B58_MAP[B58_ALPHABET[i]] = i;
|
|
565
|
+
var ED25519_MULTICODEC = Uint8Array.from([237, 1]);
|
|
566
|
+
var ED25519_KEY_BYTES = 32;
|
|
567
|
+
function base58btcEncode(bytes) {
|
|
568
|
+
let zeros = 0;
|
|
569
|
+
while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
|
|
570
|
+
const digits = [];
|
|
571
|
+
for (let i = zeros; i < bytes.length; i++) {
|
|
572
|
+
let carry = bytes[i];
|
|
573
|
+
for (let j = 0; j < digits.length; j++) {
|
|
574
|
+
carry += digits[j] * 256;
|
|
575
|
+
digits[j] = carry % 58;
|
|
576
|
+
carry = carry / 58 | 0;
|
|
577
|
+
}
|
|
578
|
+
while (carry > 0) {
|
|
579
|
+
digits.push(carry % 58);
|
|
580
|
+
carry = carry / 58 | 0;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
let out = "1".repeat(zeros);
|
|
584
|
+
for (let i = digits.length - 1; i >= 0; i--) out += B58_ALPHABET[digits[i]];
|
|
585
|
+
return out;
|
|
499
586
|
}
|
|
500
|
-
function
|
|
501
|
-
|
|
587
|
+
function base58btcDecode(s) {
|
|
588
|
+
let zeros = 0;
|
|
589
|
+
while (zeros < s.length && s[zeros] === "1") zeros++;
|
|
590
|
+
const bytes = [];
|
|
591
|
+
for (let i = zeros; i < s.length; i++) {
|
|
592
|
+
const val = B58_MAP[s[i]];
|
|
593
|
+
if (val === void 0) throw new Error(`invalid base58 character "${s[i]}"`);
|
|
594
|
+
let carry = val;
|
|
595
|
+
for (let j = 0; j < bytes.length; j++) {
|
|
596
|
+
carry += bytes[j] * 58;
|
|
597
|
+
bytes[j] = carry & 255;
|
|
598
|
+
carry >>= 8;
|
|
599
|
+
}
|
|
600
|
+
while (carry > 0) {
|
|
601
|
+
bytes.push(carry & 255);
|
|
602
|
+
carry >>= 8;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const out = new Uint8Array(zeros + bytes.length);
|
|
606
|
+
for (let i = 0; i < bytes.length; i++) out[zeros + i] = bytes[bytes.length - 1 - i];
|
|
607
|
+
return out;
|
|
502
608
|
}
|
|
503
|
-
function
|
|
504
|
-
|
|
609
|
+
function encodeDidKey(publicKey) {
|
|
610
|
+
if (publicKey.length !== ED25519_KEY_BYTES) {
|
|
611
|
+
throw new Error(`INVALID_KEY: expected ${ED25519_KEY_BYTES} bytes, got ${publicKey.length}`);
|
|
612
|
+
}
|
|
613
|
+
const prefixed = new Uint8Array(ED25519_MULTICODEC.length + publicKey.length);
|
|
614
|
+
prefixed.set(ED25519_MULTICODEC, 0);
|
|
615
|
+
prefixed.set(publicKey, ED25519_MULTICODEC.length);
|
|
616
|
+
return `did:key:z${base58btcEncode(prefixed)}`;
|
|
505
617
|
}
|
|
506
|
-
function
|
|
507
|
-
|
|
618
|
+
function decodeDidKey(did) {
|
|
619
|
+
if (!did.startsWith("did:key:z")) {
|
|
620
|
+
throw new Error(`NOT_KEY_BEARING: ${JSON.stringify(did)} is not a base58btc did:key`);
|
|
621
|
+
}
|
|
622
|
+
let decoded;
|
|
623
|
+
try {
|
|
624
|
+
decoded = base58btcDecode(did.slice("did:key:z".length));
|
|
625
|
+
} catch (err) {
|
|
626
|
+
throw new Error(`NOT_KEY_BEARING: ${err.message}`);
|
|
627
|
+
}
|
|
628
|
+
if (decoded.length !== ED25519_MULTICODEC.length + ED25519_KEY_BYTES || decoded[0] !== ED25519_MULTICODEC[0] || decoded[1] !== ED25519_MULTICODEC[1]) {
|
|
629
|
+
throw new Error("NOT_KEY_BEARING: not an ed25519-pub multicodec payload");
|
|
630
|
+
}
|
|
631
|
+
return decoded.slice(ED25519_MULTICODEC.length);
|
|
508
632
|
}
|
|
509
|
-
function
|
|
510
|
-
|
|
511
|
-
|
|
633
|
+
function isKeyBearingDid(did) {
|
|
634
|
+
try {
|
|
635
|
+
decodeDidKey(did);
|
|
636
|
+
return true;
|
|
637
|
+
} catch {
|
|
638
|
+
return false;
|
|
512
639
|
}
|
|
513
640
|
}
|
|
514
641
|
|
|
515
|
-
// src/
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
if (!profile) {
|
|
524
|
-
return { approved: false, errors: [{ code: "INVALID_PROFILE", message: `Unknown profile: ${profileId}` }] };
|
|
642
|
+
// src/mandate.ts
|
|
643
|
+
var CLOCK_SKEW_SECONDS = 300;
|
|
644
|
+
function decodeMandateBlob(blob) {
|
|
645
|
+
let json;
|
|
646
|
+
try {
|
|
647
|
+
json = new TextDecoder().decode(fromBase64Url(blob));
|
|
648
|
+
} catch (err) {
|
|
649
|
+
throw new HapError("MALFORMED_MANDATE", `failed to decode mandate blob: ${err.message}`);
|
|
525
650
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
return verifyV3(request, profile, publicKeyHex, now, executionLog, errors);
|
|
651
|
+
try {
|
|
652
|
+
return JSON.parse(json);
|
|
653
|
+
} catch (err) {
|
|
654
|
+
throw new HapError("MALFORMED_MANDATE", `mandate blob did not decode to JSON: ${err}`);
|
|
531
655
|
}
|
|
532
656
|
}
|
|
533
|
-
function
|
|
534
|
-
return
|
|
657
|
+
function encodeMandateBlob(mandate) {
|
|
658
|
+
return toBase64Url(new TextEncoder().encode(JSON.stringify(mandate)));
|
|
535
659
|
}
|
|
536
|
-
function
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
660
|
+
function mandateBlobId(blob) {
|
|
661
|
+
const hash = createHash5("sha256").update(blob, "utf8").digest("hex");
|
|
662
|
+
return `sha256:${hash}`;
|
|
663
|
+
}
|
|
664
|
+
async function signMandate(payload, privateKey, opts) {
|
|
665
|
+
const header = { typ: "HAP-mandate", alg: "EdDSA", ...opts?.kid ? { kid: opts.kid } : {} };
|
|
666
|
+
const signature = toBase64Url(await ed.signAsync(new TextEncoder().encode(canonicalize(payload)), privateKey));
|
|
667
|
+
return { header, payload, signature };
|
|
541
668
|
}
|
|
542
|
-
async function
|
|
543
|
-
|
|
544
|
-
|
|
669
|
+
async function verifyMandateSignature(mandate, opts) {
|
|
670
|
+
const { issuer } = mandate.payload;
|
|
671
|
+
if (opts?.trustedIssuers && !opts.trustedIssuers.includes(issuer)) {
|
|
672
|
+
throw new HapError("INVALID_SIGNATURE", `issuer ${JSON.stringify(issuer)} is not a trusted Authority Server`);
|
|
673
|
+
}
|
|
674
|
+
let publicKey;
|
|
545
675
|
try {
|
|
546
|
-
|
|
676
|
+
publicKey = decodeDidKey(issuer);
|
|
547
677
|
} catch (err) {
|
|
548
|
-
|
|
678
|
+
throw new HapError("INVALID_SIGNATURE", `cannot resolve a key from issuer ${JSON.stringify(issuer)}: ${err.message}`);
|
|
549
679
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
try {
|
|
555
|
-
attestation = decodeAttestationBlob(blob);
|
|
556
|
-
} catch {
|
|
557
|
-
errors.push({ code: "MALFORMED_ATTESTATION", message: "Failed to decode attestation blob" });
|
|
558
|
-
continue;
|
|
559
|
-
}
|
|
560
|
-
try {
|
|
561
|
-
await verifyAttestationSignature(attestation, publicKeyHex);
|
|
562
|
-
} catch {
|
|
563
|
-
errors.push({ code: "INVALID_SIGNATURE", message: "Attestation signature verification failed" });
|
|
564
|
-
continue;
|
|
565
|
-
}
|
|
566
|
-
try {
|
|
567
|
-
verifyFrameHash(attestation, expectedFrameHash);
|
|
568
|
-
} catch {
|
|
569
|
-
errors.push({ code: "FRAME_MISMATCH", message: "Attestation frame_hash does not match computed frame_hash" });
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
try {
|
|
573
|
-
checkAttestationExpiry(attestation.payload, now);
|
|
574
|
-
} catch {
|
|
575
|
-
const domainNames = (attestation.payload.resolved_domains ?? []).map((d) => d.domain).join(", ");
|
|
576
|
-
errors.push({ code: "TTL_EXPIRED", message: `Attestation for domain "${domainNames}" has expired` });
|
|
577
|
-
continue;
|
|
578
|
-
}
|
|
579
|
-
for (const rd of attestation.payload.resolved_domains ?? []) {
|
|
580
|
-
coveredDomains.add(rd.domain);
|
|
680
|
+
if (mandate.header.kid !== void 0) {
|
|
681
|
+
const fingerprint = issuer.startsWith("did:key:") ? issuer.slice("did:key:".length) : void 0;
|
|
682
|
+
if (mandate.header.kid !== fingerprint) {
|
|
683
|
+
throw new HapError("INVALID_SIGNATURE", "header.kid disagrees with issuer's key (Mandate rule 8) \u2014 the field a verifier cannot forge wins");
|
|
581
684
|
}
|
|
582
685
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
686
|
+
const payloadBytes = new TextEncoder().encode(canonicalize(mandate.payload));
|
|
687
|
+
let sigBytes;
|
|
688
|
+
try {
|
|
689
|
+
sigBytes = fromBase64Url(mandate.signature);
|
|
690
|
+
} catch (err) {
|
|
691
|
+
throw new HapError("INVALID_SIGNATURE", `${err.message}`);
|
|
587
692
|
}
|
|
588
|
-
|
|
589
|
-
|
|
693
|
+
const isValid = await ed.verifyAsync(sigBytes, payloadBytes, publicKey).catch(() => false);
|
|
694
|
+
if (!isValid) {
|
|
695
|
+
throw new HapError("INVALID_SIGNATURE", "mandate signature verification failed");
|
|
590
696
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
}
|
|
697
|
+
}
|
|
698
|
+
function checkMandateExpiry(payload, now = Math.floor(Date.now() / 1e3)) {
|
|
699
|
+
if (now > payload.expires_at + CLOCK_SKEW_SECONDS) {
|
|
700
|
+
throw new HapError("TTL_EXPIRED", `mandate expired at ${payload.expires_at}, current time is ${now}`);
|
|
596
701
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
702
|
+
}
|
|
703
|
+
var OWNER_SIGNATURE_FIELDS = ["alg", "signature", "signed_at", "nonce", "binding", "signing_surface"];
|
|
704
|
+
function validateMandateOwners(payload) {
|
|
705
|
+
const owners = payload.mandate_owners;
|
|
706
|
+
if (!Array.isArray(owners) || owners.length !== 1) {
|
|
707
|
+
throw new HapError(
|
|
708
|
+
"MALFORMED_MANDATE",
|
|
709
|
+
`mandate_owners MUST contain exactly one entry in v0.7, got ${Array.isArray(owners) ? owners.length : typeof owners}`
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
const entry = owners[0];
|
|
713
|
+
if (!entry.did) {
|
|
714
|
+
throw new HapError("MALFORMED_MANDATE", "mandate_owners[0].did is required");
|
|
715
|
+
}
|
|
716
|
+
const present = OWNER_SIGNATURE_FIELDS.filter((f) => entry[f] !== void 0);
|
|
717
|
+
if (present.length > 0 && present.length < OWNER_SIGNATURE_FIELDS.length) {
|
|
718
|
+
const missing = OWNER_SIGNATURE_FIELDS.filter((f) => entry[f] === void 0);
|
|
719
|
+
throw new HapError(
|
|
720
|
+
"MALFORMED_MANDATE",
|
|
721
|
+
`mandate_owners[0] carries [${present.join(", ")}] but not [${missing.join(", ")}] \u2014 signature fields are present together or absent together (Mandate rule 7)`
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
if (present.length === OWNER_SIGNATURE_FIELDS.length && !isKeyBearingDid(entry.did)) {
|
|
725
|
+
throw new HapError("OWNER_SIGNATURE_INVALID", `co-signing DID ${entry.did} is not key-bearing`);
|
|
600
726
|
}
|
|
601
|
-
return { approved: true };
|
|
602
727
|
}
|
|
603
|
-
async function
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
728
|
+
async function verifyMandate(blob, opts) {
|
|
729
|
+
const mandate = decodeMandateBlob(blob);
|
|
730
|
+
if (mandate.payload.version !== PROTOCOL_VERSION) {
|
|
731
|
+
throw new HapError(
|
|
732
|
+
"VERSION_UNSUPPORTED",
|
|
733
|
+
`mandate version ${JSON.stringify(mandate.payload.version)} is not verifiable by this implementation (verifies ${PROTOCOL_VERSION} only \u2014 re-issue the mandate)`
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
await verifyMandateSignature(mandate, opts);
|
|
737
|
+
checkMandateExpiry(mandate.payload, opts?.now);
|
|
738
|
+
validateMandateOwners(mandate.payload);
|
|
739
|
+
return mandate.payload;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// src/owner-signature.ts
|
|
743
|
+
import * as ed2 from "@noble/ed25519";
|
|
744
|
+
var OwnerSignatureError = class extends Error {
|
|
745
|
+
constructor(code, message) {
|
|
746
|
+
super(`${code}: ${message}`);
|
|
747
|
+
this.code = code;
|
|
748
|
+
this.name = "OwnerSignatureError";
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
function decodeOrThrow(signature, code) {
|
|
752
|
+
try {
|
|
753
|
+
return fromBase64Url(signature);
|
|
754
|
+
} catch (err) {
|
|
755
|
+
throw new OwnerSignatureError(code, err.message);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
function buildMandateProjection(payload, entry) {
|
|
759
|
+
const {
|
|
760
|
+
profile_id,
|
|
761
|
+
profile_hash,
|
|
762
|
+
bounds_hash,
|
|
763
|
+
scope_hash,
|
|
764
|
+
execution_context_hash,
|
|
765
|
+
gate_content_hashes,
|
|
766
|
+
commitment_mode,
|
|
767
|
+
expires_at
|
|
768
|
+
} = payload;
|
|
769
|
+
if (!profile_hash || !bounds_hash || !scope_hash || !commitment_mode) {
|
|
770
|
+
throw new OwnerSignatureError(
|
|
771
|
+
"MALFORMED_MANDATE",
|
|
772
|
+
"mandate projection requires profile_hash, bounds_hash, scope_hash and commitment_mode"
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
const projection = {
|
|
776
|
+
typ: "HAP-mandate-projection",
|
|
777
|
+
version: "0.7",
|
|
778
|
+
profile_id,
|
|
779
|
+
profile_hash,
|
|
780
|
+
owner_did: entry.did,
|
|
781
|
+
bounds_hash,
|
|
782
|
+
scope_hash,
|
|
783
|
+
execution_context_hash,
|
|
784
|
+
gate_content_hashes,
|
|
785
|
+
commitment_mode,
|
|
786
|
+
expires_at,
|
|
787
|
+
nonce: entry.nonce
|
|
788
|
+
};
|
|
789
|
+
if (payload.intent_disclosure_hash !== void 0) {
|
|
790
|
+
projection.intent_disclosure_hash = payload.intent_disclosure_hash;
|
|
791
|
+
}
|
|
792
|
+
if (payload.commitment_mode === "review_above_cap") {
|
|
793
|
+
projection.above_cap_caps = payload.above_cap_caps;
|
|
794
|
+
projection.above_cap_approvers = payload.above_cap_approvers;
|
|
795
|
+
}
|
|
796
|
+
if (payload.disclose_fields !== void 0) {
|
|
797
|
+
projection.disclose_fields = payload.disclose_fields;
|
|
798
|
+
}
|
|
799
|
+
return projection;
|
|
800
|
+
}
|
|
801
|
+
function projectionSigningBytes(projection) {
|
|
802
|
+
return new TextEncoder().encode(canonicalize(projection));
|
|
803
|
+
}
|
|
804
|
+
function approvalSigningBytes(approval) {
|
|
805
|
+
return new TextEncoder().encode(canonicalize(approval));
|
|
806
|
+
}
|
|
807
|
+
async function signMandateProjection(projection, privateKey) {
|
|
808
|
+
return toBase64Url(await ed2.signAsync(projectionSigningBytes(projection), privateKey));
|
|
809
|
+
}
|
|
810
|
+
async function signApproval(approval, privateKey) {
|
|
811
|
+
return toBase64Url(await ed2.signAsync(approvalSigningBytes(approval), privateKey));
|
|
812
|
+
}
|
|
813
|
+
async function verifyOwnerSignature(mandate, entry) {
|
|
814
|
+
const owners = mandate.payload.mandate_owners ?? [];
|
|
815
|
+
if (!owners.some((o) => o.did === entry.did)) {
|
|
816
|
+
throw new OwnerSignatureError("OWNER_SIGNATURE_INVALID", `signing DID ${entry.did} is not this mandate's mandate_owners`);
|
|
817
|
+
}
|
|
818
|
+
if (entry.alg !== void 0 && entry.alg !== "EdDSA") {
|
|
819
|
+
throw new OwnerSignatureError("OWNER_SIGNATURE_INVALID", `alg ${entry.alg} disagrees with the DID's key type (DID is authoritative)`);
|
|
820
|
+
}
|
|
821
|
+
if (!entry.signature || !entry.nonce) {
|
|
822
|
+
throw new OwnerSignatureError("OWNER_SIGNATURE_INVALID", `mandate_owners entry for ${entry.did} carries no signature to verify`);
|
|
823
|
+
}
|
|
824
|
+
let publicKey;
|
|
825
|
+
try {
|
|
826
|
+
publicKey = decodeDidKey(entry.did);
|
|
827
|
+
} catch (err) {
|
|
828
|
+
throw new OwnerSignatureError("OWNER_SIGNATURE_INVALID", err.message);
|
|
829
|
+
}
|
|
830
|
+
const projection = buildMandateProjection(mandate.payload, entry);
|
|
831
|
+
const ok = await ed2.verifyAsync(decodeOrThrow(entry.signature, "OWNER_SIGNATURE_INVALID"), projectionSigningBytes(projection), publicKey).catch(() => false);
|
|
832
|
+
if (!ok) {
|
|
833
|
+
throw new OwnerSignatureError("OWNER_SIGNATURE_INVALID", `owner signature by ${entry.did} does not verify`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
async function verifyOwnerSignatures(mandate) {
|
|
837
|
+
const entries = (mandate.payload.mandate_owners ?? []).filter((e) => e.signature !== void 0);
|
|
838
|
+
for (const entry of entries) {
|
|
839
|
+
await verifyOwnerSignature(mandate, entry);
|
|
840
|
+
}
|
|
841
|
+
return entries;
|
|
842
|
+
}
|
|
843
|
+
async function verifyApproval(approval, signature, signerDid) {
|
|
844
|
+
let publicKey;
|
|
845
|
+
try {
|
|
846
|
+
publicKey = decodeDidKey(signerDid);
|
|
847
|
+
} catch (err) {
|
|
848
|
+
throw new OwnerSignatureError("OWNER_SIGNATURE_INVALID", err.message);
|
|
849
|
+
}
|
|
850
|
+
const ok = await ed2.verifyAsync(decodeOrThrow(signature, "APPROVAL_SIGNATURE_INVALID"), approvalSigningBytes(approval), publicKey).catch(() => false);
|
|
851
|
+
if (!ok) {
|
|
852
|
+
throw new OwnerSignatureError("APPROVAL_SIGNATURE_INVALID", `approval signature by ${signerDid} does not verify`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
var BINDING_RANK = { raw: 0, webauthn: 1, eudi: 2 };
|
|
856
|
+
function checkOwnerSignatureRequirement(payload, floor) {
|
|
857
|
+
if (!floor?.required) return;
|
|
858
|
+
const entry = payload.mandate_owners[0];
|
|
859
|
+
const signed = entry?.signature !== void 0 && entry?.binding !== void 0;
|
|
860
|
+
if (!signed) {
|
|
861
|
+
throw new OwnerSignatureError(
|
|
862
|
+
"OWNER_SIGNATURE_REQUIRED",
|
|
863
|
+
"this profile requires an owner signature and mandate_owners[0] carries none"
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
const minRank = floor.minBinding ? BINDING_RANK[floor.minBinding] : 0;
|
|
867
|
+
const haveRank = BINDING_RANK[entry.binding] ?? 0;
|
|
868
|
+
if (haveRank < minRank) {
|
|
869
|
+
throw new OwnerSignatureError(
|
|
870
|
+
"OWNER_SIGNATURE_REQUIRED",
|
|
871
|
+
`this profile requires binding >= "${floor.minBinding}", mandate_owners[0] carries "${entry.binding}"`
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// src/ticket.ts
|
|
877
|
+
import * as ed3 from "@noble/ed25519";
|
|
878
|
+
async function signTicket(payload, privateKey) {
|
|
879
|
+
const signature = toBase64Url(await ed3.signAsync(new TextEncoder().encode(canonicalize(payload)), privateKey));
|
|
880
|
+
return { ...payload, signature };
|
|
881
|
+
}
|
|
882
|
+
async function verifyTicketSignature(ticket, opts) {
|
|
883
|
+
const { signature, ...unsigned } = ticket;
|
|
884
|
+
if (!signature) {
|
|
885
|
+
throw new HapError("INVALID_SIGNATURE", "ticket carries no signature");
|
|
886
|
+
}
|
|
887
|
+
if (opts?.trustedIssuers && !opts.trustedIssuers.includes(ticket.issuer)) {
|
|
888
|
+
throw new HapError("INVALID_SIGNATURE", `issuer ${JSON.stringify(ticket.issuer)} is not a trusted Authority Server`);
|
|
889
|
+
}
|
|
890
|
+
let publicKey;
|
|
891
|
+
try {
|
|
892
|
+
publicKey = decodeDidKey(ticket.issuer);
|
|
893
|
+
} catch (err) {
|
|
894
|
+
throw new HapError("INVALID_SIGNATURE", `cannot resolve a key from issuer ${JSON.stringify(ticket.issuer)}: ${err.message}`);
|
|
895
|
+
}
|
|
896
|
+
const bytes = new TextEncoder().encode(canonicalize(unsigned));
|
|
897
|
+
let sigBytes;
|
|
898
|
+
try {
|
|
899
|
+
sigBytes = fromBase64Url(signature);
|
|
900
|
+
} catch (err) {
|
|
901
|
+
throw new HapError("INVALID_SIGNATURE", `${err.message}`);
|
|
902
|
+
}
|
|
903
|
+
const ok = await ed3.verifyAsync(sigBytes, bytes, publicKey).catch(() => false);
|
|
904
|
+
if (!ok) {
|
|
905
|
+
throw new HapError("INVALID_SIGNATURE", "ticket signature verification failed");
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
function publicTicketView(ticket, governingDiscloseFields = []) {
|
|
909
|
+
const { userId: _userId, cumulativeState: _cumulativeState, limits: _limits, executionContext, ...rest } = ticket;
|
|
910
|
+
const allowed = new Set(governingDiscloseFields);
|
|
911
|
+
const disclosedExecutionContext = {};
|
|
912
|
+
for (const [key, value] of Object.entries(executionContext)) {
|
|
913
|
+
if (allowed.has(key)) disclosedExecutionContext[key] = value;
|
|
914
|
+
}
|
|
915
|
+
return { ...rest, executionContext: disclosedExecutionContext };
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// src/profiles/index.ts
|
|
919
|
+
var PROFILES = {};
|
|
920
|
+
function registerProfile(profileId, profile) {
|
|
921
|
+
PROFILES[profileId] = profile;
|
|
922
|
+
}
|
|
923
|
+
function getProfile(profileId) {
|
|
924
|
+
return PROFILES[profileId];
|
|
925
|
+
}
|
|
926
|
+
function listProfiles() {
|
|
927
|
+
return Object.keys(PROFILES);
|
|
928
|
+
}
|
|
929
|
+
function getAllProfiles() {
|
|
930
|
+
return Object.values(PROFILES);
|
|
931
|
+
}
|
|
932
|
+
function clearProfiles() {
|
|
933
|
+
for (const key of Object.keys(PROFILES)) {
|
|
934
|
+
delete PROFILES[key];
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// src/gatekeeper.ts
|
|
939
|
+
async function verify(request, opts = {}) {
|
|
940
|
+
const now = opts.now ?? Math.floor(Date.now() / 1e3);
|
|
941
|
+
const profileId = request.bounds.profile;
|
|
942
|
+
if (typeof profileId !== "string") {
|
|
943
|
+
return { approved: false, errors: [{ code: "PROFILE_NOT_FOUND", message: "Missing profile in bounds" }] };
|
|
944
|
+
}
|
|
945
|
+
const profile = getProfile(profileId);
|
|
946
|
+
if (!profile) {
|
|
947
|
+
return { approved: false, errors: [{ code: "PROFILE_NOT_FOUND", message: `Unknown profile: ${profileId}` }] };
|
|
948
|
+
}
|
|
949
|
+
if (!Array.isArray(request.mandates) || request.mandates.length === 0) {
|
|
950
|
+
return { approved: false, errors: [{ code: "MALFORMED_MANDATE", message: "No mandate supplied \u2014 nothing to verify" }] };
|
|
951
|
+
}
|
|
952
|
+
const bounds = request.bounds;
|
|
953
|
+
const scope = request.scope;
|
|
608
954
|
let expectedBoundsHash;
|
|
609
|
-
let expectedContextHash;
|
|
610
955
|
try {
|
|
611
956
|
expectedBoundsHash = computeBoundsHash(bounds, profile);
|
|
612
957
|
} catch (err) {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
if (context && Object.keys(context).length > 0) {
|
|
616
|
-
try {
|
|
617
|
-
expectedContextHash = computeContextHash(context, profile);
|
|
618
|
-
} catch (err) {
|
|
619
|
-
return { approved: false, errors: [{ code: "CONTEXT_MISMATCH", message: `Context hash computation failed: ${err}` }] };
|
|
958
|
+
if (err instanceof CanonicalValueError) {
|
|
959
|
+
return { approved: false, errors: [{ code: err.code, field: err.field, message: err.message }] };
|
|
620
960
|
}
|
|
961
|
+
return { approved: false, errors: [{ code: "BOUNDS_HASH_MISMATCH", message: `Bounds hash computation failed: ${err}` }] };
|
|
621
962
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
for (const blob of request.attestations) {
|
|
625
|
-
let attestation;
|
|
963
|
+
let expectedScopeHash;
|
|
964
|
+
if (scope && Object.keys(scope).length > 0) {
|
|
626
965
|
try {
|
|
627
|
-
|
|
628
|
-
} catch {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
}
|
|
632
|
-
try {
|
|
633
|
-
await verifyAttestationSignature(attestation, publicKeyHex);
|
|
634
|
-
} catch {
|
|
635
|
-
errors.push({ code: "INVALID_SIGNATURE", message: "Attestation signature verification failed" });
|
|
636
|
-
continue;
|
|
637
|
-
}
|
|
638
|
-
try {
|
|
639
|
-
verifyBoundsHash(attestation, expectedBoundsHash);
|
|
640
|
-
} catch {
|
|
641
|
-
errors.push({ code: "BOUNDS_MISMATCH", message: "Attestation bounds_hash does not match computed bounds_hash" });
|
|
642
|
-
continue;
|
|
643
|
-
}
|
|
644
|
-
if (isV4Attestation(attestation) && expectedContextHash) {
|
|
645
|
-
try {
|
|
646
|
-
verifyContextHash(attestation, expectedContextHash);
|
|
647
|
-
} catch {
|
|
648
|
-
errors.push({ code: "CONTEXT_MISMATCH", message: "Attestation context_hash does not match computed context_hash" });
|
|
649
|
-
continue;
|
|
966
|
+
expectedScopeHash = computeScopeHash(scope, profile);
|
|
967
|
+
} catch (err) {
|
|
968
|
+
if (err instanceof CanonicalValueError) {
|
|
969
|
+
return { approved: false, errors: [{ code: err.code, field: err.field, message: err.message }] };
|
|
650
970
|
}
|
|
651
|
-
|
|
652
|
-
try {
|
|
653
|
-
checkAttestationExpiry(attestation.payload, now);
|
|
654
|
-
} catch {
|
|
655
|
-
const domainNames = (attestation.payload.resolved_domains ?? []).map((d) => d.domain).join(", ");
|
|
656
|
-
errors.push({ code: "TTL_EXPIRED", message: `Attestation for domain "${domainNames}" has expired` });
|
|
657
|
-
continue;
|
|
658
|
-
}
|
|
659
|
-
for (const rd of attestation.payload.resolved_domains ?? []) {
|
|
660
|
-
coveredDomains.add(rd.domain);
|
|
971
|
+
return { approved: false, errors: [{ code: "SCOPE_HASH_MISMATCH", message: `Scope hash computation failed: ${err}` }] };
|
|
661
972
|
}
|
|
662
973
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
974
|
+
const expectedProfileHash = computeProfileHash(profile);
|
|
975
|
+
const errors = [];
|
|
976
|
+
for (const blob of request.mandates) {
|
|
977
|
+
const mandateErrors = await verifyOneMandate(blob, {
|
|
978
|
+
expectedBoundsHash,
|
|
979
|
+
expectedScopeHash,
|
|
980
|
+
expectedProfileHash,
|
|
981
|
+
trustedIssuers: opts.trustedIssuers,
|
|
982
|
+
now
|
|
983
|
+
});
|
|
984
|
+
errors.push(...mandateErrors);
|
|
667
985
|
}
|
|
668
986
|
if (errors.length > 0) {
|
|
669
987
|
return { approved: false, errors };
|
|
670
988
|
}
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
989
|
+
const actionType = typeof request.execution.action_type === "string" ? request.execution.action_type : void 0;
|
|
990
|
+
const registry = profile.boundsSchema?.actionTypes;
|
|
991
|
+
if (registry && registry.length > 0) {
|
|
992
|
+
if (actionType === void 0 || !registry.includes(actionType)) {
|
|
993
|
+
return {
|
|
994
|
+
approved: false,
|
|
995
|
+
errors: [{
|
|
996
|
+
code: "INVALID_ACTION_TYPE",
|
|
997
|
+
field: "action_type",
|
|
998
|
+
message: `action_type ${JSON.stringify(actionType)} is not in profile ${profile.id}'s actionTypes registry [${registry.join(", ")}]`
|
|
999
|
+
}]
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
const boundsErrors = checkBounds(request, profile, actionType);
|
|
1004
|
+
if (boundsErrors.length > 0) {
|
|
1005
|
+
return { approved: false, errors: boundsErrors };
|
|
674
1006
|
}
|
|
675
|
-
if (profile.
|
|
676
|
-
const enforceableFields = Object.entries(profile.
|
|
677
|
-
if (enforceableFields.length > 0 && !
|
|
1007
|
+
if (profile.scopeSchema && Object.keys(profile.scopeSchema.fields).length > 0) {
|
|
1008
|
+
const enforceableFields = Object.entries(profile.scopeSchema.fields).filter(([, def]) => def.constraint?.enforceable?.length);
|
|
1009
|
+
if (enforceableFields.length > 0 && !scope) {
|
|
678
1010
|
return {
|
|
679
1011
|
approved: false,
|
|
680
1012
|
errors: [{
|
|
681
1013
|
code: "BOUND_EXCEEDED",
|
|
682
|
-
message: `Declared
|
|
1014
|
+
message: `Declared scope required for constraint enforcement but not provided. Fields: ${enforceableFields.map(([n]) => n).join(", ")}`
|
|
683
1015
|
}]
|
|
684
1016
|
};
|
|
685
1017
|
}
|
|
686
|
-
if (
|
|
687
|
-
const
|
|
688
|
-
if (
|
|
689
|
-
return { approved: false, errors:
|
|
1018
|
+
if (scope) {
|
|
1019
|
+
const scopeErrors = checkScopeConstraints(scope, request.execution, profile);
|
|
1020
|
+
if (scopeErrors.length > 0) {
|
|
1021
|
+
return { approved: false, errors: scopeErrors };
|
|
690
1022
|
}
|
|
691
1023
|
}
|
|
692
1024
|
}
|
|
693
1025
|
return { approved: true };
|
|
694
1026
|
}
|
|
695
|
-
function
|
|
696
|
-
|
|
697
|
-
if (
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const constraint = fieldDef.constraint;
|
|
701
|
-
for (const enforceType of constraint.enforceable) {
|
|
702
|
-
if (enforceType === "max") {
|
|
703
|
-
const execField = fieldName.replace(/_max$/, "");
|
|
704
|
-
const boundValue = request.frame[fieldName];
|
|
705
|
-
const actualValue = request.execution[execField];
|
|
706
|
-
if (actualValue === void 0) continue;
|
|
707
|
-
if (typeof boundValue !== "number" || typeof actualValue !== "number") {
|
|
708
|
-
errors.push({
|
|
709
|
-
code: "BOUND_EXCEEDED",
|
|
710
|
-
field: execField,
|
|
711
|
-
message: `Bound check requires numeric values for "${execField}"`,
|
|
712
|
-
bound: boundValue,
|
|
713
|
-
actual: actualValue
|
|
714
|
-
});
|
|
715
|
-
continue;
|
|
716
|
-
}
|
|
717
|
-
if (actualValue > boundValue) {
|
|
718
|
-
errors.push({
|
|
719
|
-
code: "BOUND_EXCEEDED",
|
|
720
|
-
field: execField,
|
|
721
|
-
message: `Value ${actualValue} exceeds authorized maximum of ${boundValue}`,
|
|
722
|
-
bound: boundValue,
|
|
723
|
-
actual: actualValue
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
if (enforceType === "enum") {
|
|
728
|
-
const boundValue = request.frame[fieldName];
|
|
729
|
-
const actualValue = request.execution[fieldName];
|
|
730
|
-
if (actualValue === void 0) continue;
|
|
731
|
-
const allowed = typeof boundValue === "string" ? boundValue.split(",").map((s) => s.trim()) : [String(boundValue)];
|
|
732
|
-
const actualStr = String(actualValue);
|
|
733
|
-
if (!allowed.includes(actualStr)) {
|
|
734
|
-
errors.push({
|
|
735
|
-
code: "BOUND_EXCEEDED",
|
|
736
|
-
field: fieldName,
|
|
737
|
-
message: `Value "${actualStr}" not in authorized values [${allowed.join(", ")}]`,
|
|
738
|
-
bound: boundValue,
|
|
739
|
-
actual: actualValue
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
1027
|
+
function codeFromThrown(err) {
|
|
1028
|
+
if (err instanceof HapError) return err.code;
|
|
1029
|
+
if (err instanceof Error) {
|
|
1030
|
+
const [prefix] = err.message.split(":");
|
|
1031
|
+
return prefix;
|
|
744
1032
|
}
|
|
745
|
-
return
|
|
1033
|
+
return "MALFORMED_MANDATE";
|
|
746
1034
|
}
|
|
747
|
-
function
|
|
748
|
-
|
|
749
|
-
|
|
1035
|
+
async function verifyOneMandate(blob, opts) {
|
|
1036
|
+
let mandate;
|
|
1037
|
+
try {
|
|
1038
|
+
mandate = decodeMandateBlob(blob);
|
|
1039
|
+
} catch (err) {
|
|
1040
|
+
return [{ code: "MALFORMED_MANDATE", message: err.message }];
|
|
750
1041
|
}
|
|
751
|
-
if (
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
1042
|
+
if (mandate.payload.version !== PROTOCOL_VERSION) {
|
|
1043
|
+
return [{
|
|
1044
|
+
code: "VERSION_UNSUPPORTED",
|
|
1045
|
+
message: `mandate version ${JSON.stringify(mandate.payload.version)} is not verifiable by this Gatekeeper (verifies ${PROTOCOL_VERSION} only)`
|
|
1046
|
+
}];
|
|
1047
|
+
}
|
|
1048
|
+
try {
|
|
1049
|
+
await verifyMandateSignature(mandate, { trustedIssuers: opts.trustedIssuers });
|
|
1050
|
+
} catch (err) {
|
|
1051
|
+
return [{ code: codeFromThrown(err), message: err.message }];
|
|
1052
|
+
}
|
|
1053
|
+
try {
|
|
1054
|
+
checkMandateExpiry(mandate.payload, opts.now);
|
|
1055
|
+
} catch (err) {
|
|
1056
|
+
return [{ code: codeFromThrown(err), message: err.message }];
|
|
1057
|
+
}
|
|
1058
|
+
try {
|
|
1059
|
+
validateMandateOwners(mandate.payload);
|
|
1060
|
+
} catch (err) {
|
|
1061
|
+
return [{ code: codeFromThrown(err), message: err.message }];
|
|
1062
|
+
}
|
|
1063
|
+
const errors = [];
|
|
1064
|
+
const payload = mandate.payload;
|
|
1065
|
+
if (payload.bounds_hash !== opts.expectedBoundsHash) {
|
|
1066
|
+
errors.push({ code: "BOUNDS_HASH_MISMATCH", message: "Mandate bounds_hash does not match the recomputed bounds_hash" });
|
|
1067
|
+
}
|
|
1068
|
+
if (opts.expectedScopeHash !== void 0 && payload.scope_hash !== opts.expectedScopeHash) {
|
|
1069
|
+
errors.push({ code: "SCOPE_HASH_MISMATCH", message: "Mandate scope_hash does not match the recomputed scope_hash" });
|
|
1070
|
+
}
|
|
1071
|
+
if (payload.profile_hash !== opts.expectedProfileHash) {
|
|
1072
|
+
errors.push({ code: "PROFILE_HASH_MISMATCH", message: "Mandate profile_hash does not match the Gatekeeper's provisioned profile bytes" });
|
|
1073
|
+
}
|
|
1074
|
+
return errors;
|
|
756
1075
|
}
|
|
757
1076
|
function boundActionTypes(fieldName, fieldDef) {
|
|
758
1077
|
if (fieldDef.appliesTo) return fieldDef.appliesTo;
|
|
@@ -762,29 +1081,25 @@ function boundActionTypes(fieldName, fieldDef) {
|
|
|
762
1081
|
if (prefix === fieldName || prefix.startsWith("transaction")) return void 0;
|
|
763
1082
|
return [prefix];
|
|
764
1083
|
}
|
|
765
|
-
function
|
|
1084
|
+
function checkBounds(request, profile, actionType) {
|
|
766
1085
|
const errors = [];
|
|
767
1086
|
if (!profile.boundsSchema) return errors;
|
|
768
|
-
const bounds = request.
|
|
769
|
-
const profileId = String(bounds.profile ?? profile.id);
|
|
770
|
-
const actionType = typeof request.execution.action_type === "string" ? request.execution.action_type : void 0;
|
|
771
|
-
const path = request.path ?? (bounds.path ? String(bounds.path) : "");
|
|
1087
|
+
const bounds = request.bounds;
|
|
772
1088
|
for (const [fieldName, fieldDef] of Object.entries(profile.boundsSchema.fields)) {
|
|
773
|
-
if (fieldName === "profile"
|
|
1089
|
+
if (fieldName === "profile") continue;
|
|
774
1090
|
const boundValue = bounds[fieldName];
|
|
775
1091
|
if (boundValue === void 0) continue;
|
|
776
1092
|
const bt = fieldDef.boundType;
|
|
777
1093
|
if (!bt) {
|
|
778
1094
|
errors.push({
|
|
779
|
-
code: "
|
|
1095
|
+
code: "PROFILE_INVALID",
|
|
780
1096
|
field: fieldName,
|
|
781
|
-
message: `Profile ${profile.id} bound "${fieldName}" has no boundType \u2014 enforcement semantics undefined
|
|
1097
|
+
message: `Profile ${profile.id} bound "${fieldName}" has no boundType \u2014 enforcement semantics undefined.`,
|
|
782
1098
|
bound: boundValue,
|
|
783
1099
|
actual: boundValue
|
|
784
1100
|
});
|
|
785
1101
|
continue;
|
|
786
1102
|
}
|
|
787
|
-
if (!boundGovernsAction(fieldName, fieldDef, bt, actionType)) continue;
|
|
788
1103
|
switch (bt.kind) {
|
|
789
1104
|
case "per_transaction": {
|
|
790
1105
|
const actual = request.execution[bt.of];
|
|
@@ -826,72 +1141,6 @@ function checkBoundsV4(request, profile, executionLog, now) {
|
|
|
826
1141
|
}
|
|
827
1142
|
break;
|
|
828
1143
|
}
|
|
829
|
-
case "cumulative_sum": {
|
|
830
|
-
if (typeof boundValue !== "number") {
|
|
831
|
-
errors.push({
|
|
832
|
-
code: "CUMULATIVE_LIMIT_EXCEEDED",
|
|
833
|
-
field: bt.of,
|
|
834
|
-
message: `Cumulative bound "${fieldName}" must be numeric`,
|
|
835
|
-
bound: boundValue,
|
|
836
|
-
actual: 0
|
|
837
|
-
});
|
|
838
|
-
break;
|
|
839
|
-
}
|
|
840
|
-
if (!executionLog) break;
|
|
841
|
-
const runningTotal = executionLog.sumByWindow(
|
|
842
|
-
profileId,
|
|
843
|
-
path,
|
|
844
|
-
bt.of,
|
|
845
|
-
bt.window,
|
|
846
|
-
now,
|
|
847
|
-
boundActionTypes(fieldName, fieldDef)
|
|
848
|
-
);
|
|
849
|
-
const currentRaw = request.execution[bt.of];
|
|
850
|
-
const current = typeof currentRaw === "number" ? currentRaw : currentRaw !== void 0 ? Number(currentRaw) : 0;
|
|
851
|
-
const total = runningTotal + current;
|
|
852
|
-
if (total > boundValue) {
|
|
853
|
-
errors.push({
|
|
854
|
-
code: "CUMULATIVE_LIMIT_EXCEEDED",
|
|
855
|
-
field: bt.of,
|
|
856
|
-
message: `Cumulative ${bt.window} sum of ${bt.of} (${total}) exceeds limit of ${boundValue} for ${fieldName}`,
|
|
857
|
-
bound: boundValue,
|
|
858
|
-
actual: total
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
break;
|
|
862
|
-
}
|
|
863
|
-
case "cumulative_count": {
|
|
864
|
-
if (typeof boundValue !== "number") {
|
|
865
|
-
errors.push({
|
|
866
|
-
code: "CUMULATIVE_LIMIT_EXCEEDED",
|
|
867
|
-
field: fieldName,
|
|
868
|
-
message: `Cumulative count bound "${fieldName}" must be numeric`,
|
|
869
|
-
bound: boundValue,
|
|
870
|
-
actual: 0
|
|
871
|
-
});
|
|
872
|
-
break;
|
|
873
|
-
}
|
|
874
|
-
if (!executionLog) break;
|
|
875
|
-
const runningCount = executionLog.sumByWindow(
|
|
876
|
-
profileId,
|
|
877
|
-
path,
|
|
878
|
-
"_count",
|
|
879
|
-
bt.window,
|
|
880
|
-
now,
|
|
881
|
-
boundActionTypes(fieldName, fieldDef)
|
|
882
|
-
);
|
|
883
|
-
const total = runningCount + 1;
|
|
884
|
-
if (total > boundValue) {
|
|
885
|
-
errors.push({
|
|
886
|
-
code: "CUMULATIVE_LIMIT_EXCEEDED",
|
|
887
|
-
field: fieldName,
|
|
888
|
-
message: `Cumulative ${bt.window} count (${total}) exceeds limit of ${boundValue} for ${fieldName}`,
|
|
889
|
-
bound: boundValue,
|
|
890
|
-
actual: total
|
|
891
|
-
});
|
|
892
|
-
}
|
|
893
|
-
break;
|
|
894
|
-
}
|
|
895
1144
|
case "enum": {
|
|
896
1145
|
if (typeof boundValue === "string" && !bt.values.includes(boundValue)) {
|
|
897
1146
|
errors.push({
|
|
@@ -904,18 +1153,21 @@ function checkBoundsV4(request, profile, executionLog, now) {
|
|
|
904
1153
|
}
|
|
905
1154
|
break;
|
|
906
1155
|
}
|
|
1156
|
+
case "cumulative_sum":
|
|
1157
|
+
case "cumulative_count":
|
|
1158
|
+
break;
|
|
907
1159
|
}
|
|
908
1160
|
}
|
|
909
1161
|
return errors;
|
|
910
1162
|
}
|
|
911
|
-
function
|
|
1163
|
+
function checkScopeConstraints(scope, execution, profile) {
|
|
912
1164
|
const errors = [];
|
|
913
|
-
if (!profile.
|
|
914
|
-
for (const [fieldName, fieldDef] of Object.entries(profile.
|
|
1165
|
+
if (!profile.scopeSchema) return errors;
|
|
1166
|
+
for (const [fieldName, fieldDef] of Object.entries(profile.scopeSchema.fields)) {
|
|
915
1167
|
if (!fieldDef.constraint) continue;
|
|
916
1168
|
const requiredFor = fieldDef.constraint.requiredFor;
|
|
917
1169
|
if (requiredFor?.length) {
|
|
918
|
-
const boundValue =
|
|
1170
|
+
const boundValue = scope[fieldName];
|
|
919
1171
|
const actualValue = execution[fieldName];
|
|
920
1172
|
const actionType = execution["action_type"];
|
|
921
1173
|
const constrained = boundValue !== void 0 && boundValue !== "";
|
|
@@ -932,7 +1184,7 @@ function checkContextConstraints(context, execution, profile) {
|
|
|
932
1184
|
}
|
|
933
1185
|
for (const enforceType of fieldDef.constraint.enforceable) {
|
|
934
1186
|
if (enforceType === "enum") {
|
|
935
|
-
const boundValue =
|
|
1187
|
+
const boundValue = scope[fieldName];
|
|
936
1188
|
const actualValue = execution[fieldName];
|
|
937
1189
|
if (actualValue === void 0) continue;
|
|
938
1190
|
const allowed = typeof boundValue === "string" ? boundValue.split(",").map((s) => s.trim()) : [String(boundValue)];
|
|
@@ -941,14 +1193,14 @@ function checkContextConstraints(context, execution, profile) {
|
|
|
941
1193
|
errors.push({
|
|
942
1194
|
code: "BOUND_EXCEEDED",
|
|
943
1195
|
field: fieldName,
|
|
944
|
-
message: `Value "${actualStr}" not in authorized
|
|
1196
|
+
message: `Value "${actualStr}" not in authorized scope values [${allowed.join(", ")}]`,
|
|
945
1197
|
bound: boundValue,
|
|
946
1198
|
actual: actualValue
|
|
947
1199
|
});
|
|
948
1200
|
}
|
|
949
1201
|
}
|
|
950
1202
|
if (enforceType === "subset") {
|
|
951
|
-
const boundValue =
|
|
1203
|
+
const boundValue = scope[fieldName];
|
|
952
1204
|
const actualValue = execution[fieldName];
|
|
953
1205
|
if (boundValue === void 0 || boundValue === "") continue;
|
|
954
1206
|
if (actualValue === void 0 || actualValue === "") continue;
|
|
@@ -969,303 +1221,71 @@ function checkContextConstraints(context, execution, profile) {
|
|
|
969
1221
|
}
|
|
970
1222
|
return errors;
|
|
971
1223
|
}
|
|
972
|
-
function resolveCumulativeFields(request, profile, executionLog, now) {
|
|
973
|
-
const errors = [];
|
|
974
|
-
const profileId = String(request.frame.profile);
|
|
975
|
-
const boundsOrFrame = request.frame;
|
|
976
|
-
for (const [fieldName, fieldDef] of Object.entries(profile.executionContextSchema.fields)) {
|
|
977
|
-
if (fieldDef.source !== "cumulative") continue;
|
|
978
|
-
const cumDef = fieldDef;
|
|
979
|
-
const { cumulativeField, window: windowType } = cumDef;
|
|
980
|
-
const path = request.frame.path ? String(request.frame.path) : "";
|
|
981
|
-
const runningTotal = executionLog.sumByWindow(profileId, path, cumulativeField, windowType, now);
|
|
982
|
-
let currentContribution;
|
|
983
|
-
if (cumulativeField === "_count") {
|
|
984
|
-
currentContribution = 1;
|
|
985
|
-
} else {
|
|
986
|
-
const val = request.execution[cumulativeField];
|
|
987
|
-
currentContribution = typeof val === "number" ? val : val !== void 0 ? Number(val) : 0;
|
|
988
|
-
}
|
|
989
|
-
const cumulativeValue = runningTotal + currentContribution;
|
|
990
|
-
request.execution[fieldName] = cumulativeValue;
|
|
991
|
-
const boundFieldName = fieldName + "_max";
|
|
992
|
-
const boundValue = boundsOrFrame[boundFieldName];
|
|
993
|
-
if (boundValue === void 0) continue;
|
|
994
|
-
if (typeof boundValue !== "number") {
|
|
995
|
-
errors.push({
|
|
996
|
-
code: "CUMULATIVE_LIMIT_EXCEEDED",
|
|
997
|
-
field: fieldName,
|
|
998
|
-
message: `Cumulative bound requires numeric value for "${boundFieldName}"`,
|
|
999
|
-
bound: boundValue,
|
|
1000
|
-
actual: cumulativeValue
|
|
1001
|
-
});
|
|
1002
|
-
continue;
|
|
1003
|
-
}
|
|
1004
|
-
if (cumulativeValue > boundValue) {
|
|
1005
|
-
errors.push({
|
|
1006
|
-
code: "CUMULATIVE_LIMIT_EXCEEDED",
|
|
1007
|
-
field: fieldName,
|
|
1008
|
-
message: `Cumulative ${windowType} value ${cumulativeValue} exceeds limit of ${boundValue}`,
|
|
1009
|
-
bound: boundValue,
|
|
1010
|
-
actual: cumulativeValue
|
|
1011
|
-
});
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
return errors;
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// src/did-key.ts
|
|
1018
|
-
var B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
1019
|
-
var B58_MAP = {};
|
|
1020
|
-
for (let i = 0; i < B58_ALPHABET.length; i++) B58_MAP[B58_ALPHABET[i]] = i;
|
|
1021
|
-
var ED25519_MULTICODEC = Uint8Array.from([237, 1]);
|
|
1022
|
-
var ED25519_KEY_BYTES = 32;
|
|
1023
|
-
function base58btcEncode(bytes) {
|
|
1024
|
-
let zeros = 0;
|
|
1025
|
-
while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
|
|
1026
|
-
const digits = [];
|
|
1027
|
-
for (let i = zeros; i < bytes.length; i++) {
|
|
1028
|
-
let carry = bytes[i];
|
|
1029
|
-
for (let j = 0; j < digits.length; j++) {
|
|
1030
|
-
carry += digits[j] * 256;
|
|
1031
|
-
digits[j] = carry % 58;
|
|
1032
|
-
carry = carry / 58 | 0;
|
|
1033
|
-
}
|
|
1034
|
-
while (carry > 0) {
|
|
1035
|
-
digits.push(carry % 58);
|
|
1036
|
-
carry = carry / 58 | 0;
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
let out = "1".repeat(zeros);
|
|
1040
|
-
for (let i = digits.length - 1; i >= 0; i--) out += B58_ALPHABET[digits[i]];
|
|
1041
|
-
return out;
|
|
1042
|
-
}
|
|
1043
|
-
function base58btcDecode(s) {
|
|
1044
|
-
let zeros = 0;
|
|
1045
|
-
while (zeros < s.length && s[zeros] === "1") zeros++;
|
|
1046
|
-
const bytes = [];
|
|
1047
|
-
for (let i = zeros; i < s.length; i++) {
|
|
1048
|
-
const val = B58_MAP[s[i]];
|
|
1049
|
-
if (val === void 0) throw new Error(`invalid base58 character "${s[i]}"`);
|
|
1050
|
-
let carry = val;
|
|
1051
|
-
for (let j = 0; j < bytes.length; j++) {
|
|
1052
|
-
carry += bytes[j] * 58;
|
|
1053
|
-
bytes[j] = carry & 255;
|
|
1054
|
-
carry >>= 8;
|
|
1055
|
-
}
|
|
1056
|
-
while (carry > 0) {
|
|
1057
|
-
bytes.push(carry & 255);
|
|
1058
|
-
carry >>= 8;
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
const out = new Uint8Array(zeros + bytes.length);
|
|
1062
|
-
for (let i = 0; i < bytes.length; i++) out[zeros + i] = bytes[bytes.length - 1 - i];
|
|
1063
|
-
return out;
|
|
1064
|
-
}
|
|
1065
|
-
function encodeDidKey(publicKey) {
|
|
1066
|
-
if (publicKey.length !== ED25519_KEY_BYTES) {
|
|
1067
|
-
throw new Error(`INVALID_KEY: expected ${ED25519_KEY_BYTES} bytes, got ${publicKey.length}`);
|
|
1068
|
-
}
|
|
1069
|
-
const prefixed = new Uint8Array(ED25519_MULTICODEC.length + publicKey.length);
|
|
1070
|
-
prefixed.set(ED25519_MULTICODEC, 0);
|
|
1071
|
-
prefixed.set(publicKey, ED25519_MULTICODEC.length);
|
|
1072
|
-
return `did:key:z${base58btcEncode(prefixed)}`;
|
|
1073
|
-
}
|
|
1074
|
-
function decodeDidKey(did) {
|
|
1075
|
-
if (!did.startsWith("did:key:z")) {
|
|
1076
|
-
throw new Error(`NOT_KEY_BEARING: ${JSON.stringify(did)} is not a base58btc did:key`);
|
|
1077
|
-
}
|
|
1078
|
-
let decoded;
|
|
1079
|
-
try {
|
|
1080
|
-
decoded = base58btcDecode(did.slice("did:key:z".length));
|
|
1081
|
-
} catch (err) {
|
|
1082
|
-
throw new Error(`NOT_KEY_BEARING: ${err.message}`);
|
|
1083
|
-
}
|
|
1084
|
-
if (decoded.length !== ED25519_MULTICODEC.length + ED25519_KEY_BYTES || decoded[0] !== ED25519_MULTICODEC[0] || decoded[1] !== ED25519_MULTICODEC[1]) {
|
|
1085
|
-
throw new Error("NOT_KEY_BEARING: not an ed25519-pub multicodec payload");
|
|
1086
|
-
}
|
|
1087
|
-
return decoded.slice(ED25519_MULTICODEC.length);
|
|
1088
|
-
}
|
|
1089
|
-
function isKeyBearingDid(did) {
|
|
1090
|
-
try {
|
|
1091
|
-
decodeDidKey(did);
|
|
1092
|
-
return true;
|
|
1093
|
-
} catch {
|
|
1094
|
-
return false;
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
// src/mandate.ts
|
|
1099
|
-
import * as ed2 from "@noble/ed25519";
|
|
1100
|
-
var MandateError = class extends Error {
|
|
1101
|
-
constructor(code, message) {
|
|
1102
|
-
super(`${code}: ${message}`);
|
|
1103
|
-
this.code = code;
|
|
1104
|
-
this.name = "MandateError";
|
|
1105
|
-
}
|
|
1106
|
-
};
|
|
1107
|
-
function buildMandateProjection(payload, entry) {
|
|
1108
|
-
const { profile_id, bounds_hash, context_hash, execution_context_hash, gate_content_hashes, commitment_mode, expires_at } = payload;
|
|
1109
|
-
if (!bounds_hash || !context_hash || !commitment_mode) {
|
|
1110
|
-
throw new MandateError("MALFORMED_ATTESTATION", "mandate projection requires bounds_hash, context_hash and commitment_mode");
|
|
1111
|
-
}
|
|
1112
|
-
const projection = {
|
|
1113
|
-
typ: "HAP-mandate",
|
|
1114
|
-
version: "0.6",
|
|
1115
|
-
profile_id,
|
|
1116
|
-
owner_did: entry.did,
|
|
1117
|
-
bounds_hash,
|
|
1118
|
-
context_hash,
|
|
1119
|
-
execution_context_hash,
|
|
1120
|
-
gate_content_hashes,
|
|
1121
|
-
commitment_mode,
|
|
1122
|
-
expires_at,
|
|
1123
|
-
nonce: entry.nonce
|
|
1124
|
-
};
|
|
1125
|
-
if (payload.intent_disclosure_hash !== void 0) {
|
|
1126
|
-
projection.intent_disclosure_hash = payload.intent_disclosure_hash;
|
|
1127
|
-
}
|
|
1128
|
-
return projection;
|
|
1129
|
-
}
|
|
1130
|
-
function mandateSigningBytes(projection) {
|
|
1131
|
-
return new TextEncoder().encode(canonicalize(projection));
|
|
1132
|
-
}
|
|
1133
|
-
function approvalSigningBytes(approval) {
|
|
1134
|
-
return new TextEncoder().encode(canonicalize(approval));
|
|
1135
|
-
}
|
|
1136
|
-
function base64urlToBytes(s) {
|
|
1137
|
-
const base64 = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
1138
|
-
const padding = base64.length % 4 === 0 ? "" : "=".repeat(4 - base64.length % 4);
|
|
1139
|
-
return new Uint8Array(Buffer.from(base64 + padding, "base64"));
|
|
1140
|
-
}
|
|
1141
|
-
function bytesToBase64url(bytes) {
|
|
1142
|
-
return Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1143
|
-
}
|
|
1144
|
-
async function signMandateProjection(projection, privateKey) {
|
|
1145
|
-
const sig = await ed2.signAsync(mandateSigningBytes(projection), privateKey);
|
|
1146
|
-
return bytesToBase64url(sig);
|
|
1147
|
-
}
|
|
1148
|
-
async function signApproval(approval, privateKey) {
|
|
1149
|
-
const sig = await ed2.signAsync(approvalSigningBytes(approval), privateKey);
|
|
1150
|
-
return bytesToBase64url(sig);
|
|
1151
|
-
}
|
|
1152
|
-
async function verifyOwnerMandate(attestation, entry) {
|
|
1153
|
-
const owners = attestation.payload.resolved_owners ?? [];
|
|
1154
|
-
if (!owners.includes(entry.did)) {
|
|
1155
|
-
throw new MandateError("OWNER_NOT_RESOLVED", `signing DID ${entry.did} is not in resolved_owners`);
|
|
1156
|
-
}
|
|
1157
|
-
if (entry.alg !== "EdDSA") {
|
|
1158
|
-
throw new MandateError("MANDATE_SIGNATURE_INVALID", `alg ${entry.alg} disagrees with the DID's key type (DID is authoritative)`);
|
|
1159
|
-
}
|
|
1160
|
-
let publicKey;
|
|
1161
|
-
try {
|
|
1162
|
-
publicKey = decodeDidKey(entry.did);
|
|
1163
|
-
} catch (err) {
|
|
1164
|
-
throw new MandateError("NOT_KEY_BEARING", err.message);
|
|
1165
|
-
}
|
|
1166
|
-
const projection = buildMandateProjection(attestation.payload, entry);
|
|
1167
|
-
const ok = await ed2.verifyAsync(base64urlToBytes(entry.signature), mandateSigningBytes(projection), publicKey);
|
|
1168
|
-
if (!ok) {
|
|
1169
|
-
throw new MandateError("MANDATE_SIGNATURE_INVALID", `owner mandate signature by ${entry.did} does not verify`);
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
async function verifyOwnerMandates(attestation) {
|
|
1173
|
-
const entries = attestation.payload.owner_mandates ?? [];
|
|
1174
|
-
for (const entry of entries) {
|
|
1175
|
-
await verifyOwnerMandate(attestation, entry);
|
|
1176
|
-
}
|
|
1177
|
-
return entries;
|
|
1178
|
-
}
|
|
1179
|
-
async function verifyApproval(approval, signature, signerDid) {
|
|
1180
|
-
let publicKey;
|
|
1181
|
-
try {
|
|
1182
|
-
publicKey = decodeDidKey(signerDid);
|
|
1183
|
-
} catch (err) {
|
|
1184
|
-
throw new MandateError("NOT_KEY_BEARING", err.message);
|
|
1185
|
-
}
|
|
1186
|
-
const ok = await ed2.verifyAsync(base64urlToBytes(signature), approvalSigningBytes(approval), publicKey);
|
|
1187
|
-
if (!ok) {
|
|
1188
|
-
throw new MandateError("APPROVAL_SIGNATURE_INVALID", `approval signature by ${signerDid} does not verify`);
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
// src/receipt.ts
|
|
1193
|
-
import * as ed3 from "@noble/ed25519";
|
|
1194
|
-
async function verifyReceiptSignature(receipt, publicKeyHex) {
|
|
1195
|
-
const { signature, ...unsigned } = receipt;
|
|
1196
|
-
if (!signature) {
|
|
1197
|
-
throw new Error("INVALID_SIGNATURE: receipt carries no signature");
|
|
1198
|
-
}
|
|
1199
|
-
const bytes = new TextEncoder().encode(canonicalize(unsigned));
|
|
1200
|
-
const base64 = signature.replace(/-/g, "+").replace(/_/g, "/");
|
|
1201
|
-
const padding = base64.length % 4 === 0 ? "" : "=".repeat(4 - base64.length % 4);
|
|
1202
|
-
const sigBytes = new Uint8Array(Buffer.from(base64 + padding, "base64"));
|
|
1203
|
-
const publicKeyBytes = new Uint8Array(Buffer.from(publicKeyHex, "hex"));
|
|
1204
|
-
const ok = await ed3.verifyAsync(sigBytes, bytes, publicKeyBytes).catch(() => false);
|
|
1205
|
-
if (!ok) {
|
|
1206
|
-
throw new Error("INVALID_SIGNATURE: receipt signature verification failed");
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
1224
|
export {
|
|
1210
1225
|
CanonicalValueError,
|
|
1211
1226
|
ContentBindingError,
|
|
1212
|
-
|
|
1227
|
+
HapError,
|
|
1228
|
+
MANDATE_ERROR_CODES,
|
|
1229
|
+
OwnerSignatureError,
|
|
1230
|
+
PROTOCOL_VERSION,
|
|
1213
1231
|
SIGNABLE_COMMITMENT_MODES,
|
|
1232
|
+
SUPPORTED_VERSIONS,
|
|
1233
|
+
TICKET_ERROR_CODES,
|
|
1214
1234
|
allowedCommitmentModes,
|
|
1215
1235
|
approvalSigningBytes,
|
|
1216
|
-
attestationId,
|
|
1217
1236
|
bindingAppliesTo,
|
|
1218
1237
|
boundActionTypes,
|
|
1219
1238
|
buildMandateProjection,
|
|
1220
1239
|
canonicalBounds,
|
|
1221
|
-
|
|
1222
|
-
canonicalFrame,
|
|
1240
|
+
canonicalScope,
|
|
1223
1241
|
canonicalize,
|
|
1224
1242
|
canonicalizeText,
|
|
1225
|
-
|
|
1243
|
+
checkDiscloseSubset,
|
|
1244
|
+
checkMandateExpiry,
|
|
1245
|
+
checkOwnerSignatureRequirement,
|
|
1226
1246
|
clearProfiles,
|
|
1227
1247
|
computeBoundsHash,
|
|
1228
1248
|
computeContentHash,
|
|
1229
|
-
computeContextHash,
|
|
1230
1249
|
computeFieldsContentHash,
|
|
1231
|
-
computeFrameHash,
|
|
1232
1250
|
computeIntentDisclosureHash,
|
|
1233
1251
|
computeIntentHash,
|
|
1252
|
+
computeProfileHash,
|
|
1253
|
+
computeScopeHash,
|
|
1234
1254
|
contentCanonicalBytes,
|
|
1235
|
-
decodeAttestationBlob,
|
|
1236
1255
|
decodeDidKey,
|
|
1256
|
+
decodeMandateBlob,
|
|
1237
1257
|
deriveIdentityLine,
|
|
1238
|
-
encodeAttestationBlob,
|
|
1239
1258
|
encodeDidKey,
|
|
1240
|
-
|
|
1259
|
+
encodeMandateBlob,
|
|
1260
|
+
fromBase64Url,
|
|
1241
1261
|
getAllProfiles,
|
|
1242
1262
|
getProfile,
|
|
1243
1263
|
intentDisclosureCanonicalBytes,
|
|
1244
1264
|
isCommitmentModeAllowed,
|
|
1245
1265
|
isFieldBinding,
|
|
1246
1266
|
isKeyBearingDid,
|
|
1247
|
-
isV4Attestation,
|
|
1248
1267
|
listProfiles,
|
|
1249
|
-
|
|
1268
|
+
mandateBlobId,
|
|
1269
|
+
negotiateVersion,
|
|
1270
|
+
projectionSigningBytes,
|
|
1271
|
+
publicTicketView,
|
|
1250
1272
|
registerProfile,
|
|
1251
1273
|
selectBoundFields,
|
|
1252
1274
|
signApproval,
|
|
1275
|
+
signMandate,
|
|
1253
1276
|
signMandateProjection,
|
|
1277
|
+
signTicket,
|
|
1278
|
+
toBase64Url,
|
|
1254
1279
|
validateBoundsParams,
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
validateFrameParams,
|
|
1280
|
+
validateMandateOwners,
|
|
1281
|
+
validateProfile,
|
|
1282
|
+
validateScopeParams,
|
|
1259
1283
|
validateSubject,
|
|
1260
1284
|
verify,
|
|
1261
1285
|
verifyApproval,
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
verifyFrameHash,
|
|
1268
|
-
verifyOwnerMandate,
|
|
1269
|
-
verifyOwnerMandates,
|
|
1270
|
-
verifyReceiptSignature
|
|
1286
|
+
verifyMandate,
|
|
1287
|
+
verifyMandateSignature,
|
|
1288
|
+
verifyOwnerSignature,
|
|
1289
|
+
verifyOwnerSignatures,
|
|
1290
|
+
verifyTicketSignature
|
|
1271
1291
|
};
|