@oma3/omatrust 0.1.0-alpha.10 → 0.1.0-alpha.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/app-registry/index.cjs +53 -0
- package/dist/app-registry/index.cjs.map +1 -1
- package/dist/app-registry/index.js +53 -0
- package/dist/app-registry/index.js.map +1 -1
- package/dist/identity/index.cjs +1028 -6
- package/dist/identity/index.cjs.map +1 -1
- package/dist/identity/index.d.cts +2 -1
- package/dist/identity/index.d.ts +2 -1
- package/dist/identity/index.js +984 -7
- package/dist/identity/index.js.map +1 -1
- package/dist/index-B5OC9_8B.d.cts +480 -0
- package/dist/index-Bu-xxcv9.d.ts +407 -0
- package/dist/index-C6WNgPRx.d.ts +480 -0
- package/dist/index-C7odEbp6.d.cts +407 -0
- package/dist/index.cjs +2498 -373
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2342 -236
- package/dist/index.js.map +1 -1
- package/dist/reputation/index.browser.cjs +949 -67
- package/dist/reputation/index.browser.cjs.map +1 -1
- package/dist/reputation/index.browser.d.cts +2 -2
- package/dist/reputation/index.browser.d.ts +2 -2
- package/dist/reputation/index.browser.js +947 -67
- package/dist/reputation/index.browser.js.map +1 -1
- package/dist/reputation/index.cjs +1745 -232
- package/dist/reputation/index.cjs.map +1 -1
- package/dist/reputation/index.d.cts +3 -2
- package/dist/reputation/index.d.ts +3 -2
- package/dist/reputation/index.js +1727 -232
- package/dist/reputation/index.js.map +1 -1
- package/dist/{subject-ownership-CXvzEjpH.d.cts → subject-ownership-B7cFlm8c.d.cts} +256 -26
- package/dist/{subject-ownership-CXvzEjpH.d.ts → subject-ownership-B7cFlm8c.d.ts} +256 -26
- package/dist/types-Dn0OwaNj.d.cts +307 -0
- package/dist/types-Dn0OwaNj.d.ts +307 -0
- package/dist/widgets/index.cjs +70 -27
- package/dist/widgets/index.cjs.map +1 -1
- package/dist/widgets/index.d.cts +45 -18
- package/dist/widgets/index.d.ts +45 -18
- package/dist/widgets/index.js +67 -26
- package/dist/widgets/index.js.map +1 -1
- package/package.json +5 -2
- package/dist/index-B9KW02US.d.cts +0 -119
- package/dist/index-DXrwBex9.d.ts +0 -119
- package/dist/index-QZDExA4I.d.cts +0 -90
- package/dist/index-QZDExA4I.d.ts +0 -90
package/dist/index.cjs
CHANGED
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var ethers = require('ethers');
|
|
4
|
+
var jose = require('jose');
|
|
4
5
|
var canonicalize = require('canonicalize');
|
|
6
|
+
var cid = require('multiformats/cid');
|
|
7
|
+
var sha2 = require('multiformats/hashes/sha2');
|
|
8
|
+
var raw = require('multiformats/codecs/raw');
|
|
9
|
+
var base32 = require('multiformats/bases/base32');
|
|
10
|
+
var base58 = require('multiformats/bases/base58');
|
|
5
11
|
var easSdk = require('@ethereum-attestation-service/eas-sdk');
|
|
6
12
|
var promises = require('dns/promises');
|
|
7
13
|
|
|
8
14
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
15
|
|
|
16
|
+
function _interopNamespace(e) {
|
|
17
|
+
if (e && e.__esModule) return e;
|
|
18
|
+
var n = Object.create(null);
|
|
19
|
+
if (e) {
|
|
20
|
+
Object.keys(e).forEach(function (k) {
|
|
21
|
+
if (k !== 'default') {
|
|
22
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
23
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function () { return e[k]; }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
n.default = e;
|
|
31
|
+
return Object.freeze(n);
|
|
32
|
+
}
|
|
33
|
+
|
|
10
34
|
var canonicalize__default = /*#__PURE__*/_interopDefault(canonicalize);
|
|
35
|
+
var raw__namespace = /*#__PURE__*/_interopNamespace(raw);
|
|
11
36
|
|
|
12
37
|
var __defProp = Object.defineProperty;
|
|
13
38
|
var __export = (target, all) => {
|
|
@@ -19,31 +44,31 @@ var __export = (target, all) => {
|
|
|
19
44
|
var OmaTrustError = class extends Error {
|
|
20
45
|
code;
|
|
21
46
|
details;
|
|
22
|
-
constructor(
|
|
47
|
+
constructor(code2, message, details) {
|
|
23
48
|
super(message);
|
|
24
49
|
this.name = "OmaTrustError";
|
|
25
|
-
this.code =
|
|
50
|
+
this.code = code2;
|
|
26
51
|
this.details = details;
|
|
27
52
|
}
|
|
28
53
|
};
|
|
29
|
-
function toOmaTrustError(
|
|
30
|
-
return new OmaTrustError(
|
|
54
|
+
function toOmaTrustError(code2, message, details) {
|
|
55
|
+
return new OmaTrustError(code2, message, details);
|
|
31
56
|
}
|
|
32
57
|
|
|
33
58
|
// src/shared/assert.ts
|
|
34
|
-
function assertString(value, name,
|
|
59
|
+
function assertString(value, name, code2 = "INVALID_INPUT") {
|
|
35
60
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
36
|
-
throw new OmaTrustError(
|
|
61
|
+
throw new OmaTrustError(code2, `${name} must be a non-empty string`, { value });
|
|
37
62
|
}
|
|
38
63
|
}
|
|
39
|
-
function assertNumber(value, name,
|
|
64
|
+
function assertNumber(value, name, code2 = "INVALID_INPUT") {
|
|
40
65
|
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
41
|
-
throw new OmaTrustError(
|
|
66
|
+
throw new OmaTrustError(code2, `${name} must be a valid number`, { value });
|
|
42
67
|
}
|
|
43
68
|
}
|
|
44
|
-
function assertObject(value, name,
|
|
69
|
+
function assertObject(value, name, code2 = "INVALID_INPUT") {
|
|
45
70
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
46
|
-
throw new OmaTrustError(
|
|
71
|
+
throw new OmaTrustError(code2, `${name} must be an object`, { value });
|
|
47
72
|
}
|
|
48
73
|
}
|
|
49
74
|
function asError(err) {
|
|
@@ -56,37 +81,63 @@ function asError(err) {
|
|
|
56
81
|
// src/identity/index.ts
|
|
57
82
|
var identity_exports = {};
|
|
58
83
|
__export(identity_exports, {
|
|
84
|
+
artifactDidFromBytes: () => artifactDidFromBytes,
|
|
85
|
+
artifactDidFromJson: () => artifactDidFromJson,
|
|
86
|
+
assertBareDid: () => assertBareDid,
|
|
87
|
+
assertJsonSafe: () => assertJsonSafe,
|
|
59
88
|
buildCaip10: () => buildCaip10,
|
|
60
89
|
buildCaip2: () => buildCaip2,
|
|
61
90
|
buildDidPkh: () => buildDidPkh,
|
|
62
91
|
buildDidPkhFromCaip10: () => buildDidPkhFromCaip10,
|
|
63
92
|
buildDidWeb: () => buildDidWeb,
|
|
64
93
|
buildEvmDidPkh: () => buildEvmDidPkh,
|
|
94
|
+
canonicalizeAndKeccak256: () => canonicalizeAndKeccak256,
|
|
65
95
|
canonicalizeForHash: () => canonicalizeForHash,
|
|
66
96
|
canonicalizeJson: () => canonicalizeJson,
|
|
67
97
|
computeDidAddress: () => computeDidAddress,
|
|
68
98
|
computeDidHash: () => computeDidHash,
|
|
99
|
+
computeJwkThumbprint: () => computeJwkThumbprint,
|
|
100
|
+
didEthrToDidPkh: () => didEthrToDidPkh,
|
|
101
|
+
didJwkToJwk: () => didJwkToJwk,
|
|
102
|
+
didKeyToDidJwk: () => didKeyToDidJwk,
|
|
69
103
|
didToAddress: () => didToAddress,
|
|
70
104
|
extractAddressFromDid: () => extractAddressFromDid,
|
|
105
|
+
extractAuthorizationMetadata: () => extractAuthorizationMetadata,
|
|
106
|
+
extractControllerEvmAddress: () => extractControllerEvmAddress,
|
|
71
107
|
extractDidIdentifier: () => extractDidIdentifier,
|
|
72
108
|
extractDidMethod: () => extractDidMethod,
|
|
109
|
+
formatJktValue: () => formatJktValue,
|
|
73
110
|
getAddressFromDidPkh: () => getAddressFromDidPkh,
|
|
74
111
|
getChainIdFromDidPkh: () => getChainIdFromDidPkh,
|
|
75
112
|
getDomainFromDidWeb: () => getDomainFromDidWeb,
|
|
76
113
|
getNamespaceFromDidPkh: () => getNamespaceFromDidPkh,
|
|
77
114
|
hashCanonicalizedJson: () => hashCanonicalizedJson,
|
|
115
|
+
isDidUrl: () => isDidUrl,
|
|
78
116
|
isEvmDidPkh: () => isEvmDidPkh,
|
|
117
|
+
isPrivateKeyDid: () => isPrivateKeyDid,
|
|
118
|
+
isSameControllerId: () => isSameControllerId,
|
|
79
119
|
isValidDid: () => isValidDid,
|
|
120
|
+
jwkToDidJwk: () => jwkToDidJwk,
|
|
80
121
|
normalizeCaip10: () => normalizeCaip10,
|
|
81
122
|
normalizeDid: () => normalizeDid,
|
|
82
123
|
normalizeDidHandle: () => normalizeDidHandle,
|
|
124
|
+
normalizeDidJwk: () => normalizeDidJwk,
|
|
83
125
|
normalizeDidKey: () => normalizeDidKey,
|
|
84
126
|
normalizeDidPkh: () => normalizeDidPkh,
|
|
85
127
|
normalizeDidWeb: () => normalizeDidWeb,
|
|
86
128
|
normalizeDomain: () => normalizeDomain,
|
|
129
|
+
parseArtifactDid: () => parseArtifactDid,
|
|
87
130
|
parseCaip10: () => parseCaip10,
|
|
88
131
|
parseCaip2: () => parseCaip2,
|
|
89
|
-
|
|
132
|
+
parseDidUrl: () => parseDidUrl,
|
|
133
|
+
parseJsonStrict: () => parseJsonStrict,
|
|
134
|
+
publicJwkEquals: () => publicJwkEquals,
|
|
135
|
+
resolveDidUrlToControllerDid: () => resolveDidUrlToControllerDid,
|
|
136
|
+
resolveDidUrlToPublicKey: () => resolveDidUrlToPublicKey,
|
|
137
|
+
validateDidAddress: () => validateDidAddress,
|
|
138
|
+
validatePrivateKeyDid: () => validatePrivateKeyDid,
|
|
139
|
+
validatePublicJwk: () => validatePublicJwk,
|
|
140
|
+
verifyDidArtifact: () => verifyDidArtifact
|
|
90
141
|
});
|
|
91
142
|
|
|
92
143
|
// src/identity/caip.ts
|
|
@@ -142,6 +193,158 @@ function parseCaip2(caip2) {
|
|
|
142
193
|
}
|
|
143
194
|
return { namespace, reference };
|
|
144
195
|
}
|
|
196
|
+
var VALID_KTY = /* @__PURE__ */ new Set(["EC", "OKP", "RSA"]);
|
|
197
|
+
var PRIVATE_KEY_FIELDS = /* @__PURE__ */ new Set(["d", "p", "q", "dp", "dq", "qi", "oth"]);
|
|
198
|
+
var REQUIRED_PUBLIC_FIELDS = {
|
|
199
|
+
EC: ["crv", "x", "y"],
|
|
200
|
+
OKP: ["crv", "x"],
|
|
201
|
+
RSA: ["n", "e"]
|
|
202
|
+
};
|
|
203
|
+
function validatePublicJwk(jwk) {
|
|
204
|
+
if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) {
|
|
205
|
+
return { valid: false, error: "JWK must be a non-null object" };
|
|
206
|
+
}
|
|
207
|
+
const obj = jwk;
|
|
208
|
+
const kty = obj.kty;
|
|
209
|
+
if (typeof kty !== "string" || !VALID_KTY.has(kty)) {
|
|
210
|
+
return {
|
|
211
|
+
valid: false,
|
|
212
|
+
error: `Invalid or missing kty (must be one of: ${[...VALID_KTY].join(", ")})`
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
for (const field of PRIVATE_KEY_FIELDS) {
|
|
216
|
+
if (field in obj) {
|
|
217
|
+
return {
|
|
218
|
+
valid: false,
|
|
219
|
+
error: `JWK contains private key field "${field}" \u2014 only public keys are allowed`
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const required = REQUIRED_PUBLIC_FIELDS[kty];
|
|
224
|
+
if (required) {
|
|
225
|
+
for (const field of required) {
|
|
226
|
+
if (!(field in obj) || obj[field] === void 0 || obj[field] === null || obj[field] === "") {
|
|
227
|
+
return {
|
|
228
|
+
valid: false,
|
|
229
|
+
error: `Missing required public key field "${field}" for kty="${kty}"`
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return { valid: true };
|
|
235
|
+
}
|
|
236
|
+
function canonicalizeJwkJson(jwk) {
|
|
237
|
+
const sorted = Object.keys(jwk).sort();
|
|
238
|
+
const obj = {};
|
|
239
|
+
for (const key of sorted) {
|
|
240
|
+
obj[key] = jwk[key];
|
|
241
|
+
}
|
|
242
|
+
return JSON.stringify(obj);
|
|
243
|
+
}
|
|
244
|
+
function jwkToDidJwk(jwk) {
|
|
245
|
+
assertObject(jwk, "jwk", "INVALID_JWK");
|
|
246
|
+
const validation = validatePublicJwk(jwk);
|
|
247
|
+
if (!validation.valid) {
|
|
248
|
+
throw new OmaTrustError("INVALID_JWK", validation.error ?? "Invalid public JWK", { jwk });
|
|
249
|
+
}
|
|
250
|
+
const canonical = canonicalizeJwkJson(jwk);
|
|
251
|
+
const encoded = jose.base64url.encode(new TextEncoder().encode(canonical));
|
|
252
|
+
return `did:jwk:${encoded}`;
|
|
253
|
+
}
|
|
254
|
+
function didJwkToJwk(didJwk) {
|
|
255
|
+
if (typeof didJwk !== "string" || !didJwk.startsWith("did:jwk:")) {
|
|
256
|
+
throw new OmaTrustError("INVALID_DID", "Expected a did:jwk DID", { input: didJwk });
|
|
257
|
+
}
|
|
258
|
+
const parts = didJwk.split(":");
|
|
259
|
+
if (parts.length !== 3) {
|
|
260
|
+
throw new OmaTrustError("INVALID_DID", "did:jwk must have exactly 3 colon-separated parts", {
|
|
261
|
+
input: didJwk
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
const encoded = parts[2];
|
|
265
|
+
if (!encoded || encoded.length === 0) {
|
|
266
|
+
throw new OmaTrustError("INVALID_DID", "Missing base64url-encoded JWK identifier", {
|
|
267
|
+
input: didJwk
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
let decoded;
|
|
271
|
+
try {
|
|
272
|
+
const bytes = jose.base64url.decode(encoded);
|
|
273
|
+
decoded = new TextDecoder().decode(bytes);
|
|
274
|
+
} catch {
|
|
275
|
+
throw new OmaTrustError("INVALID_DID", "Failed to base64url-decode did:jwk identifier", {
|
|
276
|
+
input: didJwk
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
let jwk;
|
|
280
|
+
try {
|
|
281
|
+
jwk = JSON.parse(decoded);
|
|
282
|
+
} catch {
|
|
283
|
+
throw new OmaTrustError("INVALID_DID", "Decoded did:jwk identifier is not valid JSON", {
|
|
284
|
+
input: didJwk
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) {
|
|
288
|
+
throw new OmaTrustError("INVALID_DID", "Decoded did:jwk must be a JSON object", {
|
|
289
|
+
input: didJwk
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
const validation = validatePublicJwk(jwk);
|
|
293
|
+
if (!validation.valid) {
|
|
294
|
+
throw new OmaTrustError("INVALID_DID", validation.error ?? "Invalid public JWK in did:jwk", {
|
|
295
|
+
input: didJwk
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return jwk;
|
|
299
|
+
}
|
|
300
|
+
function extractPublicKeyFields(jwk) {
|
|
301
|
+
const result = {};
|
|
302
|
+
const metadataFields = /* @__PURE__ */ new Set(["kid", "use", "key_ops", "alg", "ext"]);
|
|
303
|
+
for (const [key, value] of Object.entries(jwk)) {
|
|
304
|
+
if (PRIVATE_KEY_FIELDS.has(key)) continue;
|
|
305
|
+
if (metadataFields.has(key)) continue;
|
|
306
|
+
result[key] = value;
|
|
307
|
+
}
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
function publicJwkEquals(a, b) {
|
|
311
|
+
assertObject(a, "a", "INVALID_JWK");
|
|
312
|
+
assertObject(b, "b", "INVALID_JWK");
|
|
313
|
+
const aObj = a;
|
|
314
|
+
const bObj = b;
|
|
315
|
+
for (const field of PRIVATE_KEY_FIELDS) {
|
|
316
|
+
if (field in aObj) {
|
|
317
|
+
throw new OmaTrustError(
|
|
318
|
+
"INVALID_JWK",
|
|
319
|
+
`First JWK contains private key field "${field}"`,
|
|
320
|
+
{ field }
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
if (field in bObj) {
|
|
324
|
+
throw new OmaTrustError(
|
|
325
|
+
"INVALID_JWK",
|
|
326
|
+
`Second JWK contains private key field "${field}"`,
|
|
327
|
+
{ field }
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const aPublic = extractPublicKeyFields(aObj);
|
|
332
|
+
const bPublic = extractPublicKeyFields(bObj);
|
|
333
|
+
return canonicalizeJwkJson(aPublic) === canonicalizeJwkJson(bPublic);
|
|
334
|
+
}
|
|
335
|
+
async function computeJwkThumbprint(jwk, digestAlgorithm = "sha256") {
|
|
336
|
+
assertObject(jwk, "jwk", "INVALID_JWK");
|
|
337
|
+
const validation = validatePublicJwk(jwk);
|
|
338
|
+
if (!validation.valid) {
|
|
339
|
+
throw new OmaTrustError("INVALID_JWK", validation.error ?? "Invalid public JWK", { jwk });
|
|
340
|
+
}
|
|
341
|
+
const alg = digestAlgorithm === "sha256" ? "sha256" : digestAlgorithm === "sha384" ? "sha384" : "sha512";
|
|
342
|
+
return jose.calculateJwkThumbprint(jwk, alg);
|
|
343
|
+
}
|
|
344
|
+
async function formatJktValue(jwk) {
|
|
345
|
+
const thumbprint = await computeJwkThumbprint(jwk, "sha256");
|
|
346
|
+
return `jkt=S256:${thumbprint}`;
|
|
347
|
+
}
|
|
145
348
|
|
|
146
349
|
// src/identity/did.ts
|
|
147
350
|
var DID_REGEX = /^did:[a-z0-9]+:.+$/i;
|
|
@@ -217,7 +420,7 @@ function normalizeDidKey(input) {
|
|
|
217
420
|
}
|
|
218
421
|
function normalizeDid(input) {
|
|
219
422
|
assertString(input, "input", "INVALID_DID");
|
|
220
|
-
const trimmed = input.trim();
|
|
423
|
+
const trimmed = input.trim().split("#")[0];
|
|
221
424
|
if (!trimmed.startsWith("did:")) {
|
|
222
425
|
return normalizeDidWeb(trimmed);
|
|
223
426
|
}
|
|
@@ -234,6 +437,8 @@ function normalizeDid(input) {
|
|
|
234
437
|
return normalizeDidHandle(trimmed);
|
|
235
438
|
case "key":
|
|
236
439
|
return normalizeDidKey(trimmed);
|
|
440
|
+
case "jwk":
|
|
441
|
+
return normalizeDidJwk(trimmed);
|
|
237
442
|
default:
|
|
238
443
|
return trimmed;
|
|
239
444
|
}
|
|
@@ -337,164 +542,994 @@ function extractAddressFromDid(identifier) {
|
|
|
337
542
|
}
|
|
338
543
|
throw new OmaTrustError("INVALID_DID", "Unsupported identifier format", { identifier });
|
|
339
544
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return jcs;
|
|
346
|
-
}
|
|
347
|
-
function canonicalizeForHash(obj) {
|
|
348
|
-
const jcsJson = canonicalizeJson(obj);
|
|
349
|
-
return {
|
|
350
|
-
jcsJson,
|
|
351
|
-
hash: ethers.keccak256(ethers.toUtf8Bytes(jcsJson))
|
|
352
|
-
};
|
|
545
|
+
var CAIP2_NAMESPACE_REGEX = /^[a-z0-9-]{3,8}$/;
|
|
546
|
+
var BASE64URL_REGEX = /^[A-Za-z0-9_-]+$/;
|
|
547
|
+
var VALID_JWK_KTY = /* @__PURE__ */ new Set(["EC", "OKP", "RSA"]);
|
|
548
|
+
function isPrivateKeyDid(did) {
|
|
549
|
+
return validatePrivateKeyDid(did).valid;
|
|
353
550
|
}
|
|
354
|
-
function
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
551
|
+
function validatePrivateKeyDid(did) {
|
|
552
|
+
if (typeof did !== "string" || did.trim().length === 0) {
|
|
553
|
+
return { valid: false, method: null, error: "DID must be a non-empty string" };
|
|
554
|
+
}
|
|
555
|
+
const trimmed = did.trim();
|
|
556
|
+
const method = extractDidMethod(trimmed);
|
|
557
|
+
switch (method) {
|
|
558
|
+
case "pkh":
|
|
559
|
+
return validateDidPkh(trimmed);
|
|
560
|
+
case "jwk":
|
|
561
|
+
return validateDidJwk(trimmed);
|
|
562
|
+
default:
|
|
563
|
+
return {
|
|
564
|
+
valid: false,
|
|
565
|
+
method: null,
|
|
566
|
+
error: method ? `DID method "${method}" is not a recognized private-key method` : "Invalid DID format"
|
|
567
|
+
};
|
|
568
|
+
}
|
|
358
569
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
calculateAverageUserReviewRating: () => calculateAverageUserReviewRating,
|
|
368
|
-
calculateTransferAmount: () => calculateTransferAmount,
|
|
369
|
-
calculateTransferAmountFromAddresses: () => calculateTransferAmountFromAddresses,
|
|
370
|
-
callControllerWitness: () => callControllerWitness,
|
|
371
|
-
constructSeed: () => constructSeed,
|
|
372
|
-
createEvidencePointerProof: () => createEvidencePointerProof,
|
|
373
|
-
createPopEip712Proof: () => createPopEip712Proof,
|
|
374
|
-
createPopJwsProof: () => createPopJwsProof,
|
|
375
|
-
createTxEncodedValueProof: () => createTxEncodedValueProof,
|
|
376
|
-
createTxInteractionProof: () => createTxInteractionProof,
|
|
377
|
-
createX402OfferProof: () => createX402OfferProof,
|
|
378
|
-
createX402ReceiptProof: () => createX402ReceiptProof,
|
|
379
|
-
decodeAttestationData: () => decodeAttestationData,
|
|
380
|
-
deduplicateReviews: () => deduplicateReviews,
|
|
381
|
-
encodeAttestationData: () => encodeAttestationData,
|
|
382
|
-
extractAddressesFromDidDocument: () => extractAddressesFromDidDocument,
|
|
383
|
-
extractExpirationTime: () => extractExpirationTime,
|
|
384
|
-
fetchDidDocument: () => fetchDidDocument,
|
|
385
|
-
formatSchemaUid: () => formatSchemaUid,
|
|
386
|
-
formatTransferAmount: () => formatTransferAmount,
|
|
387
|
-
getAttestation: () => getAttestation,
|
|
388
|
-
getAttestationsByAttester: () => getAttestationsByAttester,
|
|
389
|
-
getAttestationsForDid: () => getAttestationsForDid,
|
|
390
|
-
getChainConstants: () => getChainConstants,
|
|
391
|
-
getExplorerAddressUrl: () => getExplorerAddressUrl,
|
|
392
|
-
getExplorerTxUrl: () => getExplorerTxUrl,
|
|
393
|
-
getLatestAttestations: () => getLatestAttestations,
|
|
394
|
-
getMajorVersion: () => getMajorVersion,
|
|
395
|
-
getOmaTrustProofEip712Types: () => getOmaTrustProofEip712Types,
|
|
396
|
-
getSchemaDetails: () => getSchemaDetails,
|
|
397
|
-
getSupportedChainIds: () => getSupportedChainIds,
|
|
398
|
-
hashSeed: () => hashSeed,
|
|
399
|
-
isChainSupported: () => isChainSupported,
|
|
400
|
-
listAttestations: () => listAttestations,
|
|
401
|
-
normalizeSchema: () => normalizeSchema,
|
|
402
|
-
parseDnsTxtRecord: () => parseDnsTxtRecord,
|
|
403
|
-
prepareDelegatedAttestation: () => prepareDelegatedAttestation,
|
|
404
|
-
revokeAttestation: () => revokeAttestation,
|
|
405
|
-
schemaToString: () => schemaToString,
|
|
406
|
-
splitSignature: () => splitSignature,
|
|
407
|
-
submitAttestation: () => submitAttestation,
|
|
408
|
-
submitDelegatedAttestation: () => submitDelegatedAttestation,
|
|
409
|
-
validateAttestationData: () => validateAttestationData,
|
|
410
|
-
verifyAttestation: () => verifyAttestation,
|
|
411
|
-
verifyDidDocumentControllerDid: () => verifyDidDocumentControllerDid,
|
|
412
|
-
verifyDidJsonControllerDid: () => verifyDidJsonControllerDid,
|
|
413
|
-
verifyDidPkhOwnership: () => verifyDidPkhOwnership,
|
|
414
|
-
verifyDidWebOwnership: () => verifyDidWebOwnership,
|
|
415
|
-
verifyDnsTxtControllerDid: () => verifyDnsTxtControllerDid,
|
|
416
|
-
verifyEip712Signature: () => verifyEip712Signature,
|
|
417
|
-
verifyProof: () => verifyProof,
|
|
418
|
-
verifySchemaExists: () => verifySchemaExists,
|
|
419
|
-
verifySubjectOwnership: () => verifySubjectOwnership
|
|
420
|
-
});
|
|
421
|
-
function normalizeSchema(schema) {
|
|
422
|
-
if (Array.isArray(schema)) {
|
|
423
|
-
if (schema.length === 0) {
|
|
424
|
-
throw new OmaTrustError("INVALID_INPUT", "schema array cannot be empty");
|
|
425
|
-
}
|
|
426
|
-
return schema;
|
|
570
|
+
function validateDidPkh(did) {
|
|
571
|
+
const parts = did.split(":");
|
|
572
|
+
if (parts.length !== 5) {
|
|
573
|
+
return {
|
|
574
|
+
valid: false,
|
|
575
|
+
method: "pkh",
|
|
576
|
+
error: `did:pkh must have exactly 5 colon-separated parts, got ${parts.length}`
|
|
577
|
+
};
|
|
427
578
|
}
|
|
428
|
-
|
|
429
|
-
|
|
579
|
+
const [, , namespace, chainId, address] = parts;
|
|
580
|
+
if (!namespace) {
|
|
581
|
+
return { valid: false, method: "pkh", error: "Missing namespace" };
|
|
430
582
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
583
|
+
if (!CAIP2_NAMESPACE_REGEX.test(namespace)) {
|
|
584
|
+
return {
|
|
585
|
+
valid: false,
|
|
586
|
+
method: "pkh",
|
|
587
|
+
error: `Invalid CAIP-2 namespace "${namespace}" (must be 3-8 lowercase alphanumeric/hyphen chars)`
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
if (!chainId) {
|
|
591
|
+
return { valid: false, method: "pkh", error: "Missing chain ID (reference)" };
|
|
592
|
+
}
|
|
593
|
+
if (!address) {
|
|
594
|
+
return { valid: false, method: "pkh", error: "Missing address" };
|
|
595
|
+
}
|
|
596
|
+
if (namespace === "eip155") {
|
|
597
|
+
if (!/^\d+$/.test(chainId)) {
|
|
598
|
+
return {
|
|
599
|
+
valid: false,
|
|
600
|
+
method: "pkh",
|
|
601
|
+
error: `Invalid eip155 chain ID "${chainId}" (must be numeric)`
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
if (!ethers.isAddress(address)) {
|
|
605
|
+
return {
|
|
606
|
+
valid: false,
|
|
607
|
+
method: "pkh",
|
|
608
|
+
error: `Invalid EVM address "${address}" (must be 0x + 40 hex chars)`
|
|
609
|
+
};
|
|
435
610
|
}
|
|
436
|
-
const type = pieces[0];
|
|
437
|
-
const name = pieces.slice(1).join(" ");
|
|
438
|
-
return { type, name };
|
|
439
|
-
});
|
|
440
|
-
if (fields.length === 0) {
|
|
441
|
-
throw new OmaTrustError("INVALID_INPUT", "No schema fields found");
|
|
442
611
|
}
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
612
|
+
if (namespace === "solana") {
|
|
613
|
+
if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address)) {
|
|
614
|
+
return {
|
|
615
|
+
valid: false,
|
|
616
|
+
method: "pkh",
|
|
617
|
+
error: `Invalid Solana address "${address}" (must be 32-44 base58 characters)`
|
|
618
|
+
};
|
|
619
|
+
}
|
|
448
620
|
}
|
|
449
|
-
return
|
|
621
|
+
return { valid: true, method: "pkh" };
|
|
450
622
|
}
|
|
451
|
-
function
|
|
452
|
-
|
|
453
|
-
|
|
623
|
+
function validateDidJwk(did) {
|
|
624
|
+
const parts = did.split(":");
|
|
625
|
+
if (parts.length !== 3) {
|
|
626
|
+
return {
|
|
627
|
+
valid: false,
|
|
628
|
+
method: "jwk",
|
|
629
|
+
error: `did:jwk must have exactly 3 colon-separated parts, got ${parts.length}`
|
|
630
|
+
};
|
|
454
631
|
}
|
|
455
|
-
const
|
|
456
|
-
if (
|
|
457
|
-
|
|
458
|
-
throw new OmaTrustError("INVALID_INPUT", `Attestation data validation failed: ${summary}`, {
|
|
459
|
-
errors: validationErrors
|
|
460
|
-
});
|
|
632
|
+
const [, , encoded] = parts;
|
|
633
|
+
if (!encoded || encoded.length === 0) {
|
|
634
|
+
return { valid: false, method: "jwk", error: "Missing base64url-encoded JWK identifier" };
|
|
461
635
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
type: field.type,
|
|
469
|
-
value: data[field.name]
|
|
470
|
-
}))
|
|
471
|
-
);
|
|
472
|
-
return encoded;
|
|
473
|
-
}
|
|
474
|
-
function getActualType(value) {
|
|
475
|
-
if (value === null) {
|
|
476
|
-
return "null";
|
|
636
|
+
if (!BASE64URL_REGEX.test(encoded)) {
|
|
637
|
+
return {
|
|
638
|
+
valid: false,
|
|
639
|
+
method: "jwk",
|
|
640
|
+
error: "Identifier contains invalid base64url characters"
|
|
641
|
+
};
|
|
477
642
|
}
|
|
478
|
-
|
|
479
|
-
|
|
643
|
+
let decoded;
|
|
644
|
+
try {
|
|
645
|
+
const bytes = jose.base64url.decode(encoded);
|
|
646
|
+
decoded = new TextDecoder().decode(bytes);
|
|
647
|
+
} catch {
|
|
648
|
+
return { valid: false, method: "jwk", error: "Failed to base64url-decode identifier" };
|
|
480
649
|
}
|
|
481
|
-
|
|
482
|
-
|
|
650
|
+
let jwk;
|
|
651
|
+
try {
|
|
652
|
+
jwk = JSON.parse(decoded);
|
|
653
|
+
} catch {
|
|
654
|
+
return { valid: false, method: "jwk", error: "Decoded identifier is not valid JSON" };
|
|
483
655
|
}
|
|
484
|
-
if (typeof
|
|
485
|
-
return
|
|
656
|
+
if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) {
|
|
657
|
+
return { valid: false, method: "jwk", error: "Decoded JWK must be a JSON object" };
|
|
486
658
|
}
|
|
487
|
-
|
|
659
|
+
const kty = jwk.kty;
|
|
660
|
+
if (typeof kty !== "string" || !VALID_JWK_KTY.has(kty)) {
|
|
661
|
+
return {
|
|
662
|
+
valid: false,
|
|
663
|
+
method: "jwk",
|
|
664
|
+
error: `Invalid or missing kty field (must be one of: EC, OKP, RSA)`
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
if ("d" in jwk) {
|
|
668
|
+
return {
|
|
669
|
+
valid: false,
|
|
670
|
+
method: "jwk",
|
|
671
|
+
error: "DID must reference a public key \u2014 private key component (d) is not allowed"
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
const jwkValidation = validatePublicJwk(jwk);
|
|
675
|
+
if (!jwkValidation.valid) {
|
|
676
|
+
return { valid: false, method: "jwk", error: jwkValidation.error };
|
|
677
|
+
}
|
|
678
|
+
return { valid: true, method: "jwk" };
|
|
488
679
|
}
|
|
489
|
-
function
|
|
490
|
-
|
|
491
|
-
|
|
680
|
+
function normalizeDidJwk(input) {
|
|
681
|
+
assertString(input, "input", "INVALID_DID");
|
|
682
|
+
const trimmed = input.trim();
|
|
683
|
+
if (!trimmed.startsWith("did:jwk:")) {
|
|
684
|
+
throw new OmaTrustError("INVALID_DID", "Expected did:jwk DID", { input });
|
|
492
685
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
686
|
+
const result = validateDidJwk(trimmed);
|
|
687
|
+
if (!result.valid) {
|
|
688
|
+
throw new OmaTrustError("INVALID_DID", result.error ?? "Invalid did:jwk", { input });
|
|
689
|
+
}
|
|
690
|
+
return trimmed;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/identity/did-url.ts
|
|
694
|
+
var DID_URL_REGEX = /^did:[a-z0-9]+:.+$/i;
|
|
695
|
+
function parseDidUrl(input) {
|
|
696
|
+
assertString(input, "input", "INVALID_DID_URL");
|
|
697
|
+
const trimmed = input.trim();
|
|
698
|
+
const hashIndex = trimmed.indexOf("#");
|
|
699
|
+
let did;
|
|
700
|
+
let fragment;
|
|
701
|
+
if (hashIndex === -1) {
|
|
702
|
+
did = trimmed;
|
|
703
|
+
fragment = null;
|
|
704
|
+
} else {
|
|
705
|
+
did = trimmed.slice(0, hashIndex);
|
|
706
|
+
const rawFragment = trimmed.slice(hashIndex + 1);
|
|
707
|
+
if (rawFragment.length === 0) {
|
|
708
|
+
throw new OmaTrustError(
|
|
709
|
+
"INVALID_DID_URL",
|
|
710
|
+
"DID URL has empty fragment (trailing '#' with no identifier)",
|
|
711
|
+
{ input }
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
fragment = rawFragment;
|
|
715
|
+
}
|
|
716
|
+
if (!DID_URL_REGEX.test(did)) {
|
|
717
|
+
throw new OmaTrustError(
|
|
718
|
+
"INVALID_DID_URL",
|
|
719
|
+
"Invalid DID URL: base DID portion is malformed",
|
|
720
|
+
{ input, did }
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
didUrl: trimmed,
|
|
725
|
+
did,
|
|
726
|
+
fragment
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
function isDidUrl(input) {
|
|
730
|
+
if (typeof input !== "string") return false;
|
|
731
|
+
const trimmed = input.trim();
|
|
732
|
+
return trimmed.includes("#") && DID_URL_REGEX.test(trimmed.split("#")[0]);
|
|
733
|
+
}
|
|
734
|
+
function assertBareDid(input, paramName = "did") {
|
|
735
|
+
assertString(input, paramName, "INVALID_DID");
|
|
736
|
+
if (isDidUrl(input)) {
|
|
737
|
+
throw new OmaTrustError(
|
|
738
|
+
"INVALID_DID",
|
|
739
|
+
`Expected a bare DID but received a DID URL with a fragment. Use parseDidUrl() to handle DID URLs.`,
|
|
740
|
+
{ input }
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/shared/did-document.ts
|
|
746
|
+
async function fetchDidDocument(domain) {
|
|
747
|
+
const normalized = domain.toLowerCase().replace(/\.$/, "");
|
|
748
|
+
const url = `https://${normalized}/.well-known/did.json`;
|
|
749
|
+
let response;
|
|
750
|
+
try {
|
|
751
|
+
response = await fetch(url, { headers: { Accept: "application/json" } });
|
|
752
|
+
} catch (err) {
|
|
753
|
+
throw new OmaTrustError("NETWORK_ERROR", "Failed to fetch DID document", { domain, err });
|
|
754
|
+
}
|
|
755
|
+
if (!response.ok) {
|
|
756
|
+
throw new OmaTrustError("NETWORK_ERROR", `DID document fetch failed: ${response.status}`, {
|
|
757
|
+
domain,
|
|
758
|
+
status: response.status
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
return await response.json();
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/identity/resolve-key.ts
|
|
765
|
+
function findVerificationMethod(didDocument, didUrl, fragment) {
|
|
766
|
+
const methods = didDocument.verificationMethod;
|
|
767
|
+
if (!Array.isArray(methods)) {
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
for (const method of methods) {
|
|
771
|
+
if (!method || typeof method !== "object") continue;
|
|
772
|
+
const id2 = method.id;
|
|
773
|
+
if (typeof id2 !== "string") continue;
|
|
774
|
+
if (id2 === didUrl) return method;
|
|
775
|
+
if (fragment && (id2 === `#${fragment}` || id2.endsWith(`#${fragment}`))) {
|
|
776
|
+
return method;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
function extractMethodIdsFromDidDocument(didDocument) {
|
|
782
|
+
const methods = didDocument.verificationMethod;
|
|
783
|
+
if (!Array.isArray(methods)) return [];
|
|
784
|
+
return methods.filter((m) => m && typeof m.id === "string").map((m) => m.id);
|
|
785
|
+
}
|
|
786
|
+
async function resolveDidUrlToPublicKey(didUrl, options) {
|
|
787
|
+
assertString(didUrl, "didUrl", "INVALID_DID_URL");
|
|
788
|
+
const parsed = parseDidUrl(didUrl);
|
|
789
|
+
const method = extractDidMethod(parsed.did);
|
|
790
|
+
if (!method) {
|
|
791
|
+
throw new OmaTrustError("INVALID_DID_URL", "Cannot extract DID method from DID URL", {
|
|
792
|
+
didUrl,
|
|
793
|
+
did: parsed.did
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
switch (method) {
|
|
797
|
+
case "web":
|
|
798
|
+
return resolveDidUrlKey(parsed.didUrl, parsed.did, parsed.fragment, options);
|
|
799
|
+
default:
|
|
800
|
+
throw new OmaTrustError(
|
|
801
|
+
"UNSUPPORTED_DID_METHOD",
|
|
802
|
+
`DID URL key resolution is not supported for method "${method}"`,
|
|
803
|
+
{ didUrl, method }
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
async function resolveDidUrlToControllerDid(didUrl, options) {
|
|
808
|
+
const resolved = await resolveDidUrlToPublicKey(didUrl, options);
|
|
809
|
+
const controllerDid = jwkToDidJwk(resolved.publicKeyJwk);
|
|
810
|
+
return {
|
|
811
|
+
...resolved,
|
|
812
|
+
controllerDid
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
async function resolveDidUrlKey(didUrl, did, fragment, options) {
|
|
816
|
+
const domain = getDomainFromDidWeb(did);
|
|
817
|
+
if (!domain) {
|
|
818
|
+
throw new OmaTrustError("INVALID_DID_URL", "Cannot extract domain from did:web DID", {
|
|
819
|
+
didUrl,
|
|
820
|
+
did
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
const fetchFn = options?.fetchDidDocument ?? fetchDidDocument;
|
|
824
|
+
const didDocument = await fetchFn(domain);
|
|
825
|
+
const method = findVerificationMethod(didDocument, didUrl, fragment);
|
|
826
|
+
if (!method) {
|
|
827
|
+
throw new OmaTrustError(
|
|
828
|
+
"KEY_NOT_FOUND",
|
|
829
|
+
`No verification method found matching "${fragment ? `#${fragment}` : didUrl}"`,
|
|
830
|
+
{ didUrl, fragment, availableMethods: extractMethodIdsFromDidDocument(didDocument) }
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
const publicKeyJwk = method.publicKeyJwk;
|
|
834
|
+
if (!publicKeyJwk || typeof publicKeyJwk !== "object") {
|
|
835
|
+
throw new OmaTrustError(
|
|
836
|
+
"KEY_NOT_FOUND",
|
|
837
|
+
`Verification method "${method.id}" does not contain publicKeyJwk`,
|
|
838
|
+
{ didUrl, verificationMethodId: method.id }
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
const validation = validatePublicJwk(publicKeyJwk);
|
|
842
|
+
if (!validation.valid) {
|
|
843
|
+
throw new OmaTrustError(
|
|
844
|
+
"INVALID_JWK",
|
|
845
|
+
`publicKeyJwk in verification method "${method.id}" is invalid: ${validation.error}`,
|
|
846
|
+
{ didUrl, verificationMethodId: method.id }
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
return {
|
|
850
|
+
didUrl,
|
|
851
|
+
did,
|
|
852
|
+
fragment,
|
|
853
|
+
publicKeyJwk,
|
|
854
|
+
verificationMethodId: method.id
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/identity/controller-id.ts
|
|
859
|
+
function isSameControllerId(a, b) {
|
|
860
|
+
let normalizedA = null;
|
|
861
|
+
let normalizedB = null;
|
|
862
|
+
try {
|
|
863
|
+
normalizedA = normalizeDid(a);
|
|
864
|
+
} catch {
|
|
865
|
+
}
|
|
866
|
+
try {
|
|
867
|
+
normalizedB = normalizeDid(b);
|
|
868
|
+
} catch {
|
|
869
|
+
}
|
|
870
|
+
if (normalizedA && normalizedB && normalizedA === normalizedB) {
|
|
871
|
+
return true;
|
|
872
|
+
}
|
|
873
|
+
const evmA = extractControllerEvmAddress(a);
|
|
874
|
+
const evmB = extractControllerEvmAddress(b);
|
|
875
|
+
if (evmA && evmB && evmA.toLowerCase() === evmB.toLowerCase()) {
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
878
|
+
const methodA = extractDidMethod(a);
|
|
879
|
+
const methodB = extractDidMethod(b);
|
|
880
|
+
if (methodA === "jwk" && methodB === "jwk") {
|
|
881
|
+
try {
|
|
882
|
+
const jwkA = didJwkToJwk(a);
|
|
883
|
+
const jwkB = didJwkToJwk(b);
|
|
884
|
+
return publicJwkEquals(jwkA, jwkB);
|
|
885
|
+
} catch {
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
function extractControllerEvmAddress(controllerDid) {
|
|
892
|
+
try {
|
|
893
|
+
const method = extractDidMethod(controllerDid);
|
|
894
|
+
if (method === "pkh" && controllerDid.includes("eip155")) {
|
|
895
|
+
return extractAddressFromDid(controllerDid);
|
|
896
|
+
}
|
|
897
|
+
} catch {
|
|
898
|
+
}
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// src/identity/types.ts
|
|
903
|
+
function extractAuthorizationMetadata(result) {
|
|
904
|
+
const payload = result.payload;
|
|
905
|
+
const resourceUrl = typeof payload.resourceUrl === "string" ? payload.resourceUrl : null;
|
|
906
|
+
const issuedAt = typeof payload.issuedAt === "string" ? payload.issuedAt : typeof payload.issuedAt === "number" ? payload.issuedAt : null;
|
|
907
|
+
let subjectDid = null;
|
|
908
|
+
if (resourceUrl) {
|
|
909
|
+
try {
|
|
910
|
+
const url = new URL(resourceUrl);
|
|
911
|
+
subjectDid = `did:web:${url.hostname}`;
|
|
912
|
+
} catch {
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
if ("publicKeyDid" in result) {
|
|
916
|
+
const jwsResult = result;
|
|
917
|
+
return {
|
|
918
|
+
controllerDid: jwsResult.publicKeyDid,
|
|
919
|
+
subjectDid,
|
|
920
|
+
resourceUrl,
|
|
921
|
+
issuedAt,
|
|
922
|
+
kid: jwsResult.kid,
|
|
923
|
+
publicKeyJwk: jwsResult.publicKeyJwk,
|
|
924
|
+
signer: null
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
const eip712Result = result;
|
|
928
|
+
const signerAddress = eip712Result.signer.toLowerCase();
|
|
929
|
+
return {
|
|
930
|
+
controllerDid: `did:pkh:eip155:1:${signerAddress}`,
|
|
931
|
+
subjectDid,
|
|
932
|
+
resourceUrl,
|
|
933
|
+
issuedAt,
|
|
934
|
+
kid: null,
|
|
935
|
+
publicKeyJwk: null,
|
|
936
|
+
signer: eip712Result.signer
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
var VALID_ALGORITHMS = /* @__PURE__ */ new Set(["keccak256", "sha256"]);
|
|
940
|
+
var MAX_JSON_DEPTH = 32;
|
|
941
|
+
function assertDepthLimit(depth, context) {
|
|
942
|
+
if (depth > MAX_JSON_DEPTH) {
|
|
943
|
+
const msg = context ? `JSON nesting depth exceeds maximum of ${MAX_JSON_DEPTH} at ${context}` : `JSON nesting depth exceeds maximum of ${MAX_JSON_DEPTH}`;
|
|
944
|
+
throw new OmaTrustError("INVALID_INPUT", msg);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
function parseJsonStrict(input) {
|
|
948
|
+
if (typeof input !== "string") {
|
|
949
|
+
throw new OmaTrustError("INVALID_INPUT", "Input must be a string");
|
|
950
|
+
}
|
|
951
|
+
const duplicateKeys = detectDuplicateKeys(input);
|
|
952
|
+
if (duplicateKeys.length > 0) {
|
|
953
|
+
throw new OmaTrustError(
|
|
954
|
+
"INVALID_INPUT",
|
|
955
|
+
`Duplicate JSON key${duplicateKeys.length > 1 ? "s" : ""} detected: ${duplicateKeys.map((k) => `"${k}"`).join(", ")}`,
|
|
956
|
+
{ duplicateKeys }
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
try {
|
|
960
|
+
return JSON.parse(input);
|
|
961
|
+
} catch {
|
|
962
|
+
throw new OmaTrustError("INVALID_INPUT", "Input is not valid JSON");
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
function detectDuplicateKeys(input) {
|
|
966
|
+
const duplicates = [];
|
|
967
|
+
const objectKeyStack = [];
|
|
968
|
+
let inString = false;
|
|
969
|
+
let escaped = false;
|
|
970
|
+
let currentKey = "";
|
|
971
|
+
let collectingKey = false;
|
|
972
|
+
let afterColon = false;
|
|
973
|
+
let i = 0;
|
|
974
|
+
while (i < input.length) {
|
|
975
|
+
const ch = input[i];
|
|
976
|
+
if (escaped) {
|
|
977
|
+
if (collectingKey) currentKey += ch;
|
|
978
|
+
escaped = false;
|
|
979
|
+
i++;
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
if (ch === "\\") {
|
|
983
|
+
escaped = true;
|
|
984
|
+
if (collectingKey) currentKey += ch;
|
|
985
|
+
i++;
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
if (ch === '"') {
|
|
989
|
+
if (!inString) {
|
|
990
|
+
inString = true;
|
|
991
|
+
if (objectKeyStack.length > 0 && !afterColon) {
|
|
992
|
+
collectingKey = true;
|
|
993
|
+
currentKey = "";
|
|
994
|
+
}
|
|
995
|
+
} else {
|
|
996
|
+
inString = false;
|
|
997
|
+
if (collectingKey) {
|
|
998
|
+
collectingKey = false;
|
|
999
|
+
const keySet = objectKeyStack[objectKeyStack.length - 1];
|
|
1000
|
+
if (keySet.has(currentKey)) {
|
|
1001
|
+
if (!duplicates.includes(currentKey)) {
|
|
1002
|
+
duplicates.push(currentKey);
|
|
1003
|
+
}
|
|
1004
|
+
} else {
|
|
1005
|
+
keySet.add(currentKey);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
i++;
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
if (inString) {
|
|
1013
|
+
if (collectingKey) currentKey += ch;
|
|
1014
|
+
i++;
|
|
1015
|
+
continue;
|
|
1016
|
+
}
|
|
1017
|
+
if (ch === "{") {
|
|
1018
|
+
objectKeyStack.push(/* @__PURE__ */ new Set());
|
|
1019
|
+
assertDepthLimit(objectKeyStack.length);
|
|
1020
|
+
afterColon = false;
|
|
1021
|
+
} else if (ch === "}") {
|
|
1022
|
+
objectKeyStack.pop();
|
|
1023
|
+
afterColon = objectKeyStack.length > 0;
|
|
1024
|
+
} else if (ch === "[") {
|
|
1025
|
+
objectKeyStack.push(/* @__PURE__ */ new Set(["__array__"]));
|
|
1026
|
+
assertDepthLimit(objectKeyStack.length);
|
|
1027
|
+
afterColon = false;
|
|
1028
|
+
} else if (ch === "]") {
|
|
1029
|
+
objectKeyStack.pop();
|
|
1030
|
+
afterColon = objectKeyStack.length > 0;
|
|
1031
|
+
} else if (ch === ":") {
|
|
1032
|
+
afterColon = true;
|
|
1033
|
+
} else if (ch === ",") {
|
|
1034
|
+
afterColon = false;
|
|
1035
|
+
}
|
|
1036
|
+
i++;
|
|
1037
|
+
}
|
|
1038
|
+
return duplicates;
|
|
1039
|
+
}
|
|
1040
|
+
function assertJsonSafe(value, path = "$", depth = 0) {
|
|
1041
|
+
assertDepthLimit(depth, path);
|
|
1042
|
+
if (value === void 0) {
|
|
1043
|
+
throw new OmaTrustError("INVALID_INPUT", `Non-JSON-safe value at ${path}: undefined`);
|
|
1044
|
+
}
|
|
1045
|
+
if (value === null) return;
|
|
1046
|
+
switch (typeof value) {
|
|
1047
|
+
case "string":
|
|
1048
|
+
case "boolean":
|
|
1049
|
+
return;
|
|
1050
|
+
case "number":
|
|
1051
|
+
if (!Number.isFinite(value)) {
|
|
1052
|
+
throw new OmaTrustError("INVALID_INPUT", `Non-JSON-safe value at ${path}: ${value} (must be finite number)`);
|
|
1053
|
+
}
|
|
1054
|
+
return;
|
|
1055
|
+
case "bigint":
|
|
1056
|
+
throw new OmaTrustError("INVALID_INPUT", `Non-JSON-safe value at ${path}: BigInt (use number or string)`);
|
|
1057
|
+
case "function":
|
|
1058
|
+
throw new OmaTrustError("INVALID_INPUT", `Non-JSON-safe value at ${path}: function`);
|
|
1059
|
+
case "symbol":
|
|
1060
|
+
throw new OmaTrustError("INVALID_INPUT", `Non-JSON-safe value at ${path}: Symbol`);
|
|
1061
|
+
case "object":
|
|
1062
|
+
if (value instanceof Date) {
|
|
1063
|
+
throw new OmaTrustError("INVALID_INPUT", `Non-JSON-safe value at ${path}: Date (use ISO string or timestamp)`);
|
|
1064
|
+
}
|
|
1065
|
+
if (value instanceof RegExp) {
|
|
1066
|
+
throw new OmaTrustError("INVALID_INPUT", `Non-JSON-safe value at ${path}: RegExp`);
|
|
1067
|
+
}
|
|
1068
|
+
if (Array.isArray(value)) {
|
|
1069
|
+
for (let i = 0; i < value.length; i++) {
|
|
1070
|
+
assertJsonSafe(value[i], `${path}[${i}]`, depth + 1);
|
|
1071
|
+
}
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
for (const [key, val] of Object.entries(value)) {
|
|
1075
|
+
assertJsonSafe(val, `${path}.${key}`, depth + 1);
|
|
1076
|
+
}
|
|
1077
|
+
return;
|
|
1078
|
+
default:
|
|
1079
|
+
throw new OmaTrustError("INVALID_INPUT", `Non-JSON-safe value at ${path}: unknown type "${typeof value}"`);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
function canonicalizeJson(obj) {
|
|
1083
|
+
assertJsonSafe(obj);
|
|
1084
|
+
const jcs = canonicalize__default.default(obj);
|
|
1085
|
+
if (!jcs) {
|
|
1086
|
+
throw new OmaTrustError("INVALID_INPUT", "Object cannot be canonicalized", { obj });
|
|
1087
|
+
}
|
|
1088
|
+
return jcs;
|
|
1089
|
+
}
|
|
1090
|
+
function canonicalizeAndKeccak256(obj) {
|
|
1091
|
+
const jcsJson = canonicalizeJson(obj);
|
|
1092
|
+
return {
|
|
1093
|
+
jcsJson,
|
|
1094
|
+
hash: ethers.keccak256(ethers.toUtf8Bytes(jcsJson))
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
function canonicalizeForHash(obj) {
|
|
1098
|
+
return canonicalizeAndKeccak256(obj);
|
|
1099
|
+
}
|
|
1100
|
+
function hashCanonicalizedJson(obj, algorithm) {
|
|
1101
|
+
if (!VALID_ALGORITHMS.has(algorithm)) {
|
|
1102
|
+
throw new OmaTrustError("INVALID_INPUT", `Unsupported hash algorithm: "${algorithm}". Must be "keccak256" or "sha256".`, { algorithm });
|
|
1103
|
+
}
|
|
1104
|
+
const jcs = canonicalizeJson(obj);
|
|
1105
|
+
const bytes = ethers.toUtf8Bytes(jcs);
|
|
1106
|
+
return algorithm === "keccak256" ? ethers.keccak256(bytes) : ethers.sha256(bytes);
|
|
1107
|
+
}
|
|
1108
|
+
var DID_ARTIFACT_PREFIX = "did:artifact:";
|
|
1109
|
+
var CID_VERSION = 1;
|
|
1110
|
+
var RAW_CODEC = raw__namespace.code;
|
|
1111
|
+
var SHA2_256_CODE = 18;
|
|
1112
|
+
var SHA2_256_DIGEST_LENGTH = 32;
|
|
1113
|
+
async function artifactDidFromBytes(bytes) {
|
|
1114
|
+
if (!(bytes instanceof Uint8Array) || bytes.length === 0) {
|
|
1115
|
+
throw new OmaTrustError(
|
|
1116
|
+
"INVALID_INPUT",
|
|
1117
|
+
"bytes must be a non-empty Uint8Array"
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
const digest = await sha2.sha256.digest(bytes);
|
|
1121
|
+
const cid$1 = cid.CID.createV1(RAW_CODEC, digest);
|
|
1122
|
+
return `${DID_ARTIFACT_PREFIX}${cid$1.toString(base32.base32)}`;
|
|
1123
|
+
}
|
|
1124
|
+
async function artifactDidFromJson(input) {
|
|
1125
|
+
const value = typeof input === "string" ? parseJsonStrict(input) : input;
|
|
1126
|
+
const jcs = canonicalizeJson(value);
|
|
1127
|
+
const bytes = new TextEncoder().encode(jcs);
|
|
1128
|
+
const digest = await sha2.sha256.digest(bytes);
|
|
1129
|
+
const cid$1 = cid.CID.createV1(RAW_CODEC, digest);
|
|
1130
|
+
return `${DID_ARTIFACT_PREFIX}${cid$1.toString(base32.base32)}`;
|
|
1131
|
+
}
|
|
1132
|
+
function parseArtifactDid(did) {
|
|
1133
|
+
if (typeof did !== "string" || !did.startsWith(DID_ARTIFACT_PREFIX)) {
|
|
1134
|
+
throw new OmaTrustError("INVALID_DID", "Expected a did:artifact DID", { did });
|
|
1135
|
+
}
|
|
1136
|
+
const identifier = did.slice(DID_ARTIFACT_PREFIX.length);
|
|
1137
|
+
if (!identifier || identifier.length === 0) {
|
|
1138
|
+
throw new OmaTrustError("INVALID_DID", "Missing method-specific identifier", { did });
|
|
1139
|
+
}
|
|
1140
|
+
if (identifier[0] !== "b") {
|
|
1141
|
+
throw new OmaTrustError(
|
|
1142
|
+
"INVALID_DID",
|
|
1143
|
+
`Invalid multibase prefix "${identifier[0]}", expected "b" (base32lower)`,
|
|
1144
|
+
{ did }
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
let cid$1;
|
|
1148
|
+
try {
|
|
1149
|
+
cid$1 = cid.CID.parse(identifier, base32.base32);
|
|
1150
|
+
} catch (err) {
|
|
1151
|
+
throw new OmaTrustError(
|
|
1152
|
+
"INVALID_DID",
|
|
1153
|
+
"Failed to decode base32 CID from did:artifact identifier",
|
|
1154
|
+
{ did, cause: err }
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
if (cid$1.version !== CID_VERSION) {
|
|
1158
|
+
throw new OmaTrustError(
|
|
1159
|
+
"INVALID_DID",
|
|
1160
|
+
`CID version must be 1, got ${cid$1.version}`,
|
|
1161
|
+
{ did }
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
if (cid$1.code !== RAW_CODEC) {
|
|
1165
|
+
throw new OmaTrustError(
|
|
1166
|
+
"INVALID_DID",
|
|
1167
|
+
`Multicodec must be raw (0x55), got 0x${cid$1.code.toString(16)}`,
|
|
1168
|
+
{ did }
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
if (cid$1.multihash.code !== SHA2_256_CODE) {
|
|
1172
|
+
throw new OmaTrustError(
|
|
1173
|
+
"INVALID_DID",
|
|
1174
|
+
`Multihash function must be sha2-256 (0x12), got 0x${cid$1.multihash.code.toString(16)}`,
|
|
1175
|
+
{ did }
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
if (cid$1.multihash.digest.length !== SHA2_256_DIGEST_LENGTH) {
|
|
1179
|
+
throw new OmaTrustError(
|
|
1180
|
+
"INVALID_DID",
|
|
1181
|
+
`SHA-256 digest must be 32 bytes, got ${cid$1.multihash.digest.length}`,
|
|
1182
|
+
{ did }
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
const digest = cid$1.multihash.digest;
|
|
1186
|
+
const digestHex = Array.from(digest).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1187
|
+
return { did, identifier, digest, digestHex };
|
|
1188
|
+
}
|
|
1189
|
+
async function verifyDidArtifact(did, content) {
|
|
1190
|
+
let parsed;
|
|
1191
|
+
try {
|
|
1192
|
+
parsed = parseArtifactDid(did);
|
|
1193
|
+
} catch (err) {
|
|
1194
|
+
return {
|
|
1195
|
+
valid: false,
|
|
1196
|
+
reason: err instanceof OmaTrustError ? err.message : "Invalid did:artifact DID"
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
const expectedHex = parsed.digestHex;
|
|
1200
|
+
if (typeof content === "string" || typeof content === "object" && content !== null && !(content instanceof Uint8Array)) {
|
|
1201
|
+
try {
|
|
1202
|
+
const value = typeof content === "string" ? parseJsonStrict(content) : content;
|
|
1203
|
+
const jcs = canonicalizeJson(value);
|
|
1204
|
+
const jcsBytes = new TextEncoder().encode(jcs);
|
|
1205
|
+
const digest2 = await sha2.sha256.digest(jcsBytes);
|
|
1206
|
+
const digestHex2 = Array.from(digest2.digest).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1207
|
+
if (digestHex2 === expectedHex) {
|
|
1208
|
+
return { valid: true, matchedAs: "json" };
|
|
1209
|
+
}
|
|
1210
|
+
} catch {
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
let rawBytes;
|
|
1214
|
+
if (content instanceof Uint8Array) {
|
|
1215
|
+
rawBytes = content;
|
|
1216
|
+
} else if (typeof content === "string") {
|
|
1217
|
+
rawBytes = new TextEncoder().encode(content);
|
|
1218
|
+
} else {
|
|
1219
|
+
return {
|
|
1220
|
+
valid: false,
|
|
1221
|
+
reason: "Content did not match as canonical JSON and is not raw bytes"
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
const digest = await sha2.sha256.digest(rawBytes);
|
|
1225
|
+
const digestHex = Array.from(digest.digest).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1226
|
+
if (digestHex === expectedHex) {
|
|
1227
|
+
return { valid: true, matchedAs: "binary" };
|
|
1228
|
+
}
|
|
1229
|
+
return {
|
|
1230
|
+
valid: false,
|
|
1231
|
+
reason: "Content does not match the did:artifact identifier (neither as canonical JSON nor as raw bytes)"
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
function didEthrToDidPkh(did) {
|
|
1235
|
+
if (typeof did !== "string" || !did.startsWith("did:ethr:")) {
|
|
1236
|
+
throw new OmaTrustError("INVALID_DID", "Expected a did:ethr DID", { did });
|
|
1237
|
+
}
|
|
1238
|
+
const remainder = did.slice("did:ethr:".length);
|
|
1239
|
+
if (!remainder) {
|
|
1240
|
+
throw new OmaTrustError("INVALID_DID", "Missing identifier in did:ethr DID", { did });
|
|
1241
|
+
}
|
|
1242
|
+
let chainId;
|
|
1243
|
+
let address;
|
|
1244
|
+
const parts = remainder.split(":");
|
|
1245
|
+
if (parts.length === 1) {
|
|
1246
|
+
chainId = "1";
|
|
1247
|
+
address = parts[0];
|
|
1248
|
+
} else if (parts.length === 2) {
|
|
1249
|
+
const rawChainId = parts[0];
|
|
1250
|
+
address = parts[1];
|
|
1251
|
+
if (rawChainId.startsWith("0x")) {
|
|
1252
|
+
chainId = String(parseInt(rawChainId, 16));
|
|
1253
|
+
} else if (/^\d+$/.test(rawChainId)) {
|
|
1254
|
+
chainId = rawChainId;
|
|
1255
|
+
} else {
|
|
1256
|
+
const networkMap = {
|
|
1257
|
+
mainnet: "1",
|
|
1258
|
+
goerli: "5",
|
|
1259
|
+
sepolia: "11155111",
|
|
1260
|
+
polygon: "137",
|
|
1261
|
+
arbitrum: "42161",
|
|
1262
|
+
optimism: "10",
|
|
1263
|
+
base: "8453"
|
|
1264
|
+
};
|
|
1265
|
+
const mapped = networkMap[rawChainId.toLowerCase()];
|
|
1266
|
+
if (!mapped) {
|
|
1267
|
+
throw new OmaTrustError(
|
|
1268
|
+
"INVALID_DID",
|
|
1269
|
+
`Unknown did:ethr network "${rawChainId}"`,
|
|
1270
|
+
{ did, network: rawChainId }
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
chainId = mapped;
|
|
1274
|
+
}
|
|
1275
|
+
} else {
|
|
1276
|
+
throw new OmaTrustError("INVALID_DID", "Invalid did:ethr format", { did });
|
|
1277
|
+
}
|
|
1278
|
+
if (!ethers.isAddress(address)) {
|
|
1279
|
+
throw new OmaTrustError("INVALID_DID", "Invalid Ethereum address in did:ethr DID", {
|
|
1280
|
+
did,
|
|
1281
|
+
address
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
const checksummed = ethers.getAddress(address);
|
|
1285
|
+
return `did:pkh:eip155:${chainId}:${checksummed.toLowerCase()}`;
|
|
1286
|
+
}
|
|
1287
|
+
var MULTICODEC_KEY_TYPES = {
|
|
1288
|
+
// Ed25519 public key: 0xed (varint encoded as [0xed, 0x01])
|
|
1289
|
+
237: { kty: "OKP", crv: "Ed25519" },
|
|
1290
|
+
// X25519 public key: 0xec (varint encoded as [0xec, 0x01])
|
|
1291
|
+
236: { kty: "OKP", crv: "X25519" },
|
|
1292
|
+
// secp256k1 public key (compressed): 0xe7 (varint encoded as [0xe7, 0x01])
|
|
1293
|
+
231: { kty: "EC", crv: "secp256k1", coordSize: 33 },
|
|
1294
|
+
// P-256 public key (compressed): 0x80 0x24 (varint 0x1200)
|
|
1295
|
+
4608: { kty: "EC", crv: "P-256", coordSize: 33 },
|
|
1296
|
+
// P-384 public key (compressed): 0x81 0x24 (varint 0x1201)
|
|
1297
|
+
4609: { kty: "EC", crv: "P-384", coordSize: 49 }
|
|
1298
|
+
};
|
|
1299
|
+
function readVarint(bytes, offset) {
|
|
1300
|
+
let value = 0;
|
|
1301
|
+
let shift = 0;
|
|
1302
|
+
let bytesRead = 0;
|
|
1303
|
+
while (offset + bytesRead < bytes.length) {
|
|
1304
|
+
const byte = bytes[offset + bytesRead];
|
|
1305
|
+
value |= (byte & 127) << shift;
|
|
1306
|
+
bytesRead++;
|
|
1307
|
+
if ((byte & 128) === 0) {
|
|
1308
|
+
return { value, bytesRead };
|
|
1309
|
+
}
|
|
1310
|
+
shift += 7;
|
|
1311
|
+
if (shift > 28) {
|
|
1312
|
+
throw new OmaTrustError("INVALID_DID", "Varint too long in did:key multicodec prefix");
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
throw new OmaTrustError("INVALID_DID", "Truncated varint in did:key multicodec prefix");
|
|
1316
|
+
}
|
|
1317
|
+
function decompressEcKey(compressedKey, crv) {
|
|
1318
|
+
throw new OmaTrustError(
|
|
1319
|
+
"UNSUPPORTED_KEY_TYPE",
|
|
1320
|
+
`Cannot convert compressed EC key (${crv}) from did:key to did:jwk. Decompressing EC points requires elliptic curve arithmetic. For EVM keys, use didEthrToDidPkh() instead.`,
|
|
1321
|
+
{ crv, keyLength: compressedKey.length }
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
function didKeyToDidJwk(did) {
|
|
1325
|
+
if (typeof did !== "string" || !did.startsWith("did:key:")) {
|
|
1326
|
+
throw new OmaTrustError("INVALID_DID", "Expected a did:key DID", { did });
|
|
1327
|
+
}
|
|
1328
|
+
const identifier = did.slice("did:key:".length);
|
|
1329
|
+
if (!identifier || !identifier.startsWith("z")) {
|
|
1330
|
+
throw new OmaTrustError(
|
|
1331
|
+
"INVALID_DID",
|
|
1332
|
+
"did:key identifier must start with 'z' (base58btc multibase prefix)",
|
|
1333
|
+
{ did }
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
let decoded;
|
|
1337
|
+
try {
|
|
1338
|
+
decoded = base58.base58btc.decode(identifier);
|
|
1339
|
+
} catch (err) {
|
|
1340
|
+
throw new OmaTrustError(
|
|
1341
|
+
"INVALID_DID",
|
|
1342
|
+
"Failed to decode base58btc from did:key identifier",
|
|
1343
|
+
{ did, cause: err }
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
if (decoded.length < 3) {
|
|
1347
|
+
throw new OmaTrustError("INVALID_DID", "did:key decoded bytes too short", { did });
|
|
1348
|
+
}
|
|
1349
|
+
const { value: codecValue, bytesRead } = readVarint(decoded, 0);
|
|
1350
|
+
const keyType = MULTICODEC_KEY_TYPES[codecValue];
|
|
1351
|
+
if (!keyType) {
|
|
1352
|
+
throw new OmaTrustError(
|
|
1353
|
+
"UNSUPPORTED_KEY_TYPE",
|
|
1354
|
+
`Unsupported multicodec key type 0x${codecValue.toString(16)} in did:key`,
|
|
1355
|
+
{ did, codec: `0x${codecValue.toString(16)}` }
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
1358
|
+
const keyBytes = decoded.slice(bytesRead);
|
|
1359
|
+
if (keyType.kty === "OKP") {
|
|
1360
|
+
if (keyBytes.length !== 32) {
|
|
1361
|
+
throw new OmaTrustError(
|
|
1362
|
+
"INVALID_DID",
|
|
1363
|
+
`Expected 32-byte ${keyType.crv} key, got ${keyBytes.length} bytes`,
|
|
1364
|
+
{ did, crv: keyType.crv }
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
const jwk = {
|
|
1368
|
+
kty: keyType.kty,
|
|
1369
|
+
crv: keyType.crv,
|
|
1370
|
+
x: jose.base64url.encode(keyBytes)
|
|
1371
|
+
};
|
|
1372
|
+
return jwkToDidJwk(jwk);
|
|
1373
|
+
}
|
|
1374
|
+
return decompressEcKey(keyBytes, keyType.crv).x.toString();
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// src/reputation/index.ts
|
|
1378
|
+
var reputation_exports = {};
|
|
1379
|
+
__export(reputation_exports, {
|
|
1380
|
+
EIP1967_ADMIN_SLOT: () => EIP1967_ADMIN_SLOT,
|
|
1381
|
+
OWNERSHIP_PATTERNS: () => OWNERSHIP_PATTERNS,
|
|
1382
|
+
buildDelegatedAttestationTypedData: () => buildDelegatedAttestationTypedData,
|
|
1383
|
+
buildDelegatedTypedDataFromEncoded: () => buildDelegatedTypedDataFromEncoded,
|
|
1384
|
+
buildDnsTxtRecord: () => buildDnsTxtRecord,
|
|
1385
|
+
buildEip712Domain: () => buildEip712Domain,
|
|
1386
|
+
calculateAverageUserReviewRating: () => calculateAverageUserReviewRating,
|
|
1387
|
+
calculateTransferAmount: () => calculateTransferAmount,
|
|
1388
|
+
calculateTransferAmountFromAddresses: () => calculateTransferAmountFromAddresses,
|
|
1389
|
+
callControllerWitness: () => callControllerWitness,
|
|
1390
|
+
constructSeed: () => constructSeed,
|
|
1391
|
+
createEvidencePointerProof: () => createEvidencePointerProof,
|
|
1392
|
+
createPopEip712Proof: () => createPopEip712Proof,
|
|
1393
|
+
createPopJwsProof: () => createPopJwsProof,
|
|
1394
|
+
createTxEncodedValueProof: () => createTxEncodedValueProof,
|
|
1395
|
+
createTxInteractionProof: () => createTxInteractionProof,
|
|
1396
|
+
createX402OfferProof: () => createX402OfferProof,
|
|
1397
|
+
createX402ReceiptProof: () => createX402ReceiptProof,
|
|
1398
|
+
decodeAttestationData: () => decodeAttestationData,
|
|
1399
|
+
deduplicateReviews: () => deduplicateReviews,
|
|
1400
|
+
discoverContractOwner: () => discoverContractOwner,
|
|
1401
|
+
discoverControllingWalletDid: () => discoverControllingWalletDid,
|
|
1402
|
+
encodeAttestationData: () => encodeAttestationData,
|
|
1403
|
+
extractEvmAddressesFromDidDocument: () => extractEvmAddressesFromDidDocument,
|
|
1404
|
+
extractExpirationTime: () => extractExpirationTime,
|
|
1405
|
+
extractJwksFromDidDocument: () => extractJwksFromDidDocument,
|
|
1406
|
+
fetchDidDocument: () => fetchDidDocument,
|
|
1407
|
+
formatSchemaUid: () => formatSchemaUid,
|
|
1408
|
+
formatTransferAmount: () => formatTransferAmount,
|
|
1409
|
+
getAttestation: () => getAttestation,
|
|
1410
|
+
getAttestationsByAttester: () => getAttestationsByAttester,
|
|
1411
|
+
getAttestationsForDid: () => getAttestationsForDid,
|
|
1412
|
+
getChainConstants: () => getChainConstants,
|
|
1413
|
+
getControllerAuthorization: () => getControllerAuthorization,
|
|
1414
|
+
getExplorerAddressUrl: () => getExplorerAddressUrl,
|
|
1415
|
+
getExplorerTxUrl: () => getExplorerTxUrl,
|
|
1416
|
+
getLatestAttestations: () => getLatestAttestations,
|
|
1417
|
+
getMajorVersion: () => getMajorVersion,
|
|
1418
|
+
getOmaTrustProofEip712Types: () => getOmaTrustProofEip712Types,
|
|
1419
|
+
getSchemaDetails: () => getSchemaDetails,
|
|
1420
|
+
getSupportedChainIds: () => getSupportedChainIds,
|
|
1421
|
+
hashSeed: () => hashSeed,
|
|
1422
|
+
isChainSupported: () => isChainSupported,
|
|
1423
|
+
listAttestations: () => listAttestations,
|
|
1424
|
+
normalizeSchema: () => normalizeSchema,
|
|
1425
|
+
parseDnsTxtRecord: () => parseDnsTxtRecord,
|
|
1426
|
+
prepareDelegatedAttestation: () => prepareDelegatedAttestation,
|
|
1427
|
+
readOwnerFromContract: () => readOwnerFromContract,
|
|
1428
|
+
requestControllerWitness: () => requestControllerWitness,
|
|
1429
|
+
revokeAttestation: () => revokeAttestation,
|
|
1430
|
+
schemaToString: () => schemaToString,
|
|
1431
|
+
splitSignature: () => splitSignature,
|
|
1432
|
+
submitAttestation: () => submitAttestation,
|
|
1433
|
+
submitDelegatedAttestation: () => submitDelegatedAttestation,
|
|
1434
|
+
validateAttestationData: () => validateAttestationData,
|
|
1435
|
+
verifyAttestation: () => verifyAttestation,
|
|
1436
|
+
verifyDidDocumentControllerDid: () => verifyDidDocumentControllerDid,
|
|
1437
|
+
verifyDidJsonControllerDid: () => verifyDidJsonControllerDid,
|
|
1438
|
+
verifyDidPkhOwnership: () => verifyDidPkhOwnership,
|
|
1439
|
+
verifyDidWebOwnership: () => verifyDidWebOwnership,
|
|
1440
|
+
verifyDnsTxtControllerDid: () => verifyDnsTxtControllerDid2,
|
|
1441
|
+
verifyEip712Signature: () => verifyEip712Signature,
|
|
1442
|
+
verifyKeyBindingProofs: () => verifyKeyBindingProofs,
|
|
1443
|
+
verifyLinkedIdentifierProofs: () => verifyLinkedIdentifierProofs,
|
|
1444
|
+
verifyProof: () => verifyProof,
|
|
1445
|
+
verifySchemaExists: () => verifySchemaExists,
|
|
1446
|
+
verifySubjectOwnership: () => verifySubjectOwnership,
|
|
1447
|
+
verifyTransferProof: () => verifyTransferProof,
|
|
1448
|
+
verifyX402Artifact: () => verifyX402Artifact,
|
|
1449
|
+
verifyX402Eip712Artifact: () => verifyX402Eip712Artifact,
|
|
1450
|
+
verifyX402Eip712Offer: () => verifyX402Eip712Offer,
|
|
1451
|
+
verifyX402Eip712Receipt: () => verifyX402Eip712Receipt,
|
|
1452
|
+
verifyX402JwsArtifact: () => verifyX402JwsArtifact,
|
|
1453
|
+
verifyX402JwsOffer: () => verifyX402JwsOffer,
|
|
1454
|
+
verifyX402JwsReceipt: () => verifyX402JwsReceipt
|
|
1455
|
+
});
|
|
1456
|
+
function normalizeSchema(schema) {
|
|
1457
|
+
if (Array.isArray(schema)) {
|
|
1458
|
+
if (schema.length === 0) {
|
|
1459
|
+
throw new OmaTrustError("INVALID_INPUT", "schema array cannot be empty");
|
|
1460
|
+
}
|
|
1461
|
+
return schema;
|
|
1462
|
+
}
|
|
1463
|
+
if (typeof schema !== "string" || schema.trim().length === 0) {
|
|
1464
|
+
throw new OmaTrustError("INVALID_INPUT", "schema must be a non-empty string or SchemaField[]");
|
|
1465
|
+
}
|
|
1466
|
+
const fields = schema.split(",").map((part) => part.trim()).filter((part) => part.length > 0).map((part) => {
|
|
1467
|
+
const pieces = part.split(/\s+/);
|
|
1468
|
+
if (pieces.length < 2) {
|
|
1469
|
+
throw new OmaTrustError("INVALID_INPUT", "Invalid schema field", { part });
|
|
1470
|
+
}
|
|
1471
|
+
const type = pieces[0];
|
|
1472
|
+
const name = pieces.slice(1).join(" ");
|
|
1473
|
+
return { type, name };
|
|
1474
|
+
});
|
|
1475
|
+
if (fields.length === 0) {
|
|
1476
|
+
throw new OmaTrustError("INVALID_INPUT", "No schema fields found");
|
|
1477
|
+
}
|
|
1478
|
+
return fields;
|
|
1479
|
+
}
|
|
1480
|
+
function schemaToString(schema) {
|
|
1481
|
+
if (typeof schema === "string") {
|
|
1482
|
+
return schema;
|
|
1483
|
+
}
|
|
1484
|
+
return schema.map((field) => `${field.type} ${field.name}`).join(", ");
|
|
1485
|
+
}
|
|
1486
|
+
function encodeAttestationData(schema, data) {
|
|
1487
|
+
if (!data || typeof data !== "object") {
|
|
1488
|
+
throw new OmaTrustError("INVALID_INPUT", "data must be an object");
|
|
1489
|
+
}
|
|
1490
|
+
const validationErrors = validateAttestationData(schema, data);
|
|
1491
|
+
if (validationErrors.length > 0) {
|
|
1492
|
+
const summary = validationErrors.map((error) => `Field "${error.schemaFieldName}" (${error.expectedType}) got ${error.providedType}`).join("; ");
|
|
1493
|
+
throw new OmaTrustError("INVALID_INPUT", `Attestation data validation failed: ${summary}`, {
|
|
1494
|
+
errors: validationErrors
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
const fields = normalizeSchema(schema);
|
|
1498
|
+
const schemaString = schemaToString(fields);
|
|
1499
|
+
const encoder = new easSdk.SchemaEncoder(schemaString);
|
|
1500
|
+
const encoded = encoder.encodeData(
|
|
1501
|
+
fields.map((field) => ({
|
|
1502
|
+
name: field.name,
|
|
1503
|
+
type: field.type,
|
|
1504
|
+
value: data[field.name]
|
|
1505
|
+
}))
|
|
1506
|
+
);
|
|
1507
|
+
return encoded;
|
|
1508
|
+
}
|
|
1509
|
+
function getActualType(value) {
|
|
1510
|
+
if (value === null) {
|
|
1511
|
+
return "null";
|
|
1512
|
+
}
|
|
1513
|
+
if (value === void 0) {
|
|
1514
|
+
return "undefined";
|
|
1515
|
+
}
|
|
1516
|
+
if (Array.isArray(value)) {
|
|
1517
|
+
return "array";
|
|
1518
|
+
}
|
|
1519
|
+
if (typeof value === "number") {
|
|
1520
|
+
return Number.isFinite(value) ? "number" : "non-finite-number";
|
|
1521
|
+
}
|
|
1522
|
+
return typeof value;
|
|
1523
|
+
}
|
|
1524
|
+
function isNumericValue(value, allowNegative) {
|
|
1525
|
+
if (typeof value === "bigint") {
|
|
1526
|
+
return allowNegative || value >= 0n;
|
|
1527
|
+
}
|
|
1528
|
+
if (typeof value === "number") {
|
|
1529
|
+
if (!Number.isFinite(value) || !Number.isInteger(value)) {
|
|
1530
|
+
return false;
|
|
1531
|
+
}
|
|
1532
|
+
return allowNegative || value >= 0;
|
|
498
1533
|
}
|
|
499
1534
|
if (typeof value === "string") {
|
|
500
1535
|
if (value.length === 0) {
|
|
@@ -875,19 +1910,32 @@ async function submitDelegatedAttestation(params) {
|
|
|
875
1910
|
payload = {};
|
|
876
1911
|
}
|
|
877
1912
|
if (!response.ok) {
|
|
878
|
-
|
|
879
|
-
|
|
1913
|
+
const relayError = {
|
|
1914
|
+
httpStatus: response.status,
|
|
1915
|
+
code: payload.code,
|
|
1916
|
+
error: payload.error ?? payload.message,
|
|
880
1917
|
payload
|
|
881
|
-
}
|
|
1918
|
+
};
|
|
1919
|
+
throw new OmaTrustError("RELAY_ERROR", `Relay submission failed (HTTP ${response.status})`, relayError);
|
|
882
1920
|
}
|
|
883
1921
|
const uid = payload.uid ?? ZERO_UID;
|
|
884
1922
|
const txHash = payload.txHash;
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1923
|
+
const hasConfirmationSignals = payload.status === "confirmed" || payload.success === true || typeof payload.blockNumber === "number";
|
|
1924
|
+
const status = hasConfirmationSignals ? "confirmed" : "submitted";
|
|
1925
|
+
const relay = {};
|
|
1926
|
+
if (typeof payload.blockNumber === "number") relay.blockNumber = payload.blockNumber;
|
|
1927
|
+
if (typeof payload.chain === "string") relay.chain = payload.chain;
|
|
1928
|
+
if (typeof payload.success === "boolean") relay.success = payload.success;
|
|
1929
|
+
for (const key of Object.keys(payload)) {
|
|
1930
|
+
if (!["uid", "txHash", "status", "blockNumber", "chain", "success"].includes(key)) {
|
|
1931
|
+
relay[key] = payload[key];
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
const result = { uid, txHash, status };
|
|
1935
|
+
if (Object.keys(relay).length > 0) {
|
|
1936
|
+
result.relay = relay;
|
|
1937
|
+
}
|
|
1938
|
+
return result;
|
|
891
1939
|
}
|
|
892
1940
|
var EAS_EVENT_ABI = [
|
|
893
1941
|
"event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)"
|
|
@@ -989,7 +2037,7 @@ async function getAttestationsForDid(params) {
|
|
|
989
2037
|
provider,
|
|
990
2038
|
fromBlock,
|
|
991
2039
|
toBlock,
|
|
992
|
-
{ recipient: didToAddress(params.
|
|
2040
|
+
{ recipient: didToAddress(params.subjectDid), schemas: params.schemas }
|
|
993
2041
|
);
|
|
994
2042
|
}
|
|
995
2043
|
async function getAttestationsByAttester(params) {
|
|
@@ -1230,25 +2278,7 @@ function getExplorerTxUrl(chainId, txHash) {
|
|
|
1230
2278
|
function getExplorerAddressUrl(chainId, address) {
|
|
1231
2279
|
return `${getConfig(chainId).explorer}/address/${address}`;
|
|
1232
2280
|
}
|
|
1233
|
-
|
|
1234
|
-
const normalized = domain.toLowerCase().replace(/\.$/, "");
|
|
1235
|
-
const url = `https://${normalized}/.well-known/did.json`;
|
|
1236
|
-
let response;
|
|
1237
|
-
try {
|
|
1238
|
-
response = await fetch(url, { headers: { Accept: "application/json" } });
|
|
1239
|
-
} catch (err) {
|
|
1240
|
-
throw new OmaTrustError("NETWORK_ERROR", "Failed to fetch DID document", { domain, err });
|
|
1241
|
-
}
|
|
1242
|
-
if (!response.ok) {
|
|
1243
|
-
throw new OmaTrustError("NETWORK_ERROR", "DID document fetch failed", {
|
|
1244
|
-
domain,
|
|
1245
|
-
status: response.status
|
|
1246
|
-
});
|
|
1247
|
-
}
|
|
1248
|
-
const body = await response.json();
|
|
1249
|
-
return body;
|
|
1250
|
-
}
|
|
1251
|
-
function extractAddressesFromDidDocument(didDocument) {
|
|
2281
|
+
function extractEvmAddressesFromDidDocument(didDocument) {
|
|
1252
2282
|
const methods = didDocument.verificationMethod;
|
|
1253
2283
|
if (!Array.isArray(methods)) {
|
|
1254
2284
|
return [];
|
|
@@ -1272,89 +2302,453 @@ function extractAddressesFromDidDocument(didDocument) {
|
|
|
1272
2302
|
}
|
|
1273
2303
|
return [...addresses];
|
|
1274
2304
|
}
|
|
2305
|
+
function extractJwksFromDidDocument(didDocument) {
|
|
2306
|
+
const methods = didDocument.verificationMethod;
|
|
2307
|
+
if (!Array.isArray(methods)) {
|
|
2308
|
+
return [];
|
|
2309
|
+
}
|
|
2310
|
+
const jwks = [];
|
|
2311
|
+
for (const method of methods) {
|
|
2312
|
+
const publicKeyJwk = method.publicKeyJwk;
|
|
2313
|
+
if (publicKeyJwk && typeof publicKeyJwk === "object" && !Array.isArray(publicKeyJwk)) {
|
|
2314
|
+
jwks.push(publicKeyJwk);
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
return jwks;
|
|
2318
|
+
}
|
|
1275
2319
|
function verifyDidDocumentControllerDid(didDocument, expectedControllerDid) {
|
|
2320
|
+
const method = extractDidMethod(expectedControllerDid);
|
|
2321
|
+
if (method === "jwk") {
|
|
2322
|
+
try {
|
|
2323
|
+
const expectedJwk = didJwkToJwk(expectedControllerDid);
|
|
2324
|
+
const documentJwks = extractJwksFromDidDocument(didDocument);
|
|
2325
|
+
for (const docJwk of documentJwks) {
|
|
2326
|
+
try {
|
|
2327
|
+
if (publicJwkEquals(docJwk, expectedJwk)) {
|
|
2328
|
+
return { valid: true };
|
|
2329
|
+
}
|
|
2330
|
+
} catch {
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
return {
|
|
2334
|
+
valid: false,
|
|
2335
|
+
reason: "No matching publicKeyJwk found in DID document verification methods"
|
|
2336
|
+
};
|
|
2337
|
+
} catch {
|
|
2338
|
+
return {
|
|
2339
|
+
valid: false,
|
|
2340
|
+
reason: "Failed to decode did:jwk controller DID"
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
1276
2344
|
let expectedAddress;
|
|
1277
2345
|
try {
|
|
1278
2346
|
expectedAddress = ethers.getAddress(extractAddressFromDid(expectedControllerDid));
|
|
1279
2347
|
} catch {
|
|
1280
|
-
return { valid: false, reason: "
|
|
2348
|
+
return { valid: false, reason: "Controller DID does not resolve to an EVM address and is not did:jwk" };
|
|
2349
|
+
}
|
|
2350
|
+
const addresses = extractEvmAddressesFromDidDocument(didDocument);
|
|
2351
|
+
if (addresses.some((address) => address.toLowerCase() === expectedAddress.toLowerCase())) {
|
|
2352
|
+
return { valid: true };
|
|
2353
|
+
}
|
|
2354
|
+
return {
|
|
2355
|
+
valid: false,
|
|
2356
|
+
reason: `No matching address found in DID document (expected ${expectedAddress})`
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
async function verifyDidJsonControllerDid(domain, expectedControllerDid, options = {}) {
|
|
2360
|
+
if (!domain || typeof domain !== "string") {
|
|
2361
|
+
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
2362
|
+
}
|
|
2363
|
+
const didDocument = options.fetchDidDocument ? await options.fetchDidDocument(domain) : await fetchDidDocument(domain);
|
|
2364
|
+
return verifyDidDocumentControllerDid(didDocument, expectedControllerDid);
|
|
2365
|
+
}
|
|
2366
|
+
function buildEip712Domain(name, version, chainId, verifyingContract) {
|
|
2367
|
+
return { name, version, chainId, verifyingContract };
|
|
2368
|
+
}
|
|
2369
|
+
function getOmaTrustProofEip712Types() {
|
|
2370
|
+
return {
|
|
2371
|
+
primaryType: "OmaTrustProof",
|
|
2372
|
+
types: {
|
|
2373
|
+
OmaTrustProof: [
|
|
2374
|
+
{ name: "signer", type: "address" },
|
|
2375
|
+
{ name: "authorizedEntity", type: "string" },
|
|
2376
|
+
{ name: "signingPurpose", type: "string" },
|
|
2377
|
+
{ name: "creationTimestamp", type: "uint256" },
|
|
2378
|
+
{ name: "expirationTimestamp", type: "uint256" },
|
|
2379
|
+
{ name: "randomValue", type: "bytes32" },
|
|
2380
|
+
{ name: "statement", type: "string" }
|
|
2381
|
+
]
|
|
2382
|
+
}
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
function verifyEip712Signature(typedData, signature) {
|
|
2386
|
+
try {
|
|
2387
|
+
const signer = ethers.verifyTypedData(
|
|
2388
|
+
typedData.domain,
|
|
2389
|
+
typedData.types,
|
|
2390
|
+
typedData.message,
|
|
2391
|
+
signature
|
|
2392
|
+
);
|
|
2393
|
+
return { valid: true, signer };
|
|
2394
|
+
} catch (err) {
|
|
2395
|
+
throw new OmaTrustError("INVALID_INPUT", "Failed to verify EIP-712 signature", { err });
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
// src/reputation/proof/dns-txt-record.ts
|
|
2400
|
+
function parseDnsTxtRecord(record) {
|
|
2401
|
+
if (!record || typeof record !== "string") {
|
|
2402
|
+
throw new OmaTrustError("INVALID_INPUT", "record must be a non-empty string", { record });
|
|
2403
|
+
}
|
|
2404
|
+
const entries = record.split(/[;\s]+/).map((entry) => entry.trim()).filter(Boolean);
|
|
2405
|
+
const parsed = {};
|
|
2406
|
+
const controllers = [];
|
|
2407
|
+
for (const entry of entries) {
|
|
2408
|
+
const eqIndex = entry.indexOf("=");
|
|
2409
|
+
if (eqIndex === -1) continue;
|
|
2410
|
+
const key = entry.slice(0, eqIndex).trim();
|
|
2411
|
+
const value = entry.slice(eqIndex + 1).trim();
|
|
2412
|
+
if (!key || !value) continue;
|
|
2413
|
+
if (key === "controller") {
|
|
2414
|
+
controllers.push(value);
|
|
2415
|
+
} else {
|
|
2416
|
+
parsed[key] = value;
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
return {
|
|
2420
|
+
version: parsed.v,
|
|
2421
|
+
controller: controllers[0],
|
|
2422
|
+
controllers
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
function buildDnsTxtRecord(controllerDid) {
|
|
2426
|
+
const normalized = normalizeDid(controllerDid);
|
|
2427
|
+
return `v=1;controller=${normalized}`;
|
|
2428
|
+
}
|
|
2429
|
+
var REQUIRED_OFFER_FIELDS = [
|
|
2430
|
+
"version",
|
|
2431
|
+
"resourceUrl",
|
|
2432
|
+
"scheme",
|
|
2433
|
+
"network",
|
|
2434
|
+
"asset",
|
|
2435
|
+
"payTo",
|
|
2436
|
+
"amount"
|
|
2437
|
+
];
|
|
2438
|
+
var REQUIRED_RECEIPT_FIELDS = [
|
|
2439
|
+
"version",
|
|
2440
|
+
"network",
|
|
2441
|
+
"resourceUrl",
|
|
2442
|
+
"payer",
|
|
2443
|
+
"issuedAt"
|
|
2444
|
+
];
|
|
2445
|
+
function validateOfferPayload(payload) {
|
|
2446
|
+
for (const field of REQUIRED_OFFER_FIELDS) {
|
|
2447
|
+
if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
|
|
2448
|
+
return { valid: false, field };
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
return { valid: true };
|
|
2452
|
+
}
|
|
2453
|
+
function validateReceiptPayload(payload) {
|
|
2454
|
+
for (const field of REQUIRED_RECEIPT_FIELDS) {
|
|
2455
|
+
if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
|
|
2456
|
+
return { valid: false, field };
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
return { valid: true };
|
|
2460
|
+
}
|
|
2461
|
+
function failure(code2, message, partial) {
|
|
2462
|
+
return {
|
|
2463
|
+
valid: false,
|
|
2464
|
+
error: { code: code2, message },
|
|
2465
|
+
...partial
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
async function verifyX402JwsArtifact(artifact, options) {
|
|
2469
|
+
if (!artifact || typeof artifact !== "object") {
|
|
2470
|
+
return failure("INVALID_ARTIFACT", "Artifact must be a non-null object");
|
|
2471
|
+
}
|
|
2472
|
+
if (artifact.format !== "jws") {
|
|
2473
|
+
return failure("INVALID_ARTIFACT", `Expected format "jws", got "${artifact.format}"`);
|
|
2474
|
+
}
|
|
2475
|
+
if (typeof artifact.signature !== "string" || artifact.signature.length === 0) {
|
|
2476
|
+
return failure("INVALID_ARTIFACT", "Artifact signature must be a non-empty string");
|
|
2477
|
+
}
|
|
2478
|
+
const compactJws = artifact.signature;
|
|
2479
|
+
const parts = compactJws.split(".");
|
|
2480
|
+
if (parts.length !== 3) {
|
|
2481
|
+
return failure("MALFORMED_JWS", "Invalid compact JWS format: expected 3 dot-separated parts");
|
|
2482
|
+
}
|
|
2483
|
+
let header;
|
|
2484
|
+
try {
|
|
2485
|
+
header = jose.decodeProtectedHeader(compactJws);
|
|
2486
|
+
} catch {
|
|
2487
|
+
return failure("MALFORMED_JWS", "Failed to decode JWS protected header");
|
|
2488
|
+
}
|
|
2489
|
+
if (!header.alg || typeof header.alg !== "string") {
|
|
2490
|
+
return failure("MISSING_ALG", "JWS header must include alg", { header });
|
|
2491
|
+
}
|
|
2492
|
+
const kid = typeof header.kid === "string" ? header.kid : null;
|
|
2493
|
+
const embeddedJwk = header.jwk;
|
|
2494
|
+
if (!kid && !embeddedJwk) {
|
|
2495
|
+
return failure(
|
|
2496
|
+
"MISSING_KEY_MATERIAL",
|
|
2497
|
+
"JWS header must include at least one of kid or jwk",
|
|
2498
|
+
{ header }
|
|
2499
|
+
);
|
|
2500
|
+
}
|
|
2501
|
+
let publicKeyJwk;
|
|
2502
|
+
let publicKeySource;
|
|
2503
|
+
if (embeddedJwk) {
|
|
2504
|
+
const validation = validatePublicJwk(embeddedJwk);
|
|
2505
|
+
if (!validation.valid) {
|
|
2506
|
+
return failure(
|
|
2507
|
+
"INVALID_EMBEDDED_JWK",
|
|
2508
|
+
`Embedded JWK is invalid: ${validation.error}`,
|
|
2509
|
+
{ header }
|
|
2510
|
+
);
|
|
2511
|
+
}
|
|
2512
|
+
publicKeyJwk = embeddedJwk;
|
|
2513
|
+
publicKeySource = "embedded-jwk";
|
|
2514
|
+
if (kid) {
|
|
2515
|
+
try {
|
|
2516
|
+
const resolved = await resolveDidUrlToPublicKey(kid, options?.resolveOptions);
|
|
2517
|
+
if (!publicJwkEquals(publicKeyJwk, resolved.publicKeyJwk)) {
|
|
2518
|
+
return failure(
|
|
2519
|
+
"KEY_CONFLICT",
|
|
2520
|
+
"Embedded jwk conflicts with public key resolved from kid",
|
|
2521
|
+
{ header, kid }
|
|
2522
|
+
);
|
|
2523
|
+
}
|
|
2524
|
+
} catch (err) {
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
} else {
|
|
2528
|
+
publicKeySource = "kid-resolution";
|
|
2529
|
+
try {
|
|
2530
|
+
const resolved = await resolveDidUrlToPublicKey(kid, options?.resolveOptions);
|
|
2531
|
+
publicKeyJwk = resolved.publicKeyJwk;
|
|
2532
|
+
} catch (err) {
|
|
2533
|
+
const message = err instanceof OmaTrustError ? `Failed to resolve kid "${kid}": ${err.message}` : `Failed to resolve kid "${kid}"`;
|
|
2534
|
+
return failure("KID_RESOLUTION_FAILED", message, { header, kid });
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
let payload;
|
|
2538
|
+
try {
|
|
2539
|
+
const key = await jose.importJWK(publicKeyJwk, header.alg);
|
|
2540
|
+
const result = await jose.compactVerify(compactJws, key);
|
|
2541
|
+
const payloadText = new TextDecoder().decode(result.payload);
|
|
2542
|
+
payload = JSON.parse(payloadText);
|
|
2543
|
+
} catch (err) {
|
|
2544
|
+
const message = err instanceof Error ? err.message : "Signature verification failed";
|
|
2545
|
+
return failure("SIGNATURE_INVALID", message, { header, kid });
|
|
2546
|
+
}
|
|
2547
|
+
const publicKeyDid = jwkToDidJwk(publicKeyJwk);
|
|
2548
|
+
return {
|
|
2549
|
+
valid: true,
|
|
2550
|
+
header,
|
|
2551
|
+
payload,
|
|
2552
|
+
kid,
|
|
2553
|
+
publicKeyJwk,
|
|
2554
|
+
publicKeySource,
|
|
2555
|
+
publicKeyDid
|
|
2556
|
+
};
|
|
2557
|
+
}
|
|
2558
|
+
async function verifyX402JwsOffer(artifact, options) {
|
|
2559
|
+
const result = await verifyX402JwsArtifact(artifact, options);
|
|
2560
|
+
if (!result.valid) return result;
|
|
2561
|
+
const payloadCheck = validateOfferPayload(result.payload);
|
|
2562
|
+
if (!payloadCheck.valid) {
|
|
2563
|
+
return failure(
|
|
2564
|
+
"INVALID_OFFER_PAYLOAD",
|
|
2565
|
+
`Missing required offer field: ${payloadCheck.field}`,
|
|
2566
|
+
{
|
|
2567
|
+
header: result.header,
|
|
2568
|
+
payload: result.payload,
|
|
2569
|
+
kid: result.kid,
|
|
2570
|
+
publicKeyJwk: result.publicKeyJwk,
|
|
2571
|
+
publicKeySource: result.publicKeySource,
|
|
2572
|
+
publicKeyDid: result.publicKeyDid
|
|
2573
|
+
}
|
|
2574
|
+
);
|
|
2575
|
+
}
|
|
2576
|
+
return result;
|
|
2577
|
+
}
|
|
2578
|
+
async function verifyX402JwsReceipt(artifact, options) {
|
|
2579
|
+
const result = await verifyX402JwsArtifact(artifact, options);
|
|
2580
|
+
if (!result.valid) return result;
|
|
2581
|
+
const payloadCheck = validateReceiptPayload(result.payload);
|
|
2582
|
+
if (!payloadCheck.valid) {
|
|
2583
|
+
return failure(
|
|
2584
|
+
"INVALID_RECEIPT_PAYLOAD",
|
|
2585
|
+
`Missing required receipt field: ${payloadCheck.field}`,
|
|
2586
|
+
{
|
|
2587
|
+
header: result.header,
|
|
2588
|
+
payload: result.payload,
|
|
2589
|
+
kid: result.kid,
|
|
2590
|
+
publicKeyJwk: result.publicKeyJwk,
|
|
2591
|
+
publicKeySource: result.publicKeySource,
|
|
2592
|
+
publicKeyDid: result.publicKeyDid
|
|
2593
|
+
}
|
|
2594
|
+
);
|
|
2595
|
+
}
|
|
2596
|
+
return result;
|
|
2597
|
+
}
|
|
2598
|
+
var OFFER_DOMAIN = {
|
|
2599
|
+
name: "x402 offer",
|
|
2600
|
+
version: "1",
|
|
2601
|
+
chainId: 1
|
|
2602
|
+
};
|
|
2603
|
+
var RECEIPT_DOMAIN = {
|
|
2604
|
+
name: "x402 receipt",
|
|
2605
|
+
version: "1",
|
|
2606
|
+
chainId: 1
|
|
2607
|
+
};
|
|
2608
|
+
var OFFER_TYPES = {
|
|
2609
|
+
Offer: [
|
|
2610
|
+
{ name: "version", type: "uint256" },
|
|
2611
|
+
{ name: "resourceUrl", type: "string" },
|
|
2612
|
+
{ name: "scheme", type: "string" },
|
|
2613
|
+
{ name: "network", type: "string" },
|
|
2614
|
+
{ name: "asset", type: "string" },
|
|
2615
|
+
{ name: "payTo", type: "string" },
|
|
2616
|
+
{ name: "amount", type: "string" },
|
|
2617
|
+
{ name: "validUntil", type: "uint256" }
|
|
2618
|
+
]
|
|
2619
|
+
};
|
|
2620
|
+
var RECEIPT_TYPES = {
|
|
2621
|
+
Receipt: [
|
|
2622
|
+
{ name: "version", type: "uint256" },
|
|
2623
|
+
{ name: "network", type: "string" },
|
|
2624
|
+
{ name: "resourceUrl", type: "string" },
|
|
2625
|
+
{ name: "payer", type: "string" },
|
|
2626
|
+
{ name: "issuedAt", type: "uint256" },
|
|
2627
|
+
{ name: "transaction", type: "string" }
|
|
2628
|
+
]
|
|
2629
|
+
};
|
|
2630
|
+
var REQUIRED_OFFER_FIELDS2 = [
|
|
2631
|
+
"version",
|
|
2632
|
+
"resourceUrl",
|
|
2633
|
+
"scheme",
|
|
2634
|
+
"network",
|
|
2635
|
+
"asset",
|
|
2636
|
+
"payTo",
|
|
2637
|
+
"amount"
|
|
2638
|
+
];
|
|
2639
|
+
var REQUIRED_RECEIPT_FIELDS2 = [
|
|
2640
|
+
"version",
|
|
2641
|
+
"network",
|
|
2642
|
+
"resourceUrl",
|
|
2643
|
+
"payer",
|
|
2644
|
+
"issuedAt"
|
|
2645
|
+
];
|
|
2646
|
+
function validateOfferPayload2(payload) {
|
|
2647
|
+
for (const field of REQUIRED_OFFER_FIELDS2) {
|
|
2648
|
+
if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
|
|
2649
|
+
return { valid: false, field };
|
|
2650
|
+
}
|
|
1281
2651
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
2652
|
+
return { valid: true };
|
|
2653
|
+
}
|
|
2654
|
+
function validateReceiptPayload2(payload) {
|
|
2655
|
+
for (const field of REQUIRED_RECEIPT_FIELDS2) {
|
|
2656
|
+
if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
|
|
2657
|
+
return { valid: false, field };
|
|
2658
|
+
}
|
|
1285
2659
|
}
|
|
2660
|
+
return { valid: true };
|
|
2661
|
+
}
|
|
2662
|
+
function failure2(code2, message, partial) {
|
|
1286
2663
|
return {
|
|
1287
2664
|
valid: false,
|
|
1288
|
-
|
|
2665
|
+
error: { code: code2, message },
|
|
2666
|
+
...partial
|
|
1289
2667
|
};
|
|
1290
2668
|
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
2669
|
+
function prepareOfferMessage(payload) {
|
|
2670
|
+
return {
|
|
2671
|
+
version: payload.version,
|
|
2672
|
+
resourceUrl: payload.resourceUrl,
|
|
2673
|
+
scheme: payload.scheme,
|
|
2674
|
+
network: payload.network,
|
|
2675
|
+
asset: payload.asset,
|
|
2676
|
+
payTo: payload.payTo,
|
|
2677
|
+
amount: payload.amount,
|
|
2678
|
+
validUntil: payload.validUntil ?? 0
|
|
2679
|
+
};
|
|
1300
2680
|
}
|
|
1301
|
-
function
|
|
2681
|
+
function prepareReceiptMessage(payload) {
|
|
1302
2682
|
return {
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
{ name: "creationTimestamp", type: "uint256" },
|
|
1310
|
-
{ name: "expirationTimestamp", type: "uint256" },
|
|
1311
|
-
{ name: "randomValue", type: "bytes32" },
|
|
1312
|
-
{ name: "statement", type: "string" }
|
|
1313
|
-
]
|
|
1314
|
-
}
|
|
2683
|
+
version: payload.version,
|
|
2684
|
+
network: payload.network,
|
|
2685
|
+
resourceUrl: payload.resourceUrl,
|
|
2686
|
+
payer: payload.payer,
|
|
2687
|
+
issuedAt: payload.issuedAt,
|
|
2688
|
+
transaction: payload.transaction ?? ""
|
|
1315
2689
|
};
|
|
1316
2690
|
}
|
|
1317
|
-
function
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
typedData.domain,
|
|
1321
|
-
typedData.types,
|
|
1322
|
-
typedData.message,
|
|
1323
|
-
signature
|
|
1324
|
-
);
|
|
1325
|
-
return { valid: true, signer };
|
|
1326
|
-
} catch (err) {
|
|
1327
|
-
throw new OmaTrustError("INVALID_INPUT", "Failed to verify EIP-712 signature", { err });
|
|
2691
|
+
function verifyX402Eip712Artifact(artifact, artifactType) {
|
|
2692
|
+
if (!artifact || typeof artifact !== "object") {
|
|
2693
|
+
return failure2("INVALID_ARTIFACT", "Artifact must be a non-null object");
|
|
1328
2694
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
// src/reputation/proof/dns-txt-record.ts
|
|
1332
|
-
function parseDnsTxtRecord(record) {
|
|
1333
|
-
if (!record || typeof record !== "string") {
|
|
1334
|
-
throw new OmaTrustError("INVALID_INPUT", "record must be a non-empty string", { record });
|
|
2695
|
+
if (artifact.format !== "eip712") {
|
|
2696
|
+
return failure2("INVALID_ARTIFACT", `Expected format "eip712", got "${artifact.format}"`);
|
|
1335
2697
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
2698
|
+
if (!artifact.payload || typeof artifact.payload !== "object" || Array.isArray(artifact.payload)) {
|
|
2699
|
+
return failure2("MISSING_PAYLOAD", "EIP-712 artifact must include a payload object");
|
|
2700
|
+
}
|
|
2701
|
+
if (typeof artifact.signature !== "string" || artifact.signature.length === 0) {
|
|
2702
|
+
return failure2("MISSING_SIGNATURE", "EIP-712 artifact must include a non-empty signature");
|
|
2703
|
+
}
|
|
2704
|
+
const payload = artifact.payload;
|
|
2705
|
+
if (artifactType === "offer") {
|
|
2706
|
+
const check = validateOfferPayload2(payload);
|
|
2707
|
+
if (!check.valid) {
|
|
2708
|
+
return failure2(
|
|
2709
|
+
"INVALID_OFFER_PAYLOAD",
|
|
2710
|
+
`Missing required offer field: ${check.field}`,
|
|
2711
|
+
{ payload }
|
|
2712
|
+
);
|
|
1342
2713
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
2714
|
+
} else {
|
|
2715
|
+
const check = validateReceiptPayload2(payload);
|
|
2716
|
+
if (!check.valid) {
|
|
2717
|
+
return failure2(
|
|
2718
|
+
"INVALID_RECEIPT_PAYLOAD",
|
|
2719
|
+
`Missing required receipt field: ${check.field}`,
|
|
2720
|
+
{ payload }
|
|
2721
|
+
);
|
|
1346
2722
|
}
|
|
1347
|
-
|
|
2723
|
+
}
|
|
2724
|
+
const domain = artifactType === "offer" ? OFFER_DOMAIN : RECEIPT_DOMAIN;
|
|
2725
|
+
const types = artifactType === "offer" ? OFFER_TYPES : RECEIPT_TYPES;
|
|
2726
|
+
const message = artifactType === "offer" ? prepareOfferMessage(payload) : prepareReceiptMessage(payload);
|
|
2727
|
+
let signer;
|
|
2728
|
+
try {
|
|
2729
|
+
signer = ethers.verifyTypedData(
|
|
2730
|
+
domain,
|
|
2731
|
+
types,
|
|
2732
|
+
message,
|
|
2733
|
+
artifact.signature
|
|
2734
|
+
);
|
|
2735
|
+
signer = ethers.getAddress(signer);
|
|
2736
|
+
} catch (err) {
|
|
2737
|
+
const message2 = err instanceof Error ? err.message : "EIP-712 signature verification failed";
|
|
2738
|
+
return failure2("SIGNATURE_INVALID", message2, { payload });
|
|
1348
2739
|
}
|
|
1349
2740
|
return {
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
2741
|
+
valid: true,
|
|
2742
|
+
payload,
|
|
2743
|
+
signer,
|
|
2744
|
+
artifactType
|
|
1353
2745
|
};
|
|
1354
2746
|
}
|
|
1355
|
-
function
|
|
1356
|
-
|
|
1357
|
-
|
|
2747
|
+
function verifyX402Eip712Offer(artifact) {
|
|
2748
|
+
return verifyX402Eip712Artifact(artifact, "offer");
|
|
2749
|
+
}
|
|
2750
|
+
function verifyX402Eip712Receipt(artifact) {
|
|
2751
|
+
return verifyX402Eip712Artifact(artifact, "receipt");
|
|
1358
2752
|
}
|
|
1359
2753
|
|
|
1360
2754
|
// src/reputation/verify.ts
|
|
@@ -1414,18 +2808,18 @@ function decodeJwtPayload(compactJws) {
|
|
|
1414
2808
|
return JSON.parse(json);
|
|
1415
2809
|
}
|
|
1416
2810
|
async function verifyProof(params) {
|
|
1417
|
-
const { proof, provider,
|
|
2811
|
+
const { proof, provider, expectedSubjectDid, expectedControllerDid } = params;
|
|
1418
2812
|
try {
|
|
1419
2813
|
switch (proof.proofType) {
|
|
1420
2814
|
case "tx-encoded-value": {
|
|
1421
2815
|
if (!provider) {
|
|
1422
2816
|
return { valid: false, proofType: proof.proofType, reason: "Provider is required" };
|
|
1423
2817
|
}
|
|
1424
|
-
if (!
|
|
2818
|
+
if (!expectedSubjectDid || !expectedControllerDid) {
|
|
1425
2819
|
return {
|
|
1426
2820
|
valid: false,
|
|
1427
2821
|
proofType: proof.proofType,
|
|
1428
|
-
reason: "
|
|
2822
|
+
reason: "expectedSubjectDid and expectedControllerDid are required"
|
|
1429
2823
|
};
|
|
1430
2824
|
}
|
|
1431
2825
|
const proofObject = proof.proofObject;
|
|
@@ -1437,13 +2831,13 @@ async function verifyProof(params) {
|
|
|
1437
2831
|
return { valid: false, proofType: proof.proofType, reason: "Transaction not found" };
|
|
1438
2832
|
}
|
|
1439
2833
|
const expectedAmount = calculateTransferAmount(
|
|
1440
|
-
|
|
1441
|
-
|
|
2834
|
+
expectedSubjectDid,
|
|
2835
|
+
expectedControllerDid,
|
|
1442
2836
|
chainId,
|
|
1443
2837
|
getProofPurpose(proof)
|
|
1444
2838
|
);
|
|
1445
|
-
const subjectAddress = ethers.getAddress(extractAddressFromDid(
|
|
1446
|
-
const controllerAddress = ethers.getAddress(extractAddressFromDid(
|
|
2839
|
+
const subjectAddress = ethers.getAddress(extractAddressFromDid(expectedSubjectDid));
|
|
2840
|
+
const controllerAddress = ethers.getAddress(extractAddressFromDid(expectedControllerDid));
|
|
1447
2841
|
if (tx.from && ethers.getAddress(tx.from) !== subjectAddress) {
|
|
1448
2842
|
return { valid: false, proofType: proof.proofType, reason: "Transaction sender mismatch" };
|
|
1449
2843
|
}
|
|
@@ -1517,6 +2911,38 @@ async function verifyProof(params) {
|
|
|
1517
2911
|
if (!proof.proofObject || typeof proof.proofObject !== "object") {
|
|
1518
2912
|
return { valid: false, proofType: proof.proofType, reason: "Invalid x402 proof object" };
|
|
1519
2913
|
}
|
|
2914
|
+
const proofObj = proof.proofObject;
|
|
2915
|
+
if (proofObj.format === "jws" && typeof proofObj.signature === "string") {
|
|
2916
|
+
const artifact = {
|
|
2917
|
+
format: "jws",
|
|
2918
|
+
signature: proofObj.signature
|
|
2919
|
+
};
|
|
2920
|
+
const jwsResult = proof.proofType === "x402-offer" ? await verifyX402JwsOffer(artifact) : await verifyX402JwsReceipt(artifact);
|
|
2921
|
+
if (!jwsResult.valid) {
|
|
2922
|
+
return {
|
|
2923
|
+
valid: false,
|
|
2924
|
+
proofType: proof.proofType,
|
|
2925
|
+
reason: jwsResult.error?.message ?? "JWS verification failed"
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
return { valid: true, proofType: proof.proofType };
|
|
2929
|
+
}
|
|
2930
|
+
if (proofObj.format === "eip712" && typeof proofObj.signature === "string" && proofObj.payload && typeof proofObj.payload === "object") {
|
|
2931
|
+
const artifact = {
|
|
2932
|
+
format: "eip712",
|
|
2933
|
+
payload: proofObj.payload,
|
|
2934
|
+
signature: proofObj.signature
|
|
2935
|
+
};
|
|
2936
|
+
const eip712Result = proof.proofType === "x402-offer" ? verifyX402Eip712Offer(artifact) : verifyX402Eip712Receipt(artifact);
|
|
2937
|
+
if (!eip712Result.valid) {
|
|
2938
|
+
return {
|
|
2939
|
+
valid: false,
|
|
2940
|
+
proofType: proof.proofType,
|
|
2941
|
+
reason: eip712Result.error?.message ?? "EIP-712 verification failed"
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2944
|
+
return { valid: true, proofType: proof.proofType };
|
|
2945
|
+
}
|
|
1520
2946
|
return { valid: true, proofType: proof.proofType };
|
|
1521
2947
|
}
|
|
1522
2948
|
case "evidence-pointer": {
|
|
@@ -1528,9 +2954,9 @@ async function verifyProof(params) {
|
|
|
1528
2954
|
if (!response.ok) {
|
|
1529
2955
|
return { valid: false, proofType: proof.proofType, reason: `Evidence fetch failed (${response.status})` };
|
|
1530
2956
|
}
|
|
1531
|
-
if (object.url.endsWith("/.well-known/did.json") &&
|
|
2957
|
+
if (object.url.endsWith("/.well-known/did.json") && expectedControllerDid) {
|
|
1532
2958
|
const didDoc = await response.json();
|
|
1533
|
-
const didCheck = verifyDidDocumentControllerDid(didDoc,
|
|
2959
|
+
const didCheck = verifyDidDocumentControllerDid(didDoc, expectedControllerDid);
|
|
1534
2960
|
return {
|
|
1535
2961
|
valid: didCheck.valid,
|
|
1536
2962
|
proofType: proof.proofType,
|
|
@@ -1538,10 +2964,10 @@ async function verifyProof(params) {
|
|
|
1538
2964
|
};
|
|
1539
2965
|
}
|
|
1540
2966
|
const body = await response.text();
|
|
1541
|
-
if (
|
|
2967
|
+
if (expectedControllerDid && !body.includes(expectedControllerDid)) {
|
|
1542
2968
|
try {
|
|
1543
2969
|
const parsed = parseDnsTxtRecord(body);
|
|
1544
|
-
if (parsed.
|
|
2970
|
+
if (!parsed.controllers.includes(expectedControllerDid)) {
|
|
1545
2971
|
return {
|
|
1546
2972
|
valid: false,
|
|
1547
2973
|
proofType: proof.proofType,
|
|
@@ -1597,8 +3023,8 @@ async function verifyAttestation(params) {
|
|
|
1597
3023
|
const result = await verifyProof({
|
|
1598
3024
|
proof,
|
|
1599
3025
|
provider: params.provider,
|
|
1600
|
-
|
|
1601
|
-
|
|
3026
|
+
expectedSubjectDid: params.context?.subjectDid,
|
|
3027
|
+
expectedControllerDid: params.context?.controllerDid
|
|
1602
3028
|
});
|
|
1603
3029
|
checks[proof.proofType] = result.valid;
|
|
1604
3030
|
if (!result.valid) {
|
|
@@ -1681,25 +3107,479 @@ async function callMethod(params, method) {
|
|
|
1681
3107
|
if (!response.ok) {
|
|
1682
3108
|
return null;
|
|
1683
3109
|
}
|
|
1684
|
-
return {
|
|
1685
|
-
ok: true,
|
|
1686
|
-
method,
|
|
1687
|
-
details
|
|
1688
|
-
};
|
|
3110
|
+
return {
|
|
3111
|
+
ok: true,
|
|
3112
|
+
method,
|
|
3113
|
+
details
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
async function callControllerWitness(params) {
|
|
3117
|
+
const dnsResult = await callMethod(params, "dns-txt").catch(() => null);
|
|
3118
|
+
if (dnsResult) {
|
|
3119
|
+
return dnsResult;
|
|
3120
|
+
}
|
|
3121
|
+
const didJsonResult = await callMethod(params, "did-json").catch(() => null);
|
|
3122
|
+
if (didJsonResult) {
|
|
3123
|
+
return didJsonResult;
|
|
3124
|
+
}
|
|
3125
|
+
return {
|
|
3126
|
+
ok: false,
|
|
3127
|
+
method: "did-json"
|
|
3128
|
+
};
|
|
3129
|
+
}
|
|
3130
|
+
var DEFAULT_CONTROLLER_WITNESS_URL = "https://api.omatrust.org/v1/controller-witness";
|
|
3131
|
+
async function requestControllerWitness(params) {
|
|
3132
|
+
const url = params.gatewayUrl ?? DEFAULT_CONTROLLER_WITNESS_URL;
|
|
3133
|
+
const body = {
|
|
3134
|
+
subjectDid: params.subjectDid,
|
|
3135
|
+
controllerDid: params.controllerDid
|
|
3136
|
+
};
|
|
3137
|
+
if (params.chainId !== void 0) {
|
|
3138
|
+
body.chainId = params.chainId;
|
|
3139
|
+
}
|
|
3140
|
+
let response;
|
|
3141
|
+
try {
|
|
3142
|
+
response = await fetch(url, {
|
|
3143
|
+
method: "POST",
|
|
3144
|
+
headers: { "Content-Type": "application/json" },
|
|
3145
|
+
credentials: "include",
|
|
3146
|
+
body: JSON.stringify(body),
|
|
3147
|
+
signal: AbortSignal.timeout(params.timeoutMs ?? 15e3)
|
|
3148
|
+
});
|
|
3149
|
+
} catch (err) {
|
|
3150
|
+
throw new OmaTrustError("NETWORK_ERROR", "Controller witness request failed", { err });
|
|
3151
|
+
}
|
|
3152
|
+
if (!response.ok) {
|
|
3153
|
+
const errorBody = await response.json().catch(() => ({ error: response.statusText }));
|
|
3154
|
+
throw new OmaTrustError(
|
|
3155
|
+
"API_ERROR",
|
|
3156
|
+
errorBody.error ?? `Controller witness failed: ${response.status}`,
|
|
3157
|
+
{ status: response.status, ...errorBody }
|
|
3158
|
+
);
|
|
3159
|
+
}
|
|
3160
|
+
return await response.json();
|
|
3161
|
+
}
|
|
3162
|
+
var EIP1967_ADMIN_SLOT = "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103";
|
|
3163
|
+
var OWNERSHIP_PATTERNS = [
|
|
3164
|
+
{ method: "owner", signature: "function owner() view returns (address)" },
|
|
3165
|
+
{ method: "admin", signature: "function admin() view returns (address)" },
|
|
3166
|
+
{ method: "getOwner", signature: "function getOwner() view returns (address)" }
|
|
3167
|
+
];
|
|
3168
|
+
async function readOwnerFromContract(provider, contractAddress, signature, method) {
|
|
3169
|
+
try {
|
|
3170
|
+
const iface = new ethers.Interface([signature]);
|
|
3171
|
+
const data = iface.encodeFunctionData(method, []);
|
|
3172
|
+
const result = await provider.call({ to: contractAddress, data });
|
|
3173
|
+
const [value] = iface.decodeFunctionResult(method, result);
|
|
3174
|
+
if (typeof value === "string" && ethers.isAddress(value) && value !== ethers.ZeroAddress) {
|
|
3175
|
+
return ethers.getAddress(value);
|
|
3176
|
+
}
|
|
3177
|
+
} catch {
|
|
3178
|
+
}
|
|
3179
|
+
return null;
|
|
3180
|
+
}
|
|
3181
|
+
async function discoverContractOwner(provider, contractAddress) {
|
|
3182
|
+
try {
|
|
3183
|
+
const code2 = await provider.getCode(contractAddress);
|
|
3184
|
+
if (code2 === "0x" || code2 === "0x0") {
|
|
3185
|
+
return null;
|
|
3186
|
+
}
|
|
3187
|
+
} catch {
|
|
3188
|
+
return null;
|
|
3189
|
+
}
|
|
3190
|
+
for (const pattern of OWNERSHIP_PATTERNS) {
|
|
3191
|
+
const address = await readOwnerFromContract(
|
|
3192
|
+
provider,
|
|
3193
|
+
contractAddress,
|
|
3194
|
+
pattern.signature,
|
|
3195
|
+
pattern.method
|
|
3196
|
+
);
|
|
3197
|
+
if (address) {
|
|
3198
|
+
return address;
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
try {
|
|
3202
|
+
const adminValue = await provider.getStorage(contractAddress, EIP1967_ADMIN_SLOT);
|
|
3203
|
+
if (adminValue && adminValue !== "0x" && adminValue !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
3204
|
+
const adminAddress = ethers.getAddress(`0x${adminValue.slice(-40)}`);
|
|
3205
|
+
if (adminAddress !== ethers.ZeroAddress) {
|
|
3206
|
+
return adminAddress;
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
} catch {
|
|
3210
|
+
}
|
|
3211
|
+
return null;
|
|
3212
|
+
}
|
|
3213
|
+
async function discoverControllingWalletDid(provider, contractAddress, chainId) {
|
|
3214
|
+
const owner = await discoverContractOwner(provider, contractAddress);
|
|
3215
|
+
return owner ? buildEvmDidPkh(chainId, owner) : null;
|
|
3216
|
+
}
|
|
3217
|
+
async function verifyTransferProof(provider, txHash, subjectDid, attesterAddress, chainId) {
|
|
3218
|
+
try {
|
|
3219
|
+
const tx = await provider.getTransaction(txHash);
|
|
3220
|
+
if (!tx || !tx.from || !tx.to) {
|
|
3221
|
+
return false;
|
|
3222
|
+
}
|
|
3223
|
+
if (ethers.getAddress(tx.to) !== ethers.getAddress(attesterAddress)) {
|
|
3224
|
+
return false;
|
|
3225
|
+
}
|
|
3226
|
+
const attesterDid = buildEvmDidPkh(chainId, attesterAddress);
|
|
3227
|
+
const expectedAmount = calculateTransferAmount(
|
|
3228
|
+
subjectDid,
|
|
3229
|
+
attesterDid,
|
|
3230
|
+
chainId,
|
|
3231
|
+
"shared-control"
|
|
3232
|
+
);
|
|
3233
|
+
const actualValue = BigInt(tx.value ?? 0n);
|
|
3234
|
+
return actualValue === expectedAmount;
|
|
3235
|
+
} catch {
|
|
3236
|
+
return false;
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
// src/shared/trust-anchors.ts
|
|
3241
|
+
var DEFAULT_TRUST_ANCHORS_URL = "https://api.omatrust.org/v1/trust-anchors";
|
|
3242
|
+
var TRUST_ANCHORS_URL = DEFAULT_TRUST_ANCHORS_URL;
|
|
3243
|
+
var cachedAnchors = null;
|
|
3244
|
+
var cacheTimestamp = 0;
|
|
3245
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
3246
|
+
async function fetchTrustAnchors(url) {
|
|
3247
|
+
const now = Date.now();
|
|
3248
|
+
if (cachedAnchors && now - cacheTimestamp < CACHE_TTL_MS) {
|
|
3249
|
+
return cachedAnchors;
|
|
3250
|
+
}
|
|
3251
|
+
let res;
|
|
3252
|
+
try {
|
|
3253
|
+
res = await fetch(url ?? TRUST_ANCHORS_URL);
|
|
3254
|
+
} catch (err) {
|
|
3255
|
+
throw new OmaTrustError("NETWORK_ERROR", "Failed to fetch trust anchors", { err });
|
|
3256
|
+
}
|
|
3257
|
+
if (!res.ok) {
|
|
3258
|
+
throw new OmaTrustError("NETWORK_ERROR", `Failed to fetch trust anchors: ${res.status} ${res.statusText}`);
|
|
3259
|
+
}
|
|
3260
|
+
const anchors = await res.json();
|
|
3261
|
+
if (!anchors.version || !anchors.chains || typeof anchors.chains !== "object") {
|
|
3262
|
+
throw new OmaTrustError("INVALID_INPUT", "Invalid trust anchors format");
|
|
3263
|
+
}
|
|
3264
|
+
cachedAnchors = anchors;
|
|
3265
|
+
cacheTimestamp = now;
|
|
3266
|
+
return anchors;
|
|
3267
|
+
}
|
|
3268
|
+
function getChainAnchors(anchors, caip2) {
|
|
3269
|
+
const chain = anchors.chains[caip2];
|
|
3270
|
+
if (!chain) {
|
|
3271
|
+
throw new OmaTrustError("UNSUPPORTED_CHAIN", `Chain ${caip2} is not in the trust anchors`, {
|
|
3272
|
+
caip2,
|
|
3273
|
+
supportedChains: Object.keys(anchors.chains)
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
return chain;
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3279
|
+
// src/reputation/proof/dns-txt-shared.ts
|
|
3280
|
+
async function verifyDnsTxtControllerDid(domain, expectedControllerDid, options = {}) {
|
|
3281
|
+
if (!domain || typeof domain !== "string") {
|
|
3282
|
+
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
3283
|
+
}
|
|
3284
|
+
if (!options.resolveTxt) {
|
|
3285
|
+
throw new OmaTrustError("NETWORK_ERROR", "No DNS TXT resolver was provided", { domain });
|
|
3286
|
+
}
|
|
3287
|
+
const prefix = options.recordPrefix ?? "_controllers";
|
|
3288
|
+
const host = `${prefix}.${domain.toLowerCase().replace(/\.$/, "")}`;
|
|
3289
|
+
let records;
|
|
3290
|
+
try {
|
|
3291
|
+
records = await options.resolveTxt(host);
|
|
3292
|
+
} catch (err) {
|
|
3293
|
+
throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
|
|
3294
|
+
}
|
|
3295
|
+
for (const recordParts of records) {
|
|
3296
|
+
const record = recordParts.join("");
|
|
3297
|
+
const parsed = parseDnsTxtRecord(record);
|
|
3298
|
+
if (parsed.version !== "1") continue;
|
|
3299
|
+
for (const recordController of parsed.controllers) {
|
|
3300
|
+
if (isSameControllerId(recordController, expectedControllerDid)) {
|
|
3301
|
+
return { valid: true, record };
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
return { valid: false, reason: "Controller DID not found in DNS TXT records" };
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
// src/reputation/attester-authorization.ts
|
|
3309
|
+
var DEFAULT_CHAIN = "eip155:6623";
|
|
3310
|
+
var DEFAULT_PURPOSES = ["authentication", "assertionMethod"];
|
|
3311
|
+
async function getControllerAuthorization(params) {
|
|
3312
|
+
const chain = params.chain ?? DEFAULT_CHAIN;
|
|
3313
|
+
const purposes = params.purpose && params.purpose.length > 0 ? params.purpose : DEFAULT_PURPOSES;
|
|
3314
|
+
const controllerDid = normalizeDid(params.controllerDid);
|
|
3315
|
+
const controllerMethod = extractDidMethod(controllerDid);
|
|
3316
|
+
const controllerEvmAddress = extractControllerEvmAddress(controllerDid);
|
|
3317
|
+
const anchors = await fetchTrustAnchors();
|
|
3318
|
+
const chainAnchors = getChainAnchors(anchors, chain);
|
|
3319
|
+
const easContractAddress = params.easContractAddress ?? chainAnchors.easContract;
|
|
3320
|
+
const provider = params.provider;
|
|
3321
|
+
const controllerWitnessSchemaUid = chainAnchors.schemas["controller-witness"];
|
|
3322
|
+
const keyBindingSchemaUid = chainAnchors.schemas["key-binding"];
|
|
3323
|
+
const subjectAddress = didToAddress(params.subjectDid);
|
|
3324
|
+
let relevantWitnesses = [];
|
|
3325
|
+
if (controllerWitnessSchemaUid) {
|
|
3326
|
+
const rawWitnesses = await queryByRecipientAndSchema(
|
|
3327
|
+
easContractAddress,
|
|
3328
|
+
provider,
|
|
3329
|
+
subjectAddress,
|
|
3330
|
+
[controllerWitnessSchemaUid],
|
|
3331
|
+
params.fromBlock ?? 0
|
|
3332
|
+
);
|
|
3333
|
+
relevantWitnesses = rawWitnesses.filter((att) => {
|
|
3334
|
+
const witnessController = att.data?.controller;
|
|
3335
|
+
if (typeof witnessController !== "string") return false;
|
|
3336
|
+
return isSameControllerId(witnessController, controllerDid);
|
|
3337
|
+
});
|
|
3338
|
+
relevantWitnesses.sort((a, b) => Number(a.time - b.time));
|
|
3339
|
+
}
|
|
3340
|
+
const controllerWitnesses = relevantWitnesses.map((att) => ({
|
|
3341
|
+
uid: att.uid,
|
|
3342
|
+
issuedAt: att.time,
|
|
3343
|
+
attester: att.attester,
|
|
3344
|
+
method: resolveWitnessMethod(att.data?.method)
|
|
3345
|
+
}));
|
|
3346
|
+
let keyBindingUid = null;
|
|
3347
|
+
let until = null;
|
|
3348
|
+
let keyPurposeStatus = "not-required";
|
|
3349
|
+
if (keyBindingSchemaUid) {
|
|
3350
|
+
const keyBindings = await queryByRecipientAndSchema(
|
|
3351
|
+
easContractAddress,
|
|
3352
|
+
provider,
|
|
3353
|
+
subjectAddress,
|
|
3354
|
+
[keyBindingSchemaUid],
|
|
3355
|
+
params.fromBlock ?? 0
|
|
3356
|
+
);
|
|
3357
|
+
const relevantKeyBindings = keyBindings.filter((att) => controllerMatchesKeyBinding(att, controllerDid, controllerMethod)).sort((a, b) => Number(b.time - a.time));
|
|
3358
|
+
const relevantKeyBinding = relevantKeyBindings[0] ?? null;
|
|
3359
|
+
if (relevantKeyBinding) {
|
|
3360
|
+
keyBindingUid = relevantKeyBinding.uid;
|
|
3361
|
+
if (relevantKeyBinding.revocationTime > 0n) {
|
|
3362
|
+
until = relevantKeyBinding.revocationTime;
|
|
3363
|
+
}
|
|
3364
|
+
const keyPurposes = relevantKeyBinding.data?.keyPurpose;
|
|
3365
|
+
if (!Array.isArray(keyPurposes) || keyPurposes.length === 0) {
|
|
3366
|
+
keyPurposeStatus = "unknown";
|
|
3367
|
+
} else {
|
|
3368
|
+
const allMatched = purposes.every((p) => keyPurposes.includes(p));
|
|
3369
|
+
keyPurposeStatus = allMatched ? "matched" : "mismatch";
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
let currentlyVerified = false;
|
|
3374
|
+
let liveMethod = null;
|
|
3375
|
+
let transferProofVerified = false;
|
|
3376
|
+
let transferProofAnchor = null;
|
|
3377
|
+
const subjectMethod = extractDidMethod(params.subjectDid);
|
|
3378
|
+
if (subjectMethod === "web") {
|
|
3379
|
+
const domain = getDomainFromDidWeb(params.subjectDid);
|
|
3380
|
+
if (domain) {
|
|
3381
|
+
try {
|
|
3382
|
+
const dnsResult = await verifyDnsTxtControllerDid(domain, controllerDid, {
|
|
3383
|
+
resolveTxt: params.resolveTxt
|
|
3384
|
+
});
|
|
3385
|
+
if (dnsResult.valid) {
|
|
3386
|
+
currentlyVerified = true;
|
|
3387
|
+
liveMethod = "dns";
|
|
3388
|
+
}
|
|
3389
|
+
} catch {
|
|
3390
|
+
}
|
|
3391
|
+
if (!currentlyVerified) {
|
|
3392
|
+
try {
|
|
3393
|
+
const fetchDoc = params.fetchDidDocument ?? fetchDidDocument;
|
|
3394
|
+
const didDoc = await fetchDoc(domain);
|
|
3395
|
+
const didJsonResult = verifyDidDocumentControllerDid(didDoc, controllerDid);
|
|
3396
|
+
if (didJsonResult.valid) {
|
|
3397
|
+
currentlyVerified = true;
|
|
3398
|
+
liveMethod = "did-document";
|
|
3399
|
+
}
|
|
3400
|
+
} catch {
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
} else if (subjectMethod === "pkh") {
|
|
3405
|
+
if (isEvmDidPkh(params.subjectDid) && controllerEvmAddress) {
|
|
3406
|
+
const subjectContractAddress = getAddressFromDidPkh(params.subjectDid);
|
|
3407
|
+
const subjectChainId = getChainIdFromDidPkh(params.subjectDid);
|
|
3408
|
+
const chainIdNum = subjectChainId ? Number(subjectChainId) : null;
|
|
3409
|
+
if (subjectContractAddress && chainIdNum) {
|
|
3410
|
+
const ownerAddress = await discoverContractOwner(
|
|
3411
|
+
provider,
|
|
3412
|
+
subjectContractAddress
|
|
3413
|
+
);
|
|
3414
|
+
if (ownerAddress && ethers.getAddress(ownerAddress) === ethers.getAddress(controllerEvmAddress)) {
|
|
3415
|
+
currentlyVerified = true;
|
|
3416
|
+
liveMethod = "contract-ownership";
|
|
3417
|
+
}
|
|
3418
|
+
if (!currentlyVerified && keyBindingUid) {
|
|
3419
|
+
const relevantKeyBinding = await findKeyBindingWithTransferProof(
|
|
3420
|
+
keyBindingSchemaUid,
|
|
3421
|
+
easContractAddress,
|
|
3422
|
+
provider,
|
|
3423
|
+
subjectAddress,
|
|
3424
|
+
controllerDid,
|
|
3425
|
+
controllerMethod,
|
|
3426
|
+
params.fromBlock ?? 0
|
|
3427
|
+
);
|
|
3428
|
+
if (relevantKeyBinding) {
|
|
3429
|
+
const proofTxHash = relevantKeyBinding.data?.proofTxHash;
|
|
3430
|
+
if (proofTxHash && /^0x[0-9a-fA-F]{64}$/.test(proofTxHash)) {
|
|
3431
|
+
const verified = await verifyTransferProof(
|
|
3432
|
+
provider,
|
|
3433
|
+
proofTxHash,
|
|
3434
|
+
params.subjectDid,
|
|
3435
|
+
controllerEvmAddress,
|
|
3436
|
+
chainIdNum
|
|
3437
|
+
);
|
|
3438
|
+
if (verified) {
|
|
3439
|
+
transferProofVerified = true;
|
|
3440
|
+
transferProofAnchor = relevantKeyBinding.time;
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
const witnessAnchor = relevantWitnesses.length > 0 ? relevantWitnesses[0].time : null;
|
|
3449
|
+
const anchoredFrom = witnessAnchor ?? transferProofAnchor;
|
|
3450
|
+
const purposeBlocks = keyPurposeStatus === "mismatch";
|
|
3451
|
+
const hasOpenWindow = relevantWitnesses.length > 0 && until === null;
|
|
3452
|
+
const hasKeyBindingWithProof = keyBindingUid !== null && transferProofVerified && until === null;
|
|
3453
|
+
const authorized = !purposeBlocks && (hasOpenWindow || currentlyVerified && until === null || hasKeyBindingWithProof);
|
|
3454
|
+
return {
|
|
3455
|
+
authorized,
|
|
3456
|
+
anchoredFrom,
|
|
3457
|
+
until,
|
|
3458
|
+
currentlyVerified,
|
|
3459
|
+
liveMethod,
|
|
3460
|
+
controllerWitnesses,
|
|
3461
|
+
keyBindingUid,
|
|
3462
|
+
keyPurposeStatus,
|
|
3463
|
+
transferProofVerified: transferProofVerified || void 0
|
|
3464
|
+
};
|
|
3465
|
+
}
|
|
3466
|
+
function controllerMatchesKeyBinding(att, controllerDid, controllerMethod) {
|
|
3467
|
+
const keyId = att.data?.keyId;
|
|
3468
|
+
if (typeof keyId === "string") {
|
|
3469
|
+
if (isSameControllerId(keyId, controllerDid)) {
|
|
3470
|
+
return true;
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
if (controllerMethod === "jwk") {
|
|
3474
|
+
const publicKeyJwkRaw = att.data?.publicKeyJwk;
|
|
3475
|
+
if (publicKeyJwkRaw) {
|
|
3476
|
+
try {
|
|
3477
|
+
const onChainJwk = typeof publicKeyJwkRaw === "string" ? JSON.parse(publicKeyJwkRaw) : publicKeyJwkRaw;
|
|
3478
|
+
const controllerJwk = didJwkToJwk(controllerDid);
|
|
3479
|
+
return publicJwkEquals(onChainJwk, controllerJwk);
|
|
3480
|
+
} catch {
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
return false;
|
|
3485
|
+
}
|
|
3486
|
+
var EAS_EVENT_ABI2 = [
|
|
3487
|
+
"event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)"
|
|
3488
|
+
];
|
|
3489
|
+
function resolveWitnessMethod(value) {
|
|
3490
|
+
if (typeof value !== "string") return void 0;
|
|
3491
|
+
switch (value) {
|
|
3492
|
+
case "dns":
|
|
3493
|
+
case "dns-txt":
|
|
3494
|
+
return "dns";
|
|
3495
|
+
case "did-document":
|
|
3496
|
+
case "did-json":
|
|
3497
|
+
return "did-document";
|
|
3498
|
+
case "manual":
|
|
3499
|
+
return "manual";
|
|
3500
|
+
default:
|
|
3501
|
+
return "other";
|
|
3502
|
+
}
|
|
1689
3503
|
}
|
|
1690
|
-
async function
|
|
1691
|
-
const
|
|
1692
|
-
|
|
1693
|
-
|
|
3504
|
+
async function queryByRecipientAndSchema(easContractAddress, provider, recipient, schemas, fromBlock) {
|
|
3505
|
+
const contract = new ethers.Contract(easContractAddress, EAS_EVENT_ABI2, provider);
|
|
3506
|
+
const filter = contract.filters.Attested(recipient, null);
|
|
3507
|
+
const toBlock = await provider.getBlockNumber();
|
|
3508
|
+
let events;
|
|
3509
|
+
try {
|
|
3510
|
+
events = await contract.queryFilter(filter, fromBlock, toBlock);
|
|
3511
|
+
} catch (err) {
|
|
3512
|
+
throw new OmaTrustError("NETWORK_ERROR", "Failed to query attestation events", { err });
|
|
1694
3513
|
}
|
|
1695
|
-
const
|
|
1696
|
-
|
|
1697
|
-
|
|
3514
|
+
const eas = new easSdk.EAS(easContractAddress);
|
|
3515
|
+
eas.connect(provider);
|
|
3516
|
+
const schemaFilter = schemas.map((s) => s.toLowerCase());
|
|
3517
|
+
const results = [];
|
|
3518
|
+
for (const event of events) {
|
|
3519
|
+
if (!("args" in event) || !Array.isArray(event.args)) continue;
|
|
3520
|
+
const args = event.args;
|
|
3521
|
+
const uid = args?.[2];
|
|
3522
|
+
const schemaUid = args?.[3];
|
|
3523
|
+
if (!uid || !schemaUid) continue;
|
|
3524
|
+
if (!schemaFilter.includes(schemaUid.toLowerCase())) continue;
|
|
3525
|
+
const attestation = await eas.getAttestation(uid);
|
|
3526
|
+
if (!attestation || !attestation.uid) continue;
|
|
3527
|
+
let data = {};
|
|
3528
|
+
const rawData = attestation.data;
|
|
3529
|
+
if (rawData && rawData !== "0x") {
|
|
3530
|
+
try {
|
|
3531
|
+
data = decodeAttestationData(
|
|
3532
|
+
"string subject, string controller, string method",
|
|
3533
|
+
rawData
|
|
3534
|
+
);
|
|
3535
|
+
} catch {
|
|
3536
|
+
try {
|
|
3537
|
+
data = decodeAttestationData(
|
|
3538
|
+
"string subject, string keyId, string publicKeyJwk, string[] keyPurpose, string[] proofs, uint256 issuedAt, uint256 effectiveAt, uint256 expiresAt",
|
|
3539
|
+
rawData
|
|
3540
|
+
);
|
|
3541
|
+
} catch {
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
const toBigIntSafe = (value) => {
|
|
3546
|
+
if (typeof value === "bigint") return value;
|
|
3547
|
+
if (typeof value === "number") return BigInt(value);
|
|
3548
|
+
if (typeof value === "string" && value.length > 0) return BigInt(value);
|
|
3549
|
+
return 0n;
|
|
3550
|
+
};
|
|
3551
|
+
results.push({
|
|
3552
|
+
uid: attestation.uid,
|
|
3553
|
+
schema: attestation.schema,
|
|
3554
|
+
attester: attestation.attester,
|
|
3555
|
+
recipient: attestation.recipient,
|
|
3556
|
+
revocable: Boolean(attestation.revocable),
|
|
3557
|
+
revocationTime: toBigIntSafe(attestation.revocationTime),
|
|
3558
|
+
expirationTime: toBigIntSafe(attestation.expirationTime),
|
|
3559
|
+
time: toBigIntSafe(attestation.time),
|
|
3560
|
+
refUID: attestation.refUID,
|
|
3561
|
+
data
|
|
3562
|
+
});
|
|
1698
3563
|
}
|
|
1699
|
-
return
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
3564
|
+
return results;
|
|
3565
|
+
}
|
|
3566
|
+
async function findKeyBindingWithTransferProof(keyBindingSchemaUid, easContractAddress, provider, subjectAddress, controllerDid, controllerMethod, fromBlock) {
|
|
3567
|
+
const keyBindings = await queryByRecipientAndSchema(
|
|
3568
|
+
easContractAddress,
|
|
3569
|
+
provider,
|
|
3570
|
+
subjectAddress,
|
|
3571
|
+
[keyBindingSchemaUid],
|
|
3572
|
+
fromBlock
|
|
3573
|
+
);
|
|
3574
|
+
const withProof = keyBindings.filter((att) => {
|
|
3575
|
+
if (!controllerMatchesKeyBinding(att, controllerDid, controllerMethod)) return false;
|
|
3576
|
+
const proofs = att.data?.proofs;
|
|
3577
|
+
if (Array.isArray(proofs)) {
|
|
3578
|
+
return proofs.some((p) => typeof p === "string" && /^0x[0-9a-fA-F]{64}$/.test(p));
|
|
3579
|
+
}
|
|
3580
|
+
return typeof att.data?.proofTxHash === "string";
|
|
3581
|
+
}).sort((a, b) => Number(b.time - a.time));
|
|
3582
|
+
return withProof[0] ?? null;
|
|
1703
3583
|
}
|
|
1704
3584
|
|
|
1705
3585
|
// src/reputation/proof/tx-interaction.ts
|
|
@@ -1809,6 +3689,42 @@ function createX402OfferProof(offer) {
|
|
|
1809
3689
|
};
|
|
1810
3690
|
}
|
|
1811
3691
|
|
|
3692
|
+
// src/reputation/proof/x402-verify.ts
|
|
3693
|
+
async function verifyX402Artifact(artifact, options) {
|
|
3694
|
+
if (!artifact || typeof artifact !== "object") {
|
|
3695
|
+
return {
|
|
3696
|
+
valid: false,
|
|
3697
|
+
error: { code: "INVALID_ARTIFACT", message: "Artifact must be a non-null object" }
|
|
3698
|
+
};
|
|
3699
|
+
}
|
|
3700
|
+
const format = artifact.format;
|
|
3701
|
+
switch (format) {
|
|
3702
|
+
case "jws": {
|
|
3703
|
+
const jwsArtifact = artifact;
|
|
3704
|
+
const jwsOptions = options.resolveOptions ? { resolveOptions: options.resolveOptions } : void 0;
|
|
3705
|
+
if (options.artifactType === "offer") {
|
|
3706
|
+
return verifyX402JwsOffer(jwsArtifact, jwsOptions);
|
|
3707
|
+
}
|
|
3708
|
+
return verifyX402JwsReceipt(jwsArtifact, jwsOptions);
|
|
3709
|
+
}
|
|
3710
|
+
case "eip712": {
|
|
3711
|
+
const eip712Artifact = artifact;
|
|
3712
|
+
if (options.artifactType === "offer") {
|
|
3713
|
+
return verifyX402Eip712Offer(eip712Artifact);
|
|
3714
|
+
}
|
|
3715
|
+
return verifyX402Eip712Receipt(eip712Artifact);
|
|
3716
|
+
}
|
|
3717
|
+
default:
|
|
3718
|
+
return {
|
|
3719
|
+
valid: false,
|
|
3720
|
+
error: {
|
|
3721
|
+
code: "UNSUPPORTED_FORMAT",
|
|
3722
|
+
message: `Unsupported x402 artifact format: "${format}"`
|
|
3723
|
+
}
|
|
3724
|
+
};
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
|
|
1812
3728
|
// src/reputation/proof/evidence-pointer.ts
|
|
1813
3729
|
function createEvidencePointerProof(url) {
|
|
1814
3730
|
if (!url || typeof url !== "string") {
|
|
@@ -1822,63 +3738,12 @@ function createEvidencePointerProof(url) {
|
|
|
1822
3738
|
issuedAt: Math.floor(Date.now() / 1e3)
|
|
1823
3739
|
};
|
|
1824
3740
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
const prefix = options.recordPrefix ?? "_controllers";
|
|
1831
|
-
const host = `${prefix}.${domain.toLowerCase().replace(/\.$/, "")}`;
|
|
1832
|
-
let records;
|
|
1833
|
-
try {
|
|
1834
|
-
records = await (options.resolveTxt ?? promises.resolveTxt)(host);
|
|
1835
|
-
} catch (err) {
|
|
1836
|
-
throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
|
|
1837
|
-
}
|
|
1838
|
-
for (const recordParts of records) {
|
|
1839
|
-
const record = recordParts.join("");
|
|
1840
|
-
const parsed = parseDnsTxtRecord(record);
|
|
1841
|
-
if (parsed.version === "1" && parsed.controller && normalizeDid(parsed.controller) === expected) {
|
|
1842
|
-
return { valid: true, record };
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1845
|
-
return { valid: false, reason: "No TXT record matched expected controller DID" };
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
// src/reputation/proof/dns-txt-shared.ts
|
|
1849
|
-
async function verifyDnsTxtControllerDid2(domain, expectedControllerDid, options = {}) {
|
|
1850
|
-
if (!domain || typeof domain !== "string") {
|
|
1851
|
-
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
1852
|
-
}
|
|
1853
|
-
if (!options.resolveTxt) {
|
|
1854
|
-
throw new OmaTrustError("NETWORK_ERROR", "No DNS TXT resolver was provided", { domain });
|
|
1855
|
-
}
|
|
1856
|
-
const expected = normalizeDid(expectedControllerDid);
|
|
1857
|
-
const prefix = options.recordPrefix ?? "_controllers";
|
|
1858
|
-
const host = `${prefix}.${domain.toLowerCase().replace(/\.$/, "")}`;
|
|
1859
|
-
let records;
|
|
1860
|
-
try {
|
|
1861
|
-
records = await options.resolveTxt(host);
|
|
1862
|
-
} catch (err) {
|
|
1863
|
-
throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
|
|
1864
|
-
}
|
|
1865
|
-
for (const recordParts of records) {
|
|
1866
|
-
const record = recordParts.join("");
|
|
1867
|
-
const parsed = parseDnsTxtRecord(record);
|
|
1868
|
-
if (parsed.version === "1" && parsed.controller && normalizeDid(parsed.controller) === expected) {
|
|
1869
|
-
return { valid: true, record };
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
return { valid: false, reason: "No TXT record matched expected controller DID" };
|
|
3741
|
+
function verifyDnsTxtControllerDid2(domain, expectedControllerDid, options = {}) {
|
|
3742
|
+
return verifyDnsTxtControllerDid(domain, expectedControllerDid, {
|
|
3743
|
+
...options,
|
|
3744
|
+
resolveTxt: options.resolveTxt ?? promises.resolveTxt
|
|
3745
|
+
});
|
|
1873
3746
|
}
|
|
1874
|
-
|
|
1875
|
-
// src/reputation/proof/subject-ownership.ts
|
|
1876
|
-
var EIP1967_ADMIN_SLOT = "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103";
|
|
1877
|
-
var OWNERSHIP_PATTERNS = [
|
|
1878
|
-
{ method: "owner", signature: "function owner() view returns (address)" },
|
|
1879
|
-
{ method: "admin", signature: "function admin() view returns (address)" },
|
|
1880
|
-
{ method: "getOwner", signature: "function getOwner() view returns (address)" }
|
|
1881
|
-
];
|
|
1882
3747
|
function assertConnectedWalletDid(input) {
|
|
1883
3748
|
const normalized = normalizeDid(input);
|
|
1884
3749
|
if (extractDidMethod(normalized) !== "pkh") {
|
|
@@ -1891,43 +3756,6 @@ function assertConnectedWalletDid(input) {
|
|
|
1891
3756
|
function normalizeSubjectDid(input) {
|
|
1892
3757
|
return normalizeDid(input);
|
|
1893
3758
|
}
|
|
1894
|
-
async function readAddressFromContract(provider, contractAddress, signature, method) {
|
|
1895
|
-
try {
|
|
1896
|
-
const iface = new ethers.Interface([signature]);
|
|
1897
|
-
const data = iface.encodeFunctionData(method, []);
|
|
1898
|
-
const result = await provider.call({ to: contractAddress, data });
|
|
1899
|
-
const [value] = iface.decodeFunctionResult(method, result);
|
|
1900
|
-
if (typeof value === "string" && ethers.isAddress(value) && value !== ethers.ZeroAddress) {
|
|
1901
|
-
return ethers.getAddress(value);
|
|
1902
|
-
}
|
|
1903
|
-
} catch {
|
|
1904
|
-
}
|
|
1905
|
-
return null;
|
|
1906
|
-
}
|
|
1907
|
-
async function discoverControllingWallet(provider, contractAddress, chainId) {
|
|
1908
|
-
for (const pattern of OWNERSHIP_PATTERNS) {
|
|
1909
|
-
const address = await readAddressFromContract(
|
|
1910
|
-
provider,
|
|
1911
|
-
contractAddress,
|
|
1912
|
-
pattern.signature,
|
|
1913
|
-
pattern.method
|
|
1914
|
-
);
|
|
1915
|
-
if (address) {
|
|
1916
|
-
return buildEvmDidPkh(chainId, address);
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1919
|
-
try {
|
|
1920
|
-
const adminValue = await provider.getStorage(contractAddress, EIP1967_ADMIN_SLOT);
|
|
1921
|
-
if (adminValue && adminValue !== "0x" && adminValue !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
1922
|
-
const adminAddress = ethers.getAddress(`0x${adminValue.slice(-40)}`);
|
|
1923
|
-
if (adminAddress !== ethers.ZeroAddress) {
|
|
1924
|
-
return buildEvmDidPkh(chainId, adminAddress);
|
|
1925
|
-
}
|
|
1926
|
-
}
|
|
1927
|
-
} catch {
|
|
1928
|
-
}
|
|
1929
|
-
return null;
|
|
1930
|
-
}
|
|
1931
3759
|
async function verifyDidWebOwnership(params) {
|
|
1932
3760
|
const subjectDid = normalizeSubjectDid(params.subjectDid);
|
|
1933
3761
|
const connectedWalletDid = assertConnectedWalletDid(params.connectedWalletDid);
|
|
@@ -1939,7 +3767,7 @@ async function verifyDidWebOwnership(params) {
|
|
|
1939
3767
|
}
|
|
1940
3768
|
let dnsReason;
|
|
1941
3769
|
try {
|
|
1942
|
-
const dnsResult = await
|
|
3770
|
+
const dnsResult = await verifyDnsTxtControllerDid(domain, connectedWalletDid, {
|
|
1943
3771
|
resolveTxt: params.resolveTxt,
|
|
1944
3772
|
recordPrefix: params.recordPrefix
|
|
1945
3773
|
});
|
|
@@ -2005,8 +3833,8 @@ async function verifyDidPkhOwnership(params) {
|
|
|
2005
3833
|
subjectDid: params.subjectDid
|
|
2006
3834
|
});
|
|
2007
3835
|
}
|
|
2008
|
-
const
|
|
2009
|
-
const isContract =
|
|
3836
|
+
const code2 = await params.provider.getCode(subjectAddress);
|
|
3837
|
+
const isContract = code2 !== "0x" && code2 !== "0x0";
|
|
2010
3838
|
if (!isContract) {
|
|
2011
3839
|
if (ethers.getAddress(subjectAddress) === ethers.getAddress(connectedWalletAddress)) {
|
|
2012
3840
|
return {
|
|
@@ -2025,7 +3853,7 @@ async function verifyDidPkhOwnership(params) {
|
|
|
2025
3853
|
connectedWalletDid
|
|
2026
3854
|
};
|
|
2027
3855
|
}
|
|
2028
|
-
const controllingWalletDid = await
|
|
3856
|
+
const controllingWalletDid = await discoverControllingWalletDid(params.provider, subjectAddress, chainId);
|
|
2029
3857
|
if (params.txHash) {
|
|
2030
3858
|
if (!controllingWalletDid) {
|
|
2031
3859
|
return {
|
|
@@ -2108,7 +3936,7 @@ async function verifyDidPkhOwnership(params) {
|
|
|
2108
3936
|
};
|
|
2109
3937
|
}
|
|
2110
3938
|
for (const pattern of OWNERSHIP_PATTERNS) {
|
|
2111
|
-
const ownerAddress = await
|
|
3939
|
+
const ownerAddress = await readOwnerFromContract(
|
|
2112
3940
|
params.provider,
|
|
2113
3941
|
subjectAddress,
|
|
2114
3942
|
pattern.signature,
|
|
@@ -2182,6 +4010,300 @@ async function verifySubjectOwnership(params) {
|
|
|
2182
4010
|
subjectDid
|
|
2183
4011
|
});
|
|
2184
4012
|
}
|
|
4013
|
+
function extractEip712Signer(proof) {
|
|
4014
|
+
const { domain, message, signature } = proof.proofObject;
|
|
4015
|
+
const typedData = {
|
|
4016
|
+
domain,
|
|
4017
|
+
types: {
|
|
4018
|
+
OmaTrustProof: [
|
|
4019
|
+
{ name: "signer", type: "address" },
|
|
4020
|
+
{ name: "authorizedEntity", type: "string" },
|
|
4021
|
+
{ name: "signingPurpose", type: "string" },
|
|
4022
|
+
{ name: "creationTimestamp", type: "uint256" },
|
|
4023
|
+
{ name: "expirationTimestamp", type: "uint256" },
|
|
4024
|
+
{ name: "randomValue", type: "bytes32" },
|
|
4025
|
+
{ name: "statement", type: "string" }
|
|
4026
|
+
]
|
|
4027
|
+
},
|
|
4028
|
+
message
|
|
4029
|
+
};
|
|
4030
|
+
try {
|
|
4031
|
+
const result = verifyEip712Signature(typedData, signature);
|
|
4032
|
+
return result.valid && result.signer ? result.signer : null;
|
|
4033
|
+
} catch (err) {
|
|
4034
|
+
console.warn("[omatrust] EIP-712 signer extraction failed:", err);
|
|
4035
|
+
return null;
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
function extractJwsHeaderJwk(proof) {
|
|
4039
|
+
const jws = proof.proofObject;
|
|
4040
|
+
if (typeof jws !== "string") return null;
|
|
4041
|
+
const parts = jws.split(".");
|
|
4042
|
+
if (parts.length !== 3) return null;
|
|
4043
|
+
try {
|
|
4044
|
+
const headerB64 = parts[0].replace(/-/g, "+").replace(/_/g, "/");
|
|
4045
|
+
const padded = headerB64.padEnd(
|
|
4046
|
+
headerB64.length + (4 - headerB64.length % 4) % 4,
|
|
4047
|
+
"="
|
|
4048
|
+
);
|
|
4049
|
+
let headerJson;
|
|
4050
|
+
if (typeof atob === "function") {
|
|
4051
|
+
headerJson = decodeURIComponent(
|
|
4052
|
+
Array.from(atob(padded)).map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`).join("")
|
|
4053
|
+
);
|
|
4054
|
+
} else {
|
|
4055
|
+
headerJson = Buffer.from(padded, "base64").toString("utf8");
|
|
4056
|
+
}
|
|
4057
|
+
const header = JSON.parse(headerJson);
|
|
4058
|
+
return header.jwk ?? null;
|
|
4059
|
+
} catch {
|
|
4060
|
+
return null;
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
function extractJwsPayload(proof) {
|
|
4064
|
+
const jws = proof.proofObject;
|
|
4065
|
+
if (typeof jws !== "string") return null;
|
|
4066
|
+
const parts = jws.split(".");
|
|
4067
|
+
if (parts.length !== 3) return null;
|
|
4068
|
+
try {
|
|
4069
|
+
const payloadB64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
4070
|
+
const padded = payloadB64.padEnd(
|
|
4071
|
+
payloadB64.length + (4 - payloadB64.length % 4) % 4,
|
|
4072
|
+
"="
|
|
4073
|
+
);
|
|
4074
|
+
let payloadJson;
|
|
4075
|
+
if (typeof atob === "function") {
|
|
4076
|
+
payloadJson = decodeURIComponent(
|
|
4077
|
+
Array.from(atob(padded)).map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`).join("")
|
|
4078
|
+
);
|
|
4079
|
+
} else {
|
|
4080
|
+
payloadJson = Buffer.from(padded, "base64").toString("utf8");
|
|
4081
|
+
}
|
|
4082
|
+
return JSON.parse(payloadJson);
|
|
4083
|
+
} catch {
|
|
4084
|
+
return null;
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
function signerMatchesDid(signerAddress, did) {
|
|
4088
|
+
try {
|
|
4089
|
+
const didAddress = extractAddressFromDid(did);
|
|
4090
|
+
return ethers.getAddress(signerAddress).toLowerCase() === ethers.getAddress(didAddress).toLowerCase();
|
|
4091
|
+
} catch {
|
|
4092
|
+
return false;
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
function jwkMatchesDid(jwk, did) {
|
|
4096
|
+
const method = extractDidMethod(did);
|
|
4097
|
+
if (method === "jwk") {
|
|
4098
|
+
try {
|
|
4099
|
+
const didJwk = didJwkToJwk(did);
|
|
4100
|
+
return publicJwkEquals(jwk, didJwk);
|
|
4101
|
+
} catch {
|
|
4102
|
+
return false;
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
return false;
|
|
4106
|
+
}
|
|
4107
|
+
function proofSignerMatchesDid(proof, did) {
|
|
4108
|
+
switch (proof.proofType) {
|
|
4109
|
+
case "pop-eip712": {
|
|
4110
|
+
const signer = extractEip712Signer(proof);
|
|
4111
|
+
if (!signer) return false;
|
|
4112
|
+
return signerMatchesDid(signer, did);
|
|
4113
|
+
}
|
|
4114
|
+
case "pop-jws": {
|
|
4115
|
+
const jwsProof = proof;
|
|
4116
|
+
const jwk = extractJwsHeaderJwk(jwsProof);
|
|
4117
|
+
if (jwk && jwkMatchesDid(jwk, did)) return true;
|
|
4118
|
+
const payload = extractJwsPayload(jwsProof);
|
|
4119
|
+
if (payload && typeof payload.iss === "string") {
|
|
4120
|
+
if (payload.iss === did) return true;
|
|
4121
|
+
}
|
|
4122
|
+
return false;
|
|
4123
|
+
}
|
|
4124
|
+
case "tx-encoded-value":
|
|
4125
|
+
case "tx-interaction":
|
|
4126
|
+
return false;
|
|
4127
|
+
case "evidence-pointer":
|
|
4128
|
+
return false;
|
|
4129
|
+
default:
|
|
4130
|
+
return false;
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
function proofAuthorizedEntityMatchesDid(proof, did) {
|
|
4134
|
+
switch (proof.proofType) {
|
|
4135
|
+
case "pop-eip712": {
|
|
4136
|
+
const eip712 = proof;
|
|
4137
|
+
const authorizedEntity = eip712.proofObject.message.authorizedEntity;
|
|
4138
|
+
return typeof authorizedEntity === "string" && authorizedEntity === did;
|
|
4139
|
+
}
|
|
4140
|
+
case "pop-jws": {
|
|
4141
|
+
const payload = extractJwsPayload(proof);
|
|
4142
|
+
if (!payload) return false;
|
|
4143
|
+
return typeof payload.aud === "string" && payload.aud === did;
|
|
4144
|
+
}
|
|
4145
|
+
default:
|
|
4146
|
+
return false;
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
function verifyLinkedIdentifierProofs(data) {
|
|
4150
|
+
if (!data.subject || !data.linkedId) {
|
|
4151
|
+
return {
|
|
4152
|
+
valid: false,
|
|
4153
|
+
checks: [],
|
|
4154
|
+
reasons: ["subject and linkedId are required"]
|
|
4155
|
+
};
|
|
4156
|
+
}
|
|
4157
|
+
if (!Array.isArray(data.proofs) || data.proofs.length === 0) {
|
|
4158
|
+
return {
|
|
4159
|
+
valid: false,
|
|
4160
|
+
checks: [],
|
|
4161
|
+
reasons: ["At least one proof is required"]
|
|
4162
|
+
};
|
|
4163
|
+
}
|
|
4164
|
+
const checks = [];
|
|
4165
|
+
let subjectProved = false;
|
|
4166
|
+
let linkedIdProved = false;
|
|
4167
|
+
for (let i = 0; i < data.proofs.length; i++) {
|
|
4168
|
+
const proof = data.proofs[i];
|
|
4169
|
+
if (proofSignerMatchesDid(proof, data.subject)) {
|
|
4170
|
+
checks.push({
|
|
4171
|
+
proofIndex: i,
|
|
4172
|
+
proofType: proof.proofType,
|
|
4173
|
+
checkType: "signer-is-subject",
|
|
4174
|
+
valid: true
|
|
4175
|
+
});
|
|
4176
|
+
subjectProved = true;
|
|
4177
|
+
if (!proofAuthorizedEntityMatchesDid(proof, data.linkedId)) {
|
|
4178
|
+
checks.push({
|
|
4179
|
+
proofIndex: i,
|
|
4180
|
+
proofType: proof.proofType,
|
|
4181
|
+
checkType: "signer-is-subject",
|
|
4182
|
+
valid: false,
|
|
4183
|
+
reason: "Proof from subject does not authorize linkedId (aud mismatch)"
|
|
4184
|
+
});
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
if (proofSignerMatchesDid(proof, data.linkedId)) {
|
|
4188
|
+
checks.push({
|
|
4189
|
+
proofIndex: i,
|
|
4190
|
+
proofType: proof.proofType,
|
|
4191
|
+
checkType: "signer-is-linkedId",
|
|
4192
|
+
valid: true
|
|
4193
|
+
});
|
|
4194
|
+
linkedIdProved = true;
|
|
4195
|
+
if (!proofAuthorizedEntityMatchesDid(proof, data.subject)) {
|
|
4196
|
+
checks.push({
|
|
4197
|
+
proofIndex: i,
|
|
4198
|
+
proofType: proof.proofType,
|
|
4199
|
+
checkType: "signer-is-linkedId",
|
|
4200
|
+
valid: false,
|
|
4201
|
+
reason: "Proof from linkedId does not authorize subject (aud mismatch)"
|
|
4202
|
+
});
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
if (proof.proofType === "evidence-pointer") {
|
|
4206
|
+
checks.push({
|
|
4207
|
+
proofIndex: i,
|
|
4208
|
+
proofType: proof.proofType,
|
|
4209
|
+
checkType: "signer-is-subject",
|
|
4210
|
+
valid: true,
|
|
4211
|
+
reason: "Evidence pointer accepted (requires URL fetch for full verification)"
|
|
4212
|
+
});
|
|
4213
|
+
subjectProved = true;
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
const reasons = [];
|
|
4217
|
+
if (!subjectProved) {
|
|
4218
|
+
reasons.push("No proof demonstrates control by subject");
|
|
4219
|
+
}
|
|
4220
|
+
if (!linkedIdProved) {
|
|
4221
|
+
reasons.push("No proof demonstrates control by linkedId");
|
|
4222
|
+
}
|
|
4223
|
+
return {
|
|
4224
|
+
valid: reasons.length === 0,
|
|
4225
|
+
checks,
|
|
4226
|
+
reasons
|
|
4227
|
+
};
|
|
4228
|
+
}
|
|
4229
|
+
function verifyKeyBindingProofs(data) {
|
|
4230
|
+
if (!data.subject || !data.keyId) {
|
|
4231
|
+
return {
|
|
4232
|
+
valid: false,
|
|
4233
|
+
checks: [],
|
|
4234
|
+
reasons: ["subject and keyId are required"]
|
|
4235
|
+
};
|
|
4236
|
+
}
|
|
4237
|
+
if (!Array.isArray(data.proofs) || data.proofs.length === 0) {
|
|
4238
|
+
return {
|
|
4239
|
+
valid: false,
|
|
4240
|
+
checks: [],
|
|
4241
|
+
reasons: ["At least one proof is required"]
|
|
4242
|
+
};
|
|
4243
|
+
}
|
|
4244
|
+
const checks = [];
|
|
4245
|
+
let subjectAuthorizedKey = false;
|
|
4246
|
+
for (let i = 0; i < data.proofs.length; i++) {
|
|
4247
|
+
const proof = data.proofs[i];
|
|
4248
|
+
if (proofSignerMatchesDid(proof, data.subject)) {
|
|
4249
|
+
const authorizesKey = proofAuthorizedEntityMatchesDid(proof, data.keyId);
|
|
4250
|
+
checks.push({
|
|
4251
|
+
proofIndex: i,
|
|
4252
|
+
proofType: proof.proofType,
|
|
4253
|
+
checkType: "signer-is-subject-for-key",
|
|
4254
|
+
valid: authorizesKey,
|
|
4255
|
+
reason: authorizesKey ? void 0 : "Proof signed by subject but does not authorize the keyId"
|
|
4256
|
+
});
|
|
4257
|
+
if (authorizesKey) {
|
|
4258
|
+
subjectAuthorizedKey = true;
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4261
|
+
if (proofSignerMatchesDid(proof, data.keyId)) {
|
|
4262
|
+
checks.push({
|
|
4263
|
+
proofIndex: i,
|
|
4264
|
+
proofType: proof.proofType,
|
|
4265
|
+
checkType: "signer-is-keyId",
|
|
4266
|
+
valid: true,
|
|
4267
|
+
reason: "Key proved possession (supplementary, not sufficient alone)"
|
|
4268
|
+
});
|
|
4269
|
+
}
|
|
4270
|
+
if (proof.proofType === "evidence-pointer") {
|
|
4271
|
+
checks.push({
|
|
4272
|
+
proofIndex: i,
|
|
4273
|
+
proofType: proof.proofType,
|
|
4274
|
+
checkType: "signer-is-subject-for-key",
|
|
4275
|
+
valid: true,
|
|
4276
|
+
reason: "Evidence pointer accepted (requires URL fetch for full verification)"
|
|
4277
|
+
});
|
|
4278
|
+
subjectAuthorizedKey = true;
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
if (data.publicKeyJwk && extractDidMethod(data.keyId) === "jwk") {
|
|
4282
|
+
try {
|
|
4283
|
+
const keyIdJwk = didJwkToJwk(data.keyId);
|
|
4284
|
+
const jwkConsistent = publicJwkEquals(data.publicKeyJwk, keyIdJwk);
|
|
4285
|
+
if (!jwkConsistent) {
|
|
4286
|
+
return {
|
|
4287
|
+
valid: false,
|
|
4288
|
+
checks,
|
|
4289
|
+
reasons: ["publicKeyJwk does not match the key material in keyId (did:jwk)"]
|
|
4290
|
+
};
|
|
4291
|
+
}
|
|
4292
|
+
} catch {
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
const reasons = [];
|
|
4296
|
+
if (!subjectAuthorizedKey) {
|
|
4297
|
+
reasons.push(
|
|
4298
|
+
"No proof demonstrates that subject authorized the key binding"
|
|
4299
|
+
);
|
|
4300
|
+
}
|
|
4301
|
+
return {
|
|
4302
|
+
valid: reasons.length === 0,
|
|
4303
|
+
checks,
|
|
4304
|
+
reasons
|
|
4305
|
+
};
|
|
4306
|
+
}
|
|
2185
4307
|
|
|
2186
4308
|
// src/app-registry/index.ts
|
|
2187
4309
|
var app_registry_exports = {};
|
|
@@ -2269,8 +4391,8 @@ function getInterfaceTypes(bitmap) {
|
|
|
2269
4391
|
}
|
|
2270
4392
|
|
|
2271
4393
|
// src/app-registry/status.ts
|
|
2272
|
-
function registryCodeToStatus(
|
|
2273
|
-
switch (
|
|
4394
|
+
function registryCodeToStatus(code2) {
|
|
4395
|
+
switch (code2) {
|
|
2274
4396
|
case 0:
|
|
2275
4397
|
return "Active";
|
|
2276
4398
|
case 1:
|
|
@@ -2278,7 +4400,7 @@ function registryCodeToStatus(code) {
|
|
|
2278
4400
|
case 2:
|
|
2279
4401
|
return "Replaced";
|
|
2280
4402
|
default:
|
|
2281
|
-
throw new OmaTrustError("INVALID_INPUT", "Invalid registry status code", { code });
|
|
4403
|
+
throw new OmaTrustError("INVALID_INPUT", "Invalid registry status code", { code: code2 });
|
|
2282
4404
|
}
|
|
2283
4405
|
}
|
|
2284
4406
|
function registryStatusToCode(status) {
|
|
@@ -2380,6 +4502,9 @@ async function computeDataHashFromUrl(url, algorithm) {
|
|
|
2380
4502
|
if (!url || typeof url !== "string") {
|
|
2381
4503
|
throw new OmaTrustError("INVALID_INPUT", "url must be a non-empty string", { url });
|
|
2382
4504
|
}
|
|
4505
|
+
if (algorithm !== "keccak256" && algorithm !== "sha256") {
|
|
4506
|
+
throw new OmaTrustError("INVALID_INPUT", `Unsupported hash algorithm: "${algorithm}". Must be "keccak256" or "sha256".`, { algorithm });
|
|
4507
|
+
}
|
|
2383
4508
|
const json = await fetchJson(url);
|
|
2384
4509
|
const jcsJson = canonicalizeJson(json);
|
|
2385
4510
|
return hashJcs(jcsJson, algorithm);
|