@naughtbot/sdk 0.0.1

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.
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Core captcha session logic.
3
+ *
4
+ * Handles ephemeral key generation, QR code URL creation, relay polling,
5
+ * E2E encrypted challenge/response exchange, and SAS computation.
6
+ */
7
+ import { generateKeyPair, deriveRequestKey, deriveResponseKey, encrypt, decrypt, requestIdToBytes, } from "@ackagent/web-sdk";
8
+ import { base64urlEncode } from "@ackagent/web-sdk";
9
+ import { computeSAS } from "@ackagent/web-sdk";
10
+ import { DEFAULT_TIMEOUT_MS, DEFAULT_LOGIN_URL } from "./types.js";
11
+ /** Polling interval for checking phone connection (ms) */
12
+ const POLL_INTERVAL_MS = 1_500;
13
+ /** Create a new captcha session */
14
+ export async function createSession(options) {
15
+ const { nonce, relayUrl, loginUrl = DEFAULT_LOGIN_URL, timeoutMs = DEFAULT_TIMEOUT_MS, debug = false, } = options;
16
+ if (!relayUrl) {
17
+ throw new Error("relayUrl is required for QR mode");
18
+ }
19
+ const log = debug ? (...args) => console.debug("[NaughtBot]", ...args) : () => { };
20
+ // Generate ephemeral P-256 key pair (not persisted)
21
+ const ephemeralKeyPair = await generateKeyPair();
22
+ let currentState = "initializing";
23
+ let currentSas = null;
24
+ let cancelled = false;
25
+ const stateCallbacks = [];
26
+ function setState(newState, sas) {
27
+ currentState = newState;
28
+ if (sas !== undefined) {
29
+ currentSas = sas;
30
+ }
31
+ for (const cb of stateCallbacks) {
32
+ cb(currentState, currentSas);
33
+ }
34
+ }
35
+ // Create session on relay — server generates session ID
36
+ const sessionResult = await createRelaySession(relayUrl, ephemeralKeyPair.publicKey, log);
37
+ if (!sessionResult) {
38
+ setState("error");
39
+ throw new Error("Failed to create captcha session on relay");
40
+ }
41
+ const sessionId = sessionResult.sessionId;
42
+ const requestIdBytes = requestIdToBytes(sessionId);
43
+ log("Session created:", sessionId);
44
+ // Build QR code URL using server-returned session ID
45
+ const publicKeyB64 = base64urlEncode(ephemeralKeyPair.publicKey);
46
+ const qrCodeUrl = `${loginUrl}/link/captcha?sid=${sessionId}&pk=${publicKeyB64}`;
47
+ log("QR URL:", qrCodeUrl);
48
+ setState("waiting_for_scan");
49
+ const session = {
50
+ get sessionId() {
51
+ return sessionId;
52
+ },
53
+ get qrCodeUrl() {
54
+ return qrCodeUrl;
55
+ },
56
+ get state() {
57
+ return currentState;
58
+ },
59
+ get sas() {
60
+ return currentSas;
61
+ },
62
+ async waitForResult() {
63
+ const domain = typeof window !== "undefined" ? window.location.hostname : "unknown";
64
+ const deadline = Date.now() + timeoutMs;
65
+ // Phase 1: Poll for phone connection (phone posts its ephemeral public key)
66
+ const phonePublicKey = await pollForPhoneConnection(relayUrl, sessionId, deadline, () => cancelled, log);
67
+ if (cancelled)
68
+ throw new Error("Session cancelled");
69
+ setState("phone_connected");
70
+ // Compute SAS from both public keys
71
+ const approverKey = {
72
+ encryptionPublicKeyHex: hexEncode(phonePublicKey),
73
+ publicKey: phonePublicKey,
74
+ };
75
+ const sasResult = computeSAS(ephemeralKeyPair.publicKey, [approverKey]);
76
+ setState("sas_verification", sasResult);
77
+ log("SAS:", sasResult.wordString);
78
+ // Phase 1.5: Wait for phone to confirm SAS match (relay-mediated)
79
+ await pollForSASConfirmation(relayUrl, sessionId, deadline, () => cancelled, log);
80
+ if (cancelled)
81
+ throw new Error("Session cancelled");
82
+ // Phase 2: Send encrypted captcha challenge
83
+ const challenge = {
84
+ type: "captcha",
85
+ domain,
86
+ nonce,
87
+ timestamp: Date.now(),
88
+ };
89
+ const challengeBytes = new TextEncoder().encode(JSON.stringify(challenge));
90
+ const requestKey = await deriveRequestKey(ephemeralKeyPair.privateKey, phonePublicKey, requestIdBytes);
91
+ const { ciphertext, nonce: encNonce } = encrypt(requestKey, challengeBytes, requestIdBytes);
92
+ await postEncryptedChallenge(relayUrl, sessionId, ciphertext, encNonce, log);
93
+ setState("verifying");
94
+ // Phase 3: Poll for encrypted response
95
+ const encryptedResponse = await pollForResponse(relayUrl, sessionId, deadline, () => cancelled, log);
96
+ if (cancelled)
97
+ throw new Error("Session cancelled");
98
+ // Decrypt response (fields are hex-encoded from relay)
99
+ const responseKey = await deriveResponseKey(ephemeralKeyPair.privateKey, phonePublicKey, requestIdBytes);
100
+ const decrypted = decrypt(responseKey, hexDecodeBytes(encryptedResponse.nonce), hexDecodeBytes(encryptedResponse.ciphertext), requestIdBytes);
101
+ const response = JSON.parse(new TextDecoder().decode(decrypted));
102
+ if (!response.approved) {
103
+ setState("error");
104
+ throw new Error("Verification declined by user");
105
+ }
106
+ const result = {
107
+ proof: response.attestation
108
+ ? base64urlEncode(new TextEncoder().encode(JSON.stringify(response.attestation)))
109
+ : "",
110
+ nonce,
111
+ domain,
112
+ };
113
+ setState("verified");
114
+ log("Verification complete");
115
+ return result;
116
+ },
117
+ confirmSAS() {
118
+ // No-op for QR mode — SAS confirmation happens on the phone side
119
+ },
120
+ cancel() {
121
+ cancelled = true;
122
+ setState("error");
123
+ },
124
+ onStateChange(callback) {
125
+ stateCallbacks.push(callback);
126
+ },
127
+ };
128
+ return session;
129
+ }
130
+ // --- Relay communication helpers ---
131
+ // All endpoints target the naughtbot-relay service (separate from AckAgent relay).
132
+ // Session creation is IP rate-limited; all other endpoints are unauthenticated.
133
+ // Phone-initiated endpoints (connect, confirm-sas, respond) use BBS+ anonymous auth.
134
+ /** Create a captcha session on the naughtbot-relay server */
135
+ async function createRelaySession(relayUrl, ephemeralPublicKey, log) {
136
+ try {
137
+ const response = await fetch(`${relayUrl}/api/v1/captcha-sessions`, {
138
+ method: "POST",
139
+ headers: { "Content-Type": "application/json" },
140
+ body: JSON.stringify({
141
+ requesterEphemeralKeyHex: hexEncode(ephemeralPublicKey),
142
+ }),
143
+ });
144
+ if (!response.ok) {
145
+ const body = await response.text().catch(() => "");
146
+ log("Failed to create session:", response.status, body);
147
+ return null;
148
+ }
149
+ const data = await response.json();
150
+ return { sessionId: data.sessionId };
151
+ }
152
+ catch (error) {
153
+ log("Error creating session:", error);
154
+ return null;
155
+ }
156
+ }
157
+ /** Poll relay for phone connection (phone's ephemeral public key) */
158
+ async function pollForPhoneConnection(relayUrl, sessionId, deadline, isCancelled, log) {
159
+ while (Date.now() < deadline && !isCancelled()) {
160
+ try {
161
+ const response = await fetch(`${relayUrl}/api/v1/captcha-sessions/${sessionId}`);
162
+ if (response.ok) {
163
+ const data = await response.json();
164
+ if (data.approverEphemeralKeyHex) {
165
+ log("Phone connected");
166
+ return hexDecodeBytes(data.approverEphemeralKeyHex);
167
+ }
168
+ }
169
+ }
170
+ catch {
171
+ // Network error, retry
172
+ }
173
+ await sleep(POLL_INTERVAL_MS);
174
+ }
175
+ if (isCancelled()) {
176
+ throw new Error("Session cancelled");
177
+ }
178
+ throw new Error("Session expired waiting for phone connection");
179
+ }
180
+ /** Poll relay until phone confirms SAS match */
181
+ async function pollForSASConfirmation(relayUrl, sessionId, deadline, isCancelled, log) {
182
+ while (Date.now() < deadline && !isCancelled()) {
183
+ try {
184
+ const response = await fetch(`${relayUrl}/api/v1/captcha-sessions/${sessionId}`);
185
+ if (response.ok) {
186
+ const data = await response.json();
187
+ if (data.status === "sas_confirmed" ||
188
+ data.status === "challenged" ||
189
+ data.status === "responded") {
190
+ log("Phone confirmed SAS match");
191
+ return;
192
+ }
193
+ }
194
+ }
195
+ catch {
196
+ // Network error, retry
197
+ }
198
+ await sleep(POLL_INTERVAL_MS);
199
+ }
200
+ if (isCancelled()) {
201
+ throw new Error("Session cancelled");
202
+ }
203
+ throw new Error("Session expired waiting for SAS confirmation");
204
+ }
205
+ /** Post encrypted captcha challenge to relay */
206
+ async function postEncryptedChallenge(relayUrl, sessionId, ciphertext, nonce, log) {
207
+ const response = await fetch(`${relayUrl}/api/v1/captcha-sessions/${sessionId}/challenge`, {
208
+ method: "POST",
209
+ headers: { "Content-Type": "application/json" },
210
+ body: JSON.stringify({
211
+ encryptedChallenge: hexEncode(ciphertext),
212
+ challengeNonce: hexEncode(nonce),
213
+ }),
214
+ });
215
+ if (!response.ok) {
216
+ throw new Error(`Failed to post challenge: ${response.status}`);
217
+ }
218
+ log("Challenge posted");
219
+ }
220
+ /** Poll unified status endpoint for encrypted response from phone */
221
+ async function pollForResponse(relayUrl, sessionId, deadline, isCancelled, log) {
222
+ while (Date.now() < deadline && !isCancelled()) {
223
+ try {
224
+ const response = await fetch(`${relayUrl}/api/v1/captcha-sessions/${sessionId}`);
225
+ if (response.ok) {
226
+ const data = await response.json();
227
+ if (data.status === "responded" && data.encryptedResponse && data.responseNonce) {
228
+ log("Response received");
229
+ return {
230
+ ciphertext: data.encryptedResponse,
231
+ nonce: data.responseNonce,
232
+ };
233
+ }
234
+ }
235
+ }
236
+ catch {
237
+ // Network error, retry
238
+ }
239
+ await sleep(POLL_INTERVAL_MS);
240
+ }
241
+ if (isCancelled()) {
242
+ throw new Error("Session cancelled");
243
+ }
244
+ throw new Error("Session expired waiting for response");
245
+ }
246
+ function sleep(ms) {
247
+ return new Promise((resolve) => setTimeout(resolve, ms));
248
+ }
249
+ /** Hex-encode bytes (lowercase) */
250
+ export function hexEncode(data) {
251
+ return Array.from(data, (b) => b.toString(16).padStart(2, "0")).join("");
252
+ }
253
+ /** Hex-decode string to bytes */
254
+ function hexDecodeBytes(hex) {
255
+ if (hex.length % 2 !== 0) {
256
+ throw new Error("hex string must have even length");
257
+ }
258
+ const bytes = new Uint8Array(hex.length / 2);
259
+ for (let i = 0; i < hex.length; i += 2) {
260
+ bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
261
+ }
262
+ return bytes;
263
+ }
264
+ //# sourceMappingURL=captcha.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"captcha.js","sourceRoot":"","sources":["../src/captcha.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,OAAO,EACP,OAAO,EACP,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAuB,MAAM,mBAAmB,CAAC;AASpE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEnE,0DAA0D;AAC1D,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAiC/B,mCAAmC;AACnC,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAuB;IACzD,MAAM,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,GAAG,iBAAiB,EAC5B,SAAS,GAAG,kBAAkB,EAC9B,KAAK,GAAG,KAAK,GACd,GAAG,OAAO,CAAC;IAEZ,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;IAE7F,oDAAoD;IACpD,MAAM,gBAAgB,GAAG,MAAM,eAAe,EAAE,CAAC;IAEjD,IAAI,YAAY,GAAwB,cAAc,CAAC;IACvD,IAAI,UAAU,GAAsB,IAAI,CAAC;IACzC,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,MAAM,cAAc,GAAwE,EAAE,CAAC;IAE/F,SAAS,QAAQ,CAAC,QAA6B,EAAE,GAAuB;QACtE,YAAY,GAAG,QAAQ,CAAC;QACxB,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,UAAU,GAAG,GAAG,CAAC;QACnB,CAAC;QACD,KAAK,MAAM,EAAE,IAAI,cAAc,EAAE,CAAC;YAChC,EAAE,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,wDAAwD;IACxD,MAAM,aAAa,GAAG,MAAM,kBAAkB,CAAC,QAAQ,EAAE,gBAAgB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAC1F,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,QAAQ,CAAC,OAAO,CAAC,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,SAAS,GAAG,aAAa,CAAC,SAAS,CAAC;IAC1C,MAAM,cAAc,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAEnD,GAAG,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;IAEnC,qDAAqD;IACrD,MAAM,YAAY,GAAG,eAAe,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;IACjE,MAAM,SAAS,GAAG,GAAG,QAAQ,qBAAqB,SAAS,OAAO,YAAY,EAAE,CAAC;IAEjF,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAE1B,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAE7B,MAAM,OAAO,GAAmB;QAC9B,IAAI,SAAS;YACX,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,SAAS;YACX,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,KAAK;YACP,OAAO,YAAY,CAAC;QACtB,CAAC;QACD,IAAI,GAAG;YACL,OAAO,UAAU,CAAC;QACpB,CAAC;QAED,KAAK,CAAC,aAAa;YACjB,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;YACpF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAExC,4EAA4E;YAC5E,MAAM,cAAc,GAAG,MAAM,sBAAsB,CACjD,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,CACJ,CAAC;YAEF,IAAI,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;YAEpD,QAAQ,CAAC,iBAAiB,CAAC,CAAC;YAE5B,oCAAoC;YACpC,MAAM,WAAW,GAAmB;gBAClC,sBAAsB,EAAE,SAAS,CAAC,cAAc,CAAC;gBACjD,SAAS,EAAE,cAAc;aAC1B,CAAC;YACF,MAAM,SAAS,GAAG,UAAU,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;YAExE,QAAQ,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;YACxC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;YAElC,kEAAkE;YAClE,MAAM,sBAAsB,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YAClF,IAAI,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;YAEpD,4CAA4C;YAC5C,MAAM,SAAS,GAAqB;gBAClC,IAAI,EAAE,SAAS;gBACf,MAAM;gBACN,KAAK;gBACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;aACtB,CAAC;YAEF,MAAM,cAAc,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC;YAC3E,MAAM,UAAU,GAAG,MAAM,gBAAgB,CACvC,gBAAgB,CAAC,UAAU,EAC3B,cAAc,EACd,cAAc,CACf,CAAC;YACF,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;YAE5F,MAAM,sBAAsB,CAAC,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;YAE7E,QAAQ,CAAC,WAAW,CAAC,CAAC;YAEtB,uCAAuC;YACvC,MAAM,iBAAiB,GAAG,MAAM,eAAe,CAC7C,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,CACJ,CAAC;YAEF,IAAI,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;YAEpD,uDAAuD;YACvD,MAAM,WAAW,GAAG,MAAM,iBAAiB,CACzC,gBAAgB,CAAC,UAAU,EAC3B,cAAc,EACd,cAAc,CACf,CAAC;YACF,MAAM,SAAS,GAAG,OAAO,CACvB,WAAW,EACX,cAAc,CAAC,iBAAiB,CAAC,KAAK,CAAC,EACvC,cAAc,CAAC,iBAAiB,CAAC,UAAU,CAAC,EAC5C,cAAc,CACf,CAAC;YACF,MAAM,QAAQ,GAAoB,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;YAElF,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;gBACvB,QAAQ,CAAC,OAAO,CAAC,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;YACnD,CAAC;YAED,MAAM,MAAM,GAAkB;gBAC5B,KAAK,EAAE,QAAQ,CAAC,WAAW;oBACzB,CAAC,CAAC,eAAe,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;oBACjF,CAAC,CAAC,EAAE;gBACN,KAAK;gBACL,MAAM;aACP,CAAC;YAEF,QAAQ,CAAC,UAAU,CAAC,CAAC;YACrB,GAAG,CAAC,uBAAuB,CAAC,CAAC;YAC7B,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,UAAU;YACR,iEAAiE;QACnE,CAAC;QAED,MAAM;YACJ,SAAS,GAAG,IAAI,CAAC;YACjB,QAAQ,CAAC,OAAO,CAAC,CAAC;QACpB,CAAC;QAED,aAAa,CAAC,QAAQ;YACpB,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC;KACF,CAAC;IAEF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,sCAAsC;AACtC,mFAAmF;AACnF,gFAAgF;AAChF,qFAAqF;AAErF,6DAA6D;AAC7D,KAAK,UAAU,kBAAkB,CAC/B,QAAgB,EAChB,kBAA8B,EAC9B,GAAiC;IAEjC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,0BAA0B,EAAE;YAClE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,wBAAwB,EAAE,SAAS,CAAC,kBAAkB,CAAC;aACxD,CAAC;SACH,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YACnD,GAAG,CAAC,2BAA2B,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACxD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,qEAAqE;AACrE,KAAK,UAAU,sBAAsB,CACnC,QAAgB,EAChB,SAAiB,EACjB,QAAgB,EAChB,WAA0B,EAC1B,GAAiC;IAEjC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QAC/C,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,4BAA4B,SAAS,EAAE,CAAC,CAAC;YAEjF,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAChB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACnC,IAAI,IAAI,CAAC,uBAAuB,EAAE,CAAC;oBACjC,GAAG,CAAC,iBAAiB,CAAC,CAAC;oBACvB,OAAO,cAAc,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;gBACtD,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;QAED,MAAM,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAChC,CAAC;IAED,IAAI,WAAW,EAAE,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACvC,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;AAClE,CAAC;AAED,gDAAgD;AAChD,KAAK,UAAU,sBAAsB,CACnC,QAAgB,EAChB,SAAiB,EACjB,QAAgB,EAChB,WAA0B,EAC1B,GAAiC;IAEjC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QAC/C,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,4BAA4B,SAAS,EAAE,CAAC,CAAC;YAEjF,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAChB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACnC,IACE,IAAI,CAAC,MAAM,KAAK,eAAe;oBAC/B,IAAI,CAAC,MAAM,KAAK,YAAY;oBAC5B,IAAI,CAAC,MAAM,KAAK,WAAW,EAC3B,CAAC;oBACD,GAAG,CAAC,2BAA2B,CAAC,CAAC;oBACjC,OAAO;gBACT,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;QAED,MAAM,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAChC,CAAC;IAED,IAAI,WAAW,EAAE,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACvC,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;AAClE,CAAC;AAED,gDAAgD;AAChD,KAAK,UAAU,sBAAsB,CACnC,QAAgB,EAChB,SAAiB,EACjB,UAAsB,EACtB,KAAiB,EACjB,GAAiC;IAEjC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,4BAA4B,SAAS,YAAY,EAAE;QACzF,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,kBAAkB,EAAE,SAAS,CAAC,UAAU,CAAC;YACzC,cAAc,EAAE,SAAS,CAAC,KAAK,CAAC;SACjC,CAAC;KACH,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,6BAA6B,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,GAAG,CAAC,kBAAkB,CAAC,CAAC;AAC1B,CAAC;AAED,qEAAqE;AACrE,KAAK,UAAU,eAAe,CAC5B,QAAgB,EAChB,SAAiB,EACjB,QAAgB,EAChB,WAA0B,EAC1B,GAAiC;IAEjC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;QAC/C,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,4BAA4B,SAAS,EAAE,CAAC,CAAC;YAEjF,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAChB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBACnC,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW,IAAI,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;oBAChF,GAAG,CAAC,mBAAmB,CAAC,CAAC;oBACzB,OAAO;wBACL,UAAU,EAAE,IAAI,CAAC,iBAAiB;wBAClC,KAAK,EAAE,IAAI,CAAC,aAAa;qBAC1B,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;QAED,MAAM,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAChC,CAAC;IAED,IAAI,WAAW,EAAE,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;IACvC,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,SAAS,CAAC,IAAgB;IACxC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC3E,CAAC;AAED,iCAAiC;AACjC,SAAS,cAAc,CAAC,GAAW;IACjC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * NaughtBot SDK — cryptographic proof of human interaction via biometric verification.
3
+ *
4
+ * Provides a captcha replacement that uses E2E encrypted communication with
5
+ * a user's mobile device for biometric verification and BBS+ anonymous attestation.
6
+ */
7
+ export type { CaptchaOptions, CaptchaResult, CaptchaSessionState, CaptchaWidgetOptions, SASDisplay, } from "./types.js";
8
+ export { createSession } from "./captcha.js";
9
+ export type { CaptchaSession } from "./captcha.js";
10
+ export { createWidget } from "./widget.js";
11
+ export type { CaptchaWidget } from "./widget.js";
12
+ export { verifyCaptchaProof } from "./verify.js";
13
+ export type { VerificationResult, IssuerPublicKey } from "./verify.js";
14
+ export { createBLESession, isBLESupported } from "./ble.js";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,YAAY,EACV,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,UAAU,GACX,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGnD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGvE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * NaughtBot SDK — cryptographic proof of human interaction via biometric verification.
3
+ *
4
+ * Provides a captcha replacement that uses E2E encrypted communication with
5
+ * a user's mobile device for biometric verification and BBS+ anonymous attestation.
6
+ */
7
+ // Core session API
8
+ export { createSession } from "./captcha.js";
9
+ // Widget API
10
+ export { createWidget } from "./widget.js";
11
+ // Verification
12
+ export { verifyCaptchaProof } from "./verify.js";
13
+ // BLE transport
14
+ export { createBLESession, isBLESupported } from "./ble.js";
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,mBAAmB;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG7C,aAAa;AACb,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,eAAe;AACf,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGjD,gBAAgB;AAChB,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Type definitions for NaughtBot captcha sessions.
3
+ */
4
+ /** Options for creating a captcha session */
5
+ export interface CaptchaOptions {
6
+ /** Server-generated nonce for replay protection */
7
+ nonce: string;
8
+ /** Relay server URL (e.g., "https://relay.naughtbot.com"). Required for QR mode. */
9
+ relayUrl?: string;
10
+ /** Login/identity service URL for QR code links (e.g., "https://login.naughtbot.com") */
11
+ loginUrl?: string;
12
+ /** Transport mode */
13
+ mode?: "qr" | "ble" | "auto";
14
+ /** Session timeout in milliseconds (default: 300000 = 5 min) */
15
+ timeoutMs?: number;
16
+ /** Enable debug logging */
17
+ debug?: boolean;
18
+ }
19
+ /** Options for the captcha widget */
20
+ export interface CaptchaWidgetOptions extends CaptchaOptions {
21
+ /** Callback when verification completes successfully */
22
+ onVerified: (result: CaptchaResult) => void;
23
+ /** Callback when an error occurs */
24
+ onError?: (error: Error) => void;
25
+ /** Callback when the session expires */
26
+ onExpired?: () => void;
27
+ /** Callback when session state changes */
28
+ onStateChange?: (state: CaptchaSessionState) => void;
29
+ }
30
+ /** Result of a successful captcha verification */
31
+ export interface CaptchaResult {
32
+ /** BBS+ anonymous proof (base64url) */
33
+ proof: string;
34
+ /** Original nonce (for server-side correlation) */
35
+ nonce: string;
36
+ /** Domain that was verified (e.g., "example.com") */
37
+ domain: string;
38
+ }
39
+ /** State of a captcha session */
40
+ export type CaptchaSessionState = "initializing" | "waiting_for_scan" | "phone_connected" | "sas_verification" | "verifying" | "verified" | "expired" | "error";
41
+ /** SAS (Short Authentication String) for visual verification */
42
+ export interface SASDisplay {
43
+ words: string[];
44
+ emojis: string[];
45
+ wordString: string;
46
+ emojiString: string;
47
+ }
48
+ /** Internal: captcha challenge sent from browser to phone */
49
+ export interface CaptchaChallenge {
50
+ type: "captcha";
51
+ domain: string;
52
+ nonce: string;
53
+ timestamp: number;
54
+ }
55
+ /** Internal: captcha response from phone to browser */
56
+ export interface CaptchaResponse {
57
+ approved: boolean;
58
+ plaintextHash?: string;
59
+ attestation?: {
60
+ "@context": string[];
61
+ type: string[];
62
+ proof: {
63
+ type: string;
64
+ cryptosuite: string;
65
+ proofValue: string;
66
+ created?: string;
67
+ verificationMethod?: string;
68
+ proofPurpose?: string;
69
+ };
70
+ ackagentAnonymousAttestation: {
71
+ pseudonym: string;
72
+ scope: string;
73
+ presentationHeader: string;
74
+ revealedMessages: {
75
+ attestationType: string;
76
+ deviceType: string;
77
+ expiresAt: number;
78
+ };
79
+ issuerPublicKeyId: string;
80
+ };
81
+ };
82
+ }
83
+ /** Default timeout for captcha sessions (5 minutes) */
84
+ export declare const DEFAULT_TIMEOUT_MS = 300000;
85
+ /** Default login URL for QR code links */
86
+ export declare const DEFAULT_LOGIN_URL = "https://login.naughtbot.com";
87
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IAEd,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,yFAAyF;IACzF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,qBAAqB;IACrB,IAAI,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC;IAE7B,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,qCAAqC;AACrC,MAAM,WAAW,oBAAqB,SAAQ,cAAc;IAC1D,wDAAwD;IACxD,UAAU,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAE5C,oCAAoC;IACpC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IAEvB,0CAA0C;IAC1C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACtD;AAED,kDAAkD;AAClD,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IAEd,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IAEd,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,iCAAiC;AACjC,MAAM,MAAM,mBAAmB,GAC3B,cAAc,GACd,kBAAkB,GAClB,iBAAiB,GACjB,kBAAkB,GAClB,WAAW,GACX,UAAU,GACV,SAAS,GACT,OAAO,CAAC;AAEZ,gEAAgE;AAChE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,6DAA6D;AAC7D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE;QACZ,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,KAAK,EAAE;YACL,IAAI,EAAE,MAAM,CAAC;YACb,WAAW,EAAE,MAAM,CAAC;YACpB,UAAU,EAAE,MAAM,CAAC;YACnB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;YAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;SACvB,CAAC;QACF,4BAA4B,EAAE;YAC5B,SAAS,EAAE,MAAM,CAAC;YAClB,KAAK,EAAE,MAAM,CAAC;YACd,kBAAkB,EAAE,MAAM,CAAC;YAC3B,gBAAgB,EAAE;gBAChB,eAAe,EAAE,MAAM,CAAC;gBACxB,UAAU,EAAE,MAAM,CAAC;gBACnB,SAAS,EAAE,MAAM,CAAC;aACnB,CAAC;YACF,iBAAiB,EAAE,MAAM,CAAC;SAC3B,CAAC;KACH,CAAC;CACH;AAED,uDAAuD;AACvD,eAAO,MAAM,kBAAkB,SAAU,CAAC;AAE1C,0CAA0C;AAC1C,eAAO,MAAM,iBAAiB,gCAAgC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Type definitions for NaughtBot captcha sessions.
3
+ */
4
+ /** Default timeout for captcha sessions (5 minutes) */
5
+ export const DEFAULT_TIMEOUT_MS = 300_000;
6
+ /** Default login URL for QR code links */
7
+ export const DEFAULT_LOGIN_URL = "https://login.naughtbot.com";
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AA0GH,uDAAuD;AACvD,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAE1C,0CAA0C;AAC1C,MAAM,CAAC,MAAM,iBAAiB,GAAG,6BAA6B,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Client-side BBS+ proof verification for captcha results.
3
+ *
4
+ * This provides a convenience wrapper around @ackagent/web-sdk's BBS+ verification
5
+ * for verifying NaughtBot captcha proofs in the browser. Server-side verification
6
+ * is recommended for production use.
7
+ */
8
+ import type { CaptchaResult } from "./types.js";
9
+ /** Issuer public key for verifying BBS+ proofs */
10
+ export interface IssuerPublicKey {
11
+ /** Key ID matching the proof's issuerPublicKeyId */
12
+ keyId: string;
13
+ /** BLS12-381 G2 public key (96 bytes, base64url) */
14
+ publicKey: string;
15
+ }
16
+ /** Result of verifying a captcha proof */
17
+ export interface VerificationResult {
18
+ /** Whether the proof is valid */
19
+ valid: boolean;
20
+ /** Error message if verification failed */
21
+ error?: string;
22
+ /** Revealed attestation attributes */
23
+ attestation?: {
24
+ attestationType: string;
25
+ deviceType: string;
26
+ expiresAt: number;
27
+ };
28
+ }
29
+ /**
30
+ * Verify a NaughtBot captcha proof.
31
+ *
32
+ * This performs client-side verification of the BBS+ anonymous attestation
33
+ * included in the captcha result. For production use, verify server-side
34
+ * using the NaughtBot verification API.
35
+ *
36
+ * @param result - The captcha result to verify
37
+ * @param issuerPublicKey - The issuer's public key for BBS+ verification
38
+ * @returns Verification result
39
+ */
40
+ export declare function verifyCaptchaProof(result: CaptchaResult, issuerPublicKey: IssuerPublicKey, expectedDomain?: string): Promise<VerificationResult>;
41
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAKhD,kDAAkD;AAClD,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IAEd,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,KAAK,EAAE,OAAO,CAAC;IAEf,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,sCAAsC;IACtC,WAAW,CAAC,EAAE;QACZ,eAAe,EAAE,MAAM,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,aAAa,EACrB,eAAe,EAAE,eAAe,EAChC,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,kBAAkB,CAAC,CA+G7B"}
package/dist/verify.js ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Client-side BBS+ proof verification for captcha results.
3
+ *
4
+ * This provides a convenience wrapper around @ackagent/web-sdk's BBS+ verification
5
+ * for verifying NaughtBot captcha proofs in the browser. Server-side verification
6
+ * is recommended for production use.
7
+ */
8
+ import { verifyBbsProofWithPseudonym, base64urlDecode, base64urlEncode } from "@ackagent/web-sdk";
9
+ /** Total number of signer messages in BBS+ credential */
10
+ const BBS_TOTAL_SIGNER_MESSAGES = 4;
11
+ /**
12
+ * Verify a NaughtBot captcha proof.
13
+ *
14
+ * This performs client-side verification of the BBS+ anonymous attestation
15
+ * included in the captcha result. For production use, verify server-side
16
+ * using the NaughtBot verification API.
17
+ *
18
+ * @param result - The captcha result to verify
19
+ * @param issuerPublicKey - The issuer's public key for BBS+ verification
20
+ * @returns Verification result
21
+ */
22
+ export async function verifyCaptchaProof(result, issuerPublicKey, expectedDomain) {
23
+ if (!result.proof) {
24
+ return { valid: false, error: "No proof in captcha result" };
25
+ }
26
+ try {
27
+ // Decode the attestation from the proof field
28
+ const attestationJson = new TextDecoder().decode(base64urlDecode(result.proof));
29
+ const attestation = JSON.parse(attestationJson);
30
+ const payload = attestation.ackagentAnonymousAttestation;
31
+ if (!payload) {
32
+ return { valid: false, error: "Missing ackagentAnonymousAttestation in proof" };
33
+ }
34
+ // Verify the issuer key ID matches
35
+ if (payload.issuerPublicKeyId !== issuerPublicKey.keyId) {
36
+ return {
37
+ valid: false,
38
+ error: `Issuer key mismatch: expected ${issuerPublicKey.keyId}, got ${payload.issuerPublicKeyId}`,
39
+ };
40
+ }
41
+ // Verify scope is non-empty (it should be the sessionId)
42
+ if (!payload.scope) {
43
+ return { valid: false, error: "Missing scope in attestation" };
44
+ }
45
+ // Verify domain if expected domain is provided
46
+ // Domain binding comes from the presentation header (challenge-bound), not the scope
47
+ if (expectedDomain && result.domain !== expectedDomain) {
48
+ return {
49
+ valid: false,
50
+ error: `Domain mismatch: expected ${expectedDomain}, got ${result.domain}`,
51
+ };
52
+ }
53
+ // Verify BBS+ proof with pseudonym
54
+ const proofValue = attestation.proof?.proofValue;
55
+ if (!proofValue) {
56
+ return { valid: false, error: "Missing proofValue in attestation" };
57
+ }
58
+ // Strip multibase "u" prefix from proofValue (iOS produces "u" + base64url(proof))
59
+ const rawProofValue = proofValue.startsWith("u") ? proofValue.slice(1) : proofValue;
60
+ // Build disclosed messages map from revealed attributes
61
+ // Signer indices: 0=attestationType, 1=deviceType, 3=expiresAt
62
+ const disclosed = payload.revealedMessages;
63
+ const disclosedMessages = new Map();
64
+ if (disclosed.attestationType) {
65
+ disclosedMessages.set(0, new TextEncoder().encode(disclosed.attestationType));
66
+ }
67
+ if (disclosed.deviceType) {
68
+ disclosedMessages.set(1, new TextEncoder().encode(disclosed.deviceType));
69
+ }
70
+ if (disclosed.expiresAt) {
71
+ const buf = new ArrayBuffer(8);
72
+ new DataView(buf).setBigInt64(0, BigInt(disclosed.expiresAt));
73
+ disclosedMessages.set(3, new Uint8Array(buf));
74
+ }
75
+ // Recompute expected presentation header from result nonce + domain
76
+ // This binds the proof to the specific challenge (sha256(nonce || 0x00 || domain))
77
+ const phMessage = new Uint8Array([
78
+ ...new TextEncoder().encode(result.nonce),
79
+ 0x00,
80
+ ...new TextEncoder().encode(result.domain),
81
+ ]);
82
+ const phDigest = new Uint8Array(await crypto.subtle.digest("SHA-256", phMessage));
83
+ // iOS passes base64urlEncode(digest) as a String to generateRelayAuthProof,
84
+ // which UTF-8 encodes it internally. Match that encoding here.
85
+ const phBytes = new TextEncoder().encode(base64urlEncode(phDigest));
86
+ const bbsResult = await verifyBbsProofWithPseudonym(base64urlDecode(issuerPublicKey.publicKey), base64urlDecode(rawProofValue), base64urlDecode(payload.pseudonym), new TextEncoder().encode("ackagent-anonymous-attestation-v2"), phBytes, new TextEncoder().encode(payload.scope), disclosedMessages, BBS_TOTAL_SIGNER_MESSAGES, new Map(), // disclosed committed messages
87
+ []);
88
+ if (!bbsResult.verified) {
89
+ return { valid: false, error: bbsResult.error || "BBS+ proof verification failed" };
90
+ }
91
+ // Check credential expiry
92
+ const revealed = payload.revealedMessages;
93
+ if (revealed.expiresAt && revealed.expiresAt < Math.floor(Date.now() / 1000)) {
94
+ return { valid: false, error: "Credential has expired" };
95
+ }
96
+ return {
97
+ valid: true,
98
+ attestation: {
99
+ attestationType: revealed.attestationType,
100
+ deviceType: revealed.deviceType,
101
+ expiresAt: revealed.expiresAt,
102
+ },
103
+ };
104
+ }
105
+ catch (error) {
106
+ return {
107
+ valid: false,
108
+ error: error instanceof Error ? error.message : "Verification failed",
109
+ };
110
+ }
111
+ }
112
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * QR code widget for NaughtBot captcha.
3
+ * Renders a QR code, shows SAS words/emojis, handles lifecycle.
4
+ *
5
+ * Uses safe DOM APIs (createElement, textContent) instead of innerHTML.
6
+ */
7
+ import { type CaptchaSession } from "./captcha.js";
8
+ import type { CaptchaWidgetOptions } from "./types.js";
9
+ /** Widget instance that can be destroyed */
10
+ export interface CaptchaWidget {
11
+ /** Destroy the widget and clean up */
12
+ destroy(): void;
13
+ /** Get the underlying session */
14
+ readonly session: CaptchaSession | null;
15
+ }
16
+ /**
17
+ * Create a captcha widget in the specified container.
18
+ *
19
+ * @param container - CSS selector or DOM element
20
+ * @param options - Widget options
21
+ * @returns Widget instance
22
+ */
23
+ export declare function createWidget(container: string | HTMLElement, options: CaptchaWidgetOptions): Promise<CaptchaWidget>;
24
+ //# sourceMappingURL=widget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.d.ts","sourceRoot":"","sources":["../src/widget.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAElE,OAAO,KAAK,EACV,oBAAoB,EAIrB,MAAM,YAAY,CAAC;AAEpB,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC5B,sCAAsC;IACtC,OAAO,IAAI,IAAI,CAAC;IAEhB,iCAAiC;IACjC,QAAQ,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;CACzC;AAKD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,GAAG,WAAW,EAC/B,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,aAAa,CAAC,CAwGxB"}