@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.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { bech32 } from '@scure/base';
|
|
1
2
|
import { Buffer } from 'buffer';
|
|
2
3
|
import { Verifier } from 'bip322-js';
|
|
3
4
|
import * as bitcoinMessage from 'bitcoinjs-message';
|
|
4
5
|
import { schnorr } from '@noble/curves/secp256k1.js';
|
|
5
6
|
import { sha256 } from '@noble/hashes/sha2.js';
|
|
6
|
-
import { bech32 } from '@scure/base';
|
|
7
7
|
|
|
8
8
|
// src/canonical.ts
|
|
9
9
|
function toLowerHex(bytes) {
|
|
@@ -128,6 +128,31 @@ async function createAttestationEnvelope(message, signature, scheme, address, id
|
|
|
128
128
|
relay_hints
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
|
+
var HEX_64_RE = /^[0-9a-f]{64}$/;
|
|
132
|
+
function toHex(bytes) {
|
|
133
|
+
let out = "";
|
|
134
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
function nostrPubkeyToHex(value) {
|
|
138
|
+
const trimmed = value.trim().toLowerCase();
|
|
139
|
+
if (HEX_64_RE.test(trimmed)) return trimmed;
|
|
140
|
+
if (!trimmed.startsWith("npub1")) return null;
|
|
141
|
+
try {
|
|
142
|
+
const decoded = bech32.decode(trimmed, 1023);
|
|
143
|
+
if (decoded.prefix !== "npub") return null;
|
|
144
|
+
const bytes = bech32.fromWords(decoded.words);
|
|
145
|
+
return bytes.length === 32 ? toHex(Uint8Array.from(bytes)) : null;
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function nostrIdentifierForms(identifier) {
|
|
151
|
+
const hex = nostrPubkeyToHex(identifier);
|
|
152
|
+
if (!hex) return [identifier];
|
|
153
|
+
const bytes = Uint8Array.from(hex.match(/../g).map((b) => parseInt(b, 16)));
|
|
154
|
+
return [bech32.encode("npub", bech32.toWords(bytes), 1023), hex];
|
|
155
|
+
}
|
|
131
156
|
function createLogger(namespace) {
|
|
132
157
|
const shouldLog = (level) => {
|
|
133
158
|
return false;
|
|
@@ -449,6 +474,7 @@ async function queryByAddress(address, relays = DEFAULT_RELAYS) {
|
|
|
449
474
|
async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
|
|
450
475
|
const events = [];
|
|
451
476
|
const identityTag = `${protocol}:${identifier}`;
|
|
477
|
+
const identityTags = protocol === "nostr" ? nostrIdentifierForms(identifier).map((f) => `nostr:${f}`) : [identityTag];
|
|
452
478
|
log.info({ protocol, identifier, relayCount: relays.length }, "Querying relays for identity");
|
|
453
479
|
const deadlineAt = Date.now() + FANOUT_DEADLINE_MS;
|
|
454
480
|
const queryPromises = relays.map(async (relayUrl) => {
|
|
@@ -467,7 +493,7 @@ async function queryByIdentity(protocol, identifier, relays = DEFAULT_RELAYS) {
|
|
|
467
493
|
subscriptionId,
|
|
468
494
|
{
|
|
469
495
|
kinds: [30078],
|
|
470
|
-
"#i":
|
|
496
|
+
"#i": identityTags,
|
|
471
497
|
limit: 10
|
|
472
498
|
}
|
|
473
499
|
])
|
|
@@ -633,6 +659,12 @@ async function publishAttestation(options) {
|
|
|
633
659
|
);
|
|
634
660
|
return result;
|
|
635
661
|
}
|
|
662
|
+
function eventIndexesEnvelope(event, envelope) {
|
|
663
|
+
const d = event.tags.find((t) => t[0] === "d")?.[1];
|
|
664
|
+
const id = envelope.attestation_id;
|
|
665
|
+
if (d !== id && d !== `orangecheck:${id}`) return false;
|
|
666
|
+
return event.tags.some((t) => t[0] === "t" && t[1] === envelope.address);
|
|
667
|
+
}
|
|
636
668
|
async function discoverAttestations(options) {
|
|
637
669
|
const { attestationId, address, identity, relays = DEFAULT_RELAYS } = options;
|
|
638
670
|
let events = [];
|
|
@@ -651,7 +683,9 @@ async function discoverAttestations(options) {
|
|
|
651
683
|
const envelopes = [];
|
|
652
684
|
for (const event of events) {
|
|
653
685
|
const envelope = parseAttestationFromEvent(event);
|
|
654
|
-
if (envelope) {
|
|
686
|
+
if (envelope && !eventIndexesEnvelope(event, envelope)) {
|
|
687
|
+
log3.warn({ eventId: event.id }, "Skipping event whose index tags disagree with its envelope");
|
|
688
|
+
} else if (envelope) {
|
|
655
689
|
const isValid = await verifyAttestationEnvelope(envelope);
|
|
656
690
|
if (isValid) {
|
|
657
691
|
envelopes.push(envelope);
|
|
@@ -1135,18 +1169,15 @@ async function check(params) {
|
|
|
1135
1169
|
if (!addr && !id && !identity) {
|
|
1136
1170
|
return { ok: false, sats: 0, days: 0, score: 0, reasons: ["bad_request"] };
|
|
1137
1171
|
}
|
|
1138
|
-
let
|
|
1172
|
+
let found = [];
|
|
1139
1173
|
if (id) {
|
|
1140
|
-
|
|
1174
|
+
found = await discoverAttestations({ attestationId: id, relays });
|
|
1141
1175
|
} else if (addr) {
|
|
1142
|
-
|
|
1176
|
+
found = await getAttestationsForAddress(addr, relays);
|
|
1143
1177
|
} else if (identity) {
|
|
1144
|
-
|
|
1145
|
-
identity.protocol,
|
|
1146
|
-
identity.identifier,
|
|
1147
|
-
relays
|
|
1148
|
-
);
|
|
1178
|
+
found = await getAttestationsForIdentity(identity.protocol, identity.identifier, relays);
|
|
1149
1179
|
}
|
|
1180
|
+
const envelopes = found.filter((e) => signedMatch(e, { addr, id, identity }));
|
|
1150
1181
|
if (envelopes.length === 0) {
|
|
1151
1182
|
return { ok: false, sats: 0, days: 0, score: 0, reasons: ["not_found"] };
|
|
1152
1183
|
}
|
|
@@ -1155,6 +1186,7 @@ async function check(params) {
|
|
|
1155
1186
|
if (!env) {
|
|
1156
1187
|
return { ok: false, sats: 0, days: 0, score: 0, reasons: ["not_found"] };
|
|
1157
1188
|
}
|
|
1189
|
+
const shared = identity ? await backsOtherIdentity(env, identity, relays) : false;
|
|
1158
1190
|
const outcome = await verify2(
|
|
1159
1191
|
{
|
|
1160
1192
|
addr: env.address,
|
|
@@ -1169,9 +1201,10 @@ async function check(params) {
|
|
|
1169
1201
|
const score = outcome.metrics?.score ?? 0;
|
|
1170
1202
|
const reasons = [];
|
|
1171
1203
|
if (!outcome.ok) reasons.push(...outcome.codes);
|
|
1204
|
+
if (shared) reasons.push("stake_shared");
|
|
1172
1205
|
if (sats < minSats) reasons.push("below_min_sats");
|
|
1173
1206
|
if (days < minDays) reasons.push("below_min_days");
|
|
1174
|
-
const ok = outcome.ok && sats >= minSats && days >= minDays;
|
|
1207
|
+
const ok = outcome.ok && !shared && sats >= minSats && days >= minDays;
|
|
1175
1208
|
return {
|
|
1176
1209
|
ok,
|
|
1177
1210
|
sats,
|
|
@@ -1184,6 +1217,50 @@ async function check(params) {
|
|
|
1184
1217
|
...reasons.length ? { reasons } : {}
|
|
1185
1218
|
};
|
|
1186
1219
|
}
|
|
1220
|
+
function identityKey(b) {
|
|
1221
|
+
const protocol = b.protocol.trim().toLowerCase();
|
|
1222
|
+
if (protocol === "nostr") {
|
|
1223
|
+
const hex = nostrPubkeyToHex(b.identifier);
|
|
1224
|
+
return hex ? `nostr:${hex}` : null;
|
|
1225
|
+
}
|
|
1226
|
+
return `${protocol}:${b.identifier.trim()}`;
|
|
1227
|
+
}
|
|
1228
|
+
function signedIdentities(env) {
|
|
1229
|
+
try {
|
|
1230
|
+
return parseCanonicalMessage(env.message).core.identities;
|
|
1231
|
+
} catch {
|
|
1232
|
+
return [];
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
function signedMatch(env, q) {
|
|
1236
|
+
if (q.id) return env.attestation_id.toLowerCase() === q.id.trim().toLowerCase();
|
|
1237
|
+
let signedAddress;
|
|
1238
|
+
try {
|
|
1239
|
+
signedAddress = parseCanonicalMessage(env.message).core.address;
|
|
1240
|
+
} catch {
|
|
1241
|
+
return false;
|
|
1242
|
+
}
|
|
1243
|
+
if (signedAddress !== env.address) return false;
|
|
1244
|
+
if (q.addr) return signedAddress === q.addr;
|
|
1245
|
+
if (q.identity) {
|
|
1246
|
+
const want = identityKey(q.identity);
|
|
1247
|
+
return want !== null && signedIdentities(env).some((b) => identityKey(b) === want);
|
|
1248
|
+
}
|
|
1249
|
+
return false;
|
|
1250
|
+
}
|
|
1251
|
+
async function backsOtherIdentity(env, identity, relays) {
|
|
1252
|
+
const want = identityKey(identity);
|
|
1253
|
+
const protocol = identity.protocol.trim().toLowerCase();
|
|
1254
|
+
const siblings = await getAttestationsForAddress(env.address, relays);
|
|
1255
|
+
for (const e of [env, ...siblings]) {
|
|
1256
|
+
if (!signedMatch(e, { addr: env.address })) continue;
|
|
1257
|
+
for (const b of signedIdentities(e)) {
|
|
1258
|
+
if (b.protocol.trim().toLowerCase() !== protocol) continue;
|
|
1259
|
+
if (identityKey(b) !== want) return true;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
return false;
|
|
1263
|
+
}
|
|
1187
1264
|
var AUTH_HEADER = "orangecheck-auth";
|
|
1188
1265
|
var AUTH_ACK = "I authorize this session under the OrangeCheck auth challenge.";
|
|
1189
1266
|
function randomNonce() {
|
|
@@ -1638,8 +1715,8 @@ function getGistCreationUrl() {
|
|
|
1638
1715
|
return "https://gist.github.com/";
|
|
1639
1716
|
}
|
|
1640
1717
|
var log7 = createLogger("identity-verification/nostr");
|
|
1641
|
-
var
|
|
1642
|
-
function
|
|
1718
|
+
var HEX_64_RE2 = /^[0-9a-f]{64}$/;
|
|
1719
|
+
function toHex2(bytes) {
|
|
1643
1720
|
let out = "";
|
|
1644
1721
|
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
1645
1722
|
return out;
|
|
@@ -1653,22 +1730,9 @@ function fromHex(hex) {
|
|
|
1653
1730
|
return out;
|
|
1654
1731
|
}
|
|
1655
1732
|
function npubToHex(npub) {
|
|
1656
|
-
const
|
|
1657
|
-
if (
|
|
1658
|
-
|
|
1659
|
-
}
|
|
1660
|
-
if (!trimmed.toLowerCase().startsWith("npub1")) {
|
|
1661
|
-
throw new Error(`not an npub: ${trimmed.slice(0, 12)}\u2026`);
|
|
1662
|
-
}
|
|
1663
|
-
const decoded = bech32.decode(trimmed.toLowerCase(), 1023);
|
|
1664
|
-
if (decoded.prefix !== "npub") {
|
|
1665
|
-
throw new Error(`bech32 prefix mismatch: expected npub, got ${decoded.prefix}`);
|
|
1666
|
-
}
|
|
1667
|
-
const bytes = bech32.fromWords(decoded.words);
|
|
1668
|
-
if (bytes.length !== 32) {
|
|
1669
|
-
throw new Error(`invalid npub payload length: ${bytes.length}`);
|
|
1670
|
-
}
|
|
1671
|
-
return toHex(Uint8Array.from(bytes));
|
|
1733
|
+
const hex = nostrPubkeyToHex(npub);
|
|
1734
|
+
if (!hex) throw new Error(`not an npub: ${npub.trim().slice(0, 12)}\u2026`);
|
|
1735
|
+
return hex;
|
|
1672
1736
|
}
|
|
1673
1737
|
async function queryNostrEvents(options) {
|
|
1674
1738
|
const { authors, kinds, search, relays = DEFAULT_RELAYS, limit = 100 } = options;
|
|
@@ -1740,7 +1804,7 @@ async function queryNostrEvents(options) {
|
|
|
1740
1804
|
}
|
|
1741
1805
|
async function verifyNostrEventSignature(event) {
|
|
1742
1806
|
try {
|
|
1743
|
-
if (!event.id || !event.sig || !event.pubkey || typeof event.id !== "string" || typeof event.sig !== "string" || typeof event.pubkey !== "string" || event.sig.length !== 128 || !
|
|
1807
|
+
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)) {
|
|
1744
1808
|
return false;
|
|
1745
1809
|
}
|
|
1746
1810
|
const preimage = JSON.stringify([
|
|
@@ -1751,7 +1815,7 @@ async function verifyNostrEventSignature(event) {
|
|
|
1751
1815
|
event.tags ?? [],
|
|
1752
1816
|
event.content ?? ""
|
|
1753
1817
|
]);
|
|
1754
|
-
const computedId =
|
|
1818
|
+
const computedId = toHex2(sha256(new TextEncoder().encode(preimage)));
|
|
1755
1819
|
if (computedId !== event.id.toLowerCase()) {
|
|
1756
1820
|
log7.warn(
|
|
1757
1821
|
{ eventId: event.id, computedId },
|
|
@@ -2119,6 +2183,6 @@ var STATUS_META = {
|
|
|
2119
2183
|
}
|
|
2120
2184
|
};
|
|
2121
2185
|
|
|
2122
|
-
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, parseAttestationFromEvent, parseIdentities, publishAttestation, publishToRelays, queryByAddress, queryByAttestationId, queryByIdentity, requiresManualVerification, signNostrEvent, supportsAutomaticVerification, verify2 as verify, verifyAllIdentities, verifyAttestationById, verifyChallenge, verifyDnsIdentity, verifyGitHubIdentity, verifyIdentity, verifyNostrIdentity, verifyTwitterIdentity };
|
|
2186
|
+
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, requiresManualVerification, signNostrEvent, supportsAutomaticVerification, verify2 as verify, verifyAllIdentities, verifyAttestationById, verifyChallenge, verifyDnsIdentity, verifyGitHubIdentity, verifyIdentity, verifyNostrIdentity, verifyTwitterIdentity };
|
|
2123
2187
|
//# sourceMappingURL=index.mjs.map
|
|
2124
2188
|
//# sourceMappingURL=index.mjs.map
|