@orangecheck/sdk 1.12.0 → 1.14.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/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  var base = require('@scure/base');
4
4
  var buffer = require('buffer');
5
5
  var bip322 = require('@orangecheck/bip322');
6
+ var hmac_js = require('@noble/hashes/hmac.js');
6
7
  var sha2_js = require('@noble/hashes/sha2.js');
7
8
  var utils_js = require('@noble/hashes/utils.js');
8
9
  var secp256k1_js = require('@noble/curves/secp256k1.js');
@@ -219,7 +220,9 @@ var ATTESTATION_EVENT_KIND = 30078;
219
220
  var DEFAULT_RELAYS = [
220
221
  "wss://relay.damus.io",
221
222
  "wss://nos.lol",
222
- "wss://relay.snort.social",
223
+ // relay.snort.social sat here until 2026-10-21. It now runs in memory: it
224
+ // answered OK true to family events and served none of them back.
225
+ "wss://nostr.oxtr.dev",
223
226
  "wss://relay.ochk.io"
224
227
  ];
225
228
  var FANOUT_DEADLINE_MS = 4e3;
@@ -1451,11 +1454,12 @@ async function verifyChallenge(opts) {
1451
1454
  };
1452
1455
  }
1453
1456
  var HEADER = "orangecheck-bond";
1454
- var ACK = "I pledge this coin to back this subject. It stays in my wallet.";
1457
+ var ACK = "I back this subject with this coin. It stays in my wallet.";
1455
1458
  var FIELDS = ["address", "outpoint", "subject", "nonce", "issued_at", "expires_at"];
1456
1459
  var BOND_SATS_FLOORS = [1e3, 1e4, 1e5, 1e6, 1e7];
1457
1460
  var BOND_DAYS_FLOORS = [7, 30, 90, 365];
1458
1461
  var BOND_MIN_CONFIRMATIONS = 6;
1462
+ var BOND_MAX_WINDOW_SECONDS = 3600;
1459
1463
  var OUTPOINT_RE = /^[0-9a-f]{64}:(0|[1-9][0-9]{0,9})$/;
1460
1464
  var NONCE_RE = /^[0-9a-f]{32}$/;
1461
1465
  var TIME_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
@@ -1463,13 +1467,17 @@ var SUBJECT_RE = /^[\x21-\x7e]{1,256}$/;
1463
1467
  function iso2(d) {
1464
1468
  return d.toISOString().replace(/\.\d{3}Z$/, "Z");
1465
1469
  }
1470
+ function instant(s) {
1471
+ const t = Date.parse(s);
1472
+ return TIME_RE.test(s) && Number.isFinite(t) && iso2(new Date(t)) === s;
1473
+ }
1466
1474
  function randomNonce2() {
1467
1475
  const b = new Uint8Array(16);
1468
1476
  globalThis.crypto.getRandomValues(b);
1469
1477
  return utils_js.bytesToHex(b);
1470
1478
  }
1471
1479
  function wellFormed(f) {
1472
- return bip322.decodeAddress(f.address) !== null && OUTPOINT_RE.test(f.outpoint) && SUBJECT_RE.test(f.subject) && NONCE_RE.test(f.nonce) && TIME_RE.test(f.issued_at) && TIME_RE.test(f.expires_at) && Date.parse(f.expires_at) > Date.parse(f.issued_at);
1480
+ return bip322.decodeAddress(f.address) !== null && OUTPOINT_RE.test(f.outpoint) && SUBJECT_RE.test(f.subject) && NONCE_RE.test(f.nonce) && instant(f.issued_at) && instant(f.expires_at) && Date.parse(f.expires_at) > Date.parse(f.issued_at);
1473
1481
  }
