@orangecheck/sdk 1.0.0 → 1.2.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 +4 -0
- package/dist/index.d.mts +68 -1
- package/dist/index.d.ts +68 -1
- package/dist/index.js +251 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +249 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# `@orangecheck/sdk`
|
|
2
2
|
|
|
3
|
+
> **Full reference:** [docs.ochk.io/sdk/sdk](https://docs.ochk.io/sdk/sdk) — auto-generated from the TypeScript source on every release.
|
|
4
|
+
> Hand-written prose below is the high-level overview; the docs site is the source of truth for every export, type, and signature.
|
|
5
|
+
|
|
6
|
+
|
|
3
7
|
**Proof of Bitcoin stake for the open web.**
|
|
4
8
|
|
|
5
9
|
A sybil-resistance primitive for forums, Nostr relays, airdrop gates, DAO votes, and any open platform that needs to filter bots without KYC. Bitcoin UTXOs are the only credible signal of commitment: honest users pay nothing, attackers pay real opportunity cost.
|
package/dist/index.d.mts
CHANGED
|
@@ -33,6 +33,73 @@ interface CheckResult {
|
|
|
33
33
|
}
|
|
34
34
|
declare function check(params: CheckParams): Promise<CheckResult>;
|
|
35
35
|
|
|
36
|
+
declare const BINDING_KIND = 30079;
|
|
37
|
+
interface BindingInput {
|
|
38
|
+
principal: string;
|
|
39
|
+
btc: string;
|
|
40
|
+
nostr: string;
|
|
41
|
+
nonce: string;
|
|
42
|
+
issued_at: string;
|
|
43
|
+
extensions?: Record<string, string>;
|
|
44
|
+
}
|
|
45
|
+
interface BindingNostrEvent {
|
|
46
|
+
id: string;
|
|
47
|
+
pubkey: string;
|
|
48
|
+
kind: number;
|
|
49
|
+
created_at: number;
|
|
50
|
+
tags: string[][];
|
|
51
|
+
content: string;
|
|
52
|
+
sig: string;
|
|
53
|
+
}
|
|
54
|
+
interface BindingEnvelope {
|
|
55
|
+
binding_id?: string;
|
|
56
|
+
v?: number;
|
|
57
|
+
principal?: string;
|
|
58
|
+
btc?: string;
|
|
59
|
+
nostr?: string;
|
|
60
|
+
message: string;
|
|
61
|
+
message_b64url?: string;
|
|
62
|
+
btc_signature?: string;
|
|
63
|
+
btc_scheme?: 'bip322' | 'legacy';
|
|
64
|
+
nostr_event?: BindingNostrEvent;
|
|
65
|
+
}
|
|
66
|
+
type BindingStatus = 'binding_ok' | 'malformed' | 'bad_header' | 'bad_ack' | 'bad_principal' | 'ext_unsorted' | 'id_mismatch' | 'field_mismatch' | 'expired' | 'network' | 'bad_scheme' | 'btc_sig_invalid' | 'nostr_id_invalid' | 'nostr_sig_invalid' | 'nostr_key_mismatch' | 'message_mismatch';
|
|
67
|
+
type BindingVerifyResult = {
|
|
68
|
+
valid: true;
|
|
69
|
+
status: 'binding_ok';
|
|
70
|
+
binding_id: string;
|
|
71
|
+
principal: string;
|
|
72
|
+
btc: string;
|
|
73
|
+
nostr: string;
|
|
74
|
+
} | {
|
|
75
|
+
valid: false;
|
|
76
|
+
status: Exclude<BindingStatus, 'binding_ok'>;
|
|
77
|
+
};
|
|
78
|
+
declare function buildBindingMessage(input: BindingInput): string;
|
|
79
|
+
declare function bindingId(message: string): string;
|
|
80
|
+
declare function verifyBinding(envelope: BindingEnvelope): BindingVerifyResult;
|
|
81
|
+
declare function xOnlyHexToNpub(hex: string): string;
|
|
82
|
+
interface BindingEventTemplate {
|
|
83
|
+
kind: typeof BINDING_KIND;
|
|
84
|
+
pubkey: string;
|
|
85
|
+
created_at: number;
|
|
86
|
+
tags: string[][];
|
|
87
|
+
content: string;
|
|
88
|
+
}
|
|
89
|
+
declare function buildBindingEventTemplate(params: {
|
|
90
|
+
message: string;
|
|
91
|
+
btcSignature: string;
|
|
92
|
+
nostrPubkeyHex: string;
|
|
93
|
+
btc: string;
|
|
94
|
+
createdAt?: number;
|
|
95
|
+
}): BindingEventTemplate;
|
|
96
|
+
declare function assembleBindingEnvelope(params: {
|
|
97
|
+
message: string;
|
|
98
|
+
btcSignature: string;
|
|
99
|
+
btcScheme?: 'bip322' | 'legacy';
|
|
100
|
+
nostrEvent: BindingNostrEvent;
|
|
101
|
+
}): BindingEnvelope;
|
|
102
|
+
|
|
36
103
|
declare function isNip07Available(): boolean;
|
|
37
104
|
declare function getNostrPublicKey(): Promise<string | null>;
|
|
38
105
|
declare function signNostrEvent(event: Omit<NostrEvent, 'id' | 'sig'>): Promise<NostrEvent | null>;
|
|
@@ -50,4 +117,4 @@ interface StatusMeta {
|
|
|
50
117
|
}
|
|
51
118
|
declare const STATUS_META: Record<StatusCode, StatusMeta>;
|
|
52
119
|
|
|
53
|
-
export { type CheckParams, type CheckResult, IdentityBinding, Network, NostrEvent, STATUS_META, type Severity, StatusCode, type StatusMeta, VerifyOptions, check, getNip07Info, getNostrPublicKey, isNip07Available, signNostrEvent };
|
|
120
|
+
export { type BindingEnvelope, type BindingEventTemplate, type BindingInput, type BindingNostrEvent, type BindingStatus, type BindingVerifyResult, type CheckParams, type CheckResult, IdentityBinding, Network, NostrEvent, STATUS_META, type Severity, StatusCode, type StatusMeta, VerifyOptions, assembleBindingEnvelope, bindingId, buildBindingEventTemplate, buildBindingMessage, check, getNip07Info, getNostrPublicKey, isNip07Available, signNostrEvent, verifyBinding, xOnlyHexToNpub };
|
package/dist/index.d.ts
CHANGED
|
@@ -33,6 +33,73 @@ interface CheckResult {
|
|
|
33
33
|
}
|
|
34
34
|
declare function check(params: CheckParams): Promise<CheckResult>;
|
|
35
35
|
|
|
36
|
+
declare const BINDING_KIND = 30079;
|
|
37
|
+
interface BindingInput {
|
|
38
|
+
principal: string;
|
|
39
|
+
btc: string;
|
|
40
|
+
nostr: string;
|
|
41
|
+
nonce: string;
|
|
42
|
+
issued_at: string;
|
|
43
|
+
extensions?: Record<string, string>;
|
|
44
|
+
}
|
|
45
|
+
interface BindingNostrEvent {
|
|
46
|
+
id: string;
|
|
47
|
+
pubkey: string;
|
|
48
|
+
kind: number;
|
|
49
|
+
created_at: number;
|
|
50
|
+
tags: string[][];
|
|
51
|
+
content: string;
|
|
52
|
+
sig: string;
|
|
53
|
+
}
|
|
54
|
+
interface BindingEnvelope {
|
|
55
|
+
binding_id?: string;
|
|
56
|
+
v?: number;
|
|
57
|
+
principal?: string;
|
|
58
|
+
btc?: string;
|
|
59
|
+
nostr?: string;
|
|
60
|
+
message: string;
|
|
61
|
+
message_b64url?: string;
|
|
62
|
+
btc_signature?: string;
|
|
63
|
+
btc_scheme?: 'bip322' | 'legacy';
|
|
64
|
+
nostr_event?: BindingNostrEvent;
|
|
65
|
+
}
|
|
66
|
+
type BindingStatus = 'binding_ok' | 'malformed' | 'bad_header' | 'bad_ack' | 'bad_principal' | 'ext_unsorted' | 'id_mismatch' | 'field_mismatch' | 'expired' | 'network' | 'bad_scheme' | 'btc_sig_invalid' | 'nostr_id_invalid' | 'nostr_sig_invalid' | 'nostr_key_mismatch' | 'message_mismatch';
|
|
67
|
+
type BindingVerifyResult = {
|
|
68
|
+
valid: true;
|
|
69
|
+
status: 'binding_ok';
|
|
70
|
+
binding_id: string;
|
|
71
|
+
principal: string;
|
|
72
|
+
btc: string;
|
|
73
|
+
nostr: string;
|
|
74
|
+
} | {
|
|
75
|
+
valid: false;
|
|
76
|
+
status: Exclude<BindingStatus, 'binding_ok'>;
|
|
77
|
+
};
|
|
78
|
+
declare function buildBindingMessage(input: BindingInput): string;
|
|
79
|
+
declare function bindingId(message: string): string;
|
|
80
|
+
declare function verifyBinding(envelope: BindingEnvelope): BindingVerifyResult;
|
|
81
|
+
declare function xOnlyHexToNpub(hex: string): string;
|
|
82
|
+
interface BindingEventTemplate {
|
|
83
|
+
kind: typeof BINDING_KIND;
|
|
84
|
+
pubkey: string;
|
|
85
|
+
created_at: number;
|
|
86
|
+
tags: string[][];
|
|
87
|
+
content: string;
|
|
88
|
+
}
|
|
89
|
+
declare function buildBindingEventTemplate(params: {
|
|
90
|
+
message: string;
|
|
91
|
+
btcSignature: string;
|
|
92
|
+
nostrPubkeyHex: string;
|
|
93
|
+
btc: string;
|
|
94
|
+
createdAt?: number;
|
|
95
|
+
}): BindingEventTemplate;
|
|
96
|
+
declare function assembleBindingEnvelope(params: {
|
|
97
|
+
message: string;
|
|
98
|
+
btcSignature: string;
|
|
99
|
+
btcScheme?: 'bip322' | 'legacy';
|
|
100
|
+
nostrEvent: BindingNostrEvent;
|
|
101
|
+
}): BindingEnvelope;
|
|
102
|
+
|
|
36
103
|
declare function isNip07Available(): boolean;
|
|
37
104
|
declare function getNostrPublicKey(): Promise<string | null>;
|
|
38
105
|
declare function signNostrEvent(event: Omit<NostrEvent, 'id' | 'sig'>): Promise<NostrEvent | null>;
|
|
@@ -50,4 +117,4 @@ interface StatusMeta {
|
|
|
50
117
|
}
|
|
51
118
|
declare const STATUS_META: Record<StatusCode, StatusMeta>;
|
|
52
119
|
|
|
53
|
-
export { type CheckParams, type CheckResult, IdentityBinding, Network, NostrEvent, STATUS_META, type Severity, StatusCode, type StatusMeta, VerifyOptions, check, getNip07Info, getNostrPublicKey, isNip07Available, signNostrEvent };
|
|
120
|
+
export { type BindingEnvelope, type BindingEventTemplate, type BindingInput, type BindingNostrEvent, type BindingStatus, type BindingVerifyResult, type CheckParams, type CheckResult, IdentityBinding, Network, NostrEvent, STATUS_META, type Severity, StatusCode, type StatusMeta, VerifyOptions, assembleBindingEnvelope, bindingId, buildBindingEventTemplate, buildBindingMessage, check, getNip07Info, getNostrPublicKey, isNip07Available, signNostrEvent, verifyBinding, xOnlyHexToNpub };
|
package/dist/index.js
CHANGED
|
@@ -1177,6 +1177,246 @@ async function check(params) {
|
|
|
1177
1177
|
...reasons.length ? { reasons } : {}
|
|
1178
1178
|
};
|
|
1179
1179
|
}
|
|
1180
|
+
var HEADER = "orangecheck-binding";
|
|
1181
|
+
var ACK = "I attest the keys named in this message are one principal.";
|
|
1182
|
+
var BINDING_KIND = 30079;
|
|
1183
|
+
function toHex(bytes) {
|
|
1184
|
+
let out = "";
|
|
1185
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
1186
|
+
return out;
|
|
1187
|
+
}
|
|
1188
|
+
function fromHex(hex) {
|
|
1189
|
+
const clean = hex.toLowerCase();
|
|
1190
|
+
if (clean.length % 2 !== 0 || /[^0-9a-f]/.test(clean)) {
|
|
1191
|
+
throw new Error("invalid hex");
|
|
1192
|
+
}
|
|
1193
|
+
const out = new Uint8Array(clean.length / 2);
|
|
1194
|
+
for (let i = 0; i < out.length; i++) out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
1195
|
+
return out;
|
|
1196
|
+
}
|
|
1197
|
+
function assertPrintableAscii(field, value) {
|
|
1198
|
+
for (let i = 0; i < value.length; i++) {
|
|
1199
|
+
const c = value.charCodeAt(i);
|
|
1200
|
+
if (c < 32 || c > 126) {
|
|
1201
|
+
throw new Error(
|
|
1202
|
+
`binding ${field} contains an unsafe character (0x${c.toString(16)}) \u2014 line-smuggling defense (SPEC-BINDING \xA73.5)`
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
function buildBindingMessage(input) {
|
|
1208
|
+
assertPrintableAscii("principal", input.principal);
|
|
1209
|
+
assertPrintableAscii("btc", input.btc);
|
|
1210
|
+
assertPrintableAscii("nostr", input.nostr);
|
|
1211
|
+
assertPrintableAscii("nonce", input.nonce);
|
|
1212
|
+
assertPrintableAscii("issued_at", input.issued_at);
|
|
1213
|
+
const core = [
|
|
1214
|
+
HEADER,
|
|
1215
|
+
"v: 1",
|
|
1216
|
+
`principal: ${input.principal}`,
|
|
1217
|
+
`btc: ${input.btc}`,
|
|
1218
|
+
`nostr: ${input.nostr}`,
|
|
1219
|
+
`nonce: ${input.nonce}`,
|
|
1220
|
+
`issued_at: ${input.issued_at}`,
|
|
1221
|
+
`ack: ${ACK}`
|
|
1222
|
+
];
|
|
1223
|
+
const ext = [];
|
|
1224
|
+
if (input.extensions) {
|
|
1225
|
+
for (const key of Object.keys(input.extensions).sort()) {
|
|
1226
|
+
const value = input.extensions[key];
|
|
1227
|
+
if (value == null) continue;
|
|
1228
|
+
if (!/^[a-z]+$/.test(key)) {
|
|
1229
|
+
throw new Error(`binding extension key "${key}" must be lowercase ASCII`);
|
|
1230
|
+
}
|
|
1231
|
+
assertPrintableAscii(`extension ${key}`, value);
|
|
1232
|
+
ext.push(`${key}: ${value}`);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return [...core, ...ext].join("\n") + "\n";
|
|
1236
|
+
}
|
|
1237
|
+
function bindingId(message) {
|
|
1238
|
+
return toHex(sha2_js.sha256(new TextEncoder().encode(message)));
|
|
1239
|
+
}
|
|
1240
|
+
function linePrefix(line, prefix) {
|
|
1241
|
+
if (typeof line !== "string" || !line.startsWith(prefix)) return null;
|
|
1242
|
+
return line.slice(prefix.length);
|
|
1243
|
+
}
|
|
1244
|
+
function nostrEventId(ev) {
|
|
1245
|
+
const serialized = JSON.stringify([
|
|
1246
|
+
0,
|
|
1247
|
+
ev.pubkey,
|
|
1248
|
+
ev.created_at,
|
|
1249
|
+
ev.kind,
|
|
1250
|
+
ev.tags,
|
|
1251
|
+
ev.content
|
|
1252
|
+
]);
|
|
1253
|
+
return toHex(sha2_js.sha256(new TextEncoder().encode(serialized)));
|
|
1254
|
+
}
|
|
1255
|
+
function npubToXOnlyHex(npub) {
|
|
1256
|
+
const decoded = base.bech32.decode(npub, 90);
|
|
1257
|
+
if (decoded.prefix !== "npub") throw new Error("not an npub");
|
|
1258
|
+
const bytes = Uint8Array.from(base.bech32.fromWords(decoded.words));
|
|
1259
|
+
if (bytes.length !== 32) throw new Error("npub payload is not 32 bytes");
|
|
1260
|
+
return toHex(bytes);
|
|
1261
|
+
}
|
|
1262
|
+
function verifyBinding(envelope) {
|
|
1263
|
+
const fail = (status) => ({
|
|
1264
|
+
valid: false,
|
|
1265
|
+
status
|
|
1266
|
+
});
|
|
1267
|
+
try {
|
|
1268
|
+
const msg = envelope.message;
|
|
1269
|
+
if (typeof msg !== "string" || msg.length === 0) return fail("malformed");
|
|
1270
|
+
const lines = msg.split("\n");
|
|
1271
|
+
if ((lines[0] ?? "") !== HEADER) return fail("bad_header");
|
|
1272
|
+
if (lines.length < 9 || lines[lines.length - 1] !== "") return fail("malformed");
|
|
1273
|
+
const body = lines.slice(0, -1);
|
|
1274
|
+
if (body.length < 8) return fail("malformed");
|
|
1275
|
+
if (body[1] !== "v: 1") return fail("malformed");
|
|
1276
|
+
if (body[7] !== `ack: ${ACK}`) return fail("bad_ack");
|
|
1277
|
+
const principal = linePrefix(body[2], "principal: ");
|
|
1278
|
+
const btc = linePrefix(body[3], "btc: ");
|
|
1279
|
+
const nostr = linePrefix(body[4], "nostr: ");
|
|
1280
|
+
const nonce = linePrefix(body[5], "nonce: ");
|
|
1281
|
+
const issuedAt = linePrefix(body[6], "issued_at: ");
|
|
1282
|
+
if (principal == null || btc == null || nostr == null || nonce == null || issuedAt == null) {
|
|
1283
|
+
return fail("malformed");
|
|
1284
|
+
}
|
|
1285
|
+
for (const value of [principal, btc, nostr, nonce, issuedAt]) {
|
|
1286
|
+
for (let i = 0; i < value.length; i++) {
|
|
1287
|
+
const c = value.charCodeAt(i);
|
|
1288
|
+
if (c < 32 || c > 126) return fail("malformed");
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
if (!/^did:oc:[0-9a-f]{32}$/.test(principal)) return fail("bad_principal");
|
|
1292
|
+
if (!/^[0-9a-f]{32}$/.test(nonce)) return fail("malformed");
|
|
1293
|
+
const ext = {};
|
|
1294
|
+
let prevKey = "";
|
|
1295
|
+
for (const line of body.slice(8)) {
|
|
1296
|
+
const idx = line.indexOf(": ");
|
|
1297
|
+
if (idx <= 0) return fail("malformed");
|
|
1298
|
+
const key = line.slice(0, idx);
|
|
1299
|
+
const value = line.slice(idx + 2);
|
|
1300
|
+
if (!/^[a-z]+$/.test(key)) return fail("malformed");
|
|
1301
|
+
if (key <= prevKey) return fail("ext_unsorted");
|
|
1302
|
+
prevKey = key;
|
|
1303
|
+
ext[key] = value;
|
|
1304
|
+
}
|
|
1305
|
+
const bid = bindingId(msg);
|
|
1306
|
+
if (envelope.binding_id && envelope.binding_id.toLowerCase() !== bid) {
|
|
1307
|
+
return fail("id_mismatch");
|
|
1308
|
+
}
|
|
1309
|
+
if (envelope.btc && envelope.btc !== btc) return fail("field_mismatch");
|
|
1310
|
+
if (envelope.nostr && envelope.nostr !== nostr) return fail("field_mismatch");
|
|
1311
|
+
if (envelope.principal && envelope.principal !== principal) return fail("field_mismatch");
|
|
1312
|
+
if (ext.expires) {
|
|
1313
|
+
const t = Date.parse(ext.expires);
|
|
1314
|
+
if (!Number.isNaN(t) && Date.now() > t) return fail("expired");
|
|
1315
|
+
}
|
|
1316
|
+
if (typeof envelope.btc_signature !== "string" || !envelope.btc_signature) {
|
|
1317
|
+
return fail("btc_sig_invalid");
|
|
1318
|
+
}
|
|
1319
|
+
const scheme = envelope.btc_scheme ?? "bip322";
|
|
1320
|
+
let btcOk = false;
|
|
1321
|
+
try {
|
|
1322
|
+
if (scheme === "bip322") {
|
|
1323
|
+
btcOk = bip322Js.Verifier.verifySignature(btc, msg, envelope.btc_signature);
|
|
1324
|
+
} else if (scheme === "legacy") {
|
|
1325
|
+
btcOk = bitcoinMessage__namespace.verify(msg, btc, envelope.btc_signature);
|
|
1326
|
+
} else {
|
|
1327
|
+
return fail("bad_scheme");
|
|
1328
|
+
}
|
|
1329
|
+
} catch {
|
|
1330
|
+
btcOk = false;
|
|
1331
|
+
}
|
|
1332
|
+
if (!btcOk) return fail("btc_sig_invalid");
|
|
1333
|
+
const ev = envelope.nostr_event;
|
|
1334
|
+
if (!ev || ev.kind !== BINDING_KIND || typeof ev.id !== "string") return fail("malformed");
|
|
1335
|
+
if (nostrEventId(ev) !== ev.id.toLowerCase()) return fail("nostr_id_invalid");
|
|
1336
|
+
let nostrSigOk = false;
|
|
1337
|
+
try {
|
|
1338
|
+
nostrSigOk = secp256k1_js.schnorr.verify(fromHex(ev.sig), fromHex(ev.id), fromHex(ev.pubkey));
|
|
1339
|
+
} catch {
|
|
1340
|
+
nostrSigOk = false;
|
|
1341
|
+
}
|
|
1342
|
+
if (!nostrSigOk) return fail("nostr_sig_invalid");
|
|
1343
|
+
let nostrXOnly;
|
|
1344
|
+
try {
|
|
1345
|
+
nostrXOnly = npubToXOnlyHex(nostr);
|
|
1346
|
+
} catch {
|
|
1347
|
+
return fail("nostr_key_mismatch");
|
|
1348
|
+
}
|
|
1349
|
+
if (nostrXOnly !== ev.pubkey.toLowerCase()) return fail("nostr_key_mismatch");
|
|
1350
|
+
let content;
|
|
1351
|
+
try {
|
|
1352
|
+
content = JSON.parse(ev.content);
|
|
1353
|
+
} catch {
|
|
1354
|
+
return fail("message_mismatch");
|
|
1355
|
+
}
|
|
1356
|
+
if (content.message !== msg) return fail("message_mismatch");
|
|
1357
|
+
if (content.btc_signature !== envelope.btc_signature) return fail("message_mismatch");
|
|
1358
|
+
return { valid: true, status: "binding_ok", binding_id: bid, principal, btc, nostr };
|
|
1359
|
+
} catch {
|
|
1360
|
+
return fail("malformed");
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
function xOnlyHexToNpub(hex) {
|
|
1364
|
+
const bytes = fromHex(hex);
|
|
1365
|
+
if (bytes.length !== 32) throw new Error("x-only Nostr key must be 32 bytes");
|
|
1366
|
+
return base.bech32.encode("npub", base.bech32.toWords(bytes), 90);
|
|
1367
|
+
}
|
|
1368
|
+
function buildBindingEventTemplate(params) {
|
|
1369
|
+
const id = bindingId(params.message);
|
|
1370
|
+
return {
|
|
1371
|
+
kind: BINDING_KIND,
|
|
1372
|
+
pubkey: params.nostrPubkeyHex.toLowerCase(),
|
|
1373
|
+
created_at: params.createdAt ?? Math.floor(Date.now() / 1e3),
|
|
1374
|
+
tags: [
|
|
1375
|
+
["d", `oc-attest-binding:${id}`],
|
|
1376
|
+
["btc", params.btc],
|
|
1377
|
+
["oc", "binding-attestation"],
|
|
1378
|
+
["v", "1"]
|
|
1379
|
+
],
|
|
1380
|
+
content: JSON.stringify({ message: params.message, btc_signature: params.btcSignature })
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
function parseBindingFields(message) {
|
|
1384
|
+
const body = message.split("\n");
|
|
1385
|
+
const at = (i, prefix) => {
|
|
1386
|
+
const line = body[i];
|
|
1387
|
+
if (typeof line !== "string" || !line.startsWith(prefix)) {
|
|
1388
|
+
throw new Error(`binding message line ${i + 1} is malformed`);
|
|
1389
|
+
}
|
|
1390
|
+
return line.slice(prefix.length);
|
|
1391
|
+
};
|
|
1392
|
+
return {
|
|
1393
|
+
principal: at(2, "principal: "),
|
|
1394
|
+
btc: at(3, "btc: "),
|
|
1395
|
+
nostr: at(4, "nostr: ")
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
function toBase64Url(s) {
|
|
1399
|
+
const bytes = new TextEncoder().encode(s);
|
|
1400
|
+
let bin = "";
|
|
1401
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
1402
|
+
const b64 = typeof btoa !== "undefined" ? btoa(bin) : Buffer.from(bytes).toString("base64");
|
|
1403
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1404
|
+
}
|
|
1405
|
+
function assembleBindingEnvelope(params) {
|
|
1406
|
+
const { principal, btc, nostr } = parseBindingFields(params.message);
|
|
1407
|
+
return {
|
|
1408
|
+
binding_id: bindingId(params.message),
|
|
1409
|
+
v: 1,
|
|
1410
|
+
principal,
|
|
1411
|
+
btc,
|
|
1412
|
+
nostr,
|
|
1413
|
+
message: params.message,
|
|
1414
|
+
message_b64url: toBase64Url(params.message),
|
|
1415
|
+
btc_signature: params.btcSignature,
|
|
1416
|
+
btc_scheme: params.btcScheme ?? "bip322",
|
|
1417
|
+
nostr_event: params.nostrEvent
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1180
1420
|
var AUTH_HEADER = "orangecheck-auth";
|
|
1181
1421
|
var AUTH_ACK = "I authorize this session under the OrangeCheck auth challenge.";
|
|
1182
1422
|
function randomNonce() {
|
|
@@ -1632,12 +1872,12 @@ function getGistCreationUrl() {
|
|
|
1632
1872
|
}
|
|
1633
1873
|
var log7 = createLogger("identity-verification/nostr");
|
|
1634
1874
|
var HEX_64_RE = /^[0-9a-f]{64}$/;
|
|
1635
|
-
function
|
|
1875
|
+
function toHex2(bytes) {
|
|
1636
1876
|
let out = "";
|
|
1637
1877
|
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
1638
1878
|
return out;
|
|
1639
1879
|
}
|
|
1640
|
-
function
|
|
1880
|
+
function fromHex2(hex) {
|
|
1641
1881
|
if (hex.length % 2 !== 0) throw new Error("odd-length hex");
|
|
1642
1882
|
const out = new Uint8Array(hex.length / 2);
|
|
1643
1883
|
for (let i = 0; i < out.length; i++) {
|
|
@@ -1661,7 +1901,7 @@ function npubToHex(npub) {
|
|
|
1661
1901
|
if (bytes.length !== 32) {
|
|
1662
1902
|
throw new Error(`invalid npub payload length: ${bytes.length}`);
|
|
1663
1903
|
}
|
|
1664
|
-
return
|
|
1904
|
+
return toHex2(Uint8Array.from(bytes));
|
|
1665
1905
|
}
|
|
1666
1906
|
async function queryNostrEvents(options) {
|
|
1667
1907
|
const { authors, kinds, search, relays = DEFAULT_RELAYS, limit = 100 } = options;
|
|
@@ -1744,7 +1984,7 @@ async function verifyNostrEventSignature(event) {
|
|
|
1744
1984
|
event.tags ?? [],
|
|
1745
1985
|
event.content ?? ""
|
|
1746
1986
|
]);
|
|
1747
|
-
const computedId =
|
|
1987
|
+
const computedId = toHex2(sha2_js.sha256(new TextEncoder().encode(preimage)));
|
|
1748
1988
|
if (computedId !== event.id.toLowerCase()) {
|
|
1749
1989
|
log7.warn(
|
|
1750
1990
|
{ eventId: event.id, computedId },
|
|
@@ -1752,7 +1992,7 @@ async function verifyNostrEventSignature(event) {
|
|
|
1752
1992
|
);
|
|
1753
1993
|
return false;
|
|
1754
1994
|
}
|
|
1755
|
-
return secp256k1_js.schnorr.verify(
|
|
1995
|
+
return secp256k1_js.schnorr.verify(fromHex2(event.sig), fromHex2(event.id), fromHex2(event.pubkey));
|
|
1756
1996
|
} catch (err) {
|
|
1757
1997
|
log7.warn({ err }, "schnorr verify threw");
|
|
1758
1998
|
return false;
|
|
@@ -2114,6 +2354,10 @@ var STATUS_META = {
|
|
|
2114
2354
|
|
|
2115
2355
|
exports.DEFAULT_RELAYS = DEFAULT_RELAYS;
|
|
2116
2356
|
exports.STATUS_META = STATUS_META;
|
|
2357
|
+
exports.assembleBindingEnvelope = assembleBindingEnvelope;
|
|
2358
|
+
exports.bindingId = bindingId;
|
|
2359
|
+
exports.buildBindingEventTemplate = buildBindingEventTemplate;
|
|
2360
|
+
exports.buildBindingMessage = buildBindingMessage;
|
|
2117
2361
|
exports.buildCanonicalMessage = buildCanonicalMessage;
|
|
2118
2362
|
exports.check = check;
|
|
2119
2363
|
exports.computeAllScores = computeAllScores;
|
|
@@ -2165,11 +2409,13 @@ exports.supportsAutomaticVerification = supportsAutomaticVerification;
|
|
|
2165
2409
|
exports.verify = verify2;
|
|
2166
2410
|
exports.verifyAllIdentities = verifyAllIdentities;
|
|
2167
2411
|
exports.verifyAttestationById = verifyAttestationById;
|
|
2412
|
+
exports.verifyBinding = verifyBinding;
|
|
2168
2413
|
exports.verifyChallenge = verifyChallenge;
|
|
2169
2414
|
exports.verifyDnsIdentity = verifyDnsIdentity;
|
|
2170
2415
|
exports.verifyGitHubIdentity = verifyGitHubIdentity;
|
|
2171
2416
|
exports.verifyIdentity = verifyIdentity;
|
|
2172
2417
|
exports.verifyNostrIdentity = verifyNostrIdentity;
|
|
2173
2418
|
exports.verifyTwitterIdentity = verifyTwitterIdentity;
|
|
2419
|
+
exports.xOnlyHexToNpub = xOnlyHexToNpub;
|
|
2174
2420
|
//# sourceMappingURL=index.js.map
|
|
2175
2421
|
//# sourceMappingURL=index.js.map
|