@orangecheck/sdk 1.2.0 → 1.3.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.d.mts +1 -68
- package/dist/index.d.ts +1 -68
- package/dist/index.js +5 -251
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -249
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Buffer
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
2
|
import { Verifier } from 'bip322-js';
|
|
3
3
|
import * as bitcoinMessage from 'bitcoinjs-message';
|
|
4
4
|
import { schnorr } from '@noble/curves/secp256k1.js';
|
|
@@ -712,7 +712,7 @@ function looksLikeHex(s) {
|
|
|
712
712
|
}
|
|
713
713
|
function toBase64Maybe(sig) {
|
|
714
714
|
if (looksLikeHex(sig)) {
|
|
715
|
-
return Buffer
|
|
715
|
+
return Buffer.from(sig, "hex").toString("base64");
|
|
716
716
|
}
|
|
717
717
|
return sig;
|
|
718
718
|
}
|
|
@@ -1155,246 +1155,6 @@ async function check(params) {
|
|
|
1155
1155
|
...reasons.length ? { reasons } : {}
|
|
1156
1156
|
};
|
|
1157
1157
|
}
|
|
1158
|
-
var HEADER = "orangecheck-binding";
|
|
1159
|
-
var ACK = "I attest the keys named in this message are one principal.";
|
|
1160
|
-
var BINDING_KIND = 30079;
|
|
1161
|
-
function toHex(bytes) {
|
|
1162
|
-
let out = "";
|
|
1163
|
-
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
1164
|
-
return out;
|
|
1165
|
-
}
|
|
1166
|
-
function fromHex(hex) {
|
|
1167
|
-
const clean = hex.toLowerCase();
|
|
1168
|
-
if (clean.length % 2 !== 0 || /[^0-9a-f]/.test(clean)) {
|
|
1169
|
-
throw new Error("invalid hex");
|
|
1170
|
-
}
|
|
1171
|
-
const out = new Uint8Array(clean.length / 2);
|
|
1172
|
-
for (let i = 0; i < out.length; i++) out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
1173
|
-
return out;
|
|
1174
|
-
}
|
|
1175
|
-
function assertPrintableAscii(field, value) {
|
|
1176
|
-
for (let i = 0; i < value.length; i++) {
|
|
1177
|
-
const c = value.charCodeAt(i);
|
|
1178
|
-
if (c < 32 || c > 126) {
|
|
1179
|
-
throw new Error(
|
|
1180
|
-
`binding ${field} contains an unsafe character (0x${c.toString(16)}) \u2014 line-smuggling defense (SPEC-BINDING \xA73.5)`
|
|
1181
|
-
);
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
function buildBindingMessage(input) {
|
|
1186
|
-
assertPrintableAscii("principal", input.principal);
|
|
1187
|
-
assertPrintableAscii("btc", input.btc);
|
|
1188
|
-
assertPrintableAscii("nostr", input.nostr);
|
|
1189
|
-
assertPrintableAscii("nonce", input.nonce);
|
|
1190
|
-
assertPrintableAscii("issued_at", input.issued_at);
|
|
1191
|
-
const core = [
|
|
1192
|
-
HEADER,
|
|
1193
|
-
"v: 1",
|
|
1194
|
-
`principal: ${input.principal}`,
|
|
1195
|
-
`btc: ${input.btc}`,
|
|
1196
|
-
`nostr: ${input.nostr}`,
|
|
1197
|
-
`nonce: ${input.nonce}`,
|
|
1198
|
-
`issued_at: ${input.issued_at}`,
|
|
1199
|
-
`ack: ${ACK}`
|
|
1200
|
-
];
|
|
1201
|
-
const ext = [];
|
|
1202
|
-
if (input.extensions) {
|
|
1203
|
-
for (const key of Object.keys(input.extensions).sort()) {
|
|
1204
|
-
const value = input.extensions[key];
|
|
1205
|
-
if (value == null) continue;
|
|
1206
|
-
if (!/^[a-z]+$/.test(key)) {
|
|
1207
|
-
throw new Error(`binding extension key "${key}" must be lowercase ASCII`);
|
|
1208
|
-
}
|
|
1209
|
-
assertPrintableAscii(`extension ${key}`, value);
|
|
1210
|
-
ext.push(`${key}: ${value}`);
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
return [...core, ...ext].join("\n") + "\n";
|
|
1214
|
-
}
|
|
1215
|
-
function bindingId(message) {
|
|
1216
|
-
return toHex(sha256(new TextEncoder().encode(message)));
|
|
1217
|
-
}
|
|
1218
|
-
function linePrefix(line, prefix) {
|
|
1219
|
-
if (typeof line !== "string" || !line.startsWith(prefix)) return null;
|
|
1220
|
-
return line.slice(prefix.length);
|
|
1221
|
-
}
|
|
1222
|
-
function nostrEventId(ev) {
|
|
1223
|
-
const serialized = JSON.stringify([
|
|
1224
|
-
0,
|
|
1225
|
-
ev.pubkey,
|
|
1226
|
-
ev.created_at,
|
|
1227
|
-
ev.kind,
|
|
1228
|
-
ev.tags,
|
|
1229
|
-
ev.content
|
|
1230
|
-
]);
|
|
1231
|
-
return toHex(sha256(new TextEncoder().encode(serialized)));
|
|
1232
|
-
}
|
|
1233
|
-
function npubToXOnlyHex(npub) {
|
|
1234
|
-
const decoded = bech32.decode(npub, 90);
|
|
1235
|
-
if (decoded.prefix !== "npub") throw new Error("not an npub");
|
|
1236
|
-
const bytes = Uint8Array.from(bech32.fromWords(decoded.words));
|
|
1237
|
-
if (bytes.length !== 32) throw new Error("npub payload is not 32 bytes");
|
|
1238
|
-
return toHex(bytes);
|
|
1239
|
-
}
|
|
1240
|
-
function verifyBinding(envelope) {
|
|
1241
|
-
const fail = (status) => ({
|
|
1242
|
-
valid: false,
|
|
1243
|
-
status
|
|
1244
|
-
});
|
|
1245
|
-
try {
|
|
1246
|
-
const msg = envelope.message;
|
|
1247
|
-
if (typeof msg !== "string" || msg.length === 0) return fail("malformed");
|
|
1248
|
-
const lines = msg.split("\n");
|
|
1249
|
-
if ((lines[0] ?? "") !== HEADER) return fail("bad_header");
|
|
1250
|
-
if (lines.length < 9 || lines[lines.length - 1] !== "") return fail("malformed");
|
|
1251
|
-
const body = lines.slice(0, -1);
|
|
1252
|
-
if (body.length < 8) return fail("malformed");
|
|
1253
|
-
if (body[1] !== "v: 1") return fail("malformed");
|
|
1254
|
-
if (body[7] !== `ack: ${ACK}`) return fail("bad_ack");
|
|
1255
|
-
const principal = linePrefix(body[2], "principal: ");
|
|
1256
|
-
const btc = linePrefix(body[3], "btc: ");
|
|
1257
|
-
const nostr = linePrefix(body[4], "nostr: ");
|
|
1258
|
-
const nonce = linePrefix(body[5], "nonce: ");
|
|
1259
|
-
const issuedAt = linePrefix(body[6], "issued_at: ");
|
|
1260
|
-
if (principal == null || btc == null || nostr == null || nonce == null || issuedAt == null) {
|
|
1261
|
-
return fail("malformed");
|
|
1262
|
-
}
|
|
1263
|
-
for (const value of [principal, btc, nostr, nonce, issuedAt]) {
|
|
1264
|
-
for (let i = 0; i < value.length; i++) {
|
|
1265
|
-
const c = value.charCodeAt(i);
|
|
1266
|
-
if (c < 32 || c > 126) return fail("malformed");
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
if (!/^did:oc:[0-9a-f]{32}$/.test(principal)) return fail("bad_principal");
|
|
1270
|
-
if (!/^[0-9a-f]{32}$/.test(nonce)) return fail("malformed");
|
|
1271
|
-
const ext = {};
|
|
1272
|
-
let prevKey = "";
|
|
1273
|
-
for (const line of body.slice(8)) {
|
|
1274
|
-
const idx = line.indexOf(": ");
|
|
1275
|
-
if (idx <= 0) return fail("malformed");
|
|
1276
|
-
const key = line.slice(0, idx);
|
|
1277
|
-
const value = line.slice(idx + 2);
|
|
1278
|
-
if (!/^[a-z]+$/.test(key)) return fail("malformed");
|
|
1279
|
-
if (key <= prevKey) return fail("ext_unsorted");
|
|
1280
|
-
prevKey = key;
|
|
1281
|
-
ext[key] = value;
|
|
1282
|
-
}
|
|
1283
|
-
const bid = bindingId(msg);
|
|
1284
|
-
if (envelope.binding_id && envelope.binding_id.toLowerCase() !== bid) {
|
|
1285
|
-
return fail("id_mismatch");
|
|
1286
|
-
}
|
|
1287
|
-
if (envelope.btc && envelope.btc !== btc) return fail("field_mismatch");
|
|
1288
|
-
if (envelope.nostr && envelope.nostr !== nostr) return fail("field_mismatch");
|
|
1289
|
-
if (envelope.principal && envelope.principal !== principal) return fail("field_mismatch");
|
|
1290
|
-
if (ext.expires) {
|
|
1291
|
-
const t = Date.parse(ext.expires);
|
|
1292
|
-
if (!Number.isNaN(t) && Date.now() > t) return fail("expired");
|
|
1293
|
-
}
|
|
1294
|
-
if (typeof envelope.btc_signature !== "string" || !envelope.btc_signature) {
|
|
1295
|
-
return fail("btc_sig_invalid");
|
|
1296
|
-
}
|
|
1297
|
-
const scheme = envelope.btc_scheme ?? "bip322";
|
|
1298
|
-
let btcOk = false;
|
|
1299
|
-
try {
|
|
1300
|
-
if (scheme === "bip322") {
|
|
1301
|
-
btcOk = Verifier.verifySignature(btc, msg, envelope.btc_signature);
|
|
1302
|
-
} else if (scheme === "legacy") {
|
|
1303
|
-
btcOk = bitcoinMessage.verify(msg, btc, envelope.btc_signature);
|
|
1304
|
-
} else {
|
|
1305
|
-
return fail("bad_scheme");
|
|
1306
|
-
}
|
|
1307
|
-
} catch {
|
|
1308
|
-
btcOk = false;
|
|
1309
|
-
}
|
|
1310
|
-
if (!btcOk) return fail("btc_sig_invalid");
|
|
1311
|
-
const ev = envelope.nostr_event;
|
|
1312
|
-
if (!ev || ev.kind !== BINDING_KIND || typeof ev.id !== "string") return fail("malformed");
|
|
1313
|
-
if (nostrEventId(ev) !== ev.id.toLowerCase()) return fail("nostr_id_invalid");
|
|
1314
|
-
let nostrSigOk = false;
|
|
1315
|
-
try {
|
|
1316
|
-
nostrSigOk = schnorr.verify(fromHex(ev.sig), fromHex(ev.id), fromHex(ev.pubkey));
|
|
1317
|
-
} catch {
|
|
1318
|
-
nostrSigOk = false;
|
|
1319
|
-
}
|
|
1320
|
-
if (!nostrSigOk) return fail("nostr_sig_invalid");
|
|
1321
|
-
let nostrXOnly;
|
|
1322
|
-
try {
|
|
1323
|
-
nostrXOnly = npubToXOnlyHex(nostr);
|
|
1324
|
-
} catch {
|
|
1325
|
-
return fail("nostr_key_mismatch");
|
|
1326
|
-
}
|
|
1327
|
-
if (nostrXOnly !== ev.pubkey.toLowerCase()) return fail("nostr_key_mismatch");
|
|
1328
|
-
let content;
|
|
1329
|
-
try {
|
|
1330
|
-
content = JSON.parse(ev.content);
|
|
1331
|
-
} catch {
|
|
1332
|
-
return fail("message_mismatch");
|
|
1333
|
-
}
|
|
1334
|
-
if (content.message !== msg) return fail("message_mismatch");
|
|
1335
|
-
if (content.btc_signature !== envelope.btc_signature) return fail("message_mismatch");
|
|
1336
|
-
return { valid: true, status: "binding_ok", binding_id: bid, principal, btc, nostr };
|
|
1337
|
-
} catch {
|
|
1338
|
-
return fail("malformed");
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
function xOnlyHexToNpub(hex) {
|
|
1342
|
-
const bytes = fromHex(hex);
|
|
1343
|
-
if (bytes.length !== 32) throw new Error("x-only Nostr key must be 32 bytes");
|
|
1344
|
-
return bech32.encode("npub", bech32.toWords(bytes), 90);
|
|
1345
|
-
}
|
|
1346
|
-
function buildBindingEventTemplate(params) {
|
|
1347
|
-
const id = bindingId(params.message);
|
|
1348
|
-
return {
|
|
1349
|
-
kind: BINDING_KIND,
|
|
1350
|
-
pubkey: params.nostrPubkeyHex.toLowerCase(),
|
|
1351
|
-
created_at: params.createdAt ?? Math.floor(Date.now() / 1e3),
|
|
1352
|
-
tags: [
|
|
1353
|
-
["d", `oc-attest-binding:${id}`],
|
|
1354
|
-
["btc", params.btc],
|
|
1355
|
-
["oc", "binding-attestation"],
|
|
1356
|
-
["v", "1"]
|
|
1357
|
-
],
|
|
1358
|
-
content: JSON.stringify({ message: params.message, btc_signature: params.btcSignature })
|
|
1359
|
-
};
|
|
1360
|
-
}
|
|
1361
|
-
function parseBindingFields(message) {
|
|
1362
|
-
const body = message.split("\n");
|
|
1363
|
-
const at = (i, prefix) => {
|
|
1364
|
-
const line = body[i];
|
|
1365
|
-
if (typeof line !== "string" || !line.startsWith(prefix)) {
|
|
1366
|
-
throw new Error(`binding message line ${i + 1} is malformed`);
|
|
1367
|
-
}
|
|
1368
|
-
return line.slice(prefix.length);
|
|
1369
|
-
};
|
|
1370
|
-
return {
|
|
1371
|
-
principal: at(2, "principal: "),
|
|
1372
|
-
btc: at(3, "btc: "),
|
|
1373
|
-
nostr: at(4, "nostr: ")
|
|
1374
|
-
};
|
|
1375
|
-
}
|
|
1376
|
-
function toBase64Url(s) {
|
|
1377
|
-
const bytes = new TextEncoder().encode(s);
|
|
1378
|
-
let bin = "";
|
|
1379
|
-
for (const b of bytes) bin += String.fromCharCode(b);
|
|
1380
|
-
const b64 = typeof btoa !== "undefined" ? btoa(bin) : Buffer.from(bytes).toString("base64");
|
|
1381
|
-
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1382
|
-
}
|
|
1383
|
-
function assembleBindingEnvelope(params) {
|
|
1384
|
-
const { principal, btc, nostr } = parseBindingFields(params.message);
|
|
1385
|
-
return {
|
|
1386
|
-
binding_id: bindingId(params.message),
|
|
1387
|
-
v: 1,
|
|
1388
|
-
principal,
|
|
1389
|
-
btc,
|
|
1390
|
-
nostr,
|
|
1391
|
-
message: params.message,
|
|
1392
|
-
message_b64url: toBase64Url(params.message),
|
|
1393
|
-
btc_signature: params.btcSignature,
|
|
1394
|
-
btc_scheme: params.btcScheme ?? "bip322",
|
|
1395
|
-
nostr_event: params.nostrEvent
|
|
1396
|
-
};
|
|
1397
|
-
}
|
|
1398
1158
|
var AUTH_HEADER = "orangecheck-auth";
|
|
1399
1159
|
var AUTH_ACK = "I authorize this session under the OrangeCheck auth challenge.";
|
|
1400
1160
|
function randomNonce() {
|
|
@@ -1512,7 +1272,7 @@ async function verifyChallenge(opts) {
|
|
|
1512
1272
|
let sigValid = false;
|
|
1513
1273
|
try {
|
|
1514
1274
|
if (scheme === "bip322") {
|
|
1515
|
-
const sigBase64 = /^[0-9a-fA-F]+$/.test(opts.signature) && opts.signature.length % 2 === 0 ? Buffer
|
|
1275
|
+
const sigBase64 = /^[0-9a-fA-F]+$/.test(opts.signature) && opts.signature.length % 2 === 0 ? Buffer.from(opts.signature, "hex").toString("base64") : opts.signature;
|
|
1516
1276
|
sigValid = Verifier.verifySignature(address, opts.message, sigBase64);
|
|
1517
1277
|
} else if (scheme === "legacy") {
|
|
1518
1278
|
if (!/^[1m][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(address)) {
|
|
@@ -1850,12 +1610,12 @@ function getGistCreationUrl() {
|
|
|
1850
1610
|
}
|
|
1851
1611
|
var log7 = createLogger("identity-verification/nostr");
|
|
1852
1612
|
var HEX_64_RE = /^[0-9a-f]{64}$/;
|
|
1853
|
-
function
|
|
1613
|
+
function toHex(bytes) {
|
|
1854
1614
|
let out = "";
|
|
1855
1615
|
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
1856
1616
|
return out;
|
|
1857
1617
|
}
|
|
1858
|
-
function
|
|
1618
|
+
function fromHex(hex) {
|
|
1859
1619
|
if (hex.length % 2 !== 0) throw new Error("odd-length hex");
|
|
1860
1620
|
const out = new Uint8Array(hex.length / 2);
|
|
1861
1621
|
for (let i = 0; i < out.length; i++) {
|
|
@@ -1879,7 +1639,7 @@ function npubToHex(npub) {
|
|
|
1879
1639
|
if (bytes.length !== 32) {
|
|
1880
1640
|
throw new Error(`invalid npub payload length: ${bytes.length}`);
|
|
1881
1641
|
}
|
|
1882
|
-
return
|
|
1642
|
+
return toHex(Uint8Array.from(bytes));
|
|
1883
1643
|
}
|
|
1884
1644
|
async function queryNostrEvents(options) {
|
|
1885
1645
|
const { authors, kinds, search, relays = DEFAULT_RELAYS, limit = 100 } = options;
|
|
@@ -1962,7 +1722,7 @@ async function verifyNostrEventSignature(event) {
|
|
|
1962
1722
|
event.tags ?? [],
|
|
1963
1723
|
event.content ?? ""
|
|
1964
1724
|
]);
|
|
1965
|
-
const computedId =
|
|
1725
|
+
const computedId = toHex(sha256(new TextEncoder().encode(preimage)));
|
|
1966
1726
|
if (computedId !== event.id.toLowerCase()) {
|
|
1967
1727
|
log7.warn(
|
|
1968
1728
|
{ eventId: event.id, computedId },
|
|
@@ -1970,7 +1730,7 @@ async function verifyNostrEventSignature(event) {
|
|
|
1970
1730
|
);
|
|
1971
1731
|
return false;
|
|
1972
1732
|
}
|
|
1973
|
-
return schnorr.verify(
|
|
1733
|
+
return schnorr.verify(fromHex(event.sig), fromHex(event.id), fromHex(event.pubkey));
|
|
1974
1734
|
} catch (err) {
|
|
1975
1735
|
log7.warn({ err }, "schnorr verify threw");
|
|
1976
1736
|
return false;
|
|
@@ -2330,6 +2090,6 @@ var STATUS_META = {
|
|
|
2330
2090
|
}
|
|
2331
2091
|
};
|
|
2332
2092
|
|
|
2333
|
-
export { DEFAULT_RELAYS, STATUS_META,
|
|
2093
|
+
export { DEFAULT_RELAYS, 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 };
|
|
2334
2094
|
//# sourceMappingURL=index.mjs.map
|
|
2335
2095
|
//# sourceMappingURL=index.mjs.map
|