@orangecheck/sdk 1.14.0 → 1.15.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/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 buffer = require('buffer');
5
+ var bip322 = require('@orangecheck/bip322');
4
6
 
5
7
  // src/nostr-pubkey.ts
6
8
  var HEX_64_RE = /^[0-9a-f]{64}$/;
@@ -73,8 +75,216 @@ 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
+
127
+ // src/verify.ts
128
+ var log = createLogger("ocp/verify");
129
+ function looksLikeHex(s) {
130
+ return /^[0-9a-fA-F]+$/.test(s) && s.length % 2 === 0;
131
+ }
132
+ function toBase64Maybe(sig) {
133
+ if (looksLikeHex(sig)) {
134
+ return buffer.Buffer.from(sig, "hex").toString("base64");
135
+ }
136
+ return sig;
137
+ }
138
+ function detectSignatureScheme(addr, sig) {
139
+ const isSegWit = addr.startsWith("bc1") || addr.startsWith("tb1");
140
+ if (isSegWit) {
141
+ return "bip322";
142
+ }
143
+ const sigBase64 = toBase64Maybe(sig);
144
+ const looksLikeLegacy = sigBase64.length === 88 && /^[IHG]/.test(sigBase64);
145
+ if (looksLikeLegacy) {
146
+ return "legacy";
147
+ }
148
+ return "bip322";
149
+ }
150
+ async function verifySignature({
151
+ addr,
152
+ msg,
153
+ sig,
154
+ scheme
155
+ }) {
156
+ const detectedScheme = scheme || detectSignatureScheme(addr, sig);
157
+ log.debug(
158
+ { addr, msgLength: msg.length, sigLength: sig.length, scheme: detectedScheme },
159
+ "Verifying signature"
160
+ );
161
+ if (detectedScheme === "bip322") {
162
+ const sigBase64 = toBase64Maybe(sig);
163
+ log.debug(
164
+ { sigBase64: sigBase64.substring(0, 50) + "..." },
165
+ "Signature converted to base64"
166
+ );
167
+ log.debug(
168
+ {
169
+ msgFull: msg,
170
+ msgLines: msg.split("\n").length,
171
+ msgFirstLine: msg.split("\n")[0],
172
+ msgLastLine: msg.split("\n")[msg.split("\n").length - 1]
173
+ },
174
+ "Message being verified"
175
+ );
176
+ const ok = bip322.verifySignature(addr, msg, sigBase64);
177
+ log.info({ addr, ok }, "BIP-322 verification result");
178
+ if (!ok) {
179
+ const isSegWit = addr.startsWith("bc1") || addr.startsWith("tb1");
180
+ if (isSegWit && sigBase64.length === 88) {
181
+ log.warn(
182
+ { addr, wallet: "possibly Electrum", sigLength: sigBase64.length },
183
+ "BIP-322 verification failed - likely Electrum wallet (does not support BIP-322 for SegWit)"
184
+ );
185
+ } else {
186
+ log.error(
187
+ {
188
+ addr,
189
+ sigLength: sigBase64.length,
190
+ msgLength: msg.length,
191
+ msgPreview: msg.substring(0, 200)
192
+ },
193
+ "BIP-322 verification failed - signature does not match message"
194
+ );
195
+ }
196
+ }
197
+ return ok ? "sig_ok_bip322" : "sig_invalid";
198
+ }
199
+ if (detectedScheme === "legacy") {
200
+ const sigBase64 = toBase64Maybe(sig);
201
+ if (!addr.startsWith("1") && !addr.startsWith("m") && !addr.startsWith("n")) {
202
+ log.warn({ addr }, "Legacy signature attempted on non-P2PKH address");
203
+ return "sig_unsupported_script";
204
+ }
205
+ log.debug(
206
+ {
207
+ addr,
208
+ sigBase64: sigBase64.substring(0, 50) + "...",
209
+ msgPreview: msg.substring(0, 100) + "...",
210
+ msgLength: msg.length
211
+ },
212
+ "Verifying legacy signature"
213
+ );
214
+ const ok = bip322.verifySignature(addr, msg, sigBase64);
215
+ log.info({ addr, ok, sigBase64Length: sigBase64.length }, "Legacy verification result");
216
+ if (!ok) {
217
+ log.error(
218
+ {
219
+ addr,
220
+ msgFirstLine: msg.split("\n")[0],
221
+ sigFormat: sigBase64.substring(0, 10)
222
+ },
223
+ "Legacy signature verification failed - check message format and signature"
224
+ );
225
+ }
226
+ return ok ? "sig_ok_legacy" : "sig_invalid";
227
+ }
228
+ return "invalid_scheme";
229
+ }
230
+ async function signatureIsValid(input) {
231
+ try {
232
+ const code = await verifySignature(input);
233
+ return code === "sig_ok_bip322" || code === "sig_ok_legacy";
234
+ } catch {
235
+ return false;
236
+ }
237
+ }
238
+ function parseCanonicalMessage(msg) {
239
+ const lines = msg.split("\n").map((l) => l.trim());
240
+ const getVal = (prefix) => {
241
+ const needle = prefix + ":";
242
+ const hit = lines.find((l) => l.toLowerCase().startsWith(needle));
243
+ if (!hit) return void 0;
244
+ const idx = hit.indexOf(":");
245
+ return hit.slice(idx + 1).trim();
246
+ };
247
+ const address = getVal("address") || "";
248
+ const identitiesStr = getVal("identities") || "";
249
+ const issued_at = getVal("issued_at") || "";
250
+ const expires = getVal("expires");
251
+ let identities = [];
252
+ try {
253
+ identities = parseIdentities(identitiesStr);
254
+ } catch (err) {
255
+ log.warn({ identitiesStr, error: err }, "Failed to parse identities field");
256
+ }
257
+ const coreKeys = /* @__PURE__ */ new Set([
258
+ "orangecheck",
259
+ "identities",
260
+ "address",
261
+ "purpose",
262
+ "nonce",
263
+ "issued_at",
264
+ "ack"
265
+ ]);
266
+ const extensions = {};
267
+ for (const line of lines) {
268
+ const idx = line.indexOf(":");
269
+ if (idx === -1) continue;
270
+ const key = line.slice(0, idx).trim().toLowerCase();
271
+ const val = line.slice(idx + 1).trim();
272
+ if (!coreKeys.has(key) && val) extensions[key] = val;
273
+ }
274
+ const network = extensions["network"] || "mainnet";
275
+ const bondStr = extensions["bond"];
276
+ const bond = bondStr ? parseInt(bondStr, 10) : void 0;
277
+ return {
278
+ core: { address, identities, issued_at },
279
+ expires,
280
+ network,
281
+ extensions,
282
+ bond: bond !== void 0 && !isNaN(bond) && bond > 0 ? bond : void 0
283
+ };
284
+ }
285
+
76
286
  // src/nostr.ts
