@oma3/omatrust 0.1.0-alpha.1 → 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.
- package/README.md +21 -7
- package/dist/identity/index.cjs +544 -1
- 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 +528 -2
- package/dist/identity/index.js.map +1 -1
- package/dist/index-BOvk-7Ku.d.cts +235 -0
- package/dist/index-C2w5EvFH.d.ts +329 -0
- package/dist/index-QueRiudB.d.cts +329 -0
- package/dist/index-R78TpAhN.d.ts +235 -0
- package/dist/index.cjs +2020 -145
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2021 -146
- package/dist/index.js.map +1 -1
- package/dist/reputation/index.browser.cjs +3010 -0
- package/dist/reputation/index.browser.cjs.map +1 -0
- package/dist/reputation/index.browser.d.cts +14 -0
- package/dist/reputation/index.browser.d.ts +14 -0
- package/dist/reputation/index.browser.js +2946 -0
- package/dist/reputation/index.browser.js.map +1 -0
- package/dist/reputation/index.cjs +1884 -123
- package/dist/reputation/index.cjs.map +1 -1
- package/dist/reputation/index.d.cts +3 -1
- package/dist/reputation/index.d.ts +3 -1
- package/dist/reputation/index.js +1861 -123
- package/dist/reputation/index.js.map +1 -1
- package/dist/subject-ownership-B7cFlm8c.d.cts +664 -0
- package/dist/subject-ownership-B7cFlm8c.d.ts +664 -0
- package/dist/types-dpYxRq8N.d.cts +281 -0
- package/dist/types-dpYxRq8N.d.ts +281 -0
- package/dist/widgets/index.cjs +238 -0
- package/dist/widgets/index.cjs.map +1 -0
- package/dist/widgets/index.d.cts +158 -0
- package/dist/widgets/index.d.ts +158 -0
- package/dist/widgets/index.js +226 -0
- package/dist/widgets/index.js.map +1 -0
- package/package.json +35 -7
- package/dist/index-ChbJxwOA.d.cts +0 -415
- package/dist/index-ChbJxwOA.d.ts +0 -415
- 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
|
|
|
@@ -58,6 +59,13 @@ function encodeAttestationData(schema, data) {
|
|
|
58
59
|
if (!data || typeof data !== "object") {
|
|
59
60
|
throw new OmaTrustError("INVALID_INPUT", "data must be an object");
|
|
60
61
|
}
|
|
62
|
+
const validationErrors = validateAttestationData(schema, data);
|
|
63
|
+
if (validationErrors.length > 0) {
|
|
64
|
+
const summary = validationErrors.map((error) => `Field "${error.schemaFieldName}" (${error.expectedType}) got ${error.providedType}`).join("; ");
|
|
65
|
+
throw new OmaTrustError("INVALID_INPUT", `Attestation data validation failed: ${summary}`, {
|
|
66
|
+
errors: validationErrors
|
|
67
|
+
});
|
|
68
|
+
}
|
|
61
69
|
const fields = normalizeSchema(schema);
|
|
62
70
|
const schemaString = schemaToString(fields);
|
|
63
71
|
const encoder = new easSdk.SchemaEncoder(schemaString);
|
|
@@ -70,6 +78,99 @@ function encodeAttestationData(schema, data) {
|
|
|
70
78
|
);
|
|
71
79
|
return encoded;
|
|
72
80
|
}
|
|
81
|
+
function getActualType(value) {
|
|
82
|
+
if (value === null) {
|
|
83
|
+
return "null";
|
|
84
|
+
}
|
|
85
|
+
if (value === void 0) {
|
|
86
|
+
return "undefined";
|
|
87
|
+
}
|
|
88
|
+
if (Array.isArray(value)) {
|
|
89
|
+
return "array";
|
|
90
|
+
}
|
|
91
|
+
if (typeof value === "number") {
|
|
92
|
+
return Number.isFinite(value) ? "number" : "non-finite-number";
|
|
93
|
+
}
|
|
94
|
+
return typeof value;
|
|
95
|
+
}
|
|
96
|
+
function isNumericValue(value, allowNegative) {
|
|
97
|
+
if (typeof value === "bigint") {
|
|
98
|
+
return allowNegative || value >= 0n;
|
|
99
|
+
}
|
|
100
|
+
if (typeof value === "number") {
|
|
101
|
+
if (!Number.isFinite(value) || !Number.isInteger(value)) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return allowNegative || value >= 0;
|
|
105
|
+
}
|
|
106
|
+
if (typeof value === "string") {
|
|
107
|
+
if (value.length === 0) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const pattern = allowNegative ? /^-?\d+$/ : /^\d+$/;
|
|
111
|
+
return pattern.test(value);
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
function isHex(value) {
|
|
116
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]*$/.test(value);
|
|
117
|
+
}
|
|
118
|
+
function validateFieldValue(type, value) {
|
|
119
|
+
const normalizedType = type.trim().toLowerCase();
|
|
120
|
+
const bytesNMatch = normalizedType.match(/^bytes([1-9]|[12]\d|3[0-2])$/);
|
|
121
|
+
if (/^uint\d*$/.test(normalizedType)) {
|
|
122
|
+
return isNumericValue(value, false);
|
|
123
|
+
}
|
|
124
|
+
if (/^int\d*$/.test(normalizedType)) {
|
|
125
|
+
return isNumericValue(value, true);
|
|
126
|
+
}
|
|
127
|
+
if (normalizedType === "string") {
|
|
128
|
+
return typeof value === "string";
|
|
129
|
+
}
|
|
130
|
+
if (normalizedType === "string[]") {
|
|
131
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
132
|
+
}
|
|
133
|
+
if (normalizedType === "bool") {
|
|
134
|
+
return typeof value === "boolean";
|
|
135
|
+
}
|
|
136
|
+
if (normalizedType === "address") {
|
|
137
|
+
return typeof value === "string" && ethers.isAddress(value);
|
|
138
|
+
}
|
|
139
|
+
if (normalizedType === "bytes") {
|
|
140
|
+
return isHex(value) && (value.length - 2) % 2 === 0;
|
|
141
|
+
}
|
|
142
|
+
if (bytesNMatch) {
|
|
143
|
+
const expectedBytes = Number(bytesNMatch[1]);
|
|
144
|
+
return isHex(value) && value.length === 2 + expectedBytes * 2;
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
function validateAttestationData(schema, data) {
|
|
149
|
+
if (!data || typeof data !== "object") {
|
|
150
|
+
return [{
|
|
151
|
+
schemaFieldName: "data",
|
|
152
|
+
expectedType: "object",
|
|
153
|
+
providedType: getActualType(data),
|
|
154
|
+
providedValue: data
|
|
155
|
+
}];
|
|
156
|
+
}
|
|
157
|
+
const fields = normalizeSchema(schema);
|
|
158
|
+
const errors = [];
|
|
159
|
+
for (const field of fields) {
|
|
160
|
+
const value = data[field.name];
|
|
161
|
+
const isValid = validateFieldValue(field.type, value);
|
|
162
|
+
if (isValid) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
errors.push({
|
|
166
|
+
schemaFieldName: field.name,
|
|
167
|
+
expectedType: field.type,
|
|
168
|
+
providedType: getActualType(value),
|
|
169
|
+
providedValue: value
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return errors;
|
|
173
|
+
}
|
|
73
174
|
function decodeAttestationData(schema, encodedData) {
|
|
74
175
|
if (typeof encodedData !== "string" || !encodedData.startsWith("0x")) {
|
|
75
176
|
throw new OmaTrustError("INVALID_INPUT", "encodedData must be a hex string");
|
|
@@ -90,22 +191,11 @@ function decodeAttestationData(schema, encodedData) {
|
|
|
90
191
|
return result;
|
|
91
192
|
}
|
|
92
193
|
function extractExpirationTime(data) {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
if (typeof value === "bigint") {
|
|
100
|
-
return value;
|
|
101
|
-
}
|
|
102
|
-
if (typeof value === "number") {
|
|
103
|
-
return value;
|
|
104
|
-
}
|
|
105
|
-
if (typeof value === "string" && /^\d+$/.test(value)) {
|
|
106
|
-
return BigInt(value);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
194
|
+
const value = data["expiresAt"];
|
|
195
|
+
if (value === void 0 || value === null) return void 0;
|
|
196
|
+
if (typeof value === "bigint") return value;
|
|
197
|
+
if (typeof value === "number") return value;
|
|
198
|
+
if (typeof value === "string" && /^\d+$/.test(value)) return BigInt(value);
|
|
109
199
|
return void 0;
|
|
110
200
|
}
|
|
111
201
|
|
|
@@ -115,6 +205,11 @@ function assertString(value, name, code = "INVALID_INPUT") {
|
|
|
115
205
|
throw new OmaTrustError(code, `${name} must be a non-empty string`, { value });
|
|
116
206
|
}
|
|
117
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
|
+
}
|
|
118
213
|
|
|
119
214
|
// src/identity/caip.ts
|
|
120
215
|
var CAIP_10_REGEX = /^(?<namespace>[a-z0-9-]+):(?<reference>[a-zA-Z0-9-]+):(?<address>.+)$/;
|
|
@@ -145,7 +240,7 @@ function extractDidMethod(did) {
|
|
|
145
240
|
}
|
|
146
241
|
function normalizeDomain(domain) {
|
|
147
242
|
assertString(domain, "domain", "INVALID_DID");
|
|
148
|
-
return domain.trim().toLowerCase().replace(/\.$/, "");
|
|
243
|
+
return domain.trim().toLowerCase().replace(/\.$/, "").replace(/^www\./, "");
|
|
149
244
|
}
|
|
150
245
|
function normalizeDidWeb(input) {
|
|
151
246
|
assertString(input, "input", "INVALID_DID");
|
|
@@ -221,6 +316,8 @@ function normalizeDid(input) {
|
|
|
221
316
|
return normalizeDidHandle(trimmed);
|
|
222
317
|
case "key":
|
|
223
318
|
return normalizeDidKey(trimmed);
|
|
319
|
+
case "jwk":
|
|
320
|
+
return normalizeDidJwk(trimmed);
|
|
224
321
|
default:
|
|
225
322
|
return trimmed;
|
|
226
323
|
}
|
|
@@ -264,6 +361,26 @@ function parseDidPkh(did) {
|
|
|
264
361
|
}
|
|
265
362
|
return { namespace, chainId, address };
|
|
266
363
|
}
|
|
364
|
+
function getChainIdFromDidPkh(did) {
|
|
365
|
+
return parseDidPkh(did)?.chainId ?? null;
|
|
366
|
+
}
|
|
367
|
+
function getAddressFromDidPkh(did) {
|
|
368
|
+
return parseDidPkh(did)?.address ?? null;
|
|
369
|
+
}
|
|
370
|
+
function getNamespaceFromDidPkh(did) {
|
|
371
|
+
return parseDidPkh(did)?.namespace ?? null;
|
|
372
|
+
}
|
|
373
|
+
function isEvmDidPkh(did) {
|
|
374
|
+
return getNamespaceFromDidPkh(did) === "eip155";
|
|
375
|
+
}
|
|
376
|
+
function getDomainFromDidWeb(did) {
|
|
377
|
+
if (!did.startsWith("did:web:")) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
const identifier = did.slice("did:web:".length);
|
|
381
|
+
const [domain] = identifier.split("/");
|
|
382
|
+
return domain || null;
|
|
383
|
+
}
|
|
267
384
|
function extractAddressFromDid(identifier) {
|
|
268
385
|
assertString(identifier, "identifier", "INVALID_DID");
|
|
269
386
|
if (identifier.startsWith("did:pkh:")) {
|
|
@@ -290,6 +407,73 @@ function extractAddressFromDid(identifier) {
|
|
|
290
407
|
}
|
|
291
408
|
throw new OmaTrustError("INVALID_DID", "Unsupported identifier format", { identifier });
|
|
292
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
|
+
}
|
|
293
477
|
|
|
294
478
|
// src/reputation/internal.ts
|
|
295
479
|
var ZERO_UID = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
@@ -330,6 +514,53 @@ function resolveRecipientAddress(data) {
|
|
|
330
514
|
return ethers.ZeroAddress;
|
|
331
515
|
}
|
|
332
516
|
|
|
517
|
+
// src/reputation/eas-adapter.ts
|
|
518
|
+
function isRecord(value) {
|
|
519
|
+
return Boolean(value) && typeof value === "object";
|
|
520
|
+
}
|
|
521
|
+
function isHex32(value) {
|
|
522
|
+
return typeof value === "string" && /^0x[0-9a-fA-F]{64}$/.test(value);
|
|
523
|
+
}
|
|
524
|
+
function getEasTransactionReceipt(tx) {
|
|
525
|
+
if (!isRecord(tx)) {
|
|
526
|
+
return void 0;
|
|
527
|
+
}
|
|
528
|
+
const candidates = [tx.receipt, tx.tx, tx.transaction];
|
|
529
|
+
for (const candidate of candidates) {
|
|
530
|
+
if (isRecord(candidate)) {
|
|
531
|
+
return candidate;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return void 0;
|
|
535
|
+
}
|
|
536
|
+
function getEasTransactionHash(tx) {
|
|
537
|
+
const receipt = getEasTransactionReceipt(tx);
|
|
538
|
+
if (receipt) {
|
|
539
|
+
const receiptHash = receipt.hash;
|
|
540
|
+
if (isHex32(receiptHash)) {
|
|
541
|
+
return receiptHash;
|
|
542
|
+
}
|
|
543
|
+
const transactionHash = receipt.transactionHash;
|
|
544
|
+
if (isHex32(transactionHash)) {
|
|
545
|
+
return transactionHash;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (isRecord(tx) && isHex32(tx.hash)) {
|
|
549
|
+
return tx.hash;
|
|
550
|
+
}
|
|
551
|
+
return void 0;
|
|
552
|
+
}
|
|
553
|
+
function isEasSchemaNotFoundError(err) {
|
|
554
|
+
if (!isRecord(err)) {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
const message = err.message;
|
|
558
|
+
if (typeof message !== "string" || message.length === 0) {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
return /schema not found/i.test(message);
|
|
562
|
+
}
|
|
563
|
+
|
|
333
564
|
// src/reputation/submit.ts
|
|
334
565
|
async function submitAttestation(params) {
|
|
335
566
|
if (!params || typeof params !== "object") {
|
|
@@ -360,25 +591,46 @@ async function submitAttestation(params) {
|
|
|
360
591
|
}
|
|
361
592
|
});
|
|
362
593
|
const uid = await tx.wait();
|
|
363
|
-
const
|
|
364
|
-
const
|
|
594
|
+
const txHash = getEasTransactionHash(tx) ?? ZERO_UID;
|
|
595
|
+
const receipt = getEasTransactionReceipt(tx);
|
|
365
596
|
return {
|
|
366
597
|
uid,
|
|
367
598
|
txHash,
|
|
368
|
-
receipt
|
|
599
|
+
receipt
|
|
369
600
|
};
|
|
370
601
|
} catch (err) {
|
|
371
602
|
throw new OmaTrustError("NETWORK_ERROR", "Failed to submit attestation", { err });
|
|
372
603
|
}
|
|
373
604
|
}
|
|
374
|
-
function
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
);
|
|
605
|
+
async function revokeAttestation(params) {
|
|
606
|
+
if (!params || typeof params !== "object") {
|
|
607
|
+
throw new OmaTrustError("INVALID_INPUT", "params must be provided");
|
|
608
|
+
}
|
|
609
|
+
if (!params.signer) {
|
|
610
|
+
throw new OmaTrustError("INVALID_INPUT", "signer is required");
|
|
611
|
+
}
|
|
612
|
+
const eas = new easSdk.EAS(params.easContractAddress);
|
|
613
|
+
eas.connect(params.signer);
|
|
614
|
+
try {
|
|
615
|
+
const tx = await eas.revoke({
|
|
616
|
+
schema: params.schemaUid,
|
|
617
|
+
data: {
|
|
618
|
+
uid: params.uid,
|
|
619
|
+
value: toBigIntOrDefault(params.value, 0n)
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
await tx.wait();
|
|
623
|
+
const txHash = getEasTransactionHash(tx) ?? ZERO_UID;
|
|
624
|
+
const receipt = getEasTransactionReceipt(tx);
|
|
625
|
+
return {
|
|
626
|
+
txHash,
|
|
627
|
+
receipt
|
|
628
|
+
};
|
|
629
|
+
} catch (err) {
|
|
630
|
+
throw new OmaTrustError("NETWORK_ERROR", "Failed to revoke attestation", { err });
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function buildDelegatedTypedData(params) {
|
|
382
634
|
return {
|
|
383
635
|
domain: {
|
|
384
636
|
name: "EAS",
|
|
@@ -403,17 +655,52 @@ function buildDelegatedAttestationTypedData(params) {
|
|
|
403
655
|
message: {
|
|
404
656
|
attester: params.attester,
|
|
405
657
|
schema: params.schemaUid,
|
|
406
|
-
recipient,
|
|
407
|
-
expirationTime:
|
|
658
|
+
recipient: params.recipient,
|
|
659
|
+
expirationTime: toBigIntOrDefault(params.expirationTime, 0n),
|
|
408
660
|
revocable: params.revocable ?? true,
|
|
409
661
|
refUID: params.refUid ?? ZERO_UID,
|
|
410
|
-
data: encodedData,
|
|
662
|
+
data: params.encodedData,
|
|
411
663
|
value: toBigIntOrDefault(params.value, 0n),
|
|
412
664
|
nonce: toBigIntOrDefault(params.nonce, 0n),
|
|
413
665
|
deadline: toBigIntOrDefault(params.deadline, BigInt(Math.floor(Date.now() / 1e3) + 600))
|
|
414
666
|
}
|
|
415
667
|
};
|
|
416
668
|
}
|
|
669
|
+
function buildDelegatedAttestationTypedData(params) {
|
|
670
|
+
const dataWithHash = withAutoSubjectDidHash(params.schema, params.data);
|
|
671
|
+
const encodedData = encodeAttestationData(params.schema, dataWithHash);
|
|
672
|
+
const recipient = resolveRecipientAddress(dataWithHash);
|
|
673
|
+
return buildDelegatedTypedData({
|
|
674
|
+
chainId: params.chainId,
|
|
675
|
+
easContractAddress: params.easContractAddress,
|
|
676
|
+
schemaUid: params.schemaUid,
|
|
677
|
+
encodedData,
|
|
678
|
+
recipient,
|
|
679
|
+
attester: params.attester,
|
|
680
|
+
nonce: params.nonce,
|
|
681
|
+
revocable: params.revocable,
|
|
682
|
+
expirationTime: params.expirationTime ?? extractExpirationTime(dataWithHash),
|
|
683
|
+
refUid: params.refUid,
|
|
684
|
+
value: params.value,
|
|
685
|
+
deadline: params.deadline
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
function buildDelegatedTypedDataFromEncoded(params) {
|
|
689
|
+
return buildDelegatedTypedData({
|
|
690
|
+
chainId: params.chainId,
|
|
691
|
+
easContractAddress: params.easContractAddress,
|
|
692
|
+
schemaUid: params.schemaUid,
|
|
693
|
+
encodedData: params.encodedData,
|
|
694
|
+
recipient: params.recipient,
|
|
695
|
+
attester: params.attester,
|
|
696
|
+
nonce: params.nonce,
|
|
697
|
+
revocable: params.revocable,
|
|
698
|
+
expirationTime: params.expirationTime,
|
|
699
|
+
refUid: params.refUid,
|
|
700
|
+
value: params.value,
|
|
701
|
+
deadline: params.deadline
|
|
702
|
+
});
|
|
703
|
+
}
|
|
417
704
|
function splitSignature(signature) {
|
|
418
705
|
try {
|
|
419
706
|
const parsed = ethers.Signature.from(signature);
|
|
@@ -473,24 +760,44 @@ async function submitDelegatedAttestation(params) {
|
|
|
473
760
|
payload = {};
|
|
474
761
|
}
|
|
475
762
|
if (!response.ok) {
|
|
476
|
-
|
|
477
|
-
|
|
763
|
+
const relayError = {
|
|
764
|
+
httpStatus: response.status,
|
|
765
|
+
code: payload.code,
|
|
766
|
+
error: payload.error ?? payload.message,
|
|
478
767
|
payload
|
|
479
|
-
}
|
|
768
|
+
};
|
|
769
|
+
throw new OmaTrustError("RELAY_ERROR", `Relay submission failed (HTTP ${response.status})`, relayError);
|
|
480
770
|
}
|
|
481
771
|
const uid = payload.uid ?? ZERO_UID;
|
|
482
772
|
const txHash = payload.txHash;
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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;
|
|
489
789
|
}
|
|
490
790
|
var EAS_EVENT_ABI = [
|
|
491
791
|
"event Attested(address indexed recipient, address indexed attester, bytes32 uid, bytes32 indexed schemaUID)"
|
|
492
792
|
];
|
|
493
|
-
function
|
|
793
|
+
function parseEventTxHash(event) {
|
|
794
|
+
const txHash = event.transactionHash;
|
|
795
|
+
if (typeof txHash !== "string") {
|
|
796
|
+
return void 0;
|
|
797
|
+
}
|
|
798
|
+
return /^0x[0-9a-fA-F]{64}$/.test(txHash) ? txHash : void 0;
|
|
799
|
+
}
|
|
800
|
+
function parseAttestation(attestation, schema, txHash) {
|
|
494
801
|
const rawData = attestation.data ?? "0x";
|
|
495
802
|
const decoded = schema ? decodeAttestationData(schema, rawData) : {};
|
|
496
803
|
const toBigIntSafe = (value) => {
|
|
@@ -510,6 +817,7 @@ function parseAttestation(attestation, schema) {
|
|
|
510
817
|
schema: attestation.schema,
|
|
511
818
|
attester: attestation.attester,
|
|
512
819
|
recipient: attestation.recipient,
|
|
820
|
+
txHash,
|
|
513
821
|
revocable: Boolean(attestation.revocable),
|
|
514
822
|
revocationTime: toBigIntSafe(attestation.revocationTime),
|
|
515
823
|
expirationTime: toBigIntSafe(attestation.expirationTime),
|
|
@@ -535,21 +843,18 @@ async function getAttestation(params) {
|
|
|
535
843
|
throw new OmaTrustError("NETWORK_ERROR", "Failed to read attestation", { err });
|
|
536
844
|
}
|
|
537
845
|
}
|
|
538
|
-
async function
|
|
539
|
-
const
|
|
540
|
-
const
|
|
541
|
-
const toBlock = params.toBlock ?? await provider.getBlockNumber();
|
|
542
|
-
const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
|
|
543
|
-
const filter = contract.filters.Attested(didToAddress(params.did));
|
|
846
|
+
async function queryAttestationEvents(easContractAddress, provider, fromBlock, toBlock, options) {
|
|
847
|
+
const contract = new ethers.Contract(easContractAddress, EAS_EVENT_ABI, provider);
|
|
848
|
+
const filter = contract.filters.Attested(options?.recipient ?? null, options?.attester ?? null);
|
|
544
849
|
let events;
|
|
545
850
|
try {
|
|
546
851
|
events = await contract.queryFilter(filter, fromBlock, toBlock);
|
|
547
852
|
} catch (err) {
|
|
548
853
|
throw new OmaTrustError("NETWORK_ERROR", "Failed to query attestation events", { err });
|
|
549
854
|
}
|
|
550
|
-
const eas = new easSdk.EAS(
|
|
855
|
+
const eas = new easSdk.EAS(easContractAddress);
|
|
551
856
|
eas.connect(provider);
|
|
552
|
-
const schemaFilter =
|
|
857
|
+
const schemaFilter = options?.schemas?.map((s) => s.toLowerCase());
|
|
553
858
|
const results = [];
|
|
554
859
|
for (const event of events) {
|
|
555
860
|
if (!("args" in event && Array.isArray(event.args))) {
|
|
@@ -568,11 +873,39 @@ async function getAttestationsForDid(params) {
|
|
|
568
873
|
if (!attestation || !attestation.uid) {
|
|
569
874
|
continue;
|
|
570
875
|
}
|
|
571
|
-
results.push(parseAttestation(attestation));
|
|
876
|
+
results.push(parseAttestation(attestation, void 0, parseEventTxHash(event)));
|
|
572
877
|
}
|
|
573
878
|
results.sort((a, b) => Number(b.time - a.time));
|
|
574
879
|
return results;
|
|
575
880
|
}
|
|
881
|
+
async function getAttestationsForDid(params) {
|
|
882
|
+
const provider = params.provider;
|
|
883
|
+
const toBlock = params.toBlock ?? await provider.getBlockNumber();
|
|
884
|
+
const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
|
|
885
|
+
return queryAttestationEvents(
|
|
886
|
+
params.easContractAddress,
|
|
887
|
+
provider,
|
|
888
|
+
fromBlock,
|
|
889
|
+
toBlock,
|
|
890
|
+
{ recipient: didToAddress(params.subjectDid), schemas: params.schemas }
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
async function getAttestationsByAttester(params) {
|
|
894
|
+
const provider = params.provider;
|
|
895
|
+
const toBlock = params.toBlock ?? await provider.getBlockNumber();
|
|
896
|
+
const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
|
|
897
|
+
if (params.limit !== void 0 && params.limit <= 0) {
|
|
898
|
+
return [];
|
|
899
|
+
}
|
|
900
|
+
const results = await queryAttestationEvents(
|
|
901
|
+
params.easContractAddress,
|
|
902
|
+
provider,
|
|
903
|
+
fromBlock,
|
|
904
|
+
toBlock,
|
|
905
|
+
{ attester: params.attester, schemas: params.schemas }
|
|
906
|
+
);
|
|
907
|
+
return params.limit !== void 0 ? results.slice(0, params.limit) : results;
|
|
908
|
+
}
|
|
576
909
|
async function listAttestations(params) {
|
|
577
910
|
const limit = params.limit ?? 20;
|
|
578
911
|
if (limit <= 0) {
|
|
@@ -583,34 +916,15 @@ async function listAttestations(params) {
|
|
|
583
916
|
}
|
|
584
917
|
async function getLatestAttestations(params) {
|
|
585
918
|
const provider = params.provider;
|
|
586
|
-
const
|
|
587
|
-
const
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if (!("args" in event && Array.isArray(event.args))) {
|
|
596
|
-
continue;
|
|
597
|
-
}
|
|
598
|
-
const args = event.args;
|
|
599
|
-
const uid = args?.[2];
|
|
600
|
-
const schemaUid = args?.[3];
|
|
601
|
-
if (!uid || !schemaUid) {
|
|
602
|
-
continue;
|
|
603
|
-
}
|
|
604
|
-
if (schemaFilter && !schemaFilter.includes(schemaUid.toLowerCase())) {
|
|
605
|
-
continue;
|
|
606
|
-
}
|
|
607
|
-
const attestation = await eas.getAttestation(uid);
|
|
608
|
-
if (!attestation || !attestation.uid) {
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
results.push(parseAttestation(attestation));
|
|
612
|
-
}
|
|
613
|
-
results.sort((a, b) => Number(b.time - a.time));
|
|
919
|
+
const toBlock = await provider.getBlockNumber();
|
|
920
|
+
const fromBlock = params.fromBlock ?? Math.max(0, toBlock - 5e4);
|
|
921
|
+
const results = await queryAttestationEvents(
|
|
922
|
+
params.easContractAddress,
|
|
923
|
+
provider,
|
|
924
|
+
fromBlock,
|
|
925
|
+
toBlock,
|
|
926
|
+
{ schemas: params.schemas }
|
|
927
|
+
);
|
|
614
928
|
return results.slice(0, params.limit ?? 20);
|
|
615
929
|
}
|
|
616
930
|
function deduplicateReviews(attestations) {
|
|
@@ -814,6 +1128,147 @@ function getExplorerTxUrl(chainId, txHash) {
|
|
|
814
1128
|
function getExplorerAddressUrl(chainId, address) {
|
|
815
1129
|
return `${getConfig(chainId).explorer}/address/${address}`;
|
|
816
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
|
|
817
1272
|
async function fetchDidDocument(domain) {
|
|
818
1273
|
const normalized = domain.toLowerCase().replace(/\.$/, "");
|
|
819
1274
|
const url = `https://${normalized}/.well-known/did.json`;
|
|
@@ -824,15 +1279,16 @@ async function fetchDidDocument(domain) {
|
|
|
824
1279
|
throw new OmaTrustError("NETWORK_ERROR", "Failed to fetch DID document", { domain, err });
|
|
825
1280
|
}
|
|
826
1281
|
if (!response.ok) {
|
|
827
|
-
throw new OmaTrustError("NETWORK_ERROR",
|
|
1282
|
+
throw new OmaTrustError("NETWORK_ERROR", `DID document fetch failed: ${response.status}`, {
|
|
828
1283
|
domain,
|
|
829
1284
|
status: response.status
|
|
830
1285
|
});
|
|
831
1286
|
}
|
|
832
|
-
|
|
833
|
-
return body;
|
|
1287
|
+
return await response.json();
|
|
834
1288
|
}
|
|
835
|
-
|
|
1289
|
+
|
|
1290
|
+
// src/reputation/proof/did-json.ts
|
|
1291
|
+
function extractEvmAddressesFromDidDocument(didDocument) {
|
|
836
1292
|
const methods = didDocument.verificationMethod;
|
|
837
1293
|
if (!Array.isArray(methods)) {
|
|
838
1294
|
return [];
|
|
@@ -856,14 +1312,52 @@ function extractAddressesFromDidDocument(didDocument) {
|
|
|
856
1312
|
}
|
|
857
1313
|
return [...addresses];
|
|
858
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
|
+
}
|
|
859
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
|
+
}
|
|
860
1354
|
let expectedAddress;
|
|
861
1355
|
try {
|
|
862
1356
|
expectedAddress = ethers.getAddress(extractAddressFromDid(expectedControllerDid));
|
|
863
1357
|
} catch {
|
|
864
|
-
return { valid: false, reason: "
|
|
1358
|
+
return { valid: false, reason: "Controller DID does not resolve to an EVM address and is not did:jwk" };
|
|
865
1359
|
}
|
|
866
|
-
const addresses =
|
|
1360
|
+
const addresses = extractEvmAddressesFromDidDocument(didDocument);
|
|
867
1361
|
if (addresses.some((address) => address.toLowerCase() === expectedAddress.toLowerCase())) {
|
|
868
1362
|
return { valid: true };
|
|
869
1363
|
}
|
|
@@ -872,6 +1366,13 @@ function verifyDidDocumentControllerDid(didDocument, expectedControllerDid) {
|
|
|
872
1366
|
reason: `No matching address found in DID document (expected ${expectedAddress})`
|
|
873
1367
|
};
|
|
874
1368
|
}
|
|
1369
|
+
async function verifyDidJsonControllerDid(domain, expectedControllerDid, options = {}) {
|
|
1370
|
+
if (!domain || typeof domain !== "string") {
|
|
1371
|
+
throw new OmaTrustError("INVALID_INPUT", "domain must be a non-empty string", { domain });
|
|
1372
|
+
}
|
|
1373
|
+
const didDocument = options.fetchDidDocument ? await options.fetchDidDocument(domain) : await fetchDidDocument(domain);
|
|
1374
|
+
return verifyDidDocumentControllerDid(didDocument, expectedControllerDid);
|
|
1375
|
+
}
|
|
875
1376
|
function buildEip712Domain(name, version, chainId, verifyingContract) {
|
|
876
1377
|
return { name, version, chainId, verifyingContract };
|
|
877
1378
|
}
|
|
@@ -904,53 +1405,485 @@ function verifyEip712Signature(typedData, signature) {
|
|
|
904
1405
|
throw new OmaTrustError("INVALID_INPUT", "Failed to verify EIP-712 signature", { err });
|
|
905
1406
|
}
|
|
906
1407
|
}
|
|
1408
|
+
|
|
1409
|
+
// src/reputation/proof/dns-txt-record.ts
|
|
907
1410
|
function parseDnsTxtRecord(record) {
|
|
908
1411
|
if (!record || typeof record !== "string") {
|
|
909
1412
|
throw new OmaTrustError("INVALID_INPUT", "record must be a non-empty string", { record });
|
|
910
1413
|
}
|
|
911
1414
|
const entries = record.split(/[;\s]+/).map((entry) => entry.trim()).filter(Boolean);
|
|
912
1415
|
const parsed = {};
|
|
1416
|
+
const controllers = [];
|
|
913
1417
|
for (const entry of entries) {
|
|
914
|
-
const
|
|
915
|
-
if (
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
if (
|
|
920
|
-
|
|
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;
|
|
921
1427
|
}
|
|
922
|
-
parsed[key.trim()] = value.trim();
|
|
923
1428
|
}
|
|
924
1429
|
return {
|
|
925
1430
|
version: parsed.v,
|
|
926
|
-
controller:
|
|
927
|
-
|
|
1431
|
+
controller: controllers[0],
|
|
1432
|
+
controllers
|
|
928
1433
|
};
|
|
929
1434
|
}
|
|
930
1435
|
function buildDnsTxtRecord(controllerDid) {
|
|
931
1436
|
const normalized = normalizeDid(controllerDid);
|
|
932
1437
|
return `v=1;controller=${normalized}`;
|
|
933
1438
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1439
|
+
|
|
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
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
fragment = rawFragment;
|
|
937
1462
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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
|
+
};
|
|
1475
|
+
}
|
|
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;
|
|
1482
|
+
}
|
|
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;
|
|
1490
|
+
}
|
|
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 };
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return { valid: true };
|
|
1587
|
+
}
|
|
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
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
return { valid: true };
|
|
1595
|
+
}
|
|
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;
|
|
1614
|
+
const parts = compactJws.split(".");
|
|
1615
|
+
if (parts.length !== 3) {
|
|
1616
|
+
return failure("MALFORMED_JWS", "Invalid compact JWS format: expected 3 dot-separated parts");
|
|
1617
|
+
}
|
|
1618
|
+
let header;
|
|
941
1619
|
try {
|
|
942
|
-
|
|
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 }
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
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
|
+
);
|
|
1658
|
+
}
|
|
1659
|
+
} catch (err) {
|
|
1660
|
+
}
|
|
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);
|
|
943
1678
|
} catch (err) {
|
|
944
|
-
|
|
1679
|
+
const message = err instanceof Error ? err.message : "Signature verification failed";
|
|
1680
|
+
return failure("SIGNATURE_INVALID", message, { header, kid });
|
|
945
1681
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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 };
|
|
951
1793
|
}
|
|
952
1794
|
}
|
|
953
|
-
return { valid:
|
|
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");
|
|
954
1887
|
}
|
|
955
1888
|
|
|
956
1889
|
// src/reputation/verify.ts
|
|
@@ -1010,18 +1943,18 @@ function decodeJwtPayload(compactJws) {
|
|
|
1010
1943
|
return JSON.parse(json);
|
|
1011
1944
|
}
|
|
1012
1945
|
async function verifyProof(params) {
|
|
1013
|
-
const { proof, provider,
|
|
1946
|
+
const { proof, provider, expectedSubjectDid, expectedControllerDid } = params;
|
|
1014
1947
|
try {
|
|
1015
1948
|
switch (proof.proofType) {
|
|
1016
1949
|
case "tx-encoded-value": {
|
|
1017
1950
|
if (!provider) {
|
|
1018
1951
|
return { valid: false, proofType: proof.proofType, reason: "Provider is required" };
|
|
1019
1952
|
}
|
|
1020
|
-
if (!
|
|
1953
|
+
if (!expectedSubjectDid || !expectedControllerDid) {
|
|
1021
1954
|
return {
|
|
1022
1955
|
valid: false,
|
|
1023
1956
|
proofType: proof.proofType,
|
|
1024
|
-
reason: "
|
|
1957
|
+
reason: "expectedSubjectDid and expectedControllerDid are required"
|
|
1025
1958
|
};
|
|
1026
1959
|
}
|
|
1027
1960
|
const proofObject = proof.proofObject;
|
|
@@ -1033,13 +1966,13 @@ async function verifyProof(params) {
|
|
|
1033
1966
|
return { valid: false, proofType: proof.proofType, reason: "Transaction not found" };
|
|
1034
1967
|
}
|
|
1035
1968
|
const expectedAmount = calculateTransferAmount(
|
|
1036
|
-
|
|
1037
|
-
|
|
1969
|
+
expectedSubjectDid,
|
|
1970
|
+
expectedControllerDid,
|
|
1038
1971
|
chainId,
|
|
1039
1972
|
getProofPurpose(proof)
|
|
1040
1973
|
);
|
|
1041
|
-
const subjectAddress = ethers.getAddress(extractAddressFromDid(
|
|
1042
|
-
const controllerAddress = ethers.getAddress(extractAddressFromDid(
|
|
1974
|
+
const subjectAddress = ethers.getAddress(extractAddressFromDid(expectedSubjectDid));
|
|
1975
|
+
const controllerAddress = ethers.getAddress(extractAddressFromDid(expectedControllerDid));
|
|
1043
1976
|
if (tx.from && ethers.getAddress(tx.from) !== subjectAddress) {
|
|
1044
1977
|
return { valid: false, proofType: proof.proofType, reason: "Transaction sender mismatch" };
|
|
1045
1978
|
}
|
|
@@ -1113,6 +2046,38 @@ async function verifyProof(params) {
|
|
|
1113
2046
|
if (!proof.proofObject || typeof proof.proofObject !== "object") {
|
|
1114
2047
|
return { valid: false, proofType: proof.proofType, reason: "Invalid x402 proof object" };
|
|
1115
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
|
+
}
|
|
1116
2081
|
return { valid: true, proofType: proof.proofType };
|
|
1117
2082
|
}
|
|
1118
2083
|
case "evidence-pointer": {
|
|
@@ -1124,9 +2089,9 @@ async function verifyProof(params) {
|
|
|
1124
2089
|
if (!response.ok) {
|
|
1125
2090
|
return { valid: false, proofType: proof.proofType, reason: `Evidence fetch failed (${response.status})` };
|
|
1126
2091
|
}
|
|
1127
|
-
if (object.url.endsWith("/.well-known/did.json") &&
|
|
2092
|
+
if (object.url.endsWith("/.well-known/did.json") && expectedControllerDid) {
|
|
1128
2093
|
const didDoc = await response.json();
|
|
1129
|
-
const didCheck = verifyDidDocumentControllerDid(didDoc,
|
|
2094
|
+
const didCheck = verifyDidDocumentControllerDid(didDoc, expectedControllerDid);
|
|
1130
2095
|
return {
|
|
1131
2096
|
valid: didCheck.valid,
|
|
1132
2097
|
proofType: proof.proofType,
|
|
@@ -1134,10 +2099,10 @@ async function verifyProof(params) {
|
|
|
1134
2099
|
};
|
|
1135
2100
|
}
|
|
1136
2101
|
const body = await response.text();
|
|
1137
|
-
if (
|
|
2102
|
+
if (expectedControllerDid && !body.includes(expectedControllerDid)) {
|
|
1138
2103
|
try {
|
|
1139
2104
|
const parsed = parseDnsTxtRecord(body);
|
|
1140
|
-
if (parsed.
|
|
2105
|
+
if (!parsed.controllers.includes(expectedControllerDid)) {
|
|
1141
2106
|
return {
|
|
1142
2107
|
valid: false,
|
|
1143
2108
|
proofType: proof.proofType,
|
|
@@ -1193,8 +2158,8 @@ async function verifyAttestation(params) {
|
|
|
1193
2158
|
const result = await verifyProof({
|
|
1194
2159
|
proof,
|
|
1195
2160
|
provider: params.provider,
|
|
1196
|
-
|
|
1197
|
-
|
|
2161
|
+
expectedSubjectDid: params.context?.subjectDid,
|
|
2162
|
+
expectedControllerDid: params.context?.controllerDid
|
|
1198
2163
|
});
|
|
1199
2164
|
checks[proof.proofType] = result.valid;
|
|
1200
2165
|
if (!result.valid) {
|
|
@@ -1232,6 +2197,9 @@ async function getSchemaDetails(schemaRegistry, schemaUid) {
|
|
|
1232
2197
|
revocable: Boolean(details.revocable)
|
|
1233
2198
|
};
|
|
1234
2199
|
} catch (err) {
|
|
2200
|
+
if (isEasSchemaNotFoundError(err)) {
|
|
2201
|
+
throw new OmaTrustError("SCHEMA_NOT_FOUND", "Schema was not found", { schemaUid });
|
|
2202
|
+
}
|
|
1235
2203
|
if (err instanceof OmaTrustError) {
|
|
1236
2204
|
throw err;
|
|
1237
2205
|
}
|
|
@@ -1294,6 +2262,504 @@ async function callControllerWitness(params) {
|
|
|
1294
2262
|
method: "did-json"
|
|
1295
2263
|
};
|
|
1296
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
|
+
}
|
|
1297
2763
|
|
|
1298
2764
|
// src/reputation/proof/tx-interaction.ts
|
|
1299
2765
|
function createTxInteractionProof(chainId, txHash) {
|
|
@@ -1415,8 +2881,283 @@ function createEvidencePointerProof(url) {
|
|
|
1415
2881
|
issuedAt: Math.floor(Date.now() / 1e3)
|
|
1416
2882
|
};
|
|
1417
2883
|
}
|
|
2884
|
+
function verifyDnsTxtControllerDid2(domain, expectedControllerDid, options = {}) {
|
|
2885
|
+
return verifyDnsTxtControllerDid(domain, expectedControllerDid, {
|
|
2886
|
+
...options,
|
|
2887
|
+
resolveTxt: options.resolveTxt ?? promises.resolveTxt
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
function assertConnectedWalletDid(input) {
|
|
2891
|
+
const normalized = normalizeDid(input);
|
|
2892
|
+
if (extractDidMethod(normalized) !== "pkh") {
|
|
2893
|
+
throw new OmaTrustError("INVALID_INPUT", "connectedWalletDid must be a did:pkh DID", {
|
|
2894
|
+
connectedWalletDid: input
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
return normalized;
|
|
2898
|
+
}
|
|
2899
|
+
function normalizeSubjectDid(input) {
|
|
2900
|
+
return normalizeDid(input);
|
|
2901
|
+
}
|
|
2902
|
+
async function verifyDidWebOwnership(params) {
|
|
2903
|
+
const subjectDid = normalizeSubjectDid(params.subjectDid);
|
|
2904
|
+
const connectedWalletDid = assertConnectedWalletDid(params.connectedWalletDid);
|
|
2905
|
+
const domain = getDomainFromDidWeb(subjectDid);
|
|
2906
|
+
if (!domain) {
|
|
2907
|
+
throw new OmaTrustError("INVALID_INPUT", "subjectDid must be a did:web DID", {
|
|
2908
|
+
subjectDid: params.subjectDid
|
|
2909
|
+
});
|
|
2910
|
+
}
|
|
2911
|
+
let dnsReason;
|
|
2912
|
+
try {
|
|
2913
|
+
const dnsResult = await verifyDnsTxtControllerDid(domain, connectedWalletDid, {
|
|
2914
|
+
resolveTxt: params.resolveTxt,
|
|
2915
|
+
recordPrefix: params.recordPrefix
|
|
2916
|
+
});
|
|
2917
|
+
if (dnsResult.valid) {
|
|
2918
|
+
return {
|
|
2919
|
+
valid: true,
|
|
2920
|
+
method: "dns",
|
|
2921
|
+
details: `Verified via DNS TXT record at ${params.recordPrefix ?? "_controllers"}.${domain}`,
|
|
2922
|
+
subjectDid,
|
|
2923
|
+
connectedWalletDid
|
|
2924
|
+
};
|
|
2925
|
+
}
|
|
2926
|
+
dnsReason = dnsResult.reason;
|
|
2927
|
+
} catch (error) {
|
|
2928
|
+
dnsReason = error instanceof Error ? error.message : "DNS TXT verification failed";
|
|
2929
|
+
}
|
|
2930
|
+
let didDocReason;
|
|
2931
|
+
try {
|
|
2932
|
+
const didDocumentResult = await verifyDidJsonControllerDid(domain, connectedWalletDid, {
|
|
2933
|
+
fetchDidDocument: params.fetchDidDocument
|
|
2934
|
+
});
|
|
2935
|
+
if (didDocumentResult.valid) {
|
|
2936
|
+
return {
|
|
2937
|
+
valid: true,
|
|
2938
|
+
method: "did-document",
|
|
2939
|
+
details: `Verified via DID document at https://${domain}/.well-known/did.json`,
|
|
2940
|
+
subjectDid,
|
|
2941
|
+
connectedWalletDid
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2944
|
+
didDocReason = didDocumentResult.reason;
|
|
2945
|
+
} catch (error) {
|
|
2946
|
+
didDocReason = error instanceof Error ? error.message : "DID document verification failed";
|
|
2947
|
+
}
|
|
2948
|
+
return {
|
|
2949
|
+
valid: false,
|
|
2950
|
+
reason: "DID ownership verification failed",
|
|
2951
|
+
details: `DNS check: ${dnsReason ?? "failed"}. DID document check: ${didDocReason ?? "failed"}.`,
|
|
2952
|
+
subjectDid,
|
|
2953
|
+
connectedWalletDid
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
async function verifyDidPkhOwnership(params) {
|
|
2957
|
+
const subjectDid = normalizeSubjectDid(params.subjectDid);
|
|
2958
|
+
const connectedWalletDid = assertConnectedWalletDid(params.connectedWalletDid);
|
|
2959
|
+
if (!isEvmDidPkh(subjectDid)) {
|
|
2960
|
+
throw new OmaTrustError("INVALID_INPUT", "subjectDid must be an EVM did:pkh DID", {
|
|
2961
|
+
subjectDid: params.subjectDid
|
|
2962
|
+
});
|
|
2963
|
+
}
|
|
2964
|
+
const subjectAddress = getAddressFromDidPkh(subjectDid);
|
|
2965
|
+
const connectedWalletAddress = getAddressFromDidPkh(connectedWalletDid);
|
|
2966
|
+
const chainIdRaw = getChainIdFromDidPkh(subjectDid);
|
|
2967
|
+
if (!subjectAddress || !connectedWalletAddress || !chainIdRaw) {
|
|
2968
|
+
throw new OmaTrustError("INVALID_INPUT", "Could not parse did:pkh ownership inputs", {
|
|
2969
|
+
subjectDid: params.subjectDid,
|
|
2970
|
+
connectedWalletDid: params.connectedWalletDid
|
|
2971
|
+
});
|
|
2972
|
+
}
|
|
2973
|
+
const chainId = Number(chainIdRaw);
|
|
2974
|
+
if (!Number.isFinite(chainId)) {
|
|
2975
|
+
throw new OmaTrustError("INVALID_INPUT", "Invalid chain id in subjectDid", {
|
|
2976
|
+
subjectDid: params.subjectDid
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
const code = await params.provider.getCode(subjectAddress);
|
|
2980
|
+
const isContract = code !== "0x" && code !== "0x0";
|
|
2981
|
+
if (!isContract) {
|
|
2982
|
+
if (ethers.getAddress(subjectAddress) === ethers.getAddress(connectedWalletAddress)) {
|
|
2983
|
+
return {
|
|
2984
|
+
valid: true,
|
|
2985
|
+
method: "wallet",
|
|
2986
|
+
details: "Verified direct wallet ownership from matching did:pkh subject and connected wallet",
|
|
2987
|
+
subjectDid,
|
|
2988
|
+
connectedWalletDid
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
return {
|
|
2992
|
+
valid: false,
|
|
2993
|
+
reason: "EOA did:pkh subject does not match connected wallet",
|
|
2994
|
+
details: "For direct wallet did:pkh subjects, connectedWalletDid must match the subject DID.",
|
|
2995
|
+
subjectDid,
|
|
2996
|
+
connectedWalletDid
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
const controllingWalletDid = await discoverControllingWalletDid(params.provider, subjectAddress, chainId);
|
|
3000
|
+
if (params.txHash) {
|
|
3001
|
+
if (!controllingWalletDid) {
|
|
3002
|
+
return {
|
|
3003
|
+
valid: false,
|
|
3004
|
+
reason: "Could not discover controlling wallet",
|
|
3005
|
+
details: "Contract does not expose owner/admin/getOwner or a readable EIP-1967 admin slot for transfer verification.",
|
|
3006
|
+
subjectDid,
|
|
3007
|
+
connectedWalletDid
|
|
3008
|
+
};
|
|
3009
|
+
}
|
|
3010
|
+
const tx = await params.provider.getTransaction(params.txHash);
|
|
3011
|
+
if (!tx) {
|
|
3012
|
+
return {
|
|
3013
|
+
valid: false,
|
|
3014
|
+
reason: "Transaction not found",
|
|
3015
|
+
details: `Transaction ${params.txHash} was not found on chain ${chainId}.`,
|
|
3016
|
+
subjectDid,
|
|
3017
|
+
connectedWalletDid,
|
|
3018
|
+
controllingWalletDid
|
|
3019
|
+
};
|
|
3020
|
+
}
|
|
3021
|
+
const receipt = await params.provider.getTransactionReceipt(params.txHash);
|
|
3022
|
+
if (!receipt) {
|
|
3023
|
+
return {
|
|
3024
|
+
valid: false,
|
|
3025
|
+
reason: "Transaction not confirmed",
|
|
3026
|
+
details: "Transaction exists but is not yet confirmed.",
|
|
3027
|
+
subjectDid,
|
|
3028
|
+
connectedWalletDid,
|
|
3029
|
+
controllingWalletDid
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
const controllingWalletAddress = getAddressFromDidPkh(controllingWalletDid);
|
|
3033
|
+
if (!tx.from || !controllingWalletAddress || ethers.getAddress(tx.from) !== ethers.getAddress(controllingWalletAddress)) {
|
|
3034
|
+
return {
|
|
3035
|
+
valid: false,
|
|
3036
|
+
reason: "Wrong sender",
|
|
3037
|
+
details: `Transfer must originate from controlling wallet ${controllingWalletDid}.`,
|
|
3038
|
+
subjectDid,
|
|
3039
|
+
connectedWalletDid,
|
|
3040
|
+
controllingWalletDid
|
|
3041
|
+
};
|
|
3042
|
+
}
|
|
3043
|
+
if (!tx.to || ethers.getAddress(tx.to) !== ethers.getAddress(connectedWalletAddress)) {
|
|
3044
|
+
return {
|
|
3045
|
+
valid: false,
|
|
3046
|
+
reason: "Wrong recipient",
|
|
3047
|
+
details: `Transfer must be sent to connected wallet ${connectedWalletDid}.`,
|
|
3048
|
+
subjectDid,
|
|
3049
|
+
connectedWalletDid,
|
|
3050
|
+
controllingWalletDid
|
|
3051
|
+
};
|
|
3052
|
+
}
|
|
3053
|
+
const expectedAmount = calculateTransferAmount(
|
|
3054
|
+
subjectDid,
|
|
3055
|
+
connectedWalletDid,
|
|
3056
|
+
chainId,
|
|
3057
|
+
"shared-control"
|
|
3058
|
+
);
|
|
3059
|
+
const actualValue = BigInt(tx.value ?? 0n);
|
|
3060
|
+
if (actualValue !== expectedAmount) {
|
|
3061
|
+
return {
|
|
3062
|
+
valid: false,
|
|
3063
|
+
reason: "Wrong amount",
|
|
3064
|
+
details: `Transfer amount ${actualValue.toString()} does not match expected proof amount ${expectedAmount.toString()}.`,
|
|
3065
|
+
subjectDid,
|
|
3066
|
+
connectedWalletDid,
|
|
3067
|
+
controllingWalletDid
|
|
3068
|
+
};
|
|
3069
|
+
}
|
|
3070
|
+
await params.provider.getBlockNumber();
|
|
3071
|
+
await params.provider.getBlock(receipt.blockNumber);
|
|
3072
|
+
return {
|
|
3073
|
+
valid: true,
|
|
3074
|
+
method: "transfer",
|
|
3075
|
+
details: `Verified via transfer proof ${params.txHash}.`,
|
|
3076
|
+
subjectDid,
|
|
3077
|
+
connectedWalletDid,
|
|
3078
|
+
controllingWalletDid
|
|
3079
|
+
};
|
|
3080
|
+
}
|
|
3081
|
+
for (const pattern of OWNERSHIP_PATTERNS) {
|
|
3082
|
+
const ownerAddress = await readOwnerFromContract(
|
|
3083
|
+
params.provider,
|
|
3084
|
+
subjectAddress,
|
|
3085
|
+
pattern.signature,
|
|
3086
|
+
pattern.method
|
|
3087
|
+
);
|
|
3088
|
+
if (ownerAddress && ethers.getAddress(ownerAddress) === ethers.getAddress(connectedWalletAddress)) {
|
|
3089
|
+
return {
|
|
3090
|
+
valid: true,
|
|
3091
|
+
method: "contract",
|
|
3092
|
+
details: `Verified via ${pattern.method}() ownership check.`,
|
|
3093
|
+
subjectDid,
|
|
3094
|
+
connectedWalletDid,
|
|
3095
|
+
controllingWalletDid: controllingWalletDid ?? void 0
|
|
3096
|
+
};
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
try {
|
|
3100
|
+
const adminValue = await params.provider.getStorage(subjectAddress, EIP1967_ADMIN_SLOT);
|
|
3101
|
+
if (adminValue && adminValue !== "0x" && adminValue !== "0x0000000000000000000000000000000000000000000000000000000000000000") {
|
|
3102
|
+
const adminAddress = ethers.getAddress(`0x${adminValue.slice(-40)}`);
|
|
3103
|
+
if (adminAddress === ethers.getAddress(connectedWalletAddress)) {
|
|
3104
|
+
return {
|
|
3105
|
+
valid: true,
|
|
3106
|
+
method: "contract",
|
|
3107
|
+
details: "Verified via EIP-1967 admin slot.",
|
|
3108
|
+
subjectDid,
|
|
3109
|
+
connectedWalletDid,
|
|
3110
|
+
controllingWalletDid: controllingWalletDid ?? buildEvmDidPkh(chainId, adminAddress)
|
|
3111
|
+
};
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
} catch {
|
|
3115
|
+
}
|
|
3116
|
+
if (ethers.getAddress(subjectAddress) === ethers.getAddress(connectedWalletAddress) && controllingWalletDid) {
|
|
3117
|
+
return {
|
|
3118
|
+
valid: true,
|
|
3119
|
+
method: "minting-wallet",
|
|
3120
|
+
details: `Verified because connected wallet matches the contract DID address. Controlling wallet is ${controllingWalletDid}.`,
|
|
3121
|
+
subjectDid,
|
|
3122
|
+
connectedWalletDid,
|
|
3123
|
+
controllingWalletDid
|
|
3124
|
+
};
|
|
3125
|
+
}
|
|
3126
|
+
return {
|
|
3127
|
+
valid: false,
|
|
3128
|
+
reason: "Contract ownership verification failed",
|
|
3129
|
+
details: controllingWalletDid ? `Connected wallet ${connectedWalletDid} does not match the controlling wallet ${controllingWalletDid} or the subject contract address ${subjectDid}.` : `Could not match connected wallet ${connectedWalletDid} to contract ownership for ${subjectDid}.`,
|
|
3130
|
+
subjectDid,
|
|
3131
|
+
connectedWalletDid,
|
|
3132
|
+
controllingWalletDid: controllingWalletDid ?? void 0
|
|
3133
|
+
};
|
|
3134
|
+
}
|
|
3135
|
+
async function verifySubjectOwnership(params) {
|
|
3136
|
+
const subjectDid = normalizeSubjectDid(params.subjectDid);
|
|
3137
|
+
const method = extractDidMethod(subjectDid);
|
|
3138
|
+
if (method === "web") {
|
|
3139
|
+
return verifyDidWebOwnership(params);
|
|
3140
|
+
}
|
|
3141
|
+
if (method === "pkh") {
|
|
3142
|
+
const didPkhParams = params;
|
|
3143
|
+
if (!didPkhParams.provider) {
|
|
3144
|
+
throw new OmaTrustError(
|
|
3145
|
+
"INVALID_INPUT",
|
|
3146
|
+
"provider is required for did:pkh ownership verification",
|
|
3147
|
+
{ subjectDid }
|
|
3148
|
+
);
|
|
3149
|
+
}
|
|
3150
|
+
return verifyDidPkhOwnership(didPkhParams);
|
|
3151
|
+
}
|
|
3152
|
+
throw new OmaTrustError("INVALID_INPUT", "Unsupported DID type for ownership verification", {
|
|
3153
|
+
subjectDid
|
|
3154
|
+
});
|
|
3155
|
+
}
|
|
1418
3156
|
|
|
3157
|
+
exports.EIP1967_ADMIN_SLOT = EIP1967_ADMIN_SLOT;
|
|
3158
|
+
exports.OWNERSHIP_PATTERNS = OWNERSHIP_PATTERNS;
|
|
1419
3159
|
exports.buildDelegatedAttestationTypedData = buildDelegatedAttestationTypedData;
|
|
3160
|
+
exports.buildDelegatedTypedDataFromEncoded = buildDelegatedTypedDataFromEncoded;
|
|
1420
3161
|
exports.buildDnsTxtRecord = buildDnsTxtRecord;
|
|
1421
3162
|
exports.buildEip712Domain = buildEip712Domain;
|
|
1422
3163
|
exports.calculateAverageUserReviewRating = calculateAverageUserReviewRating;
|
|
@@ -1433,15 +3174,20 @@ exports.createX402OfferProof = createX402OfferProof;
|
|
|
1433
3174
|
exports.createX402ReceiptProof = createX402ReceiptProof;
|
|
1434
3175
|
exports.decodeAttestationData = decodeAttestationData;
|
|
1435
3176
|
exports.deduplicateReviews = deduplicateReviews;
|
|
3177
|
+
exports.discoverContractOwner = discoverContractOwner;
|
|
3178
|
+
exports.discoverControllingWalletDid = discoverControllingWalletDid;
|
|
1436
3179
|
exports.encodeAttestationData = encodeAttestationData;
|
|
1437
|
-
exports.
|
|
3180
|
+
exports.extractEvmAddressesFromDidDocument = extractEvmAddressesFromDidDocument;
|
|
1438
3181
|
exports.extractExpirationTime = extractExpirationTime;
|
|
3182
|
+
exports.extractJwksFromDidDocument = extractJwksFromDidDocument;
|
|
1439
3183
|
exports.fetchDidDocument = fetchDidDocument;
|
|
1440
3184
|
exports.formatSchemaUid = formatSchemaUid;
|
|
1441
3185
|
exports.formatTransferAmount = formatTransferAmount;
|
|
1442
3186
|
exports.getAttestation = getAttestation;
|
|
3187
|
+
exports.getAttestationsByAttester = getAttestationsByAttester;
|
|
1443
3188
|
exports.getAttestationsForDid = getAttestationsForDid;
|
|
1444
3189
|
exports.getChainConstants = getChainConstants;
|
|
3190
|
+
exports.getControllerAuthorization = getControllerAuthorization;
|
|
1445
3191
|
exports.getExplorerAddressUrl = getExplorerAddressUrl;
|
|
1446
3192
|
exports.getExplorerTxUrl = getExplorerTxUrl;
|
|
1447
3193
|
exports.getLatestAttestations = getLatestAttestations;
|
|
@@ -1455,15 +3201,30 @@ exports.listAttestations = listAttestations;
|
|
|
1455
3201
|
exports.normalizeSchema = normalizeSchema;
|
|
1456
3202
|
exports.parseDnsTxtRecord = parseDnsTxtRecord;
|
|
1457
3203
|
exports.prepareDelegatedAttestation = prepareDelegatedAttestation;
|
|
3204
|
+
exports.readOwnerFromContract = readOwnerFromContract;
|
|
3205
|
+
exports.requestControllerWitness = requestControllerWitness;
|
|
3206
|
+
exports.revokeAttestation = revokeAttestation;
|
|
1458
3207
|
exports.schemaToString = schemaToString;
|
|
1459
3208
|
exports.splitSignature = splitSignature;
|
|
1460
3209
|
exports.submitAttestation = submitAttestation;
|
|
1461
3210
|
exports.submitDelegatedAttestation = submitDelegatedAttestation;
|
|
3211
|
+
exports.validateAttestationData = validateAttestationData;
|
|
1462
3212
|
exports.verifyAttestation = verifyAttestation;
|
|
1463
3213
|
exports.verifyDidDocumentControllerDid = verifyDidDocumentControllerDid;
|
|
1464
|
-
exports.
|
|
3214
|
+
exports.verifyDidJsonControllerDid = verifyDidJsonControllerDid;
|
|
3215
|
+
exports.verifyDidPkhOwnership = verifyDidPkhOwnership;
|
|
3216
|
+
exports.verifyDidWebOwnership = verifyDidWebOwnership;
|
|
3217
|
+
exports.verifyDnsTxtControllerDid = verifyDnsTxtControllerDid2;
|
|
1465
3218
|
exports.verifyEip712Signature = verifyEip712Signature;
|
|
1466
3219
|
exports.verifyProof = verifyProof;
|
|
1467
3220
|
exports.verifySchemaExists = verifySchemaExists;
|
|
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;
|
|
1468
3229
|
//# sourceMappingURL=index.cjs.map
|
|
1469
3230
|
//# sourceMappingURL=index.cjs.map
|