@suveren/gateway 0.7.4 → 0.8.0
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/mollie.json +103 -28
- package/dist/control-plane/index.mjs +248 -58
- package/dist/mcp-server/http.mjs +577 -124
- package/dist/ui/assets/index-BFnSVa9N.js +110 -0
- package/dist/ui/assets/{index-CkWaRck1.css → index-BYVb_0oG.css} +1 -1
- package/dist/ui/index.html +2 -2
- package/node_modules/@hap/core/dist/index.d.mts +285 -13
- package/node_modules/@hap/core/dist/index.d.ts +285 -13
- package/node_modules/@hap/core/dist/index.js +281 -13
- package/node_modules/@hap/core/dist/index.mjs +266 -12
- package/node_modules/@hap/core/package.json +4 -2
- package/node_modules/@hap/core/src/did-key.ts +126 -0
- package/node_modules/@hap/core/src/frame.ts +115 -14
- package/node_modules/@hap/core/src/gatekeeper.ts +26 -0
- package/node_modules/@hap/core/src/identity.ts +21 -5
- package/node_modules/@hap/core/src/index.ts +3 -0
- package/node_modules/@hap/core/src/mandate.ts +207 -0
- package/node_modules/@hap/core/src/receipt.ts +74 -0
- package/node_modules/@hap/core/src/types.ts +87 -5
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/accept.js +54 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/charset.js +10 -34
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/encoding.js +9 -30
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/language.js +15 -36
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/mediaType.js +16 -134
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/LICENSE +22 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/README.md +71 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/dist/index.d.ts +46 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/dist/index.js +176 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/dist/index.js.map +1 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/package.json +52 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/package.json +16 -10
- package/node_modules/@types/node/README.md +1 -1
- package/node_modules/@types/node/buffer.buffer.d.ts +10 -5
- package/node_modules/@types/node/crypto.d.ts +2 -4
- package/node_modules/@types/node/dgram.d.ts +119 -4
- package/node_modules/@types/node/ffi.d.ts +1 -1
- package/node_modules/@types/node/fs/promises.d.ts +61 -65
- package/node_modules/@types/node/fs.d.ts +86 -76
- package/node_modules/@types/node/http.d.ts +17 -1
- package/node_modules/@types/node/http2.d.ts +12 -4
- package/node_modules/@types/node/index.d.ts +1 -0
- package/node_modules/@types/node/inspector.d.ts +2 -1
- package/node_modules/@types/node/net.d.ts +108 -16
- package/node_modules/@types/node/package.json +2 -2
- package/node_modules/@types/node/process.d.ts +52 -0
- package/node_modules/@types/node/quic.d.ts +246 -38
- package/node_modules/@types/node/sqlite.d.ts +6 -2
- package/node_modules/@types/node/stream/iter.d.ts +4 -4
- package/node_modules/@types/node/test.d.ts +36 -0
- package/node_modules/@types/node/tls.d.ts +24 -0
- package/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +5 -0
- package/node_modules/@types/node/ts5.6/index.d.ts +1 -0
- package/node_modules/@types/node/ts5.7/index.d.ts +1 -0
- package/node_modules/@types/node/tty.d.ts +4 -3
- package/node_modules/@types/node/vfs.d.ts +210 -0
- package/node_modules/@types/node/vm.d.ts +1 -1
- package/node_modules/express-rate-limit/dist/index.cjs +6 -3
- package/node_modules/express-rate-limit/dist/index.d.cts +11 -0
- package/node_modules/express-rate-limit/dist/index.d.mts +11 -0
- package/node_modules/express-rate-limit/dist/index.d.ts +11 -0
- package/node_modules/express-rate-limit/dist/index.mjs +6 -3
- package/node_modules/express-rate-limit/{license.md → license} +7 -6
- package/node_modules/express-rate-limit/package.json +5 -5
- package/node_modules/express-rate-limit/readme.md +1 -0
- package/node_modules/fast-uri/index.js +215 -47
- package/node_modules/fast-uri/lib/schemes.js +9 -4
- package/node_modules/fast-uri/lib/utils.js +396 -92
- package/node_modules/fast-uri/package.json +1 -1
- package/node_modules/fast-uri/test/component-safe-serialization.test.js +163 -0
- package/node_modules/fast-uri/test/equal.test.js +31 -3
- package/node_modules/fast-uri/test/fixtures/uri-js-parse.json +2 -0
- package/node_modules/fast-uri/test/ipv6-canonical.test.js +34 -0
- package/node_modules/fast-uri/test/ipv6-validation.test.js +124 -0
- package/node_modules/fast-uri/test/malformed-percent.test.js +77 -0
- package/node_modules/fast-uri/test/malformed-urn.test.js +61 -0
- package/node_modules/fast-uri/test/parse.test.js +7 -3
- package/node_modules/fast-uri/test/query-fragment-normalization.test.js +33 -0
- package/node_modules/fast-uri/test/reserved-path-normalization.test.js +109 -0
- package/node_modules/fast-uri/test/scheme-validation.test.js +124 -0
- package/node_modules/fast-uri/test/security-normalization.test.js +101 -0
- package/node_modules/fast-uri/test/security.test.js +75 -3
- package/node_modules/fast-uri/test/urn-full-input.test.js +29 -0
- package/node_modules/fast-uri/test/websocket-query-preservation.test.js +24 -0
- package/node_modules/hono/dist/cjs/client/client.js +25 -11
- package/node_modules/hono/dist/cjs/client/utils.js +4 -1
- package/node_modules/hono/dist/cjs/context.js +4 -0
- package/node_modules/hono/dist/cjs/helper/accepts/accepts.js +36 -2
- package/node_modules/hono/dist/cjs/helper/ssg/ssg.js +1 -1
- package/node_modules/hono/dist/cjs/helper/ssg/utils.js +30 -10
- package/node_modules/hono/dist/cjs/jsx/dom/render.js +2 -0
- package/node_modules/hono/dist/cjs/middleware/cache/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/cors/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/csrf/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/etag/digest.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/etag/index.js +3 -3
- package/node_modules/hono/dist/cjs/middleware/pretty-json/index.js +3 -1
- package/node_modules/hono/dist/cjs/request.js +8 -4
- package/node_modules/hono/dist/cjs/router/linear-router/router.js +7 -2
- package/node_modules/hono/dist/cjs/router/pattern-router/router.js +3 -9
- package/node_modules/hono/dist/cjs/router/reg-exp-router/node.js +10 -3
- package/node_modules/hono/dist/cjs/router/reg-exp-router/router.js +47 -83
- package/node_modules/hono/dist/cjs/router/reg-exp-router/trie.js +2 -1
- package/node_modules/hono/dist/cjs/router/trie-router/node.js +46 -69
- package/node_modules/hono/dist/cjs/router/trie-router/router.js +3 -11
- package/node_modules/hono/dist/cjs/router/utils.js +27 -0
- package/node_modules/hono/dist/cjs/utils/body.js +15 -3
- package/node_modules/hono/dist/cjs/utils/cookie.js +1 -1
- package/node_modules/hono/dist/cjs/utils/ipaddr.js +5 -3
- package/node_modules/hono/dist/cjs/utils/stream.js +7 -1
- package/node_modules/hono/dist/cjs/utils/url.js +9 -1
- package/node_modules/hono/dist/client/client.js +25 -11
- package/node_modules/hono/dist/client/utils.js +4 -1
- package/node_modules/hono/dist/context.js +4 -0
- package/node_modules/hono/dist/helper/accepts/accepts.js +36 -2
- package/node_modules/hono/dist/helper/ssg/ssg.js +1 -1
- package/node_modules/hono/dist/helper/ssg/utils.js +30 -10
- package/node_modules/hono/dist/jsx/dom/render.js +2 -0
- package/node_modules/hono/dist/middleware/cache/index.js +1 -1
- package/node_modules/hono/dist/middleware/cors/index.js +1 -1
- package/node_modules/hono/dist/middleware/csrf/index.js +1 -1
- package/node_modules/hono/dist/middleware/etag/digest.js +1 -1
- package/node_modules/hono/dist/middleware/etag/index.js +3 -3
- package/node_modules/hono/dist/middleware/pretty-json/index.js +3 -1
- package/node_modules/hono/dist/request.js +8 -4
- package/node_modules/hono/dist/router/linear-router/router.js +7 -2
- package/node_modules/hono/dist/router/pattern-router/router.js +3 -9
- package/node_modules/hono/dist/router/reg-exp-router/node.js +6 -2
- package/node_modules/hono/dist/router/reg-exp-router/router.js +53 -84
- package/node_modules/hono/dist/router/reg-exp-router/trie.js +2 -1
- package/node_modules/hono/dist/router/trie-router/node.js +46 -69
- package/node_modules/hono/dist/router/trie-router/router.js +3 -11
- package/node_modules/hono/dist/router/utils.js +5 -0
- package/node_modules/hono/dist/types/context.d.ts +4 -0
- package/node_modules/hono/dist/types/router/reg-exp-router/node.d.ts +3 -0
- package/node_modules/hono/dist/types/router/trie-router/node.d.ts +1 -2
- package/node_modules/hono/dist/types/router/trie-router/router.d.ts +0 -1
- package/node_modules/hono/dist/types/router/utils.d.ts +1 -0
- package/node_modules/hono/dist/types/utils/url.d.ts +4 -0
- package/node_modules/hono/dist/utils/body.js +15 -3
- package/node_modules/hono/dist/utils/cookie.js +1 -1
- package/node_modules/hono/dist/utils/ipaddr.js +5 -3
- package/node_modules/hono/dist/utils/stream.js +7 -1
- package/node_modules/hono/dist/utils/url.js +9 -1
- package/node_modules/hono/package.json +1 -1
- package/node_modules/ip-address/README.md +92 -82
- package/node_modules/ip-address/dist/common.d.ts +20 -0
- package/node_modules/ip-address/dist/common.js +36 -0
- package/node_modules/ip-address/dist/common.js.map +1 -1
- package/node_modules/ip-address/dist/ipv4.d.ts +50 -0
- package/node_modules/ip-address/dist/ipv4.js +75 -0
- package/node_modules/ip-address/dist/ipv4.js.map +1 -1
- package/node_modules/ip-address/dist/ipv6.d.ts +79 -10
- package/node_modules/ip-address/dist/ipv6.js +120 -30
- package/node_modules/ip-address/dist/ipv6.js.map +1 -1
- package/node_modules/ip-address/dist/v4/constants.d.ts +12 -0
- package/node_modules/ip-address/dist/v4/constants.js +40 -1
- package/node_modules/ip-address/dist/v4/constants.js.map +1 -1
- package/node_modules/ip-address/dist/v6/constants.d.ts +14 -0
- package/node_modules/ip-address/dist/v6/constants.js +47 -1
- package/node_modules/ip-address/dist/v6/constants.js.map +1 -1
- package/node_modules/ip-address/package.json +1 -1
- package/node_modules/is-plain-object/README.md +39 -95
- package/node_modules/is-plain-object/dist/is-plain-object.js +0 -2
- package/node_modules/is-plain-object/is-plain-object.d.ts +6 -1
- package/node_modules/is-plain-object/package.json +8 -34
- package/node_modules/jose/dist/types/jwe/compact/decrypt.d.ts +7 -16
- package/node_modules/jose/dist/types/jwe/compact/encrypt.d.ts +10 -20
- package/node_modules/jose/dist/types/jwe/flattened/decrypt.d.ts +7 -16
- package/node_modules/jose/dist/types/jwe/flattened/encrypt.d.ts +16 -38
- package/node_modules/jose/dist/types/jwe/general/decrypt.d.ts +7 -16
- package/node_modules/jose/dist/types/jwe/general/encrypt.d.ts +19 -38
- package/node_modules/jose/dist/types/jwk/embedded.d.ts +3 -8
- package/node_modules/jose/dist/types/jwk/thumbprint.d.ts +5 -5
- package/node_modules/jose/dist/types/jwks/local.d.ts +6 -10
- package/node_modules/jose/dist/types/jwks/remote.d.ts +17 -28
- package/node_modules/jose/dist/types/jws/compact/sign.d.ts +5 -10
- package/node_modules/jose/dist/types/jws/compact/verify.d.ts +9 -18
- package/node_modules/jose/dist/types/jws/flattened/sign.d.ts +7 -16
- package/node_modules/jose/dist/types/jws/flattened/verify.d.ts +9 -18
- package/node_modules/jose/dist/types/jws/general/sign.d.ts +11 -21
- package/node_modules/jose/dist/types/jws/general/verify.d.ts +12 -21
- package/node_modules/jose/dist/types/jwt/decrypt.d.ts +10 -20
- package/node_modules/jose/dist/types/jwt/encrypt.d.ts +17 -29
- package/node_modules/jose/dist/types/jwt/sign.d.ts +12 -19
- package/node_modules/jose/dist/types/jwt/unsecured.d.ts +17 -28
- package/node_modules/jose/dist/types/jwt/verify.d.ts +9 -19
- package/node_modules/jose/dist/types/key/export.d.ts +4 -4
- package/node_modules/jose/dist/types/key/generate_key_pair.d.ts +6 -8
- package/node_modules/jose/dist/types/key/generate_secret.d.ts +4 -11
- package/node_modules/jose/dist/types/key/import.d.ts +10 -17
- package/node_modules/jose/dist/types/types.d.ts +48 -86
- package/node_modules/jose/dist/types/util/base64url.d.ts +2 -2
- package/node_modules/jose/dist/types/util/decode_jwt.d.ts +3 -10
- package/node_modules/jose/dist/types/util/decode_protected_header.d.ts +4 -4
- package/node_modules/jose/dist/types/util/errors.d.ts +26 -53
- package/node_modules/jose/dist/webapi/index.js +70 -32
- package/node_modules/jose/dist/webapi/jwe/compact/decrypt.js +7 -8
- package/node_modules/jose/dist/webapi/jwe/compact/encrypt.js +42 -26
- package/node_modules/jose/dist/webapi/jwe/flattened/decrypt.js +14 -10
- package/node_modules/jose/dist/webapi/jwe/flattened/encrypt.js +53 -71
- package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +38 -49
- package/node_modules/jose/dist/webapi/jwe/general/encrypt.js +121 -158
- package/node_modules/jose/dist/webapi/jwk/embedded.js +30 -18
- package/node_modules/jose/dist/webapi/jwk/thumbprint.js +54 -64
- package/node_modules/jose/dist/webapi/jwks/local.js +49 -74
- package/node_modules/jose/dist/webapi/jwks/remote.js +91 -160
- package/node_modules/jose/dist/webapi/jws/compact/sign.js +21 -20
- package/node_modules/jose/dist/webapi/jws/compact/verify.js +7 -8
- package/node_modules/jose/dist/webapi/jws/flattened/sign.js +29 -30
- package/node_modules/jose/dist/webapi/jws/flattened/verify.js +21 -23
- package/node_modules/jose/dist/webapi/jws/general/sign.js +61 -70
- package/node_modules/jose/dist/webapi/jws/general/verify.js +47 -41
- package/node_modules/jose/dist/webapi/jwt/decrypt.js +13 -22
- package/node_modules/jose/dist/webapi/jwt/encrypt.js +58 -100
- package/node_modules/jose/dist/webapi/jwt/sign.js +18 -50
- package/node_modules/jose/dist/webapi/jwt/unsecured.js +34 -63
- package/node_modules/jose/dist/webapi/jwt/verify.js +12 -14
- package/node_modules/jose/dist/webapi/key/export.js +32 -37
- package/node_modules/jose/dist/webapi/key/generate_key_pair.js +40 -44
- package/node_modules/jose/dist/webapi/key/generate_secret.js +35 -39
- package/node_modules/jose/dist/webapi/key/import.js +52 -52
- package/node_modules/jose/dist/webapi/lib/asn1.js +130 -164
- package/node_modules/jose/dist/webapi/lib/base64.js +18 -20
- package/node_modules/jose/dist/webapi/lib/buffer_utils.js +34 -38
- package/node_modules/jose/dist/webapi/lib/content_encryption.js +79 -123
- package/node_modules/jose/dist/webapi/lib/crypto_key.js +24 -25
- package/node_modules/jose/dist/webapi/lib/deflate.js +46 -40
- package/node_modules/jose/dist/webapi/lib/helpers.js +40 -37
- package/node_modules/jose/dist/webapi/lib/invalid_key_input.js +10 -24
- package/node_modules/jose/dist/webapi/lib/is_key_like.js +17 -15
- package/node_modules/jose/dist/webapi/lib/jwe_algorithms.js +106 -93
- package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +210 -176
- package/node_modules/jose/dist/webapi/lib/jwe_encrypt.js +105 -107
- package/node_modules/jose/dist/webapi/lib/jwk_metadata.js +19 -0
- package/node_modules/jose/dist/webapi/lib/jwk_to_key.js +9 -16
- package/node_modules/jose/dist/webapi/lib/jws_algorithms.js +62 -61
- package/node_modules/jose/dist/webapi/lib/jws_sign.js +58 -72
- package/node_modules/jose/dist/webapi/lib/jws_verify.js +90 -103
- package/node_modules/jose/dist/webapi/lib/jwt_claims_set.js +134 -185
- package/node_modules/jose/dist/webapi/lib/key.js +84 -121
- package/node_modules/jose/dist/webapi/lib/key_algorithm.js +13 -8
- package/node_modules/jose/dist/webapi/lib/key_descriptor.js +8 -6
- package/node_modules/jose/dist/webapi/lib/key_management.js +200 -225
- package/node_modules/jose/dist/webapi/lib/key_options.js +8 -0
- package/node_modules/jose/dist/webapi/lib/options.js +57 -44
- package/node_modules/jose/dist/webapi/lib/signing.js +18 -22
- package/node_modules/jose/dist/webapi/lib/type_checks.js +28 -33
- package/node_modules/jose/dist/webapi/util/base64url.js +26 -35
- package/node_modules/jose/dist/webapi/util/decode_jwt.js +32 -31
- package/node_modules/jose/dist/webapi/util/decode_protected_header.js +18 -23
- package/node_modules/jose/dist/webapi/util/errors.js +94 -84
- package/node_modules/jose/package.json +17 -81
- package/package.json +2 -2
- package/profiles/calendar/0.5.profile.json +190 -0
- package/profiles/charge/0.5.profile.json +210 -0
- package/profiles/customers/0.7.profile.json +157 -0
- package/profiles/deploy/0.9.profile.json +176 -0
- package/profiles/email/0.6.profile.json +208 -0
- package/profiles/index.json +9 -1
- package/profiles/publish/0.5.profile.json +186 -0
- package/profiles/purchase/0.5.profile.json +231 -0
- package/profiles/records/0.5.profile.json +117 -0
- package/dist/ui/assets/index-0Q2eNytX.js +0 -110
- package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/HISTORY.md +0 -114
|
@@ -151,9 +151,9 @@ function computeIntentDisclosureHash(intentCiphertext, approversFrozen) {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
// src/identity.ts
|
|
154
|
-
function validateSubject(subject) {
|
|
154
|
+
function validateSubject(subject, opts) {
|
|
155
155
|
const errors = [];
|
|
156
|
-
const { did, assurance, method, trust_root, verifier, disclose
|
|
156
|
+
const { did, assurance, method, trust_root, verifier, disclose } = subject;
|
|
157
157
|
if (!did) errors.push("subject.did is required");
|
|
158
158
|
if (assurance === "low") {
|
|
159
159
|
if (method !== "self_declared") errors.push(`low assurance requires method "self_declared", got "${method}"`);
|
|
@@ -166,7 +166,10 @@ function validateSubject(subject) {
|
|
|
166
166
|
} else if (method === "eudi") {
|
|
167
167
|
if (trust_root !== "external") errors.push('eudi requires trust_root "external"');
|
|
168
168
|
if (!verifier) errors.push("eudi requires a verifier");
|
|
169
|
-
if (
|
|
169
|
+
if (opts?.ownerMandates !== void 0) {
|
|
170
|
+
const entry = opts.ownerMandates.find((m) => m.did === did && m.binding === "eudi");
|
|
171
|
+
if (!entry) errors.push('eudi requires an owner_mandates entry with binding "eudi" for this DID');
|
|
172
|
+
}
|
|
170
173
|
} else {
|
|
171
174
|
errors.push(`high assurance requires method "as_vouched" or "eudi", got "${method}"`);
|
|
172
175
|
}
|
|
@@ -284,15 +287,51 @@ function validateContextParams(params, profile) {
|
|
|
284
287
|
}
|
|
285
288
|
return { valid: errors.length === 0, errors };
|
|
286
289
|
}
|
|
290
|
+
var CanonicalValueError = class extends Error {
|
|
291
|
+
code;
|
|
292
|
+
field;
|
|
293
|
+
constructor(code, field, message) {
|
|
294
|
+
super(message);
|
|
295
|
+
this.name = "CanonicalValueError";
|
|
296
|
+
this.code = code;
|
|
297
|
+
this.field = field;
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
function percentEncodeCanonicalValue(raw) {
|
|
301
|
+
const bytes = new TextEncoder().encode(raw);
|
|
302
|
+
let out = "";
|
|
303
|
+
for (const b of bytes) {
|
|
304
|
+
if (b === 61 || b === 37 || b < 32 || b > 126) {
|
|
305
|
+
out += "%" + b.toString(16).toUpperCase().padStart(2, "0");
|
|
306
|
+
} else {
|
|
307
|
+
out += String.fromCharCode(b);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return out;
|
|
311
|
+
}
|
|
312
|
+
function canonicalRecords(params, keyOrder, code) {
|
|
313
|
+
const lines = [];
|
|
314
|
+
for (const key of keyOrder) {
|
|
315
|
+
const value = params[key];
|
|
316
|
+
if (value === void 0 || value === null) continue;
|
|
317
|
+
const raw = String(value);
|
|
318
|
+
if (raw.includes("\n") || raw.includes("\r")) {
|
|
319
|
+
throw new CanonicalValueError(
|
|
320
|
+
code,
|
|
321
|
+
key,
|
|
322
|
+
`Value for "${key}" contains a raw newline or carriage return. Refusing: a hash over stripped or normalized input would not represent what was authorized.`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
lines.push(`${key}=${percentEncodeCanonicalValue(raw)}`);
|
|
326
|
+
}
|
|
327
|
+
return lines.join("\n");
|
|
328
|
+
}
|
|
287
329
|
function canonicalBounds(params, profile) {
|
|
288
330
|
const validation = validateBoundsParams(params, profile);
|
|
289
331
|
if (!validation.valid) {
|
|
290
332
|
throw new Error(`Invalid bounds parameters: ${validation.errors.join("; ")}`);
|
|
291
333
|
}
|
|
292
|
-
|
|
293
|
-
(key) => `${key}=${String(params[key])}`
|
|
294
|
-
);
|
|
295
|
-
return lines.join("\n");
|
|
334
|
+
return canonicalRecords(params, profile.boundsSchema.keyOrder, "BOUNDS_INVALID_VALUE");
|
|
296
335
|
}
|
|
297
336
|
function canonicalContext(params, profile) {
|
|
298
337
|
if (!profile.contextSchema || Object.keys(profile.contextSchema.fields).length === 0) {
|
|
@@ -302,10 +341,7 @@ function canonicalContext(params, profile) {
|
|
|
302
341
|
if (!validation.valid) {
|
|
303
342
|
throw new Error(`Invalid context parameters: ${validation.errors.join("; ")}`);
|
|
304
343
|
}
|
|
305
|
-
|
|
306
|
-
(key) => `${key}=${String(params[key])}`
|
|
307
|
-
);
|
|
308
|
-
return lines.join("\n");
|
|
344
|
+
return canonicalRecords(params, profile.contextSchema.keyOrder, "CONTEXT_INVALID_VALUE");
|
|
309
345
|
}
|
|
310
346
|
function computeBoundsHash(params, profile) {
|
|
311
347
|
const canonical = canonicalBounds(params, profile);
|
|
@@ -435,7 +471,17 @@ async function verify(request, publicKeyHex, now = Math.floor(Date.now() / 1e3),
|
|
|
435
471
|
return verifyV3(request, profile, publicKeyHex, now, executionLog, errors);
|
|
436
472
|
}
|
|
437
473
|
}
|
|
474
|
+
function noAttestationSupplied(request) {
|
|
475
|
+
return !Array.isArray(request.attestations) || request.attestations.length === 0;
|
|
476
|
+
}
|
|
477
|
+
function noAttestationResult() {
|
|
478
|
+
return {
|
|
479
|
+
approved: false,
|
|
480
|
+
errors: [{ code: "MALFORMED_ATTESTATION", message: "No attestation supplied \u2014 nothing to verify" }]
|
|
481
|
+
};
|
|
482
|
+
}
|
|
438
483
|
async function verifyV3(request, profile, publicKeyHex, now, executionLog, errors) {
|
|
484
|
+
if (noAttestationSupplied(request)) return noAttestationResult();
|
|
439
485
|
let expectedFrameHash;
|
|
440
486
|
try {
|
|
441
487
|
expectedFrameHash = computeFrameHash(request.frame, profile);
|
|
@@ -496,6 +542,7 @@ async function verifyV3(request, profile, publicKeyHex, now, executionLog, error
|
|
|
496
542
|
return { approved: true };
|
|
497
543
|
}
|
|
498
544
|
async function verifyV4(request, profile, publicKeyHex, now, executionLog) {
|
|
545
|
+
if (noAttestationSupplied(request)) return noAttestationResult();
|
|
499
546
|
const errors = [];
|
|
500
547
|
const bounds = request.frame;
|
|
501
548
|
const context = request.context;
|
|
@@ -869,10 +916,207 @@ function resolveCumulativeFields(request, profile, executionLog, now) {
|
|
|
869
916
|
}
|
|
870
917
|
return errors;
|
|
871
918
|
}
|
|
919
|
+
|
|
920
|
+
// src/did-key.ts
|
|
921
|
+
var B58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
922
|
+
var B58_MAP = {};
|
|
923
|
+
for (let i = 0; i < B58_ALPHABET.length; i++) B58_MAP[B58_ALPHABET[i]] = i;
|
|
924
|
+
var ED25519_MULTICODEC = Uint8Array.from([237, 1]);
|
|
925
|
+
var ED25519_KEY_BYTES = 32;
|
|
926
|
+
function base58btcEncode(bytes) {
|
|
927
|
+
let zeros = 0;
|
|
928
|
+
while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
|
|
929
|
+
const digits = [];
|
|
930
|
+
for (let i = zeros; i < bytes.length; i++) {
|
|
931
|
+
let carry = bytes[i];
|
|
932
|
+
for (let j = 0; j < digits.length; j++) {
|
|
933
|
+
carry += digits[j] * 256;
|
|
934
|
+
digits[j] = carry % 58;
|
|
935
|
+
carry = carry / 58 | 0;
|
|
936
|
+
}
|
|
937
|
+
while (carry > 0) {
|
|
938
|
+
digits.push(carry % 58);
|
|
939
|
+
carry = carry / 58 | 0;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
let out = "1".repeat(zeros);
|
|
943
|
+
for (let i = digits.length - 1; i >= 0; i--) out += B58_ALPHABET[digits[i]];
|
|
944
|
+
return out;
|
|
945
|
+
}
|
|
946
|
+
function base58btcDecode(s) {
|
|
947
|
+
let zeros = 0;
|
|
948
|
+
while (zeros < s.length && s[zeros] === "1") zeros++;
|
|
949
|
+
const bytes = [];
|
|
950
|
+
for (let i = zeros; i < s.length; i++) {
|
|
951
|
+
const val = B58_MAP[s[i]];
|
|
952
|
+
if (val === void 0) throw new Error(`invalid base58 character "${s[i]}"`);
|
|
953
|
+
let carry = val;
|
|
954
|
+
for (let j = 0; j < bytes.length; j++) {
|
|
955
|
+
carry += bytes[j] * 58;
|
|
956
|
+
bytes[j] = carry & 255;
|
|
957
|
+
carry >>= 8;
|
|
958
|
+
}
|
|
959
|
+
while (carry > 0) {
|
|
960
|
+
bytes.push(carry & 255);
|
|
961
|
+
carry >>= 8;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
const out = new Uint8Array(zeros + bytes.length);
|
|
965
|
+
for (let i = 0; i < bytes.length; i++) out[zeros + i] = bytes[bytes.length - 1 - i];
|
|
966
|
+
return out;
|
|
967
|
+
}
|
|
968
|
+
function encodeDidKey(publicKey) {
|
|
969
|
+
if (publicKey.length !== ED25519_KEY_BYTES) {
|
|
970
|
+
throw new Error(`INVALID_KEY: expected ${ED25519_KEY_BYTES} bytes, got ${publicKey.length}`);
|
|
971
|
+
}
|
|
972
|
+
const prefixed = new Uint8Array(ED25519_MULTICODEC.length + publicKey.length);
|
|
973
|
+
prefixed.set(ED25519_MULTICODEC, 0);
|
|
974
|
+
prefixed.set(publicKey, ED25519_MULTICODEC.length);
|
|
975
|
+
return `did:key:z${base58btcEncode(prefixed)}`;
|
|
976
|
+
}
|
|
977
|
+
function decodeDidKey(did) {
|
|
978
|
+
if (!did.startsWith("did:key:z")) {
|
|
979
|
+
throw new Error(`NOT_KEY_BEARING: ${JSON.stringify(did)} is not a base58btc did:key`);
|
|
980
|
+
}
|
|
981
|
+
let decoded;
|
|
982
|
+
try {
|
|
983
|
+
decoded = base58btcDecode(did.slice("did:key:z".length));
|
|
984
|
+
} catch (err) {
|
|
985
|
+
throw new Error(`NOT_KEY_BEARING: ${err.message}`);
|
|
986
|
+
}
|
|
987
|
+
if (decoded.length !== ED25519_MULTICODEC.length + ED25519_KEY_BYTES || decoded[0] !== ED25519_MULTICODEC[0] || decoded[1] !== ED25519_MULTICODEC[1]) {
|
|
988
|
+
throw new Error("NOT_KEY_BEARING: not an ed25519-pub multicodec payload");
|
|
989
|
+
}
|
|
990
|
+
return decoded.slice(ED25519_MULTICODEC.length);
|
|
991
|
+
}
|
|
992
|
+
function isKeyBearingDid(did) {
|
|
993
|
+
try {
|
|
994
|
+
decodeDidKey(did);
|
|
995
|
+
return true;
|
|
996
|
+
} catch {
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// src/mandate.ts
|
|
1002
|
+
import * as ed2 from "@noble/ed25519";
|
|
1003
|
+
var MandateError = class extends Error {
|
|
1004
|
+
constructor(code, message) {
|
|
1005
|
+
super(`${code}: ${message}`);
|
|
1006
|
+
this.code = code;
|
|
1007
|
+
this.name = "MandateError";
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
function buildMandateProjection(payload, entry) {
|
|
1011
|
+
const { profile_id, bounds_hash, context_hash, execution_context_hash, gate_content_hashes, commitment_mode, expires_at } = payload;
|
|
1012
|
+
if (!bounds_hash || !context_hash || !commitment_mode) {
|
|
1013
|
+
throw new MandateError("MALFORMED_ATTESTATION", "mandate projection requires bounds_hash, context_hash and commitment_mode");
|
|
1014
|
+
}
|
|
1015
|
+
const projection = {
|
|
1016
|
+
typ: "HAP-mandate",
|
|
1017
|
+
version: "0.6",
|
|
1018
|
+
profile_id,
|
|
1019
|
+
owner_did: entry.did,
|
|
1020
|
+
bounds_hash,
|
|
1021
|
+
context_hash,
|
|
1022
|
+
execution_context_hash,
|
|
1023
|
+
gate_content_hashes,
|
|
1024
|
+
commitment_mode,
|
|
1025
|
+
expires_at,
|
|
1026
|
+
nonce: entry.nonce
|
|
1027
|
+
};
|
|
1028
|
+
if (payload.intent_disclosure_hash !== void 0) {
|
|
1029
|
+
projection.intent_disclosure_hash = payload.intent_disclosure_hash;
|
|
1030
|
+
}
|
|
1031
|
+
return projection;
|
|
1032
|
+
}
|
|
1033
|
+
function mandateSigningBytes(projection) {
|
|
1034
|
+
return new TextEncoder().encode(canonicalize(projection));
|
|
1035
|
+
}
|
|
1036
|
+
function approvalSigningBytes(approval) {
|
|
1037
|
+
return new TextEncoder().encode(canonicalize(approval));
|
|
1038
|
+
}
|
|
1039
|
+
function base64urlToBytes(s) {
|
|
1040
|
+
const base64 = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
1041
|
+
const padding = base64.length % 4 === 0 ? "" : "=".repeat(4 - base64.length % 4);
|
|
1042
|
+
return new Uint8Array(Buffer.from(base64 + padding, "base64"));
|
|
1043
|
+
}
|
|
1044
|
+
function bytesToBase64url(bytes) {
|
|
1045
|
+
return Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1046
|
+
}
|
|
1047
|
+
async function signMandateProjection(projection, privateKey) {
|
|
1048
|
+
const sig = await ed2.signAsync(mandateSigningBytes(projection), privateKey);
|
|
1049
|
+
return bytesToBase64url(sig);
|
|
1050
|
+
}
|
|
1051
|
+
async function signApproval(approval, privateKey) {
|
|
1052
|
+
const sig = await ed2.signAsync(approvalSigningBytes(approval), privateKey);
|
|
1053
|
+
return bytesToBase64url(sig);
|
|
1054
|
+
}
|
|
1055
|
+
async function verifyOwnerMandate(attestation, entry) {
|
|
1056
|
+
const owners = attestation.payload.resolved_owners ?? [];
|
|
1057
|
+
if (!owners.includes(entry.did)) {
|
|
1058
|
+
throw new MandateError("OWNER_NOT_RESOLVED", `signing DID ${entry.did} is not in resolved_owners`);
|
|
1059
|
+
}
|
|
1060
|
+
if (entry.alg !== "EdDSA") {
|
|
1061
|
+
throw new MandateError("MANDATE_SIGNATURE_INVALID", `alg ${entry.alg} disagrees with the DID's key type (DID is authoritative)`);
|
|
1062
|
+
}
|
|
1063
|
+
let publicKey;
|
|
1064
|
+
try {
|
|
1065
|
+
publicKey = decodeDidKey(entry.did);
|
|
1066
|
+
} catch (err) {
|
|
1067
|
+
throw new MandateError("NOT_KEY_BEARING", err.message);
|
|
1068
|
+
}
|
|
1069
|
+
const projection = buildMandateProjection(attestation.payload, entry);
|
|
1070
|
+
const ok = await ed2.verifyAsync(base64urlToBytes(entry.signature), mandateSigningBytes(projection), publicKey);
|
|
1071
|
+
if (!ok) {
|
|
1072
|
+
throw new MandateError("MANDATE_SIGNATURE_INVALID", `owner mandate signature by ${entry.did} does not verify`);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
async function verifyOwnerMandates(attestation) {
|
|
1076
|
+
const entries = attestation.payload.owner_mandates ?? [];
|
|
1077
|
+
for (const entry of entries) {
|
|
1078
|
+
await verifyOwnerMandate(attestation, entry);
|
|
1079
|
+
}
|
|
1080
|
+
return entries;
|
|
1081
|
+
}
|
|
1082
|
+
async function verifyApproval(approval, signature, signerDid) {
|
|
1083
|
+
let publicKey;
|
|
1084
|
+
try {
|
|
1085
|
+
publicKey = decodeDidKey(signerDid);
|
|
1086
|
+
} catch (err) {
|
|
1087
|
+
throw new MandateError("NOT_KEY_BEARING", err.message);
|
|
1088
|
+
}
|
|
1089
|
+
const ok = await ed2.verifyAsync(base64urlToBytes(signature), approvalSigningBytes(approval), publicKey);
|
|
1090
|
+
if (!ok) {
|
|
1091
|
+
throw new MandateError("APPROVAL_SIGNATURE_INVALID", `approval signature by ${signerDid} does not verify`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// src/receipt.ts
|
|
1096
|
+
import * as ed3 from "@noble/ed25519";
|
|
1097
|
+
async function verifyReceiptSignature(receipt, publicKeyHex) {
|
|
1098
|
+
const { signature, ...unsigned } = receipt;
|
|
1099
|
+
if (!signature) {
|
|
1100
|
+
throw new Error("INVALID_SIGNATURE: receipt carries no signature");
|
|
1101
|
+
}
|
|
1102
|
+
const bytes = new TextEncoder().encode(canonicalize(unsigned));
|
|
1103
|
+
const base64 = signature.replace(/-/g, "+").replace(/_/g, "/");
|
|
1104
|
+
const padding = base64.length % 4 === 0 ? "" : "=".repeat(4 - base64.length % 4);
|
|
1105
|
+
const sigBytes = new Uint8Array(Buffer.from(base64 + padding, "base64"));
|
|
1106
|
+
const publicKeyBytes = new Uint8Array(Buffer.from(publicKeyHex, "hex"));
|
|
1107
|
+
const ok = await ed3.verifyAsync(sigBytes, bytes, publicKeyBytes).catch(() => false);
|
|
1108
|
+
if (!ok) {
|
|
1109
|
+
throw new Error("INVALID_SIGNATURE: receipt signature verification failed");
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
872
1112
|
export {
|
|
1113
|
+
CanonicalValueError,
|
|
873
1114
|
ContentBindingError,
|
|
1115
|
+
MandateError,
|
|
1116
|
+
approvalSigningBytes,
|
|
874
1117
|
attestationId,
|
|
875
1118
|
bindingAppliesTo,
|
|
1119
|
+
buildMandateProjection,
|
|
876
1120
|
canonicalBounds,
|
|
877
1121
|
canonicalContext,
|
|
878
1122
|
canonicalFrame,
|
|
@@ -889,26 +1133,36 @@ export {
|
|
|
889
1133
|
computeIntentHash,
|
|
890
1134
|
contentCanonicalBytes,
|
|
891
1135
|
decodeAttestationBlob,
|
|
1136
|
+
decodeDidKey,
|
|
892
1137
|
deriveIdentityLine,
|
|
893
1138
|
encodeAttestationBlob,
|
|
1139
|
+
encodeDidKey,
|
|
894
1140
|
frameHash,
|
|
895
1141
|
getAllProfiles,
|
|
896
1142
|
getProfile,
|
|
897
1143
|
intentDisclosureCanonicalBytes,
|
|
898
1144
|
isFieldBinding,
|
|
1145
|
+
isKeyBearingDid,
|
|
899
1146
|
isV4Attestation,
|
|
900
1147
|
listProfiles,
|
|
1148
|
+
mandateSigningBytes,
|
|
901
1149
|
registerProfile,
|
|
902
1150
|
selectBoundFields,
|
|
1151
|
+
signApproval,
|
|
1152
|
+
signMandateProjection,
|
|
903
1153
|
validateBoundsParams,
|
|
904
1154
|
validateContextParams,
|
|
905
1155
|
validateFrameParams,
|
|
906
1156
|
validateSubject,
|
|
907
1157
|
verify,
|
|
1158
|
+
verifyApproval,
|
|
908
1159
|
verifyAttestation,
|
|
909
1160
|
verifyAttestationSignature,
|
|
910
1161
|
verifyAttestationV4,
|
|
911
1162
|
verifyBoundsHash,
|
|
912
1163
|
verifyContextHash,
|
|
913
|
-
verifyFrameHash
|
|
1164
|
+
verifyFrameHash,
|
|
1165
|
+
verifyOwnerMandate,
|
|
1166
|
+
verifyOwnerMandates,
|
|
1167
|
+
verifyReceiptSignature
|
|
914
1168
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanagencyp/hap-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Core types, cryptographic primitives, and verification logic for the Human Agency Protocol",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
26
26
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
27
28
|
"test": "vitest run",
|
|
28
29
|
"test:watch": "vitest",
|
|
29
30
|
"prepublishOnly": "pnpm build",
|
|
@@ -39,5 +40,6 @@
|
|
|
39
40
|
"tsup": "^8.0.0",
|
|
40
41
|
"typescript": "^5.3.0",
|
|
41
42
|
"vitest": "^1.0.0"
|
|
42
|
-
}
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://humanagencyprotocol.org"
|
|
43
45
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* did:key — encode/decode for KEY-BEARING DIDs (HAP v0.6).
|
|
3
|
+
*
|
|
4
|
+
* A real `did:key` is self-certifying: the public key IS the identifier, so
|
|
5
|
+
* substituting the key produces a DIFFERENT DID and no key directory is ever
|
|
6
|
+
* consulted. That property is what the Owner Mandate design stands on
|
|
7
|
+
* (protocol.md → "Identity DIDs vs signing DIDs"): a signing DID MUST be
|
|
8
|
+
* key-bearing, and verification uses the key carried in the identifier —
|
|
9
|
+
* deliberately, there is no `public_key` field anywhere on the wire.
|
|
10
|
+
*
|
|
11
|
+
* Format (W3C did:key method, Ed25519):
|
|
12
|
+
* did:key:z<base58btc( 0xed 0x01 ‖ 32-byte-public-key )>
|
|
13
|
+
* `z` is the multibase prefix for base58btc; `0xed 0x01` is the multicodec
|
|
14
|
+
* varint for ed25519-pub. The result always starts `did:key:z6Mk`.
|
|
15
|
+
*
|
|
16
|
+
* Scope: Ed25519 only — the one signing curve this protocol version uses.
|
|
17
|
+
* Anything else (including the reference implementation's legacy decorative
|
|
18
|
+
* `did:key:<uuid-fragment>` identifiers) decodes as NOT key-bearing.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const B58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
22
|
+
const B58_MAP: Record<string, number> = {};
|
|
23
|
+
for (let i = 0; i < B58_ALPHABET.length; i++) B58_MAP[B58_ALPHABET[i]] = i;
|
|
24
|
+
|
|
25
|
+
/** multicodec ed25519-pub (0xed) as unsigned varint, followed by key bytes. */
|
|
26
|
+
const ED25519_MULTICODEC = Uint8Array.from([0xed, 0x01]);
|
|
27
|
+
const ED25519_KEY_BYTES = 32;
|
|
28
|
+
|
|
29
|
+
function base58btcEncode(bytes: Uint8Array): string {
|
|
30
|
+
// Count leading zero bytes — they encode as leading '1's.
|
|
31
|
+
let zeros = 0;
|
|
32
|
+
while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
|
|
33
|
+
|
|
34
|
+
const digits: number[] = [];
|
|
35
|
+
for (let i = zeros; i < bytes.length; i++) {
|
|
36
|
+
let carry = bytes[i];
|
|
37
|
+
for (let j = 0; j < digits.length; j++) {
|
|
38
|
+
carry += digits[j] * 256;
|
|
39
|
+
digits[j] = carry % 58;
|
|
40
|
+
carry = (carry / 58) | 0;
|
|
41
|
+
}
|
|
42
|
+
while (carry > 0) {
|
|
43
|
+
digits.push(carry % 58);
|
|
44
|
+
carry = (carry / 58) | 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let out = '1'.repeat(zeros);
|
|
49
|
+
for (let i = digits.length - 1; i >= 0; i--) out += B58_ALPHABET[digits[i]];
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function base58btcDecode(s: string): Uint8Array {
|
|
54
|
+
let zeros = 0;
|
|
55
|
+
while (zeros < s.length && s[zeros] === '1') zeros++;
|
|
56
|
+
|
|
57
|
+
const bytes: number[] = [];
|
|
58
|
+
for (let i = zeros; i < s.length; i++) {
|
|
59
|
+
const val = B58_MAP[s[i]];
|
|
60
|
+
if (val === undefined) throw new Error(`invalid base58 character "${s[i]}"`);
|
|
61
|
+
let carry = val;
|
|
62
|
+
for (let j = 0; j < bytes.length; j++) {
|
|
63
|
+
carry += bytes[j] * 58;
|
|
64
|
+
bytes[j] = carry & 0xff;
|
|
65
|
+
carry >>= 8;
|
|
66
|
+
}
|
|
67
|
+
while (carry > 0) {
|
|
68
|
+
bytes.push(carry & 0xff);
|
|
69
|
+
carry >>= 8;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const out = new Uint8Array(zeros + bytes.length);
|
|
74
|
+
for (let i = 0; i < bytes.length; i++) out[zeros + i] = bytes[bytes.length - 1 - i];
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Encode a 32-byte Ed25519 public key as a key-bearing `did:key:z6Mk…`. */
|
|
79
|
+
export function encodeDidKey(publicKey: Uint8Array): string {
|
|
80
|
+
if (publicKey.length !== ED25519_KEY_BYTES) {
|
|
81
|
+
throw new Error(`INVALID_KEY: expected ${ED25519_KEY_BYTES} bytes, got ${publicKey.length}`);
|
|
82
|
+
}
|
|
83
|
+
const prefixed = new Uint8Array(ED25519_MULTICODEC.length + publicKey.length);
|
|
84
|
+
prefixed.set(ED25519_MULTICODEC, 0);
|
|
85
|
+
prefixed.set(publicKey, ED25519_MULTICODEC.length);
|
|
86
|
+
return `did:key:z${base58btcEncode(prefixed)}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Decode a key-bearing `did:key` to its Ed25519 public key bytes.
|
|
91
|
+
*
|
|
92
|
+
* @throws Error prefixed `NOT_KEY_BEARING:` for anything that is not a valid
|
|
93
|
+
* base58btc-multibase Ed25519 did:key — including the legacy decorative
|
|
94
|
+
* `did:key:<uuid-fragment>` identifiers, other multibase prefixes, and other
|
|
95
|
+
* key types. The error name is the point: under the mandate design, a
|
|
96
|
+
* non-key-bearing signing DID must fail STRUCTURALLY, never fall back.
|
|
97
|
+
*/
|
|
98
|
+
export function decodeDidKey(did: string): Uint8Array {
|
|
99
|
+
if (!did.startsWith('did:key:z')) {
|
|
100
|
+
throw new Error(`NOT_KEY_BEARING: ${JSON.stringify(did)} is not a base58btc did:key`);
|
|
101
|
+
}
|
|
102
|
+
let decoded: Uint8Array;
|
|
103
|
+
try {
|
|
104
|
+
decoded = base58btcDecode(did.slice('did:key:z'.length));
|
|
105
|
+
} catch (err) {
|
|
106
|
+
throw new Error(`NOT_KEY_BEARING: ${(err as Error).message}`);
|
|
107
|
+
}
|
|
108
|
+
if (
|
|
109
|
+
decoded.length !== ED25519_MULTICODEC.length + ED25519_KEY_BYTES ||
|
|
110
|
+
decoded[0] !== ED25519_MULTICODEC[0] ||
|
|
111
|
+
decoded[1] !== ED25519_MULTICODEC[1]
|
|
112
|
+
) {
|
|
113
|
+
throw new Error('NOT_KEY_BEARING: not an ed25519-pub multicodec payload');
|
|
114
|
+
}
|
|
115
|
+
return decoded.slice(ED25519_MULTICODEC.length);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** True iff the DID decodes as a key-bearing Ed25519 did:key. Never throws. */
|
|
119
|
+
export function isKeyBearingDid(did: string): boolean {
|
|
120
|
+
try {
|
|
121
|
+
decodeDidKey(did);
|
|
122
|
+
return true;
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
* Frame Canonicalization for Agent Profiles
|
|
3
3
|
*
|
|
4
4
|
* Agent profiles support mixed-type fields (strings and numbers).
|
|
5
|
-
* Canonical form:
|
|
6
|
-
*
|
|
5
|
+
* Canonical form: `key=value` records joined with LF, keys in the profile's
|
|
6
|
+
* keyOrder. Values are stringified with String(value) — the shortest
|
|
7
|
+
* round-trippable form for numbers — then percent-encoded per protocol.md
|
|
8
|
+
* (`=`, `%`, and every byte outside printable ASCII); a value carrying a raw
|
|
9
|
+
* LF/CR is refused, never normalized. See `canonicalRecords` below.
|
|
7
10
|
*
|
|
8
11
|
* v0.3: frameSchema
|
|
9
12
|
* v0.4: boundsSchema + contextSchema (separate hashes)
|
|
@@ -176,11 +179,115 @@ export function validateContextParams(
|
|
|
176
179
|
return { valid: errors.length === 0, errors };
|
|
177
180
|
}
|
|
178
181
|
|
|
182
|
+
// ─── Value Encoding (normative, v0.5+) ───────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Thrown when a value cannot be canonicalized at all — currently only for raw
|
|
186
|
+
* LF/CR inside a value. Carries the protocol error code so callers can map it
|
|
187
|
+
* straight onto the wire without string-matching a message.
|
|
188
|
+
*
|
|
189
|
+
* protocol.md → *Bounds & Scope Canonicalization* → Value encoding:
|
|
190
|
+
* "Values MUST NOT contain raw newline (\n) or carriage-return (\r)
|
|
191
|
+
* characters. Implementations MUST reject input containing them; silent
|
|
192
|
+
* stripping or normalization is a violation because it produces a hash that
|
|
193
|
+
* does not faithfully represent the input."
|
|
194
|
+
*/
|
|
195
|
+
export class CanonicalValueError extends Error {
|
|
196
|
+
readonly code: 'BOUNDS_INVALID_VALUE' | 'CONTEXT_INVALID_VALUE';
|
|
197
|
+
readonly field: string;
|
|
198
|
+
|
|
199
|
+
constructor(code: 'BOUNDS_INVALID_VALUE' | 'CONTEXT_INVALID_VALUE', field: string, message: string) {
|
|
200
|
+
super(message);
|
|
201
|
+
this.name = 'CanonicalValueError';
|
|
202
|
+
this.code = code;
|
|
203
|
+
this.field = field;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Percent-encode a value per protocol.md → *Value encoding*.
|
|
209
|
+
*
|
|
210
|
+
* Encoded, over the value's UTF-8 bytes, as `%` + two UPPERCASE hex digits:
|
|
211
|
+
* - `=` (0x3D) — otherwise it would be read as the key/value separator
|
|
212
|
+
* - `%` (0x25) — so the encoding is self-inverse
|
|
213
|
+
* - every byte outside printable ASCII 0x20–0x7E
|
|
214
|
+
*
|
|
215
|
+
* LF and CR are deliberately NOT in this list: they are refused upstream, so
|
|
216
|
+
* encoding them is unreachable (v0.7 removed the spec's contradiction here).
|
|
217
|
+
*
|
|
218
|
+
* This runs at canonicalization time only. Stored values keep the human's
|
|
219
|
+
* original bytes.
|
|
220
|
+
*/
|
|
221
|
+
function percentEncodeCanonicalValue(raw: string): string {
|
|
222
|
+
const bytes = new TextEncoder().encode(raw);
|
|
223
|
+
let out = '';
|
|
224
|
+
for (const b of bytes) {
|
|
225
|
+
if (b === 0x3d || b === 0x25 || b < 0x20 || b > 0x7e) {
|
|
226
|
+
out += '%' + b.toString(16).toUpperCase().padStart(2, '0');
|
|
227
|
+
} else {
|
|
228
|
+
out += String.fromCharCode(b);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The one place `key=value` records are built for bounds and context.
|
|
236
|
+
*
|
|
237
|
+
* Rules applied here (all normative, protocol.md → *Bounds & Scope
|
|
238
|
+
* Canonicalization*):
|
|
239
|
+
* - keys in the schema's keyOrder, never alphabetical
|
|
240
|
+
* - a value carrying a raw LF/CR is REFUSED (never stripped or encoded)
|
|
241
|
+
* - `=`, `%`, and any byte outside 0x20–0x7E are percent-encoded (UPPERCASE)
|
|
242
|
+
* - numbers use JS `String()`, which is the shortest round-trippable form
|
|
243
|
+
* (`String(20.0)` === "20")
|
|
244
|
+
* - a key with no value is OMITTED entirely — it emits no record
|
|
245
|
+
*
|
|
246
|
+
* On the omission rule: required keys are guaranteed present by the caller's
|
|
247
|
+
* validation (`validateBoundsParams` / `validateContextParams` reject a missing
|
|
248
|
+
* required field), so "explicit inclusion of all required keys" still holds.
|
|
249
|
+
* What remains are *optional* keys the human never set. Rendering those as the
|
|
250
|
+
* literal string "undefined" — the pre-fix behaviour — hashed a JavaScript
|
|
251
|
+
* artifact that no other language would produce and that collides with a real
|
|
252
|
+
* value of "undefined". Omitting them (rather than emitting `key=`) also keeps
|
|
253
|
+
* "the human set no limit" distinct from "the human set an empty value", and
|
|
254
|
+
* matches this package's JSON canonicalization, which drops undefined-valued
|
|
255
|
+
* properties.
|
|
256
|
+
*/
|
|
257
|
+
function canonicalRecords(
|
|
258
|
+
params: Record<string, string | number | undefined>,
|
|
259
|
+
keyOrder: string[],
|
|
260
|
+
code: 'BOUNDS_INVALID_VALUE' | 'CONTEXT_INVALID_VALUE',
|
|
261
|
+
): string {
|
|
262
|
+
const lines: string[] = [];
|
|
263
|
+
|
|
264
|
+
for (const key of keyOrder) {
|
|
265
|
+
const value = params[key];
|
|
266
|
+
if (value === undefined || value === null) continue;
|
|
267
|
+
|
|
268
|
+
const raw = String(value);
|
|
269
|
+
if (raw.includes('\n') || raw.includes('\r')) {
|
|
270
|
+
throw new CanonicalValueError(
|
|
271
|
+
code,
|
|
272
|
+
key,
|
|
273
|
+
`Value for "${key}" contains a raw newline or carriage return. ` +
|
|
274
|
+
'Refusing: a hash over stripped or normalized input would not represent what was authorized.',
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
lines.push(`${key}=${percentEncodeCanonicalValue(raw)}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return lines.join('\n');
|
|
282
|
+
}
|
|
283
|
+
|
|
179
284
|
/**
|
|
180
285
|
* Builds the canonical bounds string from parameters.
|
|
181
|
-
*
|
|
286
|
+
* Keys are ordered per profile's boundsSchema.keyOrder; values are encoded per
|
|
287
|
+
* protocol.md → *Value encoding* (see `canonicalRecords`).
|
|
182
288
|
*
|
|
183
289
|
* @throws Error if any field fails validation
|
|
290
|
+
* @throws CanonicalValueError (code BOUNDS_INVALID_VALUE) if a value carries a raw LF/CR
|
|
184
291
|
*/
|
|
185
292
|
export function canonicalBounds(params: AgentBoundsParams, profile: AgentProfile): string {
|
|
186
293
|
const validation = validateBoundsParams(params, profile);
|
|
@@ -188,19 +295,17 @@ export function canonicalBounds(params: AgentBoundsParams, profile: AgentProfile
|
|
|
188
295
|
throw new Error(`Invalid bounds parameters: ${validation.errors.join('; ')}`);
|
|
189
296
|
}
|
|
190
297
|
|
|
191
|
-
|
|
192
|
-
(key) => `${key}=${String(params[key])}`
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
return lines.join('\n');
|
|
298
|
+
return canonicalRecords(params, profile.boundsSchema!.keyOrder, 'BOUNDS_INVALID_VALUE');
|
|
196
299
|
}
|
|
197
300
|
|
|
198
301
|
/**
|
|
199
302
|
* Builds the canonical context string from parameters.
|
|
200
|
-
*
|
|
303
|
+
* Keys are ordered per profile's contextSchema.keyOrder; values are encoded per
|
|
304
|
+
* protocol.md → *Value encoding* (see `canonicalRecords`).
|
|
201
305
|
* For empty context (no contextSchema or no fields), returns "".
|
|
202
306
|
*
|
|
203
307
|
* @throws Error if any field fails validation
|
|
308
|
+
* @throws CanonicalValueError (code CONTEXT_INVALID_VALUE) if a value carries a raw LF/CR
|
|
204
309
|
*/
|
|
205
310
|
export function canonicalContext(params: AgentContextParams, profile: AgentProfile): string {
|
|
206
311
|
// No contextSchema or no fields → empty context
|
|
@@ -213,11 +318,7 @@ export function canonicalContext(params: AgentContextParams, profile: AgentProfi
|
|
|
213
318
|
throw new Error(`Invalid context parameters: ${validation.errors.join('; ')}`);
|
|
214
319
|
}
|
|
215
320
|
|
|
216
|
-
|
|
217
|
-
(key) => `${key}=${String(params[key])}`
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
return lines.join('\n');
|
|
321
|
+
return canonicalRecords(params, profile.contextSchema.keyOrder, 'CONTEXT_INVALID_VALUE');
|
|
221
322
|
}
|
|
222
323
|
|
|
223
324
|
/**
|