77
- var log = createLogger("ocp/nostr");
287
+ var log2 = createLogger("ocp/nostr");
78
288
  var ATTESTATION_EVENT_KIND = 30078;
79
289
  var DEFAULT_RELAYS = [
80
290
  "wss://relay.damus.io",
@@ -150,7 +360,7 @@ function getAddressFromEvent(event) {
150
360
  async function publishToRelays(event, relays = DEFAULT_RELAYS) {
151
361
  const success = [];
152
362
  const failed = [];
153
- log.info({ eventId: event.id, relayCount: relays.length }, "Publishing event to relays");
363
+ log2.info({ eventId: event.id, relayCount: relays.length }, "Publishing event to relays");
154
364
  const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
155
365
  const publishPromises = relays.map(async (relayUrl) => {
156
366
  let ws = null;
@@ -189,7 +399,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
189
399
  cleanup();
190
400
  resolve();
191
401
  } else {
192
- log.warn(
402
+ log2.warn(
193
403
  { relay: relayUrl, reason: data[3] },
194
404
  "Relay rejected event"
195
405
  );
@@ -199,7 +409,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
199
409
  }
200
410
  }
201
411
  } catch (err) {
202
- log.error(
412
+ log2.error(
203
413
  { relay: relayUrl, error: err },
204
414
  "Failed to parse relay response"
205
415
  );
@@ -209,7 +419,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
209
419
  }
210
420
  };
211
421
  wsRef.onerror = (err) => {
212
- log.error({ relay: relayUrl, error: err }, "WebSocket error");
422
+ log2.error({ relay: relayUrl, error: err }, "WebSocket error");
213
423
  recordFailure();
214
424
  cleanup();
215
425
  reject(err);
@@ -219,7 +429,7 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
219
429
  };
220
430
  });
221
431
  } catch (err) {
222
- log.error({ relay: relayUrl, error: err }, "Failed to publish to relay");
432
+ log2.error({ relay: relayUrl, error: err }, "Failed to publish to relay");
223
433
  recordFailure();
224
434
  } finally {
225
435
  if (ws && ws.readyState !== WebSocket.CLOSED) {
@@ -228,12 +438,12 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
228
438
  }
229
439
  });
