@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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var easSdk = require('@ethereum-attestation-service/eas-sdk');
|
|
4
4
|
var ethers = require('ethers');
|
|
5
|
+
var jose = require('jose');
|
|
5
6
|
var canonicalize = require('canonicalize');
|
|
6
7
|
var promises = require('dns/promises');
|
|
7
8
|
|
|
@@ -204,6 +205,11 @@ function assertString(value, name, code = "INVALID_INPUT") {
|
|
|
204
205
|
throw new OmaTrustError(code, `${name} must be a non-empty string`, { value });
|
|
205
206
|
}
|
|
206
207
|
}
|
|
208
|
+
function assertObject(value, name, code = "INVALID_INPUT") {
|
|
209
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
210
|
+
throw new OmaTrustError(code, `${name} must be an object`, { value });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
207
213
|
|
|
208
214
|
// src/identity/caip.ts
|
|
209
215
|
var CAIP_10_REGEX = /^(?<namespace>[a-z0-9-]+):(?<reference>[a-zA-Z0-9-]+):(?<address>.+)$/;
|
|
@@ -222,6 +228,145 @@ function parseCaip10(input) {
|
|
|
222
228
|
}
|
|
223
229
|
return { namespace, reference, address };
|
|
224
230
|
}
|
|
231
|
+
var VALID_KTY = /* @__PURE__ */ new Set(["EC", "OKP", "RSA"]);
|
|
232
|
+
var PRIVATE_KEY_FIELDS = /* @__PURE__ */ new Set(["d", "p", "q", "dp", "dq", "qi", "oth"]);
|
|
233
|
+
var REQUIRED_PUBLIC_FIELDS = {
|
|
234
|
+
EC: ["crv", "x", "y"],
|
|
235
|
+
OKP: ["crv", "x"],
|
|
236
|
+
RSA: ["n", "e"]
|
|
237
|
+
};
|
|
238
|
+
function validatePublicJwk(jwk) {
|
|
239
|
+
if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) {
|
|
240
|
+
return { valid: false, error: "JWK must be a non-null object" };
|
|
241
|
+
}
|
|
242
|
+
const obj = jwk;
|
|
243
|
+
const kty = obj.kty;
|
|
244
|
+
if (typeof kty !== "string" || !VALID_KTY.has(kty)) {
|
|
245
|
+
return {
|
|
246
|
+
valid: false,
|
|
247
|
+
error: `Invalid or missing kty (must be one of: ${[...VALID_KTY].join(", ")})`
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
for (const field of PRIVATE_KEY_FIELDS) {
|
|
251
|
+
if (field in obj) {
|
|
252
|
+
return {
|
|
253
|
+
valid: false,
|
|
254
|
+
error: `JWK contains private key field "${field}" \u2014 only public keys are allowed`
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const required = REQUIRED_PUBLIC_FIELDS[kty];
|
|
259
|
+
if (required) {
|
|
260
|
+
for (const field of required) {
|
|
261
|
+
if (!(field in obj) || obj[field] === void 0 || obj[field] === null || obj[field] === "") {
|
|
262
|
+
return {
|
|
263
|
+
valid: false,
|
|
264
|
+
error: `Missing required public key field "${field}" for kty="${kty}"`
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { valid: true };
|
|
270
|
+
}
|
|
271
|
+
function canonicalizeJwkJson(jwk) {
|
|
272
|
+
const sorted = Object.keys(jwk).sort();
|
|
273
|
+
const obj = {};
|
|
274
|
+
for (const key of sorted) {
|
|
275
|
+
obj[key] = jwk[key];
|
|
276
|
+
}
|
|
277
|
+
return JSON.stringify(obj);
|
|
278
|
+
}
|
|
279
|
+
function jwkToDidJwk(jwk) {
|
|
280
|
+
assertObject(jwk, "jwk", "INVALID_JWK");
|
|
281
|
+
const validation = validatePublicJwk(jwk);
|
|
282
|
+
if (!validation.valid) {
|
|
283
|
+
throw new OmaTrustError("INVALID_JWK", validation.error ?? "Invalid public JWK", { jwk });
|
|
284
|
+
}
|
|
285
|
+
const canonical = canonicalizeJwkJson(jwk);
|
|
286
|
+
const encoded = jose.base64url.encode(new TextEncoder().encode(canonical));
|
|
287
|
+
return `did:jwk:${encoded}`;
|
|
288
|
+
}
|
|
289
|
+
function didJwkToJwk(didJwk) {
|
|
290
|
+
if (typeof didJwk !== "string" || !didJwk.startsWith("did:jwk:")) {
|
|
291
|
+
throw new OmaTrustError("INVALID_DID", "Expected a did:jwk DID", { input: didJwk });
|
|
292
|
+
}
|
|
293
|
+
const parts = didJwk.split(":");
|
|
294
|
+
if (parts.length !== 3) {
|
|
295
|
+
throw new OmaTrustError("INVALID_DID", "did:jwk must have exactly 3 colon-separated parts", {
|
|
296
|
+
input: didJwk
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
const encoded = parts[2];
|
|
300
|
+
if (!encoded || encoded.length === 0) {
|
|
301
|
+
throw new OmaTrustError("INVALID_DID", "Missing base64url-encoded JWK identifier", {
|
|
302
|
+
input: didJwk
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
let decoded;
|
|
306
|
+
try {
|
|
307
|
+
const bytes = jose.base64url.decode(encoded);
|
|
308
|
+
decoded = new TextDecoder().decode(bytes);
|
|
309
|
+
} catch {
|
|
310
|
+
throw new OmaTrustError("INVALID_DID", "Failed to base64url-decode did:jwk identifier", {
|
|
311
|
+
input: didJwk
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
let jwk;
|
|
315
|
+
try {
|
|
316
|
+
jwk = JSON.parse(decoded);
|
|
317
|
+
} catch {
|
|
318
|
+
throw new OmaTrustError("INVALID_DID", "Decoded did:jwk identifier is not valid JSON", {
|
|
319
|
+
input: didJwk
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) {
|
|
323
|
+
throw new OmaTrustError("INVALID_DID", "Decoded did:jwk must be a JSON object", {
|
|
324
|
+
input: didJwk
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
const validation = validatePublicJwk(jwk);
|
|
328
|
+
if (!validation.valid) {
|
|
329
|
+
throw new OmaTrustError("INVALID_DID", validation.error ?? "Invalid public JWK in did:jwk", {
|
|
330
|
+
input: didJwk
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return jwk;
|
|
334
|
+
}
|
|
335
|
+
function extractPublicKeyFields(jwk) {
|
|
336
|
+
const result = {};
|
|
337
|
+
const metadataFields = /* @__PURE__ */ new Set(["kid", "use", "key_ops", "alg", "ext"]);
|
|
338
|
+
for (const [key, value] of Object.entries(jwk)) {
|
|
339
|
+
if (PRIVATE_KEY_FIELDS.has(key)) continue;
|
|
340
|
+
if (metadataFields.has(key)) continue;
|
|
341
|
+
result[key] = value;
|
|
342
|
+
}
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
function publicJwkEquals(a, b) {
|
|
346
|
+
assertObject(a, "a", "INVALID_JWK");
|
|
347
|
+
assertObject(b, "b", "INVALID_JWK");
|
|
348
|
+
const aObj = a;
|
|
349
|
+
const bObj = b;
|
|
350
|
+
for (const field of PRIVATE_KEY_FIELDS) {
|
|
351
|
+
if (field in aObj) {
|
|
352
|
+
throw new OmaTrustError(
|
|
353
|
+
"INVALID_JWK",
|
|
354
|
+
`First JWK contains private key field "${field}"`,
|
|
355
|
+
{ field }
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
if (field in bObj) {
|
|
359
|
+
throw new OmaTrustError(
|
|
360
|
+
"INVALID_JWK",
|
|
361
|
+
`Second JWK contains private key field "${field}"`,
|
|
362
|
+
{ field }
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const aPublic = extractPublicKeyFields(aObj);
|
|
367
|
+
const bPublic = extractPublicKeyFields(bObj);
|
|
368
|
+
return canonicalizeJwkJson(aPublic) === canonicalizeJwkJson(bPublic);
|
|
369
|
+
}
|
|
225
370
|
|
|
226
371
|
// src/identity/did.ts
|
|
227
372
|
var DID_REGEX = /^did:[a-z0-9]+:.+$/i;
|
|
@@ -293,7 +438,7 @@ function normalizeDidKey(input) {
|
|
|
293
438
|
}
|
|
294
439
|
function normalizeDid(input) {
|
|
295
440
|
assertString(input, "input", "INVALID_DID");
|
|
296
|
-
const trimmed = input.trim();
|
|
441
|
+
const trimmed = input.trim().split("#")[0];
|
|
297
442
|
if (!trimmed.startsWith("did:")) {
|
|
298
443
|
return normalizeDidWeb(trimmed);
|
|
299
444
|
}
|
|
@@ -310,6 +455,8 @@ function normalizeDid(input) {
|
|
|
310
455
|
return normalizeDidHandle(trimmed);
|
|
311
456
|
case "key":
|
|
312
457
|
return normalizeDidKey(trimmed);
|
|
458
|
+
case "jwk":
|
|
459
|
+
return normalizeDidJwk(trimmed);
|
|
313
460
|
default:
|
|
314
461
|
return trimmed;
|
|
315
462
|
}
|
|
@@ -399,6 +546,77 @@ function extractAddressFromDid(identifier) {
|
|
|
399
546
|
}
|
|
400
547
|
throw new OmaTrustError("INVALID_DID", "Unsupported identifier format", { identifier });
|
|
401
548
|
}
|
|
549
|
+
var BASE64URL_REGEX = /^[A-Za-z0-9_-]+$/;
|
|
550
|
+
var VALID_JWK_KTY = /* @__PURE__ */ new Set(["EC", "OKP", "RSA"]);
|
|
551
|
+
function validateDidJwk(did) {
|
|
552
|
+
const parts = did.split(":");
|
|
553
|
+
if (parts.length !== 3) {
|
|
554
|
+
return {
|
|
555
|
+
valid: false,
|
|
556
|
+
method: "jwk",
|
|
557
|
+
error: `did:jwk must have exactly 3 colon-separated parts, got ${parts.length}`
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
const [, , encoded] = parts;
|
|
561
|
+
if (!encoded || encoded.length === 0) {
|
|
562
|
+
return { valid: false, method: "jwk", error: "Missing base64url-encoded JWK identifier" };
|
|
563
|
+
}
|
|
564
|
+
if (!BASE64URL_REGEX.test(encoded)) {
|
|
565
|
+
return {
|
|
566
|
+
valid: false,
|
|
567
|
+
method: "jwk",
|
|
568
|
+
error: "Identifier contains invalid base64url characters"
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
let decoded;
|
|
572
|
+
try {
|
|
573
|
+
const bytes = jose.base64url.decode(encoded);
|
|
574
|
+
decoded = new TextDecoder().decode(bytes);
|
|
575
|
+
} catch {
|
|
576
|
+
return { valid: false, method: "jwk", error: "Failed to base64url-decode identifier" };
|
|
577
|
+
}
|
|
578
|
+
let jwk;
|
|
579
|
+
try {
|
|
580
|
+
jwk = JSON.parse(decoded);
|
|
581
|
+
} catch {
|
|
582
|
+
return { valid: false, method: "jwk", error: "Decoded identifier is not valid JSON" };
|
|
583
|
+
}
|
|
584
|
+
if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) {
|
|
585
|
+
return { valid: false, method: "jwk", error: "Decoded JWK must be a JSON object" };
|
|
586
|
+
}
|
|
587
|
+
const kty = jwk.kty;
|
|
588
|
+
if (typeof kty !== "string" || !VALID_JWK_KTY.has(kty)) {
|
|
589
|
+
return {
|
|
590
|
+
valid: false,
|
|
591
|
+
method: "jwk",
|
|
592
|
+
error: `Invalid or missing kty field (must be one of: EC, OKP, RSA)`
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
if ("d" in jwk) {
|
|
596
|
+
return {
|
|
597
|
+
valid: false,
|
|
598
|
+
method: "jwk",
|
|
599
|
+
error: "DID must reference a public key \u2014 private key component (d) is not allowed"
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
const jwkValidation = validatePublicJwk(jwk);
|
|
603
|
+
if (!jwkValidation.valid) {
|
|
604
|
+
return { valid: false, method: "jwk", error: jwkValidation.error };
|
|
605
|
+
}
|
|
606
|
+
return { valid: true, method: "jwk" };
|
|
607
|
+
}
|
|
608
|
+
function normalizeDidJwk(input) {
|
|
609
|
+
assertString(input, "input", "INVALID_DID");
|
|
610
|
+
const trimmed = input.trim();
|
|
611
|
+
if (!trimmed.startsWith("did:jwk:")) {
|
|
612
|
+
throw new OmaTrustError("INVALID_DID", "Expected did:jwk DID", { input });
|
|
613
|
+
}
|
|
614
|
+
const result = validateDidJwk(trimmed);
|
|
615
|
+
if (!result.valid) {
|
|
616
|
+
throw new OmaTrustError("INVALID_DID", result.error ?? "Invalid did:jwk", { input });
|
|
617
|
+
}
|
|
618
|
+
return trimmed;
|
|
619
|
+
}
|
|
402
620
|
|
|
403
621
|
// src/reputation/internal.ts
|
|
404
622
|
var ZERO_UID = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
@@ -685,19 +903,32 @@ async function submitDelegatedAttestation(params) {
|
|
|
685
903
|
payload = {};
|
|
686
904
|
}
|
|
687
905
|
if (!response.ok) {
|
|
688
|
-
|
|
689
|
-
|
|
906
|
+
const relayError = {
|
|
907
|
+
httpStatus: response.status,
|
|
908
|
+
code: payload.code,
|
|
909
|
+
error: payload.error ?? payload.message,
|
|
690
910
|
payload
|
|
691
|
-
}
|
|
911
|
+
};
|
|
912
|
+
throw new OmaTrustError("RELAY_ERROR", `Relay submission failed (HTTP ${response.status})`, relayError);
|
|
692
913
|
}
|
|
693
914
|
const uid = payload.uid ?? ZERO_UID;
|
|
694
915
|
const txHash = payload.txHash;
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
916
|
+
const hasConfirmationSignals = payload.status === "confirmed" || payload.success === true || typeof payload.blockNumber === "number";
|
|
917
|
+
const status = hasConfirmationSignals ? "confirmed" : "submitted";
|
|
918
|
+
const relay = {};
|
|
919
|
+
if (typeof payload.blockNumber === "number") relay.blockNumber = payload.blockNumber;
|
|
920
|
+
if (typeof payload.chain === "string") relay.chain = payload.chain;
|
|
921
|
+
if (typeof payload.success === "boolean") relay.success = payload.success;
|
|
922
|
+
for (const key of Object.keys(payload)) {
|
|
923
|
+
if (!["uid", "txHash", "status", "blockNumber", "chain", "success"].includes(key)) {
|
|
924
|
+
relay[key] = payload[key];
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
const result = { uid, txHash, status };
|
|
928
|
+
if (Object.keys(relay).length > 0) {
|
|
929
|
+
result.relay = relay;
|
|
930
|
+
}
|
|
931
|
+
return result;
|
|
701
932
|
}
|
|
702
933
|
var EAS_EVENT_ABI = [
|
|
703
934
|
"event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)"
|
|
@@ -799,7 +1030,7 @@ async function getAttestationsForDid(params) {
|
|
|
799
1030
|
provider,
|
|
800
1031
|
fromBlock,
|
|
801
1032
|
toBlock,
|
|
802
|
-
{ recipient: didToAddress(params.
|
|
1033
|
+
{ recipient: didToAddress(params.subjectDid), schemas: params.schemas }
|
|
803
1034
|
);
|
|
804
1035
|
}
|
|
805
1036
|
async function getAttestationsByAttester(params) {
|
|
@@ -1040,6 +1271,8 @@ function getExplorerTxUrl(chainId, txHash) {
|
|
|
1040
1271
|
function getExplorerAddressUrl(chainId, address) {
|
|
1041
1272
|
return `${getConfig(chainId).explorer}/address/${address}`;
|
|
1042
1273
|
}
|
|
1274
|
+
|
|
1275
|
+
// src/shared/did-document.ts
|
|
1043
1276
|
async function fetchDidDocument(domain) {
|
|
1044
1277
|
const normalized = domain.toLowerCase().replace(/\.$/, "");
|
|
1045
1278
|
const url = `https://${normalized}/.well-known/did.json`;
|
|
@@ -1050,15 +1283,16 @@ async function fetchDidDocument(domain) {
|
|
|
1050
1283
|
throw new OmaTrustError("NETWORK_ERROR", "Failed to fetch DID document", { domain, err });
|
|
1051
1284
|
}
|
|
1052
1285
|
if (!response.ok) {
|
|
1053
|
-
throw new OmaTrustError("NETWORK_ERROR",
|
|
1286
|
+
throw new OmaTrustError("NETWORK_ERROR", `DID document fetch failed: ${response.status}`, {
|
|
1054
1287
|
domain,
|
|
1055
1288
|
status: response.status
|
|
1056
1289
|
});
|
|
1057
1290
|
}
|
|
1058
|
-
|
|
1059
|
-
return body;
|
|
1291
|
+
return await response.json();
|
|
1060
1292
|
}
|
|
1061
|
-
|
|
1293
|
+
|
|
1294
|
+
// src/reputation/proof/did-json.ts
|
|
1295
|
+
function extractEvmAddressesFromDidDocument(didDocument) {
|
|
1062
1296
|
const methods = didDocument.verificationMethod;
|
|
1063
1297
|
if (!Array.isArray(methods)) {
|
|
1064
1298
|
return [];
|
|
@@ -1082,14 +1316,52 @@ function extractAddressesFromDidDocument(didDocument) {
|
|
|
1082
1316
|
}
|
|
1083
1317
|
return [...addresses];
|
|
1084
1318
|
}
|
|
1319
|
+
function extractJwksFromDidDocument(didDocument) {
|
|
1320
|
+
const methods = didDocument.verificationMethod;
|
|
1321
|
+
if (!Array.isArray(methods)) {
|
|
1322
|
+
return [];
|
|
1323
|
+
}
|
|
1324
|
+
const jwks = [];
|
|
1325
|
+
for (const method of methods) {
|
|
1326
|
+
const publicKeyJwk = method.publicKeyJwk;
|
|
1327
|
+
if (publicKeyJwk && typeof publicKeyJwk === "object" && !Array.isArray(publicKeyJwk)) {
|
|
1328
|
+
jwks.push(publicKeyJwk);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return jwks;
|
|
1332
|
+
}
|
|
1085
1333
|
function verifyDidDocumentControllerDid(didDocument, expectedControllerDid) {
|
|
1334
|
+
const method = extractDidMethod(expectedControllerDid);
|
|
1335
|
+
if (method === "jwk") {
|
|
1336
|
+
try {
|
|
1337
|
+
const expectedJwk = didJwkToJwk(expectedControllerDid);
|
|
1338
|
+
const documentJwks = extractJwksFromDidDocument(didDocument);
|
|
1339
|
+
for (const docJwk of documentJwks) {
|
|
1340
|
+
try {
|
|
1341
|
+
if (publicJwkEquals(docJwk, expectedJwk)) {
|
|
1342
|
+
return { valid: true };
|
|
1343
|
+
}
|
|
1344
|
+
} catch {
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return {
|
|
1348
|
+
valid: false,
|
|
1349
|
+
reason: "No matching publicKeyJwk found in DID document verification methods"
|
|
1350
|
+
};
|
|
1351
|
+
} catch {
|
|
1352
|
+
return {
|
|
1353
|
+
valid: false,
|
|
1354
|
+
reason: "Failed to decode did:jwk controller DID"
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1086
1358
|
let expectedAddress;
|
|
1087
1359
|
try {
|
|
1088
1360
|
expectedAddress = ethers.getAddress(extractAddressFromDid(expectedControllerDid));
|
|
1089
1361
|
} catch {
|
|
1090
|
-
return { valid: false, reason: "
|
|
1362
|
+
return { valid: false, reason: "Controller DID does not resolve to an EVM address and is not did:jwk" };
|
|
1091
1363
|
}
|
|
1092
|
-
const addresses =
|
|
1364
|
+
const addresses = extractEvmAddressesFromDidDocument(didDocument);
|
|
1093
1365
|
if (addresses.some((address) => address.toLowerCase() === expectedAddress.toLowerCase())) {
|
|
1094
1366
|
return { valid: true };
|
|
1095
1367
|
}
|
|
@@ -1145,21 +1417,23 @@ function parseDnsTxtRecord(record) {
|
|
|
1145
1417
|
}
|
|
1146
1418
|
const entries = record.split(/[;\s]+/).map((entry) => entry.trim()).filter(Boolean);
|
|
1147
1419
|
const parsed = {};
|
|
1420
|
+
const controllers = [];
|
|
1148
1421
|
for (const entry of entries) {
|
|
1149
|
-
const
|
|
1150
|
-
if (
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
if (
|
|
1155
|
-
|
|
1422
|
+
const eqIndex = entry.indexOf("=");
|
|
1423
|
+
if (eqIndex === -1) continue;
|
|
1424
|
+
const key = entry.slice(0, eqIndex).trim();
|
|
1425
|
+
const value = entry.slice(eqIndex + 1).trim();
|
|
1426
|
+
if (!key || !value) continue;
|
|
1427
|
+
if (key === "controller") {
|
|
1428
|
+
controllers.push(value);
|
|
1429
|
+
} else {
|
|
1430
|
+
parsed[key] = value;
|
|
1156
1431
|
}
|
|
1157
|
-
parsed[key.trim()] = value.trim();
|
|
1158
1432
|
}
|
|
1159
1433
|
return {
|
|
1160
1434
|
version: parsed.v,
|
|
1161
|
-
controller:
|
|
1162
|
-
|
|
1435
|
+
controller: controllers[0],
|
|
1436
|
+
controllers
|
|
1163
1437
|
};
|
|
1164
1438
|
}
|
|
1165
1439
|
function buildDnsTxtRecord(controllerDid) {
|
|
@@ -1167,107 +1441,556 @@ function buildDnsTxtRecord(controllerDid) {
|
|
|
1167
1441
|
return `v=1;controller=${normalized}`;
|
|
1168
1442
|
}
|
|
1169
1443
|
|
|
1170
|
-
// src/
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1444
|
+
// src/identity/did-url.ts
|
|
1445
|
+
var DID_URL_REGEX = /^did:[a-z0-9]+:.+$/i;
|
|
1446
|
+
function parseDidUrl(input) {
|
|
1447
|
+
assertString(input, "input", "INVALID_DID_URL");
|
|
1448
|
+
const trimmed = input.trim();
|
|
1449
|
+
const hashIndex = trimmed.indexOf("#");
|
|
1450
|
+
let did;
|
|
1451
|
+
let fragment;
|
|
1452
|
+
if (hashIndex === -1) {
|
|
1453
|
+
did = trimmed;
|
|
1454
|
+
fragment = null;
|
|
1455
|
+
} else {
|
|
1456
|
+
did = trimmed.slice(0, hashIndex);
|
|
1457
|
+
const rawFragment = trimmed.slice(hashIndex + 1);
|
|
1458
|
+
if (rawFragment.length === 0) {
|
|
1459
|
+
throw new OmaTrustError(
|
|
1460
|
+
"INVALID_DID_URL",
|
|
1461
|
+
"DID URL has empty fragment (trailing '#' with no identifier)",
|
|
1462
|
+
{ input }
|
|
1463
|
+
);
|
|
1179
1464
|
}
|
|
1180
|
-
|
|
1465
|
+
fragment = rawFragment;
|
|
1181
1466
|
}
|
|
1182
|
-
|
|
1467
|
+
if (!DID_URL_REGEX.test(did)) {
|
|
1468
|
+
throw new OmaTrustError(
|
|
1469
|
+
"INVALID_DID_URL",
|
|
1470
|
+
"Invalid DID URL: base DID portion is malformed",
|
|
1471
|
+
{ input, did }
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
return {
|
|
1475
|
+
didUrl: trimmed,
|
|
1476
|
+
did,
|
|
1477
|
+
fragment
|
|
1478
|
+
};
|
|
1183
1479
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1480
|
+
|
|
1481
|
+
// src/identity/resolve-key.ts
|
|
1482
|
+
function findVerificationMethod(didDocument, didUrl, fragment) {
|
|
1483
|
+
const methods = didDocument.verificationMethod;
|
|
1484
|
+
if (!Array.isArray(methods)) {
|
|
1485
|
+
return null;
|
|
1188
1486
|
}
|
|
1189
|
-
|
|
1190
|
-
if (typeof
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1487
|
+
for (const method of methods) {
|
|
1488
|
+
if (!method || typeof method !== "object") continue;
|
|
1489
|
+
const id = method.id;
|
|
1490
|
+
if (typeof id !== "string") continue;
|
|
1491
|
+
if (id === didUrl) return method;
|
|
1492
|
+
if (fragment && (id === `#${fragment}` || id.endsWith(`#${fragment}`))) {
|
|
1493
|
+
return method;
|
|
1196
1494
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1495
|
+
}
|
|
1496
|
+
return null;
|
|
1497
|
+
}
|
|
1498
|
+
function extractMethodIdsFromDidDocument(didDocument) {
|
|
1499
|
+
const methods = didDocument.verificationMethod;
|
|
1500
|
+
if (!Array.isArray(methods)) return [];
|
|
1501
|
+
return methods.filter((m) => m && typeof m.id === "string").map((m) => m.id);
|
|
1502
|
+
}
|
|
1503
|
+
async function resolveDidUrlToPublicKey(didUrl, options) {
|
|
1504
|
+
assertString(didUrl, "didUrl", "INVALID_DID_URL");
|
|
1505
|
+
const parsed = parseDidUrl(didUrl);
|
|
1506
|
+
const method = extractDidMethod(parsed.did);
|
|
1507
|
+
if (!method) {
|
|
1508
|
+
throw new OmaTrustError("INVALID_DID_URL", "Cannot extract DID method from DID URL", {
|
|
1509
|
+
didUrl,
|
|
1510
|
+
did: parsed.did
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
switch (method) {
|
|
1514
|
+
case "web":
|
|
1515
|
+
return resolveDidUrlKey(parsed.didUrl, parsed.did, parsed.fragment, options);
|
|
1516
|
+
default:
|
|
1517
|
+
throw new OmaTrustError(
|
|
1518
|
+
"UNSUPPORTED_DID_METHOD",
|
|
1519
|
+
`DID URL key resolution is not supported for method "${method}"`,
|
|
1520
|
+
{ didUrl, method }
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
async function resolveDidUrlKey(didUrl, did, fragment, options) {
|
|
1525
|
+
const domain = getDomainFromDidWeb(did);
|
|
1526
|
+
if (!domain) {
|
|
1527
|
+
throw new OmaTrustError("INVALID_DID_URL", "Cannot extract domain from did:web DID", {
|
|
1528
|
+
didUrl,
|
|
1529
|
+
did
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
const fetchFn = options?.fetchDidDocument ?? fetchDidDocument;
|
|
1533
|
+
const didDocument = await fetchFn(domain);
|
|
1534
|
+
const method = findVerificationMethod(didDocument, didUrl, fragment);
|
|
1535
|
+
if (!method) {
|
|
1536
|
+
throw new OmaTrustError(
|
|
1537
|
+
"KEY_NOT_FOUND",
|
|
1538
|
+
`No verification method found matching "${fragment ? `#${fragment}` : didUrl}"`,
|
|
1539
|
+
{ didUrl, fragment, availableMethods: extractMethodIdsFromDidDocument(didDocument) }
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
const publicKeyJwk = method.publicKeyJwk;
|
|
1543
|
+
if (!publicKeyJwk || typeof publicKeyJwk !== "object") {
|
|
1544
|
+
throw new OmaTrustError(
|
|
1545
|
+
"KEY_NOT_FOUND",
|
|
1546
|
+
`Verification method "${method.id}" does not contain publicKeyJwk`,
|
|
1547
|
+
{ didUrl, verificationMethodId: method.id }
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
const validation = validatePublicJwk(publicKeyJwk);
|
|
1551
|
+
if (!validation.valid) {
|
|
1552
|
+
throw new OmaTrustError(
|
|
1553
|
+
"INVALID_JWK",
|
|
1554
|
+
`publicKeyJwk in verification method "${method.id}" is invalid: ${validation.error}`,
|
|
1555
|
+
{ didUrl, verificationMethodId: method.id }
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
return {
|
|
1559
|
+
didUrl,
|
|
1560
|
+
did,
|
|
1561
|
+
fragment,
|
|
1562
|
+
publicKeyJwk,
|
|
1563
|
+
verificationMethodId: method.id
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
// src/reputation/proof/x402-jws.ts
|
|
1568
|
+
var REQUIRED_OFFER_FIELDS = [
|
|
1569
|
+
"version",
|
|
1570
|
+
"resourceUrl",
|
|
1571
|
+
"scheme",
|
|
1572
|
+
"network",
|
|
1573
|
+
"asset",
|
|
1574
|
+
"payTo",
|
|
1575
|
+
"amount"
|
|
1576
|
+
];
|
|
1577
|
+
var REQUIRED_RECEIPT_FIELDS = [
|
|
1578
|
+
"version",
|
|
1579
|
+
"network",
|
|
1580
|
+
"resourceUrl",
|
|
1581
|
+
"payer",
|
|
1582
|
+
"issuedAt"
|
|
1583
|
+
];
|
|
1584
|
+
function validateOfferPayload(payload) {
|
|
1585
|
+
for (const field of REQUIRED_OFFER_FIELDS) {
|
|
1586
|
+
if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
|
|
1587
|
+
return { valid: false, field };
|
|
1199
1588
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1589
|
+
}
|
|
1590
|
+
return { valid: true };
|
|
1202
1591
|
}
|
|
1203
|
-
function
|
|
1204
|
-
|
|
1205
|
-
|
|
1592
|
+
function validateReceiptPayload(payload) {
|
|
1593
|
+
for (const field of REQUIRED_RECEIPT_FIELDS) {
|
|
1594
|
+
if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
|
|
1595
|
+
return { valid: false, field };
|
|
1596
|
+
}
|
|
1206
1597
|
}
|
|
1207
|
-
return
|
|
1598
|
+
return { valid: true };
|
|
1208
1599
|
}
|
|
1209
|
-
function
|
|
1600
|
+
function failure(code, message, partial) {
|
|
1601
|
+
return {
|
|
1602
|
+
valid: false,
|
|
1603
|
+
error: { code, message },
|
|
1604
|
+
...partial
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
async function verifyX402JwsArtifact(artifact, options) {
|
|
1608
|
+
if (!artifact || typeof artifact !== "object") {
|
|
1609
|
+
return failure("INVALID_ARTIFACT", "Artifact must be a non-null object");
|
|
1610
|
+
}
|
|
1611
|
+
if (artifact.format !== "jws") {
|
|
1612
|
+
return failure("INVALID_ARTIFACT", `Expected format "jws", got "${artifact.format}"`);
|
|
1613
|
+
}
|
|
1614
|
+
if (typeof artifact.signature !== "string" || artifact.signature.length === 0) {
|
|
1615
|
+
return failure("INVALID_ARTIFACT", "Artifact signature must be a non-empty string");
|
|
1616
|
+
}
|
|
1617
|
+
const compactJws = artifact.signature;
|
|
1210
1618
|
const parts = compactJws.split(".");
|
|
1211
1619
|
if (parts.length !== 3) {
|
|
1212
|
-
|
|
1620
|
+
return failure("MALFORMED_JWS", "Invalid compact JWS format: expected 3 dot-separated parts");
|
|
1213
1621
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1622
|
+
let header;
|
|
1623
|
+
try {
|
|
1624
|
+
header = jose.decodeProtectedHeader(compactJws);
|
|
1625
|
+
} catch {
|
|
1626
|
+
return failure("MALFORMED_JWS", "Failed to decode JWS protected header");
|
|
1627
|
+
}
|
|
1628
|
+
if (!header.alg || typeof header.alg !== "string") {
|
|
1629
|
+
return failure("MISSING_ALG", "JWS header must include alg", { header });
|
|
1630
|
+
}
|
|
1631
|
+
const kid = typeof header.kid === "string" ? header.kid : null;
|
|
1632
|
+
const embeddedJwk = header.jwk;
|
|
1633
|
+
if (!kid && !embeddedJwk) {
|
|
1634
|
+
return failure(
|
|
1635
|
+
"MISSING_KEY_MATERIAL",
|
|
1636
|
+
"JWS header must include at least one of kid or jwk",
|
|
1637
|
+
{ header }
|
|
1220
1638
|
);
|
|
1221
|
-
} else {
|
|
1222
|
-
json = Buffer.from(padded, "base64").toString("utf8");
|
|
1223
1639
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
if (!tx) {
|
|
1247
|
-
return { valid: false, proofType: proof.proofType, reason: "Transaction not found" };
|
|
1248
|
-
}
|
|
1249
|
-
const expectedAmount = calculateTransferAmount(
|
|
1250
|
-
expectedSubject,
|
|
1251
|
-
expectedController,
|
|
1252
|
-
chainId,
|
|
1253
|
-
getProofPurpose(proof)
|
|
1254
|
-
);
|
|
1255
|
-
const subjectAddress = ethers.getAddress(extractAddressFromDid(expectedSubject));
|
|
1256
|
-
const controllerAddress = ethers.getAddress(extractAddressFromDid(expectedController));
|
|
1257
|
-
if (tx.from && ethers.getAddress(tx.from) !== subjectAddress) {
|
|
1258
|
-
return { valid: false, proofType: proof.proofType, reason: "Transaction sender mismatch" };
|
|
1259
|
-
}
|
|
1260
|
-
if (tx.to && ethers.getAddress(tx.to) !== controllerAddress) {
|
|
1261
|
-
return { valid: false, proofType: proof.proofType, reason: "Transaction recipient mismatch" };
|
|
1262
|
-
}
|
|
1263
|
-
if (BigInt(tx.value) !== expectedAmount) {
|
|
1264
|
-
return { valid: false, proofType: proof.proofType, reason: "Transaction amount mismatch" };
|
|
1640
|
+
let publicKeyJwk;
|
|
1641
|
+
let publicKeySource;
|
|
1642
|
+
if (embeddedJwk) {
|
|
1643
|
+
const validation = validatePublicJwk(embeddedJwk);
|
|
1644
|
+
if (!validation.valid) {
|
|
1645
|
+
return failure(
|
|
1646
|
+
"INVALID_EMBEDDED_JWK",
|
|
1647
|
+
`Embedded JWK is invalid: ${validation.error}`,
|
|
1648
|
+
{ header }
|
|
1649
|
+
);
|
|
1650
|
+
}
|
|
1651
|
+
publicKeyJwk = embeddedJwk;
|
|
1652
|
+
publicKeySource = "embedded-jwk";
|
|
1653
|
+
if (kid) {
|
|
1654
|
+
try {
|
|
1655
|
+
const resolved = await resolveDidUrlToPublicKey(kid, options?.resolveOptions);
|
|
1656
|
+
if (!publicJwkEquals(publicKeyJwk, resolved.publicKeyJwk)) {
|
|
1657
|
+
return failure(
|
|
1658
|
+
"KEY_CONFLICT",
|
|
1659
|
+
"Embedded jwk conflicts with public key resolved from kid",
|
|
1660
|
+
{ header, kid }
|
|
1661
|
+
);
|
|
1265
1662
|
}
|
|
1266
|
-
|
|
1663
|
+
} catch (err) {
|
|
1267
1664
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1665
|
+
}
|
|
1666
|
+
} else {
|
|
1667
|
+
publicKeySource = "kid-resolution";
|
|
1668
|
+
try {
|
|
1669
|
+
const resolved = await resolveDidUrlToPublicKey(kid, options?.resolveOptions);
|
|
1670
|
+
publicKeyJwk = resolved.publicKeyJwk;
|
|
1671
|
+
} catch (err) {
|
|
1672
|
+
const message = err instanceof OmaTrustError ? `Failed to resolve kid "${kid}": ${err.message}` : `Failed to resolve kid "${kid}"`;
|
|
1673
|
+
return failure("KID_RESOLUTION_FAILED", message, { header, kid });
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
let payload;
|
|
1677
|
+
try {
|
|
1678
|
+
const key = await jose.importJWK(publicKeyJwk, header.alg);
|
|
1679
|
+
const result = await jose.compactVerify(compactJws, key);
|
|
1680
|
+
const payloadText = new TextDecoder().decode(result.payload);
|
|
1681
|
+
payload = JSON.parse(payloadText);
|
|
1682
|
+
} catch (err) {
|
|
1683
|
+
const message = err instanceof Error ? err.message : "Signature verification failed";
|
|
1684
|
+
return failure("SIGNATURE_INVALID", message, { header, kid });
|
|
1685
|
+
}
|
|
1686
|
+
const publicKeyDid = jwkToDidJwk(publicKeyJwk);
|
|
1687
|
+
return {
|
|
1688
|
+
valid: true,
|
|
1689
|
+
header,
|
|
1690
|
+
payload,
|
|
1691
|
+
kid,
|
|
1692
|
+
publicKeyJwk,
|
|
1693
|
+
publicKeySource,
|
|
1694
|
+
publicKeyDid
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
async function verifyX402JwsOffer(artifact, options) {
|
|
1698
|
+
const result = await verifyX402JwsArtifact(artifact, options);
|
|
1699
|
+
if (!result.valid) return result;
|
|
1700
|
+
const payloadCheck = validateOfferPayload(result.payload);
|
|
1701
|
+
if (!payloadCheck.valid) {
|
|
1702
|
+
return failure(
|
|
1703
|
+
"INVALID_OFFER_PAYLOAD",
|
|
1704
|
+
`Missing required offer field: ${payloadCheck.field}`,
|
|
1705
|
+
{
|
|
1706
|
+
header: result.header,
|
|
1707
|
+
payload: result.payload,
|
|
1708
|
+
kid: result.kid,
|
|
1709
|
+
publicKeyJwk: result.publicKeyJwk,
|
|
1710
|
+
publicKeySource: result.publicKeySource,
|
|
1711
|
+
publicKeyDid: result.publicKeyDid
|
|
1712
|
+
}
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
return result;
|
|
1716
|
+
}
|
|
1717
|
+
async function verifyX402JwsReceipt(artifact, options) {
|
|
1718
|
+
const result = await verifyX402JwsArtifact(artifact, options);
|
|
1719
|
+
if (!result.valid) return result;
|
|
1720
|
+
const payloadCheck = validateReceiptPayload(result.payload);
|
|
1721
|
+
if (!payloadCheck.valid) {
|
|
1722
|
+
return failure(
|
|
1723
|
+
"INVALID_RECEIPT_PAYLOAD",
|
|
1724
|
+
`Missing required receipt field: ${payloadCheck.field}`,
|
|
1725
|
+
{
|
|
1726
|
+
header: result.header,
|
|
1727
|
+
payload: result.payload,
|
|
1728
|
+
kid: result.kid,
|
|
1729
|
+
publicKeyJwk: result.publicKeyJwk,
|
|
1730
|
+
publicKeySource: result.publicKeySource,
|
|
1731
|
+
publicKeyDid: result.publicKeyDid
|
|
1732
|
+
}
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
return result;
|
|
1736
|
+
}
|
|
1737
|
+
var OFFER_DOMAIN = {
|
|
1738
|
+
name: "x402 offer",
|
|
1739
|
+
version: "1",
|
|
1740
|
+
chainId: 1
|
|
1741
|
+
};
|
|
1742
|
+
var RECEIPT_DOMAIN = {
|
|
1743
|
+
name: "x402 receipt",
|
|
1744
|
+
version: "1",
|
|
1745
|
+
chainId: 1
|
|
1746
|
+
};
|
|
1747
|
+
var OFFER_TYPES = {
|
|
1748
|
+
Offer: [
|
|
1749
|
+
{ name: "version", type: "uint256" },
|
|
1750
|
+
{ name: "resourceUrl", type: "string" },
|
|
1751
|
+
{ name: "scheme", type: "string" },
|
|
1752
|
+
{ name: "network", type: "string" },
|
|
1753
|
+
{ name: "asset", type: "string" },
|
|
1754
|
+
{ name: "payTo", type: "string" },
|
|
1755
|
+
{ name: "amount", type: "string" },
|
|
1756
|
+
{ name: "validUntil", type: "uint256" }
|
|
1757
|
+
]
|
|
1758
|
+
};
|
|
1759
|
+
var RECEIPT_TYPES = {
|
|
1760
|
+
Receipt: [
|
|
1761
|
+
{ name: "version", type: "uint256" },
|
|
1762
|
+
{ name: "network", type: "string" },
|
|
1763
|
+
{ name: "resourceUrl", type: "string" },
|
|
1764
|
+
{ name: "payer", type: "string" },
|
|
1765
|
+
{ name: "issuedAt", type: "uint256" },
|
|
1766
|
+
{ name: "transaction", type: "string" }
|
|
1767
|
+
]
|
|
1768
|
+
};
|
|
1769
|
+
var REQUIRED_OFFER_FIELDS2 = [
|
|
1770
|
+
"version",
|
|
1771
|
+
"resourceUrl",
|
|
1772
|
+
"scheme",
|
|
1773
|
+
"network",
|
|
1774
|
+
"asset",
|
|
1775
|
+
"payTo",
|
|
1776
|
+
"amount"
|
|
1777
|
+
];
|
|
1778
|
+
var REQUIRED_RECEIPT_FIELDS2 = [
|
|
1779
|
+
"version",
|
|
1780
|
+
"network",
|
|
1781
|
+
"resourceUrl",
|
|
1782
|
+
"payer",
|
|
1783
|
+
"issuedAt"
|
|
1784
|
+
];
|
|
1785
|
+
function validateOfferPayload2(payload) {
|
|
1786
|
+
for (const field of REQUIRED_OFFER_FIELDS2) {
|
|
1787
|
+
if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
|
|
1788
|
+
return { valid: false, field };
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
return { valid: true };
|
|
1792
|
+
}
|
|
1793
|
+
function validateReceiptPayload2(payload) {
|
|
1794
|
+
for (const field of REQUIRED_RECEIPT_FIELDS2) {
|
|
1795
|
+
if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
|
|
1796
|
+
return { valid: false, field };
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
return { valid: true };
|
|
1800
|
+
}
|
|
1801
|
+
function failure2(code, message, partial) {
|
|
1802
|
+
return {
|
|
1803
|
+
valid: false,
|
|
1804
|
+
error: { code, message },
|
|
1805
|
+
...partial
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
function prepareOfferMessage(payload) {
|
|
1809
|
+
return {
|
|
1810
|
+
version: payload.version,
|
|
1811
|
+
resourceUrl: payload.resourceUrl,
|
|
1812
|
+
scheme: payload.scheme,
|
|
1813
|
+
network: payload.network,
|
|
1814
|
+
asset: payload.asset,
|
|
1815
|
+
payTo: payload.payTo,
|
|
1816
|
+
amount: payload.amount,
|
|
1817
|
+
validUntil: payload.validUntil ?? 0
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
function prepareReceiptMessage(payload) {
|
|
1821
|
+
return {
|
|
1822
|
+
version: payload.version,
|
|
1823
|
+
network: payload.network,
|
|
1824
|
+
resourceUrl: payload.resourceUrl,
|
|
1825
|
+
payer: payload.payer,
|
|
1826
|
+
issuedAt: payload.issuedAt,
|
|
1827
|
+
transaction: payload.transaction ?? ""
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
function verifyX402Eip712Artifact(artifact, artifactType) {
|
|
1831
|
+
if (!artifact || typeof artifact !== "object") {
|
|
1832
|
+
return failure2("INVALID_ARTIFACT", "Artifact must be a non-null object");
|
|
1833
|
+
}
|
|
1834
|
+
if (artifact.format !== "eip712") {
|
|
1835
|
+
return failure2("INVALID_ARTIFACT", `Expected format "eip712", got "${artifact.format}"`);
|
|
1836
|
+
}
|
|
1837
|
+
if (!artifact.payload || typeof artifact.payload !== "object" || Array.isArray(artifact.payload)) {
|
|
1838
|
+
return failure2("MISSING_PAYLOAD", "EIP-712 artifact must include a payload object");
|
|
1839
|
+
}
|
|
1840
|
+
if (typeof artifact.signature !== "string" || artifact.signature.length === 0) {
|
|
1841
|
+
return failure2("MISSING_SIGNATURE", "EIP-712 artifact must include a non-empty signature");
|
|
1842
|
+
}
|
|
1843
|
+
const payload = artifact.payload;
|
|
1844
|
+
if (artifactType === "offer") {
|
|
1845
|
+
const check = validateOfferPayload2(payload);
|
|
1846
|
+
if (!check.valid) {
|
|
1847
|
+
return failure2(
|
|
1848
|
+
"INVALID_OFFER_PAYLOAD",
|
|
1849
|
+
`Missing required offer field: ${check.field}`,
|
|
1850
|
+
{ payload }
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
} else {
|
|
1854
|
+
const check = validateReceiptPayload2(payload);
|
|
1855
|
+
if (!check.valid) {
|
|
1856
|
+
return failure2(
|
|
1857
|
+
"INVALID_RECEIPT_PAYLOAD",
|
|
1858
|
+
`Missing required receipt field: ${check.field}`,
|
|
1859
|
+
{ payload }
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
const domain = artifactType === "offer" ? OFFER_DOMAIN : RECEIPT_DOMAIN;
|
|
1864
|
+
const types = artifactType === "offer" ? OFFER_TYPES : RECEIPT_TYPES;
|
|
1865
|
+
const message = artifactType === "offer" ? prepareOfferMessage(payload) : prepareReceiptMessage(payload);
|
|
1866
|
+
let signer;
|
|
1867
|
+
try {
|
|
1868
|
+
signer = ethers.verifyTypedData(
|
|
1869
|
+
domain,
|
|
1870
|
+
types,
|
|
1871
|
+
message,
|
|
1872
|
+
artifact.signature
|
|
1873
|
+
);
|
|
1874
|
+
signer = ethers.getAddress(signer);
|
|
1875
|
+
} catch (err) {
|
|
1876
|
+
const message2 = err instanceof Error ? err.message : "EIP-712 signature verification failed";
|
|
1877
|
+
return failure2("SIGNATURE_INVALID", message2, { payload });
|
|
1878
|
+
}
|
|
1879
|
+
return {
|
|
1880
|
+
valid: true,
|
|
1881
|
+
payload,
|
|
1882
|
+
signer,
|
|
1883
|
+
artifactType
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
function verifyX402Eip712Offer(artifact) {
|
|
1887
|
+
return verifyX402Eip712Artifact(artifact, "offer");
|
|
1888
|
+
}
|
|
1889
|
+
function verifyX402Eip712Receipt(artifact) {
|
|
1890
|
+
return verifyX402Eip712Artifact(artifact, "receipt");
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
// src/reputation/verify.ts
|
|
1894
|
+
function parseChainId(input) {
|
|
1895
|
+
if (typeof input === "number") {
|
|
1896
|
+
return input;
|
|
1897
|
+
}
|
|
1898
|
+
if (typeof input === "string") {
|
|
1899
|
+
if (input.includes(":")) {
|
|
1900
|
+
const [, chainId] = input.split(":");
|
|
1901
|
+
return Number(chainId);
|
|
1902
|
+
}
|
|
1903
|
+
return Number(input);
|
|
1904
|
+
}
|
|
1905
|
+
return NaN;
|
|
1906
|
+
}
|
|
1907
|
+
function parseProofs(attestation) {
|
|
1908
|
+
const rawProofs = attestation.data.proofs;
|
|
1909
|
+
if (!Array.isArray(rawProofs)) {
|
|
1910
|
+
return [];
|
|
1911
|
+
}
|
|
1912
|
+
return rawProofs.map((entry) => {
|
|
1913
|
+
if (typeof entry === "string") {
|
|
1914
|
+
try {
|
|
1915
|
+
return JSON.parse(entry);
|
|
1916
|
+
} catch {
|
|
1917
|
+
return null;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
if (entry && typeof entry === "object") {
|
|
1921
|
+
return entry;
|
|
1922
|
+
}
|
|
1923
|
+
return null;
|
|
1924
|
+
}).filter((proof) => Boolean(proof?.proofType));
|
|
1925
|
+
}
|
|
1926
|
+
function getProofPurpose(proof) {
|
|
1927
|
+
if (proof.proofPurpose === "commercial-tx") {
|
|
1928
|
+
return "commercial-tx";
|
|
1929
|
+
}
|
|
1930
|
+
return "shared-control";
|
|
1931
|
+
}
|
|
1932
|
+
function decodeJwtPayload(compactJws) {
|
|
1933
|
+
const parts = compactJws.split(".");
|
|
1934
|
+
if (parts.length !== 3) {
|
|
1935
|
+
throw new OmaTrustError("PROOF_VERIFICATION_FAILED", "Invalid compact JWS format");
|
|
1936
|
+
}
|
|
1937
|
+
const payloadB64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
1938
|
+
const padded = payloadB64.padEnd(payloadB64.length + (4 - payloadB64.length % 4) % 4, "=");
|
|
1939
|
+
let json;
|
|
1940
|
+
if (typeof atob === "function") {
|
|
1941
|
+
json = decodeURIComponent(
|
|
1942
|
+
Array.from(atob(padded)).map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`).join("")
|
|
1943
|
+
);
|
|
1944
|
+
} else {
|
|
1945
|
+
json = Buffer.from(padded, "base64").toString("utf8");
|
|
1946
|
+
}
|
|
1947
|
+
return JSON.parse(json);
|
|
1948
|
+
}
|
|
1949
|
+
async function verifyProof(params) {
|
|
1950
|
+
const { proof, provider, expectedSubjectDid, expectedControllerDid } = params;
|
|
1951
|
+
try {
|
|
1952
|
+
switch (proof.proofType) {
|
|
1953
|
+
case "tx-encoded-value": {
|
|
1954
|
+
if (!provider) {
|
|
1955
|
+
return { valid: false, proofType: proof.proofType, reason: "Provider is required" };
|
|
1956
|
+
}
|
|
1957
|
+
if (!expectedSubjectDid || !expectedControllerDid) {
|
|
1958
|
+
return {
|
|
1959
|
+
valid: false,
|
|
1960
|
+
proofType: proof.proofType,
|
|
1961
|
+
reason: "expectedSubjectDid and expectedControllerDid are required"
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
const proofObject = proof.proofObject;
|
|
1965
|
+
const chainId = parseChainId(proofObject.chainId);
|
|
1966
|
+
const tx = await provider.getTransaction(
|
|
1967
|
+
proofObject.txHash
|
|
1968
|
+
);
|
|
1969
|
+
if (!tx) {
|
|
1970
|
+
return { valid: false, proofType: proof.proofType, reason: "Transaction not found" };
|
|
1971
|
+
}
|
|
1972
|
+
const expectedAmount = calculateTransferAmount(
|
|
1973
|
+
expectedSubjectDid,
|
|
1974
|
+
expectedControllerDid,
|
|
1975
|
+
chainId,
|
|
1976
|
+
getProofPurpose(proof)
|
|
1977
|
+
);
|
|
1978
|
+
const subjectAddress = ethers.getAddress(extractAddressFromDid(expectedSubjectDid));
|
|
1979
|
+
const controllerAddress = ethers.getAddress(extractAddressFromDid(expectedControllerDid));
|
|
1980
|
+
if (tx.from && ethers.getAddress(tx.from) !== subjectAddress) {
|
|
1981
|
+
return { valid: false, proofType: proof.proofType, reason: "Transaction sender mismatch" };
|
|
1982
|
+
}
|
|
1983
|
+
if (tx.to && ethers.getAddress(tx.to) !== controllerAddress) {
|
|
1984
|
+
return { valid: false, proofType: proof.proofType, reason: "Transaction recipient mismatch" };
|
|
1985
|
+
}
|
|
1986
|
+
if (BigInt(tx.value) !== expectedAmount) {
|
|
1987
|
+
return { valid: false, proofType: proof.proofType, reason: "Transaction amount mismatch" };
|
|
1988
|
+
}
|
|
1989
|
+
return { valid: true, proofType: proof.proofType };
|
|
1990
|
+
}
|
|
1991
|
+
case "tx-interaction": {
|
|
1992
|
+
if (!provider) {
|
|
1993
|
+
return { valid: false, proofType: proof.proofType, reason: "Provider is required" };
|
|
1271
1994
|
}
|
|
1272
1995
|
const proofObject = proof.proofObject;
|
|
1273
1996
|
const tx = await provider.getTransaction(
|
|
@@ -1327,6 +2050,38 @@ async function verifyProof(params) {
|
|
|
1327
2050
|
if (!proof.proofObject || typeof proof.proofObject !== "object") {
|
|
1328
2051
|
return { valid: false, proofType: proof.proofType, reason: "Invalid x402 proof object" };
|
|
1329
2052
|
}
|
|
2053
|
+
const proofObj = proof.proofObject;
|
|
2054
|
+
if (proofObj.format === "jws" && typeof proofObj.signature === "string") {
|
|
2055
|
+
const artifact = {
|
|
2056
|
+
format: "jws",
|
|
2057
|
+
signature: proofObj.signature
|
|
2058
|
+
};
|
|
2059
|
+
const jwsResult = proof.proofType === "x402-offer" ? await verifyX402JwsOffer(artifact) : await verifyX402JwsReceipt(artifact);
|
|
2060
|
+
if (!jwsResult.valid) {
|
|
2061
|
+
return {
|
|
2062
|
+
valid: false,
|
|
2063
|
+
proofType: proof.proofType,
|
|
2064
|
+
reason: jwsResult.error?.message ?? "JWS verification failed"
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
return { valid: true, proofType: proof.proofType };
|
|
2068
|
+
}
|
|
2069
|
+
if (proofObj.format === "eip712" && typeof proofObj.signature === "string" && proofObj.payload && typeof proofObj.payload === "object") {
|
|
2070
|
+
const artifact = {
|
|
2071
|
+
format: "eip712",
|
|
2072
|
+
payload: proofObj.payload,
|
|
2073
|
+
signature: proofObj.signature
|
|
2074
|
+
};
|
|
2075
|
+
const eip712Result = proof.proofType === "x402-offer" ? verifyX402Eip712Offer(artifact) : verifyX402Eip712Receipt(artifact);
|
|
2076
|
+
if (!eip712Result.valid) {
|
|
2077
|
+
return {
|
|
2078
|
+
valid: false,
|
|
2079
|
+
proofType: proof.proofType,
|
|
2080
|
+
reason: eip712Result.error?.message ?? "EIP-712 verification failed"
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
return { valid: true, proofType: proof.proofType };
|
|
2084
|
+
}
|
|
1330
2085
|
return { valid: true, proofType: proof.proofType };
|
|
1331
2086
|
}
|
|
1332
2087
|
case "evidence-pointer": {
|
|
@@ -1338,9 +2093,9 @@ async function verifyProof(params) {
|
|
|
1338
2093
|
if (!response.ok) {
|
|
1339
2094
|
return { valid: false, proofType: proof.proofType, reason: `Evidence fetch failed (${response.status})` };
|
|
1340
2095
|
}
|
|
1341
|
-
if (object.url.endsWith("/.well-known/did.json") &&
|
|
2096
|
+
if (object.url.endsWith("/.well-known/did.json") && expectedControllerDid) {
|
|
1342
2097
|
const didDoc = await response.json();
|
|
1343
|
-
const didCheck = verifyDidDocumentControllerDid(didDoc,
|
|
2098
|
+
const didCheck = verifyDidDocumentControllerDid(didDoc, expectedControllerDid);
|
|
1344
2099
|
return {
|
|
1345
2100
|
valid: didCheck.valid,
|
|
1346
2101
|
proofType: proof.proofType,
|
|
@@ -1348,10 +2103,10 @@ async function verifyProof(params) {
|
|
|
1348
2103
|
};
|
|
1349
2104
|
}
|
|
1350
2105
|
const body = await response.text();
|
|
1351
|
-
if (
|
|
2106
|
+
if (expectedControllerDid && !body.includes(expectedControllerDid)) {
|
|
1352
2107
|
try {
|
|
1353
2108
|
const parsed = parseDnsTxtRecord(body);
|
|
1354
|
-
if (parsed.
|
|
2109
|
+
if (!parsed.controllers.includes(expectedControllerDid)) {
|
|
1355
2110
|
return {
|
|
1356
2111
|
valid: false,
|
|
1357
2112
|
proofType: proof.proofType,
|
|
@@ -1407,8 +2162,8 @@ async function verifyAttestation(params) {
|
|
|
1407
2162
|
const result = await verifyProof({
|
|
1408
2163
|
proof,
|
|
1409
2164
|
provider: params.provider,
|
|
1410
|
-
|
|
1411
|
-
|
|
2165
|
+
expectedSubjectDid: params.context?.subjectDid,
|
|
2166
|
+
expectedControllerDid: params.context?.controllerDid
|
|
1412
2167
|
});
|
|
1413
2168
|
checks[proof.proofType] = result.valid;
|
|
1414
2169
|
if (!result.valid) {
|
|
@@ -1491,25 +2246,523 @@ async function callMethod(params, method) {
|
|
|
1491
2246
|
if (!response.ok) {
|
|
1492
2247
|
return null;
|
|
1493
2248
|
}
|
|
1494
|
-
return {
|
|
1495
|
-
ok: true,
|
|
1496
|
-
method,
|
|
1497
|
-
details
|
|
1498
|
-
};
|
|
2249
|
+
return {
|
|
2250
|
+
ok: true,
|
|
2251
|
+
method,
|
|
2252
|
+
details
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
async function callControllerWitness(params) {
|
|
2256
|
+
const dnsResult = await callMethod(params, "dns-txt").catch(() => null);
|
|
2257
|
+
if (dnsResult) {
|
|
2258
|
+
return dnsResult;
|
|
2259
|
+
}
|
|
2260
|
+
const didJsonResult = await callMethod(params, "did-json").catch(() => null);
|
|
2261
|
+
if (didJsonResult) {
|
|
2262
|
+
return didJsonResult;
|
|
2263
|
+
}
|
|
2264
|
+
return {
|
|
2265
|
+
ok: false,
|
|
2266
|
+
method: "did-json"
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
var DEFAULT_CONTROLLER_WITNESS_URL = "https://api.omatrust.org/v1/controller-witness";
|
|
2270
|
+
async function requestControllerWitness(params) {
|
|
2271
|
+
const url = params.gatewayUrl ?? DEFAULT_CONTROLLER_WITNESS_URL;
|
|
2272
|
+
const body = {
|
|
2273
|
+
subjectDid: params.subjectDid,
|
|
2274
|
+
controllerDid: params.controllerDid
|
|
2275
|
+
};
|
|
2276
|
+
if (params.chainId !== void 0) {
|
|
2277
|
+
body.chainId = params.chainId;
|
|
2278
|
+
}
|
|
2279
|
+
let response;
|
|
2280
|
+
try {
|
|
2281
|
+
response = await fetch(url, {
|
|
2282
|
+
method: "POST",
|
|
2283
|
+
headers: { "Content-Type": "application/json" },
|
|
2284
|
+
credentials: "include",
|
|
2285
|
+
body: JSON.stringify(body),
|
|
2286
|
+
signal: AbortSignal.timeout(params.timeoutMs ?? 15e3)
|
|
2287
|
+
});
|
|
2288
|
+
} catch (err) {
|
|
2289
|
+
throw new OmaTrustError("NETWORK_ERROR", "Controller witness request failed", { err });
|
|
2290
|
+
}
|
|
2291
|
+
if (!response.ok) {
|
|
2292
|
+
const errorBody = await response.json().catch(() => ({ error: response.statusText }));
|
|
2293
|
+
throw new OmaTrustError(
|
|
2294
|
+
"API_ERROR",
|
|
2295
|
+
errorBody.error ?? `Controller witness failed: ${response.status}`,
|
|
2296
|
+
{ status: response.status, ...errorBody }
|
|
2297
|
+
);
|
|
2298
|
+
}
|
|
2299
|
+
return await response.json();
|
|
2300
|
+
}
|
|
2301
|
+
var EIP1967_ADMIN_SLOT = "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103";
|
|
2302
|
+
var OWNERSHIP_PATTERNS = [
|
|
2303
|
+
{ method: "owner", signature: "function owner() view returns (address)" },
|
|
2304
|
+
{ method: "admin", signature: "function admin() view returns (address)" },
|
|
2305
|
+
{ method: "getOwner", signature: "function getOwner() view returns (address)" }
|
|
2306
|
+
];
|
|
2307
|
+
async function readOwnerFromContract(provider, contractAddress, signature, method) {
|
|
2308
|
+
try {
|
|
2309
|
+
const iface = new ethers.Interface([signature]);
|
|
2310
|
+
const data = iface.encodeFunctionData(method, []);
|
|
2311
|
+
const result = await provider.call({ to: contractAddress, data });
|
|
2312
|
+
const [value] = iface.decodeFunctionResult(method, result);
|
|
2313
|
+
if (typeof value === "string" && ethers.isAddress(value) && value !== ethers.ZeroAddress) {
|
|
2314
|
+
return ethers.getAddress(value);
|
|
2315
|
+
}
|
|
2316
|
+
} catch {
|
|
2317
|
+
}
|
|
2318
|
+
return null;
|
|
2319
|
+
}
|
|
2320
|
+
async function discoverContractOwner(provider, contractAddress) {
|
|
2321
|
+
try {
|
|
2322
|
+
const code = await provider.getCode(contractAddress);
|
|
2323
|
+
if (code === "0x" || code === "0x0") {
|
|
2324
|
+
return null;
|
|
2325
|
+
}
|
|
2326
|
+
} catch {
|
|
2327
|
+
return null;
|
|
2328
|
+
}
|
|
2329
|
+
for (const pattern of OWNERSHIP_PATTERNS) {
|
|
2330
|
+
const address = await readOwnerFromContract(
|
|
2331
|
+
provider,
|
|
2332
|
+
contractAddress,
|
|
2333
|
+
pattern.signature,
|
|
2334
|
+
pattern.method
|
|
2335
|
+
);
|
|
2336
|
+
if (address) {
|
|
2337
|
+
return address;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
try {
|
|
2341
|
+
const adminValue = await provider.getStorage(contractAddress, EIP1967_ADMIN_SLOT);
|
|
2342
|
+
if (adminValue && adminValue !== "0x" && adminValue !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
2343
|
+
const adminAddress = ethers.getAddress(`0x${adminValue.slice(-40)}`);
|
|
2344
|
+
if (adminAddress !== ethers.ZeroAddress) {
|
|
2345
|
+
return adminAddress;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
} catch {
|
|
2349
|
+
}
|
|
2350
|
+
return null;
|
|
2351
|
+
}
|
|
2352
|
+
async function discoverControllingWalletDid(provider, contractAddress, chainId) {
|
|
2353
|
+
const owner = await discoverContractOwner(provider, contractAddress);
|
|
2354
|
+
return owner ? buildEvmDidPkh(chainId, owner) : null;
|
|
2355
|
+
}
|
|
2356
|
+
async function verifyTransferProof(provider, txHash, subjectDid, attesterAddress, chainId) {
|
|
2357
|
+
try {
|
|
2358
|
+
const tx = await provider.getTransaction(txHash);
|
|
2359
|
+
if (!tx || !tx.from || !tx.to) {
|
|
2360
|
+
return false;
|
|
2361
|
+
}
|
|
2362
|
+
if (ethers.getAddress(tx.to) !== ethers.getAddress(attesterAddress)) {
|
|
2363
|
+
return false;
|
|
2364
|
+
}
|
|
2365
|
+
const attesterDid = buildEvmDidPkh(chainId, attesterAddress);
|
|
2366
|
+
const expectedAmount = calculateTransferAmount(
|
|
2367
|
+
subjectDid,
|
|
2368
|
+
attesterDid,
|
|
2369
|
+
chainId,
|
|
2370
|
+
"shared-control"
|
|
2371
|
+
);
|
|
2372
|
+
const actualValue = BigInt(tx.value ?? 0n);
|
|
2373
|
+
return actualValue === expectedAmount;
|
|
2374
|
+
} catch {
|
|
2375
|
+
return false;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
// src/identity/controller-id.ts
|
|
2380
|
+
function isSameControllerId(a, b) {
|
|
2381
|
+
let normalizedA = null;
|
|
2382
|
+
let normalizedB = null;
|
|
2383
|
+
try {
|
|
2384
|
+
normalizedA = normalizeDid(a);
|
|
2385
|
+
} catch {
|
|
2386
|
+
}
|
|
2387
|
+
try {
|
|
2388
|
+
normalizedB = normalizeDid(b);
|
|
2389
|
+
} catch {
|
|
2390
|
+
}
|
|
2391
|
+
if (normalizedA && normalizedB && normalizedA === normalizedB) {
|
|
2392
|
+
return true;
|
|
2393
|
+
}
|
|
2394
|
+
const evmA = extractControllerEvmAddress(a);
|
|
2395
|
+
const evmB = extractControllerEvmAddress(b);
|
|
2396
|
+
if (evmA && evmB && evmA.toLowerCase() === evmB.toLowerCase()) {
|
|
2397
|
+
return true;
|
|
2398
|
+
}
|
|
2399
|
+
const methodA = extractDidMethod(a);
|
|
2400
|
+
const methodB = extractDidMethod(b);
|
|
2401
|
+
if (methodA === "jwk" && methodB === "jwk") {
|
|
2402
|
+
try {
|
|
2403
|
+
const jwkA = didJwkToJwk(a);
|
|
2404
|
+
const jwkB = didJwkToJwk(b);
|
|
2405
|
+
return publicJwkEquals(jwkA, jwkB);
|
|
2406
|
+
} catch {
|
|
2407
|
+
return false;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
return false;
|
|
2411
|
+
}
|
|
2412
|
+
function extractControllerEvmAddress(controllerDid) {
|
|
2413
|
+
try {
|
|
2414
|
+
const method = extractDidMethod(controllerDid);
|
|
2415
|
+
if (method === "pkh" && controllerDid.includes("eip155")) {
|
|
2416
|
+
return extractAddressFromDid(controllerDid);
|
|
2417
|
+
}
|
|
2418
|
+
} catch {
|
|
2419
|
+
}
|
|
2420
|
+
return null;
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
// src/shared/trust-anchors.ts
|
|
2424
|
+
var DEFAULT_TRUST_ANCHORS_URL = "https://api.omatrust.org/v1/trust-anchors";
|
|
2425
|
+
var TRUST_ANCHORS_URL = DEFAULT_TRUST_ANCHORS_URL;
|
|
2426
|
+
var cachedAnchors = null;
|
|
2427
|
+
var cacheTimestamp = 0;
|
|
2428
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
2429
|
+
async function fetchTrustAnchors(url) {
|
|
2430
|
+
const now = Date.now();
|
|
2431
|
+
if (cachedAnchors && now - cacheTimestamp < CACHE_TTL_MS) {
|
|
2432
|
+
return cachedAnchors;
|
|
2433
|
+
}
|
|
2434
|
+
let res;
|
|
2435
|
+
try {
|
|
2436
|
+
res = await fetch(url ?? TRUST_ANCHORS_URL);
|
|
2437
|
+
} catch (err) {
|
|
2438
|
+
throw new OmaTrustError("NETWORK_ERROR", "Failed to fetch trust anchors", { err });
|
|
2439
|
+
}
|
|
2440
|
+
if (!res.ok) {
|
|
2441
|
+
throw new OmaTrustError("NETWORK_ERROR", `Failed to fetch trust anchors: ${res.status} ${res.statusText}`);
|
|
2442
|
+
}
|
|
2443
|
+
const anchors = await res.json();
|
|
2444
|
+
if (!anchors.version || !anchors.chains || typeof anchors.chains !== "object") {
|
|
2445
|
+
throw new OmaTrustError("INVALID_INPUT", "Invalid trust anchors format");
|
|
2446
|
+
}
|
|
2447
|
+
cachedAnchors = anchors;
|
|
2448
|
+
cacheTimestamp = now;
|
|
2449
|
+
return anchors;
|
|
2450
|
+
}
|
|
2451
|
+
function getChainAnchors(anchors, caip2) {
|
|
2452
|
+
const chain = anchors.chains[caip2];
|
|
2453
|
+
if (!chain) {
|
|
2454
|
+
throw new OmaTrustError("UNSUPPORTED_CHAIN", `Chain ${caip2} is not in the trust anchors`, {
|
|
2455
|
+
caip2,
|
|
2456
|
+
supportedChains: Object.keys(anchors.chains)
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2459
|
+
return chain;
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
// src/reputation/proof/dns-txt-shared.ts
|
|
2463
|
+
async function verifyDnsTxtControllerDid(domain, expectedControllerDid, options = {}) {
|
|
2464
|
+
if (!domain || typeof domain !== "string") {
|
|
2465
|
+
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
2466
|
+
}
|
|
2467
|
+
if (!options.resolveTxt) {
|
|
2468
|
+
throw new OmaTrustError("NETWORK_ERROR", "No DNS TXT resolver was provided", { domain });
|
|
2469
|
+
}
|
|
2470
|
+
const prefix = options.recordPrefix ?? "_controllers";
|
|
2471
|
+
const host = `${prefix}.${domain.toLowerCase().replace(/\.$/, "")}`;
|
|
2472
|
+
let records;
|
|
2473
|
+
try {
|
|
2474
|
+
records = await options.resolveTxt(host);
|
|
2475
|
+
} catch (err) {
|
|
2476
|
+
throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
|
|
2477
|
+
}
|
|
2478
|
+
for (const recordParts of records) {
|
|
2479
|
+
const record = recordParts.join("");
|
|
2480
|
+
const parsed = parseDnsTxtRecord(record);
|
|
2481
|
+
if (parsed.version !== "1") continue;
|
|
2482
|
+
for (const recordController of parsed.controllers) {
|
|
2483
|
+
if (isSameControllerId(recordController, expectedControllerDid)) {
|
|
2484
|
+
return { valid: true, record };
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
return { valid: false, reason: "Controller DID not found in DNS TXT records" };
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
// src/reputation/attester-authorization.ts
|
|
2492
|
+
var DEFAULT_CHAIN = "eip155:6623";
|
|
2493
|
+
var DEFAULT_PURPOSES = ["authentication", "assertionMethod"];
|
|
2494
|
+
async function getControllerAuthorization(params) {
|
|
2495
|
+
const chain = params.chain ?? DEFAULT_CHAIN;
|
|
2496
|
+
const purposes = params.purpose && params.purpose.length > 0 ? params.purpose : DEFAULT_PURPOSES;
|
|
2497
|
+
const controllerDid = normalizeDid(params.controllerDid);
|
|
2498
|
+
const controllerMethod = extractDidMethod(controllerDid);
|
|
2499
|
+
const controllerEvmAddress = extractControllerEvmAddress(controllerDid);
|
|
2500
|
+
const anchors = await fetchTrustAnchors();
|
|
2501
|
+
const chainAnchors = getChainAnchors(anchors, chain);
|
|
2502
|
+
const easContractAddress = params.easContractAddress ?? chainAnchors.easContract;
|
|
2503
|
+
const provider = params.provider;
|
|
2504
|
+
const controllerWitnessSchemaUid = chainAnchors.schemas["controller-witness"];
|
|
2505
|
+
const keyBindingSchemaUid = chainAnchors.schemas["key-binding"];
|
|
2506
|
+
const subjectAddress = didToAddress(params.subjectDid);
|
|
2507
|
+
let relevantWitnesses = [];
|
|
2508
|
+
if (controllerWitnessSchemaUid) {
|
|
2509
|
+
const rawWitnesses = await queryByRecipientAndSchema(
|
|
2510
|
+
easContractAddress,
|
|
2511
|
+
provider,
|
|
2512
|
+
subjectAddress,
|
|
2513
|
+
[controllerWitnessSchemaUid],
|
|
2514
|
+
params.fromBlock ?? 0
|
|
2515
|
+
);
|
|
2516
|
+
relevantWitnesses = rawWitnesses.filter((att) => {
|
|
2517
|
+
const witnessController = att.data?.controller;
|
|
2518
|
+
if (typeof witnessController !== "string") return false;
|
|
2519
|
+
return isSameControllerId(witnessController, controllerDid);
|
|
2520
|
+
});
|
|
2521
|
+
relevantWitnesses.sort((a, b) => Number(a.time - b.time));
|
|
2522
|
+
}
|
|
2523
|
+
const controllerWitnesses = relevantWitnesses.map((att) => ({
|
|
2524
|
+
uid: att.uid,
|
|
2525
|
+
issuedAt: att.time,
|
|
2526
|
+
attester: att.attester,
|
|
2527
|
+
method: resolveWitnessMethod(att.data?.method)
|
|
2528
|
+
}));
|
|
2529
|
+
let keyBindingUid = null;
|
|
2530
|
+
let until = null;
|
|
2531
|
+
let keyPurposeStatus = "not-required";
|
|
2532
|
+
if (keyBindingSchemaUid) {
|
|
2533
|
+
const keyBindings = await queryByRecipientAndSchema(
|
|
2534
|
+
easContractAddress,
|
|
2535
|
+
provider,
|
|
2536
|
+
subjectAddress,
|
|
2537
|
+
[keyBindingSchemaUid],
|
|
2538
|
+
params.fromBlock ?? 0
|
|
2539
|
+
);
|
|
2540
|
+
const relevantKeyBindings = keyBindings.filter((att) => controllerMatchesKeyBinding(att, controllerDid, controllerMethod)).sort((a, b) => Number(b.time - a.time));
|
|
2541
|
+
const relevantKeyBinding = relevantKeyBindings[0] ?? null;
|
|
2542
|
+
if (relevantKeyBinding) {
|
|
2543
|
+
keyBindingUid = relevantKeyBinding.uid;
|
|
2544
|
+
if (relevantKeyBinding.revocationTime > 0n) {
|
|
2545
|
+
until = relevantKeyBinding.revocationTime;
|
|
2546
|
+
}
|
|
2547
|
+
const keyPurposes = relevantKeyBinding.data?.keyPurpose;
|
|
2548
|
+
if (!Array.isArray(keyPurposes) || keyPurposes.length === 0) {
|
|
2549
|
+
keyPurposeStatus = "unknown";
|
|
2550
|
+
} else {
|
|
2551
|
+
const allMatched = purposes.every((p) => keyPurposes.includes(p));
|
|
2552
|
+
keyPurposeStatus = allMatched ? "matched" : "mismatch";
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
let currentlyVerified = false;
|
|
2557
|
+
let liveMethod = null;
|
|
2558
|
+
let transferProofVerified = false;
|
|
2559
|
+
let transferProofAnchor = null;
|
|
2560
|
+
const subjectMethod = extractDidMethod(params.subjectDid);
|
|
2561
|
+
if (subjectMethod === "web") {
|
|
2562
|
+
const domain = getDomainFromDidWeb(params.subjectDid);
|
|
2563
|
+
if (domain) {
|
|
2564
|
+
try {
|
|
2565
|
+
const dnsResult = await verifyDnsTxtControllerDid(domain, controllerDid, {
|
|
2566
|
+
resolveTxt: params.resolveTxt
|
|
2567
|
+
});
|
|
2568
|
+
if (dnsResult.valid) {
|
|
2569
|
+
currentlyVerified = true;
|
|
2570
|
+
liveMethod = "dns";
|
|
2571
|
+
}
|
|
2572
|
+
} catch {
|
|
2573
|
+
}
|
|
2574
|
+
if (!currentlyVerified) {
|
|
2575
|
+
try {
|
|
2576
|
+
const fetchDoc = params.fetchDidDocument ?? fetchDidDocument;
|
|
2577
|
+
const didDoc = await fetchDoc(domain);
|
|
2578
|
+
const didJsonResult = verifyDidDocumentControllerDid(didDoc, controllerDid);
|
|
2579
|
+
if (didJsonResult.valid) {
|
|
2580
|
+
currentlyVerified = true;
|
|
2581
|
+
liveMethod = "did-document";
|
|
2582
|
+
}
|
|
2583
|
+
} catch {
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
} else if (subjectMethod === "pkh") {
|
|
2588
|
+
if (isEvmDidPkh(params.subjectDid) && controllerEvmAddress) {
|
|
2589
|
+
const subjectContractAddress = getAddressFromDidPkh(params.subjectDid);
|
|
2590
|
+
const subjectChainId = getChainIdFromDidPkh(params.subjectDid);
|
|
2591
|
+
const chainIdNum = subjectChainId ? Number(subjectChainId) : null;
|
|
2592
|
+
if (subjectContractAddress && chainIdNum) {
|
|
2593
|
+
const ownerAddress = await discoverContractOwner(
|
|
2594
|
+
provider,
|
|
2595
|
+
subjectContractAddress
|
|
2596
|
+
);
|
|
2597
|
+
if (ownerAddress && ethers.getAddress(ownerAddress) === ethers.getAddress(controllerEvmAddress)) {
|
|
2598
|
+
currentlyVerified = true;
|
|
2599
|
+
liveMethod = "contract-ownership";
|
|
2600
|
+
}
|
|
2601
|
+
if (!currentlyVerified && keyBindingUid) {
|
|
2602
|
+
const relevantKeyBinding = await findKeyBindingWithTransferProof(
|
|
2603
|
+
keyBindingSchemaUid,
|
|
2604
|
+
easContractAddress,
|
|
2605
|
+
provider,
|
|
2606
|
+
subjectAddress,
|
|
2607
|
+
controllerDid,
|
|
2608
|
+
controllerMethod,
|
|
2609
|
+
params.fromBlock ?? 0
|
|
2610
|
+
);
|
|
2611
|
+
if (relevantKeyBinding) {
|
|
2612
|
+
const proofTxHash = relevantKeyBinding.data?.proofTxHash;
|
|
2613
|
+
if (proofTxHash && /^0x[0-9a-fA-F]{64}$/.test(proofTxHash)) {
|
|
2614
|
+
const verified = await verifyTransferProof(
|
|
2615
|
+
provider,
|
|
2616
|
+
proofTxHash,
|
|
2617
|
+
params.subjectDid,
|
|
2618
|
+
controllerEvmAddress,
|
|
2619
|
+
chainIdNum
|
|
2620
|
+
);
|
|
2621
|
+
if (verified) {
|
|
2622
|
+
transferProofVerified = true;
|
|
2623
|
+
transferProofAnchor = relevantKeyBinding.time;
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
const witnessAnchor = relevantWitnesses.length > 0 ? relevantWitnesses[0].time : null;
|
|
2632
|
+
const anchoredFrom = witnessAnchor ?? transferProofAnchor;
|
|
2633
|
+
const purposeBlocks = keyPurposeStatus === "mismatch";
|
|
2634
|
+
const hasOpenWindow = relevantWitnesses.length > 0 && until === null;
|
|
2635
|
+
const hasKeyBindingWithProof = keyBindingUid !== null && transferProofVerified && until === null;
|
|
2636
|
+
const authorized = !purposeBlocks && (hasOpenWindow || currentlyVerified && until === null || hasKeyBindingWithProof);
|
|
2637
|
+
return {
|
|
2638
|
+
authorized,
|
|
2639
|
+
anchoredFrom,
|
|
2640
|
+
until,
|
|
2641
|
+
currentlyVerified,
|
|
2642
|
+
liveMethod,
|
|
2643
|
+
controllerWitnesses,
|
|
2644
|
+
keyBindingUid,
|
|
2645
|
+
keyPurposeStatus,
|
|
2646
|
+
transferProofVerified: transferProofVerified || void 0
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
function controllerMatchesKeyBinding(att, controllerDid, controllerMethod) {
|
|
2650
|
+
const keyId = att.data?.keyId;
|
|
2651
|
+
if (typeof keyId === "string") {
|
|
2652
|
+
if (isSameControllerId(keyId, controllerDid)) {
|
|
2653
|
+
return true;
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
if (controllerMethod === "jwk") {
|
|
2657
|
+
const publicKeyJwkRaw = att.data?.publicKeyJwk;
|
|
2658
|
+
if (publicKeyJwkRaw) {
|
|
2659
|
+
try {
|
|
2660
|
+
const onChainJwk = typeof publicKeyJwkRaw === "string" ? JSON.parse(publicKeyJwkRaw) : publicKeyJwkRaw;
|
|
2661
|
+
const controllerJwk = didJwkToJwk(controllerDid);
|
|
2662
|
+
return publicJwkEquals(onChainJwk, controllerJwk);
|
|
2663
|
+
} catch {
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
return false;
|
|
2668
|
+
}
|
|
2669
|
+
var EAS_EVENT_ABI2 = [
|
|
2670
|
+
"event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)"
|
|
2671
|
+
];
|
|
2672
|
+
function resolveWitnessMethod(value) {
|
|
2673
|
+
if (typeof value !== "string") return void 0;
|
|
2674
|
+
switch (value) {
|
|
2675
|
+
case "dns":
|
|
2676
|
+
case "dns-txt":
|
|
2677
|
+
return "dns";
|
|
2678
|
+
case "did-document":
|
|
2679
|
+
case "did-json":
|
|
2680
|
+
return "did-document";
|
|
2681
|
+
case "manual":
|
|
2682
|
+
return "manual";
|
|
2683
|
+
default:
|
|
2684
|
+
return "other";
|
|
2685
|
+
}
|
|
1499
2686
|
}
|
|
1500
|
-
async function
|
|
1501
|
-
const
|
|
1502
|
-
|
|
1503
|
-
|
|
2687
|
+
async function queryByRecipientAndSchema(easContractAddress, provider, recipient, schemas, fromBlock) {
|
|
2688
|
+
const contract = new ethers.Contract(easContractAddress, EAS_EVENT_ABI2, provider);
|
|
2689
|
+
const filter = contract.filters.Attested(recipient, null);
|
|
2690
|
+
const toBlock = await provider.getBlockNumber();
|
|
2691
|
+
let events;
|
|
2692
|
+
try {
|
|
2693
|
+
events = await contract.queryFilter(filter, fromBlock, toBlock);
|
|
2694
|
+
} catch (err) {
|
|
2695
|
+
throw new OmaTrustError("NETWORK_ERROR", "Failed to query attestation events", { err });
|
|
1504
2696
|
}
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
|
|
2697
|
+
const eas = new easSdk.EAS(easContractAddress);
|
|
2698
|
+
eas.connect(provider);
|
|
2699
|
+
const schemaFilter = schemas.map((s) => s.toLowerCase());
|
|
2700
|
+
const results = [];
|
|
2701
|
+
for (const event of events) {
|
|
2702
|
+
if (!("args" in event) || !Array.isArray(event.args)) continue;
|
|
2703
|
+
const args = event.args;
|
|
2704
|
+
const uid = args?.[2];
|
|
2705
|
+
const schemaUid = args?.[3];
|
|
2706
|
+
if (!uid || !schemaUid) continue;
|
|
2707
|
+
if (!schemaFilter.includes(schemaUid.toLowerCase())) continue;
|
|
2708
|
+
const attestation = await eas.getAttestation(uid);
|
|
2709
|
+
if (!attestation || !attestation.uid) continue;
|
|
2710
|
+
let data = {};
|
|
2711
|
+
const rawData = attestation.data;
|
|
2712
|
+
if (rawData && rawData !== "0x") {
|
|
2713
|
+
try {
|
|
2714
|
+
data = decodeAttestationData(
|
|
2715
|
+
"string subject, string controller, string method",
|
|
2716
|
+
rawData
|
|
2717
|
+
);
|
|
2718
|
+
} catch {
|
|
2719
|
+
try {
|
|
2720
|
+
data = decodeAttestationData(
|
|
2721
|
+
"string subject, string keyId, string publicKeyJwk, string[] keyPurpose, string[] proofs, uint256 issuedAt, uint256 effectiveAt, uint256 expiresAt",
|
|
2722
|
+
rawData
|
|
2723
|
+
);
|
|
2724
|
+
} catch {
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
const toBigIntSafe = (value) => {
|
|
2729
|
+
if (typeof value === "bigint") return value;
|
|
2730
|
+
if (typeof value === "number") return BigInt(value);
|
|
2731
|
+
if (typeof value === "string" && value.length > 0) return BigInt(value);
|
|
2732
|
+
return 0n;
|
|
2733
|
+
};
|
|
2734
|
+
results.push({
|
|
2735
|
+
uid: attestation.uid,
|
|
2736
|
+
schema: attestation.schema,
|
|
2737
|
+
attester: attestation.attester,
|
|
2738
|
+
recipient: attestation.recipient,
|
|
2739
|
+
revocable: Boolean(attestation.revocable),
|
|
2740
|
+
revocationTime: toBigIntSafe(attestation.revocationTime),
|
|
2741
|
+
expirationTime: toBigIntSafe(attestation.expirationTime),
|
|
2742
|
+
time: toBigIntSafe(attestation.time),
|
|
2743
|
+
refUID: attestation.refUID,
|
|
2744
|
+
data
|
|
2745
|
+
});
|
|
1508
2746
|
}
|
|
1509
|
-
return
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
2747
|
+
return results;
|
|
2748
|
+
}
|
|
2749
|
+
async function findKeyBindingWithTransferProof(keyBindingSchemaUid, easContractAddress, provider, subjectAddress, controllerDid, controllerMethod, fromBlock) {
|
|
2750
|
+
const keyBindings = await queryByRecipientAndSchema(
|
|
2751
|
+
easContractAddress,
|
|
2752
|
+
provider,
|
|
2753
|
+
subjectAddress,
|
|
2754
|
+
[keyBindingSchemaUid],
|
|
2755
|
+
fromBlock
|
|
2756
|
+
);
|
|
2757
|
+
const withProof = keyBindings.filter((att) => {
|
|
2758
|
+
if (!controllerMatchesKeyBinding(att, controllerDid, controllerMethod)) return false;
|
|
2759
|
+
const proofs = att.data?.proofs;
|
|
2760
|
+
if (Array.isArray(proofs)) {
|
|
2761
|
+
return proofs.some((p) => typeof p === "string" && /^0x[0-9a-fA-F]{64}$/.test(p));
|
|
2762
|
+
}
|
|
2763
|
+
return typeof att.data?.proofTxHash === "string";
|
|
2764
|
+
}).sort((a, b) => Number(b.time - a.time));
|
|
2765
|
+
return withProof[0] ?? null;
|
|
1513
2766
|
}
|
|
1514
2767
|
|
|
1515
2768
|
// src/reputation/proof/tx-interaction.ts
|
|
@@ -1619,6 +2872,42 @@ function createX402OfferProof(offer) {
|
|
|
1619
2872
|
};
|
|
1620
2873
|
}
|
|
1621
2874
|
|
|
2875
|
+
// src/reputation/proof/x402-verify.ts
|
|
2876
|
+
async function verifyX402Artifact(artifact, options) {
|
|
2877
|
+
if (!artifact || typeof artifact !== "object") {
|
|
2878
|
+
return {
|
|
2879
|
+
valid: false,
|
|
2880
|
+
error: { code: "INVALID_ARTIFACT", message: "Artifact must be a non-null object" }
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
const format = artifact.format;
|
|
2884
|
+
switch (format) {
|
|
2885
|
+
case "jws": {
|
|
2886
|
+
const jwsArtifact = artifact;
|
|
2887
|
+
const jwsOptions = options.resolveOptions ? { resolveOptions: options.resolveOptions } : void 0;
|
|
2888
|
+
if (options.artifactType === "offer") {
|
|
2889
|
+
return verifyX402JwsOffer(jwsArtifact, jwsOptions);
|
|
2890
|
+
}
|
|
2891
|
+
return verifyX402JwsReceipt(jwsArtifact, jwsOptions);
|
|
2892
|
+
}
|
|
2893
|
+
case "eip712": {
|
|
2894
|
+
const eip712Artifact = artifact;
|
|
2895
|
+
if (options.artifactType === "offer") {
|
|
2896
|
+
return verifyX402Eip712Offer(eip712Artifact);
|
|
2897
|
+
}
|
|
2898
|
+
return verifyX402Eip712Receipt(eip712Artifact);
|
|
2899
|
+
}
|
|
2900
|
+
default:
|
|
2901
|
+
return {
|
|
2902
|
+
valid: false,
|
|
2903
|
+
error: {
|
|
2904
|
+
code: "UNSUPPORTED_FORMAT",
|
|
2905
|
+
message: `Unsupported x402 artifact format: "${format}"`
|
|
2906
|
+
}
|
|
2907
|
+
};
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
|
|
1622
2911
|
// src/reputation/proof/evidence-pointer.ts
|
|
1623
2912
|
function createEvidencePointerProof(url) {
|
|
1624
2913
|
if (!url || typeof url !== "string") {
|
|
@@ -1632,63 +2921,12 @@ function createEvidencePointerProof(url) {
|
|
|
1632
2921
|
issuedAt: Math.floor(Date.now() / 1e3)
|
|
1633
2922
|
};
|
|
1634
2923
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
const prefix = options.recordPrefix ?? "_controllers";
|
|
1641
|
-
const host = `${prefix}.${domain.toLowerCase().replace(/\.$/, "")}`;
|
|
1642
|
-
let records;
|
|
1643
|
-
try {
|
|
1644
|
-
records = await (options.resolveTxt ?? promises.resolveTxt)(host);
|
|
1645
|
-
} catch (err) {
|
|
1646
|
-
throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
|
|
1647
|
-
}
|
|
1648
|
-
for (const recordParts of records) {
|
|
1649
|
-
const record = recordParts.join("");
|
|
1650
|
-
const parsed = parseDnsTxtRecord(record);
|
|
1651
|
-
if (parsed.version === "1" && parsed.controller && normalizeDid(parsed.controller) === expected) {
|
|
1652
|
-
return { valid: true, record };
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
return { valid: false, reason: "No TXT record matched expected controller DID" };
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
// src/reputation/proof/dns-txt-shared.ts
|
|
1659
|
-
async function verifyDnsTxtControllerDid2(domain, expectedControllerDid, options = {}) {
|
|
1660
|
-
if (!domain || typeof domain !== "string") {
|
|
1661
|
-
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
1662
|
-
}
|
|
1663
|
-
if (!options.resolveTxt) {
|
|
1664
|
-
throw new OmaTrustError("NETWORK_ERROR", "No DNS TXT resolver was provided", { domain });
|
|
1665
|
-
}
|
|
1666
|
-
const expected = normalizeDid(expectedControllerDid);
|
|
1667
|
-
const prefix = options.recordPrefix ?? "_controllers";
|
|
1668
|
-
const host = `${prefix}.${domain.toLowerCase().replace(/\.$/, "")}`;
|
|
1669
|
-
let records;
|
|
1670
|
-
try {
|
|
1671
|
-
records = await options.resolveTxt(host);
|
|
1672
|
-
} catch (err) {
|
|
1673
|
-
throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
|
|
1674
|
-
}
|
|
1675
|
-
for (const recordParts of records) {
|
|
1676
|
-
const record = recordParts.join("");
|
|
1677
|
-
const parsed = parseDnsTxtRecord(record);
|
|
1678
|
-
if (parsed.version === "1" && parsed.controller && normalizeDid(parsed.controller) === expected) {
|
|
1679
|
-
return { valid: true, record };
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
return { valid: false, reason: "No TXT record matched expected controller DID" };
|
|
2924
|
+
function verifyDnsTxtControllerDid2(domain, expectedControllerDid, options = {}) {
|
|
2925
|
+
return verifyDnsTxtControllerDid(domain, expectedControllerDid, {
|
|
2926
|
+
...options,
|
|
2927
|
+
resolveTxt: options.resolveTxt ?? promises.resolveTxt
|
|
2928
|
+
});
|
|
1683
2929
|
}
|
|
1684
|
-
|
|
1685
|
-
// src/reputation/proof/subject-ownership.ts
|
|
1686
|
-
var EIP1967_ADMIN_SLOT = "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103";
|
|
1687
|
-
var OWNERSHIP_PATTERNS = [
|
|
1688
|
-
{ method: "owner", signature: "function owner() view returns (address)" },
|
|
1689
|
-
{ method: "admin", signature: "function admin() view returns (address)" },
|
|
1690
|
-
{ method: "getOwner", signature: "function getOwner() view returns (address)" }
|
|
1691
|
-
];
|
|
1692
2930
|
function assertConnectedWalletDid(input) {
|
|
1693
2931
|
const normalized = normalizeDid(input);
|
|
1694
2932
|
if (extractDidMethod(normalized) !== "pkh") {
|
|
@@ -1701,43 +2939,6 @@ function assertConnectedWalletDid(input) {
|
|
|
1701
2939
|
function normalizeSubjectDid(input) {
|
|
1702
2940
|
return normalizeDid(input);
|
|
1703
2941
|
}
|
|
1704
|
-
async function readAddressFromContract(provider, contractAddress, signature, method) {
|
|
1705
|
-
try {
|
|
1706
|
-
const iface = new ethers.Interface([signature]);
|
|
1707
|
-
const data = iface.encodeFunctionData(method, []);
|
|
1708
|
-
const result = await provider.call({ to: contractAddress, data });
|
|
1709
|
-
const [value] = iface.decodeFunctionResult(method, result);
|
|
1710
|
-
if (typeof value === "string" && ethers.isAddress(value) && value !== ethers.ZeroAddress) {
|
|
1711
|
-
return ethers.getAddress(value);
|
|
1712
|
-
}
|
|
1713
|
-
} catch {
|
|
1714
|
-
}
|
|
1715
|
-
return null;
|
|
1716
|
-
}
|
|
1717
|
-
async function discoverControllingWallet(provider, contractAddress, chainId) {
|
|
1718
|
-
for (const pattern of OWNERSHIP_PATTERNS) {
|
|
1719
|
-
const address = await readAddressFromContract(
|
|
1720
|
-
provider,
|
|
1721
|
-
contractAddress,
|
|
1722
|
-
pattern.signature,
|
|
1723
|
-
pattern.method
|
|
1724
|
-
);
|
|
1725
|
-
if (address) {
|
|
1726
|
-
return buildEvmDidPkh(chainId, address);
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
try {
|
|
1730
|
-
const adminValue = await provider.getStorage(contractAddress, EIP1967_ADMIN_SLOT);
|
|
1731
|
-
if (adminValue && adminValue !== "0x" && adminValue !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
1732
|
-
const adminAddress = ethers.getAddress(`0x${adminValue.slice(-40)}`);
|
|
1733
|
-
if (adminAddress !== ethers.ZeroAddress) {
|
|
1734
|
-
return buildEvmDidPkh(chainId, adminAddress);
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
} catch {
|
|
1738
|
-
}
|
|
1739
|
-
return null;
|
|
1740
|
-
}
|
|
1741
2942
|
async function verifyDidWebOwnership(params) {
|
|
1742
2943
|
const subjectDid = normalizeSubjectDid(params.subjectDid);
|
|
1743
2944
|
const connectedWalletDid = assertConnectedWalletDid(params.connectedWalletDid);
|
|
@@ -1749,7 +2950,7 @@ async function verifyDidWebOwnership(params) {
|
|
|
1749
2950
|
}
|
|
1750
2951
|
let dnsReason;
|
|
1751
2952
|
try {
|
|
1752
|
-
const dnsResult = await
|
|
2953
|
+
const dnsResult = await verifyDnsTxtControllerDid(domain, connectedWalletDid, {
|
|
1753
2954
|
resolveTxt: params.resolveTxt,
|
|
1754
2955
|
recordPrefix: params.recordPrefix
|
|
1755
2956
|
});
|
|
@@ -1835,7 +3036,7 @@ async function verifyDidPkhOwnership(params) {
|
|
|
1835
3036
|
connectedWalletDid
|
|
1836
3037
|
};
|
|
1837
3038
|
}
|
|
1838
|
-
const controllingWalletDid = await
|
|
3039
|
+
const controllingWalletDid = await discoverControllingWalletDid(params.provider, subjectAddress, chainId);
|
|
1839
3040
|
if (params.txHash) {
|
|
1840
3041
|
if (!controllingWalletDid) {
|
|
1841
3042
|
return {
|
|
@@ -1918,7 +3119,7 @@ async function verifyDidPkhOwnership(params) {
|
|
|
1918
3119
|
};
|
|
1919
3120
|
}
|
|
1920
3121
|
for (const pattern of OWNERSHIP_PATTERNS) {
|
|
1921
|
-
const ownerAddress = await
|
|
3122
|
+
const ownerAddress = await readOwnerFromContract(
|
|
1922
3123
|
params.provider,
|
|
1923
3124
|
subjectAddress,
|
|
1924
3125
|
pattern.signature,
|
|
@@ -1992,7 +3193,303 @@ async function verifySubjectOwnership(params) {
|
|
|
1992
3193
|
subjectDid
|
|
1993
3194
|
});
|
|
1994
3195
|
}
|
|
3196
|
+
function extractEip712Signer(proof) {
|
|
3197
|
+
const { domain, message, signature } = proof.proofObject;
|
|
3198
|
+
const typedData = {
|
|
3199
|
+
domain,
|
|
3200
|
+
types: {
|
|
3201
|
+
OmaTrustProof: [
|
|
3202
|
+
{ name: "signer", type: "address" },
|
|
3203
|
+
{ name: "authorizedEntity", type: "string" },
|
|
3204
|
+
{ name: "signingPurpose", type: "string" },
|
|
3205
|
+
{ name: "creationTimestamp", type: "uint256" },
|
|
3206
|
+
{ name: "expirationTimestamp", type: "uint256" },
|
|
3207
|
+
{ name: "randomValue", type: "bytes32" },
|
|
3208
|
+
{ name: "statement", type: "string" }
|
|
3209
|
+
]
|
|
3210
|
+
},
|
|
3211
|
+
message
|
|
3212
|
+
};
|
|
3213
|
+
try {
|
|
3214
|
+
const result = verifyEip712Signature(typedData, signature);
|
|
3215
|
+
return result.valid && result.signer ? result.signer : null;
|
|
3216
|
+
} catch (err) {
|
|
3217
|
+
console.warn("[omatrust] EIP-712 signer extraction failed:", err);
|
|
3218
|
+
return null;
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
function extractJwsHeaderJwk(proof) {
|
|
3222
|
+
const jws = proof.proofObject;
|
|
3223
|
+
if (typeof jws !== "string") return null;
|
|
3224
|
+
const parts = jws.split(".");
|
|
3225
|
+
if (parts.length !== 3) return null;
|
|
3226
|
+
try {
|
|
3227
|
+
const headerB64 = parts[0].replace(/-/g, "+").replace(/_/g, "/");
|
|
3228
|
+
const padded = headerB64.padEnd(
|
|
3229
|
+
headerB64.length + (4 - headerB64.length % 4) % 4,
|
|
3230
|
+
"="
|
|
3231
|
+
);
|
|
3232
|
+
let headerJson;
|
|
3233
|
+
if (typeof atob === "function") {
|
|
3234
|
+
headerJson = decodeURIComponent(
|
|
3235
|
+
Array.from(atob(padded)).map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`).join("")
|
|
3236
|
+
);
|
|
3237
|
+
} else {
|
|
3238
|
+
headerJson = Buffer.from(padded, "base64").toString("utf8");
|
|
3239
|
+
}
|
|
3240
|
+
const header = JSON.parse(headerJson);
|
|
3241
|
+
return header.jwk ?? null;
|
|
3242
|
+
} catch {
|
|
3243
|
+
return null;
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
function extractJwsPayload(proof) {
|
|
3247
|
+
const jws = proof.proofObject;
|
|
3248
|
+
if (typeof jws !== "string") return null;
|
|
3249
|
+
const parts = jws.split(".");
|
|
3250
|
+
if (parts.length !== 3) return null;
|
|
3251
|
+
try {
|
|
3252
|
+
const payloadB64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
3253
|
+
const padded = payloadB64.padEnd(
|
|
3254
|
+
payloadB64.length + (4 - payloadB64.length % 4) % 4,
|
|
3255
|
+
"="
|
|
3256
|
+
);
|
|
3257
|
+
let payloadJson;
|
|
3258
|
+
if (typeof atob === "function") {
|
|
3259
|
+
payloadJson = decodeURIComponent(
|
|
3260
|
+
Array.from(atob(padded)).map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`).join("")
|
|
3261
|
+
);
|
|
3262
|
+
} else {
|
|
3263
|
+
payloadJson = Buffer.from(padded, "base64").toString("utf8");
|
|
3264
|
+
}
|
|
3265
|
+
return JSON.parse(payloadJson);
|
|
3266
|
+
} catch {
|
|
3267
|
+
return null;
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
function signerMatchesDid(signerAddress, did) {
|
|
3271
|
+
try {
|
|
3272
|
+
const didAddress = extractAddressFromDid(did);
|
|
3273
|
+
return ethers.getAddress(signerAddress).toLowerCase() === ethers.getAddress(didAddress).toLowerCase();
|
|
3274
|
+
} catch {
|
|
3275
|
+
return false;
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
function jwkMatchesDid(jwk, did) {
|
|
3279
|
+
const method = extractDidMethod(did);
|
|
3280
|
+
if (method === "jwk") {
|
|
3281
|
+
try {
|
|
3282
|
+
const didJwk = didJwkToJwk(did);
|
|
3283
|
+
return publicJwkEquals(jwk, didJwk);
|
|
3284
|
+
} catch {
|
|
3285
|
+
return false;
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
return false;
|
|
3289
|
+
}
|
|
3290
|
+
function proofSignerMatchesDid(proof, did) {
|
|
3291
|
+
switch (proof.proofType) {
|
|
3292
|
+
case "pop-eip712": {
|
|
3293
|
+
const signer = extractEip712Signer(proof);
|
|
3294
|
+
if (!signer) return false;
|
|
3295
|
+
return signerMatchesDid(signer, did);
|
|
3296
|
+
}
|
|
3297
|
+
case "pop-jws": {
|
|
3298
|
+
const jwsProof = proof;
|
|
3299
|
+
const jwk = extractJwsHeaderJwk(jwsProof);
|
|
3300
|
+
if (jwk && jwkMatchesDid(jwk, did)) return true;
|
|
3301
|
+
const payload = extractJwsPayload(jwsProof);
|
|
3302
|
+
if (payload && typeof payload.iss === "string") {
|
|
3303
|
+
if (payload.iss === did) return true;
|
|
3304
|
+
}
|
|
3305
|
+
return false;
|
|
3306
|
+
}
|
|
3307
|
+
case "tx-encoded-value":
|
|
3308
|
+
case "tx-interaction":
|
|
3309
|
+
return false;
|
|
3310
|
+
case "evidence-pointer":
|
|
3311
|
+
return false;
|
|
3312
|
+
default:
|
|
3313
|
+
return false;
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
function proofAuthorizedEntityMatchesDid(proof, did) {
|
|
3317
|
+
switch (proof.proofType) {
|
|
3318
|
+
case "pop-eip712": {
|
|
3319
|
+
const eip712 = proof;
|
|
3320
|
+
const authorizedEntity = eip712.proofObject.message.authorizedEntity;
|
|
3321
|
+
return typeof authorizedEntity === "string" && authorizedEntity === did;
|
|
3322
|
+
}
|
|
3323
|
+
case "pop-jws": {
|
|
3324
|
+
const payload = extractJwsPayload(proof);
|
|
3325
|
+
if (!payload) return false;
|
|
3326
|
+
return typeof payload.aud === "string" && payload.aud === did;
|
|
3327
|
+
}
|
|
3328
|
+
default:
|
|
3329
|
+
return false;
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
function verifyLinkedIdentifierProofs(data) {
|
|
3333
|
+
if (!data.subject || !data.linkedId) {
|
|
3334
|
+
return {
|
|
3335
|
+
valid: false,
|
|
3336
|
+
checks: [],
|
|
3337
|
+
reasons: ["subject and linkedId are required"]
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
if (!Array.isArray(data.proofs) || data.proofs.length === 0) {
|
|
3341
|
+
return {
|
|
3342
|
+
valid: false,
|
|
3343
|
+
checks: [],
|
|
3344
|
+
reasons: ["At least one proof is required"]
|
|
3345
|
+
};
|
|
3346
|
+
}
|
|
3347
|
+
const checks = [];
|
|
3348
|
+
let subjectProved = false;
|
|
3349
|
+
let linkedIdProved = false;
|
|
3350
|
+
for (let i = 0; i < data.proofs.length; i++) {
|
|
3351
|
+
const proof = data.proofs[i];
|
|
3352
|
+
if (proofSignerMatchesDid(proof, data.subject)) {
|
|
3353
|
+
checks.push({
|
|
3354
|
+
proofIndex: i,
|
|
3355
|
+
proofType: proof.proofType,
|
|
3356
|
+
checkType: "signer-is-subject",
|
|
3357
|
+
valid: true
|
|
3358
|
+
});
|
|
3359
|
+
subjectProved = true;
|
|
3360
|
+
if (!proofAuthorizedEntityMatchesDid(proof, data.linkedId)) {
|
|
3361
|
+
checks.push({
|
|
3362
|
+
proofIndex: i,
|
|
3363
|
+
proofType: proof.proofType,
|
|
3364
|
+
checkType: "signer-is-subject",
|
|
3365
|
+
valid: false,
|
|
3366
|
+
reason: "Proof from subject does not authorize linkedId (aud mismatch)"
|
|
3367
|
+
});
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
if (proofSignerMatchesDid(proof, data.linkedId)) {
|
|
3371
|
+
checks.push({
|
|
3372
|
+
proofIndex: i,
|
|
3373
|
+
proofType: proof.proofType,
|
|
3374
|
+
checkType: "signer-is-linkedId",
|
|
3375
|
+
valid: true
|
|
3376
|
+
});
|
|
3377
|
+
linkedIdProved = true;
|
|
3378
|
+
if (!proofAuthorizedEntityMatchesDid(proof, data.subject)) {
|
|
3379
|
+
checks.push({
|
|
3380
|
+
proofIndex: i,
|
|
3381
|
+
proofType: proof.proofType,
|
|
3382
|
+
checkType: "signer-is-linkedId",
|
|
3383
|
+
valid: false,
|
|
3384
|
+
reason: "Proof from linkedId does not authorize subject (aud mismatch)"
|
|
3385
|
+
});
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
if (proof.proofType === "evidence-pointer") {
|
|
3389
|
+
checks.push({
|
|
3390
|
+
proofIndex: i,
|
|
3391
|
+
proofType: proof.proofType,
|
|
3392
|
+
checkType: "signer-is-subject",
|
|
3393
|
+
valid: true,
|
|
3394
|
+
reason: "Evidence pointer accepted (requires URL fetch for full verification)"
|
|
3395
|
+
});
|
|
3396
|
+
subjectProved = true;
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
const reasons = [];
|
|
3400
|
+
if (!subjectProved) {
|
|
3401
|
+
reasons.push("No proof demonstrates control by subject");
|
|
3402
|
+
}
|
|
3403
|
+
if (!linkedIdProved) {
|
|
3404
|
+
reasons.push("No proof demonstrates control by linkedId");
|
|
3405
|
+
}
|
|
3406
|
+
return {
|
|
3407
|
+
valid: reasons.length === 0,
|
|
3408
|
+
checks,
|
|
3409
|
+
reasons
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
function verifyKeyBindingProofs(data) {
|
|
3413
|
+
if (!data.subject || !data.keyId) {
|
|
3414
|
+
return {
|
|
3415
|
+
valid: false,
|
|
3416
|
+
checks: [],
|
|
3417
|
+
reasons: ["subject and keyId are required"]
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
3420
|
+
if (!Array.isArray(data.proofs) || data.proofs.length === 0) {
|
|
3421
|
+
return {
|
|
3422
|
+
valid: false,
|
|
3423
|
+
checks: [],
|
|
3424
|
+
reasons: ["At least one proof is required"]
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
3427
|
+
const checks = [];
|
|
3428
|
+
let subjectAuthorizedKey = false;
|
|
3429
|
+
for (let i = 0; i < data.proofs.length; i++) {
|
|
3430
|
+
const proof = data.proofs[i];
|
|
3431
|
+
if (proofSignerMatchesDid(proof, data.subject)) {
|
|
3432
|
+
const authorizesKey = proofAuthorizedEntityMatchesDid(proof, data.keyId);
|
|
3433
|
+
checks.push({
|
|
3434
|
+
proofIndex: i,
|
|
3435
|
+
proofType: proof.proofType,
|
|
3436
|
+
checkType: "signer-is-subject-for-key",
|
|
3437
|
+
valid: authorizesKey,
|
|
3438
|
+
reason: authorizesKey ? void 0 : "Proof signed by subject but does not authorize the keyId"
|
|
3439
|
+
});
|
|
3440
|
+
if (authorizesKey) {
|
|
3441
|
+
subjectAuthorizedKey = true;
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
if (proofSignerMatchesDid(proof, data.keyId)) {
|
|
3445
|
+
checks.push({
|
|
3446
|
+
proofIndex: i,
|
|
3447
|
+
proofType: proof.proofType,
|
|
3448
|
+
checkType: "signer-is-keyId",
|
|
3449
|
+
valid: true,
|
|
3450
|
+
reason: "Key proved possession (supplementary, not sufficient alone)"
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
3453
|
+
if (proof.proofType === "evidence-pointer") {
|
|
3454
|
+
checks.push({
|
|
3455
|
+
proofIndex: i,
|
|
3456
|
+
proofType: proof.proofType,
|
|
3457
|
+
checkType: "signer-is-subject-for-key",
|
|
3458
|
+
valid: true,
|
|
3459
|
+
reason: "Evidence pointer accepted (requires URL fetch for full verification)"
|
|
3460
|
+
});
|
|
3461
|
+
subjectAuthorizedKey = true;
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
if (data.publicKeyJwk && extractDidMethod(data.keyId) === "jwk") {
|
|
3465
|
+
try {
|
|
3466
|
+
const keyIdJwk = didJwkToJwk(data.keyId);
|
|
3467
|
+
const jwkConsistent = publicJwkEquals(data.publicKeyJwk, keyIdJwk);
|
|
3468
|
+
if (!jwkConsistent) {
|
|
3469
|
+
return {
|
|
3470
|
+
valid: false,
|
|
3471
|
+
checks,
|
|
3472
|
+
reasons: ["publicKeyJwk does not match the key material in keyId (did:jwk)"]
|
|
3473
|
+
};
|
|
3474
|
+
}
|
|
3475
|
+
} catch {
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
const reasons = [];
|
|
3479
|
+
if (!subjectAuthorizedKey) {
|
|
3480
|
+
reasons.push(
|
|
3481
|
+
"No proof demonstrates that subject authorized the key binding"
|
|
3482
|
+
);
|
|
3483
|
+
}
|
|
3484
|
+
return {
|
|
3485
|
+
valid: reasons.length === 0,
|
|
3486
|
+
checks,
|
|
3487
|
+
reasons
|
|
3488
|
+
};
|
|
3489
|
+
}
|
|
1995
3490
|
|
|
3491
|
+
exports.EIP1967_ADMIN_SLOT = EIP1967_ADMIN_SLOT;
|
|
3492
|
+
exports.OWNERSHIP_PATTERNS = OWNERSHIP_PATTERNS;
|
|
1996
3493
|
exports.buildDelegatedAttestationTypedData = buildDelegatedAttestationTypedData;
|
|
1997
3494
|
exports.buildDelegatedTypedDataFromEncoded = buildDelegatedTypedDataFromEncoded;
|
|
1998
3495
|
exports.buildDnsTxtRecord = buildDnsTxtRecord;
|
|
@@ -2011,9 +3508,12 @@ exports.createX402OfferProof = createX402OfferProof;
|
|
|
2011
3508
|
exports.createX402ReceiptProof = createX402ReceiptProof;
|
|
2012
3509
|
exports.decodeAttestationData = decodeAttestationData;
|
|
2013
3510
|
exports.deduplicateReviews = deduplicateReviews;
|
|
3511
|
+
exports.discoverContractOwner = discoverContractOwner;
|
|
3512
|
+
exports.discoverControllingWalletDid = discoverControllingWalletDid;
|
|
2014
3513
|
exports.encodeAttestationData = encodeAttestationData;
|
|
2015
|
-
exports.
|
|
3514
|
+
exports.extractEvmAddressesFromDidDocument = extractEvmAddressesFromDidDocument;
|
|
2016
3515
|
exports.extractExpirationTime = extractExpirationTime;
|
|
3516
|
+
exports.extractJwksFromDidDocument = extractJwksFromDidDocument;
|
|
2017
3517
|
exports.fetchDidDocument = fetchDidDocument;
|
|
2018
3518
|
exports.formatSchemaUid = formatSchemaUid;
|
|
2019
3519
|
exports.formatTransferAmount = formatTransferAmount;
|
|
@@ -2021,6 +3521,7 @@ exports.getAttestation = getAttestation;
|
|
|
2021
3521
|
exports.getAttestationsByAttester = getAttestationsByAttester;
|
|
2022
3522
|
exports.getAttestationsForDid = getAttestationsForDid;
|
|
2023
3523
|
exports.getChainConstants = getChainConstants;
|
|
3524
|
+
exports.getControllerAuthorization = getControllerAuthorization;
|
|
2024
3525
|
exports.getExplorerAddressUrl = getExplorerAddressUrl;
|
|
2025
3526
|
exports.getExplorerTxUrl = getExplorerTxUrl;
|
|
2026
3527
|
exports.getLatestAttestations = getLatestAttestations;
|
|
@@ -2034,6 +3535,8 @@ exports.listAttestations = listAttestations;
|
|
|
2034
3535
|
exports.normalizeSchema = normalizeSchema;
|
|
2035
3536
|
exports.parseDnsTxtRecord = parseDnsTxtRecord;
|
|
2036
3537
|
exports.prepareDelegatedAttestation = prepareDelegatedAttestation;
|
|
3538
|
+
exports.readOwnerFromContract = readOwnerFromContract;
|
|
3539
|
+
exports.requestControllerWitness = requestControllerWitness;
|
|
2037
3540
|
exports.revokeAttestation = revokeAttestation;
|
|
2038
3541
|
exports.schemaToString = schemaToString;
|
|
2039
3542
|
exports.splitSignature = splitSignature;
|
|
@@ -2045,10 +3548,20 @@ exports.verifyDidDocumentControllerDid = verifyDidDocumentControllerDid;
|
|
|
2045
3548
|
exports.verifyDidJsonControllerDid = verifyDidJsonControllerDid;
|
|
2046
3549
|
exports.verifyDidPkhOwnership = verifyDidPkhOwnership;
|
|
2047
3550
|
exports.verifyDidWebOwnership = verifyDidWebOwnership;
|
|
2048
|
-
exports.verifyDnsTxtControllerDid =
|
|
3551
|
+
exports.verifyDnsTxtControllerDid = verifyDnsTxtControllerDid2;
|
|
2049
3552
|
exports.verifyEip712Signature = verifyEip712Signature;
|
|
3553
|
+
exports.verifyKeyBindingProofs = verifyKeyBindingProofs;
|
|
3554
|
+
exports.verifyLinkedIdentifierProofs = verifyLinkedIdentifierProofs;
|
|
2050
3555
|
exports.verifyProof = verifyProof;
|
|
2051
3556
|
exports.verifySchemaExists = verifySchemaExists;
|
|
2052
3557
|
exports.verifySubjectOwnership = verifySubjectOwnership;
|
|
3558
|
+
exports.verifyTransferProof = verifyTransferProof;
|
|
3559
|
+
exports.verifyX402Artifact = verifyX402Artifact;
|
|
3560
|
+
exports.verifyX402Eip712Artifact = verifyX402Eip712Artifact;
|
|
3561
|
+
exports.verifyX402Eip712Offer = verifyX402Eip712Offer;
|
|
3562
|
+
exports.verifyX402Eip712Receipt = verifyX402Eip712Receipt;
|
|
3563
|
+
exports.verifyX402JwsArtifact = verifyX402JwsArtifact;
|
|
3564
|
+
exports.verifyX402JwsOffer = verifyX402JwsOffer;
|
|
3565
|
+
exports.verifyX402JwsReceipt = verifyX402JwsReceipt;
|
|
2053
3566
|
//# sourceMappingURL=index.cjs.map
|
|
2054
3567
|
//# sourceMappingURL=index.cjs.map
|