@oma3/omatrust 0.1.0-alpha.10 → 0.1.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/identity/index.cjs +543 -0
  2. package/dist/identity/index.cjs.map +1 -1
  3. package/dist/identity/index.d.cts +2 -1
  4. package/dist/identity/index.d.ts +2 -1
  5. package/dist/identity/index.js +527 -1
  6. package/dist/identity/index.js.map +1 -1
  7. package/dist/index-BOvk-7Ku.d.cts +235 -0
  8. package/dist/index-C2w5EvFH.d.ts +329 -0
  9. package/dist/index-QueRiudB.d.cts +329 -0
  10. package/dist/index-R78TpAhN.d.ts +235 -0
  11. package/dist/index.cjs +1475 -165
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +4 -3
  14. package/dist/index.d.ts +4 -3
  15. package/dist/index.js +1476 -166
  16. package/dist/index.js.map +1 -1
  17. package/dist/reputation/index.browser.cjs +943 -66
  18. package/dist/reputation/index.browser.cjs.map +1 -1
  19. package/dist/reputation/index.browser.d.cts +2 -2
  20. package/dist/reputation/index.browser.d.ts +2 -2
  21. package/dist/reputation/index.browser.js +941 -66
  22. package/dist/reputation/index.browser.js.map +1 -1
  23. package/dist/reputation/index.cjs +1393 -217
  24. package/dist/reputation/index.cjs.map +1 -1
  25. package/dist/reputation/index.d.cts +3 -2
  26. package/dist/reputation/index.d.ts +3 -2
  27. package/dist/reputation/index.js +1378 -217
  28. package/dist/reputation/index.js.map +1 -1
  29. package/dist/{subject-ownership-CXvzEjpH.d.cts → subject-ownership-B7cFlm8c.d.cts} +256 -26
  30. package/dist/{subject-ownership-CXvzEjpH.d.ts → subject-ownership-B7cFlm8c.d.ts} +256 -26
  31. package/dist/types-dpYxRq8N.d.cts +281 -0
  32. package/dist/types-dpYxRq8N.d.ts +281 -0
  33. package/dist/widgets/index.cjs +70 -27
  34. package/dist/widgets/index.cjs.map +1 -1
  35. package/dist/widgets/index.d.cts +42 -18
  36. package/dist/widgets/index.d.ts +42 -18
  37. package/dist/widgets/index.js +67 -26
  38. package/dist/widgets/index.js.map +1 -1
  39. package/package.json +3 -2
  40. package/dist/index-B9KW02US.d.cts +0 -119
  41. package/dist/index-DXrwBex9.d.ts +0 -119
  42. package/dist/index-QZDExA4I.d.cts +0 -90
  43. 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>.+)$/;
@@ -310,6 +316,8 @@ function normalizeDid(input) {
310
316
  return normalizeDidHandle(trimmed);
311
317
  case "key":
312
318
  return normalizeDidKey(trimmed);
319
+ case "jwk":
320
+ return normalizeDidJwk(trimmed);
313
321
  default:
314
322
  return trimmed;
315
323
  }
@@ -399,6 +407,73 @@ function extractAddressFromDid(identifier) {
399
407
  }
400
408
  throw new OmaTrustError("INVALID_DID", "Unsupported identifier format", { identifier });
401
409
  }
410
+ var BASE64URL_REGEX = /^[A-Za-z0-9_-]+$/;
411
+ var VALID_JWK_KTY = /* @__PURE__ */ new Set(["EC", "OKP", "RSA"]);
412
+ function validateDidJwk(did) {
413
+ const parts = did.split(":");
414
+ if (parts.length !== 3) {
415
+ return {
416
+ valid: false,
417
+ method: "jwk",
418
+ error: `did:jwk must have exactly 3 colon-separated parts, got ${parts.length}`
419
+ };
420
+ }
421
+ const [, , encoded] = parts;
422
+ if (!encoded || encoded.length === 0) {
423
+ return { valid: false, method: "jwk", error: "Missing base64url-encoded JWK identifier" };
424
+ }
425
+ if (!BASE64URL_REGEX.test(encoded)) {
426
+ return {
427
+ valid: false,
428
+ method: "jwk",
429
+ error: "Identifier contains invalid base64url characters"
430
+ };
431
+ }
432
+ let decoded;
433
+ try {
434
+ const bytes = jose.base64url.decode(encoded);
435
+ decoded = new TextDecoder().decode(bytes);
436
+ } catch {
437
+ return { valid: false, method: "jwk", error: "Failed to base64url-decode identifier" };
438
+ }
439
+ let jwk;
440
+ try {
441
+ jwk = JSON.parse(decoded);
442
+ } catch {
443
+ return { valid: false, method: "jwk", error: "Decoded identifier is not valid JSON" };
444
+ }
445
+ if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) {
446
+ return { valid: false, method: "jwk", error: "Decoded JWK must be a JSON object" };
447
+ }
448
+ const kty = jwk.kty;
449
+ if (typeof kty !== "string" || !VALID_JWK_KTY.has(kty)) {
450
+ return {
451
+ valid: false,
452
+ method: "jwk",
453
+ error: `Invalid or missing kty field (must be one of: EC, OKP, RSA)`
454
+ };
455
+ }
456
+ if ("d" in jwk) {
457
+ return {
458
+ valid: false,
459
+ method: "jwk",
460
+ error: "DID must reference a public key \u2014 private key component (d) is not allowed"
461
+ };
462
+ }
463
+ return { valid: true, method: "jwk" };
464
+ }
465
+ function normalizeDidJwk(input) {
466
+ assertString(input, "input", "INVALID_DID");
467
+ const trimmed = input.trim();
468
+ if (!trimmed.startsWith("did:jwk:")) {
469
+ throw new OmaTrustError("INVALID_DID", "Expected did:jwk DID", { input });
470
+ }
471
+ const result = validateDidJwk(trimmed);
472
+ if (!result.valid) {
473
+ throw new OmaTrustError("INVALID_DID", result.error ?? "Invalid did:jwk", { input });
474
+ }
475
+ return trimmed;
476
+ }
402
477
 
403
478
  // src/reputation/internal.ts
404
479
  var ZERO_UID = "0x0000000000000000000000000000000000000000000000000000000000000000";
@@ -685,19 +760,32 @@ async function submitDelegatedAttestation(params) {
685
760
  payload = {};
686
761
  }
687
762
  if (!response.ok) {
688
- throw new OmaTrustError("NETWORK_ERROR", "Relay submission failed", {
689
- status: response.status,
763
+ const relayError = {
764
+ httpStatus: response.status,
765
+ code: payload.code,
766
+ error: payload.error ?? payload.message,
690
767
  payload
691
- });
768
+ };
769
+ throw new OmaTrustError("RELAY_ERROR", `Relay submission failed (HTTP ${response.status})`, relayError);
692
770
  }
693
771
  const uid = payload.uid ?? ZERO_UID;
694
772
  const txHash = payload.txHash;
695
- const status = payload.status ?? "submitted";
696
- return {
697
- uid,
698
- txHash,
699
- status
700
- };
773
+ const hasConfirmationSignals = payload.status === "confirmed" || payload.success === true || typeof payload.blockNumber === "number";
774
+ const status = hasConfirmationSignals ? "confirmed" : "submitted";
775
+ const relay = {};
776
+ if (typeof payload.blockNumber === "number") relay.blockNumber = payload.blockNumber;
777
+ if (typeof payload.chain === "string") relay.chain = payload.chain;
778
+ if (typeof payload.success === "boolean") relay.success = payload.success;
779
+ for (const key of Object.keys(payload)) {
780
+ if (!["uid", "txHash", "status", "blockNumber", "chain", "success"].includes(key)) {
781
+ relay[key] = payload[key];
782
+ }
783
+ }
784
+ const result = { uid, txHash, status };
785
+ if (Object.keys(relay).length > 0) {
786
+ result.relay = relay;
787
+ }
788
+ return result;
701
789
  }
702
790
  var EAS_EVENT_ABI = [
703
791
  "event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)"
@@ -799,7 +887,7 @@ async function getAttestationsForDid(params) {
799
887
  provider,
800
888
  fromBlock,
801
889
  toBlock,
802
- { recipient: didToAddress(params.did), schemas: params.schemas }
890
+ { recipient: didToAddress(params.subjectDid), schemas: params.schemas }
803
891
  );
804
892
  }
