@orangecheck/sdk 0.1.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/LICENSE +21 -0
- package/README.md +277 -0
- package/dist/attestation.d.mts +44 -0
- package/dist/attestation.d.ts +44 -0
- package/dist/attestation.js +579 -0
- package/dist/attestation.js.map +1 -0
- package/dist/attestation.mjs +567 -0
- package/dist/attestation.mjs.map +1 -0
- package/dist/canonical.d.mts +15 -0
- package/dist/canonical.d.ts +15 -0
- package/dist/canonical.js +109 -0
- package/dist/canonical.js.map +1 -0
- package/dist/canonical.mjs +102 -0
- package/dist/canonical.mjs.map +1 -0
- package/dist/challenge.d.mts +40 -0
- package/dist/challenge.d.ts +40 -0
- package/dist/challenge.js +169 -0
- package/dist/challenge.js.map +1 -0
- package/dist/challenge.mjs +146 -0
- package/dist/challenge.mjs.map +1 -0
- package/dist/identity.d.mts +88 -0
- package/dist/identity.d.ts +88 -0
- package/dist/identity.js +730 -0
- package/dist/identity.js.map +1 -0
- package/dist/identity.mjs +702 -0
- package/dist/identity.mjs.map +1 -0
- package/dist/index.d.mts +47 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +2004 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1930 -0
- package/dist/index.mjs.map +1 -0
- package/dist/nostr.d.mts +19 -0
- package/dist/nostr.d.ts +19 -0
- package/dist/nostr.js +395 -0
- package/dist/nostr.js.map +1 -0
- package/dist/nostr.mjs +382 -0
- package/dist/nostr.mjs.map +1 -0
- package/dist/scoring.d.mts +16 -0
- package/dist/scoring.d.ts +16 -0
- package/dist/scoring.js +75 -0
- package/dist/scoring.js.map +1 -0
- package/dist/scoring.mjs +69 -0
- package/dist/scoring.mjs.map +1 -0
- package/dist/types-BJKDroFv.d.mts +62 -0
- package/dist/types-BJKDroFv.d.ts +62 -0
- package/dist/verify.d.mts +6 -0
- package/dist/verify.d.ts +6 -0
- package/dist/verify.js +509 -0
- package/dist/verify.js.map +1 -0
- package/dist/verify.mjs +485 -0
- package/dist/verify.mjs.map +1 -0
- package/package.json +106 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var buffer = require('buffer');
|
|
4
|
+
var bip322Js = require('bip322-js');
|
|
5
|
+
var bitcoinMessage = require('bitcoinjs-message');
|
|
6
|
+
|
|
7
|
+
function _interopNamespace(e) {
|
|
8
|
+
if (e && e.__esModule) return e;
|
|
9
|
+
var n = Object.create(null);
|
|
10
|
+
if (e) {
|
|
11
|
+
Object.keys(e).forEach(function (k) {
|
|
12
|
+
if (k !== 'default') {
|
|
13
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
14
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: function () { return e[k]; }
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
n.default = e;
|
|
22
|
+
return Object.freeze(n);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
var bitcoinMessage__namespace = /*#__PURE__*/_interopNamespace(bitcoinMessage);
|
|
26
|
+
|
|
27
|
+
// src/challenge.ts
|
|
28
|
+
var AUTH_HEADER = "orangecheck-auth";
|
|
29
|
+
var AUTH_ACK = "I authorize this session under the OrangeCheck auth challenge.";
|
|
30
|
+
function randomNonce() {
|
|
31
|
+
const bytes = new Uint8Array(16);
|
|
32
|
+
if (typeof globalThis.crypto?.getRandomValues === "function") {
|
|
33
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
34
|
+
} else {
|
|
35
|
+
for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
36
|
+
}
|
|
37
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
38
|
+
}
|
|
39
|
+
function iso(date) {
|
|
40
|
+
return date.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
41
|
+
}
|
|
42
|
+
function issueChallenge(opts) {
|
|
43
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
44
|
+
const ttl = opts.ttlSeconds ?? 300;
|
|
45
|
+
const expiresAt = new Date(now.getTime() + ttl * 1e3);
|
|
46
|
+
const nonce = opts.nonce ?? randomNonce();
|
|
47
|
+
if (!/^[0-9a-f]{32}$/.test(nonce)) {
|
|
48
|
+
throw new Error("nonce must be 32 lowercase hex characters");
|
|
49
|
+
}
|
|
50
|
+
const coreLines = [
|
|
51
|
+
AUTH_HEADER,
|
|
52
|
+
`address: ${opts.address}`,
|
|
53
|
+
`nonce: ${nonce}`,
|
|
54
|
+
`issued_at: ${iso(now)}`,
|
|
55
|
+
`expires_at: ${iso(expiresAt)}`,
|
|
56
|
+
`ack: ${AUTH_ACK}`
|
|
57
|
+
];
|
|
58
|
+
const extensions = [];
|
|
59
|
+
if (opts.audience) extensions.push(["audience", opts.audience]);
|
|
60
|
+
if (opts.purpose) extensions.push(["purpose", opts.purpose]);
|
|
61
|
+
extensions.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
62
|
+
const extLines = extensions.map(([k, v]) => `${k}: ${v}`);
|
|
63
|
+
const message = [...coreLines, ...extLines].join("\n") + "\n";
|
|
64
|
+
return {
|
|
65
|
+
message,
|
|
66
|
+
nonce,
|
|
67
|
+
expiresAt: expiresAt.getTime(),
|
|
68
|
+
expiresAtIso: iso(expiresAt)
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function parseChallenge(message) {
|
|
72
|
+
const lines = message.endsWith("\n") ? message.slice(0, -1).split("\n") : message.split("\n");
|
|
73
|
+
if (lines[0] !== AUTH_HEADER) {
|
|
74
|
+
throw new Error("not an orangecheck-auth challenge");
|
|
75
|
+
}
|
|
76
|
+
const out = { extensions: [] };
|
|
77
|
+
const coreKeys = /* @__PURE__ */ new Set(["address", "nonce", "issued_at", "expires_at", "ack"]);
|
|
78
|
+
let seenCore = 0;
|
|
79
|
+
for (let i = 1; i < lines.length; i++) {
|
|
80
|
+
const line = lines[i] ?? "";
|
|
81
|
+
const sep = line.indexOf(": ");
|
|
82
|
+
if (sep === -1) continue;
|
|
83
|
+
const key = line.slice(0, sep);
|
|
84
|
+
const val = line.slice(sep + 2);
|
|
85
|
+
if (coreKeys.has(key)) {
|
|
86
|
+
seenCore++;
|
|
87
|
+
if (key === "address") out.address = val;
|
|
88
|
+
else if (key === "nonce") out.nonce = val;
|
|
89
|
+
else if (key === "issued_at") out.issuedAt = new Date(val);
|
|
90
|
+
else if (key === "expires_at") out.expiresAt = new Date(val);
|
|
91
|
+
else if (key === "ack") out.ack = val;
|
|
92
|
+
} else {
|
|
93
|
+
out.extensions.push([key, val]);
|
|
94
|
+
if (key === "audience") out.audience = val;
|
|
95
|
+
if (key === "purpose") out.purpose = val;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (seenCore !== 5) {
|
|
99
|
+
throw new Error(`challenge missing core lines (${seenCore}/5)`);
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
async function verifyChallenge(opts) {
|
|
104
|
+
let parsed;
|
|
105
|
+
try {
|
|
106
|
+
parsed = parseChallenge(opts.message);
|
|
107
|
+
} catch {
|
|
108
|
+
return { ok: false, reason: "malformed" };
|
|
109
|
+
}
|
|
110
|
+
const { address, nonce, issuedAt, expiresAt, ack, audience, purpose } = parsed;
|
|
111
|
+
if (!address || !nonce || !issuedAt || !expiresAt || !ack) {
|
|
112
|
+
return { ok: false, reason: "malformed" };
|
|
113
|
+
}
|
|
114
|
+
if (ack !== AUTH_ACK) {
|
|
115
|
+
return { ok: false, reason: "malformed" };
|
|
116
|
+
}
|
|
117
|
+
if (!/^[0-9a-f]{32}$/.test(nonce)) {
|
|
118
|
+
return { ok: false, reason: "malformed" };
|
|
119
|
+
}
|
|
120
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
121
|
+
const skew = opts.clockSkewMs ?? 0;
|
|
122
|
+
if (now.getTime() - skew > expiresAt.getTime()) {
|
|
123
|
+
return { ok: false, reason: "expired" };
|
|
124
|
+
}
|
|
125
|
+
if (now.getTime() + skew < issuedAt.getTime()) {
|
|
126
|
+
return { ok: false, reason: "not_yet_valid" };
|
|
127
|
+
}
|
|
128
|
+
if (opts.expectedNonce && nonce !== opts.expectedNonce) {
|
|
129
|
+
return { ok: false, reason: "nonce_mismatch" };
|
|
130
|
+
}
|
|
131
|
+
if (opts.expectedAudience && audience !== opts.expectedAudience) {
|
|
132
|
+
return { ok: false, reason: "audience_mismatch" };
|
|
133
|
+
}
|
|
134
|
+
if (opts.expectedPurpose && purpose !== opts.expectedPurpose) {
|
|
135
|
+
return { ok: false, reason: "purpose_mismatch" };
|
|
136
|
+
}
|
|
137
|
+
const scheme = opts.scheme ?? "bip322";
|
|
138
|
+
let sigValid = false;
|
|
139
|
+
try {
|
|
140
|
+
if (scheme === "bip322") {
|
|
141
|
+
const sigBase64 = /^[0-9a-fA-F]+$/.test(opts.signature) && opts.signature.length % 2 === 0 ? buffer.Buffer.from(opts.signature, "hex").toString("base64") : opts.signature;
|
|
142
|
+
sigValid = bip322Js.Verifier.verifySignature(address, opts.message, sigBase64);
|
|
143
|
+
} else if (scheme === "legacy") {
|
|
144
|
+
if (!/^[1m][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(address)) {
|
|
145
|
+
return { ok: false, reason: "sig_unsupported_scheme" };
|
|
146
|
+
}
|
|
147
|
+
sigValid = bitcoinMessage__namespace.verify(opts.message, address, opts.signature);
|
|
148
|
+
} else {
|
|
149
|
+
return { ok: false, reason: "sig_unsupported_scheme" };
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
sigValid = false;
|
|
153
|
+
}
|
|
154
|
+
if (!sigValid) return { ok: false, reason: "sig_invalid" };
|
|
155
|
+
return {
|
|
156
|
+
ok: true,
|
|
157
|
+
reason: "ok",
|
|
158
|
+
address,
|
|
159
|
+
expiresAt: expiresAt.getTime(),
|
|
160
|
+
nonce,
|
|
161
|
+
...audience !== void 0 ? { audience } : {},
|
|
162
|
+
...purpose !== void 0 ? { purpose } : {}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
exports.issueChallenge = issueChallenge;
|
|
167
|
+
exports.verifyChallenge = verifyChallenge;
|
|
168
|
+
//# sourceMappingURL=challenge.js.map
|
|
169
|
+
//# sourceMappingURL=challenge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/challenge.ts"],"names":["Buffer","Verifier","bitcoinMessage"],"mappings":"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","file":"challenge.js","sourcesContent":["/**\n * Signed-challenge auth\n *\n * When a gate can't trust the address source (public header, query string,\n * unsigned cookie), run a one-shot BIP-322 challenge first to cryptographically\n * prove the caller controls the address. Bind the proven address to a session\n * cookie or JWT, and the gate reads from there.\n *\n * This is a different wire format from the main OCP attestation — the header\n * literal is `orangecheck-auth`, not `orangecheck`, so a signed auth challenge\n * can never be confused with a reputation attestation.\n *\n * // Server, on the \"connect wallet\" flow:\n * const challenge = issueChallenge({ address, ttlSeconds: 300 });\n * // → send challenge.message to the client\n *\n * // Client signs challenge.message with BIP-322 via the user's wallet,\n * // then posts { message, signature } back.\n *\n * // Server, on callback:\n * const result = await verifyChallenge({ message, signature });\n * if (result.ok) {\n * // result.address is now cryptographically proven; attach to session.\n * }\n */\n\nimport type { Scheme } from './types';\n\nimport { Buffer } from 'buffer';\n\nimport { Verifier } from 'bip322-js';\nimport * as bitcoinMessage from 'bitcoinjs-message';\n\nconst AUTH_HEADER = 'orangecheck-auth';\nconst AUTH_ACK = 'I authorize this session under the OrangeCheck auth challenge.';\n\nfunction randomNonce(): string {\n // 16 bytes → 32 lowercase hex chars. Matches OCP core nonce format.\n const bytes = new Uint8Array(16);\n // Runtimes: Node 20+ (webcrypto), browsers.\n if (typeof globalThis.crypto?.getRandomValues === 'function') {\n globalThis.crypto.getRandomValues(bytes);\n } else {\n // Fallback: Math.random is *not* cryptographic — acceptable only for\n // environments without webcrypto (old Node). Flagged explicitly.\n for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nfunction iso(date: Date): string {\n return date.toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\nexport interface IssueChallengeOptions {\n /** Bitcoin address the caller claims to control. */\n address: string;\n /** How long the challenge is valid for. Default 300 (5 minutes). */\n ttlSeconds?: number;\n /** Explicit nonce. Rare; prefer auto-generated. */\n nonce?: string;\n /** Origin-binding extension. If set, verify will require a match. */\n audience?: string;\n /** Human-readable label baked into the signed message. */\n purpose?: string;\n /** Freeze `now` for tests. */\n now?: Date;\n}\n\nexport interface Challenge {\n /** The canonical text the client MUST sign. */\n message: string;\n /** The generated nonce (kept for correlation / server-side storage). */\n nonce: string;\n /** Unix ms epoch the challenge expires at. */\n expiresAt: number;\n /** ISO string of the same. */\n expiresAtIso: string;\n}\n\n/**\n * Build a new signed-challenge message. The server MUST remember the nonce\n * it issued against the session (Redis, signed cookie, DB) so it can detect\n * replay or a different nonce coming back on verify.\n */\nexport function issueChallenge(opts: IssueChallengeOptions): Challenge {\n const now = opts.now ?? new Date();\n const ttl = opts.ttlSeconds ?? 300;\n const expiresAt = new Date(now.getTime() + ttl * 1000);\n const nonce = opts.nonce ?? randomNonce();\n\n if (!/^[0-9a-f]{32}$/.test(nonce)) {\n throw new Error('nonce must be 32 lowercase hex characters');\n }\n\n const coreLines = [\n AUTH_HEADER,\n `address: ${opts.address}`,\n `nonce: ${nonce}`,\n `issued_at: ${iso(now)}`,\n `expires_at: ${iso(expiresAt)}`,\n `ack: ${AUTH_ACK}`,\n ];\n\n const extensions: Array<[string, string]> = [];\n if (opts.audience) extensions.push(['audience', opts.audience]);\n if (opts.purpose) extensions.push(['purpose', opts.purpose]);\n // Lexicographic sort by key — matches OCP canonicalization rules.\n extensions.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n\n const extLines = extensions.map(([k, v]) => `${k}: ${v}`);\n const message = [...coreLines, ...extLines].join('\\n') + '\\n';\n\n return {\n message,\n nonce,\n expiresAt: expiresAt.getTime(),\n expiresAtIso: iso(expiresAt),\n };\n}\n\ninterface ParsedChallenge {\n address?: string;\n nonce?: string;\n issuedAt?: Date;\n expiresAt?: Date;\n ack?: string;\n audience?: string;\n purpose?: string;\n extensions: Array<[string, string]>;\n}\n\nfunction parseChallenge(message: string): ParsedChallenge {\n const lines = message.endsWith('\\n') ? message.slice(0, -1).split('\\n') : message.split('\\n');\n\n if (lines[0] !== AUTH_HEADER) {\n throw new Error('not an orangecheck-auth challenge');\n }\n\n const out: ParsedChallenge = { extensions: [] };\n const coreKeys = new Set(['address', 'nonce', 'issued_at', 'expires_at', 'ack']);\n let seenCore = 0;\n\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i] ?? '';\n const sep = line.indexOf(': ');\n if (sep === -1) continue;\n const key = line.slice(0, sep);\n const val = line.slice(sep + 2);\n\n if (coreKeys.has(key)) {\n seenCore++;\n if (key === 'address') out.address = val;\n else if (key === 'nonce') out.nonce = val;\n else if (key === 'issued_at') out.issuedAt = new Date(val);\n else if (key === 'expires_at') out.expiresAt = new Date(val);\n else if (key === 'ack') out.ack = val;\n } else {\n out.extensions.push([key, val]);\n if (key === 'audience') out.audience = val;\n if (key === 'purpose') out.purpose = val;\n }\n }\n\n if (seenCore !== 5) {\n throw new Error(`challenge missing core lines (${seenCore}/5)`);\n }\n return out;\n}\n\nexport interface VerifyChallengeOptions {\n /** The signed canonical challenge text, exactly as issued. */\n message: string;\n /** BIP-322 or legacy signature. */\n signature: string;\n /** Scheme override. Default 'bip322'. */\n scheme?: Scheme;\n\n /** If set, require the challenge's `audience:` extension to match. */\n expectedAudience?: string;\n /** If set, require the challenge's `purpose:` extension to match. */\n expectedPurpose?: string;\n /**\n * If set, require the challenge's `nonce:` to match. Use this to defeat\n * replay — pair with server-side nonce storage (Redis / signed cookie).\n */\n expectedNonce?: string;\n\n /** Tolerance on expiry check in ms. Default 0. */\n clockSkewMs?: number;\n /** Freeze `now` for tests. */\n now?: Date;\n}\n\nexport type VerifyChallengeReason =\n | 'ok'\n | 'malformed'\n | 'expired'\n | 'not_yet_valid'\n | 'sig_invalid'\n | 'sig_unsupported_scheme'\n | 'audience_mismatch'\n | 'purpose_mismatch'\n | 'nonce_mismatch';\n\nexport interface VerifyChallengeResult {\n ok: boolean;\n reason: VerifyChallengeReason;\n /** Present on success — the cryptographically proven Bitcoin address. */\n address?: string;\n /** Present on success — when the challenge expires (unix ms). */\n expiresAt?: number;\n /** Present on success — the nonce from the challenge. */\n nonce?: string;\n /** Present on success — the audience extension, if any. */\n audience?: string;\n /** Present on success — the purpose extension, if any. */\n purpose?: string;\n}\n\n/**\n * Verify a signed challenge. Returns the proven address on success.\n *\n * The caller is responsible for nonce replay prevention — pass `expectedNonce`\n * with the value you stashed at issue time.\n */\nexport async function verifyChallenge(\n opts: VerifyChallengeOptions\n): Promise<VerifyChallengeResult> {\n let parsed: ParsedChallenge;\n try {\n parsed = parseChallenge(opts.message);\n } catch {\n return { ok: false, reason: 'malformed' };\n }\n\n const { address, nonce, issuedAt, expiresAt, ack, audience, purpose } = parsed;\n if (!address || !nonce || !issuedAt || !expiresAt || !ack) {\n return { ok: false, reason: 'malformed' };\n }\n if (ack !== AUTH_ACK) {\n return { ok: false, reason: 'malformed' };\n }\n if (!/^[0-9a-f]{32}$/.test(nonce)) {\n return { ok: false, reason: 'malformed' };\n }\n\n const now = opts.now ?? new Date();\n const skew = opts.clockSkewMs ?? 0;\n\n if (now.getTime() - skew > expiresAt.getTime()) {\n return { ok: false, reason: 'expired' };\n }\n if (now.getTime() + skew < issuedAt.getTime()) {\n return { ok: false, reason: 'not_yet_valid' };\n }\n\n if (opts.expectedNonce && nonce !== opts.expectedNonce) {\n return { ok: false, reason: 'nonce_mismatch' };\n }\n if (opts.expectedAudience && audience !== opts.expectedAudience) {\n return { ok: false, reason: 'audience_mismatch' };\n }\n if (opts.expectedPurpose && purpose !== opts.expectedPurpose) {\n return { ok: false, reason: 'purpose_mismatch' };\n }\n\n const scheme: Scheme = opts.scheme ?? 'bip322';\n let sigValid = false;\n\n try {\n if (scheme === 'bip322') {\n const sigBase64 =\n /^[0-9a-fA-F]+$/.test(opts.signature) && opts.signature.length % 2 === 0\n ? Buffer.from(opts.signature, 'hex').toString('base64')\n : opts.signature;\n sigValid = Verifier.verifySignature(address, opts.message, sigBase64);\n } else if (scheme === 'legacy') {\n // Legacy signmessage only works for P2PKH addresses.\n if (!/^[1m][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(address)) {\n return { ok: false, reason: 'sig_unsupported_scheme' };\n }\n sigValid = bitcoinMessage.verify(opts.message, address, opts.signature);\n } else {\n return { ok: false, reason: 'sig_unsupported_scheme' };\n }\n } catch {\n sigValid = false;\n }\n\n if (!sigValid) return { ok: false, reason: 'sig_invalid' };\n\n return {\n ok: true,\n reason: 'ok',\n address,\n expiresAt: expiresAt.getTime(),\n nonce,\n ...(audience !== undefined ? { audience } : {}),\n ...(purpose !== undefined ? { purpose } : {}),\n };\n}\n"]}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import { Verifier } from 'bip322-js';
|
|
3
|
+
import * as bitcoinMessage from 'bitcoinjs-message';
|
|
4
|
+
|
|
5
|
+
// src/challenge.ts
|
|
6
|
+
var AUTH_HEADER = "orangecheck-auth";
|
|
7
|
+
var AUTH_ACK = "I authorize this session under the OrangeCheck auth challenge.";
|
|
8
|
+
function randomNonce() {
|
|
9
|
+
const bytes = new Uint8Array(16);
|
|
10
|
+
if (typeof globalThis.crypto?.getRandomValues === "function") {
|
|
11
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
12
|
+
} else {
|
|
13
|
+
for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);
|
|
14
|
+
}
|
|
15
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
16
|
+
}
|
|
17
|
+
function iso(date) {
|
|
18
|
+
return date.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
19
|
+
}
|
|
20
|
+
function issueChallenge(opts) {
|
|
21
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
22
|
+
const ttl = opts.ttlSeconds ?? 300;
|
|
23
|
+
const expiresAt = new Date(now.getTime() + ttl * 1e3);
|
|
24
|
+
const nonce = opts.nonce ?? randomNonce();
|
|
25
|
+
if (!/^[0-9a-f]{32}$/.test(nonce)) {
|
|
26
|
+
throw new Error("nonce must be 32 lowercase hex characters");
|
|
27
|
+
}
|
|
28
|
+
const coreLines = [
|
|
29
|
+
AUTH_HEADER,
|
|
30
|
+
`address: ${opts.address}`,
|
|
31
|
+
`nonce: ${nonce}`,
|
|
32
|
+
`issued_at: ${iso(now)}`,
|
|
33
|
+
`expires_at: ${iso(expiresAt)}`,
|
|
34
|
+
`ack: ${AUTH_ACK}`
|
|
35
|
+
];
|
|
36
|
+
const extensions = [];
|
|
37
|
+
if (opts.audience) extensions.push(["audience", opts.audience]);
|
|
38
|
+
if (opts.purpose) extensions.push(["purpose", opts.purpose]);
|
|
39
|
+
extensions.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
40
|
+
const extLines = extensions.map(([k, v]) => `${k}: ${v}`);
|
|
41
|
+
const message = [...coreLines, ...extLines].join("\n") + "\n";
|
|
42
|
+
return {
|
|
43
|
+
message,
|
|
44
|
+
nonce,
|
|
45
|
+
expiresAt: expiresAt.getTime(),
|
|
46
|
+
expiresAtIso: iso(expiresAt)
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function parseChallenge(message) {
|
|
50
|
+
const lines = message.endsWith("\n") ? message.slice(0, -1).split("\n") : message.split("\n");
|
|
51
|
+
if (lines[0] !== AUTH_HEADER) {
|
|
52
|
+
throw new Error("not an orangecheck-auth challenge");
|
|
53
|
+
}
|
|
54
|
+
const out = { extensions: [] };
|
|
55
|
+
const coreKeys = /* @__PURE__ */ new Set(["address", "nonce", "issued_at", "expires_at", "ack"]);
|
|
56
|
+
let seenCore = 0;
|
|
57
|
+
for (let i = 1; i < lines.length; i++) {
|
|
58
|
+
const line = lines[i] ?? "";
|
|
59
|
+
const sep = line.indexOf(": ");
|
|
60
|
+
if (sep === -1) continue;
|
|
61
|
+
const key = line.slice(0, sep);
|
|
62
|
+
const val = line.slice(sep + 2);
|
|
63
|
+
if (coreKeys.has(key)) {
|
|
64
|
+
seenCore++;
|
|
65
|
+
if (key === "address") out.address = val;
|
|
66
|
+
else if (key === "nonce") out.nonce = val;
|
|
67
|
+
else if (key === "issued_at") out.issuedAt = new Date(val);
|
|
68
|
+
else if (key === "expires_at") out.expiresAt = new Date(val);
|
|
69
|
+
else if (key === "ack") out.ack = val;
|
|
70
|
+
} else {
|
|
71
|
+
out.extensions.push([key, val]);
|
|
72
|
+
if (key === "audience") out.audience = val;
|
|
73
|
+
if (key === "purpose") out.purpose = val;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (seenCore !== 5) {
|
|
77
|
+
throw new Error(`challenge missing core lines (${seenCore}/5)`);
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
async function verifyChallenge(opts) {
|
|
82
|
+
let parsed;
|
|
83
|
+
try {
|
|
84
|
+
parsed = parseChallenge(opts.message);
|
|
85
|
+
} catch {
|
|
86
|
+
return { ok: false, reason: "malformed" };
|
|
87
|
+
}
|
|
88
|
+
const { address, nonce, issuedAt, expiresAt, ack, audience, purpose } = parsed;
|
|
89
|
+
if (!address || !nonce || !issuedAt || !expiresAt || !ack) {
|
|
90
|
+
return { ok: false, reason: "malformed" };
|
|
91
|
+
}
|
|
92
|
+
if (ack !== AUTH_ACK) {
|
|
93
|
+
return { ok: false, reason: "malformed" };
|
|
94
|
+
}
|
|
95
|
+
if (!/^[0-9a-f]{32}$/.test(nonce)) {
|
|
96
|
+
return { ok: false, reason: "malformed" };
|
|
97
|
+
}
|
|
98
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
99
|
+
const skew = opts.clockSkewMs ?? 0;
|
|
100
|
+
if (now.getTime() - skew > expiresAt.getTime()) {
|
|
101
|
+
return { ok: false, reason: "expired" };
|
|
102
|
+
}
|
|
103
|
+
if (now.getTime() + skew < issuedAt.getTime()) {
|
|
104
|
+
return { ok: false, reason: "not_yet_valid" };
|
|
105
|
+
}
|
|
106
|
+
if (opts.expectedNonce && nonce !== opts.expectedNonce) {
|
|
107
|
+
return { ok: false, reason: "nonce_mismatch" };
|
|
108
|
+
}
|
|
109
|
+
if (opts.expectedAudience && audience !== opts.expectedAudience) {
|
|
110
|
+
return { ok: false, reason: "audience_mismatch" };
|
|
111
|
+
}
|
|
112
|
+
if (opts.expectedPurpose && purpose !== opts.expectedPurpose) {
|
|
113
|
+
return { ok: false, reason: "purpose_mismatch" };
|
|
114
|
+
}
|
|
115
|
+
const scheme = opts.scheme ?? "bip322";
|
|
116
|
+
let sigValid = false;
|
|
117
|
+
try {
|
|
118
|
+
if (scheme === "bip322") {
|
|
119
|
+
const sigBase64 = /^[0-9a-fA-F]+$/.test(opts.signature) && opts.signature.length % 2 === 0 ? Buffer.from(opts.signature, "hex").toString("base64") : opts.signature;
|
|
120
|
+
sigValid = Verifier.verifySignature(address, opts.message, sigBase64);
|
|
121
|
+
} else if (scheme === "legacy") {
|
|
122
|
+
if (!/^[1m][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(address)) {
|
|
123
|
+
return { ok: false, reason: "sig_unsupported_scheme" };
|
|
124
|
+
}
|
|
125
|
+
sigValid = bitcoinMessage.verify(opts.message, address, opts.signature);
|
|
126
|
+
} else {
|
|
127
|
+
return { ok: false, reason: "sig_unsupported_scheme" };
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
sigValid = false;
|
|
131
|
+
}
|
|
132
|
+
if (!sigValid) return { ok: false, reason: "sig_invalid" };
|
|
133
|
+
return {
|
|
134
|
+
ok: true,
|
|
135
|
+
reason: "ok",
|
|
136
|
+
address,
|
|
137
|
+
expiresAt: expiresAt.getTime(),
|
|
138
|
+
nonce,
|
|
139
|
+
...audience !== void 0 ? { audience } : {},
|
|
140
|
+
...purpose !== void 0 ? { purpose } : {}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export { issueChallenge, verifyChallenge };
|
|
145
|
+
//# sourceMappingURL=challenge.mjs.map
|
|
146
|
+
//# sourceMappingURL=challenge.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/challenge.ts"],"names":[],"mappings":"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","file":"challenge.mjs","sourcesContent":["/**\n * Signed-challenge auth\n *\n * When a gate can't trust the address source (public header, query string,\n * unsigned cookie), run a one-shot BIP-322 challenge first to cryptographically\n * prove the caller controls the address. Bind the proven address to a session\n * cookie or JWT, and the gate reads from there.\n *\n * This is a different wire format from the main OCP attestation — the header\n * literal is `orangecheck-auth`, not `orangecheck`, so a signed auth challenge\n * can never be confused with a reputation attestation.\n *\n * // Server, on the \"connect wallet\" flow:\n * const challenge = issueChallenge({ address, ttlSeconds: 300 });\n * // → send challenge.message to the client\n *\n * // Client signs challenge.message with BIP-322 via the user's wallet,\n * // then posts { message, signature } back.\n *\n * // Server, on callback:\n * const result = await verifyChallenge({ message, signature });\n * if (result.ok) {\n * // result.address is now cryptographically proven; attach to session.\n * }\n */\n\nimport type { Scheme } from './types';\n\nimport { Buffer } from 'buffer';\n\nimport { Verifier } from 'bip322-js';\nimport * as bitcoinMessage from 'bitcoinjs-message';\n\nconst AUTH_HEADER = 'orangecheck-auth';\nconst AUTH_ACK = 'I authorize this session under the OrangeCheck auth challenge.';\n\nfunction randomNonce(): string {\n // 16 bytes → 32 lowercase hex chars. Matches OCP core nonce format.\n const bytes = new Uint8Array(16);\n // Runtimes: Node 20+ (webcrypto), browsers.\n if (typeof globalThis.crypto?.getRandomValues === 'function') {\n globalThis.crypto.getRandomValues(bytes);\n } else {\n // Fallback: Math.random is *not* cryptographic — acceptable only for\n // environments without webcrypto (old Node). Flagged explicitly.\n for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\nfunction iso(date: Date): string {\n return date.toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n}\n\nexport interface IssueChallengeOptions {\n /** Bitcoin address the caller claims to control. */\n address: string;\n /** How long the challenge is valid for. Default 300 (5 minutes). */\n ttlSeconds?: number;\n /** Explicit nonce. Rare; prefer auto-generated. */\n nonce?: string;\n /** Origin-binding extension. If set, verify will require a match. */\n audience?: string;\n /** Human-readable label baked into the signed message. */\n purpose?: string;\n /** Freeze `now` for tests. */\n now?: Date;\n}\n\nexport interface Challenge {\n /** The canonical text the client MUST sign. */\n message: string;\n /** The generated nonce (kept for correlation / server-side storage). */\n nonce: string;\n /** Unix ms epoch the challenge expires at. */\n expiresAt: number;\n /** ISO string of the same. */\n expiresAtIso: string;\n}\n\n/**\n * Build a new signed-challenge message. The server MUST remember the nonce\n * it issued against the session (Redis, signed cookie, DB) so it can detect\n * replay or a different nonce coming back on verify.\n */\nexport function issueChallenge(opts: IssueChallengeOptions): Challenge {\n const now = opts.now ?? new Date();\n const ttl = opts.ttlSeconds ?? 300;\n const expiresAt = new Date(now.getTime() + ttl * 1000);\n const nonce = opts.nonce ?? randomNonce();\n\n if (!/^[0-9a-f]{32}$/.test(nonce)) {\n throw new Error('nonce must be 32 lowercase hex characters');\n }\n\n const coreLines = [\n AUTH_HEADER,\n `address: ${opts.address}`,\n `nonce: ${nonce}`,\n `issued_at: ${iso(now)}`,\n `expires_at: ${iso(expiresAt)}`,\n `ack: ${AUTH_ACK}`,\n ];\n\n const extensions: Array<[string, string]> = [];\n if (opts.audience) extensions.push(['audience', opts.audience]);\n if (opts.purpose) extensions.push(['purpose', opts.purpose]);\n // Lexicographic sort by key — matches OCP canonicalization rules.\n extensions.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n\n const extLines = extensions.map(([k, v]) => `${k}: ${v}`);\n const message = [...coreLines, ...extLines].join('\\n') + '\\n';\n\n return {\n message,\n nonce,\n expiresAt: expiresAt.getTime(),\n expiresAtIso: iso(expiresAt),\n };\n}\n\ninterface ParsedChallenge {\n address?: string;\n nonce?: string;\n issuedAt?: Date;\n expiresAt?: Date;\n ack?: string;\n audience?: string;\n purpose?: string;\n extensions: Array<[string, string]>;\n}\n\nfunction parseChallenge(message: string): ParsedChallenge {\n const lines = message.endsWith('\\n') ? message.slice(0, -1).split('\\n') : message.split('\\n');\n\n if (lines[0] !== AUTH_HEADER) {\n throw new Error('not an orangecheck-auth challenge');\n }\n\n const out: ParsedChallenge = { extensions: [] };\n const coreKeys = new Set(['address', 'nonce', 'issued_at', 'expires_at', 'ack']);\n let seenCore = 0;\n\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i] ?? '';\n const sep = line.indexOf(': ');\n if (sep === -1) continue;\n const key = line.slice(0, sep);\n const val = line.slice(sep + 2);\n\n if (coreKeys.has(key)) {\n seenCore++;\n if (key === 'address') out.address = val;\n else if (key === 'nonce') out.nonce = val;\n else if (key === 'issued_at') out.issuedAt = new Date(val);\n else if (key === 'expires_at') out.expiresAt = new Date(val);\n else if (key === 'ack') out.ack = val;\n } else {\n out.extensions.push([key, val]);\n if (key === 'audience') out.audience = val;\n if (key === 'purpose') out.purpose = val;\n }\n }\n\n if (seenCore !== 5) {\n throw new Error(`challenge missing core lines (${seenCore}/5)`);\n }\n return out;\n}\n\nexport interface VerifyChallengeOptions {\n /** The signed canonical challenge text, exactly as issued. */\n message: string;\n /** BIP-322 or legacy signature. */\n signature: string;\n /** Scheme override. Default 'bip322'. */\n scheme?: Scheme;\n\n /** If set, require the challenge's `audience:` extension to match. */\n expectedAudience?: string;\n /** If set, require the challenge's `purpose:` extension to match. */\n expectedPurpose?: string;\n /**\n * If set, require the challenge's `nonce:` to match. Use this to defeat\n * replay — pair with server-side nonce storage (Redis / signed cookie).\n */\n expectedNonce?: string;\n\n /** Tolerance on expiry check in ms. Default 0. */\n clockSkewMs?: number;\n /** Freeze `now` for tests. */\n now?: Date;\n}\n\nexport type VerifyChallengeReason =\n | 'ok'\n | 'malformed'\n | 'expired'\n | 'not_yet_valid'\n | 'sig_invalid'\n | 'sig_unsupported_scheme'\n | 'audience_mismatch'\n | 'purpose_mismatch'\n | 'nonce_mismatch';\n\nexport interface VerifyChallengeResult {\n ok: boolean;\n reason: VerifyChallengeReason;\n /** Present on success — the cryptographically proven Bitcoin address. */\n address?: string;\n /** Present on success — when the challenge expires (unix ms). */\n expiresAt?: number;\n /** Present on success — the nonce from the challenge. */\n nonce?: string;\n /** Present on success — the audience extension, if any. */\n audience?: string;\n /** Present on success — the purpose extension, if any. */\n purpose?: string;\n}\n\n/**\n * Verify a signed challenge. Returns the proven address on success.\n *\n * The caller is responsible for nonce replay prevention — pass `expectedNonce`\n * with the value you stashed at issue time.\n */\nexport async function verifyChallenge(\n opts: VerifyChallengeOptions\n): Promise<VerifyChallengeResult> {\n let parsed: ParsedChallenge;\n try {\n parsed = parseChallenge(opts.message);\n } catch {\n return { ok: false, reason: 'malformed' };\n }\n\n const { address, nonce, issuedAt, expiresAt, ack, audience, purpose } = parsed;\n if (!address || !nonce || !issuedAt || !expiresAt || !ack) {\n return { ok: false, reason: 'malformed' };\n }\n if (ack !== AUTH_ACK) {\n return { ok: false, reason: 'malformed' };\n }\n if (!/^[0-9a-f]{32}$/.test(nonce)) {\n return { ok: false, reason: 'malformed' };\n }\n\n const now = opts.now ?? new Date();\n const skew = opts.clockSkewMs ?? 0;\n\n if (now.getTime() - skew > expiresAt.getTime()) {\n return { ok: false, reason: 'expired' };\n }\n if (now.getTime() + skew < issuedAt.getTime()) {\n return { ok: false, reason: 'not_yet_valid' };\n }\n\n if (opts.expectedNonce && nonce !== opts.expectedNonce) {\n return { ok: false, reason: 'nonce_mismatch' };\n }\n if (opts.expectedAudience && audience !== opts.expectedAudience) {\n return { ok: false, reason: 'audience_mismatch' };\n }\n if (opts.expectedPurpose && purpose !== opts.expectedPurpose) {\n return { ok: false, reason: 'purpose_mismatch' };\n }\n\n const scheme: Scheme = opts.scheme ?? 'bip322';\n let sigValid = false;\n\n try {\n if (scheme === 'bip322') {\n const sigBase64 =\n /^[0-9a-fA-F]+$/.test(opts.signature) && opts.signature.length % 2 === 0\n ? Buffer.from(opts.signature, 'hex').toString('base64')\n : opts.signature;\n sigValid = Verifier.verifySignature(address, opts.message, sigBase64);\n } else if (scheme === 'legacy') {\n // Legacy signmessage only works for P2PKH addresses.\n if (!/^[1m][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(address)) {\n return { ok: false, reason: 'sig_unsupported_scheme' };\n }\n sigValid = bitcoinMessage.verify(opts.message, address, opts.signature);\n } else {\n return { ok: false, reason: 'sig_unsupported_scheme' };\n }\n } catch {\n sigValid = false;\n }\n\n if (!sigValid) return { ok: false, reason: 'sig_invalid' };\n\n return {\n ok: true,\n reason: 'ok',\n address,\n expiresAt: expiresAt.getTime(),\n nonce,\n ...(audience !== undefined ? { audience } : {}),\n ...(purpose !== undefined ? { purpose } : {}),\n };\n}\n"]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { N as NostrEvent, I as IdentityBinding } from './types-BJKDroFv.mjs';
|
|
2
|
+
|
|
3
|
+
interface DnsVerificationResult {
|
|
4
|
+
verified: boolean;
|
|
5
|
+
method?: 'well-known' | 'dns-txt';
|
|
6
|
+
url?: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
declare function verifyDnsIdentity(attestationId: string, domain: string): Promise<DnsVerificationResult>;
|
|
10
|
+
declare function getDnsVerificationInstructions(attestationId: string, domain: string): string;
|
|
11
|
+
declare function generateWellKnownContent(attestationId: string, domain: string): string;
|
|
12
|
+
declare function getWellKnownUrl(domain: string): string;
|
|
13
|
+
declare function isValidDomain(domain: string): boolean;
|
|
14
|
+
declare function isDnsIdentityVerified(attestationId: string, _domain: string, cachedContent?: string): boolean;
|
|
15
|
+
|
|
16
|
+
interface GitHubVerificationResult {
|
|
17
|
+
verified: boolean;
|
|
18
|
+
gistUrl?: string;
|
|
19
|
+
gistId?: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
interface GitHubGistFile {
|
|
23
|
+
filename: string;
|
|
24
|
+
type: string;
|
|
25
|
+
language: string | null;
|
|
26
|
+
raw_url: string;
|
|
27
|
+
size: number;
|
|
28
|
+
content?: string;
|
|
29
|
+
}
|
|
30
|
+
interface GitHubGist {
|
|
31
|
+
id: string;
|
|
32
|
+
html_url: string;
|
|
33
|
+
description: string;
|
|
34
|
+
public: boolean;
|
|
35
|
+
created_at: string;
|
|
36
|
+
updated_at: string;
|
|
37
|
+
files: Record<string, GitHubGistFile>;
|
|
38
|
+
}
|
|
39
|
+
declare function verifyGitHubIdentity(attestationId: string, username: string): Promise<GitHubVerificationResult>;
|
|
40
|
+
declare function getGitHubVerificationInstructions(attestationId: string): string;
|
|
41
|
+
declare function generateGistTemplate(attestationId: string, username: string): string;
|
|
42
|
+
declare function isGitHubIdentityVerified(attestationId: string, _username: string, cachedGists: GitHubGist[]): boolean;
|
|
43
|
+
declare function getGistCreationUrl(): string;
|
|
44
|
+
|
|
45
|
+
interface NostrVerificationResult {
|
|
46
|
+
verified: boolean;
|
|
47
|
+
event?: NostrEvent;
|
|
48
|
+
error?: string;
|
|
49
|
+
}
|
|
50
|
+
declare function verifyNostrIdentity(attestationId: string, npub: string, relays?: string[]): Promise<NostrVerificationResult>;
|
|
51
|
+
declare function getNostrVerificationInstructions(attestationId: string): string;
|
|
52
|
+
declare function isNostrIdentityVerified(attestationId: string, npub: string, cachedEvents: NostrEvent[]): boolean;
|
|
53
|
+
|
|
54
|
+
interface TwitterVerificationResult {
|
|
55
|
+
verified: boolean;
|
|
56
|
+
tweetUrl?: string;
|
|
57
|
+
tweetId?: string;
|
|
58
|
+
error?: string;
|
|
59
|
+
}
|
|
60
|
+
declare function verifyTwitterIdentity(attestationId: string, handle: string, tweetUrl?: string): Promise<TwitterVerificationResult>;
|
|
61
|
+
declare function getTwitterVerificationInstructions(attestationId: string, handle: string): string;
|
|
62
|
+
declare function generateTweetText(attestationId: string): string;
|
|
63
|
+
declare function getTwitterComposeUrl(attestationId: string): string;
|
|
64
|
+
declare function isValidTweetUrl(url: string): boolean;
|
|
65
|
+
declare function getTweetUrl(handle: string, tweetId: string): string;
|
|
66
|
+
declare function getManualVerificationInstructions(attestationId: string, handle: string, tweetUrl: string): string;
|
|
67
|
+
|
|
68
|
+
type VerificationResult = NostrVerificationResult | GitHubVerificationResult | TwitterVerificationResult | DnsVerificationResult;
|
|
69
|
+
interface VerifyIdentityOptions {
|
|
70
|
+
tweetUrl?: string;
|
|
71
|
+
relays?: string[];
|
|
72
|
+
}
|
|
73
|
+
declare function verifyIdentity(attestationId: string, identity: IdentityBinding, options?: VerifyIdentityOptions): Promise<VerificationResult>;
|
|
74
|
+
declare function getVerificationInstructions(attestationId: string, identity: IdentityBinding): string;
|
|
75
|
+
declare function supportsAutomaticVerification(protocol: string): boolean;
|
|
76
|
+
declare function requiresManualVerification(protocol: string): boolean;
|
|
77
|
+
declare function verifyAllIdentities(attestationId: string, identities: IdentityBinding[], options?: {
|
|
78
|
+
tweetUrls?: Record<string, string>;
|
|
79
|
+
relays?: string[];
|
|
80
|
+
}): Promise<Map<string, VerificationResult>>;
|
|
81
|
+
declare function getVerificationSummary(results: Map<string, VerificationResult>): {
|
|
82
|
+
total: number;
|
|
83
|
+
verified: number;
|
|
84
|
+
failed: number;
|
|
85
|
+
pending: number;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export { type DnsVerificationResult, type GitHubVerificationResult, type NostrVerificationResult, type TwitterVerificationResult, type VerificationResult, type VerifyIdentityOptions, generateGistTemplate, generateTweetText, generateWellKnownContent, getDnsVerificationInstructions, getGistCreationUrl, getGitHubVerificationInstructions, getManualVerificationInstructions, getNostrVerificationInstructions, getTweetUrl, getTwitterComposeUrl, getTwitterVerificationInstructions, getVerificationInstructions, getVerificationSummary, getWellKnownUrl, isDnsIdentityVerified, isGitHubIdentityVerified, isNostrIdentityVerified, isValidDomain, isValidTweetUrl, requiresManualVerification, supportsAutomaticVerification, verifyAllIdentities, verifyDnsIdentity, verifyGitHubIdentity, verifyIdentity, verifyNostrIdentity, verifyTwitterIdentity };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { N as NostrEvent, I as IdentityBinding } from './types-BJKDroFv.js';
|
|
2
|
+
|
|
3
|
+
interface DnsVerificationResult {
|
|
4
|
+
verified: boolean;
|
|
5
|
+
method?: 'well-known' | 'dns-txt';
|
|
6
|
+
url?: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
declare function verifyDnsIdentity(attestationId: string, domain: string): Promise<DnsVerificationResult>;
|
|
10
|
+
declare function getDnsVerificationInstructions(attestationId: string, domain: string): string;
|
|
11
|
+
declare function generateWellKnownContent(attestationId: string, domain: string): string;
|
|
12
|
+
declare function getWellKnownUrl(domain: string): string;
|
|
13
|
+
declare function isValidDomain(domain: string): boolean;
|
|
14
|
+
declare function isDnsIdentityVerified(attestationId: string, _domain: string, cachedContent?: string): boolean;
|
|
15
|
+
|
|
16
|
+
interface GitHubVerificationResult {
|
|
17
|
+
verified: boolean;
|
|
18
|
+
gistUrl?: string;
|
|
19
|
+
gistId?: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
interface GitHubGistFile {
|
|
23
|
+
filename: string;
|
|
24
|
+
type: string;
|
|
25
|
+
language: string | null;
|
|
26
|
+
raw_url: string;
|
|
27
|
+
size: number;
|
|
28
|
+
content?: string;
|
|
29
|
+
}
|
|
30
|
+
interface GitHubGist {
|
|
31
|
+
id: string;
|
|
32
|
+
html_url: string;
|
|
33
|
+
description: string;
|
|
34
|
+
public: boolean;
|
|
35
|
+
created_at: string;
|
|
36
|
+
updated_at: string;
|
|
37
|
+
files: Record<string, GitHubGistFile>;
|
|
38
|
+
}
|
|
39
|
+
declare function verifyGitHubIdentity(attestationId: string, username: string): Promise<GitHubVerificationResult>;
|
|
40
|
+
declare function getGitHubVerificationInstructions(attestationId: string): string;
|
|
41
|
+
declare function generateGistTemplate(attestationId: string, username: string): string;
|
|
42
|
+
declare function isGitHubIdentityVerified(attestationId: string, _username: string, cachedGists: GitHubGist[]): boolean;
|
|
43
|
+
declare function getGistCreationUrl(): string;
|
|
44
|
+
|
|
45
|
+
interface NostrVerificationResult {
|
|
46
|
+
verified: boolean;
|
|
47
|
+
event?: NostrEvent;
|
|
48
|
+
error?: string;
|
|
49
|
+
}
|
|
50
|
+
declare function verifyNostrIdentity(attestationId: string, npub: string, relays?: string[]): Promise<NostrVerificationResult>;
|
|
51
|
+
declare function getNostrVerificationInstructions(attestationId: string): string;
|
|
52
|
+
declare function isNostrIdentityVerified(attestationId: string, npub: string, cachedEvents: NostrEvent[]): boolean;
|
|
53
|
+
|
|
54
|
+
interface TwitterVerificationResult {
|
|
55
|
+
verified: boolean;
|
|
56
|
+
tweetUrl?: string;
|
|
57
|
+
tweetId?: string;
|
|
58
|
+
error?: string;
|
|
59
|
+
}
|
|
60
|
+
declare function verifyTwitterIdentity(attestationId: string, handle: string, tweetUrl?: string): Promise<TwitterVerificationResult>;
|
|
61
|
+
declare function getTwitterVerificationInstructions(attestationId: string, handle: string): string;
|
|
62
|
+
declare function generateTweetText(attestationId: string): string;
|
|
63
|
+
declare function getTwitterComposeUrl(attestationId: string): string;
|
|
64
|
+
declare function isValidTweetUrl(url: string): boolean;
|
|
65
|
+
declare function getTweetUrl(handle: string, tweetId: string): string;
|
|
66
|
+
declare function getManualVerificationInstructions(attestationId: string, handle: string, tweetUrl: string): string;
|
|
67
|
+
|
|
68
|
+
type VerificationResult = NostrVerificationResult | GitHubVerificationResult | TwitterVerificationResult | DnsVerificationResult;
|
|
69
|
+
interface VerifyIdentityOptions {
|
|
70
|
+
tweetUrl?: string;
|
|
71
|
+
relays?: string[];
|
|
72
|
+
}
|
|
73
|
+
declare function verifyIdentity(attestationId: string, identity: IdentityBinding, options?: VerifyIdentityOptions): Promise<VerificationResult>;
|
|
74
|
+
declare function getVerificationInstructions(attestationId: string, identity: IdentityBinding): string;
|
|
75
|
+
declare function supportsAutomaticVerification(protocol: string): boolean;
|
|
76
|
+
declare function requiresManualVerification(protocol: string): boolean;
|
|
77
|
+
declare function verifyAllIdentities(attestationId: string, identities: IdentityBinding[], options?: {
|
|
78
|
+
tweetUrls?: Record<string, string>;
|
|
79
|
+
relays?: string[];
|
|
80
|
+
}): Promise<Map<string, VerificationResult>>;
|
|
81
|
+
declare function getVerificationSummary(results: Map<string, VerificationResult>): {
|
|
82
|
+
total: number;
|
|
83
|
+
verified: number;
|
|
84
|
+
failed: number;
|
|
85
|
+
pending: number;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export { type DnsVerificationResult, type GitHubVerificationResult, type NostrVerificationResult, type TwitterVerificationResult, type VerificationResult, type VerifyIdentityOptions, generateGistTemplate, generateTweetText, generateWellKnownContent, getDnsVerificationInstructions, getGistCreationUrl, getGitHubVerificationInstructions, getManualVerificationInstructions, getNostrVerificationInstructions, getTweetUrl, getTwitterComposeUrl, getTwitterVerificationInstructions, getVerificationInstructions, getVerificationSummary, getWellKnownUrl, isDnsIdentityVerified, isGitHubIdentityVerified, isNostrIdentityVerified, isValidDomain, isValidTweetUrl, requiresManualVerification, supportsAutomaticVerification, verifyAllIdentities, verifyDnsIdentity, verifyGitHubIdentity, verifyIdentity, verifyNostrIdentity, verifyTwitterIdentity };
|