230
440
  await Promise.allSettled(publishPromises);
231
- log.info({ successCount: success.length, failedCount: failed.length }, "Publishing complete");
441
+ log2.info({ successCount: success.length, failedCount: failed.length }, "Publishing complete");
232
442
  return { success, failed };
233
443
  }
234
444
  async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
235
445
  const events = [];
236
- log.info({ attestationId, relayCount: relays.length }, "Querying relays for attestation");
446
+ log2.info({ attestationId, relayCount: relays.length }, "Querying relays for attestation");
237
447
  const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
238
448
  const queryPromises = relays.map(async (relayUrl) => {
239
449
  let ws = null;
@@ -273,11 +483,11 @@ async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
273
483
  resolve();
274
484
  }
275
485
  } catch (err) {
276
- log.error({ relay: relayUrl, error: err }, "Failed to parse event");
486
+ log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
277
487
  }
278
488
  };
279
489
  wsRef.onerror = (err) => {
280
- log.error({ relay: relayUrl, error: err }, "WebSocket error during query");
490
+ log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
281
491
  cleanup();
282
492
  reject(err);
283
493
  };
@@ -286,7 +496,7 @@ async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
286
496
  };
287
497
  });
288
498
  } catch (err) {
289
- log.error({ relay: relayUrl, error: err }, "Failed to query relay");
499
+ log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
290
500
  } finally {
291
501
  if (ws && ws.readyState !== WebSocket.CLOSED) {
292
502
  ws.close();
@@ -294,12 +504,12 @@ async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
294
504
  }
295
505
  });
296
506
  await Promise.allSettled(queryPromises);
297
- log.info({ eventCount: events.length }, "Query complete");
507
+ log2.info({ eventCount: events.length }, "Query complete");
298
508
  return events;
299
509
  }
300
510
  async function queryByAddress(address, relays = DEFAULT_RELAYS) {
301
511
  const events = [];
302
- log.info({ address, relayCount: relays.length }, "Querying relays for address");
512
+ log2.info({ address, relayCount: relays.length }, "Querying relays for address");
303
513
  const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
304
514
  const queryPromises = relays.map(async (relayUrl) => {
305
515
  try {
@@ -341,28 +551,28 @@ async function queryByAddress(address, relays = DEFAULT_RELAYS) {
341
551
  resolve();
342
552
  }
343
553
  } catch (err) {
344
- log.error({ relay: relayUrl, error: err }, "Failed to parse event");
554
+ log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
345
555
  }
346
556
  };
347
557
  ws.onerror = (err) => {
348
558
  clearTimeout(timeout);
349
- log.error({ relay: relayUrl, error: err }, "WebSocket error during query");
559
+ log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
350
560
  reject(err);
351
561
  };
352
562
  });