805
893
  async function getAttestationsByAttester(params) {
@@ -1040,6 +1128,147 @@ function getExplorerTxUrl(chainId, txHash) {
1040
1128
  function getExplorerAddressUrl(chainId, address) {
1041
1129
  return `${getConfig(chainId).explorer}/address/${address}`;
1042
1130
  }
1131
+ var VALID_KTY = /* @__PURE__ */ new Set(["EC", "OKP", "RSA"]);
1132
+ var PRIVATE_KEY_FIELDS = /* @__PURE__ */ new Set(["d", "p", "q", "dp", "dq", "qi", "oth"]);
1133
+ var REQUIRED_PUBLIC_FIELDS = {
1134
+ EC: ["crv", "x", "y"],
1135
+ OKP: ["crv", "x"],
1136
+ RSA: ["n", "e"]
1137
+ };
1138
+ function validatePublicJwk(jwk) {
1139
+ if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) {
1140
+ return { valid: false, error: "JWK must be a non-null object" };
1141
+ }
1142
+ const obj = jwk;
1143
+ const kty = obj.kty;
1144
+ if (typeof kty !== "string" || !VALID_KTY.has(kty)) {
1145
+ return {
1146
+ valid: false,
1147
+ error: `Invalid or missing kty (must be one of: ${[...VALID_KTY].join(", ")})`
1148
+ };
1149
+ }
1150
+ for (const field of PRIVATE_KEY_FIELDS) {
1151
+ if (field in obj) {
1152
+ return {
1153
+ valid: false,
1154
+ error: `JWK contains private key field "${field}" \u2014 only public keys are allowed`
1155
+ };
1156
+ }
1157
+ }
1158
+ const required = REQUIRED_PUBLIC_FIELDS[kty];
1159
+ if (required) {
1160
+ for (const field of required) {
1161
+ if (!(field in obj) || obj[field] === void 0 || obj[field] === null || obj[field] === "") {
1162
+ return {
1163
+ valid: false,
1164
+ error: `Missing required public key field "${field}" for kty="${kty}"`
1165
+ };
1166
+ }
1167
+ }
1168
+ }
1169
+ return { valid: true };
1170
+ }
1171
+ function canonicalizeJwkJson(jwk) {
1172
+ const sorted = Object.keys(jwk).sort();
1173
+ const obj = {};
1174
+ for (const key of sorted) {
1175
+ obj[key] = jwk[key];
1176
+ }
1177
+ return JSON.stringify(obj);
1178
+ }
1179
+ function jwkToDidJwk(jwk) {
1180
+ assertObject(jwk, "jwk", "INVALID_JWK");
1181
+ const validation = validatePublicJwk(jwk);
1182
+ if (!validation.valid) {
1183
+ throw new OmaTrustError("INVALID_JWK", validation.error ?? "Invalid public JWK", { jwk });
1184
+ }
1185
+ const canonical = canonicalizeJwkJson(jwk);
1186
+ const encoded = jose.base64url.encode(new TextEncoder().encode(canonical));
1187
+ return `did:jwk:${encoded}`;
1188
+ }
1189
+ function didJwkToJwk(didJwk) {
1190
+ if (typeof didJwk !== "string" || !didJwk.startsWith("did:jwk:")) {
1191
+ throw new OmaTrustError("INVALID_DID", "Expected a did:jwk DID", { input: didJwk });
1192
+ }
1193
+ const parts = didJwk.split(":");
1194
+ if (parts.length !== 3) {
1195
+ throw new OmaTrustError("INVALID_DID", "did:jwk must have exactly 3 colon-separated parts", {
1196
+ input: didJwk
1197
+ });
1198
+ }
1199
+ const encoded = parts[2];
1200
+ if (!encoded || encoded.length === 0) {
1201
+ throw new OmaTrustError("INVALID_DID", "Missing base64url-encoded JWK identifier", {
1202
+ input: didJwk
1203
+ });
1204
+ }
1205
+ let decoded;
1206
+ try {
1207
+ const bytes = jose.base64url.decode(encoded);
1208
+ decoded = new TextDecoder().decode(bytes);
1209
+ } catch {
1210
+ throw new OmaTrustError("INVALID_DID", "Failed to base64url-decode did:jwk identifier", {
1211
+ input: didJwk
1212
+ });
1213
+ }
1214
+ let jwk;
1215
+ try {
1216
+ jwk = JSON.parse(decoded);
1217
+ } catch {
1218
+ throw new OmaTrustError("INVALID_DID", "Decoded did:jwk identifier is not valid JSON", {
1219
+ input: didJwk
1220
+ });
1221
+ }
1222
+ if (!jwk || typeof jwk !== "object" || Array.isArray(jwk)) {
1223
+ throw new OmaTrustError("INVALID_DID", "Decoded did:jwk must be a JSON object", {
1224
+ input: didJwk
1225
+ });
1226
+ }
1227
+ const validation = validatePublicJwk(jwk);
1228
+ if (!validation.valid) {
1229
+ throw new OmaTrustError("INVALID_DID", validation.error ?? "Invalid public JWK in did:jwk", {
1230
+ input: didJwk
1231
+ });
1232
+ }
1233
+ return jwk;
1234
+ }
1235
+ function extractPublicKeyFields(jwk) {
1236
+ const result = {};
1237
+ const metadataFields = /* @__PURE__ */ new Set(["kid", "use", "key_ops", "alg", "ext"]);
1238
+ for (const [key, value] of Object.entries(jwk)) {
1239
+ if (PRIVATE_KEY_FIELDS.has(key)) continue;
1240
+ if (metadataFields.has(key)) continue;
1241
+ result[key] = value;
1242
+ }
1243
+ return result;
1244
+ }
1245
+ function publicJwkEquals(a, b) {
1246
+ assertObject(a, "a", "INVALID_JWK");
1247
+ assertObject(b, "b", "INVALID_JWK");
1248
+ const aObj = a;
1249
+ const bObj = b;
1250
+ for (const field of PRIVATE_KEY_FIELDS) {
1251
+ if (field in aObj) {
1252
+ throw new OmaTrustError(
1253
+ "INVALID_JWK",
1254
+ `First JWK contains private key field "${field}"`,
1255
+ { field }
1256
+ );
1257
+ }
1258
+ if (field in bObj) {
1259
+ throw new OmaTrustError(
1260
+ "INVALID_JWK",
1261
+ `Second JWK contains private key field "${field}"`,
1262
+ { field }
1263
+ );
1264
+ }
1265
+ }
1266
+ const aPublic = extractPublicKeyFields(aObj);
1267
+ const bPublic = extractPublicKeyFields(bObj);
1268
+ return canonicalizeJwkJson(aPublic) === canonicalizeJwkJson(bPublic);
1269
+ }
1270
+
1271
+ // src/shared/did-document.ts
1043
1272
  async function fetchDidDocument(domain) {
1044
1273
  const normalized = domain.toLowerCase().replace(/\.$/, "");
1045
1274
  const url = `https://${normalized}/.well-known/did.json`;
@@ -1050,15 +1279,16 @@ async function fetchDidDocument(domain) {
1050
1279
  throw new OmaTrustError("NETWORK_ERROR", "Failed to fetch DID document", { domain, err });
1051
1280
  }
1052
1281
  if (!response.ok) {
1053
- throw new OmaTrustError("NETWORK_ERROR", "DID document fetch failed", {
1282
+ throw new OmaTrustError("NETWORK_ERROR", `DID document fetch failed: ${response.status}`, {
1054
1283
  domain,
1055
1284
  status: response.status
1056
1285
  });
1057
1286
  }
1058
- const body = await response.json();
1059
- return body;
1287
+ return await response.json();
1060
1288
  }
1061
- function extractAddressesFromDidDocument(didDocument) {
1289
+
1290
+ // src/reputation/proof/did-json.ts
1291
+ function extractEvmAddressesFromDidDocument(didDocument) {
1062
1292
  const methods = didDocument.verificationMethod;
1063
1293
  if (!Array.isArray(methods)) {
1064
1294
  return [];
@@ -1082,14 +1312,52 @@ function extractAddressesFromDidDocument(didDocument) {
1082
1312
  }
1083
1313
  return [...addresses];
1084
1314
  }
1315
+ function extractJwksFromDidDocument(didDocument) {
1316
+ const methods = didDocument.verificationMethod;
1317
+ if (!Array.isArray(methods)) {
1318
+ return [];
1319
+ }
1320
+ const jwks = [];
1321
+ for (const method of methods) {
1322
+ const publicKeyJwk = method.publicKeyJwk;
1323
+ if (publicKeyJwk && typeof publicKeyJwk === "object" && !Array.isArray(publicKeyJwk)) {
1324
+ jwks.push(publicKeyJwk);
1325
+ }
1326
+ }
1327
+ return jwks;
1328
+ }
1085
1329
  function verifyDidDocumentControllerDid(didDocument, expectedControllerDid) {
1330
+ const method = extractDidMethod(expectedControllerDid);
1331
+ if (method === "jwk") {
1332
+ try {
1333
+ const expectedJwk = didJwkToJwk(expectedControllerDid);
1334
+ const documentJwks = extractJwksFromDidDocument(didDocument);
1335
+ for (const docJwk of documentJwks) {
1336
+ try {
1337
+ if (publicJwkEquals(docJwk, expectedJwk)) {
1338
+ return { valid: true };
1339
+ }
1340
+ } catch {
1341
+ }
1342
+ }
1343
+ return {
1344
+ valid: false,
1345
+ reason: "No matching publicKeyJwk found in DID document verification methods"
1346
+ };
1347
+ } catch {
1348
+ return {
1349
+ valid: false,
1350
+ reason: "Failed to decode did:jwk controller DID"
1351
+ };
1352
+ }
1353
+ }
1086
1354
  let expectedAddress;
1087
1355
  try {
1088
1356
  expectedAddress = ethers.getAddress(extractAddressFromDid(expectedControllerDid));
1089
1357
  } catch {
1090
- return { valid: false, reason: "Expected controller DID does not resolve to an EVM address" };
1358
+ return { valid: false, reason: "Controller DID does not resolve to an EVM address and is not did:jwk" };
1091
1359
  }
1092
- const addresses = extractAddressesFromDidDocument(didDocument);
1360
+ const addresses = extractEvmAddressesFromDidDocument(didDocument);
1093
1361
  if (addresses.some((address) => address.toLowerCase() === expectedAddress.toLowerCase())) {
1094
1362
  return { valid: true };
1095
1363
  }
@@ -1145,21 +1413,23 @@ function parseDnsTxtRecord(record) {
1145
1413
  }
1146
1414
  const entries = record.split(/[;\s]+/).map((entry) => entry.trim()).filter(Boolean);
1147
1415
  const parsed = {};
1416
+ const controllers = [];
1148
1417
  for (const entry of entries) {
1149
- const [key, ...valueParts] = entry.split("=");
1150
- if (!key) {
1151
- continue;
1152
- }
1153
- const value = valueParts.join("=");
1154
- if (!value) {
1155
- continue;
1418
+ const eqIndex = entry.indexOf("=");
1419
+ if (eqIndex === -1) continue;
1420
+ const key = entry.slice(0, eqIndex).trim();
1421
+ const value = entry.slice(eqIndex + 1).trim();
1422
+ if (!key || !value) continue;
1423
+ if (key === "controller") {
1424
+ controllers.push(value);
1425
+ } else {
1426
+ parsed[key] = value;
1156
1427
  }
1157
- parsed[key.trim()] = value.trim();
1158
1428
  }
1159
1429
  return {
1160
1430
  version: parsed.v,
1161
- controller: parsed.controller,
1162
- ...parsed
1431
+ controller: controllers[0],
1432
+ controllers
1163
1433
  };
1164
1434
  }
1165
1435
  function buildDnsTxtRecord(controllerDid) {
@@ -1167,109 +1437,558 @@ function buildDnsTxtRecord(controllerDid) {
1167
1437
  return `v=1;controller=${normalized}`;
1168
1438
  }
1169
1439
 
1170
- // src/reputation/verify.ts
1171
- function parseChainId(input) {
1172
- if (typeof input === "number") {
1173
- return input;
1174
- }
1175
- if (typeof input === "string") {
1176
- if (input.includes(":")) {
1177
- const [, chainId] = input.split(":");
1178
- return Number(chainId);
1440
+ // src/identity/did-url.ts
1441
+ var DID_URL_REGEX = /^did:[a-z0-9]+:.+$/i;
1442
+ function parseDidUrl(input) {
1443
+ assertString(input, "input", "INVALID_DID_URL");
1444
+ const trimmed = input.trim();
1445
+ const hashIndex = trimmed.indexOf("#");
1446
+ let did;
1447
+ let fragment;
1448
+ if (hashIndex === -1) {
1449
+ did = trimmed;
1450
+ fragment = null;
1451
+ } else {
1452
+ did = trimmed.slice(0, hashIndex);
1453
+ const rawFragment = trimmed.slice(hashIndex + 1);
1454
+ if (rawFragment.length === 0) {
1455
+ throw new OmaTrustError(
1456
+ "INVALID_DID_URL",
1457
+ "DID URL has empty fragment (trailing '#' with no identifier)",
1458
+ { input }
1459
+ );
1179
1460
  }
1180
- return Number(input);
1461
+ fragment = rawFragment;
1181
1462
  }
1182
- return NaN;
1463
+ if (!DID_URL_REGEX.test(did)) {
1464
+ throw new OmaTrustError(
1465
+ "INVALID_DID_URL",
1466
+ "Invalid DID URL: base DID portion is malformed",
1467
+ { input, did }
1468
+ );
1469
+ }
1470
+ return {
1471
+ didUrl: trimmed,
1472
+ did,
1473
+ fragment
1474
+ };
1183
1475
  }
1184
- function parseProofs(attestation) {
1185
- const rawProofs = attestation.data.proofs;
1186
- if (!Array.isArray(rawProofs)) {
1187
- return [];
1476
+
1477
+ // src/identity/resolve-key.ts
1478
+ function findVerificationMethod(didDocument, didUrl, fragment) {
1479
+ const methods = didDocument.verificationMethod;
1480
+ if (!Array.isArray(methods)) {
1481
+ return null;
1188
1482
  }
1189
- return rawProofs.map((entry) => {
1190
- if (typeof entry === "string") {
1191
- try {
1192
- return JSON.parse(entry);
1193
- } catch {
1194
- return null;
1195
- }
1483
+ for (const method of methods) {
1484
+ if (!method || typeof method !== "object") continue;
1485
+ const id = method.id;
1486
+ if (typeof id !== "string") continue;
1487
+ if (id === didUrl) return method;
1488
+ if (fragment && (id === `#${fragment}` || id.endsWith(`#${fragment}`))) {
1489
+ return method;
1196
1490
  }
1197
- if (entry && typeof entry === "object") {
1198
- return entry;
1491
+ }
1492
+ return null;
1493
+ }
1494
+ function extractMethodIdsFromDidDocument(didDocument) {
1495
+ const methods = didDocument.verificationMethod;
1496
+ if (!Array.isArray(methods)) return [];
1497
+ return methods.filter((m) => m && typeof m.id === "string").map((m) => m.id);
1498
+ }
1499
+ async function resolveDidUrlToPublicKey(didUrl, options) {
1500
+ assertString(didUrl, "didUrl", "INVALID_DID_URL");
1501
+ const parsed = parseDidUrl(didUrl);
1502
+ const method = extractDidMethod(parsed.did);
1503
+ if (!method) {
1504
+ throw new OmaTrustError("INVALID_DID_URL", "Cannot extract DID method from DID URL", {
1505
+ didUrl,
1506
+ did: parsed.did
1507
+ });
1508
+ }
1509
+ switch (method) {
1510
+ case "web":
1511
+ return resolveDidUrlKey(parsed.didUrl, parsed.did, parsed.fragment, options);
1512
+ default:
1513
+ throw new OmaTrustError(
1514
+ "UNSUPPORTED_DID_METHOD",
1515
+ `DID URL key resolution is not supported for method "${method}"`,
1516
+ { didUrl, method }
1517
+ );
1518
+ }
1519
+ }
1520
+ async function resolveDidUrlKey(didUrl, did, fragment, options) {
1521
+ const domain = getDomainFromDidWeb(did);
1522
+ if (!domain) {
1523
+ throw new OmaTrustError("INVALID_DID_URL", "Cannot extract domain from did:web DID", {
1524
+ didUrl,
1525
+ did
1526
+ });
1527
+ }
1528
+ const fetchFn = options?.fetchDidDocument ?? fetchDidDocument;
1529
+ const didDocument = await fetchFn(domain);
1530
+ const method = findVerificationMethod(didDocument, didUrl, fragment);
1531
+ if (!method) {
1532
+ throw new OmaTrustError(
1533
+ "KEY_NOT_FOUND",
1534
+ `No verification method found matching "${fragment ? `#${fragment}` : didUrl}"`,
1535
+ { didUrl, fragment, availableMethods: extractMethodIdsFromDidDocument(didDocument) }
1536
+ );
1537
+ }
1538
+ const publicKeyJwk = method.publicKeyJwk;
1539
+ if (!publicKeyJwk || typeof publicKeyJwk !== "object") {
1540
+ throw new OmaTrustError(
1541
+ "KEY_NOT_FOUND",
1542
+ `Verification method "${method.id}" does not contain publicKeyJwk`,
1543
+ { didUrl, verificationMethodId: method.id }
1544
+ );
1545
+ }
1546
+ const validation = validatePublicJwk(publicKeyJwk);
1547
+ if (!validation.valid) {
1548
+ throw new OmaTrustError(
1549
+ "INVALID_JWK",
1550
+ `publicKeyJwk in verification method "${method.id}" is invalid: ${validation.error}`,
1551
+ { didUrl, verificationMethodId: method.id }
1552
+ );
1553
+ }
1554
+ return {
1555
+ didUrl,
1556
+ did,
1557
+ fragment,
1558
+ publicKeyJwk,
1559
+ verificationMethodId: method.id
1560
+ };
1561
+ }
1562
+
1563
+ // src/reputation/proof/x402-jws.ts
1564
+ var REQUIRED_OFFER_FIELDS = [
1565
+ "version",
1566
+ "resourceUrl",
1567
+ "scheme",
1568
+ "network",
1569
+ "asset",
1570
+ "payTo",
1571
+ "amount"
1572
+ ];
1573
+ var REQUIRED_RECEIPT_FIELDS = [
1574
+ "version",
1575
+ "network",
1576
+ "resourceUrl",
1577
+ "payer",
1578
+ "issuedAt"
1579
+ ];
1580
+ function validateOfferPayload(payload) {
1581
+ for (const field of REQUIRED_OFFER_FIELDS) {
1582
+ if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
1583
+ return { valid: false, field };
1199
1584
  }
1200
- return null;
1201
- }).filter((proof) => Boolean(proof?.proofType));
1585
+ }
1586
+ return { valid: true };
1202
1587
  }
1203
- function getProofPurpose(proof) {
1204
- if (proof.proofPurpose === "commercial-tx") {
1205
- return "commercial-tx";
1588
+ function validateReceiptPayload(payload) {
1589
+ for (const field of REQUIRED_RECEIPT_FIELDS) {
1590
+ if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
1591
+ return { valid: false, field };
1592
+ }
1206
1593
  }
1207
- return "shared-control";
1594
+ return { valid: true };
1208
1595
  }
1209
- function decodeJwtPayload(compactJws) {
1596
+ function failure(code, message, partial) {
1597
+ return {
1598
+ valid: false,
1599
+ error: { code, message },
1600
+ ...partial
1601
+ };
1602
+ }
1603
+ async function verifyX402JwsArtifact(artifact, options) {
1604
+ if (!artifact || typeof artifact !== "object") {
1605
+ return failure("INVALID_ARTIFACT", "Artifact must be a non-null object");
1606
+ }
1607
+ if (artifact.format !== "jws") {
1608
+ return failure("INVALID_ARTIFACT", `Expected format "jws", got "${artifact.format}"`);
1609
+ }
1610
+ if (typeof artifact.signature !== "string" || artifact.signature.length === 0) {
1611
+ return failure("INVALID_ARTIFACT", "Artifact signature must be a non-empty string");
1612
+ }
1613
+ const compactJws = artifact.signature;
1210
1614
  const parts = compactJws.split(".");
1211
1615
  if (parts.length !== 3) {
1212
- throw new OmaTrustError("PROOF_VERIFICATION_FAILED", "Invalid compact JWS format");
1616
+ return failure("MALFORMED_JWS", "Invalid compact JWS format: expected 3 dot-separated parts");
1213
1617
  }
1214
- const payloadB64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
1215
- const padded = payloadB64.padEnd(payloadB64.length + (4 - payloadB64.length % 4) % 4, "=");
1216
- let json;
1217
- if (typeof atob === "function") {
1218
- json = decodeURIComponent(
1219
- Array.from(atob(padded)).map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`).join("")
1618
+ let header;
1619
+ try {
1620
+ header = jose.decodeProtectedHeader(compactJws);
1621
+ } catch {
1622
+ return failure("MALFORMED_JWS", "Failed to decode JWS protected header");
1623
+ }
1624
+ if (!header.alg || typeof header.alg !== "string") {
1625
+ return failure("MISSING_ALG", "JWS header must include alg", { header });
1626
+ }
1627
+ const kid = typeof header.kid === "string" ? header.kid : null;
1628
+ const embeddedJwk = header.jwk;
1629
+ if (!kid && !embeddedJwk) {
1630
+ return failure(
1631
+ "MISSING_KEY_MATERIAL",
1632
+ "JWS header must include at least one of kid or jwk",
1633
+ { header }
1220
1634
  );
1221
- } else {
1222
- json = Buffer.from(padded, "base64").toString("utf8");
1223
1635
  }
1224
- return JSON.parse(json);
1225
- }
1226
- async function verifyProof(params) {
1227
- const { proof, provider, expectedSubject, expectedController } = params;
1228
- try {
1229
- switch (proof.proofType) {
1230
- case "tx-encoded-value": {
1231
- if (!provider) {
1232
- return { valid: false, proofType: proof.proofType, reason: "Provider is required" };
1233
- }
1234
- if (!expectedSubject || !expectedController) {
1235
- return {
1236
- valid: false,
1237
- proofType: proof.proofType,
1238
- reason: "expectedSubject and expectedController are required"
1239
- };
1240
- }
1241
- const proofObject = proof.proofObject;
1242
- const chainId = parseChainId(proofObject.chainId);
1243
- const tx = await provider.getTransaction(
1244
- proofObject.txHash
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" };
1636
+ let publicKeyJwk;
1637
+ let publicKeySource;
1638
+ if (embeddedJwk) {
1639
+ const validation = validatePublicJwk(embeddedJwk);
1640
+ if (!validation.valid) {
1641
+ return failure(
1642
+ "INVALID_EMBEDDED_JWK",
1643
+ `Embedded JWK is invalid: ${validation.error}`,
1644
+ { header }
1645
+ );
1646
+ }
1647
+ publicKeyJwk = embeddedJwk;
1648
+ publicKeySource = "embedded-jwk";
1649
+ if (kid) {
1650
+ try {
1651
+ const resolved = await resolveDidUrlToPublicKey(kid, options?.resolveOptions);
1652
+ if (!publicJwkEquals(publicKeyJwk, resolved.publicKeyJwk)) {
1653
+ return failure(
1654
+ "KEY_CONFLICT",
1655
+ "Embedded jwk conflicts with public key resolved from kid",
1656
+ { header, kid }
1657
+ );
1265
1658
  }
1266
- return { valid: true, proofType: proof.proofType };
1659
+ } catch (err) {
1267
1660
  }
1268
- case "tx-interaction": {
1269
- if (!provider) {
1270
- return { valid: false, proofType: proof.proofType, reason: "Provider is required" };
1271
- }
1272
- const proofObject = proof.proofObject;
1661
+ }
1662
+ } else {
1663
+ publicKeySource = "kid-resolution";
1664
+ try {
1665
+ const resolved = await resolveDidUrlToPublicKey(kid, options?.resolveOptions);
1666
+ publicKeyJwk = resolved.publicKeyJwk;
1667
+ } catch (err) {
1668
+ const message = err instanceof OmaTrustError ? `Failed to resolve kid "${kid}": ${err.message}` : `Failed to resolve kid "${kid}"`;
1669
+ return failure("KID_RESOLUTION_FAILED", message, { header, kid });
1670
+ }
1671
+ }
1672
+ let payload;
1673
+ try {
1674
+ const key = await jose.importJWK(publicKeyJwk, header.alg);
1675
+ const result = await jose.compactVerify(compactJws, key);
1676
+ const payloadText = new TextDecoder().decode(result.payload);
1677
+ payload = JSON.parse(payloadText);
1678
+ } catch (err) {
1679
+ const message = err instanceof Error ? err.message : "Signature verification failed";
1680
+ return failure("SIGNATURE_INVALID", message, { header, kid });
1681
+ }
1682
+ const publicKeyDid = jwkToDidJwk(publicKeyJwk);
1683
+ return {
1684
+ valid: true,
1685
+ header,
1686
+ payload,
1687
+ kid,
1688
+ publicKeyJwk,
1689
+ publicKeySource,
1690
+ publicKeyDid
1691
+ };
1692
+ }
1693
+ async function verifyX402JwsOffer(artifact, options) {
1694
+ const result = await verifyX402JwsArtifact(artifact, options);
1695
+ if (!result.valid) return result;
1696
+ const payloadCheck = validateOfferPayload(result.payload);
1697
+ if (!payloadCheck.valid) {
1698
+ return failure(
1699
+ "INVALID_OFFER_PAYLOAD",
1700
+ `Missing required offer field: ${payloadCheck.field}`,
1701
+ {
1702
+ header: result.header,
1703
+ payload: result.payload,
1704
+ kid: result.kid,
1705
+ publicKeyJwk: result.publicKeyJwk,
1706
+ publicKeySource: result.publicKeySource,
1707
+ publicKeyDid: result.publicKeyDid
1708
+ }
1709
+ );
1710
+ }
1711
+ return result;
1712
+ }
1713
+ async function verifyX402JwsReceipt(artifact, options) {
1714
+ const result = await verifyX402JwsArtifact(artifact, options);
1715
+ if (!result.valid) return result;
1716
+ const payloadCheck = validateReceiptPayload(result.payload);
1717
+ if (!payloadCheck.valid) {
1718
+ return failure(
1719
+ "INVALID_RECEIPT_PAYLOAD",
1720
+ `Missing required receipt field: ${payloadCheck.field}`,
1721
+ {
1722
+ header: result.header,
1723
+ payload: result.payload,
1724
+ kid: result.kid,
1725
+ publicKeyJwk: result.publicKeyJwk,
1726
+ publicKeySource: result.publicKeySource,
1727
+ publicKeyDid: result.publicKeyDid
1728
+ }
1729
+ );
1730
+ }
1731
+ return result;
1732
+ }
1733
+ var OFFER_DOMAIN = {
1734
+ name: "x402 offer",
1735
+ version: "1",
1736
+ chainId: 1
1737
+ };
1738
+ var RECEIPT_DOMAIN = {
1739
+ name: "x402 receipt",
1740
+ version: "1",
1741
+ chainId: 1
1742
+ };
1743
+ var OFFER_TYPES = {
1744
+ Offer: [
1745
+ { name: "version", type: "uint256" },
1746
+ { name: "resourceUrl", type: "string" },
1747
+ { name: "scheme", type: "string" },
1748
+ { name: "network", type: "string" },
1749
+ { name: "asset", type: "string" },
1750
+ { name: "payTo", type: "string" },
1751
+ { name: "amount", type: "string" },
1752
+ { name: "validUntil", type: "uint256" }
1753
+ ]
1754
+ };
1755
+ var RECEIPT_TYPES = {
1756
+ Receipt: [
1757
+ { name: "version", type: "uint256" },
1758
+ { name: "network", type: "string" },
1759
+ { name: "resourceUrl", type: "string" },
1760
+ { name: "payer", type: "string" },
1761
+ { name: "issuedAt", type: "uint256" },
1762
+ { name: "transaction", type: "string" }
1763
+ ]
1764
+ };
1765
+ var REQUIRED_OFFER_FIELDS2 = [
1766
+ "version",
1767
+ "resourceUrl",
1768
+ "scheme",
1769
+ "network",
1770
+ "asset",
1771
+ "payTo",
1772
+ "amount"
1773
+ ];
1774
+ var REQUIRED_RECEIPT_FIELDS2 = [
1775
+ "version",
1776
+ "network",
1777
+ "resourceUrl",
1778
+ "payer",
1779
+ "issuedAt"
1780
+ ];
1781
+ function validateOfferPayload2(payload) {
1782
+ for (const field of REQUIRED_OFFER_FIELDS2) {
1783
+ if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
1784
+ return { valid: false, field };
1785
+ }
1786
+ }
1787
+ return { valid: true };
1788
+ }
1789
+ function validateReceiptPayload2(payload) {
1790
+ for (const field of REQUIRED_RECEIPT_FIELDS2) {
1791
+ if (!(field in payload) || payload[field] === void 0 || payload[field] === null) {
1792
+ return { valid: false, field };
1793
+ }
1794
+ }
1795
+ return { valid: true };
1796
+ }
1797
+ function failure2(code, message, partial) {
1798
+ return {
1799
+ valid: false,
1800
+ error: { code, message },
1801
+ ...partial
1802
+ };
1803
+ }
1804
+ function prepareOfferMessage(payload) {
1805
+ return {
1806
+ version: payload.version,
1807
+ resourceUrl: payload.resourceUrl,
1808
+ scheme: payload.scheme,
1809
+ network: payload.network,
1810
+ asset: payload.asset,
1811
+ payTo: payload.payTo,
1812
+ amount: payload.amount,
1813
+ validUntil: payload.validUntil ?? 0
1814
+ };
1815
+ }
1816
+ function prepareReceiptMessage(payload) {
1817
+ return {
1818
+ version: payload.version,
1819
+ network: payload.network,
1820
+ resourceUrl: payload.resourceUrl,
1821
+ payer: payload.payer,
1822
+ issuedAt: payload.issuedAt,
1823
+ transaction: payload.transaction ?? ""
1824
+ };
1825
+ }
1826
+ function verifyX402Eip712Artifact(artifact, artifactType) {
1827
+ if (!artifact || typeof artifact !== "object") {
1828
+ return failure2("INVALID_ARTIFACT", "Artifact must be a non-null object");
1829
+ }
1830
+ if (artifact.format !== "eip712") {
1831
+ return failure2("INVALID_ARTIFACT", `Expected format "eip712", got "${artifact.format}"`);
1832
+ }
1833
+ if (!artifact.payload || typeof artifact.payload !== "object" || Array.isArray(artifact.payload)) {
1834
+ return failure2("MISSING_PAYLOAD", "EIP-712 artifact must include a payload object");
1835
+ }
1836
+ if (typeof artifact.signature !== "string" || artifact.signature.length === 0) {
1837
+ return failure2("MISSING_SIGNATURE", "EIP-712 artifact must include a non-empty signature");
1838
+ }
1839
+ const payload = artifact.payload;
1840
+ if (artifactType === "offer") {
1841
+ const check = validateOfferPayload2(payload);
1842
+ if (!check.valid) {
1843
+ return failure2(
1844
+ "INVALID_OFFER_PAYLOAD",
1845
+ `Missing required offer field: ${check.field}`,
1846
+ { payload }
1847
+ );
1848
+ }
1849
+ } else {
1850
+ const check = validateReceiptPayload2(payload);
1851
+ if (!check.valid) {
1852
+ return failure2(
1853
+ "INVALID_RECEIPT_PAYLOAD",
1854
+ `Missing required receipt field: ${check.field}`,
1855
+ { payload }
1856
+ );
1857
+ }
1858
+ }
1859
+ const domain = artifactType === "offer" ? OFFER_DOMAIN : RECEIPT_DOMAIN;
1860
+ const types = artifactType === "offer" ? OFFER_TYPES : RECEIPT_TYPES;
1861
+ const message = artifactType === "offer" ? prepareOfferMessage(payload) : prepareReceiptMessage(payload);
1862
+ let signer;
1863
+ try {
1864
+ signer = ethers.verifyTypedData(
1865
+ domain,
1866
+ types,
1867
+ message,
1868
+ artifact.signature
1869
+ );
1870
+ signer = ethers.getAddress(signer);
1871
+ } catch (err) {
1872
+ const message2 = err instanceof Error ? err.message : "EIP-712 signature verification failed";
1873
+ return failure2("SIGNATURE_INVALID", message2, { payload });
1874
+ }
1875
+ return {
1876
+ valid: true,
1877
+ payload,
1878
+ signer,
1879
+ artifactType
1880
+ };
1881
+ }
1882
+ function verifyX402Eip712Offer(artifact) {
1883
+ return verifyX402Eip712Artifact(artifact, "offer");
1884
+ }
1885
+ function verifyX402Eip712Receipt(artifact) {
1886
+ return verifyX402Eip712Artifact(artifact, "receipt");
1887
+ }
1888
+
1889
+ // src/reputation/verify.ts
1890
+ function parseChainId(input) {
1891
+ if (typeof input === "number") {
1892
+ return input;
1893
+ }
1894
+ if (typeof input === "string") {
1895
+ if (input.includes(":")) {
1896
+ const [, chainId] = input.split(":");
1897
+ return Number(chainId);
1898
+ }
1899
+ return Number(input);
1900
+ }
1901
+ return NaN;
1902
+ }
1903
+ function parseProofs(attestation) {
1904
+ const rawProofs = attestation.data.proofs;
1905
+ if (!Array.isArray(rawProofs)) {
1906
+ return [];
1907
+ }
1908
+ return rawProofs.map((entry) => {
1909
+ if (typeof entry === "string") {
1910
+ try {
1911
+ return JSON.parse(entry);
1912
+ } catch {
1913
+ return null;
1914
+ }
1915
+ }
1916
+ if (entry && typeof entry === "object") {
1917
+ return entry;
1918
+ }
1919
+ return null;
1920
+ }).filter((proof) => Boolean(proof?.proofType));
1921
+ }
1922
+ function getProofPurpose(proof) {
1923
+ if (proof.proofPurpose === "commercial-tx") {
1924
+ return "commercial-tx";
1925
+ }
1926
+ return "shared-control";
1927
+ }
1928
+ function decodeJwtPayload(compactJws) {
1929
+ const parts = compactJws.split(".");
1930
+ if (parts.length !== 3) {
1931
+ throw new OmaTrustError("PROOF_VERIFICATION_FAILED", "Invalid compact JWS format");
1932
+ }
1933
+ const payloadB64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
1934
+ const padded = payloadB64.padEnd(payloadB64.length + (4 - payloadB64.length % 4) % 4, "=");
1935
+ let json;
1936
+ if (typeof atob === "function") {
1937
+ json = decodeURIComponent(
1938
+ Array.from(atob(padded)).map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`).join("")
1939
+ );
1940
+ } else {
1941
+ json = Buffer.from(padded, "base64").toString("utf8");
1942
+ }
1943
+ return JSON.parse(json);
1944
+ }
1945
+ async function verifyProof(params) {
1946
+ const { proof, provider, expectedSubjectDid, expectedControllerDid } = params;
1947
+ try {
1948
+ switch (proof.proofType) {
1949
+ case "tx-encoded-value": {
1950
+ if (!provider) {
1951
+ return { valid: false, proofType: proof.proofType, reason: "Provider is required" };
1952
+ }
1953
+ if (!expectedSubjectDid || !expectedControllerDid) {
1954
+ return {
1955
+ valid: false,
1956
+ proofType: proof.proofType,
1957
+ reason: "expectedSubjectDid and expectedControllerDid are required"
1958
+ };
1959
+ }
1960
+ const proofObject = proof.proofObject;
1961
+ const chainId = parseChainId(proofObject.chainId);
1962
+ const tx = await provider.getTransaction(
1963
+ proofObject.txHash
1964
+ );
1965
+ if (!tx) {
1966
+ return { valid: false, proofType: proof.proofType, reason: "Transaction not found" };
1967
+ }
1968
+ const expectedAmount = calculateTransferAmount(
1969
+ expectedSubjectDid,
1970
+ expectedControllerDid,
1971
+ chainId,
1972
+ getProofPurpose(proof)
1973
+ );
1974
+ const subjectAddress = ethers.getAddress(extractAddressFromDid(expectedSubjectDid));
1975
+ const controllerAddress = ethers.getAddress(extractAddressFromDid(expectedControllerDid));
1976
+ if (tx.from && ethers.getAddress(tx.from) !== subjectAddress) {
1977
+ return { valid: false, proofType: proof.proofType, reason: "Transaction sender mismatch" };
1978
+ }
1979
+ if (tx.to && ethers.getAddress(tx.to) !== controllerAddress) {
1980
+ return { valid: false, proofType: proof.proofType, reason: "Transaction recipient mismatch" };
1981
+ }
1982
+ if (BigInt(tx.value) !== expectedAmount) {
1983
+ return { valid: false, proofType: proof.proofType, reason: "Transaction amount mismatch" };
1984
+ }
1985
+ return { valid: true, proofType: proof.proofType };
1986
+ }
1987
+ case "tx-interaction": {
1988
+ if (!provider) {
1989
+ return { valid: false, proofType: proof.proofType, reason: "Provider is required" };
1990
+ }
1991
+ const proofObject = proof.proofObject;
1273
1992
  const tx = await provider.getTransaction(
1274
1993
  proofObject.txHash
1275
1994
  );
@@ -1327,6 +2046,38 @@ async function verifyProof(params) {
1327
2046
  if (!proof.proofObject || typeof proof.proofObject !== "object") {
1328
2047
  return { valid: false, proofType: proof.proofType, reason: "Invalid x402 proof object" };
1329
2048
  }
2049
+ const proofObj = proof.proofObject;
2050
+ if (proofObj.format === "jws" && typeof proofObj.signature === "string") {
2051
+ const artifact = {
2052
+ format: "jws",
2053
+ signature: proofObj.signature
2054
+ };
2055
+ const jwsResult = proof.proofType === "x402-offer" ? await verifyX402JwsOffer(artifact) : await verifyX402JwsReceipt(artifact);
2056
+ if (!jwsResult.valid) {
2057
+ return {
2058
+ valid: false,
2059
+ proofType: proof.proofType,
2060
+ reason: jwsResult.error?.message ?? "JWS verification failed"
2061
+ };
2062
+ }
2063
+ return { valid: true, proofType: proof.proofType };
2064
+ }
2065
+ if (proofObj.format === "eip712" && typeof proofObj.signature === "string" && proofObj.payload && typeof proofObj.payload === "object") {
2066
+ const artifact = {
2067
+ format: "eip712",
2068
+ payload: proofObj.payload,
2069
+ signature: proofObj.signature
2070
+ };
2071
+ const eip712Result = proof.proofType === "x402-offer" ? verifyX402Eip712Offer(artifact) : verifyX402Eip712Receipt(artifact);
2072
+ if (!eip712Result.valid) {
2073
+ return {
2074
+ valid: false,
2075
+ proofType: proof.proofType,
2076
+ reason: eip712Result.error?.message ?? "EIP-712 verification failed"
2077
+ };
2078
+ }
2079
+ return { valid: true, proofType: proof.proofType };
2080
+ }
1330
2081
  return { valid: true, proofType: proof.proofType };
1331
2082
  }
1332
2083
  case "evidence-pointer": {
@@ -1338,9 +2089,9 @@ async function verifyProof(params) {
1338
2089
  if (!response.ok) {
1339
2090
  return { valid: false, proofType: proof.proofType, reason: `Evidence fetch failed (${response.status})` };
1340
2091
  }
1341
- if (object.url.endsWith("/.well-known/did.json") && expectedController) {
2092
+ if (object.url.endsWith("/.well-known/did.json") && expectedControllerDid) {
1342
2093
  const didDoc = await response.json();
1343
- const didCheck = verifyDidDocumentControllerDid(didDoc, expectedController);
2094
+ const didCheck = verifyDidDocumentControllerDid(didDoc, expectedControllerDid);
1344
2095
  return {
1345
2096
  valid: didCheck.valid,
1346
2097
  proofType: proof.proofType,
@@ -1348,10 +2099,10 @@ async function verifyProof(params) {
1348
2099
  };
1349
2100
  }
1350
2101
  const body = await response.text();
1351
- if (expectedController && !body.includes(expectedController)) {
2102
+ if (expectedControllerDid && !body.includes(expectedControllerDid)) {
1352
2103
  try {
1353
2104
  const parsed = parseDnsTxtRecord(body);
1354
- if (parsed.controller !== expectedController) {
2105
+ if (!parsed.controllers.includes(expectedControllerDid)) {
1355
2106
  return {
1356
2107
  valid: false,
1357
2108
  proofType: proof.proofType,
@@ -1407,8 +2158,8 @@ async function verifyAttestation(params) {
1407
2158
  const result = await verifyProof({
1408
2159
  proof,
1409
2160
  provider: params.provider,
1410
- expectedSubject: params.context?.subject,
1411
- expectedController: params.context?.controller
2161
+ expectedSubjectDid: params.context?.subjectDid,
2162
+ expectedControllerDid: params.context?.controllerDid
1412
2163
  });
1413
2164
  checks[proof.proofType] = result.valid;
1414
2165
  if (!result.valid) {
@@ -1511,6 +2262,504 @@ async function callControllerWitness(params) {
1511
2262
  method: "did-json"
1512
2263
  };
1513
2264
  }
2265
+ var DEFAULT_CONTROLLER_WITNESS_URL = "https://api.omatrust.org/v1/controller-witness";
2266
+ async function requestControllerWitness(params) {
2267
+ const url = params.gatewayUrl ?? DEFAULT_CONTROLLER_WITNESS_URL;
2268
+ const body = {
2269
+ subjectDid: params.subjectDid,
2270
+ controllerDid: params.controllerDid
2271
+ };
2272
+ if (params.chainId !== void 0) {
2273
+ body.chainId = params.chainId;
2274
+ }
2275
+ let response;
2276
+ try {
2277
+ response = await fetch(url, {
2278
+ method: "POST",
2279
+ headers: { "Content-Type": "application/json" },
2280
+ credentials: "include",
2281
+ body: JSON.stringify(body),
2282
+ signal: AbortSignal.timeout(params.timeoutMs ?? 15e3)
2283
+ });
2284
+ } catch (err) {
2285
+ throw new OmaTrustError("NETWORK_ERROR", "Controller witness request failed", { err });
2286
+ }
2287
+ if (!response.ok) {
2288
+ const errorBody = await response.json().catch(() => ({ error: response.statusText }));
2289
+ throw new OmaTrustError(
2290
+ "API_ERROR",
2291
+ errorBody.error ?? `Controller witness failed: ${response.status}`,
2292
+ { status: response.status, ...errorBody }
2293
+ );
2294
+ }
2295
+ return await response.json();
2296
+ }
2297
+ var EIP1967_ADMIN_SLOT = "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103";
2298
+ var OWNERSHIP_PATTERNS = [
2299
+ { method: "owner", signature: "function owner() view returns (address)" },
2300
+ { method: "admin", signature: "function admin() view returns (address)" },
2301
+ { method: "getOwner", signature: "function getOwner() view returns (address)" }
2302
+ ];
2303
+ async function readOwnerFromContract(provider, contractAddress, signature, method) {
2304
+ try {
2305
+ const iface = new ethers.Interface([signature]);
2306
+ const data = iface.encodeFunctionData(method, []);
2307
+ const result = await provider.call({ to: contractAddress, data });
2308
+ const [value] = iface.decodeFunctionResult(method, result);
2309
+ if (typeof value === "string" && ethers.isAddress(value) && value !== ethers.ZeroAddress) {
2310
+ return ethers.getAddress(value);
2311
+ }
2312
+ } catch {
2313
+ }
2314
+ return null;
2315
+ }
2316
+ async function discoverContractOwner(provider, contractAddress) {
2317
+ try {
2318
+ const code = await provider.getCode(contractAddress);
2319
+ if (code === "0x" || code === "0x0") {
2320
+ return null;
2321
+ }
2322
+ } catch {
2323
+ return null;
2324
+ }
2325
+ for (const pattern of OWNERSHIP_PATTERNS) {
2326
+ const address = await readOwnerFromContract(
2327
+ provider,
2328
+ contractAddress,
2329
+ pattern.signature,
2330
+ pattern.method
2331
+ );
2332
+ if (address) {
2333
+ return address;
2334
+ }
2335
+ }
2336
+ try {
2337
+ const adminValue = await provider.getStorage(contractAddress, EIP1967_ADMIN_SLOT);
2338
+ if (adminValue && adminValue !== "0x" && adminValue !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
2339
+ const adminAddress = ethers.getAddress(`0x${adminValue.slice(-40)}`);
2340
+ if (adminAddress !== ethers.ZeroAddress) {
2341
+ return adminAddress;
2342
+ }
2343
+ }
2344
+ } catch {
2345
+ }
2346
+ return null;
2347
+ }
2348
+ async function discoverControllingWalletDid(provider, contractAddress, chainId) {
2349
+ const owner = await discoverContractOwner(provider, contractAddress);
2350
+ return owner ? buildEvmDidPkh(chainId, owner) : null;
2351
+ }
2352
+ async function verifyTransferProof(provider, txHash, subjectDid, attesterAddress, chainId) {
2353
+ try {
2354
+ const tx = await provider.getTransaction(txHash);
2355
+ if (!tx || !tx.from || !tx.to) {
2356
+ return false;
2357
+ }
2358
+ if (ethers.getAddress(tx.to) !== ethers.getAddress(attesterAddress)) {
2359
+ return false;
2360
+ }
2361
+ const attesterDid = buildEvmDidPkh(chainId, attesterAddress);
2362
+ const expectedAmount = calculateTransferAmount(
2363
+ subjectDid,
2364
+ attesterDid,
2365
+ chainId,
2366
+ "shared-control"
2367
+ );
2368
+ const actualValue = BigInt(tx.value ?? 0n);
2369
+ return actualValue === expectedAmount;
2370
+ } catch {
2371
+ return false;
2372
+ }
2373
+ }
2374
+
2375
+ // src/identity/controller-id.ts
2376
+ function isSameControllerId(a, b) {
2377
+ let normalizedA = null;
2378
+ let normalizedB = null;
2379
+ try {
2380
+ normalizedA = normalizeDid(a);
2381
+ } catch {
2382
+ }
2383
+ try {
2384
+ normalizedB = normalizeDid(b);
2385
+ } catch {
2386
+ }
2387
+ if (normalizedA && normalizedB && normalizedA === normalizedB) {
2388
+ return true;
2389
+ }
2390
+ const evmA = extractControllerEvmAddress(a);
2391
+ const evmB = extractControllerEvmAddress(b);
2392
+ if (evmA && evmB && evmA.toLowerCase() === evmB.toLowerCase()) {
2393
+ return true;
2394
+ }
2395
+ const methodA = extractDidMethod(a);
2396
+ const methodB = extractDidMethod(b);
2397
+ if (methodA === "jwk" && methodB === "jwk") {
2398
+ try {
2399
+ const jwkA = didJwkToJwk(a);
2400
+ const jwkB = didJwkToJwk(b);
2401
+ return publicJwkEquals(jwkA, jwkB);
2402
+ } catch {
2403
+ return false;
2404
+ }
2405
+ }
2406
+ return false;
2407
+ }
2408
+ function extractControllerEvmAddress(controllerDid) {
2409
+ try {
2410
+ const method = extractDidMethod(controllerDid);
2411
+ if (method === "pkh" && controllerDid.includes("eip155")) {
2412
+ return extractAddressFromDid(controllerDid);
2413
+ }
2414
+ } catch {
2415
+ }
2416
+ return null;
2417
+ }
2418
+
2419
+ // src/shared/trust-anchors.ts
2420
+ var DEFAULT_TRUST_ANCHORS_URL = "https://api.omatrust.org/v1/trust-anchors";
2421
+ var TRUST_ANCHORS_URL = DEFAULT_TRUST_ANCHORS_URL;
2422
+ var cachedAnchors = null;
2423
+ var cacheTimestamp = 0;
2424
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
2425
+ async function fetchTrustAnchors(url) {
2426
+ const now = Date.now();
2427
+ if (cachedAnchors && now - cacheTimestamp < CACHE_TTL_MS) {
2428
+ return cachedAnchors;
2429
+ }
2430
+ let res;
2431
+ try {
2432
+ res = await fetch(url ?? TRUST_ANCHORS_URL);
2433
+ } catch (err) {
2434
+ throw new OmaTrustError("NETWORK_ERROR", "Failed to fetch trust anchors", { err });
2435
+ }
2436
+ if (!res.ok) {
2437
+ throw new OmaTrustError("NETWORK_ERROR", `Failed to fetch trust anchors: ${res.status} ${res.statusText}`);
2438
+ }
2439
+ const anchors = await res.json();
2440
+ if (!anchors.version || !anchors.chains || typeof anchors.chains !== "object") {
2441
+ throw new OmaTrustError("INVALID_INPUT", "Invalid trust anchors format");
2442
+ }
2443
+ cachedAnchors = anchors;
2444
+ cacheTimestamp = now;
2445
+ return anchors;
2446
+ }
2447
+ function getChainAnchors(anchors, caip2) {
2448
+ const chain = anchors.chains[caip2];
2449
+ if (!chain) {
2450
+ throw new OmaTrustError("UNSUPPORTED_CHAIN", `Chain ${caip2} is not in the trust anchors`, {
2451
+ caip2,
2452
+ supportedChains: Object.keys(anchors.chains)
2453
+ });
2454
+ }
2455
+ return chain;
2456
+ }
2457
+
2458
+ // src/reputation/proof/dns-txt-shared.ts
2459
+ async function verifyDnsTxtControllerDid(domain, expectedControllerDid, options = {}) {
2460
+ if (!domain || typeof domain !== "string") {
2461
+ throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
2462
+ }
2463
+ if (!options.resolveTxt) {
2464
+ throw new OmaTrustError("NETWORK_ERROR", "No DNS TXT resolver was provided", { domain });
2465
+ }
2466
+ const prefix = options.recordPrefix ?? "_controllers";
2467
+ const host = `${prefix}.${domain.toLowerCase().replace(/\.$/, "")}`;
2468
+ let records;
2469
+ try {
2470
+ records = await options.resolveTxt(host);
2471
+ } catch (err) {
2472
+ throw new OmaTrustError("NETWORK_ERROR", "Failed to resolve DNS TXT records", { domain, err });
2473
+ }
2474
+ for (const recordParts of records) {
2475
+ const record = recordParts.join("");
2476
+ const parsed = parseDnsTxtRecord(record);
2477
+ if (parsed.version !== "1") continue;
2478
+ for (const recordController of parsed.controllers) {
2479
+ if (isSameControllerId(recordController, expectedControllerDid)) {
2480
+ return { valid: true, record };
2481
+ }
2482
+ }
2483
+ }
2484
+ return { valid: false, reason: "Controller DID not found in DNS TXT records" };
2485
+ }
2486
+
2487
+ // src/reputation/attester-authorization.ts
2488
+ var DEFAULT_CHAIN = "eip155:6623";
2489
+ var DEFAULT_PURPOSES = ["authentication", "assertionMethod"];
2490
+ async function getControllerAuthorization(params) {
2491
+ const chain = params.chain ?? DEFAULT_CHAIN;
2492
+ const purposes = params.purpose && params.purpose.length > 0 ? params.purpose : DEFAULT_PURPOSES;
2493
+ const controllerDid = normalizeDid(params.controllerDid);
2494
+ const controllerMethod = extractDidMethod(controllerDid);
2495
+ const controllerEvmAddress = extractControllerEvmAddress(controllerDid);
2496
+ const anchors = await fetchTrustAnchors();
2497
+ const chainAnchors = getChainAnchors(anchors, chain);
2498
+ const easContractAddress = params.easContractAddress ?? chainAnchors.easContract;
2499
+ const provider = params.provider;
2500
+ const controllerWitnessSchemaUid = chainAnchors.schemas["controller-witness"];
2501
+ const keyBindingSchemaUid = chainAnchors.schemas["key-binding"];
2502
+ const subjectAddress = didToAddress(params.subjectDid);
2503
+ let relevantWitnesses = [];
2504
+ if (controllerWitnessSchemaUid) {
2505
+ const rawWitnesses = await queryByRecipientAndSchema(
2506
+ easContractAddress,
2507
+ provider,
2508
+ subjectAddress,
2509
+ [controllerWitnessSchemaUid],
2510
+ params.fromBlock ?? 0
2511
+ );
2512
+ relevantWitnesses = rawWitnesses.filter((att) => {
2513
+ const witnessController = att.data?.controller;
2514
+ if (typeof witnessController !== "string") return false;
2515
+ return isSameControllerId(witnessController, controllerDid);
2516
+ });
2517
+ relevantWitnesses.sort((a, b) => Number(a.time - b.time));
2518
+ }
2519
+ const controllerWitnesses = relevantWitnesses.map((att) => ({
2520
+ uid: att.uid,
2521
+ issuedAt: att.time,
2522
+ attester: att.attester,
2523
+ method: resolveWitnessMethod(att.data?.method)
2524
+ }));
2525
+ let keyBindingUid = null;
2526
+ let until = null;
2527
+ let keyPurposeStatus = "not-required";
2528
+ if (keyBindingSchemaUid) {
2529
+ const keyBindings = await queryByRecipientAndSchema(
2530
+ easContractAddress,
2531
+ provider,
2532
+ subjectAddress,
2533
+ [keyBindingSchemaUid],
2534
+ params.fromBlock ?? 0
2535
+ );
2536
+ const relevantKeyBindings = keyBindings.filter((att) => controllerMatchesKeyBinding(att, controllerDid, controllerMethod)).sort((a, b) => Number(b.time - a.time));
2537
+ const relevantKeyBinding = relevantKeyBindings[0] ?? null;
2538
+ if (relevantKeyBinding) {
2539
+ keyBindingUid = relevantKeyBinding.uid;
2540
+ if (relevantKeyBinding.revocationTime > 0n) {
2541
+ until = relevantKeyBinding.revocationTime;
2542
+ }
2543
+ const keyPurposes = relevantKeyBinding.data?.keyPurpose;
2544
+ if (!Array.isArray(keyPurposes) || keyPurposes.length === 0) {
2545
+ keyPurposeStatus = "unknown";
2546
+ } else {
2547
+ const allMatched = purposes.every((p) => keyPurposes.includes(p));
2548
+ keyPurposeStatus = allMatched ? "matched" : "mismatch";
2549
+ }
2550
+ }
2551
+ }
2552
+ let currentlyVerified = false;
2553
+ let liveMethod = null;
2554
+ let transferProofVerified = false;
2555
+ let transferProofAnchor = null;
2556
+ const subjectMethod = extractDidMethod(params.subjectDid);
2557
+ if (subjectMethod === "web") {
2558
+ const domain = getDomainFromDidWeb(params.subjectDid);
2559
+ if (domain) {
2560
+ try {
2561
+ const dnsResult = await verifyDnsTxtControllerDid(domain, controllerDid, {
2562
+ resolveTxt: params.resolveTxt
2563
+ });
2564
+ if (dnsResult.valid) {
2565
+ currentlyVerified = true;
2566
+ liveMethod = "dns";
2567
+ }
2568
+ } catch {
2569
+ }
2570
+ if (!currentlyVerified) {
2571
+ try {
2572
+ const fetchDoc = params.fetchDidDocument ?? fetchDidDocument;
2573
+ const didDoc = await fetchDoc(domain);
2574
+ const didJsonResult = verifyDidDocumentControllerDid(didDoc, controllerDid);
2575
+ if (didJsonResult.valid) {
2576
+ currentlyVerified = true;
2577
+ liveMethod = "did-document";
2578
+ }
2579
+ } catch {
2580
+ }
2581
+ }
2582
+ }
2583
+ } else if (subjectMethod === "pkh") {
2584
+ if (isEvmDidPkh(params.subjectDid) && controllerEvmAddress) {
2585
+ const subjectContractAddress = getAddressFromDidPkh(params.subjectDid);
2586
+ const subjectChainId = getChainIdFromDidPkh(params.subjectDid);
2587
+ const chainIdNum = subjectChainId ? Number(subjectChainId) : null;
2588
+ if (subjectContractAddress && chainIdNum) {
2589
+ const ownerAddress = await discoverContractOwner(
2590
+ provider,
2591
+ subjectContractAddress
2592
+ );
2593
+ if (ownerAddress && ethers.getAddress(ownerAddress) === ethers.getAddress(controllerEvmAddress)) {
2594
+ currentlyVerified = true;
2595
+ liveMethod = "contract-ownership";
2596
+ }
2597
+ if (!currentlyVerified && keyBindingUid) {
2598
+ const relevantKeyBinding = await findKeyBindingWithTransferProof(
2599
+ keyBindingSchemaUid,
2600
+ easContractAddress,
2601
+ provider,
2602
+ subjectAddress,
2603
+ controllerDid,
2604
+ controllerMethod,
2605
+ params.fromBlock ?? 0
2606
+ );
2607
+ if (relevantKeyBinding) {
2608
+ const proofTxHash = relevantKeyBinding.data?.proofTxHash;
2609
+ if (proofTxHash && /^0x[0-9a-fA-F]{64}$/.test(proofTxHash)) {
2610
+ const verified = await verifyTransferProof(
2611
+ provider,
2612
+ proofTxHash,
2613
+ params.subjectDid,
2614
+ controllerEvmAddress,
2615
+ chainIdNum
2616
+ );
2617
+ if (verified) {
2618
+ transferProofVerified = true;
2619
+ transferProofAnchor = relevantKeyBinding.time;
2620
+ }
2621
+ }
2622
+ }
2623
+ }
2624
+ }
2625
+ }
2626
+ }
2627
+ const witnessAnchor = relevantWitnesses.length > 0 ? relevantWitnesses[0].time : null;
2628
+ const anchoredFrom = witnessAnchor ?? transferProofAnchor;
2629
+ const purposeBlocks = keyPurposeStatus === "mismatch";
2630
+ const hasOpenWindow = relevantWitnesses.length > 0 && until === null;
2631
+ const hasKeyBindingWithProof = keyBindingUid !== null && transferProofVerified && until === null;
2632
+ const authorized = !purposeBlocks && (hasOpenWindow || currentlyVerified && until === null || hasKeyBindingWithProof);
2633
+ return {
2634
+ authorized,
2635
+ anchoredFrom,
2636
+ until,
2637
+ currentlyVerified,
2638
+ liveMethod,
2639
+ controllerWitnesses,
2640
+ keyBindingUid,
2641
+ keyPurposeStatus,
2642
+ transferProofVerified: transferProofVerified || void 0
2643
+ };
2644
+ }
2645
+ function controllerMatchesKeyBinding(att, controllerDid, controllerMethod) {
2646
+ const keyId = att.data?.keyId;
2647
+ if (typeof keyId === "string") {
2648
+ if (isSameControllerId(keyId, controllerDid)) {
2649
+ return true;
2650
+ }
2651
+ }
2652
+ if (controllerMethod === "jwk") {
2653
+ const publicKeyJwkRaw = att.data?.publicKeyJwk;
2654
+ if (publicKeyJwkRaw) {
2655
+ try {
2656
+ const onChainJwk = typeof publicKeyJwkRaw === "string" ? JSON.parse(publicKeyJwkRaw) : publicKeyJwkRaw;
2657
+ const controllerJwk = didJwkToJwk(controllerDid);
2658
+ return publicJwkEquals(onChainJwk, controllerJwk);
2659
+ } catch {
2660
+ }
2661
+ }
2662
+ }
2663
+ return false;
2664
+ }
2665
+ var EAS_EVENT_ABI2 = [
2666
+ "event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)"
2667
+ ];
2668
+ function resolveWitnessMethod(value) {
2669
+ if (typeof value !== "string") return void 0;
2670
+ switch (value) {
2671
+ case "dns":
2672
+ case "dns-txt":
2673
+ return "dns";
2674
+ case "did-document":
2675
+ case "did-json":
2676
+ return "did-document";
2677
+ case "manual":
2678
+ return "manual";
2679
+ default:
2680
+ return "other";
2681
+ }
2682
+ }
2683
+ async function queryByRecipientAndSchema(easContractAddress, provider, recipient, schemas, fromBlock) {
2684
+ const contract = new ethers.Contract(easContractAddress, EAS_EVENT_ABI2, provider);
2685
+ const filter = contract.filters.Attested(recipient, null);
2686
+ const toBlock = await provider.getBlockNumber();
2687
+ let events;
2688
+ try {
2689
+ events = await contract.queryFilter(filter, fromBlock, toBlock);
2690
+ } catch (err) {
2691
+ throw new OmaTrustError("NETWORK_ERROR", "Failed to query attestation events", { err });
2692
+ }
2693
+ const eas = new easSdk.EAS(easContractAddress);
2694
+ eas.connect(provider);
2695
+ const schemaFilter = schemas.map((s) => s.toLowerCase());
2696
+ const results = [];
2697
+ for (const event of events) {
2698
+ if (!("args" in event) || !Array.isArray(event.args)) continue;
2699
+ const args = event.args;
2700
+ const uid = args?.[2];
2701
+ const schemaUid = args?.[3];
2702
+ if (!uid || !schemaUid) continue;
2703
+ if (!schemaFilter.includes(schemaUid.toLowerCase())) continue;
2704
+ const attestation = await eas.getAttestation(uid);
2705
+ if (!attestation || !attestation.uid) continue;
2706
+ let data = {};
2707
+ const rawData = attestation.data;
2708
+ if (rawData && rawData !== "0x") {
2709
+ try {
2710
+ data = decodeAttestationData(
2711
+ "string subject, string controller, string method",
2712
+ rawData
2713
+ );
2714
+ } catch {
2715
+ try {
2716
+ data = decodeAttestationData(
2717
+ "string subject, string keyId, string publicKeyJwk, string[] keyPurpose, string[] proofs, uint256 issuedAt, uint256 effectiveAt, uint256 expiresAt",
2718
+ rawData
2719
+ );
2720
+ } catch {
2721
+ }
2722
+ }
2723
+ }
2724
+ const toBigIntSafe = (value) => {
2725
+ if (typeof value === "bigint") return value;
2726
+ if (typeof value === "number") return BigInt(value);
2727
+ if (typeof value === "string" && value.length > 0) return BigInt(value);
2728
+ return 0n;
2729
+ };
2730
+ results.push({
2731
+ uid: attestation.uid,
2732
+ schema: attestation.schema,
2733
+ attester: attestation.attester,
2734
+ recipient: attestation.recipient,
2735
+ revocable: Boolean(attestation.revocable),
2736
+ revocationTime: toBigIntSafe(attestation.revocationTime),
2737
+ expirationTime: toBigIntSafe(attestation.expirationTime),
2738
+ time: toBigIntSafe(attestation.time),
2739
+ refUID: attestation.refUID,
2740
+ data
2741
+ });
2742
+ }
2743
+ return results;
2744
+ }
2745
+ async function findKeyBindingWithTransferProof(keyBindingSchemaUid, easContractAddress, provider, subjectAddress, controllerDid, controllerMethod, fromBlock) {
2746
+ const keyBindings = await queryByRecipientAndSchema(
2747
+ easContractAddress,
2748
+ provider,
2749
+ subjectAddress,
2750
+ [keyBindingSchemaUid],
2751
+ fromBlock
2752
+ );
2753
+ const withProof = keyBindings.filter((att) => {
2754
+ if (!controllerMatchesKeyBinding(att, controllerDid, controllerMethod)) return false;
2755
+ const proofs = att.data?.proofs;
2756
+ if (Array.isArray(proofs)) {
2757
+ return proofs.some((p) => typeof p === "string" && /^0x[0-9a-fA-F]{64}$/.test(p));
2758
+ }
2759
+ return typeof att.data?.proofTxHash === "string";
2760
+ }).sort((a, b) => Number(b.time - a.time));
2761
+ return withProof[0] ?? null;
2762
+ }
1514
2763
 
1515
2764
  // src/reputation/proof/tx-interaction.ts
1516
2765
  function createTxInteractionProof(chainId, txHash) {
@@ -1632,63 +2881,12 @@ function createEvidencePointerProof(url) {
1632
2881
  issuedAt: Math.floor(Date.now() / 1e3)
1633
2882
  };
1634
2883
  }
1635
- async function verifyDnsTxtControllerDid(domain, expectedControllerDid, options = {}) {
1636
- if (!domain || typeof domain !== "string") {
1637
- throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
1638
- }
1639
- const expected = normalizeDid(expectedControllerDid);
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" };
2884
+ function verifyDnsTxtControllerDid2(domain, expectedControllerDid, options = {}) {
2885
+ return verifyDnsTxtControllerDid(domain, expectedControllerDid, {
2886
+ ...options,
2887
+ resolveTxt: options.resolveTxt ?? promises.resolveTxt
2888
+ });
1683
2889
  }
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
2890
  function assertConnectedWalletDid(input) {
1693
2891
  const normalized = normalizeDid(input);
1694
2892
  if (extractDidMethod(normalized) !== "pkh") {
@@ -1701,43 +2899,6 @@ function assertConnectedWalletDid(input) {
1701
2899
  function normalizeSubjectDid(input) {
1702
2900
  return normalizeDid(input);
1703
2901
  }
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
2902
  async function verifyDidWebOwnership(params) {
1742
2903
  const subjectDid = normalizeSubjectDid(params.subjectDid);
1743
2904
  const connectedWalletDid = assertConnectedWalletDid(params.connectedWalletDid);
@@ -1749,7 +2910,7 @@ async function verifyDidWebOwnership(params) {
1749
2910
  }
1750
2911
  let dnsReason;
1751
2912
  try {
1752
- const dnsResult = await verifyDnsTxtControllerDid2(domain, connectedWalletDid, {
2913
+ const dnsResult = await verifyDnsTxtControllerDid(domain, connectedWalletDid, {
1753
2914
  resolveTxt: params.resolveTxt,
1754
2915
  recordPrefix: params.recordPrefix
1755
2916
  });
@@ -1835,7 +2996,7 @@ async function verifyDidPkhOwnership(params) {
1835
2996
  connectedWalletDid
1836
2997
  };
1837
2998
  }
1838
- const controllingWalletDid = await discoverControllingWallet(params.provider, subjectAddress, chainId);
2999
+ const controllingWalletDid = await discoverControllingWalletDid(params.provider, subjectAddress, chainId);
1839
3000
  if (params.txHash) {
1840
3001
  if (!controllingWalletDid) {
1841
3002
  return {
@@ -1918,7 +3079,7 @@ async function verifyDidPkhOwnership(params) {
1918
3079
  };
1919
3080
  }
1920
3081
  for (const pattern of OWNERSHIP_PATTERNS) {
1921
- const ownerAddress = await readAddressFromContract(
3082
+ const ownerAddress = await readOwnerFromContract(
1922
3083
  params.provider,
1923
3084
  subjectAddress,
1924
3085
  pattern.signature,
@@ -1993,6 +3154,8 @@ async function verifySubjectOwnership(params) {
1993
3154
  });
1994
3155
  }
1995
3156
 
3157
+ exports.EIP1967_ADMIN_SLOT = EIP1967_ADMIN_SLOT;
3158
+ exports.OWNERSHIP_PATTERNS = OWNERSHIP_PATTERNS;
1996
3159
  exports.buildDelegatedAttestationTypedData = buildDelegatedAttestationTypedData;
1997
3160
  exports.buildDelegatedTypedDataFromEncoded = buildDelegatedTypedDataFromEncoded;
1998
3161
  exports.buildDnsTxtRecord = buildDnsTxtRecord;
@@ -2011,9 +3174,12 @@ exports.createX402OfferProof = createX402OfferProof;
2011
3174
  exports.createX402ReceiptProof = createX402ReceiptProof;
2012
3175
  exports.decodeAttestationData = decodeAttestationData;
2013
3176
  exports.deduplicateReviews = deduplicateReviews;
3177
+ exports.discoverContractOwner = discoverContractOwner;
3178
+ exports.discoverControllingWalletDid = discoverControllingWalletDid;
2014
3179
  exports.encodeAttestationData = encodeAttestationData;
2015
- exports.extractAddressesFromDidDocument = extractAddressesFromDidDocument;
3180
+ exports.extractEvmAddressesFromDidDocument = extractEvmAddressesFromDidDocument;
2016
3181
  exports.extractExpirationTime = extractExpirationTime;
3182
+ exports.extractJwksFromDidDocument = extractJwksFromDidDocument;
2017
3183
  exports.fetchDidDocument = fetchDidDocument;
2018
3184
  exports.formatSchemaUid = formatSchemaUid;
2019
3185
  exports.formatTransferAmount = formatTransferAmount;
@@ -2021,6 +3187,7 @@ exports.getAttestation = getAttestation;
2021
3187
  exports.getAttestationsByAttester = getAttestationsByAttester;
2022
3188
  exports.getAttestationsForDid = getAttestationsForDid;
2023
3189
  exports.getChainConstants = getChainConstants;
3190
+ exports.getControllerAuthorization = getControllerAuthorization;
2024
3191
  exports.getExplorerAddressUrl = getExplorerAddressUrl;
2025
3192
  exports.getExplorerTxUrl = getExplorerTxUrl;
2026
3193
  exports.getLatestAttestations = getLatestAttestations;
@@ -2034,6 +3201,8 @@ exports.listAttestations = listAttestations;
2034
3201
  exports.normalizeSchema = normalizeSchema;
2035
3202
  exports.parseDnsTxtRecord = parseDnsTxtRecord;
2036
3203
  exports.prepareDelegatedAttestation = prepareDelegatedAttestation;
3204
+ exports.readOwnerFromContract = readOwnerFromContract;
3205
+ exports.requestControllerWitness = requestControllerWitness;
2037
3206
  exports.revokeAttestation = revokeAttestation;
2038
3207
  exports.schemaToString = schemaToString;
2039
3208
  exports.splitSignature = splitSignature;
@@ -2045,10 +3214,17 @@ exports.verifyDidDocumentControllerDid = verifyDidDocumentControllerDid;
2045
3214
  exports.verifyDidJsonControllerDid = verifyDidJsonControllerDid;
2046
3215
  exports.verifyDidPkhOwnership = verifyDidPkhOwnership;
2047
3216
  exports.verifyDidWebOwnership = verifyDidWebOwnership;
2048
- exports.verifyDnsTxtControllerDid = verifyDnsTxtControllerDid;
3217
+ exports.verifyDnsTxtControllerDid = verifyDnsTxtControllerDid2;
2049
3218
  exports.verifyEip712Signature = verifyEip712Signature;
2050
3219
  exports.verifyProof = verifyProof;
2051
3220
  exports.verifySchemaExists = verifySchemaExists;
2052
3221
  exports.verifySubjectOwnership = verifySubjectOwnership;
3222
+ exports.verifyTransferProof = verifyTransferProof;
3223
+ exports.verifyX402Eip712Artifact = verifyX402Eip712Artifact;
3224
+ exports.verifyX402Eip712Offer = verifyX402Eip712Offer;
3225
+ exports.verifyX402Eip712Receipt = verifyX402Eip712Receipt;
3226
+ exports.verifyX402JwsArtifact = verifyX402JwsArtifact;
3227
+ exports.verifyX402JwsOffer = verifyX402JwsOffer;
3228
+ exports.verifyX402JwsReceipt = verifyX402JwsReceipt;
2053
3229
  //# sourceMappingURL=index.cjs.map
2054
3230
  //# sourceMappingURL=index.cjs.map