@orangecheck/sdk 1.14.1 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/attestation.js +228 -64
- package/dist/attestation.js.map +1 -1
- package/dist/attestation.mjs +228 -64
- package/dist/attestation.mjs.map +1 -1
- package/dist/challenge.d.mts +1 -0
- package/dist/challenge.d.ts +1 -0
- package/dist/challenge.js +20 -14
- package/dist/challenge.js.map +1 -1
- package/dist/challenge.mjs +20 -14
- package/dist/challenge.mjs.map +1 -1
- package/dist/identity.js +19 -17
- package/dist/identity.js.map +1 -1
- package/dist/identity.mjs +19 -17
- package/dist/identity.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +555 -585
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +554 -586
- package/dist/index.mjs.map +1 -1
- package/dist/nostr.d.mts +3 -1
- package/dist/nostr.d.ts +3 -1
- package/dist/nostr.js +213 -38
- package/dist/nostr.js.map +1 -1
- package/dist/nostr.mjs +212 -39
- package/dist/nostr.mjs.map +1 -1
- package/dist/verify.d.mts +11 -2
- package/dist/verify.d.ts +11 -2
- package/dist/verify.js +39 -100
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +39 -101
- package/dist/verify.mjs.map +1 -1
- package/package.json +2 -2
package/dist/attestation.mjs
CHANGED
|
@@ -1,9 +1,50 @@
|
|
|
1
1
|
import { bech32 } from '@scure/base';
|
|
2
|
+
import { verifySignature as verifySignature$1 } from '@orangecheck/bip322';
|
|
3
|
+
import { Buffer } from 'buffer';
|
|
2
4
|
|
|
3
5
|
// src/canonical.ts
|
|
4
6
|
function toLowerHex(bytes) {
|
|
5
7
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
6
8
|
}
|
|
9
|
+
var IDENTITY_PROTOCOL_RE = /^[a-z][a-z0-9_-]*$/;
|
|
10
|
+
var IDENTITY_FORBIDDEN_CHARS = /[\r\n,]/;
|
|
11
|
+
function assertSafeIdentity({ protocol, identifier }) {
|
|
12
|
+
if (!IDENTITY_PROTOCOL_RE.test(protocol)) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`invalid identity protocol: ${JSON.stringify(protocol)} \u2014 must match /^[a-z][a-z0-9_-]*$/`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
if (!identifier || IDENTITY_FORBIDDEN_CHARS.test(identifier)) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`invalid identity identifier: ${JSON.stringify(identifier)} \u2014 must be non-empty and contain no newlines or commas`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function parseIdentities(identitiesStr) {
|
|
24
|
+
if (!identitiesStr || identitiesStr.trim() === "") {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
if (IDENTITY_FORBIDDEN_CHARS.test(identitiesStr.replace(/,/g, ""))) {
|
|
28
|
+
throw new Error("identities field contains forbidden characters (newline)");
|
|
29
|
+
}
|
|
30
|
+
const bindings = identitiesStr.split(",").map((binding) => {
|
|
31
|
+
const colonIndex = binding.indexOf(":");
|
|
32
|
+
if (colonIndex === -1) {
|
|
33
|
+
throw new Error(`Invalid identity binding format: ${binding}`);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
protocol: binding.substring(0, colonIndex).trim(),
|
|
37
|
+
identifier: binding.substring(colonIndex + 1).trim()
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
for (const b of bindings) {
|
|
41
|
+
if (!b.protocol || !b.identifier) {
|
|
42
|
+
throw new Error(`Invalid identity binding: ${b.protocol}:${b.identifier}`);
|
|
43
|
+
}
|
|
44
|
+
assertSafeIdentity(b);
|
|
45
|
+
}
|
|
46
|
+
return bindings;
|
|
47
|
+
}
|
|
7
48
|
async function generateAttestationId(message) {
|
|
8
49
|
const encoder = new TextEncoder();
|
|
9
50
|
const data = encoder.encode(message);
|
|
@@ -107,8 +148,94 @@ function createLogger(namespace) {
|
|
|
107
148
|
};
|
|
108
149
|
}
|
|
109
150
|
|
|
151
|
+
// src/utils/time.ts
|
|
152
|
+
var RFC3339 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
153
|
+
function rfc3339Ms(value) {
|
|
154
|
+
if (!value || !RFC3339.test(value)) return null;
|
|
155
|
+
const ms = Date.parse(value);
|
|
156
|
+
return Number.isNaN(ms) ? null : ms;
|
|
157
|
+
}
|
|
158
|
+
function toBase64Maybe(sig) {
|
|
159
|
+
if (/^[0-9a-fA-F]+$/.test(sig) && sig.length % 2 === 0) {
|
|
160
|
+
return Buffer.from(sig, "hex").toString("base64");
|
|
161
|
+
}
|
|
162
|
+
return sig;
|
|
163
|
+
}
|
|
164
|
+
function signatureScheme(sigBase64) {
|
|
165
|
+
if (/^(ful|pof)/.test(sigBase64)) return null;
|
|
166
|
+
if (sigBase64.startsWith("smp")) return "bip322";
|
|
167
|
+
return Buffer.from(sigBase64, "base64").length === 65 ? "legacy" : "bip322";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/verify.ts
|
|
171
|
+
var log = createLogger("ocp/verify");
|
|
172
|
+
async function verifySignature({ addr, msg, sig }) {
|
|
173
|
+
const sigBase64 = toBase64Maybe(sig);
|
|
174
|
+
const scheme = signatureScheme(sigBase64);
|
|
175
|
+
if (!scheme) return "sig_unsupported_script";
|
|
176
|
+
const ok = verifySignature$1(addr, msg, sigBase64);
|
|
177
|
+
log.info({ addr, ok, scheme, msgLength: msg.length }, "Signature verification result");
|
|
178
|
+
if (!ok) return "sig_invalid";
|
|
179
|
+
return scheme === "legacy" ? "sig_ok_legacy" : "sig_ok_bip322";
|
|
180
|
+
}
|
|
181
|
+
async function signatureIsValid(input) {
|
|
182
|
+
try {
|
|
183
|
+
const code = await verifySignature(input);
|
|
184
|
+
return code === "sig_ok_bip322" || code === "sig_ok_legacy";
|
|
185
|
+
} catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function parseCanonicalMessage(msg) {
|
|
190
|
+
const lines = msg.split("\n").map((l) => l.trim());
|
|
191
|
+
const getVal = (prefix) => {
|
|
192
|
+
const needle = prefix + ":";
|
|
193
|
+
const hit = lines.find((l) => l.toLowerCase().startsWith(needle));
|
|
194
|
+
if (!hit) return void 0;
|
|
195
|
+
const idx = hit.indexOf(":");
|
|
196
|
+
return hit.slice(idx + 1).trim();
|
|
197
|
+
};
|
|
198
|
+
const address = getVal("address") || "";
|
|
199
|
+
const identitiesStr = getVal("identities") || "";
|
|
200
|
+
const issued_at = getVal("issued_at") || "";
|
|
201
|
+
const expires = getVal("expires");
|
|
202
|
+
let identities = [];
|
|
203
|
+
try {
|
|
204
|
+
identities = parseIdentities(identitiesStr);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
log.warn({ identitiesStr, error: err }, "Failed to parse identities field");
|
|
207
|
+
}
|
|
208
|
+
const coreKeys = /* @__PURE__ */ new Set([
|
|
209
|
+
"orangecheck",
|
|
210
|
+
"identities",
|
|
211
|
+
"address",
|
|
212
|
+
"purpose",
|
|
213
|
+
"nonce",
|
|
214
|
+
"issued_at",
|
|
215
|
+
"ack"
|
|
216
|
+
]);
|
|
217
|
+
const extensions = {};
|
|
218
|
+
for (const line of lines) {
|
|
219
|
+
const idx = line.indexOf(":");
|
|
220
|
+
if (idx === -1) continue;
|
|
221
|
+
const key = line.slice(0, idx).trim().toLowerCase();
|
|
222
|
+
const val = line.slice(idx + 1).trim();
|
|
223
|
+
if (!coreKeys.has(key) && val) extensions[key] = val;
|
|
224
|
+
}
|
|
225
|
+
const network = extensions["network"] || "mainnet";
|
|
226
|
+
const bondStr = extensions["bond"];
|
|
227
|
+
const bond = bondStr ? parseInt(bondStr, 10) : void 0;
|
|
228
|
+
return {
|
|
229
|
+
core: { address, identities, issued_at },
|
|
230
|
+
expires,
|
|
231
|
+
network,
|
|
232
|
+
extensions,
|
|
233
|
+
bond: bond !== void 0 && !isNaN(bond) && bond > 0 ? bond : void 0
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
110
237
|
// src/nostr.ts
|
|
111
|
-
var
|
|
238
|
+
var log2 = createLogger("ocp/nostr");
|
|
112
239
|
var ATTESTATION_EVENT_KIND = 30078;
|
|
113
240
|
var DEFAULT_RELAYS = [
|
|
114
241
|
"wss://relay.damus.io",
|
|
@@ -161,7 +288,7 @@ function createAttestationEvent(envelope, pubkey) {
|
|
|
161
288
|
async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
162
289
|
const success = [];
|
|
163
290
|
const failed = [];
|
|
164
|
-
|
|
291
|
+
log2.info({ eventId: event.id, relayCount: relays.length }, "Publishing event to relays");
|
|
165
292
|
const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
|
|
166
293
|
const publishPromises = relays.map(async (relayUrl) => {
|
|
167
294
|
let ws = null;
|
|
@@ -200,7 +327,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
|
200
327
|
cleanup();
|
|
201
328
|
resolve();
|
|
202
329
|
} else {
|
|
203
|
-
|
|
330
|
+
log2.warn(
|
|
204
331
|
{ relay: relayUrl, reason: data[3] },
|
|
205
332
|
"Relay rejected event"
|
|
206
333
|
);
|
|
@@ -210,7 +337,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
|
210
337
|
}
|
|
211
338
|
}
|
|
212
339
|
} catch (err) {
|
|
213
|
-
|
|
340
|
+
log2.error(
|
|
214
341
|
{ relay: relayUrl, error: err },
|
|
215
342
|
"Failed to parse relay response"
|
|
216
343
|
);
|
|
@@ -220,7 +347,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
|
220
347
|
}
|
|
221
348
|
};
|
|
222
349
|
wsRef.onerror = (err) => {
|
|
223
|
-
|
|
350
|
+
log2.error({ relay: relayUrl, error: err }, "WebSocket error");
|
|
224
351
|
recordFailure();
|
|
225
352
|
cleanup();
|
|
226
353
|
reject(err);
|
|
@@ -230,7 +357,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
|
230
357
|
};
|
|
231
358
|
});
|
|
232
359
|
} catch (err) {
|
|
233
|
-
|
|
360
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to publish to relay");
|
|
234
361
|
recordFailure();
|
|
235
362
|
} finally {
|
|
236
363
|
if (ws && ws.readyState !== WebSocket.CLOSED) {
|
|
@@ -239,12 +366,12 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
|
239
366
|
}
|
|
240
367
|
});
|
|
241
368
|
await Promise.allSettled(publishPromises);
|
|
242
|
-
|
|
369
|
+
log2.info({ successCount: success.length, failedCount: failed.length }, "Publishing complete");
|
|
243
370
|
return { success, failed };
|
|
244
371
|
}
|
|
245
372
|
async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
|
|
246
373
|
const events = [];
|
|
247
|
-
|
|
374
|
+
log2.info({ attestationId, relayCount: relays.length }, "Querying relays for attestation");
|
|
248
375
|
const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
|
|
249
376
|
const queryPromises = relays.map(async (relayUrl) => {
|
|
250
377
|
let ws = null;
|
|
@@ -284,11 +411,11 @@ async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
|
|
|
284
411
|
resolve();
|
|
285
412
|
}
|
|
286
413
|
} catch (err) {
|
|
287
|
-
|
|
414
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
|
|
288
415
|
}
|
|
289
416
|
};
|
|
290
417
|
wsRef.onerror = (err) => {
|
|
291
|
-
|
|
418
|
+
log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
|
|
292
419
|
cleanup();
|
|
293
420
|
reject(err);
|
|
294
421
|
};
|
|
@@ -297,7 +424,7 @@ async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
|
|
|
297
424
|
};
|
|
298
425
|
});
|
|
299
426
|
} catch (err) {
|
|
300
|
-
|
|
427
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
301
428
|
} finally {
|
|
302
429
|
if (ws && ws.readyState !== WebSocket.CLOSED) {
|
|
303
430
|
ws.close();
|
|
@@ -305,12 +432,12 @@ async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
|
|
|
305
432
|
}
|
|
306
433
|
});
|
|
307
434
|
await Promise.allSettled(queryPromises);
|
|
308
|
-
|
|
435
|
+
log2.info({ eventCount: events.length }, "Query complete");
|
|
309
436
|
return events;
|
|
310
437
|
}
|
|
311
438
|
async function queryByAddress(address, relays = DEFAULT_RELAYS) {
|
|
312
439
|
const events = [];
|
|
313
|
-
|
|
440
|
+
log2.info({ address, relayCount: relays.length }, "Querying relays for address");
|
|
314
441
|
const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
|
|
315
442
|
const queryPromises = relays.map(async (relayUrl) => {
|
|
316
443
|
try {
|
|
@@ -352,28 +479,28 @@ async function queryByAddress(address, relays = DEFAULT_RELAYS) {
|
|
|
352
479
|
resolve();
|
|
353
480
|
}
|
|
354
481
|
} catch (err) {
|
|
355
|
-
|
|
482
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
|
|
356
483
|
}
|
|
357
484
|
};
|
|
358
485
|
ws.onerror = (err) => {
|
|
359
486
|
clearTimeout(timeout);
|
|
360
|
-
|
|
487
|
+
log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
|
|
361
488
|
reject(err);
|
|
362
489
|
};
|
|
363
490
|
});
|
|
364
491
|
} catch (err) {
|
|
365
|
-
|
|
492
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
366
493
|
}
|
|
367
494
|
});
|
|
368
495
|
await Promise.allSettled(queryPromises);
|
|
369
|
-
|
|
496
|
+
log2.info({ eventCount: events.length }, "Query complete");
|
|
370
497
|
return events;
|
|
371
498
|
}
|
|
372
499
|
async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
|
|
373
500
|
const events = [];
|
|
374
501
|
const identityTag = `${protocol}:${identifier}`;
|
|
375
502
|
const identityTags = protocol === "nostr" ? nostrIdentifierForms(identifier).map((f) => `nostr:${f}`) : [identityTag];
|
|
376
|
-
|
|
503
|
+
log2.info({ protocol, identifier, relayCount: relays.length }, "Querying relays for identity");
|
|
377
504
|
const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
|
|
378
505
|
const queryPromises = relays.map(async (relayUrl) => {
|
|
379
506
|
try {
|
|
@@ -408,21 +535,21 @@ async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
|
|
|
408
535
|
resolve();
|
|
409
536
|
}
|
|
410
537
|
} catch (err) {
|
|
411
|
-
|
|
538
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
|
|
412
539
|
}
|
|
413
540
|
};
|
|
414
541
|
ws.onerror = (err) => {
|
|
415
542
|
clearTimeout(timeout);
|
|
416
|
-
|
|
543
|
+
log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
|
|
417
544
|
reject(err);
|
|
418
545
|
};
|
|
419
546
|
});
|
|
420
547
|
} catch (err) {
|
|
421
|
-
|
|
548
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
422
549
|
}
|
|
423
550
|
});
|
|
424
551
|
await Promise.allSettled(queryPromises);
|
|
425
|
-
|
|
552
|
+
log2.info({ eventCount: events.length }, "Query complete");
|
|
426
553
|
return events;
|
|
427
554
|
}
|
|
428
555
|
async function verifyAttestationEnvelope(envelope) {
|
|
@@ -434,22 +561,74 @@ async function verifyAttestationEnvelope(envelope) {
|
|
|
434
561
|
const computedId = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
435
562
|
return computedId === envelope.attestation_id;
|
|
436
563
|
} catch (err) {
|
|
437
|
-
|
|
564
|
+
log2.error({ error: err }, "Failed to verify attestation envelope");
|
|
438
565
|
return false;
|
|
439
566
|
}
|
|
440
567
|
}
|
|
568
|
+
function base64Url(text) {
|
|
569
|
+
const base64 = btoa(String.fromCharCode(...new TextEncoder().encode(text)));
|
|
570
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
571
|
+
}
|
|
572
|
+
function signedEnvelope(envelope) {
|
|
573
|
+
let parsed;
|
|
574
|
+
try {
|
|
575
|
+
parsed = parseCanonicalMessage(envelope.message);
|
|
576
|
+
} catch {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
if (!parsed.core.address || rfc3339Ms(parsed.core.issued_at) === null) return null;
|
|
580
|
+
const { expires_at: _copy, ...rest } = envelope;
|
|
581
|
+
return {
|
|
582
|
+
...rest,
|
|
583
|
+
address: parsed.core.address,
|
|
584
|
+
identities: parsed.core.identities,
|
|
585
|
+
issued_at: parsed.core.issued_at,
|
|
586
|
+
...parsed.expires !== void 0 ? { expires_at: parsed.expires } : {},
|
|
587
|
+
message_b64url: base64Url(envelope.message)
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
async function collectSignedEnvelopes(events, accept = () => true) {
|
|
591
|
+
const copies = /* @__PURE__ */ new Map();
|
|
592
|
+
for (const event of events) {
|
|
593
|
+
const raw = parseAttestationFromEvent(event);
|
|
594
|
+
if (!raw || !await verifyAttestationEnvelope(raw)) continue;
|
|
595
|
+
const envelope = signedEnvelope(raw);
|
|
596
|
+
if (!envelope || !accept(event, envelope)) continue;
|
|
597
|
+
const same = copies.get(envelope.attestation_id) ?? [];
|
|
598
|
+
if (!same.some((e) => e.signature === envelope.signature && e.scheme === envelope.scheme)) {
|
|
599
|
+
same.push(envelope);
|
|
600
|
+
}
|
|
601
|
+
copies.set(envelope.attestation_id, same);
|
|
602
|
+
}
|
|
603
|
+
const kept = [];
|
|
604
|
+
for (const same of copies.values()) {
|
|
605
|
+
if (same.length === 1) {
|
|
606
|
+
kept.push(same[0]);
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
let valid;
|
|
610
|
+
for (const e of same) {
|
|
611
|
+
if (await signatureIsValid({ addr: e.address, msg: e.message, sig: e.signature, scheme: e.scheme })) {
|
|
612
|
+
valid = e;
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
kept.push(valid ?? same[0]);
|
|
617
|
+
}
|
|
618
|
+
return kept;
|
|
619
|
+
}
|
|
441
620
|
function parseAttestationFromEvent(event) {
|
|
442
621
|
try {
|
|
443
622
|
const envelope = JSON.parse(event.content);
|
|
444
623
|
return envelope;
|
|
445
624
|
} catch (err) {
|
|
446
|
-
|
|
625
|
+
log2.error({ eventId: event.id, error: err }, "Failed to parse attestation from event");
|
|
447
626
|
return null;
|
|
448
627
|
}
|
|
449
628
|
}
|
|
450
629
|
|
|
451
630
|
// src/nostr-crypto.ts
|
|
452
|
-
var
|
|
631
|
+
var log3 = createLogger("ocp/nostr-crypto");
|
|
453
632
|
function isNip07Available() {
|
|
454
633
|
if (typeof window === "undefined") {
|
|
455
634
|
return false;
|
|
@@ -458,22 +637,22 @@ function isNip07Available() {
|
|
|
458
637
|
}
|
|
459
638
|
async function signNostrEvent(event) {
|
|
460
639
|
if (!isNip07Available()) {
|
|
461
|
-
|
|
640
|
+
log3.warn("NIP-07 extension not available");
|
|
462
641
|
return null;
|
|
463
642
|
}
|
|
464
643
|
try {
|
|
465
644
|
const nostr = window.nostr;
|
|
466
645
|
const signedEvent = await nostr.signEvent(event);
|
|
467
|
-
|
|
646
|
+
log3.info({ eventId: signedEvent.id }, "Signed Nostr event with NIP-07");
|
|
468
647
|
return signedEvent;
|
|
469
648
|
} catch (err) {
|
|
470
|
-
|
|
649
|
+
log3.error({ error: err }, "Failed to sign Nostr event");
|
|
471
650
|
return null;
|
|
472
651
|
}
|
|
473
652
|
}
|
|
474
653
|
|
|
475
654
|
// src/attestation.ts
|
|
476
|
-
var
|
|
655
|
+
var log4 = createLogger("ocp/attestation");
|
|
477
656
|
var AttestationError = class extends Error {
|
|
478
657
|
constructor(message, code) {
|
|
479
658
|
super(message);
|
|
@@ -483,7 +662,7 @@ var AttestationError = class extends Error {
|
|
|
483
662
|
};
|
|
484
663
|
async function createAttestation(options) {
|
|
485
664
|
const { message, signature, scheme, address, identities = [], extensions = {} } = options;
|
|
486
|
-
|
|
665
|
+
log4.info({ address, identityCount: identities.length }, "Creating attestation");
|
|
487
666
|
const envelope = await createAttestationEnvelope(
|
|
488
667
|
message,
|
|
489
668
|
signature,
|
|
@@ -499,29 +678,29 @@ async function createAttestation(options) {
|
|
|
499
678
|
"INTEGRITY_CHECK_FAILED"
|
|
500
679
|
);
|
|
501
680
|
}
|
|
502
|
-
|
|
681
|
+
log4.info({ attestationId: envelope.attestation_id }, "Attestation created successfully");
|
|
503
682
|
return envelope;
|
|
504
683
|
}
|
|
505
684
|
async function publishAttestation(options) {
|
|
506
685
|
const { envelope, npub, relays = DEFAULT_RELAYS } = options;
|
|
507
|
-
|
|
686
|
+
log4.info(
|
|
508
687
|
{ attestationId: envelope.attestation_id, relayCount: relays.length },
|
|
509
688
|
"Publishing attestation"
|
|
510
689
|
);
|
|
511
690
|
const unsignedEvent = createAttestationEvent(envelope, npub);
|
|
512
691
|
if (!isNip07Available()) {
|
|
513
|
-
|
|
692
|
+
log4.warn("NIP-07 unavailable \u2014 skipping Nostr publish");
|
|
514
693
|
throw new Error(
|
|
515
694
|
"nostr_signer_unavailable: install a NIP-07 extension (e.g. nos2x, Alby) to publish"
|
|
516
695
|
);
|
|
517
696
|
}
|
|
518
|
-
|
|
697
|
+
log4.info("Using NIP-07 to sign event");
|
|
519
698
|
const signedEvent = await signNostrEvent(unsignedEvent);
|
|
520
699
|
if (!signedEvent) {
|
|
521
700
|
throw new Error("nostr_signing_failed: NIP-07 extension rejected or errored");
|
|
522
701
|
}
|
|
523
702
|
const result = await publishToRelays(signedEvent, relays);
|
|
524
|
-
|
|
703
|
+
log4.info(
|
|
525
704
|
{
|
|
526
705
|
attestationId: envelope.attestation_id,
|
|
527
706
|
successCount: result.success.length,
|
|
@@ -541,49 +720,34 @@ async function discoverAttestations(options) {
|
|
|
541
720
|
const { attestationId, address, identity, relays = DEFAULT_RELAYS } = options;
|
|
542
721
|
let events = [];
|
|
543
722
|
if (attestationId) {
|
|
544
|
-
|
|
723
|
+
log4.info({ attestationId }, "Discovering by attestation ID");
|
|
545
724
|
events = await queryByAttestationId(attestationId, relays);
|
|
546
725
|
} else if (address) {
|
|
547
|
-
|
|
726
|
+
log4.info({ address }, "Discovering by Bitcoin address");
|
|
548
727
|
events = await queryByAddress(address, relays);
|
|
549
728
|
} else if (identity) {
|
|
550
|
-
|
|
729
|
+
log4.info({ identity }, "Discovering by identity");
|
|
551
730
|
events = await queryByIdentity(identity.protocol, identity.identifier, relays);
|
|
552
731
|
} else {
|
|
553
732
|
throw new Error("Must provide attestationId, address, or identity");
|
|
554
733
|
}
|
|
555
|
-
const envelopes =
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
log3.warn({ eventId: event.id }, "Skipping event whose index tags disagree with its envelope");
|
|
562
|
-
} else if (envelope) {
|
|
563
|
-
const isValid = await verifyAttestationEnvelope(envelope);
|
|
564
|
-
if (isValid) {
|
|
565
|
-
kept.add(envelope.attestation_id);
|
|
566
|
-
envelopes.push(envelope);
|
|
567
|
-
} else {
|
|
568
|
-
log3.warn(
|
|
569
|
-
{ attestationId: envelope.attestation_id },
|
|
570
|
-
"Skipping invalid attestation envelope"
|
|
571
|
-
);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
log3.info({ eventCount: events.length, validCount: envelopes.length }, "Discovery complete");
|
|
734
|
+
const envelopes = await collectSignedEnvelopes(events, (event, envelope) => {
|
|
735
|
+
if (eventIndexesEnvelope(event, envelope)) return true;
|
|
736
|
+
log4.warn({ eventId: event.id }, "Skipping event whose index tags disagree with its envelope");
|
|
737
|
+
return false;
|
|
738
|
+
});
|
|
739
|
+
log4.info({ eventCount: events.length, validCount: envelopes.length }, "Discovery complete");
|
|
576
740
|
return envelopes;
|
|
577
741
|
}
|
|
578
742
|
async function verifyAttestationById(attestationId, relays = DEFAULT_RELAYS) {
|
|
579
|
-
|
|
743
|
+
log4.info({ attestationId }, "Verifying attestation by ID");
|
|
580
744
|
const envelopes = await discoverAttestations({ attestationId, relays });
|
|
581
745
|
if (envelopes.length === 0) {
|
|
582
|
-
|
|
746
|
+
log4.warn({ attestationId }, "Attestation not found on any relay");
|
|
583
747
|
return null;
|
|
584
748
|
}
|
|
585
749
|
if (envelopes.length > 1) {
|
|
586
|
-
|
|
750
|
+
log4.warn(
|
|
587
751
|
{ attestationId, count: envelopes.length },
|
|
588
752
|
"Multiple attestations found with same ID - using first"
|
|
589
753
|
);
|
|
@@ -591,11 +755,11 @@ async function verifyAttestationById(attestationId, relays = DEFAULT_RELAYS) {
|
|
|
591
755
|
return envelopes[0] || null;
|
|
592
756
|
}
|
|
593
757
|
async function getAttestationsForAddress(address, relays = DEFAULT_RELAYS) {
|
|
594
|
-
|
|
758
|
+
log4.info({ address }, "Getting all attestations for address");
|
|
595
759
|
return discoverAttestations({ address, relays });
|
|
596
760
|
}
|
|
597
761
|
async function getAttestationsForIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
|
|
598
|
-
|
|
762
|
+
log4.info({ protocol, identifier }, "Getting all attestations for identity");
|
|
599
763
|
return discoverAttestations({ identity: { protocol, identifier }, relays });
|
|
600
764
|
}
|
|
601
765
|
function formatIdentitiesForDisplay(identities) {
|