353
563
  } catch (err) {
354
- log.error({ relay: relayUrl, error: err }, "Failed to query relay");
564
+ log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
355
565
  }
356
566
  });
357
567
  await Promise.allSettled(queryPromises);
358
- log.info({ eventCount: events.length }, "Query complete");
568
+ log2.info({ eventCount: events.length }, "Query complete");
359
569
  return events;
360
570
  }
361
571
  async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
362
572
  const events = [];
363
573
  const identityTag = `${protocol}:${identifier}`;
364
574
  const identityTags = protocol === "nostr" ? nostrIdentifierForms(identifier).map((f) => `nostr:${f}`) : [identityTag];
365
- log.info({ protocol, identifier, relayCount: relays.length }, "Querying relays for identity");
575
+ log2.info({ protocol, identifier, relayCount: relays.length }, "Querying relays for identity");
366
576
  const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
367
577
  const queryPromises = relays.map(async (relayUrl) => {
368
578
  try {
@@ -397,26 +607,26 @@ async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
397
607
  resolve();
398
608
  }
399
609
  } catch (err) {
400
- log.error({ relay: relayUrl, error: err }, "Failed to parse event");
610
+ log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
401
611
  }
402
612
  };
403
613
  ws.onerror = (err) => {
404
614
  clearTimeout(timeout);
405
- log.error({ relay: relayUrl, error: err }, "WebSocket error during query");
615
+ log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
406
616
  reject(err);
407
617
  };
408
618
  });
409
619
  } catch (err) {
410
- log.error({ relay: relayUrl, error: err }, "Failed to query relay");
620
+ log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
411
621
  }
412
622
  });
413
623
  await Promise.allSettled(queryPromises);
414
- log.info({ eventCount: events.length }, "Query complete");
624
+ log2.info({ eventCount: events.length }, "Query complete");
415
625
  return events;
416
626
  }
417
627
  async function queryRecent(relays = DEFAULT_RELAYS, limit = 200) {
418
628
  const events = [];
419
- log.info({ relayCount: relays.length, limit }, "Querying relays for recent attestations");
629
+ log2.info({ relayCount: relays.length, limit }, "Querying relays for recent attestations");
420
630
  const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
421
631
  const queryPromises = relays.map(async (relayUrl) => {
422
632
  try {
@@ -452,30 +662,22 @@ async function queryRecent(relays = DEFAULT_RELAYS, limit = 200) {
452
662
  resolve();
453
663
  }
454
664
  } catch (err) {
455
- log.error({ relay: relayUrl, error: err }, "Failed to parse event");
665
+ log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
456
666
  }
457
667
  };
458
668
  ws.onerror = (err) => {
459
669
  clearTimeout(timeout);
460
- log.error({ relay: relayUrl, error: err }, "WebSocket error during query");
670
+ log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
461
671
  reject(err);
462
672
  };
463
673
  });
464
674
  } catch (err) {
465
- log.error({ relay: relayUrl, error: err }, "Failed to query relay");
675
+ log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
466
676
  }
467
677
  });
468
678
  await Promise.allSettled(queryPromises);
469
- const seen = /* @__PURE__ */ new Set();
470
- const envelopes = [];
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");
679
+ const envelopes = await collectSignedEnvelopes(events);
680
+ log2.info({ eventCount: events.length, unique: envelopes.length }, "Recent query complete");
479
681
  return envelopes;
480
682
  }
481
683
  async function verifyAttestationEnvelope(envelope) {
@@ -487,16 +689,68 @@ async function verifyAttestationEnvelope(envelope) {
487
689
  const computedId = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
488
690
  return computedId === envelope.attestation_id;
489
691
  } catch (err) {
490
- log.error({ error: err }, "Failed to verify attestation envelope");
692
+ log2.error({ error: err }, "Failed to verify attestation envelope");
491
693
  return false;
492
694
  }
493
695
  }
