@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/nostr.d.mts
CHANGED
|
@@ -16,6 +16,8 @@ declare function queryByAddress(address: string, relays?: string[]): Promise<Nos
|
|
|
16
16
|
declare function queryByIdentity(protocol: string, identifier: string, relays?: string[]): Promise<NostrEvent[]>;
|
|
17
17
|
declare function queryRecent(relays?: string[], limit?: number): Promise<AttestationEnvelope[]>;
|
|
18
18
|
declare function verifyAttestationEnvelope(envelope: AttestationEnvelope): Promise<boolean>;
|
|
19
|
+
declare function signedEnvelope(envelope: AttestationEnvelope): AttestationEnvelope | null;
|
|
20
|
+
declare function collectSignedEnvelopes(events: NostrEvent[], accept?: (event: NostrEvent, envelope: AttestationEnvelope) => boolean): Promise<AttestationEnvelope[]>;
|
|
19
21
|
declare function parseAttestationFromEvent(event: NostrEvent): AttestationEnvelope | null;
|
|
20
22
|
|
|
21
|
-
export { ATTESTATION_EVENT_KIND, DEFAULT_RELAYS, FANOUT_DEADLINE_MS, createAttestationEvent, getAddressFromEvent, getAttestationIdFromEvent, parseAttestationFromEvent, parseIdentitiesFromEvent, publishToRelays, queryByAddress, queryByAttestationId, queryByIdentity, queryRecent, verifyAttestationEnvelope };
|
|
23
|
+
export { ATTESTATION_EVENT_KIND, DEFAULT_RELAYS, FANOUT_DEADLINE_MS, collectSignedEnvelopes, createAttestationEvent, getAddressFromEvent, getAttestationIdFromEvent, parseAttestationFromEvent, parseIdentitiesFromEvent, publishToRelays, queryByAddress, queryByAttestationId, queryByIdentity, queryRecent, signedEnvelope, verifyAttestationEnvelope };
|
package/dist/nostr.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ declare function queryByAddress(address: string, relays?: string[]): Promise<Nos
|
|
|
16
16
|
declare function queryByIdentity(protocol: string, identifier: string, relays?: string[]): Promise<NostrEvent[]>;
|
|
17
17
|
declare function queryRecent(relays?: string[], limit?: number): Promise<AttestationEnvelope[]>;
|
|
18
18
|
declare function verifyAttestationEnvelope(envelope: AttestationEnvelope): Promise<boolean>;
|
|
19
|
+
declare function signedEnvelope(envelope: AttestationEnvelope): AttestationEnvelope | null;
|
|
20
|
+
declare function collectSignedEnvelopes(events: NostrEvent[], accept?: (event: NostrEvent, envelope: AttestationEnvelope) => boolean): Promise<AttestationEnvelope[]>;
|
|
19
21
|
declare function parseAttestationFromEvent(event: NostrEvent): AttestationEnvelope | null;
|
|
20
22
|
|
|
21
|
-
export { ATTESTATION_EVENT_KIND, DEFAULT_RELAYS, FANOUT_DEADLINE_MS, createAttestationEvent, getAddressFromEvent, getAttestationIdFromEvent, parseAttestationFromEvent, parseIdentitiesFromEvent, publishToRelays, queryByAddress, queryByAttestationId, queryByIdentity, queryRecent, verifyAttestationEnvelope };
|
|
23
|
+
export { ATTESTATION_EVENT_KIND, DEFAULT_RELAYS, FANOUT_DEADLINE_MS, collectSignedEnvelopes, createAttestationEvent, getAddressFromEvent, getAttestationIdFromEvent, parseAttestationFromEvent, parseIdentitiesFromEvent, publishToRelays, queryByAddress, queryByAttestationId, queryByIdentity, queryRecent, signedEnvelope, verifyAttestationEnvelope };
|
package/dist/nostr.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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/nostr-pubkey.ts
|
|
6
8
|
var HEX_64_RE = /^[0-9a-f]{64}$/;
|
|
@@ -73,8 +75,135 @@ function createLogger(namespace) {
|
|
|
73
75
|
};
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
// src/utils/time.ts
|
|
79
|
+
var RFC3339 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
80
|
+
function rfc3339Ms(value) {
|
|
81
|
+
if (!value || !RFC3339.test(value)) return null;
|
|
82
|
+
const ms = Date.parse(value);
|
|
83
|
+
return Number.isNaN(ms) ? null : ms;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/canonical.ts
|
|
87
|
+
var IDENTITY_PROTOCOL_RE = /^[a-z][a-z0-9_-]*$/;
|
|
88
|
+
var IDENTITY_FORBIDDEN_CHARS = /[\r\n,]/;
|
|
89
|
+
function assertSafeIdentity({ protocol, identifier }) {
|
|
90
|
+
if (!IDENTITY_PROTOCOL_RE.test(protocol)) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`invalid identity protocol: ${JSON.stringify(protocol)} \u2014 must match /^[a-z][a-z0-9_-]*$/`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (!identifier || IDENTITY_FORBIDDEN_CHARS.test(identifier)) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`invalid identity identifier: ${JSON.stringify(identifier)} \u2014 must be non-empty and contain no newlines or commas`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function parseIdentities(identitiesStr) {
|
|
102
|
+
if (!identitiesStr || identitiesStr.trim() === "") {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
if (IDENTITY_FORBIDDEN_CHARS.test(identitiesStr.replace(/,/g, ""))) {
|
|
106
|
+
throw new Error("identities field contains forbidden characters (newline)");
|
|
107
|
+
}
|
|
108
|
+
const bindings = identitiesStr.split(",").map((binding) => {
|
|
109
|
+
const colonIndex = binding.indexOf(":");
|
|
110
|
+
if (colonIndex === -1) {
|
|
111
|
+
throw new Error(`Invalid identity binding format: ${binding}`);
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
protocol: binding.substring(0, colonIndex).trim(),
|
|
115
|
+
identifier: binding.substring(colonIndex + 1).trim()
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
for (const b of bindings) {
|
|
119
|
+
if (!b.protocol || !b.identifier) {
|
|
120
|
+
throw new Error(`Invalid identity binding: ${b.protocol}:${b.identifier}`);
|
|
121
|
+
}
|
|
122
|
+
assertSafeIdentity(b);
|
|
123
|
+
}
|
|
124
|
+
return bindings;
|
|
125
|
+
}
|
|
126
|
+
function toBase64Maybe(sig) {
|
|
127
|
+
if (/^[0-9a-fA-F]+$/.test(sig) && sig.length % 2 === 0) {
|
|
128
|
+
return buffer.Buffer.from(sig, "hex").toString("base64");
|
|
129
|
+
}
|
|
130
|
+
return sig;
|
|
131
|
+
}
|
|
132
|
+
function signatureScheme(sigBase64) {
|
|
133
|
+
if (/^(ful|pof)/.test(sigBase64)) return null;
|
|
134
|
+
if (sigBase64.startsWith("smp")) return "bip322";
|
|
135
|
+
return buffer.Buffer.from(sigBase64, "base64").length === 65 ? "legacy" : "bip322";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/verify.ts
|
|
139
|
+
var log = createLogger("ocp/verify");
|
|
140
|
+
async function verifySignature({ addr, msg, sig }) {
|
|
141
|
+
const sigBase64 = toBase64Maybe(sig);
|
|
142
|
+
const scheme = signatureScheme(sigBase64);
|
|
143
|
+
if (!scheme) return "sig_unsupported_script";
|
|
144
|
+
const ok = bip322.verifySignature(addr, msg, sigBase64);
|
|
145
|
+
log.info({ addr, ok, scheme, msgLength: msg.length }, "Signature verification result");
|
|
146
|
+
if (!ok) return "sig_invalid";
|
|
147
|
+
return scheme === "legacy" ? "sig_ok_legacy" : "sig_ok_bip322";
|
|
148
|
+
}
|
|
149
|
+
async function signatureIsValid(input) {
|
|
150
|
+
try {
|
|
151
|
+
const code = await verifySignature(input);
|
|
152
|
+
return code === "sig_ok_bip322" || code === "sig_ok_legacy";
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function parseCanonicalMessage(msg) {
|
|
158
|
+
const lines = msg.split("\n").map((l) => l.trim());
|
|
159
|
+
const getVal = (prefix) => {
|
|
160
|
+
const needle = prefix + ":";
|
|
161
|
+
const hit = lines.find((l) => l.toLowerCase().startsWith(needle));
|
|
162
|
+
if (!hit) return void 0;
|
|
163
|
+
const idx = hit.indexOf(":");
|
|
164
|
+
return hit.slice(idx + 1).trim();
|
|
165
|
+
};
|
|
166
|
+
const address = getVal("address") || "";
|
|
167
|
+
const identitiesStr = getVal("identities") || "";
|
|
168
|
+
const issued_at = getVal("issued_at") || "";
|
|
169
|
+
const expires = getVal("expires");
|
|
170
|
+
let identities = [];
|
|
171
|
+
try {
|
|
172
|
+
identities = parseIdentities(identitiesStr);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
log.warn({ identitiesStr, error: err }, "Failed to parse identities field");
|
|
175
|
+
}
|
|
176
|
+
const coreKeys = /* @__PURE__ */ new Set([
|
|
177
|
+
"orangecheck",
|
|
178
|
+
"identities",
|
|
179
|
+
"address",
|
|
180
|
+
"purpose",
|
|
181
|
+
"nonce",
|
|
182
|
+
"issued_at",
|
|
183
|
+
"ack"
|
|
184
|
+
]);
|
|
185
|
+
const extensions = {};
|
|
186
|
+
for (const line of lines) {
|
|
187
|
+
const idx = line.indexOf(":");
|
|
188
|
+
if (idx === -1) continue;
|
|
189
|
+
const key = line.slice(0, idx).trim().toLowerCase();
|
|
190
|
+
const val = line.slice(idx + 1).trim();
|
|
191
|
+
if (!coreKeys.has(key) && val) extensions[key] = val;
|
|
192
|
+
}
|
|
193
|
+
const network = extensions["network"] || "mainnet";
|
|
194
|
+
const bondStr = extensions["bond"];
|
|
195
|
+
const bond = bondStr ? parseInt(bondStr, 10) : void 0;
|
|
196
|
+
return {
|
|
197
|
+
core: { address, identities, issued_at },
|
|
198
|
+
expires,
|
|
199
|
+
network,
|
|
200
|
+
extensions,
|
|
201
|
+
bond: bond !== void 0 && !isNaN(bond) && bond > 0 ? bond : void 0
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
76
205
|
// src/nostr.ts
|
|
77
|
-
var
|
|
206
|
+
var log2 = createLogger("ocp/nostr");
|
|
78
207
|
var ATTESTATION_EVENT_KIND = 30078;
|
|
79
208
|
var DEFAULT_RELAYS = [
|
|
80
209
|
"wss://relay.damus.io",
|
|
@@ -150,7 +279,7 @@ function getAddressFromEvent(event) {
|
|
|
150
279
|
async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
151
280
|
const success = [];
|
|
152
281
|
const failed = [];
|
|
153
|
-
|
|
282
|
+
log2.info({ eventId: event.id, relayCount: relays.length }, "Publishing event to relays");
|
|
154
283
|
const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
|
|
155
284
|
const publishPromises = relays.map(async (relayUrl) => {
|
|
156
285
|
let ws = null;
|
|
@@ -189,7 +318,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
|
189
318
|
cleanup();
|
|
190
319
|
resolve();
|
|
191
320
|
} else {
|
|
192
|
-
|
|
321
|
+
log2.warn(
|
|
193
322
|
{ relay: relayUrl, reason: data[3] },
|
|
194
323
|
"Relay rejected event"
|
|
195
324
|
);
|
|
@@ -199,7 +328,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
|
199
328
|
}
|
|
200
329
|
}
|
|
201
330
|
} catch (err) {
|
|
202
|
-
|
|
331
|
+
log2.error(
|
|
203
332
|
{ relay: relayUrl, error: err },
|
|
204
333
|
"Failed to parse relay response"
|
|
205
334
|
);
|
|
@@ -209,7 +338,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
|
209
338
|
}
|
|
210
339
|
};
|
|
211
340
|
wsRef.onerror = (err) => {
|
|
212
|
-
|
|
341
|
+
log2.error({ relay: relayUrl, error: err }, "WebSocket error");
|
|
213
342
|
recordFailure();
|
|
214
343
|
cleanup();
|
|
215
344
|
reject(err);
|
|
@@ -219,7 +348,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
|
219
348
|
};
|
|
220
349
|
});
|
|
221
350
|
} catch (err) {
|
|
222
|
-
|
|
351
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to publish to relay");
|
|
223
352
|
recordFailure();
|
|
224
353
|
} finally {
|
|
225
354
|
if (ws && ws.readyState !== WebSocket.CLOSED) {
|
|
@@ -228,12 +357,12 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
|
228
357
|
}
|
|
229
358
|
});
|
|
230
359
|
await Promise.allSettled(publishPromises);
|
|
231
|
-
|
|
360
|
+
log2.info({ successCount: success.length, failedCount: failed.length }, "Publishing complete");
|
|
232
361
|
return { success, failed };
|
|
233
362
|
}
|
|
234
363
|
async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
|
|
235
364
|
const events = [];
|
|
236
|
-
|
|
365
|
+
log2.info({ attestationId, relayCount: relays.length }, "Querying relays for attestation");
|
|
237
366
|
const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
|
|
238
367
|
const queryPromises = relays.map(async (relayUrl) => {
|
|
239
368
|
let ws = null;
|
|
@@ -273,11 +402,11 @@ async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
|
|
|
273
402
|
resolve();
|
|
274
403
|
}
|
|
275
404
|
} catch (err) {
|
|
276
|
-
|
|
405
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
|
|
277
406
|
}
|
|
278
407
|
};
|
|
279
408
|
wsRef.onerror = (err) => {
|
|
280
|
-
|
|
409
|
+
log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
|
|
281
410
|
cleanup();
|
|
282
411
|
reject(err);
|
|
283
412
|
};
|
|
@@ -286,7 +415,7 @@ async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
|
|
|
286
415
|
};
|
|
287
416
|
});
|
|
288
417
|
} catch (err) {
|
|
289
|
-
|
|
418
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
290
419
|
} finally {
|
|
291
420
|
if (ws && ws.readyState !== WebSocket.CLOSED) {
|
|
292
421
|
ws.close();
|
|
@@ -294,12 +423,12 @@ async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
|
|
|
294
423
|
}
|
|
295
424
|
});
|
|
296
425
|
await Promise.allSettled(queryPromises);
|
|
297
|
-
|
|
426
|
+
log2.info({ eventCount: events.length }, "Query complete");
|
|
298
427
|
return events;
|
|
299
428
|
}
|
|
300
429
|
async function queryByAddress(address, relays = DEFAULT_RELAYS) {
|
|
301
430
|
const events = [];
|
|
302
|
-
|
|
431
|
+
log2.info({ address, relayCount: relays.length }, "Querying relays for address");
|
|
303
432
|
const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
|
|
304
433
|
const queryPromises = relays.map(async (relayUrl) => {
|
|
305
434
|
try {
|
|
@@ -341,28 +470,28 @@ async function queryByAddress(address, relays = DEFAULT_RELAYS) {
|
|
|
341
470
|
resolve();
|
|
342
471
|
}
|
|
343
472
|
} catch (err) {
|
|
344
|
-
|
|
473
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
|
|
345
474
|
}
|
|
346
475
|
};
|
|
347
476
|
ws.onerror = (err) => {
|
|
348
477
|
clearTimeout(timeout);
|
|
349
|
-
|
|
478
|
+
log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
|
|
350
479
|
reject(err);
|
|
351
480
|
};
|
|
352
481
|
});
|
|
353
482
|
} catch (err) {
|
|
354
|
-
|
|
483
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
355
484
|
}
|
|
356
485
|
});
|
|
357
486
|
await Promise.allSettled(queryPromises);
|
|
358
|
-
|
|
487
|
+
log2.info({ eventCount: events.length }, "Query complete");
|
|
359
488
|
return events;
|
|
360
489
|
}
|
|
361
490
|
async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
|
|
362
491
|
const events = [];
|
|
363
492
|
const identityTag = `${protocol}:${identifier}`;
|
|
364
493
|
const identityTags = protocol === "nostr" ? nostrIdentifierForms(identifier).map((f) => `nostr:${f}`) : [identityTag];
|
|
365
|
-
|
|
494
|
+
log2.info({ protocol, identifier, relayCount: relays.length }, "Querying relays for identity");
|
|
366
495
|
const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
|
|
367
496
|
const queryPromises = relays.map(async (relayUrl) => {
|
|
368
497
|
try {
|
|
@@ -397,26 +526,26 @@ async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
|
|
|
397
526
|
resolve();
|
|
398
527
|
}
|
|
399
528
|
} catch (err) {
|
|
400
|
-
|
|
529
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
|
|
401
530
|
}
|
|
402
531
|
};
|
|
403
532
|
ws.onerror = (err) => {
|
|
404
533
|
clearTimeout(timeout);
|
|
405
|
-
|
|
534
|
+
log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
|
|
406
535
|
reject(err);
|
|
407
536
|
};
|
|
408
537
|
});
|
|
409
538
|
} catch (err) {
|
|
410
|
-
|
|
539
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
411
540
|
}
|
|
412
541
|
});
|
|
413
542
|
await Promise.allSettled(queryPromises);
|
|
414
|
-
|
|
543
|
+
log2.info({ eventCount: events.length }, "Query complete");
|
|
415
544
|
return events;
|
|
416
545
|
}
|
|
417
546
|
async function queryRecent(relays = DEFAULT_RELAYS, limit = 200) {
|
|
418
547
|
const events = [];
|
|
419
|
-
|
|
548
|
+
log2.info({ relayCount: relays.length, limit }, "Querying relays for recent attestations");
|
|
420
549
|
const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
|
|
421
550
|
const queryPromises = relays.map(async (relayUrl) => {
|
|
422
551
|
try {
|
|
@@ -452,30 +581,22 @@ async function queryRecent(relays = DEFAULT_RELAYS, limit = 200) {
|
|
|
452
581
|
resolve();
|
|
453
582
|
}
|
|
454
583
|
} catch (err) {
|
|
455
|
-
|
|
584
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
|
|
456
585
|
}
|
|
457
586
|
};
|
|
458
587
|
ws.onerror = (err) => {
|
|
459
588
|
clearTimeout(timeout);
|
|
460
|
-
|
|
589
|
+
log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
|
|
461
590
|
reject(err);
|
|
462
591
|
};
|
|
463
592
|
});
|
|
464
593
|
} catch (err) {
|
|
465
|
-
|
|
594
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
466
595
|
}
|
|
467
596
|
});
|
|
468
597
|
await Promise.allSettled(queryPromises);
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
for (const ev of events) {
|
|
472
|
-
const envelope = parseAttestationFromEvent(ev);
|
|
473
|
-
if (!envelope || seen.has(envelope.attestation_id)) continue;
|
|
474
|
-
if (!await verifyAttestationEnvelope(envelope)) continue;
|
|
475
|
-
seen.add(envelope.attestation_id);
|
|
476
|
-
envelopes.push(envelope);
|
|
477
|
-
}
|
|
478
|
-
log.info({ eventCount: events.length, unique: envelopes.length }, "Recent query complete");
|
|
598
|
+
const envelopes = await collectSignedEnvelopes(events);
|
|
599
|
+
log2.info({ eventCount: events.length, unique: envelopes.length }, "Recent query complete");
|
|
479
600
|
return envelopes;
|
|
480
601
|
}
|
|
481
602
|
async function verifyAttestationEnvelope(envelope) {
|
|
@@ -487,16 +608,68 @@ async function verifyAttestationEnvelope(envelope) {
|
|
|
487
608
|
const computedId = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
488
609
|
return computedId === envelope.attestation_id;
|
|
489
610
|
} catch (err) {
|
|
490
|
-
|
|
611
|
+
log2.error({ error: err }, "Failed to verify attestation envelope");
|
|
491
612
|
return false;
|
|
492
613
|
}
|
|
493
614
|
}
|
|
615
|
+
function base64Url(text) {
|
|
616
|
+
const base64 = btoa(String.fromCharCode(...new TextEncoder().encode(text)));
|
|
617
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
618
|
+
}
|
|
619
|
+
function signedEnvelope(envelope) {
|
|
620
|
+
let parsed;
|
|
621
|
+
try {
|
|
622
|
+
parsed = parseCanonicalMessage(envelope.message);
|
|
623
|
+
} catch {
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
if (!parsed.core.address || rfc3339Ms(parsed.core.issued_at) === null) return null;
|
|
627
|
+
const { expires_at: _copy, ...rest } = envelope;
|
|
628
|
+
return {
|
|
629
|
+
...rest,
|
|
630
|
+
address: parsed.core.address,
|
|
631
|
+
identities: parsed.core.identities,
|
|
632
|
+
issued_at: parsed.core.issued_at,
|
|
633
|
+
...parsed.expires !== void 0 ? { expires_at: parsed.expires } : {},
|
|
634
|
+
message_b64url: base64Url(envelope.message)
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
async function collectSignedEnvelopes(events, accept = () => true) {
|
|
638
|
+
const copies = /* @__PURE__ */ new Map();
|
|
639
|
+
for (const event of events) {
|
|
640
|
+
const raw = parseAttestationFromEvent(event);
|
|
641
|
+
if (!raw || !await verifyAttestationEnvelope(raw)) continue;
|
|
642
|
+
const envelope = signedEnvelope(raw);
|
|
643
|
+
if (!envelope || !accept(event, envelope)) continue;
|
|
644
|
+
const same = copies.get(envelope.attestation_id) ?? [];
|
|
645
|
+
if (!same.some((e) => e.signature === envelope.signature && e.scheme === envelope.scheme)) {
|
|
646
|
+
same.push(envelope);
|
|
647
|
+
}
|
|
648
|
+
copies.set(envelope.attestation_id, same);
|
|
649
|
+
}
|
|
650
|
+
const kept = [];
|
|
651
|
+
for (const same of copies.values()) {
|
|
652
|
+
if (same.length === 1) {
|
|
653
|
+
kept.push(same[0]);
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
let valid;
|
|
657
|
+
for (const e of same) {
|
|
658
|
+
if (await signatureIsValid({ addr: e.address, msg: e.message, sig: e.signature, scheme: e.scheme })) {
|
|
659
|
+
valid = e;
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
kept.push(valid ?? same[0]);
|
|
664
|
+
}
|
|
665
|
+
return kept;
|
|
666
|
+
}
|
|
494
667
|
function parseAttestationFromEvent(event) {
|
|
495
668
|
try {
|
|
496
669
|
const envelope = JSON.parse(event.content);
|
|
497
670
|
return envelope;
|
|
498
671
|
} catch (err) {
|
|
499
|
-
|
|
672
|
+
log2.error({ eventId: event.id, error: err }, "Failed to parse attestation from event");
|
|
500
673
|
return null;
|
|
501
674
|
}
|
|
502
675
|
}
|
|
@@ -504,6 +677,7 @@ function parseAttestationFromEvent(event) {
|
|
|
504
677
|
exports.ATTESTATION_EVENT_KIND = ATTESTATION_EVENT_KIND;
|
|
505
678
|
exports.DEFAULT_RELAYS = DEFAULT_RELAYS;
|
|
506
679
|
exports.FANOUT_DEADLINE_MS = FANOUT_DEADLINE_MS;
|
|
680
|
+
exports.collectSignedEnvelopes = collectSignedEnvelopes;
|
|
507
681
|
exports.createAttestationEvent = createAttestationEvent;
|
|
508
682
|
exports.getAddressFromEvent = getAddressFromEvent;
|
|
509
683
|
exports.getAttestationIdFromEvent = getAttestationIdFromEvent;
|
|
@@ -514,6 +688,7 @@ exports.queryByAddress = queryByAddress;
|
|
|
514
688
|
exports.queryByAttestationId = queryByAttestationId;
|
|
515
689
|
exports.queryByIdentity = queryByIdentity;
|
|
516
690
|
exports.queryRecent = queryRecent;
|
|
691
|
+
exports.signedEnvelope = signedEnvelope;
|
|
517
692
|
exports.verifyAttestationEnvelope = verifyAttestationEnvelope;
|
|
518
693
|
//# sourceMappingURL=nostr.js.map
|
|
519
694
|
//# sourceMappingURL=nostr.js.map
|