@zerodev/wallet-core 0.0.1-alpha.18 → 0.0.1-alpha.20

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.
Files changed (144) hide show
  1. package/README.md +1 -1
  2. package/dist/_cjs/actions/auth/authenticateWithOAuth.js +2 -2
  3. package/dist/_cjs/actions/auth/authenticateWithOAuth.js.map +1 -1
  4. package/dist/_cjs/actions/auth/getOAuthLoginUrl.js +18 -0
  5. package/dist/_cjs/actions/auth/getOAuthLoginUrl.js.map +1 -0
  6. package/dist/_cjs/actions/auth/getWhoami.js +2 -2
  7. package/dist/_cjs/actions/auth/getWhoami.js.map +1 -1
  8. package/dist/_cjs/actions/auth/index.js +3 -1
  9. package/dist/_cjs/actions/auth/index.js.map +1 -1
  10. package/dist/_cjs/actions/auth/loginWithStamp.js +5 -5
  11. package/dist/_cjs/actions/auth/loginWithStamp.js.map +1 -1
  12. package/dist/_cjs/actions/index.js +2 -1
  13. package/dist/_cjs/actions/index.js.map +1 -1
  14. package/dist/_cjs/actions/wallet/signingUtils.js +2 -2
  15. package/dist/_cjs/actions/wallet/signingUtils.js.map +1 -1
  16. package/dist/_cjs/client/createClient.js +5 -5
  17. package/dist/_cjs/client/createClient.js.map +1 -1
  18. package/dist/_cjs/client/decorators/client.js +1 -0
  19. package/dist/_cjs/client/decorators/client.js.map +1 -1
  20. package/dist/_cjs/client/index.js.map +1 -1
  21. package/dist/_cjs/client/transports/createTransport.js +6 -5
  22. package/dist/_cjs/client/transports/createTransport.js.map +1 -1
  23. package/dist/_cjs/client/transports/rest.js +7 -7
  24. package/dist/_cjs/client/transports/rest.js.map +1 -1
  25. package/dist/_cjs/core/createZeroDevWallet.js +32 -61
  26. package/dist/_cjs/core/createZeroDevWallet.js.map +1 -1
  27. package/dist/_cjs/index.js.map +1 -1
  28. package/dist/_cjs/stampers/indexedDbStamper.js +20 -2
  29. package/dist/_cjs/stampers/indexedDbStamper.js.map +1 -1
  30. package/dist/_cjs/stampers/webauthnStamper.js +23 -3
  31. package/dist/_cjs/stampers/webauthnStamper.js.map +1 -1
  32. package/dist/_cjs/utils/exportPrivateKey.js +1 -1
  33. package/dist/_cjs/utils/exportPrivateKey.js.map +1 -1
  34. package/dist/_cjs/utils/exportWallet.js +2 -6
  35. package/dist/_cjs/utils/exportWallet.js.map +1 -1
  36. package/dist/_cjs/utils/hpke.js +4 -15
  37. package/dist/_cjs/utils/hpke.js.map +1 -1
  38. package/dist/_cjs/utils/utils.js +5 -6
  39. package/dist/_cjs/utils/utils.js.map +1 -1
  40. package/dist/_esm/actions/auth/authenticateWithOAuth.js +3 -2
  41. package/dist/_esm/actions/auth/authenticateWithOAuth.js.map +1 -1
  42. package/dist/_esm/actions/auth/getOAuthLoginUrl.js +23 -0
  43. package/dist/_esm/actions/auth/getOAuthLoginUrl.js.map +1 -0
  44. package/dist/_esm/actions/auth/getWhoami.js +2 -2
  45. package/dist/_esm/actions/auth/getWhoami.js.map +1 -1
  46. package/dist/_esm/actions/auth/index.js +1 -0
  47. package/dist/_esm/actions/auth/index.js.map +1 -1
  48. package/dist/_esm/actions/auth/loginWithStamp.js +5 -5
  49. package/dist/_esm/actions/auth/loginWithStamp.js.map +1 -1
  50. package/dist/_esm/actions/index.js +1 -1
  51. package/dist/_esm/actions/index.js.map +1 -1
  52. package/dist/_esm/actions/wallet/signingUtils.js +2 -2
  53. package/dist/_esm/actions/wallet/signingUtils.js.map +1 -1
  54. package/dist/_esm/client/createClient.js +5 -5
  55. package/dist/_esm/client/createClient.js.map +1 -1
  56. package/dist/_esm/client/decorators/client.js +2 -1
  57. package/dist/_esm/client/decorators/client.js.map +1 -1
  58. package/dist/_esm/client/index.js +1 -1
  59. package/dist/_esm/client/index.js.map +1 -1
  60. package/dist/_esm/client/transports/createTransport.js +6 -5
  61. package/dist/_esm/client/transports/createTransport.js.map +1 -1
  62. package/dist/_esm/client/transports/rest.js +7 -7
  63. package/dist/_esm/client/transports/rest.js.map +1 -1
  64. package/dist/_esm/core/createZeroDevWallet.js +33 -62
  65. package/dist/_esm/core/createZeroDevWallet.js.map +1 -1
  66. package/dist/_esm/index.js.map +1 -1
  67. package/dist/_esm/stampers/indexedDbStamper.js +20 -2
  68. package/dist/_esm/stampers/indexedDbStamper.js.map +1 -1
  69. package/dist/_esm/stampers/webauthnStamper.js +23 -4
  70. package/dist/_esm/stampers/webauthnStamper.js.map +1 -1
  71. package/dist/_esm/utils/exportPrivateKey.js +1 -1
  72. package/dist/_esm/utils/exportPrivateKey.js.map +1 -1
  73. package/dist/_esm/utils/exportWallet.js +2 -6
  74. package/dist/_esm/utils/exportWallet.js.map +1 -1
  75. package/dist/_esm/utils/hpke.js +6 -22
  76. package/dist/_esm/utils/hpke.js.map +1 -1
  77. package/dist/_esm/utils/utils.js +5 -6
  78. package/dist/_esm/utils/utils.js.map +1 -1
  79. package/dist/_types/actions/auth/authenticateWithOAuth.d.ts +3 -0
  80. package/dist/_types/actions/auth/authenticateWithOAuth.d.ts.map +1 -1
  81. package/dist/_types/actions/auth/getOAuthLoginUrl.d.ts +30 -0
  82. package/dist/_types/actions/auth/getOAuthLoginUrl.d.ts.map +1 -0
  83. package/dist/_types/actions/auth/index.d.ts +1 -0
  84. package/dist/_types/actions/auth/index.d.ts.map +1 -1
  85. package/dist/_types/actions/auth/loginWithStamp.d.ts +2 -1
  86. package/dist/_types/actions/auth/loginWithStamp.d.ts.map +1 -1
  87. package/dist/_types/actions/index.d.ts +1 -1
  88. package/dist/_types/actions/index.d.ts.map +1 -1
  89. package/dist/_types/client/decorators/client.d.ts +7 -1
  90. package/dist/_types/client/decorators/client.d.ts.map +1 -1
  91. package/dist/_types/client/index.d.ts +1 -1
  92. package/dist/_types/client/index.d.ts.map +1 -1
  93. package/dist/_types/client/transports/createTransport.d.ts +2 -0
  94. package/dist/_types/client/transports/createTransport.d.ts.map +1 -1
  95. package/dist/_types/client/transports/rest.d.ts +5 -4
  96. package/dist/_types/client/transports/rest.d.ts.map +1 -1
  97. package/dist/_types/client/types.d.ts +9 -9
  98. package/dist/_types/client/types.d.ts.map +1 -1
  99. package/dist/_types/core/createZeroDevWallet.d.ts +5 -1
  100. package/dist/_types/core/createZeroDevWallet.d.ts.map +1 -1
  101. package/dist/_types/index.d.ts +2 -2
  102. package/dist/_types/index.d.ts.map +1 -1
  103. package/dist/_types/stampers/index.d.ts +1 -1
  104. package/dist/_types/stampers/index.d.ts.map +1 -1
  105. package/dist/_types/stampers/indexedDbStamper.d.ts +2 -2
  106. package/dist/_types/stampers/indexedDbStamper.d.ts.map +1 -1
  107. package/dist/_types/stampers/types.d.ts +36 -5
  108. package/dist/_types/stampers/types.d.ts.map +1 -1
  109. package/dist/_types/stampers/webauthnStamper.d.ts +2 -2
  110. package/dist/_types/stampers/webauthnStamper.d.ts.map +1 -1
  111. package/dist/_types/types/session.d.ts +2 -3
  112. package/dist/_types/types/session.d.ts.map +1 -1
  113. package/dist/_types/utils/buildClientSignature.d.ts +3 -3
  114. package/dist/_types/utils/buildClientSignature.d.ts.map +1 -1
  115. package/dist/_types/utils/exportWallet.d.ts.map +1 -1
  116. package/dist/_types/utils/hpke.d.ts.map +1 -1
  117. package/dist/_types/utils/utils.d.ts.map +1 -1
  118. package/dist/tsconfig.build.tsbuildinfo +1 -1
  119. package/package.json +2 -1
  120. package/src/actions/auth/authenticateWithOAuth.ts +5 -2
  121. package/src/actions/auth/getOAuthLoginUrl.ts +48 -0
  122. package/src/actions/auth/getWhoami.ts +2 -2
  123. package/src/actions/auth/index.ts +5 -0
  124. package/src/actions/auth/loginWithStamp.ts +7 -6
  125. package/src/actions/index.ts +3 -0
  126. package/src/actions/wallet/signingUtils.ts +2 -2
  127. package/src/client/createClient.ts +6 -6
  128. package/src/client/decorators/client.ts +13 -0
  129. package/src/client/index.ts +4 -1
  130. package/src/client/transports/createTransport.ts +8 -5
  131. package/src/client/transports/rest.ts +13 -12
  132. package/src/client/types.ts +9 -9
  133. package/src/core/createZeroDevWallet.ts +40 -77
  134. package/src/index.ts +11 -3
  135. package/src/stampers/index.ts +2 -2
  136. package/src/stampers/indexedDbStamper.ts +27 -4
  137. package/src/stampers/types.ts +38 -5
  138. package/src/stampers/webauthnStamper.ts +27 -6
  139. package/src/types/session.ts +2 -3
  140. package/src/utils/buildClientSignature.ts +3 -3
  141. package/src/utils/exportPrivateKey.ts +1 -1
  142. package/src/utils/exportWallet.ts +2 -6
  143. package/src/utils/hpke.ts +7 -33
  144. package/src/utils/utils.ts +5 -6