1474
1482
  function buildBondMessage(fields) {
1475
1483
  if (!wellFormed(fields)) throw new Error("bond fields are malformed");
@@ -1507,10 +1515,12 @@ function parseBondMessage(message) {
1507
1515
  function bondId(message) {
1508
1516
  return utils_js.bytesToHex(sha2_js.sha256(utils_js.utf8ToBytes(message)));
1509
1517
  }
1510
- function bondNullifier(audience, outpoint) {
1511
- return utils_js.bytesToHex(sha2_js.sha256(utils_js.utf8ToBytes(`orangecheck-bond-nullifier
1518
+ function bondNullifier(key, audience, outpoint) {
1519
+ if (key.length < 32) throw new Error("bond nullifier key must be at least 32 bytes");
1520
+ const message = utils_js.utf8ToBytes(`orangecheck-bond-nullifier
1512
1521
  ${audience}
1513
- ${outpoint}`)));
1522
+ ${outpoint}`);
1523
+ return utils_js.bytesToHex(hmac_js.hmac(sha2_js.sha256, key, message));
1514
1524
  }
1515
1525
  function bondFloors(sats, ageBlocks) {
1516
1526
  const days = Math.floor(ageBlocks / 144);
@@ -1523,6 +1533,10 @@ function verifyBondSignature(opts) {
1523
1533
  if (opts.subject !== void 0 && fields.subject !== opts.subject) {
1524
1534
  return { ok: false, reason: "bond_subject_mismatch" };
1525
1535
  }
1536
+ const window2 = Date.parse(fields.expires_at) - Date.parse(fields.issued_at);
1537
+ if (window2 > (opts.maxWindowSeconds ?? BOND_MAX_WINDOW_SECONDS) * 1e3) {
1538
+ return { ok: false, reason: "bond_window_too_long" };
1539
+ }
1526
1540
  const now = (opts.now ?? /* @__PURE__ */ new Date()).getTime();
1527
1541
  const skew = (opts.skewSeconds ?? 60) * 1e3;
1528
1542
  if (Date.parse(fields.issued_at) - skew > now) return { ok: false, reason: "bond_not_yet_valid" };
@@ -1578,22 +1592,28 @@ async function verifyBond(opts) {
1578
1592
  };
1579
1593
  }
1580
1594
  function esploraSource(base = "https://mempool.space/api", opts = {}) {
1581
- const get = async (path) => {
1595
+ const get = async (path, missingOk = false) => {
1582
1596
  const r = await (opts.fetch ?? fetch)(`${base}${path}`, {
1583
1597
  signal: AbortSignal.timeout(opts.timeoutMs ?? 5e3)
1584
1598
  });
1585
- if (!r.ok && r.status !== 404) throw new Error(`esplora ${path}: HTTP ${r.status}`);
1599
+ if (!r.ok && !(missingOk && r.status === 404)) {
1600
+ throw new Error(`esplora ${path}: HTTP ${r.status}`);
1601
+ }
1586
1602
  return r;
1587
1603
  };
1588
1604
  return {
1589
1605
  async tip() {
1590
1606
  const [h, hash] = await Promise.all([get("/blocks/tip/height"), get("/blocks/tip/hash")]);
1591
- return { height: Number(await h.text()), hash: (await hash.text()).trim() };
1607
+ const tip = { height: Number(await h.text()), hash: (await hash.text()).trim() };
1608
+ if (!Number.isSafeInteger(tip.height) || !/^[0-9a-f]{64}$/.test(tip.hash)) {
1609
+ throw new Error("esplora tip: malformed");
1610
+ }
1611
+ return tip;
1592
1612
  },
1593
1613
  async coin(outpoint) {
1594
1614
  const [txid, voutStr] = outpoint.split(":");
1595
1615
  const vout = Number(voutStr);
1596
- const txr = await get(`/tx/${txid}`);
1616
+ const txr = await get(`/tx/${txid}`, true);
1597
1617
  if (txr.status === 404) return null;
1598
1618
  const tx = await txr.json();
1599
1619
  const out = tx.vout[vout];
@@ -2398,6 +2418,7 @@ var STATUS_META = {
2398
2418
  };
2399
2419
 
2400
2420
  exports.BOND_DAYS_FLOORS = BOND_DAYS_FLOORS;
2421
+ exports.BOND_MAX_WINDOW_SECONDS = BOND_MAX_WINDOW_SECONDS;
2401
2422
  exports.BOND_MIN_CONFIRMATIONS = BOND_MIN_CONFIRMATIONS;
2402
2423
  exports.BOND_SATS_FLOORS = BOND_SATS_FLOORS;
2403
2424
  exports.DEFAULT_RELAYS = DEFAULT_RELAYS;