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