696
+ function base64Url(text) {
697
+ const base64 = btoa(String.fromCharCode(...new TextEncoder().encode(text)));
698
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
699
+ }
700
+ function signedEnvelope(envelope) {
701
+ let parsed;
702
+ try {
703
+ parsed = parseCanonicalMessage(envelope.message);
704
+ } catch {
705
+ return null;
706
+ }
707
+ if (!parsed.core.address || rfc3339Ms(parsed.core.issued_at) === null) return null;
708
+ const { expires_at: _copy, ...rest } = envelope;
709
+ return {
710
+ ...rest,
711
+ address: parsed.core.address,
712
+ identities: parsed.core.identities,
713
+ issued_at: parsed.core.issued_at,
714
+ ...parsed.expires !== void 0 ? { expires_at: parsed.expires } : {},
715
+ message_b64url: base64Url(envelope.message)
716
+ };
717
+ }
718
+ async function collectSignedEnvelopes(events, accept = () => true) {
719
+ const copies = /* @__PURE__ */ new Map();
720
+ for (const event of events) {
721
+ const raw = parseAttestationFromEvent(event);
722
+ if (!raw || !await verifyAttestationEnvelope(raw)) continue;
723
+ const envelope = signedEnvelope(raw);
724
+ if (!envelope || !accept(event, envelope)) continue;
725
+ const same = copies.get(envelope.attestation_id) ?? [];
726
+ if (!same.some((e) => e.signature === envelope.signature && e.scheme === envelope.scheme)) {
727
+ same.push(envelope);
728
+ }
729
+ copies.set(envelope.attestation_id, same);
730
+ }
731
+ const kept = [];
732
+ for (const same of copies.values()) {
733
+ if (same.length === 1) {
734
+ kept.push(same[0]);
735
+ continue;
736
+ }
737
+ let valid;
738
+ for (const e of same) {
739
+ if (await signatureIsValid({ addr: e.address, msg: e.message, sig: e.signature, scheme: e.scheme })) {
740
+ valid = e;
741
+ break;
742
+ }
743
+ }
744
+ kept.push(valid ?? same[0]);
745
+ }
746
+ return kept;
747
+ }
494
748
  function parseAttestationFromEvent(event) {
495
749
  try {
496
750
  const envelope = JSON.parse(event.content);
497
751
  return envelope;
498
752
  } catch (err) {
499
- log.error({ eventId: event.id, error: err }, "Failed to parse attestation from event");
753
+ log2.error({ eventId: event.id, error: err }, "Failed to parse attestation from event");
500
754
  return null;
501
755
  }
502
756
  }
@@ -504,6 +758,7 @@ function parseAttestationFromEvent(event) {
504
758
  exports.ATTESTATION_EVENT_KIND = ATTESTATION_EVENT_KIND;
505
759
  exports.DEFAULT_RELAYS = DEFAULT_RELAYS;
506
760
  exports.FANOUT_DEADLINE_MS = FANOUT_DEADLINE_MS;
761
+ exports.collectSignedEnvelopes = collectSignedEnvelopes;
507
762
  exports.createAttestationEvent = createAttestationEvent;
508
763
  exports.getAddressFromEvent = getAddressFromEvent;
509
764
  exports.getAttestationIdFromEvent = getAttestationIdFromEvent;
@@ -514,6 +769,7 @@ exports.queryByAddress = queryByAddress;
514
769
  exports.queryByAttestationId = queryByAttestationId;
515
770
  exports.queryByIdentity = queryByIdentity;
516
771
  exports.queryRecent = queryRecent;
772
+ exports.signedEnvelope = signedEnvelope;
517
773
  exports.verifyAttestationEnvelope = verifyAttestationEnvelope;
518
774
  //# sourceMappingURL=nostr.js.map
519
775
  //# sourceMappingURL=nostr.js.map