@orangecheck/sdk 1.6.0 → 1.7.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/attestation.d.mts +1 -1
- package/dist/attestation.d.ts +1 -1
- package/dist/attestation.js +38 -2
- package/dist/attestation.js.map +1 -1
- package/dist/attestation.mjs +38 -2
- package/dist/attestation.mjs.map +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/identity.js +26 -20
- package/dist/identity.js.map +1 -1
- package/dist/identity.mjs +26 -20
- package/dist/identity.mjs.map +1 -1
- package/dist/index.d.mts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +98 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +97 -33
- package/dist/index.mjs.map +1 -1
- package/dist/nostr.d.mts +1 -1
- package/dist/nostr.d.ts +1 -1
- package/dist/nostr.js +30 -2
- package/dist/nostr.js.map +1 -1
- package/dist/nostr.mjs +30 -2
- package/dist/nostr.mjs.map +1 -1
- package/dist/{types-BJKDroFv.d.mts → types-D9Tb1roj.d.mts} +1 -1
- package/dist/{types-BJKDroFv.d.ts → types-D9Tb1roj.d.ts} +1 -1
- package/dist/verify.d.mts +11 -2
- package/dist/verify.d.ts +11 -2
- package/dist/verify.js +1 -0
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +1 -1
- package/dist/verify.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var base = require('@scure/base');
|
|
3
4
|
var buffer = require('buffer');
|
|
4
5
|
var bip322Js = require('bip322-js');
|
|
5
6
|
var bitcoinMessage = require('bitcoinjs-message');
|
|
6
7
|
var secp256k1_js = require('@noble/curves/secp256k1.js');
|
|
7
8
|
var sha2_js = require('@noble/hashes/sha2.js');
|
|
8
|
-
var base = require('@scure/base');
|
|
9
9
|
|
|
10
10
|
function _interopNamespace(e) {
|
|
11
11
|
if (e && e.__esModule) return e;
|
|
@@ -150,6 +150,31 @@ async function createAttestationEnvelope(message, signature, scheme, address, id
|
|
|
150
150
|
relay_hints
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
|
+
var HEX_64_RE = /^[0-9a-f]{64}$/;
|
|
154
|
+
function toHex(bytes) {
|
|
155
|
+
let out = "";
|
|
156
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
function nostrPubkeyToHex(value) {
|
|
160
|
+
const trimmed = value.trim().toLowerCase();
|
|
161
|
+
if (HEX_64_RE.test(trimmed)) return trimmed;
|
|
162
|
+
if (!trimmed.startsWith("npub1")) return null;
|
|
163
|
+
try {
|
|
164
|
+
const decoded = base.bech32.decode(trimmed, 1023);
|
|
165
|
+
if (decoded.prefix !== "npub") return null;
|
|
166
|
+
const bytes = base.bech32.fromWords(decoded.words);
|
|
167
|
+
return bytes.length === 32 ? toHex(Uint8Array.from(bytes)) : null;
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function nostrIdentifierForms(identifier) {
|
|
173
|
+
const hex = nostrPubkeyToHex(identifier);
|
|
174
|
+
if (!hex) return [identifier];
|
|
175
|
+
const bytes = Uint8Array.from(hex.match(/../g).map((b) => parseInt(b, 16)));
|
|
176
|
+
return [base.bech32.encode("npub", base.bech32.toWords(bytes), 1023), hex];
|
|
177
|
+
}
|
|
153
178
|
function createLogger(namespace) {
|
|
154
179
|
const shouldLog = (level) => {
|
|
155
180
|
return false;
|
|
@@ -471,6 +496,7 @@ async function queryByAddress(address, relays = DEFAULT_RELAYS) {
|
|
|
471
496
|
async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
|
|
472
497
|
const events = [];
|
|
473
498
|
const identityTag = `${protocol}:${identifier}`;
|
|
499
|
+
const identityTags = protocol === "nostr" ? nostrIdentifierForms(identifier).map((f) => `nostr:${f}`) : [identityTag];
|
|
474
500
|
log.info({ protocol, identifier, relayCount: relays.length }, "Querying relays for identity");
|
|
475
501
|
const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
|
|
476
502
|
const queryPromises = relays.map(async (relayUrl) => {
|
|
@@ -489,7 +515,7 @@ async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
|
|
|
489
515
|
subscriptionId,
|
|
490
516
|
{
|
|
491
517
|
kinds: [30078],
|
|
492
|
-
"#i":
|
|
518
|
+
"#i": identityTags,
|
|
493
519
|
limit: 10
|
|
494
520
|
}
|
|
495
521
|
])
|
|
@@ -655,6 +681,12 @@ async function publishAttestation(options) {
|
|
|
655
681
|
);
|
|
656
682
|
return result;
|
|
657
683
|
}
|
|
684
|
+
function eventIndexesEnvelope(event, envelope) {
|
|
685
|
+
const d = event.tags.find((t) => t[0] === "d")?.[1];
|
|
686
|
+
const id = envelope.attestation_id;
|
|
687
|
+
if (d !== id && d !== `orangecheck:${id}`) return false;
|
|
688
|
+
return event.tags.some((t) => t[0] === "t" && t[1] === envelope.address);
|
|
689
|
+
}
|
|
658
690
|
async function discoverAttestations(options) {
|
|
659
691
|
const { attestationId, address, identity, relays = DEFAULT_RELAYS } = options;
|
|
660
692
|
let events = [];
|
|
@@ -673,7 +705,9 @@ async function discoverAttestations(options) {
|
|
|
673
705
|
const envelopes = [];
|
|
674
706
|
for (const event of events) {
|
|
675
707
|
const envelope = parseAttestationFromEvent(event);
|
|
676
|
-
if (envelope) {
|
|
708
|
+
if (envelope && !eventIndexesEnvelope(event, envelope)) {
|
|
709
|
+
log3.warn({ eventId: event.id }, "Skipping event whose index tags disagree with its envelope");
|
|
710
|
+
} else if (envelope) {
|
|
677
711
|
const isValid = await verifyAttestationEnvelope(envelope);
|
|
678
712
|
if (isValid) {
|
|
679
713
|
envelopes.push(envelope);
|
|
@@ -1157,18 +1191,15 @@ async function check(params) {
|
|
|
1157
1191
|
if (!addr && !id && !identity) {
|
|
1158
1192
|
return { ok: false, sats: 0, days: 0, score: 0, reasons: ["bad_request"] };
|
|
1159
1193
|
}
|
|
1160
|
-
let
|
|
1194
|
+
let found = [];
|
|
1161
1195
|
if (id) {
|
|
1162
|
-
|
|
1196
|
+
found = await discoverAttestations({ attestationId: id, relays });
|
|
1163
1197
|
} else if (addr) {
|
|
1164
|
-
|
|
1198
|
+
found = await getAttestationsForAddress(addr, relays);
|
|
1165
1199
|
} else if (identity) {
|
|
1166
|
-
|
|
1167
|
-
identity.protocol,
|
|
1168
|
-
identity.identifier,
|
|
1169
|
-
relays
|
|
1170
|
-
);
|
|
1200
|
+
found = await getAttestationsForIdentity(identity.protocol, identity.identifier, relays);
|
|
1171
1201
|
}
|
|
1202
|
+
const envelopes = found.filter((e) => signedMatch(e, { addr, id, identity }));
|
|
1172
1203
|
if (envelopes.length === 0) {
|
|
1173
1204
|
return { ok: false, sats: 0, days: 0, score: 0, reasons: ["not_found"] };
|
|
1174
1205
|
}
|
|
@@ -1177,6 +1208,7 @@ async function check(params) {
|
|
|
1177
1208
|
if (!env) {
|
|
1178
1209
|
return { ok: false, sats: 0, days: 0, score: 0, reasons: ["not_found"] };
|
|
1179
1210
|
}
|
|
1211
|
+
const shared = identity ? await backsOtherIdentity(env, identity, relays) : false;
|
|
1180
1212
|
const outcome = await verify2(
|
|
1181
1213
|
{
|
|
1182
1214
|
addr: env.address,
|
|
@@ -1191,9 +1223,10 @@ async function check(params) {
|
|
|
1191
1223
|
const score = outcome.metrics?.score ?? 0;
|
|
1192
1224
|
const reasons = [];
|
|
1193
1225
|
if (!outcome.ok) reasons.push(...outcome.codes);
|
|
1226
|
+
if (shared) reasons.push("stake_shared");
|
|
1194
1227
|
if (sats < minSats) reasons.push("below_min_sats");
|
|
1195
1228
|
if (days < minDays) reasons.push("below_min_days");
|
|
1196
|
-
const ok = outcome.ok && sats >= minSats && days >= minDays;
|
|
1229
|
+
const ok = outcome.ok && !shared && sats >= minSats && days >= minDays;
|
|
1197
1230
|
return {
|
|
1198
1231
|
ok,
|
|
1199
1232
|
sats,
|
|
@@ -1206,6 +1239,50 @@ async function check(params) {
|
|
|
1206
1239
|
...reasons.length ? { reasons } : {}
|
|
1207
1240
|
};
|
|
1208
1241
|
}
|
|
1242
|
+
function identityKey(b) {
|
|
1243
|
+
const protocol = b.protocol.trim().toLowerCase();
|
|
1244
|
+
if (protocol === "nostr") {
|
|
1245
|
+
const hex = nostrPubkeyToHex(b.identifier);
|
|
1246
|
+
return hex ? `nostr:${hex}` : null;
|
|
1247
|
+
}
|
|
1248
|
+
return `${protocol}:${b.identifier.trim()}`;
|
|
1249
|
+
}
|
|
1250
|
+
function signedIdentities(env) {
|
|
1251
|
+
try {
|
|
1252
|
+
return parseCanonicalMessage(env.message).core.identities;
|
|
1253
|
+
} catch {
|
|
1254
|
+
return [];
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
function signedMatch(env, q) {
|
|
1258
|
+
if (q.id) return env.attestation_id.toLowerCase() === q.id.trim().toLowerCase();
|
|
1259
|
+
let signedAddress;
|
|
1260
|
+
try {
|
|
1261
|
+
signedAddress = parseCanonicalMessage(env.message).core.address;
|
|
1262
|
+
} catch {
|
|
1263
|
+
return false;
|
|
1264
|
+
}
|
|
1265
|
+
if (signedAddress !== env.address) return false;
|
|
1266
|
+
if (q.addr) return signedAddress === q.addr;
|
|
1267
|
+
if (q.identity) {
|
|
1268
|
+
const want = identityKey(q.identity);
|
|
1269
|
+
return want !== null && signedIdentities(env).some((b) => identityKey(b) === want);
|
|
1270
|
+
}
|
|
1271
|
+
return false;
|
|
1272
|
+
}
|
|
1273
|
+
async function backsOtherIdentity(env, identity, relays) {
|
|
1274
|
+
const want = identityKey(identity);
|
|
1275
|
+
const protocol = identity.protocol.trim().toLowerCase();
|
|
1276
|
+
const siblings = await getAttestationsForAddress(env.address, relays);
|
|
1277
|
+
for (const e of [env, ...siblings]) {
|
|
1278
|
+
if (!signedMatch(e, { addr: env.address })) continue;
|
|
1279
|
+
for (const b of signedIdentities(e)) {
|
|
1280
|
+
if (b.protocol.trim().toLowerCase() !== protocol) continue;
|
|
1281
|
+
if (identityKey(b) !== want) return true;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
return false;
|
|
1285
|
+
}
|
|
1209
1286
|
var AUTH_HEADER = "orangecheck-auth";
|
|
1210
1287
|
var AUTH_ACK = "I authorize this session under the OrangeCheck auth challenge.";
|
|
1211
1288
|
function randomNonce() {
|
|
@@ -1660,8 +1737,8 @@ function getGistCreationUrl() {
|
|
|
1660
1737
|
return "https://gist.github.com/";
|
|
1661
1738
|
}
|
|
1662
1739
|
var log7 = createLogger("identity-verification/nostr");
|
|
1663
|
-
var
|
|
1664
|
-
function
|
|
1740
|
+
var HEX_64_RE2 = /^[0-9a-f]{64}$/;
|
|
1741
|
+
function toHex2(bytes) {
|
|
1665
1742
|
let out = "";
|
|
1666
1743
|
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
1667
1744
|
return out;
|
|
@@ -1675,22 +1752,9 @@ function fromHex(hex) {
|
|
|
1675
1752
|
return out;
|
|
1676
1753
|
}
|
|
1677
1754
|
function npubToHex(npub) {
|
|
1678
|
-
const
|
|
1679
|
-
if (
|
|
1680
|
-
|
|
1681
|
-
}
|
|
1682
|
-
if (!trimmed.toLowerCase().startsWith("npub1")) {
|
|
1683
|
-
throw new Error(`not an npub: ${trimmed.slice(0, 12)}\u2026`);
|
|
1684
|
-
}
|
|
1685
|
-
const decoded = base.bech32.decode(trimmed.toLowerCase(), 1023);
|
|
1686
|
-
if (decoded.prefix !== "npub") {
|
|
1687
|
-
throw new Error(`bech32 prefix mismatch: expected npub, got ${decoded.prefix}`);
|
|
1688
|
-
}
|
|
1689
|
-
const bytes = base.bech32.fromWords(decoded.words);
|
|
1690
|
-
if (bytes.length !== 32) {
|
|
1691
|
-
throw new Error(`invalid npub payload length: ${bytes.length}`);
|
|
1692
|
-
}
|
|
1693
|
-
return toHex(Uint8Array.from(bytes));
|
|
1755
|
+
const hex = nostrPubkeyToHex(npub);
|
|
1756
|
+
if (!hex) throw new Error(`not an npub: ${npub.trim().slice(0, 12)}\u2026`);
|
|
1757
|
+
return hex;
|
|
1694
1758
|
}
|
|
1695
1759
|
async function queryNostrEvents(options) {
|
|
1696
1760
|
const { authors, kinds, search, relays = DEFAULT_RELAYS, limit = 100 } = options;
|
|
@@ -1762,7 +1826,7 @@ async function queryNostrEvents(options) {
|
|
|
1762
1826
|
}
|
|
1763
1827
|
async function verifyNostrEventSignature(event) {
|
|
1764
1828
|
try {
|
|
1765
|
-
if (!event.id || !event.sig || !event.pubkey || typeof event.id !== "string" || typeof event.sig !== "string" || typeof event.pubkey !== "string" || event.sig.length !== 128 || !
|
|
1829
|
+
if (!event.id || !event.sig || !event.pubkey || typeof event.id !== "string" || typeof event.sig !== "string" || typeof event.pubkey !== "string" || event.sig.length !== 128 || !HEX_64_RE2.test(event.id) || !HEX_64_RE2.test(event.pubkey)) {
|
|
1766
1830
|
return false;
|
|
1767
1831
|
}
|
|
1768
1832
|
const preimage = JSON.stringify([
|
|
@@ -1773,7 +1837,7 @@ async function verifyNostrEventSignature(event) {
|
|
|
1773
1837
|
event.tags ?? [],
|
|
1774
1838
|
event.content ?? ""
|
|
1775
1839
|
]);
|
|
1776
|
-
const computedId =
|
|
1840
|
+
const computedId = toHex2(sha2_js.sha256(new TextEncoder().encode(preimage)));
|
|
1777
1841
|
if (computedId !== event.id.toLowerCase()) {
|
|
1778
1842
|
log7.warn(
|
|
1779
1843
|
{ eventId: event.id, computedId },
|
|
@@ -2182,6 +2246,8 @@ exports.isNostrIdentityVerified = isNostrIdentityVerified;
|
|
|
2182
2246
|
exports.isValidDomain = isValidDomain;
|
|
2183
2247
|
exports.isValidTweetUrl = isValidTweetUrl;
|
|
2184
2248
|
exports.issueChallenge = issueChallenge;
|
|
2249
|
+
exports.nostrIdentifierForms = nostrIdentifierForms;
|
|
2250
|
+
exports.nostrPubkeyToHex = nostrPubkeyToHex;
|
|
2185
2251
|
exports.parseAttestationFromEvent = parseAttestationFromEvent;
|
|
2186
2252
|
exports.parseIdentities = parseIdentities;
|
|
2187
2253
|
exports.publishAttestation = publishAttestation;
|