@chipi-stack/chipi-passkey 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/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # @chipi-stack/chipi-passkey
2
+
3
+ WebAuthn passkey utilities for secure wallet encryption in Chipi SDK.
4
+
5
+ ## Features
6
+
7
+ - 🔐 WebAuthn/Passkey authentication for wallet encryption
8
+ - 🔑 PRF (Pseudo-Random Function) extension support for deterministic key derivation
9
+ - 🌐 Browser-based biometric authentication
10
+ - 🔄 Migration utilities for switching from PIN to passkey
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @chipi-stack/chipi-passkey
16
+ # or
17
+ pnpm add @chipi-stack/chipi-passkey
18
+ # or
19
+ yarn add @chipi-stack/chipi-passkey
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ### Check Passkey Support
25
+
26
+ ```typescript
27
+ import {
28
+ isWebAuthnSupported,
29
+ hasWalletPasskey,
30
+ } from "@chipi-stack/chipi-passkey";
31
+
32
+ if (isWebAuthnSupported()) {
33
+ const hasPasskey = hasWalletPasskey();
34
+ console.log("Passkey available:", hasPasskey);
35
+ }
36
+ ```
37
+
38
+ ### Create Wallet with Passkey
39
+
40
+ ```typescript
41
+ import { createWalletPasskey } from "@chipi-stack/chipi-passkey";
42
+
43
+ const result = await createWalletPasskey("user-123", "john@example.com");
44
+ // result.encryptKey - Use this as wallet encryption key
45
+ // result.credentialId - Passkey credential ID
46
+ // result.prfSupported - Whether PRF extension is supported
47
+ ```
48
+
49
+ ### Authenticate with Passkey
50
+
51
+ ```typescript
52
+ import { getWalletEncryptKey } from "@chipi-stack/chipi-passkey";
53
+
54
+ // Prompts user for biometric authentication
55
+ const encryptKey = await getWalletEncryptKey();
56
+ // Use this encryptKey for transactions
57
+ ```
58
+
59
+ ### React Hooks
60
+
61
+ ```typescript
62
+ import { usePasskeySetup, usePasskeyAuth } from "@chipi-stack/chipi-passkey";
63
+
64
+ function WalletSetup() {
65
+ const { setupPasskey, isLoading } = usePasskeySetup();
66
+
67
+ const handleSetup = async () => {
68
+ const { encryptKey } = await setupPasskey("user-123", "john@example.com");
69
+ // Create wallet with encryptKey
70
+ };
71
+ }
72
+
73
+ function SendTransaction() {
74
+ const { authenticate } = usePasskeyAuth();
75
+
76
+ const handleSend = async () => {
77
+ const encryptKey = await authenticate();
78
+ // Use encryptKey for transaction
79
+ };
80
+ }
81
+ ```
82
+
83
+ ## Requirements
84
+
85
+ - Modern browser with WebAuthn support
86
+ - HTTPS (required for WebAuthn)
87
+ - React 18+ (for hooks)
88
+
89
+ ## License
90
+
91
+ MIT
@@ -0,0 +1,18 @@
1
+ import { UsePasskeySetupResult, UsePasskeyAuthResult, UsePasskeyStatusResult } from './index.mjs';
2
+
3
+ /**
4
+ * Hook for setting up a new passkey
5
+ */
6
+ declare function usePasskeySetup(): UsePasskeySetupResult;
7
+
8
+ /**
9
+ * Hook for authenticating with passkey to get encryption key
10
+ */
11
+ declare function usePasskeyAuth(): UsePasskeyAuthResult;
12
+
13
+ /**
14
+ * Hook for checking passkey status
15
+ */
16
+ declare function usePasskeyStatus(): UsePasskeyStatusResult;
17
+
18
+ export { usePasskeyAuth, usePasskeySetup, usePasskeyStatus };
@@ -0,0 +1,18 @@
1
+ import { UsePasskeySetupResult, UsePasskeyAuthResult, UsePasskeyStatusResult } from './index.js';
2
+
3
+ /**
4
+ * Hook for setting up a new passkey
5
+ */
6
+ declare function usePasskeySetup(): UsePasskeySetupResult;
7
+
8
+ /**
9
+ * Hook for authenticating with passkey to get encryption key
10
+ */
11
+ declare function usePasskeyAuth(): UsePasskeyAuthResult;
12
+
13
+ /**
14
+ * Hook for checking passkey status
15
+ */
16
+ declare function usePasskeyStatus(): UsePasskeyStatusResult;
17
+
18
+ export { usePasskeyAuth, usePasskeySetup, usePasskeyStatus };
package/dist/hooks.js ADDED
@@ -0,0 +1,331 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var browser = require('@simplewebauthn/browser');
5
+
6
+ // src/hooks/usePasskeySetup.ts
7
+ var WALLET_PRF_SALT_STRING = "chipi-wallet-encryption-key-v1";
8
+ var WALLET_CREDENTIAL_KEY = "chipi_wallet_passkey_credential";
9
+ function generateRandomBase64URL(length = 32) {
10
+ const array = new Uint8Array(length);
11
+ crypto.getRandomValues(array);
12
+ return btoa(String.fromCharCode(...array)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
13
+ }
14
+ function stringToBase64URL(str) {
15
+ return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
16
+ }
17
+ function arrayBufferToHex(buffer) {
18
+ const bytes = new Uint8Array(buffer);
19
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
20
+ }
21
+ function base64URLToArrayBuffer(base64url) {
22
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
23
+ const paddedBase64 = base64.padEnd(
24
+ base64.length + (4 - base64.length % 4) % 4,
25
+ "="
26
+ );
27
+ const binary = atob(paddedBase64);
28
+ const bytes = new Uint8Array(binary.length);
29
+ for (let i = 0; i < binary.length; i++) {
30
+ bytes[i] = binary.charCodeAt(i);
31
+ }
32
+ return bytes.buffer;
33
+ }
34
+ function getPRFSaltBase64URL() {
35
+ return stringToBase64URL(WALLET_PRF_SALT_STRING);
36
+ }
37
+ function isWebAuthnSupported() {
38
+ if (typeof window === "undefined") return false;
39
+ return browser.browserSupportsWebAuthn();
40
+ }
41
+ function getStoredCredential() {
42
+ if (typeof window === "undefined") return null;
43
+ const stored = localStorage.getItem(WALLET_CREDENTIAL_KEY);
44
+ if (!stored) return null;
45
+ try {
46
+ return JSON.parse(stored);
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+ function storeCredential(metadata) {
52
+ localStorage.setItem(WALLET_CREDENTIAL_KEY, JSON.stringify(metadata));
53
+ }
54
+ function hasWalletPasskey() {
55
+ return getStoredCredential() !== null;
56
+ }
57
+ async function createWalletPasskey(userId, userName) {
58
+ if (!isWebAuthnSupported()) {
59
+ throw new Error("WebAuthn is not supported in this browser");
60
+ }
61
+ const challenge = generateRandomBase64URL(32);
62
+ const userIdBase64 = stringToBase64URL(userId);
63
+ const prfSalt = getPRFSaltBase64URL();
64
+ const registrationOptions = {
65
+ challenge,
66
+ rp: {
67
+ name: "Chipi Wallet",
68
+ id: window.location.hostname
69
+ },
70
+ user: {
71
+ id: userIdBase64,
72
+ name: userName,
73
+ displayName: `${userName} - Wallet Key`
74
+ },
75
+ pubKeyCredParams: [
76
+ { alg: -7, type: "public-key" },
77
+ // ES256
78
+ { alg: -257, type: "public-key" }
79
+ // RS256
80
+ ],
81
+ authenticatorSelection: {
82
+ authenticatorAttachment: "platform",
83
+ residentKey: "required",
84
+ userVerification: "required"
85
+ },
86
+ timeout: 6e4,
87
+ attestation: "none",
88
+ extensions: {
89
+ // @ts-expect-error - PRF extension not in types yet
90
+ prf: {
91
+ eval: {
92
+ first: base64URLToArrayBuffer(prfSalt)
93
+ }
94
+ }
95
+ }
96
+ };
97
+ let registration;
98
+ try {
99
+ registration = await browser.startRegistration(registrationOptions);
100
+ } catch (error) {
101
+ if (error instanceof Error) {
102
+ if (error.name === "NotAllowedError") {
103
+ throw new Error("Passkey creation was cancelled");
104
+ }
105
+ if (error.name === "InvalidStateError") {
106
+ throw new Error("A passkey already exists for this account");
107
+ }
108
+ }
109
+ throw error;
110
+ }
111
+ const credentialId = registration.id;
112
+ const prfResults = registration.clientExtensionResults?.prf;
113
+ let encryptKey;
114
+ let prfSupported = false;
115
+ if (prfResults?.results?.first) {
116
+ const prfOutput = prfResults.results.first;
117
+ if (typeof prfOutput === "string") {
118
+ encryptKey = arrayBufferToHex(base64URLToArrayBuffer(prfOutput));
119
+ } else {
120
+ encryptKey = arrayBufferToHex(prfOutput);
121
+ }
122
+ prfSupported = true;
123
+ } else if (prfResults?.enabled) {
124
+ const prfOutput = await getWalletEncryptKey(credentialId);
125
+ if (!prfOutput) {
126
+ throw new Error("Failed to derive encryption key from passkey");
127
+ }
128
+ encryptKey = prfOutput;
129
+ prfSupported = true;
130
+ } else {
131
+ console.warn("PRF extension not supported, using fallback key derivation");
132
+ encryptKey = await deriveKeyFromCredential(credentialId, userId);
133
+ prfSupported = false;
134
+ }
135
+ const transports = registration.response.transports;
136
+ storeCredential({
137
+ credentialId,
138
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
139
+ userId,
140
+ transports
141
+ });
142
+ return {
143
+ encryptKey,
144
+ credentialId,
145
+ prfSupported
146
+ };
147
+ }
148
+ async function getWalletEncryptKey(credentialId) {
149
+ if (!isWebAuthnSupported()) {
150
+ throw new Error("WebAuthn is not supported in this browser");
151
+ }
152
+ const storedCredential = getStoredCredential();
153
+ const targetCredentialId = credentialId || storedCredential?.credentialId;
154
+ if (!targetCredentialId) {
155
+ throw new Error("No wallet passkey found");
156
+ }
157
+ const challenge = generateRandomBase64URL(32);
158
+ const prfSalt = getPRFSaltBase64URL();
159
+ const authenticationOptions = {
160
+ challenge,
161
+ rpId: window.location.hostname,
162
+ allowCredentials: [
163
+ {
164
+ id: targetCredentialId,
165
+ type: "public-key",
166
+ transports: storedCredential?.transports
167
+ }
168
+ ],
169
+ userVerification: "required",
170
+ timeout: 6e4,
171
+ extensions: {
172
+ // @ts-expect-error - PRF extension not in types
173
+ prf: {
174
+ eval: {
175
+ first: base64URLToArrayBuffer(prfSalt)
176
+ }
177
+ }
178
+ }
179
+ };
180
+ let authentication;
181
+ try {
182
+ authentication = await browser.startAuthentication(authenticationOptions);
183
+ } catch (error) {
184
+ if (error instanceof Error && error.name === "NotAllowedError") {
185
+ return null;
186
+ }
187
+ throw error;
188
+ }
189
+ const prfResults = authentication.clientExtensionResults?.prf;
190
+ if (prfResults?.results?.first) {
191
+ const prfOutput = prfResults.results.first;
192
+ if (typeof prfOutput === "string") {
193
+ return arrayBufferToHex(base64URLToArrayBuffer(prfOutput));
194
+ }
195
+ return arrayBufferToHex(prfOutput);
196
+ }
197
+ console.warn("PRF not available, using fallback key derivation");
198
+ if (storedCredential) {
199
+ return deriveKeyFromCredential(targetCredentialId, storedCredential.userId);
200
+ }
201
+ throw new Error("Unable to derive encryption key");
202
+ }
203
+ async function deriveKeyFromCredential(credentialId, userId) {
204
+ const encoder = new TextEncoder();
205
+ const keyMaterial = await crypto.subtle.importKey(
206
+ "raw",
207
+ encoder.encode(credentialId),
208
+ "PBKDF2",
209
+ false,
210
+ ["deriveBits"]
211
+ );
212
+ const salt = encoder.encode(`chipi-wallet-${userId}`);
213
+ const derivedBits = await crypto.subtle.deriveBits(
214
+ {
215
+ name: "PBKDF2",
216
+ salt,
217
+ iterations: 1e5,
218
+ hash: "SHA-256"
219
+ },
220
+ keyMaterial,
221
+ 256
222
+ );
223
+ return arrayBufferToHex(derivedBits);
224
+ }
225
+ async function verifyWalletPasskey() {
226
+ try {
227
+ const encryptKey = await getWalletEncryptKey();
228
+ return encryptKey !== null;
229
+ } catch {
230
+ return false;
231
+ }
232
+ }
233
+
234
+ // src/hooks/usePasskeySetup.ts
235
+ function usePasskeySetup() {
236
+ const [isLoading, setIsLoading] = react.useState(false);
237
+ const [error, setError] = react.useState(null);
238
+ const [data, setData] = react.useState(null);
239
+ const setupPasskey = react.useCallback(async (userId, userName) => {
240
+ setIsLoading(true);
241
+ setError(null);
242
+ try {
243
+ const result = await createWalletPasskey(userId, userName);
244
+ setData(result);
245
+ return result;
246
+ } catch (err) {
247
+ const error2 = err instanceof Error ? err : new Error("Failed to setup passkey");
248
+ setError(error2);
249
+ throw error2;
250
+ } finally {
251
+ setIsLoading(false);
252
+ }
253
+ }, []);
254
+ const reset = react.useCallback(() => {
255
+ setIsLoading(false);
256
+ setError(null);
257
+ setData(null);
258
+ }, []);
259
+ return {
260
+ setupPasskey,
261
+ isLoading,
262
+ error,
263
+ data,
264
+ reset
265
+ };
266
+ }
267
+ function usePasskeyAuth() {
268
+ const [isLoading, setIsLoading] = react.useState(false);
269
+ const [error, setError] = react.useState(null);
270
+ const [encryptKey, setEncryptKey] = react.useState(null);
271
+ const authenticate = react.useCallback(async (credentialId) => {
272
+ setIsLoading(true);
273
+ setError(null);
274
+ try {
275
+ const key = await getWalletEncryptKey(credentialId);
276
+ setEncryptKey(key);
277
+ return key;
278
+ } catch (err) {
279
+ const error2 = err instanceof Error ? err : new Error("Failed to authenticate");
280
+ setError(error2);
281
+ throw error2;
282
+ } finally {
283
+ setIsLoading(false);
284
+ }
285
+ }, []);
286
+ const reset = react.useCallback(() => {
287
+ setIsLoading(false);
288
+ setError(null);
289
+ setEncryptKey(null);
290
+ }, []);
291
+ return {
292
+ authenticate,
293
+ isLoading,
294
+ error,
295
+ encryptKey,
296
+ reset
297
+ };
298
+ }
299
+ function usePasskeyStatus() {
300
+ const [isValid, setIsValid] = react.useState(null);
301
+ const [isChecking, setIsChecking] = react.useState(false);
302
+ const checkValidity = react.useCallback(async () => {
303
+ setIsChecking(true);
304
+ try {
305
+ const valid = await verifyWalletPasskey();
306
+ setIsValid(valid);
307
+ return valid;
308
+ } catch {
309
+ setIsValid(false);
310
+ return false;
311
+ } finally {
312
+ setIsChecking(false);
313
+ }
314
+ }, []);
315
+ const status = {
316
+ isSupported: isWebAuthnSupported(),
317
+ hasPasskey: hasWalletPasskey(),
318
+ isValid
319
+ };
320
+ return {
321
+ status,
322
+ checkValidity,
323
+ isChecking
324
+ };
325
+ }
326
+
327
+ exports.usePasskeyAuth = usePasskeyAuth;
328
+ exports.usePasskeySetup = usePasskeySetup;
329
+ exports.usePasskeyStatus = usePasskeyStatus;
330
+ //# sourceMappingURL=hooks.js.map
331
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/passkey.ts","../src/hooks/usePasskeySetup.ts","../src/hooks/usePasskeyAuth.ts","../src/hooks/usePasskeyStatus.ts"],"names":["browserSupportsWebAuthn","startRegistration","startAuthentication","useState","useCallback","error"],"mappings":"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","file":"hooks.js","sourcesContent":["/* eslint-disable no-undef */\n/**\n * Wallet Passkey Utilities\n *\n * This module provides WebAuthn passkey functionality with PRF (Pseudo-Random Function)\n * extension support for securely deriving encryption keys for wallet creation.\n *\n * Uses @simplewebauthn/browser for robust browser compatibility.\n */\n\nimport {\n startRegistration,\n startAuthentication,\n browserSupportsWebAuthn,\n} from \"@simplewebauthn/browser\";\nimport type {\n PublicKeyCredentialCreationOptionsJSON,\n PublicKeyCredentialRequestOptionsJSON,\n AuthenticationResponseJSON,\n RegistrationResponseJSON,\n} from \"@simplewebauthn/types\";\n\n// Salt used for PRF - this should be constant for your app\nconst WALLET_PRF_SALT_STRING = \"chipi-wallet-encryption-key-v1\";\n\n// Storage key for credential metadata\nconst WALLET_CREDENTIAL_KEY = \"chipi_wallet_passkey_credential\";\n\nexport interface WalletCredentialMetadata {\n credentialId: string;\n createdAt: string;\n userId: string;\n transports?: AuthenticatorTransport[];\n}\n\n/**\n * Generate a random base64url string for challenges\n */\nfunction generateRandomBase64URL(length: number = 32): string {\n const array = new Uint8Array(length);\n crypto.getRandomValues(array);\n return btoa(String.fromCharCode(...array))\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=/g, \"\");\n}\n\n/**\n * Convert string to base64url\n */\nfunction stringToBase64URL(str: string): string {\n return btoa(str).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=/g, \"\");\n}\n\n/**\n * Convert ArrayBuffer to hex string\n */\nfunction arrayBufferToHex(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * Convert base64url to ArrayBuffer\n */\nfunction base64URLToArrayBuffer(base64url: string): ArrayBuffer {\n const base64 = base64url.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const paddedBase64 = base64.padEnd(\n base64.length + ((4 - (base64.length % 4)) % 4),\n \"=\"\n );\n const binary = atob(paddedBase64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * Get PRF salt as base64url for SimpleWebAuthn\n */\nfunction getPRFSaltBase64URL(): string {\n return stringToBase64URL(WALLET_PRF_SALT_STRING);\n}\n\n/**\n * Check if WebAuthn is supported in this browser\n */\nexport function isWebAuthnSupported(): boolean {\n if (typeof window === \"undefined\") return false;\n return browserSupportsWebAuthn();\n}\n\n/**\n * Get stored credential metadata\n */\nexport function getStoredCredential(): WalletCredentialMetadata | null {\n if (typeof window === \"undefined\") return null;\n\n const stored = localStorage.getItem(WALLET_CREDENTIAL_KEY);\n if (!stored) return null;\n\n try {\n return JSON.parse(stored) as WalletCredentialMetadata;\n } catch {\n return null;\n }\n}\n\n/**\n * Store credential metadata\n */\nfunction storeCredential(metadata: WalletCredentialMetadata): void {\n localStorage.setItem(WALLET_CREDENTIAL_KEY, JSON.stringify(metadata));\n}\n\n/**\n * Remove stored credential\n */\nexport function removeStoredCredential(): void {\n localStorage.removeItem(WALLET_CREDENTIAL_KEY);\n}\n\n/**\n * Check if a wallet passkey already exists\n */\nexport function hasWalletPasskey(): boolean {\n return getStoredCredential() !== null;\n}\n\nexport interface CreateWalletPasskeyResult {\n encryptKey: string;\n credentialId: string;\n prfSupported: boolean;\n}\n\n/**\n * Create a new wallet passkey with PRF extension\n */\nexport async function createWalletPasskey(\n userId: string,\n userName: string\n): Promise<CreateWalletPasskeyResult> {\n if (!isWebAuthnSupported()) {\n throw new Error(\"WebAuthn is not supported in this browser\");\n }\n\n const challenge = generateRandomBase64URL(32);\n const userIdBase64 = stringToBase64URL(userId);\n const prfSalt = getPRFSaltBase64URL();\n\n // Build registration options for SimpleWebAuthn\n const registrationOptions: PublicKeyCredentialCreationOptionsJSON = {\n challenge,\n rp: {\n name: \"Chipi Wallet\",\n id: window.location.hostname,\n },\n user: {\n id: userIdBase64,\n name: userName,\n displayName: `${userName} - Wallet Key`,\n },\n pubKeyCredParams: [\n { alg: -7, type: \"public-key\" }, // ES256\n { alg: -257, type: \"public-key\" }, // RS256\n ],\n authenticatorSelection: {\n authenticatorAttachment: \"platform\",\n residentKey: \"required\",\n userVerification: \"required\",\n },\n timeout: 60000,\n attestation: \"none\",\n extensions: {\n // @ts-expect-error - PRF extension not in types yet\n prf: {\n eval: {\n first: base64URLToArrayBuffer(prfSalt),\n },\n },\n },\n };\n\n let registration: RegistrationResponseJSON;\n try {\n registration = await startRegistration(registrationOptions);\n } catch (error) {\n if (error instanceof Error) {\n if (error.name === \"NotAllowedError\") {\n throw new Error(\"Passkey creation was cancelled\");\n }\n if (error.name === \"InvalidStateError\") {\n throw new Error(\"A passkey already exists for this account\");\n }\n }\n throw error;\n }\n\n const credentialId = registration.id;\n\n // Check PRF results\n // @ts-expect-error - PRF extension not in types\n const prfResults = registration.clientExtensionResults?.prf;\n\n let encryptKey: string;\n let prfSupported = false;\n\n if (prfResults?.results?.first) {\n // PRF was evaluated during creation\n const prfOutput = prfResults.results.first;\n if (typeof prfOutput === \"string\") {\n encryptKey = arrayBufferToHex(base64URLToArrayBuffer(prfOutput));\n } else {\n encryptKey = arrayBufferToHex(prfOutput);\n }\n prfSupported = true;\n } else if (prfResults?.enabled) {\n // PRF is supported but needs assertion to get output\n const prfOutput = await getWalletEncryptKey(credentialId);\n if (!prfOutput) {\n throw new Error(\"Failed to derive encryption key from passkey\");\n }\n encryptKey = prfOutput;\n prfSupported = true;\n } else {\n // PRF not supported - use fallback derivation\n console.warn(\"PRF extension not supported, using fallback key derivation\");\n encryptKey = await deriveKeyFromCredential(credentialId, userId);\n prfSupported = false;\n }\n\n // Get transports if available\n const transports = registration.response.transports as\n | AuthenticatorTransport[]\n | undefined;\n\n // Store credential metadata\n storeCredential({\n credentialId,\n createdAt: new Date().toISOString(),\n userId,\n transports,\n });\n\n return {\n encryptKey,\n credentialId,\n prfSupported,\n };\n}\n\n/**\n * Get the encryption key by authenticating with the wallet passkey\n */\nexport async function getWalletEncryptKey(\n credentialId?: string\n): Promise<string | null> {\n if (!isWebAuthnSupported()) {\n throw new Error(\"WebAuthn is not supported in this browser\");\n }\n\n const storedCredential = getStoredCredential();\n const targetCredentialId = credentialId || storedCredential?.credentialId;\n\n if (!targetCredentialId) {\n throw new Error(\"No wallet passkey found\");\n }\n\n const challenge = generateRandomBase64URL(32);\n const prfSalt = getPRFSaltBase64URL();\n\n // Build authentication options\n const authenticationOptions: PublicKeyCredentialRequestOptionsJSON = {\n challenge,\n rpId: window.location.hostname,\n allowCredentials: [\n {\n id: targetCredentialId,\n type: \"public-key\",\n transports: storedCredential?.transports,\n },\n ],\n userVerification: \"required\",\n timeout: 60000,\n extensions: {\n // @ts-expect-error - PRF extension not in types\n prf: {\n eval: {\n first: base64URLToArrayBuffer(prfSalt),\n },\n },\n },\n };\n\n let authentication: AuthenticationResponseJSON;\n try {\n authentication = await startAuthentication(authenticationOptions);\n } catch (error) {\n if (error instanceof Error && error.name === \"NotAllowedError\") {\n // User cancelled\n return null;\n }\n throw error;\n }\n\n // Check PRF results\n // @ts-expect-error - PRF extension not in types\n const prfResults = authentication.clientExtensionResults?.prf;\n\n if (prfResults?.results?.first) {\n const prfOutput = prfResults.results.first;\n if (typeof prfOutput === \"string\") {\n return arrayBufferToHex(base64URLToArrayBuffer(prfOutput));\n }\n return arrayBufferToHex(prfOutput);\n }\n\n // PRF not available, use fallback\n console.warn(\"PRF not available, using fallback key derivation\");\n if (storedCredential) {\n return deriveKeyFromCredential(targetCredentialId, storedCredential.userId);\n }\n\n throw new Error(\"Unable to derive encryption key\");\n}\n\n/**\n * Fallback key derivation when PRF is not supported\n */\nasync function deriveKeyFromCredential(\n credentialId: string,\n userId: string\n): Promise<string> {\n const encoder = new TextEncoder();\n const keyMaterial = await crypto.subtle.importKey(\n \"raw\",\n encoder.encode(credentialId),\n \"PBKDF2\",\n false,\n [\"deriveBits\"]\n );\n\n const salt = encoder.encode(`chipi-wallet-${userId}`);\n\n const derivedBits = await crypto.subtle.deriveBits(\n {\n name: \"PBKDF2\",\n salt,\n iterations: 100000,\n hash: \"SHA-256\",\n },\n keyMaterial,\n 256\n );\n\n return arrayBufferToHex(derivedBits);\n}\n\n/**\n * Verify that the stored passkey is still valid\n */\nexport async function verifyWalletPasskey(): Promise<boolean> {\n try {\n const encryptKey = await getWalletEncryptKey();\n return encryptKey !== null;\n } catch {\n return false;\n }\n}\n\n// Re-export for backwards compatibility\nexport const isWebAuthnPRFSupported = isWebAuthnSupported;\n","\"use client\";\n\nimport { useState, useCallback } from \"react\";\nimport {\n createWalletPasskey,\n type CreateWalletPasskeyResult,\n} from \"../passkey\";\nimport type { UsePasskeySetupResult } from \"./types\";\n\n/**\n * Hook for setting up a new passkey\n */\nexport function usePasskeySetup(): UsePasskeySetupResult {\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [data, setData] = useState<CreateWalletPasskeyResult | null>(null);\n\n const setupPasskey = useCallback(async (userId: string, userName: string) => {\n setIsLoading(true);\n setError(null);\n\n try {\n const result = await createWalletPasskey(userId, userName);\n setData(result);\n return result;\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error(\"Failed to setup passkey\");\n setError(error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n }, []);\n\n const reset = useCallback(() => {\n setIsLoading(false);\n setError(null);\n setData(null);\n }, []);\n\n return {\n setupPasskey,\n isLoading,\n error,\n data,\n reset,\n };\n}\n","\"use client\";\n\nimport { useState, useCallback } from \"react\";\nimport { getWalletEncryptKey } from \"../passkey\";\nimport type { UsePasskeyAuthResult } from \"./types\";\n\n/**\n * Hook for authenticating with passkey to get encryption key\n */\nexport function usePasskeyAuth(): UsePasskeyAuthResult {\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [encryptKey, setEncryptKey] = useState<string | null>(null);\n\n const authenticate = useCallback(async (credentialId?: string) => {\n setIsLoading(true);\n setError(null);\n\n try {\n const key = await getWalletEncryptKey(credentialId);\n setEncryptKey(key);\n return key;\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error(\"Failed to authenticate\");\n setError(error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n }, []);\n\n const reset = useCallback(() => {\n setIsLoading(false);\n setError(null);\n setEncryptKey(null);\n }, []);\n\n return {\n authenticate,\n isLoading,\n error,\n encryptKey,\n reset,\n };\n}\n\n","\"use client\";\n\nimport { useState, useCallback } from \"react\";\nimport {\n hasWalletPasskey,\n isWebAuthnSupported,\n verifyWalletPasskey,\n} from \"../passkey\";\nimport type { UsePasskeyStatusResult, PasskeyStatus } from \"./types\";\n\n/**\n * Hook for checking passkey status\n */\nexport function usePasskeyStatus(): UsePasskeyStatusResult {\n const [isValid, setIsValid] = useState<boolean | null>(null);\n const [isChecking, setIsChecking] = useState(false);\n\n const checkValidity = useCallback(async () => {\n setIsChecking(true);\n try {\n const valid = await verifyWalletPasskey();\n setIsValid(valid);\n return valid;\n } catch {\n setIsValid(false);\n return false;\n } finally {\n setIsChecking(false);\n }\n }, []);\n\n const status: PasskeyStatus = {\n isSupported: isWebAuthnSupported(),\n hasPasskey: hasWalletPasskey(),\n isValid,\n };\n\n return {\n status,\n checkValidity,\n isChecking,\n };\n}\n\n"]}