@orangecheck/sdk 1.16.1 → 1.17.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 +1 -0
- package/dist/attestation.js +86 -170
- package/dist/attestation.js.map +1 -1
- package/dist/attestation.mjs +86 -170
- package/dist/attestation.mjs.map +1 -1
- package/dist/identity.js.map +1 -1
- package/dist/identity.mjs.map +1 -1
- package/dist/index.js +131 -172
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +131 -172
- package/dist/index.mjs.map +1 -1
- package/dist/nostr.js +86 -170
- package/dist/nostr.js.map +1 -1
- package/dist/nostr.mjs +86 -170
- package/dist/nostr.mjs.map +1 -1
- package/dist/verify.d.mts +1 -0
- package/dist/verify.d.ts +1 -0
- package/dist/verify.js +35 -0
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +35 -0
- package/dist/verify.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -307,6 +307,7 @@ Full type definitions ship with the package.
|
|
|
307
307
|
- **No custody.** The SDK never touches user funds. It signs messages, it doesn't spend coins.
|
|
308
308
|
- **No telemetry.** The SDK makes network calls only to public Bitcoin explorers (mempool.space, blockstream.info) and Nostr relays you pass in. No OrangeCheck server is in the path for `verify()`.
|
|
309
309
|
- **Offline-verifiable.** Given `(addr, msg, sig)`, anyone — including you, sitting on an island with a Raspberry Pi — can verify.
|
|
310
|
+
- **Unknown options throw.** `check()` and `verify()` refuse a key they do not read, so a misspelt `minSats` or `expectedAud` is never ignored.
|
|
310
311
|
|
|
311
312
|
---
|
|
312
313
|
|
package/dist/attestation.js
CHANGED
|
@@ -217,11 +217,15 @@ function parseCanonicalMessage(msg) {
|
|
|
217
217
|
"ack"
|
|
218
218
|
]);
|
|
219
219
|
const extensions = {};
|
|
220
|
+
const keys = /* @__PURE__ */ new Set();
|
|
221
|
+
const repeated = [];
|
|
220
222
|
for (const line of lines) {
|
|
221
223
|
const idx = line.indexOf(":");
|
|
222
224
|
if (idx === -1) continue;
|
|
223
225
|
const key = line.slice(0, idx).trim().toLowerCase();
|
|
224
226
|
const val = line.slice(idx + 1).trim();
|
|
227
|
+
if (keys.has(key)) repeated.push(key);
|
|
228
|
+
keys.add(key);
|
|
225
229
|
if (!coreKeys.has(key) && val) extensions[key] = val;
|
|
226
230
|
}
|
|
227
231
|
const network = extensions["network"] || "mainnet";
|
|
@@ -230,6 +234,7 @@ function parseCanonicalMessage(msg) {
|
|
|
230
234
|
return {
|
|
231
235
|
core: { address, identities, issued_at },
|
|
232
236
|
expires,
|
|
237
|
+
repeated,
|
|
233
238
|
network,
|
|
234
239
|
extensions,
|
|
235
240
|
bond: bond !== void 0 && !isNaN(bond) && bond > 0 ? bond : void 0
|
|
@@ -371,188 +376,98 @@ async function publishToRelays(event, relays = DEFAULT_RELAYS) {
|
|
|
371
376
|
log2.info({ successCount: success.length, failedCount: failed.length }, "Publishing complete");
|
|
372
377
|
return { success, failed };
|
|
373
378
|
}
|
|
374
|
-
|
|
379
|
+
var PAGE_LIMIT = 500;
|
|
380
|
+
var MAX_PAGES = 4;
|
|
381
|
+
async function querySubject(filter, relays, label) {
|
|
375
382
|
const events = [];
|
|
376
|
-
log2.info({ attestationId, relayCount: relays.length }, "Querying relays for attestation");
|
|
377
383
|
const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
384
|
+
await Promise.allSettled(
|
|
385
|
+
relays.map(async (relayUrl) => {
|
|
386
|
+
let ws = null;
|
|
387
|
+
try {
|
|
388
|
+
ws = new WebSocket(relayUrl);
|
|
389
|
+
const wsRef = ws;
|
|
390
|
+
await new Promise((resolve, reject) => {
|
|
391
|
+
const timeout = setTimeout(() => {
|
|
392
|
+
wsRef.close();
|
|
393
|
+
reject(new Error("Query timeout"));
|
|
394
|
+
}, budgetFor(deadlineAt));
|
|
395
|
+
const seen = /* @__PURE__ */ new Set();
|
|
396
|
+
let page = 0;
|
|
397
|
+
let inPage = 0;
|
|
398
|
+
let newInPage = 0;
|
|
399
|
+
let oldest = Infinity;
|
|
400
|
+
const ask = () => {
|
|
401
|
+
page++;
|
|
402
|
+
inPage = 0;
|
|
403
|
+
newInPage = 0;
|
|
404
|
+
const until = Number.isFinite(oldest) ? { until: oldest } : {};
|
|
405
|
+
wsRef.send(
|
|
406
|
+
JSON.stringify([
|
|
407
|
+
"REQ",
|
|
408
|
+
`ochk_${label}_${Date.now()}_${page}`,
|
|
409
|
+
{ ...filter, ...until, limit: PAGE_LIMIT }
|
|
410
|
+
])
|
|
411
|
+
);
|
|
412
|
+
};
|
|
413
|
+
wsRef.onopen = ask;
|
|
414
|
+
wsRef.onmessage = (msg) => {
|
|
415
|
+
try {
|
|
416
|
+
const data = JSON.parse(msg.data);
|
|
417
|
+
if (data[0] === "EVENT") {
|
|
418
|
+
const event = data[2];
|
|
419
|
+
const key = event.id ?? JSON.stringify(event);
|
|
420
|
+
inPage++;
|
|
421
|
+
if (!seen.has(key)) {
|
|
422
|
+
seen.add(key);
|
|
423
|
+
newInPage++;
|
|
424
|
+
events.push(event);
|
|
425
|
+
}
|
|
426
|
+
if (typeof event.created_at === "number") {
|
|
427
|
+
oldest = Math.min(oldest, event.created_at);
|
|
428
|
+
}
|
|
429
|
+
} else if (data[0] === "EOSE") {
|
|
430
|
+
wsRef.send(JSON.stringify(["CLOSE", data[1]]));
|
|
431
|
+
if (inPage >= PAGE_LIMIT && newInPage > 0 && page < MAX_PAGES) {
|
|
432
|
+
ask();
|
|
433
|
+
} else {
|
|
434
|
+
clearTimeout(timeout);
|
|
435
|
+
wsRef.close();
|
|
436
|
+
resolve();
|
|
437
|
+
}
|
|
402
438
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
};
|
|
406
|
-
wsRef.onmessage = (msg) => {
|
|
407
|
-
try {
|
|
408
|
-
const data = JSON.parse(msg.data);
|
|
409
|
-
if (data[0] === "EVENT") {
|
|
410
|
-
events.push(data[2]);
|
|
411
|
-
} else if (data[0] === "EOSE") {
|
|
412
|
-
cleanup();
|
|
413
|
-
resolve();
|
|
439
|
+
} catch (err) {
|
|
440
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
|
|
414
441
|
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
};
|
|
427
|
-
});
|
|
428
|
-
} catch (err) {
|
|
429
|
-
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
430
|
-
} finally {
|
|
431
|
-
if (ws && ws.readyState !== WebSocket.CLOSED) {
|
|
432
|
-
ws.close();
|
|
442
|
+
};
|
|
443
|
+
wsRef.onerror = (err) => {
|
|
444
|
+
clearTimeout(timeout);
|
|
445
|
+
log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
|
|
446
|
+
reject(err);
|
|
447
|
+
};
|
|
448
|
+
});
|
|
449
|
+
} catch (err) {
|
|
450
|
+
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
451
|
+
} finally {
|
|
452
|
+
if (ws && ws.readyState !== WebSocket.CLOSED) ws.close();
|
|
433
453
|
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
log2.info({ eventCount: events.length }, "Query complete");
|
|
454
|
+
})
|
|
455
|
+
);
|
|
456
|
+
log2.info({ label, eventCount: events.length }, "Query complete");
|
|
438
457
|
return events;
|
|
439
458
|
}
|
|
459
|
+
async function queryByAttestationId(attestationId, relays = DEFAULT_RELAYS) {
|
|
460
|
+
log2.info({ attestationId, relayCount: relays.length }, "Querying relays for attestation");
|
|
461
|
+
return querySubject({ kinds: [30078], "#d": [attestationId] }, relays, "id");
|
|
462
|
+
}
|
|
440
463
|
async function queryByAddress(address, relays = DEFAULT_RELAYS) {
|
|
441
|
-
const events = [];
|
|
442
464
|
log2.info({ address, relayCount: relays.length }, "Querying relays for address");
|
|
443
|
-
|
|
444
|
-
const queryPromises = relays.map(async (relayUrl) => {
|
|
445
|
-
try {
|
|
446
|
-
const ws = new WebSocket(relayUrl);
|
|
447
|
-
await new Promise((resolve, reject) => {
|
|
448
|
-
const timeout = setTimeout(() => {
|
|
449
|
-
ws.close();
|
|
450
|
-
reject(new Error("Query timeout"));
|
|
451
|
-
}, budgetFor(deadlineAt));
|
|
452
|
-
ws.onopen = () => {
|
|
453
|
-
const subscriptionId = `ochk_addr_${Date.now()}`;
|
|
454
|
-
ws.send(
|
|
455
|
-
JSON.stringify([
|
|
456
|
-
"REQ",
|
|
457
|
-
subscriptionId,
|
|
458
|
-
{
|
|
459
|
-
kinds: [30078],
|
|
460
|
-
// `#t`, NOT `#address`: NIP-12 relays index
|
|
461
|
-
// single-letter tags only, so `#address`
|
|
462
|
-
// matched nothing on any relay and this
|
|
463
|
-
// function returned empty for every real
|
|
464
|
-
// attestation. attest.ochk.io's verifier has
|
|
465
|
-
// queried `#t` since the same fix landed
|
|
466
|
-
// there; this is the back-port.
|
|
467
|
-
"#t": [address],
|
|
468
|
-
limit: 50
|
|
469
|
-
}
|
|
470
|
-
])
|
|
471
|
-
);
|
|
472
|
-
};
|
|
473
|
-
ws.onmessage = (msg) => {
|
|
474
|
-
try {
|
|
475
|
-
const data = JSON.parse(msg.data);
|
|
476
|
-
if (data[0] === "EVENT") {
|
|
477
|
-
events.push(data[2]);
|
|
478
|
-
} else if (data[0] === "EOSE") {
|
|
479
|
-
clearTimeout(timeout);
|
|
480
|
-
ws.close();
|
|
481
|
-
resolve();
|
|
482
|
-
}
|
|
483
|
-
} catch (err) {
|
|
484
|
-
log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
|
|
485
|
-
}
|
|
486
|
-
};
|
|
487
|
-
ws.onerror = (err) => {
|
|
488
|
-
clearTimeout(timeout);
|
|
489
|
-
log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
|
|
490
|
-
reject(err);
|
|
491
|
-
};
|
|
492
|
-
});
|
|
493
|
-
} catch (err) {
|
|
494
|
-
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
495
|
-
}
|
|
496
|
-
});
|
|
497
|
-
await Promise.allSettled(queryPromises);
|
|
498
|
-
log2.info({ eventCount: events.length }, "Query complete");
|
|
499
|
-
return events;
|
|
465
|
+
return querySubject({ kinds: [30078], "#t": [address] }, relays, "addr");
|
|
500
466
|
}
|
|
501
467
|
async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
|
|
502
|
-
const
|
|
503
|
-
const identityTag = `${protocol}:${identifier}`;
|
|
504
|
-
const identityTags = protocol === "nostr" ? nostrIdentifierForms(identifier).map((f) => `nostr:${f}`) : [identityTag];
|
|
468
|
+
const identityTags = protocol === "nostr" ? nostrIdentifierForms(identifier).map((f) => `nostr:${f}`) : [`${protocol}:${identifier}`];
|
|
505
469
|
log2.info({ protocol, identifier, relayCount: relays.length }, "Querying relays for identity");
|
|
506
|
-
|
|
507
|
-
const queryPromises = relays.map(async (relayUrl) => {
|
|
508
|
-
try {
|
|
509
|
-
const ws = new WebSocket(relayUrl);
|
|
510
|
-
await new Promise((resolve, reject) => {
|
|
511
|
-
const timeout = setTimeout(() => {
|
|
512
|
-
ws.close();
|
|
513
|
-
reject(new Error("Query timeout"));
|
|
514
|
-
}, budgetFor(deadlineAt));
|
|
515
|
-
ws.onopen = () => {
|
|
516
|
-
const subscriptionId = `ochk_identity_${Date.now()}`;
|
|
517
|
-
ws.send(
|
|
518
|
-
JSON.stringify([
|
|
519
|
-
"REQ",
|
|
520
|
-
subscriptionId,
|
|
521
|
-
{
|
|
522
|
-
kinds: [30078],
|
|
523
|
-
"#i": identityTags,
|
|
524
|
-
limit: 10
|
|
525
|
-
}
|
|
526
|
-
])
|
|
527
|
-
);
|
|
528
|
-
};
|
|
529
|
-
ws.onmessage = (msg) => {
|
|
530
|
-
try {
|
|
531
|
-
const data = JSON.parse(msg.data);
|
|
532
|
-
if (data[0] === "EVENT") {
|
|
533
|
-
events.push(data[2]);
|
|
534
|
-
} else if (data[0] === "EOSE") {
|
|
535
|
-
clearTimeout(timeout);
|
|
536
|
-
ws.close();
|
|
537
|
-
resolve();
|
|
538
|
-
}
|
|
539
|
-
} catch (err) {
|
|
540
|
-
log2.error({ relay: relayUrl, error: err }, "Failed to parse event");
|
|
541
|
-
}
|
|
542
|
-
};
|
|
543
|
-
ws.onerror = (err) => {
|
|
544
|
-
clearTimeout(timeout);
|
|
545
|
-
log2.error({ relay: relayUrl, error: err }, "WebSocket error during query");
|
|
546
|
-
reject(err);
|
|
547
|
-
};
|
|
548
|
-
});
|
|
549
|
-
} catch (err) {
|
|
550
|
-
log2.error({ relay: relayUrl, error: err }, "Failed to query relay");
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
await Promise.allSettled(queryPromises);
|
|
554
|
-
log2.info({ eventCount: events.length }, "Query complete");
|
|
555
|
-
return events;
|
|
470
|
+
return querySubject({ kinds: [30078], "#i": identityTags }, relays, "identity");
|
|
556
471
|
}
|
|
557
472
|
async function verifyAttestationEnvelope(envelope) {
|
|
558
473
|
try {
|
|
@@ -579,6 +494,7 @@ function signedEnvelope(envelope) {
|
|
|
579
494
|
return null;
|
|
580
495
|
}
|
|
581
496
|
if (!parsed.core.address || rfc3339Ms(parsed.core.issued_at) === null) return null;
|
|
497
|
+
if (parsed.repeated.length > 0) return null;
|
|
582
498
|
const { expires_at: _copy, ...rest } = envelope;
|
|
583
499
|
return {
|
|
584
500
|
...rest,
|