@@ -1,4 +1,3 @@
1
- import { getWebAuthnAttestation } from '@turnkey/http'
2
1
  import type { LocalAccount } from 'viem/accounts'
3
2
  import type {
4
3
  EmailCustomization,
@@ -6,6 +5,7 @@ import type {
6
5
  } from '../actions/auth/index.js'
7
6
  import { toViemAccount } from '../adapters/viem.js'
8
7
  import {
8
+ type CreateTransportOptions,
9
9
  createAuthProxyClient,
10
10
  createClient,
11
11
  type ZeroDevWalletClient,
@@ -17,6 +17,7 @@ import {
17
17
  KMS_SERVER_URL,
18
18
  } from '../constants.js'
19
19
  import { createIndexedDbStamper } from '../stampers/indexedDbStamper.js'
20
+ import type { ApiKeyStamper, PasskeyStamper } from '../stampers/types.js'
20
21
  import { createWebauthnStamper } from '../stampers/webauthnStamper.js'
21
22
  import { createWebStorageAdapter } from '../storage/adapters.js'
22
23
  import {
@@ -26,19 +27,16 @@ import {
26
27
  import { SessionType, type ZeroDevWalletSession } from '../types/session.js'
27
28
  import { buildClientSignature } from '../utils/buildClientSignature.js'
28
29
  import { encryptOtpAttempt } from '../utils/encryptOtpAttempt.js'
29
- import {
30
- base64UrlEncode,
31
- generateCompressedPublicKeyFromKeyPair,
32
- generateRandomBuffer,
33
- humanReadableDateTime,
34
- parseSession,
35
- } from '../utils/utils.js'
30
+ import { humanReadableDateTime, parseSession } from '../utils/utils.js'
36
31
  export interface ZeroDevWalletConfig {
37
32
  organizationId?: string
38
33
  proxyBaseUrl?: string
39
34
  projectId: string
40
35
  sessionStorage?: StorageAdapter
41
36
  rpId?: string
37
+ apiKeyStamper?: ApiKeyStamper
38
+ passkeyStamper?: PasskeyStamper
39
+ fetchOptions?: CreateTransportOptions['fetchOptions']
42
40
  }
43
41
 
44
42
  // Re-export EmailCustomization for convenience
@@ -134,15 +132,17 @@ export async function createZeroDevWallet(
134
132
  sessionStorage || createWebStorageAdapter(),
135
133
  )
136
134
 
137
- const indexedDbStamper = await createIndexedDbStamper()
135
+ const apiKeyStamper = config.apiKeyStamper ?? (await createIndexedDbStamper())
138
136
 
139
- const webauthnStamper = await createWebauthnStamper({ rpId })
137
+ const passkeyStamper =
138
+ config.passkeyStamper ?? (await createWebauthnStamper({ rpId }))
140
139
 
141
140
  const client = createClient({
142
- indexedDbStamper,
143
- webauthnStamper,
141
+ apiKeyStamper,
142
+ passkeyStamper,
144
143
  transport: zeroDevWalletTransport({
145
144
  baseUrl: config.proxyBaseUrl || `${KMS_SERVER_URL}/api/v1`,
145
+ ...(config.fetchOptions && { fetchOptions: config.fetchOptions }),
146
146
  }),
147
147
  })
148
148
 
@@ -151,8 +151,8 @@ export async function createZeroDevWallet(
151
151
  return {
152
152
  client,
153
153
  async getPublicKey() {
154
- await client.indexedDbStamper.resetKeyPair()
155
- const compressedPublicKey = await client.indexedDbStamper.getPublicKey()
154
+ await client.apiKeyStamper.resetKeyPair()
155
+ const compressedPublicKey = await client.apiKeyStamper.getPublicKey()
156
156
  return compressedPublicKey
157
157
  },
158
158
 
@@ -191,30 +191,22 @@ export async function createZeroDevWallet(
191
191
  if (!activeSession) {
192
192
  throw new Error('No active session')
193
193
  }
194
- if (activeSession.stamperType === 'indexedDb') {
195
- const newKeyPair = await crypto.subtle.generateKey(
196
- {
197
- name: 'ECDSA',
198
- namedCurve: 'P-256',
199
- },
200
- false,
201
- ['sign', 'verify'],
202
- )
194
+ if (activeSession.stamperType === 'apiKey') {
203
195
  const compressedPublicKeyHex =
204
- await generateCompressedPublicKeyFromKeyPair(newKeyPair)
196
+ await client.apiKeyStamper.prepareKeyRotation()
205
197
  const data = await client.loginWithStamp({
206
198
  targetPublicKey: compressedPublicKeyHex,
207
199
  projectId,
208
200
  organizationId: activeSession.organizationId,
209
- stampWith: 'indexedDb',
201
+ stampWith: 'apiKey',
210
202
  })
211
- await client.indexedDbStamper.resetKeyPair(newKeyPair)
203
+ await client.apiKeyStamper.commitKeyRotation()
212
204
  const parsedSession = parseSession(data.session)
213
205
  const session: ZeroDevWalletSession = {
214
206
  id: `session_indexedDb_${Date.now()}`,
215
207
  userId: parsedSession.userId,
216
208
  organizationId: parsedSession.organizationId,
217
- stamperType: 'indexedDb',
209
+ stamperType: 'apiKey',
218
210
  sessionType: SessionType.READ_WRITE,
219
211
  token: data.session,
220
212
  expiry: parsedSession.expiry,
@@ -231,26 +223,26 @@ export async function createZeroDevWallet(
231
223
  async auth(params: AuthParams) {
232
224
  switch (params.type) {
233
225
  case 'oauth': {
226
+ const popSignature = await client.apiKeyStamper.sign(params.sessionId)
234
227
  const data = await client.authenticateWithOAuth({
235
228
  provider: params.provider,
236
229
  projectId,
237
230
  sessionId: params.sessionId,
231
+ popSignature,
238
232
  })
239
233
 
240
234
  if (data.session) {
241
235
  // Parse the JWT to get session data
242
236
  const parsedSession = parseSession(data.session)
243
- const publicKey = await client.indexedDbStamper.getPublicKey()
244
237
  const session: ZeroDevWalletSession = {
245
238
  id: `session_oauth_${Date.now()}`,
246
239
  userId: parsedSession.userId,
247
240
  organizationId: parsedSession.organizationId,
248
- stamperType: 'indexedDb',
241
+ stamperType: 'apiKey',
249
242
  sessionType: parsedSession.sessionType || SessionType.READ_WRITE,
250
243
  token: data.session,
251
244
  expiry: parsedSession.expiry,
252
245
  createdAt: Date.now(),
253
- publicKey: publicKey || '',
254
246
  }
255
247
  await sessionStorageManager.storeSession(session, session.id)
256
248
  }
@@ -263,62 +255,35 @@ export async function createZeroDevWallet(
263
255
  'mode' in params &&
264
256
  params.mode === 'register'
265
257
  ) {
266
- await client.indexedDbStamper.resetKeyPair()
267
- const tempPublicKey = await client.indexedDbStamper.getPublicKey()
258
+ await client.apiKeyStamper.resetKeyPair()
259
+ const tempPublicKey = await client.apiKeyStamper.getPublicKey()
268
260
  if (!tempPublicKey) {
269
261
  throw new Error('Failed to get public key')
270
262
  }
271
- const challenge = generateRandomBuffer()
272
- const encodedChallenge = base64UrlEncode(challenge)
273
- const authenticatorUserId = generateRandomBuffer()
274
263
  const name = `ZeroDevWallet-${humanReadableDateTime()}`
275
- const attestation = await getWebAuthnAttestation({
276
- publicKey: {
264
+ const { attestation, encodedChallenge } =
265
+ await passkeyStamper.register({
277
266
  rp: { id: rpId, name: '' },
278
- challenge,
279
- pubKeyCredParams: [
280
- {
281
- type: 'public-key',
282
- alg: -7,
283
- },
284
- {
285
- type: 'public-key',
286
- alg: -257,
287
- },
288
- ],
289
- user: {
290
- id: authenticatorUserId,
291
- name,
292
- displayName: name,
293
- },
294
- },
295
- })
267
+ userName: name,
268
+ })
296
269
  const data = await client.registerWithPasskey({
297
270
  attestation,
298
271
  challenge: encodedChallenge,
299
272
  projectId,
300
273
  encodedPublicKey: tempPublicKey,
301
274
  })
302
- const newKeyPair = await crypto.subtle.generateKey(
303
- {
304
- name: 'ECDSA',
305
- namedCurve: 'P-256',
306
- },
307
- false,
308
- ['sign', 'verify'],
309
- )
310
275
  const compressedPublicKeyHex =
311
- await generateCompressedPublicKeyFromKeyPair(newKeyPair)
276
+ await client.apiKeyStamper.prepareKeyRotation()
312
277
  const loginData = await client.loginWithStamp({
313
278
  projectId,
314
279
  targetPublicKey: compressedPublicKeyHex,
315
280
  organizationId: data.subOrganizationId,
316
281
  })
317
- await client.indexedDbStamper.resetKeyPair(newKeyPair)
282
+ await client.apiKeyStamper.commitKeyRotation()
318
283
  const parsedSession = parseSession(loginData.session)
319
284
  const session: ZeroDevWalletSession = {
320
285
  id: `session_indexedDb_${Date.now()}`,
321
- stamperType: 'indexedDb',
286
+ stamperType: 'apiKey',
322
287
  createdAt: Date.now(),
323
288
  sessionType: SessionType.READ_WRITE,
324
289
  userId: parsedSession.userId,
@@ -336,9 +301,8 @@ export async function createZeroDevWallet(
336
301
  'mode' in params &&
337
302
  params.mode === 'login'
338
303
  ) {
339
- await client.indexedDbStamper.resetKeyPair()
340
- const generatedPublicKey =
341
- await client.indexedDbStamper.getPublicKey()
304
+ await client.apiKeyStamper.resetKeyPair()
305
+ const generatedPublicKey = await client.apiKeyStamper.getPublicKey()
342
306
  if (!generatedPublicKey) {
343
307
  throw new Error('Failed to get public key')
344
308
  }
@@ -346,12 +310,12 @@ export async function createZeroDevWallet(
346
310
  targetPublicKey: generatedPublicKey,
347
311
  projectId,
348
312
  organizationId,
349
- stampWith: 'webauthn',
313
+ stampWith: 'passkey',
350
314
  })
351
315
  const parsedSession = parseSession(loginData.session)
352
316
  const session: ZeroDevWalletSession = {
353
317
  id: `session_indexedDb_${Date.now()}`,
354
- stamperType: 'indexedDb',
318
+ stamperType: 'apiKey',
355
319
  createdAt: Date.now(),
356
320
  sessionType: SessionType.READ_WRITE,
357
321
  userId: parsedSession.userId,
@@ -416,8 +380,8 @@ export async function createZeroDevWallet(
416
380
  const { otpId, otpCode, otpEncryptionTargetBundle } = otpParams
417
381
 
418
382
  // Step 1: Generate new key pair
419
- await client.indexedDbStamper.resetKeyPair()
420
- const targetPublicKey = await client.indexedDbStamper.getPublicKey()
383
+ await client.apiKeyStamper.resetKeyPair()
384
+ const targetPublicKey = await client.apiKeyStamper.getPublicKey()
421
385
 
422
386
  if (!targetPublicKey) {
423
387
  throw new Error('Failed to get public key')
@@ -449,7 +413,7 @@ export async function createZeroDevWallet(
449
413
  const clientSignature = await buildClientSignature({
450
414
  verificationToken,
451
415
  publicKey: targetPublicKey,
452
- stamper: client.indexedDbStamper,
416
+ stamper: client.apiKeyStamper,
453
417
  })
454
418
 
455
419
  // Step 4: Login via backend (not Auth Proxy!)
@@ -466,13 +430,12 @@ export async function createZeroDevWallet(
466
430
  id: `session_otp_${Date.now()}`,
467
431
  userId: parsedSession.userId,
468
432
  organizationId: parsedSession.organizationId,
469
- stamperType: 'indexedDb',
433
+ stamperType: 'apiKey',
470
434
  sessionType:
471
435
  parsedSession.sessionType || SessionType.READ_WRITE,
472
436
  token: data.session,
473
437
  expiry: parsedSession.expiry,
474
438
  createdAt: Date.now(),
475
- publicKey: targetPublicKey,
476
439
  }
477
440
  await sessionStorageManager.storeSession(session, session.id)
478
441
  }
@@ -488,7 +451,7 @@ export async function createZeroDevWallet(
488
451
 
489
452
  async logout() {
490
453
  await sessionStorageManager.clearAllSessions()
491
- await client.indexedDbStamper.resetKeyPair()
454
+ await client.apiKeyStamper.resetKeyPair()
492
455
  return true
493
456
  },
494
457
 
package/src/index.ts CHANGED
@@ -56,7 +56,12 @@ export { toViemAccount } from './adapters/viem.js'
56
56
  export type { ZeroDevWalletActions } from './client/decorators/client.js'
57
57
  // Client decorators
58
58
  export { zeroDevWalletActions } from './client/decorators/client.js'
59
- export type { Client, ClientConfig, Transport } from './client/index.js'
59
+ export type {
60
+ Client,
61
+ ClientConfig,
62
+ CreateTransportOptions,
63
+ Transport,
64
+ } from './client/index.js'
60
65
  // Client
61
66
  export {
62
67
  createBaseClient,
@@ -80,9 +85,12 @@ export {
80
85
  createWebauthnStamper,
81
86
  } from './stampers/index.js'
82
87
  export type {
88
+ ApiKeyStamper,
89
+ Attestation,
83
90
  IframeStamper,
84
- IndexedDbStamper,
85
- WebauthnStamper,
91
+ PasskeyRegistrationOptions,
92
+ PasskeyRegistrationResult,
93
+ PasskeyStamper,
86
94
  } from './stampers/types.js'
87
95
  // Storage
88
96
  export type { StorageAdapter, StorageManager } from './storage/manager.js'
@@ -1,8 +1,8 @@
1
1
  export { createIframeStamper } from './iframeStamper.js'
2
2
  export { createIndexedDbStamper } from './indexedDbStamper.js'
3
3
  export type {
4
+ ApiKeyStamper,
4
5
  IframeStamper,
5
- IndexedDbStamper,
6
- WebauthnStamper,
6
+ PasskeyStamper,
7
7
  } from './types.js'
8
8
  export { createWebauthnStamper } from './webauthnStamper.js'
@@ -1,10 +1,13 @@
1
1
  import { IndexedDbStamper as TurnkeyIndexedDbStamper } from '@turnkey/indexed-db-stamper'
2
- import type { IndexedDbStamper } from './types.js'
2
+ import { generateCompressedPublicKeyFromKeyPair } from '../utils/utils.js'
3
+ import type { ApiKeyStamper } from './types.js'
3
4
 
4
- export async function createIndexedDbStamper(): Promise<IndexedDbStamper> {
5
+ export async function createIndexedDbStamper(): Promise<ApiKeyStamper> {
5
6
  const inner = new TurnkeyIndexedDbStamper()
6
7
  await inner.init()
7
8
 
9
+ let pendingKeyPair: CryptoKeyPair | null = null
10
+
8
11
  return {
9
12
  async getPublicKey() {
10
13
  return await inner.getPublicKey()
@@ -12,11 +15,31 @@ export async function createIndexedDbStamper(): Promise<IndexedDbStamper> {
12
15
  async stamp(payload: string) {
13
16
  return await inner.stamp(payload)
14
17
  },
18
+ async sign(payload: string) {
19
+ return await inner.sign(payload)
20
+ },
15
21
  async clear() {
16
22
  await inner.clear()
17
23
  },
18
- async resetKeyPair(externalKeyPair?: CryptoKeyPair) {
19
- await inner.resetKeyPair(externalKeyPair)
24
+ async resetKeyPair() {
25
+ pendingKeyPair = null
26
+ await inner.resetKeyPair()
27
+ },
28
+ async prepareKeyRotation() {
29
+ const keyPair = await crypto.subtle.generateKey(
30
+ { name: 'ECDSA', namedCurve: 'P-256' },
31
+ false,
32
+ ['sign', 'verify'],
33
+ )
34
+ pendingKeyPair = keyPair
35
+ return await generateCompressedPublicKeyFromKeyPair(keyPair)
36
+ },
37
+ async commitKeyRotation() {
38
+ if (!pendingKeyPair) {
39
+ throw new Error('No pending key rotation to commit')
40
+ }
41
+ await inner.resetKeyPair(pendingKeyPair)
42
+ pendingKeyPair = null
20
43
  },
21
44
  }
22
45
  }
@@ -5,8 +5,6 @@ export type Stamp = {
5
5
  }
6
6
 
7
7
  export type Stamper = {
8
- /** retrieve public key compressed or otherwise as per the stamper */
9
- getPublicKey: () => Promise<string | null>
10
8
  /** produce Turnkey header value for a given request body */
11
9
  stamp: (payload: string) => Promise<Stamp>
12
10
  /** clear local state (embedded key, IDB keypair, etc.) */
@@ -16,6 +14,8 @@ export type Stamper = {
16
14
  export type KeyFormat = 'Hexadecimal' | 'Solana'
17
15
 
18
16
  export type IframeStamper = Stamper & {
17
+ /** retrieve public key compressed or otherwise as per the stamper */
18
+ getPublicKey: () => Promise<string | null>
19
19
  init(): Promise<string>
20
20
  injectCredentialBundle(bundle: string): Promise<boolean>
21
21
  injectWalletExportBundle(
@@ -30,7 +30,40 @@ export type IframeStamper = Stamper & {
30
30
  applySettings(settings: { styles?: Record<string, string> }): Promise<boolean>
31
31
  }
32
32
 
33
- export type IndexedDbStamper = Stamper & {
34
- resetKeyPair: (externalKeyPair?: CryptoKeyPair) => Promise<void>
33
+ export type ApiKeyStamper = Stamper & {
34
+ /** retrieve public key compressed or otherwise as per the stamper */
35
+ getPublicKey: () => Promise<string | null>
36
+ /** Generate + activate a new key pair immediately (simple cases: login init, logout). */
37
+ resetKeyPair: () => Promise<void>
38
+ /** Generate a new key pair internally, return its compressed public key, but keep the OLD key active for stamp(). */
39
+ prepareKeyRotation: () => Promise<string>
40
+ /** Promote the pending key to active. Call after the server accepts the new key. */
41
+ commitKeyRotation: () => Promise<void>
42
+ /**
43
+ * Sign `payload` with the currently active key. Returns a hex-encoded
44
+ * ECDSA-P256 / SHA-256 signature in ASN.1 DER form.
45
+ */
46
+ sign: (payload: string) => Promise<string>
47
+ }
48
+ export type Attestation = {
49
+ attestationObject: string
50
+ clientDataJson: string
51
+ credentialId: string
52
+ }
53
+
54
+ export type PasskeyRegistrationOptions = {
55
+ rp: { id: string; name: string }
56
+ userName: string
57
+ }
58
+
59
+ export type PasskeyRegistrationResult = {
60
+ attestation: Attestation
61
+ encodedChallenge: string
62
+ }
63
+
64
+ export type PasskeyStamper = Stamper & {
65
+ /** Create a new passkey credential. Owns challenge and user ID generation internally. */
66
+ register: (
67
+ options: PasskeyRegistrationOptions,
68
+ ) => Promise<PasskeyRegistrationResult>
35
69
  }
36
- export type WebauthnStamper = Stamper
@@ -1,21 +1,42 @@
1
+ import { getWebAuthnAttestation } from '@turnkey/http'
1
2
  import { WebauthnStamper as TurnkeyWebauthnStamper } from '@turnkey/webauthn-stamper'
2
- import type { WebauthnStamper } from './types.js'
3
+ import { base64UrlEncode, generateRandomBuffer } from '../utils/utils.js'
4
+ import type { PasskeyRegistrationOptions, PasskeyStamper } from './types.js'
3
5
 
4
6
  export async function createWebauthnStamper({
5
7
  rpId,
6
8
  }: {
7
9
  rpId: string
8
- }): Promise<WebauthnStamper> {
10
+ }): Promise<PasskeyStamper> {
9
11
  const inner = new TurnkeyWebauthnStamper({ rpId })
10
12
 
11
13
  return {
12
- async getPublicKey() {
13
- // return await inner.();
14
- return null
15
- },
16
14
  async stamp(payload: string) {
17
15
  return await inner.stamp(payload)
18
16
  },
19
17
  async clear() {},
18
+ async register(options: PasskeyRegistrationOptions) {
19
+ const challenge = generateRandomBuffer()
20
+ const encodedChallenge = base64UrlEncode(challenge)
21
+ const authenticatorUserId = generateRandomBuffer()
22
+
23
+ const attestation = await getWebAuthnAttestation({
24
+ publicKey: {
25
+ rp: options.rp,
26
+ challenge,
27
+ pubKeyCredParams: [
28
+ { type: 'public-key', alg: -7 },
29
+ { type: 'public-key', alg: -257 },
30
+ ],
31
+ user: {
32
+ id: authenticatorUserId,
33
+ name: options.userName,
34
+ displayName: options.userName,
35
+ },
36
+ },
37
+ })
38
+
39
+ return { attestation, encodedChallenge }
40
+ },
20
41
  }
21
42
  }
@@ -3,7 +3,7 @@ export enum SessionType {
3
3
  READ_WRITE = 'SESSION_TYPE_READ_WRITE',
4
4
  }
5
5
 
6
- export type StamperType = 'iframe' | 'indexedDb' | 'passkey'
6
+ export type StamperType = 'apiKey' | 'passkey'
7
7
 
8
8
  export type ZeroDevWalletSession = {
9
9
  id: string
@@ -11,8 +11,7 @@ export type ZeroDevWalletSession = {
11
11
  organizationId: string
12
12
  stamperType: StamperType
13
13
  sessionType?: SessionType
14
- token?: string
15
- publicKey?: string
14
+ token: string
16
15
  expiry: number
17
16
  createdAt: number
18
17
  }
@@ -1,4 +1,4 @@
1
- import type { IndexedDbStamper } from '../stampers/types.js'
1
+ import type { ApiKeyStamper } from '../stampers/types.js'
2
2
  import { derToRawSignature } from './derToRawSignature.js'
3
3
 
4
4
  export type BuildClientSignatureParams = {
@@ -6,8 +6,8 @@ export type BuildClientSignatureParams = {
6
6
  verificationToken: string
7
7
  /** The compressed public key hex */
8
8
  publicKey: string
9
- /** The IndexedDB stamper for signing */
10
- stamper: IndexedDbStamper
9
+ /** The API key stamper for signing */
10
+ stamper: ApiKeyStamper
11
11
  }
12
12
 
13
13
  /**
@@ -72,7 +72,7 @@ export async function exportPrivateKey(
72
72
  })
73
73
 
74
74
  const stamperKey =
75
- session.stamperType === 'indexedDb' ? 'indexedDbStamper' : 'webauthnStamper'
75
+ session.stamperType === 'apiKey' ? 'apiKeyStamper' : 'passkeyStamper'
76
76
  const stamper = wallet.client[stamperKey]
77
77
  if (!stamper) {
78
78
  throw new Error(`Stamper '${stamperKey}' not found on wallet.client`)
@@ -56,9 +56,7 @@ export async function exportWallet(
56
56
 
57
57
  const listWalletsStamp =
58
58
  await wallet.client[
59
- session.stamperType === 'indexedDb'
60
- ? 'indexedDbStamper'
61
- : 'webauthnStamper'
59
+ session.stamperType === 'apiKey' ? 'apiKeyStamper' : 'passkeyStamper'
62
60
  ].stamp(listWalletsBody)
63
61
  if (!listWalletsStamp) {
64
62
  throw new Error('Failed to stamp list wallets body')
@@ -93,9 +91,7 @@ export async function exportWallet(
93
91
  })
94
92
  const exportWalletStamp =
95
93
  await wallet.client[
96
- session.stamperType === 'indexedDb'
97
- ? 'indexedDbStamper'
98
- : 'webauthnStamper'
94
+ session.stamperType === 'apiKey' ? 'apiKeyStamper' : 'passkeyStamper'
99
95
  ].stamp(exportWalletBody)
100
96
  if (!exportWalletStamp) {
101
97
  throw new Error('Failed to stamp export wallet body')
package/src/utils/hpke.ts CHANGED
@@ -15,6 +15,7 @@
15
15
  * - tkhq/go-sdk/pkg/enclave_encrypt
16
16
  */
17
17
 
18
+ import { gcm } from '@noble/ciphers/aes.js'
18
19
  import { p256 } from '@noble/curves/nist.js'
19
20
  import { expand, extract } from '@noble/hashes/hkdf.js'
20
21
  import { sha256 } from '@noble/hashes/sha2.js'
@@ -165,42 +166,15 @@ function keySchedule(
165
166
  return { key, baseNonce }
166
167
  }
167
168
 
168
- // Web Crypto's BufferSource type rejects `Uint8Array<ArrayBufferLike>` (which
169
- // noble/v2 returns) under strict TS lib settings because the underlying buffer
170
- // could in principle be a SharedArrayBuffer. Copy into a fresh ArrayBuffer to
171
- // satisfy the type.
172
- function toArrayBuffer(u8: Uint8Array): ArrayBuffer {
173
- const out = new ArrayBuffer(u8.byteLength)
174
- new Uint8Array(out).set(u8)
175
- return out
176
- }
177
-
178
- async function aesGcmSeal(
169
+ function aesGcmSeal(
179
170
  key: Uint8Array,
180
171
  nonce: Uint8Array,
181
172
  aad: Uint8Array,
182
173
  plaintext: Uint8Array,
183
- ): Promise<Uint8Array> {
184
- // Web Crypto returns ciphertext || tag (16 bytes appended). Matches the
185
- // single-blob format Turnkey's `Sealer.Seal` produces.
186
- const cryptoKey = await crypto.subtle.importKey(
187
- 'raw',
188
- toArrayBuffer(key),
189
- { name: 'AES-GCM' },
190
- /* extractable */ false,
191
- ['encrypt'],
192
- )
193
- const ct = await crypto.subtle.encrypt(
194
- {
195
- name: 'AES-GCM',
196
- iv: toArrayBuffer(nonce),
197
- additionalData: toArrayBuffer(aad),
198
- tagLength: 128,
199
- },
200
- cryptoKey,
201
- toArrayBuffer(plaintext),
202
- )
203
- return new Uint8Array(ct)
174
+ ): Uint8Array {
175
+ // Returns ciphertext || tag (16 bytes appended) matches the single-blob
176
+ // format Turnkey's `Sealer.Seal` and Web Crypto's AES-GCM produce.
177
+ return gcm(key, nonce, aad).encrypt(plaintext)
204
178
  }
205
179
 
206
180
  export type HpkeSealResult = {
@@ -239,7 +213,7 @@ export async function hpkeSealP256({
239
213
 
240
214
  // First message of the context, sequence 0 → nonce = base_nonce.
241
215
  const aad = concat(enc, receiverPublicKey)
242
- const ciphertext = await aesGcmSeal(key, baseNonce, aad, plaintext)
216
+ const ciphertext = aesGcmSeal(key, baseNonce, aad, plaintext)
243
217
 
244
218
  return { encappedPublic: enc, ciphertext }
245
219
  }
@@ -18,7 +18,8 @@ export function parseSession(
18
18
  throw new Error('Invalid JWT: Missing payload')
19
19
  }
20
20
 
21
- const decoded = JSON.parse(Buffer.from(payload, 'base64').toString())
21
+ const base64 = payload.replace(/-/g, '+').replace(/_/g, '/')
22
+ const decoded = JSON.parse(atob(base64))
22
23
  const {
23
24
  exp,
24
25
  public_key: publicKey,
@@ -68,11 +69,9 @@ export const generateRandomBuffer = (): ArrayBuffer => {
68
69
  * @returns {string} - The encoded challenge.
69
70
  */
70
71
  export const base64UrlEncode = (challenge: ArrayBuffer): string => {
71
- return Buffer.from(challenge)
72
- .toString('base64')
73
- .replace(/\+/g, '-')
74
- .replace(/\//g, '_')
75
- .replace(/=/g, '')
72
+ const bytes = new Uint8Array(challenge)
73
+ const binary = String.fromCharCode(...bytes)
74
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
76
75
  }
77
76
 
78
77
  /**