@orangecheck/sdk 1.11.0 → 1.13.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 +41 -0
- package/dist/attestation.d.mts +1 -1
- package/dist/attestation.d.ts +1 -1
- package/dist/canonical.d.mts +1 -1
- package/dist/canonical.d.ts +1 -1
- package/dist/challenge.d.mts +1 -1
- package/dist/challenge.d.ts +1 -1
- package/dist/identity.d.mts +1 -1
- package/dist/identity.d.ts +1 -1
- package/dist/index.d.mts +108 -3
- package/dist/index.d.ts +108 -3
- package/dist/index.js +213 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +201 -6
- package/dist/index.mjs.map +1 -1
- package/dist/nostr.d.mts +1 -1
- package/dist/nostr.d.ts +1 -1
- package/dist/{types-D9Tb1roj.d.mts → types-BBRMCgMo.d.mts} +1 -0
- package/dist/{types-D9Tb1roj.d.ts → types-BBRMCgMo.d.ts} +1 -0
- package/dist/verify.d.mts +1 -1
- package/dist/verify.d.ts +1 -1
- package/dist/verify.js +20 -2
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +20 -2
- package/dist/verify.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { bech32 } from '@scure/base';
|
|
2
2
|
import { Buffer } from 'buffer';
|
|
3
|
-
import { verifySignature as verifySignature$1 } from '@orangecheck/bip322';
|
|
4
|
-
import {
|
|
3
|
+
import { verifySignature as verifySignature$1, decodeAddress } from '@orangecheck/bip322';
|
|
4
|
+
import { hmac } from '@noble/hashes/hmac.js';
|
|
5
5
|
import { sha256 } from '@noble/hashes/sha2.js';
|
|
6
|
+
import { bytesToHex, utf8ToBytes } from '@noble/hashes/utils.js';
|
|
7
|
+
import { schnorr } from '@noble/curves/secp256k1.js';
|
|
6
8
|
|
|
7
9
|
// src/canonical.ts
|
|
8
10
|
function toLowerHex(bytes) {
|
|
@@ -1024,7 +1026,7 @@ async function getAddressUtxos(address, network, opts) {
|
|
|
1024
1026
|
log4.error({ endpoints, error: lastError?.message, network }, "All Esplora endpoints failed");
|
|
1025
1027
|
throw new Error(`esplora_all_failed: ${lastError?.message || "unknown"}`);
|
|
1026
1028
|
}
|
|
1027
|
-
function computeMetrics(utxos, bondAmount) {
|
|
1029
|
+
function computeMetrics(utxos, bondAmount, coverSats) {
|
|
1028
1030
|
const confirmedUtxos = utxos.filter((u) => u.status.confirmed);
|
|
1029
1031
|
if (confirmedUtxos.length === 0) {
|
|
1030
1032
|
return { sats_bonded: 0, days_unspent: 0, confirmed_balance: 0 };
|
|
@@ -1057,6 +1059,23 @@ function computeMetrics(utxos, bondAmount) {
|
|
|
1057
1059
|
};
|
|
1058
1060
|
}
|
|
1059
1061
|
const sats_bonded = confirmed_balance;
|
|
1062
|
+
if (coverSats !== void 0 && coverSats > 0) {
|
|
1063
|
+
const sorted = [...confirmedUtxos].sort(
|
|
1064
|
+
(a, b) => (a.status.block_height ?? Infinity) - (b.status.block_height ?? Infinity)
|
|
1065
|
+
);
|
|
1066
|
+
let sum = 0;
|
|
1067
|
+
let youngestTime = 0;
|
|
1068
|
+
for (const utxo of sorted) {
|
|
1069
|
+
sum += utxo.value;
|
|
1070
|
+
youngestTime = Math.max(youngestTime, utxo.status.block_time || Date.now() / 1e3);
|
|
1071
|
+
if (sum >= coverSats) break;
|
|
1072
|
+
}
|
|
1073
|
+
const days_unspent2 = Math.max(
|
|
1074
|
+
0,
|
|
1075
|
+
Math.floor((Date.now() / 1e3 - youngestTime) / (24 * 60 * 60))
|
|
1076
|
+
);
|
|
1077
|
+
return { sats_bonded, days_unspent: days_unspent2, confirmed_balance };
|
|
1078
|
+
}
|
|
1060
1079
|
const oldestTime = Math.min(
|
|
1061
1080
|
...confirmedUtxos.map((u) => u.status.block_time || Date.now() / 1e3)
|
|
1062
1081
|
);
|
|
@@ -1120,7 +1139,8 @@ async function verify(input, opts = {}) {
|
|
|
1120
1139
|
});
|
|
1121
1140
|
const { sats_bonded, days_unspent, confirmed_balance } = computeMetrics(
|
|
1122
1141
|
utxos,
|
|
1123
|
-
parsed.bond
|
|
1142
|
+
parsed.bond,
|
|
1143
|
+
opts.coverSats
|
|
1124
1144
|
);
|
|
1125
1145
|
if (parsed.bond !== void 0 && parsed.bond > 0) {
|
|
1126
1146
|
if (confirmed_balance < parsed.bond) {
|
|
@@ -1225,7 +1245,7 @@ async function check(params) {
|
|
|
1225
1245
|
sig: env.signature,
|
|
1226
1246
|
scheme: env.scheme
|
|
1227
1247
|
},
|
|
1228
|
-
verifyOptions
|
|
1248
|
+
{ ...verifyOptions, coverSats: minSats > 0 ? minSats : void 0 }
|
|
1229
1249
|
);
|
|
1230
1250
|
const sats = outcome.metrics?.sats_bonded ?? 0;
|
|
1231
1251
|
const days = outcome.metrics?.days_unspent ?? 0;
|
|
@@ -1429,6 +1449,181 @@ async function verifyChallenge(opts) {
|
|
|
1429
1449
|
...purpose !== void 0 ? { purpose } : {}
|
|
1430
1450
|
};
|
|
1431
1451
|
}
|
|
1452
|
+
var HEADER = "orangecheck-bond";
|
|
1453
|
+
var ACK = "I back this subject with this coin. It stays in my wallet.";
|
|
1454
|
+
var FIELDS = ["address", "outpoint", "subject", "nonce", "issued_at", "expires_at"];
|
|
1455
|
+
var BOND_SATS_FLOORS = [1e3, 1e4, 1e5, 1e6, 1e7];
|
|
1456
|
+
var BOND_DAYS_FLOORS = [7, 30, 90, 365];
|
|
1457
|
+
var BOND_MIN_CONFIRMATIONS = 6;
|
|
1458
|
+
var BOND_MAX_WINDOW_SECONDS = 3600;
|
|
1459
|
+
var OUTPOINT_RE = /^[0-9a-f]{64}:(0|[1-9][0-9]{0,9})$/;
|
|
1460
|
+
var NONCE_RE = /^[0-9a-f]{32}$/;
|
|
1461
|
+
var TIME_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
|
|
1462
|
+
var SUBJECT_RE = /^[\x21-\x7e]{1,256}$/;
|
|
1463
|
+
function iso2(d) {
|
|
1464
|
+
return d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
1465
|
+
}
|
|
1466
|
+
function instant(s) {
|
|
1467
|
+
const t = Date.parse(s);
|
|
1468
|
+
return TIME_RE.test(s) && Number.isFinite(t) && iso2(new Date(t)) === s;
|
|
1469
|
+
}
|
|
1470
|
+
function randomNonce2() {
|
|
1471
|
+
const b = new Uint8Array(16);
|
|
1472
|
+
globalThis.crypto.getRandomValues(b);
|
|
1473
|
+
return bytesToHex(b);
|
|
1474
|
+
}
|
|
1475
|
+
function wellFormed(f) {
|
|
1476
|
+
return 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);
|
|
1477
|
+
}
|
|
1478
|
+
function buildBondMessage(fields) {
|
|
1479
|
+
if (!wellFormed(fields)) throw new Error("bond fields are malformed");
|
|
1480
|
+
return [HEADER, ...FIELDS.map((k) => `${k}: ${fields[k]}`), `ack: ${ACK}`].join("\n") + "\n";
|
|
1481
|
+
}
|
|
1482
|
+
function issueBondMessage(opts) {
|
|
1483
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
1484
|
+
const expires = new Date(now.getTime() + (opts.ttlSeconds ?? 600) * 1e3);
|
|
1485
|
+
const nonce = randomNonce2();
|
|
1486
|
+
const message = buildBondMessage({
|
|
1487
|
+
address: opts.address,
|
|
1488
|
+
outpoint: opts.outpoint,
|
|
1489
|
+
subject: opts.subject,
|
|
1490
|
+
nonce,
|
|
1491
|
+
issued_at: iso2(now),
|
|
1492
|
+
expires_at: iso2(expires)
|
|
1493
|
+
});
|
|
1494
|
+
return { message, nonce, expiresAt: expires.getTime() };
|
|
1495
|
+
}
|
|
1496
|
+
function parseBondMessage(message) {
|
|
1497
|
+
if (!message.endsWith("\n") || message.includes("\r")) return null;
|
|
1498
|
+
const lines = message.slice(0, -1).split("\n");
|
|
1499
|
+
if (lines.length !== FIELDS.length + 2 || lines[0] !== HEADER) return null;
|
|
1500
|
+
if (lines[lines.length - 1] !== `ack: ${ACK}`) return null;
|
|
1501
|
+
const out = {};
|
|
1502
|
+
for (const [i, key] of FIELDS.entries()) {
|
|
1503
|
+
const line = lines[i + 1];
|
|
1504
|
+
const prefix = `${key}: `;
|
|
1505
|
+
if (!line.startsWith(prefix)) return null;
|
|
1506
|
+
out[key] = line.slice(prefix.length);
|
|
1507
|
+
}
|
|
1508
|
+
const fields = out;
|
|
1509
|
+
return wellFormed(fields) && buildBondMessage(fields) === message ? fields : null;
|
|
1510
|
+
}
|
|
1511
|
+
function bondId(message) {
|
|
1512
|
+
return bytesToHex(sha256(utf8ToBytes(message)));
|
|
1513
|
+
}
|
|
1514
|
+
function bondNullifier(key, audience, outpoint) {
|
|
1515
|
+
if (key.length < 32) throw new Error("bond nullifier key must be at least 32 bytes");
|
|
1516
|
+
const message = utf8ToBytes(`orangecheck-bond-nullifier
|
|
1517
|
+
${audience}
|
|
1518
|
+
${outpoint}`);
|
|
1519
|
+
return bytesToHex(hmac(sha256, key, message));
|
|
1520
|
+
}
|
|
1521
|
+
function bondFloors(sats, ageBlocks) {
|
|
1522
|
+
const days = Math.floor(ageBlocks / 144);
|
|
1523
|
+
const floor = (ladder, v) => ladder.filter((f) => v >= f).reduce((a, b) => Math.max(a, b), 0);
|
|
1524
|
+
return { sats_floor: floor(BOND_SATS_FLOORS, sats), days_floor: floor(BOND_DAYS_FLOORS, days) };
|
|
1525
|
+
}
|
|
1526
|
+
function verifyBondSignature(opts) {
|
|
1527
|
+
const fields = parseBondMessage(opts.message);
|
|
1528
|
+
if (!fields) return { ok: false, reason: "bond_malformed" };
|
|
1529
|
+
if (opts.subject !== void 0 && fields.subject !== opts.subject) {
|
|
1530
|
+
return { ok: false, reason: "bond_subject_mismatch" };
|
|
1531
|
+
}
|
|
1532
|
+
const window2 = Date.parse(fields.expires_at) - Date.parse(fields.issued_at);
|
|
1533
|
+
if (window2 > (opts.maxWindowSeconds ?? BOND_MAX_WINDOW_SECONDS) * 1e3) {
|
|
1534
|
+
return { ok: false, reason: "bond_window_too_long" };
|
|
1535
|
+
}
|
|
1536
|
+
const now = (opts.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
1537
|
+
const skew = (opts.skewSeconds ?? 60) * 1e3;
|
|
1538
|
+
if (Date.parse(fields.issued_at) - skew > now) return { ok: false, reason: "bond_not_yet_valid" };
|
|
1539
|
+
if (Date.parse(fields.expires_at) + skew < now) return { ok: false, reason: "bond_expired" };
|
|
1540
|
+
if (!verifySignature$1(fields.address, opts.message, opts.signature)) {
|
|
1541
|
+
return { ok: false, reason: "sig_invalid" };
|
|
1542
|
+
}
|
|
1543
|
+
return { ok: true, fields, bond_id: bondId(opts.message) };
|
|
1544
|
+
}
|
|
1545
|
+
async function checkBondCoin(opts) {
|
|
1546
|
+
const decoded = decodeAddress(opts.address);
|
|
1547
|
+
if (!decoded || !OUTPOINT_RE.test(opts.outpoint)) return { ok: false, reason: "bond_malformed" };
|
|
1548
|
+
let coin;
|
|
1549
|
+
let tip;
|
|
1550
|
+
try {
|
|
1551
|
+
[coin, tip] = await Promise.all([opts.source.coin(opts.outpoint), opts.source.tip()]);
|
|
1552
|
+
} catch {
|
|
1553
|
+
return { ok: false, reason: "chain_unavailable" };
|
|
1554
|
+
}
|
|
1555
|
+
if (!coin) return { ok: false, reason: "coin_not_found" };
|
|
1556
|
+
if (coin.scriptPubKey !== bytesToHex(decoded.scriptPubKey)) {
|
|
1557
|
+
return { ok: false, reason: "coin_address_mismatch" };
|
|
1558
|
+
}
|
|
1559
|
+
if (coin.confirmedHeight === null) return { ok: false, reason: "coin_unconfirmed" };
|
|
1560
|
+
if (coin.spent) return { ok: false, reason: "coin_spent" };
|
|
1561
|
+
const confirmations = tip.height - coin.confirmedHeight + 1;
|
|
1562
|
+
if (confirmations < (opts.minConfirmations ?? BOND_MIN_CONFIRMATIONS)) {
|
|
1563
|
+
return { ok: false, reason: "coin_too_recent" };
|
|
1564
|
+
}
|
|
1565
|
+
return { ok: true, sats: coin.sats, confirmed_height: coin.confirmedHeight, tip };
|
|
1566
|
+
}
|
|
1567
|
+
async function verifyBond(opts) {
|
|
1568
|
+
const sig = verifyBondSignature(opts);
|
|
1569
|
+
if (!sig.ok) return sig;
|
|
1570
|
+
const coin = await checkBondCoin({
|
|
1571
|
+
address: sig.fields.address,
|
|
1572
|
+
outpoint: sig.fields.outpoint,
|
|
1573
|
+
source: opts.source,
|
|
1574
|
+
minConfirmations: opts.minConfirmations
|
|
1575
|
+
});
|
|
1576
|
+
if (!coin.ok) return coin;
|
|
1577
|
+
return {
|
|
1578
|
+
ok: true,
|
|
1579
|
+
bond: {
|
|
1580
|
+
bond_id: sig.bond_id,
|
|
1581
|
+
subject: sig.fields.subject,
|
|
1582
|
+
address: sig.fields.address,
|
|
1583
|
+
outpoint: sig.fields.outpoint,
|
|
1584
|
+
sats: coin.sats,
|
|
1585
|
+
confirmed_height: coin.confirmed_height,
|
|
1586
|
+
tip: coin.tip
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
function esploraSource(base = "https://mempool.space/api", opts = {}) {
|
|
1591
|
+
const get = async (path, missingOk = false) => {
|
|
1592
|
+
const r = await (opts.fetch ?? fetch)(`${base}${path}`, {
|
|
1593
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 5e3)
|
|
1594
|
+
});
|
|
1595
|
+
if (!r.ok && !(missingOk && r.status === 404)) {
|
|
1596
|
+
throw new Error(`esplora ${path}: HTTP ${r.status}`);
|
|
1597
|
+
}
|
|
1598
|
+
return r;
|
|
1599
|
+
};
|
|
1600
|
+
return {
|
|
1601
|
+
async tip() {
|
|
1602
|
+
const [h, hash] = await Promise.all([get("/blocks/tip/height"), get("/blocks/tip/hash")]);
|
|
1603
|
+
const tip = { height: Number(await h.text()), hash: (await hash.text()).trim() };
|
|
1604
|
+
if (!Number.isSafeInteger(tip.height) || !/^[0-9a-f]{64}$/.test(tip.hash)) {
|
|
1605
|
+
throw new Error("esplora tip: malformed");
|
|
1606
|
+
}
|
|
1607
|
+
return tip;
|
|
1608
|
+
},
|
|
1609
|
+
async coin(outpoint) {
|
|
1610
|
+
const [txid, voutStr] = outpoint.split(":");
|
|
1611
|
+
const vout = Number(voutStr);
|
|
1612
|
+
const txr = await get(`/tx/${txid}`, true);
|
|
1613
|
+
if (txr.status === 404) return null;
|
|
1614
|
+
const tx = await txr.json();
|
|
1615
|
+
const out = tx.vout[vout];
|
|
1616
|
+
if (!out) return null;
|
|
1617
|
+
const spend = await (await get(`/tx/${txid}/outspend/${vout}`)).json();
|
|
1618
|
+
return {
|
|
1619
|
+
sats: out.value,
|
|
1620
|
+
scriptPubKey: out.scriptpubkey.toLowerCase(),
|
|
1621
|
+
confirmedHeight: tx.status.confirmed ? tx.status.block_height ?? null : null,
|
|
1622
|
+
spent: spend.spent
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1432
1627
|
|
|
1433
1628
|
// src/utils/rate-limiter.ts
|
|
1434
1629
|
var RateLimitError = class extends Error {
|
|
@@ -2218,6 +2413,6 @@ var STATUS_META = {
|
|
|
2218
2413
|
}
|
|
2219
2414
|
};
|
|
2220
2415
|
|
|
2221
|
-
export { DEFAULT_RELAYS, FANOUT_DEADLINE_MS, STATUS_META, buildCanonicalMessage, check, computeAllScores, computeScore, createAttestation, createAttestationEnvelope, createAttestationEvent, discoverAttestations, extractAttestationIdFromUrl, formatIdentities, formatIdentitiesForDisplay, generateAttestationId, generateGistTemplate, generateTweetText, generateWellKnownContent, getAttestationsForAddress, getAttestationsForIdentity, getDnsVerificationInstructions, getGistCreationUrl, getGitHubVerificationInstructions, getManualVerificationInstructions, getNip07Info, getNostrPublicKey, getNostrVerificationInstructions, getTweetUrl, getTwitterComposeUrl, getTwitterVerificationInstructions, getVerificationInstructions, getVerificationSummary, getVerificationUrl, getWellKnownUrl, isDnsIdentityVerified, isGitHubIdentityVerified, isNip07Available, isNostrIdentityVerified, isValidDomain, isValidTweetUrl, issueChallenge, nostrIdentifierForms, nostrPubkeyToHex, parseAttestationFromEvent, parseIdentities, publishAttestation, publishToRelays, queryByAddress, queryByAttestationId, queryByIdentity, queryRecent, requiresManualVerification, signNostrEvent, supportsAutomaticVerification, verify, verifyAllIdentities, verifyAttestationById, verifyChallenge, verifyDnsIdentity, verifyGitHubIdentity, verifyIdentity, verifyNostrIdentity, verifyTwitterIdentity };
|
|
2416
|
+
export { BOND_DAYS_FLOORS, BOND_MAX_WINDOW_SECONDS, BOND_MIN_CONFIRMATIONS, BOND_SATS_FLOORS, DEFAULT_RELAYS, FANOUT_DEADLINE_MS, STATUS_META, bondFloors, bondId, bondNullifier, buildBondMessage, buildCanonicalMessage, check, checkBondCoin, computeAllScores, computeScore, createAttestation, createAttestationEnvelope, createAttestationEvent, discoverAttestations, esploraSource, extractAttestationIdFromUrl, formatIdentities, formatIdentitiesForDisplay, generateAttestationId, generateGistTemplate, generateTweetText, generateWellKnownContent, getAttestationsForAddress, getAttestationsForIdentity, getDnsVerificationInstructions, getGistCreationUrl, getGitHubVerificationInstructions, getManualVerificationInstructions, getNip07Info, getNostrPublicKey, getNostrVerificationInstructions, getTweetUrl, getTwitterComposeUrl, getTwitterVerificationInstructions, getVerificationInstructions, getVerificationSummary, getVerificationUrl, getWellKnownUrl, isDnsIdentityVerified, isGitHubIdentityVerified, isNip07Available, isNostrIdentityVerified, isValidDomain, isValidTweetUrl, issueBondMessage, issueChallenge, nostrIdentifierForms, nostrPubkeyToHex, parseAttestationFromEvent, parseBondMessage, parseIdentities, publishAttestation, publishToRelays, queryByAddress, queryByAttestationId, queryByIdentity, queryRecent, requiresManualVerification, signNostrEvent, supportsAutomaticVerification, verify, verifyAllIdentities, verifyAttestationById, verifyBond, verifyBondSignature, verifyChallenge, verifyDnsIdentity, verifyGitHubIdentity, verifyIdentity, verifyNostrIdentity, verifyTwitterIdentity };
|
|
2222
2417
|
//# sourceMappingURL=index.mjs.map
|
|
2223
2418
|
//# sourceMappingURL=index.mjs.map
|